aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--test/integration/lib/send-new-ui.js24
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js57
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js12
-rw-r--r--ui/app/components/ui/currency-input/currency-input.component.js4
-rw-r--r--ui/app/components/ui/currency-input/currency-input.container.js3
-rw-r--r--ui/app/components/ui/currency-input/tests/currency-input.container.test.js16
-rw-r--r--ui/app/components/ui/unit-input/index.scss9
-rw-r--r--ui/app/components/ui/unit-input/unit-input.component.js10
-rw-r--r--ui/app/css/itcss/components/send.scss55
-rw-r--r--ui/app/helpers/utils/conversions.util.js11
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js39
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js5
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js7
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js2
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/send-amount-row.component.js2
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js37
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js18
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-container.test.js9
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js3
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.component.js46
20 files changed, 314 insertions, 55 deletions
diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js
index 78014feef..0cca9e959 100644
--- a/test/integration/lib/send-new-ui.js
+++ b/test/integration/lib/send-new-ui.js
@@ -71,11 +71,29 @@ async function runSendFlowTest (assert) {
assert.equal(sendToAccountAddress, '0x2f8D4a878cFA04A6E60D46362f5644DeAb66572D', 'send to dropdown selects the correct address')
const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(3)')
- sendAmountField.find('.unit-input')[0].click()
-
const sendAmountFieldInput = await findAsync(sendAmountField, '.unit-input__input')
+
+ const amountMaxButton = await queryAsync($, '.send-v2__amount-max')
+ amountMaxButton.click()
+ reactTriggerChange(sendAmountField.find('input')[1])
+ assert.equal(sendAmountFieldInput.is(':disabled'), true, 'disabled the send amount input when max mode is on')
+
+ const gasPriceButtonGroup = await queryAsync($, '.gas-price-button-group--small')
+ const gasPriceButton = await gasPriceButtonGroup.find('button')[0]
+ const valueBeforeGasPriceChange = sendAmountFieldInput.prop('value')
+ gasPriceButton.click()
+ reactTriggerChange(sendAmountField.find('input')[1])
+
+ await timeout(1000)
+
+ assert.notEqual(valueBeforeGasPriceChange, sendAmountFieldInput.prop('value'), 'send amount value changes when gas price changes')
+
+ amountMaxButton.click()
+ reactTriggerChange(sendAmountField.find('input')[1])
+
+ sendAmountField.find('.unit-input').click()
sendAmountFieldInput.val('5.1')
- reactTriggerChange(sendAmountField.find('input')[0])
+ reactTriggerChange(sendAmountField.find('input')[1])
let errorMessage = await queryAsync($, '.send-v2__error')
assert.equal(errorMessage[0].textContent, 'Insufficient funds.', 'send should render an insufficient fund error message')
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
index 0e7e30347..c3fdf51e5 100644
--- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
@@ -7,6 +7,8 @@ import {
setGasPrice,
createSpeedUpTransaction,
hideSidebar,
+ updateSendAmount,
+ setGasTotal,
} from '../../../../store/actions'
import {
setCustomGasPrice,
@@ -18,6 +20,7 @@ import {
} from '../../../../ducks/gas/gas.duck'
import {
hideGasButtonGroup,
+ updateSendErrors,
} from '../../../../ducks/send/send.duck'
import {
updateGasAndCalculate,
@@ -46,6 +49,9 @@ import {
isCustomPriceSafe,
} from '../../../../selectors/custom-gas'
import {
+ getTokenBalance,
+} from '../../../../pages/send/send.selectors'
+import {
submittedPendingTransactionsSelector,
} from '../../../../selectors/transactions'
import {
@@ -53,6 +59,7 @@ import {
} from '../../../../helpers/utils/confirm-tx.util'
import {
addHexWEIsToDec,
+ subtractHexWEIsToDec,
decEthToConvertedCurrency as ethTotalToConvertedCurrency,
decGWEIToHexWEI,
hexWEIToDecGWEI,
@@ -66,6 +73,8 @@ import {
} from '../../../../pages/send/send.utils'
import { addHexPrefix } from 'ethereumjs-util'
import { getAdjacentGasPrices, extrapolateY } from '../gas-price-chart/gas-price-chart.utils'
+import { getMaxModeOn } from '../../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors'
+import { calcMaxAmount } from '../../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils'
const mapStateToProps = (state, ownProps) => {
const { transaction = {} } = ownProps
@@ -75,8 +84,6 @@ const mapStateToProps = (state, ownProps) => {
const { gasPrice: currentGasPrice, gas: currentGasLimit, value } = getTxParams(state, transaction.id)
const customModalGasPriceInHex = getCustomGasPrice(state) || currentGasPrice
const customModalGasLimitInHex = getCustomGasLimit(state) || currentGasLimit
- const gasTotal = calcGasTotal(customModalGasLimitInHex, customModalGasPriceInHex)
-
const customGasTotal = calcGasTotal(customModalGasLimitInHex, customModalGasPriceInHex)
const gasButtonInfo = getRenderableBasicEstimateData(state, customModalGasLimitInHex)
@@ -90,6 +97,8 @@ const mapStateToProps = (state, ownProps) => {
const customGasPrice = calcCustomGasPrice(customModalGasPriceInHex)
+ const maxModeOn = getMaxModeOn(state)
+
const gasPrices = getEstimatedGasPrices(state)
const estimatedTimes = getEstimatedGasTimes(state)
const balance = getCurrentEthBalance(state)
@@ -98,9 +107,13 @@ const mapStateToProps = (state, ownProps) => {
const isMainnet = getIsMainnet(state)
const showFiat = Boolean(isMainnet || showFiatInTestnets)
- const insufficientBalance = !isBalanceSufficient({
+ const newTotalEth = maxModeOn ? addHexWEIsToRenderableEth(balance, '0x0') : addHexWEIsToRenderableEth(value, customGasTotal)
+
+ const sendAmount = maxModeOn ? subtractHexWEIsFromRenderableEth(balance, customGasTotal) : addHexWEIsToRenderableEth(value, '0x0')
+
+ const insufficientBalance = maxModeOn ? false : !isBalanceSufficient({
amount: value,
- gasTotal,
+ gasTotal: customGasTotal,
balance,
conversionRate,
})
@@ -112,10 +125,12 @@ const mapStateToProps = (state, ownProps) => {
customModalGasLimitInHex,
customGasPrice,
customGasLimit: calcCustomGasLimit(customModalGasLimitInHex),
+ customGasTotal,
newTotalFiat,
currentTimeEstimate: getRenderableTimeEstimate(customGasPrice, gasPrices, estimatedTimes),
blockTime: getBasicGasEstimateBlockTime(state),
customPriceIsSafe: isCustomPriceSafe(state),
+ maxModeOn,
gasPriceButtonGroupProps: {
buttonDataLoading,
defaultActiveButtonIndex: getDefaultActiveButtonIndex(gasButtonInfo, customModalGasPriceInHex),
@@ -129,12 +144,12 @@ const mapStateToProps = (state, ownProps) => {
estimatedTimesMax: estimatedTimes[0],
},
infoRowProps: {
- originalTotalFiat: addHexWEIsToRenderableFiat(value, gasTotal, currentCurrency, conversionRate),
- originalTotalEth: addHexWEIsToRenderableEth(value, gasTotal),
+ originalTotalFiat: addHexWEIsToRenderableFiat(value, customGasTotal, currentCurrency, conversionRate),
+ originalTotalEth: addHexWEIsToRenderableEth(value, customGasTotal),
newTotalFiat: showFiat ? newTotalFiat : '',
- newTotalEth: addHexWEIsToRenderableEth(value, customGasTotal),
+ newTotalEth,
transactionFee: addHexWEIsToRenderableEth('0x0', customGasTotal),
- sendAmount: addHexWEIsToRenderableEth(value, '0x0'),
+ sendAmount,
},
isSpeedUp: transaction.status === 'submitted',
txId: transaction.id,
@@ -142,6 +157,9 @@ const mapStateToProps = (state, ownProps) => {
gasEstimatesLoading,
isMainnet,
isEthereumNetwork: isEthereumNetwork(state),
+ selectedToken: getSelectedToken(state),
+ balance,
+ tokenBalance: getTokenBalance(state),
}
}
@@ -174,11 +192,16 @@ const mapDispatchToProps = dispatch => {
hideSidebar: () => dispatch(hideSidebar()),
fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)),
fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()),
+ setGasTotal: (total) => dispatch(setGasTotal(total)),
+ setAmountToMax: (maxAmountDataObject) => {
+ dispatch(updateSendErrors({ amount: null }))
+ dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
+ },
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
- const { gasPriceButtonGroupProps, isConfirm, txId, isSpeedUp, insufficientBalance, customGasPrice } = stateProps
+ const { gasPriceButtonGroupProps, isConfirm, txId, isSpeedUp, insufficientBalance, maxModeOn, customGasPrice, customGasTotal, balance, selectedToken, tokenBalance} = stateProps
const {
updateCustomGasPrice: dispatchUpdateCustomGasPrice,
hideGasButtonGroup: dispatchHideGasButtonGroup,
@@ -188,6 +211,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
hideSidebar: dispatchHideSidebar,
cancelAndClose: dispatchCancelAndClose,
hideModal: dispatchHideModal,
+ setAmountToMax: dispatchSetAmountToMax,
...otherDispatchProps
} = dispatchProps
@@ -208,6 +232,14 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
dispatchHideGasButtonGroup()
dispatchCancelAndClose()
}
+ if (maxModeOn) {
+ dispatchSetAmountToMax({
+ balance,
+ gasTotal: customGasTotal,
+ selectedToken,
+ tokenBalance,
+ })
+ }
},
gasPriceButtonGroupProps: {
...gasPriceButtonGroupProps,
@@ -258,6 +290,13 @@ function addHexWEIsToRenderableEth (aHexWEI, bHexWEI) {
)(aHexWEI, bHexWEI)
}
+function subtractHexWEIsFromRenderableEth (aHexWEI, bHexWei) {
+ return pipe(
+ subtractHexWEIsToDec,
+ formatETHFee
+ )(aHexWEI, bHexWei)
+}
+
function addHexWEIsToRenderableFiat (aHexWEI, bHexWEI, convertedCurrency, conversionRate) {
return pipe(
addHexWEIsToDec,
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js
index ab24b9c0e..dbe61d5cf 100644
--- a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js
@@ -46,6 +46,10 @@ proxyquire('../gas-modal-page-container.container.js', {
'../../../../ducks/send/send.duck': sendActionSpies,
'../../../../selectors/selectors.js': {
getCurrentEthBalance: (state) => state.metamask.balance || '0x0',
+ getSelectedToken: () => null,
+ },
+ '../../../../pages/send/send.selectors': {
+ getTokenBalance: (state) => state.metamask.send.tokenBalance || '0x0',
},
})
@@ -68,6 +72,7 @@ describe('gas-modal-page-container container', () => {
gasLimit: '16',
gasPrice: '32',
amount: '64',
+ maxModeOn: false,
},
currentCurrency: 'abc',
conversionRate: 50,
@@ -106,6 +111,7 @@ describe('gas-modal-page-container container', () => {
},
}
const baseExpectedResult = {
+ balance: '0x0',
isConfirm: true,
customGasPrice: 4.294967295,
customGasLimit: 2863311530,
@@ -114,6 +120,7 @@ describe('gas-modal-page-container container', () => {
blockTime: 12,
customModalGasLimitInHex: 'aaaaaaaa',
customModalGasPriceInHex: 'ffffffff',
+ customGasTotal: 'aaaaaaa955555556',
customPriceIsSafe: true,
gasChartProps: {
'currentPrice': 4.294967295,
@@ -142,6 +149,9 @@ describe('gas-modal-page-container container', () => {
txId: 34,
isEthereumNetwork: true,
isMainnet: true,
+ maxModeOn: false,
+ selectedToken: null,
+ tokenBalance: '0x0',
}
const baseMockOwnProps = { transaction: { id: 34 } }
const tests = [
@@ -150,7 +160,7 @@ describe('gas-modal-page-container container', () => {
mockState: Object.assign({}, baseMockState, {
metamask: { ...baseMockState.metamask, balance: '0xfffffffffffffffffffff' },
}),
- expectedResult: Object.assign({}, baseExpectedResult, { insufficientBalance: false }),
+ expectedResult: Object.assign({}, baseExpectedResult, { balance: '0xfffffffffffffffffffff', insufficientBalance: false }),
mockOwnProps: baseMockOwnProps,
},
{
diff --git a/ui/app/components/ui/currency-input/currency-input.component.js b/ui/app/components/ui/currency-input/currency-input.component.js
index b5be0972b..1876c9591 100644
--- a/ui/app/components/ui/currency-input/currency-input.component.js
+++ b/ui/app/components/ui/currency-input/currency-input.component.js
@@ -18,6 +18,7 @@ export default class CurrencyInput extends PureComponent {
static propTypes = {
conversionRate: PropTypes.number,
currentCurrency: PropTypes.string,
+ maxModeOn: PropTypes.bool,
nativeCurrency: PropTypes.string,
onChange: PropTypes.func,
onBlur: PropTypes.func,
@@ -136,7 +137,7 @@ export default class CurrencyInput extends PureComponent {
}
render () {
- const { fiatSuffix, nativeSuffix, ...restProps } = this.props
+ const { fiatSuffix, nativeSuffix, maxModeOn, ...restProps } = this.props
const { decimalValue } = this.state
return (
@@ -146,6 +147,7 @@ export default class CurrencyInput extends PureComponent {
onChange={this.handleChange}
onBlur={this.handleBlur}
value={decimalValue}
+ maxModeOn={maxModeOn}
actionComponent={(
<div
className="currency-input__swap-component"
diff --git a/ui/app/components/ui/currency-input/currency-input.container.js b/ui/app/components/ui/currency-input/currency-input.container.js
index b5d7dfe6d..46e70bace 100644
--- a/ui/app/components/ui/currency-input/currency-input.container.js
+++ b/ui/app/components/ui/currency-input/currency-input.container.js
@@ -1,18 +1,21 @@
import { connect } from 'react-redux'
import CurrencyInput from './currency-input.component'
import { ETH } from '../../../helpers/constants/common'
+import { getMaxModeOn } from '../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors'
import {getIsMainnet, preferencesSelector} from '../../../selectors/selectors'
const mapStateToProps = state => {
const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state
const { showFiatInTestnets } = preferencesSelector(state)
const isMainnet = getIsMainnet(state)
+ const maxModeOn = getMaxModeOn(state)
return {
nativeCurrency,
currentCurrency,
conversionRate,
hideFiat: (!isMainnet && !showFiatInTestnets),
+ maxModeOn,
}
}
diff --git a/ui/app/components/ui/currency-input/tests/currency-input.container.test.js b/ui/app/components/ui/currency-input/tests/currency-input.container.test.js
index 259fe594a..f10abe09a 100644
--- a/ui/app/components/ui/currency-input/tests/currency-input.container.test.js
+++ b/ui/app/components/ui/currency-input/tests/currency-input.container.test.js
@@ -30,6 +30,9 @@ describe('CurrencyInput container', () => {
provider: {
type: 'mainnet',
},
+ send: {
+ maxModeOn: false,
+ },
},
},
expected: {
@@ -37,6 +40,7 @@ describe('CurrencyInput container', () => {
currentCurrency: 'usd',
nativeCurrency: 'ETH',
hideFiat: false,
+ maxModeOn: false,
},
},
// Test # 2
@@ -53,6 +57,9 @@ describe('CurrencyInput container', () => {
provider: {
type: 'rinkeby',
},
+ send: {
+ maxModeOn: false,
+ },
},
},
expected: {
@@ -60,6 +67,7 @@ describe('CurrencyInput container', () => {
currentCurrency: 'usd',
nativeCurrency: 'ETH',
hideFiat: true,
+ maxModeOn: false,
},
},
// Test # 3
@@ -76,6 +84,9 @@ describe('CurrencyInput container', () => {
provider: {
type: 'rinkeby',
},
+ send: {
+ maxModeOn: false,
+ },
},
},
expected: {
@@ -83,6 +94,7 @@ describe('CurrencyInput container', () => {
currentCurrency: 'usd',
nativeCurrency: 'ETH',
hideFiat: false,
+ maxModeOn: false,
},
},
// Test # 4
@@ -99,6 +111,9 @@ describe('CurrencyInput container', () => {
provider: {
type: 'mainnet',
},
+ send: {
+ maxModeOn: false,
+ },
},
},
expected: {
@@ -106,6 +121,7 @@ describe('CurrencyInput container', () => {
currentCurrency: 'usd',
nativeCurrency: 'ETH',
hideFiat: false,
+ maxModeOn: false,
},
},
]
diff --git a/ui/app/components/ui/unit-input/index.scss b/ui/app/components/ui/unit-input/index.scss
index adc4a3531..58a10c9a1 100644
--- a/ui/app/components/ui/unit-input/index.scss
+++ b/ui/app/components/ui/unit-input/index.scss
@@ -42,6 +42,10 @@
max-width: 22ch;
height: 16px;
line-height: 18px;
+
+ &__disabled {
+ background-color: rgb(222, 222, 222);
+ }
}
&__input-container {
@@ -59,4 +63,9 @@
&--error {
border-color: $red;
}
+
+ &__disabled {
+ background-color: #F2F3F4;
+ }
+
}
diff --git a/ui/app/components/ui/unit-input/unit-input.component.js b/ui/app/components/ui/unit-input/unit-input.component.js
index 6a53f4c6f..9085a0677 100644
--- a/ui/app/components/ui/unit-input/unit-input.component.js
+++ b/ui/app/components/ui/unit-input/unit-input.component.js
@@ -13,6 +13,7 @@ export default class UnitInput extends PureComponent {
children: PropTypes.node,
actionComponent: PropTypes.node,
error: PropTypes.bool,
+ maxModeOn: PropTypes.bool,
onBlur: PropTypes.func,
onChange: PropTypes.func,
placeholder: PropTypes.string,
@@ -71,25 +72,26 @@ export default class UnitInput extends PureComponent {
}
render () {
- const { error, placeholder, suffix, actionComponent, children } = this.props
+ const { error, placeholder, suffix, actionComponent, children, maxModeOn } = this.props
const { value } = this.state
return (
<div
- className={classnames('unit-input', { 'unit-input--error': error })}
- onClick={this.handleFocus}
+ className={classnames('unit-input', { 'unit-input--error': error }, { 'unit-input__disabled': maxModeOn })}
+ onClick={maxModeOn ? null : this.handleFocus}
>
<div className="unit-input__inputs">
<div className="unit-input__input-container">
<input
type="number"
- className="unit-input__input"
+ className={classnames('unit-input__input', { 'unit-input__disabled': maxModeOn })}
value={value}
placeholder={placeholder}
onChange={this.handleChange}
onBlur={this.handleBlur}
style={{ width: this.getInputWidth(value) }}
ref={ref => { this.unitInput = ref }}
+ disabled={maxModeOn}
/>
{
suffix && (
diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss
index 2e76cc842..e2f0f9b2f 100644
--- a/ui/app/css/itcss/components/send.scss
+++ b/ui/app/css/itcss/components/send.scss
@@ -520,6 +520,10 @@
color: $red;
}
+ &__error-amount {
+ margin-top: 5px;
+ }
+
&__warning {
font-size: 12px;
line-height: 12px;
@@ -557,6 +561,12 @@
justify-content: space-between;
}
+ &__form-field-container {
+ display: flex;
+ flex-direction: column;
+ width: 277px;
+ }
+
&__form-field {
flex: 1 1 auto;
min-width: 0;
@@ -817,12 +827,47 @@
}
&__amount-max {
- color: $curious-blue;
font-family: Roboto;
font-size: 12px;
- left: 8px;
- border: none;
- cursor: pointer;
+ position: relative;
+ display: inline-block;
+ width: 56px;
+ height: 20px;
+ margin-top: 5px;
+
+ &__button {
+ width: 56px;
+ height: 20px;
+ position: absolute;
+ border: 2px solid #B0D7F2;
+ border-radius: 6px;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #2f9ae0;
+
+ &__disabled {
+ color: #B0D7F2;
+ cursor: auto;
+ }
+ }
+
+ input:checked + &__button {
+ background-color: #037DD6;
+ border: 2px solid #037DD6;
+ color: #fff;
+ }
+ }
+
+ &__amount-max input {
+ opacity: 0;
+ width: 0;
+ height: 0;
}
&__gas-fee-display {
@@ -1077,7 +1122,7 @@
font-size: 14px;
color: #2f9ae0;
cursor: pointer;
- margin-top: 16px;
+ margin-top: 5px;
}
.sliders-icon-container {
diff --git a/ui/app/helpers/utils/conversions.util.js b/ui/app/helpers/utils/conversions.util.js
index b4ec50626..5e1c21ff7 100644
--- a/ui/app/helpers/utils/conversions.util.js
+++ b/ui/app/helpers/utils/conversions.util.js
@@ -1,6 +1,6 @@
import ethUtil from 'ethereumjs-util'
import { ETH, GWEI, WEI } from '../constants/common'
-import { conversionUtil, addCurrencies } from './conversion-util'
+import { conversionUtil, addCurrencies, subtractCurrencies } from './conversion-util'
export function bnToHex (inputBn) {
return ethUtil.addHexPrefix(inputBn.toString(16))
@@ -92,6 +92,15 @@ export function addHexWEIsToDec (aHexWEI, bHexWEI) {
})
}
+export function subtractHexWEIsToDec (aHexWEI, bHexWEI) {
+ return subtractCurrencies(aHexWEI, bHexWEI, {
+ aBase: 16,
+ bBase: 16,
+ fromDenomination: 'WEI',
+ numberOfDecimals: 6,
+ })
+}
+
export function decEthToConvertedCurrency (ethTotal, convertedCurrency, conversionRate) {
return conversionUtil(ethTotal, {
fromNumericBase: 'dec',
diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
index e256d1442..249703763 100644
--- a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
@@ -1,16 +1,21 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
+import classnames from 'classnames'
export default class AmountMaxButton extends Component {
static propTypes = {
balance: PropTypes.string,
+ buttonDataLoading: PropTypes.bool,
+ clearMaxAmount: PropTypes.func,
+ inError: PropTypes.bool,
gasTotal: PropTypes.string,
maxModeOn: PropTypes.bool,
selectedToken: PropTypes.object,
setAmountToMax: PropTypes.func,
setMaxModeTo: PropTypes.func,
tokenBalance: PropTypes.string,
+
}
static contextTypes = {
@@ -36,7 +41,7 @@ export default class AmountMaxButton extends Component {
}
onMaxClick = (event) => {
- const { setMaxModeTo } = this.props
+ const { setMaxModeTo, clearMaxAmount, maxModeOn } = this.props
const { metricsEvent } = this.context
metricsEvent({
@@ -46,25 +51,25 @@ export default class AmountMaxButton extends Component {
name: 'Clicked "Amount Max"',
},
})
-
- event.preventDefault()
- setMaxModeTo(true)
- this.setMaxAmount()
+ if (!maxModeOn) {
+ setMaxModeTo(true)
+ this.setMaxAmount()
+ } else {
+ setMaxModeTo(false)
+ clearMaxAmount()
+ }
}
render () {
- return this.props.maxModeOn
- ? null
- : (
- <div>
- <span
- className="send-v2__amount-max"
- onClick={this.onMaxClick}
- >
- {this.context.t('max')}
- </span>
- </div>
+ const { maxModeOn, buttonDataLoading, inError } = this.props
+
+ return (
+ <div className={'send-v2__amount-max'} onClick={buttonDataLoading || inError ? null : this.onMaxClick}>
+ <input type="checkbox" checked={maxModeOn} />
+ <div className={classnames('send-v2__amount-max__button', { 'send-v2__amount-max__button__disabled': buttonDataLoading || inError })}>
+ {this.context.t('max')}
+ </div>
+ </div>
)
}
-
}
diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
index cd48a105f..e444589a1 100644
--- a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
@@ -5,6 +5,7 @@ import {
getSendFromBalance,
getTokenBalance,
} from '../../../send.selectors.js'
+import { getBasicGasEstimateLoadingStatus } from '../../../../../selectors/custom-gas'
import { getMaxModeOn } from './amount-max-button.selectors.js'
import { calcMaxAmount } from './amount-max-button.utils.js'
import {
@@ -22,6 +23,7 @@ function mapStateToProps (state) {
return {
balance: getSendFromBalance(state),
+ buttonDataLoading: getBasicGasEstimateLoadingStatus(state),
gasTotal: getGasTotal(state),
maxModeOn: getMaxModeOn(state),
selectedToken: getSelectedToken(state),
@@ -35,6 +37,9 @@ function mapDispatchToProps (dispatch) {
dispatch(updateSendErrors({ amount: null }))
dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
},
+ clearMaxAmount: () => {
+ dispatch(updateSendAmount('0'))
+ },
setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
}
}
diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
index a6cb29d4c..f986b26bb 100644
--- a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
@@ -65,7 +65,7 @@ describe('AmountMaxButton Component', function () {
assert(wrapper.exists('.send-v2__amount-max'))
})
- it('should call setMaxModeTo and setMaxAmount when the send-v2__amount-max div is clicked', () => {
+ it('should call setMaxModeTo and setMaxAmount when the checkbox is checked', () => {
const {
onClick,
} = wrapper.find('.send-v2__amount-max').props()
@@ -81,11 +81,6 @@ describe('AmountMaxButton Component', function () {
)
})
- it('should not render anything when maxModeOn is true', () => {
- wrapper.setProps({ maxModeOn: true })
- assert.ok(!wrapper.exists('.send-v2__amount-max'))
- })
-
it('should render the expected text when maxModeOn is false', () => {
wrapper.setProps({ maxModeOn: false })
assert.equal(wrapper.find('.send-v2__amount-max').text(), 'max_t')
diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
index a75ed5e8f..dcee8fda0 100644
--- a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
@@ -29,6 +29,7 @@ proxyquire('../amount-max-button.container.js', {
},
'./amount-max-button.selectors.js': { getMaxModeOn: (s) => `mockMaxModeOn:${s}` },
'./amount-max-button.utils.js': { calcMaxAmount: (mockObj) => mockObj.val + 1 },
+ '../../../../../selectors/custom-gas': { getBasicGasEstimateLoadingStatus: (s) => `mockButtonDataLoading:${s}`},
'../../../../../store/actions': actionSpies,
'../../../../../ducks/send/send.duck': duckActionSpies,
})
@@ -40,6 +41,7 @@ describe('amount-max-button container', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
balance: 'mockBalance:mockState',
+ buttonDataLoading: 'mockButtonDataLoading:mockState',
gasTotal: 'mockGasTotal:mockState',
maxModeOn: 'mockMaxModeOn:mockState',
selectedToken: 'mockSelectedToken:mockState',
diff --git a/ui/app/pages/send/send-content/send-amount-row/send-amount-row.component.js b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.component.js
index c0241ea91..10e90c419 100644
--- a/ui/app/pages/send/send-content/send-amount-row/send-amount-row.component.js
+++ b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.component.js
@@ -110,7 +110,7 @@ export default class SendAmountRow extends Component {
showError={inError}
errorType={'amount'}
>
- {!inError && gasTotal && <AmountMaxButton />}
+ {gasTotal && <AmountMaxButton inError={inError} />}
{ this.renderInput() }
</SendRowWrapper>
)
diff --git a/ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js
index 1b850ac57..4c09ed564 100644
--- a/ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js
+++ b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js
@@ -8,14 +8,19 @@ import AdvancedGasInputs from '../../../../components/app/gas-customization/adva
export default class SendGasRow extends Component {
static propTypes = {
+ balance: PropTypes.string,
conversionRate: PropTypes.number,
convertedCurrency: PropTypes.string,
gasFeeError: PropTypes.bool,
gasLoadingError: PropTypes.bool,
gasTotal: PropTypes.string,
+ maxModeOn: PropTypes.bool,
showCustomizeGasModal: PropTypes.func,
+ selectedToken: PropTypes.object,
+ setAmountToMax: PropTypes.func,
setGasPrice: PropTypes.func,
setGasLimit: PropTypes.func,
+ tokenBalance: PropTypes.string,
gasPriceButtonGroupProps: PropTypes.object,
gasButtonGroupShown: PropTypes.bool,
advancedInlineGasShown: PropTypes.bool,
@@ -47,6 +52,23 @@ export default class SendGasRow extends Component {
</div>
}
+ setMaxAmount () {
+ const {
+ balance,
+ gasTotal,
+ selectedToken,
+ setAmountToMax,
+ tokenBalance,
+ } = this.props
+
+ setAmountToMax({
+ balance,
+ gasTotal,
+ selectedToken,
+ tokenBalance,
+ })
+ }
+
renderContent () {
const {
conversionRate,
@@ -57,6 +79,7 @@ export default class SendGasRow extends Component {
gasPriceButtonGroupProps,
gasButtonGroupShown,
advancedInlineGasShown,
+ maxModeOn,
resetGasButtons,
setGasPrice,
setGasLimit,
@@ -71,7 +94,7 @@ export default class SendGasRow extends Component {
className="gas-price-button-group--small"
showCheck={false}
{...gasPriceButtonGroupProps}
- handleGasPriceSelection={(...args) => {
+ handleGasPriceSelection={async (...args) => {
metricsEvent({
eventOpts: {
category: 'Transactions',
@@ -79,7 +102,10 @@ export default class SendGasRow extends Component {
name: 'Changed Gas Button',
},
})
- gasPriceButtonGroupProps.handleGasPriceSelection(...args)
+ await gasPriceButtonGroupProps.handleGasPriceSelection(...args)
+ if (maxModeOn) {
+ this.setMaxAmount()
+ }
}}
/>
{ this.renderAdvancedOptionsButton() }
@@ -89,7 +115,12 @@ export default class SendGasRow extends Component {
convertedCurrency={convertedCurrency}
gasLoadingError={gasLoadingError}
gasTotal={gasTotal}
- onReset={resetGasButtons}
+ onReset={() => {
+ resetGasButtons()
+ if (maxModeOn) {
+ this.setMaxAmount()
+ }
+ }}
onClick={() => showCustomizeGasModal()}
/>
const advancedGasInputs = <div>
diff --git a/ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js
index c4daa98af..10eaa50b8 100644
--- a/ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js
+++ b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js
@@ -6,11 +6,17 @@ import {
getGasPrice,
getGasLimit,
getSendAmount,
+ getSendFromBalance,
+ getTokenBalance,
} from '../../send.selectors.js'
import {
+ getMaxModeOn,
+} from '../send-amount-row/amount-max-button/amount-max-button.selectors'
+import {
isBalanceSufficient,
calcGasTotal,
} from '../../send.utils.js'
+import { calcMaxAmount } from '../send-amount-row/amount-max-button/amount-max-button.utils'
import {
getBasicGasEstimateLoadingStatus,
getRenderableEstimateDataForSmallButtonsFromGWEI,
@@ -18,6 +24,7 @@ import {
} from '../../../../selectors/custom-gas'
import {
showGasButtonGroup,
+ updateSendErrors,
} from '../../../../ducks/send/send.duck'
import {
resetCustomData,
@@ -25,10 +32,11 @@ import {
setCustomGasLimit,
} from '../../../../ducks/gas/gas.duck'
import { getGasLoadingError, gasFeeIsInError, getGasButtonGroupShown } from './send-gas-row.selectors.js'
-import { showModal, setGasPrice, setGasLimit, setGasTotal } from '../../../../store/actions'
+import { showModal, setGasPrice, setGasLimit, setGasTotal, updateSendAmount } from '../../../../store/actions'
import { getAdvancedInlineGasShown, getCurrentEthBalance, getSelectedToken } from '../../../../selectors/selectors'
import SendGasRow from './send-gas-row.component'
+
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(SendGasRow)
function mapStateToProps (state) {
@@ -49,6 +57,7 @@ function mapStateToProps (state) {
})
return {
+ balance: getSendFromBalance(state),
conversionRate,
convertedCurrency: getCurrentCurrency(state),
gasTotal,
@@ -65,6 +74,9 @@ function mapStateToProps (state) {
gasPrice,
gasLimit,
insufficientBalance,
+ maxModeOn: getMaxModeOn(state),
+ selectedToken: getSelectedToken(state),
+ tokenBalance: getTokenBalance(state),
}
}
@@ -85,6 +97,10 @@ function mapDispatchToProps (dispatch) {
dispatch(setGasTotal(calcGasTotal(newLimit, gasPrice)))
}
},
+ setAmountToMax: maxAmountDataObject => {
+ dispatch(updateSendErrors({ amount: null }))
+ dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
+ },
showGasButtonGroup: () => dispatch(showGasButtonGroup()),
resetCustomData: () => dispatch(resetCustomData()),
}
diff --git a/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-container.test.js b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
index ddc6ea985..4acb310f8 100644
--- a/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
+++ b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
@@ -44,6 +44,11 @@ proxyquire('../send-gas-row.container.js', {
getGasPrice: (s) => `mockGasPrice:${s}`,
getGasLimit: (s) => `mockGasLimit:${s}`,
getSendAmount: (s) => `mockSendAmount:${s}`,
+ getSendFromBalance: (s) => `mockBalance:${s}`,
+ getTokenBalance: (s) => `mockTokenBalance:${s}`,
+ },
+ '../send-amount-row/amount-max-button/amount-max-button.selectors': {
+ getMaxModeOn: (s) => `mockMaxModeOn:${s}`,
},
'../../send.utils.js': {
isBalanceSufficient: ({
@@ -75,6 +80,7 @@ describe('send-gas-row container', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
+ balance: 'mockBalance:mockState',
conversionRate: 'mockConversionRate:mockState',
convertedCurrency: 'mockConvertedCurrency:mockState',
gasTotal: 'mockGasTotal:mockState',
@@ -91,6 +97,9 @@ describe('send-gas-row container', () => {
gasLimit: 'mockGasLimit:mockState',
gasPrice: 'mockGasPrice:mockState',
insufficientBalance: false,
+ maxModeOn: 'mockMaxModeOn:mockState',
+ selectedToken: false,
+ tokenBalance: 'mockTokenBalance:mockState',
})
})
diff --git a/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
index 61bc7bab7..0be01996a 100644
--- a/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
@@ -1,5 +1,6 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
+import classnames from 'classnames'
export default class SendRowErrorMessage extends Component {
@@ -19,7 +20,7 @@ export default class SendRowErrorMessage extends Component {
return (
errorMessage
- ? <div className="send-v2__error">{this.context.t(errorMessage)}</div>
+ ? <div className={classnames('send-v2__error', {'send-v2__error-amount': errorType === 'amount'})}>{this.context.t(errorMessage)}</div>
: null
)
}
diff --git a/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.component.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.component.js
index 94309bd96..075b86633 100644
--- a/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.component.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.component.js
@@ -18,7 +18,7 @@ export default class SendRowWrapper extends Component {
t: PropTypes.func,
};
- render () {
+ renderAmountFormRow () {
const {
children,
errorType = '',
@@ -34,7 +34,39 @@ export default class SendRowWrapper extends Component {
<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-container">
+ <div className="send-v2__form-field">
+ {formField}
+ </div>
+ <div>
+ {showError && <SendRowErrorMessage errorType={errorType} />}
+ {!showError && showWarning && <SendRowWarningMessage warningType={warningType} />}
+ </div>
+ </div>
+ </div>
+ )
+ }
+
+ renderFormRow () {
+ const {
+ children,
+ errorType = '',
+ label,
+ showError = false,
+ showWarning = false,
+ warningType = '',
+ } = this.props
+
+ const formField = Array.isArray(children) ? children[1] || children[0] : children
+ const customLabelContent = (Array.isArray(children) && children.length) > 1 ? children[0] : null
+
+ return (
+ <div className="send-v2__form-row">
+ <div className="send-v2__form-label">
+ {label}
+ {showError && <SendRowErrorMessage errorType={errorType} />}
{!showError && showWarning && <SendRowWarningMessage warningType={warningType} />}
{customLabelContent}
</div>
@@ -45,4 +77,14 @@ export default class SendRowWrapper extends Component {
)
}
+ render () {
+ const {
+ errorType = '',
+ } = this.props
+
+ return (
+ errorType === 'amount' ? this.renderAmountFormRow() : this.renderFormRow()
+ )
+ }
+
}