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 e69de29bb..59a1fd6db 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
@@ -0,0 +1,54 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+
+export default class AmountMaxButton extends Component {
+
+ static propTypes = {
+ tokenBalance: PropTypes.string,
+ gasTotal: PropTypes.string,
+ balance: PropTypes.string,
+ selectedToken: PropTypes.object,
+ setAmountToMax: PropTypes.func,
+ setMaxModeTo: PropTypes.func,
+ maxModeOn: PropTypes.bool,
+ };
+
+ setAmountToMax = function () {
+ const {
+ balance,
+ tokenBalance,
+ selectedToken,
+ gasTotal,
+ setAmountToMax,
+ } = this.props
+
+ setAmountToMax({
+ tokenBalance,
+ selectedToken,
+ gasTotal,
+ setAmountToMax,
+ })
+ }
+
+ render () {
+ const { setMaxModeTo } = this.props
+
+ return (
+
{
+ event.preventDefault()
+ setMaxModeTo(true)
+ this.setAmountToMax()
+ }}
+ >
+ {!maxModeOn ? this.context.t('max') : '' ])}
+
+ );
+ }
+
+}
+
+AmountMaxButton.contextTypes = {
+ t: PropTypes.func,
+}
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 e69de29bb..572e1fc46 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
@@ -0,0 +1,36 @@
+import {
+ getSelectedToken,
+ getGasTotal,
+ getTokenBalance,
+ getSendFromBalance,
+} from '../../../send.selectors.js'
+import { getMaxModeOn } from '../send-amount-row.selectors.js'
+import { calcMaxAmount } from './amount-max-button.utils.js'
+import {
+ updateSendAmount,
+ setMaxModeTo,
+} from '../../../actions'
+import AmountMaxButton from './amount-max-button.component'
+
+export default connect(mapStateToProps, mapDispatchToProps)(SendToRow)
+
+function mapStateToProps (state) {
+
+ return {
+ selectedToken: getSelectedToken(state),
+ maxModeOn: getMaxModeOn(state),
+ gasTotal: getGasTotal(state),
+ tokenBalance: getTokenBalance(state),
+ balance: getSendFromBalance(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ setAmountToMax: maxAmountDataObject => {
+ updateSendErrors({ amount: null })
+ updateSendAmount(calcMaxAmount(maxAmountDataObject))
+ }
+ setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
+ }
+}
\ No newline at end of file
diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js
deleted file mode 100644
index e69de29bb..000000000
diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
index e69de29bb..54aacc8d7 100644
--- a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
+++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
@@ -0,0 +1,22 @@
+const {
+ multiplyCurrencies,
+ subtractCurrencies,
+} = require('../../../../conversion-util')
+const ethUtil = require('ethereumjs-util')
+
+function calcMaxAmount ({ balance, gasTotal, selectedToken, tokenBalance }) {
+ const { decimals } = selectedToken || {}
+ const multiplier = Math.pow(10, Number(decimals || 0))
+
+ return selectedToken
+ ? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'})
+ : subtractCurrencies(
+ ethUtil.addHexPrefix(balance),
+ ethUtil.addHexPrefix(gasTotal),
+ { toNumericBase: 'hex' }
+ )
+}
+
+module.exports = {
+ calcMaxAmount
+}
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 e69de29bb..78038f714 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
@@ -0,0 +1,91 @@
+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 CurrencyDisplay from '../../../send/currency-display'
+
+export default class SendAmountRow extends Component {
+
+ static propTypes = {
+ amountConversionRate: PropTypes.string,
+ conversionRate: PropTypes.string,
+ from: PropTypes.object,
+ gasTotal: PropTypes.string,
+ primaryCurrency: PropTypes.string,
+ selectedToken: PropTypes.object,
+ tokenBalance: PropTypes.string,
+ updateSendAmountError: PropTypes.func,
+ updateSendAmount: PropTypes.func,
+ setMaxModeTo: PropTypes.func
+ }
+
+ validateAmount (amount) {
+ const {
+ amountConversionRate,
+ conversionRate,
+ from: { balance },
+ gasTotal,
+ primaryCurrency,
+ selectedToken,
+ tokenBalance,
+ updateSendAmountError,
+ } = this.props
+
+ updateSendAmountError({
+ amount,
+ amountConversionRate,
+ balance,
+ conversionRate,
+ gasTotal,
+ primaryCurrency,
+ selectedToken,
+ tokenBalance,
+ })
+ }
+
+ handleAmountChange (amount) {
+ const { updateSendAmount, setMaxModeTo } = this.props
+
+ setMaxModeTo(false)
+ this.validateAmount(amount)
+ updateSendAmount(amount)
+ }
+
+ render () {
+ const {
+ amount,
+ amountConversionRate,
+ convertedCurrency,
+ inError,
+ gasTotal,
+ maxModeOn,
+ primaryCurrency = 'ETH',
+ selectedToken,
+ } = this.props
+
+ return (
+
+ !inError && gasTotal &&
+
+
+ );
+ }
+
+}
+
+SendAmountRow.contextTypes = {
+ t: PropTypes.func,
+}
+
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 6ae80e7f2..098855a02 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
@@ -7,42 +7,43 @@ import {
getGasTotal,
getSelectedBalance,
getTokenBalance,
+ getSendFromBalance,
} from '../../send.selectors.js'
import {
getMaxModeOn,
- getSendAmountError,
+ sendAmountIsInError,
} from './send-amount-row.selectors.js'
-import { getAmountErrorObject } from './send-to-row.utils.js'
+import { getAmountErrorObject } from './send-amount-row.utils.js'
import {
- updateSendErrors,
- updateSendTo,
+ updateSendAmount,
+ setMaxModeTo,
} from '../../../actions'
-import {
- openToDropdown,
- closeToDropdown,
-} from '../../../ducks/send'
-import SendToRow from './send-to-row.component'
+import SendAmountRow from './send-amount-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendToRow)
function mapStateToProps (state) {
updateSendTo
return {
- to: getSendTo(state),
- toAccounts: getSendToAccounts(state),
- toDropdownOpen: getToDropdownOpen(state),
- inError: sendToIsInError(state),
- network: getCurrentNetwork(state),
+ 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),
}
}
function mapDispatchToProps (dispatch) {
-return {
- updateSendToError: (to) => {
- dispatch(updateSendErrors(getToErrorObject(to)))
- },
- updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
- openToDropdown: () => dispatch(()),
- closeToDropdown: () => dispatch(()),
-}
+ return {
+ updateSendAmountError: (amountDataObject) => {
+ dispatch(updateSendErrors(getAmountErrorObject(amountDataObject)))
+ },
+ updateSendAmount: newAmount => dispatch(updateSendAmount(newAmount)),
+ setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
+ }
}
\ No newline at end of file
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 e69de29bb..724f345af 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
@@ -0,0 +1,14 @@
+const selectors = {
+ getMaxModeOn,
+ sendAmountIsInError,
+}
+
+module.exports = selectors
+
+function getMaxModeOn (state) {
+ return state.metamask.send.maxModeOn
+}
+
+function sendAmountIsInError (state) {
+ return Boolean(state.metamask.send.errors.amount)
+}
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 e69de29bb..5b01b4594 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
@@ -0,0 +1,55 @@
+const { isValidAddress } = require('../../../../util')
+
+function getAmountErrorObject ({
+ amount,
+ balance,
+ amountConversionRate,
+ conversionRate,
+ primaryCurrency,
+ selectedToken,
+ gasTotal,
+ tokenBalance,
+}) {
+ let insufficientFunds = false
+ if (gasTotal && conversionRate) {
+ insufficientFunds = !isBalanceSufficient({
+ amount: selectedToken ? '0x0' : amount,
+ gasTotal,
+ balance,
+ primaryCurrency,
+ amountConversionRate,
+ conversionRate,
+ })
+ }
+
+ let inSufficientTokens = false
+ if (selectedToken && tokenBalance !== null) {
+ const { decimals } = selectedToken
+ inSufficientTokens = !isTokenBalanceSufficient({
+ tokenBalance,
+ amount,
+ decimals,
+ })
+ }
+
+ const amountLessThanZero = conversionGreaterThan(
+ { value: 0, fromNumericBase: 'dec' },
+ { value: amount, fromNumericBase: 'hex' },
+ )
+
+ let amountError = null
+
+ if (insufficientFunds) {
+ amountError = this.context.t('insufficientFunds')
+ } else if (insufficientTokens) {
+ amountError = this.context.t('insufficientTokens')
+ } else if (amountLessThanZero) {
+ amountError = this.context.t('negativeETH')
+ }
+
+ return { amount: amountError }
+}
+
+module.exports = {
+ getAmountErrorObject
+}
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 e69de29bb..ad6b4a982 100644
--- a/ui/app/components/send_/send-content/send-content.component.js
+++ b/ui/app/components/send_/send-content/send-content.component.js
@@ -0,0 +1,23 @@
+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'
+
+export default class SendContent extends Component {
+
+ render () {
+ return (
+
+
+
+
+
+
+
+
+ );
+ }
+
+}
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 7582cb2e6..b17f749a6 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,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import SendRowWrapper from '../../../send/from-dropdown'
-import FromDropdown from ''
+import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component'
+import FromDropdown from '../../../send/from-dropdown'
export default class SendFromRow extends Component {
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 2ff3f0ccd..eeeb51629 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,6 +1,6 @@
import {
getSendFrom,
- conversionRateSelector,
+ getConversionRate,
getSelectedTokenContract,
getCurrentAccountWithSendEtherInfo,
accountsWithSendEtherInfoSelector,
@@ -23,7 +23,7 @@ function mapStateToProps (state) {
return {
from: getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state),
fromAccounts: accountsWithSendEtherInfoSelector(state),
- conversionRate: conversionRateSelector(state),
+ conversionRate: getConversionRate(state),
fromDropdownOpen: getFromDropdownOpen(state),
tokenContract: getSelectedTokenContract(state),
}
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 e69de29bb..8c1f14f48 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
@@ -0,0 +1,60 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component'
+import GasFeeDisplay from '../../../send/gas-fee-display-v2'
+
+export default class SendGasRow extends Component {
+
+ static propTypes = {
+ closeFromDropdown: PropTypes.func,
+ conversionRate: PropTypes.string,
+ from: PropTypes.string,
+ fromAccounts: PropTypes.array,
+ fromDropdownOpen: PropTypes.bool,
+ openFromDropdown: PropTypes.func,
+ tokenContract: PropTypes.object,
+ updateSendFrom: PropTypes.func,
+ updateSendTokenBalance: PropTypes.func,
+ };
+
+ async handleFromChange (newFrom) {
+ const {
+ updateSendFrom,
+ tokenContract,
+ updateSendTokenBalance,
+ } = this.props
+
+ if (tokenContract) {
+ const usersToken = await tokenContract.balanceOf(newFrom.address)
+ updateSendTokenBalance(usersToken)
+ }
+ updateSendFrom(newFrom)
+ }
+
+ render () {
+ const {
+ conversionRate,
+ convertedCurrency,
+ showCustomizeGasModal,
+ gasTotal,
+ gasLoadingError,
+ } = this.props
+
+ return (
+
+ showCustomizeGasModal()},
+ gasLoadingError={gasLoadingError},
+ />
+
+ );
+ }
+
+}
+
+SendGasRow.contextTypes = {
+ t: PropTypes.func,
+}
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 e69de29bb..7fb3a68be 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
@@ -0,0 +1,26 @@
+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'
+
+export default connect(mapStateToProps, mapDispatchToProps)(SendGasRow)
+
+function mapStateToProps (state) {
+ return {
+ conversionRate: getConversionRate(state),
+ convertedCurrency: getConvertedCurrency(state),
+ gasTotal: getGasTotal(state),
+ gasLoadingError: getGasLoadingError(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ showCustomizeGasModal: () => dispatch(showModal({ name: 'CUSTOMIZE_GAS' })),
+ }
+}
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 e69de29bb..d069ae8c6 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
@@ -0,0 +1,9 @@
+const selectors = {
+ sendGasIsInError,
+}
+
+module.exports = selectors
+
+function sendGasIsInError (state) {
+ return state.send.errors.gasLoading
+}
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 a1ac591b9..92382da01 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,14 +19,23 @@ 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]
+ }
+
return (
{label}
(showError && )
+ {customLabelContent}
- {children}
+ {formField}
);
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 abcb54efc..5f81402d8 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,6 +1,6 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import SendRowWrapper from '../../../send/from-dropdown'
+import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component'
import ToDropdown from '../../../ens-input'
export default class SendToRow extends Component {
@@ -37,7 +37,11 @@ export default class SendToRow extends Component {
} = this.props
return (
-
+