Browse Source

Merge pull request #514 from meriadec/pixelpush/import-accounts

Pixelpush/import accounts
master
Gaëtan Renaudeau 7 years ago
committed by GitHub
parent
commit
223621a15b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      src/components/MainSideBar.js
  2. 4
      src/components/modals/ImportAccounts/AccountRow.js
  3. 12
      src/components/modals/ImportAccounts/steps/01-step-choose-currency.js
  4. 209
      src/components/modals/ImportAccounts/steps/03-step-import.js
  5. 1
      src/renderer/i18n/instanciate.js
  6. 12
      static/i18n/en/importAccounts.yml

19
src/components/MainSideBar.js

@ -17,10 +17,13 @@ import type { UpdateStatus } from 'reducers/update'
import { MODAL_RECEIVE, MODAL_SEND } from 'config/constants'
import { rgba } from 'styles/helpers'
import { accountsSelector } from 'reducers/accounts'
import { openModal } from 'reducers/modals'
import { getUpdateStatus } from 'reducers/update'
import Tooltip from 'components/base/Tooltip'
import { SideBarList } from 'components/base/SideBar'
import Box, { Tabbable } from 'components/base/Box'
import Space from 'components/base/Space'
@ -140,9 +143,11 @@ class MainSideBar extends PureComponent<Props> {
scroll
title={t('sidebar:accounts')}
titleRight={
<PlusWrapper onClick={() => openModal('importAccounts')}>
<IconCirclePlus size={16} />
</PlusWrapper>
<Tooltip render={() => t('importAccounts:title')}>
<PlusWrapper onClick={() => openModal('importAccounts')}>
<IconCirclePlus size={16} />
</PlusWrapper>
</Tooltip>
}
items={accountsItems}
emptyText={t('emptyState:sidebar.text')}
@ -157,15 +162,15 @@ const PlusWrapper = styled(Tabbable).attrs({
cursor: 'pointer',
borderRadius: 1,
})`
opacity: 0.4;
color: ${p => p.theme.colors.smoke};
&:hover {
opacity: 1;
color: ${p => p.theme.colors.dark};
}
border: 1px dashed rgba(0, 0, 0, 0);
border: 1px solid transparent;
&:focus {
border: 1px dashed rgba(0, 0, 0, 0.2);
outline: none;
border-color: ${p => rgba(p.theme.colors.wallet, 0.3)};
}
`

4
src/components/modals/ImportAccounts/AccountRow.js

@ -17,7 +17,7 @@ import IconCheck from 'icons/Check'
type Props = {
account: Account,
isChecked: boolean,
isDisabled: boolean,
isDisabled?: boolean,
onClick: Account => void,
onAccountUpdate: Account => void,
}
@ -110,7 +110,7 @@ export default class AccountRow extends PureComponent<Props, State> {
fontSize={4}
color="grey"
/>
<Radio isChecked={isChecked || isDisabled} />
<Radio isChecked={isChecked || !!isDisabled} />
</AccountRowContainer>
)
}

12
src/components/modals/ImportAccounts/steps/01-step-choose-currency.js

@ -1,6 +1,7 @@
// @flow
import React, { Fragment } from 'react'
import isArray from 'lodash/isArray'
import SelectCurrency from 'components/SelectCurrency'
import Button from 'components/base/Button'
@ -9,7 +10,16 @@ import CurrencyBadge from 'components/base/CurrencyBadge'
import type { StepProps } from '../index'
function StepChooseCurrency({ currency, setState }: StepProps) {
return <SelectCurrency onChange={currency => setState({ currency })} value={currency} />
return (
<SelectCurrency
onChange={currency => {
setState({
currency: isArray(currency) && currency.length === 0 ? null : currency,
})
}}
value={currency}
/>
)
}
export function StepChooseCurrencyFooter({ transitionTo, currency, t }: StepProps) {

209
src/components/modals/ImportAccounts/steps/03-step-import.js

@ -1,7 +1,9 @@
// @flow
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import type { Account } from '@ledgerhq/live-common/lib/types'
import uniq from 'lodash/uniq'
import { getBridgeForCurrency } from 'bridge'
@ -48,10 +50,18 @@ class StepImport extends PureComponent<StepProps> {
this.scanSubscription = bridge.scanAccountsOnDevice(currency, devicePath, {
next: account => {
const { scannedAccounts } = this.props
const { scannedAccounts, checkedAccountsIds, existingAccounts } = this.props
const hasAlreadyBeenScanned = !!scannedAccounts.find(a => account.id === a.id)
const hasAlreadyBeenImported = !!existingAccounts.find(a => account.id === a.id)
const isNewAccount = account.operations.length === 0
if (!hasAlreadyBeenScanned) {
setState({ scannedAccounts: [...scannedAccounts, account] })
setState({
scannedAccounts: [...scannedAccounts, account],
checkedAccountsIds:
!hasAlreadyBeenImported && !isNewAccount
? uniq([...checkedAccountsIds, account.id])
: checkedAccountsIds,
})
}
},
complete: () => setState({ scanStatus: 'finished' }),
@ -103,63 +113,125 @@ class StepImport extends PureComponent<StepProps> {
})
}
handleToggleSelectAll = () => {
handleSelectAll = () => {
const { scannedAccounts, setState } = this.props
setState({ checkedAccountsIds: scannedAccounts.map(a => a.id) })
setState({
checkedAccountsIds: scannedAccounts.filter(a => a.operations.length > 0).map(a => a.id),
})
}
handleUnselectAll = () => this.props.setState({ checkedAccountsIds: [] })
render() {
const { scanStatus, err, scannedAccounts, checkedAccountsIds, existingAccounts } = this.props
const { scanStatus, err, scannedAccounts, checkedAccountsIds, existingAccounts, t } = this.props
const importableAccounts = scannedAccounts.filter(acc => {
if (acc.operations.length <= 0) {
return false
}
return existingAccounts.find(a => a.id === acc.id) === undefined
})
const creatableAccounts = scannedAccounts.filter(acc => {
if (acc.operations.length > 0) {
return false
}
return existingAccounts.find(a => a.id === acc.id) === undefined
})
const isAllSelected = scannedAccounts.filter(acc => acc.operations.length > 0).every(acc => {
const isChecked = !!checkedAccountsIds.find(id => acc.id === id)
const isImported = !!existingAccounts.find(a => acc.id === a.id)
return isChecked || isImported
})
return (
<Box>
{err && <Box shrink>{err.message}</Box>}
{!!scannedAccounts.length && (
<Box horizontal justify="flex-end" mb={2}>
<FakeLink onClick={this.handleToggleSelectAll} fontSize={3}>
{'Select all'}
</FakeLink>
</Box>
)}
<Box flow={2}>
{scannedAccounts.map(account => {
const isChecked = checkedAccountsIds.find(id => id === account.id) !== undefined
const existingAccount = existingAccounts.find(a => a.id === account.id)
const isDisabled = existingAccount !== undefined
return (
<Box flow={5}>
{(!!importableAccounts.length || scanStatus === 'scanning') && (
<Box>
{!!importableAccounts.length && (
<Box horizontal mb={3} align="center">
<Box
ff="Open Sans|Bold"
color="dark"
fontSize={2}
style={{ textTransform: 'uppercase' }}
>
{t('importAccounts:accountToImportSubtitle', {
count: importableAccounts.length,
})}
</Box>
<FakeLink
ml="auto"
onClick={isAllSelected ? this.handleUnselectAll : this.handleSelectAll}
fontSize={3}
>
{isAllSelected
? t('importAccounts:unselectAll')
: t('importAccounts:selectAll')}
</FakeLink>
</Box>
)}
<Box flow={2}>
{importableAccounts.map(account => {
const isChecked = checkedAccountsIds.find(id => id === account.id) !== undefined
const existingAccount = existingAccounts.find(a => a.id === account.id)
const isDisabled = existingAccount !== undefined
return (
<AccountRow
key={account.id}
account={existingAccount || account}
isChecked={isChecked}
isDisabled={isDisabled}
onClick={this.handleToggleAccount}
onAccountUpdate={this.handleAccountUpdate}
/>
)
})}
{scanStatus === 'scanning' && (
<LoadingRow>
<Spinner color="grey" size={16} />
</LoadingRow>
)}
</Box>
</Box>
)}
{creatableAccounts.length > 0 && (
<Box>
<Box horizontal mb={3} align="center">
<Box
ff="Open Sans|Bold"
color="dark"
fontSize={2}
style={{ textTransform: 'uppercase' }}
>
{t('importAccounts:createNewAccount')}
</Box>
</Box>
<AccountRow
key={account.id}
account={existingAccount || account}
isChecked={isChecked}
isDisabled={isDisabled}
account={creatableAccounts[0]}
isChecked={
checkedAccountsIds.find(id => id === creatableAccounts[0].id) !== undefined
}
onClick={this.handleToggleAccount}
onAccountUpdate={this.handleAccountUpdate}
/>
)
})}
{scanStatus === 'scanning' && (
<Box
horizontal
bg="lightGrey"
borderRadius={3}
px={3}
align="center"
justify="center"
style={{ height: 48 }}
>
<Spinner color="grey" size={24} />
</Box>
)}
</Box>
<Box horizontal mt={2}>
{['error', 'finished'].includes(scanStatus) && (
{['error'].includes(scanStatus) && (
<Button small outline onClick={this.handleRetry}>
<Box horizontal flow={2} align="center">
<IconExchange size={13} />
<span>{'retry sync'}</span>
<span>{t('importAccounts:retrySync')}</span>
</Box>
</Button>
)}
@ -171,12 +243,55 @@ class StepImport extends PureComponent<StepProps> {
export default StepImport
export const StepImportFooter = ({ scanStatus, onClickImport, checkedAccountsIds }: StepProps) => (
<Button
primary
disabled={scanStatus !== 'finished' || checkedAccountsIds.length === 0}
onClick={() => onClickImport()}
>
{'Import accounts'}
</Button>
)
export const LoadingRow = styled(Box).attrs({
horizontal: true,
borderRadius: 1,
px: 3,
align: 'center',
justify: 'center',
})`
height: 48px;
border: 1px dashed ${p => p.theme.colors.fog};
`
export const StepImportFooter = ({
scanStatus,
onClickImport,
checkedAccountsIds,
scannedAccounts,
t,
}: StepProps) => {
const willCreateAccount = checkedAccountsIds.some(id => {
const account = scannedAccounts.find(a => a.id === id)
return account && account.operations.length === 0
})
const willImportAccounts = checkedAccountsIds.some(id => {
const account = scannedAccounts.find(a => a.id === id)
return account && account.operations.length > 0
})
const importedAccountsCount = checkedAccountsIds.filter(id => {
const account = scannedAccounts.find(acc => acc.id === id)
return account && account.operations.length > 0
}).length
const ctaWording =
willCreateAccount && willImportAccounts
? `${t('importAccounts:cta.create')} / ${t('importAccounts:cta.import', {
count: importedAccountsCount,
})}`
: willCreateAccount
? t('importAccounts:cta.create')
: t('importAccounts:cta.import', { count: importedAccountsCount })
return (
<Button
primary
disabled={scanStatus !== 'finished' || checkedAccountsIds.length === 0}
onClick={() => onClickImport()}
>
{ctaWording}
</Button>
)
}

1
src/renderer/i18n/instanciate.js

@ -3,6 +3,7 @@ import i18n from 'i18next'
const commonConfig = {
fallbackLng: 'en',
debug: false,
compatibilityJSON: 'v2',
react: {
wait: process.env.NODE_ENV !== 'test',
},

12
static/i18n/en/importAccounts.yml

@ -1,6 +1,16 @@
title: Import accounts
title: Add accounts
breadcrumb:
informations: Informations
connectDevice: Connect device
import: Import
finish: End
accountToImportSubtitle: Account to import
accountToImportSubtitle_plural: 'Accounts to import ({{count}})'
selectAll: Select all
unselectAll: Unselect all
createNewAccount: Create new account
retrySync: Retry sync
cta:
create: 'Create account'
import: 'Import account'
import_plural: 'Import accounts'

Loading…
Cancel
Save