Browse Source

Merge pull request #222 from loeck/master

Add new RequestAmount component in ReceiveModal
master
Meriadec Pillet 7 years ago
committed by GitHub
parent
commit
12f59d9b48
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      package.json
  2. 24
      src/actions/counterValues.js
  3. 5
      src/components/AccountPage/index.js
  4. 275
      src/components/RequestAmount/index.js
  5. 30
      src/components/RequestAmount/stories.js
  6. 10
      src/components/SelectAccount/stories.js
  7. 10
      src/components/base/Button/index.js
  8. 98
      src/components/base/Input/index.js
  9. 132
      src/components/base/InputCurrency/index.js
  10. 15
      src/components/base/InputCurrency/stories.js
  11. 30
      src/components/modals/Receive/index.js
  12. 21
      src/components/modals/Send.js
  13. 12
      src/helpers/__tests__/balance.test.js
  14. 2
      src/helpers/balance.js
  15. 11
      src/helpers/db.js
  16. 38
      src/reducers/counterValues.js
  17. 1
      static/i18n/en/common.yml
  18. 239
      yarn.lock

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

24
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'
@ -51,21 +53,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))

5
src/components/AccountPage/index.js

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

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)

30
src/components/RequestAmount/stories.js

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

10
src/components/SelectAccount/stories.js

@ -10,20 +10,20 @@ import { SelectAccount } from 'components/SelectAccount'
const chance = new Chance()
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),
unit: getDefaultUnitByCoinType(1),
settings: {
minConfirmations: 2,
},

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

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

@ -2,42 +2,67 @@
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { space, fontSize } 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({
px: 3,
ff: 'Open Sans|SemiBold',
ff: p => p.ff || 'Open Sans|SemiBold',
fontSize: 4,
})`
${space};
${fontFamily};
${fontSize};
height: 40px;
border: 1px solid ${p => p.theme.colors.fog};
border-radius: 3px;
display: flex;
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
@ -46,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>
)
}
}

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/InputCurrency', module)
const unit = getDefaultUnitByCoinType(1)
stories.add('basic', () => <InputCurrency unit={unit} onChange={action('onChange')} />)

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

@ -2,16 +2,17 @@
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 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 type { Account as AccountType, T } from 'types/common'
@ -22,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> {
@ -51,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
@ -74,14 +85,13 @@ class ReceiveModal extends PureComponent<Props, State> {
<Fragment>
<Box flow={1}>
<Label>Request amount</Label>
<Input
type="number"
min={0}
max={account.balance / 1e8}
<RequestAmount
account={account}
value={amount}
onChange={this.handleChangeInput('amount')}
/>
</Box>
<ReceiveBox account={account} amount={amount} />
<ReceiveBox account={account} amount={amount.left} />
</Fragment>
)}
</ModalContent>

21
src/components/modals/Send.js

@ -87,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
@ -125,8 +115,6 @@ class Send extends PureComponent<Props, State> {
}
}
_items = []
handleChangeInput = (key: $Keys<InputValue>) => (value: $Values<InputValue>) =>
this.setState(prev => ({
inputValue: {
@ -145,6 +133,13 @@ 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
@ -158,7 +153,7 @@ class Send extends PureComponent<Props, State> {
<ModalTitle>{t('send:title')}</ModalTitle>
<ModalContent>
<Box mb={6} mt={2}>
<Breadcrumb currentStep={step} items={this._items} />
<Breadcrumb currentStep={step} items={this._steps} />
</Box>
<Step {...this.getStepProps(data)} />
</ModalContent>

12
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,
},
},
}

2
src/helpers/balance.js

@ -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

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
}

38
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 = {}
@ -16,4 +21,35 @@ const handlers = {
}),
}
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)

1
static/i18n/en/common.yml

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

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