aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/components/send_/send-content
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app/components/send_/send-content')
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/README.md0
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.component.js54
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.container.js36
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js22
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.component.js91
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js49
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.scss0
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.selectors.js14
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/send-amount-row.utils.js55
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-selectors.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-utils.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-content-README.md0
-rw-r--r--ui/app/components/send_/send-content/send-content.component.js23
-rw-r--r--ui/app/components/send_/send-content/send-content.scss0
-rw-r--r--ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown-README.md0
-rw-r--r--ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js0
-rw-r--r--ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.scss0
-rw-r--r--ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-from-row/send-from-row-README.md0
-rw-r--r--ui/app/components/send_/send-content/send-from-row/send-from-row.component.js64
-rw-r--r--ui/app/components/send_/send-content/send-from-row/send-from-row.container.js44
-rw-r--r--ui/app/components/send_/send-content/send-from-row/send-from-row.selectors.js9
-rw-r--r--ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js12
-rw-r--r--ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-from-row/tests/send-from-row-selectors.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/README.md0
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.component.js60
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.container.js26
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.scss0
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/send-gas-row.selectors.js9
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md0
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js23
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js11
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss0
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper-README.md0
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js48
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.scss0
-rw-r--r--ui/app/components/send_/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row-README.md0
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row.component.js66
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row.container.js43
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js14
-rw-r--r--ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js17
-rw-r--r--ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js0
-rw-r--r--ui/app/components/send_/send-content/send-to-row/tests/send-to-row-selectors.test.js0
-rw-r--r--ui/app/components/send_/send-content/tests/send-content-component.test.js0
57 files changed, 790 insertions, 0 deletions
diff --git a/ui/app/components/send_/send-content/send-amount-row/README.md b/ui/app/components/send_/send-content/send-amount-row/README.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-amount-row/README.md
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
new file mode 100644
index 000000000..59a1fd6db
--- /dev/null
+++ 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 (
+ <div
+ className='send-v2__amount-max'
+ onClick={(event) => {
+ event.preventDefault()
+ setMaxModeTo(true)
+ this.setAmountToMax()
+ }}
+ >
+ {!maxModeOn ? this.context.t('max') : '' ])}
+ </div>
+ );
+ }
+
+}
+
+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
new file mode 100644
index 000000000..572e1fc46
--- /dev/null
+++ 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.utils.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
new file mode 100644
index 000000000..54aacc8d7
--- /dev/null
+++ 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/amount-max-button/tests/amount-max-button-component.test.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js
diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js
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
new file mode 100644
index 000000000..78038f714
--- /dev/null
+++ 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 (
+ <SendRowWrapper
+ label={`${this.context.t('amount')}:`}
+ showError={inError}
+ errorType={'amount'}
+ >
+ !inError && gasTotal && <AmountMaxButton />
+ <CurrencyDisplay
+ inError={inError},
+ primaryCurrency={primaryCurrency},
+ convertedCurrency={convertedCurrency},
+ selectedToken={selectedToken},
+ value={amount || '0x0'},
+ conversionRate={amountConversionRate},
+ handleChange={this.handleAmountChange},
+ >
+ </SendRowWrapper>
+ );
+ }
+
+}
+
+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
new file mode 100644
index 000000000..098855a02
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js
@@ -0,0 +1,49 @@
+import {
+ getSelectedToken,
+ getPrimaryCurrency,
+ getAmountConversionRate,
+ getConvertedCurrency,
+ getSendAmount,
+ getGasTotal,
+ getSelectedBalance,
+ getTokenBalance,
+ getSendFromBalance,
+} from '../../send.selectors.js'
+import {
+ getMaxModeOn,
+ sendAmountIsInError,
+} from './send-amount-row.selectors.js'
+import { getAmountErrorObject } from './send-amount-row.utils.js'
+import {
+ updateSendAmount,
+ setMaxModeTo,
+} from '../../../actions'
+import SendAmountRow from './send-amount-row.component'
+
+export default connect(mapStateToProps, mapDispatchToProps)(SendToRow)
+
+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),
+}
+}
+
+function mapDispatchToProps (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.scss b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.scss
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.scss
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
new file mode 100644
index 000000000..724f345af
--- /dev/null
+++ 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
new file mode 100644
index 000000000..5b01b4594
--- /dev/null
+++ 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-amount-row/tests/send-amount-row-component.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-component.test.js
diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-container.test.js
diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-selectors.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-selectors.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-selectors.test.js
diff --git a/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-utils.test.js b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-utils.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-amount-row/tests/send-amount-row-utils.test.js
diff --git a/ui/app/components/send_/send-content/send-content-README.md b/ui/app/components/send_/send-content/send-content-README.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-content-README.md
diff --git a/ui/app/components/send_/send-content/send-content.component.js b/ui/app/components/send_/send-content/send-content.component.js
new file mode 100644
index 000000000..ad6b4a982
--- /dev/null
+++ 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 (
+ <PageContainerContent>
+ <div className='.send-v2__form'>
+ <SendFromRow />
+ <SendToRow />
+ <SendAmountRow />
+ <SendGasRow />
+ </div>
+ </PageContainerContent>
+ );
+ }
+
+}
diff --git a/ui/app/components/send_/send-content/send-content.scss b/ui/app/components/send_/send-content/send-content.scss
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-content.scss
diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown-README.md b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown-README.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown-README.md
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
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.component.js
diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.scss b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.scss
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/from-dropdown.scss
diff --git a/ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js b/ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-from-row/from-dropdown/tests/from-dropdown-component.test.js
diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row-README.md b/ui/app/components/send_/send-content/send-from-row/send-from-row-README.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-from-row/send-from-row-README.md
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
new file mode 100644
index 000000000..b17f749a6
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.component.js
@@ -0,0 +1,64 @@
+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'
+
+export default class SendFromRow 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 {
+ from,
+ fromAccounts,
+ conversionRate,
+ fromDropdownOpen,
+ tokenContract,
+ openFromDropdown,
+ closeFromDropdown,
+ } = this.props
+
+ return (
+ <SendRowWrapper label={`${this.context.t('from')}:`}>
+ <FromDropdown
+ dropdownOpen={fromDropdownOpen}
+ accounts={fromAccounts}
+ selectedAccount={from}
+ onSelect={newFrom => this.handleFromChange(newFrom)}
+ openDropdown={() => openFromDropdown()}
+ closeDropdown={() => closeFromDropdown()}
+ conversionRate={conversionRate}
+ />
+ </SendRowWrapper>
+ );
+ }
+
+}
+
+SendFromRow.contextTypes = {
+ t: PropTypes.func,
+}
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
new file mode 100644
index 000000000..eeeb51629
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js
@@ -0,0 +1,44 @@
+import {
+ getSendFrom,
+ getConversionRate,
+ getSelectedTokenContract,
+ getCurrentAccountWithSendEtherInfo,
+ accountsWithSendEtherInfoSelector,
+} from '../../send.selectors.js'
+import { getFromDropdownOpen } from './send-from-row.selectors.js'
+import { calcTokenUpdateAmount } from './send-from-row.utils.js'
+import {
+ updateSendTokenBalance,
+ updateSendFrom,
+} from '../../../actions'
+import {
+ openFromDropdown,
+ closeFromDropdown,
+} from '../../../ducks/send'
+import SendFromRow from './send-from-row.component'
+
+export default connect(mapStateToProps, mapDispatchToProps)(SendFromRow)
+
+function mapStateToProps (state) {
+ return {
+ from: getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state),
+ fromAccounts: accountsWithSendEtherInfoSelector(state),
+ conversionRate: getConversionRate(state),
+ fromDropdownOpen: getFromDropdownOpen(state),
+ tokenContract: getSelectedTokenContract(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ updateSendTokenBalance: (usersToken, selectedToken) => {
+ if (!usersToken) return
+
+ const tokenBalance = calcTokenUpdateAmount(selectedToken, selectedToken)
+ dispatch(updateSendTokenBalance(tokenBalance))
+ },
+ updateSendFrom: newFrom => dispatch(updateSendFrom(newFrom)),
+ openFromDropdown: () => dispatch(()),
+ closeFromDropdown: () => dispatch(()),
+ }
+}
diff --git a/ui/app/components/send_/send-content/send-from-row/send-from-row.selectors.js b/ui/app/components/send_/send-content/send-from-row/send-from-row.selectors.js
new file mode 100644
index 000000000..03ef4806b
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.selectors.js
@@ -0,0 +1,9 @@
+const selectors = {
+ getFromDropdownOpen,
+}
+
+module.exports = selectors
+
+function getFromDropdownOpen (state) {
+ return state.send.fromDropdownOpen
+}
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
new file mode 100644
index 000000000..2be25816f
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.utils.js
@@ -0,0 +1,12 @@
+const {
+ calcTokenAmount,
+} = require('../../token-util')
+
+function calcTokenUpdateAmount (usersToken, selectedToken) {
+ const { decimals } = selectedToken || {}
+ return calcTokenAmount(usersToken.balance.toString(), decimals)
+}
+
+module.exports = {
+ calcTokenUpdateAmount
+}
diff --git a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-component.test.js
diff --git a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-container.test.js
diff --git a/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-selectors.test.js b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-selectors.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-from-row/tests/send-from-row-selectors.test.js
diff --git a/ui/app/components/send_/send-content/send-gas-row/README.md b/ui/app/components/send_/send-content/send-gas-row/README.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-gas-row/README.md
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
new file mode 100644
index 000000000..8c1f14f48
--- /dev/null
+++ 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 (
+ <SendRowWrapper label={`${this.context.t('gasFee')}:`}>
+ <GasFeeDisplay
+ gasTotal={gasTotal},
+ conversionRate={conversionRate},
+ convertedCurrency={convertedCurrency},
+ onClick={() => showCustomizeGasModal()},
+ gasLoadingError={gasLoadingError},
+ />
+ </SendRowWrapper>
+ );
+ }
+
+}
+
+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
new file mode 100644
index 000000000..7fb3a68be
--- /dev/null
+++ 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.scss b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.scss
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-gas-row/send-gas-row.scss
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
new file mode 100644
index 000000000..d069ae8c6
--- /dev/null
+++ 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-gas-row/tests/send-gas-row-component.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-component.test.js
diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-container.test.js
diff --git a/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md
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
new file mode 100644
index 000000000..08f830cc5
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
@@ -0,0 +1,23 @@
+export default class SendRowErrorMessage extends Component {
+
+ static propTypes = {
+ errors: PropTypes.object,
+ errorType: PropTypes.string,
+ };
+
+ render () {
+ const { errors, errorType } = this.props
+ const errorMessage = errors[errorType]
+
+ return (
+ errorMessage
+ ? <div className='send-v2__error'>{errorMessage}</div>
+ : null
+ );
+ }
+
+}
+
+SendRowErrorMessage.contextTypes = {
+ t: PropTypes.func,
+} \ No newline at end of file
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
new file mode 100644
index 000000000..2278dbe63
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js
@@ -0,0 +1,11 @@
+import { getSendErrors } from '../../../send.selectors'
+import SendRowErrorMessage from './send-row-error-message.component'
+
+export default connect(mapStateToProps)(SendRowErrorMessage)
+
+function mapStateToProps (state, ownProps) {
+ return {
+ errors: getSendErrors(state),
+ errorType: ownProps.errorType,
+ }
+} \ No newline at end of file
diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss
diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper-README.md b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper-README.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper-README.md
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
new file mode 100644
index 000000000..92382da01
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js
@@ -0,0 +1,48 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import SendRowErrorMessage from './send-row-error-message/send-row-error-message.container'
+
+export default class SendRowWrapper extends Component {
+
+ static propTypes = {
+ label: PropTypes.string,
+ showError: PropTypes.bool,
+ children: PropTypes.node,
+ errorType: PropTypes.string,
+ };
+
+ render () {
+ const {
+ label,
+ errorType = '',
+ showError = false,
+ children,
+ } = this.props
+
+ let formField = children[0]
+ let customLabelContent = null
+
+ if (children.length === 2) {
+ formField = children[1]
+ customLabelContent = children[0]
+ }
+
+ return (
+ <div className="send-v2__form-row">
+ <div className="send-v2__form-label">
+ {label}
+ (showError && <SendRowErrorMessage errorType={errorType}/>)
+ {customLabelContent}
+ </div>
+ <div className="send-v2__form-field">
+ {formField}
+ </div>
+ </div>
+ );
+ }
+
+}
+
+SendRowWrapper.contextTypes = {
+ t: PropTypes.func,
+}
diff --git a/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.scss b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.scss
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.scss
diff --git a/ui/app/components/send_/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js b/ui/app/components/send_/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js
diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row-README.md b/ui/app/components/send_/send-content/send-to-row/send-to-row-README.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-to-row/send-to-row-README.md
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
new file mode 100644
index 000000000..5f81402d8
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js
@@ -0,0 +1,66 @@
+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'
+
+export default class SendToRow extends Component {
+
+ static propTypes = {
+ to: PropTypes.string,
+ toAccounts: PropTypes.array,
+ toDropdownOpen: PropTypes.bool,
+ inError: PropTypes.bool,
+ updateSendTo: PropTypes.func,
+ updateSendToError: PropTypes.func,
+ openToDropdown: PropTypes.func,
+ closeToDropdown: PropTypes.func,
+ network: PropTypes.number,
+ };
+
+ handleToChange (to, nickname = '') {
+ const { updateSendTo, updateSendToError } = this.props
+ updateSendTo(to, nickname)
+ updateSendErrors(to)
+ }
+
+ render () {
+ const {
+ from,
+ fromAccounts,
+ conversionRate,
+ fromDropdownOpen,
+ tokenContract,
+ openToDropdown,
+ closeToDropdown,
+ network,
+ inError,
+ } = this.props
+
+ return (
+ <SendRowWrapper
+ label={`${this.context.t('to')}:`}
+ showError={inError}
+ errorType={'to'}
+ >
+ <EnsInput
+ name={'address'}
+ placeholder={this.context.t('recipient Address')}
+ network={network},
+ to={to},
+ accounts={toAccounts}
+ dropdownOpen={toDropdownOpen}
+ openDropdown={() => openToDropdown()}
+ closeDropdown={() => closeToDropdown()}
+ onChange={this.handleToChange}
+ inError={inError}
+ />
+ </SendRowWrapper>
+ );
+ }
+
+}
+
+SendToRow.contextTypes = {
+ t: PropTypes.func,
+}
+
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
new file mode 100644
index 000000000..1c446c168
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.container.js
@@ -0,0 +1,43 @@
+import {
+ getSendTo,
+ getToAccounts,
+ getCurrentNetwork,
+} from '../../send.selectors.js'
+import {
+ getToDropdownOpen,
+ sendToIsInError,
+} from './send-to-row.selectors.js'
+import { getToErrorObject } from './send-to-row.utils.js'
+import {
+ updateSendErrors,
+ updateSendTo,
+} from '../../../actions'
+import {
+ openToDropdown,
+ closeToDropdown,
+} from '../../../ducks/send'
+import SendToRow from './send-to-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),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ updateSendToError: (to) => {
+ dispatch(updateSendErrors(getToErrorObject(to)))
+ },
+ updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
+ openToDropdown: () => dispatch(()),
+ closeToDropdown: () => dispatch(()),
+ }
+} \ No newline at end of file
diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js
new file mode 100644
index 000000000..c741aad84
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.selectors.js
@@ -0,0 +1,14 @@
+const selectors = {
+ getToDropdownOpen,
+ sendToIsInError,
+}
+
+module.exports = selectors
+
+function getToDropdownOpen (state) {
+ return state.send.toDropdownOpen
+}
+
+function sendToIsInError (state) {
+ return Boolean(state.metamask.send.errors.to)
+}
diff --git a/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js
new file mode 100644
index 000000000..52bfde009
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.utils.js
@@ -0,0 +1,17 @@
+const { isValidAddress } = require('../../../../util')
+
+function getToErrorObject (to) {
+ let toError = null
+
+ if (!to) {
+ toError = 'required'
+ } else if (!isValidAddress(to)) {
+ toError = 'invalidAddressRecipient'
+ }
+
+ return { to: toError }
+}
+
+module.exports = {
+ getToErrorObject
+}
diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-component.test.js
diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-container.test.js
diff --git a/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-selectors.test.js b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-selectors.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-to-row/tests/send-to-row-selectors.test.js
diff --git a/ui/app/components/send_/send-content/tests/send-content-component.test.js b/ui/app/components/send_/send-content/tests/send-content-component.test.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/app/components/send_/send-content/tests/send-content-component.test.js