diff --git a/src/actions/accounts.js b/src/actions/accounts.js index 061ca480..feb6bf0c 100644 --- a/src/actions/accounts.js +++ b/src/actions/accounts.js @@ -43,16 +43,9 @@ export type AddAccount = Account => (Function, Function) => void export const addAccount: AddAccount = payload => (dispatch, getState) => { const { settings: { orderAccounts }, - accounts, } = getState() dispatch({ type: 'ADD_ACCOUNT', payload }) dispatch(updateOrderAccounts(orderAccounts)) - - // Start sync accounts the first time you add an account - if (accounts.length === 0) { - const accounts = [payload] - startSyncAccounts(accounts) - } } export type RemoveAccount = Account => { type: string, payload: Account } diff --git a/src/components/modals/AddAccount/03-step-import.js b/src/components/modals/AddAccount/03-step-import.js index 36d6a492..c42428a4 100644 --- a/src/components/modals/AddAccount/03-step-import.js +++ b/src/components/modals/AddAccount/03-step-import.js @@ -25,74 +25,31 @@ const AccountItem = styled(AccountCard)` ` type Props = { - accountsImport: Object, - archivedAccounts: Account[], - currency?: ?CryptoCurrency, - importProgress: boolean, - onSelectAccount?: Function, - selectedAccounts?: Array, + scannedAccounts: Account[], + selectedAccounts: Account[], + existingAccounts: Account[], + onToggleAccount: Function, } function StepImport(props: Props) { - const hasAccountsImports = Object.keys(props.accountsImport).length > 0 - const unit = props.currency && props.currency.units[0] + const { scannedAccounts, selectedAccounts, existingAccounts, onToggleAccount } = props return ( - - {props.importProgress ? ( - In progress... - ) : ( - hasAccountsImports && Accounts - )} - {hasAccountsImports && ( - - {Object.keys(props.accountsImport).map(k => { - const a = props.accountsImport[k] - return ( - - - - ) - })} - - )} - {!props.importProgress && - props.archivedAccounts.length > 0 && ( - - Archived accounts - - {props.archivedAccounts.map(a => ( - - - - ))} - - - )} + + {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} + > + {isSelected && `[SELECTED]`} {isExisting && `[ALREADY IMPORTED]`} {account.name} + + ) + })} ) } -StepImport.defaultProps = { - onSelectAccount: undefined, - selectedAccounts: [], -} - export default StepImport diff --git a/src/components/modals/AddAccount/index.js b/src/components/modals/AddAccount/index.js index 3d28c463..c612d2f1 100644 --- a/src/components/modals/AddAccount/index.js +++ b/src/components/modals/AddAccount/index.js @@ -13,8 +13,14 @@ import type { Device, T } from 'types/common' import { MODAL_ADD_ACCOUNT } from 'config/constants' import { closeModal } from 'reducers/modals' -import { canCreateAccount, getAccounts, getArchivedAccounts } from 'reducers/accounts' -import { sendEvent } from 'renderer/events' +import { + canCreateAccount, + getAccounts, + getArchivedAccounts, + decodeAccount, +} from 'reducers/accounts' + +import { runJob } from 'renderer/events' import { addAccount, updateAccount } from 'actions/accounts' @@ -35,7 +41,7 @@ const GET_STEPS = t => [ ] const mapStateToProps = state => ({ - accounts: getAccounts(state), + existingAccounts: getAccounts(state), archivedAccounts: getArchivedAccounts(state), canCreateAccount: canCreateAccount(state), }) @@ -47,7 +53,7 @@ const mapDispatchToProps = { } type Props = { - accounts: Account[], + existingAccounts: Account[], addAccount: Function, archivedAccounts: Account[], canCreateAccount: boolean, @@ -57,23 +63,29 @@ type Props = { } type State = { - accountsImport: Object, + stepIndex: number, + currency: ?CryptoCurrency, deviceSelected: ?Device, + + selectedAccounts: Account[], + scannedAccounts: Account[], + + // TODO: what's that. fetchingCounterValues: boolean, - selectedAccounts: Array, appStatus: ?string, - stepIndex: number, } const INITIAL_STATE = { - accountsImport: {}, + stepIndex: 0, currency: null, deviceSelected: null, - fetchingCounterValues: false, + selectedAccounts: [], + scannedAccounts: [], + + fetchingCounterValues: false, appStatus: null, - stepIndex: 0, } class AddAccountModal extends PureComponent { @@ -88,29 +100,41 @@ class AddAccountModal extends PureComponent { const { stepIndex: nextStepIndex } = nextState if (!fetchingCounterValues && stepIndex === 0 && nextStepIndex === 1) { + // TODO: seems shady to do this here.............. await this.fetchCounterValues() } } componentWillUnmount() { - this.killProcess() + this.handleReset() ipcRenderer.removeListener('msg', this.handleMsgEvent) - clearTimeout(this._timeout) } - importsAccounts() { - const { accounts } = this.props + async startScanAccountsDevice() { const { deviceSelected, currency } = this.state if (!deviceSelected || !currency) { return } - sendEvent('usb', 'wallet.getAccounts', { - pathDevice: deviceSelected.path, - currencyId: currency.id, - currentAccounts: accounts.map(acc => acc.id), - }) + try { + // scan every account for given currency and device + await runJob({ + channel: 'usb', + job: 'wallet.scanAccountsOnDevice', + successResponse: 'wallet.scanAccountsOnDevice.success', + errorResponse: 'wallet.scanAccountsOnDevice.fail', + data: { + devicePath: deviceSelected.path, + currencyId: currency.id, + }, + }) + + // go to final step + this.setState({ stepIndex: 3 }) + } catch (err) { + console.log(err) + } } async fetchCounterValues() { @@ -160,75 +184,46 @@ class AddAccountModal extends PureComponent { _steps = GET_STEPS(this.props.t) handleMsgEvent = (e, { data, type }) => { - const { accountsImport, currency } = this.state - const { addAccount } = this.props + const { addAccount, existingAccounts } = this.props - if (type === 'wallet.getAccounts.start') { - this._pid = data.pid - } - - if (type === 'wallet.getAccounts.progress') { - this.setState(prev => ({ - stepIndex: 2, - accountsImport: { - ...(data !== null - ? { - [data.id]: { - ...data, - name: `Account ${data.accountIndex + 1}`, - }, - } - : {}), - ...prev.accountsImport, - }, - })) + if (type === 'wallet.scanAccountsOnDevice.accountScanned') { + // create Account from AccountRaw account scanned on device + const account = { + ...decodeAccount(data), + archived: true, + } - if (currency && data && data.finish) { - const { accountIndex, finish, ...account } = data - addAccount({ - ...account, - // As data is passed inside electron event system, - // dates are converted to their string equivalent - // - // so, quick & dirty way to put back Date objects - operations: account.operations.map(op => ({ - ...op, - date: new Date(op.date), - })), - name: `Account ${accountIndex + 1}`, - archived: true, - currency, - unit: currency.units[0], - }) + // add it to the reducer if needed, archived + if (!existingAccounts.find(a => a.id === account.id)) { + addAccount(account) } - } - if (type === 'wallet.getAccounts.success') { - this.setState({ - selectedAccounts: Object.keys(accountsImport).map(k => accountsImport[k].id), - stepIndex: 3, - }) + this.setState(state => ({ + scannedAccounts: [...state.scannedAccounts, account], + })) } } handleChangeDevice = d => this.setState({ deviceSelected: d }) - handleSelectAccount = a => () => - this.setState(prev => ({ - selectedAccounts: prev.selectedAccounts.includes(a) - ? prev.selectedAccounts.filter(x => x !== a) - : [a, ...prev.selectedAccounts], - })) + 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 { archivedAccounts, updateAccount, closeModal } = this.props + const { updateAccount } = this.props const { selectedAccounts } = this.state - const accounts = archivedAccounts.filter(a => selectedAccounts.includes(a.id)) - accounts.forEach(a => updateAccount({ ...a, archived: false })) + selectedAccounts.forEach(a => updateAccount({ ...a, archived: false })) this.setState({ selectedAccounts: [] }) closeModal(MODAL_ADD_ACCOUNT) } @@ -242,22 +237,12 @@ class AddAccountModal extends PureComponent { } handleReset = () => { - this.killProcess() - clearTimeout(this._timeout) this.setState(INITIAL_STATE) } - killProcess = () => - sendEvent('msg', 'kill.process', { - pid: this._pid, - }) - - _timeout = undefined - _pid = null - renderStep() { - const { accounts, archivedAccounts, t } = this.props - const { stepIndex, currency, accountsImport, deviceSelected, selectedAccounts } = this.state + const { t, existingAccounts } = this.props + const { stepIndex, scannedAccounts, currency, deviceSelected, selectedAccounts } = this.state const step = this._steps[stepIndex] if (!step) { return null @@ -269,31 +254,28 @@ class AddAccountModal extends PureComponent { 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, { - accountsImport, - importProgress: true, + selectedAccounts, + scannedAccounts, + existingAccounts, }), + // STEP FINISH AND SELECT ACCOUNTS ...props(stepIndex === 3, { - accountsImport: Object.keys(accountsImport).reduce((result, k) => { - const account = accountsImport[k] - const existingAccount = accounts.find(a => a.id === account.id) - if (!existingAccount || (existingAccount && existingAccount.archived)) { - result[account.id] = account - } - return result - }, {}), - archivedAccounts: archivedAccounts.filter(a => !accountsImport[a.id]), - importProgress: false, - onSelectAccount: this.handleSelectAccount, + onToggleAccount: this.handleToggleAccount, selectedAccounts, + scannedAccounts, + existingAccounts, }), } @@ -310,7 +292,7 @@ class AddAccountModal extends PureComponent { case 1: onClick = () => { this.handleNextStep() - this.importsAccounts() + this.startScanAccountsDevice() } break diff --git a/src/internals/usb/wallet/index.js b/src/internals/usb/wallet/index.js index ed332c46..e288b164 100644 --- a/src/internals/usb/wallet/index.js +++ b/src/internals/usb/wallet/index.js @@ -18,11 +18,13 @@ export default (sendEvent: Function) => ({ currencyId: string, }) => { try { + sendEvent('wallet.scanAccountsOnDevice.start', { pid: process.pid }, { kill: false }) const accounts = await scanAccountsOnDevice({ devicePath, currencyId, - onAccountScanned: account => - sendEvent('wallet.scanAccountsOnDevice.accountScanned', account), + onAccountScanned: account => { + sendEvent('wallet.scanAccountsOnDevice.accountScanned', account, { kill: false }) + }, }) sendEvent('wallet.scanAccountsOnDevice.success', accounts) } catch (err) { diff --git a/src/internals/usb/wallet/scanAccountsOnDevice.js b/src/internals/usb/wallet/scanAccountsOnDevice.js index 1640e2f5..e593fafa 100644 --- a/src/internals/usb/wallet/scanAccountsOnDevice.js +++ b/src/internals/usb/wallet/scanAccountsOnDevice.js @@ -173,7 +173,7 @@ async function buildRawAccount({ xpub, path: accountPath, // TODO: this should be called `accountPath` in Account/AccountRaw types rootPath: walletPath, // TODO: this should be `walletPath` in Account/AccountRaw types - name: '', // TODO: placeholder name? + name: `Account ${accountIndex}`, // TODO: placeholder name? address: bitcoinAddress, // TODO: discuss about the utility of storing it here addresses, balance: 0, diff --git a/src/reducers/accounts.js b/src/reducers/accounts.js index a22c8f5d..dfeeeaf6 100644 --- a/src/reducers/accounts.js +++ b/src/reducers/accounts.js @@ -115,6 +115,10 @@ export function canCreateAccount(state: State): boolean { return every(getAccounts(state), a => get(a, 'operations.length', 0) > 0) } +export function decodeAccount(account: AccountRaw): Account { + return accountModel.decode({ data: account }) +} + // Yeah. `any` should be `AccountRaw[]` but it can also be a map // of wrapped accounts. And as flow is apparently incapable of doing // such a simple thing, let's put any, right? I don't care.