From 66c29613181b0eb46334dae7e5339ee47c1d3c10 Mon Sep 17 00:00:00 2001 From: dasilvarosa Date: Wed, 25 Jul 2018 12:43:49 +0200 Subject: [PATCH 01/10] More information on clear cache modal --- static/i18n/en/app.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/i18n/en/app.json b/static/i18n/en/app.json index cbed0f97..331c6b5b 100644 --- a/static/i18n/en/app.json +++ b/static/i18n/en/app.json @@ -399,7 +399,7 @@ }, "softResetModal": { "title": "Clear cache", - "desc": "Clearing the Ledger Live cache forces network resynchronization" + "desc": "Clearing the Ledger Live cache forces network resynchronization. Your settings and accounts are not affected. The private keys to access your crypto assets in the blockchain remain secure on your Ledger device and on your Recovery sheet." }, "removeAccountModal": { "title": "Remove account", @@ -459,4 +459,4 @@ "desc_2": "Please beware that Ledger does not provide financial, tax, or legal advice. You should take such decisions on your own or consult with reliable experts.", "cta": "Got it" } -} \ No newline at end of file +} From 8d1b7865152720008b27ddcb99a9732adef74a74 Mon Sep 17 00:00:00 2001 From: Anastasia Poupeney Date: Thu, 26 Jul 2018 18:35:11 +0200 Subject: [PATCH 02/10] wording --- static/i18n/en/app.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/i18n/en/app.json b/static/i18n/en/app.json index cbed0f97..da867665 100644 --- a/static/i18n/en/app.json +++ b/static/i18n/en/app.json @@ -196,7 +196,7 @@ "successDescription_plural": "Your accounts have been created.", "createNewAccount": { "title": "Add a new account", - "noOperationOnLastAccount": "No transactions found on your last new account <1><0>{{accountName}}. You can add a new account after you've started transacting on that account.", + "noOperationOnLastAccount": "There are no transactions on your last created <1><0>{{accountName}} account. You must first receive a transaction on that account before you can add a new one.", "noAccountToCreate": "No <1><0>{{currencyName}} account was found to create" }, "cta": { @@ -459,4 +459,4 @@ "desc_2": "Please beware that Ledger does not provide financial, tax, or legal advice. You should take such decisions on your own or consult with reliable experts.", "cta": "Got it" } -} \ No newline at end of file +} From 3e71c489170b151d66ac2dac232356b90d458544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 30 Jul 2018 12:25:51 +0200 Subject: [PATCH 03/10] Add more timeout on the sync to avoid issues --- src/config/constants.js | 2 +- src/config/errors.js | 1 + src/helpers/libcore.js | 62 +++++++++++++++++++++++++++----------- src/helpers/promise.js | 18 +++++++++++ static/i18n/en/errors.json | 4 +++ 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/config/constants.js b/src/config/constants.js index aefe35b3..023626ef 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -46,7 +46,7 @@ export const SYNC_ALL_INTERVAL = 120 * 1000 export const SYNC_BOOT_DELAY = 2 * 1000 export const SYNC_PENDING_INTERVAL = 10 * 1000 export const SYNC_MAX_CONCURRENT = intFromEnv('LEDGER_SYNC_MAX_CONCURRENT', 1) -export const SYNC_TIMEOUT = intFromEnv('SYNC_TIMEOUT', 30 * 1000) +export const SYNC_TIMEOUT = intFromEnv('SYNC_TIMEOUT', 60 * 1000) // Endpoints... diff --git a/src/config/errors.js b/src/config/errors.js index fa6508bd..d6140d6c 100644 --- a/src/config/errors.js +++ b/src/config/errors.js @@ -12,6 +12,7 @@ export const UserRefusedAddress = createCustomErrorClass('UserRefusedAddress') export const WrongDeviceForAccount = createCustomErrorClass('WrongDeviceForAccount') export const DeviceNotGenuineError = createCustomErrorClass('DeviceNotGenuine') export const DeviceGenuineSocketEarlyClose = createCustomErrorClass('DeviceGenuineSocketEarlyClose') +export const TimeoutTagged = createCustomErrorClass('TimeoutTagged') // db stuff, no need to translate export const NoDBPathGiven = createCustomErrorClass('NoDBPathGiven') diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index a9ed673d..630c0c14 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -16,6 +16,7 @@ import { isSegwitPath, isUnsplitPath } from 'helpers/bip32' import * as accountIdHelper from 'helpers/accountId' import { createCustomErrorClass, deserializeError } from './errors' import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accountName' +import { timeoutTagged } from './promise' const NoAddressesFound = createCustomErrorClass('NoAddressesFound') @@ -202,6 +203,7 @@ const coreSyncAccount = (core, account) => new Promise((resolve, reject) => { const eventReceiver = createEventReceiver(core, e => { const code = e.getCode() + logger.debug(`syncAccountEvent ${code}`, { type: 'libcore-sync' }) if (code === core.EVENT_CODE.UNDEFINED || code === core.EVENT_CODE.SYNCHRONIZATION_FAILED) { const payload = e.getPayload() const message = ( @@ -270,7 +272,7 @@ async function scanNextAccount(props: { const shouldSyncAccount = true // TODO: let's sync everytime. maybe in the future we can optimize. if (shouldSyncAccount) { - await coreSyncAccount(core, njsAccount) + await timeoutTagged('coreSyncAccount', 30000, coreSyncAccount(core, njsAccount)) } if (isUnsubscribed()) return [] @@ -325,10 +327,10 @@ async function getOrCreateWallet( ): NJSWallet { const pool = core.getPoolInstance() try { - const wallet = await pool.getWallet(WALLET_IDENTIFIER) + const wallet = await timeoutTagged('getWallet', 5000, pool.getWallet(WALLET_IDENTIFIER)) return wallet } catch (err) { - const currency = await pool.getCurrency(currencyId) + const currency = await timeoutTagged('getCurrency', 5000, pool.getCurrency(currencyId)) const splitConfig = isUnsplit ? SPLITTED_CURRENCIES[currencyId] || null : null const coinType = splitConfig ? splitConfig.coinType : '' const walletConfig = isSegwit @@ -342,9 +344,11 @@ async function getOrCreateWallet( } : undefined const njsWalletConfig = createWalletConfig(core, walletConfig) - const wallet = await core - .getPoolInstance() - .createWallet(WALLET_IDENTIFIER, currency, njsWalletConfig) + const wallet = await timeoutTagged( + 'createWallet', + 10000, + core.getPoolInstance().createWallet(WALLET_IDENTIFIER, currency, njsWalletConfig), + ) return wallet } } @@ -368,21 +372,33 @@ async function buildAccountRaw({ core: *, ops: NJSOperation[], }): Promise { - const njsBalance = await njsAccount.getBalance() + const njsBalance = await timeoutTagged('getBalance', 10000, njsAccount.getBalance()) const balance = njsBalance.toLong() const jsCurrency = getCryptoCurrencyById(currencyId) - const { derivations } = await wallet.getAccountCreationInfo(accountIndex) + const { derivations } = await timeoutTagged( + 'getAccountCreationInfo', + 10000, + wallet.getAccountCreationInfo(accountIndex), + ) const [walletPath, accountPath] = derivations // retrieve xpub const xpub = njsAccount.getRestoreKey() // blockHeight - const { height: blockHeight } = await njsAccount.getLastBlock() + const { height: blockHeight } = await timeoutTagged( + 'getLastBlock', + 30000, + njsAccount.getLastBlock(), + ) // get a bunch of fresh addresses - const rawAddresses = await njsAccount.getFreshPublicAddresses() + const rawAddresses = await timeoutTagged( + 'getFreshPublicAddresses', + 10000, + njsAccount.getFreshPublicAddresses(), + ) const addresses = rawAddresses.map(njsAddress => ({ str: njsAddress.toString(), @@ -500,7 +516,11 @@ export async function syncAccount({ const isUnsplit = isUnsplitPath(freshAddressPath, SPLITTED_CURRENCIES[currencyId]) let njsWallet try { - njsWallet = await core.getPoolInstance().getWallet(decodedAccountId.walletName) + njsWallet = await timeoutTagged( + 'getWallet', + 10000, + core.getPoolInstance().getWallet(decodedAccountId.walletName), + ) } catch (e) { logger.warn(`Have to reimport the account... (${e})`) njsWallet = await getOrCreateWallet( @@ -514,20 +534,28 @@ export async function syncAccount({ let njsAccount try { - njsAccount = await njsWallet.getAccount(index) + njsAccount = await timeoutTagged('getAccount', 10000, njsWallet.getAccount(index)) } catch (e) { logger.warn(`Have to recreate the account... (${e.message})`) - const extendedInfos = await njsWallet.getExtendedKeyAccountCreationInfo(index) + const extendedInfos = await timeoutTagged( + 'getEKACI', + 10000, + njsWallet.getExtendedKeyAccountCreationInfo(index), + ) extendedInfos.extendedKeys.push(decodedAccountId.xpub) - njsAccount = await njsWallet.newAccountWithExtendedKeyInfo(extendedInfos) + njsAccount = await timeoutTagged( + 'newAWEKI', + 10000, + njsWallet.newAccountWithExtendedKeyInfo(extendedInfos), + ) } - const unsub = await coreSyncAccount(core, njsAccount) + const unsub = await timeoutTagged('coreSyncAccount', 30000, coreSyncAccount(core, njsAccount)) unsub() const query = njsAccount.queryOperations() - const ops = await query.complete().execute() - const njsBalance = await njsAccount.getBalance() + const ops = await timeoutTagged('ops', 30000, query.complete().execute()) + const njsBalance = await timeoutTagged('getBalance', 10000, njsAccount.getBalance()) const syncedRawAccount = await buildAccountRaw({ njsAccount, diff --git a/src/helpers/promise.js b/src/helpers/promise.js index ad494a16..0ae2d09f 100644 --- a/src/helpers/promise.js +++ b/src/helpers/promise.js @@ -2,6 +2,7 @@ // small utilities for Promises import logger from 'logger' +import { TimeoutTagged } from 'config/errors' export const delay = (ms: number): Promise => new Promise(f => setTimeout(f, ms)) @@ -65,6 +66,23 @@ export function createCancelablePolling( return { unsubscribe, promise } } +export const timeoutTagged = (tag: string, delay: number, promise: Promise): Promise => + new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new TimeoutTagged('timeout', { tag })) + }, delay) + promise.then( + r => { + clearTimeout(timeout) + resolve(r) + }, + e => { + clearTimeout(timeout) + reject(e) + }, + ) + }) + export const promisify = (fn: any) => (...args: any) => new Promise((resolve, reject) => fn(...args, (err: Error, res: any) => { diff --git a/static/i18n/en/errors.json b/static/i18n/en/errors.json index 2249bec7..154b6da5 100644 --- a/static/i18n/en/errors.json +++ b/static/i18n/en/errors.json @@ -115,6 +115,10 @@ "title": "Oops, a time out occurred", "description": "It took too long for the server to respond." }, + "TimeoutTagged": { + "title": "Oops, a time out occurred ({{tag}})", + "description": "It took too long for the server to respond." + }, "TransportError": { "title": "Something went wrong. Please reconnect your device.", "description": "{{message}}" From c1572d58f829ef1cf9de779ae3fed480da0e2bde Mon Sep 17 00:00:00 2001 From: amougel Date: Mon, 30 Jul 2018 17:55:03 +0200 Subject: [PATCH 04/10] Remove LEDGER_RESET_ALL --- README.md | 1 - src/config/constants.js | 1 - src/renderer/init.js | 6 ------ 3 files changed, 8 deletions(-) diff --git a/README.md b/README.md index fd4d0465..98bdda85 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,6 @@ DEBUG_ACTION=1 DEBUG_TAB_KEY=1 DEBUG_LIBCORE=1 DEBUG_WS=1 -LEDGER_RESET_ALL=1 LEDGER_DEBUG_ALL_LANGS=1 SKIP_GENUINE=1 SKIP_ONBOARDING=1 diff --git a/src/config/constants.js b/src/config/constants.js index aefe35b3..2734edff 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -80,7 +80,6 @@ export const DEBUG_TAB_KEY = boolFromEnv('DEBUG_TAB_KEY') export const DEBUG_LIBCORE = boolFromEnv('DEBUG_LIBCORE') export const DEBUG_WS = boolFromEnv('DEBUG_WS') export const DEBUG_SYNC = boolFromEnv('DEBUG_SYNC') -export const LEDGER_RESET_ALL = boolFromEnv('LEDGER_RESET_ALL') export const LEDGER_DEBUG_ALL_LANGS = boolFromEnv('LEDGER_DEBUG_ALL_LANGS') export const SKIP_GENUINE = boolFromEnv('SKIP_GENUINE') export const SKIP_ONBOARDING = boolFromEnv('SKIP_ONBOARDING') diff --git a/src/renderer/init.js b/src/renderer/init.js index 52fd35f7..4f7278df 100644 --- a/src/renderer/init.js +++ b/src/renderer/init.js @@ -12,7 +12,6 @@ import { runMigrations } from 'migrations' import createStore from 'renderer/createStore' import events from 'renderer/events' -import { LEDGER_RESET_ALL } from 'config/constants' import { enableGlobalTab, disableGlobalTab, isGlobalTabEnabled } from 'config/global-tab' import { fetchAccounts } from 'actions/accounts' @@ -25,7 +24,6 @@ import resolveUserDataDirectory from 'helpers/resolveUserDataDirectory' import db from 'helpers/db' import dbMiddleware from 'middlewares/db' import CounterValues from 'helpers/countervalues' -import hardReset from 'helpers/hardReset' import { decodeAccountsModel, encodeAccountsModel } from 'reducers/accounts' @@ -43,10 +41,6 @@ const TAB_KEY = 9 db.init(userDataDirectory) async function init() { - if (LEDGER_RESET_ALL) { - await hardReset() - } - await runMigrations() db.init(userDataDirectory) db.registerTransform('app', 'accounts', { get: decodeAccountsModel, set: encodeAccountsModel }) From ddce9589c52acd2bd08761a996409c0f2f79ece1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 30 Jul 2018 21:05:29 +0200 Subject: [PATCH 05/10] More modular portfolio split into components --- .../DashboardPage/AccountCardList.js | 66 +++++++ .../DashboardPage/AccountCardListHeader.js | 33 ++++ .../DashboardPage/AccountCardPlaceholder.js | 64 ++++++ .../DashboardPage/CurrentGreetings.js | 31 +++ src/components/DashboardPage/SummaryDesc.js | 22 +++ src/components/DashboardPage/index.js | 186 ++++-------------- 6 files changed, 258 insertions(+), 144 deletions(-) create mode 100644 src/components/DashboardPage/AccountCardList.js create mode 100644 src/components/DashboardPage/AccountCardListHeader.js create mode 100644 src/components/DashboardPage/AccountCardPlaceholder.js create mode 100644 src/components/DashboardPage/CurrentGreetings.js create mode 100644 src/components/DashboardPage/SummaryDesc.js diff --git a/src/components/DashboardPage/AccountCardList.js b/src/components/DashboardPage/AccountCardList.js new file mode 100644 index 00000000..eee7321e --- /dev/null +++ b/src/components/DashboardPage/AccountCardList.js @@ -0,0 +1,66 @@ +// @flow + +import React, { Component } from 'react' +import type { Account, Currency } from '@ledgerhq/live-common/lib/types' + +import Box from 'components/base/Box' +import AccountCard from './AccountCard' +import AccountCardListHeader from './AccountCardListHeader' +import AccountCardPlaceholder from './AccountCardPlaceholder' + +type Props = { + accounts: Account[], + onAccountClick: Account => void, + counterValue: Currency, + daysCount: number, +} + +class AccountCardList extends Component { + render() { + const { accounts, counterValue, daysCount, onAccountClick } = this.props + + return ( + + + + {accounts + .map(account => ({ + key: account.id, + account, + })) + .concat( + Array(3 - (accounts.length % 3)) + .fill(null) + .map((_, i) => ({ + key: `placeholder_${i}`, + withPlaceholder: i === 0, + })), + ) + .map(item => ( + + {item.account ? ( + + ) : item.withPlaceholder ? ( + + ) : null} + + ))} + + + ) + } +} + +export default AccountCardList diff --git a/src/components/DashboardPage/AccountCardListHeader.js b/src/components/DashboardPage/AccountCardListHeader.js new file mode 100644 index 00000000..9da0fde5 --- /dev/null +++ b/src/components/DashboardPage/AccountCardListHeader.js @@ -0,0 +1,33 @@ +// @flow + +import React, { PureComponent } from 'react' +import { translate } from 'react-i18next' +import type { T } from 'types/common' + +import Box from 'components/base/Box' +import Text from 'components/base/Text' +import AccountsOrder from './AccountsOrder' + +type Props = { + t: T, + accountsLength: number, +} + +class AccountCardListHeader extends PureComponent { + render() { + const { accountsLength, t } = this.props + + return ( + + + {t('app:dashboard.accounts.title', { count: accountsLength })} + + + + + + ) + } +} + +export default translate()(AccountCardListHeader) diff --git a/src/components/DashboardPage/AccountCardPlaceholder.js b/src/components/DashboardPage/AccountCardPlaceholder.js new file mode 100644 index 00000000..1936fe4d --- /dev/null +++ b/src/components/DashboardPage/AccountCardPlaceholder.js @@ -0,0 +1,64 @@ +// @flow + +import React, { PureComponent } from 'react' +import { connect } from 'react-redux' +import { translate } from 'react-i18next' +import styled from 'styled-components' + +import { openModal } from 'reducers/modals' +import { MODAL_ADD_ACCOUNTS } from 'config/constants' +import type { T } from 'types/common' +import { i } from 'helpers/staticPath' +import Box from 'components/base/Box' +import Button from 'components/base/Button' + +const Wrapper = styled(Box).attrs({ + p: 4, + flex: 1, + alignItems: 'center', +})` + border: 1px dashed ${p => p.theme.colors.fog}; + border-radius: 4px; + height: 215px; +` + +class AccountCardPlaceholder extends PureComponent<{ + t: T, + openModal: string => void, +}> { + onAddAccounts = () => this.props.openModal(MODAL_ADD_ACCOUNTS) + + render() { + const { t } = this.props + return ( + + + + + + {t('app:dashboard.emptyAccountTile.desc')} + + + + ) + } +} + +export default translate()( + connect( + null, + { + openModal, + }, + )(AccountCardPlaceholder), +) diff --git a/src/components/DashboardPage/CurrentGreetings.js b/src/components/DashboardPage/CurrentGreetings.js new file mode 100644 index 00000000..da4896b8 --- /dev/null +++ b/src/components/DashboardPage/CurrentGreetings.js @@ -0,0 +1,31 @@ +// @flow +import React, { PureComponent } from 'react' +import { translate } from 'react-i18next' +import type { T } from 'types/common' + +import Text from 'components/base/Text' + +const getCurrentGreetings = () => { + const localTimeHour = new Date().getHours() + const afternoon_breakpoint = 12 + const evening_breakpoint = 17 + if (localTimeHour >= afternoon_breakpoint && localTimeHour < evening_breakpoint) { + return 'app:dashboard.greeting.afternoon' + } else if (localTimeHour >= evening_breakpoint) { + return 'app:dashboard.greeting.evening' + } + return 'app:dashboard.greeting.morning' +} + +class CurrentGettings extends PureComponent<{ t: T }> { + render() { + const { t } = this.props + return ( + + {t(getCurrentGreetings())} + + ) + } +} + +export default translate()(CurrentGettings) diff --git a/src/components/DashboardPage/SummaryDesc.js b/src/components/DashboardPage/SummaryDesc.js new file mode 100644 index 00000000..ff8c5f3e --- /dev/null +++ b/src/components/DashboardPage/SummaryDesc.js @@ -0,0 +1,22 @@ +// @flow + +import React, { PureComponent } from 'react' +import { translate } from 'react-i18next' +import type { T } from 'types/common' +import Text from 'components/base/Text' + +class SummaryDesc extends PureComponent<{ + t: T, + totalAccounts: number, +}> { + render() { + const { totalAccounts, t } = this.props + return ( + + {t('app:dashboard.summary', { count: totalAccounts })} + + ) + } +} + +export default translate()(SummaryDesc) diff --git a/src/components/DashboardPage/index.js b/src/components/DashboardPage/index.js index 384f6d71..2b3b6340 100644 --- a/src/components/DashboardPage/index.js +++ b/src/components/DashboardPage/index.js @@ -5,7 +5,6 @@ import uniq from 'lodash/uniq' import { compose } from 'redux' import { translate } from 'react-i18next' import { connect } from 'react-redux' -import styled from 'styled-components' import { push } from 'react-router-redux' import { createStructuredSelector } from 'reselect' import type { Account, Currency } from '@ledgerhq/live-common/lib/types' @@ -14,11 +13,8 @@ import type { T } from 'types/common' import { colors } from 'styles/theme' import { accountsSelector } from 'reducers/accounts' -import { openModal } from 'reducers/modals' -import { MODAL_ADD_ACCOUNTS } from 'config/constants' import { counterValueCurrencySelector, - localeSelector, selectedTimeRangeSelector, timeRangeDaysByKey, } from 'reducers/settings' @@ -32,27 +28,23 @@ import UpdateNotifier from 'components/UpdateNotifier' import BalanceInfos from 'components/BalanceSummary/BalanceInfos' import BalanceSummary from 'components/BalanceSummary' import Box from 'components/base/Box' -import { i } from 'helpers/staticPath' import PillsDaysCount from 'components/PillsDaysCount' -import Text from 'components/base/Text' import OperationsList from 'components/OperationsList' import StickyBackToTop from 'components/StickyBackToTop' -import Button from 'components/base/Button' -import AccountCard from './AccountCard' -import AccountsOrder from './AccountsOrder' import EmptyState from './EmptyState' +import CurrentGreetings from './CurrentGreetings' +import SummaryDesc from './SummaryDesc' +import AccountCardList from './AccountCardList' const mapStateToProps = createStructuredSelector({ accounts: accountsSelector, counterValue: counterValueCurrencySelector, - locale: localeSelector, selectedTimeRange: selectedTimeRangeSelector, }) const mapDispatchToProps = { push, saveSettings, - openModal, } type Props = { @@ -62,41 +54,33 @@ type Props = { counterValue: Currency, selectedTimeRange: TimeRange, saveSettings: ({ selectedTimeRange: TimeRange }) => *, - openModal: string => void, } class DashboardPage extends PureComponent { onAccountClick = account => this.props.push(`/account/${account.id}`) - handleGreeting = () => { - const localTimeHour = new Date().getHours() - const afternoon_breakpoint = 12 - const evening_breakpoint = 17 - - if (localTimeHour >= afternoon_breakpoint && localTimeHour < evening_breakpoint) { - return 'app:dashboard.greeting.afternoon' - } else if (localTimeHour >= evening_breakpoint) { - return 'app:dashboard.greeting.evening' - } - return 'app:dashboard.greeting.morning' - } - handleChangeSelectedTime = item => { this.props.saveSettings({ selectedTimeRange: item.key }) } - _cacheBalance = null + renderHeader = ({ isAvailable, totalBalance, selectedTimeRange, sinceBalance, refBalance }) => ( + + ) render() { - const { accounts, t, counterValue, selectedTimeRange, openModal } = this.props + const { accounts, t, counterValue, selectedTimeRange } = this.props const daysCount = timeRangeDaysByKey[selectedTimeRange] - const timeFrame = this.handleGreeting() - const imagePath = i('empty-account-tile.svg') const totalAccounts = accounts.length const totalCurrencies = uniq(accounts.map(a => a.currency.id)).length const totalOperations = accounts.reduce((sum, a) => sum + a.operations.length, 0) - const displayOperationsHelper = (account: Account) => account.operations.length > 0 - const displayOperations = accounts.some(displayOperationsHelper) return ( @@ -113,12 +97,8 @@ class DashboardPage extends PureComponent { - - {t(timeFrame)} - - - {t('app:dashboard.summary', { count: totalAccounts })} - + + { /> - - + + + + {totalOperations > 0 && ( + ( - - )} + title={t('app:dashboard.recentActivity')} + withAccount /> - - - - {t('app:dashboard.accounts.title', { count: accounts.length })} - - - - - - - {accounts - .concat( - Array(3 - (accounts.length % 3)) - .fill(null) - .map((_, i) => i === 0), - ) - .map((account, i) => ( - - {account ? ( - typeof account === 'object' ? ( - - ) : ( - - - - - - {t('app:dashboard.emptyAccountTile.desc')} - - - - ) - ) : null} - - ))} - - - {displayOperations && ( - - )} - - + )} + ) : ( @@ -243,13 +151,3 @@ export default compose( ), translate(), )(DashboardPage) - -const Wrapper = styled(Box).attrs({ - p: 4, - flex: 1, - alignItems: 'center', -})` - border: 1px dashed ${p => p.theme.colors.fog}; - border-radius: 4px; - height: 215px; -` From 24915b72318cd07fac11bcc47bb2fa859b2b89a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Tue, 31 Jul 2018 08:36:10 +0200 Subject: [PATCH 06/10] Split AccountCard as well --- src/components/DashboardPage/AccountCard.js | 103 ------------------ .../DashboardPage/AccountCard/Header.js | 33 ++++++ .../DashboardPage/AccountCard/index.js | 94 ++++++++++++++++ 3 files changed, 127 insertions(+), 103 deletions(-) delete mode 100644 src/components/DashboardPage/AccountCard.js create mode 100644 src/components/DashboardPage/AccountCard/Header.js create mode 100644 src/components/DashboardPage/AccountCard/index.js diff --git a/src/components/DashboardPage/AccountCard.js b/src/components/DashboardPage/AccountCard.js deleted file mode 100644 index a9881a2f..00000000 --- a/src/components/DashboardPage/AccountCard.js +++ /dev/null @@ -1,103 +0,0 @@ -// @flow - -import React, { PureComponent } from 'react' -import styled from 'styled-components' - -import type { Account, Currency } from '@ledgerhq/live-common/lib/types' - -import Chart from 'components/base/Chart' -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' - -const Wrapper = styled(Card).attrs({ - p: 4, - flex: 1, -})` - cursor: ${p => (p.onClick ? 'pointer' : 'default')}; -` - -class AccountCard extends PureComponent<{ - counterValue: Currency, - account: Account, - onClick?: Account => void, - daysCount: number, -}> { - render() { - const { counterValue, account, onClick, daysCount, ...props } = this.props - return ( - onClick(account) : null} {...props}> - - - - - - - - {account.currency.name} - - - {account.name} - - - - - - - - - - {({ isAvailable, balanceHistory, balanceStart, balanceEnd }) => ( - - - - {isAvailable ? ( - - ) : null} - - - {isAvailable && !balanceStart.isZero() ? ( - - ) : null} - - - - - )} - - - ) - } -} - -export default AccountCard diff --git a/src/components/DashboardPage/AccountCard/Header.js b/src/components/DashboardPage/AccountCard/Header.js new file mode 100644 index 00000000..642ec56e --- /dev/null +++ b/src/components/DashboardPage/AccountCard/Header.js @@ -0,0 +1,33 @@ +// @flow + +import React, { PureComponent } from 'react' +import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' +import Box from 'components/base/Box' +import Ellipsis from 'components/base/Ellipsis' +import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon' + +class AccountCardHeader extends PureComponent<{ + currency: CryptoCurrency, + accountName: string, +}> { + render() { + const { currency, accountName } = this.props + return ( + + + + + + + {currency.name} + + + {accountName} + + + + ) + } +} + +export default AccountCardHeader diff --git a/src/components/DashboardPage/AccountCard/index.js b/src/components/DashboardPage/AccountCard/index.js new file mode 100644 index 00000000..25dc16d4 --- /dev/null +++ b/src/components/DashboardPage/AccountCard/index.js @@ -0,0 +1,94 @@ +// @flow + +import React, { PureComponent } from 'react' +import styled from 'styled-components' + +import type { Account, Currency } from '@ledgerhq/live-common/lib/types' + +import Chart from 'components/base/Chart' +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 DeltaChange from 'components/DeltaChange' +import AccountCardHeader from './Header' + +const Wrapper = styled(Card).attrs({ + p: 4, + flex: 1, +})` + cursor: ${p => (p.onClick ? 'pointer' : 'default')}; +` + +class AccountCard extends PureComponent<{ + counterValue: Currency, + account: Account, + onClick: Account => void, + daysCount: number, +}> { + renderBody = ({ isAvailable, balanceHistory, balanceStart, balanceEnd }: *) => { + const { counterValue, account } = this.props + return ( + + + + {isAvailable ? ( + + ) : null} + + + {isAvailable && !balanceStart.isZero() ? ( + + ) : null} + + + + + ) + } + onClick = () => { + const { account, onClick } = this.props + onClick(account) + } + render() { + const { counterValue, account, onClick, daysCount, ...props } = this.props + return ( + + + + + + + + + + {this.renderBody} + + + ) + } +} + +export default AccountCard From ddf5d548dc47b28488b49873da7fb021ffa06c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Tue, 31 Jul 2018 08:37:14 +0200 Subject: [PATCH 07/10] update wording --- static/i18n/en/app.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/i18n/en/app.json b/static/i18n/en/app.json index da867665..4c84564d 100644 --- a/static/i18n/en/app.json +++ b/static/i18n/en/app.json @@ -196,7 +196,7 @@ "successDescription_plural": "Your accounts have been created.", "createNewAccount": { "title": "Add a new account", - "noOperationOnLastAccount": "There are no transactions on your last created <1><0>{{accountName}} account. You must first receive a transaction on that account before you can add a new one.", + "noOperationOnLastAccount": "There are no transactions on your last created <1><0>{{accountName}} account. You must first receive crypto assets on that account before you can add a new one.", "noAccountToCreate": "No <1><0>{{currencyName}} account was found to create" }, "cta": { From caa193018088a6d7b7a3a15bf6aefad4abaf4cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Tue, 31 Jul 2018 11:09:23 +0200 Subject: [PATCH 08/10] Simplify listenDevices --- README.md | 1 - src/commands/listenDevices.js | 57 +++++------------------------------ src/config/constants.js | 3 +- 3 files changed, 9 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index fd4d0465..7ebf21be 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,6 @@ SYNC_ALL_INTERVAL=60000 CHECK_APP_INTERVAL_WHEN_INVALID=600 CHECK_APP_INTERVAL_WHEN_VALID=1200 CHECK_UPDATE_DELAY=5000 -DEVICE_DISCONNECT_DEBOUNCE=500 ``` ### Launch storybook diff --git a/src/commands/listenDevices.js b/src/commands/listenDevices.js index 7a186b7d..d0c9f48f 100644 --- a/src/commands/listenDevices.js +++ b/src/commands/listenDevices.js @@ -4,58 +4,17 @@ import logger from 'logger' import { createCommand } from 'helpers/ipc' import { Observable } from 'rxjs' import CommNodeHid from '@ledgerhq/hw-transport-node-hid' -import { DEVICE_DISCONNECT_DEBOUNCE, LISTEN_DEVICES_POLLING_INTERVAL } from 'config/constants' +import { LISTEN_DEVICES_DEBOUNCE } from 'config/constants' -CommNodeHid.setListenDevicesPollingInterval(LISTEN_DEVICES_POLLING_INTERVAL) -CommNodeHid.setListenDevicesDebug(true) +CommNodeHid.setListenDevicesPollingInterval(LISTEN_DEVICES_DEBOUNCE) -const cmd = createCommand('listenDevices', () => - Observable.create(o => { - const pendingRemovePerPath = {} - const sub = CommNodeHid.listen({ - next: e => { - // debounce the add/remove in case we see quick `remove,add` events on same path. - switch (e.type) { - case 'add': { - const pendingRemove = pendingRemovePerPath[e.descriptor] - if (pendingRemove) { - logger.warn(`Skipping remove/add usb event for ${e.descriptor}`) - // there where a recent "remove" event, we don't emit add because we didn't emit "remove" yet. - clearTimeout(pendingRemove) - delete pendingRemovePerPath[e.descriptor] - } else { - // if there were no recent "remove", we just emit the "add" - o.next(e) - } - break - } - case 'remove': { - // we we always debounce the "remove" event. emit it a bit later in case a "add" of same descriptor happen soon. - if (pendingRemovePerPath[e.descriptor]) { - clearTimeout(pendingRemovePerPath[e.descriptor]) - } - pendingRemovePerPath[e.descriptor] = setTimeout(() => { - delete pendingRemovePerPath[e.descriptor] - o.next(e) - }, DEVICE_DISCONNECT_DEBOUNCE) - break - } - default: - o.next(e) - } - }, - complete: () => { - o.complete() - }, - error: err => { - o.error(err) - }, - }) - return () => { - Object.keys(pendingRemovePerPath).map(k => clearTimeout(pendingRemovePerPath[k])) - sub.unsubscribe() - } +CommNodeHid.setListenDevicesDebug((msg, ...args) => + logger.debug(msg, { + type: 'listenDevices', + args, }), ) +const cmd = createCommand('listenDevices', () => Observable.create(CommNodeHid.listen)) + export default cmd diff --git a/src/config/constants.js b/src/config/constants.js index aefe35b3..3090cfe9 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -32,13 +32,12 @@ export const MIN_HEIGHT = intFromEnv('LEDGER_MIN_HEIGHT', 700) export const CHECK_APP_INTERVAL_WHEN_INVALID = 600 export const CHECK_APP_INTERVAL_WHEN_VALID = 1200 export const CHECK_UPDATE_DELAY = 5000 -export const DEVICE_DISCONNECT_DEBOUNCE = intFromEnv('LEDGER_DEVICE_DISCONNECT_DEBOUNCE', 1000) export const DEVICE_INFOS_TIMEOUT = intFromEnv('DEVICE_INFOS_TIMEOUT', 5 * 1000) export const GENUINE_CACHE_DELAY = intFromEnv('GENUINE_CACHE_DELAY', 1000) export const GENUINE_TIMEOUT = intFromEnv('GENUINE_TIMEOUT', 120 * 1000) export const GET_CALLS_RETRY = intFromEnv('GET_CALLS_RETRY', 2) export const GET_CALLS_TIMEOUT = intFromEnv('GET_CALLS_TIMEOUT', 30 * 1000) -export const LISTEN_DEVICES_POLLING_INTERVAL = intFromEnv('LISTEN_DEVICES_POLLING_INTERVAL', 1000) +export const LISTEN_DEVICES_DEBOUNCE = intFromEnv('LISTEN_DEVICES_DEBOUNCE', 200) // NB: technically speaking OUTDATED_CONSIDERED_DELAY should be set to ZERO. // but we'll only do that when we're sure the sync is performant and all is working smoothly export const OUTDATED_CONSIDERED_DELAY = intFromEnv('OUTDATED_CONSIDERED_DELAY', 2 * 60 * 1000) From 9bb448a3b0444969520a86327931101e6922e8a3 Mon Sep 17 00:00:00 2001 From: meriadec Date: Tue, 31 Jul 2018 11:12:45 +0200 Subject: [PATCH 09/10] Fix outline button active style --- src/components/base/Button/index.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/components/base/Button/index.js b/src/components/base/Button/index.js index 4bf4839e..1d7a0444 100644 --- a/src/components/base/Button/index.js +++ b/src/components/base/Button/index.js @@ -93,16 +93,22 @@ const buttonStyles: { [_: string]: Style } = { background: ${rgba(c, 0.1)}; ` }, - active: p => ` - color: ${darken( - p.outlineColor ? p.theme.colors[p.outlineColor] || p.outlineColor : p.theme.colors.wallet, - 0.1, - )}; - border-color: ${darken( - p.outlineColor ? p.theme.colors[p.outlineColor] || p.outlineColor : p.theme.colors.wallet, - 0.1, - )}; - `, + active: p => { + const c = p.outlineColor + ? p.theme.colors[p.outlineColor] || p.outlineColor + : p.theme.colors.wallet + return ` + background: ${rgba(c, 0.15)}; + color: ${darken( + p.outlineColor ? p.theme.colors[p.outlineColor] || p.outlineColor : p.theme.colors.wallet, + 0.1, + )}; + border-color: ${darken( + p.outlineColor ? p.theme.colors[p.outlineColor] || p.outlineColor : p.theme.colors.wallet, + 0.1, + )}; + ` + }, }, outlineGrey: { default: p => ` From a348cd1d206b740b84e627f847587f9afdafe68b Mon Sep 17 00:00:00 2001 From: meriadec Date: Tue, 31 Jul 2018 11:12:03 +0200 Subject: [PATCH 10/10] Fix storybook --- src/helpers/linking.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/helpers/linking.js b/src/helpers/linking.js index e810f123..36855a21 100644 --- a/src/helpers/linking.js +++ b/src/helpers/linking.js @@ -1,12 +1,17 @@ // @flow -import { shell } from 'electron' import { track } from 'analytics/segment' +let shell +if (!process.env.STORYBOOK_ENV) { + const electron = require('electron') + shell = electron.shell +} + export const openURL = ( url: string, customEventName: string = 'OpenURL', extraParams: Object = {}, ) => { track(customEventName, { ...extraParams, url }) - shell.openExternal(url) + shell && shell.openExternal(url) }