diff --git a/.storybook/config.js b/.storybook/config.js index 4cdff471..8cb01114 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -6,8 +6,8 @@ import { ThemeProvider } from 'styled-components' import { I18nextProvider } from 'react-i18next' import { Provider } from 'react-redux' +import 'babel-polyfill' import 'globals' - import 'styles/global' import theme from 'styles/theme' import i18n from 'renderer/i18n/storybook' diff --git a/package.json b/package.json index 8153074c..2b7643af 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@ledgerhq/hw-app-eth": "^4.7.3", "@ledgerhq/hw-transport": "^4.7.3", "@ledgerhq/hw-transport-node-hid": "^4.7.6", - "@ledgerhq/live-common": "^2.0.0", + "@ledgerhq/live-common": "^2.2.0-beta.10", "axios": "^0.18.0", "babel-runtime": "^6.26.0", "bcryptjs": "^2.4.3", diff --git a/src/__mocks__/storybook-state.js b/src/__mocks__/storybook-state.js index 47e81bd5..036894ec 100644 --- a/src/__mocks__/storybook-state.js +++ b/src/__mocks__/storybook-state.js @@ -1,15 +1,18 @@ +import { genStoreState } from '@ledgerhq/live-common/lib/countervalues/mock' +import { + getCryptoCurrencyById, + getFiatCurrencyByTicker, +} from '@ledgerhq/live-common/lib/helpers/currencies' + export default { - counterValues: { - BTC: { - USD: { - '2018-01-09': 0.00795978, - '2018-03-29': 0.007106619999999999, - '2018-03-30': 0.0068537599999999995, - '2018-03-31': 0.00694377, - '2018-04-01': 0.00683584, - '2018-04-02': 0.007061689999999999, - latest: 0.00706156, - }, + countervalues: genStoreState([ + { + from: getCryptoCurrencyById('bitcoin'), + to: getFiatCurrencyByTicker('USD'), + exchange: 'KRAKEN', + dateFrom: new Date(2015, 1, 1), + dateTo: new Date(), + rate: d => 0.007 + 0.003 * Math.max(0, (d / 1e12 + Math.sin(d / 1e9)) / 2), }, - }, + ]), } diff --git a/src/actions/accounts.js b/src/actions/accounts.js index 666df151..061ca480 100644 --- a/src/actions/accounts.js +++ b/src/actions/accounts.js @@ -7,9 +7,7 @@ import db from 'helpers/db' import type { Dispatch } from 'redux' -import { fetchCounterValues } from 'actions/counterValues' - -import { startSyncAccounts, startSyncCounterValues } from 'renderer/events' +import { startSyncAccounts } from 'renderer/events' function sortAccounts(accounts, orderAccounts) { const [order, sort] = orderAccounts.split('|') @@ -44,7 +42,7 @@ export const updateOrderAccounts: UpdateOrderAccounts = (orderAccounts: string) export type AddAccount = Account => (Function, Function) => void export const addAccount: AddAccount = payload => (dispatch, getState) => { const { - settings: { counterValue, orderAccounts }, + settings: { orderAccounts }, accounts, } = getState() dispatch({ type: 'ADD_ACCOUNT', payload }) @@ -53,7 +51,6 @@ export const addAccount: AddAccount = payload => (dispatch, getState) => { // Start sync accounts the first time you add an account if (accounts.length === 0) { const accounts = [payload] - startSyncCounterValues(counterValue, accounts) startSyncAccounts(accounts) } } @@ -64,7 +61,7 @@ export const removeAccount: RemoveAccount = payload => ({ payload, }) -export type FetchAccounts = () => (Function, Function) => Promise<*, *> +export type FetchAccounts = () => (Function, Function) => * export const fetchAccounts: FetchAccounts = () => (dispatch, getState) => { const { settings: { orderAccounts }, @@ -74,7 +71,6 @@ export const fetchAccounts: FetchAccounts = () => (dispatch, getState) => { type: 'SET_ACCOUNTS', payload: sortAccounts(accounts, orderAccounts), }) - return dispatch(fetchCounterValues()) } export type UpdateAccount = Account => (Function, Function) => void diff --git a/src/actions/counterValues.js b/src/actions/counterValues.js deleted file mode 100644 index a7ee2eca..00000000 --- a/src/actions/counterValues.js +++ /dev/null @@ -1,43 +0,0 @@ -// @flow - -import type { Dispatch } from 'redux' -import { fetchHistodayRates } from '@ledgerhq/live-common/lib/api/countervalue' -import type { CryptoCurrency, Currency } from '@ledgerhq/live-common/lib/types' - -import { counterValueCurrencySelector } from 'reducers/settings' -import { currenciesSelector } from 'reducers/accounts' -import db from 'helpers/db' -import type { State } from 'reducers' - -export type InitCounterValues = () => { type: string, payload: Object } -export const initCounterValues: InitCounterValues = () => ({ - type: 'UPDATE_COUNTER_VALUES', - payload: db.get('counterValues'), -}) - -export type UpdateCounterValues = Object => { type: string, payload: Object } -export const updateCounterValues: UpdateCounterValues = payload => ({ - type: 'DB:UPDATE_COUNTER_VALUES', - payload, -}) - -function cryptoCurrenciesToCurrencies(currencies: CryptoCurrency[]): Currency[] { - // $FlowFixMe this function is just to fix flow types. array contravariant issue. - return currencies -} - -export type FetchCounterValues = ( - currencies?: Currency[], -) => (Dispatch<*>, () => State) => Promise - -export const fetchCounterValues: FetchCounterValues = currencies => async (dispatch, getState) => { - const state = getState() - const currency = counterValueCurrencySelector(state) - if (!currency) return - if (!currencies) { - // TODO this should be default, there is no need to provide the currencies in parameter - currencies = cryptoCurrenciesToCurrencies(currenciesSelector(state)) - } - const counterValues = await fetchHistodayRates(currencies, currency) - dispatch(updateCounterValues(counterValues)) -} diff --git a/src/actions/settings.js b/src/actions/settings.js index 6c5b2652..1565662d 100644 --- a/src/actions/settings.js +++ b/src/actions/settings.js @@ -1,9 +1,8 @@ // @flow -import db from 'helpers/db' - import type { Dispatch } from 'redux' import type { Settings } from 'types/common' +import type { Currency } from '@ledgerhq/live-common/lib/types' export type SaveSettings = Settings => { type: string, payload: Settings } export const saveSettings: SaveSettings = payload => ({ @@ -11,14 +10,23 @@ export const saveSettings: SaveSettings = payload => ({ payload, }) -type FetchSettings = () => (Dispatch<*>) => void -export const fetchSettings: FetchSettings = () => dispatch => { - const settings = db.get('settings') - if (Object.keys(settings).length === 0) { - return - } +type FetchSettings = (*) => (Dispatch<*>) => void +export const fetchSettings: FetchSettings = (settings: *) => dispatch => { dispatch({ type: 'FETCH_SETTINGS', payload: settings, }) } + +type SetExchangePairs = ( + Array<{ + from: Currency, + to: Currency, + exchange: string, + }>, +) => * + +export const setExchangePairsAction: SetExchangePairs = pairs => ({ + type: 'SETTINGS_SET_PAIRS', + pairs, +}) diff --git a/src/components/App.js b/src/components/App.js index b51a631e..1a362709 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -18,6 +18,7 @@ import ThrowBlock from 'components/ThrowBlock' import Default from 'components/layout/Default' import Dev from 'components/layout/Dev' import Print from 'components/layout/Print' +import CounterValues from 'helpers/countervalues' const { DEV_TOOLS } = process.env @@ -31,20 +32,22 @@ const App = ({ language: string, }) => ( - - - - - - - {(__DEV__ || DEV_TOOLS) && } - - - - - - - + + + + + + + + {(__DEV__ || DEV_TOOLS) && } + + + + + + + + ) diff --git a/src/components/BalanceSummary/BalanceInfos.js b/src/components/BalanceSummary/BalanceInfos.js index 2e8768d2..99463c2a 100644 --- a/src/components/BalanceSummary/BalanceInfos.js +++ b/src/components/BalanceSummary/BalanceInfos.js @@ -8,6 +8,7 @@ import type { T } from 'types/common' import Box from 'components/base/Box' import FormattedVal from 'components/base/FormattedVal' +import DeltaChange from '../DeltaChange' const Sub = styled(Box).attrs({ ff: 'Open Sans', @@ -39,12 +40,12 @@ export function BalanceSincePercent(props: BalanceSinceProps) { const { t, totalBalance, sinceBalance, refBalance, since, ...otherProps } = props return ( - {t(`time:since.${since}`)} diff --git a/src/components/BalanceSummary/index.js b/src/components/BalanceSummary/index.js index 62022750..17bd93ef 100644 --- a/src/components/BalanceSummary/index.js +++ b/src/components/BalanceSummary/index.js @@ -17,7 +17,12 @@ type Props = { accounts: Account[], selectedTime: string, daysCount: number, - renderHeader: null | Function, + renderHeader?: ({ + selectedTime: *, + totalBalance: number, + sinceBalance: number, + refBalance: number, + }) => *, } const BalanceSummary = ({ @@ -38,40 +43,46 @@ const BalanceSummary = ({ counterValue={counterValue} daysCount={daysCount} onCalculate={onCalculate} - render={({ allBalances, totalBalance, sinceBalance, refBalance }) => ( - - {renderHeader !== null && ( - - {renderHeader({ - totalBalance, - selectedTime, - sinceBalance, - refBalance, - })} + > + {({ isAvailable, balanceHistory, balanceStart, balanceEnd }) => + !isAvailable ? null : ( + + {renderHeader ? ( + + {renderHeader({ + selectedTime, + // FIXME refactor these + totalBalance: balanceEnd, + sinceBalance: balanceStart, + refBalance: balanceStart, + })} + + ) : null} + + + isAvailable ? ( + + ) : null + } + /> - )} - - ( - - )} - /> - - - )} - /> + + ) + } + ) } diff --git a/src/components/CalculateBalance.js b/src/components/CalculateBalance.js index d52f8374..7c4337ce 100644 --- a/src/components/CalculateBalance.js +++ b/src/components/CalculateBalance.js @@ -1,70 +1,61 @@ // @flow +/* eslint-disable react/no-unused-prop-types */ import { PureComponent } from 'react' import { connect } from 'react-redux' -import noop from 'lodash/noop' +import type { Account, BalanceHistory } from '@ledgerhq/live-common/lib/types' +import { getBalanceHistorySum } from '@ledgerhq/live-common/lib/helpers/account' +import CounterValues from 'helpers/countervalues' +import { exchangeSettingsForAccountSelector, counterValueCurrencySelector } from 'reducers/settings' +import type { State } from 'reducers' -import type { Account } from '@ledgerhq/live-common/lib/types' - -import calculateBalance from 'helpers/balance' - -const mapStateToProps = state => ({ - counterValues: state.counterValues, -}) - -type Props = { +type OwnProps = { accounts: Account[], - counterValues: Object, daysCount: number, - onCalculate: Function, - render: Function, + children: Props => *, } -type State = { - allBalances: Array, - totalBalance: number, - sinceBalance: number, - refBalance: number, +type Props = OwnProps & { + balanceHistory: BalanceHistory, + balanceStart: number, + balanceEnd: number, + isAvailable: boolean, } -function calculateBalanceToState(props: Object) { - const { accounts, counterValue, counterValues, daysCount } = props - +const mapStateToProps = (state: State, props: OwnProps) => { + const counterValueCurrency = counterValueCurrencySelector(state) + let isAvailable = true + const balanceHistory = getBalanceHistorySum( + props.accounts, + props.daysCount, + (account, value, date) => { + const cv = CounterValues.calculateSelector(state, { + value, + date, + to: counterValueCurrency, + from: account.currency, + exchange: exchangeSettingsForAccountSelector(state, { account }), + }) + if (!cv && cv !== 0) { + isAvailable = false + return 0 + } + return cv + }, + ) return { - ...calculateBalance({ accounts, counterValue, counterValues, daysCount }), + isAvailable, + balanceHistory, + balanceStart: balanceHistory[0].value, + balanceEnd: balanceHistory[balanceHistory.length - 1].value, } } -class CalculateBalance extends PureComponent { - static defaultProps = { - onCalculate: noop, - } - - constructor(props) { - super(props) - - const state = calculateBalanceToState(props) - props.onCalculate(state) - this.state = state - } - - componentWillReceiveProps(nextProps) { - const sameAccounts = this.props.accounts === nextProps.accounts - const sameCounterValues = this.props.counterValues === nextProps.counterValues - const sameDaysCount = this.props.daysCount === nextProps.daysCount - if (!sameAccounts || !sameCounterValues || !sameDaysCount) { - const state = calculateBalanceToState(nextProps) - nextProps.onCalculate(state) - this.setState(state) - } - } - +class CalculateBalance extends PureComponent { render() { - const { render } = this.props - const { allBalances, totalBalance, sinceBalance, refBalance } = this.state - - return render({ allBalances, totalBalance, sinceBalance, refBalance }) + const { children } = this.props + return children(this.props) } } diff --git a/src/components/CounterValue/__tests__/CounterValue.test.js b/src/components/CounterValue/__tests__/CounterValue.test.js deleted file mode 100644 index 8984c8f1..00000000 --- a/src/components/CounterValue/__tests__/CounterValue.test.js +++ /dev/null @@ -1,63 +0,0 @@ -// @flow - -import React from 'react' - -import render from '__mocks__/render' -import CounterValue from '..' - -describe('components', () => { - describe('CounterValue', () => { - it('basic', () => { - const state = { counterValues: { BTC: { USD: { latest: 10e2 } } } } - const component = - const tree = render(component, state) - expect(tree).toMatchSnapshot() - }) - - it('specifying ticker different from default', () => { - const state = { counterValues: { LOL: { USD: { latest: 5e2 } } } } - const component = - const tree = render(component, state) - expect(tree).toMatchSnapshot() - }) - - it('using countervalue different from default', () => { - const state = { - counterValues: { BTC: { EUR: { latest: 42 } } }, - settings: { - counterValue: 'EUR', - }, - } - const component = - const tree = render(component, state) - expect(tree).toMatchSnapshot() - }) - - it('without countervalues populated', () => { - const state = { counterValues: {} } - const component = - const tree = render(component, state) - expect(tree).toMatchSnapshot() - }) - - it('with time travel whith date in countervalues', () => { - const state = { counterValues: { BTC: { USD: { '2018-01-01': 20e2 } } } } - - const date = new Date('2018-01-01') - const component = - const tree = render(component, state) - expect(tree).toMatchSnapshot() - }) - - it('with time travel whith date not in countervalues', () => { - const state = { counterValues: { BTC: { USD: { '2018-01-01': 20e2 } } } } - const date = new Date('2018-01-02') - const component = - const tree = render(component, state) - - // TODO: actually it returns 0 when date is not in countervalues - // do we want to use closest past value instead? - expect(tree).toMatchSnapshot() - }) - }) -}) diff --git a/src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap b/src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap deleted file mode 100644 index aec99b44..00000000 --- a/src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap +++ /dev/null @@ -1,55 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components CounterValue basic 1`] = ` -
- + USD 10.00 -
-`; - -exports[`components CounterValue specifying ticker different from default 1`] = ` -
- + USD 5.00 -
-`; - -exports[`components CounterValue using countervalue different from default 1`] = ` -
- + EUR 0.42 -
-`; - -exports[`components CounterValue with time travel whith date in countervalues 1`] = ` -
- + USD 20.00 -
-`; - -exports[`components CounterValue with time travel whith date not in countervalues 1`] = ` -
- + USD 0.00 -
-`; - -exports[`components CounterValue without countervalues populated 1`] = ` -
- + USD 0.00 -
-`; diff --git a/src/components/CounterValue/index.js b/src/components/CounterValue/index.js index 50c89100..4f026e5f 100644 --- a/src/components/CounterValue/index.js +++ b/src/components/CounterValue/index.js @@ -13,9 +13,8 @@ import type { State } from 'reducers' type OwnProps = { // wich market to query - // FIXME drop ticker in favor of currency - ticker: string, - currency?: Currency, + currency: Currency, + exchange: string, // when? if not given: take latest date?: Date, @@ -26,28 +25,17 @@ type OwnProps = { type Props = OwnProps & { // from reducers counterValueCurrency: Currency, - value: number, + value: ?number, } const mapStateToProps = (state: State, props: OwnProps) => { - const { ticker, value, date } = props - - if (ticker) { - // FIXME actually ticker should be deprecated, not currency!! - console.warn('CounterValue: `currency` should be passed instead of `ticker`') // eslint-disable-line no-console - } - - let { currency } = props - if (!currency && ticker) { - currency = generateFakeCurrency(ticker) - } - + const { currency, value, date, exchange } = props const counterValueCurrency = counterValueCurrencySelector(state) - const counterValue = - !counterValueCurrency || !currency - ? 0 - : calculateCounterValueSelector(state)(currency, counterValueCurrency)(value, date) - + const counterValue = calculateCounterValueSelector(state)( + currency, + counterValueCurrency, + exchange, + )(value, date) return { counterValueCurrency, value: counterValue, @@ -57,6 +45,7 @@ const mapStateToProps = (state: State, props: OwnProps) => { class CounterValue extends PureComponent { render() { const { value, counterValueCurrency, date, ...props } = this.props + if (!value && value !== 0) return null return ( { } } -function generateFakeCurrency(ticker) { - return { - ticker, - units: [ - { - code: ticker, - - // unused - name: 'fake-unit', - magnitude: 0, - }, - ], - - // unused - id: '', - color: '#000', - name: 'fake-coin', - scheme: 'bitcoin', - } -} - export default connect(mapStateToProps)(CounterValue) diff --git a/src/components/CounterValue/stories.js b/src/components/CounterValue/stories.js index 0bffa520..edf10f81 100644 --- a/src/components/CounterValue/stories.js +++ b/src/components/CounterValue/stories.js @@ -12,5 +12,5 @@ const stories = storiesOf('Components', module) const currency = getCryptoCurrencyById('bitcoin') stories.add('CounterValue', () => ( - + )) diff --git a/src/components/DashboardPage/AccountCard.js b/src/components/DashboardPage/AccountCard.js index 8512f680..a5fca80e 100644 --- a/src/components/DashboardPage/AccountCard.js +++ b/src/components/DashboardPage/AccountCard.js @@ -11,6 +11,7 @@ import Box, { Card } from 'components/base/Box' import CalculateBalance from 'components/CalculateBalance' import FormattedVal from 'components/base/FormattedVal' import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon' +import DeltaChange from '../DeltaChange' const Wrapper = styled(Card).attrs({ p: 4, @@ -57,35 +58,31 @@ const AccountCard = ({ /> - ( + + {({ isAvailable, balanceHistory, balanceStart, balanceEnd }) => ( - + {isAvailable ? ( + + ) : null} - + {balanceStart && isAvailable ? ( + + ) : null} )} - /> + ) diff --git a/src/components/DeltaChange.js b/src/components/DeltaChange.js new file mode 100644 index 00000000..0178aeda --- /dev/null +++ b/src/components/DeltaChange.js @@ -0,0 +1,17 @@ +// @flow +import React, { PureComponent } from 'react' +import FormattedVal from 'components/base/FormattedVal' + +class DeltaChange extends PureComponent<{ + from: number, + to: number, +}> { + render() { + const { from, to, ...rest } = this.props + const val = from ? Math.floor((to - from) / from * 100) : 0 + // TODO in future, we also want to diverge rendering when the % is way too high (this can easily happen) + return + } +} + +export default DeltaChange diff --git a/src/components/IsUnlocked.js b/src/components/IsUnlocked.js index c2da407b..d269520b 100644 --- a/src/components/IsUnlocked.js +++ b/src/components/IsUnlocked.js @@ -15,18 +15,12 @@ import { ErrorMessageInput } from 'components/base/Input' import get from 'lodash/get' -import { - startSyncCounterValues, - startSyncAccounts, - stopSyncAccounts, - stopSyncCounterValues, -} from 'renderer/events' +import { startSyncAccounts, stopSyncAccounts } from 'renderer/events' import { setEncryptionKey } from 'helpers/db' import { fetchAccounts } from 'actions/accounts' import { getAccounts } from 'reducers/accounts' import { isLocked, unlock } from 'reducers/application' -import { getCounterValueCode } from 'reducers/settings' import Box from 'components/base/Box' import InputPassword from 'components/base/InputPassword' @@ -38,7 +32,6 @@ type InputValue = { type Props = { accounts: Account[], children: any, - counterValue: string, fetchAccounts: Function, isLocked: boolean, settings: Settings, @@ -52,7 +45,6 @@ type State = { const mapStateToProps = state => ({ accounts: getAccounts(state), - counterValue: getCounterValueCode(state), isLocked: isLocked(state), settings: state.settings, }) @@ -94,19 +86,16 @@ class IsUnlocked extends Component { componentWillMount() { if (this.props.isLocked) { - stopSyncCounterValues() stopSyncAccounts() } } componentWillReceiveProps(nextProps) { if (this.props.isLocked && !nextProps.isLocked) { - startSyncCounterValues(nextProps.counterValue, nextProps.accounts) startSyncAccounts(nextProps.accounts) } if (!this.props.isLocked && nextProps.isLocked) { - stopSyncCounterValues() stopSyncAccounts() } } diff --git a/src/components/OperationsList/Operation.js b/src/components/OperationsList/Operation.js index c3baafd1..e92fcc9b 100644 --- a/src/components/OperationsList/Operation.js +++ b/src/components/OperationsList/Operation.js @@ -3,6 +3,7 @@ import React, { PureComponent } from 'react' import { connect } from 'react-redux' import styled from 'styled-components' +import { createStructuredSelector } from 'reselect' import moment from 'moment' import noop from 'lodash/noop' import { getCryptoCurrencyIcon } from '@ledgerhq/live-common/lib/react' @@ -11,7 +12,7 @@ import type { Account, Operation as OperationType } from '@ledgerhq/live-common/ import type { T } from 'types/common' -import { currencySettingsSelector, marketIndicatorSelector } from 'reducers/settings' +import { currencySettingsForAccountSelector, marketIndicatorSelector } from 'reducers/settings' import { rgba, getMarketColor } from 'styles/helpers' import Box from 'components/base/Box' @@ -21,9 +22,9 @@ import FormattedVal from 'components/base/FormattedVal' import ConfirmationCheck from './ConfirmationCheck' -const mapStateToProps = (state, props) => ({ - minConfirmations: currencySettingsSelector(state, props.account.currency).confirmationsNb, - marketIndicator: marketIndicatorSelector(state), +const mapStateToProps = createStructuredSelector({ + currencySettings: currencySettingsForAccountSelector, + marketIndicator: marketIndicatorSelector, }) const DATE_COL_SIZE = 100 @@ -49,9 +50,9 @@ const OperationRaw = styled(Box).attrs({ ` const Address = ({ value }: { value: string }) => { - if (!value) { - return - } + if (!value) { + return + } const addrSize = value.length / 2 @@ -101,7 +102,7 @@ const Cell = styled(Box).attrs({ type Props = { account: Account, - minConfirmations: number, + currencySettings: *, onAccountClick: Function, onOperationClick: Function, marketIndicator: string, @@ -120,7 +121,7 @@ class Operation extends PureComponent { render() { const { account, - minConfirmations, + currencySettings, onAccountClick, onOperationClick, t, @@ -144,7 +145,7 @@ class Operation extends PureComponent { { color="grey" fontSize={3} date={time.toDate()} - ticker={currency.units[0].code} + currency={currency} value={op.amount} + exchange={currencySettings.exchange} /> diff --git a/src/components/RequestAmount/index.js b/src/components/RequestAmount/index.js index 12ec263f..2b84332e 100644 --- a/src/components/RequestAmount/index.js +++ b/src/components/RequestAmount/index.js @@ -4,12 +4,13 @@ import React, { PureComponent } from 'react' import { compose } from 'redux' import { translate } from 'react-i18next' import styled from 'styled-components' +import { createStructuredSelector } from 'reselect' import { connect } from 'react-redux' -import type { Currency, Account, CalculateCounterValue } from '@ledgerhq/live-common/lib/types' +import type { Currency, Account } from '@ledgerhq/live-common/lib/types' import type { T } from 'types/common' -import { counterValueCurrencySelector } from 'reducers/settings' +import { counterValueCurrencySelector, exchangeSettingsForAccountSelector } from 'reducers/settings' import { calculateCounterValueSelector, reverseCounterValueSelector } from 'reducers/counterValues' import InputCurrency from 'components/base/InputCurrency' @@ -34,10 +35,12 @@ const InputCenter = styled(Box).attrs({ width: 30px; ` -const mapStateToProps = state => ({ - rightCurrency: counterValueCurrencySelector(state), - getCounterValue: calculateCounterValueSelector(state), - getReverseCounterValue: reverseCounterValueSelector(state), +const mapStateToProps = createStructuredSelector({ + exchange: exchangeSettingsForAccountSelector, + // $FlowFixMe + rightCurrency: counterValueCurrencySelector, + getCounterValue: calculateCounterValueSelector, + getReverseCounterValue: reverseCounterValueSelector, }) type Props = { @@ -53,6 +56,8 @@ type Props = { // change handler onChange: number => void, + exchange: string, + // used to determine the left input unit account: Account, @@ -61,8 +66,8 @@ type Props = { rightCurrency: Currency, // used to calculate the opposite field value (right & left) - getCounterValue: CalculateCounterValue, - getReverseCounterValue: CalculateCounterValue, + getCounterValue: *, + getReverseCounterValue: *, // display max button withMax: boolean, @@ -79,18 +84,18 @@ export class RequestAmount extends PureComponent { } handleChangeAmount = (changedField: string) => (val: number) => { - const { rightCurrency, getReverseCounterValue, account, max, onChange } = this.props + const { rightCurrency, getReverseCounterValue, account, max, onChange, exchange } = this.props if (changedField === 'left') { onChange(val > max ? max : val) } else if (changedField === 'right') { - const leftVal = getReverseCounterValue(account.currency, rightCurrency)(val) + const leftVal = getReverseCounterValue(account.currency, rightCurrency, exchange)(val) || 0 onChange(leftVal > max ? max : leftVal) } } renderInputs(containerProps: Object) { - const { value, account, rightCurrency, getCounterValue } = this.props - const right = getCounterValue(account.currency, rightCurrency)(value) + const { value, account, rightCurrency, getCounterValue, exchange } = this.props + const right = getCounterValue(account.currency, rightCurrency, exchange)(value) || 0 const rightUnit = rightCurrency.units[0] return ( diff --git a/src/components/SelectCurrency/index.js b/src/components/SelectCurrency/index.js index 249b0cb5..35e55c83 100644 --- a/src/components/SelectCurrency/index.js +++ b/src/components/SelectCurrency/index.js @@ -2,12 +2,13 @@ import React from 'react' import { translate } from 'react-i18next' -import { listCryptoCurrencies } from '@ledgerhq/live-common/lib/helpers/currencies' +import { connect } from 'react-redux' import noop from 'lodash/noop' import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' import type { T } from 'types/common' +import { availableCurrencies } from 'reducers/settings' import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon' import Select from 'components/base/Select' @@ -27,16 +28,23 @@ const renderItem = (currency: CryptoCurrency) => { ) } -const currencies = listCryptoCurrencies().sort((a, b) => a.name.localeCompare(b.name)) - -type Props = { +type OwnProps = { onChange: Function, + currencies?: CryptoCurrency[], value?: CryptoCurrency, placeholder: string, t: T, } -const SelectCurrency = ({ onChange, value, t, placeholder, ...props }: Props) => ( +type Props = OwnProps & { + currencies: CryptoCurrency[], +} + +const mapStateToProps = (state, props: OwnProps) => ({ + currencies: props.currencies || availableCurrencies(state), +}) + +const SelectCurrency = ({ onChange, value, t, placeholder, currencies, ...props }: Props) => ( e.id === exchangeId)} + renderSelected={renderItem} + renderItem={renderItem} + keyProp="id" + items={exchanges} + fontSize={4} + onChange={onChange} + /> + ) : error ? ( + + Failed to load. + + ) : ( + + Loading... + + ) + } +} + +export default ExchangeSelect diff --git a/src/components/SettingsPage/index.js b/src/components/SettingsPage/index.js index 5b480b8d..81f1ec87 100644 --- a/src/components/SettingsPage/index.js +++ b/src/components/SettingsPage/index.js @@ -9,10 +9,8 @@ import { Switch, Route } from 'react-router' import type { RouterHistory, Match, Location } from 'react-router' import type { Settings, T } from 'types/common' import type { SaveSettings } from 'actions/settings' -import type { FetchCounterValues } from 'actions/counterValues' import { saveSettings } from 'actions/settings' -import { fetchCounterValues } from 'actions/counterValues' import Pills from 'components/base/Pills' import Box from 'components/base/Box' @@ -22,17 +20,17 @@ import SectionCurrencies from './sections/Currencies' import SectionProfile from './sections/Profile' import SectionAbout from './sections/About' +// FIXME this component should not be connected. each single tab should be. +// maybe even each single settings row should be connected!! const mapStateToProps = state => ({ settings: state.settings, }) const mapDispatchToProps = { - fetchCounterValues, saveSettings, } type Props = { - fetchCounterValues: FetchCounterValues, history: RouterHistory, i18n: Object, location: Location, @@ -102,16 +100,6 @@ class SettingsPage extends PureComponent { } } - handleSaveSettings = newSettings => { - const { fetchCounterValues, saveSettings, settings } = this.props - - saveSettings(newSettings) - - if (newSettings.counterValue !== settings.counterValue) { - fetchCounterValues() - } - } - render() { const { match, settings, t, i18n, saveSettings } = this.props const { tab } = this.state diff --git a/src/components/SettingsPage/sections/Currencies.js b/src/components/SettingsPage/sections/Currencies.js index 0a60073e..0e43bae8 100644 --- a/src/components/SettingsPage/sections/Currencies.js +++ b/src/components/SettingsPage/sections/Currencies.js @@ -1,14 +1,25 @@ // @flow -import React, { PureComponent } from 'react' +// TODO refactoring: +// this component shouldn't accept the full settings object, actually it needs to be connected to nothing, +// it doesn't need saveSettings nor settings, instead, it just need to track selected Currency and delegate to 2 new components: +// - a new ConnectedSelectCurrency , that filters only the currency that comes from accounts (use the existing selector) +// - a new CurrencySettings component, that receives currency & will connect to the store to grab the relevant settings as well as everything it needs (counterValueCurrency), it also takes a saveCurrencySettings action (create if not existing) -import { listCryptoCurrencies } from '@ledgerhq/live-common/lib/helpers/currencies' +import React, { PureComponent } from 'react' +import { connect } from 'react-redux' +import { createStructuredSelector } from 'reselect' -import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' +import type { CryptoCurrency, Currency } from '@ledgerhq/live-common/lib/types' import type { Settings, CurrencySettings, T } from 'types/common' +import { counterValueCurrencySelector } from 'reducers/settings' +import { currenciesSelector } from 'reducers/accounts' +import CounterValues from 'helpers/countervalues' + import SelectCurrency from 'components/SelectCurrency' import StepperNumber from 'components/base/StepperNumber' +import ExchangeSelect from 'components/SelectExchange' import IconCurrencies from 'icons/Currencies' @@ -34,9 +45,13 @@ const CURRENCY_DEFAULTS_SETTINGS: CurrencySettings = { maxConfirmationsNb: 50, transactionFees: 10, + + exchange: '', } type Props = { + counterValueCurrency: Currency, + currencies: CryptoCurrency[], settings: Settings, saveSettings: Function, t: T, @@ -46,9 +61,14 @@ type State = { currency: CryptoCurrency, } +const mapStateToProps = createStructuredSelector({ + counterValueCurrency: counterValueCurrencySelector, + currencies: currenciesSelector, +}) + class TabCurrencies extends PureComponent { state = { - currency: listCryptoCurrencies()[0], + currency: this.props.currencies[0], } getCurrencySettings() { @@ -60,11 +80,15 @@ class TabCurrencies extends PureComponent { handleChangeCurrency = (currency: CryptoCurrency) => this.setState({ currency }) handleChangeConfirmationsToSpend = (nb: number) => - this.updateCurrencySetting('confirmationsToSpend', nb) + this.updateCurrencySettings('confirmationsToSpend', nb) + + handleChangeConfirmationsNb = (nb: number) => this.updateCurrencySettings('confirmationsNb', nb) - handleChangeConfirmationsNb = (nb: number) => this.updateCurrencySetting('confirmationsNb', nb) + handleChangeExchange = exchange => + this.updateCurrencySettings('exchange', exchange ? exchange.id : '') - updateCurrencySetting = (key: string, val: number) => { + updateCurrencySettings = (key: string, val: *) => { + // FIXME this really should be a dedicated action const { settings, saveSettings } = this.props const { currency } = this.state const currencySettings = this.getCurrencySettings() @@ -90,9 +114,9 @@ class TabCurrencies extends PureComponent { } render() { - const { t } = this.props + const { t, currencies, counterValueCurrency } = this.props const { currency } = this.state - const { confirmationsToSpend, confirmationsNb } = + const { confirmationsToSpend, confirmationsNb, exchange } = this.getCurrencySettings() || CURRENCY_DEFAULTS_SETTINGS return (
@@ -101,15 +125,34 @@ class TabCurrencies extends PureComponent { title={t('settings:tabs.currencies')} desc="Lorem ipsum dolor sit amet" renderRight={ + // TODO this should only be the subset of currencies of the app } /> + + + {polling => ( + // TODO move to a dedicated "row" component + { + this.handleChangeExchange(exchange) + polling.poll() + }} + style={{ minWidth: 200 }} + /> + )} + + { } } -export default TabCurrencies +// $FlowFixMe not sure what's wrong +export default connect(mapStateToProps)(TabCurrencies) diff --git a/src/components/SettingsPage/sections/Display.js b/src/components/SettingsPage/sections/Display.js index df8b1b01..2a34beb1 100644 --- a/src/components/SettingsPage/sections/Display.js +++ b/src/components/SettingsPage/sections/Display.js @@ -10,6 +10,7 @@ import Select from 'components/base/Select' import RadioGroup from 'components/base/RadioGroup' import IconDisplay from 'icons/Display' import languageKeys from 'config/languages' +import CounterValues from 'helpers/countervalues' import regionsByKey from 'helpers/regions.json' @@ -134,15 +135,22 @@ class TabProfile extends PureComponent { title={t('settings:display.counterValue')} desc={t('settings:display.counterValueDesc')} > - { + this.handleChangeCounterValue(item) + polling.poll() + }} + itemToString={item => (item ? item.name : '')} + renderSelected={item => item && item.name} + items={fiats} + value={cachedCounterValue} + /> + )} +