Browse Source

Integrate countervalues changes

master
Gaëtan Renaudeau 7 years ago
parent
commit
cc87c1f8ed
  1. 2
      .storybook/config.js
  2. 2
      package.json
  3. 27
      src/__mocks__/storybook-state.js
  4. 10
      src/actions/accounts.js
  5. 43
      src/actions/counterValues.js
  6. 24
      src/actions/settings.js
  7. 31
      src/components/App.js
  8. 7
      src/components/BalanceSummary/BalanceInfos.js
  9. 79
      src/components/BalanceSummary/index.js
  10. 91
      src/components/CalculateBalance.js
  11. 63
      src/components/CounterValue/__tests__/CounterValue.test.js
  12. 55
      src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap
  13. 52
      src/components/CounterValue/index.js
  14. 2
      src/components/CounterValue/stories.js
  15. 41
      src/components/DashboardPage/AccountCard.js
  16. 17
      src/components/DeltaChange.js
  17. 13
      src/components/IsUnlocked.js
  18. 24
      src/components/OperationsList/Operation.js
  19. 29
      src/components/RequestAmount/index.js
  20. 20
      src/components/SelectCurrency/index.js
  21. 110
      src/components/SelectExchange.js
  22. 16
      src/components/SettingsPage/index.js
  23. 64
      src/components/SettingsPage/sections/Currencies.js
  24. 26
      src/components/SettingsPage/sections/Display.js
  25. 28
      src/components/TopBar.js
  26. 2
      src/components/base/Box/index.js
  27. 4
      src/components/base/Chart/helpers.js
  28. 4
      src/components/base/Chart/index.js
  29. 2
      src/components/base/Chart/stories.js
  30. 2
      src/components/base/Chart/types.js
  31. 12
      src/components/base/FormattedVal/__tests__/__snapshots__/FormattedVal.test.js.snap
  32. 7
      src/components/base/FormattedVal/index.js
  33. 9
      src/components/modals/AddAccount/index.js
  34. 18
      src/components/modals/OperationDetails.js
  35. 15
      src/components/modals/Send/Footer.js
  36. 1
      src/config/constants.js
  37. 166
      src/helpers/balance.js
  38. 49
      src/helpers/countervalues.js
  39. 4
      src/helpers/db.js
  40. 4
      src/main/bridge.js
  41. 14
      src/main/counterValuesSync.js
  42. 12
      src/middlewares/db.js
  43. 53
      src/reducers/counterValues.js
  44. 8
      src/reducers/index.js
  45. 77
      src/reducers/settings.js
  46. 38
      src/renderer/events.js
  47. 14
      src/renderer/init.js
  48. 2
      src/types/common.js
  49. 13
      yarn.lock

2
.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'

2
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",

27
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),
},
},
]),
}

10
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

43
src/actions/counterValues.js

@ -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<any>
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))
}

24
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,
})

31
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,
}) => (
<Provider store={store}>
<I18nextProvider i18n={i18n} initialLanguage={language}>
<ThemeProvider theme={theme}>
<ThrowBlock>
<Onboarding />
<ConnectedRouter history={history}>
<Switch>
{(__DEV__ || DEV_TOOLS) && <Route path="/dev" component={Dev} />}
<Route path="/print" component={Print} />
<Route component={Default} />
</Switch>
</ConnectedRouter>
</ThrowBlock>
</ThemeProvider>
</I18nextProvider>
<CounterValues.PollingProvider>
<I18nextProvider i18n={i18n} initialLanguage={language}>
<ThemeProvider theme={theme}>
<ThrowBlock>
<Onboarding />
<ConnectedRouter history={history}>
<Switch>
{(__DEV__ || DEV_TOOLS) && <Route path="/dev" component={Dev} />}
<Route path="/print" component={Print} />
<Route component={Default} />
</Switch>
</ConnectedRouter>
</ThrowBlock>
</ThemeProvider>
</I18nextProvider>
</CounterValues.PollingProvider>
</Provider>
)

7
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 (
<Box {...otherProps}>
<FormattedVal
<DeltaChange
from={refBalance}
to={totalBalance}
color="dark"
animateTicker
fontSize={7}
isPercent
val={refBalance ? Math.floor((totalBalance - refBalance) / refBalance * 100) : 0}
withIcon
/>
<Sub>{t(`time:since.${since}`)}</Sub>

79
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 }) => (
<Fragment>
{renderHeader !== null && (
<Box px={6}>
{renderHeader({
totalBalance,
selectedTime,
sinceBalance,
refBalance,
})}
>
{({ isAvailable, balanceHistory, balanceStart, balanceEnd }) =>
!isAvailable ? null : (
<Fragment>
{renderHeader ? (
<Box px={6}>
{renderHeader({
selectedTime,
// FIXME refactor these
totalBalance: balanceEnd,
sinceBalance: balanceStart,
refBalance: balanceStart,
})}
</Box>
) : null}
<Box ff="Open Sans" fontSize={4} color="graphite" pt={6}>
<Chart
id={chartId}
color={chartColor}
data={balanceHistory}
height={250}
unit={unit}
tickXScale={selectedTime}
renderTooltip={d =>
isAvailable ? (
<FormattedVal
alwaysShowSign={false}
color="white"
showCode
fiat={counterValue}
val={d.value}
/>
) : null
}
/>
</Box>
)}
<Box ff="Open Sans" fontSize={4} color="graphite" pt={6}>
<Chart
id={chartId}
color={chartColor}
data={allBalances}
height={250}
unit={unit}
tickXScale={selectedTime}
renderTooltip={d => (
<FormattedVal
alwaysShowSign={false}
color="white"
showCode
fiat={counterValue}
val={d.value}
/>
)}
/>
</Box>
</Fragment>
)}
/>
</Fragment>
)
}
</CalculateBalance>
</Card>
)
}

91
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<Object>,
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<Props, State> {
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<Props> {
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)
}
}

63
src/components/CounterValue/__tests__/CounterValue.test.js

@ -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 = <CounterValue ticker="BTC" value={1} />
const tree = render(component, state)
expect(tree).toMatchSnapshot()
})
it('specifying ticker different from default', () => {
const state = { counterValues: { LOL: { USD: { latest: 5e2 } } } }
const component = <CounterValue ticker="LOL" value={1} />
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 = <CounterValue ticker="BTC" value={1} />
const tree = render(component, state)
expect(tree).toMatchSnapshot()
})
it('without countervalues populated', () => {
const state = { counterValues: {} }
const component = <CounterValue ticker="BTC" value={1} />
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 = <CounterValue ticker="BTC" value={1} date={date} />
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 = <CounterValue ticker="BTC" value={1} date={date} />
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()
})
})
})

55
src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap

@ -1,55 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components CounterValue basic 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 bJwMKx"
color="#66be54"
>
+ USD 10.00
</div>
`;
exports[`components CounterValue specifying ticker different from default 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 bJwMKx"
color="#66be54"
>
+ USD 5.00
</div>
`;
exports[`components CounterValue using countervalue different from default 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 huzgmt"
color={undefined}
>
+ EUR 0.42
</div>
`;
exports[`components CounterValue with time travel whith date in countervalues 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 bJwMKx"
color="#66be54"
>
+ USD 20.00
</div>
`;
exports[`components CounterValue with time travel whith date not in countervalues 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 bJwMKx"
color="#66be54"
>
+ USD 0.00
</div>
`;
exports[`components CounterValue without countervalues populated 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 bJwMKx"
color="#66be54"
>
+ USD 0.00
</div>
`;

52
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<Props> {
render() {
const { value, counterValueCurrency, date, ...props } = this.props
if (!value && value !== 0) return null
return (
<FormattedVal
val={value}
@ -69,25 +58,4 @@ class CounterValue extends PureComponent<Props> {
}
}
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)

2
src/components/CounterValue/stories.js

@ -12,5 +12,5 @@ const stories = storiesOf('Components', module)
const currency = getCryptoCurrencyById('bitcoin')
stories.add('CounterValue', () => (
<CounterValue ticker={currency.units[0].code} value={Number(number('value', 100000000) || 0)} />
<CounterValue exchange="KRAKEN" currency={currency} value={number('value', 100000000)} />
))

41
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 = ({
/>
</Box>
</Box>
<CalculateBalance
counterValue={counterValue}
accounts={[account]}
daysCount={daysCount}
render={({ allBalances, totalBalance, refBalance }) => (
<CalculateBalance counterValue={counterValue} accounts={[account]} daysCount={daysCount}>
{({ isAvailable, balanceHistory, balanceStart, balanceEnd }) => (
<Box flow={4}>
<Box flow={2} horizontal>
<Box justifyContent="center">
<FormattedVal
animateTicker
fiat={counterValue}
val={totalBalance}
alwaysShowSign={false}
showCode
fontSize={3}
color="graphite"
/>
{isAvailable ? (
<FormattedVal
animateTicker
fiat={counterValue}
val={balanceEnd}
alwaysShowSign={false}
showCode
fontSize={3}
color="graphite"
/>
) : null}
</Box>
<Box grow justifyContent="center">
<FormattedVal
isPercent
val={Math.floor((totalBalance - refBalance) / refBalance * 100)}
alwaysShowSign
fontSize={3}
/>
{balanceStart && isAvailable ? (
<DeltaChange from={balanceStart} to={balanceEnd} alwaysShowSign fontSize={3} />
) : null}
</Box>
</Box>
<Chart
data={allBalances}
data={balanceHistory}
color={account.currency.color}
height={52}
hideAxis
@ -95,7 +92,7 @@ const AccountCard = ({
/>
</Box>
)}
/>
</CalculateBalance>
</Wrapper>
)

17
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 <FormattedVal isPercent val={val} {...rest} />
}
}
export default DeltaChange

13
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<Props, State> {
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()
}
}

24
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 <Box />
}
if (!value) {
return <Box />
}
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<Props> {
render() {
const {
account,
minConfirmations,
currencySettings,
onAccountClick,
onOperationClick,
t,
@ -144,7 +145,7 @@ class Operation extends PureComponent<Props> {
<Cell size={CONFIRMATION_COL_SIZE} align="center" justify="flex-start">
<ConfirmationCheck
type={type}
minConfirmations={minConfirmations}
minConfirmations={currencySettings.minConfirmations}
confirmations={account.blockHeight - op.blockHeight}
marketColor={marketColor}
t={t}
@ -200,8 +201,9 @@ class Operation extends PureComponent<Props> {
color="grey"
fontSize={3}
date={time.toDate()}
ticker={currency.units[0].code}
currency={currency}
value={op.amount}
exchange={currencySettings.exchange}
/>
</Box>
</Cell>

29
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<Props> {
}
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 (
<Box horizontal grow shrink>

20
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) => (
<Select
{...props}
value={value}
@ -55,4 +63,4 @@ SelectCurrency.defaultProps = {
value: undefined,
}
export default translate()(SelectCurrency)
export default translate()(connect(mapStateToProps)(SelectCurrency))

110
src/components/SelectExchange.js

@ -0,0 +1,110 @@
// @flow
import React, { Component } from 'react'
import type { Currency } from '@ledgerhq/live-common/lib/types'
import type { Exchange } from '@ledgerhq/live-common/lib/countervalues/types'
import Select from 'components/base/Select'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import CounterValues from 'helpers/countervalues'
const renderItem = ex => (
<Box grow horizontal alignItems="center" flow={2}>
<Text ff="Open Sans|SemiBold" color="dark" fontSize={4}>
{ex.name}
</Text>
</Box>
)
class ExchangeSelect extends Component<
{
from: Currency,
to: Currency,
exchangeId: string,
onChange: (?Exchange) => void,
style?: *,
},
{
prevFromTo: string,
exchanges: ?(Exchange[]),
error: ?Error,
},
> {
static getDerivedStateFromProps(nextProps: *, prevState: *) {
const fromTo = `${nextProps.from.ticker}/${nextProps.to.ticker}`
if (fromTo !== prevState.prevFromTo) {
return {
prevFromTo: fromTo,
exchanges: null,
}
}
return null
}
state = {
prevFromTo: '', // eslint-disable-line
exchanges: null,
error: null,
}
componentDidMount() {
this._load()
}
componentDidUpdate() {
if (this.state.exchanges === null) {
this._load()
}
}
componentWillUnmount() {
this._unmounted = true
}
_unmounted = false
_loadId = 0
async _load() {
this._loadId++
if (this._unmounted) return
this.setState({ exchanges: [] })
const { _loadId } = this
const { from, to } = this.props
try {
const exchanges = await CounterValues.fetchExchangesForPair(from, to)
if (!this._unmounted && this._loadId === _loadId) {
this.setState({ exchanges })
}
} catch (error) {
console.error(error)
if (!this._unmounted && this._loadId === _loadId) {
this.setState({ error })
}
}
}
render() {
const { onChange, exchangeId, style } = this.props
const { exchanges, error } = this.state
return exchanges && exchanges.length > 0 ? (
<Select
style={style}
value={exchanges.find(e => e.id === exchangeId)}
renderSelected={renderItem}
renderItem={renderItem}
keyProp="id"
items={exchanges}
fontSize={4}
onChange={onChange}
/>
) : error ? (
<Text ff="Open Sans|SemiBold" color="dark" fontSize={4}>
Failed to load.
</Text>
) : (
<Text ff="Open Sans|SemiBold" color="dark" fontSize={4}>
Loading...
</Text>
)
}
}
export default ExchangeSelect

16
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<Props, State> {
}
}
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

64
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<Props, State> {
state = {
currency: listCryptoCurrencies()[0],
currency: this.props.currencies[0],
}
getCurrencySettings() {
@ -60,11 +80,15 @@ class TabCurrencies extends PureComponent<Props, State> {
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<Props, State> {
}
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 (
<Section key={currency.id}>
@ -101,15 +125,34 @@ class TabCurrencies extends PureComponent<Props, State> {
title={t('settings:tabs.currencies')}
desc="Lorem ipsum dolor sit amet"
renderRight={
// TODO this should only be the subset of currencies of the app
<SelectCurrency
style={{ minWidth: 200 }}
small
value={currency}
onChange={this.handleChangeCurrency}
currencies={currencies}
/>
}
/>
<Body>
<Row title="Exchange" desc="The exchange to use">
<CounterValues.PollingConsumer>
{polling => (
// TODO move to a dedicated "row" component
<ExchangeSelect
from={currency}
to={counterValueCurrency}
exchangeId={exchange}
onChange={exchange => {
this.handleChangeExchange(exchange)
polling.poll()
}}
style={{ minWidth: 200 }}
/>
)}
</CounterValues.PollingConsumer>
</Row>
<Row
title={t('settings:currencies.confirmationsToSpend')}
desc={t('settings:currencies.confirmationsToSpendDesc')}
@ -148,4 +191,5 @@ class TabCurrencies extends PureComponent<Props, State> {
}
}
export default TabCurrencies
// $FlowFixMe not sure what's wrong
export default connect(mapStateToProps)(TabCurrencies)

26
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<Props, State> {
title={t('settings:display.counterValue')}
desc={t('settings:display.counterValueDesc')}
>
<Select
style={{ minWidth: 250 }}
small
onChange={this.handleChangeCounterValue}
itemToString={item => (item ? item.name : '')}
renderSelected={item => item && item.name}
items={fiats}
value={cachedCounterValue}
/>
<CounterValues.PollingConsumer>
{polling => (
<Select
style={{ minWidth: 250 }}
small
onChange={item => {
this.handleChangeCounterValue(item)
polling.poll()
}}
itemToString={item => (item ? item.name : '')}
renderSelected={item => item && item.name}
items={fiats}
value={cachedCounterValue}
/>
)}
</CounterValues.PollingConsumer>
</Row>
<Row title={t('settings:display.language')} desc={t('settings:display.languageDesc')}>
<Select

28
src/components/TopBar.js

@ -12,7 +12,6 @@ import type { Location, RouterHistory } from 'react-router'
import type { T } from 'types/common'
import { rgba } from 'styles/helpers'
import { getAccounts } from 'reducers/accounts'
import { lock } from 'reducers/application'
import { hasPassword } from 'reducers/settings'
@ -24,6 +23,8 @@ import IconSettings from 'icons/Settings'
import Box from 'components/base/Box'
import GlobalSearch from 'components/GlobalSearch'
import CounterValues from 'helpers/countervalues'
const Container = styled(Box).attrs({
px: 6,
})`
@ -62,10 +63,10 @@ const Activity = styled.div`
position: absolute;
right: -2px;
width: 4px;
cursor: pointer;
`
const mapStateToProps = state => ({
hasAccounts: getAccounts(state).length > 0,
hasPassword: hasPassword(state),
})
@ -74,7 +75,6 @@ const mapDispatchToProps = {
}
type Props = {
hasAccounts: boolean,
hasPassword: boolean,
history: RouterHistory,
location: Location,
@ -145,7 +145,7 @@ class TopBar extends PureComponent<Props, State> {
}
}
render() {
const { hasPassword, hasAccounts, t } = this.props
const { hasPassword, t } = this.props
const { sync } = this.state
return (
@ -156,10 +156,22 @@ class TopBar extends PureComponent<Props, State> {
<Box justifyContent="center">
<IconDevices size={16} />
</Box>
<Box justifyContent="center" relative>
<IconActivity size={16} />
{hasAccounts && <Activity progress={sync.progress} fail={sync.fail} />}
</Box>
<CounterValues.PollingConsumer>
{polling => (
<Box
justifyContent="center"
relative
cursor="pointer"
onClick={() => polling.poll()}
>
<IconActivity size={16} />
<Activity
progress={polling.pending || sync.progress}
fail={polling.error || sync.fail}
/>
</Box>
)}
</CounterValues.PollingConsumer>
<Box justifyContent="center">
<Bar />
</Box>

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

@ -12,6 +12,7 @@ import {
justifyContent,
space,
style,
cursor,
} from 'styled-system'
import fontFamily from 'styles/styled/fontFamily'
@ -34,6 +35,7 @@ const Box = styled.div`
${justifyContent};
${space};
${textAlign};
${cursor};
display: flex;
flex-shrink: ${p => (p.noShrink === true ? '0' : p.shrink === true ? '1' : '')};

4
src/components/base/Chart/helpers.js

@ -1,11 +1,11 @@
import c from 'color'
export function enrichData(data, parseTime) {
export function enrichData(data) {
return data.map((d, i) => ({
...d,
ref: d,
index: i,
parsedDate: parseTime(d.date),
parsedDate: d.date,
}))
}

4
src/components/base/Chart/index.js

@ -113,9 +113,9 @@ class Chart extends PureComponent<Props> {
this.refreshChart = prevProps => {
const { _node: node, props } = this
const { data: raw, color, dateFormat, height, hideAxis, interactive, renderTooltip } = props
const { data: raw, color, height, hideAxis, interactive, renderTooltip } = props
ctx.DATA = enrichData(raw, d3.timeParse(dateFormat))
ctx.DATA = enrichData(raw)
// Detect what needs to be updated
ctx.INVALIDATED = {

2
src/components/base/Chart/stories.js

@ -64,7 +64,7 @@ function generateRandomData(n) {
const chance = new Chance()
while (!day.isSame(today)) {
data.push({
date: day.format('YYYY-MM-DD'),
date: day.toDate(),
value: chance.integer({ min: 0.5e8, max: 1e8 }),
})
day.add(1, 'day')

2
src/components/base/Chart/types.js

@ -1,7 +1,7 @@
// @flow
export type Item = {
date: string,
date: Date,
value: number,
}

12
src/components/base/FormattedVal/__tests__/__snapshots__/FormattedVal.test.js.snap

@ -2,7 +2,7 @@
exports[`components FormattedVal renders a fiat 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 bJwMKx"
className="k45ou1-0 iqaJGf e345n3-0 eoitYS"
color="#66be54"
>
20.00
@ -11,7 +11,7 @@ exports[`components FormattedVal renders a fiat 1`] = `
exports[`components FormattedVal renders a formatted val 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 bJwMKx"
className="k45ou1-0 iqaJGf e345n3-0 eoitYS"
color="#66be54"
>
4
@ -20,7 +20,7 @@ exports[`components FormattedVal renders a formatted val 1`] = `
exports[`components FormattedVal renders a percent 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 bJwMKx"
className="k45ou1-0 iqaJGf e345n3-0 eoitYS"
color="#66be54"
>
30 %
@ -29,7 +29,7 @@ exports[`components FormattedVal renders a percent 1`] = `
exports[`components FormattedVal shows code 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 bJwMKx"
className="k45ou1-0 iqaJGf e345n3-0 eoitYS"
color="#66be54"
>
BTC 4
@ -38,7 +38,7 @@ exports[`components FormattedVal shows code 1`] = `
exports[`components FormattedVal shows sign 1`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 bJwMKx"
className="k45ou1-0 iqaJGf e345n3-0 eoitYS"
color="#66be54"
>
+ 4
@ -47,7 +47,7 @@ exports[`components FormattedVal shows sign 1`] = `
exports[`components FormattedVal shows sign 2`] = `
<div
className="k45ou1-0 iqaJGf e345n3-0 dHGwW"
className="k45ou1-0 iqaJGf e345n3-0 lkKRAD"
color="#ea2e49"
>
- 4

7
src/components/base/FormattedVal/index.js

@ -64,6 +64,8 @@ type Props = OwnProps & {
locale: string,
}
let _logged
function FormattedVal(props: Props) {
const {
animateTicker,
@ -92,7 +94,10 @@ function FormattedVal(props: Props) {
text = `${alwaysShowSign ? (isNegative ? '- ' : '+ ') : ''}${isNegative ? val * -1 : val} %`
} else {
if (fiat) {
console.warn('FormattedVal: passing fiat prop is deprecated')
if (!_logged) {
_logged = true
console.warn('FormattedVal: passing fiat prop is deprecated')
}
const cur = findCurrencyByTicker(fiat)
if (cur) {
;[unit] = cur.units

9
src/components/modals/AddAccount/index.js

@ -17,7 +17,6 @@ import { canCreateAccount, getAccounts, getArchivedAccounts } from 'reducers/acc
import { sendEvent } from 'renderer/events'
import { addAccount, updateAccount } from 'actions/accounts'
import { fetchCounterValues } from 'actions/counterValues'
import Box from 'components/base/Box'
import Breadcrumb from 'components/Breadcrumb'
@ -44,7 +43,6 @@ const mapStateToProps = state => ({
const mapDispatchToProps = {
addAccount,
closeModal,
fetchCounterValues,
updateAccount,
}
@ -54,7 +52,6 @@ type Props = {
archivedAccounts: Account[],
canCreateAccount: boolean,
closeModal: Function,
fetchCounterValues: Function,
t: T,
updateAccount: Function,
}
@ -117,7 +114,6 @@ class AddAccountModal extends PureComponent<Props, State> {
}
async fetchCounterValues() {
const { fetchCounterValues } = this.props
const { currency } = this.state
if (!currency) {
@ -129,7 +125,10 @@ class AddAccountModal extends PureComponent<Props, State> {
stepIndex: 0,
})
await fetchCounterValues([currency])
// FIXME I don't really understand this step.
// also countervalues should not block the app to work.
// imagine our api is down...
// await fetchCounterValues([currency])
this.setState({
fetchingCounterValues: false,

18
src/components/modals/OperationDetails.js

@ -18,7 +18,8 @@ import Bar from 'components/base/Bar'
import FormattedVal from 'components/base/FormattedVal'
import Modal, { ModalBody, ModalTitle, ModalFooter, ModalContent } from 'components/base/Modal'
import { currencySettingsSelector } from 'reducers/settings'
import { createStructuredSelector } from 'reselect'
import { currencySettingsForAccountSelector } from 'reducers/settings'
import CounterValue from 'components/CounterValue'
import ConfirmationCheck from 'components/OperationsList/ConfirmationCheck'
@ -52,8 +53,8 @@ const B = styled(Bar).attrs({
size: 1,
})``
const mapStateToProps = (state, props) => ({
minConfirmations: currencySettingsSelector(state, props.account.currency).confirmationsNb,
const mapStateToProps = createStructuredSelector({
currencySettings: currencySettingsForAccountSelector,
})
type Props = {
@ -62,12 +63,12 @@ type Props = {
account: Account,
type: 'from' | 'to',
onClose: Function,
minConfirmations: number,
currencySettings: *,
marketColor: string,
}
const OperationDetails = connect(mapStateToProps)((props: Props) => {
const { t, type, onClose, minConfirmations, operation, account, marketColor } = props
const { t, type, onClose, operation, account, marketColor, currencySettings } = props
const { id, amount, date } = operation
// $FlowFixMe YEAH, I know those fields should not be present in operation
@ -75,7 +76,7 @@ const OperationDetails = connect(mapStateToProps)((props: Props) => {
const { name, unit, currency } = account
const confirmations = account.blockHeight - operation.blockHeight
const isConfirmed = confirmations >= minConfirmations
const isConfirmed = confirmations >= currencySettings.minConfirmations
return (
<ModalBody onClose={onClose}>
<ModalTitle>Operation details</ModalTitle>
@ -84,7 +85,7 @@ const OperationDetails = connect(mapStateToProps)((props: Props) => {
<ConfirmationCheck
marketColor={marketColor}
confirmations={confirmations}
minConfirmations={minConfirmations}
minConfirmations={currencySettings.minConfirmations}
style={{
transform: 'scale(2)',
}}
@ -101,8 +102,9 @@ const OperationDetails = connect(mapStateToProps)((props: Props) => {
color="grey"
fontSize={5}
date={date}
ticker={currency.units[0].code}
currency={currency}
value={amount}
exchange={currencySettings.exchange}
/>
</Box>
</Box>

15
src/components/modals/Send/Footer.js

@ -2,6 +2,9 @@
import React from 'react'
import type { Account } from '@ledgerhq/live-common/lib/types'
import { createStructuredSelector } from 'reselect'
import { connect } from 'react-redux'
import { exchangeSettingsForAccountSelector } from 'reducers/settings'
import type { T } from 'types/common'
@ -21,9 +24,14 @@ type Props = {
onNext: Function,
canNext: boolean,
showTotal: boolean,
exchange: string,
}
function Footer({ account, amount, fees, t, onNext, canNext, showTotal }: Props) {
const mapStateToProps = createStructuredSelector({
exchange: exchangeSettingsForAccountSelector,
})
function Footer({ exchange, account, amount, fees, t, onNext, canNext, showTotal }: Props) {
return (
<ModalFooter>
<Box horizontal alignItems="center" justifyContent="flex-end" flow={2}>
@ -43,7 +51,8 @@ function Footer({ account, amount, fees, t, onNext, canNext, showTotal }: Props)
{'('}
</Text>
<CounterValue
ticker={account.currency.units[0].code}
exchange={exchange}
currency={account.currency}
value={amount + fees}
disableRounding
color="grey"
@ -65,4 +74,4 @@ function Footer({ account, amount, fees, t, onNext, canNext, showTotal }: Props)
)
}
export default Footer
export default connect(mapStateToProps)(Footer)

1
src/config/constants.js

@ -1,6 +1,5 @@
export const CHECK_UPDATE_DELAY = 5e3
export const SYNC_ACCOUNT_DELAY = 3e3
export const SYNC_COUNTER_VALUES_DELAY = 5e3
export const MODAL_ADD_ACCOUNT = 'MODAL_ADD_ACCOUNT'
export const MODAL_OPERATION_DETAILS = 'MODAL_OPERATION_DETAILS'

166
src/helpers/balance.js

@ -1,166 +0,0 @@
// @flow
import moment from 'moment'
import get from 'lodash/get'
import type { Account } from '@ledgerhq/live-common/lib/types'
import find from 'lodash/find'
import first from 'lodash/first'
import isUndefined from 'lodash/isUndefined'
import last from 'lodash/last'
type DateInterval = {
start: string,
end: string,
}
type BalanceHistoryDay = {
date: string,
balance: number,
}
type CalculateBalance = {
accounts: Account[],
counterValue: string,
counterValues: Object,
daysCount: number,
}
// Map the given date interval
// iteratee is given day, index, and currently constructed array
// (exactly like Array.map)
function mapInterval(iv: DateInterval, cb: Function) {
const res = []
let startDate = moment(iv.start)
let i = 0
const endDate = moment(iv.end)
res.push(cb(startDate.format('YYYY-MM-DD'), i, res))
while (!startDate.isSame(endDate, 'day')) {
startDate = startDate.add(1, 'day')
res.push(cb(startDate.format('YYYY-MM-DD'), ++i, res))
}
return res
}
function getBalanceAtIntervalStart(account: Account, interval: DateInterval): number | null {
const target = moment(interval.start)
let res = 0
for (const i in account.balanceByDay) {
if (account.balanceByDay.hasOwnProperty(i)) {
const d = moment(i)
if (!d.isBefore(target, 'day')) {
break
}
res = account.balanceByDay[i] || 0
}
}
return res
}
export function getBalanceHistoryForAccount({
account,
counterValue,
counterValues,
interval,
}: {
counterValue: string,
account: Account,
counterValues: Object,
interval: DateInterval,
}): Array<BalanceHistoryDay> {
const todayDate = moment().format('YYYY-MM-DD')
const { unit } = account
const counterVals = get(counterValues, `${unit.code}.${counterValue}`)
let lastBalance = getBalanceAtIntervalStart(account, interval)
return mapInterval(interval, date => {
let balance = 0
if (!counterVals) {
return { balance, date }
}
const isToday = date === todayDate
const counterVal = isToday
? counterVals.latest || counterVals[date] || 0
: counterVals[date] || 0
// if we don't have data on account balance for that day,
// we take the prev day
if (isUndefined(account.balanceByDay[date])) {
balance = lastBalance === null ? 0 : lastBalance * counterVal
} else {
const b = account.balanceByDay[date]
lastBalance = b
balance = b * counterVal
}
if (isNaN(balance)) {
console.warn(`This should not happen. Cant calculate balance for ${date}`) // eslint-disable-line no-console
return { date, balance: 0 }
}
return { date, balance }
})
}
export function getBalanceHistoryForAccounts({
accounts,
counterValue,
counterValues,
interval,
}: {
counterValue: string,
accounts: Account[],
counterValues: Object,
interval: DateInterval,
}): Array<BalanceHistoryDay> {
// calculate balance history for each account on the given interval
const balances = accounts.map(account =>
getBalanceHistoryForAccount({
counterValue,
account,
counterValues,
interval,
}),
)
// if more than one account, addition all balances, day by day
// and returns a big summed up array
return balances.length > 1
? balances[0].map((item, i) => {
let b = item.balance
for (let j = 1; j < balances.length; j++) {
b += balances[j][i].balance
}
return { ...item, balance: b }
})
: balances.length > 0
? balances[0]
: []
}
export default function calculateBalance(props: CalculateBalance) {
const interval = {
start: moment()
.subtract(props.daysCount, 'days')
.format('YYYY-MM-DD'),
end: moment().format('YYYY-MM-DD'),
}
const allBalances = getBalanceHistoryForAccounts({
counterValue: props.counterValue,
accounts: props.accounts,
counterValues: props.counterValues,
interval,
}).map(e => ({ date: e.date, value: e.balance }))
const firstNonEmptyDay = find(allBalances, e => e.value)
const refBalance = firstNonEmptyDay ? firstNonEmptyDay.value : 0
return {
allBalances,
totalBalance: last(allBalances).value,
sinceBalance: first(allBalances).value,
refBalance,
}
}

49
src/helpers/countervalues.js

@ -0,0 +1,49 @@
// @flow
import { createSelector } from 'reselect'
import createCounterValues from '@ledgerhq/live-common/lib/countervalues'
import { setExchangePairsAction } from 'actions/settings'
import { currenciesSelector } from 'reducers/accounts'
import { counterValueCurrencySelector, currencySettingsSelector } from 'reducers/settings'
const pairsSelector = createSelector(
currenciesSelector,
counterValueCurrencySelector,
state => state,
(currencies, counterValueCurrency, state) =>
currencies.map(currency => ({
from: currency,
to: counterValueCurrency,
exchange: currencySettingsSelector(state, currency).exchange,
})),
)
const addExtraPollingHooks = (schedulePoll, cancelPoll) => {
// TODO hook to net info of Electron ? retrieving network should trigger a poll
// provide a basic mecanism to stop polling when you leave the tab
// & immediately poll when you come back.
function onWindowBlur() {
cancelPoll()
}
function onWindowFocus() {
schedulePoll(1000)
}
window.addEventListener('blur', onWindowBlur)
window.addEventListener('focus', onWindowFocus)
return () => {
window.removeEventListener('blur', onWindowBlur)
window.removeEventListener('focus', onWindowFocus)
}
}
const CounterValues = createCounterValues({
getAPIBaseURL: () => 'https://ledger-countervalue-poc.herokuapp.com',
storeSelector: state => state.countervalues,
pairsSelector,
setExchangePairsAction,
addExtraPollingHooks,
})
export default CounterValues

4
src/helpers/db.js

@ -6,7 +6,7 @@ import get from 'lodash/get'
import { serializeAccounts, deserializeAccounts } from 'reducers/accounts'
type DBKey = 'settings' | 'accounts' | 'counterValues'
type DBKey = 'settings' | 'accounts' | 'countervalues'
const encryptionKey = {}
@ -86,7 +86,7 @@ export default {
},
resetAll: () => {
const keys = ['settings', 'accounts', 'counterValues']
const keys = ['settings', 'accounts']
keys.forEach(k => {
const db = store(k)
db.clear()

4
src/main/bridge.js

@ -9,7 +9,6 @@ import { resolve } from 'path'
import cpuUsage from 'helpers/cpuUsage'
import setupAutoUpdater, { quitAndInstall } from './autoUpdate'
import counterValuesSync from './counterValuesSync'
const { DEV_TOOLS } = process.env
@ -82,9 +81,6 @@ const handlers = {
init: setupAutoUpdater,
quitAndInstall,
},
counterValues: {
sync: counterValuesSync,
},
kill: {
process: (send, { pid }) => {
try {

14
src/main/counterValuesSync.js

@ -1,14 +0,0 @@
// @flow
import { fetchCurrentRates } from '@ledgerhq/live-common/lib/api/countervalue'
type SendFunction = (type: string, data: *) => void
export default async (send: SendFunction, { counterValue, currencies }: Object) => {
try {
const data = await fetchCurrentRates(currencies, counterValue)
send('counterValues.update', data)
} catch (err) {
console.error(err) // eslint-disable-line no-console
}
}

12
src/middlewares/db.js

@ -3,6 +3,8 @@
import db from 'helpers/db'
import { getAccounts } from 'reducers/accounts'
import { settingsExportSelector } from 'reducers/settings'
import CounterValues from 'helpers/countervalues'
export default store => next => action => {
if (!action.type.startsWith('DB:')) {
@ -15,11 +17,7 @@ export default store => next => action => {
dispatch({ type, payload: action.payload })
const state = getState()
const { settings, counterValues } = state
const accounts = getAccounts(state)
db.set('settings', settings)
db.set('accounts', accounts)
db.set('counterValues', counterValues)
db.set('settings', settingsExportSelector(state))
db.set('accounts', getAccounts(state))
db.set('countervalues', CounterValues.exportSelector(state))
}

53
src/reducers/counterValues.js

@ -1,41 +1,22 @@
// @flow
import { handleActions } from 'redux-actions'
import merge from 'lodash/merge'
import get from 'lodash/get'
import {
makeCalculateCounterValue,
makeReverseCounterValue,
formatCounterValueDay,
} from '@ledgerhq/live-common/lib/helpers/countervalue'
import CounterValues from 'helpers/countervalues'
import type { CalculateCounterValue } from '@ledgerhq/live-common/lib/types'
import type { Currency } from '@ledgerhq/live-common/lib/types'
import type { State } from 'reducers'
export type CounterValuesState = {}
const state: CounterValuesState = {}
const handlers = {
UPDATE_COUNTER_VALUES: (state, { payload: counterValues }) => merge({ ...state }, counterValues),
}
const getPairHistory = state => (coinTicker, fiat) => {
const byDate = get(state, `counterValues.${coinTicker}.${fiat}`)
return date => {
if (!byDate) {
return 0
}
if (!date) {
return byDate.latest || 0
}
return byDate[formatCounterValueDay(date)] || 0
}
}
export const calculateCounterValueSelector = (state: State): CalculateCounterValue =>
makeCalculateCounterValue(getPairHistory(state))
export const reverseCounterValueSelector = (state: State): CalculateCounterValue =>
makeReverseCounterValue(getPairHistory(state))
export default handleActions(handlers, state)
// FIXME DEPRECATED approach. we will move to use calculateSelector everywhere.. it's just easier to process for now.
export const calculateCounterValueSelector = (state: State) => (
from: Currency,
to: Currency,
exchange: string,
) => (value: number, date?: Date, disableRounding?: boolean): ?number =>
CounterValues.calculateSelector(state, { from, to, exchange, value, date, disableRounding })
export const reverseCounterValueSelector = (state: State) => (
from: Currency,
to: Currency,
exchange: string,
) => (value: number, date?: Date, disableRounding?: boolean): ?number =>
CounterValues.reverseSelector(state, { from, to, exchange, value, date, disableRounding })

8
src/reducers/index.js

@ -5,9 +5,10 @@ import { routerReducer as router } from 'react-router-redux'
import type { LocationShape } from 'react-router'
import type { CounterValuesState } from '@ledgerhq/live-common/lib/countervalues/types'
import CounterValues from 'helpers/countervalues'
import accounts from './accounts'
import application from './application'
import counterValues from './counterValues'
import devices from './devices'
import modals from './modals'
import settings from './settings'
@ -15,7 +16,6 @@ import update from './update'
import type { AccountsState } from './accounts'
import type { ApplicationState } from './application'
import type { CounterValuesState } from './counterValues'
import type { DevicesState } from './devices'
import type { ModalsState } from './modals'
import type { SettingsState } from './settings'
@ -24,7 +24,7 @@ import type { UpdateState } from './update'
export type State = {
accounts: AccountsState,
application: ApplicationState,
counterValues: CounterValuesState,
countervalues: CounterValuesState,
devices: DevicesState,
modals: ModalsState,
router: LocationShape,
@ -35,7 +35,7 @@ export type State = {
export default combineReducers({
accounts,
application,
counterValues,
countervalues: CounterValues.reducer,
devices,
modals,
router,

77
src/reducers/settings.js

@ -1,8 +1,14 @@
// @flow
import { handleActions } from 'redux-actions'
import { findCurrencyByTicker } from '@ledgerhq/live-common/lib/helpers/currencies'
import type { CryptoCurrency, Currency } from '@ledgerhq/live-common/lib/types'
import {
findCurrencyByTicker,
getFiatCurrencyByTicker,
listCryptoCurrencies,
} from '@ledgerhq/live-common/lib/helpers/currencies'
import { createSelector } from 'reselect'
import type { Selector } from 'reselect'
import type { CryptoCurrency, Currency, Account } from '@ledgerhq/live-common/lib/types'
import type { Settings, CurrencySettings } from 'types/common'
import type { State } from 'reducers'
@ -46,21 +52,54 @@ const defaultState: SettingsState = {
const CURRENCY_DEFAULTS_SETTINGS: CurrencySettings = {
confirmationsToSpend: 10,
minConfirmationsToSpend: 10,
maxConfirmationsToSpend: 50,
minConfirmationsToSpend: 10, // FIXME DROP
maxConfirmationsToSpend: 50, // FIXME DROP
confirmationsNb: 10,
minConfirmationsNb: 10,
maxConfirmationsNb: 50,
minConfirmationsNb: 10, // FIXME DROP
maxConfirmationsNb: 50, // FIXME DROP
transactionFees: 10,
exchange: '',
}
const state: SettingsState = {
...defaultState,
}
function asCryptoCurrency(c: Currency): ?CryptoCurrency {
// $FlowFixMe
return 'id' in c ? c : null
}
const handlers: Object = {
SETTINGS_SET_PAIRS: (
state: SettingsState,
{
pairs,
}: {
pairs: Array<{
from: Currency,
to: Currency,
exchange: string,
}>,
},
) => {
const counterValueCurrency = counterValueCurrencyLocalSelector(state)
const copy = { ...state }
copy.currenciesSettings = { ...copy.currenciesSettings }
for (const { to, from, exchange } of pairs) {
const fromCrypto = asCryptoCurrency(from)
if (to === counterValueCurrency && fromCrypto) {
copy.currenciesSettings[fromCrypto.id] = {
...copy.currenciesSettings[fromCrypto.id],
exchange,
}
}
}
return copy
},
SAVE_SETTINGS: (state: SettingsState, { payload: settings }: { payload: Settings }) => ({
...state,
...settings,
@ -73,15 +112,26 @@ const handlers: Object = {
// TODO refactor selectors to *Selector naming convention
export const storeSelector = (state: State): SettingsState => state.settings
export const settingsExportSelector = storeSelector
export const hasPassword = (state: State): boolean => state.settings.password.isEnabled
export const getCounterValueCode = (state: State) => state.settings.counterValue
export const counterValueCurrencySelector = (state: State): ?Currency =>
findCurrencyByTicker(getCounterValueCode(state))
const counterValueCurrencyLocalSelector = (state: SettingsState): Currency =>
findCurrencyByTicker(state.counterValue) || getFiatCurrencyByTicker('USD')
export const counterValueCurrencySelector = createSelector(
storeSelector,
counterValueCurrencyLocalSelector,
)
export const developerModeSelector = (state: State): boolean => state.settings.developerMode
export const availableCurrencies = createSelector(developerModeSelector, listCryptoCurrencies)
export const getLanguage = (state: State) => state.settings.language
export const localeSelector = (state: State) => {
@ -102,6 +152,17 @@ export const currencySettingsSelector = (
return { ...CURRENCY_DEFAULTS_SETTINGS, ...currencySettings }
}
export const currencySettingsForAccountSelector = (
state: State,
{ account }: { account: Account },
) => currencySettingsSelector(state, account.currency)
type ESFAS = Selector<*, { account: Account }, string>
export const exchangeSettingsForAccountSelector: ESFAS = createSelector(
currencySettingsForAccountSelector,
settings => settings.exchange,
)
export const marketIndicatorSelector = (state: State) => state.settings.marketIndicator
export default handleActions(handlers, state)

38
src/renderer/events.js

@ -1,20 +1,18 @@
// @flow
// FIXME this file is spaghetti. we need one file per usecase.
import { ipcRenderer } from 'electron'
import objectPath from 'object-path'
import debug from 'debug'
import uniqBy from 'lodash/uniqBy'
import { getFiatCurrencyByTicker } from '@ledgerhq/live-common/lib/helpers/currencies'
import type { Currency, Account } from '@ledgerhq/live-common/lib/types'
import type { Account } from '@ledgerhq/live-common/lib/types'
import { CHECK_UPDATE_DELAY, SYNC_ACCOUNT_DELAY, SYNC_COUNTER_VALUES_DELAY } from 'config/constants'
import { CHECK_UPDATE_DELAY, SYNC_ACCOUNT_DELAY } from 'config/constants'
import { getAccounts, getAccountById } from 'reducers/accounts'
import { getCounterValueCode } from 'reducers/settings'
import { isLocked } from 'reducers/application'
import { setUpdateStatus } from 'reducers/update'
import { updateCounterValues } from 'actions/counterValues'
import { updateAccount } from 'actions/accounts'
import { updateDevices, addDevice, removeDevice } from 'actions/devices'
@ -36,8 +34,6 @@ type MsgPayload = {
let syncAccountsInProgress = false
let syncAccountsTimeout
let syncCounterValuesTimeout
export function sendEvent(channel: string, msgType: string, data: any) {
ipcRenderer.send(channel, {
type: msgType,
@ -107,18 +103,6 @@ export function stopSyncAccounts() {
clearTimeout(syncAccountsTimeout)
}
export function startSyncCounterValues(counterValueCode: string, accounts: Account[]) {
d.sync('Sync counterValues - start')
const currencies: Currency[] = uniqBy(accounts.map(a => a.currency), 'code')
const counterValue = getFiatCurrencyByTicker(counterValueCode)
sendEvent('msg', 'counterValues.sync', { currencies, counterValue })
}
export function stopSyncCounterValues() {
d.sync('Sync counterValues - stop')
clearTimeout(syncCounterValuesTimeout)
}
export function checkUpdates() {
d.update('Update - check')
setTimeout(() => sendEvent('msg', 'updater.init'), CHECK_UPDATE_DELAY)
@ -201,17 +185,6 @@ export default ({ store, locked }: { store: Object, locked: boolean }) => {
store.dispatch(removeDevice(device))
},
},
counterValues: {
update: counterValues => {
store.dispatch(updateCounterValues(counterValues))
syncCounterValuesTimeout = setTimeout(() => {
const state = store.getState()
const accounts = getAccounts(state)
const counterValue = getCounterValueCode(state)
startSyncCounterValues(counterValue, accounts)
}, SYNC_COUNTER_VALUES_DELAY)
},
},
updater: {
checking: () => store.dispatch(setUpdateStatus('checking')),
updateAvailable: info => store.dispatch(setUpdateStatus('available', info)),
@ -241,9 +214,6 @@ export default ({ store, locked }: { store: Object, locked: boolean }) => {
if (!locked) {
const accounts = getAccounts(state)
const counterValue = getCounterValueCode(state)
startSyncCounterValues(counterValue, accounts)
// Start accounts sync only if we have accounts
if (accounts.length > 0 && !DISABLED_SYNC) {

14
src/renderer/init.js

@ -12,12 +12,12 @@ import events from 'renderer/events'
import { fetchAccounts } from 'actions/accounts'
import { fetchSettings } from 'actions/settings'
import { initCounterValues } from 'actions/counterValues'
import { isLocked } from 'reducers/application'
import { getLanguage } from 'reducers/settings'
import db from 'helpers/db'
import dbMiddleware from 'middlewares/db'
import CounterValues from 'helpers/countervalues'
import App from 'components/App'
@ -29,14 +29,20 @@ if (process.env.LEDGER_RESET_ALL) {
// Init db with defaults if needed
db.init('settings', {})
db.init('counterValues', {})
const history = createHistory()
const store = createStore({ history, dbMiddleware })
const rootNode = document.getElementById('app')
store.dispatch(fetchSettings())
store.dispatch(initCounterValues())
const settings = db.get('settings')
if (Object.keys(settings).length !== 0) {
store.dispatch(fetchSettings(settings))
}
const countervaluesData = db.get('countervalues')
if (countervaluesData) {
store.dispatch(CounterValues.importAction(store.getState()))
}
const state = store.getState()
const language = getLanguage(state)

2
src/types/common.js

@ -22,6 +22,8 @@ export type CurrencySettings = {
maxConfirmationsNb: number,
transactionFees: number,
exchange: string,
}
export type CurrenciesSettings = {

13
yarn.lock

@ -1456,16 +1456,19 @@
dependencies:
events "^2.0.0"
"@ledgerhq/live-common@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-2.0.0.tgz#299634a9edfd842ae2d096bcdf34863631f495a3"
"@ledgerhq/live-common@^2.2.0-beta.10":
version "2.2.0-beta.10"
resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-2.2.0-beta.10.tgz#9dbeee35ea65dfbd041f13b622f57ec9de668d88"
dependencies:
axios "^0.18.0"
invariant "^2.2.2"
lodash "^4.17.4"
numeral "^2.0.6"
prando "^3.0.1"
react "^16.0.0"
react "^16.3.2"
react-redux "^5.0.7"
redux "^4.0.0"
reselect "^3.0.1"
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
@ -10600,7 +10603,7 @@ react-treebeard@^2.1.0:
shallowequal "^0.2.2"
velocity-react "^1.3.1"
react@^16.0.0, react@^16.2.0, react@^16.3.2:
react@^16.2.0, react@^16.3.2:
version "16.3.2"
resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9"
dependencies:

Loading…
Cancel
Save