diff options
Merge pull request #2591 from MetaMask/NewUI-flat
Release 4.0.4
Diffstat (limited to 'ui/app/components')
19 files changed, 383 insertions, 116 deletions
diff --git a/ui/app/components/currency-input.js b/ui/app/components/currency-input.js new file mode 100644 index 000000000..66880091f --- /dev/null +++ b/ui/app/components/currency-input.js @@ -0,0 +1,95 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +module.exports = CurrencyInput + +inherits(CurrencyInput, Component) +function CurrencyInput (props) { + Component.call(this) + + this.state = { + value: sanitizeValue(props.value), + } +} + +function removeNonDigits (str) { + return str.match(/\d|$/g).join('') +} + +// Removes characters that are not digits, then removes leading zeros +function sanitizeInteger (val) { + return String(parseInt(removeNonDigits(val) || '0', 10)) +} + +function sanitizeDecimal (val) { + return removeNonDigits(val) +} + +// Take a single string param and returns a non-negative integer or float as a string. +// Breaks the input into three parts: the integer, the decimal point, and the decimal/fractional part. +// Removes leading zeros from the integer, and non-digits from the integer and decimal +// The integer is returned as '0' in cases where it would be empty. A decimal point is +// included in the returned string if one is included in the param +// Examples: +// sanitizeValue('0') -> '0' +// sanitizeValue('a') -> '0' +// sanitizeValue('010.') -> '10.' +// sanitizeValue('0.005') -> '0.005' +// sanitizeValue('22.200') -> '22.200' +// sanitizeValue('.200') -> '0.200' +// sanitizeValue('a.b.1.c,89.123') -> '0.189123' +function sanitizeValue (value) { + let [ , integer, point, decimal] = (/([^.]*)([.]?)([^.]*)/).exec(value) + + integer = sanitizeInteger(integer) || '0' + decimal = sanitizeDecimal(decimal) + + return `${integer}${point}${decimal}` +} + +CurrencyInput.prototype.handleChange = function (newValue) { + const { onInputChange } = this.props + + this.setState({ value: sanitizeValue(newValue) }) + + onInputChange(sanitizeValue(newValue)) +} + +// If state.value === props.value plus a decimal point, or at least one +// zero or a decimal point and at least one zero, then this returns state.value +// after it is sanitized with getValueParts +CurrencyInput.prototype.getValueToRender = function () { + const { value } = this.props + const { value: stateValue } = this.state + + const trailingStateString = (new RegExp(`^${value}(.+)`)).exec(stateValue) + const trailingDecimalAndZeroes = trailingStateString && (/^[.0]0*/).test(trailingStateString[1]) + + return sanitizeValue(trailingDecimalAndZeroes + ? stateValue + : value) +} + +CurrencyInput.prototype.render = function () { + const { + className, + placeholder, + readOnly, + inputRef, + } = this.props + + const inputSizeMultiplier = readOnly ? 1 : 1.2 + + const valueToRender = this.getValueToRender() + + return h('input', { + className, + value: valueToRender, + placeholder, + size: valueToRender.length * inputSizeMultiplier, + readOnly, + onChange: e => this.handleChange(e.target.value), + ref: inputRef, + }) +} diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index 101a19d9f..485dacf90 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -73,6 +73,8 @@ function getOriginalState (props) { gasLimit, gasTotal, error: null, + priceSigZeros: '', + priceSigDec: '', } } @@ -107,7 +109,6 @@ CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) { const { amount, balance, - primaryCurrency, selectedToken, amountConversionRate, conversionRate, @@ -116,10 +117,9 @@ CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) { let error = null const balanceIsSufficient = isBalanceSufficient({ - amount, + amount: selectedToken ? '0' : amount, gasTotal, balance, - primaryCurrency, selectedToken, amountConversionRate, conversionRate, @@ -170,6 +170,13 @@ CustomizeGasModal.prototype.convertAndSetGasLimit = function (newGasLimit) { CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) { const { gasLimit } = this.state + const sigZeros = String(newGasPrice).match(/^\d+[.]\d*?(0+)$/) + const sigDec = String(newGasPrice).match(/^\d+([.])0*$/) + + this.setState({ + priceSigZeros: sigZeros && sigZeros[1] || '', + priceSigDec: sigDec && sigDec[1] || '', + }) const gasPrice = conversionUtil(newGasPrice, { fromNumericBase: 'dec', @@ -191,15 +198,17 @@ CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) { CustomizeGasModal.prototype.render = function () { const { hideModal } = this.props - const { gasPrice, gasLimit, gasTotal, error } = this.state + const { gasPrice, gasLimit, gasTotal, error, priceSigZeros, priceSigDec } = this.state - const convertedGasPrice = conversionUtil(gasPrice, { + let convertedGasPrice = conversionUtil(gasPrice, { fromNumericBase: 'hex', toNumericBase: 'dec', fromDenomination: 'WEI', toDenomination: 'GWEI', }) + convertedGasPrice += convertedGasPrice.match(/[.]/) ? priceSigZeros : `${priceSigDec}${priceSigZeros}` + const convertedGasLimit = conversionUtil(gasLimit, { fromNumericBase: 'hex', toNumericBase: 'dec', @@ -224,7 +233,7 @@ CustomizeGasModal.prototype.render = function () { value: convertedGasPrice, min: MIN_GAS_PRICE_GWEI, // max: 1000, - step: MIN_GAS_PRICE_GWEI, + step: multiplyCurrencies(MIN_GAS_PRICE_GWEI, 10), onChange: value => this.convertAndSetGasPrice(value), title: 'Gas Price (GWEI)', copy: 'We calculate the suggested gas prices based on network success rates.', diff --git a/ui/app/components/dropdowns/components/dropdown.js b/ui/app/components/dropdowns/components/dropdown.js index ddcb7998f..15d064be8 100644 --- a/ui/app/components/dropdowns/components/dropdown.js +++ b/ui/app/components/dropdowns/components/dropdown.js @@ -31,7 +31,7 @@ class Dropdown extends Component { containerClassName, useCssTransition, isOpen, - zIndex: 30, + zIndex: 55, onClickOutside, style, innerStyle: innerStyleDefaults, diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js index 20dfca590..0908faf01 100644 --- a/ui/app/components/dropdowns/network-dropdown.js +++ b/ui/app/components/dropdowns/network-dropdown.js @@ -75,11 +75,12 @@ NetworkDropdown.prototype.render = function () { } }, containerClassName: 'network-droppo', - zIndex: 11, + zIndex: 55, style: { position: 'absolute', top: '58px', minWidth: '309px', + zIndex: '55px', }, innerStyle: { padding: '18px 8px', diff --git a/ui/app/components/input-number.js b/ui/app/components/input-number.js index e28807c13..fd8c5c309 100644 --- a/ui/app/components/input-number.js +++ b/ui/app/components/input-number.js @@ -1,11 +1,12 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const CurrencyInput = require('./currency-input') const { addCurrencies, conversionGTE, conversionLTE, - toNegative, + subtractCurrencies, } = require('../conversion-util') module.exports = InputNumber @@ -17,18 +18,24 @@ function InputNumber () { this.setValue = this.setValue.bind(this) } +function isValidInput (text) { + const re = /^([1-9]\d*|0)(\.|\.\d*)?$/ + return re.test(text) +} + InputNumber.prototype.setValue = function (newValue) { + if (newValue && !isValidInput(newValue)) return const { fixed, min = -1, max = Infinity, onChange } = this.props - newValue = Number(fixed ? newValue.toFixed(4) : newValue) + newValue = fixed ? newValue.toFixed(4) : newValue const newValueGreaterThanMin = conversionGTE( - { value: newValue, fromNumericBase: 'dec' }, + { value: newValue || '0', fromNumericBase: 'dec' }, { value: min, fromNumericBase: 'hex' }, ) const newValueLessThanMax = conversionLTE( - { value: newValue, fromNumericBase: 'dec' }, + { value: newValue || '0', fromNumericBase: 'dec' }, { value: max, fromNumericBase: 'hex' }, ) if (newValueGreaterThanMin && newValueLessThanMax) { @@ -44,11 +51,13 @@ InputNumber.prototype.render = function () { const { unitLabel, step = 1, placeholder, value = 0 } = this.props return h('div.customize-gas-input-wrapper', {}, [ - h('input.customize-gas-input', { - placeholder, - type: 'number', + h(CurrencyInput, { + className: 'customize-gas-input', value, - onChange: (e) => this.setValue(e.target.value), + placeholder, + onInputChange: newValue => { + this.setValue(newValue) + }, }), h('span.gas-tooltip-input-detail', {}, [unitLabel]), h('div.gas-tooltip-input-arrows', {}, [ @@ -57,7 +66,7 @@ InputNumber.prototype.render = function () { }), h('i.fa.fa-angle-down', { style: { cursor: 'pointer' }, - onClick: () => this.setValue(addCurrencies(value, toNegative(step))), + onClick: () => this.setValue(subtractCurrencies(value, step)), }), ]), ]) diff --git a/ui/app/components/loading.js b/ui/app/components/loading.js index 587212015..9442121fe 100644 --- a/ui/app/components/loading.js +++ b/ui/app/components/loading.js @@ -10,20 +10,7 @@ class LoadingIndicator extends Component { render () { return ( - h('.full-flex-height', { - style: { - left: '0px', - zIndex: 50, - position: 'absolute', - flexDirection: 'column', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - height: '100%', - width: '100%', - background: 'rgba(255, 255, 255, 0.8)', - }, - }, [ + h('.full-flex-height.loading-overlay', {}, [ h('img', { src: 'images/loading.svg', }), diff --git a/ui/app/components/modals/buy-options-modal.js b/ui/app/components/modals/buy-options-modal.js index 33615c483..d735983f9 100644 --- a/ui/app/components/modals/buy-options-modal.js +++ b/ui/app/components/modals/buy-options-modal.js @@ -3,6 +3,7 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../actions') +const networkNames = require('../../../../app/scripts/config.js').networkNames function mapStateToProps (state) { return { @@ -22,6 +23,7 @@ function mapDispatchToProps (dispatch) { showAccountDetailModal: () => { dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })) }, + toFaucet: network => dispatch(actions.buyEth({ network })), } } @@ -32,7 +34,20 @@ function BuyOptions () { module.exports = connect(mapStateToProps, mapDispatchToProps)(BuyOptions) +BuyOptions.prototype.renderModalContentOption = function (title, header, onClick) { + return h('div.buy-modal-content-option', { + onClick, + }, [ + h('div.buy-modal-content-option-title', {}, title), + h('div.buy-modal-content-option-subtitle', {}, header), + ]) +} + BuyOptions.prototype.render = function () { + const { network, toCoinbase, address, toFaucet } = this.props + const isTestNetwork = ['3', '4', '42'].find(n => n === network) + const networkName = networkNames[network] + return h('div', {}, [ h('div.buy-modal-content.transfers-subview', { }, [ @@ -47,27 +62,20 @@ BuyOptions.prototype.render = function () { h('div.buy-modal-content-options.flex-column.flex-center', {}, [ - h('div.buy-modal-content-option', { - onClick: () => { - const { toCoinbase, address } = this.props - toCoinbase(address) - }, - }, [ - h('div.buy-modal-content-option-title', {}, 'Coinbase'), - h('div.buy-modal-content-option-subtitle', {}, 'Deposit with Fiat'), - ]), + isTestNetwork + ? this.renderModalContentOption(networkName, 'Test Faucet', () => toFaucet(network)) + : this.renderModalContentOption('Coinbase', 'Deposit with Fiat', () => toCoinbase(address)), // h('div.buy-modal-content-option', {}, [ // h('div.buy-modal-content-option-title', {}, 'Shapeshift'), // h('div.buy-modal-content-option-subtitle', {}, 'Trade any digital asset for any other'), // ]), - h('div.buy-modal-content-option', { - onClick: () => this.goToAccountDetailsModal(), - }, [ - h('div.buy-modal-content-option-title', {}, 'Direct Deposit'), - h('div.buy-modal-content-option-subtitle', {}, 'Deposit from another account'), - ]), + this.renderModalContentOption( + 'Direct Deposit', + 'Deposit from another account', + () => this.goToAccountDetailsModal() + ), ]), diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index 842081f40..f2909f3c3 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -189,7 +189,7 @@ const MODALS = { } const BACKDROPSTYLE = { - backgroundColor: 'rgba(245, 245, 245, 0.85)', + backgroundColor: 'rgba(0, 0, 0, 0.5)', } function mapStateToProps (state) { diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index d12bc499b..1264da153 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -19,6 +19,7 @@ function mapStateToProps (state) { conversionRate, identities, currentCurrency, + send, } = state.metamask const accounts = state.metamask.accounts const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0] @@ -27,12 +28,32 @@ function mapStateToProps (state) { identities, selectedAddress, currentCurrency, + send, } } function mapDispatchToProps (dispatch) { return { - backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)), + clearSend: () => dispatch(actions.clearSend()), + editTransaction: txMeta => { + const { id, txParams } = txMeta + const { + gas: gasLimit, + gasPrice, + to, + value: amount, + } = txParams + dispatch(actions.updateSend({ + gasLimit, + gasPrice, + gasTotal: null, + to, + amount, + errors: { to: null, amount: null }, + editingTransactionId: id, + })) + dispatch(actions.showSendPage()) + }, cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })), } } @@ -157,7 +178,7 @@ ConfirmSendEther.prototype.getData = function () { } ConfirmSendEther.prototype.render = function () { - const { backToAccountDetail, selectedAddress, currentCurrency } = this.props + const { editTransaction, currentCurrency, clearSend } = this.props const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} @@ -199,8 +220,8 @@ ConfirmSendEther.prototype.render = function () { h('div.confirm-screen-wrapper.flex-column.flex-grow', [ h('h3.flex-center.confirm-screen-header', [ h('button.confirm-screen-back-button', { - onClick: () => backToAccountDetail(selectedAddress), - }, 'BACK'), + onClick: () => editTransaction(txMeta), + }, 'EDIT'), h('div.confirm-screen-title', 'Confirm Transaction'), h('div.confirm-screen-header-tip'), ]), @@ -371,7 +392,10 @@ ConfirmSendEther.prototype.render = function () { }, [ // Cancel Button h('div.cancel.btn-light.confirm-screen-cancel-button', { - onClick: (event) => this.cancel(event, txMeta), + onClick: (event) => { + clearSend() + this.cancel(event, txMeta) + }, }, 'CANCEL'), // Accept Button @@ -421,6 +445,26 @@ ConfirmSendEther.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) + if (props.send.editingTransactionId) { + const { + send: { + memo, + amount: value, + gasLimit: gas, + gasPrice, + }, + } = props + const { txParams: { from, to } } = txData + txData.txParams = { + from: ethUtil.addHexPrefix(from), + to: ethUtil.addHexPrefix(to), + memo: memo && ethUtil.addHexPrefix(memo), + value: ethUtil.addHexPrefix(value), + gas: ethUtil.addHexPrefix(gas), + gasPrice: ethUtil.addHexPrefix(gasPrice), + } + } + // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData } diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 3b8ae7f7f..cc2df8299 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -2,9 +2,10 @@ const Component = require('react').Component const { connect } = require('react-redux') const h = require('react-hyperscript') const inherits = require('util').inherits -const abi = require('human-standard-token-abi') +const ethAbi = require('ethereumjs-abi') +const tokenAbi = require('human-standard-token-abi') const abiDecoder = require('abi-decoder') -abiDecoder.addABI(abi) +abiDecoder.addABI(tokenAbi) const actions = require('../../actions') const clone = require('clone') const Identicon = require('../identicon') @@ -15,12 +16,16 @@ const { multiplyCurrencies, addCurrencies, } = require('../../conversion-util') +const { + calcTokenAmount, +} = require('../../token-util') const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') const { getTokenExchangeRate, getSelectedAddress, + getSelectedTokenContract, } = require('../../selectors') module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmSendToken) @@ -29,6 +34,7 @@ function mapStateToProps (state, ownProps) { const { token: { symbol }, txData } = ownProps const { txParams } = txData || {} const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data) + const { conversionRate, identities, @@ -44,6 +50,8 @@ function mapStateToProps (state, ownProps) { tokenExchangeRate, tokenData: tokenData || {}, currentCurrency: currentCurrency.toUpperCase(), + send: state.metamask.send, + tokenContract: getSelectedTokenContract(state), } } @@ -54,6 +62,33 @@ function mapDispatchToProps (dispatch, ownProps) { backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)), cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })), updateTokenExchangeRate: () => dispatch(actions.updateTokenExchangeRate(symbol)), + editTransaction: txMeta => { + const { token: { address } } = ownProps + const { txParams, id } = txMeta + const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data) + const { params = [] } = tokenData + const { value } = params[1] || {} + const amount = conversionUtil(value, { + fromNumericBase: 'dec', + toNumericBase: 'hex', + }) + const { + gas: gasLimit, + gasPrice, + to, + } = txParams + dispatch(actions.setSelectedToken(address)) + dispatch(actions.updateSend({ + gasLimit, + gasPrice, + gasTotal: null, + to, + amount, + errors: { to: null, amount: null }, + editingTransactionId: id, + })) + dispatch(actions.showSendTokenPage()) + }, } } @@ -65,16 +100,34 @@ function ConfirmSendToken () { } ConfirmSendToken.prototype.componentWillMount = function () { + const { tokenContract, selectedAddress } = this.props + tokenContract && tokenContract + .balanceOf(selectedAddress) + .then(usersToken => { + }) this.props.updateTokenExchangeRate() } ConfirmSendToken.prototype.getAmount = function () { - const { conversionRate, tokenExchangeRate, token, tokenData } = this.props + const { + conversionRate, + tokenExchangeRate, + token, + tokenData, + send: { amount, editingTransactionId }, + } = this.props const { params = [] } = tokenData - const { value } = params[1] || {} + let { value } = params[1] || {} const { decimals } = token - const multiplier = Math.pow(10, Number(decimals || 0)) - const sendTokenAmount = Number(value / multiplier) + + if (editingTransactionId) { + value = conversionUtil(amount, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + }) + } + + const sendTokenAmount = calcTokenAmount(value, decimals) return { fiat: tokenExchangeRate @@ -240,9 +293,8 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () { } ConfirmSendToken.prototype.render = function () { - const { backToAccountDetail, selectedAddress } = this.props + const { editTransaction } = this.props const txMeta = this.gatherTxMeta() - const { from: { address: fromAddress, @@ -264,8 +316,8 @@ ConfirmSendToken.prototype.render = function () { h('div.confirm-screen-wrapper.flex-column.flex-grow', [ h('h3.flex-center.confirm-screen-header', [ h('button.confirm-screen-back-button', { - onClick: () => backToAccountDetail(selectedAddress), - }, 'BACK'), + onClick: () => editTransaction(txMeta), + }, 'EDIT'), h('div.confirm-screen-title', 'Confirm Transaction'), h('div.confirm-screen-header-tip'), ]), @@ -387,6 +439,38 @@ ConfirmSendToken.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) + if (props.send.editingTransactionId) { + const { + send: { + memo, + amount, + gasLimit: gas, + gasPrice, + }, + } = props + + const { txParams: { from, to } } = txData + + const tokenParams = { + from: ethUtil.addHexPrefix(from), + value: '0', + gas: ethUtil.addHexPrefix(gas), + gasPrice: ethUtil.addHexPrefix(gasPrice), + } + + const data = '0xa9059cbb' + Array.prototype.map.call( + ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]), + x => ('00' + x.toString(16)).slice(-2) + ).join('') + + txData.txParams = { + ...tokenParams, + to: ethUtil.addHexPrefix(to), + memo: memo && ethUtil.addHexPrefix(memo), + data, + } + } + // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData } diff --git a/ui/app/components/send/account-list-item.js b/ui/app/components/send/account-list-item.js index 2378a4671..1ad3f69c1 100644 --- a/ui/app/components/send/account-list-item.js +++ b/ui/app/components/send/account-list-item.js @@ -22,6 +22,7 @@ module.exports = connect(mapStateToProps)(AccountListItem) AccountListItem.prototype.render = function () { const { + className, account, handleClick, icon = null, @@ -34,6 +35,7 @@ AccountListItem.prototype.render = function () { const { name, address, balance } = account || {} return h('div.account-list-item', { + className, onClick: () => handleClick({ name, address, balance }), }, [ diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index 45986e371..819fee0a0 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -1,6 +1,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const CurrencyInput = require('../currency-input') const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') module.exports = CurrencyDisplay @@ -8,15 +9,6 @@ module.exports = CurrencyDisplay inherits(CurrencyDisplay, Component) function CurrencyDisplay () { Component.call(this) - - this.state = { - value: null, - } -} - -function isValidInput (text) { - const re = /^([1-9]\d*|0)(\.|\.\d*)?$/ - return re.test(text) } function toHexWei (value) { @@ -39,6 +31,28 @@ CurrencyDisplay.prototype.getAmount = function (value) { : toHexWei(value) } +CurrencyDisplay.prototype.getValueToRender = function () { + const { selectedToken, conversionRate, value } = this.props + + const { decimals, symbol } = selectedToken || {} + const multiplier = Math.pow(10, Number(decimals || 0)) + + return selectedToken + ? conversionUtil(value, { + fromNumericBase: 'hex', + toCurrency: symbol, + conversionRate: multiplier, + invertConversionRate: true, + }) + : conversionUtil(value, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + fromDenomination: 'WEI', + numberOfDecimals: 6, + conversionRate, + }) +} + CurrencyDisplay.prototype.render = function () { const { className = 'currency-display', @@ -49,64 +63,41 @@ CurrencyDisplay.prototype.render = function () { convertedCurrency, readOnly = false, inError = false, - value: initValue, handleChange, - validate, } = this.props - const { value } = this.state - const initValueToRender = conversionUtil(initValue, { - fromNumericBase: 'hex', - toNumericBase: 'dec', - fromDenomination: 'WEI', - numberOfDecimals: 6, - conversionRate, - }) + const valueToRender = this.getValueToRender() - const convertedValue = conversionUtil(value || initValueToRender, { + let convertedValue = conversionUtil(valueToRender, { fromNumericBase: 'dec', fromCurrency: primaryCurrency, toCurrency: convertedCurrency, numberOfDecimals: 2, conversionRate, }) - - const inputSizeMultiplier = readOnly ? 1 : 1.2 + convertedValue = Number(convertedValue).toFixed(2) return h('div', { className, style: { borderColor: inError ? 'red' : null, }, + onClick: () => this.currencyInput.focus(), }, [ h('div.currency-display__primary-row', [ h('div.currency-display__input-wrapper', [ - h('input', { + h(CurrencyInput, { className: primaryBalanceClassName, - value: `${value || initValueToRender}`, + value: `${valueToRender}`, placeholder: '0', - size: (value || initValueToRender).length * inputSizeMultiplier, readOnly, - onChange: (event) => { - let newValue = event.target.value - - if (newValue === '') { - newValue = '0' - } else if (newValue.match(/^0[1-9]$/)) { - newValue = newValue.match(/[1-9]/)[0] - } - - if (newValue && !isValidInput(newValue)) { - event.preventDefault() - } else { - validate(this.getAmount(newValue)) - this.setState({ value: newValue }) - } + onInputChange: newValue => { + handleChange(this.getAmount(newValue)) }, - onBlur: event => !readOnly && handleChange(this.getAmount(event.target.value)), + inputRef: input => { this.currencyInput = input }, }), h('span.currency-display__currency-symbol', primaryCurrency), diff --git a/ui/app/components/send/from-dropdown.js b/ui/app/components/send/from-dropdown.js index bcae5ede8..0686fbe73 100644 --- a/ui/app/components/send/from-dropdown.js +++ b/ui/app/components/send/from-dropdown.js @@ -35,6 +35,7 @@ FromDropdown.prototype.renderDropdown = function () { h('div.send-v2__from-dropdown__list', {}, [ ...accounts.map(account => h(AccountListItem, { + className: 'account-list-item__dropdown', account, handleClick: () => { onSelect(account) diff --git a/ui/app/components/send/send-constants.js b/ui/app/components/send/send-constants.js index 0a4f85bc9..a961ffcd8 100644 --- a/ui/app/components/send/send-constants.js +++ b/ui/app/components/send/send-constants.js @@ -11,6 +11,7 @@ const MIN_GAS_PRICE_GWEI = ethUtil.addHexPrefix(conversionUtil(MIN_GAS_PRICE_HEX toDenomination: 'GWEI', fromNumericBase: 'hex', toNumericBase: 'hex', + numberOfDecimals: 1, })) const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, { diff --git a/ui/app/components/send/send-utils.js b/ui/app/components/send/send-utils.js index 6ec04a223..d8211930d 100644 --- a/ui/app/components/send/send-utils.js +++ b/ui/app/components/send/send-utils.js @@ -1,11 +1,17 @@ -const { addCurrencies, conversionGreaterThan } = require('../../conversion-util') +const { + addCurrencies, + conversionUtil, + conversionGTE, +} = require('../../conversion-util') +const { + calcTokenAmount, +} = require('../../token-util') function isBalanceSufficient ({ - amount, - gasTotal, + amount = '0x0', + gasTotal = '0x0', balance, primaryCurrency, - selectedToken, amountConversionRate, conversionRate, }) { @@ -15,7 +21,7 @@ function isBalanceSufficient ({ toNumericBase: 'hex', }) - const balanceIsSufficient = conversionGreaterThan( + const balanceIsSufficient = conversionGTE( { value: balance, fromNumericBase: 'hex', @@ -26,13 +32,37 @@ function isBalanceSufficient ({ value: totalAmount, fromNumericBase: 'hex', conversionRate: amountConversionRate, - fromCurrency: selectedToken || primaryCurrency, + fromCurrency: primaryCurrency, }, ) return balanceIsSufficient } +function isTokenBalanceSufficient ({ + amount = '0x0', + tokenBalance, + decimals, +}) { + const amountInDec = conversionUtil(amount, { + fromNumericBase: 'hex', + }) + + const tokenBalanceIsSufficient = conversionGTE( + { + value: tokenBalance, + fromNumericBase: 'dec', + }, + { + value: calcTokenAmount(amountInDec, decimals), + fromNumericBase: 'dec', + }, + ) + + return tokenBalanceIsSufficient +} + module.exports = { isBalanceSufficient, + isTokenBalanceSufficient, } diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js index 5a6e83ae6..4451a6113 100644 --- a/ui/app/components/send/send-v2-container.js +++ b/ui/app/components/send/send-v2-container.js @@ -13,6 +13,7 @@ const { getSendFrom, getCurrentCurrency, getSelectedTokenToFiatRate, + getSelectedTokenContract, } = require('../../selectors') module.exports = connect(mapStateToProps, mapDispatchToProps)(SendEther) @@ -48,6 +49,7 @@ function mapStateToProps (state) { convertedCurrency: getCurrentCurrency(state), data, amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate, + tokenContract: getSelectedTokenContract(state), } } @@ -61,9 +63,13 @@ function mapDispatchToProps (dispatch) { dispatch(actions.signTokenTx(tokenAddress, toAddress, amount, txData)) ), signTx: txParams => dispatch(actions.signTx(txParams)), + updateAndApproveTx: txParams => dispatch(actions.updateAndApproveTx(txParams)), setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)), addToAddressBook: address => dispatch(actions.addToAddressBook(address)), updateGasTotal: newTotal => dispatch(actions.updateGasTotal(newTotal)), + updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)), + updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)), + updateSendTokenBalance: tokenBalance => dispatch(actions.updateSendTokenBalance(tokenBalance)), updateSendFrom: newFrom => dispatch(actions.updateSendFrom(newFrom)), updateSendTo: newTo => dispatch(actions.updateSendTo(newTo)), updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)), @@ -71,5 +77,6 @@ function mapDispatchToProps (dispatch) { updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)), goHome: () => dispatch(actions.goHome()), clearSend: () => dispatch(actions.clearSend()), + backToConfirmScreen: editingTransactionId => dispatch(actions.showConfTxPage({ id: editingTransactionId })), } } diff --git a/ui/app/components/send/to-autocomplete.js b/ui/app/components/send/to-autocomplete.js index df43fcc4a..e0cdd0a58 100644 --- a/ui/app/components/send/to-autocomplete.js +++ b/ui/app/components/send/to-autocomplete.js @@ -38,6 +38,7 @@ ToAutoComplete.prototype.renderDropdown = function () { ...accountsToRender.map(account => h(AccountListItem, { account, + className: 'account-list-item__dropdown', handleClick: () => { onChange(account.address) closeDropdown() @@ -88,7 +89,7 @@ ToAutoComplete.prototype.render = function () { inError, } = this.props - return h('div.to-autocomplete', {}, [ + return h('div.send-v2__to-autocomplete', {}, [ h('input.send-v2__to-autocomplete__input', { placeholder: 'Recipient Address', diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index b6a27fd5a..8e06e0f27 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -151,10 +151,7 @@ TokenList.prototype.componentDidUpdate = function (nextProps) { } TokenList.prototype.updateBalances = function (tokens) { - const heldTokens = tokens.filter(token => { - return token.balance !== '0' && token.string !== '0.000' - }) - this.setState({ tokens: heldTokens, isLoading: false }) + this.setState({ tokens, isLoading: false }) } TokenList.prototype.componentWillUnmount = function () { diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index 26de19f15..4e76147a1 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -10,6 +10,7 @@ const Identicon = require('./identicon') const contractMap = require('eth-contract-metadata') const { conversionUtil, multiplyCurrencies } = require('../conversion-util') +const { calcTokenAmount } = require('../token-util') const { getCurrentCurrency } = require('../selectors') @@ -135,8 +136,7 @@ TxListItem.prototype.getSendTokenTotal = async function () { const { params = [] } = decodedData || {} const { value } = params[1] || {} const { decimals, symbol } = await this.getTokenInfo() - const multiplier = Math.pow(10, Number(decimals || 0)) - const total = Number(value / multiplier) + const total = calcTokenAmount(value, decimals) const pair = symbol && `${symbol.toLowerCase()}_eth` |