diff --git a/.storybook/config.js b/.storybook/config.js index 6ee5a24c..4cdff471 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -4,22 +4,30 @@ import { withKnobs } from '@storybook/addon-knobs' import { setOptions } from '@storybook/addon-options' import { ThemeProvider } from 'styled-components' import { I18nextProvider } from 'react-i18next' +import { Provider } from 'react-redux' import 'globals' import 'styles/global' import theme from 'styles/theme' import i18n from 'renderer/i18n/storybook' +import createStore from 'renderer/createStore' + +import state from '__mocks__/storybook-state' const req = require.context('../src', true, /.stories.js$/) function loadStories() { req.keys().forEach(filename => req(filename)) } +const store = createStore({ state }) + addDecorator(story => ( -
{story()}
+ +
{story()}
+
)) diff --git a/flow-defs/globals.js b/flow-defs/globals.js index c35461a8..5e37fd59 100644 --- a/flow-defs/globals.js +++ b/flow-defs/globals.js @@ -11,5 +11,7 @@ declare var __APP_VERSION__: string declare var __static: string declare var describe: Function declare var test: Function +declare var it: Function +declare var expect: Function declare var ResizeObserver: Class diff --git a/src/__mocks__/render.js b/src/__mocks__/render.js new file mode 100644 index 00000000..7b945e49 --- /dev/null +++ b/src/__mocks__/render.js @@ -0,0 +1,19 @@ +import React from 'react' +import { Provider } from 'react-redux' +import renderer from 'react-test-renderer' +import { ThemeProvider } from 'styled-components' + +import createStore from 'renderer/createStore' + +import theme from 'styles/theme' + +export default function render(component, state) { + const store = createStore({ state }) + return renderer + .create( + + {component} + , + ) + .toJSON() +} diff --git a/src/__mocks__/storybook-state.js b/src/__mocks__/storybook-state.js new file mode 100644 index 00000000..47e81bd5 --- /dev/null +++ b/src/__mocks__/storybook-state.js @@ -0,0 +1,15 @@ +export default { + counterValues: { + BTC: { + USD: { + '2018-01-09': 0.00795978, + '2018-03-29': 0.007106619999999999, + '2018-03-30': 0.0068537599999999995, + '2018-03-31': 0.00694377, + '2018-04-01': 0.00683584, + '2018-04-02': 0.007061689999999999, + latest: 0.00706156, + }, + }, + }, +} diff --git a/src/__mocks__/withStore.js b/src/__mocks__/withStore.js new file mode 100644 index 00000000..8d4da041 --- /dev/null +++ b/src/__mocks__/withStore.js @@ -0,0 +1,8 @@ +import React from 'react' +import { Provider } from 'react-redux' +import createStore from 'renderer/createStore' + +export default function withStore(state, component) { + const store = createStore({ state }) + return {component} +} diff --git a/src/components/CounterValue/__tests__/CounterValue.test.js b/src/components/CounterValue/__tests__/CounterValue.test.js new file mode 100644 index 00000000..8984c8f1 --- /dev/null +++ b/src/components/CounterValue/__tests__/CounterValue.test.js @@ -0,0 +1,63 @@ +// @flow + +import React from 'react' + +import render from '__mocks__/render' +import CounterValue from '..' + +describe('components', () => { + describe('CounterValue', () => { + it('basic', () => { + const state = { counterValues: { BTC: { USD: { latest: 10e2 } } } } + const component = + const tree = render(component, state) + expect(tree).toMatchSnapshot() + }) + + it('specifying ticker different from default', () => { + const state = { counterValues: { LOL: { USD: { latest: 5e2 } } } } + const component = + const tree = render(component, state) + expect(tree).toMatchSnapshot() + }) + + it('using countervalue different from default', () => { + const state = { + counterValues: { BTC: { EUR: { latest: 42 } } }, + settings: { + counterValue: 'EUR', + }, + } + const component = + const tree = render(component, state) + expect(tree).toMatchSnapshot() + }) + + it('without countervalues populated', () => { + const state = { counterValues: {} } + const component = + const tree = render(component, state) + expect(tree).toMatchSnapshot() + }) + + it('with time travel whith date in countervalues', () => { + const state = { counterValues: { BTC: { USD: { '2018-01-01': 20e2 } } } } + + const date = new Date('2018-01-01') + const component = + const tree = render(component, state) + expect(tree).toMatchSnapshot() + }) + + it('with time travel whith date not in countervalues', () => { + const state = { counterValues: { BTC: { USD: { '2018-01-01': 20e2 } } } } + const date = new Date('2018-01-02') + const component = + const tree = render(component, state) + + // TODO: actually it returns 0 when date is not in countervalues + // do we want to use closest past value instead? + expect(tree).toMatchSnapshot() + }) + }) +}) diff --git a/src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap b/src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap new file mode 100644 index 00000000..ccd2f43e --- /dev/null +++ b/src/components/CounterValue/__tests__/__snapshots__/CounterValue.test.js.snap @@ -0,0 +1,55 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components CounterValue basic 1`] = ` + + + USD 10.00 + +`; + +exports[`components CounterValue specifying ticker different from default 1`] = ` + + + USD 5.00 + +`; + +exports[`components CounterValue using countervalue different from default 1`] = ` + + + EUR 0.42 + +`; + +exports[`components CounterValue with time travel whith date in countervalues 1`] = ` + + + USD 20.00 + +`; + +exports[`components CounterValue with time travel whith date not in countervalues 1`] = ` + + + USD 0.00 + +`; + +exports[`components CounterValue without countervalues populated 1`] = ` + + + USD 0.00 + +`; diff --git a/src/components/CounterValue/index.js b/src/components/CounterValue/index.js index d180a2bc..e865a8c5 100644 --- a/src/components/CounterValue/index.js +++ b/src/components/CounterValue/index.js @@ -2,57 +2,83 @@ import React, { PureComponent } from 'react' import { connect } from 'react-redux' -import moment from 'moment' -import type { Unit, Currency } from '@ledgerhq/currencies' +import { getFiatUnit } from '@ledgerhq/currencies' import { getCounterValueCode } from 'reducers/settings' import { calculateCounterValueSelector } from 'reducers/counterValues' import FormattedVal from 'components/base/FormattedVal' -const mapStateToProps = state => ({ - counterValueCode: getCounterValueCode(state), - getCounterValue: calculateCounterValueSelector(state), -}) - type Props = { - formatValue: boolean, + // wich market to query + ticker: string, + + // the value :) + value: number, + + // when? if not given: take latest + date?: Date, + + // from reducers counterValueCode: string, getCounterValue: Function, - time?: Date | string | number, - unit: Unit, - currency: Currency, - value: number, } -export class CounterValue extends PureComponent { +const mapStateToProps = (state, props) => { + const { ticker } = props + + // TODO: in wallet-common, stop using currency. + // always use ticker and remove that hack + let { currency } = props + if (!currency && ticker) { + currency = generateFakeCurrency(ticker) + } else if (currency) { + console.warn('`currency` is deprecated in CounterValue. use `ticker` instead.') // eslint-disable-line no-console + } + + const counterValueCode = getCounterValueCode(state) + const counterValueUnit = getFiatUnit(counterValueCode) + const getCounterValue = calculateCounterValueSelector(state)(currency, counterValueUnit) + + return { + counterValueCode, + getCounterValue, + } +} + +class CounterValue extends PureComponent { static defaultProps = { - formatValue: true, value: 0, - time: undefined, + date: undefined, } render() { - const { - formatValue, - value, - currency, - unit, - counterValueCode, - time, - getCounterValue, - ...props - } = this.props - - const date = moment(time).format('YYYY-MM-DD') - const v = getCounterValue(currency, counterValueCode)(value, date) - - return formatValue ? ( - - ) : ( - v + const { getCounterValue, counterValueCode, date, value, ...props } = this.props + const counterValue = getCounterValue(value, date) + return ( + ) } } +function generateFakeCurrency(ticker) { + return { + units: [ + { + code: ticker, + + // unused + name: 'fake-unit', + magnitude: 0, + }, + ], + + // unused + coinType: 0, + color: '#000', + name: 'fake-coin', + scheme: 'bitcoin', + } +} + export default connect(mapStateToProps)(CounterValue) diff --git a/src/components/CounterValue/stories.js b/src/components/CounterValue/stories.js index caa79ee0..8b48de26 100644 --- a/src/components/CounterValue/stories.js +++ b/src/components/CounterValue/stories.js @@ -1,40 +1,16 @@ // @flow import React from 'react' -import { getCurrencyByCoinType, getDefaultUnitByCoinType } from '@ledgerhq/currencies' +import { getCurrencyByCoinType } from '@ledgerhq/currencies' import { storiesOf } from '@storybook/react' -import { boolean, text } from '@storybook/addon-knobs' -import createHistory from 'history/createHashHistory' +import { number } from '@storybook/addon-knobs' -import { CounterValue } from 'components/CounterValue' -import { calculateCounterValueSelector } from 'reducers/counterValues' -import createStore from 'renderer/createStore' +import CounterValue from 'components/CounterValue' const stories = storiesOf('Components', module) const currency = getCurrencyByCoinType(0) -const unit = getDefaultUnitByCoinType(0) - -const counterValue = 'USD' -const counterValues = { - BTC: { - USD: { - '2018-01-09': 10000, - }, - }, -} - -const store = createStore(createHistory(), { counterValues }) -const getCounterValue = calculateCounterValueSelector(store.getState()) stories.add('CounterValue', () => ( - + )) diff --git a/src/components/OperationsList/index.js b/src/components/OperationsList/index.js index f9282fd0..3d8103d1 100644 --- a/src/components/OperationsList/index.js +++ b/src/components/OperationsList/index.js @@ -192,9 +192,8 @@ const Operation = ({ diff --git a/src/components/OperationsList/stories.js b/src/components/OperationsList/stories.js index 5746c2cc..ebe1f11b 100644 --- a/src/components/OperationsList/stories.js +++ b/src/components/OperationsList/stories.js @@ -4,11 +4,10 @@ import React from 'react' import { getCurrencyByCoinType, getDefaultUnitByCoinType } from '@ledgerhq/currencies' import { storiesOf } from '@storybook/react' import { boolean } from '@storybook/addon-knobs' -import { translate } from 'react-i18next' import { accounts } from 'components/SelectAccount/stories' -import { OperationsList } from 'components/OperationsList' +import OperationsList from 'components/OperationsList' const stories = storiesOf('Components', module) @@ -23,20 +22,12 @@ const account = ({ name }) => ({ unit, }) -const counterValue = 'USD' -const counterValues = { - 'BTC-USD': { - byDate: { - '2018-01-09': 10000, - }, - }, -} - const operations = [ { address: '5c6ea1716520c7d6e038d36a3223faced3c', hash: '5c6ea1716520c7d6e038d36a3223faced3c4b8f7ffb69d9fb5bd527d562fdb62', - amount: 130000000, + id: '5c6ea1716520c7d6e038d36a3223faced3c4b8f7ffb69d9fb5bd527d562fdb62', + amount: 1.3e8, date: new Date('2018-01-09T16:03:52Z'), confirmations: 1, account: account({ @@ -45,8 +36,9 @@ const operations = [ }, { address: '5c6ea1716520c7d6e038d36a3223faced3c', - hash: '5c6ea1716520c7d6e038d36a3223faced3c4b8f7ffb69d9fb5bd527d562fdb62', - amount: 130000000, + hash: '26bdf265d725db5bf9d96bff7f8b4c3decaf3223a63d830e6d7c0256171ae6c5', + id: '26bdf265d725db5bf9d96bff7f8b4c3decaf3223a63d830e6d7c0256171ae6c5', + amount: 1.6e8, date: new Date('2018-01-09T16:03:52Z'), confirmations: 11, account: account({ @@ -56,7 +48,8 @@ const operations = [ { address: '27416a48caab90fab053b507b8b6b9d4', hash: '27416a48caab90fab053b507b8b6b9d48fba75421d3bfdbae4b85f64024bc9c4', - amount: -65000000, + id: '27416a48caab90fab053b507b8b6b9d48fba75421d3bfdbae4b85f64024bc9c4', + amount: -6.5e8, date: new Date('2018-01-09T16:02:40Z'), confirmations: 11, account: account({ @@ -65,8 +58,9 @@ const operations = [ }, { address: '27416a48caab90fab053b507b8b6b9d4', - hash: '27416a48caab90fab053b507b8b6b9d48fba75421d3bfdbae4b85f64024bc9c4', - amount: -65000000, + hash: '4c9cb42046f58b4eabdfb3d12457abf84d9b6b8b705b350baf09baac84a61472', + id: '4c9cb42046f58b4eabdfb3d12457abf84d9b6b8b705b350baf09baac84a61472', + amount: -4.2e8, date: new Date('2018-01-09T16:02:40Z'), confirmations: 1, account: account({ @@ -75,12 +69,8 @@ const operations = [ }, ] -const OperationsListComp = translate()(OperationsList) - stories.add('OperationsList', () => ( - void, + onChange: number => void, // used to determine the left input unit account: Account, @@ -66,83 +66,40 @@ type Props = { getReverseCounterValue: CalculateCounterValue, } -type State = { - leftUnit: Unit, - rightUnit: Unit, - leftValue: number, - rightValue: number, -} - -export class RequestAmount extends PureComponent { - constructor(props: Props) { - super(props) - - const { account, rightUnit, value, getCounterValue } = this.props - - const rawLeftValue = value * 10 ** account.unit.magnitude - const rawRightValue = getCounterValue(account.currency, rightUnit)(rawLeftValue) - const rightValue = rawRightValue / 10 ** rightUnit.magnitude - - this.state = { - leftUnit: account.unit, - rightUnit, - leftValue: value, - rightValue, - } - } - +export class RequestAmount extends PureComponent { handleClickMax = () => { - const leftValue = this.props.max / 10 ** this.props.account.unit.magnitude - this.handleChangeAmount('left')(leftValue) - this.setState({ leftValue }) + this.props.onChange(this.props.max) } handleChangeAmount = (changedField: string) => (val: number) => { - const { getCounterValue, getReverseCounterValue, account, max, onChange } = this.props - const { rightUnit } = this.state + const { rightUnit, getReverseCounterValue, account, max, onChange } = this.props if (changedField === 'left') { - let rawLeftValue = val * 10 ** account.unit.magnitude - if (rawLeftValue > max) { - rawLeftValue = max - } - const leftValue = rawLeftValue / 10 ** account.unit.magnitude - const rawRightValue = getCounterValue(account.currency, rightUnit)(rawLeftValue) - const rightValue = rawRightValue / 10 ** rightUnit.magnitude - this.setState({ rightValue, leftValue }) - onChange({ left: rawLeftValue, right: rawRightValue }) + onChange(val > max ? max : val) } else if (changedField === 'right') { - let rawRightValue = val * 10 ** rightUnit.magnitude - let rawLeftValue = getReverseCounterValue(account.currency, rightUnit)(rawRightValue) - if (rawLeftValue > max) { - rawLeftValue = max - rawRightValue = getCounterValue(account.currency, rightUnit)(rawLeftValue) - } - const rightValue = rawRightValue / 10 ** rightUnit.magnitude - const leftValue = rawLeftValue / 10 ** account.unit.magnitude - this.setState({ rightValue, leftValue }) - onChange({ left: rawLeftValue, right: rawRightValue }) + const leftVal = getReverseCounterValue(account.currency, rightUnit)(val) + onChange(leftVal > max ? max : leftVal) } } render() { - const { t } = this.props - const { leftUnit, rightUnit, leftValue, rightValue } = this.state + const { t, value, account, rightUnit, getCounterValue } = this.props + const right = getCounterValue(account.currency, rightUnit)(value) return ( {leftUnit.code}} + renderRight={{account.unit.code}} /> = {rightUnit.code}} /> diff --git a/src/components/RequestAmount/stories.js b/src/components/RequestAmount/stories.js index 2e08839a..d4aa88a4 100644 --- a/src/components/RequestAmount/stories.js +++ b/src/components/RequestAmount/stories.js @@ -1,33 +1,39 @@ // @flow -import React from 'react' +import React, { PureComponent } 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' +import RequestAmount from 'components/RequestAmount' const stories = storiesOf('Components', module) -const props = { - counterValue: 'USD', - lastCounterValue: 9177.69, - account: accounts[0], +type State = { + value: number, } -const RequestAmountComp = translate()(RequestAmount) +class Wrapper extends PureComponent { + state = { + value: 3e8, + } + handleChange = value => { + action('onChange')(value) + this.setState({ value }) + } + render() { + const { value } = this.state + return ( + + ) + } +} -stories.add('RequestAmount', () => ( - k} - onChange={action('onChange')} - value={{ - left: number('left value', 0), - right: number('right value', 0), - }} - /> -)) +stories.add('RequestAmount', () => ) diff --git a/src/components/base/FormattedVal/__tests__/FormattedVal.test.js b/src/components/base/FormattedVal/__tests__/FormattedVal.test.js index ca6d032f..4510fac3 100644 --- a/src/components/base/FormattedVal/__tests__/FormattedVal.test.js +++ b/src/components/base/FormattedVal/__tests__/FormattedVal.test.js @@ -1,7 +1,7 @@ import React from 'react' import { getDefaultUnitByCoinType } from '@ledgerhq/currencies' -import { render } from 'test-utils' +import render from '__mocks__/render' import FormattedVal from '..' const bitcoinUnit = getDefaultUnitByCoinType(0) diff --git a/src/components/base/InputCurrency/index.js b/src/components/base/InputCurrency/index.js index 90242709..dde49cd6 100644 --- a/src/components/base/InputCurrency/index.js +++ b/src/components/base/InputCurrency/index.js @@ -17,10 +17,8 @@ function parseValue(value) { return value.toString().replace(/,/g, '.') } -function format(unit: Unit, value: Value) { - let v = value === '' ? 0 : Number(value) - v *= 10 ** unit.magnitude - return formatCurrencyUnit(unit, v, { +function format(unit: Unit, value: number) { + return formatCurrencyUnit(unit, value, { disableRounding: true, showAllDigits: false, }) @@ -28,13 +26,13 @@ function format(unit: Unit, value: Value) { function unformat(unit, value) { if (value === 0 || value === '') { - return 0 + return '0' } let v = parseCurrencyUnit(unit, value.toString()) v /= 10 ** unit.magnitude - return v + return v.toString() } const Currencies = styled(Box)` @@ -50,19 +48,18 @@ const Currency = styled(Box).attrs({ pr: 1, })`` -type Value = string | number - type Props = { onChange: Function, renderRight: any, unit: Unit, units: Array, - value: Value, + value: number, } type State = { + unit: Unit, isFocus: boolean, - value: Value, + displayValue: string, } class InputCurrency extends PureComponent { @@ -75,66 +72,74 @@ class InputCurrency extends PureComponent { state = { isFocus: false, - value: this.props.value, + displayValue: '0', + unit: this.props.unit, + } + + componentWillMount() { + const { value } = this.props + const { unit } = this.state + const displayValue = format(unit, value) + this.setState({ displayValue }) } componentWillReceiveProps(nextProps: Props) { + const { unit } = this.state if (this.props.value !== nextProps.value) { const { isFocus } = this.state - const value = isFocus ? nextProps.value : format(nextProps.unit, nextProps.value) - this.setState({ - value, - }) + const displayValue = isFocus + ? (nextProps.value / 10 ** unit.magnitude).toString() + : format(unit, nextProps.value) + this.setState({ displayValue }) } } - handleChange = (v: Value) => { + handleChange = (v: string) => { + // const { displayValue } = this.state v = parseValue(v) + if (v.startsWith('00')) { + return + } + // Check if value is valid Number if (isNaN(Number(v))) { return } this.emitOnChange(v) - this.setState({ - value: v, - }) + this.setState({ displayValue: v || '0' }) } handleBlur = () => { - const { unit } = this.props - const { value } = this.state - + const { value } = this.props + const { unit } = this.state const v = format(unit, value) - - this.setState({ - isFocus: false, - value: v, - }) + this.setState({ isFocus: false, displayValue: v }) } handleFocus = () => { - const { unit } = this.props + const { unit } = this.state this.setState(prev => ({ isFocus: true, - value: unformat(unit, prev.value), + displayValue: unformat(unit, prev.displayValue), })) } - emitOnChange = (v: Value) => { - const { onChange, unit } = this.props - const { value } = this.state + emitOnChange = (v: string) => { + const { onChange } = this.props + const { displayValue, unit } = this.state - if (value.toString() !== v.toString()) { - onChange(v.toString(), unit) + if (displayValue.toString() !== v.toString()) { + const satoshiValue = Number(v) * 10 ** unit.magnitude + onChange(satoshiValue, unit) } } renderListUnits = () => { - const { unit, units, onChange } = this.props - const { value } = this.state + const { units, value } = this.props + const { unit } = this.state if (units.length <= 1) { return null @@ -146,7 +151,10 @@ class InputCurrency extends PureComponent { bg="lightGraphite" keyProp="code" flatLeft - onChange={item => onChange(unformat(item, value), item)} + onChange={item => { + this.setState({ unit: item, displayValue: format(item, value) }) + // onChange(unformat(item, value), item) + }} items={units} value={unit} renderItem={item => item.code} @@ -158,13 +166,13 @@ class InputCurrency extends PureComponent { render() { const { renderRight } = this.props - const { value } = this.state + const { displayValue } = this.state return ( { state = { - value: 0, + value: 1e8, unit: units[0], } - handleChange = (value, unit) => this.setState({ value, unit }) + handleChange = (value, unit) => { + action('onChange')(value, unit) + this.setState({ value, unit }) + } render() { const { render } = this.props diff --git a/src/components/modals/OperationDetails.js b/src/components/modals/OperationDetails.js index f9f1840c..1c003c95 100644 --- a/src/components/modals/OperationDetails.js +++ b/src/components/modals/OperationDetails.js @@ -90,9 +90,8 @@ const OperationDetails = ({ t }: { t: T }) => ( color="grey" fontSize={5} style={{ lineHeight: 1 }} - time={date} - unit={unit} - currency={currency} + date={date} + ticker={currency.units[0].code} value={amount} /> diff --git a/src/components/modals/Send/01-step-amount.js b/src/components/modals/Send/01-step-amount.js index 1e47ca11..032f96a5 100644 --- a/src/components/modals/Send/01-step-amount.js +++ b/src/components/modals/Send/01-step-amount.js @@ -3,7 +3,6 @@ import React, { Fragment } from 'react' import type { Account } from '@ledgerhq/wallet-common/lib/types' -import type { Unit } from '@ledgerhq/currencies' import type { T } from 'types/common' import Box from 'components/base/Box' @@ -22,11 +21,8 @@ type PropsStepAmount = { account: Account | null, onChange: Function, recipientAddress: string, - amount: { left: number, right: number }, - fees: { - value: number, - unit: Unit | null, - }, + amount: number, + fees: number, isRBF: boolean, t: T, } @@ -61,10 +57,10 @@ function StepAmount(props: PropsStepAmount) { @@ -75,12 +71,7 @@ function StepAmount(props: PropsStepAmount) { - onChange('fees')({ value, unit })} - /> + onChange('fees')(value)} /> @@ -121,11 +112,10 @@ type PropsFees = { account: Account, amount: number, onChange: Function, - unit: Unit | null, } function Fees(props: PropsFees) { - const { onChange, account, unit, amount } = props + const { onChange, account, amount } = props const { units } = account.currency return ( @@ -135,11 +125,11 @@ function Fees(props: PropsFees) { items={[{ key: 'custom', name: 'Custom' }]} value={{ key: 'custom', name: 'Custom' }} renderSelected={item => item.name} - onChange={() => onChange(amount, unit)} + onChange={() => onChange(amount)} /> @@ -30,7 +31,7 @@ function Footer({ account, amount, t, onNext, canNext, counterValue }: Props) { @@ -38,12 +39,12 @@ function Footer({ account, amount, t, onNext, canNext, counterValue }: Props) { {'('} - diff --git a/src/components/modals/Send/index.js b/src/components/modals/Send/index.js index 4638f1d4..06215b9f 100644 --- a/src/components/modals/Send/index.js +++ b/src/components/modals/Send/index.js @@ -1,19 +1,14 @@ // @flow import React, { PureComponent } from 'react' -import { compose } from 'redux' -import { connect } from 'react-redux' import { translate } from 'react-i18next' import get from 'lodash/get' import type { Account } from '@ledgerhq/wallet-common/lib/types' -import type { Unit } from '@ledgerhq/currencies' import type { T } from 'types/common' import { MODAL_SEND } from 'config/constants' -import { getCounterValueCode } from 'reducers/settings' - import Breadcrumb from 'components/Breadcrumb' import Modal, { ModalBody, ModalTitle, ModalContent } from 'components/base/Modal' @@ -24,25 +19,17 @@ import StepConnectDevice from './02-step-connect-device' import StepVerification from './03-step-verification' import StepConfirmation from './04-step-confirmation' -const mapStateToProps = state => ({ - counterValue: getCounterValueCode(state), -}) - type Props = { t: T, - counterValue: string, } type State = { stepIndex: number, isDeviceReady: boolean, - amount: { left: number, right: number }, + amount: number, + fees: number, account: Account | null, recipientAddress: string, - fees: { - value: number, - unit: Unit | null, - }, isRBF: boolean, } @@ -58,14 +45,8 @@ const INITIAL_STATE = { isDeviceReady: false, account: null, recipientAddress: '', - amount: { - left: 0, - right: 0, - }, - fees: { - value: 0, - unit: null, - }, + amount: 0, + fees: 0, isRBF: false, } @@ -73,6 +54,7 @@ class SendModal extends PureComponent { state = INITIAL_STATE _steps = GET_STEPS(this.props.t) + _account: Account | null = null canNext = account => { const { stepIndex } = this.state @@ -80,7 +62,7 @@ class SendModal extends PureComponent { // informations if (stepIndex === 0) { const { amount, recipientAddress } = this.state - return !!amount.left && !!recipientAddress && !!account + return !!amount && !!recipientAddress && !!account } // connect device @@ -102,7 +84,25 @@ class SendModal extends PureComponent { this.setState({ stepIndex: stepIndex + 1 }) } - createChangeHandler = key => value => this.setState({ [key]: value }) + createChangeHandler = key => value => { + const patch = { [key]: value } + // ensure max is always restecped when changing fees + if (key === 'fees') { + const { amount } = this.state + // if changing fees goes further than max, change amount + if (this._account && amount + value > this._account.balance) { + const diff = amount + value - this._account.balance + patch.amount = amount - diff + // if the user is a little joker, and try to put fees superior + // to the max, let's reset amount to 0 and put fees to max. + if (patch.amount < 0) { + patch.amount = 0 + patch.fees = this._account.balance + } + } + } + this.setState(patch) + } renderStep = acc => { const { stepIndex, account, amount, ...othersState } = this.state @@ -121,8 +121,8 @@ class SendModal extends PureComponent { } render() { - const { t, counterValue } = this.props - const { stepIndex, amount, account } = this.state + const { t } = this.props + const { stepIndex, amount, account, fees } = this.state return ( { render={({ data, onClose }) => { const acc = account || get(data, 'account', null) const canNext = this.canNext(acc) + + // hack: access the selected account, living in modal data, outside + // of the modal render function + this._account = acc + return ( {t('send:title')} @@ -140,11 +145,11 @@ class SendModal extends PureComponent { {acc && (