From 052004c62812c1912a82816ad06c0f8ca911886a Mon Sep 17 00:00:00 2001 From: meriadec Date: Sat, 26 May 2018 00:00:00 +0200 Subject: [PATCH 01/15] Minor design fixes --- src/components/Breadcrumb/Step.js | 4 +++- src/components/DeviceConnect/index.js | 7 ++----- src/components/base/Box/index.js | 7 +++++++ src/components/modals/AddAccount/index.js | 1 + 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/components/Breadcrumb/Step.js b/src/components/Breadcrumb/Step.js index 1492365a..ad0b7126 100644 --- a/src/components/Breadcrumb/Step.js +++ b/src/components/Breadcrumb/Step.js @@ -3,6 +3,8 @@ import React from 'react' import styled from 'styled-components' +import { colors } from 'styles/theme' + import Box from 'components/base/Box' import IconCheck from 'icons/Check' @@ -33,7 +35,7 @@ const StepNumber = styled(Box).attrs({ ff: 'Rubik|Regular', })` border-radius: 50%; - border: 1px solid #d8d8d8; + border: 1px solid ${p => (['active', 'valid'].includes(p.status) ? colors.wallet : colors.fog)}; font-size: 10px; height: ${RADIUS}px; line-height: 10px; diff --git a/src/components/DeviceConnect/index.js b/src/components/DeviceConnect/index.js index c693625f..b353d607 100644 --- a/src/components/DeviceConnect/index.js +++ b/src/components/DeviceConnect/index.js @@ -111,7 +111,6 @@ const Info = styled(Box).attrs({ fontSize: 3, horizontal: true, ml: 1, - pt: 1, })` strong { font-weight: 600; @@ -310,10 +309,8 @@ class DeviceConnect extends PureComponent { {appState.fail ? ( - - - - + + {accountName ? ( {'You must use the device associated to the account '} diff --git a/src/components/base/Box/index.js b/src/components/base/Box/index.js index dc058b2f..f961388c 100644 --- a/src/components/base/Box/index.js +++ b/src/components/base/Box/index.js @@ -49,6 +49,13 @@ const Box = styled.div` overflow-y: ${p => (p.scroll === true ? 'auto' : '')}; position: ${p => (p.relative ? 'relative' : p.sticky ? 'absolute' : '')}; + ${p => + p.selectable && + ` + user-select: text; + cursor: text; + `}; + ${p => p.sticky && ` diff --git a/src/components/modals/AddAccount/index.js b/src/components/modals/AddAccount/index.js index 2799a0e9..73a22aad 100644 --- a/src/components/modals/AddAccount/index.js +++ b/src/components/modals/AddAccount/index.js @@ -274,6 +274,7 @@ class AddAccountModal extends PureComponent { ( {t('addAccount:title')} From 3a29466b03e36562a1236d6c84ef2fcfd75c1144 Mon Sep 17 00:00:00 2001 From: meriadec Date: Sat, 26 May 2018 00:00:01 +0200 Subject: [PATCH 02/15] Create new `ImportAccounts` modal --- src/components/SideBar/Item.js | 2 +- src/components/SideBar/index.js | 72 +++++---- src/components/modals/ImportAccounts/index.js | 145 ++++++++++++++++++ .../steps/01-step-choose-currency.js | 22 +++ .../steps/02-step-connect-device.js | 33 ++++ .../steps/03-step-in-progress.js | 11 ++ .../ImportAccounts/steps/04-step-finish.js | 11 ++ src/components/modals/StepConnectDevice.js | 2 +- src/components/modals/index.js | 2 +- src/icons/Home.js | 2 +- static/i18n/en/importAccounts.yml | 6 + 11 files changed, 270 insertions(+), 38 deletions(-) create mode 100644 src/components/modals/ImportAccounts/index.js create mode 100644 src/components/modals/ImportAccounts/steps/01-step-choose-currency.js create mode 100644 src/components/modals/ImportAccounts/steps/02-step-connect-device.js create mode 100644 src/components/modals/ImportAccounts/steps/03-step-in-progress.js create mode 100644 src/components/modals/ImportAccounts/steps/04-step-finish.js create mode 100644 static/i18n/en/importAccounts.yml diff --git a/src/components/SideBar/Item.js b/src/components/SideBar/Item.js index ee334406..71e48124 100644 --- a/src/components/SideBar/Item.js +++ b/src/components/SideBar/Item.js @@ -34,7 +34,7 @@ const Container = styled(Tabbable).attrs({ horizontal: true, pl: 3, })` - cursor: pointer; + cursor: ${p => (p.isActive ? 'default' : 'pointer')}; color: ${p => p.theme.colors.dark}; background: ${p => (p.isActive ? p.theme.colors.lightGrey : '')}; height: ${p => (p.big ? 50 : 36)}px; diff --git a/src/components/SideBar/index.js b/src/components/SideBar/index.js index 17f7e8ea..359b57a0 100644 --- a/src/components/SideBar/index.js +++ b/src/components/SideBar/index.js @@ -1,6 +1,6 @@ // @flow -import React, { PureComponent } from 'react' +import React, { PureComponent, Fragment } from 'react' import { compose } from 'redux' import { translate } from 'react-i18next' import styled from 'styled-components' @@ -8,7 +8,7 @@ import { connect } from 'react-redux' import { getCryptoCurrencyIcon } from '@ledgerhq/live-common/lib/react' import type { Account } from '@ledgerhq/live-common/lib/types' -import { MODAL_SEND, MODAL_RECEIVE, MODAL_ADD_ACCOUNT } from 'config/constants' +import { MODAL_SEND, MODAL_RECEIVE } from 'config/constants' import type { T } from 'types/common' @@ -56,7 +56,6 @@ const PlusBtn = styled(Tabbable).attrs({ type Props = { t: T, - accounts: Account[], openModal: Function, updateStatus: UpdateStatus, } @@ -72,7 +71,7 @@ const mapDispatchToProps: Object = { class SideBar extends PureComponent { render() { - const { t, accounts, openModal, updateStatus } = this.props + const { t, openModal, updateStatus } = this.props return ( @@ -101,35 +100,13 @@ class SideBar extends PureComponent { {t('sidebar:accounts')} t('addAccount:title')}> - openModal(MODAL_ADD_ACCOUNT)}> + openModal('importAccounts')}> - {accounts.map(account => { - const Icon = getCryptoCurrencyIcon(account.currency) - return ( - - } - iconActiveColor={account.currency.color} - icon={Icon ? : null} - key={account.id} - linkTo={`/account/${account.id}`} - > - {account.name} - - ) - })} + @@ -138,9 +115,36 @@ class SideBar extends PureComponent { } } -export default compose( - connect(mapStateToProps, mapDispatchToProps, null, { - pure: false, - }), - translate(), -)(SideBar) +const AccountsList = connect(state => ({ + accounts: getVisibleAccounts(state), +}))(({ accounts }: { accounts: Account[] }) => ( + + {accounts.map(account => { + const Icon = getCryptoCurrencyIcon(account.currency) + return ( + + } + iconActiveColor={account.currency.color} + icon={Icon ? : null} + key={account.id} + linkTo={`/account/${account.id}`} + > + {account.name} + + ) + })} + +)) + +export default compose(connect(null, mapDispatchToProps, null, { pure: false }), translate())( + SideBar, +) diff --git a/src/components/modals/ImportAccounts/index.js b/src/components/modals/ImportAccounts/index.js new file mode 100644 index 00000000..e6adf70b --- /dev/null +++ b/src/components/modals/ImportAccounts/index.js @@ -0,0 +1,145 @@ +// @flow + +import React, { PureComponent } from 'react' +import { compose } from 'redux' +import { connect } from 'react-redux' +import { translate } from 'react-i18next' +import type { Currency } from '@ledgerhq/live-common/lib/types' + +import type { T, Device } from 'types/common' + +import { getCurrentDevice } from 'reducers/devices' + +import Modal, { ModalContent, ModalTitle, ModalFooter, ModalBody } from 'components/base/Modal' +import Box from 'components/base/Box' +import Breadcrumb from 'components/Breadcrumb' + +import StepChooseCurrency, { StepChooseCurrencyFooter } from './steps/01-step-choose-currency' +import StepConnectDevice, { StepConnectDeviceFooter } from './steps/02-step-connect-device' +import StepInProgress from './steps/03-step-in-progress' +import StepFinish from './steps/04-step-finish' + +const createSteps = ({ t }: { t: T }) => [ + { + id: 'chooseCurrency', + label: t('importAccounts:breadcrumb.informations'), + component: StepChooseCurrency, + footer: StepChooseCurrencyFooter, + hideFooter: false, + }, + { + id: 'connectDevice', + label: t('importAccounts:breadcrumb.connectDevice'), + component: StepConnectDevice, + footer: StepConnectDeviceFooter, + hideFooter: false, + }, + { + id: 'import', + label: t('importAccounts:breadcrumb.import'), + component: StepInProgress, + footer: null, + hideFooter: true, + }, + { + id: 'finish', + label: t('importAccounts:breadcrumb.finish'), + component: StepFinish, + hideFooter: false, + footer: StepChooseCurrencyFooter, + }, +] + +type Props = { + t: T, + currentDevice: ?Device, +} + +type StepId = 'chooseCurrency' | 'connectDevice' | 'import' | 'finish' + +export type StepProps = { + t: T, + currency: ?Currency, + currentDevice: ?Device, + isAppOpened: boolean, + transitionTo: StepId => void, + setState: any => void, +} + +type State = { + stepId: StepId, + isAppOpened: boolean, + currency: ?Currency, + scannedAccounts: [], +} + +const mapStateToProps = state => ({ + currentDevice: getCurrentDevice(state), +}) + +const INITIAL_STATE = { + stepId: 'chooseCurrency', + isAppOpened: false, + currency: null, + scannedAccounts: [], +} + +class ImportAccounts extends PureComponent { + state = INITIAL_STATE + STEPS = createSteps({ + t: this.props.t, + }) + + transitionTo = stepId => { + this.setState({ stepId }) + } + + render() { + const { t, currentDevice } = this.props + const { stepId, currency, isAppOpened } = this.state + + const stepIndex = this.STEPS.findIndex(s => s.id === stepId) + const step = this.STEPS[stepIndex] + + if (!step) { + throw new Error(`ImportAccountsModal: step ${stepId} doesn't exists`) + } + + const { component: StepComponent, footer: StepFooter, hideFooter } = step + + const stepProps: StepProps = { + t, + currency, + currentDevice, + isAppOpened, + transitionTo: this.transitionTo, + setState: (...args) => this.setState(...args), + } + + return ( + this.setState({ ...INITIAL_STATE })} + render={({ onClose }) => ( + + {t('importAccounts:title')} + + + + + {!hideFooter && ( + + + {StepFooter ? : footer} + + + )} + + )} + /> + ) + } +} + +export default compose(connect(mapStateToProps), translate())(ImportAccounts) diff --git a/src/components/modals/ImportAccounts/steps/01-step-choose-currency.js b/src/components/modals/ImportAccounts/steps/01-step-choose-currency.js new file mode 100644 index 00000000..90179ba8 --- /dev/null +++ b/src/components/modals/ImportAccounts/steps/01-step-choose-currency.js @@ -0,0 +1,22 @@ +// @flow + +import React from 'react' + +import SelectCurrency from 'components/SelectCurrency' +import Button from 'components/base/Button' + +import type { StepProps } from '../index' + +function StepChooseCurrency({ currency, setState }: StepProps) { + return setState({ currency })} value={currency} /> +} + +export function StepChooseCurrencyFooter({ transitionTo, currency, t }: StepProps) { + return ( + + ) +} + +export default StepChooseCurrency diff --git a/src/components/modals/ImportAccounts/steps/02-step-connect-device.js b/src/components/modals/ImportAccounts/steps/02-step-connect-device.js new file mode 100644 index 00000000..c807ce56 --- /dev/null +++ b/src/components/modals/ImportAccounts/steps/02-step-connect-device.js @@ -0,0 +1,33 @@ +// @flow + +import React from 'react' + +import Button from 'components/base/Button' +import ConnectDevice from 'components/modals/StepConnectDevice' + +import type { StepProps } from '../index' + +function StepConnectDevice({ t, currency, currentDevice, setState }: StepProps) { + return ( + { + if (s === 'connected') { + setState({ isAppOpened: true }) + } + }} + /> + ) +} + +export function StepConnectDeviceFooter({ t, transitionTo, isAppOpened }: StepProps) { + return ( + + ) +} + +export default StepConnectDevice diff --git a/src/components/modals/ImportAccounts/steps/03-step-in-progress.js b/src/components/modals/ImportAccounts/steps/03-step-in-progress.js new file mode 100644 index 00000000..30dc5b9f --- /dev/null +++ b/src/components/modals/ImportAccounts/steps/03-step-in-progress.js @@ -0,0 +1,11 @@ +// @flow + +import React from 'react' + +import Box from 'components/base/Box' + +function StepInProgress() { + return {'StepInProgress'} +} + +export default StepInProgress diff --git a/src/components/modals/ImportAccounts/steps/04-step-finish.js b/src/components/modals/ImportAccounts/steps/04-step-finish.js new file mode 100644 index 00000000..1fbd9719 --- /dev/null +++ b/src/components/modals/ImportAccounts/steps/04-step-finish.js @@ -0,0 +1,11 @@ +// @flow + +import React from 'react' + +import Box from 'components/base/Box' + +function StepFinish() { + return {'StepFinish'} +} + +export default StepFinish diff --git a/src/components/modals/StepConnectDevice.js b/src/components/modals/StepConnectDevice.js index b36d6740..99a61b00 100644 --- a/src/components/modals/StepConnectDevice.js +++ b/src/components/modals/StepConnectDevice.js @@ -13,7 +13,7 @@ type Props = { account?: ?Account, currency?: ?CryptoCurrency, deviceSelected?: ?Device, - onChangeDevice: Device => void, + onChangeDevice?: Device => void, onStatusChange: string => void, } diff --git a/src/components/modals/index.js b/src/components/modals/index.js index 5f286eef..c44a197b 100644 --- a/src/components/modals/index.js +++ b/src/components/modals/index.js @@ -1,5 +1,5 @@ export Debug from './Debug' -export AddAccount from './AddAccount' +export ImportAccounts from './ImportAccounts' export OperationDetails from './OperationDetails' export Receive from './Receive' export Send from './Send' diff --git a/src/icons/Home.js b/src/icons/Home.js index 72768838..4850aa6a 100644 --- a/src/icons/Home.js +++ b/src/icons/Home.js @@ -3,7 +3,7 @@ import React from 'react' const path = ( - + ) export default ({ size, ...p }: { size: number }) => ( diff --git a/static/i18n/en/importAccounts.yml b/static/i18n/en/importAccounts.yml new file mode 100644 index 00000000..4d122a82 --- /dev/null +++ b/static/i18n/en/importAccounts.yml @@ -0,0 +1,6 @@ +title: Import accounts +breadcrumb: + informations: Informations + connectDevice: Connect device + import: Import accounts + finish: Confirm From 24d4a4842661cd8a0f6a626c8d6d7d73949a0be0 Mon Sep 17 00:00:00 2001 From: meriadec Date: Sat, 26 May 2018 15:23:06 +0200 Subject: [PATCH 03/15] Rename step-progress to step-import --- src/components/modals/ImportAccounts/index.js | 4 ++-- .../steps/{03-step-in-progress.js => 03-step-import.js} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/components/modals/ImportAccounts/steps/{03-step-in-progress.js => 03-step-import.js} (100%) diff --git a/src/components/modals/ImportAccounts/index.js b/src/components/modals/ImportAccounts/index.js index e6adf70b..46990505 100644 --- a/src/components/modals/ImportAccounts/index.js +++ b/src/components/modals/ImportAccounts/index.js @@ -16,7 +16,7 @@ import Breadcrumb from 'components/Breadcrumb' import StepChooseCurrency, { StepChooseCurrencyFooter } from './steps/01-step-choose-currency' import StepConnectDevice, { StepConnectDeviceFooter } from './steps/02-step-connect-device' -import StepInProgress from './steps/03-step-in-progress' +import StepImport from './steps/03-step-import' import StepFinish from './steps/04-step-finish' const createSteps = ({ t }: { t: T }) => [ @@ -37,7 +37,7 @@ const createSteps = ({ t }: { t: T }) => [ { id: 'import', label: t('importAccounts:breadcrumb.import'), - component: StepInProgress, + component: StepImport, footer: null, hideFooter: true, }, diff --git a/src/components/modals/ImportAccounts/steps/03-step-in-progress.js b/src/components/modals/ImportAccounts/steps/03-step-import.js similarity index 100% rename from src/components/modals/ImportAccounts/steps/03-step-in-progress.js rename to src/components/modals/ImportAccounts/steps/03-step-import.js From e58b8b693c6165266a6f9542bebfba47861c5283 Mon Sep 17 00:00:00 2001 From: meriadec Date: Sat, 26 May 2018 16:20:57 +0200 Subject: [PATCH 04/15] Integrate the connect device step of import accounts --- src/components/base/CurrencyBadge.js | 56 +++++++++++++++++++ src/components/modals/ImportAccounts/index.js | 7 +-- .../steps/01-step-choose-currency.js | 12 ++-- .../steps/02-step-connect-device.js | 42 ++++++++++---- static/i18n/en/common.yml | 2 +- static/i18n/en/importAccounts.yml | 2 +- 6 files changed, 100 insertions(+), 21 deletions(-) create mode 100644 src/components/base/CurrencyBadge.js diff --git a/src/components/base/CurrencyBadge.js b/src/components/base/CurrencyBadge.js new file mode 100644 index 00000000..0e4de748 --- /dev/null +++ b/src/components/base/CurrencyBadge.js @@ -0,0 +1,56 @@ +// @flow + +import React from 'react' +import styled from 'styled-components' +import { getCryptoCurrencyIcon } from '@ledgerhq/live-common/lib/react' + +import type { Currency } from '@ledgerhq/live-common/lib/types' + +import { rgba } from 'styles/helpers' + +import Box from 'components/base/Box' + +const CryptoIconWrapper = styled(Box).attrs({ + align: 'center', + justify: 'center', + bg: p => rgba(p.cryptoColor, 0.1), + color: p => p.cryptoColor, +})` + border-radius: 50%; + width: ${p => p.size || 40}px; + height: ${p => p.size || 40}px; +` + +export function CurrencyCircleIcon({ + currency, + size, + ...props +}: { + currency: Currency, + size: number, +}) { + const Icon = getCryptoCurrencyIcon(currency) + return ( + + {Icon && } + + ) +} + +function CurrencyBadge({ currency, ...props }: { currency: Currency }) { + return ( + + + + + {currency.ticker} + + + {currency.name} + + + + ) +} + +export default CurrencyBadge diff --git a/src/components/modals/ImportAccounts/index.js b/src/components/modals/ImportAccounts/index.js index 46990505..02e709f7 100644 --- a/src/components/modals/ImportAccounts/index.js +++ b/src/components/modals/ImportAccounts/index.js @@ -4,6 +4,7 @@ import React, { PureComponent } from 'react' import { compose } from 'redux' import { connect } from 'react-redux' import { translate } from 'react-i18next' + import type { Currency } from '@ledgerhq/live-common/lib/types' import type { T, Device } from 'types/common' @@ -129,10 +130,8 @@ class ImportAccounts extends PureComponent { {!hideFooter && ( - - - {StepFooter ? : footer} - + + {StepFooter ? : footer} )} diff --git a/src/components/modals/ImportAccounts/steps/01-step-choose-currency.js b/src/components/modals/ImportAccounts/steps/01-step-choose-currency.js index 90179ba8..27f6136e 100644 --- a/src/components/modals/ImportAccounts/steps/01-step-choose-currency.js +++ b/src/components/modals/ImportAccounts/steps/01-step-choose-currency.js @@ -1,9 +1,10 @@ // @flow -import React from 'react' +import React, { Fragment } from 'react' import SelectCurrency from 'components/SelectCurrency' import Button from 'components/base/Button' +import CurrencyBadge from 'components/base/CurrencyBadge' import type { StepProps } from '../index' @@ -13,9 +14,12 @@ function StepChooseCurrency({ currency, setState }: StepProps) { export function StepChooseCurrencyFooter({ transitionTo, currency, t }: StepProps) { return ( - + + {currency && } + + ) } diff --git a/src/components/modals/ImportAccounts/steps/02-step-connect-device.js b/src/components/modals/ImportAccounts/steps/02-step-connect-device.js index c807ce56..89ee79d2 100644 --- a/src/components/modals/ImportAccounts/steps/02-step-connect-device.js +++ b/src/components/modals/ImportAccounts/steps/02-step-connect-device.js @@ -1,24 +1,44 @@ // @flow -import React from 'react' +import React, { Fragment } from 'react' +import { Trans } from 'react-i18next' import Button from 'components/base/Button' +import Box from 'components/base/Box' import ConnectDevice from 'components/modals/StepConnectDevice' +import { CurrencyCircleIcon } from 'components/base/CurrencyBadge' import type { StepProps } from '../index' function StepConnectDevice({ t, currency, currentDevice, setState }: StepProps) { + if (!currency) { + throw new Error('No currency given') + } return ( - { - if (s === 'connected') { - setState({ isAppOpened: true }) - } - }} - /> + + + + + + {`You're about to import your `} + {`${currency.name} (${ + currency.ticker + })`} + {` account(s) from your Ledger device. Please follow the steps below:`} + + + + { + if (s === 'connected') { + setState({ isAppOpened: true }) + } + }} + /> + ) } diff --git a/static/i18n/en/common.yml b/static/i18n/en/common.yml index 8a75b33d..d158d1fe 100644 --- a/static/i18n/en/common.yml +++ b/static/i18n/en/common.yml @@ -8,7 +8,7 @@ continue: Continue chooseWalletPlaceholder: Choose a wallet... currency: Currency selectAccount: Select an account -selectCurrency: Select an currency +selectCurrency: Select a currency sortBy: Sort by search: Search save: Save diff --git a/static/i18n/en/importAccounts.yml b/static/i18n/en/importAccounts.yml index 4d122a82..0fbb3e0c 100644 --- a/static/i18n/en/importAccounts.yml +++ b/static/i18n/en/importAccounts.yml @@ -2,5 +2,5 @@ title: Import accounts breadcrumb: informations: Informations connectDevice: Connect device - import: Import accounts + import: Import finish: Confirm From a440b01af2ddf78d9c1aa6ee08df5834fa79a8c7 Mon Sep 17 00:00:00 2001 From: meriadec Date: Sat, 26 May 2018 17:12:53 +0200 Subject: [PATCH 05/15] Handle back on ModalTitle --- src/components/base/Modal/ModalBody.js | 4 ++ src/components/base/Modal/ModalTitle.js | 67 +++++++++++++++++++ src/components/base/Modal/index.js | 11 +-- src/components/modals/ImportAccounts/index.js | 18 +++-- .../ImportAccounts/steps/03-step-import.js | 12 ++-- 5 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 src/components/base/Modal/ModalTitle.js diff --git a/src/components/base/Modal/ModalBody.js b/src/components/base/Modal/ModalBody.js index 17d9fa8b..f6056f2a 100644 --- a/src/components/base/Modal/ModalBody.js +++ b/src/components/base/Modal/ModalBody.js @@ -67,6 +67,10 @@ const CloseContainer = styled(Box).attrs({ &:hover { color: ${p => p.theme.colors.grey}; } + + &:active { + color: ${p => p.theme.colors.dark}; + } ` const Body = styled(Box).attrs({ diff --git a/src/components/base/Modal/ModalTitle.js b/src/components/base/Modal/ModalTitle.js new file mode 100644 index 00000000..2ef88f09 --- /dev/null +++ b/src/components/base/Modal/ModalTitle.js @@ -0,0 +1,67 @@ +// @flow + +import React from 'react' +import styled from 'styled-components' +import { translate } from 'react-i18next' + +import type { T } from 'types/common' + +import Box from 'components/base/Box' +import IconAngleLeft from 'icons/AngleLeft' + +const Container = styled(Box).attrs({ + alignItems: 'center', + color: 'dark', + ff: 'Museo Sans|Regular', + fontSize: 6, + justifyContent: 'center', + p: 5, + relative: true, +})`` + +const Back = styled(Box).attrs({ + horizontal: true, + align: 'center', + color: 'grey', + ff: 'Open Sans', + fontSize: 3, + p: 4, +})` + cursor: pointer; + position: absolute; + top: 0; + left: 0; + + &:hover { + color: ${p => p.theme.colors.graphite}; + } + + &:active { + color: ${p => p.theme.colors.dark}; + } +` + +function ModalTitle({ + t, + onBack, + children, + ...props +}: { + t: T, + onBack: any => void, + children: any, +}) { + return ( + + {onBack && ( + + + {t('common:back')} + + )} + {children} + + ) +} + +export default translate()(ModalTitle) diff --git a/src/components/base/Modal/index.js b/src/components/base/Modal/index.js index 950970b4..af637837 100644 --- a/src/components/base/Modal/index.js +++ b/src/components/base/Modal/index.js @@ -22,6 +22,7 @@ import Defer from 'components/base/Defer' export { default as ModalBody } from './ModalBody' export { default as ConfirmModal } from './ConfirmModal' +export { default as ModalTitle } from './ModalTitle' const springConfig = { stiffness: 320, @@ -188,16 +189,6 @@ export class Modal extends Component { } } -export const ModalTitle = styled(Box).attrs({ - alignItems: 'center', - color: 'dark', - ff: 'Museo Sans|Regular', - fontSize: 6, - justifyContent: 'center', - p: 5, - relative: true, -})`` - export const ModalFooter = styled(Box).attrs({ px: 5, py: 3, diff --git a/src/components/modals/ImportAccounts/index.js b/src/components/modals/ImportAccounts/index.js index 02e709f7..b5bf0190 100644 --- a/src/components/modals/ImportAccounts/index.js +++ b/src/components/modals/ImportAccounts/index.js @@ -26,6 +26,7 @@ const createSteps = ({ t }: { t: T }) => [ label: t('importAccounts:breadcrumb.informations'), component: StepChooseCurrency, footer: StepChooseCurrencyFooter, + onBack: null, hideFooter: false, }, { @@ -33,6 +34,7 @@ const createSteps = ({ t }: { t: T }) => [ label: t('importAccounts:breadcrumb.connectDevice'), component: StepConnectDevice, footer: StepConnectDeviceFooter, + onBack: ({ transitionTo }: StepProps) => transitionTo('chooseCurrency'), hideFooter: false, }, { @@ -40,14 +42,16 @@ const createSteps = ({ t }: { t: T }) => [ label: t('importAccounts:breadcrumb.import'), component: StepImport, footer: null, + onBack: ({ transitionTo }: StepProps) => transitionTo('chooseCurrency'), hideFooter: true, }, { id: 'finish', label: t('importAccounts:breadcrumb.finish'), component: StepFinish, - hideFooter: false, footer: StepChooseCurrencyFooter, + onBack: null, + hideFooter: false, }, ] @@ -92,7 +96,11 @@ class ImportAccounts extends PureComponent { }) transitionTo = stepId => { - this.setState({ stepId }) + let nextState = { stepId } + if (stepId === 'chooseCurrency') { + nextState = { ...INITIAL_STATE } + } + this.setState(nextState) } render() { @@ -106,7 +114,7 @@ class ImportAccounts extends PureComponent { throw new Error(`ImportAccountsModal: step ${stepId} doesn't exists`) } - const { component: StepComponent, footer: StepFooter, hideFooter } = step + const { component: StepComponent, footer: StepFooter, hideFooter, onBack } = step const stepProps: StepProps = { t, @@ -124,7 +132,9 @@ class ImportAccounts extends PureComponent { onHide={() => this.setState({ ...INITIAL_STATE })} render={({ onClose }) => ( - {t('importAccounts:title')} + onBack(stepProps) : void 0}> + {t('importAccounts:title')} + diff --git a/src/components/modals/ImportAccounts/steps/03-step-import.js b/src/components/modals/ImportAccounts/steps/03-step-import.js index 30dc5b9f..6e60d287 100644 --- a/src/components/modals/ImportAccounts/steps/03-step-import.js +++ b/src/components/modals/ImportAccounts/steps/03-step-import.js @@ -1,11 +1,15 @@ // @flow -import React from 'react' +import React, { PureComponent } from 'react' import Box from 'components/base/Box' -function StepInProgress() { - return {'StepInProgress'} +import type { StepProps } from '../index' + +class StepImport extends PureComponent { + render() { + return step import + } } -export default StepInProgress +export default StepImport From 08ac8d01b78628bf1258a46f90a146013f7616e8 Mon Sep 17 00:00:00 2001 From: meriadec Date: Sat, 26 May 2018 18:22:43 +0200 Subject: [PATCH 06/15] Clean up state, and prepare sync step --- src/components/modals/ImportAccounts/index.js | 2 - .../ImportAccounts/steps/03-step-import.js | 96 ++++++++++++++++++- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/src/components/modals/ImportAccounts/index.js b/src/components/modals/ImportAccounts/index.js index b5bf0190..20ec0566 100644 --- a/src/components/modals/ImportAccounts/index.js +++ b/src/components/modals/ImportAccounts/index.js @@ -75,7 +75,6 @@ type State = { stepId: StepId, isAppOpened: boolean, currency: ?Currency, - scannedAccounts: [], } const mapStateToProps = state => ({ @@ -86,7 +85,6 @@ const INITIAL_STATE = { stepId: 'chooseCurrency', isAppOpened: false, currency: null, - scannedAccounts: [], } class ImportAccounts extends PureComponent { diff --git a/src/components/modals/ImportAccounts/steps/03-step-import.js b/src/components/modals/ImportAccounts/steps/03-step-import.js index 6e60d287..086bb5f5 100644 --- a/src/components/modals/ImportAccounts/steps/03-step-import.js +++ b/src/components/modals/ImportAccounts/steps/03-step-import.js @@ -1,15 +1,107 @@ // @flow import React, { PureComponent } from 'react' +import styled from 'styled-components' + +import type { Account } from '@ledgerhq/live-common/lib/types' + +import { getBridgeForCurrency } from 'bridge' import Box from 'components/base/Box' +import Button from 'components/base/Button' import type { StepProps } from '../index' -class StepImport extends PureComponent { +type Status = 'scanning' | 'error' | 'finished' + +type State = { + status: Status, + err: ?Error, + scannedAccounts: Account[], +} + +const INITIAL_STATE = { + status: 'scanning', + err: null, + scannedAccounts: [], +} + +class StepImport extends PureComponent { + state = INITIAL_STATE + + componentDidMount() { + console.log(`starting import...`) + this.startScanAccountsDevice() + } + + componentWillUnmount() { + console.log(`stopping import...`) + } + + startScanAccountsDevice() { + const { currency } = this.props + + if (!currency) { + throw new Error('No currency to scan') + } + + const bridge = getBridgeForCurrency(currency) + + // TODO: use the real device + const devicePath = '' + + this.scanSubscription = bridge.scanAccountsOnDevice(currency, devicePath, { + next: account => { + const { scannedAccounts } = this.state + const hasAlreadyBeenScanned = !!scannedAccounts.find(a => account.id === a.id) + if (!hasAlreadyBeenScanned) { + this.setState({ scannedAccounts: [...scannedAccounts, account] }) + } + }, + complete: () => { + this.setState({ status: 'finished' }) + }, + error: err => this.setState({ status: 'error', err }), + }) + } + + handleRetry = () => { + this.setState(INITIAL_STATE) + this.startScanAccountsDevice() + } + render() { - return step import + const { status, err, scannedAccounts } = this.state + + return ( + + {status === 'scanning' && {'Scanning in progress...'}} + {status === 'finished' && {'Finished'}} + {['error', 'finished'].includes(status) && ( + + )} + {err && {err.toString()}} + + + {scannedAccounts.map(account => )} + + + ) } } +const AccountsList = styled(Box).attrs({ + flow: 2, +})`` + +const AccountRowContainer = styled(Box).attrs({ + horizontal: true, +})`` + +const AccountRow = ({ account }: { account: Account }) => ( + {account.name} +) + export default StepImport From 2f8d7c3339f7bfedb0aec375070a69ad904acf5c Mon Sep 17 00:00:00 2001 From: meriadec Date: Tue, 29 May 2018 11:44:35 +0200 Subject: [PATCH 07/15] Mock things up for rapid development --- src/bridge/index.js | 4 ++++ src/bridge/makeMockBridge.js | 4 ++-- src/components/modals/ImportAccounts/index.js | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/bridge/index.js b/src/bridge/index.js index 3ea7fe37..ba9c8968 100644 --- a/src/bridge/index.js +++ b/src/bridge/index.js @@ -1,5 +1,8 @@ // @flow import type { Currency } from '@ledgerhq/live-common/lib/types' + +import makeMockBridge from 'bridge/makeMockBridge' + import { WalletBridge } from './types' import LibcoreBridge from './LibcoreBridge' import EthereumJSBridge from './EthereumJSBridge' @@ -12,5 +15,6 @@ export const getBridgeForCurrency = (currency: Currency): WalletBridge => { if (currency.id === 'ripple') { return RippleJSBridge // polyfill js } + return makeMockBridge({}) return LibcoreBridge // libcore for the rest } diff --git a/src/bridge/makeMockBridge.js b/src/bridge/makeMockBridge.js index 80a3de90..6cd38b72 100644 --- a/src/bridge/makeMockBridge.js +++ b/src/bridge/makeMockBridge.js @@ -79,13 +79,13 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> { async function job() { if (Math.random() > scanAccountDeviceSuccessRate) { - await delay(5000) + await delay(1000) if (!unsubscribed) error(new Error('scan failed')) return } const nbAccountToGen = 3 for (let i = 0; i < nbAccountToGen && !unsubscribed; i++) { - await delay(2000) + await delay(500) const account = genAccount(String(Math.random()), { operationsSize: 0, currency, diff --git a/src/components/modals/ImportAccounts/index.js b/src/components/modals/ImportAccounts/index.js index 20ec0566..ffd78eb8 100644 --- a/src/components/modals/ImportAccounts/index.js +++ b/src/components/modals/ImportAccounts/index.js @@ -82,9 +82,9 @@ const mapStateToProps = state => ({ }) const INITIAL_STATE = { - stepId: 'chooseCurrency', + stepId: 'import', isAppOpened: false, - currency: null, + currency: getCryptoCurrencyById('bitcoin'), } class ImportAccounts extends PureComponent { From 2d07cda83803f365ad8b1e9a11dc797916fcd9d7 Mon Sep 17 00:00:00 2001 From: meriadec Date: Tue, 29 May 2018 11:44:58 +0200 Subject: [PATCH 08/15] Integrate the first three steps of import accounts modal --- src/components/Breadcrumb/Step.js | 2 +- src/components/CryptoCurrencyIcon.js | 5 +- src/components/base/Input/index.js | 17 ++ src/components/base/Radio/index.js | 5 +- src/components/base/Spinner.js | 30 ++++ .../modals/ImportAccounts/AccountRow.js | 159 ++++++++++++++++++ src/components/modals/ImportAccounts/index.js | 8 +- .../ImportAccounts/steps/03-step-import.js | 105 +++++++++--- src/icons/Loader.js | 5 +- 9 files changed, 303 insertions(+), 33 deletions(-) create mode 100644 src/components/base/Spinner.js create mode 100644 src/components/modals/ImportAccounts/AccountRow.js diff --git a/src/components/Breadcrumb/Step.js b/src/components/Breadcrumb/Step.js index ad0b7126..9460b122 100644 --- a/src/components/Breadcrumb/Step.js +++ b/src/components/Breadcrumb/Step.js @@ -29,7 +29,7 @@ const Wrapper = styled(Box).attrs({ const StepNumber = styled(Box).attrs({ alignItems: 'center', justifyContent: 'center', - color: 'fog', + color: p => (['active', 'valid'].includes(p.status) ? 'white' : 'fog'), bg: p => ['active', 'valid'].includes(p.status) ? 'wallet' : p.status === 'error' ? 'alertRed' : 'white', ff: 'Rubik|Regular', diff --git a/src/components/CryptoCurrencyIcon.js b/src/components/CryptoCurrencyIcon.js index 843c223a..192c8b64 100644 --- a/src/components/CryptoCurrencyIcon.js +++ b/src/components/CryptoCurrencyIcon.js @@ -6,13 +6,14 @@ import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' type Props = { currency: CryptoCurrency, size: number, + color: string, } class CryptoCurrencyIcon extends PureComponent { render() { - const { currency, size } = this.props + const { currency, size, color } = this.props const IconCurrency = getCryptoCurrencyIcon(currency) - return IconCurrency ? : null + return IconCurrency ? : null } } diff --git a/src/components/base/Input/index.js b/src/components/base/Input/index.js index 906fae6e..e246ce10 100644 --- a/src/components/base/Input/index.js +++ b/src/components/base/Input/index.js @@ -68,6 +68,7 @@ type Props = { keepEvent?: boolean, onBlur: Function, onChange?: Function, + onEnter?: Function, onFocus: Function, renderLeft?: any, renderRight?: any, @@ -100,6 +101,16 @@ class Input extends PureComponent { } } + handleKeyDown = (e: SyntheticInputEvent) => { + // handle enter key + if (e.which === 13) { + const { onEnter } = this.props + if (onEnter) { + onEnter(e) + } + } + } + // FIXME this is a bad idea! this is the behavior of an input. instead renderLeft/renderRight should be pointer-event:none ! handleClick = () => this._input && this._input.focus() @@ -119,6 +130,11 @@ class Input extends PureComponent { onBlur(e) } + handleSelectEverything = () => { + this._input.setSelectionRange(0, this._input.value.length) + this._input.focus() + } + _input = null render() { @@ -142,6 +158,7 @@ class Input extends PureComponent { onFocus={this.handleFocus} onBlur={this.handleBlur} onChange={this.handleChange} + onKeyDown={this.handleKeyDown} /> {renderRight} diff --git a/src/components/base/Radio/index.js b/src/components/base/Radio/index.js index d6c9584b..c63c8a09 100644 --- a/src/components/base/Radio/index.js +++ b/src/components/base/Radio/index.js @@ -7,15 +7,16 @@ import { Tabbable } from 'components/base/Box' const Base = styled(Tabbable).attrs({ relative: true })` outline: none; - box-shadow: 0 0 0 1px ${p => (p.isChecked ? p.theme.colors.lightGrey : p.theme.colors.graphite)}; + box-shadow: 0 0 0 1px ${p => (p.isChecked ? p.theme.colors.lightGrey : p.theme.colors.fog)}; border-radius: 50%; height: 19px; width: 19px; transition: all ease-in-out 0.1s; + background-color: white; &:focus { box-shadow: 0 0 0 ${p => (p.isChecked ? 4 : 2)}px - ${p => (p.isChecked ? p.theme.colors.lightGrey : p.theme.colors.graphite)}; + ${p => (p.isChecked ? p.theme.colors.lightGrey : p.theme.colors.fog)}; } &:before, diff --git a/src/components/base/Spinner.js b/src/components/base/Spinner.js new file mode 100644 index 00000000..6d3cec98 --- /dev/null +++ b/src/components/base/Spinner.js @@ -0,0 +1,30 @@ +// @flow + +import React from 'react' +import styled, { keyframes } from 'styled-components' + +import Box from 'components/base/Box' +import IconLoader from 'icons/Loader' + +const rotate = keyframes` + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +` + +const Container = styled(Box)` + width: ${p => p.size}px; + height: ${p => p.size}px; + animation: ${rotate} 1.5s linear infinite; +` + +export default function Spinner({ size, ...props }: { size: number }) { + return ( + + + + ) +} diff --git a/src/components/modals/ImportAccounts/AccountRow.js b/src/components/modals/ImportAccounts/AccountRow.js new file mode 100644 index 00000000..b68dbd97 --- /dev/null +++ b/src/components/modals/ImportAccounts/AccountRow.js @@ -0,0 +1,159 @@ +// @flow + +import React, { PureComponent } from 'react' +import styled from 'styled-components' + +import { darken } from 'styles/helpers' + +import Box from 'components/base/Box' +import Radio from 'components/base/Radio' +import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon' +import FormattedVal from 'components/base/FormattedVal' +import Input from 'components/base/Input' +import IconEdit from 'icons/Edit' +import IconCheck from 'icons/Check' + +type Props = { + account: Account, + isChecked: boolean, + onClick: Account => void, + onAccountUpdate: Account => void, +} + +type State = { + isEditing: boolean, + accountNameCopy: string, +} + +export default class AccountRow extends PureComponent { + state = { + isEditing: false, + accountNameCopy: '', + } + + componentDidUpdate(prevProps, prevState) { + const startedEditing = !prevState.isEditing && this.state.isEditing + if (startedEditing) { + this._input.handleSelectEverything() + } + } + + handleEditClick = e => { + this.handlePreventSubmit(e) + const { account } = this.props + this.setState({ isEditing: true, accountNameCopy: account.name }) + } + + handleSubmitName = e => { + this.handlePreventSubmit(e) + const { account, onAccountUpdate, isChecked, onClick } = this.props + const { accountNameCopy } = this.state + const updatedAccount = { ...account, name: accountNameCopy } + this.setState({ isEditing: false, accountNameCopy: '' }) + onAccountUpdate(updatedAccount) + if (!isChecked) { + onClick(updatedAccount) + } + } + + handlePreventSubmit = e => { + // prevent account row to be submitted + e.preventDefault() + e.stopPropagation() + } + + handleChangeName = accountNameCopy => this.setState({ accountNameCopy }) + + _input = null + + render() { + const { account, isChecked, onClick } = this.props + const { isEditing, accountNameCopy } = this.state + + return ( + onClick(account)}> + + + {isEditing ? ( + + + + } + ref={input => (this._input = input)} + /> + ) : ( +
{account.name}
+ )} +
+ {!isEditing && ( + + + {'edit name'} + + )} + + +
+ ) + } +} + +const AccountRowContainer = styled(Box).attrs({ + horizontal: true, + align: 'center', + bg: 'lightGrey', + px: 3, + flow: 3, +})` + height: 48px; + border-radius: 4px; + cursor: pointer; + + &:hover { + background-color: ${p => darken(p.theme.colors.lightGrey, 0.015)}; + } + + &:active { + background-color: ${p => darken(p.theme.colors.lightGrey, 0.03)}; + } +` + +const Edit = styled(Box).attrs({ + color: 'wallet', + fontSize: 3, + horizontal: true, + align: 'center', + flow: 1, + py: 1, +})` + display: none; + ${AccountRowContainer}:hover & { + display: flex; + } + &:hover { + text-decoration: underline; + } +` + +const InputRight = styled(Box).attrs({ + bg: 'wallet', + color: 'white', + align: 'center', + justify: 'center', + shrink: 0, +})` + width: 40px; +` diff --git a/src/components/modals/ImportAccounts/index.js b/src/components/modals/ImportAccounts/index.js index ffd78eb8..f8267c2c 100644 --- a/src/components/modals/ImportAccounts/index.js +++ b/src/components/modals/ImportAccounts/index.js @@ -17,9 +17,11 @@ import Breadcrumb from 'components/Breadcrumb' import StepChooseCurrency, { StepChooseCurrencyFooter } from './steps/01-step-choose-currency' import StepConnectDevice, { StepConnectDeviceFooter } from './steps/02-step-connect-device' -import StepImport from './steps/03-step-import' +import StepImport, { StepImportFooter } from './steps/03-step-import' import StepFinish from './steps/04-step-finish' +const { getCryptoCurrencyById } = require('@ledgerhq/live-common/lib/helpers/currencies') + const createSteps = ({ t }: { t: T }) => [ { id: 'chooseCurrency', @@ -41,9 +43,9 @@ const createSteps = ({ t }: { t: T }) => [ id: 'import', label: t('importAccounts:breadcrumb.import'), component: StepImport, - footer: null, + footer: StepImportFooter, onBack: ({ transitionTo }: StepProps) => transitionTo('chooseCurrency'), - hideFooter: true, + hideFooter: false, }, { id: 'finish', diff --git a/src/components/modals/ImportAccounts/steps/03-step-import.js b/src/components/modals/ImportAccounts/steps/03-step-import.js index 086bb5f5..5777b48c 100644 --- a/src/components/modals/ImportAccounts/steps/03-step-import.js +++ b/src/components/modals/ImportAccounts/steps/03-step-import.js @@ -1,7 +1,7 @@ // @flow import React, { PureComponent } from 'react' -import styled from 'styled-components' +import keyBy from 'lodash/keyBy' import type { Account } from '@ledgerhq/live-common/lib/types' @@ -9,6 +9,10 @@ import { getBridgeForCurrency } from 'bridge' import Box from 'components/base/Box' import Button from 'components/base/Button' +import Spinner from 'components/base/Spinner' +import IconExchange from 'icons/Exchange' + +import AccountRow from '../AccountRow' import type { StepProps } from '../index' @@ -18,12 +22,14 @@ type State = { status: Status, err: ?Error, scannedAccounts: Account[], + checkedAccountsIds: string[], } const INITIAL_STATE = { status: 'scanning', err: null, scannedAccounts: [], + checkedAccountsIds: [], } class StepImport extends PureComponent { @@ -36,6 +42,9 @@ class StepImport extends PureComponent { componentWillUnmount() { console.log(`stopping import...`) + if (this.scanSubscription) { + this.scanSubscription.unsubscribe() + } } startScanAccountsDevice() { @@ -66,42 +75,90 @@ class StepImport extends PureComponent { } handleRetry = () => { + if (this.scanSubscription) { + this.scanSubscription.unsubscribe() + this.scanSubscription = null + } this.setState(INITIAL_STATE) this.startScanAccountsDevice() } + handleToggleAccount = account => { + const { checkedAccountsIds } = this.state + const isChecked = checkedAccountsIds.find(id => id === account.id) !== undefined + if (isChecked) { + this.setState({ checkedAccountsIds: checkedAccountsIds.filter(id => id !== account.id) }) + } else { + this.setState({ checkedAccountsIds: [...checkedAccountsIds, account.id] }) + } + } + + handleAccountUpdate = updatedAccount => { + const { scannedAccounts } = this.state + this.setState({ + scannedAccounts: scannedAccounts.map(account => { + if (account.id !== updatedAccount.id) { + return account + } + return updatedAccount + }), + }) + } + render() { - const { status, err, scannedAccounts } = this.state + const { status, err, scannedAccounts, checkedAccountsIds } = this.state return ( - {status === 'scanning' && {'Scanning in progress...'}} - {status === 'finished' && {'Finished'}} - {['error', 'finished'].includes(status) && ( - - )} {err && {err.toString()}} - - {scannedAccounts.map(account => )} - + + {scannedAccounts.map(account => { + const isChecked = checkedAccountsIds.find(id => id === account.id) !== undefined + return ( + + ) + })} + {status === 'scanning' && ( + + + + )} + + + + {['error', 'finished'].includes(status) && ( + + )} + ) } } -const AccountsList = styled(Box).attrs({ - flow: 2, -})`` - -const AccountRowContainer = styled(Box).attrs({ - horizontal: true, -})`` - -const AccountRow = ({ account }: { account: Account }) => ( - {account.name} -) - export default StepImport + +export const StepImportFooter = (props: StepProps) => { + return ( +
noetuhnoethunot
+ ) +} diff --git a/src/icons/Loader.js b/src/icons/Loader.js index eb63088e..5c205762 100644 --- a/src/icons/Loader.js +++ b/src/icons/Loader.js @@ -3,7 +3,10 @@ import React from 'react' const path = ( - + ) export default ({ size, ...p }: { size: number }) => ( From 2542498dc05cfeaee154fe6616bf697b0261327c Mon Sep 17 00:00:00 2001 From: meriadec Date: Tue, 29 May 2018 12:25:31 +0200 Subject: [PATCH 09/15] Move import step state to import modal state --- src/bridge/index.js | 4 - src/components/base/Input/index.js | 6 + src/components/base/Modal/index.js | 1 + .../modals/ImportAccounts/AccountRow.js | 3 + src/components/modals/ImportAccounts/index.js | 55 +++++++-- .../ImportAccounts/steps/03-step-import.js | 112 ++++++++---------- 6 files changed, 105 insertions(+), 76 deletions(-) diff --git a/src/bridge/index.js b/src/bridge/index.js index ba9c8968..3ea7fe37 100644 --- a/src/bridge/index.js +++ b/src/bridge/index.js @@ -1,8 +1,5 @@ // @flow import type { Currency } from '@ledgerhq/live-common/lib/types' - -import makeMockBridge from 'bridge/makeMockBridge' - import { WalletBridge } from './types' import LibcoreBridge from './LibcoreBridge' import EthereumJSBridge from './EthereumJSBridge' @@ -15,6 +12,5 @@ export const getBridgeForCurrency = (currency: Currency): WalletBridge => { if (currency.id === 'ripple') { return RippleJSBridge // polyfill js } - return makeMockBridge({}) return LibcoreBridge // libcore for the rest } diff --git a/src/components/base/Input/index.js b/src/components/base/Input/index.js index e246ce10..d2d09747 100644 --- a/src/components/base/Input/index.js +++ b/src/components/base/Input/index.js @@ -69,6 +69,7 @@ type Props = { onBlur: Function, onChange?: Function, onEnter?: Function, + onEsc?: Function, onFocus: Function, renderLeft?: any, renderRight?: any, @@ -108,6 +109,11 @@ class Input extends PureComponent { if (onEnter) { onEnter(e) } + } else if (e.which === 27) { + const { onEsc } = this.props + if (onEsc) { + onEsc(e) + } } } diff --git a/src/components/base/Modal/index.js b/src/components/base/Modal/index.js index af637837..26511ea1 100644 --- a/src/components/base/Modal/index.js +++ b/src/components/base/Modal/index.js @@ -164,6 +164,7 @@ export class Modal extends Component { isOpened={isOpened} onClose={onClose} onHide={onHide} + closeOnEsc={!preventBackdropClick} motionStyle={(spring, isVisible) => ({ opacity: spring(isVisible ? 1 : 0, springConfig), scale: spring(isVisible ? 1 : 0.95, springConfig), diff --git a/src/components/modals/ImportAccounts/AccountRow.js b/src/components/modals/ImportAccounts/AccountRow.js index b68dbd97..b9bb96b7 100644 --- a/src/components/modals/ImportAccounts/AccountRow.js +++ b/src/components/modals/ImportAccounts/AccountRow.js @@ -64,6 +64,8 @@ export default class AccountRow extends PureComponent { handleChangeName = accountNameCopy => this.setState({ accountNameCopy }) + handleReset = () => this.setState({ isEditing: false, accountNameCopy: '' }) + _input = null render() { @@ -81,6 +83,7 @@ export default class AccountRow extends PureComponent { onChange={this.handleChangeName} onClick={this.handlePreventSubmit} onEnter={this.handleSubmitName} + onEsc={this.handleReset} renderRight={ diff --git a/src/components/modals/ImportAccounts/index.js b/src/components/modals/ImportAccounts/index.js index f8267c2c..a721681b 100644 --- a/src/components/modals/ImportAccounts/index.js +++ b/src/components/modals/ImportAccounts/index.js @@ -5,11 +5,12 @@ import { compose } from 'redux' import { connect } from 'react-redux' import { translate } from 'react-i18next' -import type { Currency } from '@ledgerhq/live-common/lib/types' +import type { Currency, Account } from '@ledgerhq/live-common/lib/types' import type { T, Device } from 'types/common' import { getCurrentDevice } from 'reducers/devices' +import { getAccounts } from 'reducers/accounts' import Modal, { ModalContent, ModalTitle, ModalFooter, ModalBody } from 'components/base/Modal' import Box from 'components/base/Box' @@ -20,8 +21,6 @@ import StepConnectDevice, { StepConnectDeviceFooter } from './steps/02-step-conn import StepImport, { StepImportFooter } from './steps/03-step-import' import StepFinish from './steps/04-step-finish' -const { getCryptoCurrencyById } = require('@ledgerhq/live-common/lib/helpers/currencies') - const createSteps = ({ t }: { t: T }) => [ { id: 'chooseCurrency', @@ -60,10 +59,25 @@ const createSteps = ({ t }: { t: T }) => [ type Props = { t: T, currentDevice: ?Device, + existingAccounts: Account[], } type StepId = 'chooseCurrency' | 'connectDevice' | 'import' | 'finish' +type ScanStatus = 'idle' | 'scanning' | 'error' | 'finished' + +type State = { + stepId: StepId, + isAppOpened: boolean, + currency: ?Currency, + + // scan process + scannedAccounts: Account[], + checkedAccountsIds: string[], + scanStatus: ScanStatus, + err: ?Error, +} + export type StepProps = { t: T, currency: ?Currency, @@ -71,22 +85,26 @@ export type StepProps = { isAppOpened: boolean, transitionTo: StepId => void, setState: any => void, -} -type State = { - stepId: StepId, - isAppOpened: boolean, - currency: ?Currency, + // scan process + scannedAccounts: Account[], + existingAccounts: Account[], + checkedAccountsIds: string[], + scanStatus: ScanStatus, + err: ?Error, } const mapStateToProps = state => ({ currentDevice: getCurrentDevice(state), + existingAccounts: getAccounts(state), }) const INITIAL_STATE = { - stepId: 'import', + stepId: 'chooseCurrency', isAppOpened: false, - currency: getCryptoCurrencyById('bitcoin'), + currency: null, + scannedAccounts: [], + checkedAccountsIds: [], } class ImportAccounts extends PureComponent { @@ -104,8 +122,16 @@ class ImportAccounts extends PureComponent { } render() { - const { t, currentDevice } = this.props - const { stepId, currency, isAppOpened } = this.state + const { t, currentDevice, existingAccounts } = this.props + const { + stepId, + currency, + isAppOpened, + scannedAccounts, + checkedAccountsIds, + scanStatus, + err, + } = this.state const stepIndex = this.STEPS.findIndex(s => s.id === stepId) const step = this.STEPS[stepIndex] @@ -120,6 +146,11 @@ class ImportAccounts extends PureComponent { t, currency, currentDevice, + existingAccounts, + scannedAccounts, + checkedAccountsIds, + scanStatus, + err, isAppOpened, transitionTo: this.transitionTo, setState: (...args) => this.setState(...args), diff --git a/src/components/modals/ImportAccounts/steps/03-step-import.js b/src/components/modals/ImportAccounts/steps/03-step-import.js index 5777b48c..c884428e 100644 --- a/src/components/modals/ImportAccounts/steps/03-step-import.js +++ b/src/components/modals/ImportAccounts/steps/03-step-import.js @@ -1,9 +1,6 @@ // @flow import React, { PureComponent } from 'react' -import keyBy from 'lodash/keyBy' - -import type { Account } from '@ledgerhq/live-common/lib/types' import { getBridgeForCurrency } from 'bridge' @@ -16,25 +13,7 @@ import AccountRow from '../AccountRow' import type { StepProps } from '../index' -type Status = 'scanning' | 'error' | 'finished' - -type State = { - status: Status, - err: ?Error, - scannedAccounts: Account[], - checkedAccountsIds: string[], -} - -const INITIAL_STATE = { - status: 'scanning', - err: null, - scannedAccounts: [], - checkedAccountsIds: [], -} - -class StepImport extends PureComponent { - state = INITIAL_STATE - +class StepImport extends PureComponent { componentDidMount() { console.log(`starting import...`) this.startScanAccountsDevice() @@ -48,30 +27,37 @@ class StepImport extends PureComponent { } startScanAccountsDevice() { - const { currency } = this.props - - if (!currency) { - throw new Error('No currency to scan') + const { currency, currentDevice, setState } = this.props + try { + if (!currency) { + throw new Error('No currency to scan') + } + + if (!currentDevice) { + throw new Error('No device') + } + + const bridge = getBridgeForCurrency(currency) + + // TODO: use the real device + const devicePath = currentDevice.path + + setState({ scanStatus: 'scanning' }) + + this.scanSubscription = bridge.scanAccountsOnDevice(currency, devicePath, { + next: account => { + const { scannedAccounts } = this.props + const hasAlreadyBeenScanned = !!scannedAccounts.find(a => account.id === a.id) + if (!hasAlreadyBeenScanned) { + setState({ scannedAccounts: [...scannedAccounts, account] }) + } + }, + complete: () => setState({ scanStatus: 'finished' }), + error: err => setState({ scanStatus: 'error', err }), + }) + } catch (err) { + setState({ scanStatus: 'error', err }) } - - const bridge = getBridgeForCurrency(currency) - - // TODO: use the real device - const devicePath = '' - - this.scanSubscription = bridge.scanAccountsOnDevice(currency, devicePath, { - next: account => { - const { scannedAccounts } = this.state - const hasAlreadyBeenScanned = !!scannedAccounts.find(a => account.id === a.id) - if (!hasAlreadyBeenScanned) { - this.setState({ scannedAccounts: [...scannedAccounts, account] }) - } - }, - complete: () => { - this.setState({ status: 'finished' }) - }, - error: err => this.setState({ status: 'error', err }), - }) } handleRetry = () => { @@ -79,23 +65,33 @@ class StepImport extends PureComponent { this.scanSubscription.unsubscribe() this.scanSubscription = null } - this.setState(INITIAL_STATE) + this.handleResetState() this.startScanAccountsDevice() } + handleResetState = () => { + const { setState } = this.props + setState({ + scanStatus: 'idle', + err: null, + scannedAccounts: [], + checkedAccountsIds: [], + }) + } + handleToggleAccount = account => { - const { checkedAccountsIds } = this.state + const { checkedAccountsIds, setState } = this.props const isChecked = checkedAccountsIds.find(id => id === account.id) !== undefined if (isChecked) { - this.setState({ checkedAccountsIds: checkedAccountsIds.filter(id => id !== account.id) }) + setState({ checkedAccountsIds: checkedAccountsIds.filter(id => id !== account.id) }) } else { - this.setState({ checkedAccountsIds: [...checkedAccountsIds, account.id] }) + setState({ checkedAccountsIds: [...checkedAccountsIds, account.id] }) } } handleAccountUpdate = updatedAccount => { - const { scannedAccounts } = this.state - this.setState({ + const { scannedAccounts, setState } = this.props + setState({ scannedAccounts: scannedAccounts.map(account => { if (account.id !== updatedAccount.id) { return account @@ -106,11 +102,11 @@ class StepImport extends PureComponent { } render() { - const { status, err, scannedAccounts, checkedAccountsIds } = this.state + const { scanStatus, err, scannedAccounts, checkedAccountsIds } = this.props return ( - {err && {err.toString()}} + {err && {err.message}} {scannedAccounts.map(account => { @@ -125,7 +121,7 @@ class StepImport extends PureComponent { /> ) })} - {status === 'scanning' && ( + {scanStatus === 'scanning' && ( { - {['error', 'finished'].includes(status) && ( + {['error', 'finished'].includes(scanStatus) && ( +) From b3141f928f3fb88e3bac10da1668f07ce2ba3cff Mon Sep 17 00:00:00 2001 From: meriadec Date: Tue, 29 May 2018 14:51:07 +0200 Subject: [PATCH 11/15] Finalize the import, add accounts to reducer, finish step --- src/components/modals/ImportAccounts/index.js | 41 ++++++++++++- .../ImportAccounts/steps/03-step-import.js | 12 ++-- .../ImportAccounts/steps/04-step-finish.js | 16 ++++- src/helpers/libcore.js | 60 +------------------ static/i18n/en/importAccounts.yml | 2 +- 5 files changed, 64 insertions(+), 67 deletions(-) diff --git a/src/components/modals/ImportAccounts/index.js b/src/components/modals/ImportAccounts/index.js index a721681b..64ee5e09 100644 --- a/src/components/modals/ImportAccounts/index.js +++ b/src/components/modals/ImportAccounts/index.js @@ -11,6 +11,8 @@ import type { T, Device } from 'types/common' import { getCurrentDevice } from 'reducers/devices' import { getAccounts } from 'reducers/accounts' +import { addAccount } from 'actions/accounts' +import { closeModal } from 'reducers/modals' import Modal, { ModalContent, ModalTitle, ModalFooter, ModalBody } from 'components/base/Modal' import Box from 'components/base/Box' @@ -50,9 +52,9 @@ const createSteps = ({ t }: { t: T }) => [ id: 'finish', label: t('importAccounts:breadcrumb.finish'), component: StepFinish, - footer: StepChooseCurrencyFooter, + footer: null, onBack: null, - hideFooter: false, + hideFooter: true, }, ] @@ -85,6 +87,8 @@ export type StepProps = { isAppOpened: boolean, transitionTo: StepId => void, setState: any => void, + onClickImport: void => void, + onCloseModal: void => void, // scan process scannedAccounts: Account[], @@ -99,6 +103,11 @@ const mapStateToProps = state => ({ existingAccounts: getAccounts(state), }) +const mapDispatchToProps = { + addAccount, + closeModal, +} + const INITIAL_STATE = { stepId: 'chooseCurrency', isAppOpened: false, @@ -121,6 +130,26 @@ class ImportAccounts extends PureComponent { this.setState(nextState) } + handleClickImport = async () => { + const { addAccount } = this.props + const { scannedAccounts, checkedAccountsIds } = this.state + const accountsIdsMap = checkedAccountsIds.reduce((acc, cur) => { + acc[cur] = true + return acc + }, {}) + const accountsToImport = scannedAccounts.filter(account => accountsIdsMap[account.id] === true) + for (let i = 0; i < accountsToImport.length; i++) { + await idleCallback() + addAccount(accountsToImport[i]) + } + this.transitionTo('finish') + } + + handleCloseModal = () => { + const { closeModal } = this.props + closeModal('importAccounts') + } + render() { const { t, currentDevice, existingAccounts } = this.props const { @@ -152,6 +181,8 @@ class ImportAccounts extends PureComponent { scanStatus, err, isAppOpened, + onClickImport: this.handleClickImport, + onCloseModal: this.handleCloseModal, transitionTo: this.transitionTo, setState: (...args) => this.setState(...args), } @@ -182,4 +213,8 @@ class ImportAccounts extends PureComponent { } } -export default compose(connect(mapStateToProps), translate())(ImportAccounts) +export default compose(connect(mapStateToProps, mapDispatchToProps), translate())(ImportAccounts) + +function idleCallback() { + return new Promise(resolve => window.requestIdleCallback(resolve)) +} diff --git a/src/components/modals/ImportAccounts/steps/03-step-import.js b/src/components/modals/ImportAccounts/steps/03-step-import.js index 05463305..97dbdd68 100644 --- a/src/components/modals/ImportAccounts/steps/03-step-import.js +++ b/src/components/modals/ImportAccounts/steps/03-step-import.js @@ -141,7 +141,7 @@ class StepImport extends PureComponent { {['error', 'finished'].includes(scanStatus) && ( - ) diff --git a/src/components/modals/ImportAccounts/steps/04-step-finish.js b/src/components/modals/ImportAccounts/steps/04-step-finish.js index 1fbd9719..4ee03102 100644 --- a/src/components/modals/ImportAccounts/steps/04-step-finish.js +++ b/src/components/modals/ImportAccounts/steps/04-step-finish.js @@ -3,9 +3,21 @@ import React from 'react' import Box from 'components/base/Box' +import Button from 'components/base/Button' +import IconCheckCircle from 'icons/CheckCircle' -function StepFinish() { - return {'StepFinish'} +function StepFinish({ onCloseModal }: StepProps) { + return ( + + + + + {'Great success!'} + + + ) } export default StepFinish diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index 1db3b08f..ac42c1e0 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -136,7 +136,8 @@ async function scanNextAccount(props: { ? await wallet.getAccount(accountIndex) : await core.createAccount(wallet, hwApp) - if (!hasBeenScanned) { + const shouldSyncAccount = true // TODO: let's sync everytime. maybe in the future we can optimize. + if (shouldSyncAccount) { await core.syncAccount(njsAccount) } @@ -214,16 +215,6 @@ async function buildAccountRaw({ // $FlowFixMe ops: NJSOperation[], }): Promise { - /* - const balanceByDay = ops.length - ? await getBalanceByDaySinceOperation({ - njsAccount, - njsOperation: ops[0], - core, - }) - : {} - */ - const njsBalance = await njsAccount.getBalance() const balance = njsBalance.toLong() @@ -269,7 +260,7 @@ async function buildAccountRaw({ freshAddressPath, balance, blockHeight, - archived: true, + archived: false, index: accountIndex, operations, pendingOperations: [], @@ -318,48 +309,3 @@ function buildOperationRaw({ date: op.getDate().toISOString(), } } - -/* -async function getBalanceByDaySinceOperation({ - njsAccount, - njsOperation, - core, -}: { - njsAccount: NJSAccount, - njsOperation: NJSOperation, - core: Object, -}) { - const startDate = njsOperation.getDate() - // set end date to tomorrow - const endDate = new Date() - endDate.setDate(endDate.getDate() + 1) - const njsBalanceHistory = await njsAccount.getBalanceHistory( - startDate.toISOString(), - endDate.toISOString(), - core.TIME_PERIODS.DAY, - ) - let i = 0 - const res = {} - while (!areSameDay(startDate, endDate)) { - const dateSQLFormatted = startDate.toISOString().substr(0, 10) - const balanceDay = njsBalanceHistory[i] - if (balanceDay) { - res[dateSQLFormatted] = njsBalanceHistory[i].toLong() - } else { - console.warn(`No balance for day ${dateSQLFormatted}. This is a bug.`) // eslint-disable-line no-console - } - startDate.setDate(startDate.getDate() + 1) - i++ - } - - return res -} - -function areSameDay(date1: Date, date2: Date): boolean { - return ( - date1.getFullYear() === date2.getFullYear() && - date1.getMonth() === date2.getMonth() && - date1.getDate() === date2.getDate() - ) -} -*/ diff --git a/static/i18n/en/importAccounts.yml b/static/i18n/en/importAccounts.yml index 0fbb3e0c..9e44b2a8 100644 --- a/static/i18n/en/importAccounts.yml +++ b/static/i18n/en/importAccounts.yml @@ -3,4 +3,4 @@ breadcrumb: informations: Informations connectDevice: Connect device import: Import - finish: Confirm + finish: End From 5fea465451788708657b6f16c0471db239cc5f08 Mon Sep 17 00:00:00 2001 From: meriadec Date: Tue, 29 May 2018 15:34:05 +0200 Subject: [PATCH 12/15] Fix stupid react-router refresh problem --- src/components/SideBar/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/SideBar/index.js b/src/components/SideBar/index.js index 359b57a0..b78c5065 100644 --- a/src/components/SideBar/index.js +++ b/src/components/SideBar/index.js @@ -145,6 +145,7 @@ const AccountsList = connect(state => ({ )) -export default compose(connect(null, mapDispatchToProps, null, { pure: false }), translate())( - SideBar, -) +export default compose( + connect(mapStateToProps, mapDispatchToProps, null, { pure: false }), + translate(), +)(SideBar) From 50eb12914f0b95e33479ae85885964141e1ac751 Mon Sep 17 00:00:00 2001 From: meriadec Date: Tue, 29 May 2018 16:32:58 +0200 Subject: [PATCH 13/15] Remove old AddAccount modal --- .../modals/AddAccount/01-step-currency.js | 27 -- .../modals/AddAccount/03-step-import.js | 51 --- src/components/modals/AddAccount/index.js | 298 ------------------ 3 files changed, 376 deletions(-) delete mode 100644 src/components/modals/AddAccount/01-step-currency.js delete mode 100644 src/components/modals/AddAccount/03-step-import.js delete mode 100644 src/components/modals/AddAccount/index.js diff --git a/src/components/modals/AddAccount/01-step-currency.js b/src/components/modals/AddAccount/01-step-currency.js deleted file mode 100644 index e038db07..00000000 --- a/src/components/modals/AddAccount/01-step-currency.js +++ /dev/null @@ -1,27 +0,0 @@ -// @flow - -import React from 'react' - -import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' -import type { T } from 'types/common' - -import Box from 'components/base/Box' -import Label from 'components/base/Label' -import SelectCurrency from 'components/SelectCurrency' - -type Props = { - onChangeCurrency: Function, - currency?: ?CryptoCurrency, - t: T, -} - -export default (props: Props) => ( - - - - -) diff --git a/src/components/modals/AddAccount/03-step-import.js b/src/components/modals/AddAccount/03-step-import.js deleted file mode 100644 index 8821aaed..00000000 --- a/src/components/modals/AddAccount/03-step-import.js +++ /dev/null @@ -1,51 +0,0 @@ -// @flow - -import React from 'react' - -import type { Account } from '@ledgerhq/live-common/lib/types' - -import Box from 'components/base/Box' -import CheckBox from 'components/base/CheckBox' -import FormattedVal from 'components/base/FormattedVal' - -type Props = { - scannedAccounts: Account[], - selectedAccounts: Account[], - existingAccounts: Account[], - onToggleAccount?: Account => void, -} - -function StepImport(props: Props) { - const { scannedAccounts, selectedAccounts, existingAccounts, onToggleAccount } = props - return ( - - (design is not yet integrated!) - {scannedAccounts.map(account => { - const isSelected = selectedAccounts.find(a => a.id === account.id) - const isExisting = existingAccounts.find(a => a.id === account.id && a.archived === false) - return ( - onToggleAccount(account) : undefined} - > - - - {account.name} - - - - ) - })} - - ) -} - -export default StepImport diff --git a/src/components/modals/AddAccount/index.js b/src/components/modals/AddAccount/index.js deleted file mode 100644 index 73a22aad..00000000 --- a/src/components/modals/AddAccount/index.js +++ /dev/null @@ -1,298 +0,0 @@ -// @flow - -import React, { PureComponent } from 'react' -import { connect } from 'react-redux' -import { compose } from 'redux' -import { translate } from 'react-i18next' -import { createStructuredSelector } from 'reselect' - -import type { Account, CryptoCurrency } from '@ledgerhq/live-common/lib/types' - -import type { Device, T } from 'types/common' - -import { MODAL_ADD_ACCOUNT } from 'config/constants' - -import { closeModal } from 'reducers/modals' -import { - canCreateAccount, - getAccounts, - getVisibleAccounts, - getArchivedAccounts, -} from 'reducers/accounts' - -import { addAccount, updateAccount } from 'actions/accounts' - -import Box from 'components/base/Box' -import Breadcrumb from 'components/Breadcrumb' -import Button from 'components/base/Button' -import Modal, { ModalContent, ModalTitle, ModalFooter, ModalBody } from 'components/base/Modal' -import StepConnectDevice from 'components/modals/StepConnectDevice' -import { getBridgeForCurrency } from 'bridge' - -import StepCurrency from './01-step-currency' -import StepImport from './03-step-import' - -const GET_STEPS = t => [ - { label: t('addAccount:steps.currency.title'), Comp: StepCurrency }, - { label: t('addAccount:steps.connectDevice.title'), Comp: StepConnectDevice }, - { label: t('addAccount:steps.importProgress.title'), Comp: StepImport }, - { label: t('addAccount:steps.importAccounts.title'), Comp: StepImport }, -] - -const mapStateToProps = createStructuredSelector({ - existingAccounts: getAccounts, - visibleAccounts: getVisibleAccounts, - archivedAccounts: getArchivedAccounts, - canCreateAccount, -}) - -const mapDispatchToProps = { - addAccount, - closeModal, - updateAccount, -} - -type Props = { - existingAccounts: Account[], - addAccount: Function, - visibleAccounts: Account[], - archivedAccounts: Account[], - canCreateAccount: boolean, - closeModal: Function, - t: T, - updateAccount: Function, -} - -type State = { - stepIndex: number, - - currency: ?CryptoCurrency, - deviceSelected: ?Device, - - selectedAccounts: Account[], - scannedAccounts: Account[], - - // TODO: what's that. - fetchingCounterValues: boolean, - appStatus: ?string, -} - -const INITIAL_STATE = { - stepIndex: 0, - currency: null, - deviceSelected: null, - - selectedAccounts: [], - scannedAccounts: [], - - fetchingCounterValues: false, - appStatus: null, -} - -class AddAccountModal extends PureComponent { - state = INITIAL_STATE - - componentWillUnmount() { - this.handleReset() - } - - scanSubscription: * - - startScanAccountsDevice() { - const { visibleAccounts } = this.props - const { deviceSelected, currency } = this.state - - if (!deviceSelected || !currency) { - return - } - const bridge = getBridgeForCurrency(currency) - this.scanSubscription = bridge.scanAccountsOnDevice(currency, deviceSelected.path, { - next: account => { - if (!visibleAccounts.some(a => a.id === account.id)) { - this.setState(state => ({ - scannedAccounts: [...state.scannedAccounts, account], - })) - } - }, - complete: () => { - // we should be able to interrupt the scan too if you want to select early etc.. - // like imagine there are way too more accounts to scan, so you are not stuck here. - this.setState({ stepIndex: 3 }) - }, - error: error => { - // TODO what to do ? - console.error(error) - }, - }) - } - - canNext = () => { - const { stepIndex } = this.state - - if (stepIndex === 0) { - const { currency } = this.state - return currency !== null - } - - if (stepIndex === 1) { - const { deviceSelected, appStatus } = this.state - return deviceSelected !== null && appStatus === 'success' - } - - if (stepIndex === 3) { - const { selectedAccounts } = this.state - return selectedAccounts.length > 0 - } - - return false - } - - _steps = GET_STEPS(this.props.t) - - handleChangeDevice = d => this.setState({ deviceSelected: d }) - - handleToggleAccount = account => { - const { selectedAccounts } = this.state - const isSelected = selectedAccounts.find(a => a === account) - this.setState({ - selectedAccounts: isSelected - ? selectedAccounts.filter(a => a !== account) - : [...selectedAccounts, account], - }) - } - - handleChangeCurrency = (currency: CryptoCurrency) => this.setState({ currency }) - - handleChangeStatus = (deviceStatus, appStatus) => this.setState({ appStatus }) - - handleImportAccount = () => { - const { addAccount } = this.props - const { selectedAccounts } = this.state - selectedAccounts.forEach(a => addAccount({ ...a, archived: false })) - this.setState({ selectedAccounts: [] }) - closeModal(MODAL_ADD_ACCOUNT) - } - - handleNextStep = () => { - const { stepIndex } = this.state - if (stepIndex >= this._steps.length - 1) { - return - } - this.setState({ stepIndex: stepIndex + 1 }) - } - - handleReset = () => { - this.setState(INITIAL_STATE) - if (this.scanSubscription) this.scanSubscription.unsubscribe() - } - - renderStep() { - const { t, existingAccounts } = this.props - const { stepIndex, scannedAccounts, currency, deviceSelected, selectedAccounts } = this.state - const step = this._steps[stepIndex] - if (!step) { - return null - } - const { Comp } = step - - const props = (predicate, props) => (predicate ? props : {}) - - const stepProps = { - t, - currency, - // STEP CURRENCY - ...props(stepIndex === 0, { - onChangeCurrency: this.handleChangeCurrency, - }), - // STEP CONNECT DEVICE - ...props(stepIndex === 1, { - deviceSelected, - onStatusChange: this.handleChangeStatus, - onChangeDevice: this.handleChangeDevice, - }), - // STEP ACCOUNT IMPORT PROGRESS - ...props(stepIndex === 2, { - selectedAccounts, - scannedAccounts, - existingAccounts, - }), - // STEP FINISH AND SELECT ACCOUNTS - ...props(stepIndex === 3, { - onToggleAccount: this.handleToggleAccount, - selectedAccounts, - scannedAccounts, - existingAccounts, - }), - } - - return - } - - renderButton() { - const { t } = this.props - const { fetchingCounterValues, stepIndex, selectedAccounts } = this.state - - let onClick - - switch (stepIndex) { - case 1: - onClick = () => { - this.handleNextStep() - this.startScanAccountsDevice() - } - break - - case 3: - onClick = this.handleImportAccount - break - - default: - onClick = this.handleNextStep - } - - const props = { - primary: true, - disabled: fetchingCounterValues || !this.canNext(), - onClick, - children: fetchingCounterValues - ? 'Fetching counterValues...' - : stepIndex === 3 - ? t('addAccount:steps.importAccounts.cta', { - count: selectedAccounts.length, - }) - : t('common:next'), - } - - return