Browse Source

Merge pull request #550 from meriadec/add-accounts-feedbacks

Add accounts feedbacks, and also other stuff lol
master
Gaëtan Renaudeau 7 years ago
committed by GitHub
parent
commit
f280414f93
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .eslintrc
  2. 1
      flow-defs/globals.js
  3. 11
      src/components/AccountPage/AccountHeader.js
  4. 40
      src/components/AccountPage/index.js
  5. 7
      src/components/DashboardPage/AccountCard.js
  6. 4
      src/components/DashboardPage/EmptyState.js
  7. 6
      src/components/MainSideBar/index.js
  8. 18
      src/components/SelectExchange.js
  9. 4
      src/components/SettingsPage/sections/Display.js
  10. 4
      src/components/TopBar/ActivityIndicator.js
  11. 9
      src/components/TopBar/ItemContainer.js
  12. 9
      src/components/TopBar/index.js
  13. 2
      src/components/base/AccountsList/AccountRow.js
  14. 98
      src/components/base/AccountsList/index.js
  15. 30
      src/components/base/AccountsList/stories.js
  16. 2
      src/components/base/Box/Box.js
  17. 2
      src/components/base/Box/Tabbable.js
  18. 16
      src/components/base/Ellipsis.js
  19. 36
      src/components/modals/AddAccounts/index.js
  20. 0
      src/components/modals/AddAccounts/steps/01-step-choose-currency.js
  21. 2
      src/components/modals/AddAccounts/steps/02-step-connect-device.js
  22. 197
      src/components/modals/AddAccounts/steps/03-step-import.js
  23. 0
      src/components/modals/AddAccounts/steps/04-step-finish.js
  24. 2
      src/components/modals/index.js
  25. 2
      src/config/constants.js
  26. 7
      src/config/global-tab.js
  27. 7
      src/globals.js
  28. 6
      src/renderer/createStore.js
  29. 9
      src/renderer/init.js
  30. 3
      src/styles/theme.js
  31. 2
      static/i18n/en/app.yml
  32. 2
      static/i18n/fr/app.yml
  33. 3
      webpack/plugins.js

1
.eslintrc

@ -11,6 +11,7 @@
"__PRINT_MODE__": false,
"__GLOBAL_STYLES__": false,
"__APP_VERSION__": false,
"__STORYBOOK_ENV__": false,
"__static": false,
"window": false,
"document": false,

1
flow-defs/globals.js

@ -8,6 +8,7 @@ declare var __PRINT_MODE__: string
declare var __SENTRY_URL__: string
declare var __GLOBAL_STYLES__: string
declare var __APP_VERSION__: string
declare var __STORYBOOK_ENV__: string
declare var __static: string
declare var describe: Function
declare var test: Function

11
src/components/AccountPage/AccountHeader.js

@ -6,6 +6,7 @@ import styled from 'styled-components'
import type { Account } from '@ledgerhq/live-common/lib/types'
import Box from 'components/base/Box'
import Ellipsis from 'components/base/Ellipsis'
import Text from 'components/base/Text'
import CryptoCurrencyIcon from '../CryptoCurrencyIcon'
@ -22,7 +23,7 @@ const AccountName = styled(Text).attrs({
ff: 'Museo Sans',
fontSize: 7,
})`
line-height: 1;
line-height: 1.1;
`
type Props = {
@ -33,13 +34,15 @@ class AccountHeader extends PureComponent<Props> {
render() {
const { account } = this.props
return (
<Box horizontal align="center" flow={2}>
<Box horizontal align="center" flow={2} grow>
<Box color={account.currency.color}>
<CryptoCurrencyIcon currency={account.currency} size={24} />
</Box>
<Box>
<Box grow>
<CurName>{account.currency.name}</CurName>
<AccountName>{account.name}</AccountName>
<AccountName>
<Ellipsis>{account.name}</Ellipsis>
</AccountName>
</Box>
</Box>
)

40
src/components/AccountPage/index.js

@ -8,12 +8,13 @@ import { Redirect } from 'react-router'
import styled from 'styled-components'
import type { Currency, Account } from '@ledgerhq/live-common/lib/types'
import SyncOneAccountOnMount from 'components/SyncOneAccountOnMount'
import Tooltip from 'components/base/Tooltip'
import { MODAL_SEND, MODAL_RECEIVE, MODAL_SETTINGS_ACCOUNT } from 'config/constants'
import type { T } from 'types/common'
import { darken } from 'styles/helpers'
import { rgba } from 'styles/helpers'
import { accountSelector } from 'reducers/accounts'
import { counterValueCurrencySelector, localeSelector } from 'reducers/settings'
@ -29,7 +30,7 @@ import {
BalanceSinceDiff,
BalanceSincePercent,
} from 'components/BalanceSummary/BalanceInfos'
import Box from 'components/base/Box'
import Box, { Tabbable } from 'components/base/Box'
import Button from 'components/base/Button'
import FormattedVal from 'components/base/FormattedVal'
import PillsDaysCount from 'components/PillsDaysCount'
@ -39,15 +40,22 @@ import StickyBackToTop from 'components/StickyBackToTop'
import AccountHeader from './AccountHeader'
import EmptyStateAccount from './EmptyStateAccount'
const ButtonSettings = styled(Button).attrs({
small: true,
const ButtonSettings = styled(Tabbable).attrs({
cursor: 'pointer',
align: 'center',
justify: 'center',
borderRadius: 1,
})`
border: 2px solid ${p => p.theme.colors.grey};
width: 30px;
padding: 0;
width: 40px;
height: 40px;
&:hover {
color: ${p => (p.disabled ? '' : p.theme.colors.dark)};
background: ${p => (p.disabled ? '' : rgba(p.theme.colors.fog, 0.2))};
}
&:active {
border: 2px solid ${p => darken(p.theme.colors.grey, 0.2)};
background: ${p => (p.disabled ? '' : rgba(p.theme.colors.fog, 0.3))};
}
`
@ -100,9 +108,9 @@ class AccountPage extends PureComponent<Props, State> {
// Force re-render account page, for avoid animation
<Box key={account.id}>
<SyncOneAccountOnMount priority={10} accountId={account.id} />
<Box horizontal mb={5}>
<Box horizontal mb={5} flow={4}>
<AccountHeader account={account} />
<Box horizontal alignItems="center" justifyContent="flex-end" grow flow={2}>
<Box horizontal alignItems="center" justifyContent="flex-end" flow={2}>
{account.operations.length > 0 && (
<Fragment>
<Button small primary onClick={() => openModal(MODAL_SEND, { account })}>
@ -120,11 +128,13 @@ class AccountPage extends PureComponent<Props, State> {
</Button>
</Fragment>
)}
<ButtonSettings onClick={() => openModal(MODAL_SETTINGS_ACCOUNT, { account })}>
<Box align="center">
<IconAccountSettings size={16} />
</Box>
</ButtonSettings>
<Tooltip render={() => t('app:account.settings.title')}>
<ButtonSettings onClick={() => openModal(MODAL_SETTINGS_ACCOUNT, { account })}>
<Box align="center">
<IconAccountSettings size={16} />
</Box>
</ButtonSettings>
</Tooltip>
</Box>
</Box>
{account.operations.length > 0 ? (

7
src/components/DashboardPage/AccountCard.js

@ -10,6 +10,7 @@ import Bar from 'components/base/Bar'
import Box, { Card } from 'components/base/Box'
import CalculateBalance from 'components/CalculateBalance'
import FormattedVal from 'components/base/FormattedVal'
import Ellipsis from 'components/base/Ellipsis'
import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon'
import DeltaChange from '../DeltaChange'
@ -39,13 +40,13 @@ class AccountCard extends PureComponent<{
>
<CryptoCurrencyIcon currency={account.currency} size={20} />
</Box>
<Box>
<Box grow>
<Box style={{ textTransform: 'uppercase' }} fontSize={0} color="graphite">
{account.currency.name}
</Box>
<Box fontSize={4} color="dark">
<Ellipsis fontSize={4} color="dark">
{account.name}
</Box>
</Ellipsis>
</Box>
</Box>
<Bar size={1} color="fog" />

4
src/components/DashboardPage/EmptyState.js

@ -8,6 +8,8 @@ import { compose } from 'redux'
import { translate } from 'react-i18next'
import { push } from 'react-router-redux'
import { MODAL_ADD_ACCOUNTS } from 'config/constants'
import { openModal } from 'reducers/modals'
import type { T } from 'types/common'
@ -50,7 +52,7 @@ class EmptyState extends PureComponent<Props, *> {
padded
primary
style={{ width: 120 }}
onClick={() => openModal('importAccounts')}
onClick={() => openModal(MODAL_ADD_ACCOUNTS)}
>
{t('app:emptyState.dashboard.buttons.addAccount')}
</Button>

6
src/components/MainSideBar/index.js

@ -13,7 +13,7 @@ import type { Account } from '@ledgerhq/live-common/lib/types'
import type { T } from 'types/common'
import type { UpdateStatus } from 'reducers/update'
import { MODAL_RECEIVE, MODAL_SEND } from 'config/constants'
import { MODAL_RECEIVE, MODAL_SEND, MODAL_ADD_ACCOUNTS } from 'config/constants'
import { accountsSelector } from 'reducers/accounts'
import { openModal } from 'reducers/modals'
@ -70,7 +70,7 @@ class MainSideBar extends PureComponent<Props> {
handleOpenReceiveModal = () => this.props.openModal(MODAL_RECEIVE)
handleClickManager = () => this.push('/manager')
handleClickExchange = () => this.push('/exchange')
handleOpenImportModal = () => this.props.openModal('importAccounts')
handleOpenImportModal = () => this.props.openModal(MODAL_ADD_ACCOUNTS)
render() {
const { t, accounts, location, updateStatus } = this.props
@ -78,7 +78,7 @@ class MainSideBar extends PureComponent<Props> {
const addAccountButton = (
<AddAccountButton
tooltipText={t('app:importAccounts.title')}
tooltipText={t('app:addAccounts.title')}
onClick={this.handleOpenImportModal}
/>
)

18
src/components/SelectExchange.js

@ -6,12 +6,11 @@ import type { Exchange } from '@ledgerhq/live-common/lib/countervalues/types'
import logger from 'logger'
import Select from 'components/base/Select'
import Spinner from 'components/base/Spinner'
import Text from 'components/base/Text'
import CounterValues from 'helpers/countervalues'
import type { T } from 'types/common'
class ExchangeSelect extends Component<
class SelectExchange extends Component<
{
from: Currency,
to: Currency,
@ -84,21 +83,20 @@ class ExchangeSelect extends Component<
const options = exchanges ? exchanges.map(e => ({ value: e.id, label: e.name, ...e })) : []
return exchanges && exchanges.length > 0 ? (
return error ? (
<Text ff="Open Sans|SemiBold" color="dark" fontSize={4}>
{t('app:common.error.load')}
</Text>
) : (
<Select
value={options.find(e => e.id === exchangeId)}
options={options}
onChange={onChange}
isLoading={options.length === 0}
{...props}
/>
) : error ? (
<Text ff="Open Sans|SemiBold" color="dark" fontSize={4}>
{t('app:common.error.load')}
</Text>
) : (
<Spinner color="grey" size={24} />
)
}
}
export default translate()(ExchangeSelect)
export default translate()(SelectExchange)

4
src/components/SettingsPage/sections/Display.js

@ -14,7 +14,7 @@ import type { SettingsState as Settings } from 'reducers/settings'
import type { T } from 'types/common'
import Box from 'components/base/Box'
import ExchangeSelect from 'components/SelectExchange'
import SelectExchange from 'components/SelectExchange'
import Select from 'components/base/Select'
import RadioGroup from 'components/base/RadioGroup'
import IconDisplay from 'icons/Display'
@ -163,7 +163,7 @@ class TabProfile extends PureComponent<Props, State> {
options={fiats}
value={cvOption}
/>
<ExchangeSelect
<SelectExchange
small
from={intermediaryCurrency}
to={counterValueCurrency}

4
src/components/TopBar/ActivityIndicator.js

@ -70,11 +70,11 @@ class ActivityIndicatorInner extends Component<Props, State> {
render() {
const { isPending, isError, t } = this.props
const { hasClicked, isFirstSync } = this.state
const isDisabled = hasClicked || isError
const isDisabled = isFirstSync || hasClicked || isError
const isRotating = isPending && (hasClicked || isFirstSync)
return (
<ItemContainer isDisabled={isDisabled} onClick={isDisabled ? undefined : this.handleRefresh}>
<ItemContainer disabled={isDisabled} onClick={isDisabled ? undefined : this.handleRefresh}>
<Rotating
size={16}
isRotating={isRotating}

9
src/components/TopBar/ItemContainer.js

@ -10,18 +10,19 @@ export default styled(Tabbable).attrs({
px: 3,
ml: 0,
alignItems: 'center',
cursor: p => (p.isDisabled ? 'default' : 'pointer'),
cursor: p => (p.disabled ? 'default' : 'pointer'),
horizontal: true,
borderRadius: 1,
})`
height: 40px;
pointer-events: ${p => (p.disabled ? 'none' : 'unset')};
&:hover {
color: ${p => (p.isDisabled ? '' : p.theme.colors.dark)};
background: ${p => (p.isDisabled ? '' : rgba(p.theme.colors.fog, 0.2))};
color: ${p => (p.disabled ? '' : p.theme.colors.dark)};
background: ${p => (p.disabled ? '' : rgba(p.theme.colors.fog, 0.2))};
}
&:active {
background: ${p => (p.isDisabled ? '' : rgba(p.theme.colors.fog, 0.3))};
background: ${p => (p.disabled ? '' : rgba(p.theme.colors.fog, 0.3))};
}
`

9
src/components/TopBar/index.js

@ -19,6 +19,7 @@ import IconSettings from 'icons/Settings'
import Box from 'components/base/Box'
import GlobalSearch from 'components/GlobalSearch'
import Tooltip from 'components/base/Tooltip'
import ActivityIndicator from './ActivityIndicator'
import ItemContainer from './ItemContainer'
@ -101,9 +102,11 @@ class TopBar extends PureComponent<Props> {
<Box justifyContent="center">
<Bar />
</Box>
<ItemContainer isInteractive onClick={this.navigateToSettings}>
<IconSettings size={16} />
</ItemContainer>
<Tooltip render={() => t('app:settings.title')}>
<ItemContainer isInteractive onClick={this.navigateToSettings}>
<IconSettings size={16} />
</ItemContainer>
</Tooltip>
{hasPassword && ( // FIXME this should be a dedicated component. therefore this component don't need to connect()
<Fragment>
<Box justifyContent="center">

2
src/components/modals/ImportAccounts/AccountRow.js → src/components/base/AccountsList/AccountRow.js

@ -110,7 +110,7 @@ export default class AccountRow extends PureComponent<Props, State> {
fontSize={4}
color="grey"
/>
<Radio isChecked={isChecked || !!isDisabled} />
<Radio disabled isChecked={isChecked || !!isDisabled} />
</AccountRowContainer>
)
}

98
src/components/base/AccountsList/index.js

@ -0,0 +1,98 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import { translate } from 'react-i18next'
import type { Account } from '@ledgerhq/live-common/lib/types'
import Box from 'components/base/Box'
import FakeLink from 'components/base/FakeLink'
import Spinner from 'components/base/Spinner'
import type { T } from 'types/common'
import AccountRow from './AccountRow'
const AccountsList = ({
accounts,
checkedIds,
onToggleAccount,
onUpdateAccount,
onSelectAll,
onUnselectAll,
isLoading,
title,
emptyText,
t,
}: {
accounts: Account[],
checkedIds: string[],
onToggleAccount: Account => void,
onUpdateAccount: Account => void,
onSelectAll: () => void,
onUnselectAll: () => void,
isLoading?: boolean,
title?: string,
emptyText?: string,
t: T,
}) => {
const withToggleAll = !!onSelectAll && !!onUnselectAll && accounts.length > 1
const isAllSelected = accounts.every(acc => !!checkedIds.find(id => acc.id === id))
return (
<Box flow={3}>
{(title || withToggleAll) && (
<Box horizontal align="center">
{title && (
<Box ff="Open Sans|Bold" color="dark" fontSize={2} textTransform="uppercase">
{title}
</Box>
)}
{withToggleAll && (
<FakeLink
ml="auto"
onClick={isAllSelected ? onUnselectAll : onSelectAll}
fontSize={3}
style={{ lineHeight: '10px' }}
>
{isAllSelected ? t('app:addAccounts.unselectAll') : t('app:addAccounts.selectAll')}
</FakeLink>
)}
</Box>
)}
{accounts.length || isLoading ? (
<Box flow={2}>
{accounts.map(account => (
<AccountRow
key={account.id}
account={account}
isChecked={checkedIds.find(id => id === account.id) !== undefined}
onClick={onToggleAccount}
onAccountUpdate={onUpdateAccount}
/>
))}
{isLoading && (
<LoadingRow>
<Spinner color="grey" size={16} />
</LoadingRow>
)}
</Box>
) : emptyText && !isLoading ? (
<Box ff="Open Sans|Regular" fontSize={3}>
{emptyText}
</Box>
) : null}
</Box>
)
}
const LoadingRow = styled(Box).attrs({
horizontal: true,
borderRadius: 1,
px: 3,
align: 'center',
justify: 'center',
})`
height: 48px;
border: 1px dashed ${p => p.theme.colors.grey};
`
export default translate()(AccountsList)

30
src/components/base/AccountsList/stories.js

@ -0,0 +1,30 @@
// @flow
import React from 'react'
import { genAccount } from '@ledgerhq/live-common/lib/mock/account'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import { boolean, text } from '@storybook/addon-knobs'
import AccountsList from 'components/base/AccountsList'
const stories = storiesOf('Components/base', module)
const ACCOUNTS = [genAccount('a'), genAccount('b'), genAccount('c')]
const CHECKED_IDS = []
stories.add('AccountsList', () => (
<div style={{ maxWidth: 450 }}>
<AccountsList
title={text('title', 'this is an account list')}
accounts={ACCOUNTS}
checkedIds={CHECKED_IDS}
onToggleAccount={action('onToggleAccount')}
onUpdateAccount={action('onUpdateAccount')}
onSelectAll={action('onSelectAll')}
onUnselectAll={action('onUnselectAll')}
isLoading={boolean('isLoading', false)}
/>
</div>
))

2
src/components/base/Box/Box.js

@ -18,6 +18,7 @@ import fontFamily from 'styles/styled/fontFamily'
export const styledTextAlign = style({ prop: 'textAlign', cssProperty: 'textAlign' })
export const styledCursor = style({ prop: 'cursor', cssProperty: 'cursor' })
export const styledTextTransform = style({ prop: 'textTransform', cssProperty: 'textTransform' })
export default styled.div`
${alignItems};
@ -32,6 +33,7 @@ export default styled.div`
${space};
${styledTextAlign};
${styledCursor};
${styledTextTransform};
display: flex;
flex-shrink: ${p => (p.noShrink === true ? '0' : p.shrink === true ? '1' : '')};

2
src/components/base/Box/Tabbable.js

@ -3,7 +3,7 @@
import React, { Component } from 'react'
import styled from 'styled-components'
import { isGlobalTabEnabled } from 'renderer/init'
import { isGlobalTabEnabled } from 'config/global-tab'
import { rgba } from 'styles/helpers'
import Box from './Box'

16
src/components/base/Ellipsis.js

@ -0,0 +1,16 @@
// @flow
import React from 'react'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
const outerStyle = { width: 0 }
const innerStyle = { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }
export default ({ children, ...p }: { children: any }) => (
<Box grow horizontal>
<Box grow {...p} style={outerStyle}>
<Text style={innerStyle}>{children}</Text>
</Box>
</Box>
)

36
src/components/modals/ImportAccounts/index.js → src/components/modals/AddAccounts/index.js

@ -10,6 +10,7 @@ import SyncSkipUnderPriority from 'components/SyncSkipUnderPriority'
import type { Currency, Account } from '@ledgerhq/live-common/lib/types'
import { MODAL_ADD_ACCOUNTS } from 'config/constants'
import type { T, Device } from 'types/common'
import { getCurrentDevice } from 'reducers/devices'
@ -29,7 +30,7 @@ import StepFinish from './steps/04-step-finish'
const createSteps = ({ t }: { t: T }) => [
{
id: 'chooseCurrency',
label: t('app:importAccounts.breadcrumb.informations'),
label: t('app:addAccounts.breadcrumb.informations'),
component: StepChooseCurrency,
footer: StepChooseCurrencyFooter,
onBack: null,
@ -37,7 +38,7 @@ const createSteps = ({ t }: { t: T }) => [
},
{
id: 'connectDevice',
label: t('app:importAccounts.breadcrumb.connectDevice'),
label: t('app:addAccounts.breadcrumb.connectDevice'),
component: StepConnectDevice,
footer: StepConnectDeviceFooter,
onBack: ({ transitionTo }: StepProps) => transitionTo('chooseCurrency'),
@ -45,7 +46,7 @@ const createSteps = ({ t }: { t: T }) => [
},
{
id: 'import',
label: t('app:importAccounts.breadcrumb.import'),
label: t('app:addAccounts.breadcrumb.import'),
component: StepImport,
footer: StepImportFooter,
onBack: ({ transitionTo }: StepProps) => transitionTo('chooseCurrency'),
@ -53,7 +54,7 @@ const createSteps = ({ t }: { t: T }) => [
},
{
id: 'finish',
label: t('app:importAccounts.breadcrumb.finish'),
label: t('app:addAccounts.breadcrumb.finish'),
component: StepFinish,
footer: null,
onBack: null,
@ -91,7 +92,7 @@ export type StepProps = {
isAppOpened: boolean,
transitionTo: StepId => void,
setState: any => void,
onClickImport: void => Promise<void>,
onClickAdd: void => Promise<void>,
onCloseModal: void => void,
// scan process
@ -122,38 +123,39 @@ const INITIAL_STATE = {
scanStatus: 'idle',
}
class ImportAccounts extends PureComponent<Props, State> {
class AddAccounts extends PureComponent<Props, State> {
state = INITIAL_STATE
STEPS = createSteps({
t: this.props.t,
})
transitionTo = stepId => {
const { currency } = this.state
let nextState = { stepId }
if (stepId === 'chooseCurrency') {
nextState = { ...INITIAL_STATE }
nextState = { ...INITIAL_STATE, currency }
}
this.setState(nextState)
}
handleClickImport = async () => {
handleClickAdd = 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++) {
const accountsToAdd = scannedAccounts.filter(account => accountsIdsMap[account.id] === true)
for (let i = 0; i < accountsToAdd.length; i++) {
await idleCallback()
addAccount(accountsToImport[i])
addAccount(accountsToAdd[i])
}
this.transitionTo('finish')
}
handleCloseModal = () => {
const { closeModal } = this.props
closeModal('importAccounts')
closeModal(MODAL_ADD_ACCOUNTS)
}
render() {
@ -172,7 +174,7 @@ class ImportAccounts extends PureComponent<Props, State> {
const step = this.STEPS[stepIndex]
if (!step) {
throw new Error(`ImportAccountsModal: step ${stepId} doesn't exists`)
throw new Error(`AddAccountsModal: step ${stepId} doesn't exists`)
}
const { component: StepComponent, footer: StepFooter, hideFooter, onBack } = step
@ -187,7 +189,7 @@ class ImportAccounts extends PureComponent<Props, State> {
scanStatus,
err,
isAppOpened,
onClickImport: this.handleClickImport,
onClickAdd: this.handleClickAdd,
onCloseModal: this.handleCloseModal,
transitionTo: this.transitionTo,
setState: (...args) => this.setState(...args),
@ -195,14 +197,14 @@ class ImportAccounts extends PureComponent<Props, State> {
return (
<Modal
name="importAccounts"
name={MODAL_ADD_ACCOUNTS}
refocusWhenChange={stepId}
onHide={() => this.setState({ ...INITIAL_STATE })}
render={({ onClose }) => (
<ModalBody onClose={onClose}>
<SyncSkipUnderPriority priority={100} />
<ModalTitle onBack={onBack ? () => onBack(stepProps) : void 0}>
{t('app:importAccounts.title')}
{t('app:addAccounts.title')}
</ModalTitle>
<ModalContent>
<Breadcrumb mb={6} currentStep={stepIndex} items={this.STEPS} />
@ -226,7 +228,7 @@ export default compose(
mapDispatchToProps,
),
translate(),
)(ImportAccounts)
)(AddAccounts)
function idleCallback() {
return new Promise(resolve => window.requestIdleCallback(resolve))

0
src/components/modals/ImportAccounts/steps/01-step-choose-currency.js → src/components/modals/AddAccounts/steps/01-step-choose-currency.js

2
src/components/modals/ImportAccounts/steps/02-step-connect-device.js → src/components/modals/AddAccounts/steps/02-step-connect-device.js

@ -19,7 +19,7 @@ function StepConnectDevice({ t, currency, currentDevice, setState }: StepProps)
<Box align="center" mb={6}>
<CurrencyCircleIcon mb={3} size={40} currency={currency} />
<Box ff="Open Sans" fontSize={4} color="dark" textAlign="center" style={{ width: 370 }}>
<Trans i18nKey="importAccounts:connectDevice.desc" parent="div">
<Trans i18nKey="app:addAccounts.connectDevice.desc" parent="div">
{`You're about to import your `}
<strong style={{ fontWeight: 'bold' }}>{`${currency.name} (${
currency.ticker

197
src/components/modals/ImportAccounts/steps/03-step-import.js → src/components/modals/AddAccounts/steps/03-step-import.js

@ -1,20 +1,17 @@
// @flow
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import React, { PureComponent, Fragment } from 'react'
import type { Account } from '@ledgerhq/live-common/lib/types'
import uniq from 'lodash/uniq'
import { getBridgeForCurrency } from 'bridge'
import Box from 'components/base/Box'
import CurrencyBadge from 'components/base/CurrencyBadge'
import Button from 'components/base/Button'
import Spinner from 'components/base/Spinner'
import FakeLink from 'components/base/FakeLink'
import AccountsList from 'components/base/AccountsList'
import IconExchange from 'icons/Exchange'
import AccountRow from '../AccountRow'
import type { StepProps } from '../index'
class StepImport extends PureComponent<StepProps> {
@ -101,7 +98,7 @@ class StepImport extends PureComponent<StepProps> {
}
}
handleAccountUpdate = (updatedAccount: Account) => {
handleUpdateAccount = (updatedAccount: Account) => {
const { scannedAccounts, setState } = this.props
setState({
scannedAccounts: scannedAccounts.map(account => {
@ -123,7 +120,15 @@ class StepImport extends PureComponent<StepProps> {
handleUnselectAll = () => this.props.setState({ checkedAccountsIds: [] })
render() {
const { scanStatus, err, scannedAccounts, checkedAccountsIds, existingAccounts, t } = this.props
const {
scanStatus,
currency,
err,
scannedAccounts,
checkedAccountsIds,
existingAccounts,
t,
} = this.props
const importableAccounts = scannedAccounts.filter(acc => {
if (acc.operations.length <= 0) {
@ -139,126 +144,66 @@ class StepImport extends PureComponent<StepProps> {
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
const importableAccountsListTitle = t('app:addAccounts.accountToImportSubtitle', {
count: importableAccounts.length,
})
return (
<Box>
{err && <Box shrink>{err.message}</Box>}
const importableAccountsEmpty = `We didnt find any ${
currency ? ` ${currency.name}}` : ''
} account to import.`
return (
<Fragment>
<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('app:importAccounts.accountToImportSubtitle', {
count: importableAccounts.length,
})}
</Box>
<FakeLink
ml="auto"
onClick={isAllSelected ? this.handleUnselectAll : this.handleSelectAll}
fontSize={3}
>
{isAllSelected
? t('app:importAccounts.unselectAll')
: t('app: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('app:importAccounts.createNewAccount')}
</Box>
</Box>
<AccountRow
account={creatableAccounts[0]}
isChecked={
checkedAccountsIds.find(id => id === creatableAccounts[0].id) !== undefined
}
onClick={this.handleToggleAccount}
onAccountUpdate={this.handleAccountUpdate}
/>
</Box>
)}
<AccountsList
title={importableAccountsListTitle}
emptyText={importableAccountsEmpty}
accounts={importableAccounts}
checkedIds={checkedAccountsIds}
onToggleAccount={this.handleToggleAccount}
onUpdateAccount={this.handleUpdateAccount}
onSelectAll={this.handleSelectAll}
onUnselectAll={this.handleUnselectAll}
isLoading={scanStatus === 'scanning'}
/>
<AccountsList
title={t('app:addAccounts.createNewAccount')}
emptyText={
'You cannot create a new account because your last account has no operations'
}
accounts={creatableAccounts}
checkedIds={checkedAccountsIds}
onToggleAccount={this.handleToggleAccount}
onUpdateAccount={this.handleUpdateAccount}
isLoading={scanStatus === 'scanning'}
/>
</Box>
<Box horizontal mt={2}>
{['error'].includes(scanStatus) && (
{err && (
<Box shrink>
{err.message}
<Button small outline onClick={this.handleRetry}>
<Box horizontal flow={2} align="center">
<IconExchange size={13} />
<span>{t('app:importAccounts.retrySync')}</span>
<span>{t('app:addAccounts.retrySync')}</span>
</Box>
</Button>
)}
</Box>
</Box>
</Box>
)}
</Fragment>
)
}
}
export default StepImport
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,
onClickAdd,
onCloseModal,
checkedAccountsIds,
scannedAccounts,
currency,
t,
}: StepProps) => {
const willCreateAccount = checkedAccountsIds.some(id => {
@ -266,32 +211,38 @@ export const StepImportFooter = ({
return account && account.operations.length === 0
})
const willImportAccounts = checkedAccountsIds.some(id => {
const willAddAccounts = checkedAccountsIds.some(id => {
const account = scannedAccounts.find(a => a.id === id)
return account && account.operations.length > 0
})
const importedAccountsCount = checkedAccountsIds.filter(id => {
const addedAccountsCount = checkedAccountsIds.filter(id => {
const account = scannedAccounts.find(acc => acc.id === id)
return account && account.operations.length > 0
}).length
const ctaWording =
willCreateAccount && willImportAccounts
? `${t('app:importAccounts.cta.create')} / ${t('app:importAccounts.cta.import', {
count: importedAccountsCount,
})}`
: willCreateAccount
? t('app:importAccounts.cta.create')
: t('app:importAccounts.cta.import', { count: importedAccountsCount })
scanStatus === 'scanning'
? t('app:common.sync.syncing')
: willCreateAccount && willAddAccounts
? `${t('app:addAccounts.cta.create')} / ${t('app:addAccounts.cta.import', {
count: addedAccountsCount,
})}`
: willCreateAccount
? t('app:addAccounts.cta.create')
: willAddAccounts
? t('app:addAccounts.cta.import', { count: addedAccountsCount })
: t('app:common.close')
const willClose = !willCreateAccount && !willAddAccounts
const onClick = willClose ? onCloseModal : onClickAdd
return (
<Button
primary
disabled={scanStatus !== 'finished' || checkedAccountsIds.length === 0}
onClick={() => onClickImport()}
>
{ctaWording}
</Button>
<Fragment>
{currency && <CurrencyBadge mr="auto" currency={currency} />}
<Button primary disabled={scanStatus !== 'finished'} onClick={onClick}>
{ctaWording}
</Button>
</Fragment>
)
}

0
src/components/modals/ImportAccounts/steps/04-step-finish.js → src/components/modals/AddAccounts/steps/04-step-finish.js

2
src/components/modals/index.js

@ -1,5 +1,5 @@
export Debug from './Debug'
export ImportAccounts from './ImportAccounts'
export AddAccounts from './AddAccounts'
export OperationDetails from './OperationDetails'
export Receive from './Receive'
export Send from './Send'

2
src/config/constants.js

@ -19,7 +19,7 @@ export const CHECK_UPDATE_DELAY = 5e3
export const DEVICE_DISCONNECT_DEBOUNCE = intFromEnv('LEDGER_DEVICE_DISCONNECT_DEBOUNCE', 500)
export const MODAL_ADD_ACCOUNT = 'MODAL_ADD_ACCOUNT'
export const MODAL_ADD_ACCOUNTS = 'MODAL_ADD_ACCOUNTS'
export const MODAL_OPERATION_DETAILS = 'MODAL_OPERATION_DETAILS'
export const MODAL_RECEIVE = 'MODAL_RECEIVE'
export const MODAL_SEND = 'MODAL_SEND'

7
src/config/global-tab.js

@ -0,0 +1,7 @@
// Github like focus style:
// - focus states are not visible by default
// - first time user hit tab, enable global tab to see focus states
let IS_GLOBAL_TAB_ENABLED = false
export const isGlobalTabEnabled = () => IS_GLOBAL_TAB_ENABLED
export const enableGlobalTab = () => (IS_GLOBAL_TAB_ENABLED = true)

7
src/globals.js

@ -1,8 +1,13 @@
// @flow
const { NODE_ENV } = process.env
const { NODE_ENV, STORYBOOK_ENV } = process.env
global.__ENV__ = NODE_ENV === 'development' ? NODE_ENV : 'production'
global.__DEV__ = global.__ENV__ === 'development'
global.__PROD__ = !global.__DEV__
global.__STORYBOOK_ENV__ = STORYBOOK_ENV === '1'
global.__GLOBAL_STYLES__ = require('./styles/reset')
if (STORYBOOK_ENV === '1') {
global.__APP_VERSION__ = '1.0.0'
}

6
src/renderer/createStore.js

@ -6,7 +6,6 @@ import thunk from 'redux-thunk'
import createHistory from 'history/createHashHistory'
import type { HashHistory } from 'history'
import logger from 'middlewares/logger'
import sentry from 'middlewares/sentry'
import reducers from 'reducers'
type Props = {
@ -20,7 +19,10 @@ export default ({ state, history, dbMiddleware }: Props) => {
if (!history) {
history = createHistory()
}
const middlewares = [routerMiddleware(history), thunk, logger, sentry]
const middlewares = [routerMiddleware(history), thunk, logger]
if (!__STORYBOOK_ENV__) {
middlewares.push(require('middlewares/sentry').default)
}
if (dbMiddleware) {
middlewares.push(dbMiddleware)
}

9
src/renderer/init.js

@ -11,6 +11,8 @@ import moment from 'moment'
import createStore from 'renderer/createStore'
import events from 'renderer/events'
import { enableGlobalTab, isGlobalTabEnabled } from 'config/global-tab'
import { fetchAccounts } from 'actions/accounts'
import { fetchSettings } from 'actions/settings'
import { isLocked } from 'reducers/application'
@ -29,15 +31,8 @@ import 'styles/global'
const rootNode = document.getElementById('app')
// Github like focus style:
// - focus states are not visible by default
// - first time user hit tab, enable global tab to see focus states
let IS_GLOBAL_TAB_ENABLED = false
const TAB_KEY = 9
export const isGlobalTabEnabled = () => IS_GLOBAL_TAB_ENABLED
export const enableGlobalTab = () => (IS_GLOBAL_TAB_ENABLED = true)
async function init() {
if (process.env.LEDGER_RESET_ALL) {
await hardReset()

3
src/styles/theme.js

@ -1,7 +1,5 @@
// @flow
import { rgba } from 'styles/helpers'
export const space = [0, 5, 10, 15, 20, 30, 40, 50, 70]
export const fontSizes = [8, 9, 10, 12, 13, 16, 18, 22, 32]
export const radii = [0, 4]
@ -95,7 +93,6 @@ export default {
topBarHeight: 58,
sideBarWidth: 230,
},
focusBoxShadow: `${rgba(colors.wallet, 0.2)} 0 2px 5px`,
radii,
fontFamilies,
fontSizes,

2
static/i18n/en/app.yml

@ -116,7 +116,7 @@ exchange:
genuinecheck:
modal:
title: Genuine check, bro
importAccounts:
addAccounts:
title: Add accounts
breadcrumb:
informations: Informations

2
static/i18n/fr/app.yml

@ -117,7 +117,7 @@ exchange:
genuinecheck:
modal:
title: Genuine check, bro
importAccounts:
addAccounts:
title: Add accounts
breadcrumb:
informations: Informations

3
webpack/plugins.js

@ -3,7 +3,7 @@ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const pkg = require('../package.json')
require('../src/globals')
const { BUNDLE_ANALYZER, SENTRY_URL } = process.env
const { BUNDLE_ANALYZER, SENTRY_URL, STORYBOOK_ENV } = process.env
module.exports = type => {
const plugins = [
@ -13,6 +13,7 @@ module.exports = type => {
__DEV__,
__PROD__,
__SENTRY_URL__: JSON.stringify(SENTRY_URL || null),
__STORYBOOK_ENV__: JSON.stringify(STORYBOOK_ENV),
'process.env.NODE_ENV': JSON.stringify(__ENV__),
}),
]

Loading…
Cancel
Save