1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Add token selection to the send screen (#6445)

* Move send to pages/

* Fix unit tests

* Finish UI

* Integrate asset dropdown to send actions

* Remove console.log

* Hide asset change during edit

* Enable switch from send token to seand eth

* Enable switching from token to eth when editing

* Fix linter

* Fixing test

* Fix unit tests

* Fix linter

* Fix react warning; remove console.log

* fix flat test

* Add metrics

* Address code review comments

* Consistent spacing between send screen form rows.

* Reduce height of gas buttons on send screen.

* Make send screen gas button height dependent on size of contents.
This commit is contained in:
Chi Kei Chan 2019-04-17 12:15:13 -07:00 committed by Dan J Miller
parent a844eb20da
commit 931aaeb700
137 changed files with 444 additions and 134 deletions

View File

@ -139,6 +139,9 @@
"approved": { "approved": {
"message": "Approved" "message": "Approved"
}, },
"asset": {
"message": "Asset"
},
"attemptingConnect": { "attemptingConnect": {
"message": "Attempting to connect to blockchain." "message": "Attempting to connect to blockchain."
}, },
@ -1351,6 +1354,9 @@
"selectAnAccountHelp": { "selectAnAccountHelp": {
"message": "Select the account to view in MetaMask" "message": "Select the account to view in MetaMask"
}, },
"selectAnAsset": {
"message": "Select an Asset"
},
"selectAHigherGasFee": { "selectAHigherGasFee": {
"message": "Select a higher gas fee to accelerate the processing of your transaction.*" "message": "Select a higher gas fee to accelerate the processing of your transaction.*"
}, },

View File

@ -72,7 +72,7 @@ async function runSendFlowTest (assert, done) {
const sendToAccountAddress = sendToFieldInput.val() const sendToAccountAddress = sendToFieldInput.val()
assert.equal(sendToAccountAddress, '0x2f8D4a878cFA04A6E60D46362f5644DeAb66572D', 'send to dropdown selects the correct address') assert.equal(sendToAccountAddress, '0x2f8D4a878cFA04A6E60D46362f5644DeAb66572D', 'send to dropdown selects the correct address')
const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)') const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(3)')
sendAmountField.find('.unit-input')[0].click() sendAmountField.find('.unit-input')[0].click()
const sendAmountFieldInput = await findAsync(sendAmountField, '.unit-input__input') const sendAmountFieldInput = await findAsync(sendAmountField, '.unit-input__input')
@ -115,7 +115,7 @@ async function runSendFlowTest (assert, done) {
sendToFieldInputInEdit[0].focus() sendToFieldInputInEdit[0].focus()
sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb') sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb')
const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)') const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(3)')
sendAmountFieldInEdit.find('.unit-input')[0].click() sendAmountFieldInEdit.find('.unit-input')[0].click()
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.unit-input__input') const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.unit-input__input')

View File

@ -18,11 +18,11 @@ const {
MIN_GAS_PRICE_DEC, MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT_DEC, MIN_GAS_LIMIT_DEC,
MIN_GAS_PRICE_GWEI, MIN_GAS_PRICE_GWEI,
} = require('../send/send.constants') } = require('../../../pages/send/send.constants')
const { const {
isBalanceSufficient, isBalanceSufficient,
} = require('../send/send.utils') } = require('../../../pages/send/send.utils')
const { const {
conversionUtil, conversionUtil,
@ -47,7 +47,7 @@ const {
const { const {
getGasPrice, getGasPrice,
getGasLimit, getGasLimit,
} = require('../send/send.selectors') } = require('../../../pages/send/send.selectors')
function mapStateToProps (state) { function mapStateToProps (state) {
const selectedToken = getSelectedToken(state) const selectedToken = getSelectedToken(state)

View File

@ -10,7 +10,7 @@ const networkMap = require('ethjs-ens/lib/network-map.json')
const ensRE = /.+\..+$/ const ensRE = /.+\..+$/
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const connect = require('react-redux').connect const connect = require('react-redux').connect
const ToAutoComplete = require('./send/to-autocomplete').default const ToAutoComplete = require('../../pages/send/to-autocomplete').default
const log = require('loglevel') const log = require('loglevel')
const { isValidENSAddress } = require('../../helpers/utils/util') const { isValidENSAddress } = require('../../helpers/utils/util')

View File

@ -63,7 +63,7 @@ import {
import { import {
calcGasTotal, calcGasTotal,
isBalanceSufficient, isBalanceSufficient,
} from '../../send/send.utils' } from '../../../../pages/send/send.utils'
import { addHexPrefix } from 'ethereumjs-util' import { addHexPrefix } from 'ethereumjs-util'
import { getAdjacentGasPrices, extrapolateY } from '../gas-price-chart/gas-price-chart.utils' import { getAdjacentGasPrices, extrapolateY } from '../gas-price-chart/gas-price-chart.utils'

View File

@ -99,15 +99,13 @@
} }
&__loading-container { &__loading-container {
height: 78px; height: 54px;
} }
.button-group__button, .button-group__button--active { .button-group__button, .button-group__button--active {
height: 78px;
background: white; background: white;
color: $scorpion; color: $scorpion;
padding-top: 9px; padding: 2px 8.5px 4px 8.5px;
padding-left: 8.5px;
@media screen and (max-width: $break-small) { @media screen and (max-width: $break-small) {
padding-left: 4px; padding-left: 4px;

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import GasModalCard from '../../customize-gas-modal/gas-modal-card' import GasModalCard from '../../customize-gas-modal/gas-modal-card'
import { MIN_GAS_PRICE_GWEI } from '../../send/send.constants' import { MIN_GAS_PRICE_GWEI } from '../../../../pages/send/send.constants'
import Button from '../../../ui/button' import Button from '../../../ui/button'
import { import {

View File

@ -15,7 +15,7 @@ import {
setCustomGasLimit, setCustomGasLimit,
} from '../../../ducks/gas/gas.duck' } from '../../../ducks/gas/gas.duck'
import { getIsMainnet, preferencesSelector, getSelectedAddress, conversionRateSelector } from '../../../selectors/selectors' import { getIsMainnet, preferencesSelector, getSelectedAddress, conversionRateSelector } from '../../../selectors/selectors'
import { isBalanceSufficient } from '../send/send.utils' import { isBalanceSufficient } from '../../../pages/send/send.utils'
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state, ownProps) => {
const { metamask: { knownMethodData, accounts } } = state const { metamask: { knownMethodData, accounts } } = state

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import AccountListItem from '../../app/send/account-list-item/account-list-item.component' import AccountListItem from '../../../pages/send/account-list-item/account-list-item.component'
export default class AccountDropdownMini extends PureComponent { export default class AccountDropdownMini extends PureComponent {
static propTypes = { static propTypes = {

View File

@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert' import assert from 'assert'
import { shallow } from 'enzyme' import { shallow } from 'enzyme'
import AccountDropdownMini from '../account-dropdown-mini.component' import AccountDropdownMini from '../account-dropdown-mini.component'
import AccountListItem from '../../../app/send/account-list-item/account-list-item.component' import AccountListItem from '../../../../pages/send/account-list-item/account-list-item.component'
describe('AccountDropdownMini', () => { describe('AccountDropdownMini', () => {
it('should render an account with an icon', () => { it('should render an account with an icon', () => {

View File

@ -1,7 +1,7 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import { removeLeadingZeroes } from '../../app/send/send.utils' import { removeLeadingZeroes } from '../../../pages/send/send.utils'
/** /**
* Component that attaches a suffix or unit of measurement trailing user input, ex. 'ETH'. Also * Component that attaches a suffix or unit of measurement trailing user input, ex. 'ETH'. Also

View File

@ -549,7 +549,7 @@
} }
&__form-row { &__form-row {
margin: 14.5px 18px 0px; margin: 8px 18px 0px;
position: relative; position: relative;
display: flex; display: flex;
flex-flow: row; flex-flow: row;
@ -592,8 +592,8 @@
flex: 0 0 auto; flex: 0 0 auto;
} }
&__from-dropdown { &__from-dropdown,
height: 73px; &__asset-dropdown {
width: 100%; width: 100%;
border: 1px solid $alto; border: 1px solid $alto;
border-radius: 4px; border-radius: 4px;
@ -628,6 +628,104 @@
} }
} }
&__from-dropdown {
height: 73px;
}
&__asset-dropdown {
height: 62px;
border: none;
&__asset {
display: flex;
flex-flow: row nowrap;
align-items: center;
padding: 10px 8px;
cursor: pointer;
&:hover {
background-color: rgba($alto, 0.2);
}
}
&__asset-icon {
.identicon {
border: 1px solid $alto;
}
}
&__asset-data {
display: flex;
flex-flow: column nowrap;
margin-left: 8px;
}
&__symbol {
font-size: 16px;
margin-bottom: 2px;
}
&__name {
display: flex;
flex-flow: row nowrap;
font-size: 12px;
&__label {
margin-right: .25rem;
}
}
&__close-area {
z-index: 2000;
}
&__list {
z-index: 2050;
position: absolute;
height: 220px;
width: 100%;
border: 1px solid $geyser;
border-radius: 4px;
background-color: $white;
box-shadow: 0 3px 6px 0 rgba(0 ,0 ,0 ,.11);
top: 55px;
left: 0;
box-sizing: content-box;
overflow-y: scroll;
}
&__input-wrapper {
border: 1px solid $alto;
border-radius: 4px;
&--opened {
position: relative;
z-index: 2050;
}
.send-v2__asset-dropdown__asset {
&:hover {
background-color: $white;
}
}
}
&__input {
z-index: 1025;
position: relative;
height: 54px;
width: 100%;
border: none;
border-radius: 4px;
background-color: $white;
color: $tundora;
padding: 10px;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
}
}
&__to-autocomplete { &__to-autocomplete {
position: relative; position: relative;

View File

@ -154,9 +154,26 @@ function reduceMetamask (state, action) {
return newState return newState
case actions.SET_SELECTED_TOKEN: case actions.SET_SELECTED_TOKEN:
return extend(metamaskState, { newState = extend(metamaskState, {
selectedTokenAddress: action.value, selectedTokenAddress: action.value,
}) })
const newSend = extend(metamaskState.send)
if (metamaskState.send.editingTransactionId && !action.value) {
delete newSend.token
const unapprovedTx = newState.unapprovedTxs[newSend.editingTransactionId] || {}
const txParams = unapprovedTx.txParams || {}
newState.unapprovedTxs = extend(newState.unapprovedTxs, {
[newSend.editingTransactionId]: extend(unapprovedTx, {
txParams: extend(txParams, { data: '' }),
}),
})
newSend.tokenBalance = null
newSend.balance = '0'
}
newState.send = newSend
return newState
case actions.SET_ACCOUNT_LABEL: case actions.SET_ACCOUNT_LABEL:
const account = action.value.account const account = action.value.account

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums' import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util' import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import ConfirmPageContainer, { ConfirmDetailRow } from '../../components/app/confirm-page-container' import ConfirmPageContainer, { ConfirmDetailRow } from '../../components/app/confirm-page-container'
import { isBalanceSufficient } from '../../components/app/send/send.utils' import { isBalanceSufficient } from '../send/send.utils'
import { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } from '../../helpers/constants/routes' import { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } from '../../helpers/constants/routes'
import { import {
INSUFFICIENT_FUNDS_ERROR_KEY, INSUFFICIENT_FUNDS_ERROR_KEY,

View File

@ -14,9 +14,9 @@ import {
GAS_LIMIT_TOO_LOW_ERROR_KEY, GAS_LIMIT_TOO_LOW_ERROR_KEY,
} from '../../helpers/constants/error-keys' } from '../../helpers/constants/error-keys'
import { getHexGasTotal } from '../../helpers/utils/confirm-tx.util' import { getHexGasTotal } from '../../helpers/utils/confirm-tx.util'
import { isBalanceSufficient, calcGasTotal } from '../../components/app/send/send.utils' import { isBalanceSufficient, calcGasTotal } from '../send/send.utils'
import { conversionGreaterThan } from '../../helpers/utils/conversion-util' import { conversionGreaterThan } from '../../helpers/utils/conversion-util'
import { MIN_GAS_LIMIT_DEC } from '../../components/app/send/send.constants' import { MIN_GAS_LIMIT_DEC } from '../send/send.constants'
import { checksumAddress, addressSlicer, valuesFor } from '../../helpers/utils/util' import { checksumAddress, addressSlicer, valuesFor } from '../../helpers/utils/util'
import {getMetaMaskAccounts, getAdvancedInlineGasShown, preferencesSelector, getIsMainnet} from '../../selectors/selectors' import {getMetaMaskAccounts, getAdvancedInlineGasShown, preferencesSelector, getIsMainnet} from '../../selectors/selectors'

View File

@ -3,7 +3,7 @@ import { compose } from 'recompose'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom' import { withRouter } from 'react-router-dom'
import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction' import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction'
``
const mapStateToProps = state => { const mapStateToProps = state => {
const { metamask, appState } = state const { metamask, appState } = state
const { const {

View File

@ -10,7 +10,7 @@ import { getMetaMaskAccounts, getNetworkIdentifier } from '../../selectors/selec
// init // init
import FirstTimeFlow from '../first-time-flow' import FirstTimeFlow from '../first-time-flow'
// accounts // accounts
const SendTransactionScreen = require('../../components/app/send/send.container') const SendTransactionScreen = require('../send/send.container')
const ConfirmTransaction = require('../confirm-transaction') const ConfirmTransaction = require('../confirm-transaction')
// slideout menu // slideout menu

View File

@ -1,11 +1,11 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import { checksumAddress } from '../../../../helpers/utils/util' import { checksumAddress } from '../../../helpers/utils/util'
import Identicon from '../../../ui/identicon' import Identicon from '../../../components/ui/identicon'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display' import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../../helpers/constants/common' import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'
import Tooltip from '../../../ui/tooltip-v2' import Tooltip from '../../../components/ui/tooltip-v2'
export default class AccountListItem extends Component { export default class AccountListItem extends Component {

View File

@ -8,7 +8,7 @@ import {
getIsMainnet, getIsMainnet,
isBalanceCached, isBalanceCached,
preferencesSelector, preferencesSelector,
} from '../../../../selectors/selectors' } from '../../../selectors/selectors'
import AccountListItem from './account-list-item.component' import AccountListItem from './account-list-item.component'
export default connect(mapStateToProps)(AccountListItem) export default connect(mapStateToProps)(AccountListItem)

View File

@ -3,15 +3,15 @@ import assert from 'assert'
import { shallow } from 'enzyme' import { shallow } from 'enzyme'
import sinon from 'sinon' import sinon from 'sinon'
import proxyquire from 'proxyquire' import proxyquire from 'proxyquire'
import Identicon from '../../../../ui/identicon' import Identicon from '../../../../components/ui/identicon'
import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display' import UserPreferencedCurrencyDisplay from '../../../../components/app/user-preferenced-currency-display'
const utilsMethodStubs = { const utilsMethodStubs = {
checksumAddress: sinon.stub().returns('mockCheckSumAddress'), checksumAddress: sinon.stub().returns('mockCheckSumAddress'),
} }
const AccountListItem = proxyquire('../account-list-item.component.js', { const AccountListItem = proxyquire('../account-list-item.component.js', {
'../../../../helpers/utils/util': utilsMethodStubs, '../../../helpers/utils/util': utilsMethodStubs,
}).default }).default

View File

@ -15,7 +15,7 @@ proxyquire('../account-list-item.container.js', {
getCurrentCurrency: () => `mockCurrentCurrency`, getCurrentCurrency: () => `mockCurrentCurrency`,
getNativeCurrency: () => `mockNativeCurrency`, getNativeCurrency: () => `mockNativeCurrency`,
}, },
'../../../../selectors/selectors': { '../../../selectors/selectors': {
isBalanceCached: () => `mockBalanceIsCached`, isBalanceCached: () => `mockBalanceIsCached`,
preferencesSelector: ({ showFiatInTestnets }) => ({ preferencesSelector: ({ showFiatInTestnets }) => ({
showFiatInTestnets, showFiatInTestnets,

View File

@ -10,11 +10,11 @@ import { calcMaxAmount } from './amount-max-button.utils.js'
import { import {
updateSendAmount, updateSendAmount,
setMaxModeTo, setMaxModeTo,
} from '../../../../../../store/actions' } from '../../../../../store/actions'
import AmountMaxButton from './amount-max-button.component' import AmountMaxButton from './amount-max-button.component'
import { import {
updateSendErrors, updateSendErrors,
} from '../../../../../../ducks/send/send.duck' } from '../../../../../ducks/send/send.duck'
export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton) export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton)

View File

@ -1,7 +1,7 @@
const { const {
multiplyCurrencies, multiplyCurrencies,
subtractCurrencies, subtractCurrencies,
} = require('../../../../../../helpers/utils/conversion-util') } = require('../../../../../helpers/utils/conversion-util')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
function calcMaxAmount ({ balance, gasTotal, selectedToken, tokenBalance }) { function calcMaxAmount ({ balance, gasTotal, selectedToken, tokenBalance }) {

View File

@ -29,8 +29,8 @@ proxyquire('../amount-max-button.container.js', {
}, },
'./amount-max-button.selectors.js': { getMaxModeOn: (s) => `mockMaxModeOn:${s}` }, './amount-max-button.selectors.js': { getMaxModeOn: (s) => `mockMaxModeOn:${s}` },
'./amount-max-button.utils.js': { calcMaxAmount: (mockObj) => mockObj.val + 1 }, './amount-max-button.utils.js': { calcMaxAmount: (mockObj) => mockObj.val + 1 },
'../../../../../../store/actions': actionSpies, '../../../../../store/actions': actionSpies,
'../../../../../../ducks/send/send.duck': duckActionSpies, '../../../../../ducks/send/send.duck': duckActionSpies,
}) })
describe('amount-max-button container', () => { describe('amount-max-button container', () => {

View File

@ -2,8 +2,8 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper' import SendRowWrapper from '../send-row-wrapper'
import AmountMaxButton from './amount-max-button' import AmountMaxButton from './amount-max-button'
import UserPreferencedCurrencyInput from '../../../user-preferenced-currency-input' import UserPreferencedCurrencyInput from '../../../../components/app/user-preferenced-currency-input'
import UserPreferencedTokenInput from '../../../user-preferenced-token-input' import UserPreferencedTokenInput from '../../../../components/app/user-preferenced-token-input'
export default class SendAmountRow extends Component { export default class SendAmountRow extends Component {

View File

@ -17,10 +17,10 @@ import { getAmountErrorObject, getGasFeeErrorObject } from '../../send.utils'
import { import {
setMaxModeTo, setMaxModeTo,
updateSendAmount, updateSendAmount,
} from '../../../../../store/actions' } from '../../../../store/actions'
import { import {
updateSendErrors, updateSendErrors,
} from '../../../../../ducks/send/send.duck' } from '../../../../ducks/send/send.duck'
import SendAmountRow from './send-amount-row.component' import SendAmountRow from './send-amount-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow) export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow)

View File

@ -6,7 +6,7 @@ import SendAmountRow from '../send-amount-row.component.js'
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component' import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
import AmountMaxButton from '../amount-max-button/amount-max-button.container' import AmountMaxButton from '../amount-max-button/amount-max-button.container'
import UserPreferencedTokenInput from '../../../../user-preferenced-token-input' import UserPreferencedTokenInput from '../../../../../components/app/user-preferenced-token-input'
const propsMethodSpies = { const propsMethodSpies = {
setMaxModeTo: sinon.spy(), setMaxModeTo: sinon.spy(),

View File

@ -37,8 +37,8 @@ proxyquire('../send-amount-row.container.js', {
getAmountErrorObject: (mockDataObject) => ({ ...mockDataObject, mockChange: true }), getAmountErrorObject: (mockDataObject) => ({ ...mockDataObject, mockChange: true }),
getGasFeeErrorObject: (mockDataObject) => ({ ...mockDataObject, mockGasFeeErrorChange: true }), getGasFeeErrorObject: (mockDataObject) => ({ ...mockDataObject, mockGasFeeErrorChange: true }),
}, },
'../../../../../store/actions': actionSpies, '../../../../store/actions': actionSpies,
'../../../../../ducks/send/send.duck': duckActionSpies, '../../../../ducks/send/send.duck': duckActionSpies,
}) })
describe('send-amount-row container', () => { describe('send-amount-row container', () => {

View File

@ -0,0 +1 @@
export { default } from './send-asset-row.container'

View File

@ -0,0 +1,152 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper'
import Identicon from '../../../../components/ui/identicon/identicon.component'
import TokenBalance from '../../../../components/ui/token-balance'
import UserPreferencedCurrencyDisplay from '../../../../components/app/user-preferenced-currency-display'
import {PRIMARY} from '../../../../helpers/constants/common'
export default class SendAssetRow extends Component {
static propTypes = {
tokens: PropTypes.arrayOf(
PropTypes.shape({
address: PropTypes.string,
decimals: PropTypes.string,
symbol: PropTypes.string,
})
).isRequired,
accounts: PropTypes.object.isRequired,
selectedAddress: PropTypes.string.isRequired,
selectedTokenAddress: PropTypes.string,
setSelectedToken: PropTypes.func.isRequired,
}
static contextTypes = {
t: PropTypes.func,
metricsEvent: PropTypes.func,
}
state = {
isShowingDropdown: false,
}
openDropdown = () => this.setState({ isShowingDropdown: true })
closeDropdown = () => this.setState({ isShowingDropdown: false })
selectToken = address => {
this.setState({
isShowingDropdown: false,
}, () => {
this.context.metricsEvent({
eventOpts: {
category: 'Transactions',
action: 'Send Screen',
name: 'User clicks "Assets" dropdown',
},
customVariables: {
assetSelected: address ? 'ERC20' : 'ETH',
},
})
this.props.setSelectedToken(address)
})
}
render () {
const { t } = this.context
return (
<SendRowWrapper label={`${t('asset')}:`}>
<div className="send-v2__asset-dropdown">
{ this.renderSelectedToken() }
{ this.renderAssetDropdown() }
</div>
</SendRowWrapper>
)
}
renderSelectedToken () {
const { selectedTokenAddress } = this.props
const token = this.props.tokens.find(({ address }) => address === selectedTokenAddress)
return (
<div
className="send-v2__asset-dropdown__input-wrapper"
onClick={this.openDropdown}
>
{ token ? this.renderAsset(token) : this.renderEth() }
</div>
)
}
renderAssetDropdown () {
return this.state.isShowingDropdown && (
<div>
<div
className="send-v2__asset-dropdown__close-area"
onClick={this.closeDropdown}
/>
<div className="send-v2__asset-dropdown__list">
{ this.renderEth() }
{ this.props.tokens.map(token => this.renderAsset(token)) }
</div>
</div>
)
}
renderEth () {
const { t } = this.context
const { accounts, selectedAddress } = this.props
const balanceValue = accounts[selectedAddress] ? accounts[selectedAddress].balance : ''
return (
<div
className="send-v2__asset-dropdown__asset"
onClick={() => this.selectToken()}
>
<div className="send-v2__asset-dropdown__asset-icon">
<Identicon diameter={36} />
</div>
<div className="send-v2__asset-dropdown__asset-data">
<div className="send-v2__asset-dropdown__symbol">ETH</div>
<div className="send-v2__asset-dropdown__name">
<span className="send-v2__asset-dropdown__name__label">{`${t('balance')}:`}</span>
<UserPreferencedCurrencyDisplay
value={balanceValue}
type={PRIMARY}
/>
</div>
</div>
</div>
)
}
renderAsset (token) {
const { address, symbol } = token
const { t } = this.context
return (
<div
key={address} className="send-v2__asset-dropdown__asset"
onClick={() => this.selectToken(address)}
>
<div className="send-v2__asset-dropdown__asset-icon">
<Identicon address={address} diameter={36} />
</div>
<div className="send-v2__asset-dropdown__asset-data">
<div className="send-v2__asset-dropdown__symbol">
{ symbol }
</div>
<div className="send-v2__asset-dropdown__name">
<span className="send-v2__asset-dropdown__name__label">{`${t('balance')}:`}</span>
<TokenBalance
token={token}
withSymbol
/>
</div>
</div>
</div>
)
}
}

View File

@ -0,0 +1,21 @@
import { connect } from 'react-redux'
import SendAssetRow from './send-asset-row.component'
import {getMetaMaskAccounts} from '../../../../selectors/selectors'
import { setSelectedToken } from '../../../../store/actions'
function mapStateToProps (state) {
return {
tokens: state.metamask.tokens,
selectedAddress: state.metamask.selectedAddress,
selectedTokenAddress: state.metamask.selectedTokenAddress,
accounts: getMetaMaskAccounts(state),
}
}
function mapDispatchToProps (dispatch) {
return {
setSelectedToken: address => dispatch(setSelectedToken(address)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SendAssetRow)

View File

@ -1,11 +1,12 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import PageContainerContent from '../../../ui/page-container/page-container-content.component' import PageContainerContent from '../../../components/ui/page-container/page-container-content.component'
import SendAmountRow from './send-amount-row' import SendAmountRow from './send-amount-row'
import SendFromRow from './send-from-row' import SendFromRow from './send-from-row'
import SendGasRow from './send-gas-row' import SendGasRow from './send-gas-row'
import SendHexDataRow from './send-hex-data-row' import SendHexDataRow from './send-hex-data-row'
import SendToRow from './send-to-row' import SendToRow from './send-to-row'
import SendAssetRow from './send-asset-row'
export default class SendContent extends Component { export default class SendContent extends Component {
@ -26,6 +27,7 @@ export default class SendContent extends Component {
updateGas={this.updateGas} updateGas={this.updateGas}
scanQrCode={ _ => this.props.scanQrCode()} scanQrCode={ _ => this.props.scanQrCode()}
/> />
<SendAssetRow />
<SendAmountRow updateGas={this.updateGas} /> <SendAmountRow updateGas={this.updateGas} />
<SendGasRow /> <SendGasRow />
{(this.props.showHexData && ( {(this.props.showHexData && (

View File

@ -1,7 +1,7 @@
import React, {Component} from 'react' import React, {Component} from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display' import UserPreferencedCurrencyDisplay from '../../../../../components/app/user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../../../../helpers/constants/common' import { PRIMARY, SECONDARY } from '../../../../../helpers/constants/common'
export default class GasFeeDisplay extends Component { export default class GasFeeDisplay extends Component {

View File

@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert' import assert from 'assert'
import {shallow} from 'enzyme' import {shallow} from 'enzyme'
import GasFeeDisplay from '../gas-fee-display.component' import GasFeeDisplay from '../gas-fee-display.component'
import UserPreferencedCurrencyDisplay from '../../../../../user-preferenced-currency-display' import UserPreferencedCurrencyDisplay from '../../../../../../components/app/user-preferenced-currency-display'
import sinon from 'sinon' import sinon from 'sinon'

View File

@ -2,8 +2,8 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper' import SendRowWrapper from '../send-row-wrapper'
import GasFeeDisplay from './gas-fee-display/gas-fee-display.component' import GasFeeDisplay from './gas-fee-display/gas-fee-display.component'
import GasPriceButtonGroup from '../../../gas-customization/gas-price-button-group' import GasPriceButtonGroup from '../../../../components/app/gas-customization/gas-price-button-group'
import AdvancedGasInputs from '../../../gas-customization/advanced-gas-inputs' import AdvancedGasInputs from '../../../../components/app/gas-customization/advanced-gas-inputs'
export default class SendGasRow extends Component { export default class SendGasRow extends Component {
@ -20,8 +20,8 @@ export default class SendGasRow extends Component {
gasButtonGroupShown: PropTypes.bool, gasButtonGroupShown: PropTypes.bool,
advancedInlineGasShown: PropTypes.bool, advancedInlineGasShown: PropTypes.bool,
resetGasButtons: PropTypes.func, resetGasButtons: PropTypes.func,
gasPrice: PropTypes.number, gasPrice: PropTypes.string,
gasLimit: PropTypes.number, gasLimit: PropTypes.string,
insufficientBalance: PropTypes.bool, insufficientBalance: PropTypes.bool,
} }

View File

@ -15,18 +15,18 @@ import {
getBasicGasEstimateLoadingStatus, getBasicGasEstimateLoadingStatus,
getRenderableEstimateDataForSmallButtonsFromGWEI, getRenderableEstimateDataForSmallButtonsFromGWEI,
getDefaultActiveButtonIndex, getDefaultActiveButtonIndex,
} from '../../../../../selectors/custom-gas' } from '../../../../selectors/custom-gas'
import { import {
showGasButtonGroup, showGasButtonGroup,
} from '../../../../../ducks/send/send.duck' } from '../../../../ducks/send/send.duck'
import { import {
resetCustomData, resetCustomData,
setCustomGasPrice, setCustomGasPrice,
setCustomGasLimit, setCustomGasLimit,
} from '../../../../../ducks/gas/gas.duck' } from '../../../../ducks/gas/gas.duck'
import { getGasLoadingError, gasFeeIsInError, getGasButtonGroupShown } from './send-gas-row.selectors.js' import { getGasLoadingError, gasFeeIsInError, getGasButtonGroupShown } from './send-gas-row.selectors.js'
import { showModal, setGasPrice, setGasLimit, setGasTotal } from '../../../../../store/actions' import { showModal, setGasPrice, setGasLimit, setGasTotal } from '../../../../store/actions'
import { getAdvancedInlineGasShown, getCurrentEthBalance, getSelectedToken } from '../../../../../selectors/selectors' import { getAdvancedInlineGasShown, getCurrentEthBalance, getSelectedToken } from '../../../../selectors/selectors'
import SendGasRow from './send-gas-row.component' import SendGasRow from './send-gas-row.component'
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(SendGasRow) export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(SendGasRow)

View File

@ -6,7 +6,7 @@ import SendGasRow from '../send-gas-row.component.js'
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component' import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
import GasFeeDisplay from '../gas-fee-display/gas-fee-display.component' import GasFeeDisplay from '../gas-fee-display/gas-fee-display.component'
import GasPriceButtonGroup from '../../../../gas-customization/gas-price-button-group' import GasPriceButtonGroup from '../../../../../components/app/gas-customization/gas-price-button-group'
const propsMethodSpies = { const propsMethodSpies = {
showCustomizeGasModal: sinon.spy(), showCustomizeGasModal: sinon.spy(),

View File

@ -32,7 +32,7 @@ proxyquire('../send-gas-row.container.js', {
return () => ({}) return () => ({})
}, },
}, },
'../../../../../selectors/selectors': { '../../../../selectors/selectors': {
getCurrentEthBalance: (s) => `mockCurrentEthBalance:${s}`, getCurrentEthBalance: (s) => `mockCurrentEthBalance:${s}`,
getAdvancedInlineGasShown: (s) => `mockAdvancedInlineGasShown:${s}`, getAdvancedInlineGasShown: (s) => `mockAdvancedInlineGasShown:${s}`,
getSelectedToken: () => false, getSelectedToken: () => false,
@ -59,14 +59,14 @@ proxyquire('../send-gas-row.container.js', {
gasFeeIsInError: (s) => `mockGasFeeError:${s}`, gasFeeIsInError: (s) => `mockGasFeeError:${s}`,
getGasButtonGroupShown: (s) => `mockGetGasButtonGroupShown:${s}`, getGasButtonGroupShown: (s) => `mockGetGasButtonGroupShown:${s}`,
}, },
'../../../../../store/actions': actionSpies, '../../../../store/actions': actionSpies,
'../../../../../selectors/custom-gas': { '../../../../selectors/custom-gas': {
getBasicGasEstimateLoadingStatus: (s) => `mockBasicGasEstimateLoadingStatus:${s}`, getBasicGasEstimateLoadingStatus: (s) => `mockBasicGasEstimateLoadingStatus:${s}`,
getRenderableEstimateDataForSmallButtonsFromGWEI: (s) => `mockGasButtonInfo:${s}`, getRenderableEstimateDataForSmallButtonsFromGWEI: (s) => `mockGasButtonInfo:${s}`,
getDefaultActiveButtonIndex: (gasButtonInfo, gasPrice) => gasButtonInfo.length + gasPrice.length, getDefaultActiveButtonIndex: (gasButtonInfo, gasPrice) => gasButtonInfo.length + gasPrice.length,
}, },
'../../../../../ducks/send/send.duck': sendDuckSpies, '../../../../ducks/send/send.duck': sendDuckSpies,
'../../../../../ducks/gas/gas.duck': gasDuckSpies, '../../../../ducks/gas/gas.duck': gasDuckSpies,
}) })
describe('send-gas-row container', () => { describe('send-gas-row container', () => {

View File

@ -1,7 +1,7 @@
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { import {
updateSendHexData, updateSendHexData,
} from '../../../../../store/actions' } from '../../../../store/actions'
import SendHexDataRow from './send-hex-data-row.component' import SendHexDataRow from './send-hex-data-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendHexDataRow) export default connect(mapStateToProps, mapDispatchToProps)(SendHexDataRow)

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper' import SendRowWrapper from '../send-row-wrapper'
import EnsInput from '../../../ens-input' import EnsInput from '../../../../components/app/ens-input'
import { getToErrorObject, getToWarningObject } from './send-to-row.utils.js' import { getToErrorObject, getToWarningObject } from './send-to-row.utils.js'
export default class SendToRow extends Component { export default class SendToRow extends Component {

View File

@ -14,13 +14,13 @@ import {
} from './send-to-row.selectors.js' } from './send-to-row.selectors.js'
import { import {
updateSendTo, updateSendTo,
} from '../../../../../store/actions' } from '../../../../store/actions'
import { import {
updateSendErrors, updateSendErrors,
updateSendWarnings, updateSendWarnings,
openToDropdown, openToDropdown,
closeToDropdown, closeToDropdown,
} from '../../../../../ducks/send/send.duck' } from '../../../../ducks/send/send.duck'
import SendToRow from './send-to-row.component' import SendToRow from './send-to-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendToRow) export default connect(mapStateToProps, mapDispatchToProps)(SendToRow)

View File

@ -4,8 +4,8 @@ const {
KNOWN_RECIPIENT_ADDRESS_ERROR, KNOWN_RECIPIENT_ADDRESS_ERROR,
INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR, INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR,
} = require('../../send.constants') } = require('../../send.constants')
const { isValidAddress, isEthNetwork } = require('../../../../../helpers/utils/util') const { isValidAddress, isEthNetwork } = require('../../../../helpers/utils/util')
import { checkExistingAddresses } from '../../../../../pages/add-token/util' import { checkExistingAddresses } from '../../../add-token/util'
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const contractMap = require('eth-contract-metadata') const contractMap = require('eth-contract-metadata')

View File

@ -16,7 +16,7 @@ const SendToRow = proxyquire('../send-to-row.component.js', {
}).default }).default
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component' import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
import EnsInput from '../../../../ens-input' import EnsInput from '../../../../../components/app/ens-input'
const propsMethodSpies = { const propsMethodSpies = {
closeToDropdown: sinon.spy(), closeToDropdown: sinon.spy(),

View File

@ -36,8 +36,8 @@ proxyquire('../send-to-row.container.js', {
sendToIsInWarning: (s) => `mockInWarning:${s}`, sendToIsInWarning: (s) => `mockInWarning:${s}`,
getTokens: (s) => `mockTokens:${s}`, getTokens: (s) => `mockTokens:${s}`,
}, },
'../../../../../store/actions': actionSpies, '../../../../store/actions': actionSpies,
'../../../../../ducks/send/send.duck': duckActionSpies, '../../../../ducks/send/send.duck': duckActionSpies,
}) })
describe('send-to-row container', () => { describe('send-to-row container', () => {

Some files were not shown because too many files have changed in this diff Show More