diff options
Diffstat (limited to 'ui/app/components')
30 files changed, 417 insertions, 254 deletions
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/balance-component.js b/ui/app/components/balance-component.js index d591ab455..e31552f2d 100644 --- a/ui/app/components/balance-component.js +++ b/ui/app/components/balance-component.js @@ -4,6 +4,8 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const TokenBalance = require('./token-balance') const Identicon = require('./identicon') +const currencyFormatter = require('currency-formatter') +const currencies = require('currency-formatter/currencies') const { formatBalance, generateBalanceObject } = require('../util') @@ -97,9 +99,17 @@ BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatS const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0 if (shouldNotRenderFiat) return null + const upperCaseFiatSuffix = fiatSuffix.toUpperCase() + + const display = currencies.find(currency => currency.code === upperCaseFiatSuffix) + ? currencyFormatter.format(Number(fiatDisplayNumber), { + code: upperCaseFiatSuffix, + }) + : `${fiatPrefix}${fiatDisplayNumber} ${upperCaseFiatSuffix}` + return h('div.fiat-amount', { style: {}, - }, `${fiatPrefix}${fiatDisplayNumber} ${fiatSuffix}`) + }, display) } BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) { @@ -117,5 +127,9 @@ BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, co const splitBalance = formattedBalance.split(' ') - return (Number(splitBalance[0]) * conversionRate).toFixed(2) + const convertedNumber = (Number(splitBalance[0]) * conversionRate) + const wholePart = Math.floor(convertedNumber) + const decimalPart = convertedNumber - wholePart + + return wholePart + Number(decimalPart.toPrecision(2)) } diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 9ac565cf4..fda7c3e17 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -9,7 +9,7 @@ const ShapeshiftForm = require('./shapeshift-form') const Loading = require('./loading') const AccountPanel = require('./account-panel') const RadioList = require('./custom-radio-list') -const networkNames = require('../../../app/scripts/config.js').networkNames +const { getNetworkDisplayName } = require('../../../app/scripts/controllers/network/util') BuyButtonSubview.contextTypes = { t: PropTypes.func, @@ -148,7 +148,7 @@ BuyButtonSubview.prototype.primarySubview = function () { case '3': case '4': case '42': - const networkName = networkNames[network] + const networkName = getNetworkDisplayName(network) const label = `${networkName} ${this.context.t('testFaucet')}` return ( h('div.flex-column', { diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index 4c693d1c3..1ff8eea87 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -280,8 +280,7 @@ CustomizeGasModal.prototype.render = function () { h(GasModalCard, { value: convertedGasPrice, min: forceGasMin || MIN_GAS_PRICE_GWEI, - // max: 1000, - step: multiplyCurrencies(MIN_GAS_PRICE_GWEI, 10), + step: 1, onChange: value => this.convertAndSetGasPrice(value), title: this.context.t('gasPrice'), copy: this.context.t('gasPriceCalculation'), @@ -290,7 +289,6 @@ CustomizeGasModal.prototype.render = function () { h(GasModalCard, { value: convertedGasLimit, min: 1, - // max: 100000, step: 1, onChange: value => this.convertAndSetGasLimit(value), title: this.context.t('gasLimit'), diff --git a/ui/app/components/dropdowns/components/account-dropdowns.js b/ui/app/components/dropdowns/components/account-dropdowns.js index a133f0e29..179b6617f 100644 --- a/ui/app/components/dropdowns/components/account-dropdowns.js +++ b/ui/app/components/dropdowns/components/account-dropdowns.js @@ -7,7 +7,7 @@ const connect = require('react-redux').connect const Dropdown = require('./dropdown').Dropdown const DropdownMenuItem = require('./dropdown').DropdownMenuItem const Identicon = require('../../identicon') -const ethUtil = require('ethereumjs-util') +const { checksumAddress } = require('../../../util') const copyToClipboard = require('copy-to-clipboard') const { formatBalance } = require('../../../util') @@ -311,8 +311,7 @@ class AccountDropdowns extends Component { closeMenu: () => {}, onClick: () => { const { selected } = this.props - const checkSumAddress = selected && ethUtil.toChecksumAddress(selected) - copyToClipboard(checkSumAddress) + copyToClipboard(checksumAddress(selected)) }, style: Object.assign( dropdownMenuItemStyle, diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js index 9e47f38ef..dcd6b4370 100644 --- a/ui/app/components/dropdowns/network-dropdown.js +++ b/ui/app/components/dropdowns/network-dropdown.js @@ -3,12 +3,14 @@ const PropTypes = require('prop-types') const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect +const { withRouter } = require('react-router-dom') +const { compose } = require('recompose') const actions = require('../../actions') const Dropdown = require('./components/dropdown').Dropdown const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem const NetworkDropdownIcon = require('./components/network-dropdown-icon') const R = require('ramda') - +const { SETTINGS_ROUTE } = require('../../routes') // classes from nodes of the toggle element. const notToggleElementClassnames = [ @@ -41,9 +43,6 @@ function mapDispatchToProps (dispatch) { setRpcTarget: (target) => { dispatch(actions.setRpcTarget(target)) }, - showConfigPage: () => { - dispatch(actions.showConfigPage()) - }, showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), } @@ -59,7 +58,10 @@ NetworkDropdown.contextTypes = { t: PropTypes.func, } -module.exports = connect(mapStateToProps, mapDispatchToProps)(NetworkDropdown) +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(NetworkDropdown) // TODO: specify default props and proptypes @@ -227,7 +229,7 @@ NetworkDropdown.prototype.render = function () { DropdownMenuItem, { closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => this.props.showConfigPage(), + onClick: () => this.props.history.push(SETTINGS_ROUTE), style: dropdownMenuItemStyle, }, [ diff --git a/ui/app/components/export-text-container/export-text-container.component.js b/ui/app/components/export-text-container/export-text-container.component.js new file mode 100644 index 000000000..c2546fa9b --- /dev/null +++ b/ui/app/components/export-text-container/export-text-container.component.js @@ -0,0 +1,45 @@ +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const copyToClipboard = require('copy-to-clipboard') +const { exportAsFile } = require('../../util') + +class ExportTextContainer extends Component { + render () { + const { text = '', filename = '' } = this.props + const { t } = this.context + + return ( + h('.export-text-container', [ + h('.export-text-container__text-container', [ + h('.export-text-container__text', text), + ]), + h('.export-text-container__buttons-container', [ + h('.export-text-container__button.export-text-container__button--copy', { + onClick: () => copyToClipboard(text), + }, [ + h('img', { src: 'images/copy-to-clipboard.svg' }), + h('.export-text-container__button-text', t('copyToClipboard')), + ]), + h('.export-text-container__button', { + onClick: () => exportAsFile(filename, text), + }, [ + h('img', { src: 'images/download.svg' }), + h('.export-text-container__button-text', t('saveAsCsvFile')), + ]), + ]), + ]) + ) + } +} + +ExportTextContainer.propTypes = { + text: PropTypes.string, + filename: PropTypes.string, +} + +ExportTextContainer.contextTypes = { + t: PropTypes.func, +} + +module.exports = ExportTextContainer diff --git a/ui/app/components/export-text-container/export-text-container.scss b/ui/app/components/export-text-container/export-text-container.scss new file mode 100644 index 000000000..a42de8233 --- /dev/null +++ b/ui/app/components/export-text-container/export-text-container.scss @@ -0,0 +1,52 @@ +.export-text-container { + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + border: 1px solid $alto; + border-radius: 4px; + font-weight: 400; + + &__text-container { + width: 100%; + display: flex; + justify-content: center; + padding: 20px; + border-radius: 4px; + background: $alabaster; + } + + &__text { + resize: none; + border: none; + background: $alabaster; + font-size: 20px; + text-align: center; + } + + &__buttons-container { + display: flex; + flex-direction: row; + border-top: 1px solid $alto; + width: 100%; + } + + &__button { + padding: 10px; + flex: 1; + display: flex; + justify-content: center; + align-items: center; + font-size: 14px; + cursor: pointer; + color: $curious-blue; + + &--copy { + border-right: 1px solid $alto; + } + } + + &__button-text { + padding-left: 10px; + } +} diff --git a/ui/app/components/export-text-container/index.js b/ui/app/components/export-text-container/index.js new file mode 100644 index 000000000..b2864a717 --- /dev/null +++ b/ui/app/components/export-text-container/index.js @@ -0,0 +1,2 @@ +const ExportTextContainer = require('./export-text-container.component') +module.exports = ExportTextContainer diff --git a/ui/app/components/loading.js b/ui/app/components/loading.js index cb6fa51fb..b9afc550f 100644 --- a/ui/app/components/loading.js +++ b/ui/app/components/loading.js @@ -1,6 +1,7 @@ const { Component } = require('react') const h = require('react-hyperscript') const PropTypes = require('prop-types') +const classnames = require('classnames') class LoadingIndicator extends Component { renderMessage () { @@ -10,14 +11,16 @@ class LoadingIndicator extends Component { render () { return ( - h('.full-flex-height.loading-overlay', {}, [ - h('img', { - src: 'images/loading.svg', - }), + h('.loading-overlay', { + className: classnames({ 'loading-overlay--full-screen': this.props.fullScreen }), + }, [ + h('.flex-center.flex-column', [ + h('img', { + src: 'images/loading.svg', + }), - h('br'), - - this.renderMessage(), + this.renderMessage(), + ]), ]) ) } @@ -25,6 +28,7 @@ class LoadingIndicator extends Component { LoadingIndicator.propTypes = { loadingMessage: PropTypes.string, + fullScreen: PropTypes.bool, } module.exports = LoadingIndicator diff --git a/ui/app/components/modals/buy-options-modal.js b/ui/app/components/modals/buy-options-modal.js index d871e7516..c70510b5f 100644 --- a/ui/app/components/modals/buy-options-modal.js +++ b/ui/app/components/modals/buy-options-modal.js @@ -4,7 +4,7 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../actions') -const networkNames = require('../../../../app/scripts/config.js').networkNames +const { getNetworkDisplayName } = require('../../../../app/scripts/controllers/network/util') function mapStateToProps (state) { return { @@ -52,7 +52,7 @@ BuyOptions.prototype.renderModalContentOption = function (title, header, onClick BuyOptions.prototype.render = function () { const { network, toCoinbase, address, toFaucet } = this.props const isTestNetwork = ['3', '4', '42'].find(n => n === network) - const networkName = networkNames[network] + const networkName = getNetworkDisplayName(network) return h('div', {}, [ h('div.buy-modal-content.transfers-subview', { diff --git a/ui/app/components/modals/deposit-ether-modal.js b/ui/app/components/modals/deposit-ether-modal.js index 0dc611f50..ad5f9b695 100644 --- a/ui/app/components/modals/deposit-ether-modal.js +++ b/ui/app/components/modals/deposit-ether-modal.js @@ -4,7 +4,7 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../actions') -const networkNames = require('../../../../app/scripts/config.js').networkNames +const { getNetworkDisplayName } = require('../../../../app/scripts/controllers/network/util') const ShapeshiftForm = require('../shapeshift-form') let DIRECT_DEPOSIT_ROW_TITLE @@ -122,7 +122,7 @@ DepositEtherModal.prototype.render = function () { const { buyingWithShapeshift } = this.state const isTestNetwork = ['3', '4', '42'].find(n => n === network) - const networkName = networkNames[network] + const networkName = getNetworkDisplayName(network) return h('div.page-container.page-container--full-width.page-container--full-height', {}, [ diff --git a/ui/app/components/modals/export-private-key-modal.js b/ui/app/components/modals/export-private-key-modal.js index 1f80aed39..447e43b7a 100644 --- a/ui/app/components/modals/export-private-key-modal.js +++ b/ui/app/components/modals/export-private-key-modal.js @@ -3,12 +3,13 @@ const PropTypes = require('prop-types') const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect -const ethUtil = require('ethereumjs-util') +const { stripHexPrefix } = require('ethereumjs-util') const actions = require('../../actions') const AccountModalContainer = require('./account-modal-container') const { getSelectedIdentity } = require('../../selectors') const ReadOnlyInput = require('../readonly-input') const copyToClipboard = require('copy-to-clipboard') +const { checksumAddress } = require('../../util') function mapStateToProps (state) { return { @@ -60,7 +61,7 @@ ExportPrivateKeyModal.prototype.renderPasswordLabel = function (privateKey) { } ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) { - const plainKey = privateKey && ethUtil.stripHexPrefix(privateKey) + const plainKey = privateKey && stripHexPrefix(privateKey) return privateKey ? h(ReadOnlyInput, { @@ -121,7 +122,7 @@ ExportPrivateKeyModal.prototype.render = function () { h(ReadOnlyInput, { wrapperClass: 'ellip-address-wrapper', inputClass: 'qr-ellip-address ellip-address', - value: address, + value: checksumAddress(address), }), h('div.account-modal-divider'), diff --git a/ui/app/components/pages/add-token.js b/ui/app/components/pages/add-token.js index 566e42450..8d52571d0 100644 --- a/ui/app/components/pages/add-token.js +++ b/ui/app/components/pages/add-token.js @@ -192,7 +192,7 @@ AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) if (symbol && decimals) { this.setState({ customSymbol: symbol, - customDecimals: decimals.toString(), + customDecimals: decimals, autoFilled: true, }) } diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js index 90b8e1d37..9110f8202 100644 --- a/ui/app/components/pages/home.js +++ b/ui/app/components/pages/home.js @@ -21,7 +21,7 @@ const QrView = require('../../components/qr-code') // Routes const { - REVEAL_SEED_ROUTE, + INITIALIZE_BACKUP_PHRASE_ROUTE, RESTORE_VAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE, NOTICE_ROUTE, @@ -69,7 +69,7 @@ class Home extends Component { log.debug('rendering seed words') return h(Redirect, { to: { - pathname: REVEAL_SEED_ROUTE, + pathname: INITIALIZE_BACKUP_PHRASE_ROUTE, }, }) } diff --git a/ui/app/components/pages/keychains/reveal-seed.js b/ui/app/components/pages/keychains/reveal-seed.js index 247f3c8e2..685c81074 100644 --- a/ui/app/components/pages/keychains/reveal-seed.js +++ b/ui/app/components/pages/keychains/reveal-seed.js @@ -2,11 +2,27 @@ 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 classnames = require('classnames') + +const { requestRevealSeedWords } = require('../../../actions') const { DEFAULT_ROUTE } = require('../../../routes') +const ExportTextContainer = require('../../export-text-container') + +const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN' +const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN' class RevealSeedPage extends Component { + constructor (props) { + super(props) + + this.state = { + screen: PASSWORD_PROMPT_SCREEN, + password: '', + seedWords: null, + error: null, + } + } + componentDidMount () { const passwordBox = document.getElementById('password-box') if (passwordBox) { @@ -14,182 +30,135 @@ class RevealSeedPage extends Component { } } - checkConfirmation (event) { - if (event.key === 'Enter') { - event.preventDefault() - this.revealSeedWords() - } + handleSubmit (event) { + event.preventDefault() + this.setState({ seedWords: null, error: null }) + this.props.requestRevealSeedWords(this.state.password) + .then(seedWords => this.setState({ seedWords, screen: REVEAL_SEED_SCREEN })) + .catch(error => this.setState({ error: error.message })) } - revealSeedWords () { - const password = document.getElementById('password-box').value - this.props.requestRevealSeed(password) - } - - renderSeed () { - const { seedWords, confirmSeedWords, history } = this.props - + renderWarning () { 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('.page-container__warning-container', [ + h('img.page-container__warning-icon', { + src: 'images/warning.svg', }), - - 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'), + h('.page-container__warning-message', [ + h('.page-container__warning-title', [this.context.t('revealSeedWordsWarningTitle')]), + h('div', [this.context.t('revealSeedWordsWarning')]), + ]), ]) ) } - renderConfirmation () { - const { history, warning, inProgress } = this.props + renderContent () { + return this.state.screen === PASSWORD_PROMPT_SCREEN + ? this.renderPasswordPromptContent() + : this.renderRevealSeedContent() + } + + renderPasswordPromptContent () { + const { t } = this.context return ( - h('.initialize-screen.flex-column.flex-center.flex-grow', { - style: { maxWidth: '420px' }, + h('form', { + onSubmit: event => this.handleSubmit(event), }, [ - - 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', { + h('label.input-label', { + htmlFor: 'password-box', + }, t('enterPasswordContinue')), + h('.input-group', [ + h('input.form-control', { type: 'password', + placeholder: t('password'), id: 'password-box', - placeholder: 'Enter your password to confirm', - onKeyPress: this.checkConfirmation.bind(this), - style: { - width: 260, - marginTop: '12px', - }, + value: this.state.password, + onChange: event => this.setState({ password: event.target.value }), + className: classnames({ 'form-control--error': this.state.error }), }), + ]), + this.state.error && h('.reveal-seed__error', this.state.error), + ]) + ) + } - h('.flex-row.flex-start', { - style: { - marginTop: 30, - width: '50%', - }, - }, [ - // cancel - h('button.primary', { - onClick: () => history.push(DEFAULT_ROUTE), - }, 'CANCEL'), - - // submit - h('button.primary', { - style: { marginLeft: '10px' }, - onClick: this.revealSeedWords.bind(this), - }, 'OK'), + renderRevealSeedContent () { + const { t } = this.context - ]), + return ( + h('div', [ + h('label.reveal-seed__label', t('yourPrivateSeedPhrase')), + h(ExportTextContainer, { + text: this.state.seedWords, + filename: t('metamaskSeedWords'), + }), + ]) + ) + } - warning && ( - h('span.error', { - style: { - margin: '20px', - }, - }, warning.split('-')) - ), - - inProgress && ( - h('span.in-progress-notification', 'Generating Seed...') - ), - ]), + renderFooter () { + return this.state.screen === PASSWORD_PROMPT_SCREEN + ? this.renderPasswordPromptFooter() + : this.renderRevealSeedFooter() + } + + renderPasswordPromptFooter () { + return ( + h('.page-container__footer', [ + h('button.btn-secondary--lg.page-container__footer-button', { + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('cancel')), + h('button.btn-primary--lg.page-container__footer-button', { + onClick: event => this.handleSubmit(event), + disabled: this.state.password === '', + }, this.context.t('next')), + ]) + ) + } + + renderRevealSeedFooter () { + return ( + h('.page-container__footer', [ + h('button.btn-secondary--lg.page-container__footer-button', { + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('close')), ]) ) } render () { - return this.props.seedWords - ? this.renderSeed() - : this.renderConfirmation() + return ( + h('.page-container', [ + h('.page-container__header', [ + h('.page-container__title', this.context.t('revealSeedWordsTitle')), + h('.page-container__subtitle', this.context.t('revealSeedWordsDescription')), + ]), + h('.page-container__content', [ + this.renderWarning(), + h('.reveal-seed__content', [ + this.renderContent(), + ]), + ]), + this.renderFooter(), + ]) + ) } } RevealSeedPage.propTypes = { - requestRevealSeed: PropTypes.func, - confirmSeedWords: PropTypes.func, - seedWords: PropTypes.string, - inProgress: PropTypes.bool, + requestRevealSeedWords: PropTypes.func, history: PropTypes.object, - warning: PropTypes.string, } -const mapStateToProps = state => { - const { appState: { warning }, metamask: { seedWords } } = state - - return { - warning, - seedWords, - } +RevealSeedPage.contextTypes = { + t: PropTypes.func, } const mapDispatchToProps = dispatch => { return { - requestRevealSeed: password => dispatch(requestRevealSeed(password)), - confirmSeedWords: () => dispatch(confirmSeedWords()), + requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)), } } -module.exports = connect(mapStateToProps, mapDispatchToProps)(RevealSeedPage) +module.exports = connect(null, mapDispatchToProps)(RevealSeedPage) 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( diff --git a/ui/app/components/pages/settings/settings.js b/ui/app/components/pages/settings/settings.js index 05a7379fb..bdefe56f8 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 locales = require('../../../../../app/_locales/index.json') -const { OLD_UI_NETWORK_TYPE } = require('../../../../../app/scripts/config').enums +const { OLD_UI_NETWORK_TYPE } = require('../../../../../app/scripts/controllers/network/enums') const getInfuraCurrencyOptions = () => { const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { diff --git a/ui/app/components/pages/unlock.js b/ui/app/components/pages/unlock.js index 567b72518..30144b978 100644 --- a/ui/app/components/pages/unlock.js +++ b/ui/app/components/pages/unlock.js @@ -16,7 +16,7 @@ const { getEnvironmentType } = require('../../../../app/scripts/lib/util') const getCaretCoordinates = require('textarea-caret') const EventEmitter = require('events').EventEmitter const Mascot = require('../mascot') -const { OLD_UI_NETWORK_TYPE } = require('../../../../app/scripts/config').enums +const { OLD_UI_NETWORK_TYPE } = require('../../../../app/scripts/controllers/network/enums') const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../routes') class UnlockScreen extends Component { diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index 3ee614a2a..16dbd273b 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -23,6 +23,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') const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') @@ -275,6 +277,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.editTransaction = function (txMeta) { const { editTransaction, history } = this.props editTransaction(txMeta) @@ -319,6 +331,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 @@ -365,7 +380,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}"` : '' ]), @@ -412,7 +427,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 37130a1cc..656093b3d 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -27,6 +27,8 @@ const { calcTokenAmount, } = require('../../token-util') const classnames = require('classnames') +const currencyFormatter = require('currency-formatter') +const currencies = require('currency-formatter/currencies') const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') @@ -318,10 +320,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}"` : '' ]), @@ -369,6 +373,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 ', [ @@ -378,7 +385,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}`), ]), ]) @@ -413,6 +420,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 txMeta = this.gatherTxMeta() const { diff --git a/ui/app/components/pending-tx/index.js b/ui/app/components/pending-tx/index.js index acdd99364..fb409cb92 100644 --- a/ui/app/components/pending-tx/index.js +++ b/ui/app/components/pending-tx/index.js @@ -1,16 +1,18 @@ 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') abiDecoder.addABI(abi) const inherits = require('util').inherits const actions = require('../../actions') -const util = require('../../util') +const { getSymbolAndDecimals } = require('../../token-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', @@ -24,6 +26,7 @@ function mapStateToProps (state) { const { conversionRate, identities, + tokens: existingTokens, } = state.metamask const accounts = state.metamask.accounts const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0] @@ -31,6 +34,7 @@ function mapStateToProps (state) { conversionRate, identities, selectedAddress, + existingTokens, } } @@ -53,10 +57,25 @@ 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 { existingTokens } = this.props const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} + if (txMeta.loadingDefaults) { + return + } + if (!txParams.to) { return this.setState({ transactionType: TX_TYPES.DEPLOY_CONTRACT, @@ -73,30 +92,15 @@ PendingTx.prototype.componentWillMount = async function () { } if (isTokenTransaction) { - const token = util.getContractAtAddress(txParams.to) - const results = await Promise.all([ - token.symbol(), - token.decimals(), - ]) - const [ symbol, decimals ] = results - - if (symbol[0] && decimals[0]) { - this.setState({ - transactionType: TX_TYPES.SEND_TOKEN, - tokenAddress: txParams.to, - tokenSymbol: symbol[0], - tokenDecimals: decimals[0], - isFetching: false, - }) - } else { - this.setState({ - transactionType: TX_TYPES.SEND_TOKEN, - tokenAddress: txParams.to, - tokenSymbol: null, - tokenDecimals: null, - isFetching: false, - }) - } + const { symbol, decimals } = await getSymbolAndDecimals(txParams.to, existingTokens) + + this.setState({ + transactionType: TX_TYPES.SEND_TOKEN, + tokenAddress: txParams.to, + tokenSymbol: symbol, + tokenDecimals: decimals, + isFetching: false, + }) } else { this.setState({ transactionType: TX_TYPES.SEND_ETHER, @@ -125,7 +129,10 @@ PendingTx.prototype.render = function () { const { sendTransaction } = this.props if (isFetching) { - return h('noscript') + return h(Loading, { + fullScreen: true, + loadingMessage: this.context.t('generatingTransaction'), + }) } switch (transactionType) { @@ -150,6 +157,12 @@ PendingTx.prototype.render = function () { sendTransaction, }) default: - return h('noscript') + return h(Loading, { + fullScreen: true, + }) } } + +PendingTx.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/qr-code.js b/ui/app/components/qr-code.js index 83885539c..3b2c62f49 100644 --- a/ui/app/components/qr-code.js +++ b/ui/app/components/qr-code.js @@ -3,8 +3,9 @@ const h = require('react-hyperscript') const qrCode = require('qrcode-npm').qrcode const inherits = require('util').inherits const connect = require('react-redux').connect -const isHexPrefixed = require('ethereumjs-util').isHexPrefixed +const { isHexPrefixed } = require('ethereumjs-util') const ReadOnlyInput = require('./readonly-input') +const { checksumAddress } = require('../util') module.exports = connect(mapStateToProps)(QrCodeView) @@ -24,16 +25,16 @@ function QrCodeView () { QrCodeView.prototype.render = function () { const props = this.props - const Qr = props.Qr - const address = `${isHexPrefixed(Qr.data) ? 'ethereum:' : ''}${Qr.data}` + const { message, data } = props.Qr + const address = `${isHexPrefixed(data) ? 'ethereum:' : ''}${data}` const qrImage = qrCode(4, 'M') qrImage.addData(address) qrImage.make() return h('.div.flex-column.flex-center', [ - Array.isArray(Qr.message) + Array.isArray(message) ? h('.message-container', this.renderMultiMessage()) - : Qr.message && h('.qr-header', Qr.message), + : message && h('.qr-header', message), this.props.warning ? this.props.warning && h('span.error.flex-center', { style: { @@ -50,7 +51,7 @@ QrCodeView.prototype.render = function () { h(ReadOnlyInput, { wrapperClass: 'ellip-address-wrapper', inputClass: 'qr-ellip-address', - value: Qr.data, + value: checksumAddress(data), }), ]) } diff --git a/ui/app/components/send/account-list-item.js b/ui/app/components/send/account-list-item.js index 1ad3f69c1..b5e604a6e 100644 --- a/ui/app/components/send/account-list-item.js +++ b/ui/app/components/send/account-list-item.js @@ -2,6 +2,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect +const { checksumAddress } = require('../../util') const Identicon = require('../identicon') const CurrencyDisplay = require('./currency-display') const { conversionRateSelector, getCurrentCurrency } = require('../../selectors') @@ -56,7 +57,7 @@ AccountListItem.prototype.render = function () { ]), - displayAddress && name && h('div.account-list-item__account-address', address), + displayAddress && name && h('div.account-list-item__account-address', checksumAddress(address)), displayBalance && h(CurrencyDisplay, { primaryCurrency: 'ETH', diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index 819fee0a0..90fb2b66c 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, @@ -67,37 +89,31 @@ CurrencyDisplay.prototype.render = function () { } = this.props 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, style: { borderColor: inError ? 'red' : null, }, - onClick: () => this.currencyInput.focus(), + onClick: () => this.currencyInput && this.currencyInput.focus(), }, [ h('div.currency-display__primary-row', [ h('div.currency-display__input-wrapper', [ - h(CurrencyInput, { + h(readOnly ? 'input' : CurrencyInput, { className: primaryBalanceClassName, value: `${valueToRender}`, placeholder: '0', readOnly, - onInputChange: newValue => { - handleChange(this.getAmount(newValue)) - }, - inputRef: input => { this.currencyInput = input }, + ...(!readOnly ? { + onInputChange: newValue => { + handleChange(this.getAmount(newValue)) + }, + inputRef: input => { this.currencyInput = input }, + } : {}), }), h('span.currency-display__currency-symbol', primaryCurrency), @@ -108,7 +124,7 @@ CurrencyDisplay.prototype.render = function () { h('div', { className: convertedBalanceClassName, - }, `${convertedValue} ${convertedCurrency.toUpperCase()}`), + }, `${convertedValueToRender} ${convertedCurrency.toUpperCase()}`), ]) diff --git a/ui/app/components/send/send-constants.js b/ui/app/components/send/send-constants.js index b3ee0899a..5d89c74aa 100644 --- a/ui/app/components/send/send-constants.js +++ b/ui/app/components/send/send-constants.js @@ -1,8 +1,8 @@ const ethUtil = require('ethereumjs-util') const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') -const MIN_GAS_PRICE_HEX = (100000000).toString(16) -const MIN_GAS_PRICE_DEC = '100000000' +const MIN_GAS_PRICE_DEC = '0' +const MIN_GAS_PRICE_HEX = (parseInt(MIN_GAS_PRICE_DEC)).toString(16) const MIN_GAS_LIMIT_DEC = '21000' const MIN_GAS_LIMIT_HEX = (parseInt(MIN_GAS_LIMIT_DEC)).toString(16) diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index fd4a80a4a..22ab64426 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -55,6 +55,10 @@ function ShapeshiftForm () { } } +ShapeshiftForm.prototype.getCoinPair = function () { + return `${this.state.depositCoin.toUpperCase()}_ETH` +} + ShapeshiftForm.prototype.componentWillMount = function () { this.props.shapeShiftSubview() } @@ -120,14 +124,12 @@ ShapeshiftForm.prototype.renderMetadata = function (label, value) { } ShapeshiftForm.prototype.renderMarketInfo = function () { - const { depositCoin } = this.state - const coinPair = `${depositCoin}_eth` const { tokenExchangeRates } = this.props const { limit, rate, minimum, - } = tokenExchangeRates[coinPair] || {} + } = tokenExchangeRates[this.getCoinPair()] || {} return h('div.shapeshift-form__metadata', {}, [ @@ -172,10 +174,9 @@ ShapeshiftForm.prototype.renderQrCode = function () { ShapeshiftForm.prototype.render = function () { const { coinOptions, btnClass, warning } = this.props - const { depositCoin, errorMessage, showQrCode, depositAddress } = this.state - const coinPair = `${depositCoin}_eth` + const { errorMessage, showQrCode, depositAddress } = this.state const { tokenExchangeRates } = this.props - const token = tokenExchangeRates[coinPair] + const token = tokenExchangeRates[this.getCoinPair()] return h('div.shapeshift-form-wrapper', [ showQrCode 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' } }), ]), ]), |