diff --git a/README.md b/README.md
index bcf05541..e0a9745b 100644
--- a/README.md
+++ b/README.md
@@ -5,81 +5,161 @@
:warning: Disclaimer: this project is under active development. Use at your own risks.
-## Installation
+
-#### Requirements
+> Ledger Live Desktop is a new generation Ledger Wallet application build with React, Redux and Electron to run natively on the web. The main goal of the app is to provide our users with a single wallet for all crypto currencies supported by our devices. To learn more check out [Ledger](https://www.ledgerwallet.com/?utm_source=redirection&utm_medium=variable)
-Project has been tested with
+## Architecture
-- [NodeJS](https://nodejs.org) v9.3.0
-- [Yarn](https://yarnpkg.com) v1.3.0
+From one side Ledger Desktop app connected to the Blockchain via the in-house written C++ library - LibCore and from the other it communicates to the Ledger Hardware Device to securely sign all transactions.
+
+
+
+
+
+## Setup
+
+### Requirements
+
+- [NodeJS](https://nodejs.org) LTS
+- [Yarn](https://yarnpkg.com) LTS
- [Python](https://www.python.org/) v2.7.10 (used by [node-gyp](https://github.com/nodejs/node-gyp) to build native addons)
- You will also need a C++ compiler
-#### Optional
+### Optional
-- `Museo Sans` font - for Ledger guys, [follow that link](https://drive.google.com/drive/folders/14R6kGFtx53DuqTyIOjnT7BGogzeyMSzN), download `museosans.zip` and extract it inside the `static/fonts/museosans` directory
+- In the application we use `Museo Sans` font. To include it in the app, you need to have a zip file `museosans.zip` which you should extract and place inside the `static/fonts/museosans` directory
-#### Setup
+## Install
-1. Install dependencies
+1. Clone or fork the repo
+
+```bash
+git clone git@github.com:LedgerHQ/ledger-live-desktop.git
+```
+
+2. Install dependencies
```bash
yarn
```
-2. Create `.env` file
+## Run
+
+Launch the app
```bash
-# ENV VARIABLES
-# -------------
+yarn start
+```
+
+## Build
-# Where errors will be tracked (you may not want to edit this line)
-# SENTRY_URL=
+```bash
+# Build & package the whole app
+# Creates a .dmg for Mac, .exe installer for Windows, or .AppImage for Linux
+# Output files will be created in dist/ folder
+yarn dist
+```
-# OPTIONAL ENV VARIABLES
-# ----------------------
+**Note:** Use `yarn dist:dir` to speed up the process: it will skip the packaging step. Handy for debugging builds. You can also use `BUNDLE_ANALYZER=1 yarn dist:dir` to generate [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) report.
-# API base url, fallback to our API if not set
-API_BASE_URL=http://...
+---
-# Setup device debug mode
-DEBUG_DEVICE=0
+## Config (optional helpers)
-# Developer tools position (used only in dev)
-# can be one of: right, bottom, undocked, detach
-DEV_TOOLS_MODE=bottom
+### Environment variables
-# Filter debug output
-DEBUG=lwd*,-lwd:syncb
+(you can use a .env or export environment variables)
-# hide the dev window
+```bash
+DEV_TOOLS_MODE=bottom # devtools position Options: right, bottom, undocked, detach
HIDE_DEV_WINDOW=0
+
+## flags for development purpose
+DEBUG_DEVICE=1
+DEBUG_NETWORK=1
+DEBUG_COMMANDS=1
+DEBUG_DB=1
+DEBUG_ACTION=1
+DEBUG_TAB_KEY=1
+DEBUG_LIBCORE=1
+DEBUG_WS=1
+LEDGER_RESET_ALL=1
+LEDGER_DEBUG_ALL_LANGS=1
+SKIP_GENUINE=1
+SKIP_ONBOARDING=1
+SHOW_LEGACY_NEW_ACCOUNT=1
+HIGHLIGHT_I18N=1
+
+## constants
+GET_CALLS_TIMEOUT=30000
+GET_CALLS_RETRY=2
+SYNC_MAX_CONCURRENT=6
+SYNC_BOOT_DELAY=2000
+SYNC_ALL_INTERVAL=60000
+CHECK_APP_INTERVAL_WHEN_INVALID=600
+CHECK_APP_INTERVAL_WHEN_VALID=1200
+CHECK_UPDATE_DELAY=5000
+DEVICE_DISCONNECT_DEBOUNCE=500
```
-#### Development commands
+### Launch storybook
-```bash
-# Launch the app
-yarn start
+We use [storybook](https://storybook.js.org/) for UI development.
-# Launch the storybook
+```bash
yarn storybook
+```
-# Code quality checks
+### Run code quality checks
+
+```bash
yarn lint # launch eslint
yarn prettier # launch prettier
yarn flow # launch flow
yarn test # launch unit tests
```
-#### Building from source
+### Programmaically reset hard the app
+
+Stop the app and to clean accounts, settings, etc, run
```bash
-# Build & package the whole app
-# Creates a .dmg for Mac, .exe installer for Windows, or .AppImage for Linux
-# Output files will be created in dist/ folder
-yarn dist
+rm -rf ~/Library/Application\ Support/Electron/
```
-**Note:** Use `yarn dist:dir` to speed up the process: it will skip the packaging step. Handy for debugging builds. You can also use `BUNDLE_ANALYZER=1 yarn dist:dir` to generate [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) report.
+## File structure
+
+```
+.
+├── dist : output folder generate by the build
+├── scripts : commands (for building, releasing,...)
+├── src
+│ ├── internals : code that run on the 'internal' thread.
+│ ├── main : code that run on the 'main' thread.
+│ ├── renderer : code that run on the 'renderer' thread
+│ ├── components : all the React components
+| └── modals : sub levels for the modals
+│ ├── api : related to HTTP APIs
+│ ├── bridge : an abstraction on top of blockchains apis (libcore / js impls)
+│ ├── commands : an abstraction to run code over the internal thread
+│ ├── icons : all the icons of our app, as React components.
+│ ├── config : contains the constants,...
+│ ├── helpers : generic folder for our business logic (might be reorganized in the future)
+│ ├── middlewares : redux middlewares
+│ ├── actions : redux actions
+│ ├── reducers : redux reducers
+│ ├── sentry : for our bug tracker
+│ ├── stories : for storybook
+│ ├── styles : theme
+│ ├── logger.js : abstraction for all our console.log s
+│ └── types : global flow types
+├── static
+│ ├── docs
+│ ├── fonts
+│ ├── i18n
+│ ├── images
+│ └── videos
+├── webpack : build configuration
+└── yarn.lock
+```
diff --git a/package.json b/package.json
index 73a0d7c9..c13ba64b 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
"@ledgerhq/hw-transport": "^4.13.0",
"@ledgerhq/hw-transport-node-hid": "^4.13.0",
"@ledgerhq/ledger-core": "2.0.0-rc.1",
- "@ledgerhq/live-common": "2.30.0",
+ "@ledgerhq/live-common": "2.31.0",
"async": "^2.6.1",
"axios": "^0.18.0",
"babel-runtime": "^6.26.0",
@@ -96,6 +96,7 @@
"secp256k1": "3.3.1",
"semaphore": "^1.1.0",
"semver": "^5.5.0",
+ "smoothscroll-polyfill": "^0.4.3",
"source-map": "0.7.3",
"source-map-support": "^0.5.4",
"styled-components": "^3.3.2",
diff --git a/src/api/Ledger.js b/src/api/Ledger.js
index 2c84ab5d..f84d0f7c 100644
--- a/src/api/Ledger.js
+++ b/src/api/Ledger.js
@@ -1,7 +1,6 @@
// @flow
import type { Currency } from '@ledgerhq/live-common/lib/types'
-
-const BASE_URL = process.env.LEDGER_REST_API_BASE || 'https://api.ledgerwallet.com/'
+import { LEDGER_REST_API_BASE } from 'config/constants'
export const blockchainBaseURL = ({ ledgerExplorerId }: Currency): ?string =>
- ledgerExplorerId ? `${BASE_URL}blockchain/v2/${ledgerExplorerId}` : null
+ ledgerExplorerId ? `${LEDGER_REST_API_BASE}blockchain/v2/${ledgerExplorerId}` : null
diff --git a/src/api/Ripple.js b/src/api/Ripple.js
index 52a7fe2f..8f0d5fd4 100644
--- a/src/api/Ripple.js
+++ b/src/api/Ripple.js
@@ -1,7 +1,6 @@
// @flow
import logger from 'logger'
import { RippleAPI } from 'ripple-lib'
-import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import {
parseCurrencyUnit,
getCryptoCurrencyById,
@@ -10,14 +9,11 @@ import {
const rippleUnit = getCryptoCurrencyById('ripple').units[0]
-const apiEndpoint = {
- ripple: 'wss://s1.ripple.com',
-}
+export const defaultEndpoint = 'wss://s2.ripple.com'
-export const apiForCurrency = (currency: CryptoCurrency) => {
- const api = new RippleAPI({
- server: apiEndpoint[currency.id],
- })
+export const apiForEndpointConfig = (endpointConfig: ?string = null) => {
+ const server = endpointConfig || defaultEndpoint
+ const api = new RippleAPI({ server })
api.on('error', (errorCode, errorMessage) => {
logger.warn(`Ripple API error: ${errorCode}: ${errorMessage}`)
})
diff --git a/src/bridge/RippleJSBridge.js b/src/bridge/RippleJSBridge.js
index 27353db0..a8fb4621 100644
--- a/src/bridge/RippleJSBridge.js
+++ b/src/bridge/RippleJSBridge.js
@@ -10,7 +10,8 @@ import { getDerivations } from 'helpers/derivations'
import getAddress from 'commands/getAddress'
import signTransaction from 'commands/signTransaction'
import {
- apiForCurrency,
+ apiForEndpointConfig,
+ defaultEndpoint,
parseAPIValue,
parseAPICurrencyObject,
formatAPICurrencyXRP,
@@ -47,7 +48,7 @@ const EditAdvancedOptions = ({ onChange, value }: EditProps) => (
)
async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOperationBroadcasted }) {
- const api = apiForCurrency(a.currency)
+ const api = apiForEndpointConfig(a.endpointConfig)
try {
await api.connect()
const amount = formatAPICurrencyXRP(t.amount)
@@ -217,10 +218,11 @@ const txToOperation = (account: Account) => ({
return op
}
-const getServerInfo = (perCurrencyId => currency => {
- if (perCurrencyId[currency.id]) return perCurrencyId[currency.id]()
+const getServerInfo = (map => endpointConfig => {
+ if (!endpointConfig) endpointConfig = ''
+ if (map[endpointConfig]) return map[endpointConfig]()
const f = throttle(async () => {
- const api = apiForCurrency(currency)
+ const api = apiForEndpointConfig(endpointConfig)
try {
await api.connect()
const res = await api.getServerInfo()
@@ -232,7 +234,7 @@ const getServerInfo = (perCurrencyId => currency => {
api.disconnect()
}
}, 60000)
- perCurrencyId[currency.id] = f
+ map[endpointConfig] = f
return f()
})({})
@@ -244,10 +246,10 @@ const RippleJSBridge: WalletBridge = {
}
async function main() {
- const api = apiForCurrency(currency)
+ const api = apiForEndpointConfig()
try {
await api.connect()
- const serverInfo = await getServerInfo(currency)
+ const serverInfo = await getServerInfo()
const ledgers = serverInfo.completeLedgers.split('-')
const minLedgerVersion = Number(ledgers[0])
const maxLedgerVersion = Number(ledgers[1])
@@ -342,7 +344,7 @@ const RippleJSBridge: WalletBridge = {
return { unsubscribe }
},
- synchronize: ({ currency, freshAddress, blockHeight }) =>
+ synchronize: ({ endpointConfig, freshAddress, blockHeight }) =>
Observable.create(o => {
let finished = false
const unsubscribe = () => {
@@ -350,11 +352,11 @@ const RippleJSBridge: WalletBridge = {
}
async function main() {
- const api = apiForCurrency(currency)
+ const api = apiForEndpointConfig(endpointConfig)
try {
await api.connect()
if (finished) return
- const serverInfo = await getServerInfo(currency)
+ const serverInfo = await getServerInfo(endpointConfig)
if (finished) return
const ledgers = serverInfo.completeLedgers.split('-')
const minLedgerVersion = Number(ledgers[0])
@@ -456,7 +458,7 @@ const RippleJSBridge: WalletBridge = {
isValidTransaction: (a, t) => (t.amount > 0 && t.recipient && true) || false,
canBeSpent: async (a, t) => {
- const r = await getServerInfo(a.currency)
+ const r = await getServerInfo(a.endpointConfig)
return t.amount + t.fee + parseAPIValue(r.validatedLedger.reserveBaseXRP) <= a.balance
},
@@ -495,6 +497,13 @@ const RippleJSBridge: WalletBridge = {
),
),
}),
+
+ getDefaultEndpointConfig: () => defaultEndpoint,
+
+ validateEndpointConfig: async endpointConfig => {
+ const api = apiForEndpointConfig(endpointConfig)
+ await api.connect()
+ },
}
export default RippleJSBridge
diff --git a/src/bridge/types.js b/src/bridge/types.js
index 75bbe777..530befe4 100644
--- a/src/bridge/types.js
+++ b/src/bridge/types.js
@@ -111,4 +111,7 @@ export interface WalletBridge {
// Implement an optimistic response for signAndBroadcast.
// you likely should add the operation in account.pendingOperations but maybe you want to clean it (because maybe some are replaced / cancelled by this one?)
addPendingOperation?: (account: Account, optimisticOperation: Operation) => Account;
+
+ getDefaultEndpointConfig?: () => string;
+ validateEndpointConfig?: (endpointConfig: string) => Promise;
}
diff --git a/src/commands/getIsGenuine.js b/src/commands/getIsGenuine.js
index 8b9cfa2e..c0b66b8b 100644
--- a/src/commands/getIsGenuine.js
+++ b/src/commands/getIsGenuine.js
@@ -6,7 +6,7 @@ import { fromPromise } from 'rxjs/observable/fromPromise'
import getIsGenuine from 'helpers/devices/getIsGenuine'
import { withDevice } from 'helpers/deviceAccess'
-type Input = *
+type Input = * // FIXME !
type Result = string
const cmd: Command = createCommand('getIsGenuine', ({ devicePath, targetId }) =>
diff --git a/src/components/AccountPage/index.js b/src/components/AccountPage/index.js
index 312c6d35..5aadb530 100644
--- a/src/components/AccountPage/index.js
+++ b/src/components/AccountPage/index.js
@@ -16,8 +16,15 @@ import type { T } from 'types/common'
import { rgba } from 'styles/helpers'
+import { saveSettings } from 'actions/settings'
import { accountSelector } from 'reducers/accounts'
-import { counterValueCurrencySelector, localeSelector } from 'reducers/settings'
+import {
+ counterValueCurrencySelector,
+ localeSelector,
+ selectedTimeRangeSelector,
+ timeRangeDaysByKey,
+} from 'reducers/settings'
+import type { TimeRange } from 'reducers/settings'
import { openModal } from 'reducers/modals'
import IconAccountSettings from 'icons/AccountSettings'
@@ -63,10 +70,12 @@ const mapStateToProps = (state, props) => ({
account: accountSelector(state, { accountId: props.match.params.id }),
counterValue: counterValueCurrencySelector(state),
settings: localeSelector(state),
+ selectedTimeRange: selectedTimeRangeSelector(state),
})
const mapDispatchToProps = {
openModal,
+ saveSettings,
}
type Props = {
@@ -74,30 +83,20 @@ type Props = {
t: T,
account?: Account,
openModal: Function,
+ saveSettings: ({ selectedTimeRange: TimeRange }) => *,
+ selectedTimeRange: TimeRange,
}
-type State = {
- selectedTime: string,
- daysCount: number,
-}
-
-class AccountPage extends PureComponent {
- state = {
- selectedTime: 'month',
- daysCount: 30,
+class AccountPage extends PureComponent {
+ handleChangeSelectedTime = item => {
+ this.props.saveSettings({ selectedTimeRange: item.key })
}
- handleChangeSelectedTime = item =>
- this.setState({
- selectedTime: item.key,
- daysCount: item.value,
- })
-
_cacheBalance = null
render() {
- const { account, openModal, t, counterValue } = this.props
- const { selectedTime, daysCount } = this.state
+ const { account, openModal, t, counterValue, selectedTimeRange } = this.props
+ const daysCount = timeRangeDaysByKey[selectedTimeRange]
// Don't even throw if we jumped in wrong account route
if (!account) {
@@ -148,7 +147,7 @@ class AccountPage extends PureComponent {
chartId={`account-chart-${account.id}`}
counterValue={counterValue}
daysCount={daysCount}
- selectedTime={selectedTime}
+ selectedTimeRange={selectedTimeRange}
renderHeader={({ totalBalance, sinceBalance, refBalance }) => (
@@ -165,7 +164,7 @@ class AccountPage extends PureComponent {
@@ -177,7 +176,7 @@ class AccountPage extends PureComponent {
totalBalance={totalBalance}
sinceBalance={sinceBalance}
refBalance={refBalance}
- since={selectedTime}
+ since={selectedTimeRange}
/>
{
totalBalance={totalBalance}
sinceBalance={sinceBalance}
refBalance={refBalance}
- since={selectedTime}
+ since={selectedTimeRange}
/>
diff --git a/src/components/BalanceSummary/index.js b/src/components/BalanceSummary/index.js
index 78d47a7c..9c04ec94 100644
--- a/src/components/BalanceSummary/index.js
+++ b/src/components/BalanceSummary/index.js
@@ -14,10 +14,10 @@ type Props = {
chartColor: string,
chartId: string,
accounts: Account[],
- selectedTime: string,
+ selectedTimeRange: string,
daysCount: number,
renderHeader?: ({
- selectedTime: *,
+ selectedTimeRange: *,
totalBalance: number,
sinceBalance: number,
refBalance: number,
@@ -31,7 +31,7 @@ const BalanceSummary = ({
counterValue,
daysCount,
renderHeader,
- selectedTime,
+ selectedTimeRange,
}: Props) => {
const account = accounts.length === 1 ? accounts[0] : undefined
return (
@@ -43,7 +43,7 @@ const BalanceSummary = ({
{renderHeader ? (
{renderHeader({
- selectedTime,
+ selectedTimeRange,
// FIXME refactor these
totalBalance: balanceEnd,
sinceBalance: balanceStart,
@@ -59,7 +59,7 @@ const BalanceSummary = ({
data={balanceHistory}
height={200}
currency={counterValue}
- tickXScale={selectedTime}
+ tickXScale={selectedTimeRange}
renderTickY={val => formatShort(counterValue.units[0], val)}
renderTooltip={
isAvailable && !account
diff --git a/src/components/CalculateBalance.js b/src/components/CalculateBalance.js
index 709f7114..d94f0b6e 100644
--- a/src/components/CalculateBalance.js
+++ b/src/components/CalculateBalance.js
@@ -79,7 +79,12 @@ const mapStateToProps = (state: State, props: OwnProps) => {
}
}
+const hash = ({ balanceHistory, balanceEnd }) => `${balanceHistory.length}_${balanceEnd}`
+
class CalculateBalance extends Component {
+ shouldComponentUpdate(nextProps) {
+ return hash(nextProps) !== hash(this.props)
+ }
render() {
const { children } = this.props
return children(this.props)
diff --git a/src/components/DashboardPage/index.js b/src/components/DashboardPage/index.js
index 6894b765..4267a429 100644
--- a/src/components/DashboardPage/index.js
+++ b/src/components/DashboardPage/index.js
@@ -13,7 +13,13 @@ import type { T } from 'types/common'
import { colors } from 'styles/theme'
import { accountsSelector } from 'reducers/accounts'
-import { counterValueCurrencySelector, localeSelector } from 'reducers/settings'
+import {
+ counterValueCurrencySelector,
+ localeSelector,
+ selectedTimeRangeSelector,
+ timeRangeDaysByKey,
+} from 'reducers/settings'
+import type { TimeRange } from 'reducers/settings'
import { reorderAccounts } from 'actions/accounts'
import { saveSettings } from 'actions/settings'
@@ -35,6 +41,7 @@ const mapStateToProps = createStructuredSelector({
accounts: accountsSelector,
counterValue: counterValueCurrencySelector,
locale: localeSelector,
+ selectedTimeRange: selectedTimeRangeSelector,
})
const mapDispatchToProps = {
@@ -48,20 +55,11 @@ type Props = {
accounts: Account[],
push: Function,
counterValue: Currency,
+ selectedTimeRange: TimeRange,
+ saveSettings: ({ selectedTimeRange: TimeRange }) => *,
}
-type State = {
- selectedTime: string,
- daysCount: number,
-}
-
-class DashboardPage extends PureComponent {
- state = {
- // save to user preference?
- selectedTime: 'month',
- daysCount: 30,
- }
-
+class DashboardPage extends PureComponent {
onAccountClick = account => this.props.push(`/account/${account.id}`)
handleGreeting = () => {
@@ -77,17 +75,15 @@ class DashboardPage extends PureComponent {
return 'app:dashboard.greeting.morning'
}
- handleChangeSelectedTime = item =>
- this.setState({
- selectedTime: item.key,
- daysCount: item.value,
- })
+ handleChangeSelectedTime = item => {
+ this.props.saveSettings({ selectedTimeRange: item.key })
+ }
_cacheBalance = null
render() {
- const { accounts, t, counterValue } = this.props
- const { selectedTime, daysCount } = this.state
+ const { accounts, t, counterValue, selectedTimeRange } = this.props
+ const daysCount = timeRangeDaysByKey[selectedTimeRange]
const timeFrame = this.handleGreeting()
const totalAccounts = accounts.length
@@ -111,7 +107,7 @@ class DashboardPage extends PureComponent {
@@ -122,14 +118,14 @@ class DashboardPage extends PureComponent {
chartId="dashboard-chart"
chartColor={colors.wallet}
accounts={accounts}
- selectedTime={selectedTime}
+ selectedTimeRange={selectedTimeRange}
daysCount={daysCount}
- renderHeader={({ totalBalance, selectedTime, sinceBalance, refBalance }) => (
+ renderHeader={({ totalBalance, selectedTimeRange, sinceBalance, refBalance }) => (
diff --git a/src/components/ExchangePage/index.js b/src/components/ExchangePage/index.js
index f3f9064d..f5c7f129 100644
--- a/src/components/ExchangePage/index.js
+++ b/src/components/ExchangePage/index.js
@@ -41,7 +41,7 @@ class ExchangePage extends PureComponent {
]
return (
-
+
{t('app:exchange.title')}
diff --git a/src/components/FeesField/RippleKind.js b/src/components/FeesField/RippleKind.js
index 2677ea45..f40a22e5 100644
--- a/src/components/FeesField/RippleKind.js
+++ b/src/components/FeesField/RippleKind.js
@@ -2,7 +2,7 @@
import React, { Component } from 'react'
import type { Account } from '@ledgerhq/live-common/lib/types'
-import { apiForCurrency, parseAPIValue } from 'api/Ripple'
+import { apiForEndpointConfig, parseAPIValue } from 'api/Ripple'
import InputCurrency from 'components/base/InputCurrency'
import GenericContainer from './GenericContainer'
@@ -24,7 +24,7 @@ class FeesField extends Component {
this.sync()
}
async sync() {
- const api = apiForCurrency(this.props.account.currency)
+ const api = apiForEndpointConfig(this.props.account.endpointConfig)
try {
await api.connect()
const info = await api.getServerInfo()
diff --git a/src/components/GenuineCheckModal/index.js b/src/components/GenuineCheckModal/index.js
index 8b27eaa7..8afe0b3b 100644
--- a/src/components/GenuineCheckModal/index.js
+++ b/src/components/GenuineCheckModal/index.js
@@ -1,6 +1,6 @@
// @flow
-import React, { PureComponent } from 'react'
+import React, { PureComponent, Fragment } from 'react'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
@@ -11,14 +11,44 @@ import WorkflowDefault from 'components/Workflow/WorkflowDefault'
type Props = {
t: T,
- onGenuineCheck: (isGenuine: boolean) => void,
+ onGenuineCheckPass: () => void,
+ onGenuineCheckFailed: () => void,
+ onGenuineCheckUnavailable: Error => void,
}
type State = {}
+class GenuineCheckStatus extends PureComponent<*> {
+ componentDidUpdate() {
+ this.sideEffect()
+ }
+ sideEffect() {
+ const {
+ isGenuine,
+ error,
+ onGenuineCheckPass,
+ onGenuineCheckFailed,
+ onGenuineCheckUnavailable,
+ } = this.props
+ if (isGenuine !== null) {
+ if (isGenuine) {
+ onGenuineCheckPass()
+ } else {
+ onGenuineCheckFailed()
+ }
+ } else if (error) {
+ onGenuineCheckUnavailable(error)
+ }
+ }
+ render() {
+ return null
+ }
+}
+
+/* eslint-disable react/no-multi-comp */
class GenuineCheck extends PureComponent {
renderBody = ({ onClose }) => {
- const { t, onGenuineCheck } = this.props
+ const { t, onGenuineCheckPass, onGenuineCheckFailed, onGenuineCheckUnavailable } = this.props
// TODO: use the real devices list. for now we force choosing only
// the current device because we don't handle multi device in MVP
@@ -28,14 +58,22 @@ class GenuineCheck extends PureComponent {
{t('app:genuinecheck.modal.title')} onGenuineCheck(isGenuine)}
renderDefault={(device, deviceInfo, isGenuine, errors) => (
-
+
+
+
+
)}
/>
diff --git a/src/components/Onboarding/helperComponents.js b/src/components/Onboarding/helperComponents.js
index 4f9be4c5..4f33cb11 100644
--- a/src/components/Onboarding/helperComponents.js
+++ b/src/components/Onboarding/helperComponents.js
@@ -4,6 +4,7 @@ import styled from 'styled-components'
import { radii } from 'styles/theme'
import Box from 'components/base/Box'
+import GrowScroll from 'components/base/GrowScroll'
import IconSensitiveOperationShield from 'icons/illustrations/SensitiveOperationShield'
// GENERAL
@@ -16,6 +17,8 @@ export const Title = styled(Box).attrs({
text-align: center;
`
+export const StepContainerInner = styled(GrowScroll).attrs({ pb: 6, align: 'center' })``
+
export const Description = styled(Box).attrs({
ff: 'Museo Sans|Light',
fontSize: 5,
diff --git a/src/components/Onboarding/steps/Analytics.js b/src/components/Onboarding/steps/Analytics.js
index d92720bc..94bdea55 100644
--- a/src/components/Onboarding/steps/Analytics.js
+++ b/src/components/Onboarding/steps/Analytics.js
@@ -6,7 +6,7 @@ import { connect } from 'react-redux'
import { saveSettings } from 'actions/settings'
import Box from 'components/base/Box'
import CheckBox from 'components/base/CheckBox'
-import { Title, Description, FixedTopContainer } from '../helperComponents'
+import { Title, Description, FixedTopContainer, StepContainerInner } from '../helperComponents'
import OnboardingFooter from '../OnboardingFooter'
import type { StepProps } from '..'
@@ -51,7 +51,7 @@ class Analytics extends PureComponent {
return (
-
+ {t('onboarding:analytics.title')}{t('onboarding:analytics.desc')}
@@ -74,7 +74,7 @@ class Analytics extends PureComponent {
-
+
{
handleOpenGenuineCheckModal = () => this.setState({ isGenuineCheckModalOpened: true })
handleCloseGenuineCheckModal = (cb?: Function) =>
- this.setState(state => ({ ...state, isGenuineCheckModalOpened: false }), () => cb && cb())
+ this.setState(
+ state => ({ ...state, isGenuineCheckModalOpened: false }),
+ () => {
+ // FIXME: meh
+ if (cb && typeof cb === 'function') {
+ cb()
+ }
+ },
+ )
+
+ handleGenuineCheckPass = () => {
+ this.handleCloseGenuineCheckModal(() => {
+ this.props.updateGenuineCheck({
+ isDeviceGenuine: true,
+ genuineCheckUnavailable: null,
+ })
+ })
+ }
+ handleGenuineCheckFailed = () => {
+ this.handleCloseGenuineCheckModal(() => {
+ this.props.updateGenuineCheck({
+ isGenuineFail: true,
+ isDeviceGenuine: false,
+ genuineCheckUnavailable: null,
+ })
+ })
+ }
- handleGenuineCheck = isGenuine => {
+ handleGenuineCheckUnavailable = error => {
this.handleCloseGenuineCheckModal(() => {
this.props.updateGenuineCheck({
- isDeviceGenuine: isGenuine,
+ isDeviceGenuine: false,
+ genuineCheckUnavailable: error,
})
})
}
@@ -128,7 +159,7 @@ class GenuineCheck extends PureComponent {
return (
-
+ {t('onboarding:genuineCheck.title')}
{onboarding.isLedgerNano ? (
{t('onboarding:genuineCheck.descNano')}
@@ -193,6 +224,20 @@ class GenuineCheck extends PureComponent {
{t('onboarding:genuineCheck.isGenuinePassed')}
+ ) : genuine.genuineCheckUnavailable ? (
+
+
+
+
+
+ {t('app:common.retry')}
+
+
+
) : (
-
+
{
)
@@ -281,6 +328,7 @@ export const GenuineSuccessText = styled(Box).attrs({
ff: 'Open Sans|Regular',
fontSize: 4,
})``
+
export const CardTitle = styled(Box).attrs({
ff: 'Open Sans|SemiBold',
fontSize: 4,
@@ -303,7 +351,6 @@ const CardWrapper = styled(Box).attrs({
background-color: ${p => (p.isDisabled ? p.theme.colors.lightGrey : p.theme.colors.white)};
opacity: ${p => (p.isDisabled ? 0.7 : 1)};
&:hover {
- cursor: pointer;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.05);
}
`
diff --git a/src/components/Onboarding/steps/SelectDevice.js b/src/components/Onboarding/steps/SelectDevice.js
index eca85f3e..938ba166 100644
--- a/src/components/Onboarding/steps/SelectDevice.js
+++ b/src/components/Onboarding/steps/SelectDevice.js
@@ -11,7 +11,7 @@ import Box from 'components/base/Box'
import IconCheckCirle from 'icons/Check'
import IconLedgerNano from 'icons/illustrations/LedgerNano'
import IconLedgerBlue from 'icons/illustrations/LedgerBlue'
-import { Title, Inner, FixedTopContainer } from '../helperComponents'
+import { Title, Inner, FixedTopContainer, StepContainerInner } from '../helperComponents'
import OnboardingFooter from '../OnboardingFooter'
import type { StepProps } from '..'
@@ -35,7 +35,7 @@ class SelectDevice extends PureComponent {
const { t, onboarding, jumpStep } = this.props
return (
-
+ {t('onboarding:selectDevice.title')}
@@ -63,7 +63,7 @@ class SelectDevice extends PureComponent {
-
+
{
return (
-
+ {t('onboarding:setPassword.title')}
@@ -120,7 +121,7 @@ class SetPassword extends PureComponent {
-
+