Meriadec Pillet
7 years ago
committed by
GitHub
18 changed files with 722 additions and 243 deletions
@ -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) |
@ -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), |
|||
}} |
|||
/> |
|||
)) |
@ -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 |
@ -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')} />) |
Loading…
Reference in new issue