aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/components/currency-input
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app/components/currency-input')
-rw-r--r--ui/app/components/currency-input/currency-input.component.js120
-rw-r--r--ui/app/components/currency-input/currency-input.container.js27
-rw-r--r--ui/app/components/currency-input/index.js1
-rw-r--r--ui/app/components/currency-input/index.scss7
-rw-r--r--ui/app/components/currency-input/tests/currency-input.component.test.js239
-rw-r--r--ui/app/components/currency-input/tests/currency-input.container.test.js55
6 files changed, 449 insertions, 0 deletions
diff --git a/ui/app/components/currency-input/currency-input.component.js b/ui/app/components/currency-input/currency-input.component.js
new file mode 100644
index 000000000..54cd0e1ac
--- /dev/null
+++ b/ui/app/components/currency-input/currency-input.component.js
@@ -0,0 +1,120 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import UnitInput from '../unit-input'
+import CurrencyDisplay from '../currency-display'
+import { getValueFromWeiHex, getWeiHexFromDecimalValue } from '../../helpers/conversions.util'
+import { ETH } from '../../constants/common'
+
+/**
+ * Component that allows user to enter currency values as a number, and props receive a converted
+ * hex value in WEI. props.value, used as a default or forced value, should be a hex value, which
+ * gets converted into a decimal value depending on the currency (ETH or Fiat).
+ */
+export default class CurrencyInput extends PureComponent {
+ static propTypes = {
+ conversionRate: PropTypes.number,
+ currentCurrency: PropTypes.string,
+ onChange: PropTypes.func,
+ onBlur: PropTypes.func,
+ suffix: PropTypes.string,
+ useFiat: PropTypes.bool,
+ value: PropTypes.string,
+ }
+
+ constructor (props) {
+ super(props)
+
+ const { value: hexValue } = props
+ const decimalValue = hexValue ? this.getDecimalValue(props) : 0
+
+ this.state = {
+ decimalValue,
+ hexValue,
+ }
+ }
+
+ componentDidUpdate (prevProps) {
+ const { value: prevPropsHexValue } = prevProps
+ const { value: propsHexValue } = this.props
+ const { hexValue: stateHexValue } = this.state
+
+ if (prevPropsHexValue !== propsHexValue && propsHexValue !== stateHexValue) {
+ const decimalValue = this.getDecimalValue(this.props)
+ this.setState({ hexValue: propsHexValue, decimalValue })
+ }
+ }
+
+ getDecimalValue (props) {
+ const { value: hexValue, useFiat, currentCurrency, conversionRate } = props
+ const decimalValueString = useFiat
+ ? getValueFromWeiHex({
+ value: hexValue, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
+ })
+ : getValueFromWeiHex({
+ value: hexValue, toCurrency: ETH, numberOfDecimals: 6,
+ })
+
+ return Number(decimalValueString) || 0
+ }
+
+ handleChange = decimalValue => {
+ const { useFiat, currentCurrency: fromCurrency, conversionRate, onChange } = this.props
+
+ const hexValue = useFiat
+ ? getWeiHexFromDecimalValue({
+ value: decimalValue, fromCurrency, conversionRate, invertConversionRate: true,
+ })
+ : getWeiHexFromDecimalValue({
+ value: decimalValue, fromCurrency: ETH, fromDenomination: ETH, conversionRate,
+ })
+
+ this.setState({ hexValue, decimalValue })
+ onChange(hexValue)
+ }
+
+ handleBlur = () => {
+ this.props.onBlur(this.state.hexValue)
+ }
+
+ renderConversionComponent () {
+ const { useFiat, currentCurrency } = this.props
+ const { hexValue } = this.state
+ let currency, numberOfDecimals
+
+ if (useFiat) {
+ // Display ETH
+ currency = ETH
+ numberOfDecimals = 6
+ } else {
+ // Display Fiat
+ currency = currentCurrency
+ numberOfDecimals = 2
+ }
+
+ return (
+ <CurrencyDisplay
+ className="currency-input__conversion-component"
+ currency={currency}
+ value={hexValue}
+ numberOfDecimals={numberOfDecimals}
+ />
+ )
+ }
+
+ render () {
+ const { suffix, ...restProps } = this.props
+ const { decimalValue } = this.state
+
+ return (
+ <UnitInput
+ {...restProps}
+ suffix={suffix}
+ onChange={this.handleChange}
+ onBlur={this.handleBlur}
+ value={decimalValue}
+ >
+ { this.renderConversionComponent() }
+ </UnitInput>
+ )
+ }
+}
diff --git a/ui/app/components/currency-input/currency-input.container.js b/ui/app/components/currency-input/currency-input.container.js
new file mode 100644
index 000000000..18e5533de
--- /dev/null
+++ b/ui/app/components/currency-input/currency-input.container.js
@@ -0,0 +1,27 @@
+import { connect } from 'react-redux'
+import CurrencyInput from './currency-input.component'
+import { ETH } from '../../constants/common'
+
+const mapStateToProps = state => {
+ const { metamask: { currentCurrency, conversionRate } } = state
+
+ return {
+ currentCurrency,
+ conversionRate,
+ }
+}
+
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const { currentCurrency } = stateProps
+ const { useFiat } = ownProps
+ const suffix = useFiat ? currentCurrency.toUpperCase() : ETH
+
+ return {
+ ...stateProps,
+ ...dispatchProps,
+ ...ownProps,
+ suffix,
+ }
+}
+
+export default connect(mapStateToProps, null, mergeProps)(CurrencyInput)
diff --git a/ui/app/components/currency-input/index.js b/ui/app/components/currency-input/index.js
new file mode 100644
index 000000000..d8069fb67
--- /dev/null
+++ b/ui/app/components/currency-input/index.js
@@ -0,0 +1 @@
+export { default } from './currency-input.container'
diff --git a/ui/app/components/currency-input/index.scss b/ui/app/components/currency-input/index.scss
new file mode 100644
index 000000000..fcb2db461
--- /dev/null
+++ b/ui/app/components/currency-input/index.scss
@@ -0,0 +1,7 @@
+.currency-input {
+ &__conversion-component {
+ font-size: 12px;
+ line-height: 12px;
+ padding-left: 1px;
+ }
+}
diff --git a/ui/app/components/currency-input/tests/currency-input.component.test.js b/ui/app/components/currency-input/tests/currency-input.component.test.js
new file mode 100644
index 000000000..8de0ef863
--- /dev/null
+++ b/ui/app/components/currency-input/tests/currency-input.component.test.js
@@ -0,0 +1,239 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow, mount } from 'enzyme'
+import sinon from 'sinon'
+import { Provider } from 'react-redux'
+import configureMockStore from 'redux-mock-store'
+import CurrencyInput from '../currency-input.component'
+import UnitInput from '../../unit-input'
+import CurrencyDisplay from '../../currency-display'
+
+describe('CurrencyInput Component', () => {
+ describe('rendering', () => {
+ it('should render properly without a suffix', () => {
+ const wrapper = shallow(
+ <CurrencyInput />
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find(UnitInput).length, 1)
+ })
+
+ it('should render properly with a suffix', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+
+ const wrapper = mount(
+ <Provider store={store}>
+ <CurrencyInput
+ suffix="ETH"
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.find('.unit-input__suffix').length, 1)
+ assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH')
+ assert.equal(wrapper.find(CurrencyDisplay).length, 1)
+ })
+
+ it('should render properly with an ETH value', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+
+ const wrapper = mount(
+ <Provider store={store}>
+ <CurrencyInput
+ value="de0b6b3a7640000"
+ suffix="ETH"
+ currentCurrency="usd"
+ conversionRate={231.06}
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
+ assert.equal(currencyInputInstance.state.decimalValue, 1)
+ assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000')
+ assert.equal(wrapper.find('.unit-input__suffix').length, 1)
+ assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH')
+ assert.equal(wrapper.find('.unit-input__input').props().value, '1')
+ assert.equal(wrapper.find('.currency-display-component').text(), '$231.06 USD')
+ })
+
+ it('should render properly with a fiat value', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+
+ const wrapper = mount(
+ <Provider store={store}>
+ <CurrencyInput
+ value="f602f2234d0ea"
+ suffix="USD"
+ useFiat
+ currentCurrency="usd"
+ conversionRate={231.06}
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
+ assert.equal(currencyInputInstance.state.decimalValue, 1)
+ assert.equal(currencyInputInstance.state.hexValue, 'f602f2234d0ea')
+ assert.equal(wrapper.find('.unit-input__suffix').length, 1)
+ assert.equal(wrapper.find('.unit-input__suffix').text(), 'USD')
+ assert.equal(wrapper.find('.unit-input__input').props().value, '1')
+ assert.equal(wrapper.find('.currency-display-component').text(), '0.004328 ETH')
+ })
+ })
+
+ describe('handling actions', () => {
+ const handleChangeSpy = sinon.spy()
+ const handleBlurSpy = sinon.spy()
+
+ afterEach(() => {
+ handleChangeSpy.resetHistory()
+ handleBlurSpy.resetHistory()
+ })
+
+ it('should call onChange and onBlur on input changes with the hex value for ETH', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+ const wrapper = mount(
+ <Provider store={store}>
+ <CurrencyInput
+ onChange={handleChangeSpy}
+ onBlur={handleBlurSpy}
+ suffix="ETH"
+ currentCurrency="usd"
+ conversionRate={231.06}
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ assert.equal(handleChangeSpy.callCount, 0)
+ assert.equal(handleBlurSpy.callCount, 0)
+
+ const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
+ assert.equal(currencyInputInstance.state.decimalValue, 0)
+ assert.equal(currencyInputInstance.state.hexValue, undefined)
+ assert.equal(wrapper.find('.currency-display-component').text(), '$0.00 USD')
+ const input = wrapper.find('input')
+ assert.equal(input.props().value, 0)
+
+ input.simulate('change', { target: { value: 1 } })
+ assert.equal(handleChangeSpy.callCount, 1)
+ assert.ok(handleChangeSpy.calledWith('de0b6b3a7640000'))
+ assert.equal(wrapper.find('.currency-display-component').text(), '$231.06 USD')
+ assert.equal(currencyInputInstance.state.decimalValue, 1)
+ assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000')
+
+ assert.equal(handleBlurSpy.callCount, 0)
+ input.simulate('blur')
+ assert.equal(handleBlurSpy.callCount, 1)
+ assert.ok(handleBlurSpy.calledWith('de0b6b3a7640000'))
+ })
+
+ it('should call onChange and onBlur on input changes with the hex value for fiat', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+ const wrapper = mount(
+ <Provider store={store}>
+ <CurrencyInput
+ onChange={handleChangeSpy}
+ onBlur={handleBlurSpy}
+ suffix="USD"
+ currentCurrency="usd"
+ conversionRate={231.06}
+ useFiat
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ assert.equal(handleChangeSpy.callCount, 0)
+ assert.equal(handleBlurSpy.callCount, 0)
+
+ const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
+ assert.equal(currencyInputInstance.state.decimalValue, 0)
+ assert.equal(currencyInputInstance.state.hexValue, undefined)
+ assert.equal(wrapper.find('.currency-display-component').text(), '0 ETH')
+ const input = wrapper.find('input')
+ assert.equal(input.props().value, 0)
+
+ input.simulate('change', { target: { value: 1 } })
+ assert.equal(handleChangeSpy.callCount, 1)
+ assert.ok(handleChangeSpy.calledWith('f602f2234d0ea'))
+ assert.equal(wrapper.find('.currency-display-component').text(), '0.004328 ETH')
+ assert.equal(currencyInputInstance.state.decimalValue, 1)
+ assert.equal(currencyInputInstance.state.hexValue, 'f602f2234d0ea')
+
+ assert.equal(handleBlurSpy.callCount, 0)
+ input.simulate('blur')
+ assert.equal(handleBlurSpy.callCount, 1)
+ assert.ok(handleBlurSpy.calledWith('f602f2234d0ea'))
+ })
+
+ it('should change the state and pass in a new decimalValue when props.value changes', () => {
+ const mockStore = {
+ metamask: {
+ currentCurrency: 'usd',
+ conversionRate: 231.06,
+ },
+ }
+ const store = configureMockStore()(mockStore)
+ const wrapper = shallow(
+ <Provider store={store}>
+ <CurrencyInput
+ onChange={handleChangeSpy}
+ onBlur={handleBlurSpy}
+ suffix="USD"
+ currentCurrency="usd"
+ conversionRate={231.06}
+ useFiat
+ />
+ </Provider>
+ )
+
+ assert.ok(wrapper)
+ const currencyInputInstance = wrapper.find(CurrencyInput).dive()
+ assert.equal(currencyInputInstance.state('decimalValue'), 0)
+ assert.equal(currencyInputInstance.state('hexValue'), undefined)
+ assert.equal(currencyInputInstance.find(UnitInput).props().value, 0)
+
+ currencyInputInstance.setProps({ value: '1ec05e43e72400' })
+ currencyInputInstance.update()
+ assert.equal(currencyInputInstance.state('decimalValue'), 2)
+ assert.equal(currencyInputInstance.state('hexValue'), '1ec05e43e72400')
+ assert.equal(currencyInputInstance.find(UnitInput).props().value, 2)
+ })
+ })
+})
diff --git a/ui/app/components/currency-input/tests/currency-input.container.test.js b/ui/app/components/currency-input/tests/currency-input.container.test.js
new file mode 100644
index 000000000..e77945e4d
--- /dev/null
+++ b/ui/app/components/currency-input/tests/currency-input.container.test.js
@@ -0,0 +1,55 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+
+let mapStateToProps, mergeProps
+
+proxyquire('../currency-input.container.js', {
+ 'react-redux': {
+ connect: (ms, md, mp) => {
+ mapStateToProps = ms
+ mergeProps = mp
+ return () => ({})
+ },
+ },
+})
+
+describe('CurrencyInput container', () => {
+ describe('mapStateToProps()', () => {
+ it('should return the correct props', () => {
+ const mockState = {
+ metamask: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ },
+ }
+
+ assert.deepEqual(mapStateToProps(mockState), {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ })
+ })
+ })
+
+ describe('mergeProps()', () => {
+ it('should return the correct props', () => {
+ const mockStateProps = {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ }
+ const mockDispatchProps = {}
+
+ assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, { useFiat: true }), {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ useFiat: true,
+ suffix: 'USD',
+ })
+
+ assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, {}), {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ suffix: 'ETH',
+ })
+ })
+ })
+})