From 6f749e5576cb981a469d0cf969b15901bcc05cfa Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Fri, 16 Mar 2018 13:09:49 -0700 Subject: Update Confirm Contract screen (#3597) --- ui/app/components/modals/deposit-ether-modal.js | 2 +- .../pending-tx/confirm-deploy-contract.js | 587 +++++++++++---------- ui/app/components/sender-to-recipient.js | 45 ++ 3 files changed, 340 insertions(+), 294 deletions(-) create mode 100644 ui/app/components/sender-to-recipient.js (limited to 'ui/app/components') diff --git a/ui/app/components/modals/deposit-ether-modal.js b/ui/app/components/modals/deposit-ether-modal.js index 23e8666af..b642513d7 100644 --- a/ui/app/components/modals/deposit-ether-modal.js +++ b/ui/app/components/modals/deposit-ether-modal.js @@ -109,7 +109,7 @@ DepositEtherModal.prototype.render = function () { const isTestNetwork = ['3', '4', '42'].find(n => n === network) const networkName = networkNames[network] - return h('div.page-container.page-container--full-width', {}, [ + return h('div.page-container.page-container--full-width.page-container--full-height', {}, [ h('div.page-container__header', [ diff --git a/ui/app/components/pending-tx/confirm-deploy-contract.js b/ui/app/components/pending-tx/confirm-deploy-contract.js index 49fbe6387..e7c4c536b 100644 --- a/ui/app/components/pending-tx/confirm-deploy-contract.js +++ b/ui/app/components/pending-tx/confirm-deploy-contract.js @@ -1,349 +1,350 @@ -const Component = require('react').Component +const { Component } = require('react') const { connect } = require('react-redux') const h = require('react-hyperscript') -const inherits = require('util').inherits +const PropTypes = require('prop-types') const actions = require('../../actions') const clone = require('clone') -const Identicon = require('../identicon') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN const hexToBn = require('../../../../app/scripts/lib/hex-to-bn') const { conversionUtil } = require('../../conversion-util') const t = require('../../../i18n') +const SenderToRecipient = require('../sender-to-recipient') const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') +class ConfirmDeployContract extends Component { + constructor (props) { + super(props) -module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmDeployContract) - -function mapStateToProps (state) { - const { - conversionRate, - identities, - currentCurrency, - } = state.metamask - const accounts = state.metamask.accounts - const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0] - return { - currentCurrency, - conversionRate, - identities, - selectedAddress, + this.state = { + valid: false, + submitting: false, + } } -} -function mapDispatchToProps (dispatch) { - return { - backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)), - cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })), + onSubmit (event) { + event.preventDefault() + const txMeta = this.gatherTxMeta() + const valid = this.checkValidity() + this.setState({ valid, submitting: true }) + + if (valid && this.verifyGasParams()) { + this.props.sendTransaction(txMeta, event) + } else { + this.props.displayWarning('invalidGasParams') + this.setState({ submitting: false }) + } } -} - -inherits(ConfirmDeployContract, Component) -function ConfirmDeployContract () { - Component.call(this) - this.state = {} - this.onSubmit = this.onSubmit.bind(this) -} + cancel (event, txMeta) { + event.preventDefault() + this.props.cancelTransaction(txMeta) + } -ConfirmDeployContract.prototype.onSubmit = function (event) { - event.preventDefault() - const txMeta = this.gatherTxMeta() - const valid = this.checkValidity() - this.setState({ valid, submitting: true }) - - if (valid && this.verifyGasParams()) { - this.props.sendTransaction(txMeta, event) - } else { - this.props.dispatch(actions.displayWarning(t('invalidGasParams'))) - this.setState({ submitting: false }) + checkValidity () { + const form = this.getFormEl() + const valid = form.checkValidity() + return valid } -} -ConfirmDeployContract.prototype.cancel = function (event, txMeta) { - event.preventDefault() - this.props.cancelTransaction(txMeta) -} + getFormEl () { + const form = document.querySelector('form#pending-tx-form') + // Stub out form for unit tests: + if (!form) { + return { checkValidity () { return true } } + } + return form + } -ConfirmDeployContract.prototype.checkValidity = function () { - const form = this.getFormEl() - const valid = form.checkValidity() - return valid -} + // After a customizable state value has been updated, + gatherTxMeta () { + const props = this.props + const state = this.state + const txData = clone(state.txData) || clone(props.txData) -ConfirmDeployContract.prototype.getFormEl = function () { - const form = document.querySelector('form#pending-tx-form') - // Stub out form for unit tests: - if (!form) { - return { checkValidity () { return true } } + // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) + return txData } - return form -} - -// After a customizable state value has been updated, -ConfirmDeployContract.prototype.gatherTxMeta = function () { - const props = this.props - const state = this.state - const txData = clone(state.txData) || clone(props.txData) - // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) - return txData -} + verifyGasParams () { + // We call this in case the gas has not been modified at all + if (!this.state) { return true } + return ( + this._notZeroOrEmptyString(this.state.gas) && + this._notZeroOrEmptyString(this.state.gasPrice) + ) + } -ConfirmDeployContract.prototype.verifyGasParams = function () { - // We call this in case the gas has not been modified at all - if (!this.state) { return true } - return ( - this._notZeroOrEmptyString(this.state.gas) && - this._notZeroOrEmptyString(this.state.gasPrice) - ) -} + _notZeroOrEmptyString (obj) { + return obj !== '' && obj !== '0x0' + } -ConfirmDeployContract.prototype._notZeroOrEmptyString = function (obj) { - return obj !== '' && obj !== '0x0' -} + bnMultiplyByFraction (targetBN, numerator, denominator) { + const numBN = new BN(numerator) + const denomBN = new BN(denominator) + return targetBN.mul(numBN).div(denomBN) + } -ConfirmDeployContract.prototype.bnMultiplyByFraction = function (targetBN, numerator, denominator) { - const numBN = new BN(numerator) - const denomBN = new BN(denominator) - return targetBN.mul(numBN).div(denomBN) -} + getData () { + const { identities } = this.props + const txMeta = this.gatherTxMeta() + const txParams = txMeta.txParams || {} + + return { + from: { + address: txParams.from, + name: identities[txParams.from].name, + }, + memo: txParams.memo || '', + } + } -ConfirmDeployContract.prototype.getData = function () { - const { identities } = this.props - const txMeta = this.gatherTxMeta() - const txParams = txMeta.txParams || {} + getAmount () { + const { conversionRate, currentCurrency } = this.props + const txMeta = this.gatherTxMeta() + const txParams = txMeta.txParams || {} + + const FIAT = conversionUtil(txParams.value, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + fromCurrency: 'ETH', + toCurrency: currentCurrency, + numberOfDecimals: 2, + fromDenomination: 'WEI', + conversionRate, + }) + const ETH = conversionUtil(txParams.value, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + fromCurrency: 'ETH', + toCurrency: 'ETH', + fromDenomination: 'WEI', + conversionRate, + numberOfDecimals: 6, + }) + + return { + fiat: Number(FIAT), + token: Number(ETH), + } - return { - from: { - address: txParams.from, - name: identities[txParams.from].name, - }, - memo: txParams.memo || '', } -} - -ConfirmDeployContract.prototype.getAmount = function () { - const { conversionRate, currentCurrency } = this.props - const txMeta = this.gatherTxMeta() - const txParams = txMeta.txParams || {} - - const FIAT = conversionUtil(txParams.value, { - fromNumericBase: 'hex', - toNumericBase: 'dec', - fromCurrency: 'ETH', - toCurrency: currentCurrency, - numberOfDecimals: 2, - fromDenomination: 'WEI', - conversionRate, - }) - const ETH = conversionUtil(txParams.value, { - fromNumericBase: 'hex', - toNumericBase: 'dec', - fromCurrency: 'ETH', - toCurrency: 'ETH', - fromDenomination: 'WEI', - conversionRate, - numberOfDecimals: 6, - }) - return { - fiat: Number(FIAT), - token: Number(ETH), + getGasFee () { + const { conversionRate, currentCurrency } = this.props + const txMeta = this.gatherTxMeta() + const txParams = txMeta.txParams || {} + + // Gas + const gas = txParams.gas + const gasBn = hexToBn(gas) + + // Gas Price + const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_HEX + const gasPriceBn = hexToBn(gasPrice) + + const txFeeBn = gasBn.mul(gasPriceBn) + + const FIAT = conversionUtil(txFeeBn, { + fromNumericBase: 'BN', + toNumericBase: 'dec', + fromDenomination: 'WEI', + fromCurrency: 'ETH', + toCurrency: currentCurrency, + numberOfDecimals: 2, + conversionRate, + }) + const ETH = conversionUtil(txFeeBn, { + fromNumericBase: 'BN', + toNumericBase: 'dec', + fromDenomination: 'WEI', + fromCurrency: 'ETH', + toCurrency: 'ETH', + numberOfDecimals: 6, + conversionRate, + }) + + return { + fiat: Number(FIAT), + eth: Number(ETH), + } } -} + renderGasFee () { + const { currentCurrency } = this.props + const { fiat: fiatGas, eth: ethGas } = this.getGasFee() -ConfirmDeployContract.prototype.getGasFee = function () { - const { conversionRate, currentCurrency } = this.props - const txMeta = this.gatherTxMeta() - const txParams = txMeta.txParams || {} + return ( + h('section.flex-row.flex-center.confirm-screen-row', [ + h('span.confirm-screen-label.confirm-screen-section-column', [ t('gasFee') ]), + h('div.confirm-screen-section-column', [ + h('div.confirm-screen-row-info', `${fiatGas} ${currentCurrency.toUpperCase()}`), - // Gas - const gas = txParams.gas - const gasBn = hexToBn(gas) + h( + 'div.confirm-screen-row-detail', + `${ethGas} ETH` + ), + ]), + ]) + ) + } - // Gas Price - const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_HEX - const gasPriceBn = hexToBn(gasPrice) + renderHeroAmount () { + const { currentCurrency } = this.props + const { fiat: fiatAmount } = this.getAmount() + const txMeta = this.gatherTxMeta() + const txParams = txMeta.txParams || {} + const { memo = '' } = txParams + + return ( + 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-currency', currentCurrency.toUpperCase()), + h('div.flex-center.confirm-memo-wrapper', [ + h('h3.confirm-screen-send-memo', memo), + ]), + ]) + ) + } - const txFeeBn = gasBn.mul(gasPriceBn) + renderTotalPlusGas () { + const { currentCurrency } = this.props + const { fiat: fiatAmount, token: tokenAmount } = this.getAmount() + const { fiat: fiatGas, eth: ethGas } = this.getGasFee() - const FIAT = conversionUtil(txFeeBn, { - fromNumericBase: 'BN', - toNumericBase: 'dec', - fromDenomination: 'WEI', - fromCurrency: 'ETH', - toCurrency: currentCurrency, - numberOfDecimals: 2, - conversionRate, - }) - const ETH = conversionUtil(txFeeBn, { - fromNumericBase: 'BN', - toNumericBase: 'dec', - fromDenomination: 'WEI', - fromCurrency: 'ETH', - toCurrency: 'ETH', - numberOfDecimals: 6, - conversionRate, - }) + return ( + h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [ + h('div.confirm-screen-section-column', [ + h('span.confirm-screen-label', [ t('total') + ' ' ]), + h('div.confirm-screen-total-box__subtitle', [ t('amountPlusGas') ]), + ]), - return { - fiat: Number(FIAT), - eth: Number(ETH), + h('div.confirm-screen-section-column', [ + h('div.confirm-screen-row-info', `${fiatAmount + fiatGas} ${currentCurrency.toUpperCase()}`), + h('div.confirm-screen-row-detail', `${tokenAmount + ethGas} ETH`), + ]), + ]) + ) } -} - -ConfirmDeployContract.prototype.renderGasFee = function () { - const { currentCurrency } = this.props - const { fiat: fiatGas, eth: ethGas } = this.getGasFee() - - return ( - h('section.flex-row.flex-center.confirm-screen-row', [ - h('span.confirm-screen-label.confirm-screen-section-column', [ t('gasFee') ]), - h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', `${fiatGas} ${currentCurrency.toUpperCase()}`), - - h( - 'div.confirm-screen-row-detail', - `${ethGas} ETH` - ), - ]), - ]) - ) -} -ConfirmDeployContract.prototype.renderHeroAmount = function () { - const { currentCurrency } = this.props - const { fiat: fiatAmount } = this.getAmount() - const txMeta = this.gatherTxMeta() - const txParams = txMeta.txParams || {} - const { memo = '' } = txParams - - return ( - 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-currency', currentCurrency.toUpperCase()), - h('div.flex-center.confirm-memo-wrapper', [ - h('h3.confirm-screen-send-memo', memo), - ]), - ]) - ) -} + render () { + const { backToAccountDetail, selectedAddress } = this.props + const txMeta = this.gatherTxMeta() -ConfirmDeployContract.prototype.renderTotalPlusGas = function () { - const { currentCurrency } = this.props - const { fiat: fiatAmount, token: tokenAmount } = this.getAmount() - const { fiat: fiatGas, eth: ethGas } = this.getGasFee() - - return ( - h('section.flex-row.flex-center.confirm-screen-total-box ', [ - h('div.confirm-screen-section-column', [ - h('span.confirm-screen-label', [ t('total') + ' ' ]), - h('div.confirm-screen-total-box__subtitle', [ t('amountPlusGas') ]), - ]), - - h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', `${fiatAmount + fiatGas} ${currentCurrency.toUpperCase()}`), - h('div.confirm-screen-row-detail', `${tokenAmount + ethGas} ETH`), - ]), - ]) - ) -} + const { + from: { + address: fromAddress, + name: fromName, + }, + } = this.getData() -ConfirmDeployContract.prototype.render = function () { - const { backToAccountDetail, selectedAddress } = this.props - const txMeta = this.gatherTxMeta() + this.inputs = [] - const { - from: { - address: fromAddress, - name: fromName, - }, - } = this.getData() - - this.inputs = [] - - return ( - h('div.flex-column.flex-grow.confirm-screen-container', { - style: { minWidth: '355px' }, - }, [ - // Main Send token Card - h('div.confirm-screen-wrapper.flex-column.flex-grow', [ - h('h3.flex-center.confirm-screen-header', [ - h('button.confirm-screen-back-button.allcaps', { + return ( + h('.page-container', [ + h('.page-container__header', [ + h('.page-container__back-button', { onClick: () => backToAccountDetail(selectedAddress), }, t('back')), - h('div.confirm-screen-title', t('confirmContract')), - h('div.confirm-screen-header-tip'), - ]), - h('div.flex-row.flex-center.confirm-screen-identicons', [ - h('div.confirm-screen-account-wrapper', [ - h( - Identicon, - { - address: fromAddress, - diameter: 60, - }, - ), - h('span.confirm-screen-account-name', fromName), - // h('span.confirm-screen-account-number', fromAddress.slice(fromAddress.length - 4)), - ]), - h('i.fa.fa-arrow-right.fa-lg'), - h('div.confirm-screen-account-wrapper', [ - h('i.fa.fa-file-text-o'), - h('span.confirm-screen-account-name', t('newContract')), - h('span.confirm-screen-account-number', ' '), - ]), + h('.page-container__title', t('confirmContract')), + h('.page-container__subtitle', t('pleaseReviewTransaction')), ]), + // Main Send token Card + h('.page-container__content', [ + + h(SenderToRecipient, { + senderName: fromName, + senderAddress: fromAddress, + }), + + // h('h3.flex-center.confirm-screen-sending-to-message', { + // style: { + // textAlign: 'center', + // fontSize: '16px', + // }, + // }, [ + // `You're deploying a new contract.`, + // ]), + + this.renderHeroAmount(), + + h('div.confirm-screen-rows', [ + h('section.flex-row.flex-center.confirm-screen-row', [ + h('span.confirm-screen-label.confirm-screen-section-column', [ t('from') ]), + h('div.confirm-screen-section-column', [ + h('div.confirm-screen-row-info', fromName), + h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`), + ]), + ]), - // h('h3.flex-center.confirm-screen-sending-to-message', { - // style: { - // textAlign: 'center', - // fontSize: '16px', - // }, - // }, [ - // `You're deploying a new contract.`, - // ]), - - this.renderHeroAmount(), - - h('div.confirm-screen-rows', [ - h('section.flex-row.flex-center.confirm-screen-row', [ - h('span.confirm-screen-label.confirm-screen-section-column', [ t('from') ]), - h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', fromName), - h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`), + h('section.flex-row.flex-center.confirm-screen-row', [ + h('span.confirm-screen-label.confirm-screen-section-column', [ t('to') ]), + h('div.confirm-screen-section-column', [ + h('div.confirm-screen-row-info', t('newContract')), + ]), ]), + + this.renderGasFee(), + + this.renderTotalPlusGas(), + ]), + ]), - h('section.flex-row.flex-center.confirm-screen-row', [ - h('span.confirm-screen-label.confirm-screen-section-column', [ t('to') ]), - h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', t('newContract')), - ]), + h('form#pending-tx-form', { + onClick: event => this.onSubmit(event), + }, [ + h('.page-container__footer', [ + // Cancel Button + h('button.btn-cancel.page-container__footer-button.allcaps', { + onClick: event => this.cancel(event, txMeta), + }, t('cancel')), + + // Accept Button + h('button.btn-confirm.page-container__footer-button.allcaps', { + onClick: event => this.onSubmit(event), + }, t('confirm')), ]), + ]), + ]) + ) + } +} - this.renderGasFee(), +ConfirmDeployContract.propTypes = { + sendTransaction: PropTypes.func, + cancelTransaction: PropTypes.func, + backToAccountDetail: PropTypes.func, + displayWarning: PropTypes.func, + identities: PropTypes.object, + conversionRate: PropTypes.number, + currentCurrency: PropTypes.string, + selectedAddress: PropTypes.string, +} - this.renderTotalPlusGas(), +const mapStateToProps = state => { + const { + conversionRate, + identities, + currentCurrency, + } = state.metamask + const accounts = state.metamask.accounts + const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0] + return { + currentCurrency, + conversionRate, + identities, + selectedAddress, + } +} - ]), - ]), - - h('form#pending-tx-form', { - onSubmit: this.onSubmit, - }, [ - // Cancel Button - h('div.cancel.btn-light.confirm-screen-cancel-button.allcaps', { - onClick: (event) => this.cancel(event, txMeta), - }, t('cancel')), - - // Accept Button - h('button.confirm-screen-confirm-button.allcaps', [t('confirm')]), - - ]), - ]) - ) +const mapDispatchToProps = dispatch => { + return { + backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)), + cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })), + displayWarning: warning => actions.displayWarning(t(warning)), + } } + +module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmDeployContract) diff --git a/ui/app/components/sender-to-recipient.js b/ui/app/components/sender-to-recipient.js new file mode 100644 index 000000000..25b9c7d6b --- /dev/null +++ b/ui/app/components/sender-to-recipient.js @@ -0,0 +1,45 @@ +const { Component } = require('react') +const h = require('react-hyperscript') +const PropTypes = require('prop-types') +const t = require('../../i18n') +const Identicon = require('./identicon') + +class SenderToRecipient extends Component { + render () { + const { senderName, senderAddress } = this.props + + return ( + h('.sender-to-recipient__container', [ + h('.sender-to-recipient__sender', [ + h('.sender-to-recipient__sender-icon', [ + h(Identicon, { + address: senderAddress, + diameter: 20, + }), + ]), + h('.sender-to-recipient__name.sender-to-recipient__sender-name', senderName), + ]), + h('.sender-to-recipient__arrow-container', [ + h('.sender-to-recipient__arrow-circle', [ + h('img', { + height: 15, + width: 15, + src: '/images/arrow-right.svg', + }), + ]), + ]), + h('.sender-to-recipient__recipient', [ + h('i.fa.fa-file-text-o'), + h('.sender-to-recipient__name.sender-to-recipient__recipient-name', t('newContract')), + ]), + ]) + ) + } +} + +SenderToRecipient.propTypes = { + senderName: PropTypes.string, + senderAddress: PropTypes.string, +} + +module.exports = SenderToRecipient -- cgit