From 8ff7806f1b471a90fa3f45ebc10f0f4452ade541 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 26 Apr 2018 14:08:38 -0230 Subject: [PATCH] Core of the refactor complete --- .../page-container.component.js | 1 + .../account-list-item-README.md | 0 .../account-list-item.component.js | 74 ++++ .../account-list-item.container.js | 15 + .../account-list-item/account-list-item.scss | 0 .../amount-max-button.component.js | 4 +- .../amount-max-button.container.js | 1 + .../send-amount-row.component.js | 27 +- .../send-amount-row.container.js | 37 +- .../send-amount-row.selectors.js | 21 +- .../send-amount-row/send-amount-row.utils.js | 23 +- .../send-content/send-content.component.js | 11 +- .../from-dropdown/from-dropdown.component.js | 75 ++++ .../send-from-row/send-from-row.component.js | 8 +- .../send-from-row/send-from-row.container.js | 19 +- .../send-from-row/send-from-row.utils.js | 2 +- .../send-gas-row/send-gas-row.component.js | 13 +- .../send-gas-row/send-gas-row.container.js | 10 +- .../send-gas-row/send-gas-row.selectors.js | 2 +- .../send-row-error-message.component.js | 5 +- .../send-row-error-message.container.js | 1 + .../send-row-wrapper.component.js | 11 +- .../send-to-row/send-to-row.component.js | 17 +- .../send-to-row/send-to-row.container.js | 10 +- .../send-footer/send-footer.component.js | 93 ++++ .../send-footer/send-footer.container.js | 107 +++++ .../send-footer/send-footer.selectors.js | 12 + .../send_/send-footer/send-footer.utils.js | 84 ++++ ui/app/components/send_/send.constants.js | 33 ++ ui/app/components/send_/send.selectors.js | 46 +- ui/app/components/send_/send.utils.js | 78 ++++ ui/app/ducks/send.js | 3 +- ui/app/reducers.js | 2 +- ui/app/send-v2.js | 401 +----------------- 34 files changed, 757 insertions(+), 489 deletions(-) create mode 100644 ui/app/components/send_/account-list-item/account-list-item-README.md create mode 100644 ui/app/components/send_/account-list-item/account-list-item.component.js create mode 100644 ui/app/components/send_/account-list-item/account-list-item.container.js create mode 100644 ui/app/components/send_/account-list-item/account-list-item.scss create mode 100644 ui/app/components/send_/send.constants.js diff --git a/ui/app/components/page-container/page-container.component.js b/ui/app/components/page-container/page-container.component.js index 7df1d48d8..1b5e7f819 100644 --- a/ui/app/components/page-container/page-container.component.js +++ b/ui/app/components/page-container/page-container.component.js @@ -8,6 +8,7 @@ export default class PageContainer extends Component { }; render () { + console.log(`QQQQQQQQQQQQQQQQQ this.props.children`, this.props.children); return (
{this.props.children} diff --git a/ui/app/components/send_/account-list-item/account-list-item-README.md b/ui/app/components/send_/account-list-item/account-list-item-README.md new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/account-list-item/account-list-item.component.js b/ui/app/components/send_/account-list-item/account-list-item.component.js new file mode 100644 index 000000000..6b63aecf0 --- /dev/null +++ b/ui/app/components/send_/account-list-item/account-list-item.component.js @@ -0,0 +1,74 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { checksumAddress } from '../../../util' +import Identicon from '../../identicon' +import CurrencyDisplay from '../../send/currency-display' + +export default class AccountListItem extends Component { + + static propTypes = { + account: PropTypes.object, + className: PropTypes.string, + conversionRate: PropTypes.number, + currentCurrency: PropTypes.string, + displayAddress: PropTypes.bool, + displayBalance: PropTypes.bool, + handleClick: PropTypes.func, + icon: PropTypes.node, + }; + + render () { + const { + className, + account, + handleClick, + icon = null, + conversionRate, + currentCurrency, + displayBalance = true, + displayAddress = false, + } = this.props + + const { name, address, balance } = account || {} + + return (
handleClick({ name, address, balance })} + > + +
+ + +
{ name || address }
+ + {icon &&
{ icon }
} + +
+ + {displayAddress && name &&
+ { checksumAddress(address) } +
} + + {displayBalance && } + +
) + } +} + +AccountListItem.contextTypes = { + t: PropTypes.func, +} + diff --git a/ui/app/components/send_/account-list-item/account-list-item.container.js b/ui/app/components/send_/account-list-item/account-list-item.container.js new file mode 100644 index 000000000..e1bc1dd79 --- /dev/null +++ b/ui/app/components/send_/account-list-item/account-list-item.container.js @@ -0,0 +1,15 @@ +import { connect } from 'react-redux' +import { + getConversionRate, + getConvertedCurrency, +} from '../send.selectors.js' +import AccountListItem from './account-list-item.component' + +export default connect(mapStateToProps)(AccountListItem) + +function mapStateToProps (state) { + return { + conversionRate: getConversionRate(state), + currentCurrency: getConvertedCurrency(state), + } +} \ No newline at end of file diff --git a/ui/app/components/send_/account-list-item/account-list-item.scss b/ui/app/components/send_/account-list-item/account-list-item.scss new file mode 100644 index 000000000..e69de29bb diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js index 59a1fd6db..6705a332d 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js @@ -31,7 +31,7 @@ export default class AmountMaxButton extends Component { } render () { - const { setMaxModeTo } = this.props + const { setMaxModeTo, maxModeOn } = this.props return (
- {!maxModeOn ? this.context.t('max') : '' ])} + {!maxModeOn ? this.context.t('max') : ''}
); } diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js index 572e1fc46..1b694902f 100644 --- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js @@ -1,3 +1,4 @@ +import { connect } from 'react-redux' import { getSelectedToken, getGasTotal, diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js index 78038f714..544664dc8 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js @@ -1,15 +1,14 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' -import AmountMaxButton from '../amount-max-button/amount-max-button.component' +import AmountMaxButton from './amount-max-button/amount-max-button.component' import CurrencyDisplay from '../../../send/currency-display' export default class SendAmountRow extends Component { static propTypes = { - amountConversionRate: PropTypes.string, - conversionRate: PropTypes.string, - from: PropTypes.object, + amountConversionRate: PropTypes.number, + conversionRate: PropTypes.number, gasTotal: PropTypes.string, primaryCurrency: PropTypes.string, selectedToken: PropTypes.object, @@ -23,7 +22,7 @@ export default class SendAmountRow extends Component { const { amountConversionRate, conversionRate, - from: { balance }, + balance, gasTotal, primaryCurrency, selectedToken, @@ -69,16 +68,16 @@ export default class SendAmountRow extends Component { showError={inError} errorType={'amount'} > - !inError && gasTotal && + {!inError && gasTotal && } + inError={inError} + primaryCurrency={primaryCurrency} + convertedCurrency={convertedCurrency} + selectedToken={selectedToken} + value={amount || '0x0'} + conversionRate={amountConversionRate} + handleChange={newAmount => this.handleAmountChange(newAmount)} + /> ); } diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js index 098855a02..55df68e69 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js @@ -1,41 +1,44 @@ +import { connect } from 'react-redux' import { getSelectedToken, - getPrimaryCurrency, - getAmountConversionRate, getConvertedCurrency, getSendAmount, getGasTotal, getSelectedBalance, getTokenBalance, getSendFromBalance, + getConversionRate, } from '../../send.selectors.js' import { getMaxModeOn, sendAmountIsInError, + getPrimaryCurrency, + getAmountConversionRate, } from './send-amount-row.selectors.js' import { getAmountErrorObject } from './send-amount-row.utils.js' import { updateSendAmount, setMaxModeTo, -} from '../../../actions' + updateSendErrors, +} from '../../../../actions' import SendAmountRow from './send-amount-row.component' -export default connect(mapStateToProps, mapDispatchToProps)(SendToRow) +export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow) function mapStateToProps (state) { -updateSendTo -return { - selectedToken: getSelectedToken(state), - primaryCurrency: getPrimaryCurrency(state), - convertedCurrency: getConvertedCurrency(state), - amountConversionRate: getAmountConversionRate(state), - inError: sendAmountIsInError(state), - amount: getSendAmount(state), - maxModeOn: getMaxModeOn(state), - gasTotal: getGasTotal(state), - tokenBalance: getTokenBalance(state), - balance: getSendFromBalance(state), -} + return { + selectedToken: getSelectedToken(state), + primaryCurrency: getPrimaryCurrency(state), + convertedCurrency: getConvertedCurrency(state), + amountConversionRate: getAmountConversionRate(state), + inError: sendAmountIsInError(state), + amount: getSendAmount(state), + maxModeOn: getMaxModeOn(state), + gasTotal: getGasTotal(state), + tokenBalance: getTokenBalance(state), + balance: getSendFromBalance(state), + conversionRate: getConversionRate(state), + } } function mapDispatchToProps (dispatch) { diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js index 724f345af..c2620b4dc 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js @@ -1,6 +1,14 @@ +import { + getSelectedToken, + getSelectedTokenToFiatRate, + getConversionRate, +} from '../../send.selectors.js' + const selectors = { getMaxModeOn, sendAmountIsInError, + getPrimaryCurrency, + getAmountConversionRate, } module.exports = selectors @@ -10,5 +18,16 @@ function getMaxModeOn (state) { } function sendAmountIsInError (state) { - return Boolean(state.metamask.send.errors.amount) + return Boolean(state.metamask.send.errors.amount) +} + +function getPrimaryCurrency (state) { + const selectedToken = getSelectedToken(state) + return selectedToken && selectedToken.symbol +} + +function getAmountConversionRate (state) { + return Boolean(getSelectedToken(state)) + ? getSelectedTokenToFiatRate(state) + : getConversionRate(state) } diff --git a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js index 5b01b4594..418b98c18 100644 --- a/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js +++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js @@ -1,4 +1,11 @@ const { isValidAddress } = require('../../../../util') +const { + conversionGreaterThan, +} = require('../../../../conversion-util') +const { + isBalanceSufficient, + isTokenBalanceSufficient, +} = require('../../send.utils') function getAmountErrorObject ({ amount, @@ -10,6 +17,14 @@ function getAmountErrorObject ({ gasTotal, tokenBalance, }) { + console.log(`#& getAmountErrorObject amount`, amount); + console.log(`#& getAmountErrorObject balance`, balance); + console.log(`#& getAmountErrorObject amountConversionRate`, amountConversionRate); + console.log(`#& getAmountErrorObject conversionRate`, conversionRate); + console.log(`#& getAmountErrorObject primaryCurrency`, primaryCurrency); + console.log(`#& getAmountErrorObject selectedToken`, selectedToken); + console.log(`#& getAmountErrorObject gasTotal`, gasTotal); + console.log(`#& getAmountErrorObject tokenBalance`, tokenBalance); let insufficientFunds = false if (gasTotal && conversionRate) { insufficientFunds = !isBalanceSufficient({ @@ -40,11 +55,11 @@ function getAmountErrorObject ({ let amountError = null if (insufficientFunds) { - amountError = this.context.t('insufficientFunds') - } else if (insufficientTokens) { - amountError = this.context.t('insufficientTokens') + amountError = 'insufficientFunds' + } else if (inSufficientTokens) { + amountError = 'insufficientTokens' } else if (amountLessThanZero) { - amountError = this.context.t('negativeETH') + amountError = 'negativeETH' } return { amount: amountError } diff --git a/ui/app/components/send_/send-content/send-content.component.js b/ui/app/components/send_/send-content/send-content.component.js index ad6b4a982..2d1fa52e9 100644 --- a/ui/app/components/send_/send-content/send-content.component.js +++ b/ui/app/components/send_/send-content/send-content.component.js @@ -1,13 +1,14 @@ import React, { Component } from 'react' -import PageContainerContent from '../../page-container/page-container-header.component' -import SendFromRow from './send-from-row/send-from-row.component' -import SendToRow from './send-to-row/send-to-row.component' -import SendAmountRow from './send-amount-row/send-amount-row.component' -import SendGasRow from './send-gas-row/send-gas-row.component' +import PageContainerContent from '../../page-container/page-container-content.component' +import SendFromRow from './send-from-row/send-from-row.container' +import SendToRow from './send-to-row/send-to-row.container' +import SendAmountRow from './send-amount-row/send-amount-row.container' +import SendGasRow from './send-gas-row/send-gas-row.container' export default class SendContent extends Component { render () { + console.log('111222333444555666777888999') return (
diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js index e69de29bb..f215179ba 100644 --- a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js +++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js @@ -0,0 +1,75 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import AccountListItem from '../../../account-list-item/account-list-item.container' + +export default class FromDropdown extends Component { + + static propTypes = { + accounts: PropTypes.array, + closeDropdown: PropTypes.func, + dropdownOpen: PropTypes.bool, + onSelect: PropTypes.func, + openDropdown: PropTypes.func, + selectedAccount: PropTypes.object, + }; + + renderListItemIcon (icon, color) { + return + } + + getListItemIcon (currentAccount, selectedAccount) { + return currentAccount.address === selectedAccount.address + ? this.renderListItemIcon('fa-check', '#02c9b1') + : null + } + + renderDropdown () { + const { + accounts, + selectedAccount, + closeDropdown, + onSelect, + } = this.props + + return (
+
closeDropdown} + /> +
+ {...accounts.map(account => { + onSelect(account) + closeDropdown() + }} + icon={this.getListItemIcon(account, selectedAccount.address)} + />)} +
+
) + } + + render () { + const { + selectedAccount, + openDropdown, + dropdownOpen, + } = this.props + console.log(`&*& openDropdown`, openDropdown); + console.log(`&*& dropdownOpen`, dropdownOpen); + return
+ + {dropdownOpen && this.renderDropdown()}, +
+ } + +} + +FromDropdown.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js index b17f749a6..0ae72c4ae 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js @@ -1,14 +1,14 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' -import FromDropdown from '../../../send/from-dropdown' +import FromDropdown from './from-dropdown/from-dropdown.component' export default class SendFromRow extends Component { static propTypes = { closeFromDropdown: PropTypes.func, - conversionRate: PropTypes.string, - from: PropTypes.string, + conversionRate: PropTypes.number, + from: PropTypes.object, fromAccounts: PropTypes.array, fromDropdownOpen: PropTypes.bool, openFromDropdown: PropTypes.func, @@ -41,7 +41,7 @@ export default class SendFromRow extends Component { openFromDropdown, closeFromDropdown, } = this.props - + console.log(`$% SendFromRow fromAccounts`, fromAccounts); return ( dispatch(updateSendFrom(newFrom)), - openFromDropdown: () => dispatch(()), - closeFromDropdown: () => dispatch(()), + openFromDropdown: () => dispatch(openFromDropdown()), + closeFromDropdown: () => dispatch(closeFromDropdown()), } } diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js index 2be25816f..4faae48dc 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js @@ -1,6 +1,6 @@ const { calcTokenAmount, -} = require('../../token-util') +} = require('../../../../token-util') function calcTokenUpdateAmount (usersToken, selectedToken) { const { decimals } = selectedToken || {} diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js index 8c1f14f48..056a04aae 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js @@ -7,7 +7,7 @@ export default class SendGasRow extends Component { static propTypes = { closeFromDropdown: PropTypes.func, - conversionRate: PropTypes.string, + conversionRate: PropTypes.number, from: PropTypes.string, fromAccounts: PropTypes.array, fromDropdownOpen: PropTypes.bool, @@ -15,6 +15,7 @@ export default class SendGasRow extends Component { tokenContract: PropTypes.object, updateSendFrom: PropTypes.func, updateSendTokenBalance: PropTypes.func, + gasLoadingError: PropTypes.bool, }; async handleFromChange (newFrom) { @@ -43,11 +44,11 @@ export default class SendGasRow extends Component { return ( showCustomizeGasModal()}, - gasLoadingError={gasLoadingError}, + gasTotal={gasTotal} + conversionRate={conversionRate} + convertedCurrency={convertedCurrency} + onClick={() => showCustomizeGasModal()} + gasLoadingError={gasLoadingError} /> ); diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js index 7fb3a68be..20d3daa59 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js @@ -1,12 +1,12 @@ +import { connect } from 'react-redux' import { getConversionRate, getConvertedCurrency, getGasTotal, } from '../../send.selectors.js' -import { getGasLoadingError } from './send-gas-row.selectors.js' -import { calcTokenUpdateAmount } from './send-gas-row.utils.js' -import { showModal } from '../../../actions' -import SendGasRow from './send-from-row.component' +import { sendGasIsInError } from './send-gas-row.selectors.js' +import { showModal } from '../../../../actions' +import SendGasRow from './send-gas-row.component' export default connect(mapStateToProps, mapDispatchToProps)(SendGasRow) @@ -15,7 +15,7 @@ function mapStateToProps (state) { conversionRate: getConversionRate(state), convertedCurrency: getConvertedCurrency(state), gasTotal: getGasTotal(state), - gasLoadingError: getGasLoadingError(state), + gasLoadingError: sendGasIsInError(state), } } diff --git a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js index d069ae8c6..ad4ef4877 100644 --- a/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js +++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js @@ -5,5 +5,5 @@ const selectors = { module.exports = selectors function sendGasIsInError (state) { - return state.send.errors.gasLoading + return state.metamask.send.errors.gasLoading } diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js index 08f830cc5..e553ae67e 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js @@ -1,3 +1,6 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + export default class SendRowErrorMessage extends Component { static propTypes = { @@ -11,7 +14,7 @@ export default class SendRowErrorMessage extends Component { return ( errorMessage - ?
{errorMessage}
+ ?
{this.context.t(errorMessage)}
: null ); } diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js index 2278dbe63..002426904 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js @@ -1,3 +1,4 @@ +import { connect } from 'react-redux' import { getSendErrors } from '../../../send.selectors' import SendRowErrorMessage from './send-row-error-message.component' diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js index 92382da01..99134a466 100644 --- a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js +++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js @@ -19,19 +19,14 @@ export default class SendRowWrapper extends Component { children, } = this.props - let formField = children[0] - let customLabelContent = null - - if (children.length === 2) { - formField = children[1] - customLabelContent = children[0] - } + let formField = Array.isArray(children) ? children[1] || children[0] : children + let customLabelContent = children.length === 1 ? children[0] : null return (
{label} - (showError && ) + {showError && } {customLabelContent}
diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js index 5f81402d8..a20bbf434 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component' -import ToDropdown from '../../../ens-input' +import EnsInput from '../../../ens-input' export default class SendToRow extends Component { @@ -14,19 +14,20 @@ export default class SendToRow extends Component { updateSendToError: PropTypes.func, openToDropdown: PropTypes.func, closeToDropdown: PropTypes.func, - network: PropTypes.number, + network: PropTypes.string, }; handleToChange (to, nickname = '') { const { updateSendTo, updateSendToError } = this.props updateSendTo(to, nickname) - updateSendErrors(to) + updateSendToError(to) } render () { const { from, fromAccounts, + toAccounts, conversionRate, fromDropdownOpen, tokenContract, @@ -34,6 +35,8 @@ export default class SendToRow extends Component { closeToDropdown, network, inError, + to, + toDropdownOpen, } = this.props return ( @@ -44,14 +47,14 @@ export default class SendToRow extends Component { > openToDropdown()} closeDropdown={() => closeToDropdown()} - onChange={this.handleToChange} + onChange={(newTo, newNickname) => this.handleToChange(newTo, newNickname)} inError={inError} /> diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js index 1c446c168..661ec1f0c 100644 --- a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js +++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js @@ -1,7 +1,9 @@ +import { connect } from 'react-redux' import { getSendTo, getToAccounts, getCurrentNetwork, + getSendToAccounts, } from '../../send.selectors.js' import { getToDropdownOpen, @@ -11,11 +13,11 @@ import { getToErrorObject } from './send-to-row.utils.js' import { updateSendErrors, updateSendTo, -} from '../../../actions' +} from '../../../../actions' import { openToDropdown, closeToDropdown, -} from '../../../ducks/send' +} from '../../../../ducks/send' import SendToRow from './send-to-row.component' export default connect(mapStateToProps, mapDispatchToProps)(SendToRow) @@ -37,7 +39,7 @@ function mapDispatchToProps (dispatch) { dispatch(updateSendErrors(getToErrorObject(to))) }, updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)), - openToDropdown: () => dispatch(()), - closeToDropdown: () => dispatch(()), + openToDropdown: () => dispatch(openToDropdown()), + closeToDropdown: () => dispatch(closeToDropdown()), } } \ No newline at end of file diff --git a/ui/app/components/send_/send-footer/send-footer.component.js b/ui/app/components/send_/send-footer/send-footer.component.js index e69de29bb..64dd027cf 100644 --- a/ui/app/components/send_/send-footer/send-footer.component.js +++ b/ui/app/components/send_/send-footer/send-footer.component.js @@ -0,0 +1,93 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import PageContainerFooter from '../../page-container/page-container-footer.component' +import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../routes' + +export default class SendFooter extends Component { + + static propTypes = { + addToAddressBook: PropTypes.func, + amount: PropTypes.string, + clearSend: PropTypes.func, + editingTransactionId: PropTypes.string, + errors: PropTypes.object, + from: PropTypes.object, + gasLimit: PropTypes.string, + gasPrice: PropTypes.string, + gasTotal: PropTypes.string, + history: PropTypes.object, + selectedToken: PropTypes.object, + signTokenTx: PropTypes.func, + signTx: PropTypes.func, + to: PropTypes.string, + toAccounts: PropTypes.array, + tokenBalance: PropTypes.string, + unapprovedTxs: PropTypes.object, + updateTx: PropTypes.func, + }; + + onSubmit (event) { + event.preventDefault() + const { + addToAddressBookIfNew, + amount, + editingTransactionId, + from: {address: from}, + gasLimit: gas, + gasPrice, + selectedToken, + sign, + to, + unapprovedTxs, + // updateTx, + update, + toAccounts, + } = this.props + + // Should not be needed because submit should be disabled if there are no errors. + // const noErrors = !amountError && toError === null + + // if (!noErrors) { + // return + // } + + // TODO: add nickname functionality + addToAddressBookIfNew(to, toAccounts) + + editingTransactionId + ? update({ + from, + to, + amount, + gas, + gasPrice, + selectedToken, + editingTransactionId, + unapprovedTxs, + }) + : sign({ selectedToken, to, amount, from, gas, gasPrice }) + + this.props.history.push(CONFIRM_TRANSACTION_ROUTE) + } + + + render () { + const { clearSend, disabled, history } = this.props + + return ( + { + clearSend() + history.push(DEFAULT_ROUTE) + }} + onSubmit={e => this.onSubmit(e)} + disabled={disabled} + /> + ); + } + +} + +SendFooter.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-footer/send-footer.container.js b/ui/app/components/send_/send-footer/send-footer.container.js index e69de29bb..fff6e284f 100644 --- a/ui/app/components/send_/send-footer/send-footer.container.js +++ b/ui/app/components/send_/send-footer/send-footer.container.js @@ -0,0 +1,107 @@ +import { connect } from 'react-redux' +import ethUtil from 'ethereumjs-util' +import { + addToAddressBook, + clearSend, + goHome, + signTokenTx, + signTx, + updateTransaction, +} from '../../../actions' +import SendFooter from './send-footer.component' +import { + getGasLimit, + getGasPrice, + getGasTotal, + getSelectedToken, + getSendAmount, + getSendEditingTransactionId, + getSendFromObject, + getSendTo, + getSendToAccounts, + getTokenBalance, + getUnapprovedTxs, +} from '../send.selectors' +import { + isSendFormInError, +} from './send-footer.selectors' +import { + addressIsNew, + formShouldBeDisabled, + constructTxParams, +} from './send-footer.utils' + +export default connect(mapStateToProps, mapDispatchToProps)(SendFooter) + +function mapStateToProps (state) { + return { + isToken: Boolean(getSelectedToken(state)), + inError: isSendFormInError(state), + disabled: formShouldBeDisabled({ + inError: isSendFormInError(state), + selectedToken: getSelectedToken(state), + tokenBalance: getTokenBalance(state), + gasTotal: getGasTotal(state), + }), + amount: getSendAmount(state), + editingTransactionId: getSendEditingTransactionId(state), + from: getSendFromObject(state), + gasLimit: getGasLimit(state), + gasPrice: getGasPrice(state), + selectedToken: getSelectedToken(state), + to: getSendTo(state), + unapprovedTxs: getUnapprovedTxs(state), + toAccounts: getSendToAccounts(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + goHome: () => dispatch(goHome()), + clearSend: () => dispatch(clearSend()), + sign: ({ selectedToken, to, amount, from, gas, gasPrice }) => { + const txParams = constructTxParams({ + amount, + from, + gas, + gasPrice, + selectedToken, + to, + }) + + selectedToken + ? dispatch(signTokenTx(selectedToken.address, to, amount, txParams)) + : dispatch(signTx(txParams)) + }, + update: ({ + amount, + editingTransactionId, + from, + gas, + gasPrice, + selectedToken, + to, + unapprovedTxs, + }) => { + const editingTx = constructUpdatedTx({ + amount, + editingTransactionId, + from, + gas, + gasPrice, + selectedToken, + to, + unapprovedTxs, + }) + + dispatch(updateTransaction(editingTx)) + }, + addToAddressBookIfNew: (newAddress, toAccounts, nickname = '') => { + const hexPrefixedAddress = ethUtil.addHexPrefix(newAddress) + if (addressIsNew(toAccounts)) { + // TODO: nickname, i.e. addToAddressBook(recipient, nickname) + dispatch(addToAddressBook(hexPrefixedAddress, nickname)) + } + } + } +} diff --git a/ui/app/components/send_/send-footer/send-footer.selectors.js b/ui/app/components/send_/send-footer/send-footer.selectors.js index e69de29bb..ccd4706ea 100644 --- a/ui/app/components/send_/send-footer/send-footer.selectors.js +++ b/ui/app/components/send_/send-footer/send-footer.selectors.js @@ -0,0 +1,12 @@ +import { getSendErrors } from '../send.selectors' + +const selectors = { + isSendFormInError, +} + +module.exports = selectors + +function isSendFormInError (state) { + const { amount, to } = getSendErrors(state) + return Boolean(amount || to !== null) +} \ No newline at end of file diff --git a/ui/app/components/send_/send-footer/send-footer.utils.js b/ui/app/components/send_/send-footer/send-footer.utils.js index e69de29bb..23d5655c7 100644 --- a/ui/app/components/send_/send-footer/send-footer.utils.js +++ b/ui/app/components/send_/send-footer/send-footer.utils.js @@ -0,0 +1,84 @@ +import ethAbi from 'ethereumjs-abi' +import ethUtil from 'ethereumjs-util' +import { TOKEN_TRANSFER_FUNCTION_SIGNATURE } from '../send.constants' + +function formShouldBeDisabled ({ inError, selectedToken, tokenBalance, gasTotal }) { + const missingTokenBalance = selectedToken && !tokenBalance + return inError || !gasTotal || missingTokenBalance +} + +function addHexPrefixToObjectValues (obj) { + return Object.keys(obj).reduce((newObj, key) => { + return { ...newObj, [key]: ethUtil.addHexPrefix(obj[key]) } + }, {}) +} + +function constructTxParams ({ selectedToken, to, amount, from, gas, gasPrice }) { + const txParams = { + from, + value: '0', + gas, + gasPrice, + } + + if (!selectedToken) { + txParams.value = amount + txParams.to = to + } + + const hexPrefixedTxParams = addHexPrefixToObjectValues(txParams) + + return hexPrefixedTxParams +} + +function constructUpdatedTx ({ + amount, + editingTransactionId, + from, + gas, + gasPrice, + selectedToken, + to, + unapprovedTxs, +}) { + const editingTx = { + ...unapprovedTxs[editingTransactionId], + txParams: addHexPrefixToObjectValues({ from, gas, gasPrice }), + } + + if (selectedToken) { + const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call( + ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]), + x => ('00' + x.toString(16)).slice(-2) + ).join('') + + Object.assign(editingTx.txParams, addHexPrefixToObjectValues({ + value: '0', + to: selectedToken.address, + data, + })) + } else { + const { data } = unapprovedTxs[editingTransactionId].txParams + + Object.assign(editingTx.txParams, addHexPrefixToObjectValues({ + value: amount, + to, + data, + })) + + if (typeof editingTx.txParams.data === 'undefined') { + delete editingTx.txParams.data + } + } +} + +function addressIsNew (toAccounts, newAddress) { + return !toAccounts.find(({ address }) => newAddress === address) +} + +module.exports = { + addressIsNew, + formShouldBeDisabled, + constructTxParams, + constructUpdatedTx, +} diff --git a/ui/app/components/send_/send.constants.js b/ui/app/components/send_/send.constants.js new file mode 100644 index 000000000..b3ee0899a --- /dev/null +++ b/ui/app/components/send_/send.constants.js @@ -0,0 +1,33 @@ +const ethUtil = require('ethereumjs-util') +const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') + +const MIN_GAS_PRICE_HEX = (100000000).toString(16) +const MIN_GAS_PRICE_DEC = '100000000' +const MIN_GAS_LIMIT_DEC = '21000' +const MIN_GAS_LIMIT_HEX = (parseInt(MIN_GAS_LIMIT_DEC)).toString(16) + +const MIN_GAS_PRICE_GWEI = ethUtil.addHexPrefix(conversionUtil(MIN_GAS_PRICE_HEX, { + fromDenomination: 'WEI', + toDenomination: 'GWEI', + fromNumericBase: 'hex', + toNumericBase: 'hex', + numberOfDecimals: 1, +})) + +const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 16, +}) + +const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb' + +module.exports = { + MIN_GAS_PRICE_GWEI, + MIN_GAS_PRICE_HEX, + MIN_GAS_PRICE_DEC, + MIN_GAS_LIMIT_HEX, + MIN_GAS_LIMIT_DEC, + MIN_GAS_TOTAL, + TOKEN_TRANSFER_FUNCTION_SIGNATURE, +} diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js index 9ef13193c..4abebfa56 100644 --- a/ui/app/components/send_/send.selectors.js +++ b/ui/app/components/send_/send.selectors.js @@ -2,14 +2,14 @@ import { valuesFor } from '../../util' import abi from 'human-standard-token-abi' import { multiplyCurrencies, -} from './conversion-util' +} from '../../conversion-util' const selectors = { accountsWithSendEtherInfoSelector, autoAddToBetaUI, - getConversionRate, getAddressBook, getConversionRate, + getConvertedCurrency, getCurrentAccountWithSendEtherInfo, getCurrentCurrency, getCurrentNetwork, @@ -17,6 +17,7 @@ const selectors = { getForceGasMin, getGasLimit, getGasPrice, + getGasTotal, getSelectedAccount, getSelectedAddress, getSelectedIdentity, @@ -25,12 +26,18 @@ const selectors = { getSelectedTokenExchangeRate, getSelectedTokenToFiatRate, getSendAmount, + getSendEditingTransactionId, getSendErrors, getSendFrom, + getSendFromObject, getSendFromBalance, getSendMaxModeState, getSendTo, + getSendToAccounts, + getTokenBalance, getTokenExchangeRate, + getUnapprovedTxs, + isSendFormInError, transactionsSelector, } @@ -84,10 +91,18 @@ function getTokenExchangeRate (state, tokenSymbol) { return tokenExchangeRate } +function getUnapprovedTxs (state) { + return state.metamask.unapprovedTxs +} + function getConversionRate (state) { return state.metamask.conversionRate } +function getConvertedCurrency (state) { + return state.metamask.currentCurrency +} + function getAddressBook (state) { return state.metamask.addressBook } @@ -97,11 +112,13 @@ function accountsWithSendEtherInfoSelector (state) { accounts, identities, } = state.metamask - + console.log(`accountsWithSendEtherInfoSelector accounts`, accounts); + console.log(`accountsWithSendEtherInfoSelector identities`, identities); const accountsWithSendEtherInfo = Object.entries(accounts).map(([key, account]) => { return Object.assign({}, account, identities[key]) }) + console.log(`accountsWithSendEtherInfoSelector accountsWithSendEtherInfo`, accountsWithSendEtherInfo); return accountsWithSendEtherInfo } @@ -132,6 +149,10 @@ function getGasPrice (state) { return state.metamask.send.gasPrice } +function getGasTotal (state) { + return state.metamask.send.gasTotal +} + function getGasLimit (state) { return state.metamask.send.gasLimit } @@ -144,8 +165,12 @@ function getSendFrom (state) { return state.metamask.send.from } +function getSendFromObject (state) { + return getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state) +} + function getSendFromBalance (state) { - const from = state.metamask.send.from || {} + const from = getSendFrom(state) || getSelectedAccount(state) return from.balance } @@ -203,6 +228,10 @@ function getCurrentViewContext (state) { return currentView.context } +function getSendEditingTransactionId (state) { + return state.metamask.send.editingTransactionId +} + function getSendErrors (state) { return state.metamask.send.errors } @@ -211,6 +240,10 @@ function getSendTo (state) { return state.metamask.send.to } +function getTokenBalance (state) { + return state.metamask.send.tokenBalance +} + function getSendToAccounts (state) { const fromAccounts = accountsWithSendEtherInfoSelector(state) const addressBookAccounts = getAddressBook(state) @@ -221,4 +254,9 @@ function getSendToAccounts (state) { function getCurrentNetwork (state) { return state.metamask.network +} + +function isSendFormInError (state) { + const { amount, to } = getSendErrors(state) + return Boolean(amount || toError !== null) } \ No newline at end of file diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js index e69de29bb..f56f91e48 100644 --- a/ui/app/components/send_/send.utils.js +++ b/ui/app/components/send_/send.utils.js @@ -0,0 +1,78 @@ +const { + addCurrencies, + conversionUtil, + conversionGTE, + multiplyCurrencies, +} = require('../../conversion-util') +const { + calcTokenAmount, +} = require('../../token-util') + +function isBalanceSufficient ({ + amount = '0x0', + gasTotal = '0x0', + balance, + primaryCurrency, + amountConversionRate, + conversionRate, +}) { + const totalAmount = addCurrencies(amount, gasTotal, { + aBase: 16, + bBase: 16, + toNumericBase: 'hex', + }) + + const balanceIsSufficient = conversionGTE( + { + value: balance, + fromNumericBase: 'hex', + fromCurrency: primaryCurrency, + conversionRate, + }, + { + value: totalAmount, + fromNumericBase: 'hex', + conversionRate: amountConversionRate || conversionRate, + fromCurrency: primaryCurrency, + }, + ) + + return balanceIsSufficient +} + +function isTokenBalanceSufficient ({ + amount = '0x0', + tokenBalance, + decimals, +}) { + const amountInDec = conversionUtil(amount, { + fromNumericBase: 'hex', + }) + + const tokenBalanceIsSufficient = conversionGTE( + { + value: tokenBalance, + fromNumericBase: 'dec', + }, + { + value: calcTokenAmount(amountInDec, decimals), + fromNumericBase: 'dec', + }, + ) + + return tokenBalanceIsSufficient +} + +function getGasTotal (gasLimit, gasPrice) { + return multiplyCurrencies(gasLimit, gasPrice, { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 16, + }) +} + +module.exports = { + getGasTotal, + isBalanceSufficient, + isTokenBalanceSufficient, +} \ No newline at end of file diff --git a/ui/app/ducks/send.js b/ui/app/ducks/send.js index aeca9f92f..c4874aa8c 100644 --- a/ui/app/ducks/send.js +++ b/ui/app/ducks/send.js @@ -10,10 +10,11 @@ const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'; const initState = { fromDropdownOpen: false, toDropdownOpen: false, + errors: {}, } // Reducer -export default function reducer(state = initState, action = {}) { +export default function reducer({ send: sendState = initState }, action = {}) { switch (action.type) { case OPEN_FROM_DROPDOWN: return extend(sendState, { diff --git a/ui/app/reducers.js b/ui/app/reducers.js index ff766e856..26bf66c3c 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -8,7 +8,7 @@ const reduceIdentities = require('./reducers/identities') const reduceMetamask = require('./reducers/metamask') const reduceApp = require('./reducers/app') const reduceLocale = require('./reducers/locale') -const reduceSend = require('./ducks/send') +const reduceSend = require('./ducks/send').default window.METAMASK_CACHED_LOG_STATE = null diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index c5085d9ec..12e8b4e60 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -34,8 +34,8 @@ const { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } = require('./routes') import PageContainer from './components/page-container/page-container.component' import SendHeader from './components/send_/send-header/send-header.container' -import PageContainerContent from './components/page-container/page-container-content.component' -import PageContainerFooter from './components/page-container/page-container-footer.component' +import SendContent from './components/send_/send-content/send-content.component' +import SendFooter from './components/send_/send-footer/send-footer.container' SendTransactionScreen.contextTypes = { t: PropTypes.func, @@ -57,8 +57,6 @@ function SendTransactionScreen () { gasLoadingError: false, } - this.handleToChange = this.handleToChange.bind(this) - this.handleAmountChange = this.handleAmountChange.bind(this) this.validateAmount = this.validateAmount.bind(this) } @@ -176,158 +174,6 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) { } } -SendTransactionScreen.prototype.renderHeader = function () { - const { selectedToken, clearSend, history } = this.props - - return h('div.page-container__header', [ - - h('div.page-container__title', selectedToken ? this.context.t('sendTokens') : this.context.t('sendETH')), - - h('div.page-container__subtitle', this.context.t('onlySendToEtherAddress')), - - h('div.page-container__header-close', { - onClick: () => { - clearSend() - history.push(DEFAULT_ROUTE) - }, - }), - - ]) -} - -SendTransactionScreen.prototype.renderErrorMessage = function (errorType) { - const { errors } = this.props - const errorMessage = errors[errorType] - - return errorMessage - ? h('div.send-v2__error', [ errorMessage ]) - : null -} - -SendTransactionScreen.prototype.handleFromChange = async function (newFrom) { - const { - updateSendFrom, - tokenContract, - } = this.props - - if (tokenContract) { - const usersToken = await tokenContract.balanceOf(newFrom.address) - this.updateSendTokenBalance(usersToken) - } - updateSendFrom(newFrom) -} - -SendTransactionScreen.prototype.renderFromRow = function () { - const { - from, - fromAccounts, - conversionRate, - } = this.props - - const { fromDropdownOpen } = this.state - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', 'From:'), - - h('div.send-v2__form-field', [ - h(FromDropdown, { - dropdownOpen: fromDropdownOpen, - accounts: fromAccounts, - selectedAccount: from, - onSelect: newFrom => this.handleFromChange(newFrom), - openDropdown: () => this.setState({ fromDropdownOpen: true }), - closeDropdown: () => this.setState({ fromDropdownOpen: false }), - conversionRate, - }), - ]), - - ]) -} - -SendTransactionScreen.prototype.handleToChange = function (to, nickname = '') { - const { - updateSendTo, - updateSendErrors, - } = this.props - let toError = null - - if (!to) { - toError = this.context.t('required') - } else if (!isValidAddress(to)) { - toError = this.context.t('invalidAddressRecipient') - } - - updateSendTo(to, nickname) - updateSendErrors({ to: toError }) -} - -SendTransactionScreen.prototype.renderToRow = function () { - const { toAccounts, errors, to, network } = this.props - - const { toDropdownOpen } = this.state - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', [ - - this.context.t('to'), - - this.renderErrorMessage(this.context.t('to')), - - ]), - - h('div.send-v2__form-field', [ - h(EnsInput, { - name: 'address', - placeholder: 'Recipient Address', - network, - to, - accounts: Object.entries(toAccounts).map(([key, account]) => account), - dropdownOpen: toDropdownOpen, - openDropdown: () => this.setState({ toDropdownOpen: true }), - closeDropdown: () => this.setState({ toDropdownOpen: false }), - onChange: this.handleToChange, - inError: Boolean(errors.to), - }), - ]), - - ]) -} - -SendTransactionScreen.prototype.handleAmountChange = function (value) { - const amount = value - const { updateSendAmount, setMaxModeTo } = this.props - - setMaxModeTo(false) - this.validateAmount(amount) - updateSendAmount(amount) -} - -SendTransactionScreen.prototype.setAmountToMax = function () { - const { - from: { balance }, - updateSendAmount, - updateSendErrors, - tokenBalance, - selectedToken, - gasTotal, - } = this.props - const { decimals } = selectedToken || {} - const multiplier = Math.pow(10, Number(decimals || 0)) - - const maxAmount = selectedToken - ? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'}) - : subtractCurrencies( - ethUtil.addHexPrefix(balance), - ethUtil.addHexPrefix(gasTotal), - { toNumericBase: 'hex' } - ) - - updateSendErrors({ amount: null }) - - updateSendAmount(maxAmount) -} SendTransactionScreen.prototype.validateAmount = function (value) { const { @@ -384,254 +230,19 @@ SendTransactionScreen.prototype.validateAmount = function (value) { updateSendErrors({ amount: amountError }) } -SendTransactionScreen.prototype.renderAmountRow = function () { - const { - selectedToken, - primaryCurrency = 'ETH', - convertedCurrency, - amountConversionRate, - errors, - amount, - setMaxModeTo, - maxModeOn, - gasTotal, - } = this.props - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', [ - 'Amount:', - this.renderErrorMessage('amount'), - !errors.amount && gasTotal && h('div.send-v2__amount-max', { - onClick: (event) => { - event.preventDefault() - setMaxModeTo(true) - this.setAmountToMax() - }, - }, [ !maxModeOn ? this.context.t('max') : '' ]), - ]), - - h('div.send-v2__form-field', [ - h(CurrencyDisplay, { - inError: Boolean(errors.amount), - primaryCurrency, - convertedCurrency, - selectedToken, - value: amount || '0x0', - conversionRate: amountConversionRate, - handleChange: this.handleAmountChange, - }), - ]), - - ]) -} - -SendTransactionScreen.prototype.renderGasRow = function () { - const { - conversionRate, - convertedCurrency, - showCustomizeGasModal, - gasTotal, - } = this.props - const { gasLoadingError } = this.state - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', this.context.t('gasFee')), - - h('div.send-v2__form-field', [ - - h(GasFeeDisplay, { - gasTotal, - conversionRate, - convertedCurrency, - onClick: showCustomizeGasModal, - gasLoadingError, - }), - - ]), - - ]) -} - -SendTransactionScreen.prototype.renderMemoRow = function () { - const { updateSendMemo, memo } = this.props - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', 'Transaction Memo:'), - - h('div.send-v2__form-field', [ - h(MemoTextArea, { - memo, - onChange: (event) => updateSendMemo(event.target.value), - }), - ]), - - ]) -} - -SendTransactionScreen.prototype.renderForm = function () { - return h(PageContainerContent, [ - h('.send-v2__form', [ - this.renderFromRow(), - - this.renderToRow(), - - this.renderAmountRow(), - - this.renderGasRow(), - ]), - ]) -} - -SendTransactionScreen.prototype.renderFooter = function () { - const { - clearSend, - gasTotal, - tokenBalance, - selectedToken, - errors: { amount: amountError, to: toError }, - history, - } = this.props - - const missingTokenBalance = selectedToken && !tokenBalance - const noErrors = !amountError && toError === null - - return h(PageContainerFooter, { - onCancel: () => { - clearSend() - history.push(DEFAULT_ROUTE) - }, - onSubmit: e => this.onSubmit(e), - disabled: !noErrors || !gasTotal || missingTokenBalance, - }) -} - SendTransactionScreen.prototype.render = function () { + const { history } = this.props + return ( h(PageContainer, [ h(SendHeader), - this.renderForm(), + h(SendContent), - this.renderFooter(), + h(SendFooter, { history }), ]) ) } - -SendTransactionScreen.prototype.addToAddressBookIfNew = function (newAddress, nickname = '') { - const { toAccounts, addToAddressBook } = this.props - if (!toAccounts.find(({ address }) => newAddress === address)) { - // TODO: nickname, i.e. addToAddressBook(recipient, nickname) - addToAddressBook(newAddress, nickname) - } -} - -SendTransactionScreen.prototype.getEditedTx = function () { - const { - from: {address: from}, - to, - amount, - gasLimit: gas, - gasPrice, - selectedToken, - editingTransactionId, - unapprovedTxs, - } = this.props - - const editingTx = { - ...unapprovedTxs[editingTransactionId], - txParams: { - from: ethUtil.addHexPrefix(from), - gas: ethUtil.addHexPrefix(gas), - gasPrice: ethUtil.addHexPrefix(gasPrice), - }, - } - - if (selectedToken) { - const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call( - ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]), - x => ('00' + x.toString(16)).slice(-2) - ).join('') - - Object.assign(editingTx.txParams, { - value: ethUtil.addHexPrefix('0'), - to: ethUtil.addHexPrefix(selectedToken.address), - data, - }) - } else { - const { data } = unapprovedTxs[editingTransactionId].txParams - - Object.assign(editingTx.txParams, { - value: ethUtil.addHexPrefix(amount), - to: ethUtil.addHexPrefix(to), - data, - }) - - if (typeof editingTx.txParams.data === 'undefined') { - delete editingTx.txParams.data - } - } - - return editingTx -} - -SendTransactionScreen.prototype.onSubmit = function (event) { - event.preventDefault() - const { - from: {address: from}, - to: _to, - amount, - gasLimit: gas, - gasPrice, - signTokenTx, - signTx, - updateTx, - selectedToken, - editingTransactionId, - toNickname, - errors: { amount: amountError, to: toError }, - } = this.props - - const noErrors = !amountError && toError === null - - if (!noErrors) { - return - } - - const to = ethUtil.addHexPrefix(_to) - - this.addToAddressBookIfNew(to, toNickname) - - if (editingTransactionId) { - const editedTx = this.getEditedTx() - updateTx(editedTx) - } else { - - const txParams = { - from, - value: '0', - gas, - gasPrice, - } - - if (!selectedToken) { - txParams.value = amount - txParams.to = to - } - - Object.keys(txParams).forEach(key => { - txParams[key] = ethUtil.addHexPrefix(txParams[key]) - }) - - selectedToken - ? signTokenTx(selectedToken.address, to, amount, txParams) - : signTx(txParams) - } - - this.props.history.push(CONFIRM_TRANSACTION_ROUTE) -}