diff options
Merge branch 'master' of github.com:MetaMask/metamask-extension into i18n-translator-redux
Diffstat (limited to 'ui')
30 files changed, 574 insertions, 163 deletions
diff --git a/ui/app/accounts/import/index.js b/ui/app/accounts/import/index.js index 98924b6d9..00b1aab8e 100644 --- a/ui/app/accounts/import/index.js +++ b/ui/app/accounts/import/index.js @@ -36,7 +36,7 @@ AccountImportSubview.prototype.render = function () { h('div.new-account-import-form', [ h('.new-account-import-disclaimer', [ - h('span', 'Imported accounts will not be associated with your originally created MetaMask account seedphrase. Learn more about imported accounts '), + h('span', t('importAccountMsg')), h('span', { style: { cursor: 'pointer', @@ -47,12 +47,12 @@ AccountImportSubview.prototype.render = function () { url: 'https://metamask.helpscoutdocs.com/article/17-what-are-loose-accounts', }) }, - }, 'here'), + }, t('here')), ]), h('div.new-account-import-form__select-section', [ - h('div.new-account-import-form__select-label', 'Select Type'), + h('div.new-account-import-form__select-label', t('selectType')), h(Select, { className: 'new-account-import-form__select', diff --git a/ui/app/accounts/import/json.js b/ui/app/accounts/import/json.js index f638e07c5..326e052f2 100644 --- a/ui/app/accounts/import/json.js +++ b/ui/app/accounts/import/json.js @@ -85,7 +85,7 @@ class JsonImportSubview extends Component { const state = this.state if (!state) { - const message = 'You must select a valid file to import.' + const message = t('validFileImport') return this.props.displayWarning(message) } diff --git a/ui/app/actions.js b/ui/app/actions.js index 12cf10411..69c13ff88 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1286,8 +1286,10 @@ function retryTransaction (txId) { if (err) { return dispatch(actions.displayWarning(err.message)) } + const { selectedAddressTxList } = newState + const { id: newTxId } = selectedAddressTxList[selectedAddressTxList.length - 1] dispatch(actions.updateMetamaskState(newState)) - dispatch(actions.viewPendingTx(txId)) + dispatch(actions.viewPendingTx(newTxId)) }) } } diff --git a/ui/app/add-token.js b/ui/app/add-token.js index 917a7c9fb..9576a7aef 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -26,6 +26,7 @@ const fuse = new Fuse(contractList, { const actions = require('./actions') const ethUtil = require('ethereumjs-util') const { tokenInfoGetter } = require('./token-util') +const t = require('../i18n') const emptyAddr = '0x0000000000000000000000000000000000000000' @@ -139,28 +140,28 @@ AddTokenScreen.prototype.validate = function () { if (customAddress) { const validAddress = ethUtil.isValidAddress(customAddress) if (!validAddress) { - errors.customAddress = 'Address is invalid. ' + errors.customAddress = t('invalidAddress') } const validDecimals = customDecimals !== null && customDecimals >= 0 && customDecimals < 36 if (!validDecimals) { - errors.customDecimals = 'Decimals must be at least 0, and not over 36.' + errors.customDecimals = t('decimalsMustZerotoTen') } const symbolLen = customSymbol.trim().length const validSymbol = symbolLen > 0 && symbolLen < 10 if (!validSymbol) { - errors.customSymbol = 'Symbol must be between 0 and 10 characters.' + errors.customSymbol = t('symbolBetweenZeroTen') } const ownAddress = identitiesList.includes(standardAddress) if (ownAddress) { - errors.customAddress = 'Personal address detected. Input the token contract address.' + errors.customAddress = t('personalAddressDetected') } const tokenAlreadyAdded = this.checkExistingAddresses(customAddress) if (tokenAlreadyAdded) { - errors.customAddress = 'Token has already been added.' + errors.customAddress = t('tokenAlreadyAdded') } } else if ( Object.entries(selectedTokens) @@ -168,7 +169,7 @@ AddTokenScreen.prototype.validate = function () { isEmpty && !isSelected ), true) ) { - errors.tokenSelector = 'Must select at least 1 token.' + errors.tokenSelector = t('mustSelectOne') } return { @@ -198,7 +199,7 @@ AddTokenScreen.prototype.renderCustomForm = function () { 'add-token__add-custom-field--error': errors.customAddress, }), }, [ - h('div.add-token__add-custom-label', 'Token Address'), + h('div.add-token__add-custom-label', t('tokenAddress')), h('input.add-token__add-custom-input', { type: 'text', onChange: this.tokenAddressDidChange, @@ -211,7 +212,7 @@ AddTokenScreen.prototype.renderCustomForm = function () { 'add-token__add-custom-field--error': errors.customSymbol, }), }, [ - h('div.add-token__add-custom-label', 'Token Symbol'), + h('div.add-token__add-custom-label', t('tokenSymbol')), h('input.add-token__add-custom-input', { type: 'text', onChange: this.tokenSymbolDidChange, @@ -225,7 +226,7 @@ AddTokenScreen.prototype.renderCustomForm = function () { 'add-token__add-custom-field--error': errors.customDecimals, }), }, [ - h('div.add-token__add-custom-label', 'Decimals of Precision'), + h('div.add-token__add-custom-label', t('decimal')), h('input.add-token__add-custom-input', { type: 'number', onChange: this.tokenDecimalsDidChange, @@ -299,11 +300,11 @@ AddTokenScreen.prototype.renderConfirmation = function () { h('div.add-token', [ h('div.add-token__wrapper', [ h('div.add-token__title-container.add-token__confirmation-title', [ - h('div.add-token__title', 'Add Token'), - h('div.add-token__description', 'Would you like to add these tokens?'), + h('div.add-token__title', t('addToken')), + h('div.add-token__description', t('likeToAddTokens')), ]), h('div.add-token__content-container.add-token__confirmation-content', [ - h('div.add-token__description.add-token__confirmation-description', 'Your balances'), + h('div.add-token__description.add-token__confirmation-description', t('balances')), h('div.add-token__confirmation-token-list', Object.entries(tokens) .map(([ address, token ]) => ( @@ -322,10 +323,10 @@ AddTokenScreen.prototype.renderConfirmation = function () { h('div.add-token__buttons', [ h('button.btn-cancel.add-token__button', { onClick: () => this.setState({ isShowingConfirmation: false }), - }, 'Back'), + }, t('back')), h('button.btn-clear.add-token__button', { onClick: () => addTokens(tokens).then(goHome), - }, 'Add Tokens'), + }, t('addTokens')), ]), ]) ) @@ -341,15 +342,15 @@ AddTokenScreen.prototype.render = function () { h('div.add-token', [ h('div.add-token__wrapper', [ h('div.add-token__title-container', [ - h('div.add-token__title', 'Add Token'), - h('div.add-token__description', 'Keep track of the tokens you’ve bought with your MetaMask account. If you bought tokens using a different account, those tokens will not appear here.'), - h('div.add-token__description', 'Search for tokens or select from our list of popular tokens.'), + h('div.add-token__title', t('addToken')), + h('div.add-token__description', t('tokenWarning1')), + h('div.add-token__description', t('tokenSelection')), ]), h('div.add-token__content-container', [ h('div.add-token__input-container', [ h('input.add-token__input', { type: 'text', - placeholder: 'Search', + placeholder: t('search'), onChange: e => this.setState({ searchQuery: e.target.value }), }), h('div.add-token__search-input-error-message', errors.tokenSelector), @@ -363,7 +364,7 @@ AddTokenScreen.prototype.render = function () { h('div.add-token__add-custom', { onClick: () => this.setState({ isCollapsed: !isCollapsed }), }, [ - 'Add custom token', + t('addCustomToken'), h(`i.fa.fa-angle-${isCollapsed ? 'down' : 'up'}`), ]), this.renderCustomForm(), @@ -372,10 +373,10 @@ AddTokenScreen.prototype.render = function () { h('div.add-token__buttons', [ h('button.btn-cancel.add-token__button', { onClick: goHome, - }, 'Cancel'), + }, t('cancel')), h('button.btn-clear.add-token__button', { onClick: this.onNext, - }, 'Next'), + }, t('next')), ]), ]) ) diff --git a/ui/app/app.js b/ui/app/app.js index a1f402d85..f69efd1c7 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -132,7 +132,7 @@ App.prototype.render = function () { } = props const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config' const loadMessage = loadingMessage || isLoadingNetwork ? - `Connecting to ${this.getNetworkName()}` : null + this.getConnectingLabel() : null log.debug('Main ui render function') return ( @@ -550,6 +550,27 @@ App.prototype.toggleMetamaskActive = function () { } } +App.prototype.getConnectingLabel = function () { + const { provider } = this.props + const providerName = provider.type + + let name + + if (providerName === 'mainnet') { + name = t('connectingToMainnet') + } else if (providerName === 'ropsten') { + name = t('connectingToRopsten') + } else if (providerName === 'kovan') { + name = t('connectingToRopsten') + } else if (providerName === 'rinkeby') { + name = t('connectingToRinkeby') + } else { + name = t('connectingToUnknown') + } + + return name +} + App.prototype.getNetworkName = function () { const { provider } = this.props const providerName = provider.type @@ -557,15 +578,15 @@ App.prototype.getNetworkName = function () { let name if (providerName === 'mainnet') { - name = 'Main Ethereum Network' + name = t('mainnet') } else if (providerName === 'ropsten') { - name = 'Ropsten Test Network' + name = t('ropsten') } else if (providerName === 'kovan') { - name = 'Kovan Test Network' + name = t('kovan') } else if (providerName === 'rinkeby') { - name = 'Rinkeby Test Network' + name = t('rinkeby') } else { - name = 'Unknown Private Network' + name = t('unknownNetwork') } return name diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index ed0a3b45e..e44675880 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -22,12 +22,14 @@ const { conversionUtil, multiplyCurrencies, conversionGreaterThan, + conversionMax, subtractCurrencies, } = require('../../conversion-util') const { getGasPrice, getGasLimit, + getForceGasMin, conversionRateSelector, getSendAmount, getSelectedToken, @@ -45,6 +47,7 @@ function mapStateToProps (state) { return { gasPrice: getGasPrice(state), gasLimit: getGasLimit(state), + forceGasMin: getForceGasMin(state), conversionRate, amount: getSendAmount(state), maxModeOn: getSendMaxModeState(state), @@ -115,9 +118,9 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { updateSendAmount(maxAmount) } - updateGasPrice(gasPrice) - updateGasLimit(gasLimit) - updateGasTotal(gasTotal) + updateGasPrice(ethUtil.addHexPrefix(gasPrice)) + updateGasLimit(ethUtil.addHexPrefix(gasLimit)) + updateGasTotal(ethUtil.addHexPrefix(gasTotal)) hideModal() } @@ -218,7 +221,7 @@ CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) { } CustomizeGasModal.prototype.render = function () { - const { hideModal } = this.props + const { hideModal, forceGasMin } = this.props const { gasPrice, gasLimit, gasTotal, error, priceSigZeros, priceSigDec } = this.state let convertedGasPrice = conversionUtil(gasPrice, { @@ -230,6 +233,22 @@ CustomizeGasModal.prototype.render = function () { convertedGasPrice += convertedGasPrice.match(/[.]/) ? priceSigZeros : `${priceSigDec}${priceSigZeros}` + let newGasPrice = gasPrice + if (forceGasMin) { + const convertedMinPrice = conversionUtil(forceGasMin, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + }) + convertedGasPrice = conversionMax( + { value: convertedMinPrice, fromNumericBase: 'dec' }, + { value: convertedGasPrice, fromNumericBase: 'dec' } + ) + newGasPrice = conversionMax( + { value: gasPrice, fromNumericBase: 'hex' }, + { value: forceGasMin, fromNumericBase: 'hex' } + ) + } + const convertedGasLimit = conversionUtil(gasLimit, { fromNumericBase: 'hex', toNumericBase: 'dec', @@ -252,7 +271,7 @@ CustomizeGasModal.prototype.render = function () { h(GasModalCard, { value: convertedGasPrice, - min: MIN_GAS_PRICE_GWEI, + min: forceGasMin || MIN_GAS_PRICE_GWEI, // max: 1000, step: multiplyCurrencies(MIN_GAS_PRICE_GWEI, 10), onChange: value => this.convertAndSetGasPrice(value), @@ -288,7 +307,7 @@ CustomizeGasModal.prototype.render = function () { }, [t(this.props.localeMessages, 'cancel')]), h(`div.send-v2__customize-gas__save${error ? '__error' : ''}.allcaps`, { - onClick: () => !error && this.save(gasPrice, gasLimit, gasTotal), + onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal), }, [t(this.props.localeMessages, 'save')]), ]), diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index 3a72f575f..253b69b7a 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -8,7 +8,12 @@ const Identicon = require('../identicon') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN const hexToBn = require('../../../../app/scripts/lib/hex-to-bn') -const { conversionUtil, addCurrencies } = require('../../conversion-util') +const { + conversionUtil, + addCurrencies, + multiplyCurrencies, +} = require('../../conversion-util') +const GasFeeDisplay = require('../send/gas-fee-display-v2') const t = require('../../../i18n-helper').getMessage const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') @@ -44,6 +49,7 @@ function mapDispatchToProps (dispatch) { to, value: amount, } = txParams + dispatch(actions.updateSend({ gasLimit, gasPrice, @@ -56,6 +62,29 @@ function mapDispatchToProps (dispatch) { dispatch(actions.showSendPage()) }, cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })), + showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => { + const { id, txParams, lastGasPrice } = txMeta + const { gas: txGasLimit, gasPrice: txGasPrice } = txParams + + let forceGasMin + if (lastGasPrice) { + forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { + multiplicandBase: 16, + multiplierBase: 10, + toNumericBase: 'hex', + fromDenomination: 'WEI', + })) + } + + dispatch(actions.updateSend({ + gasLimit: sendGasLimit || txGasLimit, + gasPrice: sendGasPrice || txGasPrice, + editingTransactionId: id, + gasTotal: sendGasTotal, + forceGasMin, + })) + dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) + }, } } @@ -140,6 +169,7 @@ ConfirmSendEther.prototype.getGasFee = function () { return { FIAT, ETH, + gasFeeInHex: txFeeBn.toString(16), } } @@ -147,7 +177,7 @@ ConfirmSendEther.prototype.getData = function () { const { identities } = this.props const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} - const { FIAT: gasFeeInFIAT, ETH: gasFeeInETH } = this.getGasFee() + const { FIAT: gasFeeInFIAT, ETH: gasFeeInETH, gasFeeInHex } = this.getGasFee() const { FIAT: amountInFIAT, ETH: amountInETH } = this.getAmount() const totalInFIAT = addCurrencies(gasFeeInFIAT, amountInFIAT, { @@ -175,11 +205,20 @@ ConfirmSendEther.prototype.getData = function () { amountInETH, totalInFIAT, totalInETH, + gasFeeInHex, } } ConfirmSendEther.prototype.render = function () { - const { editTransaction, currentCurrency, clearSend } = this.props + const { + editTransaction, + currentCurrency, + clearSend, + conversionRate, + currentCurrency: convertedCurrency, + showCustomizeGasModal, + send: { gasTotal, gasLimit: sendGasLimit, gasPrice: sendGasPrice }, + } = this.props const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} @@ -193,13 +232,17 @@ ConfirmSendEther.prototype.render = function () { name: toName, }, memo, - gasFeeInFIAT, - gasFeeInETH, + gasFeeInHex, amountInFIAT, totalInFIAT, totalInETH, } = this.getData() + const title = txMeta.lastGasPrice ? 'Reprice Transaction' : 'Confirm' + const subtitle = txMeta.lastGasPrice + ? 'Increase your gas fee to attempt to overwrite and speed up your transaction' + : 'Please review your transaction.' + // This is from the latest master // It handles some of the errors that we are not currently handling // Leaving as comments fo reference @@ -218,11 +261,11 @@ ConfirmSendEther.prototype.render = function () { // Main Send token Card h('div.page-container', [ h('div.page-container__header', [ - h('button.confirm-screen-back-button', { + !txMeta.lastGasPrice && h('button.confirm-screen-back-button', { onClick: () => editTransaction(txMeta), }, 'Edit'), - h('div.page-container__title', 'Confirm'), - h('div.page-container__subtitle', `Please review your transaction.`), + h('div.page-container__title', title), + h('div.page-container__subtitle', subtitle), ]), h('.page-container__content', [ h('div.flex-row.flex-center.confirm-screen-identicons', [ @@ -286,9 +329,12 @@ ConfirmSendEther.prototype.render = function () { h('section.flex-row.flex-center.confirm-screen-row', [ h('span.confirm-screen-label.confirm-screen-section-column', [ t(this.props.localeMessages, 'gasFee') ]), h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', `${gasFeeInFIAT} ${currentCurrency.toUpperCase()}`), - - h('div.confirm-screen-row-detail', `${gasFeeInETH} ETH`), + h(GasFeeDisplay, { + gasTotal: gasTotal || gasFeeInHex, + conversionRate, + convertedCurrency, + onClick: () => showCustomizeGasModal(txMeta, sendGasLimit, sendGasPrice, gasTotal), + }), ]), ]), @@ -449,6 +495,27 @@ ConfirmSendEther.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) + const { gasPrice: sendGasPrice, gas: sendGasLimit } = props.send + const { + lastGasPrice, + txParams: { + gasPrice: txGasPrice, + gas: txGasLimit, + }, + } = txData + + let forceGasMin + if (lastGasPrice) { + forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { + multiplicandBase: 16, + multiplierBase: 10, + toNumericBase: 'hex', + })) + } + + txData.txParams.gasPrice = sendGasPrice || forceGasMin || txGasPrice + txData.txParams.gas = sendGasLimit || txGasLimit + // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData } diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 4cd6f020b..54963ae9a 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -9,6 +9,7 @@ const actions = require('../../actions') const t = require('../../../i18n-helper').getMessage const clone = require('clone') const Identicon = require('../identicon') +const GasFeeDisplay = require('../send/gas-fee-display-v2.js') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN const { @@ -89,6 +90,39 @@ function mapDispatchToProps (dispatch, ownProps) { })) dispatch(actions.showSendTokenPage()) }, + showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => { + const { id, txParams, lastGasPrice } = txMeta + const { gas: txGasLimit, gasPrice: txGasPrice } = txParams + const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data) + const { params = [] } = tokenData + const { value: to } = params[0] || {} + const { value: tokenAmountInDec } = params[1] || {} + const tokenAmountInHex = conversionUtil(tokenAmountInDec, { + fromNumericBase: 'dec', + toNumericBase: 'hex', + }) + + let forceGasMin + if (lastGasPrice) { + forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { + multiplicandBase: 16, + multiplierBase: 10, + toNumericBase: 'hex', + fromDenomination: 'WEI', + })) + } + + dispatch(actions.updateSend({ + gasLimit: sendGasLimit || txGasLimit, + gasPrice: sendGasPrice || txGasPrice, + editingTransactionId: id, + gasTotal: sendGasTotal, + to, + amount: tokenAmountInHex, + forceGasMin, + })) + dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) + }, } } @@ -188,6 +222,7 @@ ConfirmSendToken.prototype.getGasFee = function () { token: tokenExchangeRate ? tokenGas : null, + gasFeeInHex: gasTotal.toString(16), } } @@ -240,19 +275,25 @@ ConfirmSendToken.prototype.renderHeroAmount = function () { } ConfirmSendToken.prototype.renderGasFee = function () { - const { token: { symbol }, currentCurrency } = this.props - const { fiat: fiatGas, token: tokenGas, eth: ethGas } = this.getGasFee() + const { + currentCurrency: convertedCurrency, + conversionRate, + send: { gasTotal, gasLimit: sendGasLimit, gasPrice: sendGasPrice }, + showCustomizeGasModal, + } = this.props + const txMeta = this.gatherTxMeta() + const { gasFeeInHex } = this.getGasFee() return ( h('section.flex-row.flex-center.confirm-screen-row', [ h('span.confirm-screen-label.confirm-screen-section-column', [ t(this.props.localeMessages, 'gasFee') ]), h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', `${fiatGas} ${currentCurrency}`), - - h( - 'div.confirm-screen-row-detail', - tokenGas ? `${tokenGas} ${symbol}` : `${ethGas} ETH` - ), + h(GasFeeDisplay, { + gasTotal: gasTotal || gasFeeInHex, + conversionRate, + convertedCurrency, + onClick: () => showCustomizeGasModal(txMeta, sendGasLimit, sendGasPrice, gasTotal), + }), ]), ]) ) @@ -308,16 +349,22 @@ ConfirmSendToken.prototype.render = function () { this.inputs = [] + const isTxReprice = Boolean(txMeta.lastGasPrice) + const title = isTxReprice ? t(this.props.localeMessages, 'reprice:title') : t(this.props.localeMessages, 'confirm') + const subtitle = isTxReprice + ? t(this.props.localeMessages, 'reprice:subtitle') + : t(this.props.localeMessages, 'pleaseReviewTransaction') + return ( h('div.confirm-screen-container.confirm-send-token', [ // Main Send token Card h('div.page-container', [ h('div.page-container__header', [ - h('button.confirm-screen-back-button', { + !txMeta.lastGasPrice && h('button.confirm-screen-back-button', { onClick: () => editTransaction(txMeta), }, t(this.props.localeMessages, 'edit')), - h('div.page-container__title', t(this.props.localeMessages, 'confirm')), - h('div.page-container__subtitle', t(this.props.localeMessages, 'pleaseReviewTransaction')), + h('div.page-container__title', title), + h('div.page-container__subtitle', subtitle), ]), h('.page-container__content', [ h('div.flex-row.flex-center.confirm-screen-identicons', [ @@ -379,7 +426,7 @@ ConfirmSendToken.prototype.render = function () { this.renderTotalPlusGas(), ]), - + ]), h('form#pending-tx-form', { @@ -442,6 +489,27 @@ ConfirmSendToken.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) + const { gasPrice: sendGasPrice, gas: sendGasLimit } = props.send + const { + lastGasPrice, + txParams: { + gasPrice: txGasPrice, + gas: txGasLimit, + }, + } = txData + + let forceGasMin + if (lastGasPrice) { + forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { + multiplicandBase: 16, + multiplierBase: 10, + toNumericBase: 'hex', + })) + } + + txData.txParams.gasPrice = sendGasPrice || forceGasMin || txGasPrice + txData.txParams.gas = sendGasLimit || txGasLimit + // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData } diff --git a/ui/app/components/send/gas-fee-display-v2.js b/ui/app/components/send/gas-fee-display-v2.js index c156e4482..04a43df2f 100644 --- a/ui/app/components/send/gas-fee-display-v2.js +++ b/ui/app/components/send/gas-fee-display-v2.js @@ -18,6 +18,7 @@ GasFeeDisplay.prototype.render = function () { onClick, primaryCurrency = 'ETH', convertedCurrency, + gasLoadingError, } = this.props return h('div.send-v2__gas-fee-display', [ @@ -31,13 +32,15 @@ GasFeeDisplay.prototype.render = function () { convertedPrefix: '$', readOnly: true, }) - : h('div.currency-display', t(this.props.localeMessages, 'loading')), + : gasLoadingError + ? h('div.currency-display.currency-display--message', t(this.props.localeMessages, 'setGasPrice')) + : h('div.currency-display', t(this.props.localeMessages, 'loading')), - h('button.send-v2__sliders-icon-container', { + h('button.sliders-icon-container', { onClick, - disabled: !gasTotal, + disabled: !gasTotal && !gasLoadingError, }, [ - h('i.fa.fa-sliders.send-v2__sliders-icon'), + h('i.fa.fa-sliders.sliders-icon'), ]), ]) diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js index a2a3ed389..25902cfce 100644 --- a/ui/app/components/send/send-v2-container.js +++ b/ui/app/components/send/send-v2-container.js @@ -48,6 +48,7 @@ function mapStateToProps (state) { primaryCurrency, convertedCurrency: getCurrentCurrency(state), data, + selectedAddress, amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate, tokenContract: getSelectedTokenContract(state), unapprovedTxs: state.metamask.unapprovedTxs, diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index db8621434..3819de195 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -9,19 +9,28 @@ abiDecoder.addABI(abi) const Identicon = require('./identicon') const contractMap = require('eth-contract-metadata') +const actions = require('../actions') const { conversionUtil, multiplyCurrencies } = require('../conversion-util') const { calcTokenAmount } = require('../token-util') const { getCurrentCurrency } = require('../selectors') const t = require('../../i18n-helper').getMessage -module.exports = connect(mapStateToProps)(TxListItem) +module.exports = connect(mapStateToProps, mapDispatchToProps)(TxListItem) function mapStateToProps (state) { return { tokens: state.metamask.tokens, currentCurrency: getCurrentCurrency(state), tokenExchangeRates: state.metamask.tokenExchangeRates, + selectedAddressTxList: state.metamask.selectedAddressTxList, + } +} + +function mapDispatchToProps (dispatch) { + return { + setSelectedToken: tokenAddress => dispatch(actions.setSelectedToken(tokenAddress)), + retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)), } } @@ -32,6 +41,7 @@ function TxListItem () { this.state = { total: null, fiatTotal: null, + isTokenTx: null, } } @@ -40,12 +50,13 @@ TxListItem.prototype.componentDidMount = async function () { const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data) const { name: txDataName } = decodedData || {} + const isTokenTx = txDataName === 'transfer' - const { total, fiatTotal } = txDataName === 'transfer' + const { total, fiatTotal } = isTokenTx ? await this.getSendTokenTotal() : this.getSendEtherTotal() - this.setState({ total, fiatTotal }) + this.setState({ total, fiatTotal, isTokenTx }) } TxListItem.prototype.getAddressText = function () { @@ -168,22 +179,49 @@ TxListItem.prototype.getSendTokenTotal = async function () { } } +TxListItem.prototype.showRetryButton = function () { + const { + transactionSubmittedTime, + selectedAddressTxList, + transactionId, + txParams, + } = this.props + const currentNonce = txParams.nonce + const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce) + const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') + const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[currentNonceSubmittedTxs.length - 1] + const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce + && lastSubmittedTxWithCurrentNonce.id === transactionId + + return currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000 +} + +TxListItem.prototype.setSelectedToken = function (tokenAddress) { + this.props.setSelectedToken(tokenAddress) +} + +TxListItem.prototype.resubmit = function () { + const { transactionId } = this.props + this.props.retryTransaction(transactionId) +} + TxListItem.prototype.render = function () { const { transactionStatus, transactionAmount, onClick, - transActionId, + transactionId, dateString, address, className, + txParams, } = this.props - const { total, fiatTotal } = this.state + const { total, fiatTotal, isTokenTx } = this.state const showFiatTotal = transactionAmount !== '0x0' && fiatTotal return h(`div${className || ''}`, { - key: transActionId, - onClick: () => onClick && onClick(transActionId), + key: transactionId, + onClick: () => onClick && onClick(transactionId), }, [ h(`div.flex-column.tx-list-item-wrapper`, {}, [ @@ -224,9 +262,10 @@ TxListItem.prototype.render = function () { className: classnames('tx-list-status', { 'tx-list-status--rejected': transactionStatus === 'rejected', 'tx-list-status--failed': transactionStatus === 'failed', + 'tx-list-status--dropped': transactionStatus === 'dropped', }), }, - transactionStatus, + this.txStatusIndicator(), ), ]), ]), @@ -241,6 +280,48 @@ TxListItem.prototype.render = function () { ]), ]), + + this.showRetryButton() && h('div.tx-list-item-retry-container', [ + + h('span.tx-list-item-retry-copy', 'Taking too long?'), + + h('span.tx-list-item-retry-link', { + onClick: (event) => { + event.stopPropagation() + if (isTokenTx) { + this.setSelectedToken(txParams.to) + } + this.resubmit() + }, + }, 'Increase the gas price on your transaction'), + + ]), + ]), // holding on icon from design ]) } + +TxListItem.prototype.txStatusIndicator = function () { + const { transactionStatus } = this.props + + let name + + if (transactionStatus === 'unapproved') { + name = t('unapproved') + } else if (transactionStatus === 'rejected') { + name = t('rejected') + } else if (transactionStatus === 'approved') { + name = t('approved') + } else if (transactionStatus === 'signed') { + name = t('signed') + } else if (transactionStatus === 'submitted') { + name = t('submitted') + } else if (transactionStatus === 'confirmed') { + name = t('confirmed') + } else if (transactionStatus === 'failed') { + name = t('failed') + } else if (transactionStatus === 'dropped') { + name = t('dropped') + } + return name +} diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js index d536b2806..d8c882ae2 100644 --- a/ui/app/components/tx-list.js +++ b/ui/app/components/tx-list.js @@ -40,7 +40,7 @@ TxList.prototype.render = function () { return h('div.flex-column', [ h('div.flex-row.tx-list-header-wrapper', [ h('div.flex-row.tx-list-header', [ - h('div', 'transactions'), + h('div', t('transactions')), ]), ]), h('div.flex-column.tx-list-container', {}, [ @@ -75,9 +75,10 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa address: transaction.txParams.to, transactionStatus: transaction.status, transactionAmount: transaction.txParams.value, - transActionId: transaction.id, + transactionId: transaction.id, transactionHash: transaction.hash, transactionNetworkId: transaction.metamaskNetworkId, + transactionSubmittedTime: transaction.submittedTime, } const { @@ -85,29 +86,31 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa transactionStatus, transactionAmount, dateString, - transActionId, + transactionId, transactionHash, transactionNetworkId, + transactionSubmittedTime, } = props const { showConfTxPage } = this.props const opts = { - key: transActionId || transactionHash, + key: transactionId || transactionHash, txParams: transaction.txParams, transactionStatus, - transActionId, + transactionId, dateString, address, transactionAmount, transactionHash, conversionRate, tokenInfoGetter: this.tokenInfoGetter, + transactionSubmittedTime, } const isUnapproved = transactionStatus === 'unapproved' if (isUnapproved) { - opts.onClick = () => showConfTxPage({id: transActionId}) + opts.onClick = () => showConfTxPage({ id: transactionId }) opts.transactionStatus = t(this.props.localeMessages, 'Not Started') } else if (transactionHash) { opts.onClick = () => this.view(transactionHash, transactionNetworkId) diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index cbf5cd1d2..03848f490 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -40,6 +40,7 @@ function mapStateToProps (state) { currentCurrency: state.metamask.currentCurrency, blockGasLimit: state.metamask.currentBlockGasLimit, computedBalances: state.metamask.computedBalances, + selectedAddressTxList: state.metamask.selectedAddressTxList, } } @@ -48,6 +49,23 @@ function ConfirmTxScreen () { Component.call(this) } +ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) { + const { + unapprovedTxs, + network, + selectedAddressTxList, + } = this.props + const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps + const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network) + const prevTxData = prevUnconfTxList[prevIndex] || {} + const prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {} + const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) + + if (prevTx.status === 'dropped' && unconfTxList.length === 0) { + this.goHome({}) + } +} + ConfirmTxScreen.prototype.render = function () { const props = this.props const { diff --git a/ui/app/conversion-util.js b/ui/app/conversion-util.js index ee42ebea1..d484ed16d 100644 --- a/ui/app/conversion-util.js +++ b/ui/app/conversion-util.js @@ -187,6 +187,18 @@ const conversionGreaterThan = ( return firstValue.gt(secondValue) } +const conversionMax = ( + { ...firstProps }, + { ...secondProps }, +) => { + const firstIsGreater = conversionGreaterThan( + { ...firstProps }, + { ...secondProps } + ) + + return firstIsGreater ? firstProps.value : secondProps.value +} + const conversionGTE = ( { ...firstProps }, { ...secondProps }, @@ -216,6 +228,7 @@ module.exports = { conversionGreaterThan, conversionGTE, conversionLTE, + conversionMax, toNegative, subtractCurrencies, } diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss index 4752741aa..c4037d862 100644 --- a/ui/app/css/itcss/components/account-menu.scss +++ b/ui/app/css/itcss/components/account-menu.scss @@ -87,7 +87,6 @@ flex: 1 0 auto; display: flex; flex-flow: column nowrap; - padding-top: 4px; } &__check-mark { @@ -115,13 +114,11 @@ color: $white; font-size: 18px; font-weight: 300; - line-height: 16px; } &__balance { color: $dusty-gray; font-size: 14px; - line-height: 19px; } &__action { diff --git a/ui/app/css/itcss/components/confirm.scss b/ui/app/css/itcss/components/confirm.scss index 1977b49ae..abe138f54 100644 --- a/ui/app/css/itcss/components/confirm.scss +++ b/ui/app/css/itcss/components/confirm.scss @@ -242,6 +242,22 @@ section .confirm-screen-account-number, } } +@media screen and (max-width: 379px) { + .confirm-screen-row { + span.confirm-screen-section-column { + flex: 0.4; + } + + div.confirm-screen-section-column { + flex: 0.6; + } + + .currency-display__input { + font-size: 14px; + } + } +} + .confirm-screen-row-detail { font-size: 12px; line-height: 16px; diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss index c32d1de5e..374cb71b6 100644 --- a/ui/app/css/itcss/components/network.scss +++ b/ui/app/css/itcss/components/network.scss @@ -10,8 +10,9 @@ .network-component.pointer { border: 2px solid $silver; border-radius: 82px; - padding: 3px; + padding: 7px 3px; flex: 0 0 auto; + display: flex; &.ethereum-network .menu-icon-circle div { background-color: rgba(3, 135, 137, .7) !important; diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss index 5cdda5e6c..777a82318 100644 --- a/ui/app/css/itcss/components/newui-sections.scss +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -265,7 +265,6 @@ $wallet-view-bg: $alabaster; .account-name { font-size: 24px; font-weight: 300; - line-height: 20px; color: $black; margin-top: 8px; margin-bottom: .9rem; diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss index bb17e53cd..bdea1b008 100644 --- a/ui/app/css/itcss/components/send.scss +++ b/ui/app/css/itcss/components/send.scss @@ -660,6 +660,13 @@ &__gas-fee-display { width: 100%; + position: relative; + + .currency-display--message { + padding: 8px 38px 8px 10px; + display: flex; + align-items: center; + } } &__sliders-icon-container { @@ -885,3 +892,23 @@ } } } + +.sliders-icon-container { + display: flex; + align-items: center; + justify-content: center; + height: 24px; + width: 24px; + border: 1px solid $curious-blue; + border-radius: 4px; + background-color: $white; + position: absolute; + right: 15px; + top: 14px; + cursor: pointer; + font-size: 1em; +} + +.sliders-icon { + color: $curious-blue; +}
\ No newline at end of file diff --git a/ui/app/css/itcss/components/transaction-list.scss b/ui/app/css/itcss/components/transaction-list.scss index c3df493df..d03faf486 100644 --- a/ui/app/css/itcss/components/transaction-list.scss +++ b/ui/app/css/itcss/components/transaction-list.scss @@ -97,7 +97,7 @@ .tx-list-content-wrapper { align-items: stretch; - margin-bottom: 4px; + margin: 4px 0; flex: 1 0 auto; width: 100%; display: flex; @@ -126,6 +126,54 @@ } } +.tx-list-item-retry-container { + background: #d1edff; + width: 100%; + border-radius: 4px; + font-size: 0.8em; + display: flex; + justify-content: center; + margin-left: 44px; + width: calc(100% - 44px); + + @media screen and (min-width: 576px) and (max-width: 679px) { + flex-flow: column; + align-items: center; + } + + @media screen and (min-width: 380px) and (max-width: 575px) { + flex-flow: row; + } + + @media screen and (max-width: 379px) { + flex-flow: column; + align-items: center; + } +} + +.tx-list-item-retry-copy { + font-family: Roboto; +} + +.tx-list-item-retry-link { + text-decoration: underline; + margin-left: 6px; + cursor: pointer; + + @media screen and (min-width: 576px) and (max-width: 679px) { + margin-left: 0px; + } + + @media screen and (min-width: 380px) and (max-width: 575px) { + margin-left: 6px; + } + + @media screen and (max-width: 379px) { + margin-left: 0px; + text-align: center; + } +} + .tx-list-date { color: $dusty-gray; font-size: 12px; @@ -136,6 +184,7 @@ align-self: center; flex: 0 0 auto; margin-right: 16px; + display: flex; } .tx-list-account-and-status-wrapper { @@ -189,6 +238,10 @@ .tx-list-status--failed { color: $monzo; } + + .tx-list-status--dropped { + opacity: 0.5; + } } .tx-list-item { diff --git a/ui/app/css/itcss/generic/index.scss b/ui/app/css/itcss/generic/index.scss index 1fbd9896f..1e226b93e 100644 --- a/ui/app/css/itcss/generic/index.scss +++ b/ui/app/css/itcss/generic/index.scss @@ -13,7 +13,6 @@ body { font-family: Roboto, Arial; color: #4d4d4d; font-weight: 300; - line-height: 1.4em; background: #f7f7f7; width: 100%; height: 100%; @@ -103,6 +102,7 @@ input.large-input { &::after { content: '\00D7'; font-size: 40px; + line-height: 20px; } } diff --git a/ui/app/css/itcss/generic/reset.scss b/ui/app/css/itcss/generic/reset.scss index e054d533e..a417a0453 100644 --- a/ui/app/css/itcss/generic/reset.scss +++ b/ui/app/css/itcss/generic/reset.scss @@ -112,10 +112,6 @@ section { display: block; } -body { - line-height: 1; -} - ol, ul { list-style: none; diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss index d96c1ae43..640fd95b8 100644 --- a/ui/app/css/itcss/settings/variables.scss +++ b/ui/app/css/itcss/settings/variables.scss @@ -46,6 +46,7 @@ $manatee: #93949d; $spindle: #c7ddec; $mid-gray: #5b5d67; $cape-cod: #38393a; +$onahau: #d1edff; $java: #29b6af; $wild-strawberry: #ff4a8d; $cornflower-blue: #7057ff; diff --git a/ui/app/keychains/hd/recover-seed/confirmation.js b/ui/app/keychains/hd/recover-seed/confirmation.js index 5f323f7a1..4a62c8803 100644 --- a/ui/app/keychains/hd/recover-seed/confirmation.js +++ b/ui/app/keychains/hd/recover-seed/confirmation.js @@ -4,6 +4,7 @@ const Component = require('react').Component const connect = require('../../../metamask-connect') const h = require('react-hyperscript') const actions = require('../../../actions') +const t = require('../../../../i18n') module.exports = connect(mapStateToProps)(RevealSeedConfirmation) @@ -49,13 +50,13 @@ RevealSeedConfirmation.prototype.render = function () { }, }, [ - h('h4', 'Do not recover your seed words in a public place! These words can be used to steal all your accounts.'), + h('h4', t('revealSeedWordsWarning')), // confirmation h('input.large-input.letter-spacey', { type: 'password', id: 'password-box', - placeholder: 'Enter your password to confirm', + placeholder: t('enterPasswordConfirm'), onKeyPress: this.checkConfirmation.bind(this), style: { width: 260, @@ -91,7 +92,7 @@ RevealSeedConfirmation.prototype.render = function () { ), props.inProgress && ( - h('span.in-progress-notification', 'Generating Seed...') + h('span.in-progress-notification', t('generatingSeed')) ), ]), ]) diff --git a/ui/app/keychains/hd/restore-vault.js b/ui/app/keychains/hd/restore-vault.js index 0a604f505..164cf28dc 100644 --- a/ui/app/keychains/hd/restore-vault.js +++ b/ui/app/keychains/hd/restore-vault.js @@ -2,6 +2,7 @@ const inherits = require('util').inherits const PersistentForm = require('../../../lib/persistent-form') const connect = require('../../metamask-connect') const h = require('react-hyperscript') +const t = require('../../../i18n') const actions = require('../../actions') module.exports = connect(mapStateToProps)(RestoreVaultScreen) @@ -36,23 +37,23 @@ RestoreVaultScreen.prototype.render = function () { padding: 6, }, }, [ - 'Restore Vault', + t('restoreVault'), ]), // wallet seed entry - h('h3', 'Wallet Seed'), + h('h3', t('walletSeed')), h('textarea.twelve-word-phrase.letter-spacey', { dataset: { persistentFormId: 'wallet-seed', }, - placeholder: 'Enter your secret twelve word phrase here to restore your vault.', + placeholder: t('secretPhrase'), }), // password h('input.large-input.letter-spacey', { type: 'password', id: 'password-box', - placeholder: 'New Password (min 8 chars)', + placeholder: t('newPassword8Chars'), dataset: { persistentFormId: 'password', }, @@ -66,7 +67,7 @@ RestoreVaultScreen.prototype.render = function () { h('input.large-input.letter-spacey', { type: 'password', id: 'password-box-confirm', - placeholder: 'Confirm Password', + placeholder: t('confirmPassword'), onKeyPress: this.createOnEnter.bind(this), dataset: { persistentFormId: 'password-confirmation', @@ -93,16 +94,20 @@ RestoreVaultScreen.prototype.render = function () { // cancel h('button.primary', { onClick: this.showInitializeMenu.bind(this), - }, 'CANCEL'), + style: { + textTransform: 'uppercase', + }, + }, t('cancel')), // submit h('button.primary', { onClick: this.createNewVaultAndRestore.bind(this), - }, 'OK'), - + style: { + textTransform: 'uppercase', + }, + }, t('ok')), ]), ]) - ) } @@ -131,13 +136,13 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () { var passwordConfirmBox = document.getElementById('password-box-confirm') var passwordConfirm = passwordConfirmBox.value if (password.length < 8) { - this.warning = 'Password not long enough' + this.warning = t('passwordNotLongEnough') this.props.dispatch(actions.displayWarning(this.warning)) return } if (password !== passwordConfirm) { - this.warning = 'Passwords don\'t match' + this.warning = t('passwordsDontMatch') this.props.dispatch(actions.displayWarning(this.warning)) return } @@ -147,18 +152,18 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () { // true if the string has more than a space between words. if (seed.split(' ').length > 1) { - this.warning = 'there can only be a space between words' + this.warning = t('spaceBetween') this.props.dispatch(actions.displayWarning(this.warning)) return } // true if seed contains a character that is not between a-z or a space if (!seed.match(/^[a-z ]+$/)) { - this.warning = 'seed words only have lowercase characters' - this.props.dispatch(actions.displayWarning(this.warning)) + this.warning = t('loweCaseWords') + this.props.dispatch(actions.displayWarning(this.warning)) return } if (seed.split(' ').length !== 12) { - this.warning = 'seed phrases are 12 words long' + this.warning = t('seedPhraseReq') this.props.dispatch(actions.displayWarning(this.warning)) return } diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index 611c55391..6e226b0e6 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -38,6 +38,7 @@ function reduceMetamask (state, action) { errors: {}, maxModeOn: false, editingTransactionId: null, + forceGasMin: null, }, coinOptions: {}, useBlockie: false, @@ -298,6 +299,7 @@ function reduceMetamask (state, action) { memo: '', errors: {}, editingTransactionId: null, + forceGasMin: null, }, }) diff --git a/ui/app/selectors.js b/ui/app/selectors.js index 5d2635775..d37c26f7e 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -18,6 +18,7 @@ const selectors = { getCurrentAccountWithSendEtherInfo, getGasPrice, getGasLimit, + getForceGasMin, getAddressBook, getSendFrom, getCurrentCurrency, @@ -130,6 +131,10 @@ function getGasLimit (state) { return state.metamask.send.gasLimit } +function getForceGasMin (state) { + return state.metamask.send.forceGasMin +} + function getSendFrom (state) { return state.metamask.send.from } diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index fc1df1f51..31118378d 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -1,6 +1,7 @@ const { inherits } = require('util') const PersistentForm = require('../lib/persistent-form') const h = require('react-hyperscript') +const t = require('../i18n') const ethAbi = require('ethereumjs-abi') const ethUtil = require('ethereumjs-util') @@ -42,6 +43,7 @@ function SendTransactionScreen () { to: null, amount: null, }, + gasLoadingError: false, } this.handleToChange = this.handleToChange.bind(this) @@ -128,6 +130,10 @@ SendTransactionScreen.prototype.updateGas = function () { .then(([gasPrice, gas]) => { const newGasTotal = this.getGasTotal(gas, gasPrice) updateGasTotal(newGasTotal) + this.setState({ gasLoadingError: false }) + }) + .catch(err => { + this.setState({ gasLoadingError: true }) }) } else { const newGasTotal = this.getGasTotal(gasLimit, gasPrice) @@ -180,13 +186,12 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) { SendTransactionScreen.prototype.renderHeader = function () { const { selectedToken, clearSend, goHome } = this.props - const tokenText = selectedToken ? 'tokens' : 'ETH' return h('div.page-container__header', [ - h('div.page-container__title', selectedToken ? 'Send Tokens' : 'Send ETH'), + h('div.page-container__title', selectedToken ? t('sendTokens') : t('sendETH')), - h('div.page-container__subtitle', `Only send ${tokenText} to an Ethereum address.`), + h('div.page-container__subtitle', t('onlySendToEtherAddress')), h('div.page-container__header-close', { onClick: () => { @@ -257,11 +262,11 @@ SendTransactionScreen.prototype.handleToChange = function (to) { let toError = null if (!to) { - toError = 'Required' + toError = t('required') } else if (!isValidAddress(to)) { - toError = 'Recipient address is invalid' + toError = t('invalidAddressRecipient') } else if (to === from) { - toError = 'From and To address cannot be the same' + toError = t('fromToSame') } updateSendTo(to) @@ -277,9 +282,9 @@ SendTransactionScreen.prototype.renderToRow = function () { h('div.send-v2__form-label', [ - 'To:', + t('to'), - this.renderErrorMessage('to'), + this.renderErrorMessage(t('to')), ]), @@ -377,11 +382,11 @@ SendTransactionScreen.prototype.validateAmount = function (value) { ) if (conversionRate && !sufficientBalance) { - amountError = 'Insufficient funds.' + amountError = t('insufficientFunds') } else if (verifyTokenBalance && !sufficientTokens) { - amountError = 'Insufficient tokens.' + amountError = t('insufficientTokens') } else if (amountLessThanZero) { - amountError = 'Can not send negative amounts of ETH.' + amountError = t('negativeETH') } updateSendErrors({ amount: amountError }) @@ -411,7 +416,7 @@ SendTransactionScreen.prototype.renderAmountRow = function () { setMaxModeTo(true) this.setAmountToMax() }, - }, [ !maxModeOn ? 'Max' : '' ]), + }, [ !maxModeOn ? t('max') : '' ]), ]), h('div.send-v2__form-field', [ @@ -436,10 +441,11 @@ SendTransactionScreen.prototype.renderGasRow = function () { showCustomizeGasModal, gasTotal, } = this.props + const { gasLoadingError } = this.state return h('div.send-v2__form-row', [ - h('div.send-v2__form-label', 'Gas fee:'), + h('div.send-v2__form-label', h('gasFee')), h('div.send-v2__form-field', [ @@ -448,6 +454,7 @@ SendTransactionScreen.prototype.renderGasRow = function () { conversionRate, convertedCurrency, onClick: showCustomizeGasModal, + gasLoadingError, }), ]), @@ -507,11 +514,11 @@ SendTransactionScreen.prototype.renderFooter = function () { clearSend() goHome() }, - }, 'Cancel'), + }, t('cancel')), h('button.btn-clear.page-container__footer-button', { disabled: !noErrors || !gasTotal || missingTokenBalance, onClick: event => this.onSubmit(event), - }, 'Next'), + }, t('next')), ]) } @@ -571,9 +578,11 @@ SendTransactionScreen.prototype.getEditedTx = function () { data, }) } else { + const data = unapprovedTxs[editingTransactionId].txParams.data Object.assign(editingTx.txParams, { value: ethUtil.addHexPrefix(amount), to: ethUtil.addHexPrefix(to), + data, }) } diff --git a/ui/app/settings.js b/ui/app/settings.js index 1412606e5..f75b1e8b4 100644 --- a/ui/app/settings.js +++ b/ui/app/settings.js @@ -10,6 +10,7 @@ const TabBar = require('./components/tab-bar') const SimpleDropdown = require('./components/dropdowns/simple-dropdown') const ToggleButton = require('react-toggle-button') const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums +const t = require('../i18n') const getInfuraCurrencyOptions = () => { const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { @@ -61,8 +62,8 @@ class Settings extends Component { return h('div.settings__tabs', [ h(TabBar, { tabs: [ - { content: 'Settings', key: 'settings' }, - { content: 'Info', key: 'info' }, + { content: t('settings'), key: 'settings' }, + { content: t('info'), key: 'info' }, ], defaultTab: activeTab, tabSelected: key => this.setState({ activeTab: key }), @@ -75,7 +76,7 @@ class Settings extends Component { return h('div.settings__content-row', [ h('div.settings__content-item', [ - h('span', 'Use Blockies Identicon'), + h('span', t('blockiesIdenticon')), ]), h('div.settings__content-item', [ h('div.settings__content-item-col', [ @@ -95,13 +96,13 @@ class Settings extends Component { return h('div.settings__content-row', [ h('div.settings__content-item', [ - h('span', 'Current Conversion'), + h('span', t('currentConversion')), h('span.settings__content-description', `Updated ${Date(conversionDate)}`), ]), h('div.settings__content-item', [ h('div.settings__content-item-col', [ h(SimpleDropdown, { - placeholder: 'Select Currency', + placeholder: t('selectCurrency'), options: getInfuraCurrencyOptions(), selectedOption: currentCurrency, onSelect: newCurrency => setCurrentCurrency(newCurrency), @@ -141,31 +142,31 @@ class Settings extends Component { switch (provider.type) { case 'mainnet': - title = 'Current Network' - value = 'Main Ethereum Network' + title = t('currentNetwork') + value = t('mainnet') color = '#038789' break case 'ropsten': - title = 'Current Network' - value = 'Ropsten Test Network' + title = t('currentNetwork') + value = t('ropsten') color = '#e91550' break case 'kovan': - title = 'Current Network' - value = 'Kovan Test Network' + title = t('currentNetwork') + value = t('kovan') color = '#690496' break case 'rinkeby': - title = 'Current Network' - value = 'Rinkeby Test Network' + title = t('currentNetwork') + value = t('rinkeby') color = '#ebb33f' break default: - title = 'Current RPC' + title = t('currentRpc') value = provider.rpcTarget } @@ -186,12 +187,12 @@ class Settings extends Component { return ( h('div.settings__content-row', [ h('div.settings__content-item', [ - h('span', 'New RPC URL'), + h('span', t('newRPC')), ]), h('div.settings__content-item', [ h('div.settings__content-item-col', [ h('input.settings__input', { - placeholder: 'New RPC URL', + placeholder: t('newRPC'), onChange: event => this.setState({ newRpc: event.target.value }), onKeyPress: event => { if (event.key === 'Enter') { @@ -204,7 +205,7 @@ class Settings extends Component { event.preventDefault() this.validateRpc(this.state.newRpc) }, - }, 'Save'), + }, t('save')), ]), ]), ]) @@ -220,9 +221,9 @@ class Settings extends Component { const appendedRpc = `http://${newRpc}` if (validUrl.isWebUri(appendedRpc)) { - displayWarning('URIs require the appropriate HTTP/HTTPS prefix.') + displayWarning(t('uriErrorMsg')) } else { - displayWarning('Invalid RPC URI') + displayWarning(t('invalidRPC')) } } } @@ -231,10 +232,10 @@ class Settings extends Component { return ( h('div.settings__content-row', [ h('div.settings__content-item', [ - h('div', 'State Logs'), + h('div', t('stateLogs')), h( 'div.settings__content-description', - 'State logs contain your public account addresses and sent transactions.' + t('stateLogsDescription') ), ]), h('div.settings__content-item', [ @@ -243,13 +244,13 @@ class Settings extends Component { onClick (event) { window.logStateString((err, result) => { if (err) { - this.state.dispatch(actions.displayWarning('Error in retrieving state logs.')) + this.state.dispatch(actions.displayWarning(t('stateLogError'))) } else { exportAsFile('MetaMask State Logs.json', result) } }) }, - }, 'Download State Logs'), + }, t('downloadStateLogs')), ]), ]), ]) @@ -261,7 +262,7 @@ class Settings extends Component { return ( h('div.settings__content-row', [ - h('div.settings__content-item', 'Reveal Seed Words'), + h('div.settings__content-item', t('revealSeedWords')), h('div.settings__content-item', [ h('div.settings__content-item-col', [ h('button.settings__clear-button.settings__clear-button--red', { @@ -269,7 +270,7 @@ class Settings extends Component { event.preventDefault() revealSeedConfirmation() }, - }, 'Reveal Seed Words'), + }, t('revealSeedWords')), ]), ]), ]) @@ -281,7 +282,7 @@ class Settings extends Component { return ( h('div.settings__content-row', [ - h('div.settings__content-item', 'Use old UI'), + h('div.settings__content-item', t('useOldUI')), h('div.settings__content-item', [ h('div.settings__content-item-col', [ h('button.settings__clear-button.settings__clear-button--orange', { @@ -289,7 +290,7 @@ class Settings extends Component { event.preventDefault() setFeatureFlagToBeta() }, - }, 'Use old UI'), + }, t('useOldUI')), ]), ]), ]) @@ -300,7 +301,7 @@ class Settings extends Component { const { showResetAccountConfirmationModal } = this.props return h('div.settings__content-row', [ - h('div.settings__content-item', 'Reset Account'), + h('div.settings__content-item', t('resetAccount')), h('div.settings__content-item', [ h('div.settings__content-item-col', [ h('button.settings__clear-button.settings__clear-button--orange', { @@ -308,7 +309,7 @@ class Settings extends Component { event.preventDefault() showResetAccountConfirmationModal() }, - }, 'Reset Account'), + }, t('resetAccount')), ]), ]), ]) @@ -344,13 +345,13 @@ class Settings extends Component { renderInfoLinks () { return ( h('div.settings__content-item.settings__content-item--without-height', [ - h('div.settings__info-link-header', 'Links'), + h('div.settings__info-link-header', t('links')), h('div.settings__info-link-item', [ h('a', { href: 'https://metamask.io/privacy.html', target: '_blank', }, [ - h('span.settings__info-link', 'Privacy Policy'), + h('span.settings__info-link', t('privacyMsg')), ]), ]), h('div.settings__info-link-item', [ @@ -358,7 +359,7 @@ class Settings extends Component { href: 'https://metamask.io/terms.html', target: '_blank', }, [ - h('span.settings__info-link', 'Terms of Use'), + h('span.settings__info-link', t('terms')), ]), ]), h('div.settings__info-link-item', [ @@ -366,7 +367,7 @@ class Settings extends Component { href: 'https://metamask.io/attributions.html', target: '_blank', }, [ - h('span.settings__info-link', 'Attributions'), + h('span.settings__info-link', t('attributions')), ]), ]), h('hr.settings__info-separator'), @@ -375,7 +376,7 @@ class Settings extends Component { href: 'https://support.metamask.io', target: '_blank', }, [ - h('span.settings__info-link', 'Visit our Support Center'), + h('span.settings__info-link', t('supportCenter')), ]), ]), h('div.settings__info-link-item', [ @@ -383,7 +384,7 @@ class Settings extends Component { href: 'https://metamask.io/', target: '_blank', }, [ - h('span.settings__info-link', 'Visit our web site'), + h('span.settings__info-link', t('visitWebSite')), ]), ]), h('div.settings__info-link-item', [ @@ -391,7 +392,7 @@ class Settings extends Component { target: '_blank', href: 'mailto:help@metamask.io?subject=Feedback', }, [ - h('span.settings__info-link', 'Email us!'), + h('span.settings__info-link', t('emailUs')), ]), ]), ]) @@ -413,7 +414,7 @@ class Settings extends Component { h('div.settings__info-item', [ h( 'div.settings__info-about', - 'MetaMask is designed and built in California.' + t('builtInCalifornia') ), ]), ]), @@ -490,3 +491,4 @@ const mapDispatchToProps = dispatch => { } module.exports = connect(mapStateToProps, mapDispatchToProps)(Settings) + diff --git a/ui/app/unlock.js b/ui/app/unlock.js index 8b58ba1a8..423e76e10 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -67,7 +67,7 @@ UnlockScreen.prototype.render = function () { style: { margin: 10, }, - }, 'Log In'), + }, t('login')), h('p.pointer', { onClick: () => { @@ -81,7 +81,7 @@ UnlockScreen.prototype.render = function () { color: 'rgb(247, 134, 28)', textDecoration: 'underline', }, - }, 'Restore from seed phrase'), + }, t('restoreFromSeed')), h('p.pointer', { onClick: () => { @@ -94,7 +94,7 @@ UnlockScreen.prototype.render = function () { textDecoration: 'underline', marginTop: '32px', }, - }, 'Use classic interface'), + }, t('classicInterface')), ]) ) } |