From e226b10a89d87af07c7c35ff1251a8264f3bb1b8 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 28 Nov 2017 20:24:35 -0800 Subject: Add react-router to allow use of the browser back button --- ui/app/account-detail.js | 4 +- ui/app/accounts/import/json.js | 100 --- ui/app/accounts/import/private-key.js | 71 -- ui/app/accounts/import/seed.js | 30 - ui/app/actions.js | 80 +- ui/app/add-token.js | 357 -------- ui/app/app.js | 908 +++++++++++---------- ui/app/components/account-menu/index.js | 25 +- ui/app/components/notice.js | 132 --- ui/app/components/pages/add-token.js | 359 ++++++++ ui/app/components/pages/authenticated.js | 42 + ui/app/components/pages/import-account/index.js | 95 +++ ui/app/components/pages/import-account/json.js | 100 +++ .../components/pages/import-account/private-key.js | 71 ++ ui/app/components/pages/import-account/seed.js | 30 + ui/app/components/pages/keychains/restore-vault.js | 167 ++++ ui/app/components/pages/keychains/reveal-seed.js | 192 +++++ ui/app/components/pages/notice.js | 219 +++++ ui/app/components/pages/settings/index.js | 59 ++ ui/app/components/pages/settings/info.js | 108 +++ ui/app/components/pages/settings/settings.js | 283 +++++++ ui/app/components/pages/unauthenticated/unlock.js | 172 ++++ ui/app/components/pending-tx/confirm-send-ether.js | 21 +- ui/app/components/pending-tx/confirm-send-token.js | 18 +- ui/app/components/send/send-v2-container.js | 7 +- ui/app/components/tab-bar.js | 26 +- ui/app/components/tx-list.js | 12 +- ui/app/components/tx-view.js | 14 +- ui/app/components/wallet-view.js | 14 +- ui/app/conf-tx.js | 32 +- ui/app/css/index.scss | 6 + ui/app/css/itcss/components/index.scss | 2 + ui/app/css/itcss/components/pages/index.scss | 1 + ui/app/css/itcss/components/pages/unlock.scss | 9 + ui/app/css/itcss/components/sections.scss | 6 + ui/app/first-time/init-menu.js | 293 +++---- ui/app/keychains/hd/create-vault-complete.js | 91 --- ui/app/keychains/hd/recover-seed/confirmation.js | 121 --- ui/app/keychains/hd/restore-vault.js | 152 ---- ui/app/main-container.js | 34 +- ui/app/root.js | 38 +- ui/app/routes.js | 27 + ui/app/send-v2.js | 10 +- ui/app/settings.js | 410 ---------- ui/app/unlock.js | 122 --- 45 files changed, 2791 insertions(+), 2279 deletions(-) delete mode 100644 ui/app/accounts/import/json.js delete mode 100644 ui/app/accounts/import/private-key.js delete mode 100644 ui/app/accounts/import/seed.js delete mode 100644 ui/app/add-token.js delete mode 100644 ui/app/components/notice.js create mode 100644 ui/app/components/pages/add-token.js create mode 100644 ui/app/components/pages/authenticated.js create mode 100644 ui/app/components/pages/import-account/index.js create mode 100644 ui/app/components/pages/import-account/json.js create mode 100644 ui/app/components/pages/import-account/private-key.js create mode 100644 ui/app/components/pages/import-account/seed.js create mode 100644 ui/app/components/pages/keychains/restore-vault.js create mode 100644 ui/app/components/pages/keychains/reveal-seed.js create mode 100644 ui/app/components/pages/notice.js create mode 100644 ui/app/components/pages/settings/index.js create mode 100644 ui/app/components/pages/settings/info.js create mode 100644 ui/app/components/pages/settings/settings.js create mode 100644 ui/app/components/pages/unauthenticated/unlock.js create mode 100644 ui/app/css/itcss/components/pages/index.scss create mode 100644 ui/app/css/itcss/components/pages/unlock.scss delete mode 100644 ui/app/keychains/hd/create-vault-complete.js delete mode 100644 ui/app/keychains/hd/recover-seed/confirmation.js delete mode 100644 ui/app/keychains/hd/restore-vault.js create mode 100644 ui/app/routes.js delete mode 100644 ui/app/settings.js delete mode 100644 ui/app/unlock.js (limited to 'ui') diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 0da435298..85951f512 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -71,8 +71,8 @@ AccountDetailScreen.prototype.tabSections = function () { { content: 'Sent', key: 'history' }, { content: 'Tokens', key: 'tokens' }, ], - defaultTab: currentAccountTab || 'history', - tabSelected: (key) => { + selectedTab: currentAccountTab || 'history', + onSelect: key => { this.props.dispatch(actions.setCurrentAccountTab(key)) }, }), diff --git a/ui/app/accounts/import/json.js b/ui/app/accounts/import/json.js deleted file mode 100644 index 486ed8886..000000000 --- a/ui/app/accounts/import/json.js +++ /dev/null @@ -1,100 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const actions = require('../../actions') -const FileInput = require('react-simple-file-input').default - -const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts' - -module.exports = connect(mapStateToProps)(JsonImportSubview) - -function mapStateToProps (state) { - return { - error: state.appState.warning, - } -} - -inherits(JsonImportSubview, Component) -function JsonImportSubview () { - Component.call(this) -} - -JsonImportSubview.prototype.render = function () { - const { error } = this.props - - return ( - h('div', { - style: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - padding: '5px 15px 0px 15px', - }, - }, [ - - h('p', 'Used by a variety of different clients'), - h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'), - - h(FileInput, { - readAs: 'text', - onLoad: this.onLoad.bind(this), - style: { - margin: '20px 0px 12px 20px', - fontSize: '15px', - }, - }), - - h('input.large-input.letter-spacey', { - type: 'password', - placeholder: 'Enter password', - id: 'json-password-box', - onKeyPress: this.createKeyringOnEnter.bind(this), - style: { - width: 260, - marginTop: 12, - }, - }), - - h('button.primary', { - onClick: this.createNewKeychain.bind(this), - style: { - margin: 12, - }, - }, 'Import'), - - error ? h('span.error', error) : null, - ]) - ) -} - -JsonImportSubview.prototype.onLoad = function (event, file) { - this.setState({file: file, fileContents: event.target.result}) -} - -JsonImportSubview.prototype.createKeyringOnEnter = function (event) { - if (event.key === 'Enter') { - event.preventDefault() - this.createNewKeychain() - } -} - -JsonImportSubview.prototype.createNewKeychain = function () { - const state = this.state - const { fileContents } = state - - if (!fileContents) { - const message = 'You must select a file to import.' - return this.props.dispatch(actions.displayWarning(message)) - } - - const passwordInput = document.getElementById('json-password-box') - const password = passwordInput.value - - if (!password) { - const message = 'You must enter a password for the selected file.' - return this.props.dispatch(actions.displayWarning(message)) - } - - this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) -} diff --git a/ui/app/accounts/import/private-key.js b/ui/app/accounts/import/private-key.js deleted file mode 100644 index e214bcbbe..000000000 --- a/ui/app/accounts/import/private-key.js +++ /dev/null @@ -1,71 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const actions = require('../../actions') - -module.exports = connect(mapStateToProps)(PrivateKeyImportView) - -function mapStateToProps (state) { - return { - error: state.appState.warning, - } -} - -inherits(PrivateKeyImportView, Component) -function PrivateKeyImportView () { - Component.call(this) -} - -PrivateKeyImportView.prototype.componentWillUnmount = function () { - this.props.dispatch(actions.displayWarning(null)) -} - -PrivateKeyImportView.prototype.render = function () { - const { error } = this.props - - return ( - h('div', { - style: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - padding: '5px 15px 0px 15px', - }, - }, [ - h('span', 'Paste your private key string here'), - - h('input.large-input.letter-spacey', { - type: 'password', - id: 'private-key-box', - onKeyPress: this.createKeyringOnEnter.bind(this), - style: { - width: 260, - marginTop: 12, - }, - }), - - h('button.primary', { - onClick: this.createNewKeychain.bind(this), - style: { - margin: 12, - }, - }, 'Import'), - - error ? h('span.error', error) : null, - ]) - ) -} - -PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) { - if (event.key === 'Enter') { - event.preventDefault() - this.createNewKeychain() - } -} - -PrivateKeyImportView.prototype.createNewKeychain = function () { - const input = document.getElementById('private-key-box') - const privateKey = input.value - this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ])) -} diff --git a/ui/app/accounts/import/seed.js b/ui/app/accounts/import/seed.js deleted file mode 100644 index b4a7c0afa..000000000 --- a/ui/app/accounts/import/seed.js +++ /dev/null @@ -1,30 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect - -module.exports = connect(mapStateToProps)(SeedImportSubview) - -function mapStateToProps (state) { - return {} -} - -inherits(SeedImportSubview, Component) -function SeedImportSubview () { - Component.call(this) -} - -SeedImportSubview.prototype.render = function () { - return ( - h('div', { - style: { - }, - }, [ - `Paste your seed phrase here!`, - h('textarea'), - h('br'), - h('button', 'Submit'), - ]) - ) -} - diff --git a/ui/app/actions.js b/ui/app/actions.js index ed0518184..51cc4dfbf 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -266,14 +266,18 @@ function tryUnlockMetamask (password) { dispatch(actions.showLoadingIndication()) dispatch(actions.unlockInProgress()) log.debug(`background.submitPassword`) - background.submitPassword(password, (err) => { - dispatch(actions.hideLoadingIndication()) - if (err) { - dispatch(actions.unlockFailed(err.message)) - } else { - dispatch(actions.transitionForward()) - forceUpdateMetamaskState(dispatch) - } + + return new Promise((resolve, reject) => { + background.submitPassword(password, err => { + dispatch(actions.hideLoadingIndication()) + + if (err) { + reject(err) + } else { + dispatch(actions.transitionForward()) + return forceUpdateMetamaskState(dispatch).then(resolve) + } + }) }) } } @@ -291,7 +295,7 @@ function transitionBackward () { } function confirmSeedWords () { - return (dispatch) => { + return dispatch => { dispatch(actions.showLoadingIndication()) log.debug(`background.clearSeedWordCache`) return new Promise((resolve, reject) => { @@ -299,7 +303,7 @@ function confirmSeedWords () { dispatch(actions.hideLoadingIndication()) if (err) { dispatch(actions.displayWarning(err.message)) - reject(err) + return reject(err) } log.info('Seed word cache cleared. ' + account) @@ -344,14 +348,14 @@ function createNewVaultAndKeychain (password) { return reject(err) } log.debug(`background.placeSeedWords`) + background.placeSeedWords((err) => { if (err) { dispatch(actions.displayWarning(err.message)) return reject(err) } dispatch(actions.hideLoadingIndication()) - forceUpdateMetamaskState(dispatch) - resolve() + forceUpdateMetamaskState(dispatch).then(resolve) }) }) }) @@ -696,16 +700,23 @@ 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)) + + 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) + }) }) } } @@ -751,10 +762,13 @@ function cancelTypedMsg (msgData) { } function cancelTx (txData) { - return (dispatch) => { + return dispatch => { log.debug(`background.cancelTransaction`) - background.cancelTransaction(txData.id, () => { - dispatch(actions.completedTx(txData.id)) + return new Promise((resolve, reject) => { + background.cancelTransaction(txData.id, () => { + dispatch(actions.completedTx(txData.id)) + resolve(txData) + }) }) } } @@ -1585,11 +1599,17 @@ function callBackgroundThenUpdate (method, ...args) { function forceUpdateMetamaskState (dispatch) { log.debug(`background.getState`) - background.getState((err, newState) => { - if (err) { - return dispatch(actions.displayWarning(err.message)) - } - dispatch(actions.updateMetamaskState(newState)) + + return new Promise((resolve, reject) => { + background.getState((err, newState) => { + if (err) { + reject(err) + return dispatch(actions.displayWarning(err.message)) + } + + dispatch(actions.updateMetamaskState(newState)) + resolve(newState) + }) }) } diff --git a/ui/app/add-token.js b/ui/app/add-token.js deleted file mode 100644 index 10aaae103..000000000 --- a/ui/app/add-token.js +++ /dev/null @@ -1,357 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const classnames = require('classnames') -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const Fuse = require('fuse.js') -const contractMap = require('eth-contract-metadata') -const TokenBalance = require('./components/token-balance') -const Identicon = require('./components/identicon') -const contractList = Object.entries(contractMap) - .map(([ _, tokenData]) => tokenData) - .filter(tokenData => Boolean(tokenData.erc20)) -const fuse = new Fuse(contractList, { - shouldSort: true, - threshold: 0.45, - location: 0, - distance: 100, - maxPatternLength: 32, - minMatchCharLength: 1, - keys: ['address', 'name', 'symbol'], -}) -const actions = require('./actions') -const ethUtil = require('ethereumjs-util') -const { tokenInfoGetter } = require('./token-util') -const R = require('ramda') - -const emptyAddr = '0x0000000000000000000000000000000000000000' - -module.exports = connect(mapStateToProps, mapDispatchToProps)(AddTokenScreen) - -function mapStateToProps (state) { - const { identities, tokens } = state.metamask - return { - identities, - tokens, - } -} - -function mapDispatchToProps (dispatch) { - return { - goHome: () => dispatch(actions.goHome()), - addTokens: tokens => dispatch(actions.addTokens(tokens)), - } -} - -inherits(AddTokenScreen, Component) -function AddTokenScreen () { - this.state = { - isShowingConfirmation: false, - customAddress: '', - customSymbol: '', - customDecimals: 0, - searchQuery: '', - isCollapsed: true, - selectedTokens: {}, - errors: {}, - } - this.tokenAddressDidChange = this.tokenAddressDidChange.bind(this) - this.onNext = this.onNext.bind(this) - Component.call(this) -} - -AddTokenScreen.prototype.componentWillMount = function () { - this.tokenInfoGetter = tokenInfoGetter() -} - -AddTokenScreen.prototype.toggleToken = function (address, token) { - const { selectedTokens, errors } = this.state - const { [address]: selectedToken } = selectedTokens - this.setState({ - selectedTokens: { - ...selectedTokens, - [address]: selectedToken ? null : token, - }, - errors: { - ...errors, - tokenSelector: null, - }, - }) -} - -AddTokenScreen.prototype.onNext = function () { - const { isValid, errors } = this.validate() - - return !isValid - ? this.setState({ errors }) - : this.setState({ isShowingConfirmation: true }) -} - -AddTokenScreen.prototype.tokenAddressDidChange = function (e) { - const customAddress = e.target.value.trim() - this.setState({ customAddress }) - if (ethUtil.isValidAddress(customAddress) && customAddress !== emptyAddr) { - this.attemptToAutoFillTokenParams(customAddress) - } else { - this.setState({ - customSymbol: '', - customDecimals: 0, - }) - } -} - -AddTokenScreen.prototype.checkExistingAddresses = function (address) { - if (!address) return false - const tokensList = this.props.tokens - const matchesAddress = existingToken => { - return existingToken.address.toLowerCase() === address.toLowerCase() - } - - return R.any(matchesAddress)(tokensList) -} - -AddTokenScreen.prototype.validate = function () { - const errors = {} - const identitiesList = Object.keys(this.props.identities) - const { customAddress, customSymbol, customDecimals, selectedTokens } = this.state - const standardAddress = ethUtil.addHexPrefix(customAddress).toLowerCase() - - if (customAddress) { - const validAddress = ethUtil.isValidAddress(customAddress) - if (!validAddress) { - errors.customAddress = 'Address is invalid. ' - } - - const validDecimals = customDecimals >= 0 && customDecimals < 36 - if (!validDecimals) { - errors.customDecimals = 'Decimals must be at least 0, and not over 36.' - } - - const symbolLen = customSymbol.trim().length - const validSymbol = symbolLen > 0 && symbolLen < 10 - if (!validSymbol) { - errors.customSymbol = 'Symbol must be between 0 and 10 characters.' - } - - const ownAddress = identitiesList.includes(standardAddress) - if (ownAddress) { - errors.customAddress = 'Personal address detected. Input the token contract address.' - } - - const tokenAlreadyAdded = this.checkExistingAddresses(customAddress) - if (tokenAlreadyAdded) { - errors.customAddress = 'Token has already been added.' - } - } else if ( - Object.entries(selectedTokens) - .reduce((isEmpty, [ symbol, isSelected ]) => ( - isEmpty && !isSelected - ), true) - ) { - errors.tokenSelector = 'Must select at least 1 token.' - } - - return { - isValid: !Object.keys(errors).length, - errors, - } -} - -AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) { - const { symbol, decimals } = await this.tokenInfoGetter(address) - if (symbol && decimals) { - this.setState({ - customSymbol: symbol, - customDecimals: decimals.toString(), - }) - } -} - -AddTokenScreen.prototype.renderCustomForm = function () { - const { customAddress, customSymbol, customDecimals, errors } = this.state - - return !this.state.isCollapsed && ( - h('div.add-token__add-custom-form', [ - h('div', { - className: classnames('add-token__add-custom-field', { - 'add-token__add-custom-field--error': errors.customAddress, - }), - }, [ - h('div.add-token__add-custom-label', 'Token Address'), - h('input.add-token__add-custom-input', { - type: 'text', - onChange: this.tokenAddressDidChange, - value: customAddress, - }), - h('div.add-token__add-custom-error-message', errors.customAddress), - ]), - h('div', { - className: classnames('add-token__add-custom-field', { - 'add-token__add-custom-field--error': errors.customSymbol, - }), - }, [ - h('div.add-token__add-custom-label', 'Token Symbol'), - h('input.add-token__add-custom-input', { - type: 'text', - value: customSymbol, - disabled: true, - }), - h('div.add-token__add-custom-error-message', errors.customSymbol), - ]), - h('div', { - className: classnames('add-token__add-custom-field', { - 'add-token__add-custom-field--error': errors.customDecimals, - }), - }, [ - h('div.add-token__add-custom-label', 'Decimals of Precision'), - h('input.add-token__add-custom-input', { - type: 'number', - value: customDecimals, - disabled: true, - }), - h('div.add-token__add-custom-error-message', errors.customDecimals), - ]), - ]) - ) -} - -AddTokenScreen.prototype.renderTokenList = function () { - const { searchQuery = '', selectedTokens } = this.state - const results = searchQuery - ? fuse.search(searchQuery) || [] - : contractList - - return Array(6).fill(undefined) - .map((_, i) => { - const { logo, symbol, name, address } = results[i] || {} - const tokenAlreadyAdded = this.checkExistingAddresses(address) - return Boolean(logo || symbol || name) && ( - h('div.add-token__token-wrapper', { - className: classnames({ - 'add-token__token-wrapper--selected': selectedTokens[address], - 'add-token__token-wrapper--disabled': tokenAlreadyAdded, - }), - onClick: () => !tokenAlreadyAdded && this.toggleToken(address, results[i]), - }, [ - h('div.add-token__token-icon', { - style: { - backgroundImage: `url(images/contract/${logo})`, - }, - }), - h('div.add-token__token-data', [ - h('div.add-token__token-symbol', symbol), - h('div.add-token__token-name', name), - ]), - // tokenAlreadyAdded && ( - // h('div.add-token__token-message', 'Already added') - // ), - ]) - ) - }) -} - -AddTokenScreen.prototype.renderConfirmation = function () { - const { - customAddress: address, - customSymbol: symbol, - customDecimals: decimals, - selectedTokens, - } = this.state - - const { addTokens, goHome } = this.props - - const customToken = { - address, - symbol, - decimals, - } - - const tokens = address && symbol && decimals - ? { ...selectedTokens, [address]: customToken } - : selectedTokens - - return ( - h('div.add-token', [ - h('div.add-token__wrapper', [ - h('div.add-token__title-container.add-token__confirmation-title', [ - h('div.add-token__title', 'Add Token'), - h('div.add-token__description', 'Would you like to add these tokens?'), - ]), - h('div.add-token__content-container.add-token__confirmation-content', [ - h('div.add-token__description.add-token__confirmation-description', 'Your balances'), - h('div.add-token__confirmation-token-list', - Object.entries(tokens) - .map(([ address, token ]) => ( - h('span.add-token__confirmation-token-list-item', [ - h(Identicon, { - className: 'add-token__confirmation-token-icon', - diameter: 75, - address, - }), - h(TokenBalance, { token }), - ]) - )) - ), - ]), - ]), - h('div.add-token__buttons', [ - h('button.btn-secondary', { - onClick: () => addTokens(tokens).then(goHome), - }, 'Add Tokens'), - h('button.btn-tertiary', { - onClick: () => this.setState({ isShowingConfirmation: false }), - }, 'Back'), - ]), - ]) - ) -} - -AddTokenScreen.prototype.render = function () { - const { isCollapsed, errors, isShowingConfirmation } = this.state - const { goHome } = this.props - - return isShowingConfirmation - ? this.renderConfirmation() - : ( - h('div.add-token', [ - h('div.add-token__wrapper', [ - h('div.add-token__title-container', [ - h('div.add-token__title', 'Add Token'), - h('div.add-token__description', 'Keep track of the tokens you’ve bought with your MetaMask account. If you bought tokens using a different account, those tokens will not appear here.'), - h('div.add-token__description', 'Search for tokens or select from our list of popular tokens.'), - ]), - h('div.add-token__content-container', [ - h('div.add-token__input-container', [ - h('input.add-token__input', { - type: 'text', - placeholder: 'Search', - onChange: e => this.setState({ searchQuery: e.target.value }), - }), - h('div.add-token__search-input-error-message', errors.tokenSelector), - ]), - h( - 'div.add-token__token-icons-container', - this.renderTokenList(), - ), - ]), - h('div.add-token__footers', [ - h('div.add-token__add-custom', { - onClick: () => this.setState({ isCollapsed: !isCollapsed }), - }, [ - 'Add custom token', - h(`i.fa.fa-angle-${isCollapsed ? 'down' : 'up'}`), - ]), - this.renderCustomForm(), - ]), - ]), - h('div.add-token__buttons', [ - h('button.btn-secondary', { - onClick: this.onNext, - }, 'Next'), - h('button.btn-tertiary', { - onClick: goHome, - }, 'Cancel'), - ]), - ]) - ) -} diff --git a/ui/app/app.js b/ui/app/app.js index 1f40eccbe..168ec6559 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -1,6 +1,7 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const connect = require('react-redux').connect +const { Component } = require('react') +const { connect } = require('react-redux') +const { Switch, Route, Redirect, withRouter } = require('react-router-dom') +const { compose } = require('recompose') const h = require('react-hyperscript') const actions = require('./actions') // mascara @@ -14,23 +15,25 @@ const MainContainer = require('./main-container') const SendTransactionScreen2 = require('./components/send/send-v2-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 Authenticated = require('./components/pages/authenticated') +const Settings = require('./components/pages/settings') +const UnlockPage = require('./components/pages/unauthenticated/unlock') +const RestoreVaultPage = require('./components/pages/keychains/restore-vault') +const RevealSeedPage = require('./components/pages/keychains/reveal-seed') +const AddTokenPage = require('./components/pages/add-token') +const ImportAccountPage = require('./components/pages/import-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') @@ -39,482 +42,565 @@ const QrView = require('./components/qr-code') // Global Modals const Modal = require('./components/modals/index').Modal -module.exports = connect(mapStateToProps, mapDispatchToProps)(App) - -inherits(App, Component) -function App () { Component.call(this) } - -function mapStateToProps (state) { - const { - identities, - accounts, - address, - keyrings, - isInitialized, - noActiveNotices, - seedWords, - } = state.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, - 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), - seedWords: state.metamask.seedWords, - unapprovedTxs: state.metamask.unapprovedTxs, - unapprovedMsgs: state.metamask.unapprovedMsgs, - menuOpen: state.appState.menuOpen, - network: state.metamask.network, - provider: state.metamask.provider, - forgottenPassword: state.appState.forgottenPassword, - lastUnreadNotice: state.metamask.lastUnreadNotice, - lostAccounts: state.metamask.lostAccounts, - frequentRpcList: state.metamask.frequentRpcList || [], - currentCurrency: state.metamask.currentCurrency, - - // state needed to get account dropdown temporarily rendering from app bar - identities, - selected, - keyrings, +// Routes +const { + DEFAULT_ROUTE, + UNLOCK_ROUTE, + SETTINGS_ROUTE, + REVEAL_SEED_ROUTE, + RESTORE_VAULT_ROUTE, + ADD_TOKEN_ROUTE, + IMPORT_ACCOUNT_ROUTE, + SEND_ROUTE, + CONFIRM_TRANSACTION_ROUTE, + INITIALIZE_MENU_ROUTE, + NOTICE_ROUTE, +} = require('./routes') + +class App extends Component { + constructor (props) { + super(props) + + this.renderPrimary = this.renderPrimary.bind(this) } -} -function mapDispatchToProps (dispatch, ownProps) { - return { - dispatch, - hideSidebar: () => dispatch(actions.hideSidebar()), - showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), - hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), - setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), - toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), + componentWillMount () { + const { currentCurrency, setCurrentCurrency } = this.props + + if (!currentCurrency) { + setCurrentCurrencyToUSD() + } } -} -App.prototype.componentWillMount = function () { - if (!this.props.currentCurrency) { - this.props.setCurrentCurrencyToUSD() + renderRoutes () { + const exact = true + + return ( + h(Switch, [ + h(Route, { path: INITIALIZE_MENU_ROUTE, exact, component: InitializeMenuScreen }), + h(Route, { path: UNLOCK_ROUTE, exact, component: UnlockPage }), + h(Route, { path: SETTINGS_ROUTE, component: Settings }), + h(Route, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }), + h(Route, { path: NOTICE_ROUTE, exact, component: NoticeScreen }), + h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, exact, component: ConfirmTxScreen }), + h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }), + h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedPage }), + h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }), + h(Authenticated, { path: IMPORT_ACCOUNT_ROUTE, exact, component: ImportAccountPage }), + h(Authenticated, { path: DEFAULT_ROUTE, exact, component: this.renderPrimary }), + ]) + ) } -} -App.prototype.render = function () { - var props = this.props - const { isLoading, loadingMessage, network } = props - const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config' - const loadMessage = loadingMessage || isLoadingNetwork ? - `Connecting to ${this.getNetworkName()}` : null - log.debug('Main ui render function') - - return ( - h('.flex-column.full-height', { - style: { - overflowX: 'hidden', - position: 'relative', - alignItems: 'center', - }, - }, [ + render () { + var props = this.props + const { isLoading, loadingMessage, network } = props + const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config' + const loadMessage = loadingMessage || isLoadingNetwork ? + `Connecting to ${this.getNetworkName()}` : null + log.debug('Main ui render function') - // global modal - h(Modal, {}, []), + return ( + h('.flex-column.full-height', { + style: { + overflowX: 'hidden', + position: 'relative', + alignItems: 'center', + }, + }, [ - // app bar - this.renderAppBar(), + // global modal + h(Modal, {}, []), - // sidebar - this.renderSidebar(), + // app bar + this.renderAppBar(), - // network dropdown - h(NetworkDropdown, { - provider: this.props.provider, - frequentRpcList: this.props.frequentRpcList, - }, []), + // sidebar + this.renderSidebar(), - h(AccountMenu), + // network dropdown + h(NetworkDropdown, { + provider: this.props.provider, + frequentRpcList: this.props.frequentRpcList, + }, []), - (isLoading || isLoadingNetwork) && h(Loading, { - loadingMessage: loadMessage, - }), + h(AccountMenu), - // this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }), + (isLoading || isLoadingNetwork) && h(Loading, { + loadingMessage: loadMessage, + }), - // content - this.renderPrimary(), - ]) - ) -} + // this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }), -App.prototype.renderGlobalModal = function () { - return h(Modal, { - ref: 'modalRef', - }, [ - // h(BuyOptions, {}, []), - ]) -} + // content + this.renderRoutes(), + // this.renderPrimary(), + ]) + ) + } -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, + renderGlobalModal () { + return h(Modal, { + ref: 'modalRef', }, [ - // 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, - } = this.props - - if (window.METAMASK_UI_TYPE === 'notification') { - return null + // h(BuyOptions, {}, []), + ]) } - const props = this.props - const {isMascara, isOnboarding} = props + 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, - // 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 + // overlay + // TODO: add onClick for overlay to close sidebar + this.props.sidebarOpen ? h('div.sidebar-overlay', { + style: {}, + onClick: () => { + this.props.hideSidebar() + }, + }, []) : undefined, + ]) } - return ( + renderAppBar () { + const { + isUnlocked, + network, + provider, + networkDropdownOpen, + showNetworkDropdown, + hideNetworkDropdown, + currentView, + isMascara, + isOnboarding, + history, + } = this.props + + if (window.METAMASK_UI_TYPE === 'notification') { + return null + } + + // Do not render header if user is in mascara onboarding + if (isMascara && isOnboarding) { + return null + } - h('.full-width', { - style: {}, - }, [ + // Do not render header if user is in mascara buy ether + if (isMascara && currentView.name === 'buyEth') { + return null + } + + return ( - h('.app-header.flex-row.flex-space-between', { + h('.full-width', { style: {}, }, [ - h('div.app-header-contents', {}, [ - h('div.left-menu-wrapper', { - onClick: () => { - props.dispatch(actions.backToAccountDetail(props.activeAddress)) - }, - }, [ - // mini logo - h('img.metafox-icon', { - height: 29, - width: 29, - src: '/images/icon-128.png', - }), - - // metamask name - h('h1', { - style: { - position: 'relative', - paddingLeft: '9px', - color: '#5B5D67', - }, - }, 'MetaMask'), - - ]), - h('div.header__right-actions', [ - h('div.network-component-wrapper', { - style: {}, + h('.app-header.flex-row.flex-space-between', { + style: {}, + }, [ + h('div.app-header-contents', {}, [ + h('div.left-menu-wrapper', { + onClick: () => history.push(DEFAULT_ROUTE), }, [ - // Network Indicator - h(NetworkIndicator, { - network, - provider, - disabled: currentView.name === 'confTx', - onClick: (event) => { - event.preventDefault() - event.stopPropagation() - return networkDropdownOpen === false - ? showNetworkDropdown() - : hideNetworkDropdown() - }, + // mini logo + h('img.metafox-icon', { + height: 29, + width: 29, + src: '/images/icon-128.png', }), + // metamask name + h('h1', { + style: { + position: 'relative', + paddingLeft: '9px', + color: '#5B5D67', + }, + }, 'MetaMask'), + ]), - isUnlocked && h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [ - h(Identicon, { - address: this.props.selectedAddress, - diameter: 32, - }), + 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, + }), + ]), ]), ]), ]), - ]), - - ]) - ) -} - - -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} = props + renderLoadingIndicator ({ isLoading, isLoadingNetwork, loadMessage }) { + const { isMascara } = this.props - if (isMascara && isOnboarding) { - return h(MascaraFirstTime) + return isMascara + ? null + : h(Loading, { + isLoading: isLoading || isLoadingNetwork, + loadingMessage: loadMessage, + }) } - // notices - 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()), - }) - } + renderBackButton (style, justArrow = false) { + const { dispatch } = this.props - if (props.seedWords) { - log.debug('rendering seed words') - return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'}) + return ( + h('.flex-row', { + key: 'leftArrow', + style: style, + onClick: () => dispatch(actions.goBackToInitView()), + }, [ + h('i.fa.fa-arrow-left.cursor-pointer'), + justArrow ? null : h('div.cursor-pointer', { + style: { + marginLeft: '3px', + }, + onClick: () => dispatch(actions.goBackToInitView()), + }, 'BACK'), + ]) + ) } - // show initialize screen - if (!props.isInitialized || props.forgottenPassword) { - // show current view - log.debug('rendering an initialize screen') - switch (props.currentView.name) { + renderPrimary () { + log.debug('rendering primary') + const { + isMascara, + isOnboarding, + noActiveNotices, + lostAccounts, + isInitialized, + forgottenPassword, + currentView, + activeAddress, + unapprovedTxs = {}, + } = this.props + + if (isMascara && isOnboarding) { + return h(MascaraFirstTime) + } - case 'restoreVault': - log.debug('rendering restore vault screen') - return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'}) + // notices + if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) { + return h(Redirect, { + to: { + pathname: NOTICE_ROUTE, + }, + }) + } - default: - log.debug('rendering menu screen') - return h(InitializeMenuScreen, {key: 'menuScreenInit'}) + // unapprovedTxs + if (Object.keys(unapprovedTxs).length) { + return h(Redirect, { + to: { + pathname: CONFIRM_TRANSACTION_ROUTE, + }, + }) } - } - // show unlock screen - if (!props.isUnlocked) { - return h(MainContainer, { - currentViewName: props.currentView.name, - isUnlocked: props.isUnlocked, - }) - } + // 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(InitializeMenuScreen, {key: 'menuScreenInit'}) + // } + } - // show current view - switch (props.currentView.name) { + // // show unlock screen + // if (!props.isUnlocked) { + // return h(MainContainer, { + // currentViewName: props.currentView.name, + // isUnlocked: props.isUnlocked, + // }) + // } - case 'accountDetail': - log.debug('rendering main container') - return h(MainContainer, {key: 'account-detail'}) + // show current view + switch (currentView.name) { - case 'sendTransaction': - log.debug('rendering send tx screen') + case 'accountDetail': + log.debug('rendering main container') + return h(MainContainer, {key: 'account-detail'}) - // Going to leave this here until we are ready to delete SendTransactionScreen v1 - // const SendComponentToRender = checkFeatureToggle('send-v2') - // ? SendTransactionScreen2 - // : SendTransactionScreen + // case 'sendTransaction': + // log.debug('rendering send tx screen') - return h(SendTransactionScreen2, {key: 'send-transaction'}) + // // Going to leave this here until we are ready to delete SendTransactionScreen v1 + // // const SendComponentToRender = checkFeatureToggle('send-v2') + // // ? SendTransactionScreen2 + // // : SendTransactionScreen - case 'sendToken': - log.debug('rendering send token screen') + // return h(SendTransactionScreen2, {key: 'send-transaction'}) - // Going to leave this here until we are ready to delete SendTransactionScreen v1 - // const SendTokenComponentToRender = checkFeatureToggle('send-v2') - // ? SendTransactionScreen2 - // : SendTokenScreen + // case 'sendToken': + // log.debug('rendering send token screen') - return h(SendTransactionScreen2, {key: 'sendToken'}) + // // Going to leave this here until we are ready to delete SendTransactionScreen v1 + // // const SendTokenComponentToRender = checkFeatureToggle('send-v2') + // // ? SendTransactionScreen2 + // // : SendTokenScreen - case 'newKeychain': - log.debug('rendering new keychain screen') - return h(NewKeyChainScreen, {key: 'new-keychain'}) + // return h(SendTransactionScreen2, {key: 'sendToken'}) - case 'confTx': - log.debug('rendering confirm tx screen') - return h(ConfirmTxScreen, {key: 'confirm-tx'}) + case 'newKeychain': + log.debug('rendering new keychain screen') + return h(NewKeyChainScreen, {key: 'new-keychain'}) - case 'add-token': - log.debug('rendering add-token screen from unlock screen.') - return h(AddTokenScreen, {key: 'add-token'}) + // case 'confTx': + // log.debug('rendering confirm tx screen') + // return h(Redirect, { + // to: { + // pathname: CONFIRM_TRANSACTION_ROUTE, + // }, + // }) + // return h(ConfirmTxScreen, {key: 'confirm-tx'}) - case 'config': - log.debug('rendering config screen') - return h(Settings, {key: 'config'}) + // case 'add-token': + // log.debug('rendering add-token screen from unlock screen.') + // return h(AddTokenScreen, {key: 'add-token'}) - case 'import-menu': - log.debug('rendering import screen') - return h(Import, {key: 'import-menu'}) + // case 'config': + // log.debug('rendering config screen') + // return h(Settings, {key: 'config'}) - case 'reveal-seed-conf': - log.debug('rendering reveal seed confirmation screen') - return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'}) + // case 'import-menu': + // log.debug('rendering import screen') + // return h(Import, {key: 'import-menu'}) - case 'info': - log.debug('rendering info screen') - return h(Settings, {key: 'info', tab: 'info'}) + // case 'reveal-seed-conf': + // log.debug('rendering reveal seed confirmation screen') + // return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'}) - case 'buyEth': - log.debug('rendering buy ether screen') - return h(BuyView, {key: 'buyEthView'}) + // case 'info': + // log.debug('rendering info screen') + // return h(Settings, {key: 'info', tab: 'info'}) - case 'onboardingBuyEth': - log.debug('rendering onboarding buy ether screen') - return h(MascaraBuyEtherScreen, {key: 'buyEthView'}) + case 'buyEth': + log.debug('rendering buy ether screen') + return h(BuyView, {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', { + 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', - left: '44px', - width: '285px', + height: '100%', + top: '0px', + left: '0px', }, }, [ - h(QrView, {key: 'qr'}), - ]), - ]) + 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'}) + 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)) + 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)) + } + } + + getNetworkName () { + const { provider } = this.props + const providerName = provider.type + + let name + + if (providerName === 'mainnet') { + name = 'Main Ethereum Network' + } else if (providerName === 'ropsten') { + name = 'Ropsten Test Network' + } else if (providerName === 'kovan') { + name = 'Kovan Test Network' + } else if (providerName === 'rinkeby') { + name = 'Rinkeby Test Network' + } else { + name = 'Unknown Private Network' + } + + return name } } -App.prototype.getNetworkName = function () { - const { provider } = this.props - const providerName = provider.type - - let name - - if (providerName === 'mainnet') { - name = 'Main Ethereum Network' - } else if (providerName === 'ropsten') { - name = 'Ropsten Test Network' - } else if (providerName === 'kovan') { - name = 'Kovan Test Network' - } else if (providerName === 'rinkeby') { - name = 'Rinkeby Test Network' - } else { - name = 'Unknown Private Network' +function mapStateToProps (state) { + const { appState, metamask } = state + const { + networkDropdownOpen, + sidebarOpen, + isLoading, + loadingMessage, + } = appState + + const { + identities, + accounts, + address, + keyrings, + isInitialized, + noActiveNotices, + seedWords, + unapprovedTxs, + lastUnreadNotice, + lostAccounts, + } = 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), + seedWords: state.metamask.seedWords, + unapprovedTxs, + unapprovedMsgs: state.metamask.unapprovedMsgs, + 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, + + // state needed to get account dropdown temporarily rendering from app bar + identities, + selected, + keyrings, } +} - return name +function mapDispatchToProps (dispatch, ownProps) { + return { + dispatch, + hideSidebar: () => dispatch(actions.hideSidebar()), + showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), + hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), + setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), + toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), + } } + +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(App) diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js index a9f075ec7..0965cba38 100644 --- a/ui/app/components/account-menu/index.js +++ b/ui/app/components/account-menu/index.js @@ -1,13 +1,19 @@ const inherits = require('util').inherits const Component = require('react').Component const connect = require('react-redux').connect +const { compose } = require('recompose') +const { withRouter } = require('react-router-dom') 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, IMPORT_ACCOUNT_ROUTE } = require('../../routes') -module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountMenu) +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(AccountMenu) inherits(AccountMenu, Component) function AccountMenu () { Component.call(this) } @@ -19,7 +25,6 @@ function mapStateToProps (state) { keyrings: state.metamask.keyrings, identities: state.metamask.identities, accounts: state.metamask.accounts, - } } @@ -63,6 +68,7 @@ AccountMenu.prototype.render = function () { lockMetamask, showConfigPage, showInfoPage, + history, } = this.props return h(Menu, { className: 'account-menu', isShowing: isAccountMenuOpen }, [ @@ -84,18 +90,27 @@ AccountMenu.prototype.render = function () { text: 'Create Account', }), h(Item, { - onClick: showImportPage, + onClick: () => { + toggleAccountMenu() + history.push(IMPORT_ACCOUNT_ROUTE) + }, icon: h('img', { src: 'images/import-account.svg' }), text: 'Import Account', }), h(Divider), h(Item, { - onClick: showInfoPage, + onClick: () => { + toggleAccountMenu() + history.push(INFO_ROUTE) + }, icon: h('img', { src: 'images/mm-info-icon.svg' }), text: 'Info & Help', }), h(Item, { - onClick: showConfigPage, + onClick: () => { + toggleAccountMenu() + history.push(SETTINGS_ROUTE) + }, icon: h('img', { src: 'images/settings.svg' }), text: 'Settings', }), diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js deleted file mode 100644 index 941ac33e6..000000000 --- a/ui/app/components/notice.js +++ /dev/null @@ -1,132 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const ReactMarkdown = require('react-markdown') -const linker = require('extension-link-enabler') -const findDOMNode = require('react-dom').findDOMNode - -module.exports = Notice - -inherits(Notice, Component) -function Notice () { - Component.call(this) -} - -Notice.prototype.render = function () { - const { notice, onConfirm } = this.props - const { title, date, body } = notice - const state = this.state || { disclaimerDisabled: true } - const disabled = state.disclaimerDisabled - - 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, - onClick: () => { - this.setState({disclaimerDisabled: true}) - onConfirm() - }, - style: { - marginTop: '18px', - }, - }, 'Accept'), - ]) - ) -} - -Notice.prototype.componentDidMount = function () { - // 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}) - } -} - -Notice.prototype.componentWillUnmount = function () { - // eslint-disable-next-line react/no-find-dom-node - var node = findDOMNode(this) - linker.teardownListener(node) -} diff --git a/ui/app/components/pages/add-token.js b/ui/app/components/pages/add-token.js new file mode 100644 index 000000000..7f0c37475 --- /dev/null +++ b/ui/app/components/pages/add-token.js @@ -0,0 +1,359 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const classnames = require('classnames') +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const Fuse = require('fuse.js') +const contractMap = require('eth-contract-metadata') +const TokenBalance = require('../../components/token-balance') +const Identicon = require('../../components/identicon') +const contractList = Object.entries(contractMap) + .map(([ _, tokenData]) => tokenData) + .filter(tokenData => Boolean(tokenData.erc20)) +const fuse = new Fuse(contractList, { + shouldSort: true, + threshold: 0.45, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 1, + keys: ['address', 'name', 'symbol'], +}) +// const actions = require('./actions') +const actions = require('../../actions') +const ethUtil = require('ethereumjs-util') +const { tokenInfoGetter } = require('../../token-util') +const R = require('ramda') +const { DEFAULT_ROUTE } = require('../../routes') + +const emptyAddr = '0x0000000000000000000000000000000000000000' + +module.exports = connect(mapStateToProps, mapDispatchToProps)(AddTokenScreen) + +function mapStateToProps (state) { + const { identities, tokens } = state.metamask + return { + identities, + tokens, + } +} + +function mapDispatchToProps (dispatch) { + return { + goHome: () => dispatch(actions.goHome()), + addTokens: tokens => dispatch(actions.addTokens(tokens)), + } +} + +inherits(AddTokenScreen, Component) +function AddTokenScreen () { + this.state = { + isShowingConfirmation: false, + customAddress: '', + customSymbol: '', + customDecimals: 0, + searchQuery: '', + isCollapsed: true, + selectedTokens: {}, + errors: {}, + } + this.tokenAddressDidChange = this.tokenAddressDidChange.bind(this) + this.onNext = this.onNext.bind(this) + Component.call(this) +} + +AddTokenScreen.prototype.componentWillMount = function () { + this.tokenInfoGetter = tokenInfoGetter() +} + +AddTokenScreen.prototype.toggleToken = function (address, token) { + const { selectedTokens, errors } = this.state + const { [address]: selectedToken } = selectedTokens + this.setState({ + selectedTokens: { + ...selectedTokens, + [address]: selectedToken ? null : token, + }, + errors: { + ...errors, + tokenSelector: null, + }, + }) +} + +AddTokenScreen.prototype.onNext = function () { + const { isValid, errors } = this.validate() + + return !isValid + ? this.setState({ errors }) + : this.setState({ isShowingConfirmation: true }) +} + +AddTokenScreen.prototype.tokenAddressDidChange = function (e) { + const customAddress = e.target.value.trim() + this.setState({ customAddress }) + if (ethUtil.isValidAddress(customAddress) && customAddress !== emptyAddr) { + this.attemptToAutoFillTokenParams(customAddress) + } else { + this.setState({ + customSymbol: '', + customDecimals: 0, + }) + } +} + +AddTokenScreen.prototype.checkExistingAddresses = function (address) { + if (!address) return false + const tokensList = this.props.tokens + const matchesAddress = existingToken => { + return existingToken.address.toLowerCase() === address.toLowerCase() + } + + return R.any(matchesAddress)(tokensList) +} + +AddTokenScreen.prototype.validate = function () { + const errors = {} + const identitiesList = Object.keys(this.props.identities) + const { customAddress, customSymbol, customDecimals, selectedTokens } = this.state + const standardAddress = ethUtil.addHexPrefix(customAddress).toLowerCase() + + if (customAddress) { + const validAddress = ethUtil.isValidAddress(customAddress) + if (!validAddress) { + errors.customAddress = 'Address is invalid. ' + } + + const validDecimals = customDecimals >= 0 && customDecimals < 36 + if (!validDecimals) { + errors.customDecimals = 'Decimals must be at least 0, and not over 36.' + } + + const symbolLen = customSymbol.trim().length + const validSymbol = symbolLen > 0 && symbolLen < 10 + if (!validSymbol) { + errors.customSymbol = 'Symbol must be between 0 and 10 characters.' + } + + const ownAddress = identitiesList.includes(standardAddress) + if (ownAddress) { + errors.customAddress = 'Personal address detected. Input the token contract address.' + } + + const tokenAlreadyAdded = this.checkExistingAddresses(customAddress) + if (tokenAlreadyAdded) { + errors.customAddress = 'Token has already been added.' + } + } else if ( + Object.entries(selectedTokens) + .reduce((isEmpty, [ symbol, isSelected ]) => ( + isEmpty && !isSelected + ), true) + ) { + errors.tokenSelector = 'Must select at least 1 token.' + } + + return { + isValid: !Object.keys(errors).length, + errors, + } +} + +AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) { + const { symbol, decimals } = await this.tokenInfoGetter(address) + if (symbol && decimals) { + this.setState({ + customSymbol: symbol, + customDecimals: decimals.toString(), + }) + } +} + +AddTokenScreen.prototype.renderCustomForm = function () { + const { customAddress, customSymbol, customDecimals, errors } = this.state + + return !this.state.isCollapsed && ( + h('div.add-token__add-custom-form', [ + h('div', { + className: classnames('add-token__add-custom-field', { + 'add-token__add-custom-field--error': errors.customAddress, + }), + }, [ + h('div.add-token__add-custom-label', 'Token Address'), + h('input.add-token__add-custom-input', { + type: 'text', + onChange: this.tokenAddressDidChange, + value: customAddress, + }), + h('div.add-token__add-custom-error-message', errors.customAddress), + ]), + h('div', { + className: classnames('add-token__add-custom-field', { + 'add-token__add-custom-field--error': errors.customSymbol, + }), + }, [ + h('div.add-token__add-custom-label', 'Token Symbol'), + h('input.add-token__add-custom-input', { + type: 'text', + value: customSymbol, + disabled: true, + }), + h('div.add-token__add-custom-error-message', errors.customSymbol), + ]), + h('div', { + className: classnames('add-token__add-custom-field', { + 'add-token__add-custom-field--error': errors.customDecimals, + }), + }, [ + h('div.add-token__add-custom-label', 'Decimals of Precision'), + h('input.add-token__add-custom-input', { + type: 'number', + value: customDecimals, + disabled: true, + }), + h('div.add-token__add-custom-error-message', errors.customDecimals), + ]), + ]) + ) +} + +AddTokenScreen.prototype.renderTokenList = function () { + const { searchQuery = '', selectedTokens } = this.state + const results = searchQuery + ? fuse.search(searchQuery) || [] + : contractList + + return Array(6).fill(undefined) + .map((_, i) => { + const { logo, symbol, name, address } = results[i] || {} + const tokenAlreadyAdded = this.checkExistingAddresses(address) + return Boolean(logo || symbol || name) && ( + h('div.add-token__token-wrapper', { + className: classnames({ + 'add-token__token-wrapper--selected': selectedTokens[address], + 'add-token__token-wrapper--disabled': tokenAlreadyAdded, + }), + onClick: () => !tokenAlreadyAdded && this.toggleToken(address, results[i]), + }, [ + h('div.add-token__token-icon', { + style: { + backgroundImage: `url(images/contract/${logo})`, + }, + }), + h('div.add-token__token-data', [ + h('div.add-token__token-symbol', symbol), + h('div.add-token__token-name', name), + ]), + // tokenAlreadyAdded && ( + // h('div.add-token__token-message', 'Already added') + // ), + ]) + ) + }) +} + +AddTokenScreen.prototype.renderConfirmation = function () { + const { + customAddress: address, + customSymbol: symbol, + customDecimals: decimals, + selectedTokens, + } = this.state + + const { addTokens, history } = this.props + + const customToken = { + address, + symbol, + decimals, + } + + const tokens = address && symbol && decimals + ? { ...selectedTokens, [address]: customToken } + : selectedTokens + + return ( + h('div.add-token', [ + h('div.add-token__wrapper', [ + h('div.add-token__title-container.add-token__confirmation-title', [ + h('div.add-token__title', 'Add Token'), + h('div.add-token__description', 'Would you like to add these tokens?'), + ]), + h('div.add-token__content-container.add-token__confirmation-content', [ + h('div.add-token__description.add-token__confirmation-description', 'Your balances'), + h('div.add-token__confirmation-token-list', + Object.entries(tokens) + .map(([ address, token ]) => ( + h('span.add-token__confirmation-token-list-item', [ + h(Identicon, { + className: 'add-token__confirmation-token-icon', + diameter: 75, + address, + }), + h(TokenBalance, { token }), + ]) + )) + ), + ]), + ]), + h('div.add-token__buttons', [ + h('button.btn-secondary', { + onClick: () => addTokens(tokens).then(() => history.push(DEFAULT_ROUTE)), + }, 'Add Tokens'), + h('button.btn-tertiary', { + onClick: () => this.setState({ isShowingConfirmation: false }), + }, 'Back'), + ]), + ]) + ) +} + +AddTokenScreen.prototype.render = function () { + const { isCollapsed, errors, isShowingConfirmation } = this.state + const { history } = this.props + + return isShowingConfirmation + ? this.renderConfirmation() + : ( + h('div.add-token', [ + h('div.add-token__wrapper', [ + h('div.add-token__title-container', [ + h('div.add-token__title', 'Add Token'), + h('div.add-token__description', 'Keep track of the tokens you’ve bought with your MetaMask account. If you bought tokens using a different account, those tokens will not appear here.'), + h('div.add-token__description', 'Search for tokens or select from our list of popular tokens.'), + ]), + h('div.add-token__content-container', [ + h('div.add-token__input-container', [ + h('input.add-token__input', { + type: 'text', + placeholder: 'Search', + onChange: e => this.setState({ searchQuery: e.target.value }), + }), + h('div.add-token__search-input-error-message', errors.tokenSelector), + ]), + h( + 'div.add-token__token-icons-container', + this.renderTokenList(), + ), + ]), + h('div.add-token__footers', [ + h('div.add-token__add-custom', { + onClick: () => this.setState({ isCollapsed: !isCollapsed }), + }, [ + 'Add custom token', + h(`i.fa.fa-angle-${isCollapsed ? 'down' : 'up'}`), + ]), + this.renderCustomForm(), + ]), + ]), + h('div.add-token__buttons', [ + h('button.btn-secondary', { + onClick: this.onNext, + }, 'Next'), + h('button.btn-tertiary', { + onClick: () => history.goBack(), + }, 'Cancel'), + ]), + ]) + ) +} diff --git a/ui/app/components/pages/authenticated.js b/ui/app/components/pages/authenticated.js new file mode 100644 index 000000000..78f3a4225 --- /dev/null +++ b/ui/app/components/pages/authenticated.js @@ -0,0 +1,42 @@ +const { connect } = require('react-redux') +const PropTypes = require('prop-types') +const { Redirect, Route } = require('react-router-dom') +const h = require('react-hyperscript') +const { UNLOCK_ROUTE, INITIALIZE_MENU_ROUTE } = require('../../routes') + +const Authenticated = ({ component: Component, isUnlocked, isInitialized, ...props }) => { + + const render = props => { + switch (true) { + case isUnlocked: + return h(Component, { ...props }) + case !isInitialized: + return h(Redirect, { to: { pathname: INITIALIZE_MENU_ROUTE } }) + default: + return h(Redirect, { to: { pathname: UNLOCK_ROUTE } }) + } + } + + return ( + h(Route, { + ...props, + render, + }) + ) +} + +Authenticated.propTypes = { + component: PropTypes.func, + 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/components/pages/import-account/index.js b/ui/app/components/pages/import-account/index.js new file mode 100644 index 000000000..481ed6a4b --- /dev/null +++ b/ui/app/components/pages/import-account/index.js @@ -0,0 +1,95 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const PropTypes = require('prop-types') +import Select from 'react-select' + +// Subviews +const JsonImportView = require('./json.js') +const PrivateKeyImportView = require('./private-key.js') + +const PRIVATE_KEY_MENU_ITEM = 'Private Key' +const JSON_FILE_MENU_ITEM = 'JSON File' + +class ImportAccount extends Component { + constructor (props) { + super(props) + + this.state = { + current: PRIVATE_KEY_MENU_ITEM, + menuItems: [ PRIVATE_KEY_MENU_ITEM, JSON_FILE_MENU_ITEM ], + } + } + + renderImportView () { + const { current } = this.state + + switch (current) { + case 'Private Key': + return h(PrivateKeyImportView) + case 'JSON File': + return h(JsonImportView) + default: + return h(JsonImportView) + } + } + + render () { + const { history } = this.props + const { current, menuItems } = this.state + + return ( + h('div.flex-center', { + style: { + flexDirection: 'column', + marginTop: '32px', + }, + }, [ + h('.section-title.flex-row.flex-center', [ + h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { + onClick: history.goBack, + }), + h('h2.page-subtitle', 'Import Accounts'), + ]), + h('div', { + style: { + padding: '10px 0', + width: '260px', + color: 'rgb(174, 174, 174)', + }, + }, [ + + h('h3', { style: { padding: '3px' } }, 'SELECT TYPE'), + + h('style', ` + .has-value.Select--single > .Select-control .Select-value .Select-value-label, .Select-value-label { + color: rgb(174,174,174); + } + `), + + h(Select, { + name: 'import-type-select', + clearable: false, + value: current, + options: menuItems.map(type => { + return { + value: type, + label: type, + } + }), + onChange: opt => { + this.setState({ current: opt.value }) + }, + }), + ]), + + this.renderImportView(), + ]) + ) + } +} + +ImportAccount.propTypes = { + history: PropTypes.object, +} + +module.exports = ImportAccount diff --git a/ui/app/components/pages/import-account/json.js b/ui/app/components/pages/import-account/json.js new file mode 100644 index 000000000..63a44b4b7 --- /dev/null +++ b/ui/app/components/pages/import-account/json.js @@ -0,0 +1,100 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const actions = require('../../../actions') +const FileInput = require('react-simple-file-input').default + +const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts' + +module.exports = connect(mapStateToProps)(JsonImportSubview) + +function mapStateToProps (state) { + return { + error: state.appState.warning, + } +} + +inherits(JsonImportSubview, Component) +function JsonImportSubview () { + Component.call(this) +} + +JsonImportSubview.prototype.render = function () { + const { error } = this.props + + return ( + h('div', { + style: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: '5px 15px 0px 15px', + }, + }, [ + + h('p', 'Used by a variety of different clients'), + h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'), + + h(FileInput, { + readAs: 'text', + onLoad: this.onLoad.bind(this), + style: { + margin: '20px 0px 12px 20px', + fontSize: '15px', + }, + }), + + h('input.large-input.letter-spacey', { + type: 'password', + placeholder: 'Enter password', + id: 'json-password-box', + onKeyPress: this.createKeyringOnEnter.bind(this), + style: { + width: 260, + marginTop: 12, + }, + }), + + h('button.primary', { + onClick: this.createNewKeychain.bind(this), + style: { + margin: 12, + }, + }, 'Import'), + + error ? h('span.error', error) : null, + ]) + ) +} + +JsonImportSubview.prototype.onLoad = function (event, file) { + this.setState({file: file, fileContents: event.target.result}) +} + +JsonImportSubview.prototype.createKeyringOnEnter = function (event) { + if (event.key === 'Enter') { + event.preventDefault() + this.createNewKeychain() + } +} + +JsonImportSubview.prototype.createNewKeychain = function () { + const state = this.state + const { fileContents } = state + + if (!fileContents) { + const message = 'You must select a file to import.' + return this.props.dispatch(actions.displayWarning(message)) + } + + const passwordInput = document.getElementById('json-password-box') + const password = passwordInput.value + + if (!password) { + const message = 'You must enter a password for the selected file.' + return this.props.dispatch(actions.displayWarning(message)) + } + + this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) +} diff --git a/ui/app/components/pages/import-account/private-key.js b/ui/app/components/pages/import-account/private-key.js new file mode 100644 index 000000000..217e6d9f9 --- /dev/null +++ b/ui/app/components/pages/import-account/private-key.js @@ -0,0 +1,71 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const actions = require('../../../actions') + +module.exports = connect(mapStateToProps)(PrivateKeyImportView) + +function mapStateToProps (state) { + return { + error: state.appState.warning, + } +} + +inherits(PrivateKeyImportView, Component) +function PrivateKeyImportView () { + Component.call(this) +} + +PrivateKeyImportView.prototype.componentWillUnmount = function () { + this.props.dispatch(actions.displayWarning(null)) +} + +PrivateKeyImportView.prototype.render = function () { + const { error } = this.props + + return ( + h('div', { + style: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: '5px 15px 0px 15px', + }, + }, [ + h('span', 'Paste your private key string here'), + + h('input.large-input.letter-spacey', { + type: 'password', + id: 'private-key-box', + onKeyPress: this.createKeyringOnEnter.bind(this), + style: { + width: 260, + marginTop: 12, + }, + }), + + h('button.primary', { + onClick: this.createNewKeychain.bind(this), + style: { + margin: 12, + }, + }, 'Import'), + + error ? h('span.error', error) : null, + ]) + ) +} + +PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) { + if (event.key === 'Enter') { + event.preventDefault() + this.createNewKeychain() + } +} + +PrivateKeyImportView.prototype.createNewKeychain = function () { + const input = document.getElementById('private-key-box') + const privateKey = input.value + this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ])) +} diff --git a/ui/app/components/pages/import-account/seed.js b/ui/app/components/pages/import-account/seed.js new file mode 100644 index 000000000..b4a7c0afa --- /dev/null +++ b/ui/app/components/pages/import-account/seed.js @@ -0,0 +1,30 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect + +module.exports = connect(mapStateToProps)(SeedImportSubview) + +function mapStateToProps (state) { + return {} +} + +inherits(SeedImportSubview, Component) +function SeedImportSubview () { + Component.call(this) +} + +SeedImportSubview.prototype.render = function () { + return ( + h('div', { + style: { + }, + }, [ + `Paste your seed phrase here!`, + h('textarea'), + h('br'), + h('button', 'Submit'), + ]) + ) +} + 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..5573f2dd2 --- /dev/null +++ b/ui/app/components/pages/keychains/restore-vault.js @@ -0,0 +1,167 @@ +const { withRouter } = require('react-router-dom') +const PropTypes = require('prop-types') +const { compose } = require('recompose') +const PersistentForm = require('../../../../lib/persistent-form') +const { connect } = require('react-redux') +const h = require('react-hyperscript') +const { createNewVaultAndRestore } = require('../../../actions') +const { DEFAULT_ROUTE } = require('../../../routes') + +class RestoreVaultPage extends PersistentForm { + constructor (props) { + super(props) + + this.state = { + error: null, + } + } + + createOnEnter (event) { + if (event.key === 'Enter') { + this.createNewVaultAndRestore() + } + } + + 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(() => history.push(DEFAULT_ROUTE)) + .catch(({ message }) => this.setState({ error: message })) + } + + render () { + const { error } = this.state + const { history } = this.props + 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, + }, + }, [ + 'Restore Vault', + ]), + + // wallet seed entry + h('h3', 'Wallet Seed'), + h('textarea.twelve-word-phrase.letter-spacey', { + dataset: { + persistentFormId: 'wallet-seed', + }, + placeholder: 'Enter your secret twelve word phrase here to restore your vault.', + }), + + // password + h('input.large-input.letter-spacey', { + type: 'password', + id: 'password-box', + placeholder: 'New Password (min 8 chars)', + dataset: { + persistentFormId: 'password', + }, + style: { + width: 260, + marginTop: 12, + }, + }), + + // confirm password + h('input.large-input.letter-spacey', { + type: 'password', + id: 'password-box-confirm', + placeholder: 'Confirm Password', + 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: () => history.goBack() }, 'CANCEL'), + + // submit + h('button.primary', { + onClick: this.createNewVaultAndRestore.bind(this), + }, '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)) + }, + } +} + +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..dc2cfc23e --- /dev/null +++ b/ui/app/components/pages/keychains/reveal-seed.js @@ -0,0 +1,192 @@ +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 () { + document.getElementById('password-box').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.goBack(), + }, '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/notice.js b/ui/app/components/pages/notice.js new file mode 100644 index 000000000..8e68cd52b --- /dev/null +++ b/ui/app/components/pages/notice.js @@ -0,0 +1,219 @@ +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 state = this.state || { disclaimerDisabled: true } + 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 + // 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()), + // }) + // } + + 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..39e9b26ed --- /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 { 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('/'), + }), + 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..d8155eb9b --- /dev/null +++ b/ui/app/components/pages/settings/info.js @@ -0,0 +1,108 @@ +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') + +class Info 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', 'Links'), + h('div.settings__info-link-item', [ + h('a', { + href: 'https://metamask.io/privacy.html', + target: '_blank', + }, [ + h('span.settings__info-link', 'Privacy Policy'), + ]), + ]), + h('div.settings__info-link-item', [ + h('a', { + href: 'https://metamask.io/terms.html', + target: '_blank', + }, [ + h('span.settings__info-link', 'Terms of Use'), + ]), + ]), + h('div.settings__info-link-item', [ + h('a', { + href: 'https://metamask.io/attributions.html', + target: '_blank', + }, [ + h('span.settings__info-link', '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', 'Visit our Support Center'), + ]), + ]), + h('div.settings__info-link-item', [ + h('a', { + href: 'https://metamask.io/', + target: '_blank', + }, [ + h('span.settings__info-link', 'Visit our web site'), + ]), + ]), + h('div.settings__info-link-item', [ + h('a', { + target: '_blank', + href: 'mailto:help@metamask.io?subject=Feedback', + }, [ + h('span.settings__info-link', 'Email us!'), + ]), + ]), + ]) + ) + } + + 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', '4.0.0'), + ]), + h('div.settings__info-item', [ + h( + 'div.settings__info-about', + 'MetaMask is designed and built in California.' + ), + ]), + ]), + 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, + goHome: PropTypes.func, + location: PropTypes.object, + history: PropTypes.object, +} + +module.exports = Info diff --git a/ui/app/components/pages/settings/settings.js b/ui/app/components/pages/settings/settings.js new file mode 100644 index 000000000..537270dee --- /dev/null +++ b/ui/app/components/pages/settings/settings.js @@ -0,0 +1,283 @@ +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') +const actions = require('../../../actions') +const infuraCurrencies = require('../../../infura-conversion.json') +const validUrl = require('valid-url') +const { exportAsFile } = require('../../../util') +const SimpleDropdown = require('../../dropdowns/simple-dropdown') +const ToggleButton = require('react-toggle-button') +const { REVEAL_SEED_ROUTE } = require('../../../routes') + +const getInfuraCurrencyOptions = () => { + const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { + return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase()) + }) + + return sortedCurrencies.map(({ quote: { code, name } }) => { + return { + displayValue: `${code.toUpperCase()} - ${name}`, + key: code, + value: code, + } + }) +} + +class Settings extends Component { + constructor (props) { + super(props) + + this.state = { + newRpc: '', + } + } + + renderBlockieOptIn () { + const { metamask: { useBlockie }, setUseBlockie } = this.props + + return h('div.settings__content-row', [ + h('div.settings__content-item', [ + h('span', 'Use Blockies Identicon'), + ]), + h('div.settings__content-item', [ + h('div.settings__content-item-col', [ + h(ToggleButton, { + value: useBlockie, + onToggle: (value) => setUseBlockie(!value), + activeLabel: '', + inactiveLabel: '', + }), + ]), + ]), + ]) + } + + renderCurrentConversion () { + const { metamask: { currentCurrency, conversionDate }, setCurrentCurrency } = this.props + + return h('div.settings__content-row', [ + h('div.settings__content-item', [ + h('span', 'Current Conversion'), + h('span.settings__content-description', `Updated ${Date(conversionDate)}`), + ]), + h('div.settings__content-item', [ + h('div.settings__content-item-col', [ + h(SimpleDropdown, { + placeholder: 'Select Currency', + options: getInfuraCurrencyOptions(), + selectedOption: currentCurrency, + onSelect: newCurrency => setCurrentCurrency(newCurrency), + }), + ]), + ]), + ]) + } + + renderCurrentProvider () { + const { metamask: { provider = {} } } = this.props + let title, value, color + + switch (provider.type) { + + case 'mainnet': + title = 'Current Network' + value = 'Main Ethereum Network' + color = '#038789' + break + + case 'ropsten': + title = 'Current Network' + value = 'Ropsten Test Network' + color = '#e91550' + break + + case 'kovan': + title = 'Current Network' + value = 'Kovan Test Network' + color = '#690496' + break + + case 'rinkeby': + title = 'Current Network' + value = 'Rinkeby Test Network' + color = '#ebb33f' + break + + default: + title = 'Current RPC' + value = provider.rpcTarget + } + + return h('div.settings__content-row', [ + h('div.settings__content-item', title), + h('div.settings__content-item', [ + h('div.settings__content-item-col', [ + h('div.settings__provider-wrapper', [ + h('div.settings__provider-icon', { style: { background: color } }), + h('div', value), + ]), + ]), + ]), + ]) + } + + renderNewRpcUrl () { + return ( + h('div.settings__content-row', [ + h('div.settings__content-item', [ + h('span', 'New RPC URL'), + ]), + h('div.settings__content-item', [ + h('div.settings__content-item-col', [ + h('input.settings__input', { + placeholder: 'New RPC URL', + onChange: event => this.setState({ newRpc: event.target.value }), + onKeyPress: event => { + if (event.key === 'Enter') { + this.validateRpc(this.state.newRpc) + } + }, + }), + h('div.settings__rpc-save-button', { + onClick: event => { + event.preventDefault() + this.validateRpc(this.state.newRpc) + }, + }, 'Save'), + ]), + ]), + ]) + ) + } + + validateRpc (newRpc) { + const { setRpcTarget, displayWarning } = this.props + + if (validUrl.isWebUri(newRpc)) { + setRpcTarget(newRpc) + } else { + const appendedRpc = `http://${newRpc}` + + if (validUrl.isWebUri(appendedRpc)) { + displayWarning('URIs require the appropriate HTTP/HTTPS prefix.') + } else { + displayWarning('Invalid RPC URI') + } + } + } + + renderStateLogs () { + return ( + h('div.settings__content-row', [ + h('div.settings__content-item', [ + h('div', 'State Logs'), + h( + 'div.settings__content-description', + 'State logs contain your public account addresses and sent transactions.' + ), + ]), + h('div.settings__content-item', [ + h('div.settings__content-item-col', [ + h('button.settings__clear-button', { + onClick (event) { + exportAsFile('MetaMask State Logs', window.logState()) + }, + }, 'Download State Logs'), + ]), + ]), + ]) + ) + } + + renderSeedWords () { + const { history } = this.props + + return ( + h('div.settings__content-row', [ + h('div.settings__content-item', 'Reveal Seed Words'), + h('div.settings__content-item', [ + h('div.settings__content-item-col', [ + h('button.settings__clear-button.settings__clear-button--red', { + onClick: () => history.push(REVEAL_SEED_ROUTE), + }, 'Reveal Seed Words'), + ]), + ]), + ]) + ) + } + + renderOldUI () { + const { setFeatureFlagToBeta } = this.props + + return ( + h('div.settings__content-row', [ + h('div.settings__content-item', 'Use old UI'), + h('div.settings__content-item', [ + h('div.settings__content-item-col', [ + h('button.settings__clear-button.settings__clear-button--orange', { + onClick (event) { + event.preventDefault() + setFeatureFlagToBeta() + }, + }, 'Use old UI'), + ]), + ]), + ]) + ) + } + + render () { + const { warning } = this.props + + return ( + h('div.settings__content', [ + warning && h('div.settings__error', warning), + this.renderBlockieOptIn(), + this.renderCurrentConversion(), + // this.renderCurrentProvider(), + this.renderNewRpcUrl(), + this.renderStateLogs(), + this.renderSeedWords(), + this.renderOldUI(), + ]) + ) + } +} + +Settings.propTypes = { + metamask: PropTypes.object, + setUseBlockie: PropTypes.func, + setCurrentCurrency: PropTypes.func, + setRpcTarget: PropTypes.func, + displayWarning: PropTypes.func, + revealSeedConfirmation: PropTypes.func, + setFeatureFlagToBeta: PropTypes.func, + warning: PropTypes.string, + history: PropTypes.object, +} + +const mapStateToProps = state => { + return { + metamask: state.metamask, + warning: state.appState.warning, + } +} + +const mapDispatchToProps = dispatch => { + return { + setCurrentCurrency: currency => dispatch(actions.setCurrentCurrency(currency)), + setRpcTarget: newRpc => dispatch(actions.setRpcTarget(newRpc)), + displayWarning: warning => dispatch(actions.displayWarning(warning)), + revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()), + setUseBlockie: value => dispatch(actions.setUseBlockie(value)), + setFeatureFlagToBeta: () => dispatch(actions.setFeatureFlag('betaUI', false)), + } +} + +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(Settings) diff --git a/ui/app/components/pages/unauthenticated/unlock.js b/ui/app/components/pages/unauthenticated/unlock.js new file mode 100644 index 000000000..72f27c11d --- /dev/null +++ b/ui/app/components/pages/unauthenticated/unlock.js @@ -0,0 +1,172 @@ +const { Component } = require('react') +const PropTypes = require('prop-types') +const { connect } = require('react-redux') +const h = require('react-hyperscript') +const { Redirect, withRouter } = require('react-router-dom') +const { compose } = require('recompose') +const { tryUnlockMetamask, forgotPassword } = require('../../../actions') +const getCaretCoordinates = require('textarea-caret') +const EventEmitter = require('events').EventEmitter +const Mascot = require('../../mascot') +const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../../routes') + +class UnlockScreen extends Component { + constructor (props) { + super(props) + + this.state = { + error: null, + } + + this.animationEventEmitter = new EventEmitter() + } + + 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 + const { isUnlocked, history } = this.props + + if (isUnlocked) { + return ( + h(Redirect, { + to: { + pathname: DEFAULT_ROUTE, + }, + }) + ) + } + + return ( + h('.unlock-page.main-container', [ + h('.flex-column', { + style: { + width: 'inherit', + }, + }, [ + h('.unlock-screen.flex-column.flex-center.flex-grow', [ + + h(Mascot, { + animationEventEmitter: this.animationEventEmitter, + }), + + h('h1', { + style: { + fontSize: '1.4em', + textTransform: 'uppercase', + color: '#7F8082', + }, + }, 'MetaMask'), + + 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, + }, + }, 'Unlock'), + + h('.flex-row.flex-center.flex-grow', [ + h('p.pointer', { + onClick: () => history.push(RESTORE_VAULT_ROUTE), + style: { + fontSize: '0.8em', + color: 'rgb(247, 134, 28)', + textDecoration: 'underline', + }, + }, 'Restore from seed phrase'), + ]), + ]), + ]), + ]) + ) + } +} + +UnlockScreen.propTypes = { + forgotPassword: PropTypes.func, + tryUnlockMetamask: PropTypes.func, + history: PropTypes.object, + isUnlocked: PropTypes.bool, +} + +const mapStateToProps = state => { + const { metamask: { isUnlocked } } = state + return { + isUnlocked, + } +} + +const mapDispatchToProps = dispatch => { + return { + forgotPassword: () => dispatch(forgotPassword()), + tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)), + } +} + +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 1264da153..8a167f7cd 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -1,5 +1,7 @@ const Component = require('react').Component const { connect } = require('react-redux') +const { withRouter } = require('react-router-dom') +const { compose } = require('recompose') const h = require('react-hyperscript') const inherits = require('util').inherits const actions = require('../../actions') @@ -11,8 +13,12 @@ const hexToBn = require('../../../../app/scripts/lib/hex-to-bn') const { conversionUtil, addCurrencies } = require('../../conversion-util') const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') +const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') -module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmSendEther) +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(ConfirmSendEther) function mapStateToProps (state) { const { @@ -43,6 +49,7 @@ function mapDispatchToProps (dispatch) { to, value: amount, } = txParams + dispatch(actions.updateSend({ gasLimit, gasPrice, @@ -52,7 +59,6 @@ function mapDispatchToProps (dispatch) { errors: { to: null, amount: null }, editingTransactionId: id, })) - dispatch(actions.showSendPage()) }, cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })), } @@ -177,8 +183,14 @@ ConfirmSendEther.prototype.getData = function () { } } +ConfirmSendEther.prototype.editTransaction = function (txMeta) { + const { editTransaction, history } = this.props + editTransaction(txMeta) + history.push(SEND_ROUTE) +} + ConfirmSendEther.prototype.render = function () { - const { editTransaction, currentCurrency, clearSend } = this.props + const { currentCurrency, clearSend } = this.props const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} @@ -220,7 +232,7 @@ ConfirmSendEther.prototype.render = function () { h('div.confirm-screen-wrapper.flex-column.flex-grow', [ h('h3.flex-center.confirm-screen-header', [ h('button.confirm-screen-back-button', { - onClick: () => editTransaction(txMeta), + onClick: () => this.editTransaction(txMeta), }, 'EDIT'), h('div.confirm-screen-title', 'Confirm Transaction'), h('div.confirm-screen-header-tip'), @@ -422,6 +434,7 @@ ConfirmSendEther.prototype.onSubmit = function (event) { ConfirmSendEther.prototype.cancel = function (event, txMeta) { event.preventDefault() this.props.cancelTransaction(txMeta) + .then(() => this.props.history.push(DEFAULT_ROUTE)) } ConfirmSendEther.prototype.checkValidity = function () { diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 727cd260b..8087e431f 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -1,5 +1,7 @@ const Component = require('react').Component const { connect } = require('react-redux') +const { withRouter } = require('react-router-dom') +const { compose } = require('recompose') const h = require('react-hyperscript') const inherits = require('util').inherits const ethAbi = require('ethereumjs-abi') @@ -27,8 +29,12 @@ const { getSelectedAddress, getSelectedTokenContract, } = require('../../selectors') +const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') -module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmSendToken) +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(ConfirmSendToken) function mapStateToProps (state, ownProps) { const { token: { symbol }, txData } = ownProps @@ -99,6 +105,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.componentWillMount = function () { const { tokenContract, selectedAddress } = this.props tokenContract && tokenContract @@ -293,7 +305,6 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () { } ConfirmSendToken.prototype.render = function () { - const { editTransaction } = this.props const txMeta = this.gatherTxMeta() const { from: { @@ -316,7 +327,7 @@ ConfirmSendToken.prototype.render = function () { h('div.confirm-screen-wrapper.flex-column.flex-grow', [ h('h3.flex-center.confirm-screen-header', [ h('button.confirm-screen-back-button', { - onClick: () => editTransaction(txMeta), + onClick: () => this.editTransaction(txMeta), }, 'EDIT'), h('div.confirm-screen-title', 'Confirm Transaction'), h('div.confirm-screen-header-tip'), @@ -416,6 +427,7 @@ ConfirmSendToken.prototype.onSubmit = function (event) { ConfirmSendToken.prototype.cancel = function (event, txMeta) { event.preventDefault() this.props.cancelTransaction(txMeta) + .then(() => this.props.history.push(DEFAULT_ROUTE)) } ConfirmSendToken.prototype.checkValidity = function () { diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js index 655de8897..5998bd252 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,10 @@ const { getSelectedTokenContract, } = require('../../selectors') -module.exports = connect(mapStateToProps, mapDispatchToProps)(SendEther) +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(SendEther) function mapStateToProps (state) { const fromAccounts = accountsWithSendEtherInfoSelector(state) diff --git a/ui/app/components/tab-bar.js b/ui/app/components/tab-bar.js index 0edced119..32c9e4f24 100644 --- a/ui/app/components/tab-bar.js +++ b/ui/app/components/tab-bar.js @@ -4,31 +4,17 @@ const PropTypes = require('react').PropTypes 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/tx-list.js b/ui/app/components/tx-list.js index 70722f43e..6f18ea814 100644 --- a/ui/app/components/tx-list.js +++ b/ui/app/components/tx-list.js @@ -10,8 +10,14 @@ 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 = connect(mapStateToProps, mapDispatchToProps)(TxList) +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(TxList) function mapStateToProps (state) { return { @@ -88,7 +94,7 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa transactionHash, transactionNetworkId, } = props - const { showConfTxPage } = this.props + const { showConfTxPage, history } = this.props const opts = { key: transActionId || transactionHash, @@ -106,7 +112,7 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa const isUnapproved = transactionStatus === 'unapproved' if (isUnapproved) { - opts.onClick = () => showConfTxPage({id: transActionId}) + opts.onClick = () => history.push(CONFIRM_TRANSACTION_ROUTE) opts.transactionStatus = 'Not Started' } 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 e42a20c85..46029166e 100644 --- a/ui/app/components/tx-view.js +++ b/ui/app/components/tx-view.js @@ -3,14 +3,20 @@ 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 BalanceComponent = require('./balance-component') const TxList = require('./tx-list') const Identicon = require('./identicon') -module.exports = connect(mapStateToProps, mapDispatchToProps)(TxView) +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(TxView) function mapStateToProps (state) { const sidebarOpen = state.appState.sidebarOpen @@ -63,7 +69,7 @@ TxView.prototype.renderHeroBalance = function () { } TxView.prototype.renderButtons = function () { - const {selectedToken, showModal, showSendPage, showSendTokenPage } = this.props + const {selectedToken, showModal, history } = this.props return !selectedToken ? ( @@ -82,7 +88,7 @@ TxView.prototype.renderButtons = function () { textAlign: 'center', marginLeft: '0.8em', }, - onClick: showSendPage, + onClick: () => history.push(SEND_ROUTE), }, 'SEND'), ]) ) @@ -93,7 +99,7 @@ TxView.prototype.renderButtons = function () { textAlign: 'center', marginLeft: '0.8em', }, - onClick: showSendTokenPage, + onClick: () => history.push(SEND_ROUTE), }, 'SEND'), ]) ) diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 3cb7a8b76..3b4443ef6 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -1,6 +1,8 @@ const Component = require('react').Component 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 Identicon = require('./identicon') // const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns @@ -9,8 +11,12 @@ 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 = connect(mapStateToProps, mapDispatchToProps)(WalletView) +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(WalletView) function mapStateToProps (state) { @@ -89,6 +95,7 @@ WalletView.prototype.render = function () { showAccountDetailModal, hideSidebar, showAddTokenPage, + history, } = this.props // temporary logs + fake extra wallets // console.log('walletview, selectedAccount:', selectedAccount) @@ -152,10 +159,7 @@ WalletView.prototype.render = function () { h(TokenList), h('button.wallet-view__add-token-button', { - onClick: () => { - showAddTokenPage() - hideSidebar() - }, + onClick: () => history.push(ADD_TOKEN_ROUTE), }, 'Add Token'), ]) } diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 9f273aaec..ce0012d5b 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -2,6 +2,8 @@ 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') @@ -11,17 +13,12 @@ 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) { return { @@ -48,6 +45,20 @@ function ConfirmTxScreen () { Component.call(this) } +ConfirmTxScreen.prototype.componentWillMount = function () { + const { unapprovedTxs = {} } = this.props + if (Object.keys(unapprovedTxs).length === 0) { + this.props.history.push(DEFAULT_ROUTE) + } +} + +ConfirmTxScreen.prototype.componentWillReceiveProps = function (nextProps) { + const { unapprovedTxs = {} } = nextProps + if (Object.keys(unapprovedTxs).length === 0) { + this.props.history.push(DEFAULT_ROUTE) + } +} + ConfirmTxScreen.prototype.render = function () { const props = this.props const { @@ -146,6 +157,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) { 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/index.scss b/ui/app/css/itcss/components/index.scss index dfb4f23f0..e1ec42c66 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -51,3 +51,5 @@ @import './account-dropdown-mini.scss'; @import './editable-label.scss'; + +@import './pages/index.scss'; 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/first-time/init-menu.js b/ui/app/first-time/init-menu.js index b4587f1ee..6832f5ddf 100644 --- a/ui/app/first-time/init-menu.js +++ b/ui/app/first-time/init-menu.js @@ -1,184 +1,191 @@ -const inherits = require('util').inherits -const EventEmitter = require('events').EventEmitter -const Component = require('react').Component -const connect = require('react-redux').connect +const { EventEmitter } = require('events') +const { Component } = require('react') +const { connect } = require('react-redux') const h = require('react-hyperscript') +const PropTypes = require('prop-types') const Mascot = require('../components/mascot') const actions = require('../actions') const Tooltip = require('../components/tooltip') const getCaretCoordinates = require('textarea-caret') +const { RESTORE_VAULT_ROUTE, DEFAULT_ROUTE } = require('../routes') -let isSubmitting = false +class InitializeMenuScreen extends Component { + constructor (props) { + super(props) -module.exports = connect(mapStateToProps)(InitializeMenuScreen) - -inherits(InitializeMenuScreen, Component) -function InitializeMenuScreen () { - Component.call(this) - this.animationEventEmitter = new EventEmitter() -} + this.animationEventEmitter = new EventEmitter() + this.state = { + warning: null, + } + } -function mapStateToProps (state) { - return { - // state from plugin - currentView: state.appState.currentView, - warning: state.appState.warning, + componentWillMount () { + const { isInitialized, isUnlocked, history } = this.props + if (isInitialized || isUnlocked) { + history.push(DEFAULT_ROUTE) + } } -} -InitializeMenuScreen.prototype.render = function () { - var state = this.props + componentDidMount () { + document.getElementById('password-box').focus() + } - switch (state.currentView.name) { + render () { + const { history } = this.props + const { warning } = this.state - default: - return this.renderMenu(state) + return ( + h('.initialize-screen.flex-column.flex-center.flex-grow', [ - } -} + h(Mascot, { + animationEventEmitter: this.animationEventEmitter, + }), -// InitializeMenuScreen.prototype.componentDidMount = function(){ -// document.getElementById('password-box').focus() -// } + h('h1', { + style: { + fontSize: '1.3em', + textTransform: 'uppercase', + color: '#7F8082', + marginBottom: 10, + }, + }, 'MetaMask'), -InitializeMenuScreen.prototype.renderMenu = function (state) { - return ( - h('.initialize-screen.flex-column.flex-center.flex-grow', [ + h('div', [ + h('h3', { + style: { + fontSize: '0.8em', + color: '#7F8082', + display: 'inline', + }, + }, 'Encrypt your new DEN'), + + h(Tooltip, { + title: 'Your DEN is your password-encrypted storage within MetaMask.', + }, [ + h('i.fa.fa-question-circle.pointer', { + style: { + fontSize: '18px', + position: 'relative', + color: 'rgb(247, 134, 28)', + top: '2px', + marginLeft: '4px', + }, + }), + ]), + ]), - h(Mascot, { - animationEventEmitter: this.animationEventEmitter, - }), + h('span.error.in-progress-notification', warning), - h('h1', { - style: { - fontSize: '1.3em', - textTransform: 'uppercase', - color: '#7F8082', - marginBottom: 10, - }, - }, 'MetaMask'), + // password + h('input.large-input.letter-spacey', { + type: 'password', + id: 'password-box', + placeholder: 'New Password (min 8 chars)', + 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: 'Confirm Password', + onKeyPress: this.createVaultOnEnter.bind(this), + onInput: this.inputChanged.bind(this), + style: { + width: 260, + marginTop: 16, + }, + }), - h('div', [ - h('h3', { + h('button.primary', { + onClick: this.createNewVaultAndKeychain.bind(this), style: { - fontSize: '0.8em', - color: '#7F8082', - display: 'inline', + margin: 12, }, - }, 'Encrypt your new DEN'), + }, 'Create'), - h(Tooltip, { - title: 'Your DEN is your password-encrypted storage within MetaMask.', - }, [ - h('i.fa.fa-question-circle.pointer', { + h('.flex-row.flex-center.flex-grow', [ + h('p.pointer', { + onClick: () => history.push(RESTORE_VAULT_ROUTE), style: { - fontSize: '18px', - position: 'relative', + fontSize: '0.8em', color: 'rgb(247, 134, 28)', - top: '2px', - marginLeft: '4px', + textDecoration: 'underline', }, - }), + }, 'Import Existing DEN'), ]), - ]), - - h('span.in-progress-notification', state.warning), - - // password - h('input.large-input.letter-spacey', { - type: 'password', - id: 'password-box', - placeholder: 'New Password (min 8 chars)', - 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: 'Confirm Password', - 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, - }, - }, 'Create'), - - h('.flex-row.flex-center.flex-grow', [ - h('p.pointer', { - onClick: this.showRestoreVault.bind(this), - style: { - fontSize: '0.8em', - color: 'rgb(247, 134, 28)', - textDecoration: 'underline', - }, - }, 'Import Existing DEN'), - ]), - ]) - ) -} + ]) + ) + } -InitializeMenuScreen.prototype.createVaultOnEnter = function (event) { - if (event.key === 'Enter') { - event.preventDefault() - this.createNewVaultAndKeychain() + createVaultOnEnter (event) { + if (event.key === 'Enter') { + event.preventDefault() + this.createNewVaultAndKeychain() + } } -} -InitializeMenuScreen.prototype.componentDidMount = function () { - document.getElementById('password-box').focus() + 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: 'password not long enough' }) + return + } + if (password !== passwordConfirm) { + this.setState({ warning: 'passwords don\'t match' }) + return + } + + this.props.createNewVaultAndKeychain(password) + .then(() => history.push(DEFAULT_ROUTE)) + } + + 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.showRestoreVault = function () { - this.props.dispatch(actions.showRestoreVault()) +InitializeMenuScreen.propTypes = { + history: PropTypes.object, + isInitialized: PropTypes.bool, + isUnlocked: PropTypes.bool, + createNewVaultAndKeychain: PropTypes.func, } -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 +const mapStateToProps = state => { + const { metamask: { isInitialized, isUnlocked } } = state - if (password.length < 8) { - this.warning = 'password not long enough' - this.props.dispatch(actions.displayWarning(this.warning)) - return - } - if (password !== passwordConfirm) { - this.warning = 'passwords don\'t match' - this.props.dispatch(actions.displayWarning(this.warning)) - return + return { + isInitialized, + isUnlocked, } +} - if (!isSubmitting) { - isSubmitting = true - this.props.dispatch(actions.createNewVaultAndKeychain(password)) +const mapDispatchToProps = dispatch => { + return { + createNewVaultAndKeychain: password => dispatch(actions.createNewVaultAndKeychain(password)), } } -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, - }) -} +module.exports = connect(mapStateToProps, mapDispatchToProps)(InitializeMenuScreen) diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js deleted file mode 100644 index 5ab5d4c33..000000000 --- a/ui/app/keychains/hd/create-vault-complete.js +++ /dev/null @@ -1,91 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const connect = require('react-redux').connect -const h = require('react-hyperscript') -const actions = require('../../actions') -const exportAsFile = require('../../util').exportAsFile - -module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen) - -inherits(CreateVaultCompleteScreen, Component) -function CreateVaultCompleteScreen () { - Component.call(this) -} - -function mapStateToProps (state) { - return { - seed: state.appState.currentView.seedWords, - cachedSeed: state.metamask.seedWords, - } -} - -CreateVaultCompleteScreen.prototype.render = function () { - var state = this.props - var seed = state.seed || state.cachedSeed || '' - - return ( - - h('.initialize-screen.flex-column.flex-center.flex-grow', [ - - // // subtitle and nav - // h('.section-title.flex-row.flex-center', [ - // h('h2.page-subtitle', 'Vault Created'), - // ]), - - 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: seed, - }), - - h('button.primary', { - onClick: () => this.confirmSeedWords() - .then(account => this.showAccountDetail(account)), - style: { - margin: '24px', - fontSize: '0.9em', - marginBottom: '10px', - }, - }, 'I\'ve copied it somewhere safe'), - - h('button.primary', { - onClick: () => exportAsFile(`MetaMask Seed Words`, seed), - style: { - margin: '10px', - fontSize: '0.9em', - }, - }, 'Save Seed Words As File'), - ]) - ) -} - -CreateVaultCompleteScreen.prototype.confirmSeedWords = function () { - return this.props.dispatch(actions.confirmSeedWords()) -} - -CreateVaultCompleteScreen.prototype.showAccountDetail = function (account) { - return this.props.dispatch(actions.showAccountDetail(account)) -} diff --git a/ui/app/keychains/hd/recover-seed/confirmation.js b/ui/app/keychains/hd/recover-seed/confirmation.js deleted file mode 100644 index 4335186a5..000000000 --- a/ui/app/keychains/hd/recover-seed/confirmation.js +++ /dev/null @@ -1,121 +0,0 @@ -const inherits = require('util').inherits - -const Component = require('react').Component -const connect = require('react-redux').connect -const h = require('react-hyperscript') -const actions = require('../../../actions') - -module.exports = connect(mapStateToProps)(RevealSeedConfirmation) - -inherits(RevealSeedConfirmation, Component) -function RevealSeedConfirmation () { - Component.call(this) -} - -function mapStateToProps (state) { - return { - warning: state.appState.warning, - } -} - -RevealSeedConfirmation.prototype.render = function () { - const props = 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: this.goHome.bind(this), - }, 'CANCEL'), - - // submit - h('button.primary', { - style: { marginLeft: '10px' }, - onClick: this.revealSeedWords.bind(this), - }, 'OK'), - - ]), - - (props.warning) && ( - h('span.error', { - style: { - margin: '20px', - }, - }, props.warning.split('-')) - ), - - props.inProgress && ( - h('span.in-progress-notification', 'Generating Seed...') - ), - ]), - ]) - ) -} - -RevealSeedConfirmation.prototype.componentDidMount = function () { - document.getElementById('password-box').focus() -} - -RevealSeedConfirmation.prototype.goHome = function () { - this.props.dispatch(actions.showConfigPage(false)) -} - -// create vault - -RevealSeedConfirmation.prototype.checkConfirmation = function (event) { - if (event.key === 'Enter') { - event.preventDefault() - this.revealSeedWords() - } -} - -RevealSeedConfirmation.prototype.revealSeedWords = function () { - var password = document.getElementById('password-box').value - this.props.dispatch(actions.requestRevealSeed(password)) -} diff --git a/ui/app/keychains/hd/restore-vault.js b/ui/app/keychains/hd/restore-vault.js deleted file mode 100644 index 06e51d9b3..000000000 --- a/ui/app/keychains/hd/restore-vault.js +++ /dev/null @@ -1,152 +0,0 @@ -const inherits = require('util').inherits -const PersistentForm = require('../../../lib/persistent-form') -const connect = require('react-redux').connect -const h = require('react-hyperscript') -const actions = require('../../actions') - -module.exports = connect(mapStateToProps)(RestoreVaultScreen) - -inherits(RestoreVaultScreen, PersistentForm) -function RestoreVaultScreen () { - PersistentForm.call(this) -} - -function mapStateToProps (state) { - return { - warning: state.appState.warning, - forgottenPassword: state.appState.forgottenPassword, - } -} - -RestoreVaultScreen.prototype.render = function () { - var state = this.props - 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, - }, - }, [ - 'Restore Vault', - ]), - - // wallet seed entry - h('h3', 'Wallet Seed'), - h('textarea.twelve-word-phrase.letter-spacey', { - dataset: { - persistentFormId: 'wallet-seed', - }, - placeholder: 'Enter your secret twelve word phrase here to restore your vault.', - }), - - // password - h('input.large-input.letter-spacey', { - type: 'password', - id: 'password-box', - placeholder: 'New Password (min 8 chars)', - dataset: { - persistentFormId: 'password', - }, - style: { - width: 260, - marginTop: 12, - }, - }), - - // confirm password - h('input.large-input.letter-spacey', { - type: 'password', - id: 'password-box-confirm', - placeholder: 'Confirm Password', - onKeyPress: this.createOnEnter.bind(this), - dataset: { - persistentFormId: 'password-confirmation', - }, - style: { - width: 260, - marginTop: 16, - }, - }), - - (state.warning) && ( - h('span.error.in-progress-notification', state.warning) - ), - - // submit - - h('.flex-row.flex-space-between', { - style: { - marginTop: 30, - width: '50%', - }, - }, [ - - // cancel - h('button.primary', { - onClick: this.showInitializeMenu.bind(this), - }, 'CANCEL'), - - // submit - h('button.primary', { - onClick: this.createNewVaultAndRestore.bind(this), - }, 'OK'), - - ]), - ]) - - ) -} - -RestoreVaultScreen.prototype.showInitializeMenu = function () { - if (this.props.forgottenPassword) { - this.props.dispatch(actions.backToUnlockView()) - } else { - this.props.dispatch(actions.showInitializeMenu()) - } -} - -RestoreVaultScreen.prototype.createOnEnter = function (event) { - if (event.key === 'Enter') { - this.createNewVaultAndRestore() - } -} - -RestoreVaultScreen.prototype.createNewVaultAndRestore = function () { - // 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.warning = 'Password not long enough' - - this.props.dispatch(actions.displayWarning(this.warning)) - return - } - if (password !== passwordConfirm) { - this.warning = 'Passwords don\'t match' - this.props.dispatch(actions.displayWarning(this.warning)) - return - } - // check seed - var seedBox = document.querySelector('textarea.twelve-word-phrase') - var seed = seedBox.value.trim() - if (seed.split(' ').length !== 12) { - this.warning = 'seed phrases are 12 words long' - this.props.dispatch(actions.displayWarning(this.warning)) - return - } - // submit - this.warning = null - this.props.dispatch(actions.displayWarning(this.warning)) - this.props.dispatch(actions.createNewVaultAndRestore(password, seed)) -} diff --git a/ui/app/main-container.js b/ui/app/main-container.js index 031f61e84..ad50ee13d 100644 --- a/ui/app/main-container.js +++ b/ui/app/main-container.js @@ -2,9 +2,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const AccountAndTransactionDetails = require('./account-and-transaction-details') -const HDRestoreVaultScreen = require('./keychains/hd/restore-vault') -const Settings = require('./settings') -const UnlockScreen = require('./unlock') +const UnlockScreen = require('./components/pages/unauthenticated/unlock') module.exports = MainContainer @@ -26,36 +24,6 @@ MainContainer.prototype.render = function () { style: {}, } - if (this.props.isUnlocked === false) { - switch (this.props.currentViewName) { - case 'restoreVault': - log.debug('rendering restore vault screen') - contents = { - component: HDRestoreVaultScreen, - key: 'HDRestoreVaultScreen', - } - break - case 'config': - log.debug('rendering config screen from unlock screen.') - return h(Settings, {key: 'config'}) - default: - log.debug('rendering locked screen') - contents = { - component: UnlockScreen, - style: { - boxShadow: 'none', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - background: '#F7F7F7', - // must force 100%, because lock screen is full-width - width: '100%', - }, - key: 'locked', - } - } - } - return h('div.main-container', { style: contents.style, }, [ diff --git a/ui/app/root.js b/ui/app/root.js index 21d6d1829..64f365c9e 100644 --- a/ui/app/root.js +++ b/ui/app/root.js @@ -1,22 +1,28 @@ -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') +const { HashRouter } = require('react-router-dom') -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(HashRouter, { + hashType: 'noslash', + }, [ + 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..1305d6b1e --- /dev/null +++ b/ui/app/routes.js @@ -0,0 +1,27 @@ +const DEFAULT_ROUTE = '/' +const UNLOCK_ROUTE = '/unlock' +const SETTINGS_ROUTE = '/settings' +const INFO_ROUTE = '/settings/info' +const REVEAL_SEED_ROUTE = '/reveal-seed-confirm' +const RESTORE_VAULT_ROUTE = '/restore-vault' +const ADD_TOKEN_ROUTE = '/add-token' +const IMPORT_ACCOUNT_ROUTE = '/import-account' +const SEND_ROUTE = '/send' +const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction' +const INITIALIZE_MENU_ROUTE = '/initialize-menu' +const NOTICE_ROUTE = '/notice' + +module.exports = { + DEFAULT_ROUTE, + UNLOCK_ROUTE, + SETTINGS_ROUTE, + INFO_ROUTE, + REVEAL_SEED_ROUTE, + RESTORE_VAULT_ROUTE, + ADD_TOKEN_ROUTE, + IMPORT_ACCOUNT_ROUTE, + SEND_ROUTE, + CONFIRM_TRANSACTION_ROUTE, + INITIALIZE_MENU_ROUTE, + NOTICE_ROUTE, +} diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index e1b88f0db..32bbdfe6e 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -28,6 +28,7 @@ const { isTokenBalanceSufficient, } = require('./components/send/send-utils') const { isValidAddress } = require('./util') +const { CONFIRM_TRANSACTION_ROUTE } = require('./routes') module.exports = SendTransactionScreen @@ -508,9 +509,9 @@ SendTransactionScreen.prototype.renderForm = function () { SendTransactionScreen.prototype.renderFooter = function () { const { - goHome, clearSend, errors: { amount: amountError, to: toError }, + history, } = this.props const noErrors = !amountError && toError === null @@ -520,7 +521,7 @@ SendTransactionScreen.prototype.renderFooter = function () { h('button.send-v2__cancel-btn', { onClick: () => { clearSend() - goHome() + history.goBack() }, }, 'Cancel'), h(`button.send-v2__next-btn${errorClass}`, { @@ -555,7 +556,7 @@ SendTransactionScreen.prototype.addToAddressBookIfNew = function (newAddress) { SendTransactionScreen.prototype.onSubmit = function (event) { event.preventDefault() const { - from: {address: from}, + from: { address: from }, to, amount, gasLimit: gas, @@ -578,6 +579,7 @@ SendTransactionScreen.prototype.onSubmit = function (event) { if (editingTransactionId) { backToConfirmScreen(editingTransactionId) + this.props.history.push(CONFIRM_TRANSACTION_ROUTE) return } @@ -596,4 +598,6 @@ SendTransactionScreen.prototype.onSubmit = function (event) { selectedToken ? signTokenTx(selectedToken.address, to, amount, txParams) : signTx(txParams) + + this.props.history.push(CONFIRM_TRANSACTION_ROUTE) } diff --git a/ui/app/settings.js b/ui/app/settings.js deleted file mode 100644 index ca7535d26..000000000 --- a/ui/app/settings.js +++ /dev/null @@ -1,410 +0,0 @@ -const { Component } = require('react') -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const { connect } = require('react-redux') -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 ToggleButton = require('react-toggle-button') - -const getInfuraCurrencyOptions = () => { - const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { - return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase()) - }) - - return sortedCurrencies.map(({ quote: { code, name } }) => { - return { - displayValue: `${code.toUpperCase()} - ${name}`, - key: code, - value: code, - } - }) -} - -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: 'Settings', key: 'settings' }, - { content: 'Info', key: 'info' }, - ], - defaultTab: activeTab, - tabSelected: key => this.setState({ activeTab: key }), - }), - ]) - } - - renderBlockieOptIn () { - const { metamask: { useBlockie }, setUseBlockie } = this.props - - return h('div.settings__content-row', [ - h('div.settings__content-item', [ - h('span', 'Use Blockies Identicon'), - ]), - h('div.settings__content-item', [ - h('div.settings__content-item-col', [ - h(ToggleButton, { - value: useBlockie, - onToggle: (value) => setUseBlockie(!value), - activeLabel: '', - inactiveLabel: '', - }), - ]), - ]), - ]) - } - - renderCurrentConversion () { - const { metamask: { currentCurrency, conversionDate }, setCurrentCurrency } = this.props - - return h('div.settings__content-row', [ - h('div.settings__content-item', [ - h('span', 'Current Conversion'), - h('span.settings__content-description', `Updated ${Date(conversionDate)}`), - ]), - h('div.settings__content-item', [ - h('div.settings__content-item-col', [ - h(SimpleDropdown, { - placeholder: 'Select Currency', - options: getInfuraCurrencyOptions(), - selectedOption: currentCurrency, - onSelect: newCurrency => setCurrentCurrency(newCurrency), - }), - ]), - ]), - ]) - } - - renderCurrentProvider () { - const { metamask: { provider = {} } } = this.props - let title, value, color - - switch (provider.type) { - - case 'mainnet': - title = 'Current Network' - value = 'Main Ethereum Network' - color = '#038789' - break - - case 'ropsten': - title = 'Current Network' - value = 'Ropsten Test Network' - color = '#e91550' - break - - case 'kovan': - title = 'Current Network' - value = 'Kovan Test Network' - color = '#690496' - break - - case 'rinkeby': - title = 'Current Network' - value = 'Rinkeby Test Network' - color = '#ebb33f' - break - - default: - title = 'Current RPC' - value = provider.rpcTarget - } - - return h('div.settings__content-row', [ - h('div.settings__content-item', title), - h('div.settings__content-item', [ - h('div.settings__content-item-col', [ - h('div.settings__provider-wrapper', [ - h('div.settings__provider-icon', { style: { background: color } }), - h('div', value), - ]), - ]), - ]), - ]) - } - - renderNewRpcUrl () { - return ( - h('div.settings__content-row', [ - h('div.settings__content-item', [ - h('span', 'New RPC URL'), - ]), - h('div.settings__content-item', [ - h('div.settings__content-item-col', [ - h('input.settings__input', { - placeholder: 'New RPC URL', - onChange: event => this.setState({ newRpc: event.target.value }), - onKeyPress: event => { - if (event.key === 'Enter') { - this.validateRpc(this.state.newRpc) - } - }, - }), - h('div.settings__rpc-save-button', { - onClick: event => { - event.preventDefault() - this.validateRpc(this.state.newRpc) - }, - }, 'Save'), - ]), - ]), - ]) - ) - } - - validateRpc (newRpc) { - const { setRpcTarget, displayWarning } = this.props - - if (validUrl.isWebUri(newRpc)) { - setRpcTarget(newRpc) - } else { - const appendedRpc = `http://${newRpc}` - - if (validUrl.isWebUri(appendedRpc)) { - displayWarning('URIs require the appropriate HTTP/HTTPS prefix.') - } else { - displayWarning('Invalid RPC URI') - } - } - } - - renderStateLogs () { - return ( - h('div.settings__content-row', [ - h('div.settings__content-item', [ - h('div', 'State Logs'), - h( - 'div.settings__content-description', - 'State logs contain your public account addresses and sent transactions.' - ), - ]), - h('div.settings__content-item', [ - h('div.settings__content-item-col', [ - h('button.settings__clear-button', { - onClick (event) { - exportAsFile('MetaMask State Logs', window.logState()) - }, - }, 'Download State Logs'), - ]), - ]), - ]) - ) - } - - renderSeedWords () { - const { revealSeedConfirmation } = this.props - - return ( - h('div.settings__content-row', [ - h('div.settings__content-item', 'Reveal Seed Words'), - h('div.settings__content-item', [ - h('div.settings__content-item-col', [ - h('button.settings__clear-button.settings__clear-button--red', { - onClick (event) { - event.preventDefault() - revealSeedConfirmation() - }, - }, 'Reveal Seed Words'), - ]), - ]), - ]) - ) - } - - renderOldUI () { - const { setFeatureFlagToBeta } = this.props - - return ( - h('div.settings__content-row', [ - h('div.settings__content-item', 'Use old UI'), - h('div.settings__content-item', [ - h('div.settings__content-item-col', [ - h('button.settings__clear-button.settings__clear-button--orange', { - onClick (event) { - event.preventDefault() - setFeatureFlagToBeta() - }, - }, 'Use old UI'), - ]), - ]), - ]) - ) - } - - renderSettingsContent () { - const { warning } = this.props - - return ( - h('div.settings__content', [ - warning && h('div.settings__error', warning), - this.renderBlockieOptIn(), - this.renderCurrentConversion(), - // this.renderCurrentProvider(), - this.renderNewRpcUrl(), - this.renderStateLogs(), - this.renderSeedWords(), - this.renderOldUI(), - ]) - ) - } - - 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', 'Links'), - h('div.settings__info-link-item', [ - h('a', { - href: 'https://metamask.io/privacy.html', - target: '_blank', - }, [ - h('span.settings__info-link', 'Privacy Policy'), - ]), - ]), - h('div.settings__info-link-item', [ - h('a', { - href: 'https://metamask.io/terms.html', - target: '_blank', - }, [ - h('span.settings__info-link', 'Terms of Use'), - ]), - ]), - h('div.settings__info-link-item', [ - h('a', { - href: 'https://metamask.io/attributions.html', - target: '_blank', - }, [ - h('span.settings__info-link', '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', 'Visit our Support Center'), - ]), - ]), - h('div.settings__info-link-item', [ - h('a', { - href: 'https://metamask.io/', - target: '_blank', - }, [ - h('span.settings__info-link', 'Visit our web site'), - ]), - ]), - h('div.settings__info-link-item', [ - h('a', { - target: '_blank', - href: 'mailto:help@metamask.io?subject=Feedback', - }, [ - h('span.settings__info-link', 'Email us!'), - ]), - ]), - ]) - ) - } - - renderInfoContent () { - 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', '4.0.0'), - ]), - h('div.settings__info-item', [ - h( - 'div.settings__info-about', - 'MetaMask is designed and built in California.' - ), - ]), - ]), - 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, - setRpcTarget: PropTypes.func, - displayWarning: PropTypes.func, - revealSeedConfirmation: PropTypes.func, - setFeatureFlagToBeta: PropTypes.func, - warning: PropTypes.string, - goHome: PropTypes.func, -} - -const mapStateToProps = state => { - return { - metamask: state.metamask, - warning: state.appState.warning, - } -} - -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)), - revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()), - setUseBlockie: value => dispatch(actions.setUseBlockie(value)), - setFeatureFlagToBeta: () => dispatch(actions.setFeatureFlag('betaUI', false)), - } -} - -module.exports = connect(mapStateToProps, mapDispatchToProps)(Settings) diff --git a/ui/app/unlock.js b/ui/app/unlock.js deleted file mode 100644 index ec97b03bf..000000000 --- a/ui/app/unlock.js +++ /dev/null @@ -1,122 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const actions = require('./actions') -const getCaretCoordinates = require('textarea-caret') -const EventEmitter = require('events').EventEmitter - -const Mascot = require('./components/mascot') - -module.exports = connect(mapStateToProps)(UnlockScreen) - -inherits(UnlockScreen, Component) -function UnlockScreen () { - Component.call(this) - this.animationEventEmitter = new EventEmitter() -} - -function mapStateToProps (state) { - return { - warning: state.appState.warning, - } -} - -UnlockScreen.prototype.render = function () { - const state = this.props - const warning = state.warning - return ( - h('.flex-column', { - style: { - width: 'inherit', - }, - }, [ - h('.unlock-screen.flex-column.flex-center.flex-grow', [ - - h(Mascot, { - animationEventEmitter: this.animationEventEmitter, - }), - - h('h1', { - style: { - fontSize: '1.4em', - textTransform: 'uppercase', - color: '#7F8082', - }, - }, 'MetaMask'), - - 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: warning ? 'block' : 'none', - padding: '0 20px', - textAlign: 'center', - }, - }, warning), - - h('button.primary.cursor-pointer', { - onClick: this.onSubmit.bind(this), - style: { - margin: 10, - }, - }, 'Unlock'), - ]), - - h('.flex-row.flex-center.flex-grow', [ - h('p.pointer', { - onClick: () => this.props.dispatch(actions.forgotPassword()), - style: { - fontSize: '0.8em', - color: 'rgb(247, 134, 28)', - textDecoration: 'underline', - }, - }, 'Restore from seed phrase'), - ]), - ]) - ) -} - -UnlockScreen.prototype.componentDidMount = function () { - document.getElementById('password-box').focus() -} - -UnlockScreen.prototype.onSubmit = function (event) { - const input = document.getElementById('password-box') - const password = input.value - this.props.dispatch(actions.tryUnlockMetamask(password)) -} - -UnlockScreen.prototype.onKeyPress = function (event) { - if (event.key === 'Enter') { - this.submitPassword(event) - } -} - -UnlockScreen.prototype.submitPassword = function (event) { - var element = event.target - var password = element.value - // reset input - element.value = '' - this.props.dispatch(actions.tryUnlockMetamask(password)) -} - -UnlockScreen.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, - }) -} -- cgit From dde39e82b5723ba8056b73f0f823d40c3e702a99 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 4 Dec 2017 01:24:30 -0500 Subject: Add routes for mascara --- ui/app/app.js | 48 ++++++++++++++++++------ ui/app/components/account-menu/index.js | 22 +++-------- ui/app/components/pages/authenticated.js | 16 ++++---- ui/app/components/pages/keychains/reveal-seed.js | 5 ++- ui/app/components/pages/metamask-route.js | 28 ++++++++++++++ ui/app/components/pages/notice.js | 16 -------- ui/app/components/pages/unauthenticated/index.js | 38 +++++++++++++++++++ ui/app/first-time/init-menu.js | 2 +- ui/app/routes.js | 4 +- 9 files changed, 123 insertions(+), 56 deletions(-) create mode 100644 ui/app/components/pages/metamask-route.js create mode 100644 ui/app/components/pages/unauthenticated/index.js (limited to 'ui') diff --git a/ui/app/app.js b/ui/app/app.js index 168ec6559..68384f30d 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -5,8 +5,10 @@ const { compose } = require('recompose') const h = require('react-hyperscript') const actions = require('./actions') // mascara -const MascaraFirstTime = require('../../mascara/src/app/first-time').default +const MascaraCreatePassword = require('../../mascara/src/app/first-time/create-password-screen').default const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default +const MascaraNoticeScreen = require('../../mascara/src/app/first-time/notice-screen').default +const MascaraBackupPhraseScreen = require('../../mascara/src/app/first-time/backup-phrase-screen').default // init const InitializeMenuScreen = require('./first-time/init-menu') const NewKeyChainScreen = require('./new-keychain') @@ -22,6 +24,8 @@ const WalletView = require('./components/wallet-view') // other views const Authenticated = require('./components/pages/authenticated') +const Unauthenticated = require('./components/pages/unauthenticated') +const MetamaskRoute = require('./components/pages/metamask-route') const Settings = require('./components/pages/settings') const UnlockPage = require('./components/pages/unauthenticated/unlock') const RestoreVaultPage = require('./components/pages/keychains/restore-vault') @@ -53,7 +57,7 @@ const { IMPORT_ACCOUNT_ROUTE, SEND_ROUTE, CONFIRM_TRANSACTION_ROUTE, - INITIALIZE_MENU_ROUTE, + INITIALIZE_ROUTE, NOTICE_ROUTE, } = require('./routes') @@ -65,7 +69,7 @@ class App extends Component { } componentWillMount () { - const { currentCurrency, setCurrentCurrency } = this.props + const { currentCurrency, setCurrentCurrencyToUSD } = this.props if (!currentCurrency) { setCurrentCurrencyToUSD() @@ -77,14 +81,29 @@ class App extends Component { return ( h(Switch, [ - h(Route, { path: INITIALIZE_MENU_ROUTE, exact, component: InitializeMenuScreen }), - h(Route, { path: UNLOCK_ROUTE, exact, component: UnlockPage }), - h(Route, { path: SETTINGS_ROUTE, component: Settings }), - h(Route, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }), - h(Route, { path: NOTICE_ROUTE, exact, component: NoticeScreen }), + h(MetamaskRoute, { + path: INITIALIZE_ROUTE, + exact, + component: InitializeMenuScreen, + mascaraComponent: MascaraCreatePassword, + }), + h(MetamaskRoute, { + path: REVEAL_SEED_ROUTE, + exact, + component: RevealSeedPage, + mascaraComponent: MascaraBackupPhraseScreen, + }), + h(Unauthenticated, { path: UNLOCK_ROUTE, exact, component: UnlockPage }), + h(Unauthenticated, { path: SETTINGS_ROUTE, component: Settings }), + h(Unauthenticated, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }), + h(Unauthenticated, { + path: NOTICE_ROUTE, + exact, + component: NoticeScreen, + mascaraComponent: MascaraNoticeScreen, + }), h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, exact, component: ConfirmTxScreen }), h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }), - h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedPage }), h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }), h(Authenticated, { path: IMPORT_ACCOUNT_ROUTE, exact, component: ImportAccountPage }), h(Authenticated, { path: DEFAULT_ROUTE, exact, component: this.renderPrimary }), @@ -327,10 +346,17 @@ class App extends Component { currentView, activeAddress, unapprovedTxs = {}, + seedWords, } = this.props - if (isMascara && isOnboarding) { - return h(MascaraFirstTime) + // seed words + if (seedWords) { + log.debug('rendering seed words') + return h(Redirect, { + to: { + pathname: REVEAL_SEED_ROUTE, + }, + }) } // notices diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js index 0965cba38..0d3b43ae9 100644 --- a/ui/app/components/account-menu/index.js +++ b/ui/app/components/account-menu/index.js @@ -8,7 +8,7 @@ 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, IMPORT_ACCOUNT_ROUTE } = require('../../routes') +const { SETTINGS_ROUTE, INFO_ROUTE, IMPORT_ACCOUNT_ROUTE, DEFAULT_ROUTE } = require('../../routes') module.exports = compose( withRouter, @@ -40,22 +40,10 @@ function mapDispatchToProps (dispatch) { dispatch(actions.displayWarning(null)) dispatch(actions.toggleAccountMenu()) }, - showConfigPage: () => { - dispatch(actions.showConfigPage()) - dispatch(actions.toggleAccountMenu()) - }, showNewAccountModal: () => { dispatch(actions.showModal({ name: 'NEW_ACCOUNT' })) dispatch(actions.toggleAccountMenu()) }, - showImportPage: () => { - dispatch(actions.showImportPage()) - dispatch(actions.toggleAccountMenu()) - }, - showInfoPage: () => { - dispatch(actions.showInfoPage()) - dispatch(actions.toggleAccountMenu()) - }, } } @@ -64,10 +52,7 @@ AccountMenu.prototype.render = function () { isAccountMenuOpen, toggleAccountMenu, showNewAccountModal, - showImportPage, lockMetamask, - showConfigPage, - showInfoPage, history, } = this.props @@ -78,7 +63,10 @@ AccountMenu.prototype.render = function () { }, [ 'My Accounts', h('button.account-menu__logout-button', { - onClick: lockMetamask, + onClick: () => { + lockMetamask() + history.push(DEFAULT_ROUTE) + }, }, 'Log out'), ]), h(Divider), diff --git a/ui/app/components/pages/authenticated.js b/ui/app/components/pages/authenticated.js index 78f3a4225..89bd238d2 100644 --- a/ui/app/components/pages/authenticated.js +++ b/ui/app/components/pages/authenticated.js @@ -1,26 +1,26 @@ const { connect } = require('react-redux') const PropTypes = require('prop-types') -const { Redirect, Route } = require('react-router-dom') +const { Redirect } = require('react-router-dom') const h = require('react-hyperscript') -const { UNLOCK_ROUTE, INITIALIZE_MENU_ROUTE } = require('../../routes') +const MetamaskRoute = require('./metamask-route') +const { UNLOCK_ROUTE, INITIALIZE_ROUTE } = require('../../routes') const Authenticated = ({ component: Component, isUnlocked, isInitialized, ...props }) => { - - const render = props => { + const component = renderProps => { switch (true) { case isUnlocked: - return h(Component, { ...props }) + return h(Component, { ...renderProps }) case !isInitialized: - return h(Redirect, { to: { pathname: INITIALIZE_MENU_ROUTE } }) + return h(Redirect, { to: { pathname: INITIALIZE_ROUTE } }) default: return h(Redirect, { to: { pathname: UNLOCK_ROUTE } }) } } return ( - h(Route, { + h(MetamaskRoute, { ...props, - render, + component, }) ) } diff --git a/ui/app/components/pages/keychains/reveal-seed.js b/ui/app/components/pages/keychains/reveal-seed.js index dc2cfc23e..029eb7d8e 100644 --- a/ui/app/components/pages/keychains/reveal-seed.js +++ b/ui/app/components/pages/keychains/reveal-seed.js @@ -8,7 +8,10 @@ const { DEFAULT_ROUTE } = require('../../../routes') class RevealSeedPage extends Component { componentDidMount () { - document.getElementById('password-box').focus() + const passwordBox = document.getElementById('password-box') + if (passwordBox) { + passwordBox.focus() + } } checkConfirmation (event) { 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 index 8e68cd52b..2329a9147 100644 --- a/ui/app/components/pages/notice.js +++ b/ui/app/components/pages/notice.js @@ -53,7 +53,6 @@ class Notice extends Component { render () { const { notice = {} } = this.props const { title, date, body } = notice - // const state = this.state || { disclaimerDisabled: true } const { disclaimerDisabled } = this.state return ( @@ -156,21 +155,6 @@ class Notice extends Component { const mapStateToProps = state => { const { metamask } = state const { noActiveNotices, lastUnreadNotice, lostAccounts } = metamask - // 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()), - // }) - // } return { noActiveNotices, diff --git a/ui/app/components/pages/unauthenticated/index.js b/ui/app/components/pages/unauthenticated/index.js new file mode 100644 index 000000000..f8b5fa172 --- /dev/null +++ b/ui/app/components/pages/unauthenticated/index.js @@ -0,0 +1,38 @@ +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 Unauthenticated = ({ component: Component, isInitialized, ...props }) => { + const component = renderProps => { + return isInitialized + ? h(Component, { ...renderProps }) + : h(Redirect, { to: { pathname: INITIALIZE_ROUTE } }) + } + + return ( + h(MetamaskRoute, { + ...props, + component, + }) + ) +} + +Unauthenticated.propTypes = { + component: PropTypes.func, + isInitialized: PropTypes.bool, + isMascara: PropTypes.bool, + mascaraComponent: PropTypes.func, +} + +const mapStateToProps = state => { + const { metamask: { isInitialized, isMascara } } = state + return { + isInitialized, + isMascara, + } +} + +module.exports = connect(mapStateToProps)(Unauthenticated) diff --git a/ui/app/first-time/init-menu.js b/ui/app/first-time/init-menu.js index 6832f5ddf..addcedc77 100644 --- a/ui/app/first-time/init-menu.js +++ b/ui/app/first-time/init-menu.js @@ -35,7 +35,7 @@ class InitializeMenuScreen extends Component { const { warning } = this.state return ( - h('.initialize-screen.flex-column.flex-center.flex-grow', [ + h('.initialize-screen.flex-column.flex-center', [ h(Mascot, { animationEventEmitter: this.animationEventEmitter, diff --git a/ui/app/routes.js b/ui/app/routes.js index 1305d6b1e..72be616a7 100644 --- a/ui/app/routes.js +++ b/ui/app/routes.js @@ -8,7 +8,7 @@ const ADD_TOKEN_ROUTE = '/add-token' const IMPORT_ACCOUNT_ROUTE = '/import-account' const SEND_ROUTE = '/send' const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction' -const INITIALIZE_MENU_ROUTE = '/initialize-menu' +const INITIALIZE_ROUTE = '/initialize' const NOTICE_ROUTE = '/notice' module.exports = { @@ -22,6 +22,6 @@ module.exports = { IMPORT_ACCOUNT_ROUTE, SEND_ROUTE, CONFIRM_TRANSACTION_ROUTE, - INITIALIZE_MENU_ROUTE, + INITIALIZE_ROUTE, NOTICE_ROUTE, } -- cgit From ec5e0a711cac3d35afcb1ecf5881f11adb4b3a06 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 4 Dec 2017 03:06:09 -0500 Subject: Fix lint errors --- ui/app/app.js | 54 ++++++++++++++++++++++++++++++++-------- ui/app/components/tx-list.js | 2 +- ui/app/components/wallet-view.js | 1 - ui/app/main-container.js | 1 - 4 files changed, 44 insertions(+), 14 deletions(-) (limited to 'ui') diff --git a/ui/app/app.js b/ui/app/app.js index 68384f30d..3f6795386 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -1,6 +1,7 @@ const { Component } = require('react') +const PropTypes = require('prop-types') const { connect } = require('react-redux') -const { Switch, Route, Redirect, withRouter } = require('react-router-dom') +const { Switch, Redirect, withRouter } = require('react-router-dom') const { compose } = require('recompose') const h = require('react-hyperscript') const actions = require('./actions') @@ -16,8 +17,6 @@ const NewKeyChainScreen = require('./new-keychain') const MainContainer = require('./main-container') const SendTransactionScreen2 = require('./components/send/send-v2-container') const ConfirmTxScreen = require('./conf-tx') -// notice -const generateLostAccountsNotice = require('../lib/lost-accounts-notice') // slideout menu const WalletView = require('./components/wallet-view') @@ -112,9 +111,15 @@ class App extends Component { } render () { - var props = this.props - const { isLoading, loadingMessage, network } = props - const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config' + const { + isLoading, + loadingMessage, + network, + provider, + frequentRpcList, + currentView, + } = this.props + const isLoadingNetwork = network === 'loading' && currentView.name !== 'config' const loadMessage = loadingMessage || isLoadingNetwork ? `Connecting to ${this.getNetworkName()}` : null log.debug('Main ui render function') @@ -139,8 +144,8 @@ class App extends Component { // network dropdown h(NetworkDropdown, { - provider: this.props.provider, - frequentRpcList: this.props.frequentRpcList, + provider, + frequentRpcList, }, []), h(AccountMenu), @@ -153,7 +158,6 @@ class App extends Component { // content this.renderRoutes(), - // this.renderPrimary(), ]) ) } @@ -337,8 +341,6 @@ class App extends Component { renderPrimary () { log.debug('rendering primary') const { - isMascara, - isOnboarding, noActiveNotices, lostAccounts, isInitialized, @@ -558,6 +560,36 @@ class App extends Component { } } +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, + 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, +} + function mapStateToProps (state) { const { appState, metamask } = state const { diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js index 6f18ea814..2c4c3dd31 100644 --- a/ui/app/components/tx-list.js +++ b/ui/app/components/tx-list.js @@ -94,7 +94,7 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa transactionHash, transactionNetworkId, } = props - const { showConfTxPage, history } = this.props + const { history } = this.props const opts = { key: transActionId || transactionHash, diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 3b4443ef6..72ffc5d65 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -94,7 +94,6 @@ WalletView.prototype.render = function () { keyrings, showAccountDetailModal, hideSidebar, - showAddTokenPage, history, } = this.props // temporary logs + fake extra wallets diff --git a/ui/app/main-container.js b/ui/app/main-container.js index ad50ee13d..fabf4e563 100644 --- a/ui/app/main-container.js +++ b/ui/app/main-container.js @@ -2,7 +2,6 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const AccountAndTransactionDetails = require('./account-and-transaction-details') -const UnlockScreen = require('./components/pages/unauthenticated/unlock') module.exports = MainContainer -- cgit From d9ea2df6c2a2cabedead09f90c3c9bb7b4c37dd1 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 4 Dec 2017 14:29:52 -0500 Subject: Add route for Mascara confirm seed --- ui/app/app.js | 11 +++++++++-- ui/app/routes.js | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) (limited to 'ui') diff --git a/ui/app/app.js b/ui/app/app.js index 3f6795386..8c3cfee2f 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -9,7 +9,8 @@ const actions = require('./actions') const MascaraCreatePassword = require('../../mascara/src/app/first-time/create-password-screen').default const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default const MascaraNoticeScreen = require('../../mascara/src/app/first-time/notice-screen').default -const MascaraBackupPhraseScreen = require('../../mascara/src/app/first-time/backup-phrase-screen').default +const MascaraSeedScreen = require('../../mascara/src/app/first-time/seed-screen').default +const MascaraConfirmSeedScreen = require('../../mascara/src/app/first-time/confirm-seed-screen').default // init const InitializeMenuScreen = require('./first-time/init-menu') const NewKeyChainScreen = require('./new-keychain') @@ -51,6 +52,7 @@ const { UNLOCK_ROUTE, SETTINGS_ROUTE, REVEAL_SEED_ROUTE, + CONFIRM_SEED_ROUTE, RESTORE_VAULT_ROUTE, ADD_TOKEN_ROUTE, IMPORT_ACCOUNT_ROUTE, @@ -90,7 +92,12 @@ class App extends Component { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedPage, - mascaraComponent: MascaraBackupPhraseScreen, + mascaraComponent: MascaraSeedScreen, + }), + h(MetamaskRoute, { + path: CONFIRM_SEED_ROUTE, + exact, + mascaraComponent: MascaraConfirmSeedScreen, }), h(Unauthenticated, { path: UNLOCK_ROUTE, exact, component: UnlockPage }), h(Unauthenticated, { path: SETTINGS_ROUTE, component: Settings }), diff --git a/ui/app/routes.js b/ui/app/routes.js index 72be616a7..de96eb3f5 100644 --- a/ui/app/routes.js +++ b/ui/app/routes.js @@ -2,7 +2,8 @@ const DEFAULT_ROUTE = '/' const UNLOCK_ROUTE = '/unlock' const SETTINGS_ROUTE = '/settings' const INFO_ROUTE = '/settings/info' -const REVEAL_SEED_ROUTE = '/reveal-seed-confirm' +const REVEAL_SEED_ROUTE = '/seed' +const CONFIRM_SEED_ROUTE = '/confirm-seed' const RESTORE_VAULT_ROUTE = '/restore-vault' const ADD_TOKEN_ROUTE = '/add-token' const IMPORT_ACCOUNT_ROUTE = '/import-account' @@ -17,6 +18,7 @@ module.exports = { SETTINGS_ROUTE, INFO_ROUTE, REVEAL_SEED_ROUTE, + CONFIRM_SEED_ROUTE, RESTORE_VAULT_ROUTE, ADD_TOKEN_ROUTE, IMPORT_ACCOUNT_ROUTE, -- cgit From 706a6b0ad6d7b6e2d56252f17713e63430d84abc Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 4 Dec 2017 15:13:02 -0500 Subject: Add initialized checks for first time flow routes --- ui/app/app.js | 16 +- ui/app/components/pages/authenticated.js | 28 ++-- ui/app/components/pages/initialized.js | 25 ++++ ui/app/components/pages/unauthenticated/index.js | 38 ----- ui/app/components/pages/unauthenticated/unlock.js | 172 ---------------------- ui/app/components/pages/unlock.js | 170 +++++++++++++++++++++ 6 files changed, 213 insertions(+), 236 deletions(-) create mode 100644 ui/app/components/pages/initialized.js delete mode 100644 ui/app/components/pages/unauthenticated/index.js delete mode 100644 ui/app/components/pages/unauthenticated/unlock.js create mode 100644 ui/app/components/pages/unlock.js (limited to 'ui') diff --git a/ui/app/app.js b/ui/app/app.js index 8c3cfee2f..6717b3d67 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -24,10 +24,10 @@ const WalletView = require('./components/wallet-view') // other views const Authenticated = require('./components/pages/authenticated') -const Unauthenticated = require('./components/pages/unauthenticated') +const Initialized = require('./components/pages/initialized') const MetamaskRoute = require('./components/pages/metamask-route') const Settings = require('./components/pages/settings') -const UnlockPage = require('./components/pages/unauthenticated/unlock') +const UnlockPage = require('./components/pages/unlock') const RestoreVaultPage = require('./components/pages/keychains/restore-vault') const RevealSeedPage = require('./components/pages/keychains/reveal-seed') const AddTokenPage = require('./components/pages/add-token') @@ -88,21 +88,21 @@ class App extends Component { component: InitializeMenuScreen, mascaraComponent: MascaraCreatePassword, }), - h(MetamaskRoute, { + h(Initialized, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedPage, mascaraComponent: MascaraSeedScreen, }), - h(MetamaskRoute, { + h(Initialized, { path: CONFIRM_SEED_ROUTE, exact, mascaraComponent: MascaraConfirmSeedScreen, }), - h(Unauthenticated, { path: UNLOCK_ROUTE, exact, component: UnlockPage }), - h(Unauthenticated, { path: SETTINGS_ROUTE, component: Settings }), - h(Unauthenticated, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }), - h(Unauthenticated, { + 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, diff --git a/ui/app/components/pages/authenticated.js b/ui/app/components/pages/authenticated.js index 89bd238d2..1f6b0be49 100644 --- a/ui/app/components/pages/authenticated.js +++ b/ui/app/components/pages/authenticated.js @@ -5,28 +5,20 @@ const h = require('react-hyperscript') const MetamaskRoute = require('./metamask-route') const { UNLOCK_ROUTE, INITIALIZE_ROUTE } = require('../../routes') -const Authenticated = ({ component: Component, isUnlocked, isInitialized, ...props }) => { - const component = renderProps => { - switch (true) { - case isUnlocked: - return h(Component, { ...renderProps }) - case !isInitialized: - return h(Redirect, { to: { pathname: INITIALIZE_ROUTE } }) - default: - return h(Redirect, { to: { pathname: UNLOCK_ROUTE } }) - } - } +const Authenticated = props => { + const { isUnlocked, isInitialized } = props - return ( - h(MetamaskRoute, { - ...props, - component, - }) - ) + 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 = { - component: PropTypes.func, isUnlocked: PropTypes.bool, isInitialized: PropTypes.bool, } 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/unauthenticated/index.js b/ui/app/components/pages/unauthenticated/index.js deleted file mode 100644 index f8b5fa172..000000000 --- a/ui/app/components/pages/unauthenticated/index.js +++ /dev/null @@ -1,38 +0,0 @@ -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 Unauthenticated = ({ component: Component, isInitialized, ...props }) => { - const component = renderProps => { - return isInitialized - ? h(Component, { ...renderProps }) - : h(Redirect, { to: { pathname: INITIALIZE_ROUTE } }) - } - - return ( - h(MetamaskRoute, { - ...props, - component, - }) - ) -} - -Unauthenticated.propTypes = { - component: PropTypes.func, - isInitialized: PropTypes.bool, - isMascara: PropTypes.bool, - mascaraComponent: PropTypes.func, -} - -const mapStateToProps = state => { - const { metamask: { isInitialized, isMascara } } = state - return { - isInitialized, - isMascara, - } -} - -module.exports = connect(mapStateToProps)(Unauthenticated) diff --git a/ui/app/components/pages/unauthenticated/unlock.js b/ui/app/components/pages/unauthenticated/unlock.js deleted file mode 100644 index 72f27c11d..000000000 --- a/ui/app/components/pages/unauthenticated/unlock.js +++ /dev/null @@ -1,172 +0,0 @@ -const { Component } = require('react') -const PropTypes = require('prop-types') -const { connect } = require('react-redux') -const h = require('react-hyperscript') -const { Redirect, withRouter } = require('react-router-dom') -const { compose } = require('recompose') -const { tryUnlockMetamask, forgotPassword } = require('../../../actions') -const getCaretCoordinates = require('textarea-caret') -const EventEmitter = require('events').EventEmitter -const Mascot = require('../../mascot') -const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../../routes') - -class UnlockScreen extends Component { - constructor (props) { - super(props) - - this.state = { - error: null, - } - - this.animationEventEmitter = new EventEmitter() - } - - 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 - const { isUnlocked, history } = this.props - - if (isUnlocked) { - return ( - h(Redirect, { - to: { - pathname: DEFAULT_ROUTE, - }, - }) - ) - } - - return ( - h('.unlock-page.main-container', [ - h('.flex-column', { - style: { - width: 'inherit', - }, - }, [ - h('.unlock-screen.flex-column.flex-center.flex-grow', [ - - h(Mascot, { - animationEventEmitter: this.animationEventEmitter, - }), - - h('h1', { - style: { - fontSize: '1.4em', - textTransform: 'uppercase', - color: '#7F8082', - }, - }, 'MetaMask'), - - 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, - }, - }, 'Unlock'), - - h('.flex-row.flex-center.flex-grow', [ - h('p.pointer', { - onClick: () => history.push(RESTORE_VAULT_ROUTE), - style: { - fontSize: '0.8em', - color: 'rgb(247, 134, 28)', - textDecoration: 'underline', - }, - }, 'Restore from seed phrase'), - ]), - ]), - ]), - ]) - ) - } -} - -UnlockScreen.propTypes = { - forgotPassword: PropTypes.func, - tryUnlockMetamask: PropTypes.func, - history: PropTypes.object, - isUnlocked: PropTypes.bool, -} - -const mapStateToProps = state => { - const { metamask: { isUnlocked } } = state - return { - isUnlocked, - } -} - -const mapDispatchToProps = dispatch => { - return { - forgotPassword: () => dispatch(forgotPassword()), - tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)), - } -} - -module.exports = compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(UnlockScreen) diff --git a/ui/app/components/pages/unlock.js b/ui/app/components/pages/unlock.js new file mode 100644 index 000000000..27e093a29 --- /dev/null +++ b/ui/app/components/pages/unlock.js @@ -0,0 +1,170 @@ +const { Component } = require('react') +const PropTypes = require('prop-types') +const { connect } = require('react-redux') +const h = require('react-hyperscript') +const { withRouter } = require('react-router-dom') +const { compose } = require('recompose') +const { tryUnlockMetamask, forgotPassword } = require('../../actions') +const getCaretCoordinates = require('textarea-caret') +const EventEmitter = require('events').EventEmitter +const Mascot = require('../mascot') +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 + const { history } = this.props + + return ( + h('.unlock-page.main-container', [ + h('.flex-column', { + style: { + width: 'inherit', + }, + }, [ + h('.unlock-screen.flex-column.flex-center.flex-grow', [ + + h(Mascot, { + animationEventEmitter: this.animationEventEmitter, + }), + + h('h1', { + style: { + fontSize: '1.4em', + textTransform: 'uppercase', + color: '#7F8082', + }, + }, 'MetaMask'), + + 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, + }, + }, 'Unlock'), + + h('.flex-row.flex-center.flex-grow', [ + h('p.pointer', { + onClick: () => history.push(RESTORE_VAULT_ROUTE), + style: { + fontSize: '0.8em', + color: 'rgb(247, 134, 28)', + textDecoration: 'underline', + }, + }, 'Restore from seed phrase'), + ]), + ]), + ]), + ]) + ) + } +} + +UnlockScreen.propTypes = { + forgotPassword: PropTypes.func, + tryUnlockMetamask: PropTypes.func, + history: PropTypes.object, + isUnlocked: PropTypes.bool, +} + +const mapStateToProps = state => { + const { metamask: { isUnlocked } } = state + return { + isUnlocked, + } +} + +const mapDispatchToProps = dispatch => { + return { + forgotPassword: () => dispatch(forgotPassword()), + tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)), + } +} + +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(UnlockScreen) -- cgit From 5d1187c37bfee988d7384f189f228882ce847005 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 4 Dec 2017 22:27:42 -0500 Subject: Add route for signature request --- ui/app/actions.js | 131 ++++++++--- ui/app/app.js | 25 ++- ui/app/components/pages/signature-request.js | 321 +++++++++++++++++++++++++++ ui/app/components/signature-request.js | 253 --------------------- ui/app/conf-tx.js | 27 +-- ui/app/routes.js | 2 + 6 files changed, 454 insertions(+), 305 deletions(-) create mode 100644 ui/app/components/pages/signature-request.js delete mode 100644 ui/app/components/signature-request.js (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 51cc4dfbf..60bd4a1ee 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -489,35 +489,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) + }) }) } } @@ -527,16 +539,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) + }) }) } } @@ -744,21 +762,66 @@ 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) { diff --git a/ui/app/app.js b/ui/app/app.js index 6717b3d67..07be459fc 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -33,6 +33,7 @@ const RevealSeedPage = require('./components/pages/keychains/reveal-seed') const AddTokenPage = require('./components/pages/add-token') const ImportAccountPage = require('./components/pages/import-account') const NoticeScreen = require('./components/pages/notice') +const SignatureRequestPage = require('./components/pages/signature-request') const Loading = require('./components/loading') const NetworkIndicator = require('./components/network') @@ -60,6 +61,7 @@ const { CONFIRM_TRANSACTION_ROUTE, INITIALIZE_ROUTE, NOTICE_ROUTE, + SIGNATURE_REQUEST_ROUTE, } = require('./routes') class App extends Component { @@ -112,6 +114,7 @@ class App extends Component { h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }), h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }), h(Authenticated, { path: IMPORT_ACCOUNT_ROUTE, exact, component: ImportAccountPage }), + h(Authenticated, { path: SIGNATURE_REQUEST_ROUTE, exact, component: SignatureRequestPage }), h(Authenticated, { path: DEFAULT_ROUTE, exact, component: this.renderPrimary }), ]) ) @@ -356,6 +359,9 @@ class App extends Component { activeAddress, unapprovedTxs = {}, seedWords, + unapprovedMsgCount = 0, + unapprovedPersonalMsgCount = 0, + unapprovedTypedMessagesCount = 0, } = this.props // seed words @@ -386,6 +392,15 @@ class App extends Component { }) } + // unapproved messages + if (unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { + return h(Redirect, { + to: { + pathname: SIGNATURE_REQUEST_ROUTE, + }, + }) + } + // if (!props.noActiveNotices) { // log.debug('rendering notice screen for unread notices.') // return h(NoticeScreen, { @@ -595,6 +610,9 @@ App.propTypes = { activeAddress: PropTypes.string, unapprovedTxs: PropTypes.object, seedWords: PropTypes.string, + unapprovedMsgCount: PropTypes.number, + unapprovedPersonalMsgCount: PropTypes.number, + unapprovedTypedMessagesCount: PropTypes.number, } function mapStateToProps (state) { @@ -617,6 +635,9 @@ function mapStateToProps (state) { unapprovedTxs, lastUnreadNotice, lostAccounts, + unapprovedMsgCount, + unapprovedPersonalMsgCount, + unapprovedTypedMessagesCount, } = metamask const selected = address || Object.keys(accounts)[0] @@ -637,7 +658,9 @@ function mapStateToProps (state) { isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized), seedWords: state.metamask.seedWords, unapprovedTxs, - unapprovedMsgs: state.metamask.unapprovedMsgs, + unapprovedMsgCount, + unapprovedPersonalMsgCount, + unapprovedTypedMessagesCount, menuOpen: state.appState.menuOpen, network: state.metamask.network, provider: state.metamask.provider, diff --git a/ui/app/components/pages/signature-request.js b/ui/app/components/pages/signature-request.js new file mode 100644 index 000000000..0c9f4a091 --- /dev/null +++ b/ui/app/components/pages/signature-request.js @@ -0,0 +1,321 @@ +const { Component } = require('react') +const h = require('react-hyperscript') +const PropTypes = require('prop-types') +const Identicon = require('../identicon') +const { connect } = require('react-redux') +const ethUtil = require('ethereumjs-util') +const classnames = require('classnames') + +const AccountDropdownMini = require('../dropdowns/account-dropdown-mini') + +const actions = require('../../actions') +const { conversionUtil } = require('../../conversion-util') +const txHelper = require('../../../lib/tx-helper') +const { DEFAULT_ROUTE } = require('../../routes') + +const { + getSelectedAccount, + getCurrentAccountWithSendEtherInfo, + getSelectedAddress, + accountsWithSendEtherInfoSelector, + conversionRateSelector, +} = require('../../selectors.js') + +class SignatureRequest extends Component { + constructor (props) { + super(props) + + this.state = { + selectedAccount: props.selectedAccount, + accountDropdownOpen: false, + } + } + + componentWillMount () { + const { + unapprovedMsgCount = 0, + unapprovedPersonalMsgCount = 0, + unapprovedTypedMessagesCount = 0, + } = this.props + + if (unapprovedMsgCount + unapprovedPersonalMsgCount + unapprovedTypedMessagesCount < 1) { + this.props.history.push(DEFAULT_ROUTE) + } + } + + renderHeader () { + return h('div.request-signature__header', [ + + h('div.request-signature__header-background'), + + h('div.request-signature__header__text', 'Signature Request'), + + h('div.request-signature__header__tip-container', [ + h('div.request-signature__header__tip'), + ]), + + ]) + } + + renderAccountDropdown () { + const { + selectedAccount, + accountDropdownOpen, + } = this.state + + const { accounts } = this.props + + return h('div.request-signature__account', [ + + h('div.request-signature__account-text', ['Account:']), + + h(AccountDropdownMini, { + selectedAccount, + accounts, + onSelect: selectedAccount => this.setState({ selectedAccount }), + dropdownOpen: accountDropdownOpen, + openDropdown: () => this.setState({ accountDropdownOpen: true }), + closeDropdown: () => this.setState({ accountDropdownOpen: false }), + }), + + ]) + } + + renderBalance () { + const { balance, conversionRate } = this.props + + const balanceInEther = conversionUtil(balance, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + fromDenomination: 'WEI', + numberOfDecimals: 6, + conversionRate, + }) + + return h('div.request-signature__balance', [ + + h('div.request-signature__balance-text', ['Balance:']), + + h('div.request-signature__balance-value', `${balanceInEther} ETH`), + + ]) + } + + renderAccountInfo () { + return h('div.request-signature__account-info', [ + + this.renderAccountDropdown(), + + this.renderRequestIcon(), + + this.renderBalance(), + + ]) + } + + renderRequestIcon () { + const { requesterAddress } = this.props + + return h('div.request-signature__request-icon', [ + h(Identicon, { + diameter: 40, + address: requesterAddress, + }), + ]) + } + + renderRequestInfo () { + return h('div.request-signature__request-info', [ + + h('div.request-signature__headline', [ + `Your signature is being requested`, + ]), + + ]) + } + + msgHexToText (hex) { + try { + const stripped = ethUtil.stripHexPrefix(hex) + const buff = Buffer.from(stripped, 'hex') + return buff.toString('utf8') + } catch (e) { + return hex + } + } + + renderBody () { + let rows = [] + let notice = 'You are signing:' + + const { txData = {} } = this.props + const { type, msgParams = {} } = txData + const { data } = msgParams + + if (type === 'personal_sign') { + rows = [{ name: 'Message', value: this.msgHexToText(data) }] + } else if (type === 'eth_signTypedData') { + rows = data + } else if (type === 'eth_sign') { + rows = [{ name: 'Message', value: data }] + notice = `Signing this message can have + dangerous side effects. Only sign messages from + sites you fully trust with your entire account. + This dangerous method will be removed in a future version. ` + } + + return h('div.request-signature__body', {}, [ + + this.renderAccountInfo(), + + this.renderRequestInfo(), + + h('div.request-signature__notice', { + className: classnames({ + 'request-signature__notice': type === 'personal_sign' || type === 'eth_signTypedData', + 'request-signature__warning': type === 'eth_sign', + }), + }, [notice]), + + h('div.request-signature__rows', [ + + ...rows.map(({ name, value }) => { + return h('div.request-signature__row', [ + h('div.request-signature__row-title', [`${name}:`]), + h('div.request-signature__row-value', value), + ]) + }), + + ]), + + ]) + } + + renderFooter () { + const { + txData = {}, + signPersonalMessage, + signTypedMessage, + cancelPersonalMessage, + cancelTypedMessage, + signMessage, + cancelMessage, + history, + } = this.props + + const { type } = txData + + let cancel = () => Promise.resolve() + let sign = () => Promise.resolve() + const { msgParams: params = {}, id } = txData + params.id = id + params.metamaskId = id + + switch (type) { + case 'personal_sign': + cancel = () => cancelPersonalMessage(params) + sign = () => signPersonalMessage(params) + break + case 'eth_signTypedData': + cancel = () => cancelTypedMessage(params) + sign = () => signTypedMessage(params) + break + case 'eth_sign': + cancel = () => cancelMessage(params) + sign = () => signMessage(params) + break + } + + return h('div.request-signature__footer', [ + h('button.request-signature__footer__cancel-button', { + onClick: () => { + cancel().then(() => history.push(DEFAULT_ROUTE)) + }, + }, 'CANCEL'), + h('button.request-signature__footer__sign-button', { + onClick: () => { + sign().then(() => history.push(DEFAULT_ROUTE)) + }, + }, 'SIGN'), + ]) + } + + render () { + return ( + h('div.request-signature__container', [ + + this.renderHeader(), + + this.renderBody(), + + this.renderFooter(), + + ]) + ) + } +} + +SignatureRequest.propTypes = { + txData: PropTypes.object, + signPersonalMessage: PropTypes.func, + cancelPersonalMessage: PropTypes.func, + signTypedMessage: PropTypes.func, + cancelTypedMessage: PropTypes.func, + signMessage: PropTypes.func, + cancelMessage: PropTypes.func, + requesterAddress: PropTypes.string, + accounts: PropTypes.array, + conversionRate: PropTypes.number, + balance: PropTypes.string, + selectedAccount: PropTypes.object, + history: PropTypes.object, + unapprovedMsgCount: PropTypes.number, + unapprovedPersonalMsgCount: PropTypes.number, + unapprovedTypedMessagesCount: PropTypes.number, +} + +const mapStateToProps = state => { + const { metamask } = state + const { + unapprovedTxs, + unapprovedMsgs, + unapprovedPersonalMsgs, + unapprovedTypedMessages, + network, + unapprovedMsgCount, + unapprovedPersonalMsgCount, + unapprovedTypedMessagesCount, + } = metamask + const unconfTxList = txHelper( + unapprovedTxs, + unapprovedMsgs, + unapprovedPersonalMsgs, + unapprovedTypedMessages, + network + ) || [] + + return { + balance: getSelectedAccount(state).balance, + selectedAccount: getCurrentAccountWithSendEtherInfo(state), + selectedAddress: getSelectedAddress(state), + accounts: accountsWithSendEtherInfoSelector(state), + conversionRate: conversionRateSelector(state), + unapprovedMsgCount, + unapprovedPersonalMsgCount, + unapprovedTypedMessagesCount, + txData: unconfTxList[0] || {}, + } +} + +const mapDispatchToProps = dispatch => { + return { + signPersonalMessage: params => dispatch(actions.signPersonalMsg(params)), + cancelPersonalMessage: params => dispatch(actions.cancelPersonalMsg(params)), + signTypedMessage: params => dispatch(actions.signTypedMsg(params)), + cancelTypedMessage: params => dispatch(actions.cancelTypedMsg(params)), + signMessage: params => dispatch(actions.signMsg(params)), + cancelMessage: params => dispatch(actions.cancelMsg(params)), + } +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(SignatureRequest) diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js deleted file mode 100644 index c5cc23aa8..000000000 --- a/ui/app/components/signature-request.js +++ /dev/null @@ -1,253 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const Identicon = require('./identicon') -const connect = require('react-redux').connect -const ethUtil = require('ethereumjs-util') -const classnames = require('classnames') - -const AccountDropdownMini = require('./dropdowns/account-dropdown-mini') - -const actions = require('../actions') -const { conversionUtil } = require('../conversion-util') - -const { - getSelectedAccount, - getCurrentAccountWithSendEtherInfo, - getSelectedAddress, - accountsWithSendEtherInfoSelector, - conversionRateSelector, -} = require('../selectors.js') - -function mapStateToProps (state) { - return { - balance: getSelectedAccount(state).balance, - selectedAccount: getCurrentAccountWithSendEtherInfo(state), - selectedAddress: getSelectedAddress(state), - requester: null, - requesterAddress: null, - accounts: accountsWithSendEtherInfoSelector(state), - conversionRate: conversionRateSelector(state), - } -} - -function mapDispatchToProps (dispatch) { - return { - goHome: () => dispatch(actions.goHome()), - } -} - -module.exports = connect(mapStateToProps, mapDispatchToProps)(SignatureRequest) - -inherits(SignatureRequest, Component) -function SignatureRequest (props) { - Component.call(this) - - this.state = { - selectedAccount: props.selectedAccount, - accountDropdownOpen: false, - } -} - -SignatureRequest.prototype.renderHeader = function () { - return h('div.request-signature__header', [ - - h('div.request-signature__header-background'), - - h('div.request-signature__header__text', 'Signature Request'), - - h('div.request-signature__header__tip-container', [ - h('div.request-signature__header__tip'), - ]), - - ]) -} - -SignatureRequest.prototype.renderAccountDropdown = function () { - const { - selectedAccount, - accountDropdownOpen, - } = this.state - - const { - accounts, - } = this.props - - return h('div.request-signature__account', [ - - h('div.request-signature__account-text', ['Account:']), - - h(AccountDropdownMini, { - selectedAccount, - accounts, - onSelect: selectedAccount => this.setState({ selectedAccount }), - dropdownOpen: accountDropdownOpen, - openDropdown: () => this.setState({ accountDropdownOpen: true }), - closeDropdown: () => this.setState({ accountDropdownOpen: false }), - }), - - ]) -} - -SignatureRequest.prototype.renderBalance = function () { - const { balance, conversionRate } = this.props - - const balanceInEther = conversionUtil(balance, { - fromNumericBase: 'hex', - toNumericBase: 'dec', - fromDenomination: 'WEI', - numberOfDecimals: 6, - conversionRate, - }) - - return h('div.request-signature__balance', [ - - h('div.request-signature__balance-text', ['Balance:']), - - h('div.request-signature__balance-value', `${balanceInEther} ETH`), - - ]) -} - -SignatureRequest.prototype.renderAccountInfo = function () { - return h('div.request-signature__account-info', [ - - this.renderAccountDropdown(), - - this.renderRequestIcon(), - - this.renderBalance(), - - ]) -} - -SignatureRequest.prototype.renderRequestIcon = function () { - const { requesterAddress } = this.props - - return h('div.request-signature__request-icon', [ - h(Identicon, { - diameter: 40, - address: requesterAddress, - }), - ]) -} - -SignatureRequest.prototype.renderRequestInfo = function () { - return h('div.request-signature__request-info', [ - - h('div.request-signature__headline', [ - `Your signature is being requested`, - ]), - - ]) -} - -SignatureRequest.prototype.msgHexToText = function (hex) { - try { - const stripped = ethUtil.stripHexPrefix(hex) - const buff = Buffer.from(stripped, 'hex') - return buff.toString('utf8') - } catch (e) { - return hex - } -} - -SignatureRequest.prototype.renderBody = function () { - let rows - let notice = 'You are signing:' - - const { txData } = this.props - const { type, msgParams: { data } } = txData - - if (type === 'personal_sign') { - rows = [{ name: 'Message', value: this.msgHexToText(data) }] - } else if (type === 'eth_signTypedData') { - rows = data - } else if (type === 'eth_sign') { - rows = [{ name: 'Message', value: data }] - notice = `Signing this message can have - dangerous side effects. Only sign messages from - sites you fully trust with your entire account. - This dangerous method will be removed in a future version. ` - } - - return h('div.request-signature__body', {}, [ - - this.renderAccountInfo(), - - this.renderRequestInfo(), - - h('div.request-signature__notice', { - className: classnames({ - 'request-signature__notice': type === 'personal_sign' || type === 'eth_signTypedData', - 'request-signature__warning': type === 'eth_sign', - }), - }, [notice]), - - h('div.request-signature__rows', [ - - ...rows.map(({ name, value }) => { - return h('div.request-signature__row', [ - h('div.request-signature__row-title', [`${name}:`]), - h('div.request-signature__row-value', value), - ]) - }), - - ]), - - ]) -} - -SignatureRequest.prototype.renderFooter = function () { - const { - signPersonalMessage, - signTypedMessage, - cancelPersonalMessage, - cancelTypedMessage, - signMessage, - cancelMessage, - } = this.props - - const { txData } = this.props - const { type } = txData - - let cancel - let sign - if (type === 'personal_sign') { - cancel = cancelPersonalMessage - sign = signPersonalMessage - } else if (type === 'eth_signTypedData') { - cancel = cancelTypedMessage - sign = signTypedMessage - } else if (type === 'eth_sign') { - cancel = cancelMessage - sign = signMessage - } - - return h('div.request-signature__footer', [ - h('button.request-signature__footer__cancel-button', { - onClick: cancel, - }, 'CANCEL'), - h('button.request-signature__footer__sign-button', { - onClick: sign, - }, 'SIGN'), - ]) -} - -SignatureRequest.prototype.render = function () { - return ( - - h('div.request-signature__container', [ - - this.renderHeader(), - - this.renderBody(), - - this.renderFooter(), - - ]) - - ) - -} - diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index ce0012d5b..05bbd2696 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -8,7 +8,6 @@ const actions = require('./actions') const txHelper = require('../lib/tx-helper') const PendingTx = require('./components/pending-tx') -const SignatureRequest = require('./components/signature-request') // const PendingMsg = require('./components/pending-msg') // const PendingPersonalMsg = require('./components/pending-personal-msg') // const PendingTypedMsg = require('./components/pending-typed-msg') @@ -21,6 +20,12 @@ module.exports = compose( )(ConfirmTxScreen) function mapStateToProps (state) { + const { metamask } = state + const { + unapprovedMsgCount, + unapprovedPersonalMsgCount, + } = metamask + return { identities: state.metamask.identities, accounts: state.metamask.accounts, @@ -37,6 +42,8 @@ function mapStateToProps (state) { currentCurrency: state.metamask.currentCurrency, blockGasLimit: state.metamask.currentBlockGasLimit, computedBalances: state.metamask.computedBalances, + unapprovedMsgCount, + unapprovedPersonalMsgCount, } } @@ -125,27 +132,13 @@ ConfirmTxScreen.prototype.render = function () { function currentTxView (opts) { log.info('rendering current tx view') const { txData } = opts - const { txParams, msgParams } = txData + const { txParams } = txData if (txParams) { log.debug('txParams detected, rendering pending tx') return h(PendingTx, opts) - } else if (msgParams) { - log.debug('msgParams detected, rendering pending msg') - - return h(SignatureRequest, opts) - - // if (type === 'eth_sign') { - // log.debug('rendering eth_sign message') - // return h(PendingMsg, opts) - // } else if (type === 'personal_sign') { - // log.debug('rendering personal_sign message') - // return h(PendingPersonalMsg, opts) - // } else if (type === 'eth_signTypedData') { - // log.debug('rendering eth_signTypedData message') - // return h(PendingTypedMsg, opts) - // } } + return h(Loading) } diff --git a/ui/app/routes.js b/ui/app/routes.js index de96eb3f5..d1d634db2 100644 --- a/ui/app/routes.js +++ b/ui/app/routes.js @@ -11,6 +11,7 @@ const SEND_ROUTE = '/send' const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction' const INITIALIZE_ROUTE = '/initialize' const NOTICE_ROUTE = '/notice' +const SIGNATURE_REQUEST_ROUTE = '/signature-request' module.exports = { DEFAULT_ROUTE, @@ -26,4 +27,5 @@ module.exports = { CONFIRM_TRANSACTION_ROUTE, INITIALIZE_ROUTE, NOTICE_ROUTE, + SIGNATURE_REQUEST_ROUTE, } -- cgit From 0c6fef3dec4f3ba70e8e86275ee9db4f2d58d129 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 31 Jan 2018 18:08:49 -0800 Subject: Add create new account routes, fix conflicts from uat updates --- ui/app/accounts/import/index.js | 80 ---------------- ui/app/accounts/new-account/create-form.js | 96 ------------------- ui/app/accounts/new-account/index.js | 81 ---------------- ui/app/app.js | 6 +- ui/app/components/account-menu/index.js | 18 +++- ui/app/components/pages/add-token.js | 1 - .../pages/create-account/import-account/index.js | 80 ++++++++++++++++ .../pages/create-account/import-account/json.js | 106 +++++++++++++++++++++ .../create-account/import-account/private-key.js | 83 ++++++++++++++++ .../pages/create-account/import-account/seed.js | 30 ++++++ ui/app/components/pages/create-account/index.js | 81 ++++++++++++++++ .../components/pages/create-account/new-account.js | 95 ++++++++++++++++++ ui/app/components/pages/import-account/index.js | 95 ------------------ ui/app/components/pages/import-account/json.js | 100 ------------------- .../components/pages/import-account/private-key.js | 76 --------------- ui/app/components/pages/import-account/seed.js | 30 ------ ui/app/components/pages/settings/index.js | 4 +- ui/app/components/pages/settings/info.js | 1 - ui/app/components/pages/settings/settings.js | 2 +- ui/app/css/itcss/components/new-account.scss | 5 +- ui/app/root.js | 7 +- ui/app/routes.js | 4 +- ui/app/select-app.js | 10 +- 23 files changed, 511 insertions(+), 580 deletions(-) delete mode 100644 ui/app/accounts/import/index.js delete mode 100644 ui/app/accounts/new-account/create-form.js delete mode 100644 ui/app/accounts/new-account/index.js create mode 100644 ui/app/components/pages/create-account/import-account/index.js create mode 100644 ui/app/components/pages/create-account/import-account/json.js create mode 100644 ui/app/components/pages/create-account/import-account/private-key.js create mode 100644 ui/app/components/pages/create-account/import-account/seed.js create mode 100644 ui/app/components/pages/create-account/index.js create mode 100644 ui/app/components/pages/create-account/new-account.js delete mode 100644 ui/app/components/pages/import-account/index.js delete mode 100644 ui/app/components/pages/import-account/json.js delete mode 100644 ui/app/components/pages/import-account/private-key.js delete mode 100644 ui/app/components/pages/import-account/seed.js (limited to 'ui') diff --git a/ui/app/accounts/import/index.js b/ui/app/accounts/import/index.js deleted file mode 100644 index 0c901c09b..000000000 --- a/ui/app/accounts/import/index.js +++ /dev/null @@ -1,80 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect -import Select from 'react-select' - -// Subviews -const JsonImportView = require('./json.js') -const PrivateKeyImportView = require('./private-key.js') - -const menuItems = [ - 'Private Key', - 'JSON File', -] - -module.exports = connect(mapStateToProps)(AccountImportSubview) - -function mapStateToProps (state) { - return { - menuItems, - } -} - -inherits(AccountImportSubview, Component) -function AccountImportSubview () { - Component.call(this) -} - -AccountImportSubview.prototype.render = function () { - const props = this.props - const state = this.state || {} - const { menuItems } = props - const { type } = state - - return ( - h('div.new-account-import-form', [ - - h('div.new-account-import-form__select-section', [ - - h('div.new-account-import-form__select-label', 'SELECT TYPE'), - - h(Select, { - className: 'new-account-import-form__select', - name: 'import-type-select', - clearable: false, - value: type || menuItems[0], - options: menuItems.map((type) => { - return { - value: type, - label: type, - } - }), - onChange: (opt) => { - this.setState({ type: opt.value }) - }, - }), - - ]), - - this.renderImportView(), - ]) - ) -} - -AccountImportSubview.prototype.renderImportView = function () { - const props = this.props - const state = this.state || {} - const { type } = state - const { menuItems } = props - const current = type || menuItems[0] - - switch (current) { - case 'Private Key': - return h(PrivateKeyImportView) - case 'JSON File': - return h(JsonImportView) - default: - return h(JsonImportView) - } -} diff --git a/ui/app/accounts/new-account/create-form.js b/ui/app/accounts/new-account/create-form.js deleted file mode 100644 index 494726ae4..000000000 --- a/ui/app/accounts/new-account/create-form.js +++ /dev/null @@ -1,96 +0,0 @@ -const { Component } = require('react') -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const { connect } = require('react-redux') -const actions = require('../../actions') - -class NewAccountCreateForm extends Component { - constructor (props) { - super(props) - const { numberOfExistingAccounts = 0 } = props - const newAccountNumber = numberOfExistingAccounts + 1 - - this.state = { - newAccountName: `Account ${newAccountNumber}`, - } - } - - render () { - const { newAccountName } = this.state - - return h('div.new-account-create-form', [ - - h('div.new-account-create-form__input-label', {}, [ - 'Account Name', - ]), - - h('div.new-account-create-form__input-wrapper', {}, [ - h('input.new-account-create-form__input', { - value: this.state.newAccountName, - placeholder: 'E.g. My new account', - onChange: event => this.setState({ newAccountName: event.target.value }), - }, []), - ]), - - h('div.new-account-create-form__buttons', {}, [ - - h('button.new-account-create-form__button-cancel', { - onClick: () => this.props.goHome(), - }, [ - 'CANCEL', - ]), - - h('button.new-account-create-form__button-create', { - onClick: () => this.props.createAccount(newAccountName), - }, [ - 'CREATE', - ]), - - ]), - - ]) - } -} - -NewAccountCreateForm.propTypes = { - hideModal: PropTypes.func, - showImportPage: PropTypes.func, - createAccount: PropTypes.func, - goHome: PropTypes.func, - numberOfExistingAccounts: PropTypes.number, -} - -const mapStateToProps = state => { - const { metamask: { network, selectedAddress, identities = {} } } = state - const numberOfExistingAccounts = Object.keys(identities).length - - return { - network, - address: selectedAddress, - numberOfExistingAccounts, - } -} - -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) => { - if (newAccountName) { - dispatch(actions.saveAccountLabel(newAccountAddress, newAccountName)) - } - dispatch(actions.goHome()) - }) - }, - showImportPage: () => dispatch(actions.showImportPage()), - goHome: () => dispatch(actions.goHome()), - } -} - -module.exports = connect(mapStateToProps, mapDispatchToProps)(NewAccountCreateForm) diff --git a/ui/app/accounts/new-account/index.js b/ui/app/accounts/new-account/index.js deleted file mode 100644 index acf0dc6e4..000000000 --- a/ui/app/accounts/new-account/index.js +++ /dev/null @@ -1,81 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const connect = require('react-redux').connect -const actions = require('../../actions') -const { getCurrentViewContext } = require('../../selectors') -const classnames = require('classnames') - -const NewAccountCreateForm = require('./create-form') -const NewAccountImportForm = require('../import') - -function mapStateToProps (state) { - return { - displayedForm: getCurrentViewContext(state), - } -} - -function mapDispatchToProps (dispatch) { - return { - 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)), - } -} - -inherits(AccountDetailsModal, Component) -function AccountDetailsModal (props) { - Component.call(this) - - this.state = { - displayedForm: props.displayedForm, - } -} - -module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailsModal) - -AccountDetailsModal.prototype.render = function () { - const { displayedForm, displayForm } = this.props - - return h('div.new-account', {}, [ - - h('div.new-account__header', [ - - h('div.new-account__title', 'New Account'), - - h('div.new-account__tabs', [ - - h('div.new-account__tabs__tab', { - className: classnames('new-account__tabs__tab', { - 'new-account__tabs__selected': displayedForm === 'CREATE', - 'new-account__tabs__unselected cursor-pointer': displayedForm !== 'CREATE', - }), - onClick: () => displayForm('CREATE'), - }, 'Create'), - - h('div.new-account__tabs__tab', { - className: classnames('new-account__tabs__tab', { - 'new-account__tabs__selected': displayedForm === 'IMPORT', - 'new-account__tabs__unselected cursor-pointer': displayedForm !== 'IMPORT', - }), - onClick: () => displayForm('IMPORT'), - }, 'Import'), - - ]), - - ]), - - h('div.new-account__form', [ - - displayedForm === 'CREATE' - ? h(NewAccountCreateForm) - : h(NewAccountImportForm), - - ]), - - ]) -} diff --git a/ui/app/app.js b/ui/app/app.js index 22dc8c343..09af2db2c 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -33,7 +33,7 @@ const UnlockPage = require('./components/pages/unlock') const RestoreVaultPage = require('./components/pages/keychains/restore-vault') const RevealSeedPage = require('./components/pages/keychains/reveal-seed') const AddTokenPage = require('./components/pages/add-token') -const ImportAccountPage = require('./components/pages/import-account') +const CreateAccountPage = require('./components/pages/create-account') const NoticeScreen = require('./components/pages/notice') const SignatureRequestPage = require('./components/pages/signature-request') @@ -58,7 +58,7 @@ const { CONFIRM_SEED_ROUTE, RESTORE_VAULT_ROUTE, ADD_TOKEN_ROUTE, - IMPORT_ACCOUNT_ROUTE, + NEW_ACCOUNT_ROUTE, SEND_ROUTE, CONFIRM_TRANSACTION_ROUTE, INITIALIZE_ROUTE, @@ -115,7 +115,7 @@ class App extends Component { h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, exact, component: ConfirmTxScreen }), h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }), h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }), - h(Authenticated, { path: IMPORT_ACCOUNT_ROUTE, exact, component: ImportAccountPage }), + h(Authenticated, { path: NEW_ACCOUNT_ROUTE, component: CreateAccountPage }), h(Authenticated, { path: SIGNATURE_REQUEST_ROUTE, exact, component: SignatureRequestPage }), h(Authenticated, { path: DEFAULT_ROUTE, exact, component: this.renderPrimary }), ]) diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js index f031f2446..ddb751a64 100644 --- a/ui/app/components/account-menu/index.js +++ b/ui/app/components/account-menu/index.js @@ -8,7 +8,13 @@ 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, IMPORT_ACCOUNT_ROUTE, DEFAULT_ROUTE } = require('../../routes') +const { + SETTINGS_ROUTE, + INFO_ROUTE, + NEW_ACCOUNT_ROUTE, + IMPORT_ACCOUNT_ROUTE, + DEFAULT_ROUTE, +} = require('../../routes') module.exports = compose( withRouter, @@ -86,12 +92,18 @@ AccountMenu.prototype.render = function () { h('div.account-menu__accounts', this.renderAccounts()), h(Divider), h(Item, { - onClick: () => showNewAccountPage('CREATE'), + onClick: () => { + toggleAccountMenu() + history.push(NEW_ACCOUNT_ROUTE) + }, icon: h('img', { src: 'images/plus-btn-white.svg' }), text: 'Create Account', }), h(Item, { - onClick: () => showNewAccountPage('IMPORT'), + onClick: () => { + toggleAccountMenu() + history.push(IMPORT_ACCOUNT_ROUTE) + }, icon: h('img', { src: 'images/import-account.svg' }), text: 'Import Account', }), diff --git a/ui/app/components/pages/add-token.js b/ui/app/components/pages/add-token.js index 0454ee2f1..080f3eca5 100644 --- a/ui/app/components/pages/add-token.js +++ b/ui/app/components/pages/add-token.js @@ -27,7 +27,6 @@ const fuse = new Fuse(contractList, { const actions = require('../../actions') const ethUtil = require('ethereumjs-util') const { tokenInfoGetter } = require('../../token-util') -const R = require('ramda') const { DEFAULT_ROUTE } = require('../../routes') const emptyAddr = '0x0000000000000000000000000000000000000000' diff --git a/ui/app/components/pages/create-account/import-account/index.js b/ui/app/components/pages/create-account/import-account/index.js new file mode 100644 index 000000000..0c901c09b --- /dev/null +++ b/ui/app/components/pages/create-account/import-account/index.js @@ -0,0 +1,80 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect +import Select from 'react-select' + +// Subviews +const JsonImportView = require('./json.js') +const PrivateKeyImportView = require('./private-key.js') + +const menuItems = [ + 'Private Key', + 'JSON File', +] + +module.exports = connect(mapStateToProps)(AccountImportSubview) + +function mapStateToProps (state) { + return { + menuItems, + } +} + +inherits(AccountImportSubview, Component) +function AccountImportSubview () { + Component.call(this) +} + +AccountImportSubview.prototype.render = function () { + const props = this.props + const state = this.state || {} + const { menuItems } = props + const { type } = state + + return ( + h('div.new-account-import-form', [ + + h('div.new-account-import-form__select-section', [ + + h('div.new-account-import-form__select-label', 'SELECT TYPE'), + + h(Select, { + className: 'new-account-import-form__select', + name: 'import-type-select', + clearable: false, + value: type || menuItems[0], + options: menuItems.map((type) => { + return { + value: type, + label: type, + } + }), + onChange: (opt) => { + this.setState({ type: opt.value }) + }, + }), + + ]), + + this.renderImportView(), + ]) + ) +} + +AccountImportSubview.prototype.renderImportView = function () { + const props = this.props + const state = this.state || {} + const { type } = state + const { menuItems } = props + const current = type || menuItems[0] + + switch (current) { + case 'Private Key': + return h(PrivateKeyImportView) + case 'JSON File': + return h(JsonImportView) + default: + return h(JsonImportView) + } +} diff --git a/ui/app/components/pages/create-account/import-account/json.js b/ui/app/components/pages/create-account/import-account/json.js new file mode 100644 index 000000000..36644f1a0 --- /dev/null +++ b/ui/app/components/pages/create-account/import-account/json.js @@ -0,0 +1,106 @@ +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 { connect } = require('react-redux') +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' + +module.exports = compose( + withRouter, + connect(mapStateToProps) +)(JsonImportSubview) + +function mapStateToProps (state) { + return { + error: state.appState.warning, + } +} + +inherits(JsonImportSubview, Component) +function JsonImportSubview () { + Component.call(this) +} + +JsonImportSubview.prototype.render = function () { + const { error } = this.props + + return ( + h('div.new-account-import-form__json', [ + + h('p', 'Used by a variety of different clients'), + h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'), + + h(FileInput, { + readAs: 'text', + onLoad: this.onLoad.bind(this), + style: { + margin: '20px 0px 12px 34%', + fontSize: '15px', + display: 'flex', + justifyContent: 'center', + }, + }), + + h('input.new-account-import-form__input-password', { + type: 'password', + placeholder: 'Enter password', + id: 'json-password-box', + onKeyPress: this.createKeyringOnEnter.bind(this), + }), + + h('div.new-account-create-form__buttons', {}, [ + + h('button.new-account-create-form__button-cancel', { + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, [ + 'CANCEL', + ]), + + h('button.new-account-create-form__button-create', { + onClick: () => this.createNewKeychain.bind(this), + }, [ + 'IMPORT', + ]), + + ]), + + error ? h('span.error', error) : null, + ]) + ) +} + +JsonImportSubview.prototype.onLoad = function (event, file) { + this.setState({file: file, fileContents: event.target.result}) +} + +JsonImportSubview.prototype.createKeyringOnEnter = function (event) { + if (event.key === 'Enter') { + event.preventDefault() + this.createNewKeychain() + } +} + +JsonImportSubview.prototype.createNewKeychain = function () { + const state = this.state + const { fileContents } = state + + if (!fileContents) { + const message = 'You must select a file to import.' + return this.props.dispatch(actions.displayWarning(message)) + } + + const passwordInput = document.getElementById('json-password-box') + const password = passwordInput.value + + if (!password) { + const message = 'You must enter a password for the selected file.' + return this.props.dispatch(actions.displayWarning(message)) + } + + this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) +} diff --git a/ui/app/components/pages/create-account/import-account/private-key.js b/ui/app/components/pages/create-account/import-account/private-key.js new file mode 100644 index 000000000..785bf2cb6 --- /dev/null +++ b/ui/app/components/pages/create-account/import-account/private-key.js @@ -0,0 +1,83 @@ +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 { connect } = require('react-redux') +const actions = require('../../../../actions') +const { DEFAULT_ROUTE } = require('../../../../routes') + +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(PrivateKeyImportView) + +function mapStateToProps (state) { + return { + error: state.appState.warning, + } +} + +function mapDispatchToProps (dispatch) { + return { + importNewAccount: (strategy, [ privateKey ]) => { + return dispatch(actions.importNewAccount(strategy, [ privateKey ])) + }, + displayWarning: () => dispatch(actions.displayWarning(null)), + } +} + +inherits(PrivateKeyImportView, Component) +function PrivateKeyImportView () { + Component.call(this) +} + +PrivateKeyImportView.prototype.render = function () { + const { error } = this.props + + return ( + h('div.new-account-import-form__private-key', [ + h('span.new-account-create-form__instruction', 'Paste your private key string here:'), + + h('input.new-account-import-form__input-password', { + type: 'password', + id: 'private-key-box', + onKeyPress: () => this.createKeyringOnEnter(), + }), + + h('div.new-account-create-form__buttons', {}, [ + + h('button.new-account-create-form__button-cancel', { + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, [ + 'CANCEL', + ]), + + h('button.new-account-create-form__button-create', { + onClick: () => this.createNewKeychain(), + }, [ + 'IMPORT', + ]), + + ]), + + error ? h('span.error', error) : null, + ]) + ) +} + +PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) { + if (event.key === 'Enter') { + event.preventDefault() + this.createNewKeychain() + } +} + +PrivateKeyImportView.prototype.createNewKeychain = function () { + const input = document.getElementById('private-key-box') + const privateKey = input.value + const { importNewAccount, history } = this.props + + importNewAccount('Private Key', [ privateKey ]) + .then(() => history.push(DEFAULT_ROUTE)) +} diff --git a/ui/app/components/pages/create-account/import-account/seed.js b/ui/app/components/pages/create-account/import-account/seed.js new file mode 100644 index 000000000..b4a7c0afa --- /dev/null +++ b/ui/app/components/pages/create-account/import-account/seed.js @@ -0,0 +1,30 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect + +module.exports = connect(mapStateToProps)(SeedImportSubview) + +function mapStateToProps (state) { + return {} +} + +inherits(SeedImportSubview, Component) +function SeedImportSubview () { + Component.call(this) +} + +SeedImportSubview.prototype.render = function () { + return ( + h('div', { + style: { + }, + }, [ + `Paste your seed phrase here!`, + h('textarea'), + h('br'), + h('button', 'Submit'), + ]) + ) +} + 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/components/pages/create-account/new-account.js b/ui/app/components/pages/create-account/new-account.js new file mode 100644 index 000000000..a3bfefc84 --- /dev/null +++ b/ui/app/components/pages/create-account/new-account.js @@ -0,0 +1,95 @@ +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const { connect } = require('react-redux') +const actions = require('../../../actions') +const { DEFAULT_ROUTE } = require('../../../routes') + +class NewAccountCreateForm extends Component { + constructor (props) { + super(props) + const { numberOfExistingAccounts = 0 } = props + const newAccountNumber = numberOfExistingAccounts + 1 + + this.state = { + newAccountName: `Account ${newAccountNumber}`, + } + } + + render () { + const { newAccountName } = this.state + const { history, createAccount } = this.props + + return h('div.new-account-create-form', [ + + h('div.new-account-create-form__input-label', {}, [ + 'Account Name', + ]), + + h('div.new-account-create-form__input-wrapper', {}, [ + h('input.new-account-create-form__input', { + value: this.state.newAccountName, + placeholder: 'E.g. My new account', + onChange: event => this.setState({ newAccountName: event.target.value }), + }, []), + ]), + + h('div.new-account-create-form__buttons', {}, [ + + h('button.new-account-create-form__button-cancel', { + onClick: () => history.push(DEFAULT_ROUTE), + }, [ + 'CANCEL', + ]), + + h('button.new-account-create-form__button-create', { + onClick: () => { + createAccount(newAccountName) + .then(() => history.push(DEFAULT_ROUTE)) + }, + }, [ + 'CREATE', + ]), + + ]), + + ]) + } +} + +NewAccountCreateForm.propTypes = { + hideModal: PropTypes.func, + showImportPage: PropTypes.func, + createAccount: PropTypes.func, + numberOfExistingAccounts: PropTypes.number, + history: PropTypes.object, +} + +const mapStateToProps = state => { + const { metamask: { network, selectedAddress, identities = {} } } = state + const numberOfExistingAccounts = Object.keys(identities).length + + return { + network, + address: selectedAddress, + numberOfExistingAccounts, + } +} + +const mapDispatchToProps = dispatch => { + return { + 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)) + } + }) + }, + showImportPage: () => dispatch(actions.showImportPage()), + } +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(NewAccountCreateForm) diff --git a/ui/app/components/pages/import-account/index.js b/ui/app/components/pages/import-account/index.js deleted file mode 100644 index 481ed6a4b..000000000 --- a/ui/app/components/pages/import-account/index.js +++ /dev/null @@ -1,95 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const PropTypes = require('prop-types') -import Select from 'react-select' - -// Subviews -const JsonImportView = require('./json.js') -const PrivateKeyImportView = require('./private-key.js') - -const PRIVATE_KEY_MENU_ITEM = 'Private Key' -const JSON_FILE_MENU_ITEM = 'JSON File' - -class ImportAccount extends Component { - constructor (props) { - super(props) - - this.state = { - current: PRIVATE_KEY_MENU_ITEM, - menuItems: [ PRIVATE_KEY_MENU_ITEM, JSON_FILE_MENU_ITEM ], - } - } - - renderImportView () { - const { current } = this.state - - switch (current) { - case 'Private Key': - return h(PrivateKeyImportView) - case 'JSON File': - return h(JsonImportView) - default: - return h(JsonImportView) - } - } - - render () { - const { history } = this.props - const { current, menuItems } = this.state - - return ( - h('div.flex-center', { - style: { - flexDirection: 'column', - marginTop: '32px', - }, - }, [ - h('.section-title.flex-row.flex-center', [ - h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { - onClick: history.goBack, - }), - h('h2.page-subtitle', 'Import Accounts'), - ]), - h('div', { - style: { - padding: '10px 0', - width: '260px', - color: 'rgb(174, 174, 174)', - }, - }, [ - - h('h3', { style: { padding: '3px' } }, 'SELECT TYPE'), - - h('style', ` - .has-value.Select--single > .Select-control .Select-value .Select-value-label, .Select-value-label { - color: rgb(174,174,174); - } - `), - - h(Select, { - name: 'import-type-select', - clearable: false, - value: current, - options: menuItems.map(type => { - return { - value: type, - label: type, - } - }), - onChange: opt => { - this.setState({ current: opt.value }) - }, - }), - ]), - - this.renderImportView(), - ]) - ) - } -} - -ImportAccount.propTypes = { - history: PropTypes.object, -} - -module.exports = ImportAccount diff --git a/ui/app/components/pages/import-account/json.js b/ui/app/components/pages/import-account/json.js deleted file mode 100644 index c7d232d30..000000000 --- a/ui/app/components/pages/import-account/json.js +++ /dev/null @@ -1,100 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const actions = require('../../../actions') -const FileInput = require('react-simple-file-input').default - -const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts' - -module.exports = connect(mapStateToProps)(JsonImportSubview) - -function mapStateToProps (state) { - return { - error: state.appState.warning, - } -} - -inherits(JsonImportSubview, Component) -function JsonImportSubview () { - Component.call(this) -} - -JsonImportSubview.prototype.render = function () { - const { error } = this.props - - return ( - h('div.new-account-import-form__json', [ - - h('p', 'Used by a variety of different clients'), - h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'), - - h(FileInput, { - readAs: 'text', - onLoad: this.onLoad.bind(this), - style: { - margin: '20px 0px 12px 34%', - fontSize: '15px', - display: 'flex', - justifyContent: 'center', - }, - }), - - h('input.new-account-import-form__input-password', { - type: 'password', - placeholder: 'Enter password', - id: 'json-password-box', - onKeyPress: this.createKeyringOnEnter.bind(this), - }), - - h('div.new-account-create-form__buttons', {}, [ - - h('button.new-account-create-form__button-cancel', { - onClick: () => this.props.goHome(), - }, [ - 'CANCEL', - ]), - - h('button.new-account-create-form__button-create', { - onClick: () => this.createNewKeychain.bind(this), - }, [ - 'IMPORT', - ]), - - ]), - - error ? h('span.error', error) : null, - ]) - ) -} - -JsonImportSubview.prototype.onLoad = function (event, file) { - this.setState({file: file, fileContents: event.target.result}) -} - -JsonImportSubview.prototype.createKeyringOnEnter = function (event) { - if (event.key === 'Enter') { - event.preventDefault() - this.createNewKeychain() - } -} - -JsonImportSubview.prototype.createNewKeychain = function () { - const state = this.state - const { fileContents } = state - - if (!fileContents) { - const message = 'You must select a file to import.' - return this.props.dispatch(actions.displayWarning(message)) - } - - const passwordInput = document.getElementById('json-password-box') - const password = passwordInput.value - - if (!password) { - const message = 'You must enter a password for the selected file.' - return this.props.dispatch(actions.displayWarning(message)) - } - - this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) -} diff --git a/ui/app/components/pages/import-account/private-key.js b/ui/app/components/pages/import-account/private-key.js deleted file mode 100644 index a236d90ee..000000000 --- a/ui/app/components/pages/import-account/private-key.js +++ /dev/null @@ -1,76 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const actions = require('../../../actions') - -module.exports = connect(mapStateToProps, mapDispatchToProps)(PrivateKeyImportView) - -function mapStateToProps (state) { - return { - error: state.appState.warning, - } -} - -function mapDispatchToProps (dispatch) { - return { - goHome: () => dispatch(actions.goHome()), - importNewAccount: (strategy, [ privateKey ]) => { - dispatch(actions.importNewAccount(strategy, [ privateKey ])) - }, - displayWarning: () => dispatch(actions.displayWarning(null)), - } -} - -inherits(PrivateKeyImportView, Component) -function PrivateKeyImportView () { - Component.call(this) -} - -PrivateKeyImportView.prototype.render = function () { - const { error, goHome } = this.props - - return ( - h('div.new-account-import-form__private-key', [ - h('span.new-account-create-form__instruction', 'Paste your private key string here:'), - - h('input.new-account-import-form__input-password', { - type: 'password', - id: 'private-key-box', - onKeyPress: () => this.createKeyringOnEnter(), - }), - - h('div.new-account-create-form__buttons', {}, [ - - h('button.new-account-create-form__button-cancel', { - onClick: () => goHome(), - }, [ - 'CANCEL', - ]), - - h('button.new-account-create-form__button-create', { - onClick: () => this.createNewKeychain(), - }, [ - 'IMPORT', - ]), - - ]), - - error ? h('span.error', error) : null, - ]) - ) -} - -PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) { - if (event.key === 'Enter') { - event.preventDefault() - this.createNewKeychain() - } -} - -PrivateKeyImportView.prototype.createNewKeychain = function () { - const input = document.getElementById('private-key-box') - const privateKey = input.value - - this.props.importNewAccount('Private Key', [ privateKey ]) -} diff --git a/ui/app/components/pages/import-account/seed.js b/ui/app/components/pages/import-account/seed.js deleted file mode 100644 index b4a7c0afa..000000000 --- a/ui/app/components/pages/import-account/seed.js +++ /dev/null @@ -1,30 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect - -module.exports = connect(mapStateToProps)(SeedImportSubview) - -function mapStateToProps (state) { - return {} -} - -inherits(SeedImportSubview, Component) -function SeedImportSubview () { - Component.call(this) -} - -SeedImportSubview.prototype.render = function () { - return ( - h('div', { - style: { - }, - }, [ - `Paste your seed phrase here!`, - h('textarea'), - h('br'), - h('button', 'Submit'), - ]) - ) -} - diff --git a/ui/app/components/pages/settings/index.js b/ui/app/components/pages/settings/index.js index 39e9b26ed..384ae4b41 100644 --- a/ui/app/components/pages/settings/index.js +++ b/ui/app/components/pages/settings/index.js @@ -5,7 +5,7 @@ const h = require('react-hyperscript') const TabBar = require('../../tab-bar') const Settings = require('./settings') const Info = require('./info') -const { SETTINGS_ROUTE, INFO_ROUTE } = require('../../../routes') +const { DEFAULT_ROUTE, SETTINGS_ROUTE, INFO_ROUTE } = require('../../../routes') class Config extends Component { renderTabs () { @@ -30,7 +30,7 @@ class Config extends Component { h('.main-container.settings', {}, [ h('.settings__header', [ h('div.settings__close-button', { - onClick: () => history.push('/'), + onClick: () => history.push(DEFAULT_ROUTE), }), this.renderTabs(), ]), diff --git a/ui/app/components/pages/settings/info.js b/ui/app/components/pages/settings/info.js index d8155eb9b..87479c84e 100644 --- a/ui/app/components/pages/settings/info.js +++ b/ui/app/components/pages/settings/info.js @@ -100,7 +100,6 @@ Info.propTypes = { displayWarning: PropTypes.func, revealSeedConfirmation: PropTypes.func, warning: PropTypes.string, - goHome: PropTypes.func, location: PropTypes.object, history: PropTypes.object, } diff --git a/ui/app/components/pages/settings/settings.js b/ui/app/components/pages/settings/settings.js index fb3f20a95..5506df1ae 100644 --- a/ui/app/components/pages/settings/settings.js +++ b/ui/app/components/pages/settings/settings.js @@ -11,7 +11,7 @@ const { exportAsFile } = require('../../../util') const SimpleDropdown = require('../../dropdowns/simple-dropdown') const ToggleButton = require('react-toggle-button') const { REVEAL_SEED_ROUTE } = require('../../../routes') -const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums +const { OLD_UI_NETWORK_TYPE } = require('../../../../../app/scripts/config').enums const getInfuraCurrencyOptions = () => { const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { diff --git a/ui/app/css/itcss/components/new-account.scss b/ui/app/css/itcss/components/new-account.scss index c5e4ea761..5837a80bb 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-form { diff --git a/ui/app/root.js b/ui/app/root.js index 64f365c9e..09deae1b1 100644 --- a/ui/app/root.js +++ b/ui/app/root.js @@ -3,7 +3,6 @@ const PropTypes = require('prop-types') const { Provider } = require('react-redux') const h = require('react-hyperscript') const SelectedApp = require('./select-app') -const { HashRouter } = require('react-router-dom') class Root extends Component { render () { @@ -11,11 +10,7 @@ class Root extends Component { return ( h(Provider, { store }, [ - h(HashRouter, { - hashType: 'noslash', - }, [ - h(SelectedApp), - ]), + h(SelectedApp), ]) ) } diff --git a/ui/app/routes.js b/ui/app/routes.js index d1d634db2..8b4501fe3 100644 --- a/ui/app/routes.js +++ b/ui/app/routes.js @@ -6,7 +6,8 @@ const REVEAL_SEED_ROUTE = '/seed' const CONFIRM_SEED_ROUTE = '/confirm-seed' const RESTORE_VAULT_ROUTE = '/restore-vault' const ADD_TOKEN_ROUTE = '/add-token' -const IMPORT_ACCOUNT_ROUTE = '/import-account' +const NEW_ACCOUNT_ROUTE = '/new-account' +const IMPORT_ACCOUNT_ROUTE = '/new-account/import' const SEND_ROUTE = '/send' const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction' const INITIALIZE_ROUTE = '/initialize' @@ -22,6 +23,7 @@ module.exports = { CONFIRM_SEED_ROUTE, RESTORE_VAULT_ROUTE, ADD_TOKEN_ROUTE, + NEW_ACCOUNT_ROUTE, IMPORT_ACCOUNT_ROUTE, SEND_ROUTE, CONFIRM_TRANSACTION_ROUTE, diff --git a/ui/app/select-app.js b/ui/app/select-app.js index 193c98353..c6ef1abda 100644 --- a/ui/app/select-app.js +++ b/ui/app/select-app.js @@ -2,6 +2,7 @@ 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') @@ -62,7 +63,12 @@ SelectedApp.prototype.render = function () { // const Selected = betaUI || isMascara || firstTime ? App : OldApp const { betaUI, isMascara } = this.props - const Selected = betaUI || isMascara ? App : OldApp - return h(Selected) + return betaUI || isMascara + ? h(HashRouter, { + hashType: 'noslash', + }, [ + h(App), + ]) + : h(OldApp) } -- cgit From f38675c9ac99d966b39e3faccd2bf8d7facea9dc Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 7 Dec 2017 01:18:41 -0330 Subject: Checks if there is currently a send in progress before redirecting to default in conf-tx --- ui/app/conf-tx.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'ui') diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 913563d12..9a323e6c2 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -44,6 +44,7 @@ function mapStateToProps (state) { computedBalances: state.metamask.computedBalances, unapprovedMsgCount, unapprovedPersonalMsgCount, + send: state.metamask.send, } } @@ -53,15 +54,18 @@ function ConfirmTxScreen () { } ConfirmTxScreen.prototype.componentWillMount = function () { - const { unapprovedTxs = {} } = this.props - if (Object.keys(unapprovedTxs).length === 0) { + const { unapprovedTxs = {}, send } = this.props + const { to } = send + if (Object.keys(unapprovedTxs).length === 0 && !to) { this.props.history.push(DEFAULT_ROUTE) } } ConfirmTxScreen.prototype.componentWillReceiveProps = function (nextProps) { + const { send } = this.props + const { to } = send const { unapprovedTxs = {} } = nextProps - if (Object.keys(unapprovedTxs).length === 0) { + if (Object.keys(unapprovedTxs).length === 0 && !to) { this.props.history.push(DEFAULT_ROUTE) } } -- cgit From 2fd9e58e612e9fe341c9107a677238a566e3c1b8 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 1 Feb 2018 14:13:08 -0800 Subject: Fix lint errors --- ui/app/components/account-menu/index.js | 6 ------ ui/app/components/pending-tx/confirm-send-ether.js | 2 +- ui/app/info.js | 1 - ui/app/send-v2.js | 4 ++-- 4 files changed, 3 insertions(+), 10 deletions(-) (limited to 'ui') diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js index ddb751a64..e9ae61735 100644 --- a/ui/app/components/account-menu/index.js +++ b/ui/app/components/account-menu/index.js @@ -53,11 +53,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()) @@ -70,7 +65,6 @@ AccountMenu.prototype.render = function () { const { isAccountMenuOpen, toggleAccountMenu, - showNewAccountPage, lockMetamask, history, } = this.props diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index e63415194..a2d6adcd0 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -232,7 +232,7 @@ ConfirmSendEther.prototype.render = function () { h('div.confirm-screen-wrapper.flex-column.flex-grow', [ h('h3.flex-center.confirm-screen-header', [ h('button.btn-clear.confirm-screen-back-button', { - onClick: () => editTransaction(txMeta), + onClick: () => this.editTransaction(txMeta), }, 'EDIT'), h('div.confirm-screen-title', 'Confirm Transaction'), h('div.confirm-screen-header-tip'), diff --git a/ui/app/info.js b/ui/app/info.js index 92cc80c20..17e1ecbb4 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -2,7 +2,6 @@ const inherits = require('util').inherits const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect -const actions = require('./actions') module.exports = connect(mapStateToProps)(InfoScreen) diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index 5eb90143e..7cee46c9c 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -659,7 +659,7 @@ SendTransactionScreen.prototype.onSubmit = function (event) { selectedToken ? signTokenTx(selectedToken.address, to, amount, txParams) : signTx(txParams) - - this.props.history.push(CONFIRM_TRANSACTION_ROUTE) } + + this.props.history.push(CONFIRM_TRANSACTION_ROUTE) } -- cgit From 72ffa2c3f5abbcb06c8ab5fdf20b9d934c330692 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Fri, 9 Feb 2018 12:05:27 -0800 Subject: Fix react-router related exceptions --- ui/app/app.js | 2 +- ui/app/components/pending-tx/confirm-send-token.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/app.js b/ui/app/app.js index 0ecfd0dde..168dfa9ac 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -268,7 +268,7 @@ class App extends Component { }, [ h('div.app-header-contents', {}, [ h('div.left-menu-wrapper', { - onClick: () => history.push(DEFAULT_ROUTE), + onClick: () => props.history.push(DEFAULT_ROUTE), }, [ // mini logo h('img.metafox-icon', { diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index da3a92fa8..f52fd01da 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -327,7 +327,7 @@ ConfirmSendToken.prototype.render = function () { h('div.confirm-screen-wrapper.flex-column.flex-grow', [ h('h3.flex-center.confirm-screen-header', [ h('button.btn-clear.confirm-screen-back-button', { - onClick: () => editTransaction(txMeta), + onClick: () => this.editTransaction(txMeta), }, 'EDIT'), h('div.confirm-screen-title', 'Confirm Transaction'), h('div.confirm-screen-header-tip'), -- cgit From 58f52b2b8de9efd43896e23ab0ac9972f45bb278 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 28 Mar 2018 13:21:53 -0700 Subject: Fix merge conflicts. Refactor onboarding flow. --- ui/app/app.js | 26 +++++++++++++--------- ui/app/components/pages/add-token.js | 4 ++-- .../pages/create-account/import-account/index.js | 2 +- .../pages/create-account/import-account/json.js | 2 +- .../create-account/import-account/private-key.js | 2 +- .../pages/create-account/import-account/seed.js | 2 +- .../components/pages/create-account/new-account.js | 2 +- ui/app/components/pages/settings/info.js | 17 +++++++------- ui/app/components/pages/settings/settings.js | 20 ++++++++++++++++- ui/app/components/pages/signature-request.js | 2 +- ui/app/routes.js | 10 +++++++-- ui/app/send-v2.js | 4 ++-- ui/app/welcome-screen.js | 5 ++++- 13 files changed, 65 insertions(+), 33 deletions(-) (limited to 'ui') diff --git a/ui/app/app.js b/ui/app/app.js index d114cde09..2b6d5fc62 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -1,21 +1,23 @@ const { Component } = require('react') const PropTypes = require('prop-types') const { connect } = require('react-redux') -const { Switch, Redirect, withRouter } = require('react-router-dom') +const { Route, Switch, Redirect, withRouter } = require('react-router-dom') const { compose } = require('recompose') const h = require('react-hyperscript') const actions = require('./actions') const classnames = require('classnames') const t = require('../i18n') +// init +const InitializeScreen = require('../../mascara/src/app/first-time').default +const WelcomeScreen = require('./welcome-screen').default +const NewKeyChainScreen = require('./new-keychain') // mascara const MascaraCreatePassword = require('../../mascara/src/app/first-time/create-password-screen').default const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default const MascaraNoticeScreen = require('../../mascara/src/app/first-time/notice-screen').default const MascaraSeedScreen = require('../../mascara/src/app/first-time/seed-screen').default const MascaraConfirmSeedScreen = require('../../mascara/src/app/first-time/confirm-seed-screen').default -// init -const NewKeyChainScreen = require('./new-keychain') // accounts const MainContainer = require('./main-container') @@ -28,7 +30,6 @@ const WalletView = require('./components/wallet-view') // other views const Authenticated = require('./components/pages/authenticated') const Initialized = require('./components/pages/initialized') -const MetamaskRoute = require('./components/pages/metamask-route') const Settings = require('./components/pages/settings') const UnlockPage = require('./components/pages/unlock') const RestoreVaultPage = require('./components/pages/keychains/restore-vault') @@ -65,6 +66,7 @@ const { INITIALIZE_ROUTE, NOTICE_ROUTE, SIGNATURE_REQUEST_ROUTE, + WELCOME_ROUTE, } = require('./routes') class App extends Component { @@ -87,11 +89,14 @@ class App extends Component { return ( h(Switch, [ - h(MetamaskRoute, { - path: INITIALIZE_ROUTE, + h(Route, { + path: WELCOME_ROUTE, exact, - component: InitializeMenuScreen, - mascaraComponent: MascaraCreatePassword, + component: WelcomeScreen, + }), + h(Route, { + path: INITIALIZE_ROUTE, + component: InitializeScreen, }), h(Initialized, { path: REVEAL_SEED_ROUTE, @@ -110,8 +115,7 @@ class App extends Component { h(Initialized, { path: NOTICE_ROUTE, exact, - component: NoticeScreen, - mascaraComponent: MascaraNoticeScreen, + component: MascaraNoticeScreen, }), h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, exact, component: ConfirmTxScreen }), h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }), @@ -465,7 +469,7 @@ class App extends Component { // // default: // // log.debug('rendering menu screen') - // // return h(InitializeMenuScreen, {key: 'menuScreenInit'}) + // // return h(InitializeScreen, {key: 'menuScreenInit'}) // // } // } diff --git a/ui/app/components/pages/add-token.js b/ui/app/components/pages/add-token.js index 782ce79ae..3b7a65b21 100644 --- a/ui/app/components/pages/add-token.js +++ b/ui/app/components/pages/add-token.js @@ -25,7 +25,7 @@ const fuse = new Fuse(contractList, { }) const actions = require('../../actions') const ethUtil = require('ethereumjs-util') -const t = require('../i18n') +const t = require('../../../i18n') const { tokenInfoGetter } = require('../../token-util') const { DEFAULT_ROUTE } = require('../../routes') @@ -409,7 +409,7 @@ AddTokenScreen.prototype.render = function () { !isShowingConfirmation && h('div.add-token__buttons', [ h('button.btn-secondary--lg.add-token__cancel-button', { - onClick: history.goBack(), + onClick: () => history.goBack(), }, t('cancel')), h('button.btn-primary--lg.add-token__confirm-button', { onClick: this.onNext, diff --git a/ui/app/components/pages/create-account/import-account/index.js b/ui/app/components/pages/create-account/import-account/index.js index fc9031a65..8031ea36d 100644 --- a/ui/app/components/pages/create-account/import-account/index.js +++ b/ui/app/components/pages/create-account/import-account/index.js @@ -2,7 +2,7 @@ const inherits = require('util').inherits const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect -const t = require('../../../i18n') +const t = require('../../../../../i18n') import Select from 'react-select' // Subviews diff --git a/ui/app/components/pages/create-account/import-account/json.js b/ui/app/components/pages/create-account/import-account/json.js index ef056b1b1..554a67df4 100644 --- a/ui/app/components/pages/create-account/import-account/json.js +++ b/ui/app/components/pages/create-account/import-account/json.js @@ -6,7 +6,7 @@ const { compose } = require('recompose') const { connect } = require('react-redux') const actions = require('../../../../actions') const FileInput = require('react-simple-file-input').default -const t = require('../../../i18n') +const t = require('../../../../../i18n') const { DEFAULT_ROUTE } = require('../../../../routes') const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts' diff --git a/ui/app/components/pages/create-account/import-account/private-key.js b/ui/app/components/pages/create-account/import-account/private-key.js index f48feeb0e..a30492e3b 100644 --- a/ui/app/components/pages/create-account/import-account/private-key.js +++ b/ui/app/components/pages/create-account/import-account/private-key.js @@ -6,7 +6,7 @@ const { compose } = require('recompose') const { connect } = require('react-redux') const actions = require('../../../../actions') const { DEFAULT_ROUTE } = require('../../../../routes') -const t = require('../../../i18n') +const t = require('../../../../../i18n') module.exports = compose( withRouter, diff --git a/ui/app/components/pages/create-account/import-account/seed.js b/ui/app/components/pages/create-account/import-account/seed.js index 9ffc669a2..85fa93faa 100644 --- a/ui/app/components/pages/create-account/import-account/seed.js +++ b/ui/app/components/pages/create-account/import-account/seed.js @@ -2,7 +2,7 @@ const inherits = require('util').inherits const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect -const t = require('../../../i18n') +const t = require('../../../../../i18n') module.exports = connect(mapStateToProps)(SeedImportSubview) diff --git a/ui/app/components/pages/create-account/new-account.js b/ui/app/components/pages/create-account/new-account.js index 889ae9206..ceeb8a05b 100644 --- a/ui/app/components/pages/create-account/new-account.js +++ b/ui/app/components/pages/create-account/new-account.js @@ -4,7 +4,7 @@ const h = require('react-hyperscript') const { connect } = require('react-redux') const actions = require('../../../actions') const { DEFAULT_ROUTE } = require('../../../routes') -const t = require('../../../i18n') +const t = require('../../../../i18n') class NewAccountCreateForm extends Component { constructor (props) { diff --git a/ui/app/components/pages/settings/info.js b/ui/app/components/pages/settings/info.js index 87479c84e..cb1782562 100644 --- a/ui/app/components/pages/settings/info.js +++ b/ui/app/components/pages/settings/info.js @@ -1,6 +1,7 @@ const { Component } = require('react') const PropTypes = require('prop-types') const h = require('react-hyperscript') +const t = require('../../../../i18n') class Info extends Component { renderLogo () { @@ -14,13 +15,13 @@ class Info extends Component { renderInfoLinks () { return ( h('div.settings__content-item.settings__content-item--without-height', [ - h('div.settings__info-link-header', 'Links'), + h('div.settings__info-link-header', t('links')), h('div.settings__info-link-item', [ h('a', { href: 'https://metamask.io/privacy.html', target: '_blank', }, [ - h('span.settings__info-link', 'Privacy Policy'), + h('span.settings__info-link', t('privacyMsg')), ]), ]), h('div.settings__info-link-item', [ @@ -28,7 +29,7 @@ class Info extends Component { href: 'https://metamask.io/terms.html', target: '_blank', }, [ - h('span.settings__info-link', 'Terms of Use'), + h('span.settings__info-link', t('terms')), ]), ]), h('div.settings__info-link-item', [ @@ -36,7 +37,7 @@ class Info extends Component { href: 'https://metamask.io/attributions.html', target: '_blank', }, [ - h('span.settings__info-link', 'Attributions'), + h('span.settings__info-link', t('attributions')), ]), ]), h('hr.settings__info-separator'), @@ -45,7 +46,7 @@ class Info extends Component { href: 'https://support.metamask.io', target: '_blank', }, [ - h('span.settings__info-link', 'Visit our Support Center'), + h('span.settings__info-link', t('supportCenter')), ]), ]), h('div.settings__info-link-item', [ @@ -53,7 +54,7 @@ class Info extends Component { href: 'https://metamask.io/', target: '_blank', }, [ - h('span.settings__info-link', 'Visit our web site'), + h('span.settings__info-link', t('visitWebSite')), ]), ]), h('div.settings__info-link-item', [ @@ -61,7 +62,7 @@ class Info extends Component { target: '_blank', href: 'mailto:help@metamask.io?subject=Feedback', }, [ - h('span.settings__info-link', 'Email us!'), + h('span.settings__info-link', t('emailUs')), ]), ]), ]) @@ -81,7 +82,7 @@ class Info extends Component { h('div.settings__info-item', [ h( 'div.settings__info-about', - 'MetaMask is designed and built in California.' + t('builtInCalifornia') ), ]), ]), diff --git a/ui/app/components/pages/settings/settings.js b/ui/app/components/pages/settings/settings.js index 6ce0556db..219ace651 100644 --- a/ui/app/components/pages/settings/settings.js +++ b/ui/app/components/pages/settings/settings.js @@ -12,7 +12,7 @@ const SimpleDropdown = require('../../dropdowns/simple-dropdown') const ToggleButton = require('react-toggle-button') const { REVEAL_SEED_ROUTE } = require('../../../routes') const { OLD_UI_NETWORK_TYPE } = require('../../../../../app/scripts/config').enums -const t = require('../i18n') +const t = require('../../../../i18n') const getInfuraCurrencyOptions = () => { const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { @@ -237,6 +237,24 @@ class Settings extends Component { ) } + renderResetAccount () { + const { showResetAccountConfirmationModal } = this.props + + return h('div.settings__content-row', [ + h('div.settings__content-item', t('resetAccount')), + h('div.settings__content-item', [ + h('div.settings__content-item-col', [ + h('button.btn-primary--lg.settings__button--orange', { + onClick (event) { + event.preventDefault() + showResetAccountConfirmationModal() + }, + }, t('resetAccount')), + ]), + ]), + ]) + } + render () { const { warning, isMascara } = this.props diff --git a/ui/app/components/pages/signature-request.js b/ui/app/components/pages/signature-request.js index 2e7f3ea20..e9271fce1 100644 --- a/ui/app/components/pages/signature-request.js +++ b/ui/app/components/pages/signature-request.js @@ -8,7 +8,7 @@ const classnames = require('classnames') const AccountDropdownMini = require('../dropdowns/account-dropdown-mini') -const t = require('../../i18n') +const t = require('../../../i18n') const { conversionUtil } = require('../../conversion-util') const { DEFAULT_ROUTE } = require('../../routes') diff --git a/ui/app/routes.js b/ui/app/routes.js index 8b4501fe3..aadf91cf5 100644 --- a/ui/app/routes.js +++ b/ui/app/routes.js @@ -10,9 +10,12 @@ const NEW_ACCOUNT_ROUTE = '/new-account' const IMPORT_ACCOUNT_ROUTE = '/new-account/import' const SEND_ROUTE = '/send' const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction' -const INITIALIZE_ROUTE = '/initialize' const NOTICE_ROUTE = '/notice' const SIGNATURE_REQUEST_ROUTE = '/signature-request' +const WELCOME_ROUTE = '/welcome' +const INITIALIZE_ROUTE = '/initialize' +const INITIALIZE_IMPORT_ACCOUNT_ROUTE = '/initialize/import-account' +const INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE = '/initialize/import-with-seed-phrase' module.exports = { DEFAULT_ROUTE, @@ -27,7 +30,10 @@ module.exports = { IMPORT_ACCOUNT_ROUTE, SEND_ROUTE, CONFIRM_TRANSACTION_ROUTE, - INITIALIZE_ROUTE, NOTICE_ROUTE, SIGNATURE_REQUEST_ROUTE, + WELCOME_ROUTE, + INITIALIZE_ROUTE, + INITIALIZE_IMPORT_ACCOUNT_ROUTE, + INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, } diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index df819dbd8..9e0ede720 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -186,7 +186,7 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) { } SendTransactionScreen.prototype.renderHeader = function () { - const { selectedToken, clearSend, goHome } = this.props + const { selectedToken, clearSend, history } = this.props return h('div.page-container__header', [ @@ -197,7 +197,7 @@ SendTransactionScreen.prototype.renderHeader = function () { h('div.page-container__header-close', { onClick: () => { clearSend() - goHome() + history.goBack() }, }), diff --git a/ui/app/welcome-screen.js b/ui/app/welcome-screen.js index cdbb6dba8..f32491b0d 100644 --- a/ui/app/welcome-screen.js +++ b/ui/app/welcome-screen.js @@ -5,19 +5,22 @@ import PropTypes from 'prop-types' import {connect} from 'react-redux' import {closeWelcomeScreen} from './actions' import Mascot from './components/mascot' +import { INITIALIZE_ROUTE } from './routes' class WelcomeScreen extends Component { static propTypes = { closeWelcomeScreen: PropTypes.func.isRequired, + history: PropTypes.object, } - constructor(props) { + constructor (props) { super(props) this.animationEventEmitter = new EventEmitter() } initiateAccountCreation = () => { this.props.closeWelcomeScreen() + this.props.history.push(INITIALIZE_ROUTE) } render () { -- cgit From bdc4a6964ae83faa8229c50870e3bcc9b9074989 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Fri, 30 Mar 2018 14:51:11 -0700 Subject: Fix merge conflicts. Refactor renderPrimary into Home component --- ui/app/app.js | 442 ++++++++++----------- ui/app/components/pages/add-token.js | 2 +- ui/app/components/pages/home.js | 333 ++++++++++++++++ ui/app/components/pages/keychains/restore-vault.js | 26 +- ui/app/components/pages/signature-request.js | 284 ------------- ui/app/components/pages/unlock.js | 147 ++++--- ui/app/components/signature-request.js | 17 +- ui/app/conf-tx.js | 44 +- ui/app/routes.js | 4 +- 9 files changed, 693 insertions(+), 606 deletions(-) create mode 100644 ui/app/components/pages/home.js delete mode 100644 ui/app/components/pages/signature-request.js (limited to 'ui') diff --git a/ui/app/app.js b/ui/app/app.js index 719830fda..5d47a4189 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -27,6 +27,7 @@ const ConfirmTxScreen = require('./conf-tx') const WalletView = require('./components/wallet-view') // other views +const Home = require('./components/pages/home') const Authenticated = require('./components/pages/authenticated') const Initialized = require('./components/pages/initialized') const Settings = require('./components/pages/settings') @@ -36,7 +37,6 @@ const RevealSeedPage = require('./components/pages/keychains/reveal-seed') const AddTokenPage = require('./components/pages/add-token') const CreateAccountPage = require('./components/pages/create-account') const NoticeScreen = require('./components/pages/notice') -const SignatureRequestPage = require('./components/pages/signature-request') const Loading = require('./components/loading') const NetworkIndicator = require('./components/network') @@ -69,11 +69,11 @@ const { } = require('./routes') class App extends Component { - constructor (props) { - super(props) + // constructor (props) { + // super(props) - this.renderPrimary = this.renderPrimary.bind(this) - } + // this.renderPrimary = this.renderPrimary.bind(this) + // } componentWillMount () { const { currentCurrency, setCurrentCurrencyToUSD } = this.props @@ -116,12 +116,11 @@ class App extends Component { exact, component: MascaraNoticeScreen, }), - h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, exact, component: ConfirmTxScreen }), + h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }), h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }), h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }), h(Authenticated, { path: NEW_ACCOUNT_ROUTE, component: CreateAccountPage }), - h(Authenticated, { path: SIGNATURE_REQUEST_ROUTE, exact, component: SignatureRequestPage }), - h(Authenticated, { path: DEFAULT_ROUTE, exact, component: this.renderPrimary }), + h(Authenticated, { path: DEFAULT_ROUTE, exact, component: Home }), ]) ) } @@ -357,233 +356,205 @@ class App extends Component { }) } - renderBackButton (style, justArrow = false) { - const { dispatch } = this.props - - return ( - h('.flex-row', { - key: 'leftArrow', - style: style, - onClick: () => dispatch(actions.goBackToInitView()), - }, [ - h('i.fa.fa-arrow-left.cursor-pointer'), - justArrow ? null : h('div.cursor-pointer', { - style: { - marginLeft: '3px', - }, - onClick: () => dispatch(actions.goBackToInitView()), - }, 'BACK'), - ]) - ) - } - - renderPrimary () { - log.debug('rendering primary') - const { - noActiveNotices, - lostAccounts, - forgottenPassword, - currentView, - activeAddress, - unapprovedTxs = {}, - seedWords, - unapprovedMsgCount = 0, - unapprovedPersonalMsgCount = 0, - unapprovedTypedMessagesCount = 0, - } = this.props - - // 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, - }, - }) - } - - // notices - if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) { - return h(Redirect, { - to: { - pathname: NOTICE_ROUTE, - }, - }) - } - - // unapprovedTxs - if (Object.keys(unapprovedTxs).length) { - return h(Redirect, { - to: { - pathname: CONFIRM_TRANSACTION_ROUTE, - }, - }) - } - - // unapproved messages - if (unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { - return h(Redirect, { - to: { - pathname: SIGNATURE_REQUEST_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'}) - } - } + // renderPrimary () { + // log.debug('rendering primary') + // const { + // noActiveNotices, + // lostAccounts, + // forgottenPassword, + // currentView, + // activeAddress, + // unapprovedTxs = {}, + // seedWords, + // unapprovedMsgCount = 0, + // unapprovedPersonalMsgCount = 0, + // unapprovedTypedMessagesCount = 0, + // } = this.props + + // // 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, + // }, + // }) + // } + + // // notices + // if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) { + // return h(Redirect, { + // to: { + // pathname: NOTICE_ROUTE, + // }, + // }) + // } + + // // unapprovedTxs and unapproved messages + // if (Object.keys(unapprovedTxs).length || + // unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { + // return h(Redirect, { + // to: { + // pathname: CONFIRM_TRANSACTION_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'}) + // } + // } toggleMetamaskActive () { if (!this.props.isUnlocked) { @@ -676,6 +647,7 @@ App.propTypes = { betaUI: PropTypes.bool, isMouseUser: PropTypes.bool, setMouseUserState: PropTypes.func, + t: PropTypes.func, } function mapStateToProps (state) { diff --git a/ui/app/components/pages/add-token.js b/ui/app/components/pages/add-token.js index b33373c19..61c245c32 100644 --- a/ui/app/components/pages/add-token.js +++ b/ui/app/components/pages/add-token.js @@ -2,7 +2,7 @@ const inherits = require('util').inherits const Component = require('react').Component const classnames = require('classnames') const h = require('react-hyperscript') -const connect = require('./metamask-connect') +const connect = require('../../metamask-connect') const R = require('ramda') const Fuse = require('fuse.js') const contractMap = require('eth-contract-metadata') diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js new file mode 100644 index 000000000..4f8c00768 --- /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') + +// 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 { + unapprovedTxs = {}, + unapprovedMsgCount = 0, + unapprovedPersonalMsgCount = 0, + unapprovedTypedMessagesCount = 0, + } = this.props + + console.log('IN HOME COMPONENDIMOUNT') + // unapprovedTxs and unapproved messages + if (Object.keys(unapprovedTxs).length || + unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { + console.log('IN HOME SHOULD REDIRECT') + this.props.history.push(CONFIRM_TRANSACTION_ROUTE) + } + } + + render () { + log.debug('rendering primary') + const { + noActiveNotices, + lostAccounts, + forgottenPassword, + currentView, + activeAddress, + seedWords, + } = this.props + + // 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, + }, + }) + } + + // notices + if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) { + return h(Redirect, { + to: { + pathname: NOTICE_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/keychains/restore-vault.js b/ui/app/components/pages/keychains/restore-vault.js index 749da9758..24ebf89e3 100644 --- a/ui/app/components/pages/keychains/restore-vault.js +++ b/ui/app/components/pages/keychains/restore-vault.js @@ -2,9 +2,9 @@ const { withRouter } = require('react-router-dom') const PropTypes = require('prop-types') const { compose } = require('recompose') const PersistentForm = require('../../../../lib/persistent-form') -const { connect } = require('react-redux') +const connect = require('../../../metamask-connect') const h = require('react-hyperscript') -const { createNewVaultAndRestore } = require('../../../actions') +const { createNewVaultAndRestore, unMarkPasswordForgotten } = require('../../../actions') const { DEFAULT_ROUTE } = require('../../../routes') class RestoreVaultPage extends PersistentForm { @@ -22,6 +22,11 @@ class RestoreVaultPage extends PersistentForm { } } + cancel () { + this.props.unMarkPasswordForgotten() + .then(this.props.history.goBack()) + } + createNewVaultAndRestore () { this.setState({ error: null }) @@ -51,7 +56,7 @@ class RestoreVaultPage extends PersistentForm { // submit this.props.createNewVaultAndRestore(password, seed) - .then(() => history.push(DEFAULT_ROUTE)) + .then(() => this.props.history.push(DEFAULT_ROUTE)) .catch(({ message }) => { this.setState({ error: message }) log.error(message) @@ -76,7 +81,7 @@ class RestoreVaultPage extends PersistentForm { padding: 6, }, }, [ - 'Restore Vault', + this.props.t('restoreVault'), ]), // wallet seed entry @@ -85,14 +90,14 @@ class RestoreVaultPage extends PersistentForm { dataset: { persistentFormId: 'wallet-seed', }, - placeholder: 'Enter your secret twelve word phrase here to restore your vault.', + placeholder: this.props.t('secretPhrase'), }), // password h('input.large-input.letter-spacey', { type: 'password', id: 'password-box', - placeholder: 'New Password (min 8 chars)', + placeholder: this.props.t('newPassword8Chars'), dataset: { persistentFormId: 'password', }, @@ -106,7 +111,7 @@ class RestoreVaultPage extends PersistentForm { h('input.large-input.letter-spacey', { type: 'password', id: 'password-box-confirm', - placeholder: 'Confirm Password', + placeholder: this.props.t('confirmPassword'), onKeyPress: this.createOnEnter.bind(this), dataset: { persistentFormId: 'password-confirmation', @@ -130,12 +135,14 @@ class RestoreVaultPage extends PersistentForm { }, [ // cancel - h('button.primary', { onClick: () => history.goBack() }, 'CANCEL'), + h('button.primary', { + onClick: () => this.cancel(), + }, this.props.t('cancel')), // submit h('button.primary', { onClick: this.createNewVaultAndRestore.bind(this), - }, 'OK'), + }, this.props.t('ok')), ]), ]) @@ -161,6 +168,7 @@ const mapDispatchToProps = dispatch => { createNewVaultAndRestore: (password, seed) => { return dispatch(createNewVaultAndRestore(password, seed)) }, + unMarkPasswordForgotten: () => dispatch(unMarkPasswordForgotten()), } } diff --git a/ui/app/components/pages/signature-request.js b/ui/app/components/pages/signature-request.js deleted file mode 100644 index e9271fce1..000000000 --- a/ui/app/components/pages/signature-request.js +++ /dev/null @@ -1,284 +0,0 @@ -const { Component } = require('react') -const h = require('react-hyperscript') -const PropTypes = require('prop-types') -const Identicon = require('../identicon') -const { connect } = require('react-redux') -const ethUtil = require('ethereumjs-util') -const classnames = require('classnames') - -const AccountDropdownMini = require('../dropdowns/account-dropdown-mini') - -const t = require('../../../i18n') -const { conversionUtil } = require('../../conversion-util') -const { DEFAULT_ROUTE } = require('../../routes') - -const { - getSelectedAccount, - getCurrentAccountWithSendEtherInfo, - getSelectedAddress, - accountsWithSendEtherInfoSelector, - conversionRateSelector, -} = require('../../selectors.js') - -class SignatureRequest extends Component { - constructor (props) { - super(props) - - this.state = { - selectedAccount: props.selectedAccount, - accountDropdownOpen: false, - } - } - - componentWillMount () { - const { - unapprovedMsgCount = 0, - unapprovedPersonalMsgCount = 0, - unapprovedTypedMessagesCount = 0, - } = this.props - - if (unapprovedMsgCount + unapprovedPersonalMsgCount + unapprovedTypedMessagesCount < 1) { - this.props.history.push(DEFAULT_ROUTE) - } - } - - renderHeader () { - return h('div.request-signature__header', [ - - h('div.request-signature__header-background'), - - h('div.request-signature__header__text', t('sigRequest')), - - h('div.request-signature__header__tip-container', [ - h('div.request-signature__header__tip'), - ]), - - ]) - } - - renderAccountDropdown () { - const { - selectedAccount, - accountDropdownOpen, - } = this.state - - const { accounts } = this.props - - return h('div.request-signature__account', [ - - h('div.request-signature__account-text', [t('account') + ':']), - - h(AccountDropdownMini, { - selectedAccount, - accounts, - onSelect: selectedAccount => this.setState({ selectedAccount }), - dropdownOpen: accountDropdownOpen, - openDropdown: () => this.setState({ accountDropdownOpen: true }), - closeDropdown: () => this.setState({ accountDropdownOpen: false }), - }), - - ]) - } - - renderBalance () { - const { balance, conversionRate } = this.props - - const balanceInEther = conversionUtil(balance, { - fromNumericBase: 'hex', - toNumericBase: 'dec', - fromDenomination: 'WEI', - numberOfDecimals: 6, - conversionRate, - }) - - return h('div.request-signature__balance', [ - - h('div.request-signature__balance-text', [t('balance')]), - - h('div.request-signature__balance-value', `${balanceInEther} ETH`), - - ]) - } - - renderAccountInfo () { - return h('div.request-signature__account-info', [ - - this.renderAccountDropdown(), - - this.renderRequestIcon(), - - this.renderBalance(), - - ]) - } - - renderRequestIcon () { - const { requesterAddress } = this.props - - return h('div.request-signature__request-icon', [ - h(Identicon, { - diameter: 40, - address: requesterAddress, - }), - ]) - } - - renderRequestInfo () { - return h('div.request-signature__request-info', [ - - h('div.request-signature__headline', [ - t('yourSigRequested'), - ]), - - ]) - } - - msgHexToText (hex) { - try { - const stripped = ethUtil.stripHexPrefix(hex) - const buff = Buffer.from(stripped, 'hex') - return buff.toString('utf8') - } catch (e) { - return hex - } - } - - renderBody () { - let rows - let notice = t('youSign') + ':' - - const { txData } = this.props - const { type, msgParams: { data } } = txData - - if (type === 'personal_sign') { - rows = [{ name: t('message'), value: this.msgHexToText(data) }] - } else if (type === 'eth_signTypedData') { - rows = data - } else if (type === 'eth_sign') { - rows = [{ name: t('message'), value: data }] - notice = t('signNotice') - } - - return h('div.request-signature__body', {}, [ - - this.renderAccountInfo(), - - this.renderRequestInfo(), - - h('div.request-signature__notice', { - className: classnames({ - 'request-signature__notice': type === 'personal_sign' || type === 'eth_signTypedData', - 'request-signature__warning': type === 'eth_sign', - }), - }, [notice]), - - h('div.request-signature__rows', [ - - ...rows.map(({ name, value }) => { - return h('div.request-signature__row', [ - h('div.request-signature__row-title', [`${name}:`]), - h('div.request-signature__row-value', value), - ]) - }), - - ]), - - ]) - } - - renderFooter () { - const { - txData = {}, - signPersonalMessage, - signTypedMessage, - cancelPersonalMessage, - cancelTypedMessage, - signMessage, - cancelMessage, - history, - } = this.props - - const { type } = txData - - let cancel = () => Promise.resolve() - let sign = () => Promise.resolve() - const { msgParams: params = {}, id } = txData - params.id = id - params.metamaskId = id - - switch (type) { - case 'personal_sign': - cancel = () => cancelPersonalMessage(params) - sign = () => signPersonalMessage(params) - break - case 'eth_signTypedData': - cancel = () => cancelTypedMessage(params) - sign = () => signTypedMessage(params) - break - case 'eth_sign': - cancel = () => cancelMessage(params) - sign = () => signMessage(params) - break - } - - return h('div.request-signature__footer', [ - h('button.btn-secondary--lg.request-signature__footer__cancel-button', { - onClick: () => { - cancel().then(() => history.push(DEFAULT_ROUTE)) - }, - }, t('cancel')), - h('button.btn-primary--lg', { - onClick: () => { - sign().then(() => history.push(DEFAULT_ROUTE)) - }, - }, t('sign')), - ]) - } - - render () { - return ( - h('div.request-signature__container', [ - - this.renderHeader(), - - this.renderBody(), - - this.renderFooter(), - - ]) - ) - } -} - -SignatureRequest.propTypes = { - txData: PropTypes.object, - signPersonalMessage: PropTypes.func, - cancelPersonalMessage: PropTypes.func, - signTypedMessage: PropTypes.func, - cancelTypedMessage: PropTypes.func, - signMessage: PropTypes.func, - cancelMessage: PropTypes.func, - requesterAddress: PropTypes.string, - accounts: PropTypes.array, - conversionRate: PropTypes.number, - balance: PropTypes.string, - selectedAccount: PropTypes.object, - history: PropTypes.object, - unapprovedMsgCount: PropTypes.number, - unapprovedPersonalMsgCount: PropTypes.number, - unapprovedTypedMessagesCount: PropTypes.number, -} - -const mapStateToProps = state => { - return { - balance: getSelectedAccount(state).balance, - selectedAccount: getCurrentAccountWithSendEtherInfo(state), - selectedAddress: getSelectedAddress(state), - requester: null, - requesterAddress: null, - accounts: accountsWithSendEtherInfoSelector(state), - conversionRate: conversionRateSelector(state), - } -} - -module.exports = connect(mapStateToProps)(SignatureRequest) diff --git a/ui/app/components/pages/unlock.js b/ui/app/components/pages/unlock.js index 3d7a9091c..ed4b9ded7 100644 --- a/ui/app/components/pages/unlock.js +++ b/ui/app/components/pages/unlock.js @@ -1,14 +1,22 @@ const { Component } = require('react') const PropTypes = require('prop-types') -const { connect } = require('react-redux') +const connect = require('../../metamask-connect') const h = require('react-hyperscript') const { withRouter } = require('react-router-dom') const { compose } = require('recompose') -const { tryUnlockMetamask, forgotPassword, markPasswordForgotten } = require('../../actions') +const { + tryUnlockMetamask, + forgotPassword, + markPasswordForgotten, + setNetworkEndpoints, + setFeatureFlag, +} = require('../../actions') +const environmentType = require('../../../../app/scripts/lib/environment-type') const getCaretCoordinates = require('textarea-caret') const EventEmitter = require('events').EventEmitter const Mascot = require('../mascot') -const { DEFAULT_ROUTE } = require('../../routes') +const { OLD_UI_NETWORK_TYPE } = require('../../../../app/scripts/config').enums +const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../routes') class UnlockScreen extends Component { constructor (props) { @@ -77,70 +85,76 @@ class UnlockScreen extends Component { render () { const { error } = this.state - const { markPasswordForgotten } = this.props - return ( - h('.unlock-page.main-container', [ - h('.flex-column', { + 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) + + console.log('typeeee', environmentType()) + if (environmentType() === '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: { - width: 'inherit', + fontSize: '0.8em', + color: '#aeaeae', + textDecoration: 'underline', + marginTop: '32px', }, - }, [ - h('.unlock-screen.flex-column.flex-center.flex-grow', [ - - h(Mascot, { - animationEventEmitter: this.animationEventEmitter, - }), - - h('h1', { - style: { - fontSize: '1.4em', - textTransform: 'uppercase', - color: '#7F8082', - }, - }, 'MetaMask'), - - 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, - }, - }, 'Unlock'), - - h('.flex-row.flex-center.flex-grow', [ - h('p.pointer', { - onClick: () => { - markPasswordForgotten() - global.platform.openExtensionInBrowser() - }, - style: { - fontSize: '0.8em', - color: 'rgb(247, 134, 28)', - textDecoration: 'underline', - }, - }, 'Restore from seed phrase'), - ]), - ]), - ]), + }, this.props.t('classicInterface')), ]) ) } @@ -152,6 +166,9 @@ UnlockScreen.propTypes = { markPasswordForgotten: PropTypes.func, history: PropTypes.object, isUnlocked: PropTypes.bool, + t: PropTypes.func, + useOldInterface: PropTypes.func, + setNetworkEndpoints: PropTypes.func, } const mapStateToProps = state => { @@ -166,6 +183,8 @@ const mapDispatchToProps = dispatch => { 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)), } } diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js index b76c1e60f..92feb70ba 100644 --- a/ui/app/components/signature-request.js +++ b/ui/app/components/signature-request.js @@ -5,6 +5,8 @@ const Identicon = require('./identicon') const connect = require('../metamask-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') @@ -19,6 +21,8 @@ const { conversionRateSelector, } = require('../selectors.js') +const { DEFAULT_ROUTE } = require('../routes') + function mapStateToProps (state) { return { balance: getSelectedAccount(state).balance, @@ -37,7 +41,10 @@ function mapDispatchToProps (dispatch) { } } -module.exports = connect(mapStateToProps, mapDispatchToProps)(SignatureRequest) +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(SignatureRequest) inherits(SignatureRequest, Component) function SignatureRequest (props) { @@ -223,10 +230,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.props.t('cancel')), h('button.btn-primary--lg', { - onClick: sign, + onClick: event => { + sign(event).then(() => this.props.history.push(DEFAULT_ROUTE)) + }, }, this.props.t('sign')), ]) } diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 57ae89dc6..35ec6c7af 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -2,12 +2,13 @@ const inherits = require('util').inherits const Component = require('react').Component const h = require('react-hyperscript') const connect = require('./metamask-connect') -const { withRouter } = require('react-router-dom') +const { withRouter, Redirect } = require('react-router-dom') const { compose } = require('recompose') const actions = require('./actions') const txHelper = require('../lib/tx-helper') const PendingTx = require('./components/pending-tx') +const SignatureRequest = require('./components/signature-request') // const PendingMsg = require('./components/pending-msg') // const PendingPersonalMsg = require('./components/pending-personal-msg') // const PendingTypedMsg = require('./components/pending-typed-msg') @@ -55,6 +56,7 @@ function ConfirmTxScreen () { } ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) { + console.log('CONFTX COMPONENTDIDUPDATE') const { unapprovedTxs, network, @@ -67,6 +69,7 @@ ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) { const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) if (prevTx.status === 'dropped' && unconfTxList.length === 0) { + console.log('CONFTX REDIRECTINGTODEFAULT') this.props.history.push(DEFAULT_ROUTE) } } @@ -87,6 +90,7 @@ ConfirmTxScreen.prototype.render = function () { } = props var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network) + console.log('UNCONF', unconfTxList, props.index, props) var txData = unconfTxList[props.index] || {} var txParams = txData.params || {} @@ -108,7 +112,13 @@ ConfirmTxScreen.prototype.render = function () { */ log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`) - if (unconfTxList.length === 0) return h(Loading) + if (unconfTxList.length === 0) { + return h(Redirect, { + to: { + pathname: DEFAULT_ROUTE, + }, + }) + } return currentTxView({ // Properties @@ -136,11 +146,27 @@ ConfirmTxScreen.prototype.render = function () { function currentTxView (opts) { log.info('rendering current tx view') const { txData } = opts - const { txParams } = txData + const { txParams, msgParams } = txData + console.log('TXPARAMS', txParams, msgParams) if (txParams) { log.debug('txParams detected, rendering pending tx') return h(PendingTx, opts) + } else if (msgParams) { + log.debug('msgParams detected, rendering pending msg') + + return h(SignatureRequest, opts) + + // if (type === 'eth_sign') { + // log.debug('rendering eth_sign message') + // return h(PendingMsg, opts) + // } else if (type === 'personal_sign') { + // log.debug('rendering personal_sign message') + // return h(PendingPersonalMsg, opts) + // } else if (type === 'eth_signTypedData') { + // log.debug('rendering eth_signTypedData message') + // return h(PendingTypedMsg, opts) + // } } return h(Loading) @@ -174,7 +200,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) { @@ -188,7 +214,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) { @@ -196,25 +222,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/routes.js b/ui/app/routes.js index aadf91cf5..d3b305b40 100644 --- a/ui/app/routes.js +++ b/ui/app/routes.js @@ -10,12 +10,13 @@ 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 SIGNATURE_REQUEST_ROUTE = '/signature-request' const WELCOME_ROUTE = '/welcome' const INITIALIZE_ROUTE = '/initialize' 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' module.exports = { DEFAULT_ROUTE, @@ -36,4 +37,5 @@ module.exports = { INITIALIZE_ROUTE, INITIALIZE_IMPORT_ACCOUNT_ROUTE, INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, + INITIALIZE_UNIQUE_IMAGE_ROUTE, } -- cgit From 2fa554a6414d2231dcd6f2476866ea9c1c7b80ca Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Fri, 30 Mar 2018 17:37:24 -0700 Subject: Fix conf-tx render --- ui/app/app.js | 5 ++++- ui/app/components/tx-list.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/app.js b/ui/app/app.js index af2b70353..8a21220b1 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -4,7 +4,6 @@ const connect = require('react-redux').connect const { Route, Switch, Redirect, 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') @@ -732,6 +731,10 @@ function mapDispatchToProps (dispatch, ownProps) { } } +App.contextTypes = { + t: PropTypes.func, +} + module.exports = compose( withRouter, connect(mapStateToProps, mapDispatchToProps) diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js index 0cef64ca6..70643aa36 100644 --- a/ui/app/components/tx-list.js +++ b/ui/app/components/tx-list.js @@ -120,7 +120,10 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa const isUnapproved = transactionStatus === 'unapproved' if (isUnapproved) { - opts.onClick = () => history.push(CONFIRM_TRANSACTION_ROUTE) + 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) -- cgit From 6277a4c46aa2fd94f0fff047aff346d7f255224d Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 2 Apr 2018 02:59:49 -0700 Subject: Refactor onboarding flow --- ui/app/actions.js | 5 ++-- ui/app/app.js | 24 +++------------ ui/app/components/pages/add-token.js | 4 +-- ui/app/components/pages/home.js | 22 +++++++------- ui/app/components/pages/keychains/restore-vault.js | 2 +- ui/app/components/pages/keychains/reveal-seed.js | 2 +- ui/app/routes.js | 8 +++++ ui/app/send-v2.js | 6 ++-- ui/app/welcome-screen.js | 34 +++++++++++++++++----- 9 files changed, 60 insertions(+), 47 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 4550237d5..08df31e1f 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1322,12 +1322,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) } }) }) diff --git a/ui/app/app.js b/ui/app/app.js index 8a21220b1..f3e9e3470 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -88,34 +88,18 @@ class App extends Component { return ( h(Switch, [ - h(Route, { - path: WELCOME_ROUTE, - exact, - component: WelcomeScreen, - }), - h(Route, { - path: INITIALIZE_ROUTE, - component: InitializeScreen, - }), + h(Route, { path: INITIALIZE_ROUTE, component: InitializeScreen }), h(Initialized, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedPage, mascaraComponent: MascaraSeedScreen, }), - h(Initialized, { - path: CONFIRM_SEED_ROUTE, - exact, - mascaraComponent: MascaraConfirmSeedScreen, - }), + // h(Initialized, { path: CONFIRM_SEED_ROUTE, exact, component: MascaraConfirmSeedScreen }), 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: MascaraNoticeScreen, - }), + h(Initialized, { path: NOTICE_ROUTE, exact, component: NoticeScreen }), h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }), h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }), h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }), @@ -322,7 +306,7 @@ class App extends Component { ]), - isUnlocked && h('div.account-menu__icon', { onClick: this.context.toggleAccountMenu }, [ + isUnlocked && h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [ h(Identicon, { address: this.props.selectedAddress, diameter: 32, diff --git a/ui/app/components/pages/add-token.js b/ui/app/components/pages/add-token.js index a5b5ea57b..d387720e5 100644 --- a/ui/app/components/pages/add-token.js +++ b/ui/app/components/pages/add-token.js @@ -384,7 +384,7 @@ AddTokenScreen.prototype.render = function () { return h('div.add-token', [ h('div.add-token__header', [ h('div.add-token__header__cancel', { - onClick: () => history.goBack(), + onClick: () => history.push(DEFAULT_ROUTE), }, [ h('i.fa.fa-angle-left.fa-lg'), h('span', this.context.t('cancel')), @@ -417,7 +417,7 @@ AddTokenScreen.prototype.render = function () { !isShowingConfirmation && h('div.add-token__buttons', [ h('button.btn-secondary--lg.add-token__cancel-button', { - onClick: () => history.goBack(), + 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/home.js b/ui/app/components/pages/home.js index 4f8c00768..34cdb8a4d 100644 --- a/ui/app/components/pages/home.js +++ b/ui/app/components/pages/home.js @@ -35,11 +35,11 @@ class Home extends Component { unapprovedTypedMessagesCount = 0, } = this.props - console.log('IN HOME COMPONENDIMOUNT') + console.log('HOME MOUNTED') + // unapprovedTxs and unapproved messages if (Object.keys(unapprovedTxs).length || unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { - console.log('IN HOME SHOULD REDIRECT') this.props.history.push(CONFIRM_TRANSACTION_ROUTE) } } @@ -55,6 +55,15 @@ class Home extends Component { 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') @@ -74,15 +83,6 @@ class Home extends Component { }) } - // notices - if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) { - return h(Redirect, { - to: { - pathname: NOTICE_ROUTE, - }, - }) - } - // if (!props.noActiveNotices) { // log.debug('rendering notice screen for unread notices.') // return h(NoticeScreen, { diff --git a/ui/app/components/pages/keychains/restore-vault.js b/ui/app/components/pages/keychains/restore-vault.js index 24ebf89e3..77ec32efe 100644 --- a/ui/app/components/pages/keychains/restore-vault.js +++ b/ui/app/components/pages/keychains/restore-vault.js @@ -24,7 +24,7 @@ class RestoreVaultPage extends PersistentForm { cancel () { this.props.unMarkPasswordForgotten() - .then(this.props.history.goBack()) + .then(this.props.history.push(DEFAULT_ROUTE)) } createNewVaultAndRestore () { diff --git a/ui/app/components/pages/keychains/reveal-seed.js b/ui/app/components/pages/keychains/reveal-seed.js index 029eb7d8e..247f3c8e2 100644 --- a/ui/app/components/pages/keychains/reveal-seed.js +++ b/ui/app/components/pages/keychains/reveal-seed.js @@ -133,7 +133,7 @@ class RevealSeedPage extends Component { }, [ // cancel h('button.primary', { - onClick: () => history.goBack(), + onClick: () => history.push(DEFAULT_ROUTE), }, 'CANCEL'), // submit diff --git a/ui/app/routes.js b/ui/app/routes.js index d3b305b40..4b3f8f4d8 100644 --- a/ui/app/routes.js +++ b/ui/app/routes.js @@ -14,9 +14,13 @@ 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, @@ -35,7 +39,11 @@ module.exports = { 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/send-v2.js b/ui/app/send-v2.js index e483008c6..abbb97643 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -29,7 +29,7 @@ const { isTokenBalanceSufficient, } = require('./components/send/send-utils') const { isValidAddress } = require('./util') -const { CONFIRM_TRANSACTION_ROUTE } = require('./routes') +const { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } = require('./routes') SendTransactionScreen.contextTypes = { t: PropTypes.func, @@ -201,7 +201,7 @@ SendTransactionScreen.prototype.renderHeader = function () { h('div.page-container__header-close', { onClick: () => { clearSend() - history.goBack() + history.push(DEFAULT_ROUTE) }, }), @@ -521,7 +521,7 @@ SendTransactionScreen.prototype.renderFooter = function () { h('button.btn-secondary--lg.page-container__footer-button', { onClick: () => { clearSend() - history.goBack() + history.push(DEFAULT_ROUTE) }, }, this.context.t('cancel')), h('button.btn-primary--lg.page-container__footer-button', { diff --git a/ui/app/welcome-screen.js b/ui/app/welcome-screen.js index f32491b0d..99be179c6 100644 --- a/ui/app/welcome-screen.js +++ b/ui/app/welcome-screen.js @@ -3,9 +3,11 @@ 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_ROUTE } from './routes' +import { INITIALIZE_CREATE_PASSWORD_ROUTE } from './routes' class WelcomeScreen extends Component { static propTypes = { @@ -18,9 +20,18 @@ class WelcomeScreen extends Component { this.animationEventEmitter = new EventEmitter() } + componentWillMount () { + const { history, welcomeScreenSeen } = this.props + + if (welcomeScreenSeen) { + console.log('SEENT', welcomeScreenSeen) + history.push(INITIALIZE_CREATE_PASSWORD_ROUTE) + } + } + initiateAccountCreation = () => { this.props.closeWelcomeScreen() - this.props.history.push(INITIALIZE_ROUTE) + this.props.history.push(INITIALIZE_CREATE_PASSWORD_ROUTE) } render () { @@ -51,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) -- cgit From 92f8157dfeffff8173f6abc896cad59ada9a48ef Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 2 Apr 2018 13:15:31 -0700 Subject: Add withRouter to I18nProvider component --- ui/app/i18n-provider.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'ui') 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) -- cgit From 516c1869b0f366a42282a66e14185ce630f883dd Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 2 Apr 2018 16:24:37 -0700 Subject: Fix lint errors --- ui/app/app.js | 220 +-------------------- ui/app/components/pages/home.js | 2 - ui/app/components/pages/keychains/restore-vault.js | 1 - ui/app/components/pages/unlock.js | 1 - ui/app/conf-tx.js | 4 - ui/app/first-time/init-menu.js | 1 - ui/app/welcome-screen.js | 2 +- 7 files changed, 2 insertions(+), 229 deletions(-) (limited to 'ui') diff --git a/ui/app/app.js b/ui/app/app.js index f3e9e3470..52e5e00a8 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -1,7 +1,7 @@ const { Component } = require('react') const PropTypes = require('prop-types') const connect = require('react-redux').connect -const { Route, Switch, Redirect, withRouter } = require('react-router-dom') +const { Route, Switch, withRouter } = require('react-router-dom') const { compose } = require('recompose') const h = require('react-hyperscript') const actions = require('./actions') @@ -9,17 +9,10 @@ const classnames = require('classnames') // init const InitializeScreen = require('../../mascara/src/app/first-time').default -const WelcomeScreen = require('./welcome-screen').default -const NewKeyChainScreen = require('./new-keychain') // mascara -const MascaraCreatePassword = require('../../mascara/src/app/first-time/create-password-screen').default -const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default -const MascaraNoticeScreen = require('../../mascara/src/app/first-time/notice-screen').default const MascaraSeedScreen = require('../../mascara/src/app/first-time/seed-screen').default -const MascaraConfirmSeedScreen = require('../../mascara/src/app/first-time/confirm-seed-screen').default // accounts -const MainContainer = require('./main-container') const SendTransactionScreen2 = require('./components/send/send-v2-container') const ConfirmTxScreen = require('./conf-tx') @@ -41,11 +34,9 @@ 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 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 @@ -56,7 +47,6 @@ const { UNLOCK_ROUTE, SETTINGS_ROUTE, REVEAL_SEED_ROUTE, - CONFIRM_SEED_ROUTE, RESTORE_VAULT_ROUTE, ADD_TOKEN_ROUTE, NEW_ACCOUNT_ROUTE, @@ -64,17 +54,9 @@ const { CONFIRM_TRANSACTION_ROUTE, INITIALIZE_ROUTE, NOTICE_ROUTE, - SIGNATURE_REQUEST_ROUTE, - WELCOME_ROUTE, } = require('./routes') class App extends Component { - // constructor (props) { - // super(props) - - // this.renderPrimary = this.renderPrimary.bind(this) - // } - componentWillMount () { const { currentCurrency, setCurrentCurrencyToUSD } = this.props @@ -340,206 +322,6 @@ class App extends Component { }) } - // renderPrimary () { - // log.debug('rendering primary') - // const { - // noActiveNotices, - // lostAccounts, - // forgottenPassword, - // currentView, - // activeAddress, - // unapprovedTxs = {}, - // seedWords, - // unapprovedMsgCount = 0, - // unapprovedPersonalMsgCount = 0, - // unapprovedTypedMessagesCount = 0, - // } = this.props - - // // 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, - // }, - // }) - // } - - // // notices - // if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) { - // return h(Redirect, { - // to: { - // pathname: NOTICE_ROUTE, - // }, - // }) - // } - - // // unapprovedTxs and unapproved messages - // if (Object.keys(unapprovedTxs).length || - // unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { - // return h(Redirect, { - // to: { - // pathname: CONFIRM_TRANSACTION_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'}) - // } - // } - toggleMetamaskActive () { if (!this.props.isUnlocked) { // currently inactive: redirect to password box diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js index 34cdb8a4d..ffe35a2a8 100644 --- a/ui/app/components/pages/home.js +++ b/ui/app/components/pages/home.js @@ -35,8 +35,6 @@ class Home extends Component { unapprovedTypedMessagesCount = 0, } = this.props - console.log('HOME MOUNTED') - // unapprovedTxs and unapproved messages if (Object.keys(unapprovedTxs).length || unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { diff --git a/ui/app/components/pages/keychains/restore-vault.js b/ui/app/components/pages/keychains/restore-vault.js index 77ec32efe..d57894e00 100644 --- a/ui/app/components/pages/keychains/restore-vault.js +++ b/ui/app/components/pages/keychains/restore-vault.js @@ -65,7 +65,6 @@ class RestoreVaultPage extends PersistentForm { render () { const { error } = this.state - const { history } = this.props this.persistentFormParentId = 'restore-vault-form' return ( diff --git a/ui/app/components/pages/unlock.js b/ui/app/components/pages/unlock.js index ed4b9ded7..b3320da21 100644 --- a/ui/app/components/pages/unlock.js +++ b/ui/app/components/pages/unlock.js @@ -131,7 +131,6 @@ class UnlockScreen extends Component { this.props.markPasswordForgotten() this.props.history.push(RESTORE_VAULT_ROUTE) - console.log('typeeee', environmentType()) if (environmentType() === 'popup') { global.platform.openExtensionInBrowser() } diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 816127ab8..3134cf331 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -56,7 +56,6 @@ function ConfirmTxScreen () { } ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) { - console.log('CONFTX COMPONENTDIDUPDATE') const { unapprovedTxs, network, @@ -69,7 +68,6 @@ ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) { const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) if (prevTx.status === 'dropped' && unconfTxList.length === 0) { - console.log('CONFTX REDIRECTINGTODEFAULT') this.props.history.push(DEFAULT_ROUTE) } } @@ -90,7 +88,6 @@ ConfirmTxScreen.prototype.render = function () { } = props var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network) - console.log('UNCONF', unconfTxList, props.index, props) var txData = unconfTxList[props.index] || {} var txParams = txData.params || {} @@ -147,7 +144,6 @@ function currentTxView (opts) { log.info('rendering current tx view') const { txData } = opts const { txParams, msgParams } = txData - console.log('TXPARAMS', txParams, msgParams) if (txParams) { log.debug('txParams detected, rendering pending tx') diff --git a/ui/app/first-time/init-menu.js b/ui/app/first-time/init-menu.js index 9bd4c78da..692b01c8c 100644 --- a/ui/app/first-time/init-menu.js +++ b/ui/app/first-time/init-menu.js @@ -3,7 +3,6 @@ const { Component } = require('react') const PropTypes = require('prop-types') const connect = require('react-redux').connect const h = require('react-hyperscript') -const PropTypes = require('prop-types') const Mascot = require('../components/mascot') const actions = require('../actions') const Tooltip = require('../components/tooltip') diff --git a/ui/app/welcome-screen.js b/ui/app/welcome-screen.js index 99be179c6..2fa244d9f 100644 --- a/ui/app/welcome-screen.js +++ b/ui/app/welcome-screen.js @@ -12,6 +12,7 @@ import { INITIALIZE_CREATE_PASSWORD_ROUTE } from './routes' class WelcomeScreen extends Component { static propTypes = { closeWelcomeScreen: PropTypes.func.isRequired, + welcomeScreenSeen: PropTypes.bool, history: PropTypes.object, } @@ -24,7 +25,6 @@ class WelcomeScreen extends Component { const { history, welcomeScreenSeen } = this.props if (welcomeScreenSeen) { - console.log('SEENT', welcomeScreenSeen) history.push(INITIALIZE_CREATE_PASSWORD_ROUTE) } } -- cgit From 74049c19fce8ee77b1fa9933e26ac3569e3513f9 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 3 Apr 2018 13:52:01 -0700 Subject: Internationalize currency Fixes #3580 --- ui/app/components/balance-component.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js index d591ab455..f6ca6dd26 100644 --- a/ui/app/components/balance-component.js +++ b/ui/app/components/balance-component.js @@ -4,6 +4,7 @@ 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 { formatBalance, generateBalanceObject } = require('../util') @@ -97,9 +98,13 @@ BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatS const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0 if (shouldNotRenderFiat) return null + const display = currencyFormatter.format(Number(fiatDisplayNumber), { + code: fiatSuffix.toUpperCase() + }) + return h('div.fiat-amount', { style: {}, - }, `${fiatPrefix}${fiatDisplayNumber} ${fiatSuffix}`) + }, display) } BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) { -- cgit From 99c22a0835e8f9351057374ff7cb027a5104236b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 3 Apr 2018 14:00:04 -0700 Subject: Linted --- ui/app/components/balance-component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js index f6ca6dd26..22f5920a6 100644 --- a/ui/app/components/balance-component.js +++ b/ui/app/components/balance-component.js @@ -99,7 +99,7 @@ BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatS if (shouldNotRenderFiat) return null const display = currencyFormatter.format(Number(fiatDisplayNumber), { - code: fiatSuffix.toUpperCase() + code: fiatSuffix.toUpperCase(), }) return h('div.fiat-amount', { -- cgit From bf38aa6f1d793a0e18e64164a3a735e202ca34d6 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 3 Apr 2018 16:59:32 -0700 Subject: Fix transaction confirmations --- ui/app/components/pages/home.js | 30 ++++++++++++++++-------------- ui/app/conf-tx.js | 19 ++++++++++++++++--- 2 files changed, 32 insertions(+), 17 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js index 177d9b4e7..7857a2a99 100644 --- a/ui/app/components/pages/home.js +++ b/ui/app/components/pages/home.js @@ -27,6 +27,22 @@ const { } = 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 { @@ -36,10 +52,6 @@ class Home extends Component { currentView, activeAddress, seedWords, - unapprovedTxs = {}, - unapprovedMsgCount = 0, - unapprovedPersonalMsgCount = 0, - unapprovedTypedMessagesCount = 0, } = this.props // notices @@ -70,16 +82,6 @@ class Home extends Component { }) } - // unapprovedTxs and unapproved messages - if (Object.keys(unapprovedTxs).length || - unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { - return h(Redirect, { - to: { - pathname: CONFIRM_TRANSACTION_ROUTE, - }, - }) - } - // if (!props.noActiveNotices) { // log.debug('rendering notice screen for unread notices.') // return h(NoticeScreen, { diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 886d98be7..fee7cd36f 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -2,7 +2,7 @@ const inherits = require('util').inherits const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect -const { withRouter, Redirect } = require('react-router-dom') +const { withRouter } = require('react-router-dom') const { compose } = require('recompose') const actions = require('./actions') const txHelper = require('../lib/tx-helper') @@ -25,6 +25,7 @@ function mapStateToProps (state) { const { unapprovedMsgCount, unapprovedPersonalMsgCount, + unapprovedTypedMessagesCount, } = metamask return { @@ -45,6 +46,7 @@ function mapStateToProps (state) { computedBalances: state.metamask.computedBalances, unapprovedMsgCount, unapprovedPersonalMsgCount, + unapprovedTypedMessagesCount, send: state.metamask.send, selectedAddressTxList: state.metamask.selectedAddressTxList, } @@ -55,6 +57,16 @@ 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 = {}, @@ -63,7 +75,7 @@ ConfirmTxScreen.prototype.componentDidMount = function () { } = this.props const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) - if (unconfTxList.length === 0 && !send.to) { + if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) { this.props.history.push(DEFAULT_ROUTE) } } @@ -81,7 +93,8 @@ ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) { const prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {} const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) - if (unconfTxList.length === 0 && (prevTx.status === 'dropped' || !send.to)) { + if (unconfTxList.length === 0 && + (prevTx.status === 'dropped' || !send.to && this.getUnapprovedMessagesTotal() === 0)) { this.props.history.push(DEFAULT_ROUTE) } } -- cgit From 007f91cc50227eebb3db557db17c2f81625bdcf2 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 3 Apr 2018 17:43:42 -0700 Subject: commit --- ui/app/app.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'ui') diff --git a/ui/app/app.js b/ui/app/app.js index 82871d970..f71a9bb06 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -11,7 +11,6 @@ const classnames = require('classnames') const InitializeScreen = require('../../mascara/src/app/first-time').default // mascara const MascaraSeedScreen = require('../../mascara/src/app/first-time/seed-screen').default - // accounts const SendTransactionScreen2 = require('./components/send/send-v2-container') const ConfirmTxScreen = require('./conf-tx') @@ -26,7 +25,7 @@ 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 RevealSeedPage = require('./components/pages/keychains/reveal-seed') +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') @@ -71,12 +70,7 @@ class App extends Component { return ( h(Switch, [ h(Route, { path: INITIALIZE_ROUTE, component: InitializeScreen }), - h(Initialized, { - path: REVEAL_SEED_ROUTE, - exact, - component: RevealSeedPage, - mascaraComponent: MascaraSeedScreen, - }), + 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 }), -- cgit From 7776af7cd059499f34eea3408b323fd6b73fd05e Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 3 Apr 2018 18:20:51 -0700 Subject: Use new design for showing seed words --- ui/app/keychains/hd/recover-seed/confirmation.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'ui') 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)) } -- cgit From fa8500e09ad599837f6ad1b2dfc3530195a03b33 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 5 Apr 2018 09:53:24 -0700 Subject: commit --- ui/app/components/pending-tx/confirm-send-ether.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index afd459415..a9beeacbb 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -283,7 +283,6 @@ ConfirmSendEther.prototype.editTransaction = function (txMeta) { ConfirmSendEther.prototype.render = function () { const { - editTransaction, currentCurrency, clearSend, conversionRate, @@ -339,7 +338,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', }, -- cgit From 82af778e6251879b3296136c44028d2c65f9d170 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 5 Apr 2018 10:11:09 -0700 Subject: Revert "commit" This reverts commit fa8500e09ad599837f6ad1b2dfc3530195a03b33. --- ui/app/components/pending-tx/confirm-send-ether.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index a9beeacbb..afd459415 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -283,6 +283,7 @@ ConfirmSendEther.prototype.editTransaction = function (txMeta) { ConfirmSendEther.prototype.render = function () { const { + editTransaction, currentCurrency, clearSend, conversionRate, @@ -338,7 +339,7 @@ ConfirmSendEther.prototype.render = function () { h('.page-container__header', [ h('.page-container__header-row', [ h('span.page-container__back-button', { - onClick: () => this.editTransaction(txMeta), + onClick: () => editTransaction(txMeta), style: { visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden', }, -- cgit From 2db55cd0de31abacf2d00eba76505fa6b880928d Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 5 Apr 2018 10:29:16 -0700 Subject: Fix editing transaction --- ui/app/components/pending-tx/confirm-send-ether.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index afd459415..a9beeacbb 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -283,7 +283,6 @@ ConfirmSendEther.prototype.editTransaction = function (txMeta) { ConfirmSendEther.prototype.render = function () { const { - editTransaction, currentCurrency, clearSend, conversionRate, @@ -339,7 +338,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', }, -- cgit From 1e6f062bb6bbc39d6ab21a351fdb0d3bcab4d7d5 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Fri, 6 Apr 2018 02:04:39 -0700 Subject: Fix integration tests --- ui/app/app.js | 2 -- ui/app/components/pending-tx/confirm-send-ether.js | 4 +++- ui/app/components/pending-tx/confirm-send-token.js | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/app.js b/ui/app/app.js index f71a9bb06..75c3febb0 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -9,8 +9,6 @@ const classnames = require('classnames') // init const InitializeScreen = require('../../mascara/src/app/first-time').default -// mascara -const MascaraSeedScreen = require('../../mascara/src/app/first-time/seed-screen').default // accounts const SendTransactionScreen2 = require('./components/send/send-v2-container') const ConfirmTxScreen = require('./conf-tx') diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index a9beeacbb..3ee614a2a 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -516,7 +516,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')), ]), ]), ]) diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 5e6d0747d..6942f9b51 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -524,7 +524,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')]), ]), ]), ]), -- cgit From 4011dac6f6826a7588e66e1a1e07b180e19755af Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 9 Apr 2018 13:41:40 -0230 Subject: Improve display of crypto currencies when selected as the 'Current Conversion' --- ui/app/components/balance-component.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js index 22f5920a6..67e84bd5a 100644 --- a/ui/app/components/balance-component.js +++ b/ui/app/components/balance-component.js @@ -5,6 +5,7 @@ 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') @@ -98,9 +99,13 @@ BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatS const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0 if (shouldNotRenderFiat) return null - const display = currencyFormatter.format(Number(fiatDisplayNumber), { - code: fiatSuffix.toUpperCase(), - }) + 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: {}, @@ -122,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)) } -- cgit From 1382de2cda792800767e8a6fa068828282ca146e Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 9 Apr 2018 14:14:09 -0230 Subject: Internationalize converted value in currency-input.js --- ui/app/components/send/currency-display.js | 35 +++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) (limited to 'ui') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index 819fee0a0..c8df40a14 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()}`), ]) -- cgit From 9dbb9d12ad31d53b8911db171cb7d6b3fcb477e2 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 9 Apr 2018 16:45:23 -0230 Subject: Internationalize converted value in confirm screens --- ui/app/components/pending-tx/confirm-send-ether.js | 19 +++++++++++++++++-- ui/app/components/pending-tx/confirm-send-token.js | 21 +++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index d007e6661..2e4e0fbe5 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -21,6 +21,8 @@ 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') @@ -269,6 +271,16 @@ 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.render = function () { const { editTransaction, @@ -308,6 +320,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 @@ -354,7 +369,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}"` : '' ]), @@ -401,7 +416,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`), ]), diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 19e591fd6..65b9b9d07 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -25,6 +25,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') @@ -310,10 +312,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 +365,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 +377,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,6 +412,16 @@ 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() -- cgit From d8adc527f131d2eedaacea19b400db5a627d73a0 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 9 Apr 2018 16:54:52 -0230 Subject: Lint fix. --- ui/app/components/balance-component.js | 2 +- ui/app/components/send/currency-display.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js index 67e84bd5a..e31552f2d 100644 --- a/ui/app/components/balance-component.js +++ b/ui/app/components/balance-component.js @@ -5,7 +5,7 @@ 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 currencies = require('currency-formatter/currencies') const { formatBalance, generateBalanceObject } = require('../util') diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index c8df40a14..a7bd5d7ea 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -4,7 +4,7 @@ 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'); +const currencies = require('currency-formatter/currencies') module.exports = CurrencyDisplay -- cgit From 7129d7c0f3ce11839f8b8a576eb1e2c093fb96cc Mon Sep 17 00:00:00 2001 From: bitpshr Date: Thu, 12 Apr 2018 17:06:59 -0400 Subject: Require loglevel singleton in each module that uses it --- ui/app/actions.js | 1 + ui/app/app.js | 1 + ui/app/components/ens-input.js | 1 + ui/app/components/pages/home.js | 1 + ui/app/components/pages/keychains/restore-vault.js | 1 + ui/app/components/token-balance.js | 1 + ui/app/components/token-list.js | 1 + ui/app/conf-tx.js | 1 + ui/app/keychains/hd/restore-vault.js | 1 + ui/app/main-container.js | 1 + ui/app/reducers/app.js | 1 + ui/index.js | 3 +-- ui/lib/tx-helper.js | 1 + 13 files changed, 13 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 0748a5bea..780edcaba 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -3,6 +3,7 @@ const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url') const { getTokenAddressFromTokenObject } = require('./util') const ethUtil = require('ethereumjs-util') const { fetchLocale } = require('../i18n-helper') +const log = require('loglevel') var actions = { _setBackgroundConnection: _setBackgroundConnection, diff --git a/ui/app/app.js b/ui/app/app.js index 75c3febb0..827b4e9ce 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -6,6 +6,7 @@ const { compose } = require('recompose') const h = require('react-hyperscript') const actions = require('./actions') const classnames = require('classnames') +const log = require('loglevel') // init const InitializeScreen = require('../../mascara/src/app/first-time').default 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/pages/home.js b/ui/app/components/pages/home.js index 7857a2a99..90b8e1d37 100644 --- a/ui/app/components/pages/home.js +++ b/ui/app/components/pages/home.js @@ -5,6 +5,7 @@ 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') diff --git a/ui/app/components/pages/keychains/restore-vault.js b/ui/app/components/pages/keychains/restore-vault.js index d57894e00..33575bfbb 100644 --- a/ui/app/components/pages/keychains/restore-vault.js +++ b/ui/app/components/pages/keychains/restore-vault.js @@ -6,6 +6,7 @@ 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) { 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-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/conf-tx.js b/ui/app/conf-tx.js index fee7cd36f..b71538e31 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -6,6 +6,7 @@ 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') 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 6dd4ff151..c305687ea 100644 --- a/ui/app/main-container.js +++ b/ui/app/main-container.js @@ -4,6 +4,7 @@ const inherits = require('util').inherits const AccountAndTransactionDetails = require('./account-and-transaction-details') 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/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/index.js b/ui/index.js index 746e28eab..7b6faac76 100644 --- a/ui/index.js +++ b/ui/index.js @@ -6,8 +6,7 @@ const configureStore = require('./app/store') const txHelper = require('./lib/tx-helper') const { fetchLocale } = require('./i18n-helper') const { OLD_UI_NETWORK_TYPE, BETA_UI_NETWORK_TYPE } = require('../app/scripts/config').enums - -global.log = require('loglevel') +const log = require('loglevel') module.exports = launchMetamaskUi diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js index de3f00d2d..0a6f55a63 100644 --- a/ui/lib/tx-helper.js +++ b/ui/lib/tx-helper.js @@ -1,4 +1,5 @@ const valuesFor = require('../app/util').valuesFor +const log = require('loglevel') module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network) { log.debug('tx-helper called with params:') -- cgit From 34692acdf918f3e75eb61f768f3869bd76e4b3b9 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 13 Apr 2018 16:14:23 -0700 Subject: Fix action for setting locale --- ui/app/reducers/metamask.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index 6d0a5bb10..0493a1ff7 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -358,7 +358,7 @@ function reduceMetamask (state, action) { welcomeScreenSeen: true, }) - case action.SET_CURRENT_LOCALE: + case actions.SET_CURRENT_LOCALE: return extend(metamaskState, { currentLocale: action.value, }) -- cgit From a350e80feea6747a5e10088ac6ec15171a590a65 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Sun, 15 Apr 2018 23:37:42 -0400 Subject: Fetch token prices based on contract address --- ui/app/actions.js | 68 ++++++++++++++-------- ui/app/components/pending-tx/confirm-send-token.js | 8 +-- ui/app/components/send/send-v2-container.js | 2 +- ui/app/components/token-cell.js | 20 ++----- ui/app/components/tx-list-item.js | 18 +++--- ui/app/reducers/metamask.js | 23 +++++--- ui/app/selectors.js | 19 ++---- ui/app/send-v2.js | 8 +-- 8 files changed, 85 insertions(+), 81 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 0748a5bea..6453a2bc2 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -220,8 +220,10 @@ var actions = { coinBaseSubview: coinBaseSubview, SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW', shapeShiftSubview: shapeShiftSubview, - UPDATE_TOKEN_EXCHANGE_RATE: 'UPDATE_TOKEN_EXCHANGE_RATE', - updateTokenExchangeRate, + UPDATE_CONTRACT_EXCHANGE_RATES: 'UPDATE_CONTRACT_EXCHANGE_RATES', + UPDATE_CONTRACT_EXCHANGE_RATE: 'UPDATE_CONTRACT_EXCHANGE_RATE', + updateContractExchangeRates, + updateContractExchangeRate, PAIR_UPDATE: 'PAIR_UPDATE', pairUpdate: pairUpdate, coinShiftRquest: coinShiftRquest, @@ -1080,9 +1082,12 @@ function unlockMetamask (account) { } function updateMetamaskState (newState) { - return { - type: actions.UPDATE_METAMASK_STATE, - value: newState, + return async dispatch => { + await dispatch({ + type: actions.UPDATE_METAMASK_STATE, + value: newState, + }) + dispatch(updateContractExchangeRates()) } } @@ -1295,9 +1300,12 @@ function addTokens (tokens) { } function updateTokens (newTokens) { - return { - type: actions.UPDATE_TOKENS, - newTokens, + return async dispatch => { + await dispatch({ + type: actions.UPDATE_TOKENS, + newTokens, + }) + dispatch(updateContractExchangeRates()) } } @@ -1751,24 +1759,38 @@ function shapeShiftRequest (query, options, cb) { } } -function updateTokenExchangeRate (token = '') { - const pair = `${token.toLowerCase()}_eth` +async function fetchContractRate (address) { + try { + const response = await fetch(`https://exchanges.balanc3.net/prices?from=${address}&to=ETH&autoConversion=false&summaryOnly=true`) + const json = await response.json() + const rate = json && json.length ? json[0].averagePrice : 0 + return { address, rate } + } catch (error) { } +} - return dispatch => { - if (!token) { - return +function updateContractExchangeRates () { + return async (dispatch, getState) => { + const { metamask: { tokens = [] } } = getState() + const newExchangeRates = {} + + for (const i in tokens) { + const address = tokens[i].address + newExchangeRates[address] = (await fetchContractRate(address)).rate } - shapeShiftRequest('marketinfo', { pair }, marketinfo => { - if (!marketinfo.error) { - dispatch({ - type: actions.UPDATE_TOKEN_EXCHANGE_RATE, - payload: { - pair, - marketinfo, - }, - }) - } + dispatch({ + type: actions.UPDATE_CONTRACT_EXCHANGE_RATES, + payload: { newExchangeRates }, + }) + } +} + +function updateContractExchangeRate (address) { + return async dispatch => { + const { address, rate } = await fetchContractRate(address) + dispatch({ + type: actions.UPDATE_CONTRACT_EXCHANGE_RATE, + payload: { address, rate }, }) } } diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 6942f9b51..37130a1cc 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -48,7 +48,7 @@ module.exports = compose( 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) @@ -59,7 +59,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, @@ -75,12 +75,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 @@ -203,7 +200,6 @@ ConfirmSendToken.prototype.componentWillMount = function () { .balanceOf(selectedAddress) .then(usersToken => { }) - this.props.updateTokenExchangeRate() this.updateComponentSendErrors({}) } diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js index aca1a5a0a..75f16b382 100644 --- a/ui/app/components/send/send-v2-container.js +++ b/ui/app/components/send/send-v2-container.js @@ -66,7 +66,7 @@ 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)), + updateContractExchangeRate: address => dispatch(actions.updateContractExchangeRate(address)), signTokenTx: (tokenAddress, toAddress, amount, txData) => ( dispatch(actions.signTokenTx(tokenAddress, toAddress, amount, txData)) ), 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/tx-list-item.js b/ui/app/components/tx-list-item.js index 42c008798..403f83ab9 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -27,7 +27,7 @@ function mapStateToProps (state) { return { tokens: state.metamask.tokens, currentCurrency: getCurrentCurrency(state), - tokenExchangeRates: state.metamask.tokenExchangeRates, + contractExchangeRates: state.metamask.contractExchangeRates, selectedAddressTxList: state.metamask.selectedAddressTxList, } } @@ -142,31 +142,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 +218,6 @@ TxListItem.prototype.resubmit = function () { TxListItem.prototype.render = function () { const { transactionStatus, - transactionAmount, onClick, transactionId, dateString, @@ -229,7 +226,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 +284,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/reducers/metamask.js b/ui/app/reducers/metamask.js index 6d0a5bb10..d6b6de3b0 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -24,6 +24,7 @@ function reduceMetamask (state, action) { frequentRpcList: [], addressBook: [], selectedTokenAddress: null, + contractExchangeRates: {}, tokenExchangeRates: {}, tokens: [], send: { @@ -176,14 +177,22 @@ 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_CONTRACT_EXCHANGE_RATES: + const { payload: { newExchangeRates } } = action + return { + ...metamaskState, + contractExchangeRates: newExchangeRates, + } + + case actions.UPDATE_CONTRACT_EXCHANGE_RATE: + const { payload: { address, rate } } = action + return { + ...metamaskState, + contractExchangeRates: { + ...metamaskState.contractExchangeRates, + [address]: rate, }, - }) + } case actions.UPDATE_TOKENS: return extend(metamaskState, { diff --git a/ui/app/selectors.js b/ui/app/selectors.js index 2bdc39004..60cc264da 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) { diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index d0c28caa2..12cf55f62 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -89,14 +89,14 @@ SendTransactionScreen.prototype.updateSendTokenBalance = function (usersToken) { SendTransactionScreen.prototype.componentWillMount = function () { const { - updateTokenExchangeRate, + updateContractExchangeRate, selectedToken = {}, } = this.props - const { symbol } = selectedToken || {} + const { address } = selectedToken || {} - if (symbol) { - updateTokenExchangeRate(symbol) + if (address) { + updateContractExchangeRate(address) } this.updateGas() -- cgit From d0447f90583275868bb72aa7ae8f670bf3668173 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Mon, 16 Apr 2018 11:21:06 -0400 Subject: Maintain token prices using a background service --- ui/app/actions.js | 58 +++-------------------------- ui/app/components/send/send-v2-container.js | 1 - ui/app/reducers/metamask.js | 17 --------- ui/app/send-v2.js | 11 ------ 4 files changed, 6 insertions(+), 81 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 6453a2bc2..46f34e149 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -220,10 +220,6 @@ var actions = { coinBaseSubview: coinBaseSubview, SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW', shapeShiftSubview: shapeShiftSubview, - UPDATE_CONTRACT_EXCHANGE_RATES: 'UPDATE_CONTRACT_EXCHANGE_RATES', - UPDATE_CONTRACT_EXCHANGE_RATE: 'UPDATE_CONTRACT_EXCHANGE_RATE', - updateContractExchangeRates, - updateContractExchangeRate, PAIR_UPDATE: 'PAIR_UPDATE', pairUpdate: pairUpdate, coinShiftRquest: coinShiftRquest, @@ -1082,12 +1078,9 @@ function unlockMetamask (account) { } function updateMetamaskState (newState) { - return async dispatch => { - await dispatch({ - type: actions.UPDATE_METAMASK_STATE, - value: newState, - }) - dispatch(updateContractExchangeRates()) + return { + type: actions.UPDATE_METAMASK_STATE, + value: newState, } } @@ -1300,12 +1293,9 @@ function addTokens (tokens) { } function updateTokens (newTokens) { - return async dispatch => { - await dispatch({ - type: actions.UPDATE_TOKENS, - newTokens, - }) - dispatch(updateContractExchangeRates()) + return { + type: actions.UPDATE_TOKENS, + newTokens, } } @@ -1759,42 +1749,6 @@ function shapeShiftRequest (query, options, cb) { } } -async function fetchContractRate (address) { - try { - const response = await fetch(`https://exchanges.balanc3.net/prices?from=${address}&to=ETH&autoConversion=false&summaryOnly=true`) - const json = await response.json() - const rate = json && json.length ? json[0].averagePrice : 0 - return { address, rate } - } catch (error) { } -} - -function updateContractExchangeRates () { - return async (dispatch, getState) => { - const { metamask: { tokens = [] } } = getState() - const newExchangeRates = {} - - for (const i in tokens) { - const address = tokens[i].address - newExchangeRates[address] = (await fetchContractRate(address)).rate - } - - dispatch({ - type: actions.UPDATE_CONTRACT_EXCHANGE_RATES, - payload: { newExchangeRates }, - }) - } -} - -function updateContractExchangeRate (address) { - return async dispatch => { - const { address, rate } = await fetchContractRate(address) - dispatch({ - type: actions.UPDATE_CONTRACT_EXCHANGE_RATE, - payload: { address, rate }, - }) - } -} - function setFeatureFlag (feature, activated, notificationType) { return (dispatch) => { dispatch(actions.showLoadingIndication()) diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js index 75f16b382..adfc91240 100644 --- a/ui/app/components/send/send-v2-container.js +++ b/ui/app/components/send/send-v2-container.js @@ -66,7 +66,6 @@ function mapDispatchToProps (dispatch) { showCustomizeGasModal: () => dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })), estimateGas: params => dispatch(actions.estimateGas(params)), getGasPrice: () => dispatch(actions.getGasPrice()), - updateContractExchangeRate: address => dispatch(actions.updateContractExchangeRate(address)), signTokenTx: (tokenAddress, toAddress, amount, txData) => ( dispatch(actions.signTokenTx(tokenAddress, toAddress, amount, txData)) ), diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index d6b6de3b0..1ed049be2 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -177,23 +177,6 @@ function reduceMetamask (state, action) { conversionDate: action.value.conversionDate, }) - case actions.UPDATE_CONTRACT_EXCHANGE_RATES: - const { payload: { newExchangeRates } } = action - return { - ...metamaskState, - contractExchangeRates: newExchangeRates, - } - - case actions.UPDATE_CONTRACT_EXCHANGE_RATE: - const { payload: { address, rate } } = action - return { - ...metamaskState, - contractExchangeRates: { - ...metamaskState.contractExchangeRates, - [address]: rate, - }, - } - case actions.UPDATE_TOKENS: return extend(metamaskState, { tokens: action.newTokens, diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index 12cf55f62..30d3d3152 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -88,17 +88,6 @@ SendTransactionScreen.prototype.updateSendTokenBalance = function (usersToken) { } SendTransactionScreen.prototype.componentWillMount = function () { - const { - updateContractExchangeRate, - selectedToken = {}, - } = this.props - - const { address } = selectedToken || {} - - if (address) { - updateContractExchangeRate(address) - } - this.updateGas() } -- cgit From b0a105ce809b8b7e5e4431bd1ddecc523586cad0 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 16 Apr 2018 23:03:47 -0700 Subject: Fix confirmation popup not always opening --- ui/app/components/modals/modal.js | 11 ++++++----- ui/app/components/pages/unlock.js | 5 +++-- ui/app/first-time/init-menu.js | 5 +++-- ui/app/reducers/metamask.js | 5 +++-- ui/app/unlock.js | 5 +++-- 5 files changed, 18 insertions(+), 13 deletions(-) (limited to 'ui') 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/pages/unlock.js b/ui/app/components/pages/unlock.js index b3320da21..567b72518 100644 --- a/ui/app/components/pages/unlock.js +++ b/ui/app/components/pages/unlock.js @@ -11,7 +11,8 @@ const { setNetworkEndpoints, setFeatureFlag, } = require('../../actions') -const environmentType = require('../../../../app/scripts/lib/environment-type') +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') @@ -131,7 +132,7 @@ class UnlockScreen extends Component { this.props.markPasswordForgotten() this.props.history.push(RESTORE_VAULT_ROUTE) - if (environmentType() === 'popup') { + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { global.platform.openExtensionInBrowser() } }, diff --git a/ui/app/first-time/init-menu.js b/ui/app/first-time/init-menu.js index 692b01c8c..3df040922 100644 --- a/ui/app/first-time/init-menu.js +++ b/ui/app/first-time/init-menu.js @@ -8,7 +8,8 @@ const actions = require('../actions') const Tooltip = require('../components/tooltip') const getCaretCoordinates = require('textarea-caret') const { RESTORE_VAULT_ROUTE, DEFAULT_ROUTE } = require('../routes') -const environmentType = require('../../../app/scripts/lib/environment-type') +const { getEnvironmentType } = require('../../../app/scripts/lib/util') +const { ENVIRONMENT_TYPE_POPUP } = require('../../../app/scripts/lib/enums') const { OLD_UI_NETWORK_TYPE } = require('../../../app/scripts/config').enums class InitializeMenuScreen extends Component { @@ -180,7 +181,7 @@ class InitializeMenuScreen extends Component { showRestoreVault () { this.props.markPasswordForgotten() - if (environmentType() === 'popup') { + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { global.platform.openExtensionInBrowser() } diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index e65dc2d11..5f965fbe0 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -1,7 +1,8 @@ const extend = require('xtend') const actions = require('../actions') const MetamascaraPlatform = require('../../../app/scripts/platforms/window') -const environmentType = require('../../../app/scripts/lib/environment-type') +const { getEnvironmentType } = require('../../../app/scripts/lib/util') +const { ENVIRONMENT_TYPE_POPUP } = require('../../../app/scripts/lib/enums') const { OLD_UI_NETWORK_TYPE } = require('../../../app/scripts/config').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: {}, diff --git a/ui/app/unlock.js b/ui/app/unlock.js index 84d8b7e7c..1325656af 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -7,7 +7,8 @@ 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 { 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() } }, -- cgit From de7fc781a56fe21c01d8038b01da01fdec90f219 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Sat, 14 Apr 2018 00:00:48 -0700 Subject: Fix BigNumber exception in confirm-send-ether --- ui/app/components/pending-tx/index.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) (limited to 'ui') diff --git a/ui/app/components/pending-tx/index.js b/ui/app/components/pending-tx/index.js index acdd99364..dc496a8d8 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,9 @@ PendingTx.prototype.render = function () { const { sendTransaction } = this.props if (isFetching) { - return h('noscript') + return h(Loading, { + loadingMessage: this.context.t('estimatingTransaction'), + }) } switch (transactionType) { @@ -150,6 +168,10 @@ PendingTx.prototype.render = function () { sendTransaction, }) default: - return h('noscript') + return h(Loading) } } + +PendingTx.contextTypes = { + t: PropTypes.func, +} -- cgit From 053044fb6561bb3fe7b87c61bf5d5d2750c46c58 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 18 Apr 2018 00:56:52 -0700 Subject: Fix spinner layout --- ui/app/app.js | 13 ------------- ui/app/components/loading.js | 18 +++++++++++------- ui/app/components/pending-tx/index.js | 7 +++++-- ui/app/css/itcss/components/loading-overlay.scss | 12 ++++++++++-- 4 files changed, 26 insertions(+), 24 deletions(-) (limited to 'ui') diff --git a/ui/app/app.js b/ui/app/app.js index 827b4e9ce..e462701fa 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -137,8 +137,6 @@ class App extends Component { loadingMessage: loadMessage, }), - // this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }), - // content this.renderRoutes(), ]) @@ -302,17 +300,6 @@ class App extends Component { ) } - renderLoadingIndicator ({ isLoading, isLoadingNetwork, loadMessage }) { - const { isMascara } = this.props - - return isMascara - ? null - : h(Loading, { - isLoading: isLoading || isLoadingNetwork, - loadingMessage: loadMessage, - }) - } - toggleMetamaskActive () { if (!this.props.isUnlocked) { // currently inactive: redirect to password box 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/pending-tx/index.js b/ui/app/components/pending-tx/index.js index dc496a8d8..6ee83ba7e 100644 --- a/ui/app/components/pending-tx/index.js +++ b/ui/app/components/pending-tx/index.js @@ -142,7 +142,8 @@ PendingTx.prototype.render = function () { if (isFetching) { return h(Loading, { - loadingMessage: this.context.t('estimatingTransaction'), + fullScreen: true, + loadingMessage: this.context.t('generatingTransaction'), }) } @@ -168,7 +169,9 @@ PendingTx.prototype.render = function () { sendTransaction, }) default: - return h(Loading) + return h(Loading, { + fullScreen: true, + }) } } 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; + } } -- cgit From cce123c30e9562e01971edf9ae11b89cf91c7089 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Tue, 17 Apr 2018 15:57:55 -0400 Subject: Show checksummed addresses throughout the UI --- ui/app/components/account-dropdowns.js | 5 ++--- .../components/dropdowns/components/account-dropdowns.js | 5 ++--- ui/app/components/modals/export-private-key-modal.js | 7 ++++--- ui/app/components/qr-code.js | 13 +++++++------ ui/app/components/send/account-list-item.js | 3 ++- ui/app/components/tx-list-item.js | 5 ++++- ui/app/components/tx-view.js | 4 ++-- ui/app/components/wallet-view.js | 9 ++++++--- ui/app/util.js | 15 +++++++++++++-- ui/lib/icon-factory.js | 4 ++-- 10 files changed, 44 insertions(+), 26 deletions(-) (limited to 'ui') 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/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/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/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/tx-list-item.js b/ui/app/components/tx-list-item.js index 403f83ab9..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') @@ -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') diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js index 80aac35c4..263f992c0 100644 --- a/ui/app/components/tx-view.js +++ b/ui/app/components/tx-view.js @@ -2,13 +2,13 @@ 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') @@ -32,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 { diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index e3e1b8903..9e430f87b 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -6,6 +6,7 @@ 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') @@ -107,6 +108,8 @@ WalletView.prototype.render = function () { // 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) @@ -135,7 +138,7 @@ WalletView.prototype.render = function () { }, [ h(Identicon, { diameter: 54, - address: selectedAddress, + address: checksummedAddress, }), h('span.account-name', { @@ -158,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) }, @@ -169,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' } }), ]), ]), 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/lib/icon-factory.js b/ui/lib/icon-factory.js index 31498a3a9..7fadbceff 100644 --- a/ui/lib/icon-factory.js +++ b/ui/lib/icon-factory.js @@ -1,6 +1,6 @@ var iconFactory const isValidAddress = require('ethereumjs-util').isValidAddress -const toChecksumAddress = require('ethereumjs-util').toChecksumAddress +const { checksumAddress } = require('../app/util') const contractMap = require('eth-contract-metadata') module.exports = function (jazzicon) { @@ -16,7 +16,7 @@ function IconFactory (jazzicon) { } IconFactory.prototype.iconForAddress = function (address, diameter) { - const addr = toChecksumAddress(address) + const addr = checksumAddress(address) if (iconExistsFor(addr)) { return imageElFor(addr) } -- cgit From 2c8156ebe91941309d49e8f8f1ed8e9d740bb9de Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 18 Apr 2018 00:31:33 -0700 Subject: Fix UI getting stuck in Reveal Seed screen --- ui/app/actions.js | 16 ++++++++++++++-- ui/app/app.js | 14 +++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index f5cdd32bc..73335db97 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -351,7 +351,6 @@ function confirmSeedWords () { log.debug(`background.clearSeedWordCache`) return new Promise((resolve, reject) => { background.clearSeedWordCache((err, account) => { - dispatch(actions.hideLoadingIndication()) if (err) { dispatch(actions.displayWarning(err.message)) return reject(err) @@ -362,6 +361,9 @@ function confirmSeedWords () { resolve(account) }) }) + .then(() => dispatch(setIsRevealingSeedWords(false))) + .then(() => dispatch(actions.hideLoadingIndication())) + .catch(() => dispatch(actions.hideLoadingIndication())) } } @@ -446,11 +448,13 @@ function requestRevealSeed (password) { } dispatch(actions.showNewVaultSeed(result)) - dispatch(actions.hideLoadingIndication()) resolve() }) }) }) + .then(() => dispatch(setIsRevealingSeedWords(true))) + .then(() => dispatch(actions.hideLoadingIndication())) + .catch(() => dispatch(actions.hideLoadingIndication())) } } @@ -1907,3 +1911,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 827b4e9ce..1667e848d 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -56,11 +56,20 @@ const { class App extends Component { componentWillMount () { - const { currentCurrency, setCurrentCurrencyToUSD } = this.props + const { + currentCurrency, + setCurrentCurrencyToUSD, + isRevealingSeedWords, + clearSeedWords, + } = this.props if (!currentCurrency) { setCurrentCurrencyToUSD() } + + if (isRevealingSeedWords) { + clearSeedWords() + } } renderRoutes () { @@ -406,6 +415,8 @@ App.propTypes = { isMouseUser: PropTypes.bool, setMouseUserState: PropTypes.func, t: PropTypes.func, + isRevealingSeedWords: PropTypes.bool, + clearSeedWords: PropTypes.func, } function mapStateToProps (state) { @@ -486,6 +497,7 @@ function mapDispatchToProps (dispatch, ownProps) { setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)), + clearSeedWords: () => dispatch(actions.confirmSeedWords()), } } -- cgit From 1d80a4c452cc41b28e0a6a565b8fd48f0ff0252d Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 18 Apr 2018 16:10:42 -0700 Subject: Fix exception thrown when revealing seed words --- ui/app/actions.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) (limited to 'ui') diff --git a/ui/app/actions.js b/ui/app/actions.js index 73335db97..81d9c333b 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -345,10 +345,9 @@ function transitionBackward () { } } -function confirmSeedWords () { +function clearSeedWordCache () { + log.debug(`background.clearSeedWordCache`) return dispatch => { - dispatch(actions.showLoadingIndication()) - log.debug(`background.clearSeedWordCache`) return new Promise((resolve, reject) => { background.clearSeedWordCache((err, account) => { if (err) { @@ -361,9 +360,22 @@ function confirmSeedWords () { resolve(account) }) }) - .then(() => dispatch(setIsRevealingSeedWords(false))) - .then(() => dispatch(actions.hideLoadingIndication())) - .catch(() => dispatch(actions.hideLoadingIndication())) + } +} + +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 + }) } } -- cgit From 2fdf45b418133258377d9cfb46aae57954be5d29 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 18 Apr 2018 18:34:44 -0700 Subject: Show correct MetaMask version number in the Info page --- ui/app/components/pages/settings/info.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/app/components/pages/settings/info.js b/ui/app/components/pages/settings/info.js index eb9be66e6..bd9040499 100644 --- a/ui/app/components/pages/settings/info.js +++ b/ui/app/components/pages/settings/info.js @@ -3,6 +3,14 @@ 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', [ @@ -76,7 +84,7 @@ class Info extends Component { this.renderLogo(), h('div.settings__info-item', [ h('div.settings__info-version-header', 'MetaMask Version'), - h('div.settings__info-version-number', '4.0.0'), + h('div.settings__info-version-number', this.state.version), ]), h('div.settings__info-item', [ h( -- cgit