diff options
author | Alexander Tseung <alextsg@users.noreply.github.com> | 2017-12-23 03:40:20 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-23 03:40:20 +0800 |
commit | 409d1d30e9d836926e5361fb9e4b5b025b66e313 (patch) | |
tree | c7f247d1e973c50697af8b9905d9fd40d4575659 /ui/app | |
parent | b944a63ff89e3c45f7d7e49b2d93a5442cde4462 (diff) | |
parent | 5a58add797fcdbb023678af84a61f1d2bfdafaf1 (diff) | |
download | tangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.tar tangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.tar.gz tangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.tar.bz2 tangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.tar.lz tangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.tar.xz tangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.tar.zst tangerine-wallet-browser-409d1d30e9d836926e5361fb9e4b5b025b66e313.zip |
Merge pull request #2799 from MetaMask/NewUI-flat
Update UAT to version 4.0.5
Diffstat (limited to 'ui/app')
32 files changed, 745 insertions, 190 deletions
diff --git a/ui/app/actions.js b/ui/app/actions.js index 2ca62c41f..bd3aab45a 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -125,6 +125,7 @@ var actions = { sendTx: sendTx, signTx: signTx, signTokenTx: signTokenTx, + updateTransaction, updateAndApproveTx, cancelTx: cancelTx, completedTx: completedTx, @@ -149,6 +150,7 @@ var actions = { UPDATE_SEND_AMOUNT: 'UPDATE_SEND_AMOUNT', UPDATE_SEND_MEMO: 'UPDATE_SEND_MEMO', UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS', + UPDATE_MAX_MODE: 'UPDATE_MAX_MODE', UPDATE_SEND: 'UPDATE_SEND', CLEAR_SEND: 'CLEAR_SEND', updateGasLimit, @@ -160,6 +162,7 @@ var actions = { updateSendAmount, updateSendMemo, updateSendErrors, + setMaxModeTo, updateSend, clearSend, setSelectedAddress, @@ -234,6 +237,21 @@ var actions = { toggleAccountMenu, useEtherscanProvider, + + SET_USE_BLOCKIE: 'SET_USE_BLOCKIE', + setUseBlockie, + + // Feature Flags + setFeatureFlag, + updateFeatureFlags, + UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS', + + // Network + setNetworkEndpoints, + updateNetworkEndpointType, + UPDATE_NETWORK_ENDPOINT_TYPE: 'UPDATE_NETWORK_ENDPOINT_TYPE', + + retryTransaction, } module.exports = actions @@ -634,6 +652,13 @@ function updateSendErrors (error) { } } +function setMaxModeTo (bool) { + return { + type: actions.UPDATE_MAX_MODE, + value: bool, + } +} + function updateSend (newSend) { return { type: actions.UPDATE_SEND, @@ -675,6 +700,23 @@ function signTokenTx (tokenAddress, toAddress, amount, txData) { } } +function updateTransaction (txData) { + log.info('actions: updateTx: ' + JSON.stringify(txData)) + return (dispatch) => { + log.debug(`actions calling background.updateTx`) + background.updateTransaction(txData, (err) => { + dispatch(actions.hideLoadingIndication()) + dispatch(actions.updateTransactionParams(txData.id, txData.txParams)) + if (err) { + dispatch(actions.txError(err)) + dispatch(actions.goHome()) + return log.error(err.message) + } + dispatch(actions.showConfTxPage({ id: txData.id })) + }) + } +} + function updateAndApproveTx (txData) { log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData)) return (dispatch) => { @@ -737,6 +779,7 @@ function cancelTx (txData) { return (dispatch) => { log.debug(`background.cancelTransaction`) background.cancelTransaction(txData.id, () => { + dispatch(actions.clearSend()) dispatch(actions.completedTx(txData.id)) }) } @@ -985,9 +1028,10 @@ function showConfigPage (transitionForward = true) { } } -function showAddTokenPage () { +function showAddTokenPage (transitionForward = true) { return { type: actions.SHOW_ADD_TOKEN_PAGE, + value: transitionForward, } } @@ -1101,6 +1145,19 @@ function markAccountsFound () { return callBackgroundThenUpdate(background.markAccountsFound) } +function retryTransaction (txId) { + log.debug(`background.retryTransaction`) + return (dispatch) => { + background.retryTransaction(txId, (err, newState) => { + if (err) { + return dispatch(actions.displayWarning(err.message)) + } + dispatch(actions.updateMetamaskState(newState)) + dispatch(actions.viewPendingTx(txId)) + }) + } +} + // // config // @@ -1269,7 +1326,8 @@ function exportAccount (password, address) { return reject(err) } - dispatch(self.exportAccountComplete()) + // dispatch(self.exportAccountComplete()) + dispatch(self.showPrivateKey(result)) return resolve(result) }) @@ -1444,10 +1502,11 @@ function reshowQrCode (data, coin) { ] dispatch(actions.hideLoadingIndication()) - return dispatch(actions.showModal({ - name: 'SHAPESHIFT_DEPOSIT_TX', - Qr: { data, message }, - })) + return dispatch(actions.showQrView(data, message)) + // return dispatch(actions.showModal({ + // name: 'SHAPESHIFT_DEPOSIT_TX', + // Qr: { data, message }, + // })) }) } } @@ -1503,6 +1562,31 @@ function updateTokenExchangeRate (token = '') { } } +function setFeatureFlag (feature, activated, notificationType) { + return (dispatch) => { + dispatch(actions.showLoadingIndication()) + return new Promise((resolve, reject) => { + background.setFeatureFlag(feature, activated, (err, updatedFeatureFlags) => { + dispatch(actions.hideLoadingIndication()) + if (err) { + dispatch(actions.displayWarning(err.message)) + return reject(err) + } + dispatch(actions.updateFeatureFlags(updatedFeatureFlags)) + notificationType && dispatch(actions.showModal({ name: notificationType })) + resolve(updatedFeatureFlags) + }) + }) + } +} + +function updateFeatureFlags (updatedFeatureFlags) { + return { + type: actions.UPDATE_FEATURE_FLAGS, + value: updatedFeatureFlags, + } +} + // Call Background Then Update // // A function generator for a common pattern wherein: @@ -1550,3 +1634,44 @@ function toggleAccountMenu () { type: actions.TOGGLE_ACCOUNT_MENU, } } + +function setUseBlockie (val) { + return (dispatch) => { + dispatch(actions.showLoadingIndication()) + log.debug(`background.setUseBlockie`) + background.setUseBlockie(val, (err) => { + dispatch(actions.hideLoadingIndication()) + if (err) { + return dispatch(actions.displayWarning(err.message)) + } + }) + dispatch({ + type: actions.SET_USE_BLOCKIE, + value: val, + }) + } +} + +function setNetworkEndpoints (networkEndpointType) { + return dispatch => { + log.debug('background.setNetworkEndpoints') + return new Promise((resolve, reject) => { + background.setNetworkEndpoints(networkEndpointType, err => { + if (err) { + dispatch(actions.displayWarning(err.message)) + return reject(err) + } + + dispatch(actions.updateNetworkEndpointType(networkEndpointType)) + resolve(networkEndpointType) + }) + }) + } +} + +function updateNetworkEndpointType (networkEndpointType) { + return { + type: actions.UPDATE_NETWORK_ENDPOINT_TYPE, + value: networkEndpointType, + } +} diff --git a/ui/app/app.js b/ui/app/app.js index e90c3e98e..1f40eccbe 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -116,7 +116,6 @@ App.prototype.render = function () { log.debug('Main ui render function') return ( - h('.flex-column.full-height', { style: { overflowX: 'hidden', diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js index a9f075ec7..286a3b587 100644 --- a/ui/app/components/account-menu/index.js +++ b/ui/app/components/account-menu/index.js @@ -28,27 +28,33 @@ function mapDispatchToProps (dispatch) { toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), showAccountDetail: address => { dispatch(actions.showAccountDetail(address)) + dispatch(actions.hideSidebar()) dispatch(actions.toggleAccountMenu()) }, lockMetamask: () => { dispatch(actions.lockMetamask()) dispatch(actions.displayWarning(null)) + dispatch(actions.hideSidebar()) dispatch(actions.toggleAccountMenu()) }, showConfigPage: () => { dispatch(actions.showConfigPage()) + dispatch(actions.hideSidebar()) dispatch(actions.toggleAccountMenu()) }, showNewAccountModal: () => { dispatch(actions.showModal({ name: 'NEW_ACCOUNT' })) + dispatch(actions.hideSidebar()) dispatch(actions.toggleAccountMenu()) }, showImportPage: () => { dispatch(actions.showImportPage()) + dispatch(actions.hideSidebar()) dispatch(actions.toggleAccountMenu()) }, showInfoPage: () => { dispatch(actions.showInfoPage()) + dispatch(actions.hideSidebar()) dispatch(actions.toggleAccountMenu()) }, } diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js index d14aa675f..50007ce14 100644 --- a/ui/app/components/balance-component.js +++ b/ui/app/components/balance-component.js @@ -94,7 +94,8 @@ BalanceComponent.prototype.renderFiatValue = function (formattedBalance) { } BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatSuffix, fiatPrefix) { - if (fiatDisplayNumber === 'N/A') return null + const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0 + if (shouldNotRenderFiat) return null return h('div.fiat-amount', { style: {}, diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js index f44d86045..f70208625 100644 --- a/ui/app/components/coinbase-form.js +++ b/ui/app/components/coinbase-form.js @@ -40,7 +40,7 @@ CoinbaseForm.prototype.render = function () { }, 'Continue to Coinbase'), h('button.btn-red', { - onClick: () => props.dispatch(actions.backTobuyView(props.accounts.address)), + onClick: () => props.dispatch(actions.goHome()), }, 'Cancel'), ]), ]) diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index 485dacf90..826d2cd4b 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -5,6 +5,8 @@ const connect = require('react-redux').connect const actions = require('../../actions') const GasModalCard = require('./gas-modal-card') +const ethUtil = require('ethereumjs-util') + const { MIN_GAS_PRICE_DEC, MIN_GAS_LIMIT_DEC, @@ -19,6 +21,7 @@ const { conversionUtil, multiplyCurrencies, conversionGreaterThan, + subtractCurrencies, } = require('../../conversion-util') const { @@ -30,6 +33,7 @@ const { getSendFrom, getCurrentAccountWithSendEtherInfo, getSelectedTokenToFiatRate, + getSendMaxModeState, } = require('../../selectors') function mapStateToProps (state) { @@ -42,6 +46,7 @@ function mapStateToProps (state) { gasLimit: getGasLimit(state), conversionRate, amount: getSendAmount(state), + maxModeOn: getSendMaxModeState(state), balance: currentAccount.balance, primaryCurrency: selectedToken && selectedToken.symbol, selectedToken, @@ -55,6 +60,7 @@ function mapDispatchToProps (dispatch) { updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)), updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)), updateGasTotal: newGasTotal => dispatch(actions.updateGasTotal(newGasTotal)), + updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)), } } @@ -93,8 +99,21 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { updateGasLimit, hideModal, updateGasTotal, + maxModeOn, + selectedToken, + balance, + updateSendAmount, } = this.props + if (maxModeOn && !selectedToken) { + const maxAmount = subtractCurrencies( + ethUtil.addHexPrefix(balance), + ethUtil.addHexPrefix(gasTotal), + { toNumericBase: 'hex' } + ) + updateSendAmount(maxAmount) + } + updateGasPrice(gasPrice) updateGasLimit(gasLimit) updateGasTotal(gasTotal) @@ -112,12 +131,13 @@ CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) { selectedToken, amountConversionRate, conversionRate, + maxModeOn, } = this.props let error = null const balanceIsSufficient = isBalanceSufficient({ - amount: selectedToken ? '0' : amount, + amount: selectedToken || maxModeOn ? '0' : amount, gasTotal, balance, selectedToken, diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js index 0908faf01..dfaa6b22c 100644 --- a/ui/app/components/dropdowns/network-dropdown.js +++ b/ui/app/components/dropdowns/network-dropdown.js @@ -6,6 +6,16 @@ const actions = require('../../actions') const Dropdown = require('./components/dropdown').Dropdown const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem const NetworkDropdownIcon = require('./components/network-dropdown-icon') +const R = require('ramda') + +// classes from nodes of the toggle element. +const notToggleElementClassnames = [ + 'menu-icon', + 'network-name', + 'network-indicator', + 'network-caret', + 'network-component', +] function mapStateToProps (state) { return { @@ -32,8 +42,8 @@ function mapDispatchToProps (dispatch) { showConfigPage: () => { dispatch(actions.showConfigPage()) }, - showNetworkDropdown: () => { dispatch(actions.showNetworkDropdown()) }, - hideNetworkDropdown: () => { dispatch(actions.hideNetworkDropdown()) }, + showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), + hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), } } @@ -59,18 +69,13 @@ NetworkDropdown.prototype.render = function () { } return h(Dropdown, { - useCssTransition: true, isOpen, onClickOutside: (event) => { const { classList } = event.target - const isNotToggleElement = [ - classList.contains('menu-icon'), - classList.contains('network-name'), - classList.contains('network-indicator'), - ].filter(bool => bool).length === 0 - // classes from three constituent nodes of the toggle element - - if (isNotToggleElement) { + const isInClassList = className => classList.contains(className) + const notToggleElementIndex = R.findIndex(isInClassList)(notToggleElementClassnames) + + if (notToggleElementIndex === -1) { this.props.hideNetworkDropdown() } }, diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js index d30b7cd56..b803b7ceb 100644 --- a/ui/app/components/identicon.js +++ b/ui/app/components/identicon.js @@ -1,13 +1,15 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const connect = require('react-redux').connect const isNode = require('detect-node') const findDOMNode = require('react-dom').findDOMNode const jazzicon = require('jazzicon') const iconFactoryGen = require('../../lib/icon-factory') const iconFactory = iconFactoryGen(jazzicon) +const { toDataUrl } = require('../../lib/blockies') -module.exports = IdenticonComponent +module.exports = connect(mapStateToProps)(IdenticonComponent) inherits(IdenticonComponent, Component) function IdenticonComponent () { @@ -16,6 +18,12 @@ function IdenticonComponent () { this.defaultDiameter = 46 } +function mapStateToProps (state) { + return { + useBlockie: state.metamask.useBlockie, + } +} + IdenticonComponent.prototype.render = function () { var props = this.props const { className = '', address } = props @@ -51,38 +59,59 @@ IdenticonComponent.prototype.render = function () { IdenticonComponent.prototype.componentDidMount = function () { var props = this.props - const { address } = props + const { address, useBlockie } = props if (!address) return - // eslint-disable-next-line react/no-find-dom-node - var container = findDOMNode(this) - - var diameter = props.diameter || this.defaultDiameter if (!isNode) { - var img = iconFactory.iconForAddress(address, diameter) - container.appendChild(img) + // eslint-disable-next-line react/no-find-dom-node + var container = findDOMNode(this) + + const diameter = props.diameter || this.defaultDiameter + + if (useBlockie) { + _generateBlockie(container, address, diameter) + } else { + _generateJazzicon(container, address, diameter) + } } } IdenticonComponent.prototype.componentDidUpdate = function () { var props = this.props - const { address } = props + const { address, useBlockie } = props if (!address) return - // eslint-disable-next-line react/no-find-dom-node - var container = findDOMNode(this) + if (!isNode) { + // eslint-disable-next-line react/no-find-dom-node + var container = findDOMNode(this) - var children = container.children - for (var i = 0; i < children.length; i++) { - container.removeChild(children[i]) - } + var children = container.children + for (var i = 0; i < children.length; i++) { + container.removeChild(children[i]) + } - var diameter = props.diameter || this.defaultDiameter - if (!isNode) { - var img = iconFactory.iconForAddress(address, diameter) - container.appendChild(img) + const diameter = props.diameter || this.defaultDiameter + + if (useBlockie) { + _generateBlockie(container, address, diameter) + } else { + _generateJazzicon(container, address, diameter) + } } } +function _generateBlockie (container, address, diameter) { + const img = new Image() + img.src = toDataUrl(address) + const dia = !diameter || diameter < 50 ? 50 : diameter + img.height = dia * 1.25 + img.width = dia * 1.25 + container.appendChild(img) +} + +function _generateJazzicon (container, address, diameter) { + const img = iconFactory.iconForAddress(address, diameter) + container.appendChild(img) +} diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index f2909f3c3..2ff6accaa 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -16,6 +16,7 @@ const NewAccountModal = require('./new-account-modal') const ShapeshiftDepositTxModal = require('./shapeshift-deposit-tx-modal.js') const HideTokenConfirmationModal = require('./hide-token-confirmation-modal') const CustomizeGasModal = require('../customize-gas-modal') +const NotifcationModal = require('./notification-modal') const accountModalStyle = { mobileModalStyle: { @@ -133,6 +134,42 @@ const MODALS = { }, }, + BETA_UI_NOTIFICATION_MODAL: { + contents: [ + h(NotifcationModal, { + header: 'Welcome to the New UI (Beta)', + message: `You are now using the new Metamask UI. Take a look around, try out new features like sending tokens, + and let us know if you have any issues.`, + }), + ], + mobileModalStyle: { + width: '95%', + top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh', + }, + laptopModalStyle: { + width: '449px', + top: 'calc(33% + 45px)', + }, + }, + + OLD_UI_NOTIFICATION_MODAL: { + contents: [ + h(NotifcationModal, { + header: 'Old UI', + message: `You have returned to the old UI. You can switch back to the New UI through the option in the top + right dropdown menu.`, + }), + ], + mobileModalStyle: { + width: '95%', + top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh', + }, + laptopModalStyle: { + width: '449px', + top: 'calc(33% + 45px)', + }, + }, + NEW_ACCOUNT: { contents: [ h(NewAccountModal, {}, []), diff --git a/ui/app/components/modals/notification-modal.js b/ui/app/components/modals/notification-modal.js new file mode 100644 index 000000000..239144b0c --- /dev/null +++ b/ui/app/components/modals/notification-modal.js @@ -0,0 +1,51 @@ +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const { connect } = require('react-redux') +const actions = require('../../actions') + +class NotificationModal extends Component { + render () { + const { + header, + message, + } = this.props + + return h('div', [ + h('div.notification-modal-wrapper', { + }, [ + + h('div.notification-modal-header', {}, [ + header, + ]), + + h('div.notification-modal-message-wrapper', {}, [ + h('div.notification-modal-message', {}, [ + message, + ]), + ]), + + h('div.modal-close-x', { + onClick: this.props.hideModal, + }), + + ]), + ]) + } +} + +NotificationModal.propTypes = { + hideModal: PropTypes.func, + header: PropTypes.string, + message: PropTypes.string, +} + +const mapDispatchToProps = dispatch => { + return { + hideModal: () => { + dispatch(actions.hideModal()) + }, + } +} + +module.exports = connect(null, mapDispatchToProps)(NotificationModal) diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 915818009..5a8d0763d 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -39,7 +39,7 @@ Network.prototype.render = function () { }, src: 'images/loading.svg', }), - h('i.fa.fa-caret-down'), + h('i.fa.fa-caret-down.network-caret'), ]) } else if (providerName === 'mainnet') { hoverText = 'Main Ethereum Network' @@ -63,7 +63,7 @@ Network.prototype.render = function () { return ( h('div.network-component.pointer', { - className: classnames('network-component pointer', { + className: classnames({ 'network-component--disabled': this.props.disabled, 'ethereum-network': providerName === 'mainnet', 'ropsten-test-network': providerName === 'ropsten' || parseInt(networkNumber) === 3, @@ -90,7 +90,7 @@ Network.prototype.render = function () { color: '#039396', }}, 'Main Network'), - h('i.fa.fa-caret-down.fa-lg'), + h('i.fa.fa-caret-down.fa-lg.network-caret'), ]) case 'ropsten-test-network': return h('.network-indicator', [ @@ -103,7 +103,7 @@ Network.prototype.render = function () { color: '#ff6666', }}, 'Ropsten Test Net'), - h('i.fa.fa-caret-down.fa-lg'), + h('i.fa.fa-caret-down.fa-lg.network-caret'), ]) case 'kovan-test-network': return h('.network-indicator', [ @@ -116,7 +116,7 @@ Network.prototype.render = function () { color: '#690496', }}, 'Kovan Test Net'), - h('i.fa.fa-caret-down.fa-lg'), + h('i.fa.fa-caret-down.fa-lg.network-caret'), ]) case 'rinkeby-test-network': return h('.network-indicator', [ @@ -129,7 +129,7 @@ Network.prototype.render = function () { color: '#e7a218', }}, 'Rinkeby Test Net'), - h('i.fa.fa-caret-down.fa-lg'), + h('i.fa.fa-caret-down.fa-lg.network-caret'), ]) default: return h('.network-indicator', [ @@ -145,7 +145,7 @@ Network.prototype.render = function () { color: '#AEAEAE', }}, 'Private Network'), - h('i.fa.fa-caret-down.fa-lg'), + h('i.fa.fa-caret-down.fa-lg.network-caret'), ]) } })(), diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index 1264da153..566224864 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -421,7 +421,9 @@ ConfirmSendEther.prototype.onSubmit = function (event) { ConfirmSendEther.prototype.cancel = function (event, txMeta) { event.preventDefault() - this.props.cancelTransaction(txMeta) + const { cancelTransaction } = this.props + + cancelTransaction(txMeta) } ConfirmSendEther.prototype.checkValidity = function () { @@ -445,26 +447,6 @@ ConfirmSendEther.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) - if (props.send.editingTransactionId) { - const { - send: { - memo, - amount: value, - gasLimit: gas, - gasPrice, - }, - } = props - const { txParams: { from, to } } = txData - txData.txParams = { - from: ethUtil.addHexPrefix(from), - to: ethUtil.addHexPrefix(to), - memo: memo && ethUtil.addHexPrefix(memo), - value: ethUtil.addHexPrefix(value), - gas: ethUtil.addHexPrefix(gas), - gasPrice: ethUtil.addHexPrefix(gasPrice), - } - } - // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData } diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index cc2df8299..aa4f29fb0 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -2,7 +2,6 @@ const Component = require('react').Component const { connect } = require('react-redux') const h = require('react-hyperscript') const inherits = require('util').inherits -const ethAbi = require('ethereumjs-abi') const tokenAbi = require('human-standard-token-abi') const abiDecoder = require('abi-decoder') abiDecoder.addABI(tokenAbi) @@ -67,15 +66,15 @@ function mapDispatchToProps (dispatch, ownProps) { const { txParams, id } = txMeta const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data) const { params = [] } = tokenData - const { value } = params[1] || {} - const amount = conversionUtil(value, { + const { value: to } = params[0] || {} + const { value: tokenAmountInDec } = params[1] || {} + const tokenAmountInHex = conversionUtil(tokenAmountInDec, { fromNumericBase: 'dec', toNumericBase: 'hex', }) const { gas: gasLimit, gasPrice, - to, } = txParams dispatch(actions.setSelectedToken(address)) dispatch(actions.updateSend({ @@ -83,7 +82,7 @@ function mapDispatchToProps (dispatch, ownProps) { gasPrice, gasTotal: null, to, - amount, + amount: tokenAmountInHex, errors: { to: null, amount: null }, editingTransactionId: id, })) @@ -415,7 +414,9 @@ ConfirmSendToken.prototype.onSubmit = function (event) { ConfirmSendToken.prototype.cancel = function (event, txMeta) { event.preventDefault() - this.props.cancelTransaction(txMeta) + const { cancelTransaction } = this.props + + cancelTransaction(txMeta) } ConfirmSendToken.prototype.checkValidity = function () { @@ -439,38 +440,6 @@ ConfirmSendToken.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) - if (props.send.editingTransactionId) { - const { - send: { - memo, - amount, - gasLimit: gas, - gasPrice, - }, - } = props - - const { txParams: { from, to } } = txData - - const tokenParams = { - from: ethUtil.addHexPrefix(from), - value: '0', - gas: ethUtil.addHexPrefix(gas), - gasPrice: ethUtil.addHexPrefix(gasPrice), - } - - const data = '0xa9059cbb' + Array.prototype.map.call( - ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]), - x => ('00' + x.toString(16)).slice(-2) - ).join('') - - txData.txParams = { - ...tokenParams, - to: ethUtil.addHexPrefix(to), - memo: memo && ethUtil.addHexPrefix(memo), - data, - } - } - // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData } diff --git a/ui/app/components/send/send-constants.js b/ui/app/components/send/send-constants.js index a961ffcd8..b3ee0899a 100644 --- a/ui/app/components/send/send-constants.js +++ b/ui/app/components/send/send-constants.js @@ -3,8 +3,8 @@ const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') const MIN_GAS_PRICE_HEX = (100000000).toString(16) const MIN_GAS_PRICE_DEC = '100000000' -const MIN_GAS_LIMIT_HEX = (21000).toString(16) -const MIN_GAS_LIMIT_DEC = 21000 +const MIN_GAS_LIMIT_DEC = '21000' +const MIN_GAS_LIMIT_HEX = (parseInt(MIN_GAS_LIMIT_DEC)).toString(16) const MIN_GAS_PRICE_GWEI = ethUtil.addHexPrefix(conversionUtil(MIN_GAS_PRICE_HEX, { fromDenomination: 'WEI', @@ -20,6 +20,8 @@ const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, { multiplierBase: 16, }) +const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb' + module.exports = { MIN_GAS_PRICE_GWEI, MIN_GAS_PRICE_HEX, @@ -27,4 +29,5 @@ module.exports = { MIN_GAS_LIMIT_HEX, MIN_GAS_LIMIT_DEC, MIN_GAS_TOTAL, + TOKEN_TRANSFER_FUNCTION_SIGNATURE, } diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js index 4451a6113..2d2ed4546 100644 --- a/ui/app/components/send/send-v2-container.js +++ b/ui/app/components/send/send-v2-container.js @@ -50,6 +50,7 @@ function mapStateToProps (state) { data, amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate, tokenContract: getSelectedTokenContract(state), + unapprovedTxs: state.metamask.unapprovedTxs, } } @@ -64,6 +65,7 @@ function mapDispatchToProps (dispatch) { ), signTx: txParams => dispatch(actions.signTx(txParams)), updateAndApproveTx: txParams => dispatch(actions.updateAndApproveTx(txParams)), + updateTx: txData => dispatch(actions.updateTransaction(txData)), setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)), addToAddressBook: address => dispatch(actions.addToAddressBook(address)), updateGasTotal: newTotal => dispatch(actions.updateGasTotal(newTotal)), @@ -77,6 +79,6 @@ function mapDispatchToProps (dispatch) { updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)), goHome: () => dispatch(actions.goHome()), clearSend: () => dispatch(actions.clearSend()), - backToConfirmScreen: editingTransactionId => dispatch(actions.showConfTxPage({ id: editingTransactionId })), + setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)), } } diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index b40c0ec0d..677b66830 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -86,7 +86,9 @@ TokenCell.prototype.render = function () { numberOfDecimals: 2, conversionRate: currentTokenToFiatRate, }) - formattedFiat = `${currentTokenInFiat} ${currentCurrency.toUpperCase()}` + formattedFiat = currentTokenInFiat.toString() === '0' + ? '' + : `${currentTokenInFiat} ${currentCurrency.toUpperCase()}` } const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 255f0e5eb..4e3d2cb93 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -1,6 +1,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const connect = require('react-redux').connect const EthBalance = require('./eth-balance') const addressSummary = require('../util').addressSummary @@ -9,18 +10,33 @@ const CopyButton = require('./copyButton') const vreme = new (require('vreme'))() const Tooltip = require('./tooltip') const numberToBN = require('number-to-bn') +const actions = require('../actions') const TransactionIcon = require('./transaction-list-item-icon') const ShiftListItem = require('./shift-list-item') -module.exports = TransactionListItem + +const mapDispatchToProps = dispatch => { + return { + retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)), + } +} + +module.exports = connect(null, mapDispatchToProps)(TransactionListItem) inherits(TransactionListItem, Component) function TransactionListItem () { Component.call(this) } +TransactionListItem.prototype.showRetryButton = function () { + const { transaction = {} } = this.props + const { status, time } = transaction + return status === 'submitted' && Date.now() - time > 30000 +} + TransactionListItem.prototype.render = function () { const { transaction, network, conversionRate, currentCurrency } = this.props + const { status } = transaction if (transaction.key === 'shapeshift') { if (network === '1') return h(ShiftListItem, transaction) } @@ -32,7 +48,7 @@ TransactionListItem.prototype.render = function () { var isMsg = ('msgParams' in transaction) var isTx = ('txParams' in transaction) - var isPending = transaction.status === 'unapproved' + var isPending = status === 'unapproved' let txParams if (isTx) { txParams = transaction.txParams @@ -44,7 +60,7 @@ TransactionListItem.prototype.render = function () { const isClickable = ('hash' in transaction && isLinkable) || isPending return ( - h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { + h('.transaction-list-item.flex-column', { onClick: (event) => { if (isPending) { this.props.showTx(transaction.id) @@ -56,51 +72,92 @@ TransactionListItem.prototype.render = function () { }, style: { padding: '20px 0', + alignItems: 'center', }, }, [ - - h('.identicon-wrapper.flex-column.flex-center.select-none', [ - h(TransactionIcon, { txParams, transaction, isTx, isMsg }), + h(`.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { + style: { + width: '100%', + }, + }, [ + h('.identicon-wrapper.flex-column.flex-center.select-none', [ + h(TransactionIcon, { txParams, transaction, isTx, isMsg }), + ]), + + h(Tooltip, { + title: 'Transaction Number', + position: 'right', + }, [ + h('span', { + style: { + display: 'flex', + cursor: 'normal', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + padding: '10px', + }, + }, nonce), + ]), + + h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [ + domainField(txParams), + h('div', date), + recipientField(txParams, transaction, isTx, isMsg), + ]), + + // Places a copy button if tx is successful, else places a placeholder empty div. + transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}), + + isTx ? h(EthBalance, { + value: txParams.value, + conversionRate, + currentCurrency, + width: '55px', + shorten: true, + showFiat: false, + style: {fontSize: '15px'}, + }) : h('.flex-column'), ]), - h(Tooltip, { - title: 'Transaction Number', - position: 'right', + this.showRetryButton() && h('.transition-list-item__retry.grow-on-hover', { + onClick: event => { + event.stopPropagation() + this.resubmit() + }, + style: { + height: '22px', + borderRadius: '22px', + color: '#F9881B', + padding: '0 20px', + backgroundColor: '#FFE3C9', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + fontSize: '8px', + cursor: 'pointer', + }, }, [ - h('span', { + h('div', { style: { - display: 'flex', - cursor: 'normal', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - padding: '10px', + paddingRight: '2px', }, - }, nonce), - ]), - - h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [ - domainField(txParams), - h('div', date), - recipientField(txParams, transaction, isTx, isMsg), + }, 'Taking too long?'), + h('div', { + style: { + textDecoration: 'underline', + }, + }, 'Retry with a higher gas price here'), ]), - - // Places a copy button if tx is successful, else places a placeholder empty div. - transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}), - - isTx ? h(EthBalance, { - value: txParams.value, - conversionRate, - currentCurrency, - width: '55px', - shorten: true, - showFiat: false, - style: {fontSize: '15px'}, - }) : h('.flex-column'), ]) ) } +TransactionListItem.prototype.resubmit = function () { + const { transaction } = this.props + this.props.retryTransaction(transaction.id) +} + function domainField (txParams) { return h('div', { style: { diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index 4e76147a1..8a9253d4d 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -170,6 +170,7 @@ TxListItem.prototype.getSendTokenTotal = async function () { TxListItem.prototype.render = function () { const { transactionStatus, + transactionAmount, onClick, transActionId, dateString, @@ -177,6 +178,7 @@ TxListItem.prototype.render = function () { className, } = this.props const { total, fiatTotal } = this.state + const showFiatTotal = transactionAmount !== '0x0' && fiatTotal return h(`div${className || ''}`, { key: transActionId, @@ -238,7 +240,7 @@ TxListItem.prototype.render = function () { }), }, total), - fiatTotal && h('span.tx-list-fiat-value', fiatTotal), + showFiatTotal && h('span.tx-list-fiat-value', fiatTotal), ]), ]), diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js index ebef22680..e42a20c85 100644 --- a/ui/app/components/tx-view.js +++ b/ui/app/components/tx-view.js @@ -14,6 +14,7 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(TxView) function mapStateToProps (state) { const sidebarOpen = state.appState.sidebarOpen + const isMascara = state.appState.isMascara const identities = state.metamask.identities const accounts = state.metamask.accounts @@ -31,6 +32,7 @@ function mapStateToProps (state) { selectedToken: selectors.getSelectedToken(state), identity, network, + isMascara, } } @@ -98,7 +100,7 @@ TxView.prototype.renderButtons = function () { } TxView.prototype.render = function () { - const { selectedAddress, identity, network } = this.props + const { selectedAddress, identity, network, isMascara } = this.props return h('div.tx-view.flex-column', { style: {}, @@ -107,6 +109,7 @@ TxView.prototype.render = function () { h('div.flex-row.phone-visible', { style: { margin: '1em 0.9em', + justifyContent: 'space-between', alignItems: 'center', }, }, [ @@ -139,6 +142,10 @@ TxView.prototype.render = function () { identity.name, ]), + !isMascara && h('div.open-in-browser', { + onClick: () => global.platform.openExtensionInBrowser(), + }, [h('img', { src: 'images/open.svg' })]), + ]), this.renderHeroBalance(), diff --git a/ui/app/css/index.scss b/ui/app/css/index.scss index 01899ccad..445c819ff 100644 --- a/ui/app/css/index.scss +++ b/ui/app/css/index.scss @@ -4,6 +4,7 @@ http://www.creativebloq.com/web-design/manage-large-css-projects-itcss-101517528 https://www.xfive.co/blog/itcss-scalable-maintainable-css-architecture/ */ + @import './itcss/settings/index.scss'; @import './itcss/tools/index.scss'; @import './itcss/generic/index.scss'; diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss index e40e5a8c0..e16d2e024 100644 --- a/ui/app/css/itcss/components/account-menu.scss +++ b/ui/app/css/itcss/components/account-menu.scss @@ -40,7 +40,7 @@ font-size: 12px; line-height: 23px; padding: 0 24px; - font-weight: 200; + font-weight: 300; } img { @@ -113,7 +113,7 @@ &__name { color: $white; font-size: 18px; - font-weight: 200; + font-weight: 300; line-height: 16px; } @@ -126,7 +126,7 @@ &__action { font-size: 16px; line-height: 18px; - font-weight: 200; + font-weight: 300; cursor: pointer; } } diff --git a/ui/app/css/itcss/components/menu.scss b/ui/app/css/itcss/components/menu.scss index 17e24de98..eb92a1b70 100644 --- a/ui/app/css/itcss/components/menu.scss +++ b/ui/app/css/itcss/components/menu.scss @@ -11,8 +11,8 @@ flex-flow: row nowrap; align-items: center; position: relative; - z-index: 200; - font-weight: 200; + font-weight: 300; + z-index: 201; @media screen and (max-width: 575px) { padding: 14px; diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index b69bd5c7e..9b64564d6 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -563,3 +563,39 @@ } } } + +//Notification Modal + +.notification-modal-wrapper { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + position: relative; + border: 1px solid $alto; + box-shadow: 0 0 2px 2px $alto; + font-family: Roboto; +} + +.notification-modal-header { + background: $wild-sand; + width: 100%; + display: flex; + justify-content: center; + padding: 30px; + font-size: 22px; + color: $nile-blue; + height: 79px; +} + +.notification-modal-message { + padding: 20px; +} + +.notification-modal-message { + width: 100%; + display: flex; + justify-content: center; + font-size: 17px; + color: $nile-blue; +}
\ No newline at end of file diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss index 244de2ba0..61dfbd176 100644 --- a/ui/app/css/itcss/components/newui-sections.scss +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -38,6 +38,10 @@ $wallet-view-bg: $wild-sand; } } +.open-in-browser { + cursor: pointer; +} + // wallet view and sidebar .wallet-view { @@ -248,7 +252,7 @@ $wallet-view-bg: $wild-sand; // wallet view .account-name { font-size: 24px; - font-weight: 200; + font-weight: 300; line-height: 20px; color: $scorpion; margin-top: 8px; diff --git a/ui/app/css/itcss/components/settings.scss b/ui/app/css/itcss/components/settings.scss index 2f29d8017..d60ebd934 100644 --- a/ui/app/css/itcss/components/settings.scss +++ b/ui/app/css/itcss/components/settings.scss @@ -145,6 +145,11 @@ color: $monzo; } +.settings__clear-button--orange { + border: 1px solid rgba(247, 134, 28, 1); + color: rgba(247, 134, 28, 1); +} + .settings__info-logo-wrapper { height: 80px; margin-bottom: 20px; diff --git a/ui/app/first-time/init-menu.js b/ui/app/first-time/init-menu.js index cc7c51bd3..b4587f1ee 100644 --- a/ui/app/first-time/init-menu.js +++ b/ui/app/first-time/init-menu.js @@ -8,6 +8,8 @@ const actions = require('../actions') const Tooltip = require('../components/tooltip') const getCaretCoordinates = require('textarea-caret') +let isSubmitting = false + module.exports = connect(mapStateToProps)(InitializeMenuScreen) inherits(InitializeMenuScreen, Component) @@ -164,7 +166,10 @@ InitializeMenuScreen.prototype.createNewVaultAndKeychain = function () { return } - this.props.dispatch(actions.createNewVaultAndKeychain(password)) + if (!isSubmitting) { + isSubmitting = true + this.props.dispatch(actions.createNewVaultAndKeychain(password)) + } } InitializeMenuScreen.prototype.inputChanged = function (event) { diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index 83161320e..294c29948 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -1,6 +1,7 @@ const extend = require('xtend') const actions = require('../actions') const MetamascaraPlatform = require('../../../app/scripts/platforms/window') +const { OLD_UI_NETWORK_TYPE } = require('../../../app/scripts/config').enums module.exports = reduceMetamask @@ -33,9 +34,13 @@ function reduceMetamask (state, action) { amount: '0x0', memo: '', errors: {}, + maxModeOn: false, editingTransactionId: null, }, coinOptions: {}, + useBlockie: false, + featureFlags: {}, + networkEndpointType: OLD_UI_NETWORK_TYPE, }, state.metamask) switch (action.type) { @@ -257,6 +262,14 @@ function reduceMetamask (state, action) { }, }) + case actions.UPDATE_MAX_MODE: + return extend(metamaskState, { + send: { + ...metamaskState.send, + maxModeOn: action.value, + }, + }) + case actions.UPDATE_SEND: return extend(metamaskState, { send: { @@ -309,11 +322,26 @@ function reduceMetamask (state, action) { return extend(metamaskState, { tokenExchangeRates: { ...metamaskState.tokenExchangeRates, - [marketinfo.pair]: ssMarketInfo, + [ssMarketInfo.pair]: ssMarketInfo, }, coinOptions, }) + case actions.SET_USE_BLOCKIE: + return extend(metamaskState, { + useBlockie: action.value, + }) + + case actions.UPDATE_FEATURE_FLAGS: + return extend(metamaskState, { + featureFlags: action.value, + }) + + case actions.UPDATE_NETWORK_ENDPOINT_TYPE: + return extend(metamaskState, { + networkEndpointType: action.value, + }) + default: return metamaskState diff --git a/ui/app/root.js b/ui/app/root.js index 9e7314b20..21d6d1829 100644 --- a/ui/app/root.js +++ b/ui/app/root.js @@ -2,7 +2,7 @@ const inherits = require('util').inherits const Component = require('react').Component const Provider = require('react-redux').Provider const h = require('react-hyperscript') -const App = require('./app') +const SelectedApp = require('./select-app') module.exports = Root @@ -15,7 +15,7 @@ Root.prototype.render = function () { h(Provider, { store: this.props.store, }, [ - h(App), + h(SelectedApp), ]) ) diff --git a/ui/app/select-app.js b/ui/app/select-app.js new file mode 100644 index 000000000..ac6867aeb --- /dev/null +++ b/ui/app/select-app.js @@ -0,0 +1,61 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const connect = require('react-redux').connect +const h = require('react-hyperscript') +const App = require('./app') +const OldApp = require('../../old-ui/app/app') +const { autoAddToBetaUI } = require('./selectors') +const { setFeatureFlag, setNetworkEndpoints } = require('./actions') +const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums + +function mapStateToProps (state) { + return { + betaUI: state.metamask.featureFlags.betaUI, + autoAdd: autoAddToBetaUI(state), + isUnlocked: state.metamask.isUnlocked, + isMascara: state.metamask.isMascara, + firstTime: Object.keys(state.metamask.identities).length === 0, + } +} + +function mapDispatchToProps (dispatch) { + return { + setFeatureFlagWithModal: () => { + return dispatch(setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL')) + .then(() => dispatch(setNetworkEndpoints(BETA_UI_NETWORK_TYPE))) + }, + setFeatureFlagWithoutModal: () => { + return dispatch(setFeatureFlag('betaUI', true)) + .then(() => dispatch(setNetworkEndpoints(BETA_UI_NETWORK_TYPE))) + }, + } +} +module.exports = connect(mapStateToProps, mapDispatchToProps)(SelectedApp) + +inherits(SelectedApp, Component) +function SelectedApp () { + Component.call(this) +} + +SelectedApp.prototype.componentWillReceiveProps = function (nextProps) { + const { + isUnlocked, + setFeatureFlagWithModal, + setFeatureFlagWithoutModal, + isMascara, + firstTime, + } = this.props + + if (isMascara || firstTime) { + setFeatureFlagWithoutModal() + } else if (!isUnlocked && nextProps.isUnlocked && (nextProps.autoAdd)) { + setFeatureFlagWithModal() + } +} + +SelectedApp.prototype.render = function () { + const { betaUI, isMascara, firstTime } = this.props + + const Selected = betaUI || isMascara || firstTime ? App : OldApp + return h(Selected) +} diff --git a/ui/app/selectors.js b/ui/app/selectors.js index a5f9a75d8..22ef439c4 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -24,6 +24,8 @@ const selectors = { getSendAmount, getSelectedTokenToFiatRate, getSelectedTokenContract, + autoAddToBetaUI, + getSendMaxModeState, } module.exports = selectors @@ -135,6 +137,10 @@ function getSendAmount (state) { return state.metamask.send.amount } +function getSendMaxModeState (state) { + return state.metamask.send.maxModeOn +} + function getCurrentCurrency (state) { return state.metamask.currentCurrency } @@ -158,3 +164,20 @@ function getSelectedTokenContract (state) { ? global.eth.contract(abi).at(selectedToken.address) : null } + +function autoAddToBetaUI (state) { + const autoAddTransactionThreshold = 12 + const autoAddAccountsThreshold = 2 + const autoAddTokensThreshold = 1 + + const numberOfTransactions = state.metamask.selectedAddressTxList.length + const numberOfAccounts = Object.keys(state.metamask.accounts).length + const numberOfTokensAdded = state.metamask.tokens.length + + const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) && + (numberOfAccounts > autoAddAccountsThreshold) && + (numberOfTokensAdded > autoAddTokensThreshold) + const userIsNotInBeta = !state.metamask.featureFlags.betaUI + + return userIsNotInBeta && userPassesThreshold +}
\ No newline at end of file diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index 788ae87b4..7c9b6dbc6 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -2,6 +2,7 @@ const { inherits } = require('util') const PersistentForm = require('../lib/persistent-form') const h = require('react-hyperscript') +const ethAbi = require('ethereumjs-abi') const ethUtil = require('ethereumjs-util') const Identicon = require('./components/identicon') @@ -13,8 +14,7 @@ const GasFeeDisplay = require('./components/send/gas-fee-display-v2') const { MIN_GAS_TOTAL, - MIN_GAS_PRICE_HEX, - MIN_GAS_LIMIT_HEX, + TOKEN_TRANSFER_FUNCTION_SIGNATURE, } = require('./components/send/send-constants') const { @@ -313,8 +313,9 @@ SendTransactionScreen.prototype.renderToRow = function () { SendTransactionScreen.prototype.handleAmountChange = function (value) { const amount = value - const { updateSendAmount } = this.props + const { updateSendAmount, setMaxModeTo } = this.props + setMaxModeTo(false) this.validateAmount(amount) updateSendAmount(amount) } @@ -324,11 +325,9 @@ SendTransactionScreen.prototype.setAmountToMax = function () { from: { balance }, updateSendAmount, updateSendErrors, - updateGasPrice, - updateGasLimit, - updateGasTotal, tokenBalance, selectedToken, + gasTotal, } = this.props const { decimals } = selectedToken || {} const multiplier = Math.pow(10, Number(decimals || 0)) @@ -337,16 +336,12 @@ SendTransactionScreen.prototype.setAmountToMax = function () { ? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'}) : subtractCurrencies( ethUtil.addHexPrefix(balance), - ethUtil.addHexPrefix(MIN_GAS_TOTAL), + ethUtil.addHexPrefix(gasTotal), { toNumericBase: 'hex' } ) updateSendErrors({ amount: null }) - if (!selectedToken) { - updateGasPrice(MIN_GAS_PRICE_HEX) - updateGasLimit(MIN_GAS_LIMIT_HEX) - updateGasTotal(MIN_GAS_TOTAL) - } + updateSendAmount(maxAmount) } @@ -407,19 +402,22 @@ SendTransactionScreen.prototype.renderAmountRow = function () { amountConversionRate, errors, amount, + setMaxModeTo, + maxModeOn, } = this.props return h('div.send-v2__form-row', [ - h('div.send-v2__form-label', [ + h('div.send-v2__form-label', [ 'Amount:', this.renderErrorMessage('amount'), !errors.amount && h('div.send-v2__amount-max', { onClick: (event) => { event.preventDefault() + setMaxModeTo(true) this.setAmountToMax() }, - }, [ 'Max' ]), + }, [ !maxModeOn ? 'Max' : '' ]), ]), h('div.send-v2__form-field', [ @@ -556,6 +554,48 @@ SendTransactionScreen.prototype.addToAddressBookIfNew = function (newAddress) { } } +SendTransactionScreen.prototype.getEditedTx = function () { + const { + from: {address: from}, + to, + amount, + gasLimit: gas, + gasPrice, + selectedToken, + editingTransactionId, + unapprovedTxs, + } = this.props + + const editingTx = { + ...unapprovedTxs[editingTransactionId], + txParams: { + from: ethUtil.addHexPrefix(from), + gas: ethUtil.addHexPrefix(gas), + gasPrice: ethUtil.addHexPrefix(gasPrice), + }, + } + + if (selectedToken) { + const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call( + ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]), + x => ('00' + x.toString(16)).slice(-2) + ).join('') + + Object.assign(editingTx.txParams, { + value: ethUtil.addHexPrefix('0'), + to: ethUtil.addHexPrefix(selectedToken.address), + data, + }) + } else { + Object.assign(editingTx.txParams, { + value: ethUtil.addHexPrefix(amount), + to: ethUtil.addHexPrefix(to), + }) + } + + return editingTx +} + SendTransactionScreen.prototype.onSubmit = function (event) { event.preventDefault() const { @@ -566,10 +606,10 @@ SendTransactionScreen.prototype.onSubmit = function (event) { gasPrice, signTokenTx, signTx, + updateTx, selectedToken, editingTransactionId, errors: { amount: amountError, to: toError }, - backToConfirmScreen, } = this.props const noErrors = !amountError && toError === null @@ -581,23 +621,25 @@ SendTransactionScreen.prototype.onSubmit = function (event) { this.addToAddressBookIfNew(to) if (editingTransactionId) { - backToConfirmScreen(editingTransactionId) - return - } + const editedTx = this.getEditedTx() - const txParams = { - from, - value: '0', - gas, - gasPrice, - } + updateTx(editedTx) + } else { - if (!selectedToken) { - txParams.value = amount - txParams.to = to - } + const txParams = { + from, + value: '0', + gas, + gasPrice, + } + + if (!selectedToken) { + txParams.value = amount + txParams.to = to + } - selectedToken - ? signTokenTx(selectedToken.address, to, amount, txParams) - : signTx(txParams) + selectedToken + ? signTokenTx(selectedToken.address, to, amount, txParams) + : signTx(txParams) + } } diff --git a/ui/app/settings.js b/ui/app/settings.js index 786a70e7e..a3dd65f14 100644 --- a/ui/app/settings.js +++ b/ui/app/settings.js @@ -8,6 +8,8 @@ const validUrl = require('valid-url') const { exportAsFile } = require('./util') 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 getInfuraCurrencyOptions = () => { const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { @@ -51,6 +53,26 @@ class Settings extends Component { ]) } + renderBlockieOptIn () { + const { metamask: { useBlockie }, setUseBlockie } = this.props + + return h('div.settings__content-row', [ + h('div.settings__content-item', [ + h('span', 'Use Blockies Identicon'), + ]), + h('div.settings__content-item', [ + h('div.settings__content-item-col', [ + h(ToggleButton, { + value: useBlockie, + onToggle: (value) => setUseBlockie(!value), + activeLabel: '', + inactiveLabel: '', + }), + ]), + ]), + ]) + } + renderCurrentConversion () { const { metamask: { currentCurrency, conversionDate }, setCurrentCurrency } = this.props @@ -208,17 +230,39 @@ class Settings extends Component { ) } + renderOldUI () { + const { setFeatureFlagToBeta } = this.props + + return ( + h('div.settings__content-row', [ + h('div.settings__content-item', 'Use old UI'), + h('div.settings__content-item', [ + h('div.settings__content-item-col', [ + h('button.settings__clear-button.settings__clear-button--orange', { + onClick (event) { + event.preventDefault() + setFeatureFlagToBeta() + }, + }, 'Use old UI'), + ]), + ]), + ]) + ) + } + renderSettingsContent () { - const { warning } = this.props + const { warning, isMascara } = this.props return ( h('div.settings__content', [ warning && h('div.settings__error', warning), + this.renderBlockieOptIn(), this.renderCurrentConversion(), // this.renderCurrentProvider(), this.renderNewRpcUrl(), this.renderStateLogs(), this.renderSeedWords(), + !isMascara && this.renderOldUI(), ]) ) } @@ -335,18 +379,22 @@ class Settings extends Component { Settings.propTypes = { tab: PropTypes.string, metamask: PropTypes.object, + setUseBlockie: PropTypes.func, setCurrentCurrency: PropTypes.func, setRpcTarget: PropTypes.func, displayWarning: PropTypes.func, revealSeedConfirmation: PropTypes.func, + setFeatureFlagToBeta: PropTypes.func, warning: PropTypes.string, goHome: PropTypes.func, + isMascara: PropTypes.bool, } const mapStateToProps = state => { return { metamask: state.metamask, warning: state.appState.warning, + isMascara: state.metamask.isMascara, } } @@ -357,6 +405,11 @@ const mapDispatchToProps = dispatch => { setRpcTarget: newRpc => dispatch(actions.setRpcTarget(newRpc)), displayWarning: warning => dispatch(actions.displayWarning(warning)), revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()), + setUseBlockie: value => dispatch(actions.setUseBlockie(value)), + setFeatureFlagToBeta: () => { + return dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')) + .then(() => dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE))) + }, } } |