aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app
diff options
context:
space:
mode:
authorThomas <thomas.b.huang@gmail.com>2018-07-11 02:34:00 +0800
committerThomas <thomas.b.huang@gmail.com>2018-07-11 02:34:00 +0800
commit5188b727d43e91087449818dee69942c382b6d89 (patch)
tree4a89be50f09d24bc7e6ed3daf83b660f5e87380e /ui/app
parentdd18684a5d682f48528f278284619983f13bf82f (diff)
parentf6de948e42ae633d40aef72595a01caa622a280d (diff)
downloadtangerine-wallet-browser-5188b727d43e91087449818dee69942c382b6d89.tar.gz
tangerine-wallet-browser-5188b727d43e91087449818dee69942c382b6d89.tar.zst
tangerine-wallet-browser-5188b727d43e91087449818dee69942c382b6d89.zip
Merge branch 'develop' into removeNonceTrackerFunction
Diffstat (limited to 'ui/app')
-rw-r--r--ui/app/actions.js53
-rw-r--r--ui/app/app.js2
-rw-r--r--ui/app/components/customize-gas-modal/index.js55
-rw-r--r--ui/app/components/dropdowns/token-menu-dropdown.js6
-rw-r--r--ui/app/components/ens-input.js77
-rw-r--r--ui/app/components/input-number.js12
-rw-r--r--ui/app/components/pages/create-account/import-account/json.js3
-rw-r--r--ui/app/components/pages/create-account/import-account/private-key.js3
-rw-r--r--ui/app/components/pages/create-account/index.js2
-rw-r--r--ui/app/components/pages/keychains/restore-vault.js313
-rw-r--r--ui/app/components/pending-tx/confirm-send-ether.js2
-rw-r--r--ui/app/components/pending-tx/confirm-send-token.js2
-rw-r--r--ui/app/components/send/currency-display.js9
-rw-r--r--ui/app/components/send/gas-fee-display-v2.js53
-rw-r--r--ui/app/components/send_/account-list-item/account-list-item.container.js4
-rw-r--r--ui/app/components/send_/account-list-item/index.js2
-rw-r--r--ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js2
-rw-r--r--ui/app/components/send_/index.js2
-rw-r--r--ui/app/components/send_/send-content/index.js2
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/index.js2
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/index.js2
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js16
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js9
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js29
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js20
-rw-r--r--ui/app/components/send_/send-content/send-dropdown-list/index.js2
-rw-r--r--ui/app/components/send_/send-content/send-from-row/from-dropdown/index.js2
-rw-r--r--ui/app/components/send_/send-content/send-from-row/index.js2
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js61
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/gas-fee-display/index.js1
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js55
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/index.js2
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js10
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js9
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js9
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js7
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js8
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js33
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/index.js2
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/index.js2
-rw-r--r--ui/app/components/send_/send-content/send-to-row/index.js2
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row.component.js6
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js6
-rw-r--r--ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js12
-rw-r--r--ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js6
-rw-r--r--ui/app/components/send_/send-footer/index.js2
-rw-r--r--ui/app/components/send_/send-header/index.js2
-rw-r--r--ui/app/components/send_/send.component.js19
-rw-r--r--ui/app/components/send_/send.constants.js2
-rw-r--r--ui/app/components/send_/send.container.js2
-rw-r--r--ui/app/components/send_/send.selectors.js5
-rw-r--r--ui/app/components/send_/send.utils.js41
-rw-r--r--ui/app/components/send_/send.utils.test.js30
-rw-r--r--ui/app/components/send_/tests/send-component.test.js75
-rw-r--r--ui/app/components/send_/tests/send-container.test.js12
-rw-r--r--ui/app/components/send_/tests/send-selectors.test.js10
-rw-r--r--ui/app/components/send_/tests/send-utils.test.js52
-rw-r--r--ui/app/components/shapeshift-form.js2
-rw-r--r--ui/app/components/signature-request.js3
-rw-r--r--ui/app/components/token-balance.js6
-rw-r--r--ui/app/components/token-list.js5
-rw-r--r--ui/app/components/tx-list-item.js9
-rw-r--r--ui/app/conversion-util.js2
-rw-r--r--ui/app/conversion-util.test.js22
-rw-r--r--ui/app/css/itcss/components/currency-display.scss9
-rw-r--r--ui/app/css/itcss/components/modal.scss31
-rw-r--r--ui/app/css/itcss/components/newui-sections.scss9
-rw-r--r--ui/app/css/itcss/components/request-signature.scss1
-rw-r--r--ui/app/css/itcss/components/token-list.scss1
-rw-r--r--ui/app/ducks/send.duck.js7
-rw-r--r--ui/app/ducks/tests/send-duck.test.js10
-rw-r--r--ui/app/helpers/with-token-tracker.js3
-rw-r--r--ui/app/reducers/app.js11
-rw-r--r--ui/app/selectors.js11
-rw-r--r--ui/app/token-util.js2
-rw-r--r--ui/app/util.js5
76 files changed, 961 insertions, 359 deletions
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 1edf692b6..ad890f565 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -6,7 +6,6 @@ const {
calcGasTotal,
calcTokenBalance,
estimateGas,
- estimateGasPriceFromRecentBlocks,
} = require('./components/send_/send.utils')
const ethUtil = require('ethereumjs-util')
const { fetchLocale } = require('../i18n-helper')
@@ -175,6 +174,8 @@ var actions = {
CLEAR_SEND: 'CLEAR_SEND',
OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN',
CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN',
+ GAS_LOADING_STARTED: 'GAS_LOADING_STARTED',
+ GAS_LOADING_FINISHED: 'GAS_LOADING_FINISHED',
setGasLimit,
setGasPrice,
updateGasData,
@@ -190,6 +191,8 @@ var actions = {
updateSendErrors,
clearSend,
setSelectedAddress,
+ gasLoadingStarted,
+ gasLoadingFinished,
// app messages
confirmSeedWords: confirmSeedWords,
showAccountDetail: showAccountDetail,
@@ -740,20 +743,28 @@ function updateGasData ({
to,
value,
}) {
- const estimatedGasPrice = estimateGasPriceFromRecentBlocks(recentBlocks)
return (dispatch) => {
- return Promise.all([
- Promise.resolve(estimatedGasPrice),
- estimateGas({
- estimateGasMethod: background.estimateGas,
- blockGasLimit,
- selectedAddress,
- selectedToken,
- to,
- value,
- gasPrice: estimatedGasPrice,
- }),
- ])
+ dispatch(actions.gasLoadingStarted())
+ return new Promise((resolve, reject) => {
+ background.getGasPrice((err, data) => {
+ if (err) return reject(err)
+ return resolve(data)
+ })
+ })
+ .then(estimateGasPrice => {
+ return Promise.all([
+ Promise.resolve(estimateGasPrice),
+ estimateGas({
+ estimateGasMethod: background.estimateGas,
+ blockGasLimit,
+ selectedAddress,
+ selectedToken,
+ to,
+ value,
+ estimateGasPrice,
+ }),
+ ])
+ })
.then(([gasPrice, gas]) => {
dispatch(actions.setGasPrice(gasPrice))
dispatch(actions.setGasLimit(gas))
@@ -762,14 +773,28 @@ function updateGasData ({
.then((gasEstimate) => {
dispatch(actions.setGasTotal(gasEstimate))
dispatch(updateSendErrors({ gasLoadingError: null }))
+ dispatch(actions.gasLoadingFinished())
})
.catch(err => {
log.error(err)
dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' }))
+ dispatch(actions.gasLoadingFinished())
})
}
}
+function gasLoadingStarted () {
+ return {
+ type: actions.GAS_LOADING_STARTED,
+ }
+}
+
+function gasLoadingFinished () {
+ return {
+ type: actions.GAS_LOADING_FINISHED,
+ }
+}
+
function updateSendTokenBalance ({
selectedToken,
tokenContract,
diff --git a/ui/app/app.js b/ui/app/app.js
index d0e48a368..670b7e2d0 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -23,7 +23,7 @@ const Authenticated = require('./components/pages/authenticated')
const Initialized = require('./components/pages/initialized')
const Settings = require('./components/pages/settings')
const UnlockPage = require('./components/pages/unlock-page')
-const RestoreVaultPage = require('./components/pages/keychains/restore-vault')
+const RestoreVaultPage = require('./components/pages/keychains/restore-vault').default
const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed')
const AddTokenPage = require('./components/pages/add-token')
const ConfirmAddTokenPage = require('./components/pages/confirm-add-token')
diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js
index c8522a3c7..cefa428b9 100644
--- a/ui/app/components/customize-gas-modal/index.js
+++ b/ui/app/components/customize-gas-modal/index.js
@@ -31,8 +31,7 @@ const {
} = require('../../conversion-util')
const {
- getGasPrice,
- getGasLimit,
+ getGasIsLoading,
getForceGasMin,
conversionRateSelector,
getSendAmount,
@@ -43,6 +42,11 @@ const {
getSendMaxModeState,
} = require('../../selectors')
+const {
+ getGasPrice,
+ getGasLimit,
+} = require('../send_/send.selectors')
+
function mapStateToProps (state) {
const selectedToken = getSelectedToken(state)
const currentAccount = getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state)
@@ -51,6 +55,7 @@ function mapStateToProps (state) {
return {
gasPrice: getGasPrice(state),
gasLimit: getGasLimit(state),
+ gasIsLoading: getGasIsLoading(state),
forceGasMin: getForceGasMin(state),
conversionRate,
amount: getSendAmount(state),
@@ -73,7 +78,7 @@ function mapDispatchToProps (dispatch) {
}
}
-function getOriginalState (props) {
+function getFreshState (props) {
const gasPrice = props.gasPrice || MIN_GAS_PRICE_DEC
const gasLimit = props.gasLimit || MIN_GAS_LIMIT_DEC
@@ -97,7 +102,11 @@ inherits(CustomizeGasModal, Component)
function CustomizeGasModal (props) {
Component.call(this)
- this.state = getOriginalState(props)
+ const originalState = getFreshState(props)
+ this.state = {
+ ...originalState,
+ originalState,
+ }
}
CustomizeGasModal.contextTypes = {
@@ -106,6 +115,36 @@ CustomizeGasModal.contextTypes = {
module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal)
+CustomizeGasModal.prototype.componentWillReceiveProps = function (nextProps) {
+ const currentState = getFreshState(this.props)
+ const {
+ gasPrice: currentGasPrice,
+ gasLimit: currentGasLimit,
+ } = currentState
+ const newState = getFreshState(nextProps)
+ const {
+ gasPrice: newGasPrice,
+ gasLimit: newGasLimit,
+ gasTotal: newGasTotal,
+ } = newState
+ const gasPriceChanged = currentGasPrice !== newGasPrice
+ const gasLimitChanged = currentGasLimit !== newGasLimit
+
+ if (gasPriceChanged) {
+ this.setState({
+ gasPrice: newGasPrice,
+ gasTotal: newGasTotal,
+ priceSigZeros: '',
+ priceSigDec: '',
+ })
+ }
+ if (gasLimitChanged) {
+ this.setState({ gasLimit: newGasLimit, gasTotal: newGasTotal })
+ }
+ if (gasLimitChanged || gasPriceChanged) {
+ this.validate({ gasLimit: newGasLimit, gasTotal: newGasTotal })
+ }
+}
CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
const {
@@ -137,7 +176,7 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
}
CustomizeGasModal.prototype.revert = function () {
- this.setState(getOriginalState(this.props))
+ this.setState(this.state.originalState)
}
CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) {
@@ -233,7 +272,7 @@ CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) {
}
CustomizeGasModal.prototype.render = function () {
- const { hideModal, forceGasMin } = this.props
+ const { hideModal, forceGasMin, gasIsLoading } = this.props
const { gasPrice, gasLimit, gasTotal, error, priceSigZeros, priceSigDec } = this.state
let convertedGasPrice = conversionUtil(gasPrice, {
@@ -266,7 +305,7 @@ CustomizeGasModal.prototype.render = function () {
toNumericBase: 'dec',
})
- return h('div.send-v2__customize-gas', {}, [
+ return !gasIsLoading && h('div.send-v2__customize-gas', {}, [
h('div.send-v2__customize-gas__content', {
}, [
h('div.send-v2__customize-gas__header', {}, [
@@ -288,6 +327,7 @@ CustomizeGasModal.prototype.render = function () {
onChange: value => this.convertAndSetGasPrice(value),
title: this.context.t('gasPrice'),
copy: this.context.t('gasPriceCalculation'),
+ gasIsLoading,
}),
h(GasModalCard, {
@@ -297,6 +337,7 @@ CustomizeGasModal.prototype.render = function () {
onChange: value => this.convertAndSetGasLimit(value),
title: this.context.t('gasLimit'),
copy: this.context.t('gasLimitCalculation'),
+ gasIsLoading,
}),
]),
diff --git a/ui/app/components/dropdowns/token-menu-dropdown.js b/ui/app/components/dropdowns/token-menu-dropdown.js
index fac7c451b..5a794c7c1 100644
--- a/ui/app/components/dropdowns/token-menu-dropdown.js
+++ b/ui/app/components/dropdowns/token-menu-dropdown.js
@@ -54,7 +54,7 @@ TokenMenuDropdown.prototype.render = function () {
showHideTokenConfirmationModal(this.props.token)
this.props.onClose()
},
- text: this.context.t('hideToken'),
+ text: this.context.t('hideToken'),
}),
h(Item, {
onClick: (e) => {
@@ -62,7 +62,7 @@ TokenMenuDropdown.prototype.render = function () {
copyToClipboard(this.props.token.address)
this.props.onClose()
},
- text: this.context.t('copyContractAddress'),
+ text: this.context.t('copyContractAddress'),
}),
h(Item, {
onClick: (e) => {
@@ -71,7 +71,7 @@ TokenMenuDropdown.prototype.render = function () {
global.platform.openWindow({ url })
this.props.onClose()
},
- text: this.context.t('viewOnEtherscan'),
+ text: this.context.t('viewOnEtherscan'),
}),
])
}
diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js
index aff4b6ef6..292dcdde6 100644
--- a/ui/app/components/ens-input.js
+++ b/ui/app/components/ens-input.js
@@ -12,6 +12,7 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const connect = require('react-redux').connect
const ToAutoComplete = require('./send/to-autocomplete')
const log = require('loglevel')
+const { isValidENSAddress } = require('../util')
EnsInput.contextTypes = {
t: PropTypes.func,
@@ -25,31 +26,34 @@ function EnsInput () {
Component.call(this)
}
-EnsInput.prototype.render = function () {
- const props = this.props
- const opts = extend(props, {
- list: 'addresses',
- onChange: (recipient) => {
- const network = this.props.network
- const networkHasEnsSupport = getNetworkEnsSupport(network)
+EnsInput.prototype.onChange = function (recipient) {
+ const network = this.props.network
+ const networkHasEnsSupport = getNetworkEnsSupport(network)
- props.onChange(recipient)
+ this.props.onChange({ toAddress: recipient })
- if (!networkHasEnsSupport) return
+ if (!networkHasEnsSupport) return
- if (recipient.match(ensRE) === null) {
- return this.setState({
- loadingEns: false,
- ensResolution: null,
- ensFailure: null,
- })
- }
+ if (recipient.match(ensRE) === null) {
+ return this.setState({
+ loadingEns: false,
+ ensResolution: null,
+ ensFailure: null,
+ toError: null,
+ })
+ }
- this.setState({
- loadingEns: true,
- })
- this.checkName(recipient)
- },
+ this.setState({
+ loadingEns: true,
+ })
+ this.checkName(recipient)
+}
+
+EnsInput.prototype.render = function () {
+ const props = this.props
+ const opts = extend(props, {
+ list: 'addresses',
+ onChange: this.onChange.bind(this),
})
return h('div', {
style: { width: '100%', position: 'relative' },
@@ -85,17 +89,27 @@ EnsInput.prototype.lookupEnsName = function (recipient) {
nickname: recipient.trim(),
hoverText: address + '\n' + this.context.t('clickCopy'),
ensFailure: false,
+ toError: null,
})
}
})
.catch((reason) => {
- log.error(reason)
- return this.setState({
+ const setStateObj = {
loadingEns: false,
- ensResolution: ZERO_ADDRESS,
+ ensResolution: recipient,
ensFailure: true,
- hoverText: reason.message,
- })
+ toError: null,
+ }
+ if (isValidENSAddress(recipient) && reason.message === 'ENS name not defined.') {
+ setStateObj.hoverText = this.context.t('ensNameNotFound')
+ setStateObj.toError = 'ensNameNotFound'
+ setStateObj.ensFailure = false
+ } else {
+ log.error(reason)
+ setStateObj.hoverText = reason.message
+ }
+
+ return this.setState(setStateObj)
})
}
@@ -105,9 +119,14 @@ EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) {
// If an address is sent without a nickname, meaning not from ENS or from
// the user's own accounts, a default of a one-space string is used.
const nickname = state.nickname || ' '
+ if (prevProps.network !== this.props.network) {
+ const provider = global.ethereumProvider
+ this.ens = new ENS({ provider, network: this.props.network })
+ this.onChange(ensResolution)
+ }
if (prevState && ensResolution && this.props.onChange &&
ensResolution !== prevState.ensResolution) {
- this.props.onChange(ensResolution, nickname)
+ this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError })
}
}
@@ -124,7 +143,9 @@ EnsInput.prototype.ensIcon = function (recipient) {
}
EnsInput.prototype.ensIconContents = function (recipient) {
- const { loadingEns, ensFailure, ensResolution } = this.state || { ensResolution: ZERO_ADDRESS}
+ const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS }
+
+ if (toError) return
if (loadingEns) {
return h('img', {
diff --git a/ui/app/components/input-number.js b/ui/app/components/input-number.js
index de5fcca54..59c6842ef 100644
--- a/ui/app/components/input-number.js
+++ b/ui/app/components/input-number.js
@@ -22,12 +22,16 @@ function isValidInput (text) {
return re.test(text)
}
+function removeLeadingZeroes (str) {
+ return str.replace(/^0*(?=\d)/, '')
+}
+
InputNumber.prototype.setValue = function (newValue) {
+ newValue = removeLeadingZeroes(newValue)
if (newValue && !isValidInput(newValue)) return
const { fixed, min = -1, max = Infinity, onChange } = this.props
newValue = fixed ? newValue.toFixed(4) : newValue
-
const newValueGreaterThanMin = conversionGTE(
{ value: newValue || '0', fromNumericBase: 'dec' },
{ value: min, fromNumericBase: 'hex' },
@@ -47,7 +51,7 @@ InputNumber.prototype.setValue = function (newValue) {
}
InputNumber.prototype.render = function () {
- const { unitLabel, step = 1, placeholder, value = 0 } = this.props
+ const { unitLabel, step = 1, placeholder, value } = this.props
return h('div.customize-gas-input-wrapper', {}, [
h('input', {
@@ -63,11 +67,11 @@ InputNumber.prototype.render = function () {
h('span.gas-tooltip-input-detail', {}, [unitLabel]),
h('div.gas-tooltip-input-arrows', {}, [
h('i.fa.fa-angle-up', {
- onClick: () => this.setValue(addCurrencies(value, step)),
+ onClick: () => this.setValue(addCurrencies(value, step, { toNumericBase: 'dec' })),
}),
h('i.fa.fa-angle-down', {
style: { cursor: 'pointer' },
- onClick: () => this.setValue(subtractCurrencies(value, step)),
+ onClick: () => this.setValue(subtractCurrencies(value, step, { toNumericBase: 'dec' })),
}),
]),
])
diff --git a/ui/app/components/pages/create-account/import-account/json.js b/ui/app/components/pages/create-account/import-account/json.js
index 1dc2ba534..dd57256a3 100644
--- a/ui/app/components/pages/create-account/import-account/json.js
+++ b/ui/app/components/pages/create-account/import-account/json.js
@@ -109,12 +109,13 @@ class JsonImportSubview extends Component {
.then(({ selectedAddress }) => {
if (selectedAddress) {
history.push(DEFAULT_ROUTE)
+ displayWarning(null)
} else {
displayWarning('Error importing account.')
setSelectedAddress(firstAddress)
}
})
- .catch(err => displayWarning(err))
+ .catch(err => err && displayWarning(err.message || err))
}
}
diff --git a/ui/app/components/pages/create-account/import-account/private-key.js b/ui/app/components/pages/create-account/import-account/private-key.js
index 5df3777da..1db999f2f 100644
--- a/ui/app/components/pages/create-account/import-account/private-key.js
+++ b/ui/app/components/pages/create-account/import-account/private-key.js
@@ -99,10 +99,11 @@ PrivateKeyImportView.prototype.createNewKeychain = function () {
.then(({ selectedAddress }) => {
if (selectedAddress) {
history.push(DEFAULT_ROUTE)
+ displayWarning(null)
} else {
displayWarning('Error importing account.')
setSelectedAddress(firstAddress)
}
})
- .catch(err => displayWarning(err))
+ .catch(err => err && displayWarning(err.message || err))
}
diff --git a/ui/app/components/pages/create-account/index.js b/ui/app/components/pages/create-account/index.js
index 6e3b93742..5681e43a9 100644
--- a/ui/app/components/pages/create-account/index.js
+++ b/ui/app/components/pages/create-account/index.js
@@ -42,7 +42,7 @@ class CreateAccountPage extends Component {
render () {
return h('div.new-account', {}, [
h('div.new-account__header', [
- h('div.new-account__title', this.context.t('newAccount') ),
+ h('div.new-account__title', this.context.t('newAccount')),
this.renderTabs(),
]),
h('div.new-account__form', [
diff --git a/ui/app/components/pages/keychains/restore-vault.js b/ui/app/components/pages/keychains/restore-vault.js
index 33575bfbb..d90a33e49 100644
--- a/ui/app/components/pages/keychains/restore-vault.js
+++ b/ui/app/components/pages/keychains/restore-vault.js
@@ -1,178 +1,189 @@
-const { withRouter } = require('react-router-dom')
-const PropTypes = require('prop-types')
-const { compose } = require('recompose')
-const PersistentForm = require('../../../../lib/persistent-form')
-const connect = require('../../../metamask-connect')
-const h = require('react-hyperscript')
-const { createNewVaultAndRestore, unMarkPasswordForgotten } = require('../../../actions')
-const { DEFAULT_ROUTE } = require('../../../routes')
-const log = require('loglevel')
-
-class RestoreVaultPage extends PersistentForm {
- constructor (props) {
- super(props)
-
- this.state = {
- error: null,
- }
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import {connect} from 'react-redux'
+import {
+ createNewVaultAndRestore,
+ unMarkPasswordForgotten,
+} from '../../../actions'
+import { DEFAULT_ROUTE } from '../../../routes'
+import TextField from '../../text-field'
+
+class RestoreVaultPage extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
}
- createOnEnter (event) {
- if (event.key === 'Enter') {
- this.createNewVaultAndRestore()
- }
+ static propTypes = {
+ warning: PropTypes.string,
+ createNewVaultAndRestore: PropTypes.func.isRequired,
+ leaveImportSeedScreenState: PropTypes.func,
+ history: PropTypes.object,
+ isLoading: PropTypes.bool,
+ };
+
+ state = {
+ seedPhrase: '',
+ password: '',
+ confirmPassword: '',
+ seedPhraseError: null,
+ passwordError: null,
+ confirmPasswordError: null,
}
- cancel () {
- this.props.unMarkPasswordForgotten()
- .then(this.props.history.push(DEFAULT_ROUTE))
+ parseSeedPhrase = (seedPhrase) => {
+ return seedPhrase
+ .match(/\w+/g)
+ .join(' ')
}
- createNewVaultAndRestore () {
- this.setState({ error: null })
+ handleSeedPhraseChange (seedPhrase) {
+ let seedPhraseError = null
+
+ if (seedPhrase && this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
+ seedPhraseError = this.context.t('seedPhraseReq')
+ }
+
+ this.setState({ seedPhrase, seedPhraseError })
+ }
- // check password
- var passwordBox = document.getElementById('password-box')
- var password = passwordBox.value
- var passwordConfirmBox = document.getElementById('password-box-confirm')
- var passwordConfirm = passwordConfirmBox.value
+ handlePasswordChange (password) {
+ const { confirmPassword } = this.state
+ let confirmPasswordError = null
+ let passwordError = null
- if (password.length < 8) {
- this.setState({ error: 'Password not long enough' })
- return
+ if (password && password.length < 8) {
+ passwordError = this.context.t('passwordNotLongEnough')
}
- if (password !== passwordConfirm) {
- this.setState({ error: 'Passwords don\'t match' })
- return
+ if (confirmPassword && password !== confirmPassword) {
+ confirmPasswordError = this.context.t('passwordsDontMatch')
}
- // check seed
- var seedBox = document.querySelector('textarea.twelve-word-phrase')
- var seed = seedBox.value.trim()
- if (seed.split(' ').length !== 12) {
- this.setState({ error: 'Seed phrases are 12 words long' })
- return
+ this.setState({ password, passwordError, confirmPasswordError })
+ }
+
+ handleConfirmPasswordChange (confirmPassword) {
+ const { password } = this.state
+ let confirmPasswordError = null
+
+ if (password !== confirmPassword) {
+ confirmPasswordError = this.context.t('passwordsDontMatch')
}
- // submit
- this.props.createNewVaultAndRestore(password, seed)
- .then(() => this.props.history.push(DEFAULT_ROUTE))
- .catch(({ message }) => {
- this.setState({ error: message })
- log.error(message)
- })
+ this.setState({ confirmPassword, confirmPasswordError })
+ }
+
+ onClick = () => {
+ const { password, seedPhrase } = this.state
+ const {
+ createNewVaultAndRestore,
+ leaveImportSeedScreenState,
+ history,
+ } = this.props
+
+ leaveImportSeedScreenState()
+ createNewVaultAndRestore(password, this.parseSeedPhrase(seedPhrase))
+ .then(() => history.push(DEFAULT_ROUTE))
+ }
+
+ hasError () {
+ const { passwordError, confirmPasswordError, seedPhraseError } = this.state
+ return passwordError || confirmPasswordError || seedPhraseError
}
render () {
- const { error } = this.state
- this.persistentFormParentId = 'restore-vault-form'
+ const {
+ seedPhrase,
+ password,
+ confirmPassword,
+ seedPhraseError,
+ passwordError,
+ confirmPasswordError,
+ } = this.state
+ const { t } = this.context
+ const { isLoading } = this.props
+ const disabled = !seedPhrase || !password || !confirmPassword || isLoading || this.hasError()
return (
- h('.initialize-screen.flex-column.flex-center.flex-grow', [
-
- h('h3.flex-center.text-transform-uppercase', {
- style: {
- background: '#EBEBEB',
- color: '#AEAEAE',
- marginBottom: 24,
- width: '100%',
- fontSize: '20px',
- padding: 6,
- },
- }, [
- this.props.t('restoreVault'),
- ]),
-
- // wallet seed entry
- h('h3', 'Wallet Seed'),
- h('textarea.twelve-word-phrase.letter-spacey', {
- dataset: {
- persistentFormId: 'wallet-seed',
- },
- placeholder: this.props.t('secretPhrase'),
- }),
-
- // password
- h('input.large-input.letter-spacey', {
- type: 'password',
- id: 'password-box',
- placeholder: this.props.t('newPassword8Chars'),
- dataset: {
- persistentFormId: 'password',
- },
- style: {
- width: 260,
- marginTop: 12,
- },
- }),
-
- // confirm password
- h('input.large-input.letter-spacey', {
- type: 'password',
- id: 'password-box-confirm',
- placeholder: this.props.t('confirmPassword'),
- onKeyPress: this.createOnEnter.bind(this),
- dataset: {
- persistentFormId: 'password-confirmation',
- },
- style: {
- width: 260,
- marginTop: 16,
- },
- }),
-
- error && (
- h('span.error.in-progress-notification', error)
- ),
-
- // submit
- h('.flex-row.flex-space-between', {
- style: {
- marginTop: 30,
- width: '50%',
- },
- }, [
-
- // cancel
- h('button.primary', {
- onClick: () => this.cancel(),
- }, this.props.t('cancel')),
-
- // submit
- h('button.primary', {
- onClick: this.createNewVaultAndRestore.bind(this),
- }, this.props.t('ok')),
-
- ]),
- ])
+ <div className="first-view-main-wrapper">
+ <div className="first-view-main">
+ <div className="import-account">
+ <a
+ className="import-account__back-button"
+ onClick={e => {
+ e.preventDefault()
+ this.props.history.goBack()
+ }}
+ href="#"
+ >
+ {`< Back`}
+ </a>
+ <div className="import-account__title">
+ { this.context.t('restoreAccountWithSeed') }
+ </div>
+ <div className="import-account__selector-label">
+ { this.context.t('secretPhrase') }
+ </div>
+ <div className="import-account__input-wrapper">
+ <label className="import-account__input-label">Wallet Seed</label>
+ <textarea
+ className="import-account__secret-phrase"
+ onChange={e => this.handleSeedPhraseChange(e.target.value)}
+ value={this.state.seedPhrase}
+ placeholder={this.context.t('separateEachWord')}
+ />
+ </div>
+ <span className="error">
+ { seedPhraseError }
+ </span>
+ <TextField
+ id="password"
+ label={t('newPassword')}
+ type="password"
+ className="first-time-flow__input"
+ value={this.state.password}
+ onChange={event => this.handlePasswordChange(event.target.value)}
+ error={passwordError}
+ autoComplete="new-password"
+ margin="normal"
+ largeLabel
+ />
+ <TextField
+ id="confirm-password"
+ label={t('confirmPassword')}
+ type="password"
+ className="first-time-flow__input"
+ value={this.state.confirmPassword}
+ onChange={event => this.handleConfirmPasswordChange(event.target.value)}
+ error={confirmPasswordError}
+ autoComplete="confirm-password"
+ margin="normal"
+ largeLabel
+ />
+ <button
+ className="first-time-flow__button"
+ onClick={() => !disabled && this.onClick()}
+ disabled={disabled}
+ >
+ {this.context.t('restore')}
+ </button>
+ </div>
+ </div>
+ </div>
)
}
}
-RestoreVaultPage.propTypes = {
- history: PropTypes.object,
-}
-
-const mapStateToProps = state => {
- const { appState: { warning, forgottenPassword } } = state
-
- return {
- warning,
- forgottenPassword,
- }
+RestoreVaultPage.contextTypes = {
+ t: PropTypes.func,
}
-const mapDispatchToProps = dispatch => {
- return {
- createNewVaultAndRestore: (password, seed) => {
- return dispatch(createNewVaultAndRestore(password, seed))
+export default connect(
+ ({ appState: { warning, isLoading } }) => ({ warning, isLoading }),
+ dispatch => ({
+ leaveImportSeedScreenState: () => {
+ dispatch(unMarkPasswordForgotten())
},
- unMarkPasswordForgotten: () => dispatch(unMarkPasswordForgotten()),
- }
-}
-
-module.exports = compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
+ createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
+ })
)(RestoreVaultPage)
diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js
index bbf5683f0..22b2670d8 100644
--- a/ui/app/components/pending-tx/confirm-send-ether.js
+++ b/ui/app/components/pending-tx/confirm-send-ether.js
@@ -20,7 +20,7 @@ const {
calcGasTotal,
isBalanceSufficient,
} = require('../send_/send.utils')
-const GasFeeDisplay = require('../send/gas-fee-display-v2')
+const GasFeeDisplay = require('../send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component').default
const SenderToRecipient = require('../sender-to-recipient')
const NetworkDisplay = require('../network-display')
const currencyFormatter = require('currency-formatter')
diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js
index ee066b8f4..535347cee 100644
--- a/ui/app/components/pending-tx/confirm-send-token.js
+++ b/ui/app/components/pending-tx/confirm-send-token.js
@@ -11,7 +11,7 @@ abiDecoder.addABI(tokenAbi)
const actions = require('../../actions')
const clone = require('clone')
const Identicon = require('../identicon')
-const GasFeeDisplay = require('../send/gas-fee-display-v2.js')
+const GasFeeDisplay = require('../send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js').default
const NetworkDisplay = require('../network-display')
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js
index 9c8ce0044..1cf55ce1a 100644
--- a/ui/app/components/send/currency-display.js
+++ b/ui/app/components/send/currency-display.js
@@ -2,6 +2,7 @@ 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_/send.utils')
const currencyFormatter = require('currency-formatter')
const currencies = require('currency-formatter/currencies')
const ethUtil = require('ethereumjs-util')
@@ -92,10 +93,6 @@ CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValu
: convertedValue
}
-function removeLeadingZeroes (str) {
- return str.replace(/^0*(?=\d)/, '')
-}
-
CurrencyDisplay.prototype.handleChange = function (newVal) {
this.setState({ valueToRender: removeLeadingZeroes(newVal) })
this.props.onChange(this.getAmount(newVal))
@@ -118,6 +115,7 @@ CurrencyDisplay.prototype.render = function () {
readOnly = false,
inError = false,
onBlur,
+ step,
} = this.props
const { valueToRender } = this.state
@@ -149,9 +147,10 @@ CurrencyDisplay.prototype.render = function () {
} : {}),
ref: input => { this.currencyInput = input },
style: {
- minWidth: this.getInputWidth(valueToRender, readOnly),
+ width: this.getInputWidth(valueToRender, readOnly),
},
min: 0,
+ step,
}),
h('span.currency-display__currency-symbol', primaryCurrency),
diff --git a/ui/app/components/send/gas-fee-display-v2.js b/ui/app/components/send/gas-fee-display-v2.js
deleted file mode 100644
index 1423aa84d..000000000
--- a/ui/app/components/send/gas-fee-display-v2.js
+++ /dev/null
@@ -1,53 +0,0 @@
-const Component = require('react').Component
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const CurrencyDisplay = require('./currency-display')
-const connect = require('react-redux').connect
-
-GasFeeDisplay.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect()(GasFeeDisplay)
-
-
-inherits(GasFeeDisplay, Component)
-function GasFeeDisplay () {
- Component.call(this)
-}
-
-GasFeeDisplay.prototype.render = function () {
- const {
- conversionRate,
- gasTotal,
- onClick,
- primaryCurrency = 'ETH',
- convertedCurrency,
- gasLoadingError,
- } = this.props
-
- return h('div.send-v2__gas-fee-display', [
-
- gasTotal
- ? h(CurrencyDisplay, {
- primaryCurrency,
- convertedCurrency,
- value: gasTotal,
- conversionRate,
- convertedPrefix: '$',
- readOnly: true,
- })
- : gasLoadingError
- ? h('div.currency-display.currency-display--message', this.context.t('setGasPrice'))
- : h('div.currency-display', this.context.t('loading')),
-
- h('button.sliders-icon-container', {
- onClick,
- disabled: !gasTotal && !gasLoadingError,
- }, [
- h('i.fa.fa-sliders.sliders-icon'),
- ]),
-
- ])
-}
diff --git a/ui/app/components/send_/account-list-item/account-list-item.container.js b/ui/app/components/send_/account-list-item/account-list-item.container.js
index 3151b1f1d..4b4519288 100644
--- a/ui/app/components/send_/account-list-item/account-list-item.container.js
+++ b/ui/app/components/send_/account-list-item/account-list-item.container.js
@@ -1,7 +1,7 @@
import { connect } from 'react-redux'
import {
getConversionRate,
- getConvertedCurrency,
+ getCurrentCurrency,
} from '../send.selectors.js'
import AccountListItem from './account-list-item.component'
@@ -10,6 +10,6 @@ export default connect(mapStateToProps)(AccountListItem)
function mapStateToProps (state) {
return {
conversionRate: getConversionRate(state),
- currentCurrency: getConvertedCurrency(state),
+ currentCurrency: getCurrentCurrency(state),
}
}
diff --git a/ui/app/components/send_/account-list-item/index.js b/ui/app/components/send_/account-list-item/index.js
index 1fca540be..907485cf7 100644
--- a/ui/app/components/send_/account-list-item/index.js
+++ b/ui/app/components/send_/account-list-item/index.js
@@ -1 +1 @@
-export { default } from './account-list-item.container' \ No newline at end of file
+export { default } from './account-list-item.container'
diff --git a/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js b/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js
index 49da920e6..af0859117 100644
--- a/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js
+++ b/ui/app/components/send_/account-list-item/tests/account-list-item-container.test.js
@@ -12,7 +12,7 @@ proxyquire('../account-list-item.container.js', {
},
'../send.selectors.js': {
getConversionRate: (s) => `mockConversionRate:${s}`,
- getConvertedCurrency: (s) => `mockCurrentCurrency:${s}`,
+ getCurrentCurrency: (s) => `mockCurrentCurrency:${s}`,
},
})
diff --git a/ui/app/components/send_/index.js b/ui/app/components/send_/index.js
index 9a4dd5727..b5114babc 100644
--- a/ui/app/components/send_/index.js
+++ b/ui/app/components/send_/index.js
@@ -1 +1 @@
-export { default } from './send.container' \ No newline at end of file
+export { default } from './send.container'
diff --git a/ui/app/components/send_/send-content/index.js b/ui/app/components/send_/send-content/index.js
index 10b3c850e..891c17e6a 100644
--- a/ui/app/components/send_/send-content/index.js
+++ b/ui/app/components/send_/send-content/index.js
@@ -1 +1 @@
-export { default } from './send-content.component' \ No newline at end of file
+export { default } from './send-content.component'
diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/index.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/index.js
index 548b51f33..ee8271494 100644
--- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/index.js
+++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/index.js
@@ -1 +1 @@
-export { default } from './amount-max-button.container' \ No newline at end of file
+export { default } from './amount-max-button.container'
diff --git a/ui/app/components/send_/send-content/send-amount-row/index.js b/ui/app/components/send_/send-content/send-amount-row/index.js
index 94a7da56f..abc6852fe 100644
--- a/ui/app/components/send_/send-content/send-amount-row/index.js
+++ b/ui/app/components/send_/send-content/send-amount-row/index.js
@@ -1 +1 @@
-export { default } from './send-amount-row.container' \ No newline at end of file
+export { default } from './send-amount-row.container'
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 8da36d3b7..e13b95555 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
@@ -21,6 +21,7 @@ export default class SendAmountRow extends Component {
selectedToken: PropTypes.object,
setMaxModeTo: PropTypes.func,
tokenBalance: PropTypes.string,
+ updateGasFeeError: PropTypes.func,
updateSendAmount: PropTypes.func,
updateSendAmountError: PropTypes.func,
updateGas: PropTypes.func,
@@ -35,6 +36,7 @@ export default class SendAmountRow extends Component {
primaryCurrency,
selectedToken,
tokenBalance,
+ updateGasFeeError,
updateSendAmountError,
} = this.props
@@ -48,6 +50,19 @@ export default class SendAmountRow extends Component {
selectedToken,
tokenBalance,
})
+
+ if (selectedToken) {
+ updateGasFeeError({
+ amount,
+ amountConversionRate,
+ balance,
+ conversionRate,
+ gasTotal,
+ primaryCurrency,
+ selectedToken,
+ tokenBalance,
+ })
+ }
}
updateAmount (amount) {
@@ -95,6 +110,7 @@ export default class SendAmountRow extends Component {
primaryCurrency={primaryCurrency || 'ETH'}
selectedToken={selectedToken}
value={amount}
+ step="any"
/>
</SendRowWrapper>
)
diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js
index bbbf56971..3504d1b73 100644
--- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js
+++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js
@@ -2,7 +2,7 @@ import { connect } from 'react-redux'
import {
getAmountConversionRate,
getConversionRate,
- getConvertedCurrency,
+ getCurrentCurrency,
getGasTotal,
getPrimaryCurrency,
getSelectedToken,
@@ -13,7 +13,7 @@ import {
import {
sendAmountIsInError,
} from './send-amount-row.selectors'
-import { getAmountErrorObject } from '../../send.utils'
+import { getAmountErrorObject, getGasFeeErrorObject } from '../../send.utils'
import {
setMaxModeTo,
updateSendAmount,
@@ -31,7 +31,7 @@ function mapStateToProps (state) {
amountConversionRate: getAmountConversionRate(state),
balance: getSendFromBalance(state),
conversionRate: getConversionRate(state),
- convertedCurrency: getConvertedCurrency(state),
+ convertedCurrency: getCurrentCurrency(state),
gasTotal: getGasTotal(state),
inError: sendAmountIsInError(state),
primaryCurrency: getPrimaryCurrency(state),
@@ -44,6 +44,9 @@ function mapDispatchToProps (dispatch) {
return {
setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
updateSendAmount: newAmount => dispatch(updateSendAmount(newAmount)),
+ updateGasFeeError: (amountDataObject) => {
+ dispatch(updateSendErrors(getGasFeeErrorObject(amountDataObject)))
+ },
updateSendAmountError: (amountDataObject) => {
dispatch(updateSendErrors(getAmountErrorObject(amountDataObject)))
},
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 579e18585..95c000a34 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
@@ -13,6 +13,7 @@ const propsMethodSpies = {
updateSendAmount: sinon.spy(),
updateSendAmountError: sinon.spy(),
updateGas: sinon.spy(),
+ updateGasFeeError: sinon.spy(),
}
sinon.spy(SendAmountRow.prototype, 'updateAmount')
@@ -36,6 +37,7 @@ describe('SendAmountRow Component', function () {
selectedToken={ { address: 'mockTokenAddress' } }
setMaxModeTo={propsMethodSpies.setMaxModeTo}
tokenBalance={'mockTokenBalance'}
+ updateGasFeeError={propsMethodSpies.updateGasFeeError}
updateSendAmount={propsMethodSpies.updateSendAmount}
updateSendAmountError={propsMethodSpies.updateSendAmountError}
updateGas={propsMethodSpies.updateGas}
@@ -47,6 +49,7 @@ describe('SendAmountRow Component', function () {
propsMethodSpies.setMaxModeTo.resetHistory()
propsMethodSpies.updateSendAmount.resetHistory()
propsMethodSpies.updateSendAmountError.resetHistory()
+ propsMethodSpies.updateGasFeeError.resetHistory()
SendAmountRow.prototype.validateAmount.resetHistory()
SendAmountRow.prototype.updateAmount.resetHistory()
})
@@ -72,6 +75,32 @@ describe('SendAmountRow Component', function () {
)
})
+ it('should call updateGasFeeError if selectedToken is truthy', () => {
+ assert.equal(propsMethodSpies.updateGasFeeError.callCount, 0)
+ instance.validateAmount('someAmount')
+ assert.equal(propsMethodSpies.updateGasFeeError.callCount, 1)
+ assert.deepEqual(
+ propsMethodSpies.updateGasFeeError.getCall(0).args,
+ [{
+ amount: 'someAmount',
+ amountConversionRate: 'mockAmountConversionRate',
+ balance: 'mockBalance',
+ conversionRate: 7,
+ gasTotal: 'mockGasTotal',
+ primaryCurrency: 'mockPrimaryCurrency',
+ selectedToken: { address: 'mockTokenAddress' },
+ tokenBalance: 'mockTokenBalance',
+ }]
+ )
+ })
+
+ it('should call not updateGasFeeError if selectedToken is falsey', () => {
+ wrapper.setProps({ selectedToken: null })
+ assert.equal(propsMethodSpies.updateGasFeeError.callCount, 0)
+ instance.validateAmount('someAmount')
+ assert.equal(propsMethodSpies.updateGasFeeError.callCount, 0)
+ })
+
})
describe('updateAmount', () => {
diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js
index e4c913c69..52e351aee 100644
--- a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js
+++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js
@@ -24,7 +24,7 @@ proxyquire('../send-amount-row.container.js', {
'../../send.selectors': {
getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`,
getConversionRate: (s) => `mockConversionRate:${s}`,
- getConvertedCurrency: (s) => `mockConvertedCurrency:${s}`,
+ getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`,
getGasTotal: (s) => `mockGasTotal:${s}`,
getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`,
getSelectedToken: (s) => `mockSelectedToken:${s}`,
@@ -33,7 +33,10 @@ proxyquire('../send-amount-row.container.js', {
getTokenBalance: (s) => `mockTokenBalance:${s}`,
},
'./send-amount-row.selectors': { sendAmountIsInError: (s) => `mockInError:${s}` },
- '../../send.utils': { getAmountErrorObject: (mockDataObject) => ({ ...mockDataObject, mockChange: true }) },
+ '../../send.utils': {
+ getAmountErrorObject: (mockDataObject) => ({ ...mockDataObject, mockChange: true }),
+ getGasFeeErrorObject: (mockDataObject) => ({ ...mockDataObject, mockGasFeeErrorChange: true }),
+ },
'../../../../actions': actionSpies,
'../../../../ducks/send.duck': duckActionSpies,
})
@@ -66,6 +69,7 @@ describe('send-amount-row container', () => {
beforeEach(() => {
dispatchSpy = sinon.spy()
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
+ duckActionSpies.updateSendErrors.resetHistory()
})
describe('setMaxModeTo()', () => {
@@ -92,6 +96,18 @@ describe('send-amount-row container', () => {
})
})
+ describe('updateGasFeeError()', () => {
+ it('should dispatch an action', () => {
+ mapDispatchToPropsObject.updateGasFeeError({ some: 'data' })
+ assert(dispatchSpy.calledOnce)
+ assert(duckActionSpies.updateSendErrors.calledOnce)
+ assert.deepEqual(
+ duckActionSpies.updateSendErrors.getCall(0).args[0],
+ { some: 'data', mockGasFeeErrorChange: true }
+ )
+ })
+ })
+
describe('updateSendAmountError()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.updateSendAmountError({ some: 'data' })
diff --git a/ui/app/components/send_/send-content/send-dropdown-list/index.js b/ui/app/components/send_/send-content/send-dropdown-list/index.js
index ee7736376..04af6536c 100644
--- a/ui/app/components/send_/send-content/send-dropdown-list/index.js
+++ b/ui/app/components/send_/send-content/send-dropdown-list/index.js
@@ -1 +1 @@
-export { default } from './send-dropdown-list.component' \ No newline at end of file
+export { default } from './send-dropdown-list.component'
diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/index.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/index.js
index 6ab9a157a..2314ef4e3 100644
--- a/ui/app/components/send_/send-content/send-from-row/from-dropdown/index.js
+++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/index.js
@@ -1 +1 @@
-export { default } from './from-dropdown.component' \ No newline at end of file
+export { default } from './from-dropdown.component'
diff --git a/ui/app/components/send_/send-content/send-from-row/index.js b/ui/app/components/send_/send-content/send-from-row/index.js
index 4a0916dba..0a79726b2 100644
--- a/ui/app/components/send_/send-content/send-from-row/index.js
+++ b/ui/app/components/send_/send-content/send-from-row/index.js
@@ -1 +1 @@
-export { default } from './send-from-row.container' \ No newline at end of file
+export { default } from './send-from-row.container'
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
new file mode 100644
index 000000000..c8d619be5
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js
@@ -0,0 +1,61 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import CurrencyDisplay from '../../../../send/currency-display'
+
+
+export default class GasFeeDisplay extends Component {
+
+ static propTypes = {
+ conversionRate: PropTypes.number,
+ primaryCurrency: PropTypes.string,
+ convertedCurrency: PropTypes.string,
+ gasLoadingError: PropTypes.bool,
+ gasTotal: PropTypes.string,
+ onClick: PropTypes.func,
+ };
+
+ render () {
+ const {
+ conversionRate,
+ gasTotal,
+ onClick,
+ primaryCurrency = 'ETH',
+ convertedCurrency,
+ gasLoadingError,
+ } = this.props
+
+ return (
+ <div className="send-v2__gas-fee-display">
+ {gasTotal
+ ? <CurrencyDisplay
+ primaryCurrency={primaryCurrency}
+ convertedCurrency={convertedCurrency}
+ value={gasTotal}
+ conversionRate={conversionRate}
+ gasLoadingError={gasLoadingError}
+ convertedPrefix={'$'}
+ readOnly
+ />
+ : gasLoadingError
+ ? <div className="currency-display.currency-display--message">
+ {this.context.t('setGasPrice')}
+ </div>
+ : <div className="currency-display">
+ {this.context.t('loading')}
+ </div>
+ }
+ <button
+ className="sliders-icon-container"
+ onClick={onClick}
+ disabled={!gasTotal && !gasLoadingError}
+ >
+ <i className="fa fa-sliders sliders-icon" />
+ </button>
+ </div>
+ )
+ }
+}
+
+GasFeeDisplay.contextTypes = {
+ t: PropTypes.func,
+}
diff --git a/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/index.js b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/index.js
new file mode 100644
index 000000000..dba0edb7b
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/index.js
@@ -0,0 +1 @@
+export { default } from './gas-fee-display.component'
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
new file mode 100644
index 000000000..7cbe8d0df
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
@@ -0,0 +1,55 @@
+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 sinon from 'sinon'
+
+
+const propsMethodSpies = {
+ showCustomizeGasModal: sinon.spy(),
+}
+
+describe('SendGasRow Component', function () {
+ let wrapper
+
+ beforeEach(() => {
+ wrapper = shallow(<GasFeeDisplay
+ conversionRate={20}
+ gasTotal={'mockGasTotal'}
+ onClick={propsMethodSpies.showCustomizeGasModal}
+ primaryCurrency={'mockPrimaryCurrency'}
+ convertedCurrency={'mockConvertedCurrency'}
+ />, {context: {t: str => str + '_t'}})
+ })
+
+ afterEach(() => {
+ propsMethodSpies.showCustomizeGasModal.resetHistory()
+ })
+
+ describe('render', () => {
+ it('should render a CurrencyDisplay component', () => {
+ assert.equal(wrapper.find(CurrencyDisplay).length, 1)
+ })
+
+ it('should render the CurrencyDisplay with the correct props', () => {
+ const {
+ conversionRate,
+ convertedCurrency,
+ value,
+ } = wrapper.find(CurrencyDisplay).props()
+ assert.equal(conversionRate, 20)
+ assert.equal(convertedCurrency, 'mockConvertedCurrency')
+ assert.equal(value, 'mockGasTotal')
+ })
+
+ it('should render the Button with the correct props', () => {
+ const {
+ onClick,
+ } = wrapper.find('button').props()
+ assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 0)
+ onClick()
+ assert.equal(propsMethodSpies.showCustomizeGasModal.callCount, 1)
+ })
+ })
+})
diff --git a/ui/app/components/send_/send-content/send-gas-row/index.js b/ui/app/components/send_/send-content/send-gas-row/index.js
index 060ed7fd3..3c7ff1d5f 100644
--- a/ui/app/components/send_/send-content/send-gas-row/index.js
+++ b/ui/app/components/send_/send-content/send-gas-row/index.js
@@ -1 +1 @@
-export { default } from './send-gas-row.container' \ No newline at end of file
+export { default } from './send-gas-row.container'
diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js
index c80d8c0bb..ba5c22a47 100644
--- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js
+++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js
@@ -1,13 +1,14 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper/'
-import GasFeeDisplay from '../../../send/gas-fee-display-v2'
+import GasFeeDisplay from './gas-fee-display/gas-fee-display.component'
export default class SendGasRow extends Component {
static propTypes = {
conversionRate: PropTypes.number,
convertedCurrency: PropTypes.string,
+ gasFeeError: PropTypes.bool,
gasLoadingError: PropTypes.bool,
gasTotal: PropTypes.string,
showCustomizeGasModal: PropTypes.func,
@@ -19,11 +20,16 @@ export default class SendGasRow extends Component {
convertedCurrency,
gasLoadingError,
gasTotal,
+ gasFeeError,
showCustomizeGasModal,
} = this.props
return (
- <SendRowWrapper label={`${this.context.t('gasFee')}:`}>
+ <SendRowWrapper
+ label={`${this.context.t('gasFee')}:`}
+ showError={gasFeeError}
+ errorType={'gasFee'}
+ >
<GasFeeDisplay
conversionRate={conversionRate}
convertedCurrency={convertedCurrency}
diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js
index 20d3daa59..8f8e3e4dd 100644
--- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js
+++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js
@@ -1,10 +1,10 @@
import { connect } from 'react-redux'
import {
getConversionRate,
- getConvertedCurrency,
+ getCurrentCurrency,
getGasTotal,
} from '../../send.selectors.js'
-import { sendGasIsInError } from './send-gas-row.selectors.js'
+import { getGasLoadingError, gasFeeIsInError } from './send-gas-row.selectors.js'
import { showModal } from '../../../../actions'
import SendGasRow from './send-gas-row.component'
@@ -13,9 +13,10 @@ export default connect(mapStateToProps, mapDispatchToProps)(SendGasRow)
function mapStateToProps (state) {
return {
conversionRate: getConversionRate(state),
- convertedCurrency: getConvertedCurrency(state),
+ convertedCurrency: getCurrentCurrency(state),
gasTotal: getGasTotal(state),
- gasLoadingError: sendGasIsInError(state),
+ gasFeeError: gasFeeIsInError(state),
+ gasLoadingError: getGasLoadingError(state),
}
}
diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js
index d069ae8c6..96f6293c2 100644
--- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js
+++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js
@@ -1,9 +1,14 @@
const selectors = {
- sendGasIsInError,
+ gasFeeIsInError,
+ getGasLoadingError,
}
module.exports = selectors
-function sendGasIsInError (state) {
+function getGasLoadingError (state) {
return state.send.errors.gasLoading
}
+
+function gasFeeIsInError (state) {
+ return Boolean(state.send.errors.gasFee)
+}
diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js
index e4f05d708..54a92bd2d 100644
--- a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js
+++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js
@@ -5,7 +5,7 @@ import sinon from 'sinon'
import SendGasRow from '../send-gas-row.component.js'
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
-import GasFeeDisplay from '../../../../send/gas-fee-display-v2'
+import GasFeeDisplay from '../gas-fee-display/gas-fee-display.component'
const propsMethodSpies = {
showCustomizeGasModal: sinon.spy(),
@@ -18,6 +18,7 @@ describe('SendGasRow Component', function () {
wrapper = shallow(<SendGasRow
conversionRate={20}
convertedCurrency={'mockConvertedCurrency'}
+ gasFeeError={'mockGasFeeError'}
gasLoadingError={false}
gasTotal={'mockGasTotal'}
showCustomizeGasModal={propsMethodSpies.showCustomizeGasModal}
@@ -36,9 +37,13 @@ describe('SendGasRow Component', function () {
it('should pass the correct props to SendRowWrapper', () => {
const {
label,
+ showError,
+ errorType,
} = wrapper.find(SendRowWrapper).props()
assert.equal(label, 'gasFee_t:')
+ assert.equal(showError, 'mockGasFeeError')
+ assert.equal(errorType, 'gasFee')
})
it('should render a GasFeeDisplay as a child of the SendRowWrapper', () => {
diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js
index 9135524d1..2ce062505 100644
--- a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js
+++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js
@@ -19,10 +19,13 @@ proxyquire('../send-gas-row.container.js', {
},
'../../send.selectors.js': {
getConversionRate: (s) => `mockConversionRate:${s}`,
- getConvertedCurrency: (s) => `mockConvertedCurrency:${s}`,
+ getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`,
getGasTotal: (s) => `mockGasTotal:${s}`,
},
- './send-gas-row.selectors.js': { sendGasIsInError: (s) => `mockGasLoadingError:${s}` },
+ './send-gas-row.selectors.js': {
+ getGasLoadingError: (s) => `mockGasLoadingError:${s}`,
+ gasFeeIsInError: (s) => `mockGasFeeError:${s}`,
+ },
'../../../../actions': actionSpies,
})
@@ -35,6 +38,7 @@ describe('send-gas-row container', () => {
conversionRate: 'mockConversionRate:mockState',
convertedCurrency: 'mockConvertedCurrency:mockState',
gasTotal: 'mockGasTotal:mockState',
+ gasFeeError: 'mockGasFeeError:mockState',
gasLoadingError: 'mockGasLoadingError:mockState',
})
})
diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
index a5196334e..d46dd9d8b 100644
--- a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
+++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
@@ -1,11 +1,12 @@
import assert from 'assert'
import {
- sendGasIsInError,
+ gasFeeIsInError,
+ getGasLoadingError,
} from '../send-gas-row.selectors.js'
describe('send-gas-row selectors', () => {
- describe('sendGasIsInError()', () => {
+ describe('getGasLoadingError()', () => {
it('should return send.errors.gasLoading', () => {
const state = {
send: {
@@ -15,7 +16,33 @@ describe('send-gas-row selectors', () => {
},
}
- assert.equal(sendGasIsInError(state), 'abc')
+ assert.equal(getGasLoadingError(state), 'abc')
+ })
+ })
+
+ describe('gasFeeIsInError()', () => {
+ it('should return true if send.errors.gasFee is truthy', () => {
+ const state = {
+ send: {
+ errors: {
+ gasFee: 'def',
+ },
+ },
+ }
+
+ assert.equal(gasFeeIsInError(state), true)
+ })
+
+ it('should return false send.errors.gasFee is falsely', () => {
+ const state = {
+ send: {
+ errors: {
+ gasFee: null,
+ },
+ },
+ }
+
+ assert.equal(gasFeeIsInError(state), false)
})
})
diff --git a/ui/app/components/send_/send-content/send-row-wrapper/index.js b/ui/app/components/send_/send-content/send-row-wrapper/index.js
index 5715f55c6..d17545dcc 100644
--- a/ui/app/components/send_/send-content/send-row-wrapper/index.js
+++ b/ui/app/components/send_/send-content/send-row-wrapper/index.js
@@ -1 +1 @@
-export { default } from './send-row-wrapper.component' \ No newline at end of file
+export { default } from './send-row-wrapper.component'
diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/index.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/index.js
index bf49c55bd..c00617f83 100644
--- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/index.js
+++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/index.js
@@ -1 +1 @@
-export { default } from './send-row-error-message.container' \ No newline at end of file
+export { default } from './send-row-error-message.container'
diff --git a/ui/app/components/send_/send-content/send-to-row/index.js b/ui/app/components/send_/send-content/send-to-row/index.js
index 4e7aa9747..121f15148 100644
--- a/ui/app/components/send_/send-content/send-to-row/index.js
+++ b/ui/app/components/send_/send-content/send-to-row/index.js
@@ -1 +1 @@
-export { default } from './send-to-row.container' \ No newline at end of file
+export { default } from './send-to-row.container'
diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js
index 0a83186a5..1c2ecdf9c 100644
--- a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js
+++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js
@@ -19,9 +19,9 @@ export default class SendToRow extends Component {
updateSendToError: PropTypes.func,
};
- handleToChange (to, nickname = '') {
+ handleToChange (to, nickname = '', toError) {
const { updateSendTo, updateSendToError, updateGas } = this.props
- const toErrorObject = getToErrorObject(to)
+ const toErrorObject = getToErrorObject(to, toError)
updateSendTo(to, nickname)
updateSendToError(toErrorObject)
if (toErrorObject.to === null) {
@@ -53,7 +53,7 @@ export default class SendToRow extends Component {
inError={inError}
name={'address'}
network={network}
- onChange={(newTo, newNickname) => this.handleToChange(newTo, newNickname)}
+ onChange={({ toAddress, nickname, toError }) => this.handleToChange(toAddress, nickname, toError)}
openDropdown={() => openToDropdown()}
placeholder={this.context.t('recipientAddress')}
to={to}
diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js
index cea51ee20..6b90a9f09 100644
--- a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js
+++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js
@@ -4,12 +4,10 @@ const {
} = require('../../send.constants')
const { isValidAddress } = require('../../../../util')
-function getToErrorObject (to) {
- let toError = null
-
+function getToErrorObject (to, toError = null) {
if (!to) {
toError = REQUIRED_ERROR
- } else if (!isValidAddress(to)) {
+ } else if (!isValidAddress(to) && !toError) {
toError = INVALID_RECIPIENT_ADDRESS_ERROR
}
diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js
index 58fe51dcf..781371004 100644
--- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js
+++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js
@@ -6,8 +6,8 @@ import proxyquire from 'proxyquire'
const SendToRow = proxyquire('../send-to-row.component.js', {
'./send-to-row.utils.js': {
- getToErrorObject: (to) => ({
- to: to === false ? null : `mockToErrorObject:${to}`,
+ getToErrorObject: (to, toError) => ({
+ to: to === false ? null : `mockToErrorObject:${to}${toError}`,
}),
},
}).default
@@ -67,11 +67,11 @@ describe('SendToRow Component', function () {
it('should call updateSendToError', () => {
assert.equal(propsMethodSpies.updateSendToError.callCount, 0)
- instance.handleToChange('mockTo2')
+ instance.handleToChange('mockTo2', '', 'mockToError')
assert.equal(propsMethodSpies.updateSendToError.callCount, 1)
assert.deepEqual(
propsMethodSpies.updateSendToError.getCall(0).args,
- [{ to: 'mockToErrorObject:mockTo2' }]
+ [{ to: 'mockToErrorObject:mockTo2mockToError' }]
)
})
@@ -138,11 +138,11 @@ describe('SendToRow Component', function () {
openDropdown()
assert.equal(propsMethodSpies.openToDropdown.callCount, 1)
assert.equal(SendToRow.prototype.handleToChange.callCount, 0)
- onChange('mockNewTo', 'mockNewNickname')
+ onChange({ toAddress: 'mockNewTo', nickname: 'mockNewNickname', toError: 'mockToError' })
assert.equal(SendToRow.prototype.handleToChange.callCount, 1)
assert.deepEqual(
SendToRow.prototype.handleToChange.getCall(0).args,
- ['mockNewTo', 'mockNewNickname']
+ ['mockNewTo', 'mockNewNickname', 'mockToError']
)
})
})
diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js
index 615c9581b..4d2447c32 100644
--- a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js
+++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-utils.test.js
@@ -40,6 +40,12 @@ describe('send-to-row utils', () => {
to: null,
})
})
+
+ it('should return the passed error if to is truthy but invalid if to is truthy and valid', () => {
+ assert.deepEqual(getToErrorObject('invalid #$ 345878', 'someExplicitError'), {
+ to: 'someExplicitError',
+ })
+ })
})
})
diff --git a/ui/app/components/send_/send-footer/index.js b/ui/app/components/send_/send-footer/index.js
index cd1727330..58e91d622 100644
--- a/ui/app/components/send_/send-footer/index.js
+++ b/ui/app/components/send_/send-footer/index.js
@@ -1 +1 @@
-export { default } from './send-footer.container' \ No newline at end of file
+export { default } from './send-footer.container'
diff --git a/ui/app/components/send_/send-header/index.js b/ui/app/components/send_/send-header/index.js
index b808eabbf..0b17f0b7d 100644
--- a/ui/app/components/send_/send-header/index.js
+++ b/ui/app/components/send_/send-header/index.js
@@ -1 +1 @@
-export { default } from './send-header.container' \ No newline at end of file
+export { default } from './send-header.container'
diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js
index 219b362f2..b1ab57a2e 100644
--- a/ui/app/components/send_/send.component.js
+++ b/ui/app/components/send_/send.component.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import PersistentForm from '../../../lib/persistent-form'
import {
getAmountErrorObject,
+ getGasFeeErrorObject,
getToAddressForGasUpdate,
doesAmountErrorRequireUpdate,
} from './send.utils'
@@ -112,7 +113,19 @@ export default class SendTransactionScreen extends PersistentForm {
selectedToken,
tokenBalance,
})
- updateSendErrors(amountErrorObject)
+ const gasFeeErrorObject = selectedToken
+ ? getGasFeeErrorObject({
+ amount,
+ amountConversionRate,
+ balance,
+ conversionRate,
+ gasTotal,
+ primaryCurrency,
+ selectedToken,
+ tokenBalance,
+ })
+ : { gasFee: null }
+ updateSendErrors(Object.assign(amountErrorObject, gasFeeErrorObject))
}
if (!uninitialized) {
@@ -143,6 +156,10 @@ export default class SendTransactionScreen extends PersistentForm {
this.updateGas()
}
+ componentWillUnmount () {
+ this.props.resetSendState()
+ }
+
render () {
const { history } = this.props
diff --git a/ui/app/components/send_/send.constants.js b/ui/app/components/send_/send.constants.js
index df5dee371..8acdf0641 100644
--- a/ui/app/components/send_/send.constants.js
+++ b/ui/app/components/send_/send.constants.js
@@ -36,6 +36,7 @@ const ONE_GWEI_IN_WEI_HEX = ethUtil.addHexPrefix(conversionUtil('0x1', {
}))
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
+const BASE_TOKEN_GAS_COST = '0x186a0' // Hex for 100000, a base estimate for token transfers.
module.exports = {
INSUFFICIENT_FUNDS_ERROR,
@@ -52,4 +53,5 @@ module.exports = {
REQUIRED_ERROR,
SIMPLE_GAS_COST,
TOKEN_TRANSFER_FUNCTION_SIGNATURE,
+ BASE_TOKEN_GAS_COST,
}
diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js
index 185653c5f..44ebd2792 100644
--- a/ui/app/components/send_/send.container.js
+++ b/ui/app/components/send_/send.container.js
@@ -28,6 +28,7 @@ import {
setGasTotal,
} from '../../actions'
import {
+ resetSendState,
updateSendErrors,
} from '../../ducks/send.duck'
import {
@@ -87,5 +88,6 @@ function mapDispatchToProps (dispatch) {
}))
},
updateSendErrors: newError => dispatch(updateSendErrors(newError)),
+ resetSendState: () => dispatch(resetSendState()),
}
}
diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js
index 7e7cfe2e9..f910f7caf 100644
--- a/ui/app/components/send_/send.selectors.js
+++ b/ui/app/components/send_/send.selectors.js
@@ -14,7 +14,6 @@ const selectors = {
getAmountConversionRate,
getBlockGasLimit,
getConversionRate,
- getConvertedCurrency,
getCurrentAccountWithSendEtherInfo,
getCurrentCurrency,
getCurrentNetwork,
@@ -98,10 +97,6 @@ function getConversionRate (state) {
return state.metamask.conversionRate
}
-function getConvertedCurrency (state) {
- return state.metamask.currentCurrency
-}
-
function getCurrentAccountWithSendEtherInfo (state) {
const currentAddress = getSelectedAddress(state)
const accounts = accountsWithSendEtherInfoSelector(state)
diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js
index dfd459731..c4537f335 100644
--- a/ui/app/components/send_/send.utils.js
+++ b/ui/app/components/send_/send.utils.js
@@ -10,6 +10,7 @@ const {
calcTokenAmount,
} = require('../../token-util')
const {
+ BASE_TOKEN_GAS_COST,
INSUFFICIENT_FUNDS_ERROR,
INSUFFICIENT_TOKENS_ERROR,
NEGATIVE_ETH_ERROR,
@@ -29,9 +30,11 @@ module.exports = {
estimateGasPriceFromRecentBlocks,
generateTokenTransferData,
getAmountErrorObject,
+ getGasFeeErrorObject,
getToAddressForGasUpdate,
isBalanceSufficient,
isTokenBalanceSufficient,
+ removeLeadingZeroes,
}
function calcGasTotal (gasLimit, gasPrice) {
@@ -108,9 +111,9 @@ function getAmountErrorObject ({
tokenBalance,
}) {
let insufficientFunds = false
- if (gasTotal && conversionRate) {
+ if (gasTotal && conversionRate && !selectedToken) {
insufficientFunds = !isBalanceSufficient({
- amount: selectedToken ? '0x0' : amount,
+ amount,
amountConversionRate,
balance,
conversionRate,
@@ -147,6 +150,34 @@ function getAmountErrorObject ({
return { amount: amountError }
}
+function getGasFeeErrorObject ({
+ amount,
+ amountConversionRate,
+ balance,
+ conversionRate,
+ gasTotal,
+ primaryCurrency,
+}) {
+ let gasFeeError = null
+
+ if (gasTotal && conversionRate) {
+ const insufficientFunds = !isBalanceSufficient({
+ amount: '0x0',
+ amountConversionRate,
+ balance,
+ conversionRate,
+ gasTotal,
+ primaryCurrency,
+ })
+
+ if (insufficientFunds) {
+ gasFeeError = INSUFFICIENT_FUNDS_ERROR
+ }
+ }
+
+ return { gasFee: gasFeeError }
+}
+
function calcTokenBalance ({ selectedToken, usersToken }) {
const { decimals } = selectedToken || {}
return calcTokenAmount(usersToken.balance.toString(), decimals) + ''
@@ -183,6 +214,8 @@ async function estimateGas ({ selectedAddress, selectedToken, blockGasLimit, to,
if (!code || code === '0x') {
return SIMPLE_GAS_COST
}
+ } else if (selectedToken && !to) {
+ return BASE_TOKEN_GAS_COST
}
paramsForGasEstimate.to = selectedToken ? selectedToken.address : to
@@ -273,3 +306,7 @@ function estimateGasPriceFromRecentBlocks (recentBlocks) {
function getToAddressForGasUpdate (...addresses) {
return [...addresses, ''].find(str => str !== undefined && str !== null).toLowerCase()
}
+
+function removeLeadingZeroes (str) {
+ return str.replace(/^0*(?=\d)/, '')
+}
diff --git a/ui/app/components/send_/send.utils.test.js b/ui/app/components/send_/send.utils.test.js
new file mode 100644
index 000000000..36f3a5c10
--- /dev/null
+++ b/ui/app/components/send_/send.utils.test.js
@@ -0,0 +1,30 @@
+import assert from 'assert'
+import { removeLeadingZeroes } from './send.utils'
+
+
+describe('send utils', () => {
+ describe('removeLeadingZeroes()', () => {
+ it('should remove leading zeroes from int when user types', () => {
+ assert.equal(removeLeadingZeroes('0'), '0')
+ assert.equal(removeLeadingZeroes('1'), '1')
+ assert.equal(removeLeadingZeroes('00'), '0')
+ assert.equal(removeLeadingZeroes('01'), '1')
+ })
+
+ it('should remove leading zeroes from int when user copy/paste', () => {
+ assert.equal(removeLeadingZeroes('001'), '1')
+ })
+
+ it('should remove leading zeroes from float when user types', () => {
+ assert.equal(removeLeadingZeroes('0.'), '0.')
+ assert.equal(removeLeadingZeroes('0.0'), '0.0')
+ assert.equal(removeLeadingZeroes('0.00'), '0.00')
+ assert.equal(removeLeadingZeroes('0.001'), '0.001')
+ assert.equal(removeLeadingZeroes('0.10'), '0.10')
+ })
+
+ it('should remove leading zeroes from float when user copy/paste', () => {
+ assert.equal(removeLeadingZeroes('00.1'), '0.1')
+ })
+ })
+})
diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js
index 4ba9b226d..6194ec508 100644
--- a/ui/app/components/send_/tests/send-component.test.js
+++ b/ui/app/components/send_/tests/send-component.test.js
@@ -12,9 +12,11 @@ const propsMethodSpies = {
updateAndSetGasTotal: sinon.spy(),
updateSendErrors: sinon.spy(),
updateSendTokenBalance: sinon.spy(),
+ resetSendState: sinon.spy(),
}
const utilsMethodStubs = {
getAmountErrorObject: sinon.stub().returns({ amount: 'mockAmountError' }),
+ getGasFeeErrorObject: sinon.stub().returns({ gasFee: 'mockGasFeeError' }),
doesAmountErrorRequireUpdate: sinon.stub().callsFake(obj => obj.balance !== obj.prevBalance),
}
@@ -50,6 +52,7 @@ describe('Send Component', function () {
updateAndSetGasTotal={propsMethodSpies.updateAndSetGasTotal}
updateSendErrors={propsMethodSpies.updateSendErrors}
updateSendTokenBalance={propsMethodSpies.updateSendTokenBalance}
+ resetSendState={propsMethodSpies.resetSendState}
/>)
})
@@ -58,6 +61,7 @@ describe('Send Component', function () {
SendTransactionScreen.prototype.updateGas.resetHistory()
utilsMethodStubs.doesAmountErrorRequireUpdate.resetHistory()
utilsMethodStubs.getAmountErrorObject.resetHistory()
+ utilsMethodStubs.getGasFeeErrorObject.resetHistory()
propsMethodSpies.updateAndSetGasTotal.resetHistory()
propsMethodSpies.updateSendErrors.resetHistory()
propsMethodSpies.updateSendTokenBalance.resetHistory()
@@ -77,6 +81,15 @@ describe('Send Component', function () {
})
})
+ describe('componentWillUnmount', () => {
+ it('should call this.props.resetSendState', () => {
+ propsMethodSpies.resetSendState.resetHistory()
+ assert.equal(propsMethodSpies.resetSendState.callCount, 0)
+ wrapper.instance().componentWillUnmount()
+ assert.equal(propsMethodSpies.resetSendState.callCount, 1)
+ })
+ })
+
describe('componentDidUpdate', () => {
it('should call doesAmountErrorRequireUpdate with the expected params', () => {
utilsMethodStubs.getAmountErrorObject.resetHistory()
@@ -133,8 +146,66 @@ describe('Send Component', function () {
)
})
- it('should call updateSendErrors with the expected params', () => {
+ it('should call getGasFeeErrorObject if doesAmountErrorRequireUpdate returns true and selectedToken is truthy', () => {
+ utilsMethodStubs.getGasFeeErrorObject.resetHistory()
+ wrapper.instance().componentDidUpdate({
+ from: {
+ balance: 'balanceChanged',
+ },
+ })
+ assert.equal(utilsMethodStubs.getGasFeeErrorObject.callCount, 1)
+ assert.deepEqual(
+ utilsMethodStubs.getGasFeeErrorObject.getCall(0).args[0],
+ {
+ amount: 'mockAmount',
+ amountConversionRate: 'mockAmountConversionRate',
+ balance: 'mockBalance',
+ conversionRate: 10,
+ gasTotal: 'mockGasTotal',
+ primaryCurrency: 'mockPrimaryCurrency',
+ selectedToken: 'mockSelectedToken',
+ tokenBalance: 'mockTokenBalance',
+ }
+ )
+ })
+
+ it('should not call getGasFeeErrorObject if doesAmountErrorRequireUpdate returns false', () => {
+ utilsMethodStubs.getGasFeeErrorObject.resetHistory()
+ wrapper.instance().componentDidUpdate({
+ from: { address: 'mockAddress', balance: 'mockBalance' },
+ })
+ assert.equal(utilsMethodStubs.getGasFeeErrorObject.callCount, 0)
+ })
+
+ it('should not call getGasFeeErrorObject if doesAmountErrorRequireUpdate returns true but selectedToken is falsy', () => {
+ utilsMethodStubs.getGasFeeErrorObject.resetHistory()
+ wrapper.setProps({ selectedToken: null })
+ wrapper.instance().componentDidUpdate({
+ from: {
+ balance: 'balanceChanged',
+ },
+ })
+ assert.equal(utilsMethodStubs.getGasFeeErrorObject.callCount, 0)
+ })
+
+ it('should call updateSendErrors with the expected params if selectedToken is falsy', () => {
+ propsMethodSpies.updateSendErrors.resetHistory()
+ wrapper.setProps({ selectedToken: null })
+ wrapper.instance().componentDidUpdate({
+ from: {
+ balance: 'balanceChanged',
+ },
+ })
+ assert.equal(propsMethodSpies.updateSendErrors.callCount, 1)
+ assert.deepEqual(
+ propsMethodSpies.updateSendErrors.getCall(0).args[0],
+ { amount: 'mockAmountError', gasFee: null }
+ )
+ })
+
+ it('should call updateSendErrors with the expected params if selectedToken is truthy', () => {
propsMethodSpies.updateSendErrors.resetHistory()
+ wrapper.setProps({ selectedToken: 'someToken' })
wrapper.instance().componentDidUpdate({
from: {
balance: 'balanceChanged',
@@ -143,7 +214,7 @@ describe('Send Component', function () {
assert.equal(propsMethodSpies.updateSendErrors.callCount, 1)
assert.deepEqual(
propsMethodSpies.updateSendErrors.getCall(0).args[0],
- { amount: 'mockAmountError'}
+ { amount: 'mockAmountError', gasFee: 'mockGasFeeError' }
)
})
diff --git a/ui/app/components/send_/tests/send-container.test.js b/ui/app/components/send_/tests/send-container.test.js
index 91484f4d8..7a9120d24 100644
--- a/ui/app/components/send_/tests/send-container.test.js
+++ b/ui/app/components/send_/tests/send-container.test.js
@@ -12,6 +12,7 @@ const actionSpies = {
}
const duckActionSpies = {
updateSendErrors: sinon.spy(),
+ resetSendState: sinon.spy(),
}
proxyquire('../send.container.js', {
@@ -152,6 +153,17 @@ describe('send container', () => {
})
})
+ describe('resetSendState()', () => {
+ it('should dispatch an action', () => {
+ mapDispatchToPropsObject.resetSendState()
+ assert(dispatchSpy.calledOnce)
+ assert.equal(
+ duckActionSpies.resetSendState.getCall(0).args.length,
+ 0
+ )
+ })
+ })
+
})
})
diff --git a/ui/app/components/send_/tests/send-selectors.test.js b/ui/app/components/send_/tests/send-selectors.test.js
index 152af8059..218da656b 100644
--- a/ui/app/components/send_/tests/send-selectors.test.js
+++ b/ui/app/components/send_/tests/send-selectors.test.js
@@ -8,7 +8,6 @@ const {
getBlockGasLimit,
getAmountConversionRate,
getConversionRate,
- getConvertedCurrency,
getCurrentAccountWithSendEtherInfo,
getCurrentCurrency,
getCurrentNetwork,
@@ -154,15 +153,6 @@ describe('send selectors', () => {
})
})
- describe('getConvertedCurrency()', () => {
- it('should return the currently selected currency', () => {
- assert.equal(
- getConvertedCurrency(mockState),
- 'USD'
- )
- })
- })
-
describe('getCurrentAccountWithSendEtherInfo()', () => {
it('should return the currently selected account with identity info', () => {
assert.deepEqual(
diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js
index f3d5674b7..b8579e0e4 100644
--- a/ui/app/components/send_/tests/send-utils.test.js
+++ b/ui/app/components/send_/tests/send-utils.test.js
@@ -2,6 +2,7 @@ import assert from 'assert'
import sinon from 'sinon'
import proxyquire from 'proxyquire'
import {
+ BASE_TOKEN_GAS_COST,
ONE_GWEI_IN_WEI_HEX,
SIMPLE_GAS_COST,
} from '../send.constants'
@@ -16,7 +17,11 @@ const {
} = require('../send.constants')
const stubs = {
- addCurrencies: sinon.stub().callsFake((a, b, obj) => a + b),
+ addCurrencies: sinon.stub().callsFake((a, b, obj) => {
+ if (String(a).match(/^0x.+/)) a = Number(String(a).slice(2))
+ if (String(b).match(/^0x.+/)) b = Number(String(b).slice(2))
+ return a + b
+ }),
conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)),
conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value >= obj2.value),
multiplyCurrencies: sinon.stub().callsFake((a, b) => `${a}x${b}`),
@@ -48,6 +53,7 @@ const {
estimateGasPriceFromRecentBlocks,
generateTokenTransferData,
getAmountErrorObject,
+ getGasFeeErrorObject,
getToAddressForGasUpdate,
calcTokenBalance,
isBalanceSufficient,
@@ -142,6 +148,18 @@ describe('send utils', () => {
primaryCurrency: 'ABC',
expectedResult: { amount: INSUFFICIENT_FUNDS_ERROR },
},
+ 'should not return insufficientFunds error if selectedToken is truthy': {
+ amount: '0x0',
+ amountConversionRate: 2,
+ balance: 1,
+ conversionRate: 3,
+ gasTotal: 17,
+ primaryCurrency: 'ABC',
+ selectedToken: { symbole: 'DEF', decimals: 0 },
+ decimals: 0,
+ tokenBalance: 'sometokenbalance',
+ expectedResult: { amount: null },
+ },
'should return insufficientTokens error if token is selected and isTokenBalanceSufficient returns false': {
amount: '0x10',
amountConversionRate: 2,
@@ -162,6 +180,32 @@ describe('send utils', () => {
})
})
+ describe('getGasFeeErrorObject()', () => {
+ const config = {
+ 'should return insufficientFunds error if isBalanceSufficient returns false': {
+ amountConversionRate: 2,
+ balance: 16,
+ conversionRate: 3,
+ gasTotal: 17,
+ primaryCurrency: 'ABC',
+ expectedResult: { gasFee: INSUFFICIENT_FUNDS_ERROR },
+ },
+ 'should return null error if isBalanceSufficient returns true': {
+ amountConversionRate: 2,
+ balance: 16,
+ conversionRate: 3,
+ gasTotal: 15,
+ primaryCurrency: 'ABC',
+ expectedResult: { gasFee: null },
+ },
+ }
+ Object.entries(config).map(([description, obj]) => {
+ it(description, () => {
+ assert.deepEqual(getGasFeeErrorObject(obj), obj.expectedResult)
+ })
+ })
+ })
+
describe('calcTokenBalance()', () => {
it('should return the calculated token blance', () => {
assert.equal(calcTokenBalance({
@@ -221,6 +265,7 @@ describe('send utils', () => {
describe('isTokenBalanceSufficient()', () => {
it('should correctly call conversionUtil and return the result of calling conversionGTE', () => {
stubs.conversionGTE.resetHistory()
+ stubs.conversionUtil.resetHistory()
const result = isTokenBalanceSufficient({
amount: '0x10',
tokenBalance: 123,
@@ -336,6 +381,11 @@ describe('send utils', () => {
assert.notEqual(result, SIMPLE_GAS_COST)
})
+ it(`should return ${BASE_TOKEN_GAS_COST} if passed a selectedToken but no to address`, async () => {
+ const result = await estimateGas(Object.assign({}, baseMockParams, { to: null, selectedToken: { address: '' } }))
+ assert.equal(result, BASE_TOKEN_GAS_COST)
+ })
+
it(`should return the adjusted blockGasLimit if it fails with a 'Transaction execution error.'`, async () => {
const result = await estimateGas(Object.assign({}, baseMockParams, {
to: 'isContract willFailBecauseOf:Transaction execution error.',
diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js
index 93d2023b5..2c4ba40bf 100644
--- a/ui/app/components/shapeshift-form.js
+++ b/ui/app/components/shapeshift-form.js
@@ -181,7 +181,7 @@ ShapeshiftForm.prototype.render = function () {
return h('div.shapeshift-form-wrapper', [
showQrCode
? this.renderQrCode()
- : h('div.shapeshift-form', [
+ : h('div.modal-shapeshift-form', [
h('div.shapeshift-form__selectors', [
h('div.shapeshift-form__selector', [
diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js
index 2a3e929fe..bbb340fcf 100644
--- a/ui/app/components/signature-request.js
+++ b/ui/app/components/signature-request.js
@@ -204,6 +204,9 @@ SignatureRequest.prototype.renderBody = function () {
h('div.request-signature__rows', [
...rows.map(({ name, value }) => {
+ if (typeof value === 'boolean') {
+ value = value.toString()
+ }
return h('div.request-signature__row', [
h('div.request-signature__row-title', [`${name}:`]),
h('div.request-signature__row-value', value),
diff --git a/ui/app/components/token-balance.js b/ui/app/components/token-balance.js
index df3bd59bb..99ca7335c 100644
--- a/ui/app/components/token-balance.js
+++ b/ui/app/components/token-balance.js
@@ -98,6 +98,10 @@ TokenBalance.prototype.componentDidUpdate = function (nextProps) {
}
TokenBalance.prototype.updateBalance = function (tokens = []) {
+ if (!this.tracker.running) {
+ return
+ }
+
const [{ string, symbol }] = tokens
this.setState({
@@ -110,5 +114,7 @@ TokenBalance.prototype.updateBalance = function (tokens = []) {
TokenBalance.prototype.componentWillUnmount = function () {
if (!this.tracker) return
this.tracker.stop()
+ this.tracker.removeListener('update', this.balanceUpdater)
+ this.tracker.removeListener('error', this.showError)
}
diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js
index 4189cf801..42351cf89 100644
--- a/ui/app/components/token-list.js
+++ b/ui/app/components/token-list.js
@@ -158,12 +158,17 @@ TokenList.prototype.componentDidUpdate = function (nextProps) {
}
TokenList.prototype.updateBalances = function (tokens) {
+ if (!this.tracker.running) {
+ return
+ }
this.setState({ tokens, isLoading: false })
}
TokenList.prototype.componentWillUnmount = function () {
if (!this.tracker) return
this.tracker.stop()
+ this.tracker.removeListener('update', this.balanceUpdater)
+ this.tracker.removeListener('error', this.showError)
}
// function uniqueMergeTokens (tokensA, tokensB = []) {
diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js
index 9a2fb5311..e539514ec 100644
--- a/ui/app/components/tx-list-item.js
+++ b/ui/app/components/tx-list-item.js
@@ -54,6 +54,8 @@ function TxListItem () {
fiatTotal: null,
isTokenTx: null,
}
+
+ this.unmounted = false
}
TxListItem.prototype.componentDidMount = async function () {
@@ -67,9 +69,16 @@ TxListItem.prototype.componentDidMount = async function () {
? await this.getSendTokenTotal()
: this.getSendEtherTotal()
+ if (this.unmounted) {
+ return
+ }
this.setState({ total, fiatTotal, isTokenTx })
}
+TxListItem.prototype.componentWillUnmount = function () {
+ this.unmounted = true
+}
+
TxListItem.prototype.getAddressText = function () {
const {
address,
diff --git a/ui/app/conversion-util.js b/ui/app/conversion-util.js
index 337763067..a7a226cc5 100644
--- a/ui/app/conversion-util.js
+++ b/ui/app/conversion-util.js
@@ -140,7 +140,7 @@ const addCurrencies = (a, b, options = {}) => {
bBase,
...conversionOptions
} = options
- const value = (new BigNumber(a, aBase)).add(b, bBase)
+ const value = (new BigNumber(a.toString(), aBase)).add(b.toString(), bBase)
return converter({
value,
diff --git a/ui/app/conversion-util.test.js b/ui/app/conversion-util.test.js
new file mode 100644
index 000000000..368ce3bba
--- /dev/null
+++ b/ui/app/conversion-util.test.js
@@ -0,0 +1,22 @@
+import assert from 'assert'
+import {addCurrencies} from './conversion-util'
+
+
+describe('conversion utils', () => {
+ describe('addCurrencies()', () => {
+ it('add whole numbers', () => {
+ const result = addCurrencies(3, 9)
+ assert.equal(result.toNumber(), 12)
+ })
+
+ it('add decimals', () => {
+ const result = addCurrencies(1.3, 1.9)
+ assert.equal(result.toNumber(), 3.2)
+ })
+
+ it('add repeating decimals', () => {
+ const result = addCurrencies(1 / 3, 1 / 9)
+ assert.equal(result.toNumber(), 0.4444444444444444)
+ })
+ })
+})
diff --git a/ui/app/css/itcss/components/currency-display.scss b/ui/app/css/itcss/components/currency-display.scss
index 3614aa868..b1a74dce2 100644
--- a/ui/app/css/itcss/components/currency-display.scss
+++ b/ui/app/css/itcss/components/currency-display.scss
@@ -48,15 +48,20 @@
display: flex;
flex: 1;
max-width: 100%;
- overflow-x: scroll;
+
+ input[type="number"] {
+ -moz-appearance: textfield;
+ }
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
+ -moz-appearance: none;
display: none;
}
input[type="number"]:hover::-webkit-inner-spin-button {
-webkit-appearance: none;
+ -moz-appearance: none;
display: none;
}
}
@@ -69,11 +74,13 @@
.react-numeric-input {
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
+ -moz-appearance: none;
display: none;
}
input[type="number"]:hover::-webkit-inner-spin-button {
-webkit-appearance: none;
+ -moz-appearance: none;
display: none;
}
}
diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss
index 74658f656..42ef7ae0a 100644
--- a/ui/app/css/itcss/components/modal.scss
+++ b/ui/app/css/itcss/components/modal.scss
@@ -642,10 +642,31 @@
display: flex;
flex-flow: column nowrap;
flex: 1;
+ align-items: center;
@media screen and (max-width: 575px) {
height: 0;
}
+
+ .shapeshift-form-wrapper {
+ display: flex;
+ flex-flow: column;
+ justify-content: center;
+ align-items: center;
+ flex: 1 0 auto;
+
+ .shapeshift-form, .modal-shapeshift-form {
+ border-radius: 8px;
+ background-color: rgba(0, 0, 0, .05);
+ padding: 17px 15px;
+ margin-bottom: 10px;
+
+ &__caret {
+ width: auto;
+ flex: 1;
+ }
+ }
+ }
}
&__logo {
@@ -773,17 +794,15 @@
margin-top: 28px;
flex: 1 0 auto;
- .shapeshift-form {
- width: auto;
+ .shapeshift-form, .modal-shapeshift-form {
+ border-radius: 8px;
+ background-color: rgba(0, 0, 0, .05);
+ padding: 17px 15px;
&__caret {
width: auto;
flex: 1;
}
-
- @media screen and (max-width: 575px) {
- width: auto;
- }
}
}
diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss
index 667e45ba2..bbfd85c90 100644
--- a/ui/app/css/itcss/components/newui-sections.scss
+++ b/ui/app/css/itcss/components/newui-sections.scss
@@ -332,3 +332,12 @@ $wallet-view-bg: $alabaster;
align-items: center;
flex: 1 0 auto;
}
+
+.first-view-main-wrapper {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ justify-content: center;
+ padding: 0 10px;
+ background: white;
+}
diff --git a/ui/app/css/itcss/components/request-signature.scss b/ui/app/css/itcss/components/request-signature.scss
index 4707ff60e..b607aded3 100644
--- a/ui/app/css/itcss/components/request-signature.scss
+++ b/ui/app/css/itcss/components/request-signature.scss
@@ -181,6 +181,7 @@
overflow-wrap: break-word;
border-bottom: 1px solid #d2d8dd;
padding: 6px 18px 15px;
+ white-space: pre-line;
}
&__help-link {
diff --git a/ui/app/css/itcss/components/token-list.scss b/ui/app/css/itcss/components/token-list.scss
index 4b706abce..49d0c290e 100644
--- a/ui/app/css/itcss/components/token-list.scss
+++ b/ui/app/css/itcss/components/token-list.scss
@@ -34,6 +34,7 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
&__fiat-amount {
margin-top: .25%;
font-size: 105%;
+ width: 100%;
text-transform: uppercase;
@media #{$wallet-balance-breakpoint-range} {
diff --git a/ui/app/ducks/send.duck.js b/ui/app/ducks/send.duck.js
index 055cc05c1..db01bbaa9 100644
--- a/ui/app/ducks/send.duck.js
+++ b/ui/app/ducks/send.duck.js
@@ -6,6 +6,7 @@ const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN'
const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN'
const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'
const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS'
+const RESET_SEND_STATE = 'metamask/send/RESET_SEND_STATE'
// TODO: determine if this approach to initState is consistent with conventional ducks pattern
const initState = {
@@ -42,6 +43,8 @@ export default function reducer ({ send: sendState = initState }, action = {}) {
...action.value,
},
})
+ case RESET_SEND_STATE:
+ return extend({}, initState)
default:
return newState
}
@@ -70,3 +73,7 @@ export function updateSendErrors (errorObject) {
value: errorObject,
}
}
+
+export function resetSendState () {
+ return { type: RESET_SEND_STATE }
+}
diff --git a/ui/app/ducks/tests/send-duck.test.js b/ui/app/ducks/tests/send-duck.test.js
index c06cf55d2..c101132d9 100644
--- a/ui/app/ducks/tests/send-duck.test.js
+++ b/ui/app/ducks/tests/send-duck.test.js
@@ -24,6 +24,7 @@ describe('Send Duck', () => {
const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN'
const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'
const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS'
+ const RESET_SEND_STATE = 'metamask/send/RESET_SEND_STATE'
describe('SendReducer()', () => {
it('should initialize state', () => {
@@ -105,6 +106,15 @@ describe('Send Duck', () => {
})
)
})
+
+ it('should return the initial state in response to a RESET_SEND_STATE action', () => {
+ assert.deepEqual(
+ SendReducer(mockState, {
+ type: RESET_SEND_STATE,
+ }),
+ Object.assign({}, initState)
+ )
+ })
})
describe('openFromDropdown', () => {
diff --git a/ui/app/helpers/with-token-tracker.js b/ui/app/helpers/with-token-tracker.js
index e24517c18..8608b15f4 100644
--- a/ui/app/helpers/with-token-tracker.js
+++ b/ui/app/helpers/with-token-tracker.js
@@ -75,6 +75,9 @@ const withTokenTracker = WrappedComponent => {
}
updateBalance (tokens = []) {
+ if (!this.tracker.running) {
+ return
+ }
const [{ string, symbol }] = tokens
this.setState({ string, symbol, error: null })
}
diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js
index 4e9d0848c..f453812b9 100644
--- a/ui/app/reducers/app.js
+++ b/ui/app/reducers/app.js
@@ -62,6 +62,7 @@ function reduceApp (state, action) {
warning: null,
buyView: {},
isMouseUser: false,
+ gasIsLoading: false,
}, state.appState)
switch (action.type) {
@@ -675,6 +676,16 @@ function reduceApp (state, action) {
isMouseUser: action.value,
})
+ case actions.GAS_LOADING_STARTED:
+ return extend(appState, {
+ gasIsLoading: true,
+ })
+
+ case actions.GAS_LOADING_FINISHED:
+ return extend(appState, {
+ gasIsLoading: false,
+ })
+
default:
return appState
}
diff --git a/ui/app/selectors.js b/ui/app/selectors.js
index a29294b86..3e2253550 100644
--- a/ui/app/selectors.js
+++ b/ui/app/selectors.js
@@ -16,8 +16,7 @@ const selectors = {
transactionsSelector,
accountsWithSendEtherInfoSelector,
getCurrentAccountWithSendEtherInfo,
- getGasPrice,
- getGasLimit,
+ getGasIsLoading,
getForceGasMin,
getAddressBook,
getSendFrom,
@@ -117,12 +116,8 @@ function transactionsSelector (state) {
.sort((a, b) => b.time - a.time)
}
-function getGasPrice (state) {
- return state.metamask.send.gasPrice
-}
-
-function getGasLimit (state) {
- return state.metamask.send.gasLimit
+function getGasIsLoading (state) {
+ return state.appState.gasIsLoading
}
function getForceGasMin (state) {
diff --git a/ui/app/token-util.js b/ui/app/token-util.js
index 8c5b37d7b..cd6a47dbc 100644
--- a/ui/app/token-util.js
+++ b/ui/app/token-util.js
@@ -20,7 +20,7 @@ async function getSymbolAndDecimals (tokenAddress, existingTokens = []) {
if (existingToken) {
return existingToken
}
-
+
let result = []
try {
const token = util.getContractAtAddress(tokenAddress)
diff --git a/ui/app/util.js b/ui/app/util.js
index 1ccd17ba7..8c85c5926 100644
--- a/ui/app/util.js
+++ b/ui/app/util.js
@@ -36,6 +36,7 @@ module.exports = {
miniAddressSummary: miniAddressSummary,
isAllOneCase: isAllOneCase,
isValidAddress: isValidAddress,
+ isValidENSAddress,
numericBalance: numericBalance,
parseBalance: parseBalance,
formatBalance: formatBalance,
@@ -87,6 +88,10 @@ function isValidAddress (address) {
return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed)
}
+function isValidENSAddress (address) {
+ return address.match(/^.{7,}\.(eth|test)$/)
+}
+
function isInvalidChecksumAddress (address) {
var prefixed = ethUtil.addHexPrefix(address)
if (address === '0x0000000000000000000000000000000000000000') return false