aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan <danjm.com@gmail.com>2018-04-27 00:38:38 +0800
committerDan <danjm.com@gmail.com>2018-04-27 00:38:38 +0800
commit8ff7806f1b471a90fa3f45ebc10f0f4452ade541 (patch)
treefe4c9483f6b7ffadda35f304f15bbf3a02974910
parent02a6d2089ede7d3faf4990c40b85b9f773f82c64 (diff)
downloadtangerine-wallet-browser-8ff7806f1b471a90fa3f45ebc10f0f4452ade541.tar
tangerine-wallet-browser-8ff7806f1b471a90fa3f45ebc10f0f4452ade541.tar.gz
tangerine-wallet-browser-8ff7806f1b471a90fa3f45ebc10f0f4452ade541.tar.bz2
tangerine-wallet-browser-8ff7806f1b471a90fa3f45ebc10f0f4452ade541.tar.lz
tangerine-wallet-browser-8ff7806f1b471a90fa3f45ebc10f0f4452ade541.tar.xz
tangerine-wallet-browser-8ff7806f1b471a90fa3f45ebc10f0f4452ade541.tar.zst
tangerine-wallet-browser-8ff7806f1b471a90fa3f45ebc10f0f4452ade541.zip
Core of the refactor complete
-rw-r--r--ui/app/components/page-container/page-container.component.js1
-rw-r--r--ui/app/components/send_/account-list-item/account-list-item-README.md0
-rw-r--r--ui/app/components/send_/account-list-item/account-list-item.component.js74
-rw-r--r--ui/app/components/send_/account-list-item/account-list-item.container.js15
-rw-r--r--ui/app/components/send_/account-list-item/account-list-item.scss0
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js4
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js1
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js27
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js37
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js21
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js23
-rw-r--r--ui/app/components/send_/send-content/send-content.component.js11
-rw-r--r--ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js75
-rw-r--r--ui/app/components/send_/send-content/send-from-row/send-from-row.component.js8
-rw-r--r--ui/app/components/send_/send-content/send-from-row/send-from-row.container.js19
-rw-r--r--ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js2
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js13
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js10
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js2
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js5
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js1
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js11
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row.component.js17
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row.container.js10
-rw-r--r--ui/app/components/send_/send-footer/send-footer.component.js93
-rw-r--r--ui/app/components/send_/send-footer/send-footer.container.js107
-rw-r--r--ui/app/components/send_/send-footer/send-footer.selectors.js12
-rw-r--r--ui/app/components/send_/send-footer/send-footer.utils.js84
-rw-r--r--ui/app/components/send_/send.constants.js33
-rw-r--r--ui/app/components/send_/send.selectors.js46
-rw-r--r--ui/app/components/send_/send.utils.js78
-rw-r--r--ui/app/ducks/send.js3
-rw-r--r--ui/app/reducers.js2
-rw-r--r--ui/app/send-v2.js401
34 files changed, 757 insertions, 489 deletions
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 (
<div className="page-container">
{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
--- /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
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)
-}