9 changed files with 466 additions and 2 deletions
@ -0,0 +1,149 @@ |
|||||
|
/* eslint-disable react/no-multi-comp */ |
||||
|
|
||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
import { asField } from 'informed' |
||||
|
import * as yup from 'yup' |
||||
|
import { convert } from 'lib/utils/btc' |
||||
|
import { formatValue, parseNumber } from 'lib/utils/crypto' |
||||
|
import Input from 'components/UI/Input' |
||||
|
|
||||
|
/** |
||||
|
* @render react |
||||
|
* @name CryptoAmountInput |
||||
|
*/ |
||||
|
class CryptoAmountInput extends React.Component { |
||||
|
static propTypes = { |
||||
|
currency: PropTypes.string.isRequired, |
||||
|
required: PropTypes.bool, |
||||
|
onChange: PropTypes.func, |
||||
|
onBlur: PropTypes.func |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Reformat the value when the currency unit has changed. |
||||
|
*/ |
||||
|
componentDidUpdate(prevProps) { |
||||
|
const { currency, fieldApi } = this.props |
||||
|
|
||||
|
// Reformat the value when the currency unit has changed.
|
||||
|
if (currency !== prevProps.currency) { |
||||
|
const { fieldApi } = this.props |
||||
|
let value = fieldApi.getValue() |
||||
|
const convertedValue = convert(prevProps.currency, currency, value) |
||||
|
const [integer, fractional] = parseNumber(convertedValue, this.getRules().precision) |
||||
|
value = formatValue(integer, fractional) |
||||
|
fieldApi.setValue(value) |
||||
|
} |
||||
|
|
||||
|
// If the value has changed, reformat it if needed.
|
||||
|
const valueBefore = prevProps.fieldState.value |
||||
|
const valueAfter = fieldApi.getValue() |
||||
|
if (valueAfter !== valueBefore) { |
||||
|
const [integer, fractional] = parseNumber(valueAfter, this.getRules().precision) |
||||
|
const formattedValue = formatValue(integer, fractional) |
||||
|
if (formattedValue !== valueAfter) { |
||||
|
fieldApi.setValue(formattedValue) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
getRules = () => { |
||||
|
const { currency } = this.props |
||||
|
switch (currency) { |
||||
|
case 'btc': |
||||
|
return { |
||||
|
precision: 8, |
||||
|
placeholder: '0.00000000', |
||||
|
pattern: '[0-9]*.?[0-9]{0,8}?' |
||||
|
} |
||||
|
case 'bits': |
||||
|
return { |
||||
|
precision: 2, |
||||
|
placeholder: '0.00', |
||||
|
pattern: '[0-9]*.?[0-9]{0,2}?' |
||||
|
} |
||||
|
case 'sats': |
||||
|
return { |
||||
|
precision: 0, |
||||
|
placeholder: '00000000', |
||||
|
pattern: '[0-9]*' |
||||
|
} |
||||
|
default: |
||||
|
return { |
||||
|
precision: 2, |
||||
|
pattern: '[0-9]*' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
handleKeyDown = e => { |
||||
|
// Do nothing if the user did select all key combo.
|
||||
|
if (e.metaKey && e.key === 'a') { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Do not allow multiple dots.
|
||||
|
let { value } = e.target |
||||
|
if (e.key === '.') { |
||||
|
if (value.search(/\./) >= 0) { |
||||
|
e.preventDefault() |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if (e.key.length === 1 && !e.key.match(/^[0-9.]$/)) { |
||||
|
e.preventDefault() |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const rules = this.getRules() |
||||
|
return ( |
||||
|
<Input |
||||
|
{...this.props} |
||||
|
type="text" |
||||
|
placeholder={rules.placeholder} |
||||
|
pattern={rules.pattern} |
||||
|
onKeyDown={this.handleKeyDown} |
||||
|
/> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const CryptoAmountInputAsField = asField(CryptoAmountInput) |
||||
|
|
||||
|
class WrappedCryptoAmountInputAsField extends React.Component { |
||||
|
validate = value => { |
||||
|
const { disabled, required } = this.props |
||||
|
if (disabled) { |
||||
|
return |
||||
|
} |
||||
|
try { |
||||
|
const validator = yup |
||||
|
.number() |
||||
|
.positive() |
||||
|
.min(0) |
||||
|
.typeError('A number is required') |
||||
|
if (required) { |
||||
|
validator.required() |
||||
|
} |
||||
|
validator.validateSync(Number(value)) |
||||
|
} catch (error) { |
||||
|
return error.message |
||||
|
} |
||||
|
|
||||
|
// Run any additional validation provided by the caller.
|
||||
|
const { validate } = this.props |
||||
|
if (validate) { |
||||
|
return validate(value) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return <CryptoAmountInputAsField validate={this.validate} {...this.props} /> |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default WrappedCryptoAmountInputAsField |
@ -0,0 +1,131 @@ |
|||||
|
/* eslint-disable react/no-multi-comp */ |
||||
|
|
||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
import { asField } from 'informed' |
||||
|
import * as yup from 'yup' |
||||
|
import { convert } from 'lib/utils/btc' |
||||
|
import { formatValue, parseNumber } from 'lib/utils/crypto' |
||||
|
import Input from 'components/UI/Input' |
||||
|
|
||||
|
/** |
||||
|
* @render react |
||||
|
* @name FiatAmountInput |
||||
|
*/ |
||||
|
class FiatAmountInput extends React.Component { |
||||
|
static propTypes = { |
||||
|
currency: PropTypes.string.isRequired, |
||||
|
currentTicker: PropTypes.object.isRequired, |
||||
|
required: PropTypes.bool, |
||||
|
onChange: PropTypes.func, |
||||
|
onBlur: PropTypes.func |
||||
|
} |
||||
|
|
||||
|
componentDidUpdate(prevProps) { |
||||
|
const { currency, currentTicker, fieldApi } = this.props |
||||
|
|
||||
|
// Reformat the value when the currency unit has changed.
|
||||
|
if (currency !== prevProps.currency) { |
||||
|
const { fieldApi } = this.props |
||||
|
let value = fieldApi.getValue() |
||||
|
const lastPriceInOrigCurrency = currentTicker[prevProps.currency].last |
||||
|
const lastPriceInNewCurrency = currentTicker[currency].last |
||||
|
// Convert to BTC.
|
||||
|
const btcValue = convert('fiat', 'btc', value, lastPriceInOrigCurrency) |
||||
|
// Convert to new currency.
|
||||
|
const newFiatValue = convert('btc', 'fiat', btcValue, lastPriceInNewCurrency) |
||||
|
const [integer, fractional] = parseNumber(newFiatValue, this.getRules().precision) |
||||
|
value = formatValue(integer, fractional) |
||||
|
fieldApi.setValue(value) |
||||
|
} |
||||
|
|
||||
|
// If the value has changed, reformat it if needed.
|
||||
|
const valueBefore = prevProps.fieldState.value |
||||
|
const valueAfter = fieldApi.getValue() |
||||
|
if (valueAfter !== valueBefore) { |
||||
|
const [integer, fractional] = parseNumber(valueAfter, this.getRules().precision) |
||||
|
const formattedValue = formatValue(integer, fractional) |
||||
|
if (formattedValue !== valueAfter) { |
||||
|
fieldApi.setValue(formattedValue) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
getRules() { |
||||
|
return { |
||||
|
precision: 2, |
||||
|
placeholder: '0.00', |
||||
|
pattern: '[0-9]*.?[0-9]{0,2}?' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
handleKeyDown = e => { |
||||
|
// Do nothing if the user did select all key combo.
|
||||
|
if (e.metaKey && e.key === 'a') { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Do not allow multiple dots.
|
||||
|
let { value } = e.target |
||||
|
if (e.key === '.') { |
||||
|
if (value.search(/\./) >= 0) { |
||||
|
e.preventDefault() |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if (e.key.length === 1 && !e.key.match(/^[0-9.]$/)) { |
||||
|
e.preventDefault() |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const rules = this.getRules() |
||||
|
return ( |
||||
|
<Input |
||||
|
{...this.props} |
||||
|
type="text" |
||||
|
placeholder={rules.placeholder} |
||||
|
pattern={rules.pattern} |
||||
|
onKeyDown={this.handleKeyDown} |
||||
|
/> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const FiatAmountInputAsField = asField(FiatAmountInput) |
||||
|
|
||||
|
class WrappedFiatAmountInputAsField extends React.Component { |
||||
|
validate = value => { |
||||
|
const { disabled, required } = this.props |
||||
|
if (disabled) { |
||||
|
return |
||||
|
} |
||||
|
try { |
||||
|
const validator = yup |
||||
|
.number() |
||||
|
.positive() |
||||
|
.min(0) |
||||
|
.typeError('A number is required') |
||||
|
if (required) { |
||||
|
validator.required() |
||||
|
} |
||||
|
validator.validateSync(Number(value)) |
||||
|
} catch (error) { |
||||
|
return error.message |
||||
|
} |
||||
|
|
||||
|
// Run any additional validation provided by the caller.
|
||||
|
const { validate } = this.props |
||||
|
if (validate) { |
||||
|
return validate(value) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return <FiatAmountInputAsField validate={this.validate} {...this.props} /> |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default WrappedFiatAmountInputAsField |
Loading…
Reference in new issue