diff --git a/package.json b/package.json index 430191df..1e6c1849 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "Ledger Live", "description": "Ledger Live - Desktop", "repository": "https://github.com/LedgerHQ/ledger-live-desktop", - "version": "0.1.0-alpha.3", + "version": "0.1.0-alpha.4", "author": "Ledger", "license": "MIT", "scripts": { @@ -41,8 +41,8 @@ "@ledgerhq/hw-app-xrp": "^4.12.0", "@ledgerhq/hw-transport": "^4.12.0", "@ledgerhq/hw-transport-node-hid": "^4.12.0", - "@ledgerhq/ledger-core": "^1.2.1", - "@ledgerhq/live-common": "^2.9.1", + "@ledgerhq/ledger-core": "^1.3.0", + "@ledgerhq/live-common": "2.11.0", "axios": "^0.18.0", "babel-runtime": "^6.26.0", "bcryptjs": "^2.4.3", diff --git a/src/bridge/EthereumJSBridge.js b/src/bridge/EthereumJSBridge.js index cbb56d11..46924add 100644 --- a/src/bridge/EthereumJSBridge.js +++ b/src/bridge/EthereumJSBridge.js @@ -115,7 +115,6 @@ const EthereumBridge: WalletBridge = { let { txs } = await api.getTransactions(address) if (finished) return { complete: true } - const path = freshAddressPath // FIXME const freshAddress = address if (txs.length === 0) { @@ -127,7 +126,6 @@ const EthereumBridge: WalletBridge = { const account: $Exact = { id: accountId, xpub: '', - path, // FIXME we probably not want the address path in the account.path freshAddress, freshAddressPath, name: 'New Account', @@ -153,7 +151,6 @@ const EthereumBridge: WalletBridge = { const account: $Exact = { id: accountId, xpub: '', - path, // FIXME we probably not want the address path in the account.path freshAddress, freshAddressPath, name: address.slice(32), @@ -308,7 +305,7 @@ const EthereumBridge: WalletBridge = { .send({ currencyId: a.currency.id, devicePath: deviceId, - path: a.path, + path: a.freshAddressPath, transaction: { ...t, nonce }, }) .toPromise() diff --git a/src/bridge/LibcoreBridge.js b/src/bridge/LibcoreBridge.js index f95d8911..3d15dbec 100644 --- a/src/bridge/LibcoreBridge.js +++ b/src/bridge/LibcoreBridge.js @@ -1,21 +1,20 @@ // @flow import React from 'react' -import { ipcRenderer } from 'electron' - +import { map } from 'rxjs/operators' import { decodeAccount, encodeAccount } from 'reducers/accounts' -import runJob from 'renderer/runJob' import FeesBitcoinKind from 'components/FeesField/BitcoinKind' -import AdvancedOptionsBitcoinKind from 'components/AdvancedOptions/BitcoinKind' +import libcoreScanAccounts from 'commands/libcoreScanAccounts' +import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast' +// import AdvancedOptionsBitcoinKind from 'components/AdvancedOptions/BitcoinKind' import type { WalletBridge, EditProps } from './types' const notImplemented = new Error('LibcoreBridge: not implemented') -// TODO for ipcRenderer listeners we should have a concept of requestId because -// to be able to listen to events that only concerns you - -// IMPORTANT: please read ./types.js that specify & document everything - -type Transaction = * +type Transaction = { + amount: number, + feePerByte: number, + recipient: string, +} const EditFees = ({ account, onChange, value }: EditProps) => ( ) => ( /> ) +const EditAdvancedOptions = undefined // Not implemented yet +/* const EditAdvancedOptions = ({ onChange, value }: EditProps) => ( ) => ( }} /> ) +*/ const LibcoreBridge: WalletBridge = { - synchronize(initialAccount, { next, complete, error }) { - const unbind = () => ipcRenderer.removeListener('msg', handleAccountSync) - - function handleAccountSync(e, msg) { - switch (msg.type) { - case 'account.sync.progress': { - next(a => a) - // FIXME TODO: use next(), to actually emit account updates..... - // - need to sync the balance - // - need to sync block height & block hash - // - need to sync operations. - // - once all that, need to set lastSyncDate to new Date() - - // - when you implement addPendingOperation you also here need to: - // - if there were pendingOperations that are now in operations, remove them as well. - // - if there are pendingOperations that is older than a threshold (that depends on blockchain speed typically) - // then we probably should trash them out? it's a complex question for UI - break - } - case 'account.sync.fail': { - unbind() - error(new Error('failed')) // TODO more error detail - break - } - case 'account.sync.success': { - unbind() - complete() - break - } - default: - } - } - - ipcRenderer.on('msg', handleAccountSync) - - // TODO how to start the sync ?! - - return { - unsubscribe() { - unbind() - console.warn('LibcoreBridge: interrupting synchronization is not supported') - }, - } - }, - - scanAccountsOnDevice(currency, deviceId, { next, complete, error }) { - const unbind = () => ipcRenderer.removeListener('msg', handleMsgEvent) - - function handleMsgEvent(e, { data, type }) { - if (type === 'accounts.scanAccountsOnDevice.accountScanned') { - next({ ...decodeAccount(data), archived: true }) - } - } - - ipcRenderer.on('msg', handleMsgEvent) - - let unsubscribed - - runJob({ - channel: 'accounts', - job: 'scan', - successResponse: 'accounts.scanAccountsOnDevice.success', - errorResponse: 'accounts.scanAccountsOnDevice.fail', - data: { - devicePath: deviceId, + scanAccountsOnDevice(currency, devicePath, observer) { + return libcoreScanAccounts + .send({ + devicePath, currencyId: currency.id, - }, - }).then( - () => { - if (unsubscribed) return - unbind() - complete() - }, - e => { - if (unsubscribed) return - unbind() - error(e) - }, - ) + }) + .pipe(map(decodeAccount)) + .subscribe(observer) + }, + synchronize(_initialAccount, _observer) { + // FIXME TODO: use next(), to actually emit account updates..... + // - need to sync the balance + // - need to sync block height & block hash + // - need to sync operations. + // - once all that, need to set lastSyncDate to new Date() + // - when you implement addPendingOperation you also here need to: + // - if there were pendingOperations that are now in operations, remove them as well. + // - if there are pendingOperations that is older than a threshold (that depends on blockchain speed typically) + // then we probably should trash them out? it's a complex question for UI return { unsubscribe() { - unsubscribed = true - unbind() - console.warn('LibcoreBridge: interrupting scanAccounts is not implemented') // FIXME + console.warn('LibcoreBridge: sync not implemented') }, } }, @@ -165,15 +105,20 @@ const LibcoreBridge: WalletBridge = { getMaxAmount: (a, _t) => Promise.resolve(a.balance), // FIXME - signAndBroadcast: (account, transaction, deviceId) => { - const rawAccount = encodeAccount(account) - return runJob({ - channel: 'accounts', - job: 'signAndBroadcastTransactionBTCLike', - successResponse: 'accounts.signAndBroadcastTransactionBTCLike.success', - errorResponse: 'accounts.signAndBroadcastTransactionBTCLike.fail', - data: { account: rawAccount, transaction, deviceId }, - }) + signAndBroadcast: async (account, transaction, deviceId) => { + const encodedAccount = encodeAccount(account) + const rawOp = await libcoreSignAndBroadcast + .send({ + account: encodedAccount, + transaction, + deviceId, + }) + .toPromise() + + // quick HACK + const [op] = decodeAccount({ ...encodedAccount, operations: [rawOp] }).operations + + return op }, } diff --git a/src/bridge/RippleJSBridge.js b/src/bridge/RippleJSBridge.js index 51c66b0d..3113f0ed 100644 --- a/src/bridge/RippleJSBridge.js +++ b/src/bridge/RippleJSBridge.js @@ -188,8 +188,6 @@ const RippleJSBridge: WalletBridge = { for (const derivation of derivations) { for (let index = 0; index < 255; index++) { const freshAddressPath = derivation({ currency, x: index, segwit: false }) - const path = freshAddressPath - // FIXME^ we need the account path, not the address path const { address } = await await getAddress .send({ currencyId: currency.id, devicePath: deviceId, path: freshAddressPath }) .toPromise() @@ -215,7 +213,6 @@ const RippleJSBridge: WalletBridge = { next({ id: accountId, xpub: '', - path, name: 'New Account', freshAddress, freshAddressPath, @@ -247,7 +244,6 @@ const RippleJSBridge: WalletBridge = { const account: $Exact = { id: accountId, xpub: '', - path, name: address.slice(0, 8), freshAddress, freshAddressPath, @@ -426,7 +422,7 @@ const RippleJSBridge: WalletBridge = { .send({ currencyId: a.currency.id, devicePath: deviceId, - path: a.path, + path: a.freshAddressPath, transaction: JSON.parse(prepared.txJSON), }) .toPromise() diff --git a/src/bridge/types.js b/src/bridge/types.js index 97a6f211..7be81a50 100644 --- a/src/bridge/types.js +++ b/src/bridge/types.js @@ -15,7 +15,7 @@ export type Observer = { } export type Subscription = { - unsubscribe: () => void, + +unsubscribe: () => void, } export type EditProps = { diff --git a/src/commands/installOsuFirmware.js b/src/commands/installOsuFirmware.js index f5110b13..8d4a6f4f 100644 --- a/src/commands/installOsuFirmware.js +++ b/src/commands/installOsuFirmware.js @@ -2,8 +2,8 @@ import { createCommand, Command } from 'helpers/ipc' import { fromPromise } from 'rxjs/observable/fromPromise' -import { withDevice } from 'helpers/deviceAccess' +import { withDevice } from 'helpers/deviceAccess' import installOsuFirmware from 'helpers/firmware/installOsuFirmware' type Input = { diff --git a/src/commands/libcoreScanAccounts.js b/src/commands/libcoreScanAccounts.js new file mode 100644 index 00000000..0cb7fb6c --- /dev/null +++ b/src/commands/libcoreScanAccounts.js @@ -0,0 +1,44 @@ +// @flow + +import type { AccountRaw } from '@ledgerhq/live-common/lib/types' +import { createCommand, Command } from 'helpers/ipc' +import { Observable } from 'rxjs' +import { scanAccountsOnDevice } from 'helpers/libcore' + +type Input = { + devicePath: string, + currencyId: string, +} + +type Result = AccountRaw + +const cmd: Command = createCommand( + 'devices', + 'libcoreScanAccounts', + ({ devicePath, currencyId }) => + Observable.create(o => { + // TODO scanAccountsOnDevice should directly return a Observable so we just have to pass-in + scanAccountsOnDevice({ + devicePath, + currencyId, + onAccountScanned: account => { + o.next(account) + }, + }).then( + () => { + o.complete() + }, + e => { + o.error(e) + }, + ) + + function unsubscribe() { + // FIXME not implemented + } + + return unsubscribe + }), +) + +export default cmd diff --git a/src/commands/libcoreSignAndBroadcast.js b/src/commands/libcoreSignAndBroadcast.js new file mode 100644 index 00000000..9e3bea3f --- /dev/null +++ b/src/commands/libcoreSignAndBroadcast.js @@ -0,0 +1,94 @@ +// @flow + +import type { AccountRaw, OperationRaw } from '@ledgerhq/live-common/lib/types' +import Btc from '@ledgerhq/hw-app-btc' +import { createCommand, Command } from 'helpers/ipc' +import { withDevice } from 'helpers/deviceAccess' +import { getWalletIdentifier } from 'helpers/libcore' +import { fromPromise } from 'rxjs/observable/fromPromise' +import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/helpers/currencies' + +type BitcoinLikeTransaction = { + amount: number, + feePerByte: number, + recipient: string, +} + +type Input = { + account: AccountRaw, + transaction: BitcoinLikeTransaction, + deviceId: string, +} + +type Result = $Exact + +const cmd: Command = createCommand( + 'devices', + 'libcoreSignAndBroadcast', + ({ account, transaction, deviceId }) => { + // TODO: investigate why importing it on file scope causes trouble + const core = require('init-ledger-core')() + + return fromPromise( + withDevice(deviceId)(async transport => { + const hwApp = new Btc(transport) + + const WALLET_IDENTIFIER = await getWalletIdentifier({ + hwApp, + isSegwit: !!account.isSegwit, + currencyId: account.currencyId, + devicePath: deviceId, + }) + + const njsWallet = await core.getWallet(WALLET_IDENTIFIER) + const njsAccount = await njsWallet.getAccount(account.index) + const bitcoinLikeAccount = njsAccount.asBitcoinLikeAccount() + const njsWalletCurrency = njsWallet.getCurrency() + const amount = core.createAmount(njsWalletCurrency, transaction.amount) + const fees = core.createAmount(njsWalletCurrency, transaction.feePerByte) + const transactionBuilder = bitcoinLikeAccount.buildTransaction() + + // TODO: check if is valid address. if not, it will fail silently on invalid + + transactionBuilder.sendToAddress(amount, transaction.recipient) + // TODO: don't use hardcoded value for sequence (and first also maybe) + transactionBuilder.pickInputs(0, 0xffffff) + transactionBuilder.setFeesPerByte(fees) + + const builded = await transactionBuilder.build() + const sigHashType = core.helpers.bytesToHex( + njsWalletCurrency.bitcoinLikeNetworkParameters.SigHash, + ) + + const currency = getCryptoCurrencyById(account.currencyId) + const signedTransaction = await core.signTransaction({ + hwApp, + transaction: builded, + sigHashType, + supportsSegwit: currency.supportsSegwit, + isSegwit: account.isSegwit, + }) + + const txHash = await njsAccount + .asBitcoinLikeAccount() + .broadcastRawTransaction(signedTransaction) + + // optimistic operation + return { + id: txHash, + hash: txHash, + type: 'OUT', + value: amount, + blockHash: null, + blockHeight: null, + senders: [account.freshAddress], + recipients: [transaction.recipient], + accountId: account.id, + date: new Date().toISOString(), + } + }), + ) + }, +) + +export default cmd diff --git a/src/commands/listApps.js b/src/commands/listApps.js index a6659cb4..ecffac64 100644 --- a/src/commands/listApps.js +++ b/src/commands/listApps.js @@ -5,11 +5,14 @@ import { fromPromise } from 'rxjs/observable/fromPromise' import listApps from 'helpers/apps/listApps' -type Input = * +type Input = { + targetId: string | number, +} + type Result = * -const cmd: Command = createCommand('manager', 'listApps', () => - fromPromise(listApps()), +const cmd: Command = createCommand('devices', 'listApps', ({ targetId }) => + fromPromise(listApps(targetId)), ) export default cmd diff --git a/src/components/App.js b/src/components/App.js index 8a1898e4..39c94983 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -20,8 +20,6 @@ import Print from 'components/layout/Print' import CounterValues from 'helpers/countervalues' import { BridgeSyncProvider } from 'bridge/BridgeSyncContext' -const { DEV_TOOLS } = process.env - const App = ({ store, history, diff --git a/src/components/FeesField/BitcoinKind.js b/src/components/FeesField/BitcoinKind.js index e2578baf..2739adf9 100644 --- a/src/components/FeesField/BitcoinKind.js +++ b/src/components/FeesField/BitcoinKind.js @@ -53,7 +53,7 @@ class FeesField extends Component { _unmounted = false async fetchAppList() { - const appsList = CACHED_APPS || (await listApps.send().toPromise()) - CACHED_APPS = appsList - if (!this._unmounted) { - this.setState({ appsList, status: 'idle' }) + try { + const { targetId } = this.props + const appsList = CACHED_APPS || (await listApps.send({ targetId }).toPromise()) + CACHED_APPS = appsList + if (!this._unmounted) { + this.setState({ appsList, status: 'idle' }) + } + } catch (err) { + this.setState({ status: 'error', error: err.message }) } } diff --git a/src/components/ManagerPage/index.js b/src/components/ManagerPage/index.js index 88d5b9b7..cc89c859 100644 --- a/src/components/ManagerPage/index.js +++ b/src/components/ManagerPage/index.js @@ -1,13 +1,14 @@ // @flow -import React, { Component, Fragment } from 'react' +import React, { Fragment } from 'react' import { translate } from 'react-i18next' +import type { Node } from 'react' import type { T } from 'types/common' import AppsList from './AppsList' // import DeviceInfos from './DeviceInfos' -import FirmwareUpdate from './FirmwareUpdate' +// import FirmwareUpdate from './FirmwareUpdate' import EnsureDevice from './EnsureDevice' import EnsureDashboard from './EnsureDashboard' import EnsureGenuine from './EnsureGenuine' @@ -16,44 +17,35 @@ type Props = { t: T, } -type State = {} - -class ManagerPage extends Component { - render() { - const { t } = this.props - - return ( - - - {device => ( - - {deviceInfo => ( - - {deviceInfo.mcu && bootloader mode} - {deviceInfo.final && osu mode} - - {!deviceInfo.mcu && - !deviceInfo.final && ( - - - - - )} - - )} - +const ManagerPage = ({ t }: Props): Node => ( + + + {device => ( + + {deviceInfo => ( + + {deviceInfo.mcu && bootloader mode} + {deviceInfo.final && osu mode} + + {!deviceInfo.mcu && + !deviceInfo.final && ( + + {/* */} + + + )} + )} - - - ) - } -} - + + )} + + +) export default translate()(ManagerPage) diff --git a/src/components/Onboarding/OnboardingBreadcrumb.js b/src/components/Onboarding/OnboardingBreadcrumb.js index d45746fb..e3137645 100644 --- a/src/components/Onboarding/OnboardingBreadcrumb.js +++ b/src/components/Onboarding/OnboardingBreadcrumb.js @@ -18,7 +18,7 @@ type Props = { function OnboardingBreadcrumb(props: Props) { const { onboarding } = props - const { stepName, isGenuineFail } = onboarding + const { stepName, genuine } = onboarding const filteredSteps = onboarding.steps .filter(step => !step.external) @@ -29,7 +29,7 @@ function OnboardingBreadcrumb(props: Props) { return ( diff --git a/src/components/Onboarding/OnboardingFooter.js b/src/components/Onboarding/OnboardingFooter.js index 922e3423..384f360b 100644 --- a/src/components/Onboarding/OnboardingFooter.js +++ b/src/components/Onboarding/OnboardingFooter.js @@ -22,14 +22,15 @@ type Props = { t: T, nextStep: () => void, prevStep: () => void, + isContinueDisabled?: boolean, } -const OnboardingFooter = ({ t, nextStep, prevStep, ...props }: Props) => ( +const OnboardingFooter = ({ t, nextStep, prevStep, isContinueDisabled, ...props }: Props) => ( - diff --git a/src/components/Onboarding/index.js b/src/components/Onboarding/index.js index 1efdff9d..ca0515aa 100644 --- a/src/components/Onboarding/index.js +++ b/src/components/Onboarding/index.js @@ -10,13 +10,7 @@ import type { T } from 'types/common' import type { OnboardingState } from 'reducers/onboarding' import { saveSettings } from 'actions/settings' -import { - nextStep, - prevStep, - jumpStep, - setGenuineCheckFail, - isLedgerNano, -} from 'reducers/onboarding' +import { nextStep, prevStep, jumpStep, updateGenuineCheck, isLedgerNano } from 'reducers/onboarding' import { getCurrentDevice } from 'reducers/devices' // import { unlock } from 'reducers/application' @@ -80,9 +74,10 @@ export type StepProps = { nextStep: Function, jumpStep: Function, finish: Function, + saveSettings: Function, // savePassword: Function, getDeviceInfo: Function, - setGenuineCheckFail: Function, + updateGenuineCheck: Function, isLedgerNano: Function, } @@ -116,7 +111,7 @@ class Onboarding extends PureComponent { const stepProps: StepProps = { t, onboarding, - setGenuineCheckFail, + updateGenuineCheck, isLedgerNano, prevStep, nextStep, @@ -124,6 +119,7 @@ class Onboarding extends PureComponent { finish: this.finish, // savePassword: this.savePassword, getDeviceInfo: this.getDeviceInfo, + saveSettings, } return ( diff --git a/src/components/Onboarding/steps/Analytics.js b/src/components/Onboarding/steps/Analytics.js index f46e1c76..5eeb2b46 100644 --- a/src/components/Onboarding/steps/Analytics.js +++ b/src/components/Onboarding/steps/Analytics.js @@ -1,7 +1,9 @@ // @flow -import React from 'react' +import React, { PureComponent } from 'react' import styled from 'styled-components' +import { connect } from 'react-redux' +import { saveSettings } from 'actions/settings' import Box from 'components/base/Box' import CheckBox from 'components/base/CheckBox' @@ -10,51 +12,78 @@ import OnboardingFooter from '../OnboardingFooter' import type { StepProps } from '..' -export default (props: StepProps) => { - const { nextStep, prevStep, t } = props - return ( - - - {t('onboarding:analytics.title')} - {t('onboarding:analytics.desc')} +const mapDispatchToProps = { saveSettings } - - - - - {t('onboarding:analytics.shareDiagnostics.title')} +type State = { + analyticsToggle: boolean, + termsConditionsToggle: boolean, +} +class Analytics extends PureComponent { + state = { + analyticsToggle: false, + termsConditionsToggle: false, + } + + handleAnalyticsToggle = (isChecked: boolean) => { + this.setState({ analyticsToggle: !this.state.analyticsToggle }) + this.props.saveSettings({ + shareAnalytics: isChecked, + }) + } + handleTermsToggle = () => { + this.setState({ termsConditionsToggle: !this.state.termsConditionsToggle }) + } + render() { + const { nextStep, prevStep, t } = this.props + const { analyticsToggle, termsConditionsToggle } = this.state + + return ( + + + {t('onboarding:analytics.title')} + {t('onboarding:analytics.desc')} + + + + + + {t('onboarding:analytics.shareAnalytics.title')} + + {t('onboarding:analytics.shareAnalytics.desc')} - {t('onboarding:analytics.shareDiagnostics.desc')} - - - - - - - - - {t('onboarding:analytics.shareDiagnostics.title')} + + - {t('onboarding:analytics.shareDiagnostics.desc')} - - - - - + + + + + {t('onboarding:analytics.termsConditions.title')} + + {t('onboarding:analytics.termsConditions.desc')} + + + + + + + - - - ) + ) + } } +export default connect(null, mapDispatchToProps)(Analytics) + export const AnalyticsText = styled(Box).attrs({ ff: 'Open Sans|Regular', fontSize: 3, diff --git a/src/components/Onboarding/steps/GenuineCheck.js b/src/components/Onboarding/steps/GenuineCheck.js index 4ed6fcb5..5d8994f4 100644 --- a/src/components/Onboarding/steps/GenuineCheck.js +++ b/src/components/Onboarding/steps/GenuineCheck.js @@ -1,13 +1,14 @@ // @flow import React, { PureComponent, Fragment } from 'react' +import { shell } from 'electron' import { connect } from 'react-redux' import styled from 'styled-components' import { radii } from 'styles/theme' import type { T } from 'types/common' -import { setGenuineCheckFail } from 'reducers/onboarding' +import { updateGenuineCheck } from 'reducers/onboarding' import Box, { Card } from 'components/base/Box' import Button from 'components/base/Button' @@ -23,25 +24,25 @@ import { Title, Description, IconOptionRow } from '../helperComponents' import type { StepProps } from '..' import OnboardingFooter from '../OnboardingFooter' -const mapDispatchToProps = { setGenuineCheckFail } +const mapDispatchToProps = { updateGenuineCheck } type State = { - pinStepPass: boolean | null, - phraseStepPass: boolean | null, cachedPinStepButton: string, - cachedPhraseStepButton: string, + cachedRecoveryStepButton: string, isGenuineCheckModalOpened: boolean, - isDeviceGenuine: boolean, +} + +const INITIAL_STATE = { + cachedPinStepButton: '', + cachedRecoveryStepButton: '', + isGenuineCheckModalOpened: false, } class GenuineCheck extends PureComponent { state = { - pinStepPass: null, - phraseStepPass: null, - cachedPinStepButton: '', - cachedPhraseStepButton: '', - isGenuineCheckModalOpened: false, - isDeviceGenuine: false, + ...INITIAL_STATE, + cachedPinStepButton: this.props.onboarding.genuine.pinStepPass ? 'yes' : '', + cachedRecoveryStepButton: this.props.onboarding.genuine.recoveryStepPass ? 'yes' : '', } getButtonLabel() { @@ -61,15 +62,21 @@ class GenuineCheck extends PureComponent { } handleButtonPass = (item: Object, step: string) => { - this.setState({ [`${step}`]: item.pass }) + this.props.updateGenuineCheck({ [`${step}`]: item.pass }) if (step === 'pinStepPass') { this.setState({ cachedPinStepButton: item.key }) } else { - this.setState({ cachedPhraseStepButton: item.key }) + this.setState({ cachedRecoveryStepButton: item.key }) } if (!item.pass) { - this.props.setGenuineCheckFail(true) + this.setState(INITIAL_STATE) + this.props.updateGenuineCheck({ + isGenuineFail: true, + recoveryStepPass: false, + pinStepPass: false, + isDeviceGenuine: false, + }) } } @@ -79,15 +86,19 @@ class GenuineCheck extends PureComponent { handleGenuineCheck = async isGenuine => { await new Promise(r => setTimeout(r, 1e3)) // let's wait a bit before closing modal this.handleCloseGenuineCheckModal() - this.setState({ isDeviceGenuine: isGenuine }) + this.props.updateGenuineCheck({ + isDeviceGenuine: isGenuine, + }) } redoGenuineCheck = () => { - this.props.setGenuineCheckFail(false) + this.props.updateGenuineCheck({ isGenuineFail: false }) } contactSupport = () => { - console.log('contact support coming later') + const contactSupportUrl = + 'https://support.ledgerwallet.com/hc/en-us/requests/new?ticket_form_id=248165' + shell.openExternal(contactSupportUrl) } renderGenuineFail = () => ( @@ -101,16 +112,10 @@ class GenuineCheck extends PureComponent { render() { const { nextStep, prevStep, t, onboarding } = this.props - const { - pinStepPass, - phraseStepPass, - cachedPinStepButton, - cachedPhraseStepButton, - isGenuineCheckModalOpened, - isDeviceGenuine, - } = this.state + const { genuine } = onboarding + const { cachedPinStepButton, cachedRecoveryStepButton, isGenuineCheckModalOpened } = this.state - if (onboarding.isGenuineFail) { + if (genuine.isGenuineFail) { return this.renderGenuineFail() } @@ -137,7 +142,7 @@ class GenuineCheck extends PureComponent { - + 2. @@ -148,13 +153,13 @@ class GenuineCheck extends PureComponent { this.handleButtonPass(item, 'phraseStepPass')} + activeKey={cachedRecoveryStepButton} + onChange={item => this.handleButtonPass(item, 'recoveryStepPass')} /> - + 3. @@ -166,10 +171,10 @@ class GenuineCheck extends PureComponent {