diff options
Core of the refactor complete
Diffstat (limited to 'ui/app/components/send_')
30 files changed, 747 insertions, 92 deletions
diff --git a/ui/app/components/send_/account-list-item/account-list-item-README.md b/ui/app/components/send_/account-list-item/account-list-item-README.md new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ui/app/components/send_/account-list-item/account-list-item-README.md diff --git a/ui/app/components/send_/account-list-item/account-list-item.component.js b/ui/app/components/send_/account-list-item/account-list-item.component.js new file mode 100644 index 000000000..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 (<div + className={`account-list-item ${className}`} + onClick={() => handleClick({ name, address, balance })} + > + + <div className='account-list-item__top-row'> + <Identicon + address={address} + diameter={18} + className='account-list-item__identicon' + /> + + <div className='account-list-item__account-name'>{ name || address }</div> + + {icon && <div className='account-list-item__icon'>{ icon }</div>} + + </div> + + {displayAddress && name && <div className='account-list-item__account-address'> + { checksumAddress(address) } + </div>} + + {displayBalance && <CurrencyDisplay + primaryCurrency='ETH' + convertedCurrency={currentCurrency} + value={balance} + conversionRate={conversionRate} + readOnly={true} + className='account-list-item__account-balances' + primaryBalanceClassName='account-list-item__account-primary-balance' + convertedBalanceClassName='account-list-item__account-secondary-balance' + />} + + </div>) + } +} + +AccountListItem.contextTypes = { + t: PropTypes.func, +} + diff --git a/ui/app/components/send_/account-list-item/account-list-item.container.js b/ui/app/components/send_/account-list-item/account-list-item.container.js new file mode 100644 index 000000000..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 --- /dev/null +++ b/ui/app/components/send_/account-list-item/account-list-item.scss diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js index 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 ( <div @@ -42,7 +42,7 @@ export default class AmountMaxButton extends Component { this.setAmountToMax() }} > - {!maxModeOn ? this.context.t('max') : '' ])} + {!maxModeOn ? this.context.t('max') : ''} </div> ); } 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 && <AmountMaxButton /> + {!inError && gasTotal && <AmountMaxButton />} <CurrencyDisplay - inError={inError}, - primaryCurrency={primaryCurrency}, - convertedCurrency={convertedCurrency}, - selectedToken={selectedToken}, - value={amount || '0x0'}, - conversionRate={amountConversionRate}, - handleChange={this.handleAmountChange}, - > + inError={inError} + primaryCurrency={primaryCurrency} + convertedCurrency={convertedCurrency} + selectedToken={selectedToken} + value={amount || '0x0'} + conversionRate={amountConversionRate} + handleChange={newAmount => this.handleAmountChange(newAmount)} + /> </SendRowWrapper> ); } 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 ( <PageContainerContent> <div className='.send-v2__form'> 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 <i className={`fa ${icon} fa-lg`} style={ { color } }/> + } + + getListItemIcon (currentAccount, selectedAccount) { + return currentAccount.address === selectedAccount.address + ? this.renderListItemIcon('fa-check', '#02c9b1') + : null + } + + renderDropdown () { + const { + accounts, + selectedAccount, + closeDropdown, + onSelect, + } = this.props + + return (<div> + <div + className='send-v2__from-dropdown__close-area' + onClick={() => closeDropdown} + /> + <div className='send-v2__from-dropdown__list'> + {...accounts.map(account => <AccountListItem + className='account-list-item__dropdown' + account={account} + handleClick={() => { + onSelect(account) + closeDropdown() + }} + icon={this.getListItemIcon(account, selectedAccount.address)} + />)} + </div> + </div>) + } + + render () { + const { + selectedAccount, + openDropdown, + dropdownOpen, + } = this.props + console.log(`&*& openDropdown`, openDropdown); + console.log(`&*& dropdownOpen`, dropdownOpen); + return <div className='send-v2__from-dropdown'> + <AccountListItem + account={selectedAccount} + handleClick={openDropdown} + icon={this.renderListItemIcon('fa-caret-down', '#dedede')} + /> + {dropdownOpen && this.renderDropdown()}, + </div> + } + +} + +FromDropdown.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js index 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 ( <SendRowWrapper label={`${this.context.t('from')}:`}> <FromDropdown diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js index eeeb51629..e2815a01f 100644 --- a/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js +++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js @@ -1,27 +1,30 @@ +import { connect } from 'react-redux' import { - getSendFrom, getConversionRate, getSelectedTokenContract, - getCurrentAccountWithSendEtherInfo, accountsWithSendEtherInfoSelector, + getSendFromObject, } from '../../send.selectors.js' -import { getFromDropdownOpen } from './send-from-row.selectors.js' +import { + getFromDropdownOpen, +} from './send-from-row.selectors.js' import { calcTokenUpdateAmount } from './send-from-row.utils.js' import { updateSendTokenBalance, updateSendFrom, -} from '../../../actions' +} from '../../../../actions' import { openFromDropdown, closeFromDropdown, -} from '../../../ducks/send' +} from '../../../../ducks/send' import SendFromRow from './send-from-row.component' export default connect(mapStateToProps, mapDispatchToProps)(SendFromRow) function mapStateToProps (state) { + console.log(`$% mapStateToProps accountsWithSendEtherInfoSelector`, accountsWithSendEtherInfoSelector); return { - from: getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state), + from: getSendFromObject(state), fromAccounts: accountsWithSendEtherInfoSelector(state), conversionRate: getConversionRate(state), fromDropdownOpen: getFromDropdownOpen(state), @@ -38,7 +41,7 @@ function mapDispatchToProps (dispatch) { dispatch(updateSendTokenBalance(tokenBalance)) }, updateSendFrom: newFrom => 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 ( <SendRowWrapper label={`${this.context.t('gasFee')}:`}> <GasFeeDisplay - gasTotal={gasTotal}, - conversionRate={conversionRate}, - convertedCurrency={convertedCurrency}, - onClick={() => showCustomizeGasModal()}, - gasLoadingError={gasLoadingError}, + gasTotal={gasTotal} + conversionRate={conversionRate} + convertedCurrency={convertedCurrency} + onClick={() => showCustomizeGasModal()} + gasLoadingError={gasLoadingError} /> </SendRowWrapper> ); 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 - ? <div className='send-v2__error'>{errorMessage}</div> + ? <div className='send-v2__error'>{this.context.t(errorMessage)}</div> : 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 ( <div className="send-v2__form-row"> <div className="send-v2__form-label"> {label} - (showError && <SendRowErrorMessage errorType={errorType}/>) + {showError && <SendRowErrorMessage errorType={errorType}/>} {customLabelContent} </div> <div className="send-v2__form-field"> 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 { > <EnsInput name={'address'} - placeholder={this.context.t('recipient Address')} - network={network}, - to={to}, + placeholder={this.context.t('recipientAddress')} + network={network} + to={to} accounts={toAccounts} dropdownOpen={toDropdownOpen} openDropdown={() => openToDropdown()} closeDropdown={() => closeToDropdown()} - onChange={this.handleToChange} + onChange={(newTo, newNickname) => this.handleToChange(newTo, newNickname)} inError={inError} /> </SendRowWrapper> diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js index 1c446c168..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 ( + <PageContainerFooter + onCancel={() => { + clearSend() + history.push(DEFAULT_ROUTE) + }} + onSubmit={e => this.onSubmit(e)} + disabled={disabled} + /> + ); + } + +} + +SendFooter.contextTypes = { + t: PropTypes.func, +} diff --git a/ui/app/components/send_/send-footer/send-footer.container.js b/ui/app/components/send_/send-footer/send-footer.container.js index e69de29bb..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 |