Browse Source

Merge branch 'master' into l10n_master

master
Meriadec Pillet 7 years ago
committed by GitHub
parent
commit
7bb68ae5b2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .circleci/config.yml
  2. 12
      package.json
  3. 8
      src/actions/accounts.js
  4. 33
      src/actions/counterValues.js
  5. 11
      src/components/AccountPage/index.js
  6. 2
      src/components/BalanceSummary/BalanceInfos.js
  7. 36
      src/components/BalanceSummary/index.js
  8. 22
      src/components/BalanceSummary/stories.js
  9. 27
      src/components/Breadcrumb/Step.js
  10. 5
      src/components/Breadcrumb/index.js
  11. 4
      src/components/Breadcrumb/stories.js
  12. 5
      src/components/CalculateBalance.js
  13. 1
      src/components/DashboardPage/AccountCard.js
  14. 32
      src/components/DashboardPage/index.js
  15. 19
      src/components/IsUnlocked.js
  16. 2
      src/components/OperationsList/ConfirmationCheck.js
  17. 46
      src/components/OperationsList/index.js
  18. 23
      src/components/OperationsList/stories.js
  19. 4
      src/components/RecipientAddress/stories.js
  20. 275
      src/components/RequestAmount/index.js
  21. 33
      src/components/RequestAmount/stories.js
  22. 45
      src/components/SelectAccount/index.js
  23. 16
      src/components/SelectAccount/stories.js
  24. 4
      src/components/SideBar/index.js
  25. 4
      src/components/base/Bar/stories.js
  26. 2
      src/components/base/Box/stories.js
  27. 10
      src/components/base/Button/index.js
  28. 4
      src/components/base/Button/stories.js
  29. 4
      src/components/base/CheckBox/stories.js
  30. 5
      src/components/base/FormattedVal/index.js
  31. 4
      src/components/base/GrowScroll/stories.js
  32. 99
      src/components/base/Input/index.js
  33. 4
      src/components/base/Input/stories.js
  34. 132
      src/components/base/InputCurrency/index.js
  35. 15
      src/components/base/InputCurrency/stories.js
  36. 10
      src/components/base/Label.js
  37. 32
      src/components/base/Modal/index.js
  38. 23
      src/components/base/Modal/stories.js
  39. 33
      src/components/base/NewChart/Tooltip.js
  40. 4
      src/components/base/NewChart/handleMouseEvents.js
  41. 20
      src/components/base/NewChart/index.js
  42. 28
      src/components/base/NewChart/refreshDraw.js
  43. 11
      src/components/base/NewChart/stories.js
  44. 4
      src/components/base/Pills/stories.js
  45. 4
      src/components/base/QRCode/stories.js
  46. 4
      src/components/base/Radio/stories.js
  47. 79
      src/components/base/Search/stories.js
  48. 33
      src/components/base/Select/Triangles.js
  49. 48
      src/components/base/Select/index.js
  50. 2
      src/components/base/Select/stories.js
  51. 4
      src/components/base/Tabs/stories.js
  52. 4
      src/components/base/Text/stories.js
  53. 4
      src/components/base/Tooltip/stories.js
  54. 6
      src/components/modals/AddAccount/ImportAccounts.js
  55. 4
      src/components/modals/AddAccount/RestoreAccounts.js
  56. 14
      src/components/modals/AddAccount/index.js
  57. 71
      src/components/modals/Receive/index.js
  58. 81
      src/components/modals/Send.js
  59. 115
      src/components/modals/SettingsAccount.js
  60. 5
      src/constants.js
  61. 14
      src/helpers/__tests__/balance.test.js
  62. 8
      src/helpers/balance.js
  63. 47
      src/helpers/btc.js
  64. 10
      src/helpers/cpuUsage.js
  65. 11
      src/helpers/db.js
  66. 4
      src/internals/accounts/sync.js
  67. 8
      src/internals/usb/wallet/accounts.js
  68. 4
      src/main/bridge.js
  69. 24
      src/main/counterValuesSync.js
  70. 33
      src/reducers/accounts.js
  71. 56
      src/reducers/counterValues.js
  72. 73
      src/renderer/events.js
  73. 2
      src/renderer/i18n/instanciate.js
  74. 2
      src/renderer/i18n/storybook.js
  75. 4
      src/stories/currencies.stories.js
  76. 9
      src/styles/global.js
  77. 13
      src/types/common.js
  78. 1
      static/i18n/en/common.yml
  79. 0
      static/i18n/en/operationsList.yml
  80. 10
      static/i18n/fr/transactionsList.yml
  81. 239
      yarn.lock

4
.circleci/config.yml

@ -16,8 +16,8 @@ jobs:
name: Flow typed
command: yarn flow-typed
- run:
name: Temporary remove flow definitions for react-redux
command: rm flow-typed/npm/react-redux_v5.x.x.js
name: Temporary remove broken flow definitions
command: rm flow-typed/npm/{react-redux_v5.x.x.js,redux_v3.x.x.js}
- run:
name: Lint
command: yarn lint

12
package.json

@ -54,7 +54,7 @@
"bitcoinjs-lib": "^3.3.2",
"bs58check": "^2.1.1",
"color": "^3.0.0",
"cross-env": "^5.1.3",
"cross-env": "^5.1.4",
"d3": "^4.13.0",
"debug": "^3.1.0",
"downshift": "^1.30.0",
@ -69,9 +69,9 @@
"moment": "^2.20.1",
"object-path": "^0.11.4",
"qrcode": "^1.2.0",
"query-string": "^5.1.0",
"query-string": "^6.0.0",
"raven": "^2.4.2",
"raven-js": "^3.22.4",
"raven-js": "^3.23.2",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-i18next": "^7.5.0",
@ -89,9 +89,9 @@
"smooth-scrollbar": "^8.2.6",
"source-map": "0.6.0",
"source-map-support": "^0.5.3",
"styled-components": "^3.2.1",
"styled-components": "^3.2.2",
"styled-system": "^2.2.1",
"tippy.js": "^2.2.3",
"tippy.js": "^2.3.0",
"victory": "^0.25.6"
},
"devDependencies": {
@ -114,7 +114,7 @@
"concurrently": "^3.5.1",
"dotenv": "^5.0.1",
"electron": "1.8.3",
"electron-builder": "^20.4.0",
"electron-builder": "^20.5.1",
"electron-devtools-installer": "^2.2.3",
"electron-rebuild": "^1.7.3",
"electron-webpack": "1.13.0",

8
src/actions/accounts.js

@ -9,7 +9,7 @@ import type { Account } from 'types/common'
import { fetchCounterValues } from 'actions/counterValues'
import { startSyncAccounts } from 'renderer/events'
import { startSyncAccounts, startSyncCounterValues } from 'renderer/events'
function sortAccounts(accounts, orderAccounts) {
const [order, sort] = orderAccounts.split('|')
@ -43,7 +43,7 @@ export const updateOrderAccounts: UpdateOrderAccounts = (orderAccounts: string)
export type AddAccount = Account => (Function, Function) => void
export const addAccount: AddAccount = payload => (dispatch, getState) => {
const { settings: { orderAccounts }, accounts } = getState()
const { settings: { counterValue, orderAccounts }, accounts } = getState()
dispatch({
type: 'ADD_ACCOUNT',
payload,
@ -52,7 +52,9 @@ export const addAccount: AddAccount = payload => (dispatch, getState) => {
// Start sync accounts the first time you add an account
if (accounts.length === 0) {
startSyncAccounts([payload])
const accounts = [payload]
startSyncCounterValues(counterValue, accounts)
startSyncAccounts(accounts)
}
}

33
src/actions/counterValues.js

@ -6,6 +6,8 @@ import { getDefaultUnitByCoinType } from '@ledgerhq/currencies'
import type { Dispatch } from 'redux'
import { serializeCounterValues } from 'reducers/counterValues'
import get from 'lodash/get'
import db from 'helpers/db'
@ -22,6 +24,15 @@ export const updateCounterValues: UpdateCounterValues = payload => ({
payload,
})
export type UpdateLastCounterValueBySymbol = (string, Object) => { type: string, payload: Object }
export const updateLastCounterValueBySymbol: UpdateLastCounterValueBySymbol = (symbol, value) => ({
type: 'DB:UPDATE_LAST_COUNTER_VALUE',
payload: {
symbol,
value,
},
})
export type FetchCounterValues = (?number) => (Dispatch<*>, Function) => Promise<any>
export const fetchCounterValues: FetchCounterValues = coinType => (dispatch, getState) => {
const { accounts, counterValues, settings } = getState()
@ -51,21 +62,19 @@ export const fetchCounterValues: FetchCounterValues = coinType => (dispatch, get
)
.then(({ data }) => ({
symbol: `${code}-${counterValue}`,
values: data.Data.reduce((result, d) => {
const date = moment(d.time * 1000).format('YYYY-MM-DD')
result[date] = d.close
return result
}, {}),
values: data.Data.map(d => [moment(d.time * 1000).format('YYYY-MM-DD'), d.close]),
}))
}
return Promise.all(coinTypes.map(fetchCounterValuesByCoinType)).then(result => {
const newCounterValues = result.reduce((r, v) => {
if (v !== null) {
r[v.symbol] = v.values
}
return r
}, {})
return Promise.all(coinTypes.map(fetchCounterValuesByCoinType)).then(results => {
const newCounterValues = serializeCounterValues(
results.reduce((r, v) => {
if (v !== null) {
r[v.symbol] = v.values
}
return r
}, {}),
)
if (Object.keys(newCounterValues).length !== 0) {
dispatch(updateCounterValues(newCounterValues))

11
src/components/AccountPage/index.js

@ -28,7 +28,7 @@ import Box from 'components/base/Box'
import Button from 'components/base/Button'
import FormattedVal from 'components/base/FormattedVal'
import PillsDaysCount from 'components/PillsDaysCount'
import TransactionsList from 'components/TransactionsList'
import OperationsList from 'components/OperationsList'
import AccountHeader from './AccountHeader'
@ -79,19 +79,20 @@ class AccountPage extends PureComponent<Props, State> {
<Box horizontal mb={5}>
<AccountHeader account={account} />
<Box horizontal alignItems="center" justifyContent="flex-end" grow flow={2}>
<Button primary onClick={() => openModal(MODAL_SEND, { account })}>
<Button small primary onClick={() => openModal(MODAL_SEND, { account })}>
<Box horizontal flow={1} alignItems="center">
<IconArrowUp width={12} />
<Box>{t('send:title')}</Box>
</Box>
</Button>
<Button primary onClick={() => openModal(MODAL_RECEIVE, { account })}>
<Button small primary onClick={() => openModal(MODAL_RECEIVE, { account })}>
<Box horizontal flow={1} alignItems="center">
<IconArrowDown width={12} />
<Box>{t('receive:title')}</Box>
</Box>
</Button>
<Button
small
style={{ width: 30, padding: 0 }}
onClick={() => openModal(MODAL_SETTINGS_ACCOUNT, { account })}
>
@ -155,9 +156,9 @@ class AccountPage extends PureComponent<Props, State> {
)}
/>
</Box>
<TransactionsList
<OperationsList
title={t('account:lastOperations')}
transactions={account.transactions}
operations={account.operations}
minConfirmations={account.settings.minConfirmations}
/>
</Box>

2
src/components/BalanceSummary/BalanceInfos.js

@ -43,7 +43,7 @@ export function BalanceSincePercent(props: BalanceSinceProps) {
<Box {...otherProps}>
<FormattedVal
isPercent
val={Math.floor((totalBalance - refBalance) / refBalance * 100)}
val={refBalance ? Math.floor((totalBalance - refBalance) / refBalance * 100) : 0}
alwaysShowSign
fontSize={7}
/>

36
src/components/BalanceSummary/index.js

@ -1,46 +1,21 @@
// @flow
import React, { Fragment } from 'react'
import moment from 'moment'
import { formatShort, getFiatUnit } from '@ledgerhq/currencies'
import { getFiatUnit } from '@ledgerhq/currencies'
import type { Accounts } from 'types/common'
import type { Account } from 'types/common'
import Chart from 'components/base/NewChart'
import Box, { Card } from 'components/base/Box'
import CalculateBalance from 'components/CalculateBalance'
import FormattedVal from 'components/base/FormattedVal'
function getTickCountX(selectedTime) {
switch (selectedTime) {
default:
case 'week':
return 7
case 'month':
return 10
case 'year':
return 13
}
}
function renderTickX(selectedTime) {
let format = 'MMM. D'
if (selectedTime === 'year') {
format = 'MMM.'
}
return t => moment(t).format(format)
}
type Props = {
counterValue: string,
chartColor: string,
chartId: string,
accounts: Accounts,
accounts: Account[],
selectedTime: string,
daysCount: number,
renderHeader: null | Function,
@ -80,9 +55,8 @@ const BalanceSummary = ({
color={chartColor}
data={allBalances}
height={250}
nbTicksX={getTickCountX(selectedTime)}
renderTickX={renderTickX(selectedTime)}
renderTickY={t => formatShort(unit, t)}
unit={unit}
tickXScale={selectedTime}
renderTooltip={d => (
<FormattedVal
alwaysShowSign={false}

22
src/components/BalanceSummary/stories.js

@ -0,0 +1,22 @@
// @flow
import React from 'react'
import { storiesOf } from '@storybook/react'
import { number } from '@storybook/addon-knobs'
import { translate } from 'react-i18next'
import BalanceInfos from './BalanceInfos'
const stories = storiesOf('Components', module)
const BalanceInfosComp = translate()(BalanceInfos)
stories.add('BalanceInfos', () => (
<BalanceInfosComp
since="month"
counterValue="USD"
totalBalance={number('totalBalance', 1000, { min: 0 })}
sinceBalance={number('sinceBalance', 500, { min: 0 })}
refBalance={number('refBalance', 200, { min: 0 })}
/>
))

27
src/components/Breadcrumb/Step.js

@ -5,12 +5,14 @@ import styled from 'styled-components'
import Box from 'components/base/Box'
const RADIUS = 17
const Wrapper = styled(Box).attrs({
align: 'center',
justify: 'center',
color: p => (p.isActive ? 'wallet' : 'fog'),
})`
width: 40px;
width: ${RADIUS}px;
flex-shrink: 0;
text-align: center;
font-size: 9px;
@ -19,22 +21,26 @@ const Wrapper = styled(Box).attrs({
const Number = styled(Box).attrs({
align: 'center',
justify: 'center',
color: p => (p.isActive ? 'white' : 'fog'),
bg: p => (p.isActive ? 'wallet' : 'pearl'),
color: 'white',
bg: p => (p.isActive ? 'wallet' : 'fog'),
ff: 'Rubik|Regular',
})`
width: 20px;
height: 20px;
width: ${RADIUS}px;
height: ${RADIUS}px;
border-radius: 50%;
font-size: 9px;
font-size: 10px;
line-height: 10px;
box-shadow: ${p => `0 0 0 ${p.isActive ? 4 : 0}px ${p.theme.colors.lightGrey}`};
transition: all ease-in-out 0.1s ${p => (p.isActive ? 0.4 : 0)}s;
`
const Bar = styled.div`
height: 2px;
background: ${p => p.theme.colors.pearl};
background: ${p => p.theme.colors.fog};
flex-grow: 1;
max-width: 100px;
position: relative;
margin-top: -2px;
&:after {
background: ${p => p.theme.colors.pearl};
@ -52,9 +58,12 @@ const Bar = styled.div`
}
`
const Label = styled(Box)`
const Label = styled(Box).attrs({
fontSize: 3,
ff: 'Museo Sans|Bold',
})`
position: absolute;
margin-top: 30px;
margin-top: 27px;
transition: color ease-in-out 0.1s ${p => (p.isActive ? 0.4 : 0)}s;
`

5
src/components/Breadcrumb/index.js

@ -16,15 +16,16 @@ type Props = {
const Wrapper = styled(Box).attrs({
horizontal: true,
align: 'center',
justify: 'center',
})`
margin-bottom: 25px;
`
class Breadcrumb extends PureComponent<Props> {
render() {
const { items, currentStep } = this.props
const { items, currentStep, ...props } = this.props
return (
<Wrapper>
<Wrapper {...props}>
{items.map((item, i) => (
<Step key={i} isActive={i < parseInt(currentStep, 10)} isFirst={i === 0} number={i + 1}>
{item.label}

4
src/components/Breadcrumb/stories.js

@ -6,9 +6,9 @@ import { number } from '@storybook/addon-knobs'
import Breadcrumb from 'components/Breadcrumb'
const stories = storiesOf('Components/Breadcrumb', module)
const stories = storiesOf('Components', module)
stories.add('basic', () => (
stories.add('Breadcrumb', () => (
<Breadcrumb
currentStep={number('currentStep', 1, {
min: 1,

5
src/components/CalculateBalance.js

@ -3,7 +3,7 @@
import { PureComponent } from 'react'
import { connect } from 'react-redux'
import type { Accounts } from 'types/common'
import type { Account } from 'types/common'
import calculateBalance from 'helpers/balance'
@ -12,7 +12,7 @@ const mapStateToProps = state => ({
})
type Props = {
accounts: Accounts,
accounts: Account[],
counterValues: Object,
daysCount: number,
render: Function,
@ -40,7 +40,6 @@ class CalculateBalance extends PureComponent<Props, State> {
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) {
this.setState(calculateBalanceToState(nextProps))
}

1
src/components/DashboardPage/AccountCard.js

@ -91,6 +91,7 @@ const AccountCard = ({
hideAxis
interactive={false}
id={`account-chart-${account.id}`}
unit={account.unit}
/>
</Box>
)}

32
src/components/DashboardPage/index.js

@ -10,7 +10,7 @@ import chunk from 'lodash/chunk'
import get from 'lodash/get'
import sortBy from 'lodash/sortBy'
import type { Account, Accounts, T } from 'types/common'
import type { Account, Operation, T } from 'types/common'
import { getVisibleAccounts } from 'reducers/accounts'
import { getCounterValue } from 'reducers/settings'
@ -23,7 +23,7 @@ import BalanceSummary from 'components/BalanceSummary'
import Box from 'components/base/Box'
import PillsDaysCount from 'components/PillsDaysCount'
import Text from 'components/base/Text'
import TransactionsList from 'components/TransactionsList'
import OperationsList from 'components/OperationsList'
import AccountCard from './AccountCard'
import AccountsOrder from './AccountsOrder'
@ -41,28 +41,28 @@ const mapDispatchToProps = {
type Props = {
t: T,
accounts: Accounts,
accounts: Account[],
push: Function,
counterValue: string,
}
type State = {
accountsChunk: Array<Array<Account | null>>,
allTransactions: Array<Object>,
allOperations: Operation[],
selectedTime: string,
daysCount: number,
}
const ACCOUNTS_BY_LINE = 3
const ALL_TRANSACTIONS_LIMIT = 10
const ALL_OPERATIONS_LIMIT = 10
const getAllTransactions = accounts => {
const allTransactions = accounts.reduce((result, account) => {
const transactions = get(account, 'transactions', [])
const getAllOperations = accounts => {
const allOperations = accounts.reduce((result, account) => {
const operations = get(account, 'operations', [])
result = [
...result,
...transactions.map(t => ({
...operations.map(t => ({
...t,
account,
})),
@ -71,9 +71,9 @@ const getAllTransactions = accounts => {
return result
}, [])
return sortBy(allTransactions, t => t.receivedAt)
return sortBy(allOperations, t => t.receivedAt)
.reverse()
.slice(0, ALL_TRANSACTIONS_LIMIT)
.slice(0, ALL_OPERATIONS_LIMIT)
}
const getAccountsChunk = accounts => {
@ -88,7 +88,7 @@ const getAccountsChunk = accounts => {
class DashboardPage extends PureComponent<Props, State> {
state = {
accountsChunk: getAccountsChunk(this.props.accounts),
allTransactions: getAllTransactions(this.props.accounts),
allOperations: getAllOperations(this.props.accounts),
selectedTime: 'week',
daysCount: 7,
}
@ -97,7 +97,7 @@ class DashboardPage extends PureComponent<Props, State> {
if (nextProps.accounts !== this.props.accounts) {
this.setState({
accountsChunk: getAccountsChunk(nextProps.accounts),
allTransactions: getAllTransactions(nextProps.accounts),
allOperations: getAllOperations(nextProps.accounts),
})
}
}
@ -110,7 +110,7 @@ class DashboardPage extends PureComponent<Props, State> {
render() {
const { push, accounts, t, counterValue } = this.props
const { accountsChunk, allTransactions, selectedTime, daysCount } = this.state
const { accountsChunk, allOperations, selectedTime, daysCount } = this.state
const totalAccounts = accounts.length
@ -189,11 +189,11 @@ class DashboardPage extends PureComponent<Props, State> {
))}
</Box>
</Box>
<TransactionsList
<OperationsList
canShowMore
title={t('dashboard:recentActivity')}
withAccounts
transactions={allTransactions}
operations={allOperations}
onAccountClick={account => push(`/account/${account.id}`)}
/>
</Fragment>

19
src/components/IsUnlocked.js

@ -6,16 +6,22 @@ import { compose } from 'redux'
import { translate } from 'react-i18next'
import bcrypt from 'bcryptjs'
import type { Settings, Accounts, T } from 'types/common'
import type { Settings, Account, T } from 'types/common'
import get from 'lodash/get'
import { startSyncAccounts, stopSyncAccounts } from 'renderer/events'
import {
startSyncCounterValues,
startSyncAccounts,
stopSyncAccounts,
stopSyncCounterValues,
} 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 { getCounterValue } from 'reducers/settings'
import Box from 'components/base/Box'
import Input from 'components/base/Input'
@ -25,8 +31,9 @@ type InputValue = {
}
type Props = {
accounts: Accounts,
accounts: Account[],
children: any,
counterValue: string,
fetchAccounts: Function,
isLocked: boolean,
settings: Settings,
@ -39,8 +46,9 @@ type State = {
const mapStateToProps = state => ({
accounts: getAccounts(state),
settings: state.settings,
counterValue: getCounterValue(state),
isLocked: isLocked(state),
settings: state.settings,
})
const mapDispatchToProps: Object = {
@ -61,16 +69,19 @@ 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()
}
}

2
src/components/TransactionsList/ConfirmationCheck.js → src/components/OperationsList/ConfirmationCheck.js

@ -36,7 +36,7 @@ const ConfirmationCheck = ({
return (
<Tooltip
render={() =>
isConfirmed ? t('transactionsList:confirmed') : t('transactionsList:notConfirmed')
isConfirmed ? t('operationsList:confirmed') : t('operationsList:notConfirmed')
}
>
<Container isConfirmed={isConfirmed}>

46
src/components/TransactionsList/index.js → src/components/OperationsList/index.js

@ -9,7 +9,7 @@ import noop from 'lodash/noop'
import isEqual from 'lodash/isEqual'
import { getIconByCoinType } from '@ledgerhq/currencies/react'
import type { Transaction as TransactionType, T } from 'types/common'
import type { Operation as OperationType, T } from 'types/common'
import IconAngleDown from 'icons/AngleDown'
import Box, { Card } from 'components/base/Box'
@ -55,7 +55,7 @@ HeaderCol.defaultProps = {
children: undefined,
}
const TransactionRaw = styled(Box).attrs({
const OperationRaw = styled(Box).attrs({
horizontal: true,
alignItems: 'center',
})`
@ -97,7 +97,7 @@ const ShowMore = styled(Box).attrs({
}
`
const Transaction = ({
const Operation = ({
t,
onAccountClick,
tx,
@ -106,14 +106,14 @@ const Transaction = ({
}: {
t: T,
onAccountClick?: Function,
tx: TransactionType,
tx: OperationType,
withAccounts?: boolean,
minConfirmations: number,
}) => {
const time = moment(tx.receivedAt)
const Icon = getIconByCoinType(get(tx, 'account.currency.coinType'))
return (
<TransactionRaw>
<OperationRaw>
<Cell size={DATE_COL_SIZE} justifyContent="space-between">
<Box>
<Day>{time.format('DD MMM')}</Day>
@ -149,7 +149,7 @@ const Transaction = ({
}}
>
<Box ff="Open Sans" fontSize={3} color="graphite">
{tx.balance > 0 ? t('transactionsList:from') : t('transactionsList:to')}
{tx.amount > 0 ? t('operationsList:from') : t('operationsList:to')}
</Box>
<Box
color="dark"
@ -167,7 +167,7 @@ const Transaction = ({
</Cell>
<Cell size={AMOUNT_COL_SIZE} justifyContent="flex-end">
<FormattedVal
val={tx.balance}
val={tx.amount}
unit={get(tx, 'account.unit')}
showCode
fontSize={4}
@ -181,11 +181,11 @@ const Transaction = ({
t={t}
/>
</Cell>
</TransactionRaw>
</OperationRaw>
)
}
Transaction.defaultProps = {
Operation.defaultProps = {
onAccountClick: noop,
withAccounts: false,
}
@ -193,14 +193,14 @@ Transaction.defaultProps = {
type Props = {
t: T,
onAccountClick?: Function,
transactions: Array<TransactionType>,
operations: OperationType[],
withAccounts?: boolean,
minConfirmations: number,
title?: string,
canShowMore: boolean,
}
class TransactionsList extends Component<Props> {
class OperationsList extends Component<Props> {
static defaultProps = {
onAccountClick: noop,
withAccounts: false,
@ -221,16 +221,16 @@ class TransactionsList extends Component<Props> {
return true
}
return !isEqual(this._hashCache, this.getHashCache(nextProps.transactions))
return !isEqual(this._hashCache, this.getHashCache(nextProps.operations))
}
getHashCache = (transactions: Array<TransactionType>) => transactions.map(t => t.hash)
getHashCache = (operations: OperationType[]) => operations.map(t => t.hash)
_hashCache = null
render() {
const {
transactions,
operations,
title,
withAccounts,
onAccountClick,
@ -239,26 +239,26 @@ class TransactionsList extends Component<Props> {
t,
} = this.props
this._hashCache = this.getHashCache(transactions)
this._hashCache = this.getHashCache(operations)
return (
<Defer>
<Card flow={1} title={title} p={0}>
<Box horizontal pt={4}>
<HeaderCol size={DATE_COL_SIZE}>{t('transactionsList:date')}</HeaderCol>
<HeaderCol size={DATE_COL_SIZE}>{t('operationsList:date')}</HeaderCol>
{withAccounts && (
<HeaderCol size={ACCOUNT_COL_SIZE}>{t('transactionsList:account')}</HeaderCol>
<HeaderCol size={ACCOUNT_COL_SIZE}>{t('operationsList:account')}</HeaderCol>
)}
<HeaderCol grow>{t('transactionsList:address')}</HeaderCol>
<HeaderCol grow>{t('operationsList:address')}</HeaderCol>
<HeaderCol size={AMOUNT_COL_SIZE} justifyContent="flex-end">
{t('transactionsList:amount')}
{t('operationsList:amount')}
</HeaderCol>
<HeaderCol size={CONFIRMATION_COL_SIZE} px={0} />
</Box>
<Box>
{transactions.map(trans => (
<Transaction
{operations.map(trans => (
<Operation
t={t}
key={`{${trans.hash}-${trans.account ? trans.account.id : ''}`}
withAccounts={withAccounts}
@ -271,7 +271,7 @@ class TransactionsList extends Component<Props> {
{canShowMore && (
<ShowMore>
<span>{t('transactionsList:showMore')}</span>
<span>{t('operationsList:showMore')}</span>
<IconAngleDown width={8} height={8} />
</ShowMore>
)}
@ -281,4 +281,4 @@ class TransactionsList extends Component<Props> {
}
}
export default translate()(TransactionsList)
export default translate()(OperationsList)

23
src/components/TransactionsList/stories.js → src/components/OperationsList/stories.js

@ -1,28 +1,37 @@
// @flow
import React from 'react'
import { getDefaultUnitByCoinType } from '@ledgerhq/currencies'
import { storiesOf } from '@storybook/react'
import { boolean } from '@storybook/addon-knobs'
import TransactionsList from 'components/TransactionsList'
import OperationsList from 'components/OperationsList'
const stories = storiesOf('Components/TransactionsList', module)
const stories = storiesOf('Components', module)
const transactions = [
const unit = getDefaultUnitByCoinType(0)
const operations = [
{
address: '5c6ea1716520c7d6e038d36a3223faced3c',
hash: '5c6ea1716520c7d6e038d36a3223faced3c4b8f7ffb69d9fb5bd527d562fdb62',
balance: 130000000,
amount: 130000000,
receivedAt: '2018-01-09T16:03:52Z',
account: {
unit,
},
},
{
address: '27416a48caab90fab053b507b8b6b9d4',
hash: '27416a48caab90fab053b507b8b6b9d48fba75421d3bfdbae4b85f64024bc9c4',
balance: 65000000,
amount: -65000000,
receivedAt: '2018-01-09T16:02:40Z',
account: {
unit,
},
},
]
stories.add('basic', () => (
<TransactionsList transactions={transactions} canShowMore={boolean('canShowMore')} />
stories.add('OperationsList', () => (
<OperationsList operations={operations} canShowMore={boolean('canShowMore')} />
))

4
src/components/RecipientAddress/stories.js

@ -6,7 +6,7 @@ import { boolean } from '@storybook/addon-knobs'
import RecipientAddress from 'components/RecipientAddress'
const stories = storiesOf('Components/RecipientAddress', module)
const stories = storiesOf('Components', module)
type State = {
value: any,
@ -27,7 +27,7 @@ class Wrapper extends PureComponent<any, State> {
}
}
stories.add('basic', () => (
stories.add('RecipientAddress', () => (
<Wrapper
render={({ onChange, value }) => (
<RecipientAddress

275
src/components/RequestAmount/index.js

@ -0,0 +1,275 @@
// @flow
import React, { PureComponent } from 'react'
import { compose } from 'redux'
import { translate } from 'react-i18next'
import styled from 'styled-components'
import { connect } from 'react-redux'
import { getDefaultUnitByCoinType, getFiatUnit } from '@ledgerhq/currencies'
import isNaN from 'lodash/isNaN'
import noop from 'lodash/noop'
import type { T, Account } from 'types/common'
import { getCounterValue } from 'reducers/settings'
import { getLastCounterValueBySymbol } from 'reducers/counterValues'
import InputCurrency from 'components/base/InputCurrency'
import Button from 'components/base/Button'
import Box from 'components/base/Box'
const InputRight = styled(Box).attrs({
ff: 'Rubik',
color: 'graphite',
fontSize: 4,
justifyContent: 'center',
pr: 3,
})``
const InputCenter = styled(Box).attrs({
ff: 'Rubik',
color: 'graphite',
fontSize: 4,
justifyContent: 'center',
})``
const mapStateToProps = (state, { account }) => {
const counterValue = getCounterValue(state)
const unit = getDefaultUnitByCoinType(account.coinType)
const symbol = `${unit.code}-${counterValue}`
return {
counterValue,
lastCounterValue: getLastCounterValueBySymbol(symbol, state),
}
}
function maxUnitDigits(unit, value) {
const [leftDigits, rightDigits] = value.toString().split('.')
return Number(`${leftDigits}${rightDigits ? `.${rightDigits.slice(0, unit.magnitude)}` : ''}`)
}
function calculateMax(props) {
const { account, counterValue, lastCounterValue } = props
const unit = getUnit({ account, counterValue })
const leftMax = account.balance / 10 ** unit.left.magnitude
return {
left: account.balance / 10 ** unit.left.magnitude,
right: maxUnitDigits(unit.right, leftMax * lastCounterValue),
}
}
function getUnit({ account, counterValue }) {
return {
left: getDefaultUnitByCoinType(account.coinType),
right: getFiatUnit(counterValue),
}
}
function calculateValues({
dir,
value,
max,
lastCounterValue,
}: {
dir: string,
value: Object,
max: Object,
lastCounterValue: number,
}) {
const v = value[dir]
const getMax = (d, v) => {
const result = v > max[d] ? max[d] : v
return isNaN(result) ? '0' : result.toString()
}
const newValue = {}
if (dir === 'left') {
newValue.left = v === '' ? v : getMax('left', v)
newValue.right = getMax('right', Number(v) * lastCounterValue)
}
if (dir === 'right') {
newValue.left = getMax('left', Number(v) / lastCounterValue)
newValue.right = v === '' ? v : getMax('right', v)
}
return newValue
}
type Direction = 'left' | 'right'
type Props = {
account: Account,
counterValue: string,
lastCounterValue: number, // eslint-disable-line react/no-unused-prop-types
onChange: Function,
t: T,
value: Object,
}
type State = {
max: {
left: number,
right: number,
},
value: {
left: string | number,
right: string | number,
},
}
export class RequestAmount extends PureComponent<Props, State> {
static defaultProps = {
onChange: noop,
value: {},
}
constructor(props: Props) {
super()
this.props = props
const max = calculateMax(props)
let v = {
left: 0,
right: 0,
}
if (props.value.left) {
v = calculateValues({
...props,
dir: 'left',
max,
})
}
if (props.value.right) {
v = calculateValues({
...props,
dir: 'right',
max,
})
}
this.state = {
max,
value: v,
}
}
componentWillReceiveProps(nextProps: Props) {
if (this.props.account !== nextProps.account) {
const max = calculateMax(nextProps)
this.setState({
max,
value: calculateValues({
...nextProps,
dir: 'left',
max,
}),
})
}
}
componentDidUpdate(prevProps: Props) {
this.updateValueWithProps(prevProps, this.props)
}
handleChangeAmount = (dir: Direction) => (v: number | string) => {
const { onChange, value, account, counterValue, ...otherProps } = this.props
const { max } = this.state
const otherDir = dir === 'left' ? 'right' : 'left'
const unit = getUnit({
account,
counterValue,
})
const newValue = calculateValues({
...otherProps,
dir,
value: {
[dir]: v.toString(),
},
max,
})
newValue[otherDir] = maxUnitDigits(unit[otherDir], newValue[otherDir]).toString()
this.setState({
value: newValue,
})
onChange(newValue)
}
handleClickMax = () => {
const { account } = this.props
this.handleChangeAmount('left')(account.balance)
}
updateValueWithProps = (props: Props, nextProps: Props) => {
if (
props.value.left !== nextProps.value.left &&
nextProps.value.left !== this.state.value.left
) {
this.setState({
value: calculateValues({
...nextProps,
dir: 'left',
max: this.state.max,
}),
})
}
if (
props.value.right !== nextProps.value.right &&
nextProps.value.right !== this.state.value.right
) {
this.setState({
value: calculateValues({
...nextProps,
dir: 'right',
max: this.state.max,
}),
})
}
}
render() {
const { account, counterValue, t } = this.props
const { value } = this.state
const unit = getUnit({
account,
counterValue,
})
return (
<Box horizontal flow={2}>
<InputCurrency
unit={unit.left}
value={value.left}
onChange={this.handleChangeAmount('left')}
renderRight={<InputRight>{unit.left.code}</InputRight>}
/>
<InputCenter>=</InputCenter>
<InputCurrency
unit={unit.right}
value={value.right}
onChange={this.handleChangeAmount('right')}
renderRight={<InputRight>{unit.right.code}</InputRight>}
/>
<Button ml={5} primary onClick={this.handleClickMax}>
{t('common:max')}
</Button>
</Box>
)
}
}
export default compose(connect(mapStateToProps), translate())(RequestAmount)

33
src/components/RequestAmount/stories.js

@ -0,0 +1,33 @@
// @flow
import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import { number } from '@storybook/addon-knobs'
import { translate } from 'react-i18next'
import { accounts } from 'components/SelectAccount/stories'
import { RequestAmount } from 'components/RequestAmount'
const stories = storiesOf('Components', module)
const props = {
counterValue: 'USD',
lastCounterValue: 9177.69,
account: accounts[0],
}
const RequestAmountComp = translate()(RequestAmount)
stories.add('RequestAmount', () => (
<RequestAmountComp
{...props}
t={k => k}
onChange={action('onChange')}
value={{
left: number('left value', 0),
right: number('right value', 0),
}}
/>
))

45
src/components/SelectAccount/index.js

@ -1,12 +1,12 @@
// @flow
import React from 'react'
import { compose } from 'redux'
import { connect } from 'react-redux'
import { translate } from 'react-i18next'
import noop from 'lodash/noop'
import { getIconByCoinType } from '@ledgerhq/currencies/react'
import type { T, Accounts, Account } from 'types/common'
import type { T, Account } from 'types/common'
import { getVisibleAccounts } from 'reducers/accounts'
@ -19,27 +19,36 @@ const mapStateToProps = state => ({
accounts: getVisibleAccounts(state),
})
const renderItem = a => (
<Box horizontal alignItems="center">
<Box grow>
<Text ff="Open Sans|SemiBold" color="dark" fontSize={4}>
{a.name}
</Text>
const renderItem = a => {
const Icon = getIconByCoinType(a.coinType)
const { color } = a.currency
return (
<Box horizontal alignItems="center" flow={2}>
{Icon && (
<Box style={{ width: 16, height: 16, color }}>
<Icon size={16} />
</Box>
)}
<Box grow>
<Text ff="Open Sans|SemiBold" color="dark" fontSize={4}>
{a.name}
</Text>
</Box>
<Box>
<FormattedVal color="grey" val={a.balance} unit={a.unit} showCode />
</Box>
</Box>
<Box>
<FormattedVal color="grey" val={a.balance} unit={a.unit} />
</Box>
</Box>
)
)
}
type Props = {
accounts: Accounts,
accounts: Account[],
onChange?: () => Account | void,
value?: Account | null,
t: T,
}
export const SelectAccount = ({ accounts, onChange, value, t }: Props) => (
const RawSelectAccount = ({ accounts, onChange, value, t }: Props) => (
<Select
value={value && accounts.find(a => value && a.id === value.id)}
renderSelected={renderItem}
@ -52,9 +61,11 @@ export const SelectAccount = ({ accounts, onChange, value, t }: Props) => (
/>
)
SelectAccount.defaultProps = {
RawSelectAccount.defaultProps = {
onChange: noop,
value: undefined,
}
export default compose(connect(mapStateToProps), translate())(SelectAccount)
export const SelectAccount = translate()(RawSelectAccount)
export default connect(mapStateToProps)(SelectAccount)

16
src/components/SelectAccount/stories.js

@ -8,22 +8,22 @@ import { getCurrencyByCoinType, getDefaultUnitByCoinType } from '@ledgerhq/curre
import { SelectAccount } from 'components/SelectAccount'
const chance = new Chance()
const stories = storiesOf('Components/SelectAccount', module)
const stories = storiesOf('Components', module)
const accounts = [...Array(20)].map(() => ({
export const accounts = [...Array(20)].map(() => ({
id: chance.string(),
address: chance.string(),
addresses: [],
balance: chance.integer({ min: 10000000, max: 2000000000 }),
balance: chance.integer({ min: 10000000000, max: 2000000000000 }),
balanceByDay: {},
coinType: 0,
currency: getCurrencyByCoinType(0),
coinType: 1,
currency: getCurrencyByCoinType(1),
index: chance.integer({ min: 0, max: 20 }),
name: chance.name(),
path: '',
rootPath: '',
transactions: [],
unit: getDefaultUnitByCoinType(0),
operations: [],
unit: getDefaultUnitByCoinType(1),
settings: {
minConfirmations: 2,
},
@ -48,7 +48,7 @@ class Wrapper extends PureComponent<any, State> {
}
}
stories.add('basic', () => (
stories.add('SelectAccount', () => (
<Wrapper
render={({ onChange, value }) => (
<SelectAccount onChange={onChange} value={value} accounts={accounts} t={k => k} />

4
src/components/SideBar/index.js

@ -9,7 +9,7 @@ import { getIconByCoinType } from '@ledgerhq/currencies/react'
import { MODAL_SEND, MODAL_RECEIVE, MODAL_ADD_ACCOUNT } from 'constants'
import type { Accounts, T } from 'types/common'
import type { Account, T } from 'types/common'
import { openModal } from 'reducers/modals'
import { getVisibleAccounts } from 'reducers/accounts'
@ -52,7 +52,7 @@ const PlusBtn = styled(Tabbable).attrs({
type Props = {
t: T,
accounts: Accounts,
accounts: Account[],
openModal: Function,
}

4
src/components/base/Bar/stories.js

@ -7,6 +7,6 @@ import { storiesOf } from '@storybook/react'
import Bar from 'components/base/Bar'
const stories = storiesOf('Components/Bar', module)
const stories = storiesOf('Components/base', module)
stories.add('basic', () => <Bar size={number('size', 1)} color="grey" />)
stories.add('Bar', () => <Bar size={number('size', 1)} color="grey" />)

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

@ -8,7 +8,7 @@ import styled from 'styled-components'
import Box from 'components/base/Box'
const stories = storiesOf('Components/Box', module)
const stories = storiesOf('Components/base/Box', module)
const align = [
undefined,

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

@ -12,7 +12,7 @@ import fontFamily from 'styles/styled/fontFamily'
const Base = styled.button.attrs({
ff: 'Museo Sans|Regular',
fontSize: 3,
px: 2,
px: p => (p.small ? 2 : 4),
})`
${space};
${color};
@ -23,7 +23,7 @@ const Base = styled.button.attrs({
border: ${p =>
p.primary ? 'none' : `2px solid ${p.disabled ? 'transparent' : p.theme.colors.grey}`};
cursor: ${p => (p.disabled ? 'default' : 'pointer')};
height: 30px;
height: ${p => (p.small ? 30 : 40)}px;
outline: none;
&:hover {
@ -46,6 +46,7 @@ type Props = {
primary?: boolean,
disabled?: boolean,
onClick?: Function,
small?: boolean,
}
function getProps({ disabled, icon, primary }: Object) {
@ -98,10 +99,11 @@ const Button = (props: Props) => {
Button.defaultProps = {
children: undefined,
icon: undefined,
disabled: undefined,
primary: false,
icon: undefined,
onClick: noop,
primary: false,
small: false,
}
export default Button

4
src/components/base/Button/stories.js

@ -6,7 +6,7 @@ import styled from 'styled-components'
import Button from 'components/base/Button'
const stories = storiesOf('Components/Button', module)
const stories = storiesOf('Components/base', module)
const Th = styled.th`
padding: 20px;
@ -17,7 +17,7 @@ const Td = styled.td`
min-width: 150px;
`
stories.add('all', () => (
stories.add('Button', () => (
<table border={1}>
<thead>
<tr>

4
src/components/base/CheckBox/stories.js

@ -5,8 +5,8 @@ import { action } from '@storybook/addon-actions'
import CheckBox from 'components/base/CheckBox'
const stories = storiesOf('Components/CheckBox', module)
const stories = storiesOf('Components/base', module)
stories.add('basic', () => (
stories.add('CheckBox', () => (
<CheckBox isChecked={boolean('isChecked', false)} onChange={action('onChange')} />
))

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

@ -2,6 +2,7 @@
import React from 'react'
import styled from 'styled-components'
import isUndefined from 'lodash/isUndefined'
import type { Unit } from '@ledgerhq/currencies'
@ -27,6 +28,10 @@ function FormattedVal(props: Props) {
const { fiat, isPercent, alwaysShowSign, showCode, ...p } = props
let { val, unit } = props
if (isUndefined(val)) {
throw new Error('FormattedVal require a `val` prop. Received `undefined`')
}
const isNegative = val < 0
let text = ''

4
src/components/base/GrowScroll/stories.js

@ -7,9 +7,9 @@ import { boolean } from '@storybook/addon-knobs'
import Box from 'components/base/Box'
import GrowScroll from 'components/base/GrowScroll'
const stories = storiesOf('Components/GrowScroll', module)
const stories = storiesOf('Components/base', module)
stories.add('basic', () => {
stories.add('GrowScroll', () => {
const reverseColor = boolean('reverseColor', false)
return (

99
src/components/base/Input/index.js

@ -2,39 +2,67 @@
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { space } from 'styled-system'
import { fontSize } from 'styled-system'
import noop from 'lodash/noop'
import fontFamily from 'styles/styled/fontFamily'
import Box from 'components/base/Box'
const Container = styled(Box).attrs({
horizontal: true,
})`
background: ${p => p.theme.colors.white};
border-radius: 3px;
border: 1px solid ${p => p.theme.colors.fog};
box-shadow: ${p => (p.isFocus ? `rgba(0, 0, 0, 0.05) 0 2px 2px` : 'none')};
height: 40px;
`
const Base = styled.input.attrs({
p: 4,
ff: 'Open Sans|SemiBold',
ff: p => p.ff || 'Open Sans|SemiBold',
fontSize: 4,
})`
${space};
${fontFamily};
border: 1px solid ${p => p.theme.colors.fog};
border-radius: 3px;
display: flex;
${fontSize};
border: 0;
color: ${p => p.theme.colors.dark};
height: 100%;
outline: none;
padding: 0;
width: 100%;
color: ${p => p.theme.colors.graphite};
background: ${p => p.theme.colors.white};
&::placeholder {
color: ${p => p.theme.colors.fog};
}
&:focus {
outline: none;
box-shadow: rgba(0, 0, 0, 0.05) 0 2px 2px;
}
`
type Props = {
onChange?: Function,
keepEvent?: boolean,
onBlur: Function,
onChange?: Function,
onFocus: Function,
renderLeft?: any,
renderRight?: any,
}
type State = {
isFocus: boolean,
}
class Input extends PureComponent<Props> {
class Input extends PureComponent<Props, State> {
static defaultProps = {
onBlur: noop,
onFocus: noop,
renderLeft: null,
renderRight: null,
}
state = {
isFocus: false,
}
handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
const { onChange, keepEvent } = this.props
@ -43,8 +71,45 @@ class Input extends PureComponent<Props> {
}
}
handleClick = () => this._input && this._input.focus()
handleFocus = () => {
const { onFocus } = this.props
this.setState({
isFocus: true,
})
onFocus()
}
handleBlur = () => {
const { onBlur } = this.props
this.setState({
isFocus: false,
})
onBlur()
}
_input = null
render() {
return <Base {...this.props} onChange={this.handleChange} />
const { isFocus } = this.state
const { renderLeft, renderRight } = this.props
return (
<Container onClick={this.handleClick} isFocus={isFocus} shrink>
{renderLeft}
<Box px={3} grow shrink>
<Base
{...this.props}
innerRef={n => (this._input = n)}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onChange={this.handleChange}
/>
</Box>
{renderRight}
</Container>
)
}
}

4
src/components/base/Input/stories.js

@ -5,6 +5,6 @@ import { storiesOf } from '@storybook/react'
import Input from 'components/base/Input'
const stories = storiesOf('Components/Input', module)
const stories = storiesOf('Components/base', module)
stories.add('basic', () => <Input placeholder="Foo bar" />)
stories.add('Input', () => <Input placeholder="Foo bar" />)

132
src/components/base/InputCurrency/index.js

@ -0,0 +1,132 @@
// @flow
import React, { PureComponent } from 'react'
import { parseCurrencyUnit, formatCurrencyUnit } from '@ledgerhq/currencies'
import noop from 'lodash/noop'
import isNaN from 'lodash/isNaN'
import Input from 'components/base/Input'
import type { Unit } from '@ledgerhq/currencies'
function parseValue(value) {
return value.toString().replace(/,/, '.')
}
function format(unit: Unit, value: Value) {
let v = value === '' ? 0 : Number(value)
v *= 10 ** unit.magnitude
return formatCurrencyUnit(unit, v, {
disableRounding: true,
showAllDigits: false,
})
}
function unformat(unit, value) {
if (value === 0 || value === '') {
return 0
}
let v = parseCurrencyUnit(unit, value.toString())
v /= 10 ** unit.magnitude
return v
}
type Value = string | number
type Props = {
onChange: Function,
value: Value,
unit: Unit,
}
type State = {
isFocus: boolean,
value: Value,
}
class InputCurrency extends PureComponent<Props, State> {
static defaultProps = {
onChange: noop,
value: 0,
}
state = {
isFocus: false,
value: this.props.value,
}
componentWillReceiveProps(nextProps: Props) {
if (this.props.value !== nextProps.value) {
const { isFocus } = this.state
const value = isFocus ? nextProps.value : format(nextProps.unit, nextProps.value)
this.setState({
value,
})
}
}
handleChange = (v: Value) => {
v = parseValue(v)
// Check if value is valid Number
if (isNaN(Number(v))) {
return
}
this.emitOnChange(v)
this.setState({
value: v,
})
}
handleBlur = () => {
const { unit } = this.props
const { value } = this.state
const v = format(unit, value)
this.setState({
isFocus: false,
value: v,
})
}
handleFocus = () => {
const { unit } = this.props
this.setState(prev => ({
isFocus: true,
value: unformat(unit, prev.value),
}))
}
emitOnChange = (v: Value) => {
const { onChange } = this.props
const { value } = this.state
if (value.toString() !== v.toString()) {
onChange(v.toString())
}
}
render() {
const { value } = this.state
return (
<Input
{...this.props}
ff="Rubik"
value={value}
onChange={this.handleChange}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
/>
)
}
}
export default InputCurrency

15
src/components/base/InputCurrency/stories.js

@ -0,0 +1,15 @@
// @flow
import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import { getDefaultUnitByCoinType } from '@ledgerhq/currencies'
import InputCurrency from 'components/base/InputCurrency'
const stories = storiesOf('Components/base', module)
const unit = getDefaultUnitByCoinType(1)
stories.add('InputCurrency', () => <InputCurrency unit={unit} onChange={action('onChange')} />)

10
src/components/base/Label.js

@ -1,11 +1,15 @@
import styled from 'styled-components'
import { fontSize } from 'styled-system'
import { fontSize, color } from 'styled-system'
import fontFamily from 'styles/styled/fontFamily'
export default styled.label.attrs({
fontSize: 3,
ff: 'Museo Sans|Regular',
color: 'grey',
})`
${color};
${fontSize};
${fontFamily};
display: block;
text-transform: uppercase;
letter-spacing: 1px;
`

32
src/components/base/Modal/index.js

@ -12,6 +12,7 @@ import styled from 'styled-components'
import noop from 'lodash/noop'
import { rgba } from 'styles/helpers'
import { radii } from 'styles/theme'
import { closeModal, isModalOpened, getModalData } from 'reducers/modals'
@ -83,9 +84,8 @@ const Wrapper = styled(Tabbable).attrs({
const Body = styled(Box).attrs({
bg: p => p.theme.colors.white,
relative: true,
})`
border-radius: 5px;
`
borderRadius: 1,
})``
const CloseContainer = styled(Box).attrs({
p: 4,
@ -220,9 +220,7 @@ export const ModalBody = ({
<IconCross height={16} width={16} />
</CloseContainer>
)}
<Box p={5} {...props}>
{children}
</Box>
<Box {...props}>{children}</Box>
</Body>
)
@ -230,4 +228,26 @@ ModalBody.defaultProps = {
onClose: undefined,
}
export const ModalTitle = styled(Box).attrs({
ff: 'Museo Sans|Regular',
fontSize: 6,
color: 'dark',
align: 'center',
p: 5,
})``
export const ModalFooter = styled(Box).attrs({
px: 5,
py: 3,
})`
border-top: 2px solid ${p => p.theme.colors.lightGrey};
border-bottom-left-radius: ${radii[1]}px;
border-bottom-right-radius: ${radii[1]}px;
`
export const ModalContent = styled(Box).attrs({
px: 5,
pb: 5,
})``
export default connect(mapStateToProps, mapDispatchToProps)(Modal)

23
src/components/base/Modal/stories.js

@ -1,24 +1,29 @@
// @flow
import React, { Fragment } from 'react'
import React from 'react'
import { storiesOf } from '@storybook/react'
import { boolean } from '@storybook/addon-knobs'
import { Modal, ModalBody } from 'components/base/Modal'
import { Modal, ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
const stories = storiesOf('Components/Modal', module)
const stories = storiesOf('Components/base', module)
stories.add('basic', () => {
stories.add('Modal', () => {
const isOpened = boolean('isOpened', true)
return (
<Modal
isOpened={isOpened}
render={({ onClose }) => (
<Fragment>
<ModalBody>Hey!</ModalBody>
<ModalBody onClose={onClose}>Hoy!</ModalBody>
<ModalBody>Hu!</ModalBody>
</Fragment>
<ModalBody onClose={onClose}>
<ModalTitle>{'modal title'}</ModalTitle>
<ModalContent>{'this is the modal content'}</ModalContent>
<ModalFooter horizontal align="center">
<Box grow>{'modal footer'}</Box>
<Button primary>{'Next'}</Button>
</ModalFooter>
</ModalBody>
)}
/>
)

33
src/components/base/NewChart/Tooltip.js

@ -1,9 +1,12 @@
// @flow
import React, { Fragment } from 'react'
import React from 'react'
import type { Unit } from '@ledgerhq/currencies'
import { colors as themeColors } from 'styles/theme'
import { TooltipContainer } from 'components/base/Tooltip'
import FormattedVal from 'components/base/FormattedVal'
import type { Item } from './types'
@ -29,7 +32,17 @@ const Arrow = () => (
</svg>
)
const Tooltip = ({ d, renderTooltip }: { d: Item, renderTooltip?: Function }) => (
const Tooltip = ({
d,
renderTooltip,
fiat,
unit,
}: {
d: Item,
renderTooltip?: Function,
fiat?: string,
unit?: Unit,
}) => (
<div style={{ position: 'relative' }}>
<div
style={{
@ -45,12 +58,14 @@ const Tooltip = ({ d, renderTooltip }: { d: Item, renderTooltip?: Function }) =>
{renderTooltip ? (
renderTooltip(d)
) : (
<Fragment>
<div style={{ fontSize: 14 }}>
<b>{Math.round(d.value)}</b>
</div>
<span>{d.date}</span>
</Fragment>
<FormattedVal
alwaysShowSign={false}
color="white"
showCode
fiat={fiat}
unit={unit}
val={d.value}
/>
)}
</TooltipContainer>
<div style={{ background: 'red' }}>
@ -62,6 +77,8 @@ const Tooltip = ({ d, renderTooltip }: { d: Item, renderTooltip?: Function }) =>
Tooltip.defaultProps = {
renderTooltip: undefined,
fiat: undefined,
unit: undefined,
}
export default Tooltip

4
src/components/base/NewChart/handleMouseEvents.js

@ -26,7 +26,7 @@ export default function handleMouseEvents({
renderTooltip?: Function,
}) {
const { MARGINS, HEIGHT, WIDTH, NODES, DATA, x, y } = ctx
const { hideAxis } = props
const { hideAxis, unit } = props
const bisectDate = d3.bisector(d => d.parsedDate).left
@ -93,7 +93,7 @@ export default function handleMouseEvents({
.html(
renderToString(
<ThemeProvider theme={theme}>
<Tooltip renderTooltip={renderTooltip} d={d.ref} />
<Tooltip unit={unit} renderTooltip={renderTooltip} d={d.ref} />
</ThemeProvider>,
),
)

20
src/components/base/NewChart/index.js

@ -37,6 +37,8 @@ import React, { PureComponent } from 'react'
import * as d3 from 'd3'
import noop from 'lodash/noop'
import type { Unit } from '@ledgerhq/currencies'
import refreshNodes from './refreshNodes'
import refreshDraw from './refreshDraw'
import handleMouseEvents from './handleMouseEvents'
@ -46,27 +48,27 @@ import type { Data } from './types'
export type Props = {
data: Data, // eslint-disable-line react/no-unused-prop-types
unit: Unit, // eslint-disable-line react/no-unused-prop-types
id?: string, // eslint-disable-line react/no-unused-prop-types
height?: number,
tickXScale: string, // eslint-disable-line react/no-unused-prop-types
color?: string, // eslint-disable-line react/no-unused-prop-types
hideAxis?: boolean, // eslint-disable-line react/no-unused-prop-types
interactive?: boolean, // eslint-disable-line react/no-unused-prop-types
height: number,
dateFormat?: string, // eslint-disable-line react/no-unused-prop-types
id?: string, // eslint-disable-line react/no-unused-prop-types
nbTicksX: number, // eslint-disable-line react/no-unused-prop-types
interactive?: boolean, // eslint-disable-line react/no-unused-prop-types
renderTooltip?: Function, // eslint-disable-line react/no-unused-prop-types
renderTickX?: Function, // eslint-disable-line react/no-unused-prop-types
renderTickY?: Function, // eslint-disable-line react/no-unused-prop-types
}
class Chart extends PureComponent<Props> {
static defaultProps = {
id: 'chart',
color: '#000',
hideAxis: false,
interactive: true,
height: 400,
dateFormat: '%Y-%m-%d',
id: 'chart',
nbTicksX: 5,
tickXScale: 'month',
}
componentDidMount() {
@ -132,7 +134,7 @@ class Chart extends PureComponent<Props> {
}
// Derived draw variables
ctx.HEIGHT = Math.max(0, height - ctx.MARGINS.top - ctx.MARGINS.bottom)
ctx.HEIGHT = Math.max(0, (height || 0) - ctx.MARGINS.top - ctx.MARGINS.bottom)
ctx.WIDTH = Math.max(0, this._width - ctx.MARGINS.left - ctx.MARGINS.right)
// Scales and areas

28
src/components/base/NewChart/refreshDraw.js

@ -1,15 +1,41 @@
// @flow
import * as d3 from 'd3'
import moment from 'moment'
import { formatShort } from '@ledgerhq/currencies'
import { colors as themeColors } from 'styles/theme'
import type { Props } from '.'
import type { CTX } from './types'
const TICK_X_SCALE = {
week: 7,
month: 10,
year: 13,
default: 10,
}
function getTickXCount(tickXScale) {
return TICK_X_SCALE[tickXScale] || TICK_X_SCALE.default
}
const RENDER_TICK_X = {
year: 'MMM.',
default: 'MMM. D',
}
function getRenderTickX(selectedTime) {
return t => moment(t).format(RENDER_TICK_X[selectedTime] || RENDER_TICK_X.default)
}
export default function refreshDraw({ ctx, props }: { ctx: CTX, props: Props }) {
const { NODES, WIDTH, HEIGHT, MARGINS, COLORS, INVALIDATED, DATA, x, y } = ctx
const { hideAxis, interactive, renderTickX, renderTickY, nbTicksX } = props
const { hideAxis, interactive, tickXScale, unit } = props
const nbTicksX = getTickXCount(tickXScale)
const renderTickX = getRenderTickX(tickXScale)
const renderTickY = t => formatShort(unit, t)
const area = d3
.area()

11
src/components/base/NewChart/stories.js

@ -1,6 +1,7 @@
// @flow
import React, { Component, Fragment } from 'react'
import { getDefaultUnitByCoinType } from '@ledgerhq/currencies'
import Chance from 'chance'
import moment from 'moment'
import { storiesOf } from '@storybook/react'
@ -9,9 +10,10 @@ import { color } from '@storybook/addon-knobs/react'
import Chart from 'components/base/NewChart'
const stories = storiesOf('Components/Chart', module)
const stories = storiesOf('Components/base', module)
const data = generateRandomData(365)
const unit = getDefaultUnitByCoinType(0)
type State = {
start: number,
@ -45,14 +47,15 @@ class Wrapper extends Component<any, State> {
hideAxis={boolean('hideAxis', false)}
color={color('color', '#5f8ced')}
data={data.slice(start, stop)}
height={number('height', 300, { min: 100, max: 900 })}
height={number('height', 300)}
unit={unit}
/>
</Fragment>
)
}
}
stories.add('default', () => <Wrapper />)
stories.add('Chart', () => <Wrapper />)
function generateRandomData(n) {
const today = moment()
@ -62,7 +65,7 @@ function generateRandomData(n) {
while (!day.isSame(today)) {
data.push({
date: day.format('YYYY-MM-DD'),
value: chance.integer({ min: 0, max: 50e3 }),
value: chance.integer({ min: 0.5e8, max: 1e8 }),
})
day.add(1, 'day')
}

4
src/components/base/Pills/stories.js

@ -5,7 +5,7 @@ import { storiesOf } from '@storybook/react'
import Pills from 'components/base/Pills'
const stories = storiesOf('Components/Pills', module)
const stories = storiesOf('Components/base', module)
type State = {
key: string,
@ -32,4 +32,4 @@ class Wrapper extends PureComponent<any, State> {
}
}
stories.add('basic', () => <Wrapper />)
stories.add('Pills', () => <Wrapper />)

4
src/components/base/QRCode/stories.js

@ -7,6 +7,6 @@ import { text, number } from '@storybook/addon-knobs'
import QRCode from 'components/base/QRCode'
const stories = storiesOf('Components/QRCode', module)
const stories = storiesOf('Components/base', module)
stories.add('basic', () => <QRCode data={text('data', 'sample')} size={number('size', 200)} />)
stories.add('QRCode', () => <QRCode data={text('data', 'sample')} size={number('size', 200)} />)

4
src/components/base/Radio/stories.js

@ -5,8 +5,8 @@ import { boolean } from '@storybook/addon-knobs'
import Radio from 'components/base/Radio'
const stories = storiesOf('Components/Radio', module)
const stories = storiesOf('Components/base', module)
stories.add('basic', () => (
stories.add('Radio', () => (
<Radio isChecked={boolean('checked', false)} onChange={action('onChange')} />
))

79
src/components/base/Search/stories.js

@ -1,79 +0,0 @@
// @flow
import React from 'react'
import PropTypes from 'prop-types'
import { storiesOf } from '@storybook/react'
import { text, boolean } from '@storybook/addon-knobs'
import Search from 'components/base/Search'
const stories = storiesOf('Components/Search', module)
const items = [
{ key: 'aleksandr-grichtchouk', name: 'Aleksandr Grichtchouk' },
{ key: 'fabiano-caruana', name: 'Fabiano Caruana' },
{ key: 'garry-kasparov', name: 'Garry Kasparov' },
{ key: 'hikaru-nakamura', name: 'Hikaru Nakamura' },
{ key: 'levon-aronian', name: 'Levon Aronian' },
{ key: 'magnus-carlsen', name: 'Magnus Carlsen' },
{ key: 'maxime-vachier-lagrave', name: 'Maxime Vachier-Lagrave' },
{ key: 'shakhriyar-mamedyarov', name: 'Shakhriyar Mamedyarov' },
{ key: 'veselin-topalov', name: 'Veselin Topalov' },
{ key: 'viswanathan-anand', name: 'Viswanathan Anand' },
{ key: 'vladimir-kramnik', name: 'Vladimir Kramnik' },
]
const Wrapper = ({ children }) => (
<div>
<div style={{ opacity: 0.2 }}>{'(Change the search value in knobs)'}</div>
{children}
</div>
)
Wrapper.propTypes = {
children: PropTypes.any.isRequired,
}
stories.add('basic', () => {
const value = text('value', '')
const filterEmpty = boolean('filterEmpty', false)
return (
<Wrapper>
<Search
value={value}
items={items}
filterEmpty={filterEmpty}
fuseOptions={{
keys: ['name'],
}}
render={items => items.map(item => <div key={item.name}>{item.name}</div>)}
/>
</Wrapper>
)
})
stories.add('highlight matches', () => {
const value = text('value', '')
const filterEmpty = boolean('filterEmpty', false)
return (
<Wrapper>
<Search
value={value}
items={items}
filterEmpty={filterEmpty}
highlight
fuseOptions={{
keys: ['name'],
}}
renderHighlight={(text, key) => (
<b key={key} style={{ textDecoration: 'underline', color: 'red' }}>
{text}
</b>
)}
render={items =>
items.map(item => <div key={item.key}>{item.name_highlight || item.name}</div>)
}
/>
</Wrapper>
)
})

33
src/components/base/Select/Triangles.js

@ -1,33 +0,0 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import Box from 'components/base/Box'
const UpTriangle = styled.div`
width: 0;
height: 0;
border-left: ${p => p.size}px solid transparent;
border-right: ${p => p.size}px solid transparent;
border-bottom: ${p => p.size}px solid ${p => p.theme.colors[p.color]};
`
const DownTriangle = styled.div`
width: 0;
height: 0;
border-left: ${p => p.size}px solid transparent;
border-right: ${p => p.size}px solid transparent;
border-top: ${p => p.size}px solid ${p => p.theme.colors[p.color]};
`
const Triangles = ({ size, color }: { size?: number, color?: string }) => (
<Box flow={1}>
<UpTriangle size={size} color={color} />
<DownTriangle size={size} color={color} />
</Box>
)
Triangles.defaultProps = { size: 5, color: 'fog' }
export default Triangles

48
src/components/base/Select/index.js

@ -14,8 +14,7 @@ import Search from 'components/base/Search'
import Text from 'components/base/Text'
import IconCheck from 'icons/Check'
import Triangles from './Triangles'
import IconAngleDown from 'icons/AngleDown'
type Props = {
fuseOptions?: Object,
@ -37,10 +36,11 @@ const Container = styled(Box).attrs({ relative: true, color: 'graphite' })``
const TriggerBtn = styled(Box).attrs({
ff: 'Open Sans|SemiBold',
p: 4,
fontSize: 4,
pl: 3,
pr: 5,
})`
min-height: 64px;
height: 40px;
${space};
border: 1px solid ${p => p.theme.colors.fog};
border-radius: 3px;
@ -62,12 +62,6 @@ const Item = styled(Box).attrs({
background: ${p => (p.highlighted ? p.theme.colors.lightGrey : p.theme.colors.white)};
`
const ItemWrapper = styled(Box)`
& + & {
border-top: 1px solid ${p => p.theme.colors.fog};
}
`
const Dropdown = styled(Box).attrs({
mt: 1,
})`
@ -81,7 +75,7 @@ const Dropdown = styled(Box).attrs({
z-index: 1;
`
const FloatingTriangles = styled(Box).attrs({
const FloatingDown = styled(Box).attrs({
alignItems: 'center',
justifyContent: 'center',
mr: 2,
@ -90,6 +84,7 @@ const FloatingTriangles = styled(Box).attrs({
top: 0;
right: 0;
bottom: 0;
color: ${p => p.theme.colors.grey};
// to "simulate" border to make arrows appears at the exact same place as
// the no-input version
@ -97,15 +92,12 @@ const FloatingTriangles = styled(Box).attrs({
`
const IconSelected = styled(Box).attrs({
bg: 'wallet',
color: 'white',
color: 'wallet',
alignItems: 'center',
justifyContent: 'center',
})`
border-radius: 50%;
height: 15px;
font-size: 5px;
width: 15px;
height: 12px;
width: 12px;
opacity: ${p => (p.selected ? 1 : 0)};
`
@ -156,7 +148,7 @@ class Select extends PureComponent<Props> {
}}
>
{items.map((item, i) => (
<ItemWrapper
<Box
key={keyProp ? item[keyProp] : item.key}
innerRef={n => (this._children[i] = n)}
{...getItemProps({ item })}
@ -171,17 +163,17 @@ class Select extends PureComponent<Props> {
</Box>
<Box>
<IconSelected selected={selectedItem === item}>
<IconCheck height={7} width={7} />
<IconCheck height={12} width={12} />
</IconSelected>
</Box>
</Item>
</ItemWrapper>
</Box>
))}
</GrowScroll>
) : (
<ItemWrapper>
<Box>
<Item>{'No results'}</Item>
</ItemWrapper>
</Box>
)}
</Dropdown>
)
@ -231,9 +223,9 @@ class Select extends PureComponent<Props> {
{searchable ? (
<Box relative>
<Input keepEvent {...getInputProps({ placeholder })} onClick={openMenu} />
<FloatingTriangles>
<Triangles />
</FloatingTriangles>
<FloatingDown>
<IconAngleDown width={10} height={10} />
</FloatingDown>
</Box>
) : (
<TriggerBtn
@ -250,9 +242,9 @@ class Select extends PureComponent<Props> {
<Text color="fog">{placeholder}</Text>
)}
</Box>
<FloatingTriangles>
<Triangles />
</FloatingTriangles>
<FloatingDown>
<IconAngleDown width={10} height={10} />
</FloatingDown>
</TriggerBtn>
)}
{isOpen &&

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

@ -7,7 +7,7 @@ import Box from 'components/base/Box'
import Select from 'components/base/Select'
import Text from 'components/base/Text'
const stories = storiesOf('Components/Select', module)
const stories = storiesOf('Components/base/Select', module)
const itemsChessPlayers = [
{ key: 'aleksandr-grichtchouk', name: 'Aleksandr Grichtchouk' },

4
src/components/base/Tabs/stories.js

@ -6,9 +6,9 @@ import { storiesOf } from '@storybook/react'
import Tabs from 'components/base/Tabs'
const stories = storiesOf('Components/Tabs', module)
const stories = storiesOf('Components/base', module)
stories.add('basic', () => (
stories.add('Tabs', () => (
<Tabs
index={number('index', 0, {
min: 0,

4
src/components/base/Text/stories.js

@ -10,7 +10,7 @@ import Text from 'components/base/Text'
import { fontFamilies, fontSizes } from 'styles/theme'
const stories = storiesOf('Common/Text', module)
const stories = storiesOf('Common', module)
const Title = ({ children }: { children: string }) => (
<Text style={{ fontFamily: 'monospace' }} fontSize={5}>
@ -56,7 +56,7 @@ const FontSizes = () => (
</Box>
)
stories.add('all fonts styles', () => (
stories.add('Text styles', () => (
<Box flow={4}>
<FontStyles txt={text('text', 'The quick brown fox jumps over the lazy dog')} />
<FontSizes />

4
src/components/base/Tooltip/stories.js

@ -5,6 +5,6 @@ import { storiesOf } from '@storybook/react'
import Tooltip from 'components/base/Tooltip'
const stories = storiesOf('Components/Tooltip', module)
const stories = storiesOf('Components/base', module)
stories.add('basic', () => <Tooltip render={() => <div>Oyo!</div>}>Hover me!</Tooltip>)
stories.add('Tooltip', () => <Tooltip render={() => <div>Oyo!</div>}>Hover me!</Tooltip>)

6
src/components/modals/AddAccount/ImportAccounts.js

@ -3,7 +3,7 @@
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import type { T, Account } from 'types/common'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
@ -13,7 +13,7 @@ import Input from 'components/base/Input'
type Props = {
t: T,
accounts: Array<Object>,
accounts: Account[],
onImportAccounts: Function,
}
@ -110,7 +110,7 @@ class ImportAccounts extends PureComponent<Props, State> {
val={account.balance}
/>
</Box>
<Box>Transactions: {account.transactions.length}</Box>
<Box>Operations: {account.operations.length}</Box>
</Box>
</Box>
)

4
src/components/modals/AddAccount/RestoreAccounts.js

@ -7,14 +7,14 @@ import Box from 'components/base/Box'
import Button from 'components/base/Button'
import Text from 'components/base/Text'
import type { Accounts } from 'types/common'
import type { Account } from 'types/common'
const Container = styled(Box)`
border: 1px solid ${p => p.theme.colors.alertRed};
`
type Props = {
archivedAccounts: Accounts,
archivedAccounts: Account[],
updateAccount: Function,
}

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

@ -12,7 +12,7 @@ import type { Currency } from '@ledgerhq/currencies'
import { MODAL_ADD_ACCOUNT } from 'constants'
import type { Accounts, Device, T } from 'types/common'
import type { Account, Device, T } from 'types/common'
import { closeModal } from 'reducers/modals'
import { canCreateAccount, getAccounts, getArchivedAccounts } from 'reducers/accounts'
@ -88,7 +88,7 @@ const Steps = {
val={progress.balance || 0}
/>
</Box>
<Box>Transactions: {progress.transactions || 0}</Box>
<Box>Operations: {progress.operations || 0}</Box>
{progress.success && <Box>Finish ! Next account in progress...</Box>}
</Box>
)}
@ -96,8 +96,8 @@ const Steps = {
),
listAccounts: (props: Object) => {
const { accounts, archivedAccounts } = props
const emptyAccounts = accounts.filter(account => account.transactions.length === 0)
const existingAccounts = accounts.filter(account => account.transactions.length > 0)
const emptyAccounts = accounts.filter(account => account.operations.length === 0)
const existingAccounts = accounts.filter(account => account.operations.length > 0)
const canCreateAccount = props.canCreateAccount && emptyAccounts.length === 1
const newAccount = emptyAccounts[0]
return (
@ -117,9 +117,9 @@ const Steps = {
type Step = 'chooseCurrency' | 'connectDevice' | 'inProgress' | 'listAccounts'
type Props = {
accounts: Accounts,
accounts: Account[],
addAccount: Function,
archivedAccounts: Accounts,
archivedAccounts: Account[],
canCreateAccount: boolean,
closeModal: Function,
counterValues: Object,
@ -130,7 +130,7 @@ type Props = {
}
type State = {
accounts: Accounts,
accounts: Account[],
currency: Currency | null,
fetchingCounterValues: boolean,
progress: null | Object,

71
src/components/modals/Receive.js → src/components/modals/Receive/index.js

@ -2,18 +2,18 @@
import React, { PureComponent, Fragment } from 'react'
import { translate } from 'react-i18next'
import get from 'lodash/get'
import { MODAL_RECEIVE } from 'constants'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import Input from 'components/base/Input'
import Label from 'components/base/Label'
import Modal, { ModalBody } from 'components/base/Modal'
import Button from 'components/base/Button'
import Modal, { ModalBody, ModalTitle, ModalFooter, ModalContent } from 'components/base/Modal'
import ReceiveBox from 'components/ReceiveBox'
import RequestAmount from 'components/RequestAmount'
import SelectAccount from 'components/SelectAccount'
import Text from 'components/base/Text'
import type { Account as AccountType, T } from 'types/common'
@ -23,12 +23,15 @@ type Props = {
type State = {
account: AccountType | null,
amount: string,
amount: Object,
}
const defaultState = {
account: null,
amount: '',
amount: {
left: 0,
right: 0,
},
}
class ReceiveModal extends PureComponent<Props, State> {
@ -52,9 +55,16 @@ class ReceiveModal extends PureComponent<Props, State> {
...defaultState,
})
_steps = [
'receiveModal:Infos',
'receiveModal:ConnectDevice',
'receiveModal:SecureValidation',
'receiveModal:Confirmation',
].map(v => ({ label: this.props.t(v) }))
render() {
const { amount } = this.state
const { t } = this.props
const { amount } = this.state
return (
<Modal
@ -65,32 +75,31 @@ class ReceiveModal extends PureComponent<Props, State> {
return (
<ModalBody onClose={onClose} flow={3}>
<Text fontSize={4} color="graphite">
{t('receive:title')}
</Text>
<Box flow={1}>
<Label>Account</Label>
<SelectAccount value={account} onChange={this.handleChangeInput('account')} />
</Box>
{account && (
<Fragment>
<Box flow={1}>
<Label>Request amount</Label>
<Input
type="number"
min={0}
max={account.balance / 1e8}
onChange={this.handleChangeInput('amount')}
/>
</Box>
<ReceiveBox account={account} amount={amount} />
</Fragment>
)}
<Box horizontal justifyContent="center">
<ModalTitle>{t('receive:title')}</ModalTitle>
<ModalContent>
<Box flow={1}>
<Label>Account</Label>
<SelectAccount value={account} onChange={this.handleChangeInput('account')} />
</Box>
{account && (
<Fragment>
<Box flow={1}>
<Label>Request amount</Label>
<RequestAmount
account={account}
value={amount}
onChange={this.handleChangeInput('amount')}
/>
</Box>
<ReceiveBox account={account} amount={amount.left} />
</Fragment>
)}
</ModalContent>
<ModalFooter horizontal align="center" justify="flex-end">
<Button primary onClick={onClose}>
Close
{'Close'}
</Button>
</Box>
</ModalFooter>
</ModalBody>
)
}}

81
src/components/modals/Send.js

@ -1,8 +1,9 @@
// @flow
import React, { Fragment, PureComponent } from 'react'
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import get from 'lodash/get'
import { getDefaultUnitByCoinType } from '@ledgerhq/currencies'
import type { T } from 'types/common'
@ -12,11 +13,11 @@ import Box from 'components/base/Box'
import Button from 'components/base/Button'
import Input from 'components/base/Input'
import Label from 'components/base/Label'
import Modal, { ModalBody } from 'components/base/Modal'
import Modal, { ModalBody, ModalTitle, ModalFooter, ModalContent } from 'components/base/Modal'
import Breadcrumb from 'components/Breadcrumb'
import RecipientAddress from 'components/RecipientAddress'
import SelectAccount from 'components/SelectAccount'
import Text from 'components/base/Text'
import FormattedVal from 'components/base/FormattedVal'
const Steps = {
'1': ({ t, ...props }: Object) => (
@ -29,9 +30,8 @@ const Steps = {
}
}}
>
<Box flow={5}>
<Text fontSize={6}>{t('send:title')}</Text>
<Box flow={2}>
<Box flow={4}>
<Box flow={1}>
<Label>Account to debit</Label>
<SelectAccount onChange={props.onChangeInput('account')} value={props.value.account} />
</Box>
@ -43,16 +43,6 @@ const Steps = {
<Label>Amount</Label>
<Input onChange={props.onChangeInput('amount')} value={props.value.amount} />
</Box>
<Box horizontal alignItems="center">
<Box grow>
<Text>Cancel</Text>
</Box>
<Box justifyContent="flex-end">
<Button type="submit" primary disabled={!props.canSubmit}>
Next
</Button>
</Box>
</Box>
</Box>
</form>
),
@ -97,16 +87,6 @@ class Send extends PureComponent<Props, State> {
...defaultState,
}
componentWillMount() {
const { t } = this.props
this._items = [
{ label: t('sendModal:Amount') },
{ label: t('sendModal:Summary') },
{ label: t('sendModal:SecureValidation') },
{ label: t('sendModal:Confirmation') },
]
}
getStepProps(data: any) {
const { inputValue, step } = this.state
const { t } = this.props
@ -135,8 +115,6 @@ class Send extends PureComponent<Props, State> {
}
}
_items = []
handleChangeInput = (key: $Keys<InputValue>) => (value: $Values<InputValue>) =>
this.setState(prev => ({
inputValue: {
@ -155,27 +133,44 @@ class Send extends PureComponent<Props, State> {
...defaultState,
})
_steps = [
'sendModal:Amount',
'sendModal:Summary',
'sendModal:SecureValidation',
'sendModal:Confirmation',
].map(v => ({ label: this.props.t(v) }))
render() {
const { step } = this.state
const { t } = this.props
const Step = Steps[step]
return (
<Modal
name={MODAL_SEND}
onHide={this.handleHide}
render={({ data, onClose }) => {
const Step = Steps[step]
return (
<Fragment>
<ModalBody p={3}>
<Breadcrumb currentStep={step} items={this._items} />
</ModalBody>
<ModalBody onClose={onClose}>
<Step {...this.getStepProps(data)} />
</ModalBody>
</Fragment>
)
}}
render={({ data, onClose }) => (
<ModalBody onClose={onClose}>
<ModalTitle>{t('send:title')}</ModalTitle>
<ModalContent>
<Box mb={6} mt={2}>
<Breadcrumb currentStep={step} items={this._steps} />
</Box>
<Step {...this.getStepProps(data)} />
</ModalContent>
<ModalFooter horizontal align="center">
<Box grow>
<Label>{'Total spent'}</Label>
<FormattedVal
color="dark"
val={15496420404}
unit={getDefaultUnitByCoinType(0)}
showCode
/>
</Box>
<Button primary>{'Next'}</Button>
</ModalFooter>
</ModalBody>
)}
/>
)
}

115
src/components/modals/SettingsAccount.js

@ -15,8 +15,7 @@ import { setDataModal, closeModal } from 'reducers/modals'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import Input from 'components/base/Input'
import Modal, { ModalBody } from 'components/base/Modal'
import Text from 'components/base/Text'
import Modal, { ModalBody, ModalTitle, ModalFooter, ModalContent } from 'components/base/Modal'
import Label from 'components/base/Label'
import IconEdit from 'icons/Edit'
@ -51,8 +50,8 @@ const defaultState = {
nameHovered: false,
}
function hasNoTransactions(account: Account) {
return get(account, 'transactions.length', 0) === 0
function hasNoOperations(account: Account) {
return get(account, 'operations.length', 0) === 0
}
class SettingsAccount extends PureComponent<Props, State> {
@ -134,7 +133,7 @@ class SettingsAccount extends PureComponent<Props, State> {
handleArchiveAccount = (account: Account) => () => {
const { push, closeModal, updateAccount, removeAccount } = this.props
const shouldRemove = hasNoTransactions(account)
const shouldRemove = hasNoOperations(account)
if (shouldRemove) {
removeAccount(account)
@ -162,65 +161,61 @@ class SettingsAccount extends PureComponent<Props, State> {
const account = this.getAccount(data)
return (
<ModalBody onClose={onClose} flow={3}>
<Text fontSize={4} color="graphite">
Account settings
</Text>
<Box
alignItems="center"
flow={2}
horizontal
onMouseEnter={this.handleHoveredName(true)}
onMouseLeave={this.handleHoveredName(false)}
>
<Box>
{editName ? (
<form onSubmit={this.handleSubmitName(account)}>
<Box alignItems="center" horizontal flow={2}>
<Box>
<Input value={account.name} onChange={this.handleChangeName} />
</Box>
<Box flow={2} horizontal>
<Button type="button" onClick={this.handleCancelEditName(data)}>
Cancel
</Button>
<Button type="submit" primary>
Ok
</Button>
<ModalBody onClose={onClose}>
<ModalTitle>{'Account settings'}</ModalTitle>
<ModalContent flow={4}>
<Box
alignItems="center"
flow={2}
horizontal
onMouseEnter={this.handleHoveredName(true)}
onMouseLeave={this.handleHoveredName(false)}
>
<Box>
{editName ? (
<form onSubmit={this.handleSubmitName(account)}>
<Box alignItems="center" horizontal flow={2}>
<Box>
<Input value={account.name} onChange={this.handleChangeName} />
</Box>
<Box flow={2} horizontal>
<Button type="button" onClick={this.handleCancelEditName(data)}>
Cancel
</Button>
<Button type="submit" primary>
Ok
</Button>
</Box>
</Box>
</form>
) : (
account.name
)}
</Box>
{!editName &&
nameHovered && (
<Box onClick={this.handleEditName(true)} style={{ cursor: 'pointer' }}>
<IconEdit height={16} width={16} />
</Box>
</form>
) : (
account.name
)}
)}
</Box>
{!editName &&
nameHovered && (
<Box onClick={this.handleEditName(true)} style={{ cursor: 'pointer' }}>
<IconEdit height={16} width={16} />
</Box>
)}
</Box>
<Box>
<Label>{'Minimum confirmations'}</Label>
<Input
type="number"
min={1}
max={100}
value={account.settings.minConfirmations}
onChange={this.handleChangeMinConfirmations(account)}
/>
</Box>
<Box horizontal grow alignItems="flex-end" flow={2}>
<Box grow>
<Button onClick={this.handleArchiveAccount(account)}>
{hasNoTransactions(account) ? 'Remove account' : 'Archive account'}
</Button>
</Box>
<Box grow>
<Button primary>Go to account</Button>
<Box>
<Label>{'Minimum confirmations'}</Label>
<Input
type="number"
min={1}
max={100}
value={account.settings.minConfirmations}
onChange={this.handleChangeMinConfirmations(account)}
/>
</Box>
</Box>
</ModalContent>
<ModalFooter horizontal justify="flex-end" flow={2}>
<Button onClick={this.handleArchiveAccount(account)}>
{hasNoOperations(account) ? 'Remove account' : 'Archive account'}
</Button>
<Button primary>Go to account</Button>
</ModalFooter>
</ModalBody>
)
}}

5
src/constants.js

@ -1,5 +1,6 @@
export const CHECK_UPDATE_TIMEOUT = 5e3
export const SYNC_ACCOUNT_TIMEOUT = 3e3
export const CHECK_UPDATE_DELAY = 5e3
export const SYNC_ACCOUNT_DELAY = 3e3
export const SYNC_COUNTER_VALUES_DELAY = 60e3
export const MODAL_ADD_ACCOUNT = 'MODAL_ADD_ACCOUNT'
export const MODAL_SEND = 'MODAL_SEND'

14
src/helpers/__tests__/balance.test.js

@ -2,11 +2,13 @@ import { getBalanceHistoryForAccount, getBalanceHistoryForAccounts } from 'helpe
const counterValues = {
'BTC-USD': {
'2018-01-01': 1000,
'2018-01-02': 2000,
'2018-01-03': 3000,
'2018-01-04': 4000,
'2018-01-05': 5000,
byDate: {
'2018-01-01': 1000,
'2018-01-02': 2000,
'2018-01-03': 3000,
'2018-01-04': 4000,
'2018-01-05': 5000,
},
},
}
@ -67,7 +69,7 @@ describe('helpers > balance', () => {
])
})
test('should work if interval dont contain transactions', () => {
test('should work if interval dont contain operations', () => {
const account = {
coinType: 0,
balanceByDay: {

8
src/helpers/balance.js

@ -8,7 +8,7 @@ import first from 'lodash/first'
import isUndefined from 'lodash/isUndefined'
import last from 'lodash/last'
import type { Accounts, Account } from 'types/common'
import type { Account } from 'types/common'
type DateInterval = {
start: string,
@ -21,7 +21,7 @@ type BalanceHistoryDay = {
}
type CalculateBalance = {
accounts: Accounts,
accounts: Account[],
counterValue: string,
counterValues: Object,
daysCount: number,
@ -70,7 +70,7 @@ export function getBalanceHistoryForAccount({
interval: DateInterval,
}): Array<BalanceHistoryDay> {
const unit = getDefaultUnitByCoinType(account.coinType)
const counterVals = counterValues[`${unit.code}-${counterValue}`]
const counterVals = counterValues[`${unit.code}-${counterValue}`].byDate
let lastBalance = getBalanceAtIntervalStart(account, interval)
return mapInterval(interval, date => {
let balance = 0
@ -100,7 +100,7 @@ export function getBalanceHistoryForAccounts({
interval,
}: {
counterValue: string,
accounts: Accounts,
accounts: Account[],
counterValues: Object,
interval: DateInterval,
}): Array<BalanceHistoryDay> {

47
src/helpers/btc.js

@ -7,7 +7,7 @@ import groupBy from 'lodash/groupBy'
import noop from 'lodash/noop'
import uniqBy from 'lodash/uniqBy'
import type { Transactions } from 'types/common'
import type { Operation } from 'types/common'
const GAP_LIMIT_ADDRESSES = 20
@ -22,7 +22,7 @@ export const networks = [
},
]
export function computeTransaction(addresses: Array<string>) {
export function computeOperation(addresses: Array<string>) {
return (t: Object) => {
const outputVal = t.outputs
.filter(o => addresses.includes(o.address))
@ -30,10 +30,11 @@ export function computeTransaction(addresses: Array<string>) {
const inputVal = t.inputs
.filter(i => addresses.includes(i.address))
.reduce((acc, cur) => acc + cur.value, 0)
const balance = outputVal - inputVal
const amount = outputVal - inputVal
return {
address: t.balance > 0 ? t.inputs[0].address : t.outputs[0].address,
balance,
id: t.hash,
address: t.amount > 0 ? t.inputs[0].address : t.outputs[0].address,
amount,
confirmations: t.confirmations,
hash: t.hash,
receivedAt: t.received_at,
@ -41,8 +42,8 @@ export function computeTransaction(addresses: Array<string>) {
}
}
export function getBalanceByDay(transactions: Transactions) {
const txsByDate = groupBy(transactions, tx => {
export function getBalanceByDay(operations: Operation[]) {
const txsByDate = groupBy(operations, tx => {
const [date] = new Date(tx.receivedAt).toISOString().split('T')
return date
})
@ -55,7 +56,7 @@ export function getBalanceByDay(transactions: Transactions) {
const txs = txsByDate[k]
balance += txs.reduce((r, v) => {
r += v.balance
r += v.amount
return r
}, 0)
@ -91,7 +92,7 @@ export async function getAccount({
const script = segwit ? parseInt(network.scriptHash, 10) : parseInt(network.pubKeyHash, 10)
let balance = 0
let transactions = []
let operations = []
let lastAddress = null
const pubKeyToSegwitAddress = (pubKey, scriptVersion) => {
@ -149,31 +150,31 @@ export async function getAccount({
let txs = []
const transactionsOpts = { coin_type: coinType }
const operationsOpts = { coin_type: coinType }
try {
txs = await ledger.getTransactions(listAddresses, transactionsOpts)
txs = await ledger.getTransactions(listAddresses, operationsOpts)
txs = txs.filter(t => !allTxsHash.includes(t.hash)).reverse()
} catch (e) {
console.log('getTransactions', e) // eslint-disable-line no-console
console.log('getOperations', e) // eslint-disable-line no-console
}
const hasTransactions = txs.length > 0
const hasOperations = txs.length > 0
if (hasTransactions) {
const newTransactions = txs.map(computeTransaction(allAddresses))
const txHashs = transactions.map(t => t.hash)
if (hasOperations) {
const newOperations = txs.map(computeOperation(allAddresses))
const txHashs = operations.map(t => t.hash)
balance = newTransactions
balance = newOperations
.filter(t => !txHashs.includes(t.hash))
.reduce((result, v) => result + v.balance, balance)
.reduce((result, v) => result + v.amount, balance)
lastAddress = getLastAddress(addresses, txs[0])
transactions = uniqBy([...transactions, ...newTransactions], t => t.hash)
operations = uniqBy([...operations, ...newOperations], t => t.hash)
onProgress({
balance,
transactions: transactions.length,
operations: operations.length,
})
return nextPath(index + (GAP_LIMIT_ADDRESSES - 1))
@ -189,11 +190,11 @@ export async function getAccount({
return {
...nextAddress,
addresses: transactions.length > 0 ? allAddresses : [],
addresses: operations.length > 0 ? allAddresses : [],
balance,
balanceByDay: getBalanceByDay(transactions),
balanceByDay: getBalanceByDay(operations),
rootPath,
transactions,
operations,
}
})

10
src/helpers/cpuUsage.js

@ -1,9 +1,9 @@
const TIMEOUT_CPU_USAGE = 5e3
const cpuUsage = (startTime, startUsage) => {
const now = Date.now()
const wait = ms => new Promise(resolve => setTimeout(resolve, ms))
while (Date.now() - now < 500);
const cpuUsage = async (startTime, startUsage) => {
await wait(500)
const newStartTime = process.hrtime()
const newStartUsage = process.cpuUsage()
@ -28,8 +28,8 @@ const cpuUsage = (startTime, startUsage) => {
}
export default callback => {
const initCpuUsage = (startTime, startUsage) => {
const { cpuPercent, newStartTime, newStartUsage } = cpuUsage(startTime, startUsage)
const initCpuUsage = async (startTime, startUsage) => {
const { cpuPercent, newStartTime, newStartUsage } = await cpuUsage(startTime, startUsage)
callback(cpuPercent)

11
src/helpers/db.js

@ -5,6 +5,7 @@ import set from 'lodash/set'
import get from 'lodash/get'
import { serializeAccounts, deserializeAccounts } from 'reducers/accounts'
import { serializeCounterValues, deserializeCounterValues } from 'reducers/counterValues'
type DBKey = 'settings' | 'accounts' | 'counterValues'
@ -34,6 +35,16 @@ function middleware(type, key, data: any) {
}
}
if (key === 'counterValues') {
if (type === 'get') {
data = serializeCounterValues(data)
}
if (type === 'set') {
data = deserializeCounterValues(data)
}
}
return data
}

4
src/internals/accounts/sync.js

@ -4,9 +4,9 @@ import { getAccount, getHDNode, networks } from 'helpers/btc'
const network = networks[1]
function syncAccount({ id, transactions, ...currentAccount }) {
function syncAccount({ id, operations, ...currentAccount }) {
const hdnode = getHDNode({ xpub58: id, network })
const allTxsHash = transactions.map(t => t.hash)
const allTxsHash = operations.map(t => t.hash)
return getAccount({ hdnode, network, allTxsHash, segwit: true, ...currentAccount }).then(
account => ({
id,

8
src/internals/usb/wallet/accounts.js

@ -154,11 +154,11 @@ export default async ({
network,
rootPath: path,
segwit,
onProgress: ({ transactions, ...progress }) =>
transactions > 0 && onProgress({ account: currentAccount, transactions, ...progress }),
onProgress: ({ operations, ...progress }) =>
operations > 0 && onProgress({ account: currentAccount, operations, ...progress }),
})
const hasTransactions = account.transactions.length > 0
const hasOperations = account.operations.length > 0
accounts.push({
id: xpub58,
@ -166,7 +166,7 @@ export default async ({
...account,
})
if (hasTransactions) {
if (hasOperations) {
onProgress({
success: true,
})

4
src/main/bridge.js

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

24
src/main/counterValuesSync.js

@ -0,0 +1,24 @@
// @flow
import axios from 'axios'
type SendFunction = (type: string, data: *) => void
export default async (send: SendFunction, { counterValue, currencies }: Object) => {
const data = await axios
.get(
`https://min-api.cryptocompare.com/data/pricemulti?extraParams=ledger-test&fsyms=${currencies.join(
',',
)}&tsyms=${counterValue}`,
)
.then(({ data }) =>
currencies.reduce((result, code) => {
result.push({
symbol: `${code}-${counterValue}`,
value: data[code][counterValue],
})
return result
}, []),
)
send('counterValues.update', data)
}

33
src/reducers/accounts.js

@ -10,18 +10,17 @@ import defaultsDeep from 'lodash/defaultsDeep'
import { getDefaultUnitByCoinType, getCurrencyByCoinType } from '@ledgerhq/currencies'
import type { State } from 'reducers'
import type { Account, Accounts } from 'types/common'
export type AccountsState = Accounts
import type { Account } from 'types/common'
export type AccountsState = Account[]
const state: AccountsState = []
function orderAccountsTransactions(account: Account) {
const { transactions } = account
transactions.sort((a, b) => new Date(b.receivedAt) - new Date(a.receivedAt))
function orderAccountsOperations(account: Account) {
const { operations } = account
operations.sort((a, b) => new Date(b.receivedAt) - new Date(a.receivedAt))
return {
...account,
transactions,
operations,
}
}
@ -36,13 +35,13 @@ function applyDefaults(account) {
const handlers: Object = {
SET_ACCOUNTS: (
state: AccountsState,
{ payload: accounts }: { payload: Accounts },
{ payload: accounts }: { payload: Account[] },
): AccountsState => accounts.map(applyDefaults),
ADD_ACCOUNT: (
state: AccountsState,
{ payload: account }: { payload: Account },
): AccountsState => [...state, orderAccountsTransactions(account)],
): AccountsState => [...state, orderAccountsOperations(account)],
UPDATE_ACCOUNT: (
state: AccountsState,
@ -58,7 +57,7 @@ const handlers: Object = {
...account,
}
return orderAccountsTransactions(updatedAccount)
return orderAccountsOperations(updatedAccount)
}),
REMOVE_ACCOUNT: (state: AccountsState, { payload: account }: { payload: Account }) =>
@ -78,15 +77,15 @@ export function getTotalBalance(state: { accounts: AccountsState }) {
)
}
export function getAccounts(state: { accounts: AccountsState }): Array<Account> {
export function getAccounts(state: { accounts: AccountsState }): Account[] {
return state.accounts
}
export function getArchivedAccounts(state: { accounts: AccountsState }): Array<Account> {
export function getArchivedAccounts(state: { accounts: AccountsState }): Account[] {
return state.accounts.filter(acc => acc.archived === true)
}
export function getVisibleAccounts(state: { accounts: AccountsState }): Array<Account> {
export function getVisibleAccounts(state: { accounts: AccountsState }): Account[] {
return getAccounts(state).filter(account => account.archived !== true)
}
@ -96,7 +95,7 @@ export function getAccountById(state: { accounts: AccountsState }, id: string):
}
export function canCreateAccount(state: State): boolean {
return every(getAccounts(state), a => get(a, 'transactions.length', 0) > 0)
return every(getAccounts(state), a => get(a, 'operations.length', 0) > 0)
}
export function serializeAccounts(accounts: Array<Object>) {
@ -119,7 +118,7 @@ export function serializeAccounts(accounts: Array<Object>) {
return {
...a,
transactions: account.transactions.map(t => ({
operations: account.operations.map(t => ({
...t,
account: a,
})),
@ -127,7 +126,7 @@ export function serializeAccounts(accounts: Array<Object>) {
})
}
export function deserializeAccounts(accounts: Accounts) {
export function deserializeAccounts(accounts: Account[]) {
return accounts.map(account => ({
id: account.id,
address: account.address,
@ -139,7 +138,7 @@ export function deserializeAccounts(accounts: Accounts) {
name: account.name,
path: account.path,
rootPath: account.rootPath,
transactions: account.transactions.map(({ account, ...t }) => t),
operations: account.operations.map(({ account, ...t }) => t),
unit: account.unit,
settings: account.settings,
}))

56
src/reducers/counterValues.js

@ -2,7 +2,12 @@
import { handleActions } from 'redux-actions'
export type CounterValuesState = {}
export type CounterValuesState = {
[string]: {
byDate: Object,
list: Array<[string, number]>,
},
}
const state: CounterValuesState = {}
@ -14,6 +19,55 @@ const handlers = {
...state,
...counterValues,
}),
UPDATE_LAST_COUNTER_VALUE: (
state: CounterValuesState,
{ payload: { symbol, value } }: { payload: { symbol: string, value: number } },
): CounterValuesState => {
// We update only last value (newer)
if (state[symbol]) {
const [date] = state[symbol].list[0]
// [0] date, [1] value, only update value
state[symbol].list[0][1] = value
// Keep the same value for byDate object
state[symbol].byDate[date] = value
// Update reference for a proper update
return { ...state }
}
return state
},
}
export function getLastCounterValueBySymbol(
symbol: string,
state: { counterValues: CounterValuesState },
): number {
return state.counterValues[symbol].list[0][1]
}
export function serializeCounterValues(counterValues: Object) {
return Object.keys(counterValues).reduce((result, key) => {
const counterValue = counterValues[key].sort(([dateA], [dateB]) => (dateA < dateB ? 1 : -1))
result[key] = {
byDate: counterValue.reduce((r, [date, value]) => {
r[date] = value
return r
}, {}),
list: counterValue,
}
return result
}, {})
}
export function deserializeCounterValues(counterValues: Object) {
return Object.keys(counterValues).reduce((result, key) => {
const counterValue = counterValues[key]
result[key] = counterValue.list
return result
}, {})
}
export default handleActions(handlers, state)

73
src/renderer/events.js

@ -3,16 +3,20 @@
import { ipcRenderer } from 'electron'
import objectPath from 'object-path'
import debug from 'debug'
import { getDefaultUnitByCoinType } from '@ledgerhq/currencies'
import type { Accounts } from 'types/common'
import type { Account } from 'types/common'
import { CHECK_UPDATE_TIMEOUT, SYNC_ACCOUNT_TIMEOUT } from 'constants'
import { CHECK_UPDATE_DELAY, SYNC_ACCOUNT_DELAY, SYNC_COUNTER_VALUES_DELAY } from 'constants'
import { updateDevices, addDevice, removeDevice } from 'actions/devices'
import { updateAccount } from 'actions/accounts'
import { setUpdateStatus } from 'reducers/update'
import { getAccounts, getAccountById } from 'reducers/accounts'
import { getCounterValue } from 'reducers/settings'
import { isLocked } from 'reducers/application'
import { setUpdateStatus } from 'reducers/update'
import { updateLastCounterValueBySymbol } from 'actions/counterValues'
import { updateAccount } from 'actions/accounts'
import { updateDevices, addDevice, removeDevice } from 'actions/devices'
import i18n from 'renderer/i18n/electron'
@ -29,9 +33,11 @@ type MsgPayload = {
data: any,
}
let syncAccountsInProgress = true
let syncAccountsInProgress = false
let syncAccountsTimeout
let syncCounterValuesTimeout
export function sendEvent(channel: string, msgType: string, data: any) {
ipcRenderer.send(channel, {
type: msgType,
@ -46,19 +52,19 @@ export function sendSyncEvent(channel: string, msgType: string, data: any): any
})
}
export function startSyncAccounts(accounts: Accounts) {
export function startSyncAccounts(accounts: Account[]) {
d.sync('Sync accounts - start')
syncAccountsInProgress = true
sendEvent('accounts', 'sync.all', {
accounts: accounts.map(account => {
const { id, coinType, rootPath, addresses, index, transactions } = account
const { id, coinType, rootPath, addresses, index, operations } = account
return {
id,
coinType,
allAddresses: addresses,
currentIndex: index,
rootPath,
transactions,
operations,
}
}),
})
@ -70,9 +76,25 @@ export function stopSyncAccounts() {
clearTimeout(syncAccountsTimeout)
}
export function startSyncCounterValues(counterValue: string, accounts: Account[]) {
d.sync('Sync counterValues - start')
sendEvent('msg', 'counterValues.sync', {
counterValue,
currencies: [
...new Set(accounts.map(account => getDefaultUnitByCoinType(account.coinType).code)),
],
})
}
export function stopSyncCounterValues() {
d.sync('Sync counterValues - stop')
clearTimeout(syncCounterValuesTimeout)
}
export function checkUpdates() {
d.update('Update - check')
setTimeout(() => sendEvent('msg', 'updater.init'), CHECK_UPDATE_TIMEOUT)
setTimeout(() => sendEvent('msg', 'updater.init'), CHECK_UPDATE_DELAY)
}
export default ({ store, locked }: { store: Object, locked: boolean }) => {
@ -92,9 +114,9 @@ export default ({ store, locked }: { store: Object, locked: boolean }) => {
return
}
const { name, balance, balanceByDay, transactions } = existingAccount
const { name, balance, balanceByDay, operations } = existingAccount
if (account.transactions.length > 0) {
if (account.operations.length > 0) {
d.sync(`Update account - ${name}`)
const updatedAccount = {
...account,
@ -104,7 +126,7 @@ export default ({ store, locked }: { store: Object, locked: boolean }) => {
return result
}, {}),
index: account.index || existingAccount.index,
transactions: [...transactions, ...account.transactions],
operations: [...operations, ...account.operations],
}
store.dispatch(updateAccount(updatedAccount))
}
@ -132,7 +154,7 @@ export default ({ store, locked }: { store: Object, locked: boolean }) => {
syncAccountsTimeout = setTimeout(() => {
const accounts = getAccounts(store.getState())
startSyncAccounts(accounts)
}, SYNC_ACCOUNT_TIMEOUT)
}, SYNC_ACCOUNT_DELAY)
}
},
},
@ -152,6 +174,18 @@ export default ({ store, locked }: { store: Object, locked: boolean }) => {
store.dispatch(removeDevice(device))
},
},
counterValues: {
update: counterValues => {
counterValues.map(c => store.dispatch(updateLastCounterValueBySymbol(c.symbol, c.value)))
syncCounterValuesTimeout = setTimeout(() => {
const state = store.getState()
const accounts = getAccounts(state)
const counterValue = getCounterValue(state)
startSyncCounterValues(counterValue, accounts)
}, SYNC_COUNTER_VALUES_DELAY)
},
},
updater: {
checking: () => store.dispatch(setUpdateStatus('checking')),
updateAvailable: info => store.dispatch(setUpdateStatus('available', info)),
@ -177,11 +211,16 @@ export default ({ store, locked }: { store: Object, locked: boolean }) => {
// Start detection when we plug/unplug devices
sendEvent('usb', 'devices.listen')
if (!locked && !DISABLED_SYNC) {
const accounts = getAccounts(store.getState())
const state = store.getState()
if (!locked) {
const accounts = getAccounts(state)
const counterValue = getCounterValue(state)
startSyncCounterValues(counterValue, accounts)
// Start accounts sync only if we have accounts
if (accounts.length > 0) {
if (accounts.length > 0 && !DISABLED_SYNC) {
startSyncAccounts(accounts)
}
}

2
src/renderer/i18n/instanciate.js

@ -14,7 +14,7 @@ const commonConfig = {
'settings',
'sidebar',
'time',
'transactionsList',
'operationsList',
'update',
],
fallbackLng: 'en',

2
src/renderer/i18n/storybook.js

@ -13,7 +13,7 @@ const resources = {
settings: require('../../../static/i18n/en/settings.yml'),
sidebar: require('../../../static/i18n/en/sidebar.yml'),
time: require('../../../static/i18n/en/time.yml'),
transactionsList: require('../../../static/i18n/en/transactionsList.yml'),
operationsList: require('../../../static/i18n/en/operationsList.yml'),
update: require('../../../static/i18n/en/update.yml'),
}

4
src/stories/currencies.stories.js

@ -7,11 +7,11 @@ import { getIconByCoinType } from '@ledgerhq/currencies/react'
import type { Currency } from '@ledgerhq/currencies'
const stories = storiesOf('Common/Currencies', module)
const stories = storiesOf('Common', module)
const currencies: Array<Currency> = listCurrencies()
stories.add('currencies list', () => (
stories.add('Currencies', () => (
<div>
<table border="1">
<thead>

9
src/styles/global.js

@ -3,11 +3,15 @@
/* eslint-disable no-unused-expressions */
import { injectGlobal } from 'styled-components'
import omitBy from 'lodash/omitBy'
import { fontFace } from 'styles/helpers'
import { radii, colors } from 'styles/theme'
import reset from './reset'
const { STORYBOOK_ENV, NODE_ENV } = process.env
const COPYRIGHTED_FONTS = ['Museo Sans']
const fonts = {
'Open Sans': [
{
@ -73,6 +77,11 @@ const fonts = {
}
function transformFonts(allFonts) {
allFonts = omitBy(
allFonts,
(_, key: string) =>
NODE_ENV === 'production' && STORYBOOK_ENV && COPYRIGHTED_FONTS.includes(key),
)
return Object.keys(allFonts)
.map(name => {
const fonts = allFonts[name]

13
src/types/common.js

@ -10,19 +10,18 @@ export type Device = {
export type Devices = Array<Device>
// -------------------- Transactions
// -------------------- Operations
export type Transaction = {
export type Operation = {
id: string,
account?: Account,
address: string,
balance: number,
amount: number,
hash: string,
receivedAt: string,
confirmations: number,
}
export type Transactions = Array<Transaction>
// -------------------- Accounts
export type AccountSettings = {
@ -42,13 +41,11 @@ export type Account = {
name: string,
path: string,
rootPath: string,
transactions: Transactions,
operations: Operation[],
unit: Unit,
settings: AccountSettings,
}
export type Accounts = Array<Account>
// -------------------- Settings
export type SettingsProfile = {

1
static/i18n/en/common.yml

@ -9,3 +9,4 @@ save: Save
password: Password
editProfile: Edit profile
lockApplication: Lock application
max: Max

0
static/i18n/en/transactionsList.yml → static/i18n/en/operationsList.yml

10
static/i18n/fr/transactionsList.yml

@ -1,10 +0,0 @@
---
date: Date
account: Compte
address: Adresse
amount: Montant
from: Depuis
to: Vers
showMore: Voir plus
confirmed: Confirmée
notConfirmed: Non confirmée

239
yarn.lock

@ -534,58 +534,18 @@ anymatch@^2.0.0:
micromatch "^3.1.4"
normalize-path "^2.1.1"
app-builder-bin-linux@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/app-builder-bin-linux/-/app-builder-bin-linux-1.6.0.tgz#d7731d7988b8a740e74d591cbd565f168a266111"
app-builder-bin-linux@1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/app-builder-bin-linux/-/app-builder-bin-linux-1.7.1.tgz#925967add77c13d387e3fe88359a4b4dce82c525"
app-builder-bin-linux@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/app-builder-bin-linux/-/app-builder-bin-linux-1.7.2.tgz#a764c8e52ecf1b5b068f32c820c6daf1ffed6a8f"
app-builder-bin-mac@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/app-builder-bin-mac/-/app-builder-bin-mac-1.6.0.tgz#c976da70796d67aeb7134a57899636f2581d1c67"
app-builder-bin-mac@1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/app-builder-bin-mac/-/app-builder-bin-mac-1.7.1.tgz#e5c2b49be82473a737e000cfccfc65a68f9cc62d"
app-builder-bin-mac@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/app-builder-bin-mac/-/app-builder-bin-mac-1.7.2.tgz#c4ee0d950666c97c12a45ac74ec6396be3357644"
app-builder-bin-win@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/app-builder-bin-win/-/app-builder-bin-win-1.6.0.tgz#528ef96430d519c270b4de260bea0ddc70df1733"
app-builder-bin-win@1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/app-builder-bin-win/-/app-builder-bin-win-1.7.1.tgz#e1ba3d6d645896552b28e4dd05525b39561b9b88"
app-builder-bin-win@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/app-builder-bin-win/-/app-builder-bin-win-1.7.2.tgz#7acac890782f4118f09941b343ba06c56452a6f6"
app-builder-bin@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-1.6.0.tgz#c0e88a488d4c23c2e7fe0bbfb70c1d61165be206"
optionalDependencies:
app-builder-bin-linux "1.6.0"
app-builder-bin-mac "1.6.0"
app-builder-bin-win "1.6.0"
app-builder-bin@1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-1.7.1.tgz#c2540eb8b164c91c30bc8cf3f16b5c181f3f5c31"
optionalDependencies:
app-builder-bin-linux "1.7.1"
app-builder-bin-mac "1.7.1"
app-builder-bin-win "1.7.1"
app-builder-bin@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-1.7.2.tgz#daf67060a6bad8f5f611a0d2876d9db897a83f06"
@ -2204,28 +2164,9 @@ builder-util-runtime@4.0.5, builder-util-runtime@^4.0.5, builder-util-runtime@~4
fs-extra-p "^4.5.0"
sax "^1.2.4"
builder-util@5.6.1:
version "5.6.1"
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-5.6.1.tgz#ab90845cb8949ea4ac81da0ce87b8ec3424cdbf9"
dependencies:
"7zip-bin" "~3.1.0"
app-builder-bin "1.6.0"
bluebird-lst "^1.0.5"
builder-util-runtime "^4.0.5"
chalk "^2.3.2"
debug "^3.1.0"
fs-extra-p "^4.5.2"
is-ci "^1.1.0"
js-yaml "^3.10.0"
lazy-val "^1.0.3"
semver "^5.5.0"
source-map-support "^0.5.3"
stat-mode "^0.2.2"
temp-file "^3.1.1"
builder-util@5.6.4:
version "5.6.4"
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-5.6.4.tgz#d130dded98a58d8bee791408a7071f825f488c7a"
builder-util@5.6.5, builder-util@^5.6.5:
version "5.6.5"
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-5.6.5.tgz#f2d156541b8df9599456848e057566443dc04c82"
dependencies:
"7zip-bin" "~3.1.0"
app-builder-bin "1.7.2"
@ -2242,25 +2183,6 @@ builder-util@5.6.4:
stat-mode "^0.2.2"
temp-file "^3.1.1"
builder-util@^5.6.0:
version "5.6.3"
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-5.6.3.tgz#194288b979958832e0592fe91c8306ce207a60f1"
dependencies:
"7zip-bin" "~3.1.0"
app-builder-bin "1.7.1"
bluebird-lst "^1.0.5"
builder-util-runtime "^4.0.5"
chalk "^2.3.2"
debug "^3.1.0"
fs-extra-p "^4.5.2"
is-ci "^1.1.0"
js-yaml "^3.11.0"
lazy-val "^1.0.3"
semver "^5.5.0"
source-map-support "^0.5.3"
stat-mode "^0.2.2"
temp-file "^3.1.1"
builtin-modules@^1.0.0, builtin-modules@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@ -2902,9 +2824,9 @@ crocket@^0.9.11:
dependencies:
xpipe "*"
cross-env@^5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.1.3.tgz#f8ae18faac87692b0a8b4d2f7000d4ec3a85dfd7"
cross-env@^5.1.4:
version "5.1.4"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.1.4.tgz#f61c14291f7cc653bb86457002ea80a04699d022"
dependencies:
cross-spawn "^5.1.0"
is-windows "^1.0.0"
@ -3495,16 +3417,16 @@ dijkstrajs@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.1.tgz#d3cd81221e3ea40742cfcde556d4e99e98ddc71b"
dmg-builder@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-4.1.1.tgz#a12214eb3eb3cba0addccfd129f1981c9805045c"
dmg-builder@4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-4.1.2.tgz#b4d7245dc2e91812395b4268b12f888443ba5f30"
dependencies:
bluebird-lst "^1.0.5"
builder-util "^5.6.0"
electron-builder-lib "~20.2.0"
builder-util "^5.6.5"
electron-builder-lib "~20.5.0"
fs-extra-p "^4.5.2"
iconv-lite "^0.4.19"
js-yaml "^3.10.0"
js-yaml "^3.11.0"
parse-color "^1.0.0"
sanitize-filename "^1.6.1"
@ -3658,23 +3580,23 @@ ejs@^2.5.7:
version "2.5.7"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
electron-builder-lib@20.4.0:
version "20.4.0"
resolved "https://registry.yarnpkg.com/electron-builder-lib/-/electron-builder-lib-20.4.0.tgz#e9a6d34b4bc6ed848a39a9ee894a3da015474c2a"
electron-builder-lib@20.5.1, electron-builder-lib@~20.5.0:
version "20.5.1"
resolved "https://registry.yarnpkg.com/electron-builder-lib/-/electron-builder-lib-20.5.1.tgz#c155345c58d25580d316f6437c251a57e8e92d62"
dependencies:
"7zip-bin" "~3.1.0"
app-builder-bin "1.7.2"
async-exit-hook "^2.0.1"
bluebird-lst "^1.0.5"
builder-util "5.6.4"
builder-util "5.6.5"
builder-util-runtime "4.0.5"
chromium-pickle-js "^0.2.0"
debug "^3.1.0"
ejs "^2.5.7"
electron-osx-sign "0.4.8"
electron-publish "20.2.0"
electron-osx-sign "0.4.10"
electron-publish "20.5.0"
fs-extra-p "^4.5.2"
hosted-git-info "^2.5.0"
hosted-git-info "^2.6.0"
is-ci "^1.1.0"
isbinaryfile "^3.0.2"
js-yaml "^3.11.0"
@ -3687,45 +3609,16 @@ electron-builder-lib@20.4.0:
semver "^5.5.0"
temp-file "^3.1.1"
electron-builder-lib@~20.2.0:
version "20.2.1"
resolved "https://registry.yarnpkg.com/electron-builder-lib/-/electron-builder-lib-20.2.1.tgz#ff8dc6ac7f6f3c676fc370ddafb2aba464a17672"
dependencies:
"7zip-bin" "~3.1.0"
app-builder-bin "1.6.0"
async-exit-hook "^2.0.1"
bluebird-lst "^1.0.5"
builder-util "5.6.1"
builder-util-runtime "4.0.5"
chromium-pickle-js "^0.2.0"
debug "^3.1.0"
ejs "^2.5.7"
electron-osx-sign "0.4.8"
electron-publish "20.2.0"
fs-extra-p "^4.5.2"
hosted-git-info "^2.5.0"
is-ci "^1.1.0"
isbinaryfile "^3.0.2"
js-yaml "^3.10.0"
lazy-val "^1.0.3"
minimatch "^3.0.4"
normalize-package-data "^2.4.0"
plist "^2.1.0"
read-config-file "3.0.0"
sanitize-filename "^1.6.1"
semver "^5.5.0"
temp-file "^3.1.1"
electron-builder@^20.4.0:
version "20.4.0"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-20.4.0.tgz#d1393719339c17dd7c2dd16d58b4e138ca6646ce"
electron-builder@^20.0.4, electron-builder@^20.5.1:
version "20.5.1"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-20.5.1.tgz#8e6fba76dcd65aeabab60f15bd46f294ee04dd11"
dependencies:
bluebird-lst "^1.0.5"
builder-util "5.6.4"
builder-util "5.6.5"
builder-util-runtime "4.0.5"
chalk "^2.3.2"
dmg-builder "4.1.1"
electron-builder-lib "20.4.0"
dmg-builder "4.1.2"
electron-builder-lib "20.5.1"
electron-download-tf "4.3.4"
fs-extra-p "^4.5.2"
is-ci "^1.1.0"
@ -3776,9 +3669,9 @@ electron-is-dev@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.3.0.tgz#14e6fda5c68e9e4ecbeff9ccf037cbd7c05c5afe"
electron-osx-sign@0.4.8:
version "0.4.8"
resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.4.8.tgz#f0b9fadded9e1e54ec35fa89877b5c6c34c7bc40"
electron-osx-sign@0.4.10:
version "0.4.10"
resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.4.10.tgz#be4f3b89b2a75a1dc5f1e7249081ab2929ca3a26"
dependencies:
bluebird "^3.5.0"
compare-version "^0.1.2"
@ -3787,14 +3680,14 @@ electron-osx-sign@0.4.8:
minimist "^1.2.0"
plist "^2.1.0"
electron-publish@20.2.0:
version "20.2.0"
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-20.2.0.tgz#1812738c4a4e14a8e156a9a083424a6e4e8e8264"
electron-publish@20.5.0:
version "20.5.0"
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-20.5.0.tgz#bcef9949c63899d34680e89110e8d38d1016f1f6"
dependencies:
bluebird-lst "^1.0.5"
builder-util "^5.6.0"
builder-util "^5.6.5"
builder-util-runtime "^4.0.5"
chalk "^2.3.0"
chalk "^2.3.2"
fs-extra-p "^4.5.2"
lazy-val "^1.0.3"
mime "^2.2.0"
@ -3880,7 +3773,7 @@ electron-webpack@1.13.0:
webpack-merge "^4.1.1"
yargs "^11.0.0"
electron@1.8.3:
electron@1.8.3, electron@^1.8.2:
version "1.8.3"
resolved "https://registry.yarnpkg.com/electron/-/electron-1.8.3.tgz#001416ea3a25ce594e317cb5531bc41eadd22f7f"
dependencies:
@ -5253,10 +5146,14 @@ home-path@^1.0.1:
version "1.0.5"
resolved "https://registry.yarnpkg.com/home-path/-/home-path-1.0.5.tgz#788b29815b12d53bacf575648476e6f9041d133f"
hosted-git-info@^2.1.4, hosted-git-info@^2.5.0:
hosted-git-info@^2.1.4:
version "2.5.0"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
hosted-git-info@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.0.tgz#23235b29ab230c576aab0d4f13fc046b0b038222"
hpack.js@^2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
@ -6470,6 +6367,9 @@ ledger-test-library@KhalilBellakrid/ledger-test-library-nodejs#7d37482:
dependencies:
axios "^0.17.1"
bindings "^1.3.0"
electron "^1.8.2"
electron-builder "^20.0.4"
electron-rebuild "^1.7.3"
nan "^2.6.2"
prebuild-install "^2.2.2"
@ -7788,9 +7688,9 @@ pngjs@^3.3.0:
version "3.3.2"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.2.tgz#097c3c2a75feb223eadddea6bc9f0050cf830bc3"
popper.js@^1.12.9:
version "1.12.9"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.12.9.tgz#0dfbc2dff96c451bb332edcfcfaaf566d331d5b3"
popper.js@^1.13.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.13.0.tgz#e1e7ff65cc43f7cf9cf16f1510a75e81f84f4565"
portfinder@^1.0.9:
version "1.0.13"
@ -8301,13 +8201,12 @@ query-string@^4.1.0:
object-assign "^4.1.0"
strict-uri-encode "^1.0.0"
query-string@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.0.tgz#9583b15fd1307f899e973ed418886426a9976469"
query-string@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.0.0.tgz#8b8f39447b73e8290d6f5e3581779218e9171142"
dependencies:
decode-uri-component "^0.2.0"
object-assign "^4.1.0"
strict-uri-encode "^1.0.0"
strict-uri-encode "^2.0.0"
querystring-es3@^0.2.0:
version "0.2.1"
@ -8364,9 +8263,9 @@ range-parser@^1.0.3, range-parser@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
raven-js@^3.22.4:
version "3.23.1"
resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.23.1.tgz#34a5d7b5b3dd626a3d59e7a264e1d19a4c799cdb"
raven-js@^3.23.2:
version "3.23.2"
resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.23.2.tgz#b71df14a066e450326b5356cc7fcc035510fdb26"
raven@^2.4.2:
version "2.4.2"
@ -9670,6 +9569,10 @@ strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
string-length@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
@ -9790,9 +9693,9 @@ style-loader@^0.20.2:
loader-utils "^1.1.0"
schema-utils "^0.4.3"
styled-components@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.2.1.tgz#4f780c588829eb06624b686f9b793a10d04db139"
styled-components@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.2.2.tgz#3a3a49e0009a212d1fee173e3a7719a489e11299"
dependencies:
buffer "^5.0.3"
css-to-react-native "^2.0.3"
@ -9801,8 +9704,8 @@ styled-components@^3.2.1:
is-plain-object "^2.0.1"
opencollective "^1.0.3"
prop-types "^15.5.4"
stylis "^3.4.10"
stylis-rule-sheet "^0.0.8"
stylis "^3.5.0"
stylis-rule-sheet "^0.0.10"
supports-color "^3.2.3"
styled-system@^2.2.1:
@ -9811,14 +9714,18 @@ styled-system@^2.2.1:
dependencies:
prop-types "^15.6.0"
stylis-rule-sheet@^0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.8.tgz#b0d0a126c945b1f3047447a3aae0647013e8d166"
stylis-rule-sheet@^0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430"
stylis@^3.0.0, stylis@^3.4.10:
stylis@^3.0.0:
version "3.4.10"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.4.10.tgz#a135cab4b9ff208e327fbb5a6fde3fa991c638ee"
stylis@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1"
sumchecker@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-1.3.1.tgz#79bb3b4456dd04f18ebdbc0d703a1d1daec5105d"
@ -10036,11 +9943,11 @@ tinycolor2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8"
tippy.js@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-2.2.3.tgz#ae2aa54763a86d38682de199ef7f6442c2926413"
tippy.js@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-2.3.0.tgz#22505ce181f5049063c0201d310f4f0bfa794ce7"
dependencies:
popper.js "^1.12.9"
popper.js "^1.13.0"
tmp@^0.0.33:
version "0.0.33"

Loading…
Cancel
Save