diff --git a/ui/app/actions.js b/ui/app/actions.js
index ad4270cef..aae037a2d 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -167,6 +167,8 @@ var actions = {
UPDATE_MAX_MODE: 'UPDATE_MAX_MODE',
UPDATE_SEND: 'UPDATE_SEND',
CLEAR_SEND: 'CLEAR_SEND',
+ OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN',
+ CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN',
updateGasLimit,
updateGasPrice,
updateGasTotal,
diff --git a/ui/app/components/page-container/page-container-content.component.js b/ui/app/components/page-container/page-container-content.component.js
new file mode 100644
index 000000000..ffd62894c
--- /dev/null
+++ b/ui/app/components/page-container/page-container-content.component.js
@@ -0,0 +1,18 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+
+export default class PageContainerContent extends Component {
+
+ static propTypes = {
+ children: PropTypes.node.isRequired,
+ };
+
+ render () {
+ return (
+
+
+
+ {title}
+
+
+
+ {subtitle}
+
+
+
onClose()}
+ />
+
+
+ );
+ }
+
+}
diff --git a/ui/app/components/page-container/page-container.component.js b/ui/app/components/page-container/page-container.component.js
new file mode 100644
index 000000000..7df1d48d8
--- /dev/null
+++ b/ui/app/components/page-container/page-container.component.js
@@ -0,0 +1,18 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+
+export default class PageContainer extends Component {
+
+ static propTypes = {
+ children: PropTypes.node.isRequired,
+ };
+
+ render () {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+
+}
diff --git a/ui/app/components/page-container/tests/page-container-content-component.test.js b/ui/app/components/page-container/tests/page-container-content-component.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/page-container/tests/page-container-footer-component.test.js b/ui/app/components/page-container/tests/page-container-footer-component.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/page-container/tests/page-container-header-component.test.js b/ui/app/components/page-container/tests/page-container-header-component.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/README.md b/ui/app/components/send_/README.md
new file mode 100644
index 000000000..e69de29bb
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
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..e69de29bb
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..e69de29bb
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
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send-content/send-amount-row/amount-max-button/amount-max-button.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..e69de29bb
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
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
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
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
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..e69de29bb
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..6ae80e7f2
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-amount-row/send-amount-row.container.js
@@ -0,0 +1,48 @@
+import {
+ getSelectedToken,
+ getPrimaryCurrency,
+ getAmountConversionRate,
+ getConvertedCurrency,
+ getSendAmount,
+ getGasTotal,
+ getSelectedBalance,
+ getTokenBalance,
+} from '../../send.selectors.js'
+import {
+ getMaxModeOn,
+ getSendAmountError,
+} from './send-amount-row.selectors.js'
+import { getAmountErrorObject } 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-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
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..e69de29bb
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..e69de29bb
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
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
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
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
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
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..e69de29bb
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
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
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
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
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
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
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..7582cb2e6
--- /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/from-dropdown'
+import FromDropdown from ''
+
+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 (
+
+ this.handleFromChange(newFrom)}
+ openDropdown={() => openFromDropdown()}
+ closeDropdown={() => closeFromDropdown()}
+ conversionRate={conversionRate}
+ />
+
+ );
+ }
+
+}
+
+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..2ff3f0ccd
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-from-row/send-from-row.container.js
@@ -0,0 +1,44 @@
+import {
+ getSendFrom,
+ conversionRateSelector,
+ 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: conversionRateSelector(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
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
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
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
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..e69de29bb
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..e69de29bb
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
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..e69de29bb
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
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
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
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
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
+ ?
{errorMessage}
+ : 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
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
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..a1ac591b9
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-row-wrapper/send-row-wrapper.component.js
@@ -0,0 +1,39 @@
+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
+
+ return (
+
+
+ {label}
+ (showError && )
+
+
+ {children}
+
+
+ );
+ }
+
+}
+
+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
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
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
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..abcb54efc
--- /dev/null
+++ b/ui/app/components/send_/send-content/send-to-row/send-to-row.component.js
@@ -0,0 +1,62 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import SendRowWrapper from '../../../send/from-dropdown'
+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 (
+
+ openToDropdown()}
+ closeDropdown={() => closeToDropdown()}
+ onChange={this.handleToChange}
+ inError={inError}
+ />
+
+ );
+ }
+
+}
+
+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..05bb65fa3
--- /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.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
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
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
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
diff --git a/ui/app/components/send_/send-footer/README.md b/ui/app/components/send_/send-footer/README.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send-footer/send-footer.component.js b/ui/app/components/send_/send-footer/send-footer.component.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send-footer/send-footer.container.js b/ui/app/components/send_/send-footer/send-footer.container.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send-footer/send-footer.scss b/ui/app/components/send_/send-footer/send-footer.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send-footer/send-footer.selectors.js b/ui/app/components/send_/send-footer/send-footer.selectors.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send-footer/send-footer.utils.js b/ui/app/components/send_/send-footer/send-footer.utils.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send-footer/tests/send-footer-component.test.js b/ui/app/components/send_/send-footer/tests/send-footer-component.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send-footer/tests/send-footer-container.test.js b/ui/app/components/send_/send-footer/tests/send-footer-container.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send-footer/tests/send-footer-selectors.test.js b/ui/app/components/send_/send-footer/tests/send-footer-selectors.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send-footer/tests/send-footer-utils.test.js b/ui/app/components/send_/send-footer/tests/send-footer-utils.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send-header/README.md b/ui/app/components/send_/send-header/README.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send-header/send-header.component.js b/ui/app/components/send_/send-header/send-header.component.js
new file mode 100644
index 000000000..99adfc7e8
--- /dev/null
+++ b/ui/app/components/send_/send-header/send-header.component.js
@@ -0,0 +1,32 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import PageContainerHeader from '../../page-container/page-container-header.component'
+
+export default class SendHeader extends Component {
+
+ static propTypes = {
+ isToken: PropTypes.bool,
+ clearSend: PropTypes.func,
+ goHome: PropTypes.func,
+ };
+
+ render () {
+ const { isToken, clearSend, goHome } = this.props
+
+ return (
+
{
+ clearSend()
+ goHome()
+ }}
+ />
+ );
+ }
+
+}
+
+SendHeader.contextTypes = {
+ t: PropTypes.func,
+}
diff --git a/ui/app/components/send_/send-header/send-header.container.js b/ui/app/components/send_/send-header/send-header.container.js
new file mode 100644
index 000000000..a4d3ac54f
--- /dev/null
+++ b/ui/app/components/send_/send-header/send-header.container.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux'
+import { goHome, clearSend } from '../../../actions'
+import SendHeader from './send-header.component'
+import { getSelectedToken } from '../../../selectors'
+
+export default connect(mapStateToProps, mapDispatchToProps)(SendHeader)
+
+function mapStateToProps (state) {
+ return {
+ isToken: Boolean(getSelectedToken(state))
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ goHome: () => dispatch(goHome()),
+ clearSend: () => dispatch(clearSend()),
+ }
+}
diff --git a/ui/app/components/send_/send-header/tests/send-header-component.test.js b/ui/app/components/send_/send-header/tests/send-header-component.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send-header/tests/send-header-container.test.js b/ui/app/components/send_/send-header/tests/send-header-container.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send.component.js b/ui/app/components/send_/send.component.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send.container.js b/ui/app/components/send_/send.container.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send.scss b/ui/app/components/send_/send.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/send.selectors.js b/ui/app/components/send_/send.selectors.js
new file mode 100644
index 000000000..8c088098e
--- /dev/null
+++ b/ui/app/components/send_/send.selectors.js
@@ -0,0 +1,217 @@
+import { valuesFor } from '../../util'
+import abi from 'human-standard-token-abi'
+import {
+ multiplyCurrencies,
+} from './conversion-util'
+
+const selectors = {
+ getSelectedAddress,
+ getSelectedIdentity,
+ getSelectedAccount,
+ getSelectedToken,
+ getSelectedTokenExchangeRate,
+ getTokenExchangeRate,
+ conversionRateSelector,
+ transactionsSelector,
+ accountsWithSendEtherInfoSelector,
+ getCurrentAccountWithSendEtherInfo,
+ getGasPrice,
+ getGasLimit,
+ getForceGasMin,
+ getAddressBook,
+ getSendFrom,
+ getCurrentCurrency,
+ getSendAmount,
+ getSelectedTokenToFiatRate,
+ getSelectedTokenContract,
+ autoAddToBetaUI,
+ getSendMaxModeState,
+ getCurrentViewContext,
+ getSendErrors,
+ getSendTo,
+ getCurrentNetwork,
+}
+
+module.exports = selectors
+
+function getSelectedAddress (state) {
+ const selectedAddress = state.metamask.selectedAddress || Object.keys(state.metamask.accounts)[0]
+
+ return selectedAddress
+}
+
+function getSelectedIdentity (state) {
+ const selectedAddress = getSelectedAddress(state)
+ const identities = state.metamask.identities
+
+ return identities[selectedAddress]
+}
+
+function getSelectedAccount (state) {
+ const accounts = state.metamask.accounts
+ const selectedAddress = getSelectedAddress(state)
+
+ return accounts[selectedAddress]
+}
+
+function getSelectedToken (state) {
+ const tokens = state.metamask.tokens || []
+ const selectedTokenAddress = state.metamask.selectedTokenAddress
+ const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0]
+ const sendToken = state.metamask.send.token
+
+ return selectedToken || sendToken || null
+}
+
+function getSelectedTokenExchangeRate (state) {
+ const tokenExchangeRates = state.metamask.tokenExchangeRates
+ const selectedToken = getSelectedToken(state) || {}
+ const { symbol = '' } = selectedToken
+
+ const pair = `${symbol.toLowerCase()}_eth`
+ const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {}
+
+ return tokenExchangeRate
+}
+
+function getTokenExchangeRate (state, tokenSymbol) {
+ const pair = `${tokenSymbol.toLowerCase()}_eth`
+ const tokenExchangeRates = state.metamask.tokenExchangeRates
+ const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {}
+
+ return tokenExchangeRate
+}
+
+function conversionRateSelector (state) {
+ return state.metamask.conversionRate
+}
+
+function getAddressBook (state) {
+ return state.metamask.addressBook
+}
+
+function accountsWithSendEtherInfoSelector (state) {
+ const {
+ accounts,
+ identities,
+ } = state.metamask
+
+ const accountsWithSendEtherInfo = Object.entries(accounts).map(([key, account]) => {
+ return Object.assign({}, account, identities[key])
+ })
+
+ return accountsWithSendEtherInfo
+}
+
+function getCurrentAccountWithSendEtherInfo (state) {
+ const currentAddress = getSelectedAddress(state)
+ const accounts = accountsWithSendEtherInfoSelector(state)
+
+ return accounts.find(({ address }) => address === currentAddress)
+}
+
+function transactionsSelector (state) {
+ const { network, selectedTokenAddress } = state.metamask
+ const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs)
+ const shapeShiftTxList = (network === '1') ? state.metamask.shapeShiftTxList : undefined
+ const transactions = state.metamask.selectedAddressTxList || []
+ const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList)
+
+ // console.log({txsToRender, selectedTokenAddress})
+ return selectedTokenAddress
+ ? txsToRender
+ .filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress)
+ .sort((a, b) => b.time - a.time)
+ : txsToRender
+ .sort((a, b) => b.time - a.time)
+}
+
+function getGasPrice (state) {
+ return state.metamask.send.gasPrice
+}
+
+function getGasLimit (state) {
+ return state.metamask.send.gasLimit
+}
+
+function getForceGasMin (state) {
+ return state.metamask.send.forceGasMin
+}
+
+function getSendFrom (state) {
+ return state.metamask.send.from
+}
+
+function getSendAmount (state) {
+ return state.metamask.send.amount
+}
+
+function getSendMaxModeState (state) {
+ return state.metamask.send.maxModeOn
+}
+
+function getCurrentCurrency (state) {
+ return state.metamask.currentCurrency
+}
+
+function getSelectedTokenToFiatRate (state) {
+ const selectedTokenExchangeRate = getSelectedTokenExchangeRate(state)
+ const conversionRate = conversionRateSelector(state)
+
+ const tokenToFiatRate = multiplyCurrencies(
+ conversionRate,
+ selectedTokenExchangeRate,
+ { toNumericBase: 'dec' }
+ )
+
+ return tokenToFiatRate
+}
+
+function getSelectedTokenContract (state) {
+ const selectedToken = getSelectedToken(state)
+ return selectedToken
+ ? global.eth.contract(abi).at(selectedToken.address)
+ : null
+}
+
+function autoAddToBetaUI (state) {
+ const autoAddTransactionThreshold = 12
+ const autoAddAccountsThreshold = 2
+ const autoAddTokensThreshold = 1
+
+ const numberOfTransactions = state.metamask.selectedAddressTxList.length
+ const numberOfAccounts = Object.keys(state.metamask.accounts).length
+ const numberOfTokensAdded = state.metamask.tokens.length
+
+ const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) &&
+ (numberOfAccounts > autoAddAccountsThreshold) &&
+ (numberOfTokensAdded > autoAddTokensThreshold)
+ const userIsNotInBeta = !state.metamask.featureFlags.betaUI
+
+ return userIsNotInBeta && userPassesThreshold
+}
+
+function getCurrentViewContext (state) {
+ const { currentView = {} } = state.appState
+ return currentView.context
+}
+
+function getSendErrors (state) {
+ return state.metamask.send.errors
+}
+
+function getSendTo (state) {
+ return state.metamask.send.to
+}
+
+function getSendToAccounts (state) {
+ const fromAccounts = accountsWithSendEtherInfoSelector(state)
+ const addressBookAccounts = getAddressBook(state)
+ const allAccounts = [...fromAccounts, ...addressBookAccounts]
+ // TODO: figure out exactly what the below returns and put a descriptive variable name on it
+ return Object.entries(allAccounts).map(([key, account]) => account)
+}
+
+function getCurrentNetwork (state) {
+ return state.metamask.network
+}
\ No newline at end of file
diff --git a/ui/app/components/send_/send.utils.js b/ui/app/components/send_/send.utils.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/tests/send-component.test.js b/ui/app/components/send_/tests/send-component.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/tests/send-container.test.js b/ui/app/components/send_/tests/send-container.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/tests/send-selectors.test.js b/ui/app/components/send_/tests/send-selectors.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/components/send_/tests/send-utils.test.js b/ui/app/components/send_/tests/send-utils.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/app/ducks/send.js b/ui/app/ducks/send.js
new file mode 100644
index 000000000..aeca9f92f
--- /dev/null
+++ b/ui/app/ducks/send.js
@@ -0,0 +1,54 @@
+import extend from 'xtend'
+
+// Actions
+const OPEN_FROM_DROPDOWN = 'metamask/send/OPEN_FROM_DROPDOWN';
+const CLOSE_FROM_DROPDOWN = 'metamask/send/CLOSE_FROM_DROPDOWN';
+const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN';
+const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN';
+
+// TODO: determine if this approach to initState is consistent with conventional ducks pattern
+const initState = {
+ fromDropdownOpen: false,
+ toDropdownOpen: false,
+}
+
+// Reducer
+export default function reducer(state = initState, action = {}) {
+ switch (action.type) {
+ case OPEN_FROM_DROPDOWN:
+ return extend(sendState, {
+ fromDropdownOpen: true,
+ })
+ case CLOSE_FROM_DROPDOWN:
+ return extend(sendState, {
+ fromDropdownOpen: false,
+ })
+ case OPEN_TO_DROPDOWN:
+ return extend(sendState, {
+ toDropdownOpen: true,
+ })
+ case CLOSE_TO_DROPDOWN:
+ return extend(sendState, {
+ toDropdownOpen: false,
+ })
+ default:
+ return sendState
+ }
+}
+
+// Action Creators
+export function openFromDropdown() {
+ return { type: OPEN_FROM_DROPDOWN };
+}
+
+export function closeFromDropdown() {
+ return { type: CLOSE_FROM_DROPDOWN };
+}
+
+export function openToDropdown() {
+ return { type: OPEN_TO_DROPDOWN };
+}
+
+export function closeToDropdown() {
+ return { type: CLOSE_TO_DROPDOWN };
+}
\ No newline at end of file
diff --git a/ui/app/reducers.js b/ui/app/reducers.js
index f155b2bf3..ff766e856 100644
--- a/ui/app/reducers.js
+++ b/ui/app/reducers.js
@@ -8,6 +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')
window.METAMASK_CACHED_LOG_STATE = null
@@ -45,6 +46,12 @@ function rootReducer (state, action) {
state.localeMessages = reduceLocale(state, action)
+ //
+ // Send
+ //
+
+ state.send = reduceSend(state, action)
+
window.METAMASK_CACHED_LOG_STATE = state
return state
}
diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js
index 094743ff0..d608957c8 100644
--- a/ui/app/send-v2.js
+++ b/ui/app/send-v2.js
@@ -31,6 +31,11 @@ const {
} = require('./components/send/send-utils')
const { isValidAddress } = require('./util')
+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'
+
SendTransactionScreen.contextTypes = {
t: PropTypes.func,
}
@@ -181,25 +186,6 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) {
}
}
-SendTransactionScreen.prototype.renderHeader = function () {
- const { selectedToken, clearSend, goHome } = 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()
- goHome()
- },
- }),
-
- ])
-}
-
SendTransactionScreen.prototype.renderErrorMessage = function (errorType) {
const { errors } = this.props
const errorMessage = errors[errorType]
@@ -477,7 +463,7 @@ SendTransactionScreen.prototype.renderMemoRow = function () {
}
SendTransactionScreen.prototype.renderForm = function () {
- return h('.page-container__content', {}, [
+ return h(PageContainerContent, [
h('.send-v2__form', [
this.renderFromRow(),
@@ -486,9 +472,6 @@ SendTransactionScreen.prototype.renderForm = function () {
this.renderAmountRow(),
this.renderGasRow(),
-
- // this.renderMemoRow(),
-
]),
])
}
@@ -506,26 +489,22 @@ SendTransactionScreen.prototype.renderFooter = function () {
const missingTokenBalance = selectedToken && !tokenBalance
const noErrors = !amountError && toError === null
- return h('div.page-container__footer', [
- h('button.btn-secondary--lg.page-container__footer-button', {
- onClick: () => {
- clearSend()
- goHome()
- },
- }, this.context.t('cancel')),
- h('button.btn-primary--lg.page-container__footer-button', {
- disabled: !noErrors || !gasTotal || missingTokenBalance,
- onClick: event => this.onSubmit(event),
- }, this.context.t('next')),
- ])
+ return h(PageContainerFooter, {
+ onCancel: () => {
+ clearSend()
+ goHome()
+ },
+ onSubmit: e => this.onSubmit(e),
+ disabled: !noErrors || !gasTotal || missingTokenBalance,
+ })
}
SendTransactionScreen.prototype.render = function () {
return (
- h('div.page-container', [
+ h(PageContainer, [
- this.renderHeader(),
+ h(SendHeader),
this.renderForm(),