diff --git a/src/__mocks__/render.js b/src/__mocks__/render.js index 7b945e49..76871dd6 100644 --- a/src/__mocks__/render.js +++ b/src/__mocks__/render.js @@ -2,8 +2,10 @@ import React from 'react' import { Provider } from 'react-redux' import renderer from 'react-test-renderer' import { ThemeProvider } from 'styled-components' +import { I18nextProvider } from 'react-i18next' import createStore from 'renderer/createStore' +import i18n from 'renderer/i18n/electron' import theme from 'styles/theme' @@ -11,9 +13,11 @@ export default function render(component, state) { const store = createStore({ state }) return renderer .create( - - {component} - , + + + {component} + + , ) .toJSON() } diff --git a/src/components/AccountPage/index.js b/src/components/AccountPage/index.js index 1a750a1d..8615d921 100644 --- a/src/components/AccountPage/index.js +++ b/src/components/AccountPage/index.js @@ -168,11 +168,7 @@ class AccountPage extends PureComponent { )} /> - + ) } diff --git a/src/components/DashboardPage/index.js b/src/components/DashboardPage/index.js index 2e664db5..d1388651 100644 --- a/src/components/DashboardPage/index.js +++ b/src/components/DashboardPage/index.js @@ -5,11 +5,9 @@ import { compose } from 'redux' import { translate } from 'react-i18next' import { connect } from 'react-redux' import { push } from 'react-router-redux' -import type { Account, Operation } from '@ledgerhq/wallet-common/lib/types' +import type { Account } from '@ledgerhq/wallet-common/lib/types' import chunk from 'lodash/chunk' -import get from 'lodash/get' -import sortBy from 'lodash/sortBy' import type { T } from 'types/common' @@ -49,33 +47,11 @@ type Props = { type State = { accountsChunk: Array>, - allOperations: Operation[], selectedTime: string, daysCount: number, } const ACCOUNTS_BY_LINE = 3 -const ALL_OPERATIONS_LIMIT = 10 - -const getAllOperations = accounts => { - const allOperations = accounts.reduce((result, account) => { - const operations = get(account, 'operations', []) - - result = [ - ...result, - ...operations.map(t => ({ - ...t, - account, - })), - ] - - return result - }, []) - - return sortBy(allOperations, t => t.date) - .reverse() - .slice(0, ALL_OPERATIONS_LIMIT) -} const getAccountsChunk = accounts => { // create shallow copy of accounts, to be mutated @@ -89,7 +65,6 @@ const getAccountsChunk = accounts => { class DashboardPage extends PureComponent { state = { accountsChunk: getAccountsChunk(this.props.accounts), - allOperations: getAllOperations(this.props.accounts), selectedTime: 'week', daysCount: 7, } @@ -98,7 +73,6 @@ class DashboardPage extends PureComponent { if (nextProps.accounts !== this.props.accounts) { this.setState({ accountsChunk: getAccountsChunk(nextProps.accounts), - allOperations: getAllOperations(nextProps.accounts), }) } } @@ -111,7 +85,7 @@ class DashboardPage extends PureComponent { render() { const { push, accounts, t, counterValue } = this.props - const { accountsChunk, allOperations, selectedTime, daysCount } = this.state + const { accountsChunk, selectedTime, daysCount } = this.state const totalAccounts = accounts.length @@ -193,7 +167,7 @@ class DashboardPage extends PureComponent { push(`/account/${account.id}`)} - operations={allOperations} + accounts={accounts} title={t('dashboard:recentActivity')} withAccount /> diff --git a/src/components/OperationsList/index.js b/src/components/OperationsList/index.js index 3d8103d1..65561511 100644 --- a/src/components/OperationsList/index.js +++ b/src/components/OperationsList/index.js @@ -1,16 +1,20 @@ // @flow -import React, { Component } from 'react' +import React, { PureComponent } from 'react' import styled from 'styled-components' import moment from 'moment' import { connect } from 'react-redux' import { compose } from 'redux' import { translate } from 'react-i18next' import { getIconByCoinType } from '@ledgerhq/currencies/react' +import { + groupAccountOperationsByDay, + groupAccountsOperationsByDay, +} from '@ledgerhq/wallet-common/lib/helpers/account' import type { Account, Operation as OperationType } from '@ledgerhq/wallet-common/lib/types' import noop from 'lodash/noop' -import isEqual from 'lodash/isEqual' +import keyBy from 'lodash/keyBy' import type { T } from 'types/common' @@ -22,9 +26,9 @@ import IconAngleDown from 'icons/AngleDown' import Box, { Card } from 'components/base/Box' import CounterValue from 'components/CounterValue' -import Defer from 'components/base/Defer' import FormattedVal from 'components/base/FormattedVal' import Text from 'components/base/Text' +import Defer from 'components/base/Defer' import ConfirmationCheck from './ConfirmationCheck' @@ -33,6 +37,14 @@ const ACCOUNT_COL_SIZE = 150 const AMOUNT_COL_SIZE = 150 const CONFIRMATION_COL_SIZE = 44 +const calendarOpts = { + sameDay: 'LL – [Today]', + nextDay: 'LL – [Tomorrow]', + lastDay: 'LL – [Yesterday]', + lastWeek: 'LL', + sameElse: 'LL', +} + const Day = styled(Text).attrs({ color: 'dark', fontSize: 3, @@ -69,6 +81,7 @@ const Cell = styled(Box).attrs({ alignItems: 'center', })` width: ${p => (p.size ? `${p.size}px` : '')}; + overflow: ${p => (p.noOverflow ? 'hidden' : '')}; ` const ShowMore = styled(Box).attrs({ @@ -155,6 +168,7 @@ const Operation = ({ {withAccount && account && ( { +export class OperationsList extends PureComponent { static defaultProps = { - account: null, onAccountClick: noop, withAccount: false, canShowMore: false, + nbToShow: 20, } - shouldComponentUpdate(nextProps: Props) { - if (this.props.account !== nextProps.account) { - return true - } - - if (this.props.withAccount !== nextProps.withAccount) { - return true - } - - if (this.props.canShowMore !== nextProps.canShowMore) { - return true - } - - if (this._hashCache === null) { - return true - } - - return !isEqual(this._hashCache, this.getHashCache(nextProps.operations)) - } - - getHashCache = (operations: OperationType[]) => operations.map(t => t.id) - handleClickOperation = (data: Object) => this.props.openModal(MODAL_OPERATION_DETAILS, data) - _hashCache = null - render() { - const { account, canShowMore, onAccountClick, operations, t, title, withAccount } = this.props + const { + account, + title, + accounts, + canShowMore, + onAccountClick, + t, + withAccount, + nbToShow, + } = this.props + + if (!account && !accounts) { + console.warn('Preventing render OperationsList because not received account or accounts') // eslint-disable-line no-console + return null + } + const groupedOperations = accounts + ? groupAccountsOperationsByDay(accounts, nbToShow) + : groupAccountOperationsByDay(account, nbToShow) - this._hashCache = this.getHashCache(operations) + const accountsMap = accounts ? keyBy(accounts, 'id') : { [account.id]: account } return ( - - - - {operations.map(op => { - // $FlowFixMe - const acc = account || op.account - return ( - - ) - })} - - + + {title && ( + + {title} + + )} + {groupedOperations.map(group => { + const d = moment(group.day) + return ( + + + {d.calendar(null, calendarOpts)} + + + {group.data.map(op => { + const account = accountsMap[op.accountId] + if (!account) { + return null + } + return ( + + ) + })} + + + ) + })} {canShowMore && ( {t('operationsList:showMore')} diff --git a/src/components/OperationsList/stories.js b/src/components/OperationsList/stories.js index ebe1f11b..b144eac1 100644 --- a/src/components/OperationsList/stories.js +++ b/src/components/OperationsList/stories.js @@ -1,78 +1,24 @@ // @flow import React from 'react' -import { getCurrencyByCoinType, getDefaultUnitByCoinType } from '@ledgerhq/currencies' +import { genAccount } from '@ledgerhq/wallet-common/lib/mock/account' import { storiesOf } from '@storybook/react' import { boolean } from '@storybook/addon-knobs' -import { accounts } from 'components/SelectAccount/stories' - import OperationsList from 'components/OperationsList' +import Box from 'components/base/Box' const stories = storiesOf('Components', module) -const unit = getDefaultUnitByCoinType(0) - -const account = ({ name }) => ({ - ...accounts[0], - minConfirmations: 10, - currency: getCurrencyByCoinType(0), - name, - coinType: 0, - unit, -}) - -const operations = [ - { - address: '5c6ea1716520c7d6e038d36a3223faced3c', - hash: '5c6ea1716520c7d6e038d36a3223faced3c4b8f7ffb69d9fb5bd527d562fdb62', - id: '5c6ea1716520c7d6e038d36a3223faced3c4b8f7ffb69d9fb5bd527d562fdb62', - amount: 1.3e8, - date: new Date('2018-01-09T16:03:52Z'), - confirmations: 1, - account: account({ - name: 'Account 1', - }), - }, - { - address: '5c6ea1716520c7d6e038d36a3223faced3c', - hash: '26bdf265d725db5bf9d96bff7f8b4c3decaf3223a63d830e6d7c0256171ae6c5', - id: '26bdf265d725db5bf9d96bff7f8b4c3decaf3223a63d830e6d7c0256171ae6c5', - amount: 1.6e8, - date: new Date('2018-01-09T16:03:52Z'), - confirmations: 11, - account: account({ - name: 'Account 1', - }), - }, - { - address: '27416a48caab90fab053b507b8b6b9d4', - hash: '27416a48caab90fab053b507b8b6b9d48fba75421d3bfdbae4b85f64024bc9c4', - id: '27416a48caab90fab053b507b8b6b9d48fba75421d3bfdbae4b85f64024bc9c4', - amount: -6.5e8, - date: new Date('2018-01-09T16:02:40Z'), - confirmations: 11, - account: account({ - name: 'Account 2', - }), - }, - { - address: '27416a48caab90fab053b507b8b6b9d4', - hash: '4c9cb42046f58b4eabdfb3d12457abf84d9b6b8b705b350baf09baac84a61472', - id: '4c9cb42046f58b4eabdfb3d12457abf84d9b6b8b705b350baf09baac84a61472', - amount: -4.2e8, - date: new Date('2018-01-09T16:02:40Z'), - confirmations: 1, - account: account({ - name: 'Account 2', - }), - }, -] +const account1 = genAccount('account1') +const account2 = genAccount('account2') stories.add('OperationsList', () => ( - + + + )) diff --git a/src/components/SettingsPage/index.js b/src/components/SettingsPage/index.js index 826455a7..725549f2 100644 --- a/src/components/SettingsPage/index.js +++ b/src/components/SettingsPage/index.js @@ -4,6 +4,7 @@ import React, { PureComponent } from 'react' import { compose } from 'redux' import { connect } from 'react-redux' import { translate } from 'react-i18next' +import moment from 'moment' import type { Settings, T } from 'types/common' import type { SaveSettings } from 'actions/settings' @@ -56,6 +57,7 @@ class SettingsPage extends PureComponent { if (newSettings.language !== settings.language) { i18n.changeLanguage(newSettings.language) + moment.locale(newSettings.language) } if (newSettings.counterValue !== settings.counterValue) { diff --git a/src/helpers/staticPath.js b/src/helpers/staticPath.js index 1351ec63..102fe5a7 100644 --- a/src/helpers/staticPath.js +++ b/src/helpers/staticPath.js @@ -7,4 +7,4 @@ export default (__DEV__ && !STORYBOOK_ENV && NODE_ENV !== 'test' ? __static : isRunningInAsar ? __dirname.replace(/app\.asar$/, 'static') - : !STORYBOOK_ENV ? `${__dirname}/../static` : 'static') + : !STORYBOOK_ENV ? `${__dirname}/../../static` : 'static') diff --git a/src/renderer/i18n/instanciate.js b/src/renderer/i18n/instanciate.js index d8a6eec8..a4ff9e7b 100644 --- a/src/renderer/i18n/instanciate.js +++ b/src/renderer/i18n/instanciate.js @@ -4,7 +4,7 @@ const commonConfig = { fallbackLng: 'en', debug: false, react: { - wait: true, + wait: process.env.NODE_ENV !== 'test', }, } diff --git a/src/renderer/init.js b/src/renderer/init.js index f7dae0a8..63a5acb4 100644 --- a/src/renderer/init.js +++ b/src/renderer/init.js @@ -5,6 +5,7 @@ import { remote } from 'electron' import { render } from 'react-dom' import { AppContainer } from 'react-hot-loader' import createHistory from 'history/createHashHistory' +import moment from 'moment' import createStore from 'renderer/createStore' import events from 'renderer/events' @@ -37,6 +38,8 @@ const state = store.getState() || {} const language = getLanguage(state) const locked = isLocked(state) +moment.locale(language) + function r(Comp) { if (rootNode) { render({Comp}, rootNode)