Browse Source

Merge pull request #625 from meriadec/polishes

Polishes
master
Meriadec Pillet 7 years ago
committed by GitHub
parent
commit
c6abae8338
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/components/AccountPage/index.js
  2. 38
      src/components/DeviceBusyIndicator.js
  3. 37
      src/components/LibcoreBusyIndicator.js
  4. 3
      src/components/SelectAccount/index.js
  5. 3
      src/components/SelectCurrency/index.js
  6. 4
      src/components/SelectExchange.js
  7. 2
      src/components/SettingsPage/sections/Display.js
  8. 13
      src/components/TopBar/ActivityIndicator.js
  9. 9
      src/components/TopBar/index.js
  10. 8
      src/components/base/Button/index.js
  11. 1
      src/components/base/Modal/ModalTitle.js
  12. 5
      src/components/layout/Default.js
  13. 85
      src/components/modals/AddAccounts/steps/03-step-import.js
  14. 1
      src/config/constants.js
  15. 19
      src/helpers/deviceAccess.js
  16. 18
      src/helpers/withLibcore.js
  17. 15
      src/main/bridge.js
  18. 16
      src/renderer/events.js
  19. 10
      static/i18n/en/app.yml

2
src/components/AccountPage/index.js

@ -129,11 +129,9 @@ class AccountPage extends PureComponent<Props> {
)} )}
<Tooltip render={() => t('app:account.settings.title')}> <Tooltip render={() => t('app:account.settings.title')}>
<ButtonSettings onClick={() => openModal(MODAL_SETTINGS_ACCOUNT, { account })}> <ButtonSettings onClick={() => openModal(MODAL_SETTINGS_ACCOUNT, { account })}>
<Button small outlineGrey>
<Box justifyContent="center"> <Box justifyContent="center">
<IconAccountSettings size={16} /> <IconAccountSettings size={16} />
</Box> </Box>
</Button>
</ButtonSettings> </ButtonSettings>
</Tooltip> </Tooltip>
</Box> </Box>

38
src/components/DeviceBusyIndicator.js

@ -0,0 +1,38 @@
import React, { PureComponent } from 'react'
import styled from 'styled-components'
const Indicator = styled.div`
opacity: ${p => (p.busy ? 0.2 : 0)};
width: 6px;
height: 6px;
border-radius: 3px;
background-color: black;
position: fixed;
bottom: 4px;
right: 4px;
z-index: 999;
`
// NB this is done like this to be extremely performant. we don't want redux for this..
const perPaths = {}
const instances = []
export const onSetDeviceBusy = (path, busy) => {
perPaths[path] = busy
instances.forEach(i => i.forceUpdate())
}
class DeviceBusyIndicator extends PureComponent<{}> {
componentDidMount() {
instances.push(this)
}
componentWillUnmount() {
const i = instances.indexOf(this)
instances.splice(i, 1)
}
render() {
const busy = Object.values(perPaths).reduce((busy, b) => busy || b, false)
return <Indicator busy={busy} />
}
}
export default DeviceBusyIndicator

37
src/components/LibcoreBusyIndicator.js

@ -0,0 +1,37 @@
import React, { PureComponent } from 'react'
import styled from 'styled-components'
const Indicator = styled.div`
opacity: ${p => (p.busy ? 0.2 : 0)};
width: 6px;
height: 6px;
border-radius: 3px;
background-color: black;
position: fixed;
bottom: 4px;
right: 4px;
z-index: 999;
`
// NB this is done like this to be extremely performant. we don't want redux for this..
let busy = false
const instances = []
export const onSetLibcoreBusy = b => {
busy = b
instances.forEach(i => i.forceUpdate())
}
class LibcoreBusyIndicator extends PureComponent<{}> {
componentDidMount() {
instances.push(this)
}
componentWillUnmount() {
const i = instances.indexOf(this)
instances.splice(i, 1)
}
render() {
return <Indicator busy={busy} />
}
}
export default LibcoreBusyIndicator

3
src/components/SelectAccount/index.js

@ -69,6 +69,9 @@ const RawSelectAccount = ({ accounts, onChange, value, t, ...props }: Props) =>
renderValue={renderOption} renderValue={renderOption}
renderOption={renderOption} renderOption={renderOption}
placeholder={t('app:common.selectAccount')} placeholder={t('app:common.selectAccount')}
noOptionsMessage={({ inputValue }) =>
t('app:common.selectAccountNoOption', { accountName: inputValue })
}
onChange={onChange} onChange={onChange}
/> />
) )

3
src/components/SelectCurrency/index.js

@ -40,6 +40,9 @@ const SelectCurrency = ({ onChange, value, t, placeholder, currencies, ...props
renderValue={renderOption} renderValue={renderOption}
options={options} options={options}
placeholder={placeholder || t('app:common.selectCurrency')} placeholder={placeholder || t('app:common.selectCurrency')}
noOptionsMessage={({ inputValue }: { inputValue: string }) =>
t('app:common.selectCurrencyNoOption', { currencyName: inputValue })
}
onChange={item => onChange(item ? item.currency : null)} onChange={item => onChange(item ? item.currency : null)}
{...props} {...props}
/> />

4
src/components/SelectExchange.js

@ -93,6 +93,10 @@ class SelectExchange extends Component<
options={options} options={options}
onChange={onChange} onChange={onChange}
isLoading={options.length === 0} isLoading={options.length === 0}
placeholder={t('app:common.selectExchange')}
noOptionsMessage={({ inputValue }) =>
t('app:common.selectExchangeNoOption', { exchangeName: inputValue })
}
{...props} {...props}
/> />
) )

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

@ -169,7 +169,7 @@ class TabProfile extends PureComponent<Props, State> {
to={counterValueCurrency} to={counterValueCurrency}
exchangeId={counterValueExchange} exchangeId={counterValueExchange}
onChange={this.handleChangeExchange} onChange={this.handleChangeExchange}
minWidth={150} minWidth={200}
/> />
</Box> </Box>
</Row> </Row>

13
src/components/TopBar/ActivityIndicator.js

@ -10,7 +10,6 @@ import type { T } from 'types/common'
import type { AsyncState } from 'reducers/bridgeSync' import type { AsyncState } from 'reducers/bridgeSync'
import { globalSyncStateSelector } from 'reducers/bridgeSync' import { globalSyncStateSelector } from 'reducers/bridgeSync'
import { hasAccountsSelector } from 'reducers/accounts'
import { BridgeSyncConsumer } from 'bridge/BridgeSyncContext' import { BridgeSyncConsumer } from 'bridge/BridgeSyncContext'
import CounterValues from 'helpers/countervalues' import CounterValues from 'helpers/countervalues'
@ -23,7 +22,6 @@ import ItemContainer from './ItemContainer'
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
globalSyncState: globalSyncStateSelector, globalSyncState: globalSyncStateSelector,
hasAccounts: hasAccountsSelector,
}) })
type Props = { type Props = {
@ -128,16 +126,7 @@ class ActivityIndicatorInner extends PureComponent<Props, State> {
} }
} }
const ActivityIndicator = ({ const ActivityIndicator = ({ globalSyncState, t }: { globalSyncState: AsyncState, t: T }) => (
globalSyncState,
hasAccounts,
t,
}: {
globalSyncState: AsyncState,
hasAccounts: boolean,
t: T,
}) =>
!hasAccounts ? null : (
<BridgeSyncConsumer> <BridgeSyncConsumer>
{setSyncBehavior => ( {setSyncBehavior => (
<CounterValues.PollingConsumer> <CounterValues.PollingConsumer>

9
src/components/TopBar/index.js

@ -12,6 +12,7 @@ import type { T } from 'types/common'
import { lock } from 'reducers/application' import { lock } from 'reducers/application'
import { hasPassword } from 'reducers/settings' import { hasPassword } from 'reducers/settings'
import { hasAccountsSelector } from 'reducers/accounts'
import { openModal } from 'reducers/modals' import { openModal } from 'reducers/modals'
import IconLock from 'icons/Lock' import IconLock from 'icons/Lock'
@ -54,6 +55,7 @@ const Bar = styled.div`
const mapStateToProps = state => ({ const mapStateToProps = state => ({
hasPassword: hasPassword(state), hasPassword: hasPassword(state),
hasAccounts: hasAccountsSelector(state),
}) })
const mapDispatchToProps = { const mapDispatchToProps = {
@ -63,6 +65,7 @@ const mapDispatchToProps = {
type Props = { type Props = {
hasPassword: boolean, hasPassword: boolean,
hasAccounts: boolean,
history: RouterHistory, history: RouterHistory,
location: Location, location: Location,
lock: Function, lock: Function,
@ -91,17 +94,21 @@ class TopBar extends PureComponent<Props> {
} }
} }
render() { render() {
const { hasPassword, t } = this.props const { hasPassword, hasAccounts, t } = this.props
return ( return (
<Container bg="lightGrey" color="graphite"> <Container bg="lightGrey" color="graphite">
<Inner> <Inner>
<Box grow horizontal> <Box grow horizontal>
<GlobalSearch t={t} isHidden /> <GlobalSearch t={t} isHidden />
{hasAccounts && (
<Fragment>
<ActivityIndicator /> <ActivityIndicator />
<Box justifyContent="center"> <Box justifyContent="center">
<Bar /> <Bar />
</Box> </Box>
</Fragment>
)}
<Tooltip render={() => t('app:settings.title')}> <Tooltip render={() => t('app:settings.title')}>
<ItemContainer isInteractive onClick={this.navigateToSettings}> <ItemContainer isInteractive onClick={this.navigateToSettings}>
<IconSettings size={16} /> <IconSettings size={16} />

8
src/components/base/Button/index.js

@ -16,8 +16,12 @@ type Style = any // FIXME
const buttonStyles: { [_: string]: Style } = { const buttonStyles: { [_: string]: Style } = {
default: { default: {
default: noop, default: noop,
active: noop, active: p => `
hover: noop, background: ${rgba(p.theme.colors.fog, 0.3)};
`,
hover: p => `
background: ${rgba(p.theme.colors.fog, 0.2)};
`,
focus: () => ` focus: () => `
box-shadow: ${focusedShadowStyle}; box-shadow: ${focusedShadowStyle};
`, `,

1
src/components/base/Modal/ModalTitle.js

@ -30,6 +30,7 @@ const Back = styled(Box).attrs({
})` })`
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
line-height: 1;
top: 0; top: 0;
left: 0; left: 0;

5
src/components/layout/Default.js

@ -17,6 +17,8 @@ import DashboardPage from 'components/DashboardPage'
import ManagerPage from 'components/ManagerPage' import ManagerPage from 'components/ManagerPage'
import ExchangePage from 'components/ExchangePage' import ExchangePage from 'components/ExchangePage'
import SettingsPage from 'components/SettingsPage' import SettingsPage from 'components/SettingsPage'
import LibcoreBusyIndicator from 'components/LibcoreBusyIndicator'
import DeviceBusyIndicator from 'components/DeviceBusyIndicator'
import AppRegionDrag from 'components/AppRegionDrag' import AppRegionDrag from 'components/AppRegionDrag'
import IsUnlocked from 'components/IsUnlocked' import IsUnlocked from 'components/IsUnlocked'
@ -96,6 +98,9 @@ class Default extends Component<Props> {
</Main> </Main>
</Box> </Box>
</Box> </Box>
<LibcoreBusyIndicator />
<DeviceBusyIndicator />
</IsUnlocked> </IsUnlocked>
</Fragment> </Fragment>
) )

85
src/components/modals/AddAccounts/steps/03-step-import.js

@ -11,7 +11,7 @@ import Box from 'components/base/Box'
import CurrencyBadge from 'components/base/CurrencyBadge' import CurrencyBadge from 'components/base/CurrencyBadge'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import AccountsList from 'components/base/AccountsList' import AccountsList from 'components/base/AccountsList'
import IconExchange from 'icons/Exchange' import IconExclamationCircleThin from 'icons/ExclamationCircleThin'
import type { StepProps } from '../index' import type { StepProps } from '../index'
@ -20,14 +20,30 @@ class StepImport extends PureComponent<StepProps> {
this.startScanAccountsDevice() this.startScanAccountsDevice()
} }
componentDidUpdate(prevProps: StepProps) {
// handle case when we click on stop sync
if (prevProps.scanStatus !== 'finished' && this.props.scanStatus === 'finished') {
this.unsub()
}
// handle case when we click on retry sync
if (prevProps.scanStatus !== 'scanning' && this.props.scanStatus === 'scanning') {
this.startScanAccountsDevice()
}
}
componentWillUnmount() { componentWillUnmount() {
this.unsub()
}
scanSubscription = null
unsub = () => {
if (this.scanSubscription) { if (this.scanSubscription) {
this.scanSubscription.unsubscribe() this.scanSubscription.unsubscribe()
} }
} }
scanSubscription = null
translateName(account: Account) { translateName(account: Account) {
const { t } = this.props const { t } = this.props
let { name } = account let { name } = account
@ -45,6 +61,7 @@ class StepImport extends PureComponent<StepProps> {
} }
startScanAccountsDevice() { startScanAccountsDevice() {
this.unsub()
const { currency, currentDevice, setState } = this.props const { currency, currentDevice, setState } = this.props
try { try {
invariant(currency, 'No currency to scan') invariant(currency, 'No currency to scan')
@ -82,10 +99,7 @@ class StepImport extends PureComponent<StepProps> {
} }
handleRetry = () => { handleRetry = () => {
if (this.scanSubscription) { this.unsub()
this.scanSubscription.unsubscribe()
this.scanSubscription = null
}
this.handleResetState() this.handleResetState()
this.startScanAccountsDevice() this.startScanAccountsDevice()
} }
@ -131,6 +145,17 @@ class StepImport extends PureComponent<StepProps> {
handleUnselectAll = () => this.props.setState({ checkedAccountsIds: [] }) handleUnselectAll = () => this.props.setState({ checkedAccountsIds: [] })
renderError() {
const { err, t } = this.props
invariant(err, 'Trying to render inexisting error')
return (
<Box style={{ height: 200 }} align="center" justify="center" color="alertRed">
<IconExclamationCircleThin size={43} />
<Box mt={4}>{t('app:addAccounts.somethingWentWrong')}</Box>
</Box>
)
}
render() { render() {
const { const {
scanStatus, scanStatus,
@ -142,6 +167,12 @@ class StepImport extends PureComponent<StepProps> {
t, t,
} = this.props } = this.props
if (err) {
return this.renderError()
}
const currencyName = currency ? currency.name : ''
const importableAccounts = scannedAccounts.filter(acc => { const importableAccounts = scannedAccounts.filter(acc => {
if (acc.operations.length <= 0) { if (acc.operations.length <= 0) {
return false return false
@ -160,9 +191,8 @@ class StepImport extends PureComponent<StepProps> {
count: importableAccounts.length, count: importableAccounts.length,
}) })
const importableAccountsEmpty = t('app:addAccounts.noAccountToImport', { const importableAccountsEmpty = t('app:addAccounts.noAccountToImport', { currencyName })
currencyName: currency ? ` ${currency.name}}` : '', const hasAlreadyEmptyAccount = scannedAccounts.some(a => a.operations.length === 0)
})
return ( return (
<Fragment> <Fragment>
@ -180,7 +210,11 @@ class StepImport extends PureComponent<StepProps> {
/> />
<AccountsList <AccountsList
title={t('app:addAccounts.createNewAccount.title')} title={t('app:addAccounts.createNewAccount.title')}
emptyText={t('app:addAccounts.createNewAccount.noOperationOnLastAccount')} emptyText={
hasAlreadyEmptyAccount
? t('app:addAccounts.createNewAccount.noOperationOnLastAccount')
: t('app:addAccounts.createNewAccount.noAccountToCreate', { currencyName })
}
accounts={creatableAccounts} accounts={creatableAccounts}
checkedIds={checkedAccountsIds} checkedIds={checkedAccountsIds}
onToggleAccount={this.handleToggleAccount} onToggleAccount={this.handleToggleAccount}
@ -189,17 +223,7 @@ class StepImport extends PureComponent<StepProps> {
/> />
</Box> </Box>
{err && ( {err && <Box shrink>{err.message}</Box>}
<Box shrink>
{err.message}
<Button small outline onClick={this.handleRetry}>
<Box horizontal flow={2} align="center">
<IconExchange size={13} />
<span>{t('app:addAccounts.retrySync')}</span>
</Box>
</Button>
</Box>
)}
</Fragment> </Fragment>
) )
} }
@ -208,6 +232,7 @@ class StepImport extends PureComponent<StepProps> {
export default StepImport export default StepImport
export const StepImportFooter = ({ export const StepImportFooter = ({
setState,
scanStatus, scanStatus,
onClickAdd, onClickAdd,
onCloseModal, onCloseModal,
@ -250,7 +275,21 @@ export const StepImportFooter = ({
return ( return (
<Fragment> <Fragment>
{currency && <CurrencyBadge mr="auto" currency={currency} />} {currency && <CurrencyBadge mr="auto" currency={currency} />}
<Button primary disabled={scanStatus !== 'finished'} onClick={onClick}> {scanStatus === 'error' && (
<Button mr={2} onClick={() => setState({ scanStatus: 'scanning', err: null })}>
{t('app:common.retry')}
</Button>
)}
{scanStatus === 'scanning' && (
<Button mr={2} onClick={() => setState({ scanStatus: 'finished' })}>
{t('app:common.stop')}
</Button>
)}
<Button
primary
disabled={scanStatus !== 'finished' && scanStatus !== 'error'}
onClick={onClick}
>
{ctaWording} {ctaWording}
</Button> </Button>
</Fragment> </Fragment>

1
src/config/constants.js

@ -65,6 +65,7 @@ export const SKIP_GENUINE = boolFromEnv('SKIP_GENUINE')
export const SKIP_ONBOARDING = boolFromEnv('SKIP_ONBOARDING') export const SKIP_ONBOARDING = boolFromEnv('SKIP_ONBOARDING')
export const SHOW_LEGACY_NEW_ACCOUNT = boolFromEnv('SHOW_LEGACY_NEW_ACCOUNT') export const SHOW_LEGACY_NEW_ACCOUNT = boolFromEnv('SHOW_LEGACY_NEW_ACCOUNT')
export const HIGHLIGHT_I18N = boolFromEnv('HIGHLIGHT_I18N') export const HIGHLIGHT_I18N = boolFromEnv('HIGHLIGHT_I18N')
export const DISABLE_ACTIVITY_INDICATORS = boolFromEnv('DISABLE_ACTIVITY_INDICATORS')
// Other constants // Other constants

19
src/helpers/deviceAccess.js

@ -18,7 +18,7 @@ export const withDevice: WithDevice = devicePath => {
semaphorePerDevice[devicePath] || (semaphorePerDevice[devicePath] = createSemaphore(1)) semaphorePerDevice[devicePath] || (semaphorePerDevice[devicePath] = createSemaphore(1))
return job => return job =>
takeSemaphorePromise(sem, async () => { takeSemaphorePromise(sem, devicePath, async () => {
const t = await retry(() => TransportNodeHid.open(devicePath), { maxRetry: 1 }) const t = await retry(() => TransportNodeHid.open(devicePath), { maxRetry: 1 })
if (DEBUG_DEVICE) t.setDebugMode(true) if (DEBUG_DEVICE) t.setDebugMode(true)
@ -32,17 +32,32 @@ export const withDevice: WithDevice = devicePath => {
}) })
} }
function takeSemaphorePromise<T>(sem, f: () => Promise<T>): Promise<T> { function takeSemaphorePromise<T>(sem, devicePath, f: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
sem.take(() => { sem.take(() => {
process.send({
type: 'setDeviceBusy',
busy: true,
devicePath,
})
f().then( f().then(
r => { r => {
sem.leave() sem.leave()
resolve(r) resolve(r)
process.send({
type: 'setDeviceBusy',
busy: false,
devicePath,
})
}, },
e => { e => {
sem.leave() sem.leave()
reject(e) reject(e)
process.send({
type: 'setDeviceBusy',
busy: false,
devicePath,
})
}, },
) )
}) })

18
src/helpers/withLibcore.js

@ -3,8 +3,24 @@
// TODO: `core` should be typed // TODO: `core` should be typed
type Job<A> = Object => Promise<A> type Job<A> = Object => Promise<A>
export default function withLibcore<A>(job: Job<A>): Promise<A> { let counter = 0
export default async function withLibcore<A>(job: Job<A>): Promise<A> {
const core = require('./init-libcore').default const core = require('./init-libcore').default
core.getPoolInstance() core.getPoolInstance()
try {
if (counter++ === 0) {
process.send({
type: 'setLibcoreBusy',
busy: true,
})
}
return job(core) return job(core)
} finally {
if (--counter === 0) {
process.send({
type: 'setLibcoreBusy',
busy: false,
})
}
}
} }

15
src/main/bridge.js

@ -97,16 +97,19 @@ ipcMainListenReceiveCommands({
}) })
function handleGlobalInternalMessage(payload) { function handleGlobalInternalMessage(payload) {
if (payload.type === 'executeHttpQueryOnRenderer') { switch (payload.type) {
case 'setLibcoreBusy':
case 'setDeviceBusy':
case 'executeHttpQueryOnRenderer': {
const win = getMainWindow && getMainWindow() const win = getMainWindow && getMainWindow()
if (!win) { if (!win) {
logger.warn("can't executeHttpQueryOnRenderer because no renderer") logger.warn(`can't ${payload.type} because no renderer`)
return return
} }
win.webContents.send('executeHttpQuery', { win.webContents.send(payload.type, payload)
id: payload.id, break
networkArg: payload.networkArg, }
}) default:
} }
} }

16
src/renderer/events.js

@ -15,7 +15,9 @@ import network from 'api/network'
import { ipcRenderer } from 'electron' import { ipcRenderer } from 'electron'
import debug from 'debug' import debug from 'debug'
import { CHECK_UPDATE_DELAY } from 'config/constants' import { CHECK_UPDATE_DELAY, DISABLE_ACTIVITY_INDICATORS } from 'config/constants'
import { onSetDeviceBusy } from 'components/DeviceBusyIndicator'
import { onSetLibcoreBusy } from 'components/LibcoreBusyIndicator'
import { hasPassword } from 'reducers/settings' import { hasPassword } from 'reducers/settings'
import { lock } from 'reducers/application' import { lock } from 'reducers/application'
@ -87,7 +89,7 @@ export default ({ store }: { store: Object }) => {
} }
}) })
ipcRenderer.on('executeHttpQuery', (event: any, { networkArg, id }) => { ipcRenderer.on('executeHttpQueryOnRenderer', (event: any, { networkArg, id }) => {
network(networkArg).then( network(networkArg).then(
result => { result => {
ipcRenderer.send('executeHttpQueryPayload', { type: 'success', id, result }) ipcRenderer.send('executeHttpQueryPayload', { type: 'success', id, result })
@ -98,6 +100,16 @@ export default ({ store }: { store: Object }) => {
) )
}) })
if (!DISABLE_ACTIVITY_INDICATORS) {
ipcRenderer.on('setLibcoreBusy', (event: any, { busy }) => {
onSetLibcoreBusy(busy)
})
ipcRenderer.on('setDeviceBusy', (event: any, { busy, devicePath }) => {
onSetDeviceBusy(devicePath, busy)
})
}
if (__PROD__) { if (__PROD__) {
// TODO move this to "command" pattern // TODO move this to "command" pattern
const updaterHandlers = { const updaterHandlers = {

10
static/i18n/en/app.yml

@ -11,7 +11,11 @@ common:
chooseWalletPlaceholder: Choose a wallet... chooseWalletPlaceholder: Choose a wallet...
currency: Currency currency: Currency
selectAccount: Select an account selectAccount: Select an account
selectAccountNoOption: 'No account matching "{{accountName}}"'
selectCurrency: Select a currency selectCurrency: Select a currency
selectCurrencyNoOption: 'No currency matching "{{currencyName}}"'
selectExchange: Select an exchange
selectExchangeNoOption: 'No exchange matching "{{exchangeName}}"'
sortBy: Sort by sortBy: Sort by
search: Search search: Search
save: Save save: Save
@ -23,6 +27,7 @@ common:
next: Next next: Next
back: Back back: Back
retry: Retry retry: Retry
stop: Stop
close: Close close: Close
eastern: Eastern eastern: Eastern
western: Western western: Western
@ -138,12 +143,13 @@ addAccounts:
editName: Edit name editName: Edit name
newAccount: New account newAccount: New account
legacyAccount: '{{accountName}} (legacy)' legacyAccount: '{{accountName}} (legacy)'
noAccountToImport: We didnt find any {{currencyName}}} account to import. noAccountToImport: We didnt find any {{currencyName}} account to import.
success: Great success! success: Great success!
createNewAccount: createNewAccount:
title: Create new account title: Create new account
noOperationOnLastAccount: You cannot create a new account because your last account has no operations noOperationOnLastAccount: You cannot create a new account because your last account has no operations
retrySync: Retry sync noAccountToCreate: We didnt find any {{currencyName}} account to create.
somethingWentWrong: Something went wrong during synchronization.
cta: cta:
create: 'Create account' create: 'Create account'
import: 'Import account' import: 'Import account'

Loading…
Cancel
Save