From badebe017fe28b58ac742082368484c3a4b1c1bc Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 17 Oct 2018 07:03:29 +0800 Subject: Adds toggle for primary currency (#5421) * Add UnitInput component * Add CurrencyInput component * Add UserPreferencedCurrencyInput component * Add UserPreferencedCurrencyDisplay component * Add updatePreferences action * Add styles for CurrencyInput, CurrencyDisplay, and UnitInput * Update SettingsTab page with Primary Currency toggle * Refactor currency displays and inputs to use UserPreferenced displays and inputs * Add TokenInput component * Add UserPreferencedTokenInput component * Use TokenInput in the send screen * Fix unit tests * Fix e2e and integration tests * Remove send/CurrencyDisplay component * Replace diamond unicode character with Eth logo. Fix typos --- .../account-list-item.component.js | 29 ++-- .../tests/account-list-item-component.test.js | 16 +- .../send/currency-display/currency-display.js | 186 --------------------- ui/app/components/send/currency-display/index.js | 1 - .../tests/currency-display.test.js | 91 ---------- .../send-amount-row/send-amount-row.component.js | 45 +++-- .../tests/send-amount-row-component.test.js | 27 +-- .../gas-fee-display/gas-fee-display.component.js | 35 ++-- .../test/gas-fee-display.component.test.js | 12 +- 9 files changed, 70 insertions(+), 372 deletions(-) delete mode 100644 ui/app/components/send/currency-display/currency-display.js delete mode 100644 ui/app/components/send/currency-display/index.js delete mode 100644 ui/app/components/send/currency-display/tests/currency-display.test.js (limited to 'ui/app/components/send') diff --git a/ui/app/components/send/account-list-item/account-list-item.component.js b/ui/app/components/send/account-list-item/account-list-item.component.js index 9f4a96e61..14bb7471f 100644 --- a/ui/app/components/send/account-list-item/account-list-item.component.js +++ b/ui/app/components/send/account-list-item/account-list-item.component.js @@ -2,7 +2,8 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import { checksumAddress } from '../../../util' import Identicon from '../../identicon' -import CurrencyDisplay from '../currency-display' +import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display' +import { PRIMARY, SECONDARY } from '../../../constants/common' export default class AccountListItem extends Component { @@ -25,8 +26,6 @@ export default class AccountListItem extends Component { const { account, className, - conversionRate, - currentCurrency, displayAddress = false, displayBalance = true, handleClick, @@ -57,16 +56,20 @@ export default class AccountListItem extends Component { { checksumAddress(address) } } - {displayBalance && } + { + displayBalance && ( +
+ + +
+ ) + } ) } diff --git a/ui/app/components/send/account-list-item/tests/account-list-item-component.test.js b/ui/app/components/send/account-list-item/tests/account-list-item-component.test.js index ef152d2e7..f88c0dbd0 100644 --- a/ui/app/components/send/account-list-item/tests/account-list-item-component.test.js +++ b/ui/app/components/send/account-list-item/tests/account-list-item-component.test.js @@ -4,7 +4,7 @@ import { shallow } from 'enzyme' import sinon from 'sinon' import proxyquire from 'proxyquire' import Identicon from '../../../identicon' -import CurrencyDisplay from '../../currency-display' +import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display' const utilsMethodStubs = { checksumAddress: sinon.stub().returns('mockCheckSumAddress'), @@ -114,17 +114,11 @@ describe('AccountListItem Component', function () { it('should render a CurrencyDisplay with the correct props if displayBalance is true', () => { wrapper.setProps({ displayBalance: true }) - assert.equal(wrapper.find(CurrencyDisplay).length, 1) + assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2) assert.deepEqual( - wrapper.find(CurrencyDisplay).props(), + wrapper.find(UserPreferencedCurrencyDisplay).at(0).props(), { - className: 'account-list-item__account-balances', - conversionRate: 4, - convertedBalanceClassName: 'account-list-item__account-secondary-balance', - convertedCurrency: 'mockCurrentyCurrency', - primaryBalanceClassName: 'account-list-item__account-primary-balance', - primaryCurrency: 'ETH', - readOnly: true, + type: 'PRIMARY', value: 'mockBalance', } ) @@ -132,7 +126,7 @@ describe('AccountListItem Component', function () { it('should not render a CurrencyDisplay if displayBalance is false', () => { wrapper.setProps({ displayBalance: false }) - assert.equal(wrapper.find(CurrencyDisplay).length, 0) + assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 0) }) }) }) diff --git a/ui/app/components/send/currency-display/currency-display.js b/ui/app/components/send/currency-display/currency-display.js deleted file mode 100644 index 2b8eaa41f..000000000 --- a/ui/app/components/send/currency-display/currency-display.js +++ /dev/null @@ -1,186 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const { conversionUtil, multiplyCurrencies } = require('../../../conversion-util') -const { removeLeadingZeroes } = require('../send.utils') -const currencyFormatter = require('currency-formatter') -const currencies = require('currency-formatter/currencies') -const ethUtil = require('ethereumjs-util') -const PropTypes = require('prop-types') - -CurrencyDisplay.contextTypes = { - t: PropTypes.func, -} - -module.exports = CurrencyDisplay - -inherits(CurrencyDisplay, Component) -function CurrencyDisplay () { - Component.call(this) -} - -function toHexWei (value) { - return conversionUtil(value, { - fromNumericBase: 'dec', - toNumericBase: 'hex', - toDenomination: 'WEI', - }) -} - -CurrencyDisplay.prototype.componentWillMount = function () { - this.setState({ - valueToRender: this.getValueToRender(this.props), - }) -} - -CurrencyDisplay.prototype.componentWillReceiveProps = function (nextProps) { - const currentValueToRender = this.getValueToRender(this.props) - const newValueToRender = this.getValueToRender(nextProps) - if (currentValueToRender !== newValueToRender) { - this.setState({ - valueToRender: newValueToRender, - }) - } -} - -CurrencyDisplay.prototype.getAmount = function (value) { - const { selectedToken } = this.props - const { decimals } = selectedToken || {} - const multiplier = Math.pow(10, Number(decimals || 0)) - - const sendAmount = multiplyCurrencies(value || '0', multiplier, {toNumericBase: 'hex'}) - - return selectedToken - ? sendAmount - : toHexWei(value) -} - -CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversionRate, value, readOnly }) { - if (value === '0x0') return readOnly ? '0' : '' - const { decimals, symbol } = selectedToken || {} - const multiplier = Math.pow(10, Number(decimals || 0)) - - return selectedToken - ? conversionUtil(ethUtil.addHexPrefix(value), { - fromNumericBase: 'hex', - toNumericBase: 'dec', - toCurrency: symbol, - conversionRate: multiplier, - invertConversionRate: true, - }) - : conversionUtil(ethUtil.addHexPrefix(value), { - fromNumericBase: 'hex', - toNumericBase: 'dec', - fromDenomination: 'WEI', - numberOfDecimals: 9, - conversionRate, - }) -} - -CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValue) { - const { primaryCurrency, convertedCurrency, conversionRate } = this.props - - if (conversionRate === 0 || conversionRate === null || conversionRate === undefined) { - if (nonFormattedValue !== 0) { - return null - } - } - - let convertedValue = conversionUtil(nonFormattedValue, { - fromNumericBase: 'dec', - fromCurrency: primaryCurrency, - toCurrency: convertedCurrency, - numberOfDecimals: 2, - conversionRate, - }) - - convertedValue = Number(convertedValue).toFixed(2) - const upperCaseCurrencyCode = convertedCurrency.toUpperCase() - return currencies.find(currency => currency.code === upperCaseCurrencyCode) - ? currencyFormatter.format(Number(convertedValue), { - code: upperCaseCurrencyCode, - }) - : convertedValue - } - -CurrencyDisplay.prototype.handleChange = function (newVal) { - this.setState({ valueToRender: removeLeadingZeroes(newVal) }) - this.props.onChange(this.getAmount(newVal)) -} - -CurrencyDisplay.prototype.getInputWidth = function (valueToRender, readOnly) { - const valueString = String(valueToRender) - const valueLength = valueString.length || 1 - const decimalPointDeficit = valueString.match(/\./) ? -0.5 : 0 - return (valueLength + decimalPointDeficit + 0.75) + 'ch' -} - -CurrencyDisplay.prototype.onlyRenderConversions = function (convertedValueToRender) { - const { - convertedBalanceClassName = 'currency-display__converted-value', - convertedCurrency, - } = this.props - return h('div', { - className: convertedBalanceClassName, - }, convertedValueToRender == null - ? this.context.t('noConversionRateAvailable') - : `${convertedValueToRender} ${convertedCurrency.toUpperCase()}` -) - } - -CurrencyDisplay.prototype.render = function () { - const { - className = 'currency-display', - primaryBalanceClassName = 'currency-display__input', - primaryCurrency, - readOnly = false, - inError = false, - onBlur, - step, - } = this.props - const { valueToRender } = this.state - - const convertedValueToRender = this.getConvertedValueToRender(valueToRender) - - return h('div', { - className, - style: { - borderColor: inError ? 'red' : null, - }, - onClick: () => { - this.currencyInput && this.currencyInput.focus() - }, - }, [ - - h('div.currency-display__primary-row', [ - - h('div.currency-display__input-wrapper', [ - - h('input', { - className: primaryBalanceClassName, - value: `${valueToRender}`, - placeholder: '0', - type: 'number', - readOnly, - ...(!readOnly ? { - onChange: e => this.handleChange(e.target.value), - onBlur: () => onBlur(this.getAmount(valueToRender)), - } : {}), - ref: input => { this.currencyInput = input }, - style: { - width: this.getInputWidth(valueToRender, readOnly), - }, - min: 0, - step, - }), - - h('span.currency-display__currency-symbol', primaryCurrency), - - ]), - - ]), this.onlyRenderConversions(convertedValueToRender), - - ]) - -} - diff --git a/ui/app/components/send/currency-display/index.js b/ui/app/components/send/currency-display/index.js deleted file mode 100644 index 5dc269c5a..000000000 --- a/ui/app/components/send/currency-display/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './currency-display.js' diff --git a/ui/app/components/send/currency-display/tests/currency-display.test.js b/ui/app/components/send/currency-display/tests/currency-display.test.js deleted file mode 100644 index c9560b81c..000000000 --- a/ui/app/components/send/currency-display/tests/currency-display.test.js +++ /dev/null @@ -1,91 +0,0 @@ -import React from 'react' -import assert from 'assert' -import sinon from 'sinon' -import { shallow, mount } from 'enzyme' -import CurrencyDisplay from '../currency-display' - -describe('', () => { - - const token = { - address: '0xTest', - symbol: 'TST', - decimals: '13', - } - - it('retuns ETH value for wei value', () => { - const wrapper = mount(, {context: {t: str => str + '_t'}}) - - const value = wrapper.instance().getValueToRender({ - // 1000000000000000000 - value: 'DE0B6B3A7640000', - }) - - assert.equal(value, 1) - }) - - it('returns value of token based on token decimals', () => { - const wrapper = mount(, {context: {t: str => str + '_t'}}) - - const value = wrapper.instance().getValueToRender({ - selectedToken: token, - // 1000000000000000000 - value: 'DE0B6B3A7640000', - }) - - assert.equal(value, 100000) - }) - - it('returns hex value with decimal adjustment', () => { - - const wrapper = mount( - , {context: {t: str => str + '_t'}}) - - const value = wrapper.instance().getAmount(1) - // 10000000000000 - assert.equal(value, '9184e72a000') - }) - - it('#getConvertedValueToRender converts input value based on conversionRate', () => { - - const wrapper = mount( - , {context: {t: str => str + '_t'}}) - - const value = wrapper.instance().getConvertedValueToRender(32) - - assert.equal(value, 64) - }) - - it('#onlyRenderConversions renders single element for converted currency and value', () => { - const wrapper = mount( - , {context: {t: str => str + '_t'}}) - - const value = wrapper.instance().onlyRenderConversions(10) - assert.equal(value.props.className, 'currency-display__converted-value') - assert.equal(value.props.children, '10 TEST') - }) - - it('simulates change value in input', () => { - const handleChangeSpy = sinon.spy() - - const wrapper = shallow( - , {context: {t: str => str + '_t'}}) - - const input = wrapper.find('input') - input.simulate('focus') - input.simulate('change', { target: { value: '100' } }) - - assert.equal(wrapper.state().valueToRender, '100') - assert.equal(wrapper.find('input').prop('value'), '100') - }) - -}) diff --git a/ui/app/components/send/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send/send-content/send-amount-row/send-amount-row.component.js index c548a5695..0268376bf 100644 --- a/ui/app/components/send/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send/send-content/send-amount-row/send-amount-row.component.js @@ -2,7 +2,8 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import SendRowWrapper from '../send-row-wrapper/' import AmountMaxButton from './amount-max-button/' -import CurrencyDisplay from '../../currency-display' +import UserPreferencedCurrencyInput from '../../../user-preferenced-currency-input' +import UserPreferencedTokenInput from '../../../user-preferenced-token-input' export default class SendAmountRow extends Component { @@ -84,16 +85,25 @@ export default class SendAmountRow extends Component { } } + renderInput () { + const { amount, inError, selectedToken } = this.props + const Component = selectedToken ? UserPreferencedTokenInput : UserPreferencedCurrencyInput + + return ( + this.validateAmount(newAmount)} + onBlur={newAmount => { + this.updateGas(newAmount) + this.updateAmount(newAmount) + }} + error={inError} + value={amount} + /> + ) + } + render () { - const { - amount, - amountConversionRate, - convertedCurrency, - gasTotal, - inError, - primaryCurrency, - selectedToken, - } = this.props + const { gasTotal, inError } = this.props return ( {!inError && gasTotal && } - { - this.updateGas(newAmount) - this.updateAmount(newAmount) - }} - onChange={newAmount => this.validateAmount(newAmount)} - inError={inError} - primaryCurrency={primaryCurrency || 'ETH'} - selectedToken={selectedToken} - value={amount} - step="any" - /> + { this.renderInput() } ) } diff --git a/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js index 8425e076e..e63db4a2d 100644 --- a/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js +++ b/ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js @@ -6,7 +6,7 @@ import SendAmountRow from '../send-amount-row.component.js' import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component' import AmountMaxButton from '../amount-max-button/amount-max-button.container' -import CurrencyDisplay from '../../../currency-display' +import UserPreferencedTokenInput from '../../../../user-preferenced-token-input' const propsMethodSpies = { setMaxModeTo: sinon.spy(), @@ -150,26 +150,19 @@ describe('SendAmountRow Component', function () { assert(wrapper.find(SendRowWrapper).childAt(0).is(AmountMaxButton)) }) - it('should render a CurrencyDisplay as the second child of the SendRowWrapper', () => { - assert(wrapper.find(SendRowWrapper).childAt(1).is(CurrencyDisplay)) + it('should render a UserPreferencedTokenInput as the second child of the SendRowWrapper', () => { + console.log('HI', wrapper.find(SendRowWrapper).childAt(1)) + assert(wrapper.find(SendRowWrapper).childAt(1).is(UserPreferencedTokenInput)) }) - it('should render the CurrencyDisplay with the correct props', () => { + it('should render the UserPreferencedTokenInput with the correct props', () => { const { - conversionRate, - convertedCurrency, onBlur, onChange, - inError, - primaryCurrency, - selectedToken, + error, value, } = wrapper.find(SendRowWrapper).childAt(1).props() - assert.equal(conversionRate, 'mockAmountConversionRate') - assert.equal(convertedCurrency, 'mockConvertedCurrency') - assert.equal(inError, false) - assert.equal(primaryCurrency, 'mockPrimaryCurrency') - assert.deepEqual(selectedToken, { address: 'mockTokenAddress' }) + assert.equal(error, false) assert.equal(value, 'mockAmount') assert.equal(SendAmountRow.prototype.updateGas.callCount, 0) assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0) @@ -192,11 +185,5 @@ describe('SendAmountRow Component', function () { ['mockNewAmount'] ) }) - - it('should pass the default primaryCurrency to the CurrencyDisplay if primaryCurrency is falsy', () => { - wrapper.setProps({ primaryCurrency: null }) - const { primaryCurrency } = wrapper.find(SendRowWrapper).childAt(1).props() - assert.equal(primaryCurrency, 'ETH') - }) }) }) diff --git a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js b/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js index bb9a94428..9bbb67506 100644 --- a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js +++ b/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js @@ -1,7 +1,7 @@ import React, {Component} from 'react' import PropTypes from 'prop-types' -import CurrencyDisplay from '../../../../send/currency-display' - +import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display' +import { PRIMARY, SECONDARY } from '../../../../../constants/common' export default class GasFeeDisplay extends Component { @@ -19,27 +19,24 @@ export default class GasFeeDisplay extends Component { }; render () { - const { - conversionRate, - gasTotal, - onClick, - primaryCurrency = 'ETH', - convertedCurrency, - gasLoadingError, - } = this.props + const { gasTotal, onClick, gasLoadingError } = this.props return (
{gasTotal - ? + ? ( +
+ + +
+ ) : gasLoadingError ?
{this.context.t('setGasPrice')} diff --git a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js b/ui/app/components/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js index 7cbe8d0df..9ff01493a 100644 --- a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js +++ b/ui/app/components/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js @@ -2,7 +2,7 @@ import React from 'react' import assert from 'assert' import {shallow} from 'enzyme' import GasFeeDisplay from '../gas-fee-display.component' -import CurrencyDisplay from '../../../../../send/currency-display' +import UserPreferencedCurrencyDisplay from '../../../../../user-preferenced-currency-display' import sinon from 'sinon' @@ -29,17 +29,15 @@ describe('SendGasRow Component', function () { describe('render', () => { it('should render a CurrencyDisplay component', () => { - assert.equal(wrapper.find(CurrencyDisplay).length, 1) + assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2) }) it('should render the CurrencyDisplay with the correct props', () => { const { - conversionRate, - convertedCurrency, + type, value, - } = wrapper.find(CurrencyDisplay).props() - assert.equal(conversionRate, 20) - assert.equal(convertedCurrency, 'mockConvertedCurrency') + } = wrapper.find(UserPreferencedCurrencyDisplay).at(0).props() + assert.equal(type, 'PRIMARY') assert.equal(value, 'mockGasTotal') }) -- cgit