aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app
diff options
context:
space:
mode:
authorDan <danjm.com@gmail.com>2018-05-01 10:42:57 +0800
committerDan <danjm.com@gmail.com>2018-05-01 10:42:57 +0800
commit2f78fffbdbb0e41d73bcde2c15c88fff095614b7 (patch)
tree24d68a267304d085ee1b7c705ce5ade53c9439c3 /ui/app
parentf96c13d616e429447ac0a6a24c6aeee902162b88 (diff)
parent954394f81090b1a6a4afe55243caa3671b88addc (diff)
downloadtangerine-wallet-browser-2f78fffbdbb0e41d73bcde2c15c88fff095614b7.tar.gz
tangerine-wallet-browser-2f78fffbdbb0e41d73bcde2c15c88fff095614b7.tar.zst
tangerine-wallet-browser-2f78fffbdbb0e41d73bcde2c15c88fff095614b7.zip
Merge branch 'i3725-refactor-send-component-' into i3725-refactor-send-component-2
Diffstat (limited to 'ui/app')
-rw-r--r--ui/app/actions.js311
-rw-r--r--ui/app/app.js932
-rw-r--r--ui/app/components/account-dropdowns.js5
-rw-r--r--ui/app/components/account-menu/index.js54
-rw-r--r--ui/app/components/balance-component.js18
-rw-r--r--ui/app/components/buy-button-subview.js4
-rw-r--r--ui/app/components/customize-gas-modal/index.js8
-rw-r--r--ui/app/components/dropdowns/components/account-dropdowns.js5
-rw-r--r--ui/app/components/dropdowns/network-dropdown.js14
-rw-r--r--ui/app/components/ens-input.js1
-rw-r--r--ui/app/components/loading.js18
-rw-r--r--ui/app/components/modals/buy-options-modal.js4
-rw-r--r--ui/app/components/modals/deposit-ether-modal.js4
-rw-r--r--ui/app/components/modals/export-private-key-modal.js7
-rw-r--r--ui/app/components/modals/modal.js11
-rw-r--r--ui/app/components/page-container/page-container-content.component.js18
-rw-r--r--ui/app/components/page-container/page-container-header.component.js35
-rw-r--r--ui/app/components/pages/add-token.js (renamed from ui/app/add-token.js)22
-rw-r--r--ui/app/components/pages/authenticated.js34
-rw-r--r--ui/app/components/pages/create-account/import-account/index.js (renamed from ui/app/accounts/import/index.js)0
-rw-r--r--ui/app/components/pages/create-account/import-account/json.js (renamed from ui/app/accounts/import/json.js)16
-rw-r--r--ui/app/components/pages/create-account/import-account/private-key.js (renamed from ui/app/accounts/import/private-key.js)21
-rw-r--r--ui/app/components/pages/create-account/import-account/seed.js (renamed from ui/app/accounts/import/seed.js)0
-rw-r--r--ui/app/components/pages/create-account/index.js81
-rw-r--r--ui/app/components/pages/create-account/new-account.js (renamed from ui/app/accounts/new-account/create-form.js)30
-rw-r--r--ui/app/components/pages/home.js333
-rw-r--r--ui/app/components/pages/initialized.js25
-rw-r--r--ui/app/components/pages/keychains/restore-vault.js178
-rw-r--r--ui/app/components/pages/keychains/reveal-seed.js195
-rw-r--r--ui/app/components/pages/metamask-route.js28
-rw-r--r--ui/app/components/pages/notice.js203
-rw-r--r--ui/app/components/pages/settings/index.js59
-rw-r--r--ui/app/components/pages/settings/info.js120
-rw-r--r--ui/app/components/pages/settings/settings.js (renamed from ui/app/settings.js)163
-rw-r--r--ui/app/components/pages/unlock.js194
-rw-r--r--ui/app/components/pending-tx/confirm-send-ether.js58
-rw-r--r--ui/app/components/pending-tx/confirm-send-token.js61
-rw-r--r--ui/app/components/pending-tx/index.js31
-rw-r--r--ui/app/components/qr-code.js13
-rw-r--r--ui/app/components/send/account-list-item.js3
-rw-r--r--ui/app/components/send/currency-display.js35
-rw-r--r--ui/app/components/send/send-v2-container.js18
-rw-r--r--ui/app/components/send_/account-list-item/account-list-item-README.md0
-rw-r--r--ui/app/components/send_/account-list-item/account-list-item.component.js74
-rw-r--r--ui/app/components/send_/account-list-item/account-list-item.container.js15
-rw-r--r--ui/app/components/send_/account-list-item/account-list-item.scss0
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js54
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js40
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js9
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js22
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js96
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js69
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js9
-rw-r--r--ui/app/components/send_/send-content/send-content.component.js23
-rw-r--r--ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js75
-rw-r--r--ui/app/components/send_/send-content/send-from-row/send-from-row.component.js29
-rw-r--r--ui/app/components/send_/send-content/send-from-row/send-from-row.container.js34
-rw-r--r--ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js4
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js49
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js26
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js9
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js10
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js3
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js22
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row.component.js48
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row.container.js28
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js2
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js12
-rw-r--r--ui/app/components/send_/send-footer/send-footer.component.js93
-rw-r--r--ui/app/components/send_/send-footer/send-footer.container.js106
-rw-r--r--ui/app/components/send_/send-footer/send-footer.selectors.js12
-rw-r--r--ui/app/components/send_/send-footer/send-footer.utils.js84
-rw-r--r--ui/app/components/send_/send-header/send-header.component.js17
-rw-r--r--ui/app/components/send_/send-header/send-header.container.js5
-rw-r--r--ui/app/components/send_/send.component.js142
-rw-r--r--ui/app/components/send_/send.constants.js44
-rw-r--r--ui/app/components/send_/send.container.js88
-rw-r--r--ui/app/components/send_/send.selectors.js292
-rw-r--r--ui/app/components/send_/send.utils.js188
-rw-r--r--ui/app/components/signature-request.js17
-rw-r--r--ui/app/components/tab-bar.js26
-rw-r--r--ui/app/components/token-balance.js1
-rw-r--r--ui/app/components/token-cell.js20
-rw-r--r--ui/app/components/token-list.js1
-rw-r--r--ui/app/components/tx-list-item.js23
-rw-r--r--ui/app/components/tx-list.js18
-rw-r--r--ui/app/components/tx-view.js21
-rw-r--r--ui/app/components/wallet-view.js27
-rw-r--r--ui/app/conf-tx.js75
-rw-r--r--ui/app/css/index.scss6
-rw-r--r--ui/app/css/itcss/components/add-token.scss1
-rw-r--r--ui/app/css/itcss/components/index.scss2
-rw-r--r--ui/app/css/itcss/components/loading-overlay.scss12
-rw-r--r--ui/app/css/itcss/components/network.scss2
-rw-r--r--ui/app/css/itcss/components/new-account.scss5
-rw-r--r--ui/app/css/itcss/components/pages/index.scss1
-rw-r--r--ui/app/css/itcss/components/pages/unlock.scss9
-rw-r--r--ui/app/css/itcss/components/sections.scss6
-rw-r--r--ui/app/css/itcss/components/send.scss1
-rw-r--r--ui/app/ducks/send.js44
-rw-r--r--ui/app/first-time/init-menu.js352
-rw-r--r--ui/app/i18n-provider.js7
-rw-r--r--ui/app/keychains/hd/recover-seed/confirmation.js14
-rw-r--r--ui/app/keychains/hd/restore-vault.js1
-rw-r--r--ui/app/main-container.js5
-rw-r--r--ui/app/reducers.js2
-rw-r--r--ui/app/reducers/app.js1
-rw-r--r--ui/app/reducers/metamask.js30
-rw-r--r--ui/app/root.js33
-rw-r--r--ui/app/routes.js49
-rw-r--r--ui/app/select-app.js12
-rw-r--r--ui/app/selectors.js21
-rw-r--r--ui/app/unlock.js7
-rw-r--r--ui/app/util.js15
-rw-r--r--ui/app/welcome-screen.js35
115 files changed, 4596 insertions, 1509 deletions
diff --git a/ui/app/actions.js b/ui/app/actions.js
index aae037a2d..7b838940a 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -1,8 +1,14 @@
const abi = require('human-standard-token-abi')
const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
const { getTokenAddressFromTokenObject } = require('./util')
+const {
+ calcGasTotal,
+ getParamsForGasEstimate,
+ calcTokenBalance,
+} = require('./components/send_/send.utils')
const ethUtil = require('ethereumjs-util')
const { fetchLocale } = require('../i18n-helper')
+const log = require('loglevel')
var actions = {
_setBackgroundConnection: _setBackgroundConnection,
@@ -172,14 +178,16 @@ var actions = {
updateGasLimit,
updateGasPrice,
updateGasTotal,
+ setGasTotal,
+ setSendTokenBalance,
updateSendTokenBalance,
updateSendFrom,
updateSendTo,
updateSendAmount,
updateSendMemo,
- updateSendErrors,
setMaxModeTo,
updateSend,
+ updateSendErrors,
clearSend,
setSelectedAddress,
// app messages
@@ -222,8 +230,6 @@ var actions = {
coinBaseSubview: coinBaseSubview,
SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW',
shapeShiftSubview: shapeShiftSubview,
- UPDATE_TOKEN_EXCHANGE_RATE: 'UPDATE_TOKEN_EXCHANGE_RATE',
- updateTokenExchangeRate,
PAIR_UPDATE: 'PAIR_UPDATE',
pairUpdate: pairUpdate,
coinShiftRquest: coinShiftRquest,
@@ -348,16 +354,14 @@ function transitionBackward () {
}
}
-function confirmSeedWords () {
- return (dispatch) => {
- dispatch(actions.showLoadingIndication())
- log.debug(`background.clearSeedWordCache`)
+function clearSeedWordCache () {
+ log.debug(`background.clearSeedWordCache`)
+ return dispatch => {
return new Promise((resolve, reject) => {
background.clearSeedWordCache((err, account) => {
- dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
- reject(err)
+ return reject(err)
}
log.info('Seed word cache cleared. ' + account)
@@ -368,6 +372,22 @@ function confirmSeedWords () {
}
}
+function confirmSeedWords () {
+ return async dispatch => {
+ dispatch(actions.showLoadingIndication())
+ const account = await dispatch(clearSeedWordCache())
+ return dispatch(setIsRevealingSeedWords(false))
+ .then(() => {
+ dispatch(actions.hideLoadingIndication())
+ return account
+ })
+ .catch(() => {
+ dispatch(actions.hideLoadingIndication())
+ return account
+ })
+ }
+}
+
function createNewVaultAndRestore (password, seed) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
@@ -449,11 +469,13 @@ function requestRevealSeed (password) {
}
dispatch(actions.showNewVaultSeed(result))
- dispatch(actions.hideLoadingIndication())
resolve()
})
})
})
+ .then(() => dispatch(setIsRevealingSeedWords(true)))
+ .then(() => dispatch(actions.hideLoadingIndication()))
+ .catch(() => dispatch(actions.hideLoadingIndication()))
}
}
@@ -573,35 +595,47 @@ function signMsg (msgData) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
- log.debug(`actions calling background.signMessage`)
- background.signMessage(msgData, (err, newState) => {
- log.debug('signMessage called back')
- dispatch(actions.updateMetamaskState(newState))
- dispatch(actions.hideLoadingIndication())
+ return new Promise((resolve, reject) => {
+ log.debug(`actions calling background.signMessage`)
+ background.signMessage(msgData, (err, newState) => {
+ log.debug('signMessage called back')
+ dispatch(actions.updateMetamaskState(newState))
+ dispatch(actions.hideLoadingIndication())
- if (err) log.error(err)
- if (err) return dispatch(actions.displayWarning(err.message))
+ if (err) {
+ log.error(err)
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
- dispatch(actions.completedTx(msgData.metamaskId))
+ dispatch(actions.completedTx(msgData.metamaskId))
+ return resolve(msgData)
+ })
})
}
}
function signPersonalMsg (msgData) {
log.debug('action - signPersonalMsg')
- return (dispatch) => {
+ return dispatch => {
dispatch(actions.showLoadingIndication())
- log.debug(`actions calling background.signPersonalMessage`)
- background.signPersonalMessage(msgData, (err, newState) => {
- log.debug('signPersonalMessage called back')
- dispatch(actions.updateMetamaskState(newState))
- dispatch(actions.hideLoadingIndication())
+ return new Promise((resolve, reject) => {
+ log.debug(`actions calling background.signPersonalMessage`)
+ background.signPersonalMessage(msgData, (err, newState) => {
+ log.debug('signPersonalMessage called back')
+ dispatch(actions.updateMetamaskState(newState))
+ dispatch(actions.hideLoadingIndication())
- if (err) log.error(err)
- if (err) return dispatch(actions.displayWarning(err.message))
+ if (err) {
+ log.error(err)
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
- dispatch(actions.completedTx(msgData.metamaskId))
+ dispatch(actions.completedTx(msgData.metamaskId))
+ return resolve(msgData)
+ })
})
}
}
@@ -611,16 +645,22 @@ function signTypedMsg (msgData) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
- log.debug(`actions calling background.signTypedMessage`)
- background.signTypedMessage(msgData, (err, newState) => {
- log.debug('signTypedMessage called back')
- dispatch(actions.updateMetamaskState(newState))
- dispatch(actions.hideLoadingIndication())
+ return new Promise((resolve, reject) => {
+ log.debug(`actions calling background.signTypedMessage`)
+ background.signTypedMessage(msgData, (err, newState) => {
+ log.debug('signTypedMessage called back')
+ dispatch(actions.updateMetamaskState(newState))
+ dispatch(actions.hideLoadingIndication())
- if (err) log.error(err)
- if (err) return dispatch(actions.displayWarning(err.message))
+ if (err) {
+ log.error(err)
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
- dispatch(actions.completedTx(msgData.metamaskId))
+ dispatch(actions.completedTx(msgData.metamaskId))
+ return resolve(msgData)
+ })
})
}
}
@@ -683,14 +723,64 @@ function updateGasPrice (gasPrice) {
}
}
-function updateGasTotal (gasTotal) {
+function setGasTotal (gasTotal) {
return {
type: actions.UPDATE_GAS_TOTAL,
value: gasTotal,
}
}
-function updateSendTokenBalance (tokenBalance) {
+function updateGasTotal ({ selectedAddress, selectedToken, data }) {
+ return (dispatch) => {
+ const { symbol } = selectedToken || {}
+ const estimateGasParams = getParamsForGasEstimate(selectedAddress, symbol, data)
+ return Promise.all([
+ dispatch(actions.getGasPrice()),
+ dispatch(actions.estimateGas(estimateGasParams)),
+ ])
+ .then(([gasPrice, gas]) => {
+ const newGasTotal = calcGasTotal(gas, gasPrice)
+ dispatch(actions.setGasTotal(newGasTotal))
+ dispatch(updateSendErrors({ gasLoadingError: null }))
+ })
+ .catch(err => {
+ log.error(err)
+ dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' }))
+ })
+ }
+}
+
+function updateSendTokenBalance ({
+ selectedToken,
+ tokenContract,
+ address,
+}) {
+ return (dispatch) => {
+ const tokenBalancePromise = tokenContract
+ ? tokenContract.balanceOf(address)
+ : Promise.resolve()
+ return tokenBalancePromise
+ .then(usersToken => {
+ if (usersToken) {
+ const newTokenBalance = calcTokenBalance({ selectedToken, usersToken })
+ dispatch(setSendTokenBalance(newTokenBalance))
+ }
+ })
+ .catch(err => {
+ log.error(err)
+ updateSendErrors({ tokenBalance: 'tokenBalanceError' })
+ })
+ }
+}
+
+function updateSendErrors (errorObject) {
+ return {
+ type: actions.UPDATE_SEND_ERRORS,
+ value: errorObject,
+ }
+}
+
+function setSendTokenBalance (tokenBalance) {
return {
type: actions.UPDATE_SEND_TOKEN_BALANCE,
value: tokenBalance,
@@ -725,13 +815,6 @@ function updateSendMemo (memo) {
}
}
-function updateSendErrors (error) {
- return {
- type: actions.UPDATE_SEND_ERRORS,
- value: error,
- }
-}
-
function setMaxModeTo (bool) {
return {
type: actions.UPDATE_MAX_MODE,
@@ -800,17 +883,24 @@ function updateTransaction (txData) {
function updateAndApproveTx (txData) {
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
return (dispatch) => {
- log.debug(`actions calling background.updateAndApproveTx.`)
- background.updateAndApproveTransaction(txData, (err) => {
- dispatch(actions.hideLoadingIndication())
- dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
- dispatch(actions.clearSend())
- if (err) {
- dispatch(actions.txError(err))
- dispatch(actions.goHome())
- return log.error(err.message)
- }
- dispatch(actions.completedTx(txData.id))
+ log.debug(`actions calling background.updateAndApproveTx`)
+
+ return new Promise((resolve, reject) => {
+ background.updateAndApproveTransaction(txData, err => {
+ dispatch(actions.hideLoadingIndication())
+ dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
+ dispatch(actions.clearSend())
+
+ if (err) {
+ dispatch(actions.txError(err))
+ dispatch(actions.goHome())
+ log.error(err.message)
+ reject(err)
+ }
+
+ dispatch(actions.completedTx(txData.id))
+ resolve(txData)
+ })
})
}
}
@@ -838,29 +928,77 @@ function txError (err) {
}
function cancelMsg (msgData) {
- log.debug(`background.cancelMessage`)
- background.cancelMessage(msgData.id)
- return actions.completedTx(msgData.id)
+ return dispatch => {
+ dispatch(actions.showLoadingIndication())
+
+ return new Promise((resolve, reject) => {
+ log.debug(`background.cancelMessage`)
+ background.cancelMessage(msgData.id, (err, newState) => {
+ dispatch(actions.updateMetamaskState(newState))
+ dispatch(actions.hideLoadingIndication())
+
+ if (err) {
+ return reject(err)
+ }
+
+ dispatch(actions.completedTx(msgData.id))
+ return resolve(msgData)
+ })
+ })
+ }
}
function cancelPersonalMsg (msgData) {
- const id = msgData.id
- background.cancelPersonalMessage(id)
- return actions.completedTx(id)
+ return dispatch => {
+ dispatch(actions.showLoadingIndication())
+
+ return new Promise((resolve, reject) => {
+ const id = msgData.id
+ background.cancelPersonalMessage(id, (err, newState) => {
+ dispatch(actions.updateMetamaskState(newState))
+ dispatch(actions.hideLoadingIndication())
+
+ if (err) {
+ return reject(err)
+ }
+
+ dispatch(actions.completedTx(id))
+ return resolve(msgData)
+ })
+ })
+ }
}
function cancelTypedMsg (msgData) {
- const id = msgData.id
- background.cancelTypedMessage(id)
- return actions.completedTx(id)
+ return dispatch => {
+ dispatch(actions.showLoadingIndication())
+
+ return new Promise((resolve, reject) => {
+ const id = msgData.id
+ background.cancelTypedMessage(id, (err, newState) => {
+ dispatch(actions.updateMetamaskState(newState))
+ dispatch(actions.hideLoadingIndication())
+
+ if (err) {
+ return reject(err)
+ }
+
+ dispatch(actions.completedTx(id))
+ return resolve(msgData)
+ })
+ })
+ }
}
function cancelTx (txData) {
- return (dispatch) => {
+ return dispatch => {
log.debug(`background.cancelTransaction`)
- background.cancelTransaction(txData.id, () => {
- dispatch(actions.clearSend())
- dispatch(actions.completedTx(txData.id))
+ return new Promise((resolve, reject) => {
+ background.cancelTransaction(txData.id, () => {
+ dispatch(actions.clearSend())
+ dispatch(actions.completedTx(txData.id))
+ resolve(txData)
+ })
})
}
}
@@ -1251,12 +1389,13 @@ function markNoticeRead (notice) {
dispatch(actions.displayWarning(err))
return reject(err)
}
+
if (notice) {
dispatch(actions.showNotice(notice))
- resolve()
+ resolve(true)
} else {
dispatch(actions.clearNotices())
- resolve()
+ resolve(false)
}
})
})
@@ -1679,28 +1818,6 @@ function shapeShiftRequest (query, options, cb) {
}
}
-function updateTokenExchangeRate (token = '') {
- const pair = `${token.toLowerCase()}_eth`
-
- return dispatch => {
- if (!token) {
- return
- }
-
- shapeShiftRequest('marketinfo', { pair }, marketinfo => {
- if (!marketinfo.error) {
- dispatch({
- type: actions.UPDATE_TOKEN_EXCHANGE_RATE,
- payload: {
- pair,
- marketinfo,
- },
- })
- }
- })
- }
-}
-
function setFeatureFlag (feature, activated, notificationType) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
@@ -1775,7 +1892,7 @@ function forceUpdateMetamaskState (dispatch) {
}
dispatch(actions.updateMetamaskState(newState))
- resolve()
+ resolve(newState)
})
})
}
@@ -1858,3 +1975,11 @@ function updateNetworkEndpointType (networkEndpointType) {
value: networkEndpointType,
}
}
+
+function setIsRevealingSeedWords (reveal) {
+ return dispatch => {
+ log.debug(`background.setIsRevealingSeedWords`)
+ background.setIsRevealingSeedWords(reveal)
+ return forceUpdateMetamaskState(dispatch)
+ }
+}
diff --git a/ui/app/app.js b/ui/app/app.js
index 0b7a7a1e0..6511979b2 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -1,62 +1,420 @@
-const inherits = require('util').inherits
-const Component = require('react').Component
+const { Component } = require('react')
+const PropTypes = require('prop-types')
const connect = require('react-redux').connect
+const { Route, Switch, withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
const h = require('react-hyperscript')
-const PropTypes = require('prop-types')
const actions = require('./actions')
const classnames = require('classnames')
+const log = require('loglevel')
-// mascara
-const MascaraFirstTime = require('../../mascara/src/app/first-time').default
-const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default
// init
-const OldUIInitializeMenuScreen = require('./first-time/init-menu')
-const InitializeMenuScreen = MascaraFirstTime
-const NewKeyChainScreen = require('./new-keychain')
-const WelcomeScreen = require('./welcome-screen').default
-
+const InitializeScreen = require('../../mascara/src/app/first-time').default
// accounts
-const MainContainer = require('./main-container')
-const SendTransactionScreen2 = require('./components/send/send-v2-container')
+const SendTransactionScreen = require('./components/send_/send.container')
const ConfirmTxScreen = require('./conf-tx')
-// notice
-const NoticeScreen = require('./components/notice')
-const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
// slideout menu
const WalletView = require('./components/wallet-view')
// other views
-const Settings = require('./settings')
-const AddTokenScreen = require('./add-token')
-const Import = require('./accounts/import')
-const NewAccount = require('./accounts/new-account')
+const Home = require('./components/pages/home')
+const Authenticated = require('./components/pages/authenticated')
+const Initialized = require('./components/pages/initialized')
+const Settings = require('./components/pages/settings')
+const UnlockPage = require('./components/pages/unlock')
+const RestoreVaultPage = require('./components/pages/keychains/restore-vault')
+const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
+const AddTokenPage = require('./components/pages/add-token')
+const CreateAccountPage = require('./components/pages/create-account')
+const NoticeScreen = require('./components/pages/notice')
+
const Loading = require('./components/loading')
const NetworkIndicator = require('./components/network')
const Identicon = require('./components/identicon')
-const BuyView = require('./components/buy-button-subview')
-const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
-const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
-const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
const NetworkDropdown = require('./components/dropdowns/network-dropdown')
const AccountMenu = require('./components/account-menu')
-const QrView = require('./components/qr-code')
// Global Modals
const Modal = require('./components/modals/index').Modal
-App.contextTypes = {
- t: PropTypes.func,
-}
+// Routes
+const {
+ DEFAULT_ROUTE,
+ UNLOCK_ROUTE,
+ SETTINGS_ROUTE,
+ REVEAL_SEED_ROUTE,
+ RESTORE_VAULT_ROUTE,
+ ADD_TOKEN_ROUTE,
+ NEW_ACCOUNT_ROUTE,
+ SEND_ROUTE,
+ CONFIRM_TRANSACTION_ROUTE,
+ INITIALIZE_ROUTE,
+ NOTICE_ROUTE,
+} = require('./routes')
+
+class App extends Component {
+ componentWillMount () {
+ const {
+ currentCurrency,
+ setCurrentCurrencyToUSD,
+ isRevealingSeedWords,
+ clearSeedWords,
+ } = this.props
+
+ if (!currentCurrency) {
+ setCurrentCurrencyToUSD()
+ }
+
+ if (isRevealingSeedWords) {
+ clearSeedWords()
+ }
+ }
+
+ renderRoutes () {
+ const exact = true
+
+ return (
+ h(Switch, [
+ h(Route, { path: INITIALIZE_ROUTE, component: InitializeScreen }),
+ h(Initialized, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }),
+ h(Initialized, { path: UNLOCK_ROUTE, exact, component: UnlockPage }),
+ h(Initialized, { path: SETTINGS_ROUTE, component: Settings }),
+ h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }),
+ h(Initialized, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
+ h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }),
+ h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen }),
+ h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
+ h(Authenticated, { path: NEW_ACCOUNT_ROUTE, component: CreateAccountPage }),
+ h(Authenticated, { path: DEFAULT_ROUTE, exact, component: Home }),
+ ])
+ )
+ }
+
+ render () {
+ const {
+ isLoading,
+ loadingMessage,
+ network,
+ isMouseUser,
+ provider,
+ frequentRpcList,
+ currentView,
+ setMouseUserState,
+ } = this.props
+ const isLoadingNetwork = network === 'loading' && currentView.name !== 'config'
+ const loadMessage = loadingMessage || isLoadingNetwork ?
+ this.getConnectingLabel() : null
+ log.debug('Main ui render function')
+
+ return (
+ h('.flex-column.full-height', {
+ className: classnames({ 'mouse-user-styles': isMouseUser }),
+ style: {
+ overflowX: 'hidden',
+ position: 'relative',
+ alignItems: 'center',
+ },
+ tabIndex: '0',
+ onClick: () => setMouseUserState(true),
+ onKeyDown: (e) => {
+ if (e.keyCode === 9) {
+ setMouseUserState(false)
+ }
+ },
+ }, [
+
+ // global modal
+ h(Modal, {}, []),
+
+ // app bar
+ this.renderAppBar(),
+
+ // sidebar
+ this.renderSidebar(),
+
+ // network dropdown
+ h(NetworkDropdown, {
+ provider,
+ frequentRpcList,
+ }, []),
+
+ h(AccountMenu),
+
+ (isLoading || isLoadingNetwork) && h(Loading, {
+ loadingMessage: loadMessage,
+ }),
+
+ // content
+ this.renderRoutes(),
+ ])
+ )
+ }
+
+ renderGlobalModal () {
+ return h(Modal, {
+ ref: 'modalRef',
+ }, [
+ // h(BuyOptions, {}, []),
+ ])
+ }
+
+ renderSidebar () {
+ return h('div', [
+ h('style', `
+ .sidebar-enter {
+ transition: transform 300ms ease-in-out;
+ transform: translateX(-100%);
+ }
+ .sidebar-enter.sidebar-enter-active {
+ transition: transform 300ms ease-in-out;
+ transform: translateX(0%);
+ }
+ .sidebar-leave {
+ transition: transform 200ms ease-out;
+ transform: translateX(0%);
+ }
+ .sidebar-leave.sidebar-leave-active {
+ transition: transform 200ms ease-out;
+ transform: translateX(-100%);
+ }
+ `),
+
+ h(ReactCSSTransitionGroup, {
+ transitionName: 'sidebar',
+ transitionEnterTimeout: 300,
+ transitionLeaveTimeout: 200,
+ }, [
+ // A second instance of Walletview is used for non-mobile viewports
+ this.props.sidebarOpen ? h(WalletView, {
+ responsiveDisplayClassname: '.sidebar',
+ style: {},
+ }) : undefined,
+
+ ]),
+
+ // overlay
+ // TODO: add onClick for overlay to close sidebar
+ this.props.sidebarOpen ? h('div.sidebar-overlay', {
+ style: {},
+ onClick: () => {
+ this.props.hideSidebar()
+ },
+ }, []) : undefined,
+ ])
+ }
+
+ renderAppBar () {
+ const {
+ isUnlocked,
+ network,
+ provider,
+ networkDropdownOpen,
+ showNetworkDropdown,
+ hideNetworkDropdown,
+ isInitialized,
+ welcomeScreenSeen,
+ isPopup,
+ betaUI,
+ } = this.props
+
+ if (window.METAMASK_UI_TYPE === 'notification') {
+ return null
+ }
+
+ const props = this.props
+ const {isMascara, isOnboarding} = props
+
+ // Do not render header if user is in mascara onboarding
+ if (isMascara && isOnboarding) {
+ return null
+ }
+
+ // Do not render header if user is in mascara buy ether
+ if (isMascara && props.currentView.name === 'buyEth') {
+ return null
+ }
+
+ return (
+
+ h('.full-width', {
+ style: {},
+ }, [
+
+ (isInitialized || welcomeScreenSeen || isPopup || !betaUI) && h('.app-header.flex-row.flex-space-between', {
+ className: classnames({
+ 'app-header--initialized': !isOnboarding,
+ }),
+ }, [
+ h('div.app-header-contents', {}, [
+ h('div.left-menu-wrapper', {
+ onClick: () => props.history.push(DEFAULT_ROUTE),
+ }, [
+ // mini logo
+ h('img.metafox-icon', {
+ height: 42,
+ width: 42,
+ src: '/images/metamask-fox.svg',
+ }),
+
+ // metamask name
+ h('.flex-row', [
+ h('h1', this.context.t('appName')),
+ h('div.beta-label', this.context.t('beta')),
+ ]),
+
+ ]),
+
+ betaUI && isInitialized && h('div.header__right-actions', [
+ h('div.network-component-wrapper', {
+ style: {},
+ }, [
+ // Network Indicator
+ h(NetworkIndicator, {
+ network,
+ provider,
+ disabled: this.props.location.pathname === CONFIRM_TRANSACTION_ROUTE,
+ onClick: (event) => {
+ event.preventDefault()
+ event.stopPropagation()
+ return networkDropdownOpen === false
+ ? showNetworkDropdown()
+ : hideNetworkDropdown()
+ },
+ }),
+
+ ]),
+
+ isUnlocked && h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [
+ h(Identicon, {
+ address: this.props.selectedAddress,
+ diameter: 32,
+ }),
+ ]),
+ ]),
+ ]),
+ ]),
-module.exports = connect(mapStateToProps, mapDispatchToProps)(App)
+ !isInitialized && !isPopup && betaUI && h('.alpha-warning__container', {}, [
+ h('h2', {
+ className: classnames({
+ 'alpha-warning': welcomeScreenSeen,
+ 'alpha-warning-welcome-screen': !welcomeScreenSeen,
+ }),
+ }, 'Please be aware that this version is still under development'),
+ ]),
+ ])
+ )
+ }
-inherits(App, Component)
-function App () { Component.call(this) }
+ toggleMetamaskActive () {
+ if (!this.props.isUnlocked) {
+ // currently inactive: redirect to password box
+ var passwordBox = document.querySelector('input[type=password]')
+ if (!passwordBox) return
+ passwordBox.focus()
+ } else {
+ // currently active: deactivate
+ this.props.dispatch(actions.lockMetamask(false))
+ }
+ }
+
+ getConnectingLabel = function () {
+ const { provider } = this.props
+ const providerName = provider.type
+
+ let name
+
+ if (providerName === 'mainnet') {
+ name = this.context.t('connectingToMainnet')
+ } else if (providerName === 'ropsten') {
+ name = this.context.t('connectingToRopsten')
+ } else if (providerName === 'kovan') {
+ name = this.context.t('connectingToRopsten')
+ } else if (providerName === 'rinkeby') {
+ name = this.context.t('connectingToRinkeby')
+ } else {
+ name = this.context.t('connectingToUnknown')
+ }
+
+ return name
+ }
+
+ getNetworkName () {
+ const { provider } = this.props
+ const providerName = provider.type
+
+ let name
+
+ if (providerName === 'mainnet') {
+ name = this.context.t('mainnet')
+ } else if (providerName === 'ropsten') {
+ name = this.context.t('ropsten')
+ } else if (providerName === 'kovan') {
+ name = this.context.t('kovan')
+ } else if (providerName === 'rinkeby') {
+ name = this.context.t('rinkeby')
+ } else {
+ name = this.context.t('unknownNetwork')
+ }
+
+ return name
+ }
+}
+
+App.propTypes = {
+ currentCurrency: PropTypes.string,
+ setCurrentCurrencyToUSD: PropTypes.func,
+ isLoading: PropTypes.bool,
+ loadingMessage: PropTypes.string,
+ network: PropTypes.string,
+ provider: PropTypes.object,
+ frequentRpcList: PropTypes.array,
+ currentView: PropTypes.object,
+ sidebarOpen: PropTypes.bool,
+ hideSidebar: PropTypes.func,
+ isMascara: PropTypes.bool,
+ isOnboarding: PropTypes.bool,
+ isUnlocked: PropTypes.bool,
+ networkDropdownOpen: PropTypes.bool,
+ showNetworkDropdown: PropTypes.func,
+ hideNetworkDropdown: PropTypes.func,
+ history: PropTypes.object,
+ location: PropTypes.object,
+ dispatch: PropTypes.func,
+ toggleAccountMenu: PropTypes.func,
+ selectedAddress: PropTypes.string,
+ noActiveNotices: PropTypes.bool,
+ lostAccounts: PropTypes.array,
+ isInitialized: PropTypes.bool,
+ forgottenPassword: PropTypes.bool,
+ activeAddress: PropTypes.string,
+ unapprovedTxs: PropTypes.object,
+ seedWords: PropTypes.string,
+ unapprovedMsgCount: PropTypes.number,
+ unapprovedPersonalMsgCount: PropTypes.number,
+ unapprovedTypedMessagesCount: PropTypes.number,
+ welcomeScreenSeen: PropTypes.bool,
+ isPopup: PropTypes.bool,
+ betaUI: PropTypes.bool,
+ isMouseUser: PropTypes.bool,
+ setMouseUserState: PropTypes.func,
+ t: PropTypes.func,
+ isRevealingSeedWords: PropTypes.bool,
+ clearSeedWords: PropTypes.func,
+}
function mapStateToProps (state) {
+ const { appState, metamask } = state
+ const {
+ networkDropdownOpen,
+ sidebarOpen,
+ isLoading,
+ loadingMessage,
+ } = appState
+
const {
identities,
accounts,
@@ -65,17 +423,23 @@ function mapStateToProps (state) {
isInitialized,
noActiveNotices,
seedWords,
- } = state.metamask
+ unapprovedTxs,
+ lastUnreadNotice,
+ lostAccounts,
+ unapprovedMsgCount,
+ unapprovedPersonalMsgCount,
+ unapprovedTypedMessagesCount,
+ } = metamask
const selected = address || Object.keys(accounts)[0]
return {
// state from plugin
- networkDropdownOpen: state.appState.networkDropdownOpen,
- sidebarOpen: state.appState.sidebarOpen,
- isLoading: state.appState.isLoading,
- loadingMessage: state.appState.loadingMessage,
- noActiveNotices: state.metamask.noActiveNotices,
- isInitialized: state.metamask.isInitialized,
+ networkDropdownOpen,
+ sidebarOpen,
+ isLoading,
+ loadingMessage,
+ noActiveNotices,
+ isInitialized,
isUnlocked: state.metamask.isUnlocked,
selectedAddress: state.metamask.selectedAddress,
currentView: state.appState.currentView,
@@ -85,14 +449,17 @@ function mapStateToProps (state) {
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
isPopup: state.metamask.isPopup,
seedWords: state.metamask.seedWords,
- unapprovedTxs: state.metamask.unapprovedTxs,
+ unapprovedTxs,
unapprovedMsgs: state.metamask.unapprovedMsgs,
+ unapprovedMsgCount,
+ unapprovedPersonalMsgCount,
+ unapprovedTypedMessagesCount,
menuOpen: state.appState.menuOpen,
network: state.metamask.network,
provider: state.metamask.provider,
- forgottenPassword: state.metamask.forgottenPassword,
- lastUnreadNotice: state.metamask.lastUnreadNotice,
- lostAccounts: state.metamask.lostAccounts,
+ forgottenPassword: state.appState.forgottenPassword,
+ lastUnreadNotice,
+ lostAccounts,
frequentRpcList: state.metamask.frequentRpcList || [],
currentCurrency: state.metamask.currentCurrency,
isMouseUser: state.appState.isMouseUser,
@@ -117,482 +484,15 @@ function mapDispatchToProps (dispatch, ownProps) {
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)),
+ clearSeedWords: () => dispatch(actions.confirmSeedWords()),
}
}
-App.prototype.componentWillMount = function () {
- if (!this.props.currentCurrency) {
- this.props.setCurrentCurrencyToUSD()
- }
-}
-
-App.prototype.render = function () {
- var props = this.props
- const {
- isLoading,
- loadingMessage,
- network,
- isMouseUser,
- setMouseUserState,
- } = props
- const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config'
- const loadMessage = loadingMessage || isLoadingNetwork ?
- this.getConnectingLabel() : null
- log.debug('Main ui render function')
-
- return (
- h('.flex-column.full-height', {
- className: classnames({ 'mouse-user-styles': isMouseUser }),
- style: {
- overflowX: 'hidden',
- position: 'relative',
- alignItems: 'center',
- },
- tabIndex: '0',
- onClick: () => setMouseUserState(true),
- onKeyDown: (e) => {
- if (e.keyCode === 9) {
- setMouseUserState(false)
- }
- },
- }, [
-
- // global modal
- h(Modal, {}, []),
-
- // app bar
- this.renderAppBar(),
-
- // sidebar
- this.renderSidebar(),
-
- // network dropdown
- h(NetworkDropdown, {
- provider: this.props.provider,
- frequentRpcList: this.props.frequentRpcList,
- }, []),
-
- h(AccountMenu),
-
- (isLoading || isLoadingNetwork) && h(Loading, {
- loadingMessage: loadMessage,
- }),
-
- // this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }),
-
- // content
- this.renderPrimary(),
- ])
- )
-}
-
-App.prototype.renderGlobalModal = function () {
- return h(Modal, {
- ref: 'modalRef',
- }, [
- // h(BuyOptions, {}, []),
- ])
-}
-
-App.prototype.renderSidebar = function () {
-
- return h('div', {
- }, [
- h('style', `
- .sidebar-enter {
- transition: transform 300ms ease-in-out;
- transform: translateX(-100%);
- }
- .sidebar-enter.sidebar-enter-active {
- transition: transform 300ms ease-in-out;
- transform: translateX(0%);
- }
- .sidebar-leave {
- transition: transform 200ms ease-out;
- transform: translateX(0%);
- }
- .sidebar-leave.sidebar-leave-active {
- transition: transform 200ms ease-out;
- transform: translateX(-100%);
- }
- `),
-
- h(ReactCSSTransitionGroup, {
- transitionName: 'sidebar',
- transitionEnterTimeout: 300,
- transitionLeaveTimeout: 200,
- }, [
- // A second instance of Walletview is used for non-mobile viewports
- this.props.sidebarOpen ? h(WalletView, {
- responsiveDisplayClassname: '.sidebar',
- style: {},
- }) : undefined,
-
- ]),
-
- // overlay
- // TODO: add onClick for overlay to close sidebar
- this.props.sidebarOpen ? h('div.sidebar-overlay', {
- style: {},
- onClick: () => {
- this.props.hideSidebar()
- },
- }, []) : undefined,
- ])
-}
-
-App.prototype.renderAppBar = function () {
- const {
- isUnlocked,
- network,
- provider,
- networkDropdownOpen,
- showNetworkDropdown,
- hideNetworkDropdown,
- currentView,
- isInitialized,
- betaUI,
- isPopup,
- welcomeScreenSeen,
- } = this.props
-
- if (window.METAMASK_UI_TYPE === 'notification') {
- return null
- }
-
- const props = this.props
- const {isMascara, isOnboarding} = props
-
- // Do not render header if user is in mascara onboarding
- if (isMascara && isOnboarding) {
- return null
- }
-
- // Do not render header if user is in mascara buy ether
- if (isMascara && props.currentView.name === 'buyEth') {
- return null
- }
-
- return (
-
- h('.full-width', {
- style: {},
- }, [
-
- (isInitialized || welcomeScreenSeen || isPopup || !betaUI) && h('.app-header.flex-row.flex-space-between', {
- className: classnames({
- 'app-header--initialized': !isOnboarding,
- }),
- }, [
- h('div.app-header-contents', {}, [
- h('div.left-menu-wrapper', {
- onClick: () => {
- props.dispatch(actions.backToAccountDetail(props.activeAddress))
- },
- }, [
- // mini logo
- h('img.metafox-icon', {
- height: 42,
- width: 42,
- src: './images/metamask-fox.svg',
- }),
-
- // metamask name
- h('.flex-row', [
- h('h1', this.context.t('appName')),
- h('div.beta-label', this.context.t('beta')),
- ]),
- ]),
-
- betaUI && isInitialized && h('div.header__right-actions', [
- h('div.network-component-wrapper', {
- style: {},
- }, [
- // Network Indicator
- h(NetworkIndicator, {
- network,
- provider,
- disabled: currentView.name === 'confTx',
- onClick: (event) => {
- event.preventDefault()
- event.stopPropagation()
- return networkDropdownOpen === false
- ? showNetworkDropdown()
- : hideNetworkDropdown()
- },
- }),
-
- ]),
-
- isUnlocked && h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [
- h(Identicon, {
- address: this.props.selectedAddress,
- diameter: 32,
- }),
- ]),
- ]),
- ]),
- ]),
-
- !isInitialized && !isPopup && betaUI && h('.alpha-warning__container', {}, [
- h('h2', {
- className: classnames({
- 'alpha-warning': welcomeScreenSeen,
- 'alpha-warning-welcome-screen': !welcomeScreenSeen,
- }),
- }, 'Please be aware that this version is still under development'),
- ]),
-
- ])
- )
-}
-
-App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) {
- const { isMascara } = this.props
-
- return isMascara
- ? null
- : h(Loading, {
- isLoading: isLoading || isLoadingNetwork,
- loadingMessage: loadMessage,
- })
-}
-
-App.prototype.renderBackButton = function (style, justArrow = false) {
- var props = this.props
- return (
- h('.flex-row', {
- key: 'leftArrow',
- style: style,
- onClick: () => props.dispatch(actions.goBackToInitView()),
- }, [
- h('i.fa.fa-arrow-left.cursor-pointer'),
- justArrow ? null : h('div.cursor-pointer', {
- style: {
- marginLeft: '3px',
- },
- onClick: () => props.dispatch(actions.goBackToInitView()),
- }, 'BACK'),
- ])
- )
-}
-
-App.prototype.renderPrimary = function () {
- log.debug('rendering primary')
- var props = this.props
- const {
- isMascara,
- isOnboarding,
- betaUI,
- isRevealingSeedWords,
- welcomeScreenSeen,
- Qr,
- isInitialized,
- isUnlocked,
- } = props
- const isMascaraOnboarding = isMascara && isOnboarding
- const isBetaUIOnboarding = betaUI && isOnboarding
-
- if (!welcomeScreenSeen && betaUI && !isInitialized && !isUnlocked) {
- return h(WelcomeScreen)
- }
-
- if (isMascaraOnboarding || isBetaUIOnboarding) {
- return h(MascaraFirstTime)
- }
-
- // notices
- if (!props.noActiveNotices && !betaUI) {
- log.debug('rendering notice screen for unread notices.')
- return h(NoticeScreen, {
- notice: props.lastUnreadNotice,
- key: 'NoticeScreen',
- onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
- })
- } else if (props.lostAccounts && props.lostAccounts.length > 0) {
- log.debug('rendering notice screen for lost accounts view.')
- return h(NoticeScreen, {
- notice: generateLostAccountsNotice(props.lostAccounts),
- key: 'LostAccountsNotice',
- onConfirm: () => props.dispatch(actions.markAccountsFound()),
- })
- }
-
- if (props.isInitialized && props.forgottenPassword) {
- log.debug('rendering restore vault screen')
- return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
- } else if (!props.isInitialized && !props.isUnlocked && !isRevealingSeedWords) {
- log.debug('rendering menu screen')
- return !betaUI
- ? h(OldUIInitializeMenuScreen, {key: 'menuScreenInit'})
- : h(InitializeMenuScreen, {key: 'menuScreenInit'})
- }
-
- // show unlock screen
- if (!props.isUnlocked) {
- return h(MainContainer, {
- currentViewName: props.currentView.name,
- isUnlocked: props.isUnlocked,
- })
- }
-
- // show seed words screen
- if (props.seedWords) {
- log.debug('rendering seed words')
- return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
- }
-
- // show current view
- switch (props.currentView.name) {
-
- case 'accountDetail':
- log.debug('rendering main container')
- return h(MainContainer, {key: 'account-detail'})
-
- case 'sendTransaction':
- log.debug('rendering send tx screen')
-
- // Going to leave this here until we are ready to delete SendTransactionScreen v1
- // const SendComponentToRender = checkFeatureToggle('send-v2')
- // ? SendTransactionScreen2
- // : SendTransactionScreen
-
- return h(SendTransactionScreen2, {key: 'send-transaction'})
-
- case 'sendToken':
- log.debug('rendering send token screen')
-
- // Going to leave this here until we are ready to delete SendTransactionScreen v1
- // const SendTokenComponentToRender = checkFeatureToggle('send-v2')
- // ? SendTransactionScreen2
- // : SendTokenScreen
-
- return h(SendTransactionScreen2, {key: 'sendToken'})
-
- case 'newKeychain':
- log.debug('rendering new keychain screen')
- return h(NewKeyChainScreen, {key: 'new-keychain'})
-
- case 'confTx':
- log.debug('rendering confirm tx screen')
- return h(ConfirmTxScreen, {key: 'confirm-tx'})
-
- case 'add-token':
- log.debug('rendering add-token screen from unlock screen.')
- return h(AddTokenScreen, {key: 'add-token'})
-
- case 'config':
- log.debug('rendering config screen')
- return h(Settings, {key: 'config'})
-
- case 'import-menu':
- log.debug('rendering import screen')
- return h(Import, {key: 'import-menu'})
-
- case 'new-account-page':
- log.debug('rendering new account screen')
- return h(NewAccount, {key: 'new-account'})
-
- case 'reveal-seed-conf':
- log.debug('rendering reveal seed confirmation screen')
- return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
-
- case 'info':
- log.debug('rendering info screen')
- return h(Settings, {key: 'info', tab: 'info'})
-
- case 'buyEth':
- log.debug('rendering buy ether screen')
- return h(BuyView, {key: 'buyEthView'})
-
- case 'onboardingBuyEth':
- log.debug('rendering onboarding buy ether screen')
- return h(MascaraBuyEtherScreen, {key: 'buyEthView'})
-
- case 'qr':
- log.debug('rendering show qr screen')
- return h('div', {
- style: {
- position: 'absolute',
- height: '100%',
- top: '0px',
- left: '0px',
- },
- }, [
- h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
- onClick: () => props.dispatch(actions.backToAccountDetail(props.activeAddress)),
- style: {
- marginLeft: '10px',
- marginTop: '50px',
- },
- }),
- h('div', {
- style: {
- position: 'absolute',
- left: '44px',
- width: '285px',
- },
- }, [
- h(QrView, {key: 'qr', Qr}),
- ]),
- ])
-
- default:
- log.debug('rendering default, account detail screen')
- return h(MainContainer, {key: 'account-detail'})
- }
-}
-
-App.prototype.toggleMetamaskActive = function () {
- if (!this.props.isUnlocked) {
- // currently inactive: redirect to password box
- var passwordBox = document.querySelector('input[type=password]')
- if (!passwordBox) return
- passwordBox.focus()
- } else {
- // currently active: deactivate
- this.props.dispatch(actions.lockMetamask(false))
- }
-}
-
-App.prototype.getConnectingLabel = function () {
- const { provider } = this.props
- const providerName = provider.type
-
- let name
-
- if (providerName === 'mainnet') {
- name = this.context.t('connectingToMainnet')
- } else if (providerName === 'ropsten') {
- name = this.context.t('connectingToRopsten')
- } else if (providerName === 'kovan') {
- name = this.context.t('connectingToRopsten')
- } else if (providerName === 'rinkeby') {
- name = this.context.t('connectingToRinkeby')
- } else {
- name = this.context.t('connectingToUnknown')
- }
-
- return name
+App.contextTypes = {
+ t: PropTypes.func,
}
-App.prototype.getNetworkName = function () {
- const { provider } = this.props
- const providerName = provider.type
-
- let name
-
- if (providerName === 'mainnet') {
- name = this.context.t('mainnet')
- } else if (providerName === 'ropsten') {
- name = this.context.t('ropsten')
- } else if (providerName === 'kovan') {
- name = this.context.t('kovan')
- } else if (providerName === 'rinkeby') {
- name = this.context.t('rinkeby')
- } else {
- name = this.context.t('unknownNetwork')
- }
-
- return name
-}
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(App)
diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/account-dropdowns.js
index 03955e077..043008a36 100644
--- a/ui/app/components/account-dropdowns.js
+++ b/ui/app/components/account-dropdowns.js
@@ -7,8 +7,8 @@ const connect = require('react-redux').connect
const Dropdown = require('./dropdown').Dropdown
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
const Identicon = require('./identicon')
-const ethUtil = require('ethereumjs-util')
const copyToClipboard = require('copy-to-clipboard')
+const { checksumAddress } = require('../util')
class AccountDropdowns extends Component {
constructor (props) {
@@ -212,8 +212,7 @@ class AccountDropdowns extends Component {
closeMenu: () => {},
onClick: () => {
const { selected } = this.props
- const checkSumAddress = selected && ethUtil.toChecksumAddress(selected)
- copyToClipboard(checkSumAddress)
+ copyToClipboard(checksumAddress(selected))
},
},
this.context.t('copyAddress'),
diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js
index 21de358d6..7638995ea 100644
--- a/ui/app/components/account-menu/index.js
+++ b/ui/app/components/account-menu/index.js
@@ -1,20 +1,31 @@
const inherits = require('util').inherits
const Component = require('react').Component
-const PropTypes = require('prop-types')
const connect = require('react-redux').connect
+const { compose } = require('recompose')
+const { withRouter } = require('react-router-dom')
+const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const actions = require('../../actions')
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
const Identicon = require('../identicon')
const { formatBalance } = require('../../util')
+const {
+ SETTINGS_ROUTE,
+ INFO_ROUTE,
+ NEW_ACCOUNT_ROUTE,
+ IMPORT_ACCOUNT_ROUTE,
+ DEFAULT_ROUTE,
+} = require('../../routes')
+
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(AccountMenu)
AccountMenu.contextTypes = {
t: PropTypes.func,
}
-module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountMenu)
-
-
inherits(AccountMenu, Component)
function AccountMenu () { Component.call(this) }
@@ -25,7 +36,6 @@ function mapStateToProps (state) {
keyrings: state.metamask.keyrings,
identities: state.metamask.identities,
accounts: state.metamask.accounts,
-
}
}
@@ -48,11 +58,6 @@ function mapDispatchToProps (dispatch) {
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
- showNewAccountPage: (formToSelect) => {
- dispatch(actions.showNewAccountPage(formToSelect))
- dispatch(actions.hideSidebar())
- dispatch(actions.toggleAccountMenu())
- },
showInfoPage: () => {
dispatch(actions.showInfoPage())
dispatch(actions.hideSidebar())
@@ -65,10 +70,8 @@ AccountMenu.prototype.render = function () {
const {
isAccountMenuOpen,
toggleAccountMenu,
- showNewAccountPage,
lockMetamask,
- showConfigPage,
- showInfoPage,
+ history,
} = this.props
return h(Menu, { className: 'account-menu', isShowing: isAccountMenuOpen }, [
@@ -78,30 +81,45 @@ AccountMenu.prototype.render = function () {
}, [
this.context.t('myAccounts'),
h('button.account-menu__logout-button', {
- onClick: lockMetamask,
+ onClick: () => {
+ lockMetamask()
+ history.push(DEFAULT_ROUTE)
+ },
}, this.context.t('logout')),
]),
h(Divider),
h('div.account-menu__accounts', this.renderAccounts()),
h(Divider),
h(Item, {
- onClick: () => showNewAccountPage('CREATE'),
+ onClick: () => {
+ toggleAccountMenu()
+ history.push(NEW_ACCOUNT_ROUTE)
+ },
icon: h('img.account-menu__item-icon', { src: 'images/plus-btn-white.svg' }),
text: this.context.t('createAccount'),
}),
h(Item, {
- onClick: () => showNewAccountPage('IMPORT'),
+ onClick: () => {
+ toggleAccountMenu()
+ history.push(IMPORT_ACCOUNT_ROUTE)
+ },
icon: h('img.account-menu__item-icon', { src: 'images/import-account.svg' }),
text: this.context.t('importAccount'),
}),
h(Divider),
h(Item, {
- onClick: showInfoPage,
+ onClick: () => {
+ toggleAccountMenu()
+ history.push(INFO_ROUTE)
+ },
icon: h('img', { src: 'images/mm-info-icon.svg' }),
text: this.context.t('infoHelp'),
}),
h(Item, {
- onClick: showConfigPage,
+ onClick: () => {
+ toggleAccountMenu()
+ history.push(SETTINGS_ROUTE)
+ },
icon: h('img.account-menu__item-icon', { src: 'images/settings.svg' }),
text: this.context.t('settings'),
}),
diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js
index d591ab455..e31552f2d 100644
--- a/ui/app/components/balance-component.js
+++ b/ui/app/components/balance-component.js
@@ -4,6 +4,8 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const TokenBalance = require('./token-balance')
const Identicon = require('./identicon')
+const currencyFormatter = require('currency-formatter')
+const currencies = require('currency-formatter/currencies')
const { formatBalance, generateBalanceObject } = require('../util')
@@ -97,9 +99,17 @@ BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatS
const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0
if (shouldNotRenderFiat) return null
+ const upperCaseFiatSuffix = fiatSuffix.toUpperCase()
+
+ const display = currencies.find(currency => currency.code === upperCaseFiatSuffix)
+ ? currencyFormatter.format(Number(fiatDisplayNumber), {
+ code: upperCaseFiatSuffix,
+ })
+ : `${fiatPrefix}${fiatDisplayNumber} ${upperCaseFiatSuffix}`
+
return h('div.fiat-amount', {
style: {},
- }, `${fiatPrefix}${fiatDisplayNumber} ${fiatSuffix}`)
+ }, display)
}
BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) {
@@ -117,5 +127,9 @@ BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, co
const splitBalance = formattedBalance.split(' ')
- return (Number(splitBalance[0]) * conversionRate).toFixed(2)
+ const convertedNumber = (Number(splitBalance[0]) * conversionRate)
+ const wholePart = Math.floor(convertedNumber)
+ const decimalPart = convertedNumber - wholePart
+
+ return wholePart + Number(decimalPart.toPrecision(2))
}
diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js
index 9ac565cf4..fda7c3e17 100644
--- a/ui/app/components/buy-button-subview.js
+++ b/ui/app/components/buy-button-subview.js
@@ -9,7 +9,7 @@ const ShapeshiftForm = require('./shapeshift-form')
const Loading = require('./loading')
const AccountPanel = require('./account-panel')
const RadioList = require('./custom-radio-list')
-const networkNames = require('../../../app/scripts/config.js').networkNames
+const { getNetworkDisplayName } = require('../../../app/scripts/controllers/network/util')
BuyButtonSubview.contextTypes = {
t: PropTypes.func,
@@ -148,7 +148,7 @@ BuyButtonSubview.prototype.primarySubview = function () {
case '3':
case '4':
case '42':
- const networkName = networkNames[network]
+ const networkName = getNetworkDisplayName(network)
const label = `${networkName} ${this.context.t('testFaucet')}`
return (
h('div.flex-column', {
diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js
index 4c693d1c3..0761faa99 100644
--- a/ui/app/components/customize-gas-modal/index.js
+++ b/ui/app/components/customize-gas-modal/index.js
@@ -8,6 +8,10 @@ const GasModalCard = require('./gas-modal-card')
const ethUtil = require('ethereumjs-util')
+import {
+ updateSendErrors,
+} from '../../ducks/send'
+
const {
MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT_DEC,
@@ -63,9 +67,9 @@ function mapDispatchToProps (dispatch) {
hideModal: () => dispatch(actions.hideModal()),
updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)),
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)),
- updateGasTotal: newGasTotal => dispatch(actions.updateGasTotal(newGasTotal)),
+ updateGasTotal: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)),
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
- updateSendErrors: error => dispatch(actions.updateSendErrors(error)),
+ updateSendErrors: error => dispatch(updateSendErrors(error)),
}
}
diff --git a/ui/app/components/dropdowns/components/account-dropdowns.js b/ui/app/components/dropdowns/components/account-dropdowns.js
index a133f0e29..179b6617f 100644
--- a/ui/app/components/dropdowns/components/account-dropdowns.js
+++ b/ui/app/components/dropdowns/components/account-dropdowns.js
@@ -7,7 +7,7 @@ const connect = require('react-redux').connect
const Dropdown = require('./dropdown').Dropdown
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
const Identicon = require('../../identicon')
-const ethUtil = require('ethereumjs-util')
+const { checksumAddress } = require('../../../util')
const copyToClipboard = require('copy-to-clipboard')
const { formatBalance } = require('../../../util')
@@ -311,8 +311,7 @@ class AccountDropdowns extends Component {
closeMenu: () => {},
onClick: () => {
const { selected } = this.props
- const checkSumAddress = selected && ethUtil.toChecksumAddress(selected)
- copyToClipboard(checkSumAddress)
+ copyToClipboard(checksumAddress(selected))
},
style: Object.assign(
dropdownMenuItemStyle,
diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js
index 9e47f38ef..dcd6b4370 100644
--- a/ui/app/components/dropdowns/network-dropdown.js
+++ b/ui/app/components/dropdowns/network-dropdown.js
@@ -3,12 +3,14 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
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')
-
+const { SETTINGS_ROUTE } = require('../../routes')
// classes from nodes of the toggle element.
const notToggleElementClassnames = [
@@ -41,9 +43,6 @@ function mapDispatchToProps (dispatch) {
setRpcTarget: (target) => {
dispatch(actions.setRpcTarget(target))
},
- showConfigPage: () => {
- dispatch(actions.showConfigPage())
- },
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
}
@@ -59,7 +58,10 @@ NetworkDropdown.contextTypes = {
t: PropTypes.func,
}
-module.exports = connect(mapStateToProps, mapDispatchToProps)(NetworkDropdown)
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(NetworkDropdown)
// TODO: specify default props and proptypes
@@ -227,7 +229,7 @@ NetworkDropdown.prototype.render = function () {
DropdownMenuItem,
{
closeMenu: () => this.props.hideNetworkDropdown(),
- onClick: () => this.props.showConfigPage(),
+ onClick: () => this.props.history.push(SETTINGS_ROUTE),
style: dropdownMenuItemStyle,
},
[
diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js
index feb0a7037..aff4b6ef6 100644
--- a/ui/app/components/ens-input.js
+++ b/ui/app/components/ens-input.js
@@ -11,6 +11,7 @@ const ensRE = /.+\..+$/
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const connect = require('react-redux').connect
const ToAutoComplete = require('./send/to-autocomplete')
+const log = require('loglevel')
EnsInput.contextTypes = {
t: PropTypes.func,
diff --git a/ui/app/components/loading.js b/ui/app/components/loading.js
index cb6fa51fb..b9afc550f 100644
--- a/ui/app/components/loading.js
+++ b/ui/app/components/loading.js
@@ -1,6 +1,7 @@
const { Component } = require('react')
const h = require('react-hyperscript')
const PropTypes = require('prop-types')
+const classnames = require('classnames')
class LoadingIndicator extends Component {
renderMessage () {
@@ -10,14 +11,16 @@ class LoadingIndicator extends Component {
render () {
return (
- h('.full-flex-height.loading-overlay', {}, [
- h('img', {
- src: 'images/loading.svg',
- }),
+ h('.loading-overlay', {
+ className: classnames({ 'loading-overlay--full-screen': this.props.fullScreen }),
+ }, [
+ h('.flex-center.flex-column', [
+ h('img', {
+ src: 'images/loading.svg',
+ }),
- h('br'),
-
- this.renderMessage(),
+ this.renderMessage(),
+ ]),
])
)
}
@@ -25,6 +28,7 @@ class LoadingIndicator extends Component {
LoadingIndicator.propTypes = {
loadingMessage: PropTypes.string,
+ fullScreen: PropTypes.bool,
}
module.exports = LoadingIndicator
diff --git a/ui/app/components/modals/buy-options-modal.js b/ui/app/components/modals/buy-options-modal.js
index d871e7516..c70510b5f 100644
--- a/ui/app/components/modals/buy-options-modal.js
+++ b/ui/app/components/modals/buy-options-modal.js
@@ -4,7 +4,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
-const networkNames = require('../../../../app/scripts/config.js').networkNames
+const { getNetworkDisplayName } = require('../../../../app/scripts/controllers/network/util')
function mapStateToProps (state) {
return {
@@ -52,7 +52,7 @@ BuyOptions.prototype.renderModalContentOption = function (title, header, onClick
BuyOptions.prototype.render = function () {
const { network, toCoinbase, address, toFaucet } = this.props
const isTestNetwork = ['3', '4', '42'].find(n => n === network)
- const networkName = networkNames[network]
+ const networkName = getNetworkDisplayName(network)
return h('div', {}, [
h('div.buy-modal-content.transfers-subview', {
diff --git a/ui/app/components/modals/deposit-ether-modal.js b/ui/app/components/modals/deposit-ether-modal.js
index 0dc611f50..ad5f9b695 100644
--- a/ui/app/components/modals/deposit-ether-modal.js
+++ b/ui/app/components/modals/deposit-ether-modal.js
@@ -4,7 +4,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
-const networkNames = require('../../../../app/scripts/config.js').networkNames
+const { getNetworkDisplayName } = require('../../../../app/scripts/controllers/network/util')
const ShapeshiftForm = require('../shapeshift-form')
let DIRECT_DEPOSIT_ROW_TITLE
@@ -122,7 +122,7 @@ DepositEtherModal.prototype.render = function () {
const { buyingWithShapeshift } = this.state
const isTestNetwork = ['3', '4', '42'].find(n => n === network)
- const networkName = networkNames[network]
+ const networkName = getNetworkDisplayName(network)
return h('div.page-container.page-container--full-width.page-container--full-height', {}, [
diff --git a/ui/app/components/modals/export-private-key-modal.js b/ui/app/components/modals/export-private-key-modal.js
index 1f80aed39..447e43b7a 100644
--- a/ui/app/components/modals/export-private-key-modal.js
+++ b/ui/app/components/modals/export-private-key-modal.js
@@ -3,12 +3,13 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const ethUtil = require('ethereumjs-util')
+const { stripHexPrefix } = require('ethereumjs-util')
const actions = require('../../actions')
const AccountModalContainer = require('./account-modal-container')
const { getSelectedIdentity } = require('../../selectors')
const ReadOnlyInput = require('../readonly-input')
const copyToClipboard = require('copy-to-clipboard')
+const { checksumAddress } = require('../../util')
function mapStateToProps (state) {
return {
@@ -60,7 +61,7 @@ ExportPrivateKeyModal.prototype.renderPasswordLabel = function (privateKey) {
}
ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) {
- const plainKey = privateKey && ethUtil.stripHexPrefix(privateKey)
+ const plainKey = privateKey && stripHexPrefix(privateKey)
return privateKey
? h(ReadOnlyInput, {
@@ -121,7 +122,7 @@ ExportPrivateKeyModal.prototype.render = function () {
h(ReadOnlyInput, {
wrapperClass: 'ellip-address-wrapper',
inputClass: 'qr-ellip-address ellip-address',
- value: address,
+ value: checksumAddress(address),
}),
h('div.account-modal-divider'),
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
index 9250cc77e..43dcd20ae 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/modals/modal.js
@@ -5,7 +5,8 @@ const connect = require('react-redux').connect
const FadeModal = require('boron').FadeModal
const actions = require('../../actions')
const isMobileView = require('../../../lib/is-mobile-view')
-const isPopupOrNotification = require('../../../../app/scripts/lib/is-popup-or-notification')
+const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
+const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
// Modal Components
const BuyOptions = require('./buy-options-modal')
@@ -162,7 +163,7 @@ const MODALS = {
],
mobileModalStyle: {
width: '95%',
- top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
},
laptopModalStyle: {
width: '449px',
@@ -179,7 +180,7 @@ const MODALS = {
],
mobileModalStyle: {
width: '95%',
- top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
},
laptopModalStyle: {
width: '449px',
@@ -196,7 +197,7 @@ const MODALS = {
],
mobileModalStyle: {
width: '95%',
- top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
},
laptopModalStyle: {
width: '449px',
@@ -208,7 +209,7 @@ const MODALS = {
contents: h(ConfirmResetAccount),
mobileModalStyle: {
width: '95%',
- top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
},
laptopModalStyle: {
width: '473px',
diff --git a/ui/app/components/page-container/page-container-content.component.js b/ui/app/components/page-container/page-container-content.component.js
new file mode 100644
index 000000000..a1d6988cc
--- /dev/null
+++ b/ui/app/components/page-container/page-container-content.component.js
@@ -0,0 +1,18 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+
+export default class PageContainerContent extends Component {
+
+ static propTypes = {
+ children: PropTypes.node.isRequired,
+ };
+
+ render () {
+ return (
+ <div className="page-container__content">
+ {this.props.children}
+ </div>
+ )
+ }
+
+}
diff --git a/ui/app/components/page-container/page-container-header.component.js b/ui/app/components/page-container/page-container-header.component.js
new file mode 100644
index 000000000..5c9d63221
--- /dev/null
+++ b/ui/app/components/page-container/page-container-header.component.js
@@ -0,0 +1,35 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+
+export default class PageContainerHeader extends Component {
+
+ static propTypes = {
+ title: PropTypes.string,
+ subtitle: PropTypes.string,
+ onClose: PropTypes.func,
+ };
+
+ render () {
+ const { title, subtitle, onClose } = this.props
+
+ return (
+ <div className="page-container__header">
+
+ <div className="page-container__title">
+ {title}
+ </div>
+
+ <div className="page-container__subtitle">
+ {subtitle}
+ </div>
+
+ <div
+ className="page-container__header-close"
+ onClick={() => onClose()}
+ />
+
+ </div>
+ )
+ }
+
+}
diff --git a/ui/app/add-token.js b/ui/app/components/pages/add-token.js
index 46564a5e5..566e42450 100644
--- a/ui/app/add-token.js
+++ b/ui/app/components/pages/add-token.js
@@ -7,8 +7,8 @@ const connect = require('react-redux').connect
const R = require('ramda')
const Fuse = require('fuse.js')
const contractMap = require('eth-contract-metadata')
-const TokenBalance = require('./components/token-balance')
-const Identicon = require('./components/identicon')
+const TokenBalance = require('../../components/token-balance')
+const Identicon = require('../../components/identicon')
const contractList = Object.entries(contractMap)
.map(([ _, tokenData]) => tokenData)
.filter(tokenData => Boolean(tokenData.erc20))
@@ -24,9 +24,10 @@ const fuse = new Fuse(contractList, {
{ name: 'symbol', weight: 0.5 },
],
})
-const actions = require('./actions')
+const actions = require('../../actions')
const ethUtil = require('ethereumjs-util')
-const { tokenInfoGetter } = require('./token-util')
+const { tokenInfoGetter } = require('../../token-util')
+const { DEFAULT_ROUTE } = require('../../routes')
const emptyAddr = '0x0000000000000000000000000000000000000000'
@@ -47,7 +48,6 @@ function mapStateToProps (state) {
function mapDispatchToProps (dispatch) {
return {
- goHome: () => dispatch(actions.goHome()),
addTokens: tokens => dispatch(actions.addTokens(tokens)),
}
}
@@ -296,7 +296,7 @@ AddTokenScreen.prototype.renderConfirmation = function () {
selectedTokens,
} = this.state
- const { addTokens, goHome } = this.props
+ const { addTokens, history } = this.props
const customToken = {
address,
@@ -333,7 +333,7 @@ AddTokenScreen.prototype.renderConfirmation = function () {
onClick: () => this.setState({ isShowingConfirmation: false }),
}, this.context.t('back')),
h('button.btn-primary--lg', {
- onClick: () => addTokens(tokens).then(goHome),
+ onClick: () => addTokens(tokens).then(() => history.push(DEFAULT_ROUTE)),
}, this.context.t('addTokens')),
]),
])
@@ -382,12 +382,12 @@ AddTokenScreen.prototype.render = function () {
isShowingConfirmation,
displayedTab,
} = this.state
- const { goHome } = this.props
+ const { history } = this.props
return h('div.add-token', [
h('div.add-token__header', [
h('div.add-token__header__cancel', {
- onClick: () => goHome(),
+ onClick: () => history.push(DEFAULT_ROUTE),
}, [
h('i.fa.fa-angle-left.fa-lg'),
h('span', this.context.t('cancel')),
@@ -414,14 +414,14 @@ AddTokenScreen.prototype.render = function () {
]),
]),
-//
+
isShowingConfirmation
? this.renderConfirmation()
: this.renderTabs(),
!isShowingConfirmation && h('div.add-token__buttons', [
h('button.btn-secondary--lg.add-token__cancel-button', {
- onClick: goHome,
+ onClick: () => history.push(DEFAULT_ROUTE),
}, this.context.t('cancel')),
h('button.btn-primary--lg.add-token__confirm-button', {
onClick: this.onNext,
diff --git a/ui/app/components/pages/authenticated.js b/ui/app/components/pages/authenticated.js
new file mode 100644
index 000000000..1f6b0be49
--- /dev/null
+++ b/ui/app/components/pages/authenticated.js
@@ -0,0 +1,34 @@
+const { connect } = require('react-redux')
+const PropTypes = require('prop-types')
+const { Redirect } = require('react-router-dom')
+const h = require('react-hyperscript')
+const MetamaskRoute = require('./metamask-route')
+const { UNLOCK_ROUTE, INITIALIZE_ROUTE } = require('../../routes')
+
+const Authenticated = props => {
+ const { isUnlocked, isInitialized } = props
+
+ switch (true) {
+ case isUnlocked && isInitialized:
+ return h(MetamaskRoute, { ...props })
+ case !isInitialized:
+ return h(Redirect, { to: { pathname: INITIALIZE_ROUTE } })
+ default:
+ return h(Redirect, { to: { pathname: UNLOCK_ROUTE } })
+ }
+}
+
+Authenticated.propTypes = {
+ isUnlocked: PropTypes.bool,
+ isInitialized: PropTypes.bool,
+}
+
+const mapStateToProps = state => {
+ const { metamask: { isUnlocked, isInitialized } } = state
+ return {
+ isUnlocked,
+ isInitialized,
+ }
+}
+
+module.exports = connect(mapStateToProps)(Authenticated)
diff --git a/ui/app/accounts/import/index.js b/ui/app/components/pages/create-account/import-account/index.js
index 52d3dcde9..52d3dcde9 100644
--- a/ui/app/accounts/import/index.js
+++ b/ui/app/components/pages/create-account/import-account/index.js
diff --git a/ui/app/accounts/import/json.js b/ui/app/components/pages/create-account/import-account/json.js
index e53c1c9ca..946907a47 100644
--- a/ui/app/accounts/import/json.js
+++ b/ui/app/components/pages/create-account/import-account/json.js
@@ -1,11 +1,12 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
const connect = require('react-redux').connect
-const actions = require('../../actions')
+const actions = require('../../../../actions')
const FileInput = require('react-simple-file-input').default
-
-
+const { DEFAULT_ROUTE } = require('../../../../routes')
const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts'
class JsonImportSubview extends Component {
@@ -51,7 +52,7 @@ class JsonImportSubview extends Component {
h('div.new-account-create-form__buttons', {}, [
h('button.btn-secondary.new-account-create-form__button', {
- onClick: () => this.props.goHome(),
+ onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, [
this.context.t('cancel'),
]),
@@ -112,6 +113,7 @@ JsonImportSubview.propTypes = {
goHome: PropTypes.func,
displayWarning: PropTypes.func,
importNewJsonAccount: PropTypes.func,
+ history: PropTypes.object,
t: PropTypes.func,
}
@@ -133,5 +135,7 @@ JsonImportSubview.contextTypes = {
t: PropTypes.func,
}
-module.exports = connect(mapStateToProps, mapDispatchToProps)(JsonImportSubview)
-
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(JsonImportSubview)
diff --git a/ui/app/accounts/import/private-key.js b/ui/app/components/pages/create-account/import-account/private-key.js
index 0d2898cda..c77612ea4 100644
--- a/ui/app/accounts/import/private-key.js
+++ b/ui/app/components/pages/create-account/import-account/private-key.js
@@ -1,15 +1,21 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
-const actions = require('../../actions')
+const actions = require('../../../../actions')
+const { DEFAULT_ROUTE } = require('../../../../routes')
PrivateKeyImportView.contextTypes = {
t: PropTypes.func,
}
-module.exports = connect(mapStateToProps, mapDispatchToProps)(PrivateKeyImportView)
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(PrivateKeyImportView)
function mapStateToProps (state) {
@@ -20,9 +26,8 @@ function mapStateToProps (state) {
function mapDispatchToProps (dispatch) {
return {
- goHome: () => dispatch(actions.goHome()),
importNewAccount: (strategy, [ privateKey ]) => {
- dispatch(actions.importNewAccount(strategy, [ privateKey ]))
+ return dispatch(actions.importNewAccount(strategy, [ privateKey ]))
},
displayWarning: () => dispatch(actions.displayWarning(null)),
}
@@ -35,7 +40,7 @@ function PrivateKeyImportView () {
}
PrivateKeyImportView.prototype.render = function () {
- const { error, goHome } = this.props
+ const { error } = this.props
return (
h('div.new-account-import-form__private-key', [
@@ -55,7 +60,7 @@ PrivateKeyImportView.prototype.render = function () {
h('div.new-account-import-form__buttons', {}, [
h('button.btn-secondary--lg.new-account-create-form__button', {
- onClick: () => goHome(),
+ onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, [
this.context.t('cancel'),
]),
@@ -83,6 +88,8 @@ PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) {
PrivateKeyImportView.prototype.createNewKeychain = function () {
const input = document.getElementById('private-key-box')
const privateKey = input.value
+ const { importNewAccount, history } = this.props
- this.props.importNewAccount('Private Key', [ privateKey ])
+ importNewAccount('Private Key', [ privateKey ])
+ .then(() => history.push(DEFAULT_ROUTE))
}
diff --git a/ui/app/accounts/import/seed.js b/ui/app/components/pages/create-account/import-account/seed.js
index d98909baa..d98909baa 100644
--- a/ui/app/accounts/import/seed.js
+++ b/ui/app/components/pages/create-account/import-account/seed.js
diff --git a/ui/app/components/pages/create-account/index.js b/ui/app/components/pages/create-account/index.js
new file mode 100644
index 000000000..0962477d8
--- /dev/null
+++ b/ui/app/components/pages/create-account/index.js
@@ -0,0 +1,81 @@
+const Component = require('react').Component
+const { Switch, Route, matchPath } = require('react-router-dom')
+const PropTypes = require('prop-types')
+const h = require('react-hyperscript')
+const connect = require('react-redux').connect
+const actions = require('../../../actions')
+const { getCurrentViewContext } = require('../../../selectors')
+const classnames = require('classnames')
+const NewAccountCreateForm = require('./new-account')
+const NewAccountImportForm = require('./import-account')
+const { NEW_ACCOUNT_ROUTE, IMPORT_ACCOUNT_ROUTE } = require('../../../routes')
+
+class CreateAccountPage extends Component {
+ renderTabs () {
+ const { history, location } = this.props
+
+ return h('div.new-account__tabs', [
+ h('div.new-account__tabs__tab', {
+ className: classnames('new-account__tabs__tab', {
+ 'new-account__tabs__selected': matchPath(location.pathname, {
+ path: NEW_ACCOUNT_ROUTE, exact: true,
+ }),
+ }),
+ onClick: () => history.push(NEW_ACCOUNT_ROUTE),
+ }, 'Create'),
+
+ h('div.new-account__tabs__tab', {
+ className: classnames('new-account__tabs__tab', {
+ 'new-account__tabs__selected': matchPath(location.pathname, {
+ path: IMPORT_ACCOUNT_ROUTE, exact: true,
+ }),
+ }),
+ onClick: () => history.push(IMPORT_ACCOUNT_ROUTE),
+ }, 'Import'),
+ ])
+ }
+
+ render () {
+ return h('div.new-account', {}, [
+ h('div.new-account__header', [
+ h('div.new-account__title', 'New Account'),
+ this.renderTabs(),
+ ]),
+ h('div.new-account__form', [
+ h(Switch, [
+ h(Route, {
+ exact: true,
+ path: NEW_ACCOUNT_ROUTE,
+ component: NewAccountCreateForm,
+ }),
+ h(Route, {
+ exact: true,
+ path: IMPORT_ACCOUNT_ROUTE,
+ component: NewAccountImportForm,
+ }),
+ ]),
+ ]),
+ ])
+ }
+}
+
+CreateAccountPage.propTypes = {
+ location: PropTypes.object,
+ history: PropTypes.object,
+}
+
+const mapStateToProps = state => ({
+ displayedForm: getCurrentViewContext(state),
+})
+
+const mapDispatchToProps = dispatch => ({
+ displayForm: form => dispatch(actions.setNewAccountForm(form)),
+ showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
+ showExportPrivateKeyModal: () => {
+ dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
+ },
+ hideModal: () => dispatch(actions.hideModal()),
+ saveAccountLabel: (address, label) => dispatch(actions.saveAccountLabel(address, label)),
+})
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(CreateAccountPage)
diff --git a/ui/app/accounts/new-account/create-form.js b/ui/app/components/pages/create-account/new-account.js
index 48c74192a..40fa584be 100644
--- a/ui/app/accounts/new-account/create-form.js
+++ b/ui/app/components/pages/create-account/new-account.js
@@ -2,7 +2,8 @@ const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
-const actions = require('../../actions')
+const actions = require('../../../actions')
+const { DEFAULT_ROUTE } = require('../../../routes')
class NewAccountCreateForm extends Component {
constructor (props, context) {
@@ -19,7 +20,7 @@ class NewAccountCreateForm extends Component {
render () {
const { newAccountName, defaultAccountName } = this.state
-
+ const { history, createAccount } = this.props
return h('div.new-account-create-form', [
@@ -38,13 +39,16 @@ class NewAccountCreateForm extends Component {
h('div.new-account-create-form__buttons', {}, [
h('button.btn-secondary--lg.new-account-create-form__button', {
- onClick: () => this.props.goHome(),
+ onClick: () => history.push(DEFAULT_ROUTE),
}, [
this.context.t('cancel'),
]),
h('button.btn-primary--lg.new-account-create-form__button', {
- onClick: () => this.props.createAccount(newAccountName || defaultAccountName),
+ onClick: () => {
+ createAccount(newAccountName || defaultAccountName)
+ .then(() => history.push(DEFAULT_ROUTE))
+ },
}, [
this.context.t('create'),
]),
@@ -59,8 +63,8 @@ NewAccountCreateForm.propTypes = {
hideModal: PropTypes.func,
showImportPage: PropTypes.func,
createAccount: PropTypes.func,
- goHome: PropTypes.func,
numberOfExistingAccounts: PropTypes.number,
+ history: PropTypes.object,
t: PropTypes.func,
}
@@ -77,23 +81,17 @@ const mapStateToProps = state => {
const mapDispatchToProps = dispatch => {
return {
- toCoinbase: (address) => {
- dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
- },
- hideModal: () => {
- dispatch(actions.hideModal())
- },
- createAccount: (newAccountName) => {
- dispatch(actions.addNewAccount())
- .then((newAccountAddress) => {
+ toCoinbase: address => dispatch(actions.buyEth({ network: '1', address, amount: 0 })),
+ hideModal: () => dispatch(actions.hideModal()),
+ createAccount: newAccountName => {
+ return dispatch(actions.addNewAccount())
+ .then(newAccountAddress => {
if (newAccountName) {
dispatch(actions.saveAccountLabel(newAccountAddress, newAccountName))
}
- dispatch(actions.goHome())
})
},
showImportPage: () => dispatch(actions.showImportPage()),
- goHome: () => dispatch(actions.goHome()),
}
}
diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js
new file mode 100644
index 000000000..90b8e1d37
--- /dev/null
+++ b/ui/app/components/pages/home.js
@@ -0,0 +1,333 @@
+const { Component } = require('react')
+const PropTypes = require('prop-types')
+const connect = require('../../metamask-connect')
+const { Redirect, withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
+const h = require('react-hyperscript')
+const actions = require('../../actions')
+const log = require('loglevel')
+
+// init
+const NewKeyChainScreen = require('../../new-keychain')
+// mascara
+const MascaraBuyEtherScreen = require('../../../../mascara/src/app/first-time/buy-ether-screen').default
+
+// accounts
+const MainContainer = require('../../main-container')
+
+// other views
+const BuyView = require('../../components/buy-button-subview')
+const QrView = require('../../components/qr-code')
+
+// Routes
+const {
+ REVEAL_SEED_ROUTE,
+ RESTORE_VAULT_ROUTE,
+ CONFIRM_TRANSACTION_ROUTE,
+ NOTICE_ROUTE,
+} = require('../../routes')
+
+class Home extends Component {
+ componentDidMount () {
+ const {
+ history,
+ unapprovedTxs = {},
+ unapprovedMsgCount = 0,
+ unapprovedPersonalMsgCount = 0,
+ unapprovedTypedMessagesCount = 0,
+ } = this.props
+
+ // unapprovedTxs and unapproved messages
+ if (Object.keys(unapprovedTxs).length ||
+ unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) {
+ history.push(CONFIRM_TRANSACTION_ROUTE)
+ }
+ }
+
+ render () {
+ log.debug('rendering primary')
+ const {
+ noActiveNotices,
+ lostAccounts,
+ forgottenPassword,
+ currentView,
+ activeAddress,
+ seedWords,
+ } = this.props
+
+ // notices
+ if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) {
+ return h(Redirect, {
+ to: {
+ pathname: NOTICE_ROUTE,
+ },
+ })
+ }
+
+ // seed words
+ if (seedWords) {
+ log.debug('rendering seed words')
+ return h(Redirect, {
+ to: {
+ pathname: REVEAL_SEED_ROUTE,
+ },
+ })
+ }
+
+ if (forgottenPassword) {
+ log.debug('rendering restore vault screen')
+ return h(Redirect, {
+ to: {
+ pathname: RESTORE_VAULT_ROUTE,
+ },
+ })
+ }
+
+ // if (!props.noActiveNotices) {
+ // log.debug('rendering notice screen for unread notices.')
+ // return h(NoticeScreen, {
+ // notice: props.lastUnreadNotice,
+ // key: 'NoticeScreen',
+ // onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
+ // })
+ // } else if (props.lostAccounts && props.lostAccounts.length > 0) {
+ // log.debug('rendering notice screen for lost accounts view.')
+ // return h(NoticeScreen, {
+ // notice: generateLostAccountsNotice(props.lostAccounts),
+ // key: 'LostAccountsNotice',
+ // onConfirm: () => props.dispatch(actions.markAccountsFound()),
+ // })
+ // }
+
+ // if (props.seedWords) {
+ // log.debug('rendering seed words')
+ // return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
+ // }
+
+ // show initialize screen
+ // if (!isInitialized || forgottenPassword) {
+ // // show current view
+ // log.debug('rendering an initialize screen')
+ // // switch (props.currentView.name) {
+
+ // // case 'restoreVault':
+ // // log.debug('rendering restore vault screen')
+ // // return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
+
+ // // default:
+ // // log.debug('rendering menu screen')
+ // // return h(InitializeScreen, {key: 'menuScreenInit'})
+ // // }
+ // }
+
+ // // show unlock screen
+ // if (!props.isUnlocked) {
+ // return h(MainContainer, {
+ // currentViewName: props.currentView.name,
+ // isUnlocked: props.isUnlocked,
+ // })
+ // }
+
+ // show current view
+ switch (currentView.name) {
+
+ case 'accountDetail':
+ log.debug('rendering main container')
+ return h(MainContainer, {key: 'account-detail'})
+
+ // case 'sendTransaction':
+ // log.debug('rendering send tx screen')
+
+ // // Going to leave this here until we are ready to delete SendTransactionScreen v1
+ // // const SendComponentToRender = checkFeatureToggle('send-v2')
+ // // ? SendTransactionScreen2
+ // // : SendTransactionScreen
+
+ // return h(SendTransactionScreen2, {key: 'send-transaction'})
+
+ // case 'sendToken':
+ // log.debug('rendering send token screen')
+
+ // // Going to leave this here until we are ready to delete SendTransactionScreen v1
+ // // const SendTokenComponentToRender = checkFeatureToggle('send-v2')
+ // // ? SendTransactionScreen2
+ // // : SendTokenScreen
+
+ // return h(SendTransactionScreen2, {key: 'sendToken'})
+
+ case 'newKeychain':
+ log.debug('rendering new keychain screen')
+ return h(NewKeyChainScreen, {key: 'new-keychain'})
+
+ // case 'confTx':
+ // log.debug('rendering confirm tx screen')
+ // return h(Redirect, {
+ // to: {
+ // pathname: CONFIRM_TRANSACTION_ROUTE,
+ // },
+ // })
+ // return h(ConfirmTxScreen, {key: 'confirm-tx'})
+
+ // case 'add-token':
+ // log.debug('rendering add-token screen from unlock screen.')
+ // return h(AddTokenScreen, {key: 'add-token'})
+
+ // case 'config':
+ // log.debug('rendering config screen')
+ // return h(Settings, {key: 'config'})
+
+ // case 'import-menu':
+ // log.debug('rendering import screen')
+ // return h(Import, {key: 'import-menu'})
+
+ // case 'reveal-seed-conf':
+ // log.debug('rendering reveal seed confirmation screen')
+ // return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
+
+ // case 'info':
+ // log.debug('rendering info screen')
+ // return h(Settings, {key: 'info', tab: 'info'})
+
+ case 'buyEth':
+ log.debug('rendering buy ether screen')
+ return h(BuyView, {key: 'buyEthView'})
+
+ case 'onboardingBuyEth':
+ log.debug('rendering onboarding buy ether screen')
+ return h(MascaraBuyEtherScreen, {key: 'buyEthView'})
+
+ case 'qr':
+ log.debug('rendering show qr screen')
+ return h('div', {
+ style: {
+ position: 'absolute',
+ height: '100%',
+ top: '0px',
+ left: '0px',
+ },
+ }, [
+ h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
+ onClick: () => this.props.dispatch(actions.backToAccountDetail(activeAddress)),
+ style: {
+ marginLeft: '10px',
+ marginTop: '50px',
+ },
+ }),
+ h('div', {
+ style: {
+ position: 'absolute',
+ left: '44px',
+ width: '285px',
+ },
+ }, [
+ h(QrView, {key: 'qr'}),
+ ]),
+ ])
+
+ default:
+ log.debug('rendering default, account detail screen')
+ return h(MainContainer, {key: 'account-detail'})
+ }
+ }
+}
+
+Home.propTypes = {
+ currentCurrency: PropTypes.string,
+ isLoading: PropTypes.bool,
+ loadingMessage: PropTypes.string,
+ network: PropTypes.string,
+ provider: PropTypes.object,
+ frequentRpcList: PropTypes.array,
+ currentView: PropTypes.object,
+ sidebarOpen: PropTypes.bool,
+ isMascara: PropTypes.bool,
+ isOnboarding: PropTypes.bool,
+ isUnlocked: PropTypes.bool,
+ networkDropdownOpen: PropTypes.bool,
+ history: PropTypes.object,
+ dispatch: PropTypes.func,
+ selectedAddress: PropTypes.string,
+ noActiveNotices: PropTypes.bool,
+ lostAccounts: PropTypes.array,
+ isInitialized: PropTypes.bool,
+ forgottenPassword: PropTypes.bool,
+ activeAddress: PropTypes.string,
+ unapprovedTxs: PropTypes.object,
+ seedWords: PropTypes.string,
+ unapprovedMsgCount: PropTypes.number,
+ unapprovedPersonalMsgCount: PropTypes.number,
+ unapprovedTypedMessagesCount: PropTypes.number,
+ welcomeScreenSeen: PropTypes.bool,
+ isPopup: PropTypes.bool,
+ isMouseUser: PropTypes.bool,
+ t: PropTypes.func,
+}
+
+function mapStateToProps (state) {
+ const { appState, metamask } = state
+ const {
+ networkDropdownOpen,
+ sidebarOpen,
+ isLoading,
+ loadingMessage,
+ } = appState
+
+ const {
+ accounts,
+ address,
+ isInitialized,
+ noActiveNotices,
+ seedWords,
+ unapprovedTxs,
+ lastUnreadNotice,
+ lostAccounts,
+ unapprovedMsgCount,
+ unapprovedPersonalMsgCount,
+ unapprovedTypedMessagesCount,
+ } = metamask
+ const selected = address || Object.keys(accounts)[0]
+
+ return {
+ // state from plugin
+ networkDropdownOpen,
+ sidebarOpen,
+ isLoading,
+ loadingMessage,
+ noActiveNotices,
+ isInitialized,
+ isUnlocked: state.metamask.isUnlocked,
+ selectedAddress: state.metamask.selectedAddress,
+ currentView: state.appState.currentView,
+ activeAddress: state.appState.activeAddress,
+ transForward: state.appState.transForward,
+ isMascara: state.metamask.isMascara,
+ isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
+ isPopup: state.metamask.isPopup,
+ seedWords: state.metamask.seedWords,
+ unapprovedTxs,
+ unapprovedMsgs: state.metamask.unapprovedMsgs,
+ unapprovedMsgCount,
+ unapprovedPersonalMsgCount,
+ unapprovedTypedMessagesCount,
+ menuOpen: state.appState.menuOpen,
+ network: state.metamask.network,
+ provider: state.metamask.provider,
+ forgottenPassword: state.appState.forgottenPassword,
+ lastUnreadNotice,
+ lostAccounts,
+ frequentRpcList: state.metamask.frequentRpcList || [],
+ currentCurrency: state.metamask.currentCurrency,
+ isMouseUser: state.appState.isMouseUser,
+ isRevealingSeedWords: state.metamask.isRevealingSeedWords,
+ Qr: state.appState.Qr,
+ welcomeScreenSeen: state.metamask.welcomeScreenSeen,
+
+ // state needed to get account dropdown temporarily rendering from app bar
+ selected,
+ }
+}
+
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps)
+)(Home)
diff --git a/ui/app/components/pages/initialized.js b/ui/app/components/pages/initialized.js
new file mode 100644
index 000000000..3adf67b28
--- /dev/null
+++ b/ui/app/components/pages/initialized.js
@@ -0,0 +1,25 @@
+const { connect } = require('react-redux')
+const PropTypes = require('prop-types')
+const { Redirect } = require('react-router-dom')
+const h = require('react-hyperscript')
+const { INITIALIZE_ROUTE } = require('../../routes')
+const MetamaskRoute = require('./metamask-route')
+
+const Initialized = props => {
+ return props.isInitialized
+ ? h(MetamaskRoute, { ...props })
+ : h(Redirect, { to: { pathname: INITIALIZE_ROUTE } })
+}
+
+Initialized.propTypes = {
+ isInitialized: PropTypes.bool,
+}
+
+const mapStateToProps = state => {
+ const { metamask: { isInitialized } } = state
+ return {
+ isInitialized,
+ }
+}
+
+module.exports = connect(mapStateToProps)(Initialized)
diff --git a/ui/app/components/pages/keychains/restore-vault.js b/ui/app/components/pages/keychains/restore-vault.js
new file mode 100644
index 000000000..33575bfbb
--- /dev/null
+++ b/ui/app/components/pages/keychains/restore-vault.js
@@ -0,0 +1,178 @@
+const { withRouter } = require('react-router-dom')
+const PropTypes = require('prop-types')
+const { compose } = require('recompose')
+const PersistentForm = require('../../../../lib/persistent-form')
+const connect = require('../../../metamask-connect')
+const h = require('react-hyperscript')
+const { createNewVaultAndRestore, unMarkPasswordForgotten } = require('../../../actions')
+const { DEFAULT_ROUTE } = require('../../../routes')
+const log = require('loglevel')
+
+class RestoreVaultPage extends PersistentForm {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ error: null,
+ }
+ }
+
+ createOnEnter (event) {
+ if (event.key === 'Enter') {
+ this.createNewVaultAndRestore()
+ }
+ }
+
+ cancel () {
+ this.props.unMarkPasswordForgotten()
+ .then(this.props.history.push(DEFAULT_ROUTE))
+ }
+
+ createNewVaultAndRestore () {
+ this.setState({ error: null })
+
+ // check password
+ var passwordBox = document.getElementById('password-box')
+ var password = passwordBox.value
+ var passwordConfirmBox = document.getElementById('password-box-confirm')
+ var passwordConfirm = passwordConfirmBox.value
+
+ if (password.length < 8) {
+ this.setState({ error: 'Password not long enough' })
+ return
+ }
+
+ if (password !== passwordConfirm) {
+ this.setState({ error: 'Passwords don\'t match' })
+ return
+ }
+
+ // check seed
+ var seedBox = document.querySelector('textarea.twelve-word-phrase')
+ var seed = seedBox.value.trim()
+ if (seed.split(' ').length !== 12) {
+ this.setState({ error: 'Seed phrases are 12 words long' })
+ return
+ }
+
+ // submit
+ this.props.createNewVaultAndRestore(password, seed)
+ .then(() => this.props.history.push(DEFAULT_ROUTE))
+ .catch(({ message }) => {
+ this.setState({ error: message })
+ log.error(message)
+ })
+ }
+
+ render () {
+ const { error } = this.state
+ this.persistentFormParentId = 'restore-vault-form'
+
+ return (
+ h('.initialize-screen.flex-column.flex-center.flex-grow', [
+
+ h('h3.flex-center.text-transform-uppercase', {
+ style: {
+ background: '#EBEBEB',
+ color: '#AEAEAE',
+ marginBottom: 24,
+ width: '100%',
+ fontSize: '20px',
+ padding: 6,
+ },
+ }, [
+ this.props.t('restoreVault'),
+ ]),
+
+ // wallet seed entry
+ h('h3', 'Wallet Seed'),
+ h('textarea.twelve-word-phrase.letter-spacey', {
+ dataset: {
+ persistentFormId: 'wallet-seed',
+ },
+ placeholder: this.props.t('secretPhrase'),
+ }),
+
+ // password
+ h('input.large-input.letter-spacey', {
+ type: 'password',
+ id: 'password-box',
+ placeholder: this.props.t('newPassword8Chars'),
+ dataset: {
+ persistentFormId: 'password',
+ },
+ style: {
+ width: 260,
+ marginTop: 12,
+ },
+ }),
+
+ // confirm password
+ h('input.large-input.letter-spacey', {
+ type: 'password',
+ id: 'password-box-confirm',
+ placeholder: this.props.t('confirmPassword'),
+ onKeyPress: this.createOnEnter.bind(this),
+ dataset: {
+ persistentFormId: 'password-confirmation',
+ },
+ style: {
+ width: 260,
+ marginTop: 16,
+ },
+ }),
+
+ error && (
+ h('span.error.in-progress-notification', error)
+ ),
+
+ // submit
+ h('.flex-row.flex-space-between', {
+ style: {
+ marginTop: 30,
+ width: '50%',
+ },
+ }, [
+
+ // cancel
+ h('button.primary', {
+ onClick: () => this.cancel(),
+ }, this.props.t('cancel')),
+
+ // submit
+ h('button.primary', {
+ onClick: this.createNewVaultAndRestore.bind(this),
+ }, this.props.t('ok')),
+
+ ]),
+ ])
+ )
+ }
+}
+
+RestoreVaultPage.propTypes = {
+ history: PropTypes.object,
+}
+
+const mapStateToProps = state => {
+ const { appState: { warning, forgottenPassword } } = state
+
+ return {
+ warning,
+ forgottenPassword,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ createNewVaultAndRestore: (password, seed) => {
+ return dispatch(createNewVaultAndRestore(password, seed))
+ },
+ unMarkPasswordForgotten: () => dispatch(unMarkPasswordForgotten()),
+ }
+}
+
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(RestoreVaultPage)
diff --git a/ui/app/components/pages/keychains/reveal-seed.js b/ui/app/components/pages/keychains/reveal-seed.js
new file mode 100644
index 000000000..247f3c8e2
--- /dev/null
+++ b/ui/app/components/pages/keychains/reveal-seed.js
@@ -0,0 +1,195 @@
+const { Component } = require('react')
+const { connect } = require('react-redux')
+const PropTypes = require('prop-types')
+const h = require('react-hyperscript')
+const { exportAsFile } = require('../../../util')
+const { requestRevealSeed, confirmSeedWords } = require('../../../actions')
+const { DEFAULT_ROUTE } = require('../../../routes')
+
+class RevealSeedPage extends Component {
+ componentDidMount () {
+ const passwordBox = document.getElementById('password-box')
+ if (passwordBox) {
+ passwordBox.focus()
+ }
+ }
+
+ checkConfirmation (event) {
+ if (event.key === 'Enter') {
+ event.preventDefault()
+ this.revealSeedWords()
+ }
+ }
+
+ revealSeedWords () {
+ const password = document.getElementById('password-box').value
+ this.props.requestRevealSeed(password)
+ }
+
+ renderSeed () {
+ const { seedWords, confirmSeedWords, history } = this.props
+
+ return (
+ h('.initialize-screen.flex-column.flex-center.flex-grow', [
+
+ h('h3.flex-center.text-transform-uppercase', {
+ style: {
+ background: '#EBEBEB',
+ color: '#AEAEAE',
+ marginTop: 36,
+ marginBottom: 8,
+ width: '100%',
+ fontSize: '20px',
+ padding: 6,
+ },
+ }, [
+ 'Vault Created',
+ ]),
+
+ h('div', {
+ style: {
+ fontSize: '1em',
+ marginTop: '10px',
+ textAlign: 'center',
+ },
+ }, [
+ h('span.error', 'These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret.'),
+ ]),
+
+ h('textarea.twelve-word-phrase', {
+ readOnly: true,
+ value: seedWords,
+ }),
+
+ h('button.primary', {
+ onClick: () => confirmSeedWords().then(() => history.push(DEFAULT_ROUTE)),
+ style: {
+ margin: '24px',
+ fontSize: '0.9em',
+ marginBottom: '10px',
+ },
+ }, 'I\'ve copied it somewhere safe'),
+
+ h('button.primary', {
+ onClick: () => exportAsFile(`MetaMask Seed Words`, seedWords),
+ style: {
+ margin: '10px',
+ fontSize: '0.9em',
+ },
+ }, 'Save Seed Words As File'),
+ ])
+ )
+ }
+
+ renderConfirmation () {
+ const { history, warning, inProgress } = this.props
+
+ return (
+ h('.initialize-screen.flex-column.flex-center.flex-grow', {
+ style: { maxWidth: '420px' },
+ }, [
+
+ h('h3.flex-center.text-transform-uppercase', {
+ style: {
+ background: '#EBEBEB',
+ color: '#AEAEAE',
+ marginBottom: 24,
+ width: '100%',
+ fontSize: '20px',
+ padding: 6,
+ },
+ }, [
+ 'Reveal Seed Words',
+ ]),
+
+ h('.div', {
+ style: {
+ display: 'flex',
+ flexDirection: 'column',
+ padding: '20px',
+ justifyContent: 'center',
+ },
+ }, [
+
+ h('h4', 'Do not recover your seed words in a public place! These words can be used to steal all your accounts.'),
+
+ // confirmation
+ h('input.large-input.letter-spacey', {
+ type: 'password',
+ id: 'password-box',
+ placeholder: 'Enter your password to confirm',
+ onKeyPress: this.checkConfirmation.bind(this),
+ style: {
+ width: 260,
+ marginTop: '12px',
+ },
+ }),
+
+ h('.flex-row.flex-start', {
+ style: {
+ marginTop: 30,
+ width: '50%',
+ },
+ }, [
+ // cancel
+ h('button.primary', {
+ onClick: () => history.push(DEFAULT_ROUTE),
+ }, 'CANCEL'),
+
+ // submit
+ h('button.primary', {
+ style: { marginLeft: '10px' },
+ onClick: this.revealSeedWords.bind(this),
+ }, 'OK'),
+
+ ]),
+
+ warning && (
+ h('span.error', {
+ style: {
+ margin: '20px',
+ },
+ }, warning.split('-'))
+ ),
+
+ inProgress && (
+ h('span.in-progress-notification', 'Generating Seed...')
+ ),
+ ]),
+ ])
+ )
+ }
+
+ render () {
+ return this.props.seedWords
+ ? this.renderSeed()
+ : this.renderConfirmation()
+ }
+}
+
+RevealSeedPage.propTypes = {
+ requestRevealSeed: PropTypes.func,
+ confirmSeedWords: PropTypes.func,
+ seedWords: PropTypes.string,
+ inProgress: PropTypes.bool,
+ history: PropTypes.object,
+ warning: PropTypes.string,
+}
+
+const mapStateToProps = state => {
+ const { appState: { warning }, metamask: { seedWords } } = state
+
+ return {
+ warning,
+ seedWords,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ requestRevealSeed: password => dispatch(requestRevealSeed(password)),
+ confirmSeedWords: () => dispatch(confirmSeedWords()),
+ }
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(RevealSeedPage)
diff --git a/ui/app/components/pages/metamask-route.js b/ui/app/components/pages/metamask-route.js
new file mode 100644
index 000000000..23c5b5199
--- /dev/null
+++ b/ui/app/components/pages/metamask-route.js
@@ -0,0 +1,28 @@
+const { connect } = require('react-redux')
+const PropTypes = require('prop-types')
+const { Route } = require('react-router-dom')
+const h = require('react-hyperscript')
+
+const MetamaskRoute = ({ component, mascaraComponent, isMascara, ...props }) => {
+ return (
+ h(Route, {
+ ...props,
+ component: isMascara && mascaraComponent ? mascaraComponent : component,
+ })
+ )
+}
+
+MetamaskRoute.propTypes = {
+ component: PropTypes.func,
+ mascaraComponent: PropTypes.func,
+ isMascara: PropTypes.bool,
+}
+
+const mapStateToProps = state => {
+ const { metamask: { isMascara } } = state
+ return {
+ isMascara,
+ }
+}
+
+module.exports = connect(mapStateToProps)(MetamaskRoute)
diff --git a/ui/app/components/pages/notice.js b/ui/app/components/pages/notice.js
new file mode 100644
index 000000000..2329a9147
--- /dev/null
+++ b/ui/app/components/pages/notice.js
@@ -0,0 +1,203 @@
+const { Component } = require('react')
+const h = require('react-hyperscript')
+const { connect } = require('react-redux')
+const PropTypes = require('prop-types')
+const ReactMarkdown = require('react-markdown')
+const linker = require('extension-link-enabler')
+const generateLostAccountsNotice = require('../../../lib/lost-accounts-notice')
+const findDOMNode = require('react-dom').findDOMNode
+const actions = require('../../actions')
+const { DEFAULT_ROUTE } = require('../../routes')
+
+class Notice extends Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ disclaimerDisabled: true,
+ }
+ }
+
+ componentWillMount () {
+ if (!this.props.notice) {
+ this.props.history.push(DEFAULT_ROUTE)
+ }
+ }
+
+ componentDidMount () {
+ // eslint-disable-next-line react/no-find-dom-node
+ var node = findDOMNode(this)
+ linker.setupListener(node)
+ if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
+ this.setState({ disclaimerDisabled: false })
+ }
+ }
+
+ componentWillReceiveProps (nextProps) {
+ if (!nextProps.notice) {
+ this.props.history.push(DEFAULT_ROUTE)
+ }
+ }
+
+ componentWillUnmount () {
+ // eslint-disable-next-line react/no-find-dom-node
+ var node = findDOMNode(this)
+ linker.teardownListener(node)
+ }
+
+ handleAccept () {
+ this.setState({ disclaimerDisabled: true })
+ this.props.onConfirm()
+ }
+
+ render () {
+ const { notice = {} } = this.props
+ const { title, date, body } = notice
+ const { disclaimerDisabled } = this.state
+
+ return (
+ h('.flex-column.flex-center.flex-grow', {
+ style: {
+ width: '100%',
+ },
+ }, [
+ h('h3.flex-center.text-transform-uppercase.terms-header', {
+ style: {
+ background: '#EBEBEB',
+ color: '#AEAEAE',
+ width: '100%',
+ fontSize: '20px',
+ textAlign: 'center',
+ padding: 6,
+ },
+ }, [
+ title,
+ ]),
+
+ h('h5.flex-center.text-transform-uppercase.terms-header', {
+ style: {
+ background: '#EBEBEB',
+ color: '#AEAEAE',
+ marginBottom: 24,
+ width: '100%',
+ fontSize: '20px',
+ textAlign: 'center',
+ padding: 6,
+ },
+ }, [
+ date,
+ ]),
+
+ h('style', `
+
+ .markdown {
+ overflow-x: hidden;
+ }
+
+ .markdown h1, .markdown h2, .markdown h3 {
+ margin: 10px 0;
+ font-weight: bold;
+ }
+
+ .markdown strong {
+ font-weight: bold;
+ }
+ .markdown em {
+ font-style: italic;
+ }
+
+ .markdown p {
+ margin: 10px 0;
+ }
+
+ .markdown a {
+ color: #df6b0e;
+ }
+
+ `),
+
+ h('div.markdown', {
+ onScroll: (e) => {
+ var object = e.currentTarget
+ if (object.offsetHeight + object.scrollTop + 100 >= object.scrollHeight) {
+ this.setState({ disclaimerDisabled: false })
+ }
+ },
+ style: {
+ background: 'rgb(235, 235, 235)',
+ height: '310px',
+ padding: '6px',
+ width: '90%',
+ overflowY: 'scroll',
+ scroll: 'auto',
+ },
+ }, [
+ h(ReactMarkdown, {
+ className: 'notice-box',
+ source: body,
+ skipHtml: true,
+ }),
+ ]),
+
+ h('button.primary', {
+ disabled: disclaimerDisabled,
+ onClick: () => this.handleAccept(),
+ style: {
+ marginTop: '18px',
+ },
+ }, 'Accept'),
+ ])
+ )
+ }
+
+}
+
+const mapStateToProps = state => {
+ const { metamask } = state
+ const { noActiveNotices, lastUnreadNotice, lostAccounts } = metamask
+
+ return {
+ noActiveNotices,
+ lastUnreadNotice,
+ lostAccounts,
+ }
+}
+
+Notice.propTypes = {
+ notice: PropTypes.object,
+ onConfirm: PropTypes.func,
+ history: PropTypes.object,
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ markNoticeRead: lastUnreadNotice => dispatch(actions.markNoticeRead(lastUnreadNotice)),
+ markAccountsFound: () => dispatch(actions.markAccountsFound()),
+ }
+}
+
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const { noActiveNotices, lastUnreadNotice, lostAccounts } = stateProps
+ const { markNoticeRead, markAccountsFound } = dispatchProps
+
+ let notice
+ let onConfirm
+
+ if (!noActiveNotices) {
+ notice = lastUnreadNotice
+ onConfirm = () => markNoticeRead(lastUnreadNotice)
+ } else if (lostAccounts && lostAccounts.length > 0) {
+ notice = generateLostAccountsNotice(lostAccounts)
+ onConfirm = () => markAccountsFound()
+ }
+
+ return {
+ ...stateProps,
+ ...dispatchProps,
+ ...ownProps,
+ notice,
+ onConfirm,
+ }
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps, mergeProps)(Notice)
diff --git a/ui/app/components/pages/settings/index.js b/ui/app/components/pages/settings/index.js
new file mode 100644
index 000000000..384ae4b41
--- /dev/null
+++ b/ui/app/components/pages/settings/index.js
@@ -0,0 +1,59 @@
+const { Component } = require('react')
+const { Switch, Route, matchPath } = require('react-router-dom')
+const PropTypes = require('prop-types')
+const h = require('react-hyperscript')
+const TabBar = require('../../tab-bar')
+const Settings = require('./settings')
+const Info = require('./info')
+const { DEFAULT_ROUTE, SETTINGS_ROUTE, INFO_ROUTE } = require('../../../routes')
+
+class Config extends Component {
+ renderTabs () {
+ const { history, location } = this.props
+
+ return h('div.settings__tabs', [
+ h(TabBar, {
+ tabs: [
+ { content: 'Settings', key: SETTINGS_ROUTE },
+ { content: 'Info', key: INFO_ROUTE },
+ ],
+ isActive: key => matchPath(location.pathname, { path: key, exact: true }),
+ onSelect: key => history.push(key),
+ }),
+ ])
+ }
+
+ render () {
+ const { history } = this.props
+
+ return (
+ h('.main-container.settings', {}, [
+ h('.settings__header', [
+ h('div.settings__close-button', {
+ onClick: () => history.push(DEFAULT_ROUTE),
+ }),
+ this.renderTabs(),
+ ]),
+ h(Switch, [
+ h(Route, {
+ exact: true,
+ path: INFO_ROUTE,
+ component: Info,
+ }),
+ h(Route, {
+ exact: true,
+ path: SETTINGS_ROUTE,
+ component: Settings,
+ }),
+ ]),
+ ])
+ )
+ }
+}
+
+Config.propTypes = {
+ location: PropTypes.object,
+ history: PropTypes.object,
+}
+
+module.exports = Config
diff --git a/ui/app/components/pages/settings/info.js b/ui/app/components/pages/settings/info.js
new file mode 100644
index 000000000..bd9040499
--- /dev/null
+++ b/ui/app/components/pages/settings/info.js
@@ -0,0 +1,120 @@
+const { Component } = require('react')
+const PropTypes = require('prop-types')
+const h = require('react-hyperscript')
+
+class Info extends Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ version: global.platform.getVersion(),
+ }
+ }
+
+ renderLogo () {
+ return (
+ h('div.settings__info-logo-wrapper', [
+ h('img.settings__info-logo', { src: 'images/info-logo.png' }),
+ ])
+ )
+ }
+
+ renderInfoLinks () {
+ return (
+ h('div.settings__content-item.settings__content-item--without-height', [
+ h('div.settings__info-link-header', this.context.t('links')),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://metamask.io/privacy.html',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', this.context.t('privacyMsg')),
+ ]),
+ ]),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://metamask.io/terms.html',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', this.context.t('terms')),
+ ]),
+ ]),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://metamask.io/attributions.html',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', this.context.t('attributions')),
+ ]),
+ ]),
+ h('hr.settings__info-separator'),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://support.metamask.io',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', this.context.t('supportCenter')),
+ ]),
+ ]),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://metamask.io/',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', this.context.t('visitWebSite')),
+ ]),
+ ]),
+ h('div.settings__info-link-item', [
+ h('a', {
+ target: '_blank',
+ href: 'mailto:help@metamask.io?subject=Feedback',
+ }, [
+ h('span.settings__info-link', this.context.t('emailUs')),
+ ]),
+ ]),
+ ])
+ )
+ }
+
+ render () {
+ return (
+ h('div.settings__content', [
+ h('div.settings__content-row', [
+ h('div.settings__content-item.settings__content-item--without-height', [
+ this.renderLogo(),
+ h('div.settings__info-item', [
+ h('div.settings__info-version-header', 'MetaMask Version'),
+ h('div.settings__info-version-number', this.state.version),
+ ]),
+ h('div.settings__info-item', [
+ h(
+ 'div.settings__info-about',
+ this.context.t('builtInCalifornia')
+ ),
+ ]),
+ ]),
+ this.renderInfoLinks(),
+ ]),
+ ])
+ )
+ }
+}
+
+Info.propTypes = {
+ tab: PropTypes.string,
+ metamask: PropTypes.object,
+ setCurrentCurrency: PropTypes.func,
+ setRpcTarget: PropTypes.func,
+ displayWarning: PropTypes.func,
+ revealSeedConfirmation: PropTypes.func,
+ warning: PropTypes.string,
+ location: PropTypes.object,
+ history: PropTypes.object,
+ t: PropTypes.func,
+}
+
+Info.contextTypes = {
+ t: PropTypes.func,
+}
+
+module.exports = Info
diff --git a/ui/app/settings.js b/ui/app/components/pages/settings/settings.js
index 3aa7b9c6b..bdefe56f8 100644
--- a/ui/app/settings.js
+++ b/ui/app/components/pages/settings/settings.js
@@ -1,16 +1,18 @@
const { Component } = require('react')
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
-const actions = require('./actions')
-const infuraCurrencies = require('./infura-conversion.json')
+const actions = require('../../../actions')
+const infuraCurrencies = require('../../../infura-conversion.json')
const validUrl = require('valid-url')
-const { exportAsFile } = require('./util')
-const TabBar = require('./components/tab-bar')
-const SimpleDropdown = require('./components/dropdowns/simple-dropdown')
+const { exportAsFile } = require('../../../util')
+const SimpleDropdown = require('../../dropdowns/simple-dropdown')
const ToggleButton = require('react-toggle-button')
-const locales = require('../../app/_locales/index.json')
-const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums
+const { REVEAL_SEED_ROUTE } = require('../../../routes')
+const locales = require('../../../../../app/_locales/index.json')
+const { OLD_UI_NETWORK_TYPE } = require('../../../../../app/scripts/controllers/network/enums')
const getInfuraCurrencyOptions = () => {
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
@@ -40,30 +42,11 @@ class Settings extends Component {
constructor (props) {
super(props)
- const { tab } = props
- const activeTab = tab === 'info' ? 'info' : 'settings'
-
this.state = {
- activeTab,
newRpc: '',
}
}
- renderTabs () {
- const { activeTab } = this.state
-
- return h('div.settings__tabs', [
- h(TabBar, {
- tabs: [
- { content: this.context.t('settings'), key: 'settings' },
- { content: this.context.t('info'), key: 'info' },
- ],
- defaultTab: activeTab,
- tabSelected: key => this.setState({ activeTab: key }),
- }),
- ])
- }
-
renderBlockieOptIn () {
const { metamask: { useBlockie }, setUseBlockie } = this.props
@@ -253,7 +236,7 @@ class Settings extends Component {
}
renderSeedWords () {
- const { revealSeedConfirmation } = this.props
+ const { history } = this.props
return (
h('div.settings__content-row', [
@@ -261,9 +244,9 @@ class Settings extends Component {
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h('button.btn-primary--lg.settings__button--red', {
- onClick (event) {
+ onClick: event => {
event.preventDefault()
- revealSeedConfirmation()
+ history.push(REVEAL_SEED_ROUTE)
},
}, this.context.t('revealSeedWords')),
]),
@@ -310,7 +293,7 @@ class Settings extends Component {
])
}
- renderSettingsContent () {
+ render () {
const { warning, isMascara } = this.props
return (
@@ -328,120 +311,9 @@ class Settings extends Component {
])
)
}
-
- renderLogo () {
- return (
- h('div.settings__info-logo-wrapper', [
- h('img.settings__info-logo', { src: 'images/info-logo.png' }),
- ])
- )
- }
-
- renderInfoLinks () {
- return (
- h('div.settings__content-item.settings__content-item--without-height', [
- h('div.settings__info-link-header', this.context.t('links')),
- h('div.settings__info-link-item', [
- h('a', {
- href: 'https://metamask.io/privacy.html',
- target: '_blank',
- }, [
- h('span.settings__info-link', this.context.t('privacyMsg')),
- ]),
- ]),
- h('div.settings__info-link-item', [
- h('a', {
- href: 'https://metamask.io/terms.html',
- target: '_blank',
- }, [
- h('span.settings__info-link', this.context.t('terms')),
- ]),
- ]),
- h('div.settings__info-link-item', [
- h('a', {
- href: 'https://metamask.io/attributions.html',
- target: '_blank',
- }, [
- h('span.settings__info-link', this.context.t('attributions')),
- ]),
- ]),
- h('hr.settings__info-separator'),
- h('div.settings__info-link-item', [
- h('a', {
- href: 'https://support.metamask.io',
- target: '_blank',
- }, [
- h('span.settings__info-link', this.context.t('supportCenter')),
- ]),
- ]),
- h('div.settings__info-link-item', [
- h('a', {
- href: 'https://metamask.io/',
- target: '_blank',
- }, [
- h('span.settings__info-link', this.context.t('visitWebSite')),
- ]),
- ]),
- h('div.settings__info-link-item', [
- h('a', {
- target: '_blank',
- href: 'mailto:help@metamask.io?subject=Feedback',
- }, [
- h('span.settings__info-link', this.context.t('emailUs')),
- ]),
- ]),
- ])
- )
- }
-
- renderInfoContent () {
- const version = global.platform.getVersion()
-
- return (
- h('div.settings__content', [
- h('div.settings__content-row', [
- h('div.settings__content-item.settings__content-item--without-height', [
- this.renderLogo(),
- h('div.settings__info-item', [
- h('div.settings__info-version-header', 'MetaMask Version'),
- h('div.settings__info-version-number', `${version}`),
- ]),
- h('div.settings__info-item', [
- h(
- 'div.settings__info-about',
- this.context.t('builtInCalifornia')
- ),
- ]),
- ]),
- this.renderInfoLinks(),
- ]),
- ])
- )
- }
-
- render () {
- const { goHome } = this.props
- const { activeTab } = this.state
-
- return (
- h('.main-container.settings', {}, [
- h('.settings__header', [
- h('div.settings__close-button', {
- onClick: goHome,
- }),
- this.renderTabs(),
- ]),
-
- activeTab === 'settings'
- ? this.renderSettingsContent()
- : this.renderInfoContent(),
- ])
- )
- }
}
Settings.propTypes = {
- tab: PropTypes.string,
metamask: PropTypes.object,
setUseBlockie: PropTypes.func,
setCurrentCurrency: PropTypes.func,
@@ -451,7 +323,7 @@ Settings.propTypes = {
setFeatureFlagToBeta: PropTypes.func,
showResetAccountConfirmationModal: PropTypes.func,
warning: PropTypes.string,
- goHome: PropTypes.func,
+ history: PropTypes.object,
isMascara: PropTypes.bool,
updateCurrentLocale: PropTypes.func,
currentLocale: PropTypes.string,
@@ -469,7 +341,6 @@ const mapStateToProps = state => {
const mapDispatchToProps = dispatch => {
return {
- goHome: () => dispatch(actions.goHome()),
setCurrentCurrency: currency => dispatch(actions.setCurrentCurrency(currency)),
setRpcTarget: newRpc => dispatch(actions.setRpcTarget(newRpc)),
displayWarning: warning => dispatch(actions.displayWarning(warning)),
@@ -490,5 +361,7 @@ Settings.contextTypes = {
t: PropTypes.func,
}
-module.exports = connect(mapStateToProps, mapDispatchToProps)(Settings)
-
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(Settings)
diff --git a/ui/app/components/pages/unlock.js b/ui/app/components/pages/unlock.js
new file mode 100644
index 000000000..30144b978
--- /dev/null
+++ b/ui/app/components/pages/unlock.js
@@ -0,0 +1,194 @@
+const { Component } = require('react')
+const PropTypes = require('prop-types')
+const connect = require('../../metamask-connect')
+const h = require('react-hyperscript')
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
+const {
+ tryUnlockMetamask,
+ forgotPassword,
+ markPasswordForgotten,
+ setNetworkEndpoints,
+ setFeatureFlag,
+} = require('../../actions')
+const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
+const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
+const getCaretCoordinates = require('textarea-caret')
+const EventEmitter = require('events').EventEmitter
+const Mascot = require('../mascot')
+const { OLD_UI_NETWORK_TYPE } = require('../../../../app/scripts/controllers/network/enums')
+const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../routes')
+
+class UnlockScreen extends Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ error: null,
+ }
+
+ this.animationEventEmitter = new EventEmitter()
+ }
+
+ componentWillMount () {
+ const { isUnlocked, history } = this.props
+
+ if (isUnlocked) {
+ history.push(DEFAULT_ROUTE)
+ }
+ }
+
+ componentDidMount () {
+ const passwordBox = document.getElementById('password-box')
+
+ if (passwordBox) {
+ passwordBox.focus()
+ }
+ }
+
+ tryUnlockMetamask (password) {
+ const { tryUnlockMetamask, history } = this.props
+ tryUnlockMetamask(password)
+ .then(() => history.push(DEFAULT_ROUTE))
+ .catch(({ message }) => this.setState({ error: message }))
+ }
+
+ onSubmit (event) {
+ const input = document.getElementById('password-box')
+ const password = input.value
+ this.tryUnlockMetamask(password)
+ }
+
+ onKeyPress (event) {
+ if (event.key === 'Enter') {
+ this.submitPassword(event)
+ }
+ }
+
+ submitPassword (event) {
+ var element = event.target
+ var password = element.value
+ // reset input
+ element.value = ''
+ this.tryUnlockMetamask(password)
+ }
+
+ inputChanged (event) {
+ // tell mascot to look at page action
+ var element = event.target
+ var boundingRect = element.getBoundingClientRect()
+ var coordinates = getCaretCoordinates(element, element.selectionEnd)
+ this.animationEventEmitter.emit('point', {
+ x: boundingRect.left + coordinates.left - element.scrollLeft,
+ y: boundingRect.top + coordinates.top - element.scrollTop,
+ })
+ }
+
+ render () {
+ const { error } = this.state
+ return (
+ h('.unlock-screen', [
+
+ h(Mascot, {
+ animationEventEmitter: this.animationEventEmitter,
+ }),
+
+ h('h1', {
+ style: {
+ fontSize: '1.4em',
+ textTransform: 'uppercase',
+ color: '#7F8082',
+ },
+ }, this.props.t('appName')),
+
+ h('input.large-input', {
+ type: 'password',
+ id: 'password-box',
+ placeholder: 'enter password',
+ style: {
+ background: 'white',
+ },
+ onKeyPress: this.onKeyPress.bind(this),
+ onInput: this.inputChanged.bind(this),
+ }),
+
+ h('.error', {
+ style: {
+ display: error ? 'block' : 'none',
+ padding: '0 20px',
+ textAlign: 'center',
+ },
+ }, error),
+
+ h('button.primary.cursor-pointer', {
+ onClick: this.onSubmit.bind(this),
+ style: {
+ margin: 10,
+ },
+ }, this.props.t('login')),
+
+ h('p.pointer', {
+ onClick: () => {
+ this.props.markPasswordForgotten()
+ this.props.history.push(RESTORE_VAULT_ROUTE)
+
+ if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
+ global.platform.openExtensionInBrowser()
+ }
+ },
+ style: {
+ fontSize: '0.8em',
+ color: 'rgb(247, 134, 28)',
+ textDecoration: 'underline',
+ },
+ }, this.props.t('restoreFromSeed')),
+
+ h('p.pointer', {
+ onClick: () => {
+ this.props.useOldInterface()
+ .then(() => this.props.setNetworkEndpoints(OLD_UI_NETWORK_TYPE))
+ },
+ style: {
+ fontSize: '0.8em',
+ color: '#aeaeae',
+ textDecoration: 'underline',
+ marginTop: '32px',
+ },
+ }, this.props.t('classicInterface')),
+ ])
+ )
+ }
+}
+
+UnlockScreen.propTypes = {
+ forgotPassword: PropTypes.func,
+ tryUnlockMetamask: PropTypes.func,
+ markPasswordForgotten: PropTypes.func,
+ history: PropTypes.object,
+ isUnlocked: PropTypes.bool,
+ t: PropTypes.func,
+ useOldInterface: PropTypes.func,
+ setNetworkEndpoints: PropTypes.func,
+}
+
+const mapStateToProps = state => {
+ const { metamask: { isUnlocked } } = state
+ return {
+ isUnlocked,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ forgotPassword: () => dispatch(forgotPassword()),
+ tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)),
+ markPasswordForgotten: () => dispatch(markPasswordForgotten()),
+ useOldInterface: () => dispatch(setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')),
+ setNetworkEndpoints: type => dispatch(setNetworkEndpoints(type)),
+ }
+}
+
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(UnlockScreen)
diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js
index 7bf20bced..ac1b15a4f 100644
--- a/ui/app/components/pending-tx/confirm-send-ether.js
+++ b/ui/app/components/pending-tx/confirm-send-ether.js
@@ -1,4 +1,6 @@
const Component = require('react').Component
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
@@ -21,14 +23,24 @@ const {
const GasFeeDisplay = require('../send/gas-fee-display-v2')
const SenderToRecipient = require('../sender-to-recipient')
const NetworkDisplay = require('../network-display')
+const currencyFormatter = require('currency-formatter')
+const currencies = require('currency-formatter/currencies')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
+const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
+
+import {
+ updateSendErrors,
+} from '../../ducks/send'
ConfirmSendEther.contextTypes = {
t: PropTypes.func,
}
-module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmSendEther)
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(ConfirmSendEther)
function mapStateToProps (state) {
@@ -72,7 +84,6 @@ function mapDispatchToProps (dispatch) {
errors: { to: null, amount: null },
editingTransactionId: id,
}))
- dispatch(actions.showSendPage())
},
cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })),
showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => {
@@ -98,7 +109,7 @@ function mapDispatchToProps (dispatch) {
}))
dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
},
- updateSendErrors: error => dispatch(actions.updateSendErrors(error)),
+ updateSendErrors: error => dispatch(updateSendErrors(error)),
}
}
@@ -134,7 +145,7 @@ ConfirmSendEther.prototype.updateComponentSendErrors = function (prevProps) {
if (shouldUpdateBalanceSendErrors) {
const balanceIsSufficient = this.isBalanceSufficient(txMeta)
updateSendErrors({
- insufficientFunds: balanceIsSufficient ? false : this.context.t('insufficientFunds'),
+ insufficientFunds: balanceIsSufficient ? false : 'insufficientFunds',
})
}
@@ -142,7 +153,7 @@ ConfirmSendEther.prototype.updateComponentSendErrors = function (prevProps) {
if (shouldUpdateSimulationSendError) {
updateSendErrors({
- simulationFails: !txMeta.simulationFails ? false : this.context.t('transactionError'),
+ simulationFails: !txMeta.simulationFails ? false : 'transactionError',
})
}
}
@@ -270,9 +281,24 @@ ConfirmSendEther.prototype.getData = function () {
}
}
+ConfirmSendEther.prototype.convertToRenderableCurrency = function (value, currencyCode) {
+ const upperCaseCurrencyCode = currencyCode.toUpperCase()
+
+ return currencies.find(currency => currency.code === upperCaseCurrencyCode)
+ ? currencyFormatter.format(Number(value), {
+ code: upperCaseCurrencyCode,
+ })
+ : value
+}
+
+ConfirmSendEther.prototype.editTransaction = function (txMeta) {
+ const { editTransaction, history } = this.props
+ editTransaction(txMeta)
+ history.push(SEND_ROUTE)
+}
+
ConfirmSendEther.prototype.render = function () {
const {
- editTransaction,
currentCurrency,
clearSend,
conversionRate,
@@ -309,6 +335,9 @@ ConfirmSendEther.prototype.render = function () {
? 'Increase your gas fee to attempt to overwrite and speed up your transaction'
: 'Please review your transaction.'
+ const convertedAmountInFiat = this.convertToRenderableCurrency(amountInFIAT, currentCurrency)
+ const convertedTotalInFiat = this.convertToRenderableCurrency(totalInFIAT, currentCurrency)
+
// This is from the latest master
// It handles some of the errors that we are not currently handling
// Leaving as comments fo reference
@@ -328,7 +357,7 @@ ConfirmSendEther.prototype.render = function () {
h('.page-container__header', [
h('.page-container__header-row', [
h('span.page-container__back-button', {
- onClick: () => editTransaction(txMeta),
+ onClick: () => this.editTransaction(txMeta),
style: {
visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden',
},
@@ -355,7 +384,7 @@ ConfirmSendEther.prototype.render = function () {
// `You're sending to Recipient ...${toAddress.slice(toAddress.length - 4)}`,
// ]),
- h('h3.flex-center.confirm-screen-send-amount', [`${amountInFIAT}`]),
+ h('h3.flex-center.confirm-screen-send-amount', [`${convertedAmountInFiat}`]),
h('h3.flex-center.confirm-screen-send-amount-currency', [ currentCurrency.toUpperCase() ]),
h('div.flex-center.confirm-memo-wrapper', [
h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]),
@@ -402,7 +431,7 @@ ConfirmSendEther.prototype.render = function () {
]),
h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', `${totalInFIAT} ${currentCurrency.toUpperCase()}`),
+ h('div.confirm-screen-row-info', `${convertedTotalInFiat} ${currentCurrency.toUpperCase()}`),
h('div.confirm-screen-row-detail', `${totalInETH} ETH`),
]),
@@ -506,7 +535,9 @@ ConfirmSendEther.prototype.render = function () {
}, this.context.t('cancel')),
// Accept Button
- h('button.btn-confirm.page-container__footer-button.allcaps', [this.context.t('confirm')]),
+ h('button.btn-confirm.page-container__footer-button.allcaps', {
+ onClick: event => this.onSubmit(event),
+ }, this.context.t('confirm')),
]),
]),
])
@@ -532,9 +563,9 @@ ConfirmSendEther.prototype.onSubmit = function (event) {
if (valid && this.verifyGasParams() && balanceIsSufficient) {
this.props.sendTransaction(txMeta, event)
} else if (!balanceIsSufficient) {
- updateSendErrors({ insufficientFunds: this.context.t('insufficientFunds') })
+ updateSendErrors({ insufficientFunds: 'insufficientFunds' })
} else {
- updateSendErrors({ invalidGasParams: this.context.t('invalidGasParams') })
+ updateSendErrors({ invalidGasParams: 'invalidGasParams' })
this.setState({ submitting: false })
}
}
@@ -544,6 +575,7 @@ ConfirmSendEther.prototype.cancel = function (event, txMeta) {
const { cancelTransaction } = this.props
cancelTransaction(txMeta)
+ .then(() => this.props.history.push(DEFAULT_ROUTE))
}
ConfirmSendEther.prototype.isBalanceSufficient = function (txMeta) {
@@ -631,4 +663,4 @@ ConfirmSendEther.prototype.bnMultiplyByFraction = function (targetBN, numerator,
const numBN = new BN(numerator)
const denomBN = new BN(denominator)
return targetBN.mul(numBN).div(denomBN)
-} \ No newline at end of file
+}
diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js
index 19e591fd6..e1abc6d3d 100644
--- a/ui/app/components/pending-tx/confirm-send-token.js
+++ b/ui/app/components/pending-tx/confirm-send-token.js
@@ -1,4 +1,6 @@
const Component = require('react').Component
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
@@ -25,6 +27,8 @@ const {
calcTokenAmount,
} = require('../../token-util')
const classnames = require('classnames')
+const currencyFormatter = require('currency-formatter')
+const currencies = require('currency-formatter/currencies')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
@@ -33,16 +37,24 @@ const {
getSelectedAddress,
getSelectedTokenContract,
} = require('../../selectors')
+const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
+
+import {
+ updateSendErrors,
+} from '../../ducks/send'
ConfirmSendToken.contextTypes = {
t: PropTypes.func,
}
-module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmSendToken)
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(ConfirmSendToken)
function mapStateToProps (state, ownProps) {
- const { token: { symbol }, txData } = ownProps
+ const { token: { address }, txData } = ownProps
const { txParams } = txData || {}
const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data)
@@ -53,7 +65,7 @@ function mapStateToProps (state, ownProps) {
} = state.metamask
const accounts = state.metamask.accounts
const selectedAddress = getSelectedAddress(state)
- const tokenExchangeRate = getTokenExchangeRate(state, symbol)
+ const tokenExchangeRate = getTokenExchangeRate(state, address)
const { balance } = accounts[selectedAddress]
return {
conversionRate,
@@ -69,12 +81,9 @@ function mapStateToProps (state, ownProps) {
}
function mapDispatchToProps (dispatch, ownProps) {
- const { token: { symbol } } = ownProps
-
return {
backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)),
cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })),
- updateTokenExchangeRate: () => dispatch(actions.updateTokenExchangeRate(symbol)),
editTransaction: txMeta => {
const { token: { address } } = ownProps
const { txParams = {}, id } = txMeta
@@ -136,7 +145,7 @@ function mapDispatchToProps (dispatch, ownProps) {
}))
dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
},
- updateSendErrors: error => dispatch(actions.updateSendErrors(error)),
+ updateSendErrors: error => dispatch(updateSendErrors(error)),
}
}
@@ -147,6 +156,12 @@ function ConfirmSendToken () {
this.onSubmit = this.onSubmit.bind(this)
}
+ConfirmSendToken.prototype.editTransaction = function (txMeta) {
+ const { editTransaction, history } = this.props
+ editTransaction(txMeta)
+ history.push(SEND_ROUTE)
+}
+
ConfirmSendToken.prototype.updateComponentSendErrors = function (prevProps) {
const {
balance: oldBalance,
@@ -191,7 +206,6 @@ ConfirmSendToken.prototype.componentWillMount = function () {
.balanceOf(selectedAddress)
.then(usersToken => {
})
- this.props.updateTokenExchangeRate()
this.updateComponentSendErrors({})
}
@@ -310,10 +324,12 @@ ConfirmSendToken.prototype.renderHeroAmount = function () {
const txParams = txMeta.txParams || {}
const { memo = '' } = txParams
+ const convertedAmountInFiat = this.convertToRenderableCurrency(fiatAmount, currentCurrency)
+
return fiatAmount
? (
h('div.confirm-send-token__hero-amount-wrapper', [
- h('h3.flex-center.confirm-screen-send-amount', `${fiatAmount}`),
+ h('h3.flex-center.confirm-screen-send-amount', `${convertedAmountInFiat}`),
h('h3.flex-center.confirm-screen-send-amount-currency', currentCurrency),
h('div.flex-center.confirm-memo-wrapper', [
h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]),
@@ -361,6 +377,9 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () {
const { fiat: fiatAmount, token: tokenAmount } = this.getAmount()
const { fiat: fiatGas, token: tokenGas } = this.getGasFee()
+ const totalInFIAT = fiatAmount && fiatGas && addCurrencies(fiatAmount, fiatGas)
+ const convertedTotalInFiat = this.convertToRenderableCurrency(totalInFIAT, currentCurrency)
+
return fiatAmount && fiatGas
? (
h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [
@@ -370,7 +389,7 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () {
]),
h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', `${addCurrencies(fiatAmount, fiatGas)} ${currentCurrency}`),
+ h('div.confirm-screen-row-info', `${convertedTotalInFiat} ${currentCurrency}`),
h('div.confirm-screen-row-detail', `${addCurrencies(tokenAmount, tokenGas || '0')} ${symbol}`),
]),
])
@@ -405,8 +424,17 @@ ConfirmSendToken.prototype.renderErrorMessage = function (message) {
: null
}
+ConfirmSendToken.prototype.convertToRenderableCurrency = function (value, currencyCode) {
+ const upperCaseCurrencyCode = currencyCode.toUpperCase()
+
+ return currencies.find(currency => currency.code === upperCaseCurrencyCode)
+ ? currencyFormatter.format(Number(value), {
+ code: upperCaseCurrencyCode,
+ })
+ : value
+}
+
ConfirmSendToken.prototype.render = function () {
- const { editTransaction } = this.props
const txMeta = this.gatherTxMeta()
const {
from: {
@@ -433,7 +461,7 @@ ConfirmSendToken.prototype.render = function () {
h('div.page-container', [
h('div.page-container__header', [
!txMeta.lastGasPrice && h('button.confirm-screen-back-button', {
- onClick: () => editTransaction(txMeta),
+ onClick: () => this.editTransaction(txMeta),
}, this.context.t('edit')),
h('div.page-container__title', title),
h('div.page-container__subtitle', subtitle),
@@ -513,7 +541,9 @@ ConfirmSendToken.prototype.render = function () {
}, this.context.t('cancel')),
// Accept Button
- h('button.btn-confirm.page-container__footer-button.allcaps', [this.context.t('confirm')]),
+ h('button.btn-confirm.page-container__footer-button.allcaps', {
+ onClick: event => this.onSubmit(event),
+ }, [this.context.t('confirm')]),
]),
]),
]),
@@ -532,9 +562,9 @@ ConfirmSendToken.prototype.onSubmit = function (event) {
if (valid && this.verifyGasParams() && balanceIsSufficient) {
this.props.sendTransaction(txMeta, event)
} else if (!balanceIsSufficient) {
- updateSendErrors({ insufficientFunds: this.context.t('insufficientFunds') })
+ updateSendErrors({ insufficientFunds: 'insufficientFunds' })
} else {
- updateSendErrors({ invalidGasParams: this.context.t('invalidGasParams') })
+ updateSendErrors({ invalidGasParams: 'invalidGasParams' })
this.setState({ submitting: false })
}
}
@@ -566,6 +596,7 @@ ConfirmSendToken.prototype.cancel = function (event, txMeta) {
const { cancelTransaction } = this.props
cancelTransaction(txMeta)
+ .then(() => this.props.history.push(DEFAULT_ROUTE))
}
ConfirmSendToken.prototype.checkValidity = function () {
diff --git a/ui/app/components/pending-tx/index.js b/ui/app/components/pending-tx/index.js
index acdd99364..6ee83ba7e 100644
--- a/ui/app/components/pending-tx/index.js
+++ b/ui/app/components/pending-tx/index.js
@@ -1,6 +1,7 @@
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
+const PropTypes = require('prop-types')
const clone = require('clone')
const abi = require('human-standard-token-abi')
const abiDecoder = require('abi-decoder')
@@ -11,6 +12,7 @@ const util = require('../../util')
const ConfirmSendEther = require('./confirm-send-ether')
const ConfirmSendToken = require('./confirm-send-token')
const ConfirmDeployContract = require('./confirm-deploy-contract')
+const Loading = require('../loading')
const TX_TYPES = {
DEPLOY_CONTRACT: 'deploy_contract',
@@ -53,10 +55,24 @@ function PendingTx () {
}
}
-PendingTx.prototype.componentWillMount = async function () {
+PendingTx.prototype.componentDidMount = function () {
+ this.setTokenData()
+}
+
+PendingTx.prototype.componentDidUpdate = function (prevProps, prevState) {
+ if (prevState.isFetching) {
+ this.setTokenData()
+ }
+}
+
+PendingTx.prototype.setTokenData = async function () {
const txMeta = this.gatherTxMeta()
const txParams = txMeta.txParams || {}
+ if (txMeta.loadingDefaults) {
+ return
+ }
+
if (!txParams.to) {
return this.setState({
transactionType: TX_TYPES.DEPLOY_CONTRACT,
@@ -125,7 +141,10 @@ PendingTx.prototype.render = function () {
const { sendTransaction } = this.props
if (isFetching) {
- return h('noscript')
+ return h(Loading, {
+ fullScreen: true,
+ loadingMessage: this.context.t('generatingTransaction'),
+ })
}
switch (transactionType) {
@@ -150,6 +169,12 @@ PendingTx.prototype.render = function () {
sendTransaction,
})
default:
- return h('noscript')
+ return h(Loading, {
+ fullScreen: true,
+ })
}
}
+
+PendingTx.contextTypes = {
+ t: PropTypes.func,
+}
diff --git a/ui/app/components/qr-code.js b/ui/app/components/qr-code.js
index 83885539c..3b2c62f49 100644
--- a/ui/app/components/qr-code.js
+++ b/ui/app/components/qr-code.js
@@ -3,8 +3,9 @@ const h = require('react-hyperscript')
const qrCode = require('qrcode-npm').qrcode
const inherits = require('util').inherits
const connect = require('react-redux').connect
-const isHexPrefixed = require('ethereumjs-util').isHexPrefixed
+const { isHexPrefixed } = require('ethereumjs-util')
const ReadOnlyInput = require('./readonly-input')
+const { checksumAddress } = require('../util')
module.exports = connect(mapStateToProps)(QrCodeView)
@@ -24,16 +25,16 @@ function QrCodeView () {
QrCodeView.prototype.render = function () {
const props = this.props
- const Qr = props.Qr
- const address = `${isHexPrefixed(Qr.data) ? 'ethereum:' : ''}${Qr.data}`
+ const { message, data } = props.Qr
+ const address = `${isHexPrefixed(data) ? 'ethereum:' : ''}${data}`
const qrImage = qrCode(4, 'M')
qrImage.addData(address)
qrImage.make()
return h('.div.flex-column.flex-center', [
- Array.isArray(Qr.message)
+ Array.isArray(message)
? h('.message-container', this.renderMultiMessage())
- : Qr.message && h('.qr-header', Qr.message),
+ : message && h('.qr-header', message),
this.props.warning ? this.props.warning && h('span.error.flex-center', {
style: {
@@ -50,7 +51,7 @@ QrCodeView.prototype.render = function () {
h(ReadOnlyInput, {
wrapperClass: 'ellip-address-wrapper',
inputClass: 'qr-ellip-address',
- value: Qr.data,
+ value: checksumAddress(data),
}),
])
}
diff --git a/ui/app/components/send/account-list-item.js b/ui/app/components/send/account-list-item.js
index 1ad3f69c1..b5e604a6e 100644
--- a/ui/app/components/send/account-list-item.js
+++ b/ui/app/components/send/account-list-item.js
@@ -2,6 +2,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
+const { checksumAddress } = require('../../util')
const Identicon = require('../identicon')
const CurrencyDisplay = require('./currency-display')
const { conversionRateSelector, getCurrentCurrency } = require('../../selectors')
@@ -56,7 +57,7 @@ AccountListItem.prototype.render = function () {
]),
- displayAddress && name && h('div.account-list-item__account-address', address),
+ displayAddress && name && h('div.account-list-item__account-address', checksumAddress(address)),
displayBalance && h(CurrencyDisplay, {
primaryCurrency: 'ETH',
diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js
index 819fee0a0..a7bd5d7ea 100644
--- a/ui/app/components/send/currency-display.js
+++ b/ui/app/components/send/currency-display.js
@@ -3,6 +3,8 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const CurrencyInput = require('../currency-input')
const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
+const currencyFormatter = require('currency-formatter')
+const currencies = require('currency-formatter/currencies')
module.exports = CurrencyDisplay
@@ -53,12 +55,32 @@ CurrencyDisplay.prototype.getValueToRender = function () {
})
}
+CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValue) {
+ const { primaryCurrency, convertedCurrency, conversionRate } = this.props
+
+ let convertedValue = conversionUtil(nonFormattedValue, {
+ fromNumericBase: 'dec',
+ fromCurrency: primaryCurrency,
+ toCurrency: convertedCurrency,
+ numberOfDecimals: 2,
+ conversionRate,
+ })
+ convertedValue = Number(convertedValue).toFixed(2)
+
+ const upperCaseCurrencyCode = convertedCurrency.toUpperCase()
+
+ return currencies.find(currency => currency.code === upperCaseCurrencyCode)
+ ? currencyFormatter.format(Number(convertedValue), {
+ code: upperCaseCurrencyCode,
+ })
+ : convertedValue
+}
+
CurrencyDisplay.prototype.render = function () {
const {
className = 'currency-display',
primaryBalanceClassName = 'currency-display__input',
convertedBalanceClassName = 'currency-display__converted-value',
- conversionRate,
primaryCurrency,
convertedCurrency,
readOnly = false,
@@ -68,14 +90,7 @@ CurrencyDisplay.prototype.render = function () {
const valueToRender = this.getValueToRender()
- let convertedValue = conversionUtil(valueToRender, {
- fromNumericBase: 'dec',
- fromCurrency: primaryCurrency,
- toCurrency: convertedCurrency,
- numberOfDecimals: 2,
- conversionRate,
- })
- convertedValue = Number(convertedValue).toFixed(2)
+ const convertedValueToRender = this.getConvertedValueToRender(valueToRender)
return h('div', {
className,
@@ -108,7 +123,7 @@ CurrencyDisplay.prototype.render = function () {
h('div', {
className: convertedBalanceClassName,
- }, `${convertedValue} ${convertedCurrency.toUpperCase()}`),
+ }, `${convertedValueToRender} ${convertedCurrency.toUpperCase()}`),
])
diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js
index edd0657f7..cdbee0cdc 100644
--- a/ui/app/components/send/send-v2-container.js
+++ b/ui/app/components/send/send-v2-container.js
@@ -2,6 +2,8 @@ const connect = require('react-redux').connect
const actions = require('../../actions')
const abi = require('ethereumjs-abi')
const SendEther = require('../../send-v2')
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
const {
accountsWithSendEtherInfoSelector,
@@ -16,7 +18,14 @@ const {
getSelectedTokenContract,
} = require('../../selectors')
-module.exports = connect(mapStateToProps, mapDispatchToProps)(SendEther)
+import {
+ updateSendErrors,
+} from '../../ducks/send'
+
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(SendEther)
function mapStateToProps (state) {
const fromAccounts = accountsWithSendEtherInfoSelector(state)
@@ -62,7 +71,6 @@ function mapDispatchToProps (dispatch) {
showCustomizeGasModal: () => dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })),
estimateGas: params => dispatch(actions.estimateGas(params)),
getGasPrice: () => dispatch(actions.getGasPrice()),
- updateTokenExchangeRate: token => dispatch(actions.updateTokenExchangeRate(token)),
signTokenTx: (tokenAddress, toAddress, amount, txData) => (
dispatch(actions.signTokenTx(tokenAddress, toAddress, amount, txData))
),
@@ -71,7 +79,8 @@ function mapDispatchToProps (dispatch) {
updateTx: txData => dispatch(actions.updateTransaction(txData)),
setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)),
addToAddressBook: (address, nickname) => dispatch(actions.addToAddressBook(address, nickname)),
- updateGasTotal: newTotal => dispatch(actions.updateGasTotal(newTotal)),
+ setGasTotal: newTotal => dispatch(actions.setGasTotal(newTotal)),
+ updateGasTotal: () => dispatch(actions.updateGasTotal()),
updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)),
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)),
updateSendTokenBalance: tokenBalance => dispatch(actions.updateSendTokenBalance(tokenBalance)),
@@ -79,8 +88,7 @@ function mapDispatchToProps (dispatch) {
updateSendTo: (newTo, nickname) => dispatch(actions.updateSendTo(newTo, nickname)),
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
updateSendMemo: newMemo => dispatch(actions.updateSendMemo(newMemo)),
- updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)),
- goHome: () => dispatch(actions.goHome()),
+ updateSendErrors: newError => dispatch(updateSendErrors(newError)),
clearSend: () => dispatch(actions.clearSend()),
setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)),
}
diff --git a/ui/app/components/send_/account-list-item/account-list-item-README.md b/ui/app/components/send_/account-list-item/account-list-item-README.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/account-list-item/account-list-item-README.md
diff --git a/ui/app/components/send_/account-list-item/account-list-item.component.js b/ui/app/components/send_/account-list-item/account-list-item.component.js
new file mode 100644
index 000000000..b8407d147
--- /dev/null
+++ b/ui/app/components/send_/account-list-item/account-list-item.component.js
@@ -0,0 +1,74 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import { checksumAddress } from '../../../util'
+import Identicon from '../../identicon'
+import CurrencyDisplay from '../../send/currency-display'
+
+export default class AccountListItem extends Component {
+
+ static propTypes = {
+ account: PropTypes.object,
+ className: PropTypes.string,
+ conversionRate: PropTypes.number,
+ currentCurrency: PropTypes.string,
+ displayAddress: PropTypes.bool,
+ displayBalance: PropTypes.bool,
+ handleClick: PropTypes.func,
+ icon: PropTypes.node,
+ };
+
+ render () {
+ const {
+ account,
+ className,
+ conversionRate,
+ currentCurrency,
+ displayAddress = false,
+ displayBalance = true,
+ handleClick,
+ icon = null,
+ } = this.props
+
+ const { name, address, balance } = account || {}
+
+ return (<div
+ className={`account-list-item ${className}`}
+ onClick={() => handleClick({ name, address, balance })}
+ >
+
+ <div className="account-list-item__top-row">
+ <Identicon
+ address={address}
+ className="account-list-item__identicon"
+ diameter={18}
+ />
+
+ <div className="account-list-item__account-name">{ name || address }</div>
+
+ {icon && <div className="account-list-item__icon">{ icon }</div>}
+
+ </div>
+
+ {displayAddress && name && <div className="account-list-item__account-address">
+ { checksumAddress(address) }
+ </div>}
+
+ {displayBalance && <CurrencyDisplay
+ className="account-list-item__account-balances"
+ conversionRate={conversionRate}
+ convertedBalanceClassName="account-list-item__account-secondary-balance"
+ convertedCurrency={currentCurrency}
+ primaryBalanceClassName="account-list-item__account-primary-balance"
+ primaryCurrency="ETH"
+ readOnly={true}
+ value={balance}
+ />}
+
+ </div>)
+ }
+}
+
+AccountListItem.contextTypes = {
+ t: PropTypes.func,
+}
+
diff --git a/ui/app/components/send_/account-list-item/account-list-item.container.js b/ui/app/components/send_/account-list-item/account-list-item.container.js
new file mode 100644
index 000000000..3151b1f1d
--- /dev/null
+++ b/ui/app/components/send_/account-list-item/account-list-item.container.js
@@ -0,0 +1,15 @@
+import { connect } from 'react-redux'
+import {
+ getConversionRate,
+ getConvertedCurrency,
+} from '../send.selectors.js'
+import AccountListItem from './account-list-item.component'
+
+export default connect(mapStateToProps)(AccountListItem)
+
+function mapStateToProps (state) {
+ return {
+ conversionRate: getConversionRate(state),
+ currentCurrency: getConvertedCurrency(state),
+ }
+}
diff --git a/ui/app/components/send_/account-list-item/account-list-item.scss b/ui/app/components/send_/account-list-item/account-list-item.scss
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/account-list-item/account-list-item.scss
diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
index e69de29bb..bdf12b738 100644
--- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
+++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
@@ -0,0 +1,54 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+
+export default class AmountMaxButton extends Component {
+
+ static propTypes = {
+ balance: PropTypes.string,
+ gasTotal: PropTypes.string,
+ maxModeOn: PropTypes.bool,
+ selectedToken: PropTypes.object,
+ setAmountToMax: PropTypes.func,
+ setMaxModeTo: PropTypes.func,
+ tokenBalance: PropTypes.string,
+ };
+
+ setMaxAmount () {
+ const {
+ balance,
+ gasTotal,
+ selectedToken,
+ setAmountToMax,
+ tokenBalance,
+ } = this.props
+
+ setAmountToMax({
+ balance,
+ gasTotal,
+ selectedToken,
+ tokenBalance,
+ })
+ }
+
+ render () {
+ const { setMaxModeTo, maxModeOn } = this.props
+
+ return (
+ <div
+ className="send-v2__amount-max"
+ onClick={(event) => {
+ event.preventDefault()
+ setMaxModeTo(true)
+ this.setMaxAmount()
+ }}
+ >
+ {!maxModeOn ? this.context.t('max') : ''}
+ </div>
+ )
+ }
+
+}
+
+AmountMaxButton.contextTypes = {
+ t: PropTypes.func,
+}
diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
index e69de29bb..a72f41775 100644
--- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
+++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
@@ -0,0 +1,40 @@
+import { connect } from 'react-redux'
+import {
+ getGasTotal,
+ getSelectedToken,
+ getSendFromBalance,
+ getTokenBalance,
+} from '../../../send.selectors.js'
+import { getMaxModeOn } from './amount-max-button.selectors.js'
+import { calcMaxAmount } from './amount-max-button.utils.js'
+import {
+ updateSendAmount,
+ setMaxModeTo,
+} from '../../../../../actions'
+import AmountMaxButton from './amount-max-button.component'
+import {
+ updateSendErrors,
+} from '../../../../../ducks/send'
+
+export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton)
+
+function mapStateToProps (state) {
+
+ return {
+ balance: getSendFromBalance(state),
+ gasTotal: getGasTotal(state),
+ maxModeOn: getMaxModeOn(state),
+ selectedToken: getSelectedToken(state),
+ tokenBalance: getTokenBalance(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ setAmountToMax: maxAmountDataObject => {
+ dispatch(updateSendErrors({ amount: null }))
+ dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
+ },
+ setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
+ }
+}
diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js
index e69de29bb..69fec1994 100644
--- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js
+++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js
@@ -0,0 +1,9 @@
+const selectors = {
+ getMaxModeOn,
+}
+
+module.exports = selectors
+
+function getMaxModeOn (state) {
+ return state.metamask.send.maxModeOn
+}
diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
index e69de29bb..b490a7fd7 100644
--- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
+++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
@@ -0,0 +1,22 @@
+const {
+ multiplyCurrencies,
+ subtractCurrencies,
+} = require('../../../../../conversion-util')
+const ethUtil = require('ethereumjs-util')
+
+function calcMaxAmount ({ balance, gasTotal, selectedToken, tokenBalance }) {
+ const { decimals } = selectedToken || {}
+ const multiplier = Math.pow(10, Number(decimals || 0))
+
+ return selectedToken
+ ? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'})
+ : subtractCurrencies(
+ ethUtil.addHexPrefix(balance),
+ ethUtil.addHexPrefix(gasTotal),
+ { toNumericBase: 'hex' }
+ )
+}
+
+module.exports = {
+ calcMaxAmount,
+}
diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js
index e69de29bb..8e201ae41 100644
--- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js
+++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js
@@ -0,0 +1,96 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component'
+import AmountMaxButton from './amount-max-button/amount-max-button.container'
+import CurrencyDisplay from '../../../send/currency-display'
+
+export default class SendAmountRow extends Component {
+
+ static propTypes = {
+ amount: PropTypes.string,
+ amountConversionRate: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
+ balance: PropTypes.string,
+ conversionRate: PropTypes.number,
+ convertedCurrency: PropTypes.string,
+ gasTotal: PropTypes.string,
+ inError: PropTypes.bool,
+ primaryCurrency: PropTypes.string,
+ selectedToken: PropTypes.object,
+ setMaxModeTo: PropTypes.func,
+ tokenBalance: PropTypes.string,
+ updateSendAmount: PropTypes.func,
+ updateSendAmountError: PropTypes.func,
+ }
+
+ validateAmount (amount) {
+ const {
+ amountConversionRate,
+ balance,
+ conversionRate,
+ gasTotal,
+ primaryCurrency,
+ selectedToken,
+ tokenBalance,
+ updateSendAmountError,
+ } = this.props
+
+ updateSendAmountError({
+ amount,
+ amountConversionRate,
+ balance,
+ conversionRate,
+ gasTotal,
+ primaryCurrency,
+ selectedToken,
+ tokenBalance,
+ })
+ }
+
+ handleAmountChange (amount) {
+ const { updateSendAmount, setMaxModeTo } = this.props
+
+ setMaxModeTo(false)
+ this.validateAmount(amount)
+ updateSendAmount(amount)
+ }
+
+ render () {
+ const {
+ amount,
+ amountConversionRate,
+ convertedCurrency,
+ gasTotal,
+ inError,
+ primaryCurrency = 'ETH',
+ selectedToken,
+ } = this.props
+
+ return (
+ <SendRowWrapper
+ label={`${this.context.t('amount')}:`}
+ showError={inError}
+ errorType={'amount'}
+ >
+ {!inError && gasTotal && <AmountMaxButton />}
+ <CurrencyDisplay
+ conversionRate={amountConversionRate}
+ convertedCurrency={convertedCurrency}
+ handleChange={newAmount => this.handleAmountChange(newAmount)}
+ inError={inError}
+ primaryCurrency={primaryCurrency}
+ selectedToken={selectedToken}
+ value={amount || '0x0'}
+ />
+ </SendRowWrapper>
+ )
+ }
+
+}
+
+SendAmountRow.contextTypes = {
+ t: PropTypes.func,
+}
+
diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js
index 6ae80e7f2..13888ec53 100644
--- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js
+++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js
@@ -1,48 +1,51 @@
+import { connect } from 'react-redux'
import {
- getSelectedToken,
- getPrimaryCurrency,
getAmountConversionRate,
+ getConversionRate,
getConvertedCurrency,
- getSendAmount,
getGasTotal,
- getSelectedBalance,
+ getPrimaryCurrency,
+ getSelectedToken,
+ getSendAmount,
+ getSendFromBalance,
getTokenBalance,
-} from '../../send.selectors.js'
+} from '../../send.selectors'
import {
- getMaxModeOn,
- getSendAmountError,
-} from './send-amount-row.selectors.js'
-import { getAmountErrorObject } from './send-to-row.utils.js'
+ sendAmountIsInError,
+} from './send-amount-row.selectors'
+import { getAmountErrorObject } from '../../send.utils'
import {
- updateSendErrors,
- updateSendTo,
-} from '../../../actions'
+ setMaxModeTo,
+ updateSendAmount,
+} from '../../../../actions'
import {
- openToDropdown,
- closeToDropdown,
-} from '../../../ducks/send'
-import SendToRow from './send-to-row.component'
+ updateSendErrors,
+} from '../../../../ducks/send'
+import SendAmountRow from './send-amount-row.component'
-export default connect(mapStateToProps, mapDispatchToProps)(SendToRow)
+export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow)
function mapStateToProps (state) {
-updateSendTo
-return {
- to: getSendTo(state),
- toAccounts: getSendToAccounts(state),
- toDropdownOpen: getToDropdownOpen(state),
- inError: sendToIsInError(state),
- network: getCurrentNetwork(state),
-}
+ return {
+ amount: getSendAmount(state),
+ amountConversionRate: getAmountConversionRate(state),
+ balance: getSendFromBalance(state),
+ conversionRate: getConversionRate(state),
+ convertedCurrency: getConvertedCurrency(state),
+ gasTotal: getGasTotal(state),
+ inError: sendAmountIsInError(state),
+ primaryCurrency: getPrimaryCurrency(state),
+ selectedToken: getSelectedToken(state),
+ tokenBalance: getTokenBalance(state),
+ }
}
function mapDispatchToProps (dispatch) {
-return {
- updateSendToError: (to) => {
- dispatch(updateSendErrors(getToErrorObject(to)))
- },
- updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
- openToDropdown: () => dispatch(()),
- closeToDropdown: () => dispatch(()),
+ return {
+ setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
+ updateSendAmount: newAmount => dispatch(updateSendAmount(newAmount)),
+ updateSendAmountError: (amountDataObject) => {
+ dispatch(updateSendErrors(getAmountErrorObject(amountDataObject)))
+ },
+ }
}
-} \ No newline at end of file
diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js
index e69de29bb..fb08c7ed7 100644
--- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js
+++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js
@@ -0,0 +1,9 @@
+const selectors = {
+ sendAmountIsInError,
+}
+
+module.exports = selectors
+
+function sendAmountIsInError (state) {
+ return Boolean(state.send.errors.amount)
+}
diff --git a/ui/app/components/send_/send-content/send-content.component.js b/ui/app/components/send_/send-content/send-content.component.js
index e69de29bb..e213cfcf3 100644
--- a/ui/app/components/send_/send-content/send-content.component.js
+++ b/ui/app/components/send_/send-content/send-content.component.js
@@ -0,0 +1,23 @@
+import React, { Component } from 'react'
+import PageContainerContent from '../../page-container/page-container-content.component'
+import SendAmountRow from './send-amount-row/send-amount-row.container'
+import SendFromRow from './send-from-row/send-from-row.container'
+import SendGasRow from './send-gas-row/send-gas-row.container'
+import SendToRow from './send-to-row/send-to-row.container'
+
+export default class SendContent extends Component {
+
+ render () {
+ return (
+ <PageContainerContent>
+ <div className="send-v2__form">
+ <SendFromRow />
+ <SendToRow />
+ <SendAmountRow />
+ <SendGasRow />
+ </div>
+ </PageContainerContent>
+ )
+ }
+
+}
diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js
index e69de29bb..337228122 100644
--- a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js
+++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js
@@ -0,0 +1,75 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import AccountListItem from '../../../account-list-item/account-list-item.container'
+
+export default class FromDropdown extends Component {
+
+ static propTypes = {
+ accounts: PropTypes.array,
+ closeDropdown: PropTypes.func,
+ dropdownOpen: PropTypes.bool,
+ onSelect: PropTypes.func,
+ openDropdown: PropTypes.func,
+ selectedAccount: PropTypes.object,
+ };
+
+ renderListItemIcon (icon, color) {
+ return <i className={`fa ${icon} fa-lg`} style={ { color } }/>
+ }
+
+ getListItemIcon (currentAccount, selectedAccount) {
+ return currentAccount.address === selectedAccount.address
+ ? this.renderListItemIcon('fa-check', '#02c9b1')
+ : null
+ }
+
+ renderDropdown () {
+ const {
+ accounts,
+ closeDropdown,
+ onSelect,
+ selectedAccount,
+ } = this.props
+
+ return (<div>
+ <div
+ className="send-v2__from-dropdown__close-area"
+ onClick={() => closeDropdown}
+ />
+ <div className="send-v2__from-dropdown__list">
+ {accounts.map((account, index) => <AccountListItem
+ account={account}
+ className="account-list-item__dropdown"
+ handleClick={() => {
+ onSelect(account)
+ closeDropdown()
+ }}
+ icon={this.getListItemIcon(account, selectedAccount.address)}
+ key={`from-dropdown-account-#${index}`}
+ />)}
+ </div>
+ </div>)
+ }
+
+ render () {
+ const {
+ dropdownOpen,
+ openDropdown,
+ selectedAccount,
+ } = this.props
+
+ return <div className="send-v2__from-dropdown">
+ <AccountListItem
+ account={selectedAccount}
+ handleClick={openDropdown}
+ icon={this.renderListItemIcon('fa-caret-down', '#dedede')}
+ />
+ {dropdownOpen && this.renderDropdown()},
+ </div>
+ }
+
+}
+
+FromDropdown.contextTypes = {
+ t: PropTypes.func,
+}
diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js
index 7582cb2e6..17e7f8e46 100644
--- a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js
+++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js
@@ -1,60 +1,59 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import SendRowWrapper from '../../../send/from-dropdown'
-import FromDropdown from ''
+import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component'
+import FromDropdown from './from-dropdown/from-dropdown.component'
export default class SendFromRow extends Component {
static propTypes = {
closeFromDropdown: PropTypes.func,
- conversionRate: PropTypes.string,
- from: PropTypes.string,
+ conversionRate: PropTypes.number,
+ from: PropTypes.object,
fromAccounts: PropTypes.array,
fromDropdownOpen: PropTypes.bool,
openFromDropdown: PropTypes.func,
tokenContract: PropTypes.object,
updateSendFrom: PropTypes.func,
- updateSendTokenBalance: PropTypes.func,
+ setSendTokenBalance: PropTypes.func,
};
async handleFromChange (newFrom) {
const {
updateSendFrom,
tokenContract,
- updateSendTokenBalance,
+ setSendTokenBalance,
} = this.props
if (tokenContract) {
const usersToken = await tokenContract.balanceOf(newFrom.address)
- updateSendTokenBalance(usersToken)
+ setSendTokenBalance(usersToken)
}
updateSendFrom(newFrom)
}
render () {
const {
+ closeFromDropdown,
+ conversionRate,
from,
fromAccounts,
- conversionRate,
fromDropdownOpen,
- tokenContract,
openFromDropdown,
- closeFromDropdown,
} = this.props
return (
<SendRowWrapper label={`${this.context.t('from')}:`}>
<FromDropdown
- dropdownOpen={fromDropdownOpen}
accounts={fromAccounts}
- selectedAccount={from}
- onSelect={newFrom => this.handleFromChange(newFrom)}
- openDropdown={() => openFromDropdown()}
closeDropdown={() => closeFromDropdown()}
conversionRate={conversionRate}
+ dropdownOpen={fromDropdownOpen}
+ onSelect={newFrom => this.handleFromChange(newFrom)}
+ openDropdown={() => openFromDropdown()}
+ selectedAccount={from}
/>
</SendRowWrapper>
- );
+ )
}
}
diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js
index 2ff3f0ccd..9e366445d 100644
--- a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js
+++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js
@@ -1,29 +1,31 @@
+import { connect } from 'react-redux'
import {
- getSendFrom,
- conversionRateSelector,
- getSelectedTokenContract,
- getCurrentAccountWithSendEtherInfo,
accountsWithSendEtherInfoSelector,
+ getConversionRate,
+ getSelectedTokenContract,
+ getSendFromObject,
} from '../../send.selectors.js'
-import { getFromDropdownOpen } from './send-from-row.selectors.js'
+import {
+ getFromDropdownOpen,
+} from './send-from-row.selectors.js'
import { calcTokenUpdateAmount } from './send-from-row.utils.js'
import {
- updateSendTokenBalance,
updateSendFrom,
-} from '../../../actions'
+ setSendTokenBalance,
+} from '../../../../actions'
import {
- openFromDropdown,
closeFromDropdown,
-} from '../../../ducks/send'
+ openFromDropdown,
+} from '../../../../ducks/send'
import SendFromRow from './send-from-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendFromRow)
function mapStateToProps (state) {
return {
- from: getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state),
+ conversionRate: getConversionRate(state),
+ from: getSendFromObject(state),
fromAccounts: accountsWithSendEtherInfoSelector(state),
- conversionRate: conversionRateSelector(state),
fromDropdownOpen: getFromDropdownOpen(state),
tokenContract: getSelectedTokenContract(state),
}
@@ -31,14 +33,14 @@ function mapStateToProps (state) {
function mapDispatchToProps (dispatch) {
return {
- updateSendTokenBalance: (usersToken, selectedToken) => {
+ closeFromDropdown: () => dispatch(closeFromDropdown()),
+ openFromDropdown: () => dispatch(openFromDropdown()),
+ updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)),
+ setSendTokenBalance: (usersToken, selectedToken) => {
if (!usersToken) return
const tokenBalance = calcTokenUpdateAmount(selectedToken, selectedToken)
- dispatch(updateSendTokenBalance(tokenBalance))
+ dispatch(setSendTokenBalance(tokenBalance))
},
- updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)),
- openFromDropdown: () => dispatch(()),
- closeFromDropdown: () => dispatch(()),
}
}
diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js
index 2be25816f..0aaaef793 100644
--- a/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js
+++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js
@@ -1,6 +1,6 @@
const {
calcTokenAmount,
-} = require('../../token-util')
+} = require('../../../../token-util')
function calcTokenUpdateAmount (usersToken, selectedToken) {
const { decimals } = selectedToken || {}
@@ -8,5 +8,5 @@ function calcTokenUpdateAmount (usersToken, selectedToken) {
}
module.exports = {
- calcTokenUpdateAmount
+ calcTokenUpdateAmount,
}
diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js
index e69de29bb..c62c110e0 100644
--- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js
+++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js
@@ -0,0 +1,49 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component'
+import GasFeeDisplay from '../../../send/gas-fee-display-v2'
+
+export default class SendGasRow extends Component {
+
+ static propTypes = {
+ closeFromDropdown: PropTypes.func,
+ conversionRate: PropTypes.number,
+ convertedCurrency: PropTypes.string,
+ from: PropTypes.string,
+ fromAccounts: PropTypes.array,
+ fromDropdownOpen: PropTypes.bool,
+ gasLoadingError: PropTypes.bool,
+ gasTotal: PropTypes.string,
+ openFromDropdown: PropTypes.func,
+ showCustomizeGasModal: PropTypes.func,
+ tokenContract: PropTypes.object,
+ updateSendFrom: PropTypes.func,
+ };
+
+ render () {
+ const {
+ conversionRate,
+ convertedCurrency,
+ gasLoadingError,
+ gasTotal,
+ showCustomizeGasModal,
+ } = this.props
+
+ return (
+ <SendRowWrapper label={`${this.context.t('gasFee')}:`}>
+ <GasFeeDisplay
+ conversionRate={conversionRate}
+ convertedCurrency={convertedCurrency}
+ gasLoadingError={gasLoadingError}
+ gasTotal={gasTotal}
+ onClick={() => showCustomizeGasModal()}
+ />
+ </SendRowWrapper>
+ )
+ }
+
+}
+
+SendGasRow.contextTypes = {
+ t: PropTypes.func,
+}
diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js
index e69de29bb..20d3daa59 100644
--- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js
+++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js
@@ -0,0 +1,26 @@
+import { connect } from 'react-redux'
+import {
+ getConversionRate,
+ getConvertedCurrency,
+ getGasTotal,
+} from '../../send.selectors.js'
+import { sendGasIsInError } from './send-gas-row.selectors.js'
+import { showModal } from '../../../../actions'
+import SendGasRow from './send-gas-row.component'
+
+export default connect(mapStateToProps, mapDispatchToProps)(SendGasRow)
+
+function mapStateToProps (state) {
+ return {
+ conversionRate: getConversionRate(state),
+ convertedCurrency: getConvertedCurrency(state),
+ gasTotal: getGasTotal(state),
+ gasLoadingError: sendGasIsInError(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ showCustomizeGasModal: () => dispatch(showModal({ name: 'CUSTOMIZE_GAS' })),
+ }
+}
diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js
index e69de29bb..ad4ef4877 100644
--- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js
+++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js
@@ -0,0 +1,9 @@
+const selectors = {
+ sendGasIsInError,
+}
+
+module.exports = selectors
+
+function sendGasIsInError (state) {
+ return state.metamask.send.errors.gasLoading
+}
diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
index 08f830cc5..0d314208b 100644
--- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
+++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
@@ -1,3 +1,6 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+
export default class SendRowErrorMessage extends Component {
static propTypes = {
@@ -7,17 +10,18 @@ export default class SendRowErrorMessage extends Component {
render () {
const { errors, errorType } = this.props
+
const errorMessage = errors[errorType]
return (
errorMessage
- ? <div className='send-v2__error'>{errorMessage}</div>
+ ? <div className="send-v2__error">{this.context.t(errorMessage)}</div>
: null
- );
+ )
}
}
SendRowErrorMessage.contextTypes = {
t: PropTypes.func,
-} \ No newline at end of file
+}
diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js
index 2278dbe63..59622047f 100644
--- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js
+++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js
@@ -1,3 +1,4 @@
+import { connect } from 'react-redux'
import { getSendErrors } from '../../../send.selectors'
import SendRowErrorMessage from './send-row-error-message.component'
@@ -8,4 +9,4 @@ function mapStateToProps (state, ownProps) {
errors: getSendErrors(state),
errorType: ownProps.errorType,
}
-} \ No newline at end of file
+}
diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js
index a1ac591b9..707b3ae80 100644
--- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js
+++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js
@@ -5,31 +5,35 @@ import SendRowErrorMessage from './send-row-error-message/send-row-error-message
export default class SendRowWrapper extends Component {
static propTypes = {
- label: PropTypes.string,
- showError: PropTypes.bool,
children: PropTypes.node,
errorType: PropTypes.string,
+ label: PropTypes.string,
+ showError: PropTypes.bool,
};
render () {
const {
- label,
- errorType = '',
- showError = false,
- children,
+ children,
+ errorType = '',
+ label,
+ showError = false,
} = this.props
+ const formField = Array.isArray(children) ? children[1] || children[0] : children
+ const customLabelContent = children.length > 1 ? children[0] : null
+
return (
<div className="send-v2__form-row">
<div className="send-v2__form-label">
{label}
- (showError && <SendRowErrorMessage errorType={errorType}/>)
+ {showError && <SendRowErrorMessage errorType={errorType}/>}
+ {customLabelContent}
</div>
<div className="send-v2__form-field">
- {children}
+ {formField}
</div>
</div>
- );
+ )
}
}
diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js
index abcb54efc..a6e4c1624 100644
--- a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js
+++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js
@@ -1,57 +1,59 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import SendRowWrapper from '../../../send/from-dropdown'
-import ToDropdown from '../../../ens-input'
+import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component'
+import EnsInput from '../../../ens-input'
export default class SendToRow extends Component {
static propTypes = {
+ closeToDropdown: PropTypes.func,
+ inError: PropTypes.bool,
+ network: PropTypes.string,
+ openToDropdown: PropTypes.func,
to: PropTypes.string,
toAccounts: PropTypes.array,
toDropdownOpen: PropTypes.bool,
- inError: PropTypes.bool,
updateSendTo: PropTypes.func,
updateSendToError: PropTypes.func,
- openToDropdown: PropTypes.func,
- closeToDropdown: PropTypes.func,
- network: PropTypes.number,
};
handleToChange (to, nickname = '') {
const { updateSendTo, updateSendToError } = this.props
updateSendTo(to, nickname)
- updateSendErrors(to)
+ updateSendToError(to)
}
render () {
const {
- from,
- fromAccounts,
- conversionRate,
- fromDropdownOpen,
- tokenContract,
- openToDropdown,
closeToDropdown,
- network,
inError,
+ network,
+ openToDropdown,
+ to,
+ toAccounts,
+ toDropdownOpen,
} = this.props
return (
- <SendRowWrapper label={`${this.context.t('to')}:`}>
+ <SendRowWrapper
+ errorType={'to'}
+ label={`${this.context.t('to')}:`}
+ showError={inError}
+ >
<EnsInput
- name={'address'}
- placeholder={this.context.t('recipient Address')}
- network={network},
- to={to},
accounts={toAccounts}
- dropdownOpen={toDropdownOpen}
- openDropdown={() => openToDropdown()}
closeDropdown={() => closeToDropdown()}
- onChange={this.handleToChange}
+ dropdownOpen={toDropdownOpen}
inError={inError}
+ name={'address'}
+ network={network}
+ onChange={(newTo, newNickname) => this.handleToChange(newTo, newNickname)}
+ openDropdown={() => openToDropdown()}
+ placeholder={this.context.t('recipientAddress')}
+ to={to}
/>
</SendRowWrapper>
- );
+ )
}
}
diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js
index 1c446c168..bffdda49c 100644
--- a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js
+++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js
@@ -1,7 +1,8 @@
+import { connect } from 'react-redux'
import {
- getSendTo,
- getToAccounts,
getCurrentNetwork,
+ getSendTo,
+ getSendToAccounts,
} from '../../send.selectors.js'
import {
getToDropdownOpen,
@@ -9,35 +10,34 @@ import {
} from './send-to-row.selectors.js'
import { getToErrorObject } from './send-to-row.utils.js'
import {
- updateSendErrors,
updateSendTo,
-} from '../../../actions'
+} from '../../../../actions'
import {
- openToDropdown,
- closeToDropdown,
-} from '../../../ducks/send'
+ updateSendErrors,
+ openToDropdown,
+ closeToDropdown,
+} from '../../../../ducks/send'
import SendToRow from './send-to-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendToRow)
function mapStateToProps (state) {
- updateSendTo
return {
+ inError: sendToIsInError(state),
+ network: getCurrentNetwork(state),
to: getSendTo(state),
toAccounts: getSendToAccounts(state),
toDropdownOpen: getToDropdownOpen(state),
- inError: sendToIsInError(state),
- network: getCurrentNetwork(state),
}
}
function mapDispatchToProps (dispatch) {
return {
+ closeToDropdown: () => dispatch(closeToDropdown()),
+ openToDropdown: () => dispatch(openToDropdown()),
+ updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
updateSendToError: (to) => {
dispatch(updateSendErrors(getToErrorObject(to)))
},
- updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
- openToDropdown: () => dispatch(()),
- closeToDropdown: () => dispatch(()),
}
-} \ No newline at end of file
+}
diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js
index 05bb65fa3..8919014be 100644
--- a/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js
+++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js
@@ -10,5 +10,5 @@ function getToDropdownOpen (state) {
}
function sendToIsInError (state) {
- return Boolean(state.metamask.send.to)
+ return Boolean(state.send.errors.to)
}
diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js
index 52bfde009..22e2e1f34 100644
--- a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js
+++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js
@@ -1,17 +1,21 @@
+const {
+ REQUIRED_ERROR,
+ INVALID_RECIPIENT_ADDRESS_ERROR,
+} = require('../../send.constants')
const { isValidAddress } = require('../../../../util')
function getToErrorObject (to) {
let toError = null
if (!to) {
- toError = 'required'
+ toError = REQUIRED_ERROR
} else if (!isValidAddress(to)) {
- toError = 'invalidAddressRecipient'
+ toError = INVALID_RECIPIENT_ADDRESS_ERROR
}
-
+
return { to: toError }
}
module.exports = {
- getToErrorObject
+ getToErrorObject,
}
diff --git a/ui/app/components/send_/send-footer/send-footer.component.js b/ui/app/components/send_/send-footer/send-footer.component.js
index e69de29bb..fc7a78a94 100644
--- a/ui/app/components/send_/send-footer/send-footer.component.js
+++ b/ui/app/components/send_/send-footer/send-footer.component.js
@@ -0,0 +1,93 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import PageContainerFooter from '../../page-container/page-container-footer'
+import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../routes'
+
+export default class SendFooter extends Component {
+
+ static propTypes = {
+ addToAddressBookIfNew: PropTypes.func,
+ amount: PropTypes.string,
+ clearSend: PropTypes.func,
+ disabled: PropTypes.bool,
+ editingTransactionId: PropTypes.string,
+ errors: PropTypes.object,
+ from: PropTypes.object,
+ gasLimit: PropTypes.string,
+ gasPrice: PropTypes.string,
+ gasTotal: PropTypes.string,
+ history: PropTypes.object,
+ selectedToken: PropTypes.object,
+ sign: PropTypes.func,
+ to: PropTypes.string,
+ toAccounts: PropTypes.array,
+ tokenBalance: PropTypes.string,
+ unapprovedTxs: PropTypes.object,
+ update: PropTypes.func,
+ };
+
+ onSubmit (event) {
+ event.preventDefault()
+ const {
+ addToAddressBookIfNew,
+ amount,
+ editingTransactionId,
+ from: {address: from},
+ gasLimit: gas,
+ gasPrice,
+ selectedToken,
+ sign,
+ to,
+ unapprovedTxs,
+ // updateTx,
+ update,
+ toAccounts,
+ } = this.props
+
+ // Should not be needed because submit should be disabled if there are no errors.
+ // const noErrors = !amountError && toError === null
+
+ // if (!noErrors) {
+ // return
+ // }
+
+ // TODO: add nickname functionality
+ addToAddressBookIfNew(to, toAccounts)
+
+ editingTransactionId
+ ? update({
+ amount,
+ editingTransactionId,
+ from,
+ gas,
+ gasPrice,
+ selectedToken,
+ to,
+ unapprovedTxs,
+ })
+ : sign({ selectedToken, to, amount, from, gas, gasPrice })
+
+ this.props.history.push(CONFIRM_TRANSACTION_ROUTE)
+ }
+
+
+ render () {
+ const { clearSend, disabled, history } = this.props
+
+ return (
+ <PageContainerFooter
+ onCancel={() => {
+ clearSend()
+ history.push(DEFAULT_ROUTE)
+ }}
+ onSubmit={e => this.onSubmit(e)}
+ disabled={disabled}
+ />
+ )
+ }
+
+}
+
+SendFooter.contextTypes = {
+ t: PropTypes.func,
+}
diff --git a/ui/app/components/send_/send-footer/send-footer.container.js b/ui/app/components/send_/send-footer/send-footer.container.js
index e69de29bb..022b2db08 100644
--- a/ui/app/components/send_/send-footer/send-footer.container.js
+++ b/ui/app/components/send_/send-footer/send-footer.container.js
@@ -0,0 +1,106 @@
+import { connect } from 'react-redux'
+import ethUtil from 'ethereumjs-util'
+import {
+ addToAddressBook,
+ clearSend,
+ signTokenTx,
+ signTx,
+ updateTransaction,
+} from '../../../actions'
+import SendFooter from './send-footer.component'
+import {
+ getGasLimit,
+ getGasPrice,
+ getGasTotal,
+ getSelectedToken,
+ getSendAmount,
+ getSendEditingTransactionId,
+ getSendFromObject,
+ getSendTo,
+ getSendToAccounts,
+ getTokenBalance,
+ getUnapprovedTxs,
+} from '../send.selectors'
+import {
+ isSendFormInError,
+} from './send-footer.selectors'
+import {
+ addressIsNew,
+ constructTxParams,
+ constructUpdatedTx,
+ formShouldBeDisabled,
+} from './send-footer.utils'
+
+export default connect(mapStateToProps, mapDispatchToProps)(SendFooter)
+
+function mapStateToProps (state) {
+ return {
+ amount: getSendAmount(state),
+ disabled: formShouldBeDisabled({
+ inError: isSendFormInError(state),
+ selectedToken: getSelectedToken(state),
+ tokenBalance: getTokenBalance(state),
+ gasTotal: getGasTotal(state),
+ }),
+ editingTransactionId: getSendEditingTransactionId(state),
+ from: getSendFromObject(state),
+ gasLimit: getGasLimit(state),
+ gasPrice: getGasPrice(state),
+ inError: isSendFormInError(state),
+ isToken: Boolean(getSelectedToken(state)),
+ selectedToken: getSelectedToken(state),
+ to: getSendTo(state),
+ toAccounts: getSendToAccounts(state),
+ unapprovedTxs: getUnapprovedTxs(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ clearSend: () => dispatch(clearSend()),
+ sign: ({ selectedToken, to, amount, from, gas, gasPrice }) => {
+ const txParams = constructTxParams({
+ amount,
+ from,
+ gas,
+ gasPrice,
+ selectedToken,
+ to,
+ })
+
+ selectedToken
+ ? dispatch(signTokenTx(selectedToken.address, to, amount, txParams))
+ : dispatch(signTx(txParams))
+ },
+ update: ({
+ amount,
+ editingTransactionId,
+ from,
+ gas,
+ gasPrice,
+ selectedToken,
+ to,
+ unapprovedTxs,
+ }) => {
+ const editingTx = constructUpdatedTx({
+ amount,
+ editingTransactionId,
+ from,
+ gas,
+ gasPrice,
+ selectedToken,
+ to,
+ unapprovedTxs,
+ })
+
+ dispatch(updateTransaction(editingTx))
+ },
+ addToAddressBookIfNew: (newAddress, toAccounts, nickname = '') => {
+ const hexPrefixedAddress = ethUtil.addHexPrefix(newAddress)
+ if (addressIsNew(toAccounts)) {
+ // TODO: nickname, i.e. addToAddressBook(recipient, nickname)
+ dispatch(addToAddressBook(hexPrefixedAddress, nickname))
+ }
+ },
+ }
+}
diff --git a/ui/app/components/send_/send-footer/send-footer.selectors.js b/ui/app/components/send_/send-footer/send-footer.selectors.js
index e69de29bb..e8fef6be6 100644
--- a/ui/app/components/send_/send-footer/send-footer.selectors.js
+++ b/ui/app/components/send_/send-footer/send-footer.selectors.js
@@ -0,0 +1,12 @@
+import { getSendErrors } from '../send.selectors'
+
+const selectors = {
+ isSendFormInError,
+}
+
+module.exports = selectors
+
+function isSendFormInError (state) {
+ const { amount, to } = getSendErrors(state)
+ return Boolean(amount || to !== null)
+}
diff --git a/ui/app/components/send_/send-footer/send-footer.utils.js b/ui/app/components/send_/send-footer/send-footer.utils.js
index e69de29bb..353c0e347 100644
--- a/ui/app/components/send_/send-footer/send-footer.utils.js
+++ b/ui/app/components/send_/send-footer/send-footer.utils.js
@@ -0,0 +1,84 @@
+import ethAbi from 'ethereumjs-abi'
+import ethUtil from 'ethereumjs-util'
+import { TOKEN_TRANSFER_FUNCTION_SIGNATURE } from '../send.constants'
+
+function formShouldBeDisabled ({ inError, selectedToken, tokenBalance, gasTotal }) {
+ const missingTokenBalance = selectedToken && !tokenBalance
+ return inError || !gasTotal || missingTokenBalance
+}
+
+function addHexPrefixToObjectValues (obj) {
+ return Object.keys(obj).reduce((newObj, key) => {
+ return { ...newObj, [key]: ethUtil.addHexPrefix(obj[key]) }
+ }, {})
+}
+
+function constructTxParams ({ selectedToken, to, amount, from, gas, gasPrice }) {
+ const txParams = {
+ from,
+ value: '0',
+ gas,
+ gasPrice,
+ }
+
+ if (!selectedToken) {
+ txParams.value = amount
+ txParams.to = to
+ }
+
+ const hexPrefixedTxParams = addHexPrefixToObjectValues(txParams)
+
+ return hexPrefixedTxParams
+}
+
+function constructUpdatedTx ({
+ amount,
+ editingTransactionId,
+ from,
+ gas,
+ gasPrice,
+ selectedToken,
+ to,
+ unapprovedTxs,
+}) {
+ const editingTx = {
+ ...unapprovedTxs[editingTransactionId],
+ txParams: addHexPrefixToObjectValues({ from, gas, 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, addHexPrefixToObjectValues({
+ value: '0',
+ to: selectedToken.address,
+ data,
+ }))
+ } else {
+ const { data } = unapprovedTxs[editingTransactionId].txParams
+
+ Object.assign(editingTx.txParams, addHexPrefixToObjectValues({
+ value: amount,
+ to,
+ data,
+ }))
+
+ if (typeof editingTx.txParams.data === 'undefined') {
+ delete editingTx.txParams.data
+ }
+ }
+}
+
+function addressIsNew (toAccounts, newAddress) {
+ return !toAccounts.find(({ address }) => newAddress === address)
+}
+
+module.exports = {
+ addressIsNew,
+ formShouldBeDisabled,
+ constructTxParams,
+ constructUpdatedTx,
+}
diff --git a/ui/app/components/send_/send-header/send-header.component.js b/ui/app/components/send_/send-header/send-header.component.js
index 99adfc7e8..dc4190b93 100644
--- a/ui/app/components/send_/send-header/send-header.component.js
+++ b/ui/app/components/send_/send-header/send-header.component.js
@@ -1,28 +1,29 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import PageContainerHeader from '../../page-container/page-container-header.component'
+import PageContainerHeader from '../../page-container/page-container-header'
+import { DEFAULT_ROUTE } from '../../../routes'
export default class SendHeader extends Component {
static propTypes = {
- isToken: PropTypes.bool,
clearSend: PropTypes.func,
- goHome: PropTypes.func,
+ history: PropTypes.object,
+ isToken: PropTypes.bool,
};
render () {
- const { isToken, clearSend, goHome } = this.props
+ const { isToken, clearSend, history } = this.props
return (
<PageContainerHeader
- title={isToken ? this.context.t('sendTokens') : this.context.t('sendETH')}
- subtitle={this.context.t('onlySendToEtherAddress')}
onClose={() => {
clearSend()
- goHome()
+ history.push(DEFAULT_ROUTE)
}}
+ subtitle={this.context.t('onlySendToEtherAddress')}
+ title={isToken ? this.context.t('sendTokens') : this.context.t('sendETH')}
/>
- );
+ )
}
}
diff --git a/ui/app/components/send_/send-header/send-header.container.js b/ui/app/components/send_/send-header/send-header.container.js
index a4d3ac54f..0c92da3a6 100644
--- a/ui/app/components/send_/send-header/send-header.container.js
+++ b/ui/app/components/send_/send-header/send-header.container.js
@@ -1,5 +1,5 @@
import { connect } from 'react-redux'
-import { goHome, clearSend } from '../../../actions'
+import { clearSend } from '../../../actions'
import SendHeader from './send-header.component'
import { getSelectedToken } from '../../../selectors'
@@ -7,13 +7,12 @@ export default connect(mapStateToProps, mapDispatchToProps)(SendHeader)
function mapStateToProps (state) {
return {
- isToken: Boolean(getSelectedToken(state))
+ isToken: Boolean(getSelectedToken(state)),
}
}
function mapDispatchToProps (dispatch) {
return {
- goHome: () => dispatch(goHome()),
clearSend: () => dispatch(clearSend()),
}
}
diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js
index e69de29bb..e14a97537 100644
--- a/ui/app/components/send_/send.component.js
+++ b/ui/app/components/send_/send.component.js
@@ -0,0 +1,142 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import PersistentForm from '../../../lib/persistent-form'
+import {
+ getAmountErrorObject,
+ doesAmountErrorRequireUpdate,
+} from './send.utils'
+
+import SendHeader from './send-header/send-header.container'
+import SendContent from './send-content/send-content.component'
+import SendFooter from './send-footer/send-footer.container'
+
+export default class SendTransactionScreen extends PersistentForm {
+
+ static propTypes = {
+ amount: PropTypes.string,
+ amountConversionRate: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
+ conversionRate: PropTypes.number,
+ data: PropTypes.string,
+ editingTransactionId: PropTypes.string,
+ from: PropTypes.object,
+ gasLimit: PropTypes.string,
+ gasPrice: PropTypes.string,
+ gasTotal: PropTypes.string,
+ history: PropTypes.object,
+ network: PropTypes.string,
+ primaryCurrency: PropTypes.string,
+ selectedAddress: PropTypes.string,
+ selectedToken: PropTypes.object,
+ tokenBalance: PropTypes.string,
+ tokenContract: PropTypes.object,
+ updateAndSetGasTotal: PropTypes.func,
+ updateSendErrors: PropTypes.func,
+ updateSendTokenBalance: PropTypes.func,
+ };
+
+ updateGas () {
+ const {
+ data,
+ editingTransactionId,
+ gasLimit,
+ gasPrice,
+ selectedAddress,
+ selectedToken = {},
+ updateAndSetGasTotal,
+ } = this.props
+
+ updateAndSetGasTotal({
+ data,
+ editingTransactionId,
+ gasLimit,
+ gasPrice,
+ selectedAddress,
+ selectedToken,
+ })
+ }
+
+ componentDidUpdate (prevProps) {
+ const {
+ amount,
+ amountConversionRate,
+ conversionRate,
+ from: { address, balance },
+ gasTotal,
+ network,
+ primaryCurrency,
+ selectedToken,
+ tokenBalance,
+ updateSendErrors,
+ updateSendTokenBalance,
+ tokenContract,
+ } = this.props
+
+ const {
+ from: { balance: prevBalance },
+ gasTotal: prevGasTotal,
+ tokenBalance: prevTokenBalance,
+ network: prevNetwork,
+ } = prevProps
+
+ const uninitialized = [prevBalance, prevGasTotal].every(n => n === null)
+
+ if (!uninitialized) {
+ const amountErrorRequiresUpdate = doesAmountErrorRequireUpdate({
+ balance,
+ gasTotal,
+ prevBalance,
+ prevGasTotal,
+ prevTokenBalance,
+ selectedToken,
+ tokenBalance,
+ })
+
+ if (amountErrorRequiresUpdate) {
+ const amountErrorObject = getAmountErrorObject({
+ amount,
+ amountConversionRate,
+ balance,
+ conversionRate,
+ gasTotal,
+ primaryCurrency,
+ selectedToken,
+ tokenBalance,
+ })
+ updateSendErrors(amountErrorObject)
+ }
+
+ if (network !== prevNetwork && network !== 'loading') {
+ updateSendTokenBalance({
+ selectedToken,
+ tokenContract,
+ address,
+ })
+ this.updateGas()
+ }
+ }
+ }
+
+ componentWillMount () {
+ this.updateGas()
+ }
+
+ render () {
+ const { history } = this.props
+
+ return (
+ <div className="page-container">
+ <SendHeader history={history}/>
+ <SendContent/>
+ <SendFooter history={history}/>
+ </div>
+ )
+ }
+
+}
+
+SendTransactionScreen.contextTypes = {
+ t: PropTypes.func,
+}
diff --git a/ui/app/components/send_/send.constants.js b/ui/app/components/send_/send.constants.js
new file mode 100644
index 000000000..d047ed704
--- /dev/null
+++ b/ui/app/components/send_/send.constants.js
@@ -0,0 +1,44 @@
+const ethUtil = require('ethereumjs-util')
+const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
+
+const MIN_GAS_PRICE_HEX = (100000000).toString(16)
+const MIN_GAS_PRICE_DEC = '100000000'
+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',
+ toDenomination: 'GWEI',
+ fromNumericBase: 'hex',
+ toNumericBase: 'hex',
+ numberOfDecimals: 1,
+}))
+
+const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, {
+ toNumericBase: 'hex',
+ multiplicandBase: 16,
+ multiplierBase: 16,
+})
+
+const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb'
+
+const INSUFFICIENT_FUNDS_ERROR = 'insufficientFunds'
+const INSUFFICIENT_TOKENS_ERROR = 'insufficientTokens'
+const NEGATIVE_ETH_ERROR = 'negativeETH'
+const INVALID_RECIPIENT_ADDRESS_ERROR = 'invalidAddressRecipient'
+const REQUIRED_ERROR = 'required'
+
+module.exports = {
+ INSUFFICIENT_FUNDS_ERROR,
+ INSUFFICIENT_TOKENS_ERROR,
+ INVALID_RECIPIENT_ADDRESS_ERROR,
+ MIN_GAS_LIMIT_DEC,
+ MIN_GAS_LIMIT_HEX,
+ MIN_GAS_PRICE_DEC,
+ MIN_GAS_PRICE_GWEI,
+ MIN_GAS_PRICE_HEX,
+ MIN_GAS_TOTAL,
+ NEGATIVE_ETH_ERROR,
+ REQUIRED_ERROR,
+ TOKEN_TRANSFER_FUNCTION_SIGNATURE,
+}
diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js
index e69de29bb..df605469a 100644
--- a/ui/app/components/send_/send.container.js
+++ b/ui/app/components/send_/send.container.js
@@ -0,0 +1,88 @@
+import { connect } from 'react-redux'
+import abi from 'ethereumjs-abi'
+import SendEther from './send.component'
+import { withRouter } from 'react-router-dom'
+import { compose } from 'recompose'
+import {
+ getAmountConversionRate,
+ getConversionRate,
+ getCurrentNetwork,
+ getGasLimit,
+ getGasPrice,
+ getGasTotal,
+ getPrimaryCurrency,
+ getSelectedAddress,
+ getSelectedToken,
+ getSelectedTokenContract,
+ getSelectedTokenToFiatRate,
+ getSendAmount,
+ getSendEditingTransactionId,
+ getSendFromObject,
+ getTokenBalance,
+} from './send.selectors'
+import {
+ updateSendTokenBalance,
+ updateGasTotal,
+ setGasTotal,
+} from '../../actions'
+import {
+ updateSendErrors,
+} from '../../ducks/send'
+import {
+ calcGasTotal,
+ generateTokenTransferData,
+} from './send.utils.js'
+
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(SendEther)
+
+function mapStateToProps (state) {
+ const selectedAddress = getSelectedAddress(state)
+ const selectedToken = getSelectedToken(state)
+
+ return {
+ amount: getSendAmount(state),
+ amountConversionRate: getAmountConversionRate(state),
+ conversionRate: getConversionRate(state),
+ data: generateTokenTransferData(abi, selectedAddress, selectedToken),
+ editingTransactionId: getSendEditingTransactionId(state),
+ from: getSendFromObject(state),
+ gasLimit: getGasLimit(state),
+ gasPrice: getGasPrice(state),
+ gasTotal: getGasTotal(state),
+ network: getCurrentNetwork(state),
+ primaryCurrency: getPrimaryCurrency(state),
+ selectedAddress: getSelectedAddress(state),
+ selectedToken: getSelectedToken(state),
+ tokenBalance: getTokenBalance(state),
+ tokenContract: getSelectedTokenContract(state),
+ tokenToFiatRate: getSelectedTokenToFiatRate(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ updateAndSetGasTotal: ({
+ data,
+ editingTransactionId,
+ gasLimit,
+ gasPrice,
+ selectedAddress,
+ selectedToken,
+ }) => {
+ !editingTransactionId
+ ? dispatch(updateGasTotal({ selectedAddress, selectedToken, data }))
+ : dispatch(setGasTotal(calcGasTotal(gasLimit, gasPrice)))
+ },
+ updateSendTokenBalance: ({ selectedToken, tokenContract, address }) => {
+ dispatch(updateSendTokenBalance({
+ selectedToken,
+ tokenContract,
+ address,
+ }))
+ },
+ updateSendErrors: newError => dispatch(updateSendErrors(newError)),
+ }
+}
diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js
index 8c088098e..761b15182 100644
--- a/ui/app/components/send_/send.selectors.js
+++ b/ui/app/components/send_/send.selectors.js
@@ -2,105 +2,95 @@ import { valuesFor } from '../../util'
import abi from 'human-standard-token-abi'
import {
multiplyCurrencies,
-} from './conversion-util'
+} from '../../conversion-util'
const selectors = {
+ accountsWithSendEtherInfoSelector,
+ autoAddToBetaUI,
+ getAddressBook,
+ getAmountConversionRate,
+ getConversionRate,
+ getConvertedCurrency,
+ getCurrentAccountWithSendEtherInfo,
+ getCurrentCurrency,
+ getCurrentNetwork,
+ getCurrentViewContext,
+ getForceGasMin,
+ getGasLimit,
+ getGasPrice,
+ getGasTotal,
+ getPrimaryCurrency,
+ getSelectedAccount,
getSelectedAddress,
getSelectedIdentity,
- getSelectedAccount,
getSelectedToken,
+ getSelectedTokenContract,
getSelectedTokenExchangeRate,
- getTokenExchangeRate,
- conversionRateSelector,
- transactionsSelector,
- accountsWithSendEtherInfoSelector,
- getCurrentAccountWithSendEtherInfo,
- getGasPrice,
- getGasLimit,
- getForceGasMin,
- getAddressBook,
- getSendFrom,
- getCurrentCurrency,
- getSendAmount,
getSelectedTokenToFiatRate,
- getSelectedTokenContract,
- autoAddToBetaUI,
- getSendMaxModeState,
- getCurrentViewContext,
+ getSendAmount,
+ getSendEditingTransactionId,
getSendErrors,
+ getSendFrom,
+ getSendFromBalance,
+ getSendFromObject,
+ getSendMaxModeState,
getSendTo,
- getCurrentNetwork,
+ getSendToAccounts,
+ getTokenBalance,
+ getTokenExchangeRate,
+ getUnapprovedTxs,
+ isSendFormInError,
+ transactionsSelector,
}
module.exports = selectors
-function getSelectedAddress (state) {
- const selectedAddress = state.metamask.selectedAddress || Object.keys(state.metamask.accounts)[0]
-
- return selectedAddress
-}
+function accountsWithSendEtherInfoSelector (state) {
+ const {
+ accounts,
+ identities,
+ } = state.metamask
-function getSelectedIdentity (state) {
- const selectedAddress = getSelectedAddress(state)
- const identities = state.metamask.identities
+ const accountsWithSendEtherInfo = Object.entries(accounts).map(([key, account]) => {
+ return Object.assign({}, account, identities[key])
+ })
- return identities[selectedAddress]
+ return accountsWithSendEtherInfo
}
-function getSelectedAccount (state) {
- const accounts = state.metamask.accounts
- const selectedAddress = getSelectedAddress(state)
+function autoAddToBetaUI (state) {
+ const autoAddTransactionThreshold = 12
+ const autoAddAccountsThreshold = 2
+ const autoAddTokensThreshold = 1
- return accounts[selectedAddress]
-}
+ const numberOfTransactions = state.metamask.selectedAddressTxList.length
+ const numberOfAccounts = Object.keys(state.metamask.accounts).length
+ const numberOfTokensAdded = state.metamask.tokens.length
-function getSelectedToken (state) {
- const tokens = state.metamask.tokens || []
- const selectedTokenAddress = state.metamask.selectedTokenAddress
- const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0]
- const sendToken = state.metamask.send.token
+ const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) &&
+ (numberOfAccounts > autoAddAccountsThreshold) &&
+ (numberOfTokensAdded > autoAddTokensThreshold)
+ const userIsNotInBeta = !state.metamask.featureFlags.betaUI
- return selectedToken || sendToken || null
+ return userIsNotInBeta && userPassesThreshold
}
-function getSelectedTokenExchangeRate (state) {
- const tokenExchangeRates = state.metamask.tokenExchangeRates
- const selectedToken = getSelectedToken(state) || {}
- const { symbol = '' } = selectedToken
-
- const pair = `${symbol.toLowerCase()}_eth`
- const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {}
-
- return tokenExchangeRate
+function getAddressBook (state) {
+ return state.metamask.addressBook
}
-function getTokenExchangeRate (state, tokenSymbol) {
- const pair = `${tokenSymbol.toLowerCase()}_eth`
- const tokenExchangeRates = state.metamask.tokenExchangeRates
- const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {}
-
- return tokenExchangeRate
+function getAmountConversionRate (state) {
+ return getSelectedToken(state)
+ ? getSelectedTokenToFiatRate(state)
+ : getConversionRate(state)
}
-function conversionRateSelector (state) {
+function getConversionRate (state) {
return state.metamask.conversionRate
}
-function getAddressBook (state) {
- return state.metamask.addressBook
-}
-
-function accountsWithSendEtherInfoSelector (state) {
- const {
- accounts,
- identities,
- } = state.metamask
-
- const accountsWithSendEtherInfo = Object.entries(accounts).map(([key, account]) => {
- return Object.assign({}, account, identities[key])
- })
-
- return accountsWithSendEtherInfo
+function getConvertedCurrency (state) {
+ return state.metamask.currentCurrency
}
function getCurrentAccountWithSendEtherInfo (state) {
@@ -110,20 +100,21 @@ function getCurrentAccountWithSendEtherInfo (state) {
return accounts.find(({ address }) => address === currentAddress)
}
-function transactionsSelector (state) {
- const { network, selectedTokenAddress } = state.metamask
- const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs)
- const shapeShiftTxList = (network === '1') ? state.metamask.shapeShiftTxList : undefined
- const transactions = state.metamask.selectedAddressTxList || []
- const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList)
+function getCurrentCurrency (state) {
+ return state.metamask.currentCurrency
+}
- // console.log({txsToRender, selectedTokenAddress})
- return selectedTokenAddress
- ? txsToRender
- .filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress)
- .sort((a, b) => b.time - a.time)
- : txsToRender
- .sort((a, b) => b.time - a.time)
+function getCurrentNetwork (state) {
+ return state.metamask.network
+}
+
+function getCurrentViewContext (state) {
+ const { currentView = {} } = state.appState
+ return currentView.context
+}
+
+function getForceGasMin (state) {
+ return state.metamask.send.forceGasMin
}
function getGasPrice (state) {
@@ -134,29 +125,64 @@ function getGasLimit (state) {
return state.metamask.send.gasLimit
}
-function getForceGasMin (state) {
- return state.metamask.send.forceGasMin
+function getGasTotal (state) {
+ return state.metamask.send.gasTotal
}
-function getSendFrom (state) {
- return state.metamask.send.from
+function getPrimaryCurrency (state) {
+ const selectedToken = getSelectedToken(state)
+ return selectedToken && selectedToken.symbol
}
-function getSendAmount (state) {
- return state.metamask.send.amount
+function getSelectedAccount (state) {
+ const accounts = state.metamask.accounts
+ const selectedAddress = getSelectedAddress(state)
+
+ return accounts[selectedAddress]
}
-function getSendMaxModeState (state) {
- return state.metamask.send.maxModeOn
+function getSelectedAddress (state) {
+ const selectedAddress = state.metamask.selectedAddress || Object.keys(state.metamask.accounts)[0]
+
+ return selectedAddress
}
-function getCurrentCurrency (state) {
- return state.metamask.currentCurrency
+function getSelectedIdentity (state) {
+ const selectedAddress = getSelectedAddress(state)
+ const identities = state.metamask.identities
+
+ return identities[selectedAddress]
+}
+
+function getSelectedToken (state) {
+ const tokens = state.metamask.tokens || []
+ const selectedTokenAddress = state.metamask.selectedTokenAddress
+ const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0]
+ const sendToken = state.metamask.send.token
+
+ return selectedToken || sendToken || null
+}
+
+function getSelectedTokenContract (state) {
+ const selectedToken = getSelectedToken(state)
+ return selectedToken
+ ? global.eth.contract(abi).at(selectedToken.address)
+ : null
+}
+
+function getSelectedTokenExchangeRate (state) {
+ const tokenExchangeRates = state.metamask.tokenExchangeRates
+ const selectedToken = getSelectedToken(state) || {}
+ const { symbol = '' } = selectedToken
+ const pair = `${symbol.toLowerCase()}_eth`
+ const { rate: tokenExchangeRate = 0 } = tokenExchangeRates && tokenExchangeRates[pair] || {}
+
+ return tokenExchangeRate
}
function getSelectedTokenToFiatRate (state) {
const selectedTokenExchangeRate = getSelectedTokenExchangeRate(state)
- const conversionRate = conversionRateSelector(state)
+ const conversionRate = getConversionRate(state)
const tokenToFiatRate = multiplyCurrencies(
conversionRate,
@@ -167,37 +193,33 @@ function getSelectedTokenToFiatRate (state) {
return tokenToFiatRate
}
-function getSelectedTokenContract (state) {
- const selectedToken = getSelectedToken(state)
- return selectedToken
- ? global.eth.contract(abi).at(selectedToken.address)
- : null
+function getSendAmount (state) {
+ return state.metamask.send.amount
}
-function autoAddToBetaUI (state) {
- const autoAddTransactionThreshold = 12
- const autoAddAccountsThreshold = 2
- const autoAddTokensThreshold = 1
+function getSendEditingTransactionId (state) {
+ return state.metamask.send.editingTransactionId
+}
- const numberOfTransactions = state.metamask.selectedAddressTxList.length
- const numberOfAccounts = Object.keys(state.metamask.accounts).length
- const numberOfTokensAdded = state.metamask.tokens.length
+function getSendErrors (state) {
+ return state.send.errors
+}
- const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) &&
- (numberOfAccounts > autoAddAccountsThreshold) &&
- (numberOfTokensAdded > autoAddTokensThreshold)
- const userIsNotInBeta = !state.metamask.featureFlags.betaUI
+function getSendFrom (state) {
+ return state.metamask.send.from
+}
- return userIsNotInBeta && userPassesThreshold
+function getSendFromBalance (state) {
+ const from = getSendFrom(state) || getSelectedAccount(state)
+ return from.balance
}
-function getCurrentViewContext (state) {
- const { currentView = {} } = state.appState
- return currentView.context
+function getSendFromObject (state) {
+ return getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state)
}
-function getSendErrors (state) {
- return state.metamask.send.errors
+function getSendMaxModeState (state) {
+ return state.metamask.send.maxModeOn
}
function getSendTo (state) {
@@ -212,6 +234,38 @@ function getSendToAccounts (state) {
return Object.entries(allAccounts).map(([key, account]) => account)
}
-function getCurrentNetwork (state) {
- return state.metamask.network
-} \ No newline at end of file
+function getTokenBalance (state) {
+ return state.metamask.send.tokenBalance
+}
+
+function getTokenExchangeRate (state, tokenSymbol) {
+ const pair = `${tokenSymbol.toLowerCase()}_eth`
+ const tokenExchangeRates = state.metamask.tokenExchangeRates
+ const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {}
+
+ return tokenExchangeRate
+}
+
+function getUnapprovedTxs (state) {
+ return state.metamask.unapprovedTxs
+}
+
+function isSendFormInError (state) {
+ const { amount, to } = getSendErrors(state)
+ return Boolean(amount || to !== null)
+}
+
+function transactionsSelector (state) {
+ const { network, selectedTokenAddress } = state.metamask
+ const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs)
+ const shapeShiftTxList = (network === '1') ? state.metamask.shapeShiftTxList : undefined
+ const transactions = state.metamask.selectedAddressTxList || []
+ const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList)
+
+ return selectedTokenAddress
+ ? txsToRender
+ .filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress)
+ .sort((a, b) => b.time - a.time)
+ : txsToRender
+ .sort((a, b) => b.time - a.time)
+}
diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js
index e69de29bb..e537d5624 100644
--- a/ui/app/components/send_/send.utils.js
+++ b/ui/app/components/send_/send.utils.js
@@ -0,0 +1,188 @@
+const {
+ addCurrencies,
+ conversionUtil,
+ conversionGTE,
+ multiplyCurrencies,
+} = require('../../conversion-util')
+const {
+ calcTokenAmount,
+} = require('../../token-util')
+const {
+ conversionGreaterThan,
+} = require('../../conversion-util')
+const {
+ INSUFFICIENT_FUNDS_ERROR,
+ INSUFFICIENT_TOKENS_ERROR,
+ NEGATIVE_ETH_ERROR,
+} = require('./send.constants')
+
+module.exports = {
+ calcGasTotal,
+ doesAmountErrorRequireUpdate,
+ generateTokenTransferData,
+ getAmountErrorObject,
+ getParamsForGasEstimate,
+ calcTokenBalance,
+ isBalanceSufficient,
+ isTokenBalanceSufficient,
+}
+
+function calcGasTotal (gasLimit, gasPrice) {
+ return multiplyCurrencies(gasLimit, gasPrice, {
+ toNumericBase: 'hex',
+ multiplicandBase: 16,
+ multiplierBase: 16,
+ })
+}
+
+function isBalanceSufficient ({
+ amount = '0x0',
+ amountConversionRate,
+ balance,
+ conversionRate,
+ gasTotal = '0x0',
+ primaryCurrency,
+}) {
+ const totalAmount = addCurrencies(amount, gasTotal, {
+ aBase: 16,
+ bBase: 16,
+ toNumericBase: 'hex',
+ })
+
+ const balanceIsSufficient = conversionGTE(
+ {
+ value: balance,
+ fromNumericBase: 'hex',
+ fromCurrency: primaryCurrency,
+ conversionRate,
+ },
+ {
+ value: totalAmount,
+ fromNumericBase: 'hex',
+ conversionRate: amountConversionRate || conversionRate,
+ fromCurrency: primaryCurrency,
+ },
+ )
+
+ return balanceIsSufficient
+}
+
+function isTokenBalanceSufficient ({
+ amount = '0x0',
+ tokenBalance,
+ decimals,
+}) {
+ const amountInDec = conversionUtil(amount, {
+ fromNumericBase: 'hex',
+ })
+
+ const tokenBalanceIsSufficient = conversionGTE(
+ {
+ value: tokenBalance,
+ fromNumericBase: 'dec',
+ },
+ {
+ value: calcTokenAmount(amountInDec, decimals),
+ fromNumericBase: 'dec',
+ },
+ )
+
+ return tokenBalanceIsSufficient
+}
+
+function getAmountErrorObject ({
+ amount,
+ amountConversionRate,
+ balance,
+ conversionRate,
+ gasTotal,
+ primaryCurrency,
+ selectedToken,
+ tokenBalance,
+}) {
+ let insufficientFunds = false
+ if (gasTotal && conversionRate) {
+ insufficientFunds = !isBalanceSufficient({
+ amount: selectedToken ? '0x0' : amount,
+ amountConversionRate,
+ balance,
+ conversionRate,
+ gasTotal,
+ primaryCurrency,
+ })
+ }
+
+ let inSufficientTokens = false
+ if (selectedToken && tokenBalance !== null) {
+ const { decimals } = selectedToken
+ inSufficientTokens = !isTokenBalanceSufficient({
+ tokenBalance,
+ amount,
+ decimals,
+ })
+ }
+
+ const amountLessThanZero = conversionGreaterThan(
+ { value: 0, fromNumericBase: 'dec' },
+ { value: amount, fromNumericBase: 'hex' },
+ )
+
+ let amountError = null
+
+ if (insufficientFunds) {
+ amountError = INSUFFICIENT_FUNDS_ERROR
+ } else if (inSufficientTokens) {
+ amountError = INSUFFICIENT_TOKENS_ERROR
+ } else if (amountLessThanZero) {
+ amountError = NEGATIVE_ETH_ERROR
+ }
+
+ return { amount: amountError }
+}
+
+function getParamsForGasEstimate (selectedAddress, symbol, data) {
+ const estimatedGasParams = {
+ from: selectedAddress,
+ gas: '746a528800',
+ }
+
+ if (symbol) {
+ Object.assign(estimatedGasParams, { value: '0x0' })
+ }
+
+ if (data) {
+ Object.assign(estimatedGasParams, { data })
+ }
+
+ return estimatedGasParams
+}
+
+function calcTokenBalance ({ selectedToken, usersToken }) {
+ const { decimals } = selectedToken || {}
+ return calcTokenAmount(usersToken.balance.toString(), decimals)
+}
+
+function doesAmountErrorRequireUpdate ({
+ balance,
+ gasTotal,
+ prevBalance,
+ prevGasTotal,
+ prevTokenBalance,
+ selectedToken,
+ tokenBalance,
+}) {
+ const balanceHasChanged = balance !== prevBalance
+ const gasTotalHasChange = gasTotal !== prevGasTotal
+ const tokenBalanceHasChanged = selectedToken && tokenBalance !== prevTokenBalance
+ const amountErrorRequiresUpdate = balanceHasChanged || gasTotalHasChange || tokenBalanceHasChanged
+
+ return amountErrorRequiresUpdate
+}
+
+function generateTokenTransferData (abi, selectedAddress, selectedToken) {
+ if (!selectedToken) return
+ return Array.prototype.map.call(
+ abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']),
+ x => ('00' + x.toString(16)).slice(-2)
+ ).join('')
+}
diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js
index 41415411e..b958a2d2d 100644
--- a/ui/app/components/signature-request.js
+++ b/ui/app/components/signature-request.js
@@ -6,6 +6,8 @@ const Identicon = require('./identicon')
const connect = require('react-redux').connect
const ethUtil = require('ethereumjs-util')
const classnames = require('classnames')
+const { compose } = require('recompose')
+const { withRouter } = require('react-router-dom')
const AccountDropdownMini = require('./dropdowns/account-dropdown-mini')
@@ -20,6 +22,8 @@ const {
conversionRateSelector,
} = require('../selectors.js')
+const { DEFAULT_ROUTE } = require('../routes')
+
function mapStateToProps (state) {
return {
balance: getSelectedAccount(state).balance,
@@ -42,7 +46,10 @@ SignatureRequest.contextTypes = {
t: PropTypes.func,
}
-module.exports = connect(mapStateToProps, mapDispatchToProps)(SignatureRequest)
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(SignatureRequest)
inherits(SignatureRequest, Component)
@@ -229,10 +236,14 @@ SignatureRequest.prototype.renderFooter = function () {
return h('div.request-signature__footer', [
h('button.btn-secondary--lg.request-signature__footer__cancel-button', {
- onClick: cancel,
+ onClick: event => {
+ cancel(event).then(() => this.props.history.push(DEFAULT_ROUTE))
+ },
}, this.context.t('cancel')),
h('button.btn-primary--lg', {
- onClick: sign,
+ onClick: event => {
+ sign(event).then(() => this.props.history.push(DEFAULT_ROUTE))
+ },
}, this.context.t('sign')),
])
}
diff --git a/ui/app/components/tab-bar.js b/ui/app/components/tab-bar.js
index a80640116..0016a09c1 100644
--- a/ui/app/components/tab-bar.js
+++ b/ui/app/components/tab-bar.js
@@ -4,31 +4,17 @@ const PropTypes = require('prop-types')
const classnames = require('classnames')
class TabBar extends Component {
- constructor (props) {
- super(props)
- const { defaultTab, tabs } = props
-
- this.state = {
- subview: defaultTab || tabs[0].key,
- }
- }
-
render () {
- const { tabs = [], tabSelected } = this.props
- const { subview } = this.state
+ const { tabs = [], onSelect, isActive } = this.props
return (
h('.tab-bar', {}, [
- tabs.map((tab) => {
- const { key, content } = tab
+ tabs.map(({ key, content }) => {
return h('div', {
className: classnames('tab-bar__tab pointer', {
- 'tab-bar__tab--active': subview === key,
+ 'tab-bar__tab--active': isActive(key, content),
}),
- onClick: () => {
- this.setState({ subview: key })
- tabSelected(key)
- },
+ onClick: () => onSelect(key),
key,
}, content)
}),
@@ -39,9 +25,9 @@ class TabBar extends Component {
}
TabBar.propTypes = {
- defaultTab: PropTypes.string,
+ isActive: PropTypes.func.isRequired,
tabs: PropTypes.array,
- tabSelected: PropTypes.func,
+ onSelect: PropTypes.func,
}
module.exports = TabBar
diff --git a/ui/app/components/token-balance.js b/ui/app/components/token-balance.js
index 2f71c0687..1900ccec7 100644
--- a/ui/app/components/token-balance.js
+++ b/ui/app/components/token-balance.js
@@ -4,6 +4,7 @@ const inherits = require('util').inherits
const TokenTracker = require('eth-token-tracker')
const connect = require('react-redux').connect
const selectors = require('../selectors')
+const log = require('loglevel')
function mapStateToProps (state) {
return {
diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js
index 0332fde88..c84117d84 100644
--- a/ui/app/components/token-cell.js
+++ b/ui/app/components/token-cell.js
@@ -16,7 +16,7 @@ function mapStateToProps (state) {
currentCurrency: state.metamask.currentCurrency,
selectedTokenAddress: state.metamask.selectedTokenAddress,
userAddress: selectors.getSelectedAddress(state),
- tokenExchangeRates: state.metamask.tokenExchangeRates,
+ contractExchangeRates: state.metamask.contractExchangeRates,
conversionRate: state.metamask.conversionRate,
sidebarOpen: state.appState.sidebarOpen,
}
@@ -25,7 +25,6 @@ function mapStateToProps (state) {
function mapDispatchToProps (dispatch) {
return {
setSelectedToken: address => dispatch(actions.setSelectedToken(address)),
- updateTokenExchangeRate: token => dispatch(actions.updateTokenExchangeRate(token)),
hideSidebar: () => dispatch(actions.hideSidebar()),
}
}
@@ -41,15 +40,6 @@ function TokenCell () {
}
}
-TokenCell.prototype.componentWillMount = function () {
- const {
- updateTokenExchangeRate,
- symbol,
- } = this.props
-
- updateTokenExchangeRate(symbol)
-}
-
TokenCell.prototype.render = function () {
const { tokenMenuOpen } = this.state
const props = this.props
@@ -60,7 +50,7 @@ TokenCell.prototype.render = function () {
network,
setSelectedToken,
selectedTokenAddress,
- tokenExchangeRates,
+ contractExchangeRates,
conversionRate,
hideSidebar,
sidebarOpen,
@@ -68,15 +58,13 @@ TokenCell.prototype.render = function () {
// userAddress,
} = props
- const pair = `${symbol.toLowerCase()}_eth`
-
let currentTokenToFiatRate
let currentTokenInFiat
let formattedFiat = ''
- if (tokenExchangeRates[pair]) {
+ if (contractExchangeRates[address]) {
currentTokenToFiatRate = multiplyCurrencies(
- tokenExchangeRates[pair].rate,
+ contractExchangeRates[address],
conversionRate
)
currentTokenInFiat = conversionUtil(string, {
diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js
index 150a3762d..4189cf801 100644
--- a/ui/app/components/token-list.js
+++ b/ui/app/components/token-list.js
@@ -6,6 +6,7 @@ const TokenTracker = require('eth-token-tracker')
const TokenCell = require('./token-cell.js')
const connect = require('react-redux').connect
const selectors = require('../selectors')
+const log = require('loglevel')
function mapStateToProps (state) {
return {
diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js
index 42c008798..bd4ea80a6 100644
--- a/ui/app/components/tx-list-item.js
+++ b/ui/app/components/tx-list-item.js
@@ -9,6 +9,7 @@ const abiDecoder = require('abi-decoder')
abiDecoder.addABI(abi)
const Identicon = require('./identicon')
const contractMap = require('eth-contract-metadata')
+const { checksumAddress } = require('../util')
const actions = require('../actions')
const { conversionUtil, multiplyCurrencies } = require('../conversion-util')
@@ -27,7 +28,7 @@ function mapStateToProps (state) {
return {
tokens: state.metamask.tokens,
currentCurrency: getCurrentCurrency(state),
- tokenExchangeRates: state.metamask.tokenExchangeRates,
+ contractExchangeRates: state.metamask.contractExchangeRates,
selectedAddressTxList: state.metamask.selectedAddressTxList,
}
}
@@ -74,10 +75,12 @@ TxListItem.prototype.getAddressText = function () {
const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
const { name: txDataName, params = [] } = decodedData || {}
const { value } = params[0] || {}
+ const checksummedAddress = checksumAddress(address)
+ const checksummedValue = checksumAddress(value)
let addressText
if (txDataName === 'transfer' || address) {
- const addressToRender = txDataName === 'transfer' ? value : address
+ const addressToRender = txDataName === 'transfer' ? checksummedValue : checksummedAddress
addressText = `${addressToRender.slice(0, 10)}...${addressToRender.slice(-4)}`
} else if (isMsg) {
addressText = this.context.t('sigRequest')
@@ -142,31 +145,29 @@ TxListItem.prototype.getTokenInfo = async function () {
({ decimals, symbol } = await tokenInfoGetter(toAddress))
}
- return { decimals, symbol }
+ return { decimals, symbol, address: toAddress }
}
TxListItem.prototype.getSendTokenTotal = async function () {
const {
txParams = {},
conversionRate,
- tokenExchangeRates,
+ contractExchangeRates,
currentCurrency,
} = this.props
const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
const { params = [] } = decodedData || {}
const { value } = params[1] || {}
- const { decimals, symbol } = await this.getTokenInfo()
+ const { decimals, symbol, address } = await this.getTokenInfo()
const total = calcTokenAmount(value, decimals)
- const pair = symbol && `${symbol.toLowerCase()}_eth`
-
let tokenToFiatRate
let totalInFiat
- if (tokenExchangeRates[pair]) {
+ if (contractExchangeRates[address]) {
tokenToFiatRate = multiplyCurrencies(
- tokenExchangeRates[pair].rate,
+ contractExchangeRates[address],
conversionRate
)
@@ -220,7 +221,6 @@ TxListItem.prototype.resubmit = function () {
TxListItem.prototype.render = function () {
const {
transactionStatus,
- transactionAmount,
onClick,
transactionId,
dateString,
@@ -229,7 +229,6 @@ TxListItem.prototype.render = function () {
txParams,
} = this.props
const { total, fiatTotal, isTokenTx } = this.state
- const showFiatTotal = transactionAmount !== '0x0' && fiatTotal
return h(`div${className || ''}`, {
key: transactionId,
@@ -288,7 +287,7 @@ TxListItem.prototype.render = function () {
h('span.tx-list-value', total),
- showFiatTotal && h('span.tx-list-fiat-value', fiatTotal),
+ fiatTotal && h('span.tx-list-fiat-value', fiatTotal),
]),
]),
diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js
index 740c4a4ab..554febcff 100644
--- a/ui/app/components/tx-list.js
+++ b/ui/app/components/tx-list.js
@@ -11,14 +11,19 @@ const { formatDate } = require('../util')
const { showConfTxPage } = require('../actions')
const classnames = require('classnames')
const { tokenInfoGetter } = require('../token-util')
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
+const { CONFIRM_TRANSACTION_ROUTE } = require('../routes')
+
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(TxList)
TxList.contextTypes = {
t: PropTypes.func,
}
-module.exports = connect(mapStateToProps, mapDispatchToProps)(TxList)
-
-
function mapStateToProps (state) {
return {
txsToRender: selectors.transactionsSelector(state),
@@ -96,7 +101,7 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa
transactionNetworkId,
transactionSubmittedTime,
} = props
- const { showConfTxPage } = this.props
+ const { history } = this.props
const opts = {
key: transactionId || transactionHash,
@@ -116,7 +121,10 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa
const isUnapproved = transactionStatus === 'unapproved'
if (isUnapproved) {
- opts.onClick = () => showConfTxPage({ id: transactionId })
+ opts.onClick = () => {
+ this.props.showConfTxPage({ id: transactionId })
+ history.push(CONFIRM_TRANSACTION_ROUTE)
+ }
opts.transactionStatus = this.context.t('notStarted')
} else if (transactionHash) {
opts.onClick = () => this.view(transactionHash, transactionNetworkId)
diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js
index ca24e813f..263f992c0 100644
--- a/ui/app/components/tx-view.js
+++ b/ui/app/components/tx-view.js
@@ -2,22 +2,27 @@ const Component = require('react').Component
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
-const ethUtil = require('ethereumjs-util')
const inherits = require('util').inherits
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
const actions = require('../actions')
const selectors = require('../selectors')
+const { SEND_ROUTE } = require('../routes')
+const { checksumAddress: toChecksumAddress } = require('../util')
const BalanceComponent = require('./balance-component')
const TxList = require('./tx-list')
const Identicon = require('./identicon')
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(TxView)
+
TxView.contextTypes = {
t: PropTypes.func,
}
-module.exports = connect(mapStateToProps, mapDispatchToProps)(TxView)
-
-
function mapStateToProps (state) {
const sidebarOpen = state.appState.sidebarOpen
const isMascara = state.appState.isMascara
@@ -27,7 +32,7 @@ function mapStateToProps (state) {
const network = state.metamask.network
const selectedTokenAddress = state.metamask.selectedTokenAddress
const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
- const checksumAddress = selectedAddress && ethUtil.toChecksumAddress(selectedAddress)
+ const checksumAddress = toChecksumAddress(selectedAddress)
const identity = identities[selectedAddress]
return {
@@ -69,7 +74,7 @@ TxView.prototype.renderHeroBalance = function () {
}
TxView.prototype.renderButtons = function () {
- const {selectedToken, showModal, showSendPage, showSendTokenPage } = this.props
+ const {selectedToken, showModal, history } = this.props
return !selectedToken
? (
@@ -84,14 +89,14 @@ TxView.prototype.renderButtons = function () {
style: {
marginLeft: '0.8em',
},
- onClick: showSendPage,
+ onClick: () => history.push(SEND_ROUTE),
}, this.context.t('send')),
])
)
: (
h('div.flex-row.flex-center.hero-balance-buttons', [
h('button.btn-primary.hero-balance-button', {
- onClick: showSendTokenPage,
+ onClick: () => history.push(SEND_ROUTE),
}, this.context.t('send')),
])
)
diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js
index e6b94ad12..9e430f87b 100644
--- a/ui/app/components/wallet-view.js
+++ b/ui/app/components/wallet-view.js
@@ -2,8 +2,11 @@ const Component = require('react').Component
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
const inherits = require('util').inherits
const classnames = require('classnames')
+const { checksumAddress } = require('../util')
const Identicon = require('./identicon')
// const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns
const Tooltip = require('./tooltip-v2.js')
@@ -12,14 +15,17 @@ const actions = require('../actions')
const BalanceComponent = require('./balance-component')
const TokenList = require('./token-list')
const selectors = require('../selectors')
+const { ADD_TOKEN_ROUTE } = require('../routes')
+
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(WalletView)
WalletView.contextTypes = {
t: PropTypes.func,
}
-module.exports = connect(mapStateToProps, mapDispatchToProps)(WalletView)
-
-
function mapStateToProps (state) {
return {
@@ -97,11 +103,13 @@ WalletView.prototype.render = function () {
keyrings,
showAccountDetailModal,
hideSidebar,
- showAddTokenPage,
+ history,
} = this.props
// temporary logs + fake extra wallets
// console.log('walletview, selectedAccount:', selectedAccount)
+ const checksummedAddress = checksumAddress(selectedAddress)
+
const keyring = keyrings.find((kr) => {
return kr.accounts.includes(selectedAddress) ||
kr.accounts.includes(selectedIdentity.address)
@@ -130,7 +138,7 @@ WalletView.prototype.render = function () {
}, [
h(Identicon, {
diameter: 54,
- address: selectedAddress,
+ address: checksummedAddress,
}),
h('span.account-name', {
@@ -153,7 +161,7 @@ WalletView.prototype.render = function () {
'wallet-view__address__pressed': this.state.copyToClipboardPressed,
}),
onClick: () => {
- copyToClipboard(selectedAddress)
+ copyToClipboard(checksummedAddress)
this.setState({ hasCopied: true })
setTimeout(() => this.setState({ hasCopied: false }), 3000)
},
@@ -164,7 +172,7 @@ WalletView.prototype.render = function () {
this.setState({ copyToClipboardPressed: false })
},
}, [
- `${selectedAddress.slice(0, 4)}...${selectedAddress.slice(-4)}`,
+ `${checksummedAddress.slice(0, 4)}...${checksummedAddress.slice(-4)}`,
h('i.fa.fa-clipboard', { style: { marginLeft: '8px' } }),
]),
]),
@@ -174,10 +182,7 @@ WalletView.prototype.render = function () {
h(TokenList),
h('button.btn-primary.wallet-view__add-token-button', {
- onClick: () => {
- showAddTokenPage()
- hideSidebar()
- },
+ onClick: () => history.push(ADD_TOKEN_ROUTE),
}, this.context.t('addToken')),
])
}
diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js
index 1070436c3..b71538e31 100644
--- a/ui/app/conf-tx.js
+++ b/ui/app/conf-tx.js
@@ -2,8 +2,11 @@ const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
const actions = require('./actions')
const txHelper = require('../lib/tx-helper')
+const log = require('loglevel')
const PendingTx = require('./components/pending-tx')
const SignatureRequest = require('./components/signature-request')
@@ -11,19 +14,21 @@ const SignatureRequest = require('./components/signature-request')
// const PendingPersonalMsg = require('./components/pending-personal-msg')
// const PendingTypedMsg = require('./components/pending-typed-msg')
const Loading = require('./components/loading')
+const { DEFAULT_ROUTE } = require('./routes')
-// const contentDivider = h('div', {
-// style: {
-// marginLeft: '16px',
-// marginRight: '16px',
-// height:'1px',
-// background:'#E7E7E7',
-// },
-// })
-
-module.exports = connect(mapStateToProps)(ConfirmTxScreen)
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps)
+)(ConfirmTxScreen)
function mapStateToProps (state) {
+ const { metamask } = state
+ const {
+ unapprovedMsgCount,
+ unapprovedPersonalMsgCount,
+ unapprovedTypedMessagesCount,
+ } = metamask
+
return {
identities: state.metamask.identities,
accounts: state.metamask.accounts,
@@ -40,6 +45,10 @@ function mapStateToProps (state) {
currentCurrency: state.metamask.currentCurrency,
blockGasLimit: state.metamask.currentBlockGasLimit,
computedBalances: state.metamask.computedBalances,
+ unapprovedMsgCount,
+ unapprovedPersonalMsgCount,
+ unapprovedTypedMessagesCount,
+ send: state.metamask.send,
selectedAddressTxList: state.metamask.selectedAddressTxList,
}
}
@@ -49,11 +58,35 @@ function ConfirmTxScreen () {
Component.call(this)
}
+ConfirmTxScreen.prototype.getUnapprovedMessagesTotal = function () {
+ const {
+ unapprovedMsgCount = 0,
+ unapprovedPersonalMsgCount = 0,
+ unapprovedTypedMessagesCount = 0,
+ } = this.props
+
+ return unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount
+}
+
+ConfirmTxScreen.prototype.componentDidMount = function () {
+ const {
+ unapprovedTxs = {},
+ network,
+ send,
+ } = this.props
+ const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network)
+
+ if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) {
+ this.props.history.push(DEFAULT_ROUTE)
+ }
+}
+
ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) {
const {
- unapprovedTxs,
+ unapprovedTxs = {},
network,
selectedAddressTxList,
+ send,
} = this.props
const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps
const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network)
@@ -61,8 +94,9 @@ ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) {
const prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {}
const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network)
- if (prevTx.status === 'dropped' && unconfTxList.length === 0) {
- this.goHome({})
+ if (unconfTxList.length === 0 &&
+ (prevTx.status === 'dropped' || !send.to && this.getUnapprovedMessagesTotal() === 0)) {
+ this.props.history.push(DEFAULT_ROUTE)
}
}
@@ -103,7 +137,6 @@ ConfirmTxScreen.prototype.render = function () {
*/
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`)
- if (unconfTxList.length === 0) return h(Loading)
return currentTxView({
// Properties
@@ -152,6 +185,7 @@ function currentTxView (opts) {
// return h(PendingTypedMsg, opts)
// }
}
+
return h(Loading)
}
@@ -163,6 +197,7 @@ ConfirmTxScreen.prototype.buyEth = function (address, event) {
ConfirmTxScreen.prototype.sendTransaction = function (txData, event) {
this.stopPropagation(event)
this.props.dispatch(actions.updateAndApproveTx(txData))
+ .then(() => this.props.history.push(DEFAULT_ROUTE))
}
ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) {
@@ -182,7 +217,7 @@ ConfirmTxScreen.prototype.signMessage = function (msgData, event) {
var params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
- this.props.dispatch(actions.signMsg(params))
+ return this.props.dispatch(actions.signMsg(params))
}
ConfirmTxScreen.prototype.stopPropagation = function (event) {
@@ -196,7 +231,7 @@ ConfirmTxScreen.prototype.signPersonalMessage = function (msgData, event) {
var params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
- this.props.dispatch(actions.signPersonalMsg(params))
+ return this.props.dispatch(actions.signPersonalMsg(params))
}
ConfirmTxScreen.prototype.signTypedMessage = function (msgData, event) {
@@ -204,25 +239,25 @@ ConfirmTxScreen.prototype.signTypedMessage = function (msgData, event) {
var params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
- this.props.dispatch(actions.signTypedMsg(params))
+ return this.props.dispatch(actions.signTypedMsg(params))
}
ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) {
log.info('canceling message')
this.stopPropagation(event)
- this.props.dispatch(actions.cancelMsg(msgData))
+ return this.props.dispatch(actions.cancelMsg(msgData))
}
ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) {
log.info('canceling personal message')
this.stopPropagation(event)
- this.props.dispatch(actions.cancelPersonalMsg(msgData))
+ return this.props.dispatch(actions.cancelPersonalMsg(msgData))
}
ConfirmTxScreen.prototype.cancelTypedMessage = function (msgData, event) {
log.info('canceling typed message')
this.stopPropagation(event)
- this.props.dispatch(actions.cancelTypedMsg(msgData))
+ return this.props.dispatch(actions.cancelTypedMsg(msgData))
}
ConfirmTxScreen.prototype.goHome = function (event) {
diff --git a/ui/app/css/index.scss b/ui/app/css/index.scss
index 445c819ff..c068028f8 100644
--- a/ui/app/css/index.scss
+++ b/ui/app/css/index.scss
@@ -6,9 +6,15 @@
*/
@import './itcss/settings/index.scss';
+
@import './itcss/tools/index.scss';
+
@import './itcss/generic/index.scss';
+
@import './itcss/base/index.scss';
+
@import './itcss/objects/index.scss';
+
@import './itcss/components/index.scss';
+
@import './itcss/trumps/index.scss';
diff --git a/ui/app/css/itcss/components/add-token.scss b/ui/app/css/itcss/components/add-token.scss
index 2fdda6f43..01579c27c 100644
--- a/ui/app/css/itcss/components/add-token.scss
+++ b/ui/app/css/itcss/components/add-token.scss
@@ -36,6 +36,7 @@
font-weight: 400;
line-height: 21px;
margin-left: 8px;
+ cursor:pointer;
}
}
diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss
index ffd43ecbf..959eb9d15 100644
--- a/ui/app/css/itcss/components/index.scss
+++ b/ui/app/css/itcss/components/index.scss
@@ -52,6 +52,8 @@
@import './editable-label.scss';
+@import './pages/index.scss';
+
@import './new-account.scss';
@import './tooltip.scss';
diff --git a/ui/app/css/itcss/components/loading-overlay.scss b/ui/app/css/itcss/components/loading-overlay.scss
index 15009c1e6..a92fffec5 100644
--- a/ui/app/css/itcss/components/loading-overlay.scss
+++ b/ui/app/css/itcss/components/loading-overlay.scss
@@ -1,13 +1,14 @@
.loading-overlay {
- left: 0px;
+ left: 0;
z-index: 50;
position: absolute;
flex-direction: column;
display: flex;
justify-content: center;
align-items: center;
+ flex: 1 1 auto;
width: 100%;
- background: rgba(255, 255, 255, 0.8);
+ background: rgba(255, 255, 255, .8);
@media screen and (max-width: 575px) {
margin-top: 56px;
@@ -18,4 +19,11 @@
margin-top: 75px;
height: calc(100% - 75px);
}
+
+ &--full-screen {
+ position: fixed;
+ height: 100vh;
+ width: 100vw;
+ margin-top: 0;
+ }
}
diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss
index c34b5cd06..6c8be0b6d 100644
--- a/ui/app/css/itcss/components/network.scss
+++ b/ui/app/css/itcss/components/network.scss
@@ -139,7 +139,7 @@
.network-dropdown-title {
height: 25px;
- width: 75px;
+ width: 120px;
color: $white;
font-family: Roboto;
font-size: 18px;
diff --git a/ui/app/css/itcss/components/new-account.scss b/ui/app/css/itcss/components/new-account.scss
index aa7fed956..293579058 100644
--- a/ui/app/css/itcss/components/new-account.scss
+++ b/ui/app/css/itcss/components/new-account.scss
@@ -35,13 +35,14 @@
font-size: 18px;
line-height: 24px;
text-align: center;
+ cursor: pointer;
}
&__tab:first-of-type {
margin-right: 20px;
}
- &__unselected:hover {
+ &__tab:hover {
color: $black;
border-bottom: none;
}
@@ -49,9 +50,9 @@
&__selected {
color: $curious-blue;
border-bottom: 3px solid $curious-blue;
+ cursor: initial;
}
}
-
}
.new-account-import-disclaimer {
diff --git a/ui/app/css/itcss/components/pages/index.scss b/ui/app/css/itcss/components/pages/index.scss
new file mode 100644
index 000000000..82446fd7a
--- /dev/null
+++ b/ui/app/css/itcss/components/pages/index.scss
@@ -0,0 +1 @@
+@import './unlock.scss';
diff --git a/ui/app/css/itcss/components/pages/unlock.scss b/ui/app/css/itcss/components/pages/unlock.scss
new file mode 100644
index 000000000..5d438377b
--- /dev/null
+++ b/ui/app/css/itcss/components/pages/unlock.scss
@@ -0,0 +1,9 @@
+.unlock-page {
+ box-shadow: none;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background: rgb(247, 247, 247);
+ width: 100%;
+}
diff --git a/ui/app/css/itcss/components/sections.scss b/ui/app/css/itcss/components/sections.scss
index 388aea175..ace46bd8a 100644
--- a/ui/app/css/itcss/components/sections.scss
+++ b/ui/app/css/itcss/components/sections.scss
@@ -17,6 +17,12 @@ textarea.twelve-word-phrase {
resize: none;
}
+.initialize-screen {
+ width: 100%;
+ z-index: $main-container-z-index;
+ background: #f7f7f7;
+}
+
.initialize-screen hr {
width: 60px;
margin: 12px;
diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss
index 89d2be891..362feeec8 100644
--- a/ui/app/css/itcss/components/send.scss
+++ b/ui/app/css/itcss/components/send.scss
@@ -789,6 +789,7 @@
display: flex;
justify-content: center;
align-items: center;
+ padding: 0 3px;
cursor: pointer;
}
diff --git a/ui/app/ducks/send.js b/ui/app/ducks/send.js
index aeca9f92f..aef493ea0 100644
--- a/ui/app/ducks/send.js
+++ b/ui/app/ducks/send.js
@@ -1,19 +1,21 @@
import extend from 'xtend'
// Actions
-const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN';
-const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN';
-const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN';
-const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN';
+const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN'
+const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN'
+const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN'
+const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'
+const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS'
// TODO: determine if this approach to initState is consistent with conventional ducks pattern
const initState = {
fromDropdownOpen: false,
toDropdownOpen: false,
+ errors: {},
}
// Reducer
-export default function reducer(state = initState, action = {}) {
+export default function reducer ({ send: sendState = initState }, action = {}) {
switch (action.type) {
case OPEN_FROM_DROPDOWN:
return extend(sendState, {
@@ -31,24 +33,38 @@ export default function reducer(state = initState, action = {}) {
return extend(sendState, {
toDropdownOpen: false,
})
+ case UPDATE_SEND_ERRORS:
+ return extend(sendState, {
+ errors: {
+ ...sendState.errors,
+ ...action.value,
+ },
+ })
default:
return sendState
}
}
// Action Creators
-export function openFromDropdown() {
- return { type: OPEN_FROM_DROPDOWN };
+export function openFromDropdown () {
+ return { type: OPEN_FROM_DROPDOWN }
+}
+
+export function closeFromDropdown () {
+ return { type: CLOSE_FROM_DROPDOWN }
}
-export function closeFromDropdown() {
- return { type: CLOSE_FROM_DROPDOWN };
+export function openToDropdown () {
+ return { type: OPEN_TO_DROPDOWN }
}
-export function openToDropdown() {
- return { type: OPEN_TO_DROPDOWN };
+export function closeToDropdown () {
+ return { type: CLOSE_TO_DROPDOWN }
}
-export function closeToDropdown() {
- return { type: CLOSE_TO_DROPDOWN };
-} \ No newline at end of file
+export function updateSendErrors (errorObject) {
+ return {
+ type: UPDATE_SEND_ERRORS,
+ value: errorObject,
+ }
+}
diff --git a/ui/app/first-time/init-menu.js b/ui/app/first-time/init-menu.js
index 4ab5f06c0..6cb548bb9 100644
--- a/ui/app/first-time/init-menu.js
+++ b/ui/app/first-time/init-menu.js
@@ -1,6 +1,5 @@
-const inherits = require('util').inherits
-const EventEmitter = require('events').EventEmitter
-const Component = require('react').Component
+const { EventEmitter } = require('events')
+const { Component } = require('react')
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
@@ -8,205 +7,220 @@ const Mascot = require('../components/mascot')
const actions = require('../actions')
const Tooltip = require('../components/tooltip')
const getCaretCoordinates = require('textarea-caret')
-const environmentType = require('../../../app/scripts/lib/environment-type')
-const { OLD_UI_NETWORK_TYPE } = require('../../../app/scripts/config').enums
-
-let isSubmitting = false
-
-InitializeMenuScreen.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect(mapStateToProps)(InitializeMenuScreen)
-
-
-inherits(InitializeMenuScreen, Component)
-function InitializeMenuScreen () {
- Component.call(this)
- this.animationEventEmitter = new EventEmitter()
-}
-
-function mapStateToProps (state) {
- return {
- // state from plugin
- currentView: state.appState.currentView,
- warning: state.appState.warning,
+const { RESTORE_VAULT_ROUTE, DEFAULT_ROUTE } = require('../routes')
+const { getEnvironmentType } = require('../../../app/scripts/lib/util')
+const { ENVIRONMENT_TYPE_POPUP } = require('../../../app/scripts/lib/enums')
+const { OLD_UI_NETWORK_TYPE } = require('../../../app/scripts/controllers/network/enums')
+
+class InitializeMenuScreen extends Component {
+ constructor (props) {
+ super(props)
+
+ this.animationEventEmitter = new EventEmitter()
+ this.state = {
+ warning: null,
+ }
}
-}
-
-InitializeMenuScreen.prototype.render = function () {
- var state = this.props
-
- switch (state.currentView.name) {
-
- default:
- return this.renderMenu(state)
+ componentWillMount () {
+ const { isInitialized, isUnlocked, history } = this.props
+ if (isInitialized || isUnlocked) {
+ history.push(DEFAULT_ROUTE)
+ }
}
-}
-
-// InitializeMenuScreen.prototype.componentDidMount = function(){
-// document.getElementById('password-box').focus()
-// }
-InitializeMenuScreen.prototype.renderMenu = function (state) {
- return (
-
- h('.initialize-screen.flex-column.flex-center.flex-grow', [
+ componentDidMount () {
+ document.getElementById('password-box').focus()
+ }
- h(Mascot, {
- animationEventEmitter: this.animationEventEmitter,
- }),
+ render () {
+ const { warning } = this.state
- h('h1', {
- style: {
- fontSize: '1.3em',
- textTransform: 'uppercase',
- color: '#7F8082',
- marginBottom: 10,
- },
- }, this.context.t('appName')),
+ return (
+ h('.initialize-screen.flex-column.flex-center', [
+ h(Mascot, {
+ animationEventEmitter: this.animationEventEmitter,
+ }),
- h('div', [
- h('h3', {
+ h('h1', {
style: {
- fontSize: '0.8em',
+ fontSize: '1.3em',
+ textTransform: 'uppercase',
color: '#7F8082',
- display: 'inline',
+ marginBottom: 10,
},
- }, this.context.t('encryptNewDen')),
+ }, this.context.t('appName')),
- h(Tooltip, {
- title: this.context.t('denExplainer'),
- }, [
- h('i.fa.fa-question-circle.pointer', {
+ h('div', [
+ h('h3', {
style: {
- fontSize: '18px',
- position: 'relative',
- color: 'rgb(247, 134, 28)',
- top: '2px',
- marginLeft: '4px',
+ fontSize: '0.8em',
+ color: '#7F8082',
+ display: 'inline',
},
- }),
+ }, this.context.t('encryptNewDen')),
+
+ h(Tooltip, {
+ title: this.context.t('denExplainer'),
+ }, [
+ h('i.fa.fa-question-circle.pointer', {
+ style: {
+ fontSize: '18px',
+ position: 'relative',
+ color: 'rgb(247, 134, 28)',
+ top: '2px',
+ marginLeft: '4px',
+ },
+ }),
+ ]),
]),
- ]),
-
- h('span.in-progress-notification', state.warning),
-
- // password
- h('input.large-input.letter-spacey', {
- type: 'password',
- id: 'password-box',
- placeholder: this.context.t('newPassword'),
- onInput: this.inputChanged.bind(this),
- style: {
- width: 260,
- marginTop: 12,
- },
- }),
-
- // confirm password
- h('input.large-input.letter-spacey', {
- type: 'password',
- id: 'password-box-confirm',
- placeholder: this.context.t('confirmPassword'),
- onKeyPress: this.createVaultOnEnter.bind(this),
- onInput: this.inputChanged.bind(this),
- style: {
- width: 260,
- marginTop: 16,
- },
- }),
-
-
- h('button.primary', {
- onClick: this.createNewVaultAndKeychain.bind(this),
- style: {
- margin: 12,
- },
- }, this.context.t('createDen')),
-
- h('.flex-row.flex-center.flex-grow', [
- h('p.pointer', {
- onClick: this.showRestoreVault.bind(this),
+
+ h('span.error.in-progress-notification', warning),
+
+ // password
+ h('input.large-input.letter-spacey', {
+ type: 'password',
+ id: 'password-box',
+ placeholder: this.context.t('newPassword'),
+ onInput: this.inputChanged.bind(this),
style: {
- fontSize: '0.8em',
- color: 'rgb(247, 134, 28)',
- textDecoration: 'underline',
+ width: 260,
+ marginTop: 12,
},
- }, this.context.t('importDen')),
- ]),
+ }),
+
+ // confirm password
+ h('input.large-input.letter-spacey', {
+ type: 'password',
+ id: 'password-box-confirm',
+ placeholder: this.context.t('confirmPassword'),
+ onKeyPress: this.createVaultOnEnter.bind(this),
+ onInput: this.inputChanged.bind(this),
+ style: {
+ width: 260,
+ marginTop: 16,
+ },
+ }),
- h('.flex-row.flex-center.flex-grow', [
- h('p.pointer', {
- onClick: this.showOldUI.bind(this),
+
+ h('button.primary', {
+ onClick: this.createNewVaultAndKeychain.bind(this),
style: {
- fontSize: '0.8em',
- color: '#aeaeae',
- textDecoration: 'underline',
- marginTop: '32px',
+ margin: 12,
},
- }, 'Use classic interface'),
- ]),
+ }, this.context.t('createDen')),
- ])
- )
-}
+ h('.flex-row.flex-center.flex-grow', [
+ h('p.pointer', {
+ onClick: () => this.showRestoreVault(),
+ style: {
+ fontSize: '0.8em',
+ color: 'rgb(247, 134, 28)',
+ textDecoration: 'underline',
+ },
+ }, this.context.t('importDen')),
+ ]),
-InitializeMenuScreen.prototype.createVaultOnEnter = function (event) {
- if (event.key === 'Enter') {
- event.preventDefault()
- this.createNewVaultAndKeychain()
+ h('.flex-row.flex-center.flex-grow', [
+ h('p.pointer', {
+ onClick: this.showOldUI.bind(this),
+ style: {
+ fontSize: '0.8em',
+ color: '#aeaeae',
+ textDecoration: 'underline',
+ marginTop: '32px',
+ },
+ }, 'Use classic interface'),
+ ]),
+
+ ])
+ )
}
-}
-InitializeMenuScreen.prototype.componentDidMount = function () {
- document.getElementById('password-box').focus()
-}
+ createVaultOnEnter (event) {
+ if (event.key === 'Enter') {
+ event.preventDefault()
+ this.createNewVaultAndKeychain()
+ }
+ }
+
+ createNewVaultAndKeychain () {
+ const { history } = this.props
+ var passwordBox = document.getElementById('password-box')
+ var password = passwordBox.value
+ var passwordConfirmBox = document.getElementById('password-box-confirm')
+ var passwordConfirm = passwordConfirmBox.value
+
+ this.setState({ warning: null })
+
+ if (password.length < 8) {
+ this.setState({ warning: this.context.t('passwordShort') })
+ return
+ }
-InitializeMenuScreen.prototype.showRestoreVault = function () {
- this.props.dispatch(actions.markPasswordForgotten())
- if (environmentType() === 'popup') {
- global.platform.openExtensionInBrowser()
+ if (password !== passwordConfirm) {
+ this.setState({ warning: this.context.t('passwordMismatch') })
+ return
+ }
+
+ this.props.createNewVaultAndKeychain(password)
+ .then(() => history.push(DEFAULT_ROUTE))
}
-}
-InitializeMenuScreen.prototype.showOldUI = function () {
- this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
- .then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
-}
+ inputChanged (event) {
+ // tell mascot to look at page action
+ var element = event.target
+ var boundingRect = element.getBoundingClientRect()
+ var coordinates = getCaretCoordinates(element, element.selectionEnd)
+ this.animationEventEmitter.emit('point', {
+ x: boundingRect.left + coordinates.left - element.scrollLeft,
+ y: boundingRect.top + coordinates.top - element.scrollTop,
+ })
+ }
-InitializeMenuScreen.prototype.createNewVaultAndKeychain = function () {
- var passwordBox = document.getElementById('password-box')
- var password = passwordBox.value
- var passwordConfirmBox = document.getElementById('password-box-confirm')
- var passwordConfirm = passwordConfirmBox.value
+ showRestoreVault () {
+ this.props.markPasswordForgotten()
+ if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
+ global.platform.openExtensionInBrowser()
+ }
- if (password.length < 8) {
- this.warning = this.context.t('passwordShort')
- this.props.dispatch(actions.displayWarning(this.warning))
- return
+ this.props.history.push(RESTORE_VAULT_ROUTE)
}
- if (password !== passwordConfirm) {
- this.warning = this.context.t('passwordMismatch')
- this.props.dispatch(actions.displayWarning(this.warning))
- return
+
+ showOldUI () {
+ this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
+ .then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
}
+}
+
+InitializeMenuScreen.propTypes = {
+ history: PropTypes.object,
+ isInitialized: PropTypes.bool,
+ isUnlocked: PropTypes.bool,
+ createNewVaultAndKeychain: PropTypes.func,
+ markPasswordForgotten: PropTypes.func,
+ dispatch: PropTypes.func,
+}
+
+InitializeMenuScreen.contextTypes = {
+ t: PropTypes.func,
+}
- if (!isSubmitting) {
- isSubmitting = true
- this.props.dispatch(actions.createNewVaultAndKeychain(password))
+const mapStateToProps = state => {
+ const { metamask: { isInitialized, isUnlocked } } = state
+
+ return {
+ isInitialized,
+ isUnlocked,
}
}
-InitializeMenuScreen.prototype.inputChanged = function (event) {
- // tell mascot to look at page action
- var element = event.target
- var boundingRect = element.getBoundingClientRect()
- var coordinates = getCaretCoordinates(element, element.selectionEnd)
- this.animationEventEmitter.emit('point', {
- x: boundingRect.left + coordinates.left - element.scrollLeft,
- y: boundingRect.top + coordinates.top - element.scrollTop,
- })
+const mapDispatchToProps = dispatch => {
+ return {
+ createNewVaultAndKeychain: password => dispatch(actions.createNewVaultAndKeychain(password)),
+ markPasswordForgotten: () => dispatch(actions.markPasswordForgotten()),
+ }
}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(InitializeMenuScreen)
diff --git a/ui/app/i18n-provider.js b/ui/app/i18n-provider.js
index fe6d62c67..4ef618018 100644
--- a/ui/app/i18n-provider.js
+++ b/ui/app/i18n-provider.js
@@ -1,6 +1,8 @@
const { Component } = require('react')
const connect = require('react-redux').connect
const PropTypes = require('prop-types')
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
const t = require('../i18n-helper').getMessage
class I18nProvider extends Component {
@@ -32,5 +34,8 @@ const mapStateToProps = state => {
}
}
-module.exports = connect(mapStateToProps)(I18nProvider)
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps)
+)(I18nProvider)
diff --git a/ui/app/keychains/hd/recover-seed/confirmation.js b/ui/app/keychains/hd/recover-seed/confirmation.js
index 02183f096..eb588415f 100644
--- a/ui/app/keychains/hd/recover-seed/confirmation.js
+++ b/ui/app/keychains/hd/recover-seed/confirmation.js
@@ -4,12 +4,21 @@ const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../../../actions')
+const { withRouter } = require('react-router-dom')
+const { compose } = require('recompose')
+const {
+ DEFAULT_ROUTE,
+ INITIALIZE_BACKUP_PHRASE_ROUTE,
+} = require('../../../routes')
RevealSeedConfirmation.contextTypes = {
t: PropTypes.func,
}
-module.exports = connect(mapStateToProps)(RevealSeedConfirmation)
+module.exports = compose(
+ withRouter,
+ connect(mapStateToProps)
+)(RevealSeedConfirmation)
inherits(RevealSeedConfirmation, Component)
@@ -109,6 +118,8 @@ RevealSeedConfirmation.prototype.componentDidMount = function () {
RevealSeedConfirmation.prototype.goHome = function () {
this.props.dispatch(actions.showConfigPage(false))
+ this.props.dispatch(actions.confirmSeedWords())
+ .then(() => this.props.history.push(DEFAULT_ROUTE))
}
// create vault
@@ -123,4 +134,5 @@ RevealSeedConfirmation.prototype.checkConfirmation = function (event) {
RevealSeedConfirmation.prototype.revealSeedWords = function () {
var password = document.getElementById('password-box').value
this.props.dispatch(actions.requestRevealSeed(password))
+ .then(() => this.props.history.push(INITIALIZE_BACKUP_PHRASE_ROUTE))
}
diff --git a/ui/app/keychains/hd/restore-vault.js b/ui/app/keychains/hd/restore-vault.js
index 38ad14adb..913d20505 100644
--- a/ui/app/keychains/hd/restore-vault.js
+++ b/ui/app/keychains/hd/restore-vault.js
@@ -4,6 +4,7 @@ const PersistentForm = require('../../../lib/persistent-form')
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../../actions')
+const log = require('loglevel')
RestoreVaultScreen.contextTypes = {
t: PropTypes.func,
diff --git a/ui/app/main-container.js b/ui/app/main-container.js
index eed4bd164..c305687ea 100644
--- a/ui/app/main-container.js
+++ b/ui/app/main-container.js
@@ -2,8 +2,9 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const AccountAndTransactionDetails = require('./account-and-transaction-details')
-const Settings = require('./settings')
-const UnlockScreen = require('./unlock')
+const Settings = require('./components/pages/settings')
+const UnlockScreen = require('./components/pages/unlock')
+const log = require('loglevel')
module.exports = MainContainer
diff --git a/ui/app/reducers.js b/ui/app/reducers.js
index ff766e856..26bf66c3c 100644
--- a/ui/app/reducers.js
+++ b/ui/app/reducers.js
@@ -8,7 +8,7 @@ const reduceIdentities = require('./reducers/identities')
const reduceMetamask = require('./reducers/metamask')
const reduceApp = require('./reducers/app')
const reduceLocale = require('./reducers/locale')
-const reduceSend = require('./ducks/send')
+const reduceSend = require('./ducks/send').default
window.METAMASK_CACHED_LOG_STATE = null
diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js
index 74a0f9299..2b39eb8db 100644
--- a/ui/app/reducers/app.js
+++ b/ui/app/reducers/app.js
@@ -1,6 +1,7 @@
const extend = require('xtend')
const actions = require('../actions')
const txHelper = require('../../lib/tx-helper')
+const log = require('loglevel')
module.exports = reduceApp
diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js
index 6d0a5bb10..5564c8080 100644
--- a/ui/app/reducers/metamask.js
+++ b/ui/app/reducers/metamask.js
@@ -1,8 +1,9 @@
const extend = require('xtend')
const actions = require('../actions')
const MetamascaraPlatform = require('../../../app/scripts/platforms/window')
-const environmentType = require('../../../app/scripts/lib/environment-type')
-const { OLD_UI_NETWORK_TYPE } = require('../../../app/scripts/config').enums
+const { getEnvironmentType } = require('../../../app/scripts/lib/util')
+const { ENVIRONMENT_TYPE_POPUP } = require('../../../app/scripts/lib/enums')
+const { OLD_UI_NETWORK_TYPE } = require('../../../app/scripts/controllers/network/enums')
module.exports = reduceMetamask
@@ -15,7 +16,7 @@ function reduceMetamask (state, action) {
isUnlocked: false,
isAccountMenuOpen: false,
isMascara: window.platform instanceof MetamascaraPlatform,
- isPopup: environmentType() === 'popup',
+ isPopup: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP,
rpcTarget: 'https://rawtestrpc.metamask.io/',
identities: {},
unapprovedTxs: {},
@@ -24,6 +25,7 @@ function reduceMetamask (state, action) {
frequentRpcList: [],
addressBook: [],
selectedTokenAddress: null,
+ contractExchangeRates: {},
tokenExchangeRates: {},
tokens: [],
send: {
@@ -176,15 +178,6 @@ function reduceMetamask (state, action) {
conversionDate: action.value.conversionDate,
})
- case actions.UPDATE_TOKEN_EXCHANGE_RATE:
- const { payload: { pair, marketinfo } } = action
- return extend(metamaskState, {
- tokenExchangeRates: {
- ...metamaskState.tokenExchangeRates,
- [pair]: marketinfo,
- },
- })
-
case actions.UPDATE_TOKENS:
return extend(metamaskState, {
tokens: action.newTokens,
@@ -261,17 +254,6 @@ function reduceMetamask (state, action) {
},
})
- case actions.UPDATE_SEND_ERRORS:
- return extend(metamaskState, {
- send: {
- ...metamaskState.send,
- errors: {
- ...metamaskState.send.errors,
- ...action.value,
- },
- },
- })
-
case actions.UPDATE_MAX_MODE:
return extend(metamaskState, {
send: {
@@ -358,7 +340,7 @@ function reduceMetamask (state, action) {
welcomeScreenSeen: true,
})
- case action.SET_CURRENT_LOCALE:
+ case actions.SET_CURRENT_LOCALE:
return extend(metamaskState, {
currentLocale: action.value,
})
diff --git a/ui/app/root.js b/ui/app/root.js
index 21d6d1829..09deae1b1 100644
--- a/ui/app/root.js
+++ b/ui/app/root.js
@@ -1,22 +1,23 @@
-const inherits = require('util').inherits
-const Component = require('react').Component
-const Provider = require('react-redux').Provider
+const { Component } = require('react')
+const PropTypes = require('prop-types')
+const { Provider } = require('react-redux')
const h = require('react-hyperscript')
const SelectedApp = require('./select-app')
-module.exports = Root
-
-inherits(Root, Component)
-function Root () { Component.call(this) }
-
-Root.prototype.render = function () {
- return (
+class Root extends Component {
+ render () {
+ const { store } = this.props
- h(Provider, {
- store: this.props.store,
- }, [
- h(SelectedApp),
- ])
+ return (
+ h(Provider, { store }, [
+ h(SelectedApp),
+ ])
+ )
+ }
+}
- )
+Root.propTypes = {
+ store: PropTypes.object,
}
+
+module.exports = Root
diff --git a/ui/app/routes.js b/ui/app/routes.js
new file mode 100644
index 000000000..4b3f8f4d8
--- /dev/null
+++ b/ui/app/routes.js
@@ -0,0 +1,49 @@
+const DEFAULT_ROUTE = '/'
+const UNLOCK_ROUTE = '/unlock'
+const SETTINGS_ROUTE = '/settings'
+const INFO_ROUTE = '/settings/info'
+const REVEAL_SEED_ROUTE = '/seed'
+const CONFIRM_SEED_ROUTE = '/confirm-seed'
+const RESTORE_VAULT_ROUTE = '/restore-vault'
+const ADD_TOKEN_ROUTE = '/add-token'
+const NEW_ACCOUNT_ROUTE = '/new-account'
+const IMPORT_ACCOUNT_ROUTE = '/new-account/import'
+const SEND_ROUTE = '/send'
+const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction'
+const SIGNATURE_REQUEST_ROUTE = '/confirm-transaction/signature-request'
+const NOTICE_ROUTE = '/notice'
+const WELCOME_ROUTE = '/welcome'
+const INITIALIZE_ROUTE = '/initialize'
+const INITIALIZE_CREATE_PASSWORD_ROUTE = '/initialize/create-password'
+const INITIALIZE_IMPORT_ACCOUNT_ROUTE = '/initialize/import-account'
+const INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE = '/initialize/import-with-seed-phrase'
+const INITIALIZE_UNIQUE_IMAGE_ROUTE = '/initialize/unique-image'
+const INITIALIZE_NOTICE_ROUTE = '/initialize/notice'
+const INITIALIZE_BACKUP_PHRASE_ROUTE = '/initialize/backup-phrase'
+const INITIALIZE_CONFIRM_SEED_ROUTE = '/initialize/confirm-phrase'
+
+module.exports = {
+ DEFAULT_ROUTE,
+ UNLOCK_ROUTE,
+ SETTINGS_ROUTE,
+ INFO_ROUTE,
+ REVEAL_SEED_ROUTE,
+ CONFIRM_SEED_ROUTE,
+ RESTORE_VAULT_ROUTE,
+ ADD_TOKEN_ROUTE,
+ NEW_ACCOUNT_ROUTE,
+ IMPORT_ACCOUNT_ROUTE,
+ SEND_ROUTE,
+ CONFIRM_TRANSACTION_ROUTE,
+ NOTICE_ROUTE,
+ SIGNATURE_REQUEST_ROUTE,
+ WELCOME_ROUTE,
+ INITIALIZE_ROUTE,
+ INITIALIZE_CREATE_PASSWORD_ROUTE,
+ INITIALIZE_IMPORT_ACCOUNT_ROUTE,
+ INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
+ INITIALIZE_UNIQUE_IMAGE_ROUTE,
+ INITIALIZE_NOTICE_ROUTE,
+ INITIALIZE_BACKUP_PHRASE_ROUTE,
+ INITIALIZE_CONFIRM_SEED_ROUTE,
+}
diff --git a/ui/app/select-app.js b/ui/app/select-app.js
index 101eb1cf6..808f9ba56 100644
--- a/ui/app/select-app.js
+++ b/ui/app/select-app.js
@@ -2,11 +2,12 @@ const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
+const { HashRouter } = require('react-router-dom')
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
+const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/controllers/network/enums')
const I18nProvider = require('./i18n-provider')
function mapStateToProps (state) {
@@ -63,7 +64,12 @@ SelectedApp.prototype.render = function () {
// const Selected = betaUI || isMascara || firstTime ? App : OldApp
const { betaUI, isMascara } = this.props
- const Selected = betaUI || isMascara ? h(I18nProvider, [ h(App) ]) : h(OldApp)
- return Selected
+ return betaUI || isMascara
+ ? h(HashRouter, {
+ hashType: 'noslash',
+ }, [
+ h(I18nProvider, [ h(App) ]),
+ ])
+ : h(OldApp)
}
diff --git a/ui/app/selectors.js b/ui/app/selectors.js
index 2bdc39004..a29294b86 100644
--- a/ui/app/selectors.js
+++ b/ui/app/selectors.js
@@ -62,22 +62,15 @@ function getSelectedToken (state) {
}
function getSelectedTokenExchangeRate (state) {
- const tokenExchangeRates = state.metamask.tokenExchangeRates
+ const contractExchangeRates = state.metamask.contractExchangeRates
const selectedToken = getSelectedToken(state) || {}
- const { symbol = '' } = selectedToken
-
- const pair = `${symbol.toLowerCase()}_eth`
- const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {}
-
- return tokenExchangeRate
+ const { address } = selectedToken
+ return contractExchangeRates[address] || 0
}
-function getTokenExchangeRate (state, tokenSymbol) {
- const pair = `${tokenSymbol.toLowerCase()}_eth`
- const tokenExchangeRates = state.metamask.tokenExchangeRates
- const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {}
-
- return tokenExchangeRate
+function getTokenExchangeRate (state, address) {
+ const contractExchangeRates = state.metamask.contractExchangeRates
+ return contractExchangeRates[address] || 0
}
function conversionRateSelector (state) {
@@ -192,4 +185,4 @@ function autoAddToBetaUI (state) {
function getCurrentViewContext (state) {
const { currentView = {} } = state.appState
return currentView.context
-} \ No newline at end of file
+}
diff --git a/ui/app/unlock.js b/ui/app/unlock.js
index 84d8b7e7c..099e5f9c6 100644
--- a/ui/app/unlock.js
+++ b/ui/app/unlock.js
@@ -6,8 +6,9 @@ const connect = require('react-redux').connect
const actions = require('./actions')
const getCaretCoordinates = require('textarea-caret')
const EventEmitter = require('events').EventEmitter
-const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums
-const environmentType = require('../../app/scripts/lib/environment-type')
+const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/controllers/network/enums')
+const { getEnvironmentType } = require('../../app/scripts/lib/util')
+const { ENVIRONMENT_TYPE_POPUP } = require('../../app/scripts/lib/enums')
const Mascot = require('./components/mascot')
@@ -77,7 +78,7 @@ UnlockScreen.prototype.render = function () {
h('p.pointer', {
onClick: () => {
this.props.dispatch(actions.markPasswordForgotten())
- if (environmentType() === 'popup') {
+ if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
global.platform.openExtensionInBrowser()
}
},
diff --git a/ui/app/util.js b/ui/app/util.js
index bbe2bb09e..8e9390dfb 100644
--- a/ui/app/util.js
+++ b/ui/app/util.js
@@ -57,6 +57,7 @@ module.exports = {
isInvalidChecksumAddress,
allNull,
getTokenAddressFromTokenObject,
+ checksumAddress,
}
function valuesFor (obj) {
@@ -67,7 +68,7 @@ function valuesFor (obj) {
function addressSummary (address, firstSegLength = 10, lastSegLength = 4, includeHex = true) {
if (!address) return ''
- let checked = ethUtil.toChecksumAddress(address)
+ let checked = checksumAddress(address)
if (!includeHex) {
checked = ethUtil.stripHexPrefix(checked)
}
@@ -76,7 +77,7 @@ function addressSummary (address, firstSegLength = 10, lastSegLength = 4, includ
function miniAddressSummary (address) {
if (!address) return ''
- var checked = ethUtil.toChecksumAddress(address)
+ var checked = checksumAddress(address)
return checked ? checked.slice(0, 4) + '...' + checked.slice(-4) : '...'
}
@@ -287,3 +288,13 @@ function allNull (obj) {
function getTokenAddressFromTokenObject (token) {
return Object.values(token)[0].address.toLowerCase()
}
+
+/**
+ * Safely checksumms a potentially-null address
+ *
+ * @param {String} [address] - address to checksum
+ * @returns {String} - checksummed address
+ */
+function checksumAddress (address) {
+ return address ? ethUtil.toChecksumAddress(address) : ''
+}
diff --git a/ui/app/welcome-screen.js b/ui/app/welcome-screen.js
index cdbb6dba8..2fa244d9f 100644
--- a/ui/app/welcome-screen.js
+++ b/ui/app/welcome-screen.js
@@ -3,21 +3,35 @@ import h from 'react-hyperscript'
import { Component } from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import { compose } from 'recompose'
import {closeWelcomeScreen} from './actions'
import Mascot from './components/mascot'
+import { INITIALIZE_CREATE_PASSWORD_ROUTE } from './routes'
class WelcomeScreen extends Component {
static propTypes = {
closeWelcomeScreen: PropTypes.func.isRequired,
+ welcomeScreenSeen: PropTypes.bool,
+ history: PropTypes.object,
}
- constructor(props) {
+ constructor (props) {
super(props)
this.animationEventEmitter = new EventEmitter()
}
+ componentWillMount () {
+ const { history, welcomeScreenSeen } = this.props
+
+ if (welcomeScreenSeen) {
+ history.push(INITIALIZE_CREATE_PASSWORD_ROUTE)
+ }
+ }
+
initiateAccountCreation = () => {
this.props.closeWelcomeScreen()
+ this.props.history.push(INITIALIZE_CREATE_PASSWORD_ROUTE)
}
render () {
@@ -48,9 +62,18 @@ class WelcomeScreen extends Component {
}
}
-export default connect(
- null,
- dispatch => ({
- closeWelcomeScreen: () => dispatch(closeWelcomeScreen()),
- })
+const mapStateToProps = ({ metamask: { welcomeScreenSeen } }) => {
+ return {
+ welcomeScreenSeen,
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(
+ mapStateToProps,
+ dispatch => ({
+ closeWelcomeScreen: () => dispatch(closeWelcomeScreen()),
+ })
+ )
)(WelcomeScreen)