mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Improved UX for sweeping accounts (#6488)
* Changed max button to checkbox, disabled input if max mode is on, recalculate price according to gas fee if max mode is on * Disabled insufficient funds message in the modal if max mode is on, displays proper amounts in modal when max mode is on, sets the send amount according to custom gas price after gas modal save, resets the send amount after resetting custom gas price * Disabled max mode checkbox if gas buttons are loading, refactored gas-modal-page-container * Implemented new max button & max mode message. Moved insufficient funds error to underneath the send amount field * Fixed existing integration test to pass, created new tests to ensure send amount field is disabled when max button is clicked and the amount changes when the gas price is changed. Refactored some components
This commit is contained in:
parent
b27a4d2b4e
commit
0e9c8fb5cc
@ -71,11 +71,29 @@ async function runSendFlowTest (assert) {
|
|||||||
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(3)')
|
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 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')
|
sendAmountFieldInput.val('5.1')
|
||||||
reactTriggerChange(sendAmountField.find('input')[0])
|
reactTriggerChange(sendAmountField.find('input')[1])
|
||||||
|
|
||||||
let errorMessage = await queryAsync($, '.send-v2__error')
|
let errorMessage = await queryAsync($, '.send-v2__error')
|
||||||
assert.equal(errorMessage[0].textContent, 'Insufficient funds.', 'send should render an insufficient fund error message')
|
assert.equal(errorMessage[0].textContent, 'Insufficient funds.', 'send should render an insufficient fund error message')
|
||||||
|
@ -7,6 +7,8 @@ import {
|
|||||||
setGasPrice,
|
setGasPrice,
|
||||||
createSpeedUpTransaction,
|
createSpeedUpTransaction,
|
||||||
hideSidebar,
|
hideSidebar,
|
||||||
|
updateSendAmount,
|
||||||
|
setGasTotal,
|
||||||
} from '../../../../store/actions'
|
} from '../../../../store/actions'
|
||||||
import {
|
import {
|
||||||
setCustomGasPrice,
|
setCustomGasPrice,
|
||||||
@ -18,6 +20,7 @@ import {
|
|||||||
} from '../../../../ducks/gas/gas.duck'
|
} from '../../../../ducks/gas/gas.duck'
|
||||||
import {
|
import {
|
||||||
hideGasButtonGroup,
|
hideGasButtonGroup,
|
||||||
|
updateSendErrors,
|
||||||
} from '../../../../ducks/send/send.duck'
|
} from '../../../../ducks/send/send.duck'
|
||||||
import {
|
import {
|
||||||
updateGasAndCalculate,
|
updateGasAndCalculate,
|
||||||
@ -45,6 +48,9 @@ import {
|
|||||||
getBasicGasEstimateBlockTime,
|
getBasicGasEstimateBlockTime,
|
||||||
isCustomPriceSafe,
|
isCustomPriceSafe,
|
||||||
} from '../../../../selectors/custom-gas'
|
} from '../../../../selectors/custom-gas'
|
||||||
|
import {
|
||||||
|
getTokenBalance,
|
||||||
|
} from '../../../../pages/send/send.selectors'
|
||||||
import {
|
import {
|
||||||
submittedPendingTransactionsSelector,
|
submittedPendingTransactionsSelector,
|
||||||
} from '../../../../selectors/transactions'
|
} from '../../../../selectors/transactions'
|
||||||
@ -53,6 +59,7 @@ import {
|
|||||||
} from '../../../../helpers/utils/confirm-tx.util'
|
} from '../../../../helpers/utils/confirm-tx.util'
|
||||||
import {
|
import {
|
||||||
addHexWEIsToDec,
|
addHexWEIsToDec,
|
||||||
|
subtractHexWEIsToDec,
|
||||||
decEthToConvertedCurrency as ethTotalToConvertedCurrency,
|
decEthToConvertedCurrency as ethTotalToConvertedCurrency,
|
||||||
decGWEIToHexWEI,
|
decGWEIToHexWEI,
|
||||||
hexWEIToDecGWEI,
|
hexWEIToDecGWEI,
|
||||||
@ -66,6 +73,8 @@ import {
|
|||||||
} from '../../../../pages/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'
|
||||||
|
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 mapStateToProps = (state, ownProps) => {
|
||||||
const { transaction = {} } = ownProps
|
const { transaction = {} } = ownProps
|
||||||
@ -75,8 +84,6 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
const { gasPrice: currentGasPrice, gas: currentGasLimit, value } = getTxParams(state, transaction.id)
|
const { gasPrice: currentGasPrice, gas: currentGasLimit, value } = getTxParams(state, transaction.id)
|
||||||
const customModalGasPriceInHex = getCustomGasPrice(state) || currentGasPrice
|
const customModalGasPriceInHex = getCustomGasPrice(state) || currentGasPrice
|
||||||
const customModalGasLimitInHex = getCustomGasLimit(state) || currentGasLimit
|
const customModalGasLimitInHex = getCustomGasLimit(state) || currentGasLimit
|
||||||
const gasTotal = calcGasTotal(customModalGasLimitInHex, customModalGasPriceInHex)
|
|
||||||
|
|
||||||
const customGasTotal = calcGasTotal(customModalGasLimitInHex, customModalGasPriceInHex)
|
const customGasTotal = calcGasTotal(customModalGasLimitInHex, customModalGasPriceInHex)
|
||||||
|
|
||||||
const gasButtonInfo = getRenderableBasicEstimateData(state, customModalGasLimitInHex)
|
const gasButtonInfo = getRenderableBasicEstimateData(state, customModalGasLimitInHex)
|
||||||
@ -90,6 +97,8 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
|
|
||||||
const customGasPrice = calcCustomGasPrice(customModalGasPriceInHex)
|
const customGasPrice = calcCustomGasPrice(customModalGasPriceInHex)
|
||||||
|
|
||||||
|
const maxModeOn = getMaxModeOn(state)
|
||||||
|
|
||||||
const gasPrices = getEstimatedGasPrices(state)
|
const gasPrices = getEstimatedGasPrices(state)
|
||||||
const estimatedTimes = getEstimatedGasTimes(state)
|
const estimatedTimes = getEstimatedGasTimes(state)
|
||||||
const balance = getCurrentEthBalance(state)
|
const balance = getCurrentEthBalance(state)
|
||||||
@ -98,9 +107,13 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
const isMainnet = getIsMainnet(state)
|
const isMainnet = getIsMainnet(state)
|
||||||
const showFiat = Boolean(isMainnet || showFiatInTestnets)
|
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,
|
amount: value,
|
||||||
gasTotal,
|
gasTotal: customGasTotal,
|
||||||
balance,
|
balance,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
})
|
})
|
||||||
@ -112,10 +125,12 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
customModalGasLimitInHex,
|
customModalGasLimitInHex,
|
||||||
customGasPrice,
|
customGasPrice,
|
||||||
customGasLimit: calcCustomGasLimit(customModalGasLimitInHex),
|
customGasLimit: calcCustomGasLimit(customModalGasLimitInHex),
|
||||||
|
customGasTotal,
|
||||||
newTotalFiat,
|
newTotalFiat,
|
||||||
currentTimeEstimate: getRenderableTimeEstimate(customGasPrice, gasPrices, estimatedTimes),
|
currentTimeEstimate: getRenderableTimeEstimate(customGasPrice, gasPrices, estimatedTimes),
|
||||||
blockTime: getBasicGasEstimateBlockTime(state),
|
blockTime: getBasicGasEstimateBlockTime(state),
|
||||||
customPriceIsSafe: isCustomPriceSafe(state),
|
customPriceIsSafe: isCustomPriceSafe(state),
|
||||||
|
maxModeOn,
|
||||||
gasPriceButtonGroupProps: {
|
gasPriceButtonGroupProps: {
|
||||||
buttonDataLoading,
|
buttonDataLoading,
|
||||||
defaultActiveButtonIndex: getDefaultActiveButtonIndex(gasButtonInfo, customModalGasPriceInHex),
|
defaultActiveButtonIndex: getDefaultActiveButtonIndex(gasButtonInfo, customModalGasPriceInHex),
|
||||||
@ -129,12 +144,12 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
estimatedTimesMax: estimatedTimes[0],
|
estimatedTimesMax: estimatedTimes[0],
|
||||||
},
|
},
|
||||||
infoRowProps: {
|
infoRowProps: {
|
||||||
originalTotalFiat: addHexWEIsToRenderableFiat(value, gasTotal, currentCurrency, conversionRate),
|
originalTotalFiat: addHexWEIsToRenderableFiat(value, customGasTotal, currentCurrency, conversionRate),
|
||||||
originalTotalEth: addHexWEIsToRenderableEth(value, gasTotal),
|
originalTotalEth: addHexWEIsToRenderableEth(value, customGasTotal),
|
||||||
newTotalFiat: showFiat ? newTotalFiat : '',
|
newTotalFiat: showFiat ? newTotalFiat : '',
|
||||||
newTotalEth: addHexWEIsToRenderableEth(value, customGasTotal),
|
newTotalEth,
|
||||||
transactionFee: addHexWEIsToRenderableEth('0x0', customGasTotal),
|
transactionFee: addHexWEIsToRenderableEth('0x0', customGasTotal),
|
||||||
sendAmount: addHexWEIsToRenderableEth(value, '0x0'),
|
sendAmount,
|
||||||
},
|
},
|
||||||
isSpeedUp: transaction.status === 'submitted',
|
isSpeedUp: transaction.status === 'submitted',
|
||||||
txId: transaction.id,
|
txId: transaction.id,
|
||||||
@ -142,6 +157,9 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
gasEstimatesLoading,
|
gasEstimatesLoading,
|
||||||
isMainnet,
|
isMainnet,
|
||||||
isEthereumNetwork: isEthereumNetwork(state),
|
isEthereumNetwork: isEthereumNetwork(state),
|
||||||
|
selectedToken: getSelectedToken(state),
|
||||||
|
balance,
|
||||||
|
tokenBalance: getTokenBalance(state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,11 +192,16 @@ const mapDispatchToProps = dispatch => {
|
|||||||
hideSidebar: () => dispatch(hideSidebar()),
|
hideSidebar: () => dispatch(hideSidebar()),
|
||||||
fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)),
|
fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)),
|
||||||
fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()),
|
fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()),
|
||||||
|
setGasTotal: (total) => dispatch(setGasTotal(total)),
|
||||||
|
setAmountToMax: (maxAmountDataObject) => {
|
||||||
|
dispatch(updateSendErrors({ amount: null }))
|
||||||
|
dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
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 {
|
const {
|
||||||
updateCustomGasPrice: dispatchUpdateCustomGasPrice,
|
updateCustomGasPrice: dispatchUpdateCustomGasPrice,
|
||||||
hideGasButtonGroup: dispatchHideGasButtonGroup,
|
hideGasButtonGroup: dispatchHideGasButtonGroup,
|
||||||
@ -188,6 +211,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|||||||
hideSidebar: dispatchHideSidebar,
|
hideSidebar: dispatchHideSidebar,
|
||||||
cancelAndClose: dispatchCancelAndClose,
|
cancelAndClose: dispatchCancelAndClose,
|
||||||
hideModal: dispatchHideModal,
|
hideModal: dispatchHideModal,
|
||||||
|
setAmountToMax: dispatchSetAmountToMax,
|
||||||
...otherDispatchProps
|
...otherDispatchProps
|
||||||
} = dispatchProps
|
} = dispatchProps
|
||||||
|
|
||||||
@ -208,6 +232,14 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|||||||
dispatchHideGasButtonGroup()
|
dispatchHideGasButtonGroup()
|
||||||
dispatchCancelAndClose()
|
dispatchCancelAndClose()
|
||||||
}
|
}
|
||||||
|
if (maxModeOn) {
|
||||||
|
dispatchSetAmountToMax({
|
||||||
|
balance,
|
||||||
|
gasTotal: customGasTotal,
|
||||||
|
selectedToken,
|
||||||
|
tokenBalance,
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
gasPriceButtonGroupProps: {
|
gasPriceButtonGroupProps: {
|
||||||
...gasPriceButtonGroupProps,
|
...gasPriceButtonGroupProps,
|
||||||
@ -258,6 +290,13 @@ function addHexWEIsToRenderableEth (aHexWEI, bHexWEI) {
|
|||||||
)(aHexWEI, bHexWEI)
|
)(aHexWEI, bHexWEI)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function subtractHexWEIsFromRenderableEth (aHexWEI, bHexWei) {
|
||||||
|
return pipe(
|
||||||
|
subtractHexWEIsToDec,
|
||||||
|
formatETHFee
|
||||||
|
)(aHexWEI, bHexWei)
|
||||||
|
}
|
||||||
|
|
||||||
function addHexWEIsToRenderableFiat (aHexWEI, bHexWEI, convertedCurrency, conversionRate) {
|
function addHexWEIsToRenderableFiat (aHexWEI, bHexWEI, convertedCurrency, conversionRate) {
|
||||||
return pipe(
|
return pipe(
|
||||||
addHexWEIsToDec,
|
addHexWEIsToDec,
|
||||||
|
@ -46,6 +46,10 @@ proxyquire('../gas-modal-page-container.container.js', {
|
|||||||
'../../../../ducks/send/send.duck': sendActionSpies,
|
'../../../../ducks/send/send.duck': sendActionSpies,
|
||||||
'../../../../selectors/selectors.js': {
|
'../../../../selectors/selectors.js': {
|
||||||
getCurrentEthBalance: (state) => state.metamask.balance || '0x0',
|
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',
|
gasLimit: '16',
|
||||||
gasPrice: '32',
|
gasPrice: '32',
|
||||||
amount: '64',
|
amount: '64',
|
||||||
|
maxModeOn: false,
|
||||||
},
|
},
|
||||||
currentCurrency: 'abc',
|
currentCurrency: 'abc',
|
||||||
conversionRate: 50,
|
conversionRate: 50,
|
||||||
@ -106,6 +111,7 @@ describe('gas-modal-page-container container', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
const baseExpectedResult = {
|
const baseExpectedResult = {
|
||||||
|
balance: '0x0',
|
||||||
isConfirm: true,
|
isConfirm: true,
|
||||||
customGasPrice: 4.294967295,
|
customGasPrice: 4.294967295,
|
||||||
customGasLimit: 2863311530,
|
customGasLimit: 2863311530,
|
||||||
@ -114,6 +120,7 @@ describe('gas-modal-page-container container', () => {
|
|||||||
blockTime: 12,
|
blockTime: 12,
|
||||||
customModalGasLimitInHex: 'aaaaaaaa',
|
customModalGasLimitInHex: 'aaaaaaaa',
|
||||||
customModalGasPriceInHex: 'ffffffff',
|
customModalGasPriceInHex: 'ffffffff',
|
||||||
|
customGasTotal: 'aaaaaaa955555556',
|
||||||
customPriceIsSafe: true,
|
customPriceIsSafe: true,
|
||||||
gasChartProps: {
|
gasChartProps: {
|
||||||
'currentPrice': 4.294967295,
|
'currentPrice': 4.294967295,
|
||||||
@ -142,6 +149,9 @@ describe('gas-modal-page-container container', () => {
|
|||||||
txId: 34,
|
txId: 34,
|
||||||
isEthereumNetwork: true,
|
isEthereumNetwork: true,
|
||||||
isMainnet: true,
|
isMainnet: true,
|
||||||
|
maxModeOn: false,
|
||||||
|
selectedToken: null,
|
||||||
|
tokenBalance: '0x0',
|
||||||
}
|
}
|
||||||
const baseMockOwnProps = { transaction: { id: 34 } }
|
const baseMockOwnProps = { transaction: { id: 34 } }
|
||||||
const tests = [
|
const tests = [
|
||||||
@ -150,7 +160,7 @@ describe('gas-modal-page-container container', () => {
|
|||||||
mockState: Object.assign({}, baseMockState, {
|
mockState: Object.assign({}, baseMockState, {
|
||||||
metamask: { ...baseMockState.metamask, balance: '0xfffffffffffffffffffff' },
|
metamask: { ...baseMockState.metamask, balance: '0xfffffffffffffffffffff' },
|
||||||
}),
|
}),
|
||||||
expectedResult: Object.assign({}, baseExpectedResult, { insufficientBalance: false }),
|
expectedResult: Object.assign({}, baseExpectedResult, { balance: '0xfffffffffffffffffffff', insufficientBalance: false }),
|
||||||
mockOwnProps: baseMockOwnProps,
|
mockOwnProps: baseMockOwnProps,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -18,6 +18,7 @@ export default class CurrencyInput extends PureComponent {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
conversionRate: PropTypes.number,
|
conversionRate: PropTypes.number,
|
||||||
currentCurrency: PropTypes.string,
|
currentCurrency: PropTypes.string,
|
||||||
|
maxModeOn: PropTypes.bool,
|
||||||
nativeCurrency: PropTypes.string,
|
nativeCurrency: PropTypes.string,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
onBlur: PropTypes.func,
|
onBlur: PropTypes.func,
|
||||||
@ -136,7 +137,7 @@ export default class CurrencyInput extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { fiatSuffix, nativeSuffix, ...restProps } = this.props
|
const { fiatSuffix, nativeSuffix, maxModeOn, ...restProps } = this.props
|
||||||
const { decimalValue } = this.state
|
const { decimalValue } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -146,6 +147,7 @@ export default class CurrencyInput extends PureComponent {
|
|||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
value={decimalValue}
|
value={decimalValue}
|
||||||
|
maxModeOn={maxModeOn}
|
||||||
actionComponent={(
|
actionComponent={(
|
||||||
<div
|
<div
|
||||||
className="currency-input__swap-component"
|
className="currency-input__swap-component"
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import CurrencyInput from './currency-input.component'
|
import CurrencyInput from './currency-input.component'
|
||||||
import { ETH } from '../../../helpers/constants/common'
|
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'
|
import {getIsMainnet, preferencesSelector} from '../../../selectors/selectors'
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state
|
const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state
|
||||||
const { showFiatInTestnets } = preferencesSelector(state)
|
const { showFiatInTestnets } = preferencesSelector(state)
|
||||||
const isMainnet = getIsMainnet(state)
|
const isMainnet = getIsMainnet(state)
|
||||||
|
const maxModeOn = getMaxModeOn(state)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nativeCurrency,
|
nativeCurrency,
|
||||||
currentCurrency,
|
currentCurrency,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
hideFiat: (!isMainnet && !showFiatInTestnets),
|
hideFiat: (!isMainnet && !showFiatInTestnets),
|
||||||
|
maxModeOn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,9 @@ describe('CurrencyInput container', () => {
|
|||||||
provider: {
|
provider: {
|
||||||
type: 'mainnet',
|
type: 'mainnet',
|
||||||
},
|
},
|
||||||
|
send: {
|
||||||
|
maxModeOn: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: {
|
expected: {
|
||||||
@ -37,6 +40,7 @@ describe('CurrencyInput container', () => {
|
|||||||
currentCurrency: 'usd',
|
currentCurrency: 'usd',
|
||||||
nativeCurrency: 'ETH',
|
nativeCurrency: 'ETH',
|
||||||
hideFiat: false,
|
hideFiat: false,
|
||||||
|
maxModeOn: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Test # 2
|
// Test # 2
|
||||||
@ -53,6 +57,9 @@ describe('CurrencyInput container', () => {
|
|||||||
provider: {
|
provider: {
|
||||||
type: 'rinkeby',
|
type: 'rinkeby',
|
||||||
},
|
},
|
||||||
|
send: {
|
||||||
|
maxModeOn: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: {
|
expected: {
|
||||||
@ -60,6 +67,7 @@ describe('CurrencyInput container', () => {
|
|||||||
currentCurrency: 'usd',
|
currentCurrency: 'usd',
|
||||||
nativeCurrency: 'ETH',
|
nativeCurrency: 'ETH',
|
||||||
hideFiat: true,
|
hideFiat: true,
|
||||||
|
maxModeOn: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Test # 3
|
// Test # 3
|
||||||
@ -76,6 +84,9 @@ describe('CurrencyInput container', () => {
|
|||||||
provider: {
|
provider: {
|
||||||
type: 'rinkeby',
|
type: 'rinkeby',
|
||||||
},
|
},
|
||||||
|
send: {
|
||||||
|
maxModeOn: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: {
|
expected: {
|
||||||
@ -83,6 +94,7 @@ describe('CurrencyInput container', () => {
|
|||||||
currentCurrency: 'usd',
|
currentCurrency: 'usd',
|
||||||
nativeCurrency: 'ETH',
|
nativeCurrency: 'ETH',
|
||||||
hideFiat: false,
|
hideFiat: false,
|
||||||
|
maxModeOn: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Test # 4
|
// Test # 4
|
||||||
@ -99,6 +111,9 @@ describe('CurrencyInput container', () => {
|
|||||||
provider: {
|
provider: {
|
||||||
type: 'mainnet',
|
type: 'mainnet',
|
||||||
},
|
},
|
||||||
|
send: {
|
||||||
|
maxModeOn: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: {
|
expected: {
|
||||||
@ -106,6 +121,7 @@ describe('CurrencyInput container', () => {
|
|||||||
currentCurrency: 'usd',
|
currentCurrency: 'usd',
|
||||||
nativeCurrency: 'ETH',
|
nativeCurrency: 'ETH',
|
||||||
hideFiat: false,
|
hideFiat: false,
|
||||||
|
maxModeOn: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -42,6 +42,10 @@
|
|||||||
max-width: 22ch;
|
max-width: 22ch;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
|
|
||||||
|
&__disabled {
|
||||||
|
background-color: rgb(222, 222, 222);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__input-container {
|
&__input-container {
|
||||||
@ -59,4 +63,9 @@
|
|||||||
&--error {
|
&--error {
|
||||||
border-color: $red;
|
border-color: $red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__disabled {
|
||||||
|
background-color: #F2F3F4;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ export default class UnitInput extends PureComponent {
|
|||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
actionComponent: PropTypes.node,
|
actionComponent: PropTypes.node,
|
||||||
error: PropTypes.bool,
|
error: PropTypes.bool,
|
||||||
|
maxModeOn: PropTypes.bool,
|
||||||
onBlur: PropTypes.func,
|
onBlur: PropTypes.func,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
@ -71,25 +72,26 @@ export default class UnitInput extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { error, placeholder, suffix, actionComponent, children } = this.props
|
const { error, placeholder, suffix, actionComponent, children, maxModeOn } = this.props
|
||||||
const { value } = this.state
|
const { value } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames('unit-input', { 'unit-input--error': error })}
|
className={classnames('unit-input', { 'unit-input--error': error }, { 'unit-input__disabled': maxModeOn })}
|
||||||
onClick={this.handleFocus}
|
onClick={maxModeOn ? null : this.handleFocus}
|
||||||
>
|
>
|
||||||
<div className="unit-input__inputs">
|
<div className="unit-input__inputs">
|
||||||
<div className="unit-input__input-container">
|
<div className="unit-input__input-container">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
className="unit-input__input"
|
className={classnames('unit-input__input', { 'unit-input__disabled': maxModeOn })}
|
||||||
value={value}
|
value={value}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
style={{ width: this.getInputWidth(value) }}
|
style={{ width: this.getInputWidth(value) }}
|
||||||
ref={ref => { this.unitInput = ref }}
|
ref={ref => { this.unitInput = ref }}
|
||||||
|
disabled={maxModeOn}
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
suffix && (
|
suffix && (
|
||||||
|
@ -520,6 +520,10 @@
|
|||||||
color: $red;
|
color: $red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__error-amount {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
&__warning {
|
&__warning {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 12px;
|
line-height: 12px;
|
||||||
@ -557,6 +561,12 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__form-field-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 277px;
|
||||||
|
}
|
||||||
|
|
||||||
&__form-field {
|
&__form-field {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@ -817,12 +827,47 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__amount-max {
|
&__amount-max {
|
||||||
color: $curious-blue;
|
|
||||||
font-family: Roboto;
|
font-family: Roboto;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
left: 8px;
|
position: relative;
|
||||||
border: none;
|
display: inline-block;
|
||||||
cursor: pointer;
|
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 {
|
&__gas-fee-display {
|
||||||
@ -1077,7 +1122,7 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #2f9ae0;
|
color: #2f9ae0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-top: 16px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sliders-icon-container {
|
.sliders-icon-container {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import ethUtil from 'ethereumjs-util'
|
import ethUtil from 'ethereumjs-util'
|
||||||
import { ETH, GWEI, WEI } from '../constants/common'
|
import { ETH, GWEI, WEI } from '../constants/common'
|
||||||
import { conversionUtil, addCurrencies } from './conversion-util'
|
import { conversionUtil, addCurrencies, subtractCurrencies } from './conversion-util'
|
||||||
|
|
||||||
export function bnToHex (inputBn) {
|
export function bnToHex (inputBn) {
|
||||||
return ethUtil.addHexPrefix(inputBn.toString(16))
|
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) {
|
export function decEthToConvertedCurrency (ethTotal, convertedCurrency, conversionRate) {
|
||||||
return conversionUtil(ethTotal, {
|
return conversionUtil(ethTotal, {
|
||||||
fromNumericBase: 'dec',
|
fromNumericBase: 'dec',
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
export default class AmountMaxButton extends Component {
|
export default class AmountMaxButton extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
balance: PropTypes.string,
|
balance: PropTypes.string,
|
||||||
|
buttonDataLoading: PropTypes.bool,
|
||||||
|
clearMaxAmount: PropTypes.func,
|
||||||
|
inError: PropTypes.bool,
|
||||||
gasTotal: PropTypes.string,
|
gasTotal: PropTypes.string,
|
||||||
maxModeOn: PropTypes.bool,
|
maxModeOn: PropTypes.bool,
|
||||||
selectedToken: PropTypes.object,
|
selectedToken: PropTypes.object,
|
||||||
setAmountToMax: PropTypes.func,
|
setAmountToMax: PropTypes.func,
|
||||||
setMaxModeTo: PropTypes.func,
|
setMaxModeTo: PropTypes.func,
|
||||||
tokenBalance: PropTypes.string,
|
tokenBalance: PropTypes.string,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -36,7 +41,7 @@ export default class AmountMaxButton extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMaxClick = (event) => {
|
onMaxClick = (event) => {
|
||||||
const { setMaxModeTo } = this.props
|
const { setMaxModeTo, clearMaxAmount, maxModeOn } = this.props
|
||||||
const { metricsEvent } = this.context
|
const { metricsEvent } = this.context
|
||||||
|
|
||||||
metricsEvent({
|
metricsEvent({
|
||||||
@ -46,25 +51,25 @@ export default class AmountMaxButton extends Component {
|
|||||||
name: 'Clicked "Amount Max"',
|
name: 'Clicked "Amount Max"',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
if (!maxModeOn) {
|
||||||
event.preventDefault()
|
setMaxModeTo(true)
|
||||||
setMaxModeTo(true)
|
this.setMaxAmount()
|
||||||
this.setMaxAmount()
|
} else {
|
||||||
|
setMaxModeTo(false)
|
||||||
|
clearMaxAmount()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return this.props.maxModeOn
|
const { maxModeOn, buttonDataLoading, inError } = this.props
|
||||||
? null
|
|
||||||
: (
|
return (
|
||||||
<div>
|
<div className={'send-v2__amount-max'} onClick={buttonDataLoading || inError ? null : this.onMaxClick}>
|
||||||
<span
|
<input type="checkbox" checked={maxModeOn} />
|
||||||
className="send-v2__amount-max"
|
<div className={classnames('send-v2__amount-max__button', { 'send-v2__amount-max__button__disabled': buttonDataLoading || inError })}>
|
||||||
onClick={this.onMaxClick}
|
{this.context.t('max')}
|
||||||
>
|
</div>
|
||||||
{this.context.t('max')}
|
</div>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
getSendFromBalance,
|
getSendFromBalance,
|
||||||
getTokenBalance,
|
getTokenBalance,
|
||||||
} from '../../../send.selectors.js'
|
} from '../../../send.selectors.js'
|
||||||
|
import { getBasicGasEstimateLoadingStatus } from '../../../../../selectors/custom-gas'
|
||||||
import { getMaxModeOn } from './amount-max-button.selectors.js'
|
import { getMaxModeOn } from './amount-max-button.selectors.js'
|
||||||
import { calcMaxAmount } from './amount-max-button.utils.js'
|
import { calcMaxAmount } from './amount-max-button.utils.js'
|
||||||
import {
|
import {
|
||||||
@ -22,6 +23,7 @@ function mapStateToProps (state) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
balance: getSendFromBalance(state),
|
balance: getSendFromBalance(state),
|
||||||
|
buttonDataLoading: getBasicGasEstimateLoadingStatus(state),
|
||||||
gasTotal: getGasTotal(state),
|
gasTotal: getGasTotal(state),
|
||||||
maxModeOn: getMaxModeOn(state),
|
maxModeOn: getMaxModeOn(state),
|
||||||
selectedToken: getSelectedToken(state),
|
selectedToken: getSelectedToken(state),
|
||||||
@ -35,6 +37,9 @@ function mapDispatchToProps (dispatch) {
|
|||||||
dispatch(updateSendErrors({ amount: null }))
|
dispatch(updateSendErrors({ amount: null }))
|
||||||
dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
|
dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
|
||||||
},
|
},
|
||||||
|
clearMaxAmount: () => {
|
||||||
|
dispatch(updateSendAmount('0'))
|
||||||
|
},
|
||||||
setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
|
setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ describe('AmountMaxButton Component', function () {
|
|||||||
assert(wrapper.exists('.send-v2__amount-max'))
|
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 {
|
const {
|
||||||
onClick,
|
onClick,
|
||||||
} = wrapper.find('.send-v2__amount-max').props()
|
} = 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', () => {
|
it('should render the expected text when maxModeOn is false', () => {
|
||||||
wrapper.setProps({ maxModeOn: false })
|
wrapper.setProps({ maxModeOn: false })
|
||||||
assert.equal(wrapper.find('.send-v2__amount-max').text(), 'max_t')
|
assert.equal(wrapper.find('.send-v2__amount-max').text(), 'max_t')
|
||||||
|
@ -29,6 +29,7 @@ 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 },
|
||||||
|
'../../../../../selectors/custom-gas': { getBasicGasEstimateLoadingStatus: (s) => `mockButtonDataLoading:${s}`},
|
||||||
'../../../../../store/actions': actionSpies,
|
'../../../../../store/actions': actionSpies,
|
||||||
'../../../../../ducks/send/send.duck': duckActionSpies,
|
'../../../../../ducks/send/send.duck': duckActionSpies,
|
||||||
})
|
})
|
||||||
@ -40,6 +41,7 @@ describe('amount-max-button container', () => {
|
|||||||
it('should map the correct properties to props', () => {
|
it('should map the correct properties to props', () => {
|
||||||
assert.deepEqual(mapStateToProps('mockState'), {
|
assert.deepEqual(mapStateToProps('mockState'), {
|
||||||
balance: 'mockBalance:mockState',
|
balance: 'mockBalance:mockState',
|
||||||
|
buttonDataLoading: 'mockButtonDataLoading:mockState',
|
||||||
gasTotal: 'mockGasTotal:mockState',
|
gasTotal: 'mockGasTotal:mockState',
|
||||||
maxModeOn: 'mockMaxModeOn:mockState',
|
maxModeOn: 'mockMaxModeOn:mockState',
|
||||||
selectedToken: 'mockSelectedToken:mockState',
|
selectedToken: 'mockSelectedToken:mockState',
|
||||||
|
@ -110,7 +110,7 @@ export default class SendAmountRow extends Component {
|
|||||||
showError={inError}
|
showError={inError}
|
||||||
errorType={'amount'}
|
errorType={'amount'}
|
||||||
>
|
>
|
||||||
{!inError && gasTotal && <AmountMaxButton />}
|
{gasTotal && <AmountMaxButton inError={inError} />}
|
||||||
{ this.renderInput() }
|
{ this.renderInput() }
|
||||||
</SendRowWrapper>
|
</SendRowWrapper>
|
||||||
)
|
)
|
||||||
|
@ -8,14 +8,19 @@ import AdvancedGasInputs from '../../../../components/app/gas-customization/adva
|
|||||||
export default class SendGasRow extends Component {
|
export default class SendGasRow extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
balance: PropTypes.string,
|
||||||
conversionRate: PropTypes.number,
|
conversionRate: PropTypes.number,
|
||||||
convertedCurrency: PropTypes.string,
|
convertedCurrency: PropTypes.string,
|
||||||
gasFeeError: PropTypes.bool,
|
gasFeeError: PropTypes.bool,
|
||||||
gasLoadingError: PropTypes.bool,
|
gasLoadingError: PropTypes.bool,
|
||||||
gasTotal: PropTypes.string,
|
gasTotal: PropTypes.string,
|
||||||
|
maxModeOn: PropTypes.bool,
|
||||||
showCustomizeGasModal: PropTypes.func,
|
showCustomizeGasModal: PropTypes.func,
|
||||||
|
selectedToken: PropTypes.object,
|
||||||
|
setAmountToMax: PropTypes.func,
|
||||||
setGasPrice: PropTypes.func,
|
setGasPrice: PropTypes.func,
|
||||||
setGasLimit: PropTypes.func,
|
setGasLimit: PropTypes.func,
|
||||||
|
tokenBalance: PropTypes.string,
|
||||||
gasPriceButtonGroupProps: PropTypes.object,
|
gasPriceButtonGroupProps: PropTypes.object,
|
||||||
gasButtonGroupShown: PropTypes.bool,
|
gasButtonGroupShown: PropTypes.bool,
|
||||||
advancedInlineGasShown: PropTypes.bool,
|
advancedInlineGasShown: PropTypes.bool,
|
||||||
@ -47,6 +52,23 @@ export default class SendGasRow extends Component {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setMaxAmount () {
|
||||||
|
const {
|
||||||
|
balance,
|
||||||
|
gasTotal,
|
||||||
|
selectedToken,
|
||||||
|
setAmountToMax,
|
||||||
|
tokenBalance,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
setAmountToMax({
|
||||||
|
balance,
|
||||||
|
gasTotal,
|
||||||
|
selectedToken,
|
||||||
|
tokenBalance,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
renderContent () {
|
renderContent () {
|
||||||
const {
|
const {
|
||||||
conversionRate,
|
conversionRate,
|
||||||
@ -57,6 +79,7 @@ export default class SendGasRow extends Component {
|
|||||||
gasPriceButtonGroupProps,
|
gasPriceButtonGroupProps,
|
||||||
gasButtonGroupShown,
|
gasButtonGroupShown,
|
||||||
advancedInlineGasShown,
|
advancedInlineGasShown,
|
||||||
|
maxModeOn,
|
||||||
resetGasButtons,
|
resetGasButtons,
|
||||||
setGasPrice,
|
setGasPrice,
|
||||||
setGasLimit,
|
setGasLimit,
|
||||||
@ -71,7 +94,7 @@ export default class SendGasRow extends Component {
|
|||||||
className="gas-price-button-group--small"
|
className="gas-price-button-group--small"
|
||||||
showCheck={false}
|
showCheck={false}
|
||||||
{...gasPriceButtonGroupProps}
|
{...gasPriceButtonGroupProps}
|
||||||
handleGasPriceSelection={(...args) => {
|
handleGasPriceSelection={async (...args) => {
|
||||||
metricsEvent({
|
metricsEvent({
|
||||||
eventOpts: {
|
eventOpts: {
|
||||||
category: 'Transactions',
|
category: 'Transactions',
|
||||||
@ -79,7 +102,10 @@ export default class SendGasRow extends Component {
|
|||||||
name: 'Changed Gas Button',
|
name: 'Changed Gas Button',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
gasPriceButtonGroupProps.handleGasPriceSelection(...args)
|
await gasPriceButtonGroupProps.handleGasPriceSelection(...args)
|
||||||
|
if (maxModeOn) {
|
||||||
|
this.setMaxAmount()
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{ this.renderAdvancedOptionsButton() }
|
{ this.renderAdvancedOptionsButton() }
|
||||||
@ -89,7 +115,12 @@ export default class SendGasRow extends Component {
|
|||||||
convertedCurrency={convertedCurrency}
|
convertedCurrency={convertedCurrency}
|
||||||
gasLoadingError={gasLoadingError}
|
gasLoadingError={gasLoadingError}
|
||||||
gasTotal={gasTotal}
|
gasTotal={gasTotal}
|
||||||
onReset={resetGasButtons}
|
onReset={() => {
|
||||||
|
resetGasButtons()
|
||||||
|
if (maxModeOn) {
|
||||||
|
this.setMaxAmount()
|
||||||
|
}
|
||||||
|
}}
|
||||||
onClick={() => showCustomizeGasModal()}
|
onClick={() => showCustomizeGasModal()}
|
||||||
/>
|
/>
|
||||||
const advancedGasInputs = <div>
|
const advancedGasInputs = <div>
|
||||||
|
@ -6,11 +6,17 @@ import {
|
|||||||
getGasPrice,
|
getGasPrice,
|
||||||
getGasLimit,
|
getGasLimit,
|
||||||
getSendAmount,
|
getSendAmount,
|
||||||
|
getSendFromBalance,
|
||||||
|
getTokenBalance,
|
||||||
} from '../../send.selectors.js'
|
} from '../../send.selectors.js'
|
||||||
|
import {
|
||||||
|
getMaxModeOn,
|
||||||
|
} from '../send-amount-row/amount-max-button/amount-max-button.selectors'
|
||||||
import {
|
import {
|
||||||
isBalanceSufficient,
|
isBalanceSufficient,
|
||||||
calcGasTotal,
|
calcGasTotal,
|
||||||
} from '../../send.utils.js'
|
} from '../../send.utils.js'
|
||||||
|
import { calcMaxAmount } from '../send-amount-row/amount-max-button/amount-max-button.utils'
|
||||||
import {
|
import {
|
||||||
getBasicGasEstimateLoadingStatus,
|
getBasicGasEstimateLoadingStatus,
|
||||||
getRenderableEstimateDataForSmallButtonsFromGWEI,
|
getRenderableEstimateDataForSmallButtonsFromGWEI,
|
||||||
@ -18,6 +24,7 @@ import {
|
|||||||
} from '../../../../selectors/custom-gas'
|
} from '../../../../selectors/custom-gas'
|
||||||
import {
|
import {
|
||||||
showGasButtonGroup,
|
showGasButtonGroup,
|
||||||
|
updateSendErrors,
|
||||||
} from '../../../../ducks/send/send.duck'
|
} from '../../../../ducks/send/send.duck'
|
||||||
import {
|
import {
|
||||||
resetCustomData,
|
resetCustomData,
|
||||||
@ -25,10 +32,11 @@ import {
|
|||||||
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, updateSendAmount } 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)
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
@ -49,6 +57,7 @@ function mapStateToProps (state) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
balance: getSendFromBalance(state),
|
||||||
conversionRate,
|
conversionRate,
|
||||||
convertedCurrency: getCurrentCurrency(state),
|
convertedCurrency: getCurrentCurrency(state),
|
||||||
gasTotal,
|
gasTotal,
|
||||||
@ -65,6 +74,9 @@ function mapStateToProps (state) {
|
|||||||
gasPrice,
|
gasPrice,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
insufficientBalance,
|
insufficientBalance,
|
||||||
|
maxModeOn: getMaxModeOn(state),
|
||||||
|
selectedToken: getSelectedToken(state),
|
||||||
|
tokenBalance: getTokenBalance(state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +97,10 @@ function mapDispatchToProps (dispatch) {
|
|||||||
dispatch(setGasTotal(calcGasTotal(newLimit, gasPrice)))
|
dispatch(setGasTotal(calcGasTotal(newLimit, gasPrice)))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setAmountToMax: maxAmountDataObject => {
|
||||||
|
dispatch(updateSendErrors({ amount: null }))
|
||||||
|
dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
|
||||||
|
},
|
||||||
showGasButtonGroup: () => dispatch(showGasButtonGroup()),
|
showGasButtonGroup: () => dispatch(showGasButtonGroup()),
|
||||||
resetCustomData: () => dispatch(resetCustomData()),
|
resetCustomData: () => dispatch(resetCustomData()),
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,11 @@ proxyquire('../send-gas-row.container.js', {
|
|||||||
getGasPrice: (s) => `mockGasPrice:${s}`,
|
getGasPrice: (s) => `mockGasPrice:${s}`,
|
||||||
getGasLimit: (s) => `mockGasLimit:${s}`,
|
getGasLimit: (s) => `mockGasLimit:${s}`,
|
||||||
getSendAmount: (s) => `mockSendAmount:${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': {
|
'../../send.utils.js': {
|
||||||
isBalanceSufficient: ({
|
isBalanceSufficient: ({
|
||||||
@ -75,6 +80,7 @@ describe('send-gas-row container', () => {
|
|||||||
|
|
||||||
it('should map the correct properties to props', () => {
|
it('should map the correct properties to props', () => {
|
||||||
assert.deepEqual(mapStateToProps('mockState'), {
|
assert.deepEqual(mapStateToProps('mockState'), {
|
||||||
|
balance: 'mockBalance:mockState',
|
||||||
conversionRate: 'mockConversionRate:mockState',
|
conversionRate: 'mockConversionRate:mockState',
|
||||||
convertedCurrency: 'mockConvertedCurrency:mockState',
|
convertedCurrency: 'mockConvertedCurrency:mockState',
|
||||||
gasTotal: 'mockGasTotal:mockState',
|
gasTotal: 'mockGasTotal:mockState',
|
||||||
@ -91,6 +97,9 @@ describe('send-gas-row container', () => {
|
|||||||
gasLimit: 'mockGasLimit:mockState',
|
gasLimit: 'mockGasLimit:mockState',
|
||||||
gasPrice: 'mockGasPrice:mockState',
|
gasPrice: 'mockGasPrice:mockState',
|
||||||
insufficientBalance: false,
|
insufficientBalance: false,
|
||||||
|
maxModeOn: 'mockMaxModeOn:mockState',
|
||||||
|
selectedToken: false,
|
||||||
|
tokenBalance: 'mockTokenBalance:mockState',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
export default class SendRowErrorMessage extends Component {
|
export default class SendRowErrorMessage extends Component {
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ export default class SendRowErrorMessage extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
errorMessage
|
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
|
: null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ export default class SendRowWrapper extends Component {
|
|||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
renderAmountFormRow () {
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
errorType = '',
|
errorType = '',
|
||||||
@ -34,7 +34,39 @@ export default class SendRowWrapper extends Component {
|
|||||||
<div className="send-v2__form-row">
|
<div className="send-v2__form-row">
|
||||||
<div className="send-v2__form-label">
|
<div className="send-v2__form-label">
|
||||||
{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} />}
|
{!showError && showWarning && <SendRowWarningMessage warningType={warningType} />}
|
||||||
{customLabelContent}
|
{customLabelContent}
|
||||||
</div>
|
</div>
|
||||||
@ -45,4 +77,14 @@ export default class SendRowWrapper extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
errorType = '',
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
errorType === 'amount' ? this.renderAmountFormRow() : this.renderFormRow()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user