From e3f015c88f30fb4243ebbb3d2f109be8f3d68196 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 26 Oct 2018 03:20:36 -0230 Subject: Adds speed up slide-in gas customization sidebar --- .../advanced-tab-content.component.js | 25 +++++--- .../advanced-tab-content/index.scss | 12 ++++ .../tests/advanced-tab-content-component.test.js | 18 +++--- .../gas-modal-page-container.component.js | 4 +- .../gas-modal-page-container.container.js | 57 +++++++++++++++---- .../gas-modal-page-container-component.test.js | 1 + .../gas-modal-page-container-container.test.js | 66 +++++++++++++++++++--- 7 files changed, 149 insertions(+), 34 deletions(-) (limited to 'ui/app/components/gas-customization') diff --git a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js index b9ae93684..23945483d 100644 --- a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js +++ b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js @@ -1,5 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' +import classnames from 'classnames' import GasPriceChart from '../../gas-price-chart' export default class AdvancedTabContent extends Component { @@ -16,23 +17,31 @@ export default class AdvancedTabContent extends Component { totalFee: PropTypes.string, timeRemaining: PropTypes.string, gasChartProps: PropTypes.object, + insufficientBalance: PropTypes.bool, } - gasInput (value, onChange, min, precision, showGWEI) { + gasInput (value, onChange, min, insufficientBalance, precision, showGWEI) { return (
onChange(Number(event.target.value))} /> -
+
onChange(value + 1)} />
onChange(value - 1)} />
+ {insufficientBalance &&
+ Insufficient Balance +
}
) } @@ -70,11 +79,11 @@ export default class AdvancedTabContent extends Component { ) } - renderGasEditRows (customGasPrice, updateCustomGasPrice, customGasLimit, updateCustomGasLimit) { + renderGasEditRows (customGasPrice, updateCustomGasPrice, customGasLimit, updateCustomGasLimit, insufficientBalance) { return (
- { this.renderGasEditRow('gasPrice', customGasPrice, updateCustomGasPrice, customGasPrice, 9, true) } - { this.renderGasEditRow('gasLimit', customGasLimit, updateCustomGasLimit, customGasLimit, 0) } + { this.renderGasEditRow('gasPrice', customGasPrice, updateCustomGasPrice, customGasPrice, insufficientBalance, 9, true) } + { this.renderGasEditRow('gasLimit', customGasLimit, updateCustomGasLimit, customGasLimit, insufficientBalance, 0) }
) } @@ -86,6 +95,7 @@ export default class AdvancedTabContent extends Component { timeRemaining, customGasPrice, customGasLimit, + insufficientBalance, totalFee, gasChartProps, } = this.props @@ -98,7 +108,8 @@ export default class AdvancedTabContent extends Component { customGasPrice, updateCustomGasPrice, customGasLimit, - updateCustomGasLimit + updateCustomGasLimit, + insufficientBalance ) }
Live Gas Price Predictions
diff --git a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss index 69bb65f2f..b62919c0a 100644 --- a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss +++ b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss @@ -102,6 +102,11 @@ } } + &__insufficient-balance { + font-size: 12px; + color: red; + } + &__input-wrapper { position: relative; @@ -119,6 +124,10 @@ margin-top: 7px; } + &__input--error { + border: 1px solid $red; + } + &__input-arrows { position: absolute; top: 7px; @@ -155,6 +164,9 @@ } } + &__input-arrows--error { + border: 1px solid $red; + } input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; diff --git a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js index d4549f4cd..0c25874bb 100644 --- a/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js +++ b/ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js @@ -27,6 +27,7 @@ describe('AdvancedTabContent Component', function () { customGasLimit={23456} timeRemaining={21500} totalFee={'$0.25'} + insufficientBalance={false} />, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }) }) @@ -63,11 +64,13 @@ describe('AdvancedTabContent Component', function () { assert.equal(AdvancedTabContent.prototype.renderGasEditRows.callCount, 1) const renderDataSummaryArgs = AdvancedTabContent.prototype.renderDataSummary.getCall(0).args assert.deepEqual(renderDataSummaryArgs, ['$0.25', 21500]) + }) + it('should call renderGasEditRows with the expected params', () => { assert.equal(AdvancedTabContent.prototype.renderGasEditRows.callCount, 1) const renderGasEditRowArgs = AdvancedTabContent.prototype.renderGasEditRows.getCall(0).args assert.deepEqual(renderGasEditRowArgs, [ - 11, propsMethodSpies.updateCustomGasPrice, 23456, propsMethodSpies.updateCustomGasLimit, + 11, propsMethodSpies.updateCustomGasPrice, 23456, propsMethodSpies.updateCustomGasLimit, false, ]) }) }) @@ -142,7 +145,8 @@ describe('AdvancedTabContent Component', function () { 'mockGasPrice', () => 'mockUpdateCustomGasPriceReturn', 'mockGasLimit', - () => 'mockUpdateCustomGasLimitReturn' + () => 'mockUpdateCustomGasLimitReturn', + false )) }) @@ -161,10 +165,10 @@ describe('AdvancedTabContent Component', function () { const renderGasEditRowSpyArgs = AdvancedTabContent.prototype.renderGasEditRow.args assert.equal(renderGasEditRowSpyArgs.length, 2) assert.deepEqual(renderGasEditRowSpyArgs[0].map(String), [ - 'gasPrice', 'mockGasPrice', () => 'mockUpdateCustomGasPriceReturn', 'mockGasPrice', 9, true, + 'gasPrice', 'mockGasPrice', () => 'mockUpdateCustomGasPriceReturn', 'mockGasPrice', false, 9, true, ].map(String)) assert.deepEqual(renderGasEditRowSpyArgs[1].map(String), [ - 'gasLimit', 'mockGasLimit', () => 'mockUpdateCustomGasLimitReturn', 'mockGasLimit', 0, + 'gasLimit', 'mockGasLimit', () => 'mockUpdateCustomGasLimitReturn', 'mockGasLimit', false, 0, ].map(String)) }) }) @@ -195,8 +199,8 @@ describe('AdvancedTabContent Component', function () { 321, value => value + 7, 0, - 8, - false + false, + 8 )) }) @@ -204,7 +208,7 @@ describe('AdvancedTabContent Component', function () { assert(gasInput.hasClass('advanced-tab__gas-edit-row__input-wrapper')) }) - it('should render an input, but not a GWEI symbol', () => { + it('should render two children, including an input', () => { assert.equal(gasInput.children().length, 2) assert(gasInput.children().at(0).hasClass('advanced-tab__gas-edit-row__input')) }) diff --git a/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js index 8f23b22e0..a804f7b64 100644 --- a/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js +++ b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js @@ -48,6 +48,7 @@ export default class GasModalPageContainer extends Component { newTotalFiat, gasChartProps, currentTimeEstimate, + insufficientBalance, }) { const { transactionFee } = this.props return ( @@ -60,6 +61,7 @@ export default class GasModalPageContainer extends Component { transactionFee={transactionFee} totalFee={newTotalFiat} gasChartProps={gasChartProps} + insufficientBalance={insufficientBalance} /> ) } @@ -139,7 +141,7 @@ export default class GasModalPageContainer extends Component { title={this.context.t('customGas')} subtitle={this.context.t('customGasSubTitle')} tabsComponent={this.renderTabs(infoRowProps, tabProps)} - disabled={false} + disabled={tabProps.insufficientBalance} onCancel={() => cancelAndClose()} onClose={() => cancelAndClose()} onSubmit={() => { diff --git a/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js index d098d49cc..c3b7a5960 100644 --- a/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js +++ b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js @@ -5,6 +5,8 @@ import { hideModal, setGasLimit, setGasPrice, + createSpeedUpTransaction, + hideSidebar, } from '../../../actions' import { setCustomGasPrice, @@ -22,6 +24,7 @@ import { getCurrentCurrency, conversionRateSelector as getConversionRate, getSelectedToken, + getCurrentEthBalance, } from '../../../selectors.js' import { formatTimeEstimate, @@ -34,6 +37,9 @@ import { getEstimatedGasTimes, getRenderableBasicEstimateData, } from '../../../selectors/custom-gas' +import { + submittedPendingTransactionsSelector, +} from '../../../selectors/transactions' import { formatCurrency, } from '../../../helpers/confirm-transaction/util' @@ -48,17 +54,19 @@ import { } from '../../../helpers/formatters' import { calcGasTotal, + isBalanceSufficient, } from '../../send/send.utils' import { addHexPrefix } from 'ethereumjs-util' import { getAdjacentGasPrices, extrapolateY } from '../gas-price-chart/gas-price-chart.utils' -const mapStateToProps = state => { +const mapStateToProps = (state, ownProps) => { + const { transaction = {} } = ownProps const buttonDataLoading = getBasicGasEstimateLoadingStatus(state) - const { gasPrice: currentGasPrice, gas: currentGasLimit, value } = getTxParams(state) - const gasTotal = calcGasTotal(currentGasLimit, currentGasPrice) - + 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) @@ -74,6 +82,14 @@ const mapStateToProps = state => { const gasPrices = getEstimatedGasPrices(state) const estimatedTimes = getEstimatedGasTimes(state) + const balance = getCurrentEthBalance(state) + + const insufficientBalance = !isBalanceSufficient({ + amount: value, + gasTotal, + balance, + conversionRate, + }) return { hideBasic, @@ -104,6 +120,9 @@ const mapStateToProps = state => { transactionFee: addHexWEIsToRenderableEth('0x0', customGasTotal), sendAmount: addHexWEIsToRenderableEth(value, '0x0'), }, + isSpeedUp: transaction.status === 'submitted', + txId: transaction.id, + insufficientBalance, } } @@ -125,18 +144,24 @@ const mapDispatchToProps = dispatch => { updateConfirmTxGasAndCalculate: (gasLimit, gasPrice) => { return dispatch(updateGasAndCalculate({ gasLimit, gasPrice })) }, + createSpeedUpTransaction: (txId, gasPrice) => { + return dispatch(createSpeedUpTransaction(txId, gasPrice)) + }, hideGasButtonGroup: () => dispatch(hideGasButtonGroup()), setCustomTimeEstimate: (timeEstimateInSeconds) => dispatch(setCustomTimeEstimate(timeEstimateInSeconds)), + hideSidebar: () => dispatch(hideSidebar()), } } const mergeProps = (stateProps, dispatchProps, ownProps) => { - const { gasPriceButtonGroupProps, isConfirm } = stateProps + const { gasPriceButtonGroupProps, isConfirm, isSpeedUp, txId } = stateProps const { updateCustomGasPrice: dispatchUpdateCustomGasPrice, hideGasButtonGroup: dispatchHideGasButtonGroup, setGasData: dispatchSetGasData, updateConfirmTxGasAndCalculate: dispatchUpdateConfirmTxGasAndCalculate, + createSpeedUpTransaction: dispatchCreateSpeedUpTransaction, + hideSidebar: dispatchHideSidebar, ...otherDispatchProps } = dispatchProps @@ -144,12 +169,17 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { ...stateProps, ...otherDispatchProps, ...ownProps, - onSubmit: isConfirm - ? dispatchUpdateConfirmTxGasAndCalculate - : (newLimit, newPrice) => { - dispatchSetGasData(newLimit, newPrice) + onSubmit: (gasLimit, gasPrice) => { + if (isConfirm) { + dispatchUpdateConfirmTxGasAndCalculate(gasLimit, gasPrice) + } else if (isSpeedUp) { + dispatchCreateSpeedUpTransaction(txId, gasPrice) + dispatchHideSidebar() + } else { + dispatchSetGasData(gasLimit, gasPrice) dispatchHideGasButtonGroup() - }, + } + }, gasPriceButtonGroupProps: { ...gasPriceButtonGroupProps, handleGasPriceSelection: dispatchUpdateCustomGasPrice, @@ -171,9 +201,12 @@ function calcCustomGasLimit (customGasLimitInHex) { return parseInt(customGasLimitInHex, 16) } -function getTxParams (state) { +function getTxParams (state, transactionId) { const { confirmTransaction: { txData }, metamask: { send } } = state - return txData.txParams || { + const pendingTransactions = submittedPendingTransactionsSelector(state) + const pendingTransaction = pendingTransactions.find(({ id }) => id === transactionId) + const { txParams: pendingTxParams } = pendingTransaction || {} + return txData.txParams || pendingTxParams || { from: send.from, gas: send.gasLimit, gasPrice: send.gasPrice || getAveragePriceEstimateInHexWEI(state), diff --git a/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js b/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js index 61871f5f3..16f4b8cd7 100644 --- a/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js +++ b/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js @@ -68,6 +68,7 @@ describe('GasModalPageContainer Component', function () { currentTimeEstimate={'1 min 31 sec'} customGasPriceInHex={'mockCustomGasPriceInHex'} customGasLimitInHex={'mockCustomGasLimitInHex'} + insufficientBalance={false} />, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }) }) diff --git a/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js b/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js index 54fbfb66c..1ed28f33e 100644 --- a/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js +++ b/ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js @@ -44,14 +44,16 @@ proxyquire('../gas-modal-page-container.container.js', { '../../../ducks/gas.duck': gasActionSpies, '../../../ducks/confirm-transaction.duck': confirmTransactionActionSpies, '../../../ducks/send.duck': sendActionSpies, + '../../../selectors.js': { + getCurrentEthBalance: (state) => state.metamask.balance || '0x0', + }, }) describe('gas-modal-page-container container', () => { describe('mapStateToProps()', () => { - it('should map the correct properties to props', () => { - const mockState2 = { + const baseMockState = { appState: { modal: { modalState: { @@ -92,9 +94,7 @@ describe('gas-modal-page-container container', () => { }, }, } - const result2 = mapStateToProps(mockState2) - - assert.deepEqual(result2, { + const baseExpectedResult = { isConfirm: true, customGasPrice: 4.294967295, customGasLimit: 2863311530, @@ -116,13 +116,40 @@ describe('gas-modal-page-container container', () => { }, hideBasic: true, infoRowProps: { - originalTotalFiat: '22.58', - originalTotalEth: '0.451569 ETH', + originalTotalFiat: '637.41', + originalTotalEth: '12.748189 ETH', newTotalFiat: '637.41', newTotalEth: '12.748189 ETH', sendAmount: '0.45036 ETH', transactionFee: '12.297829 ETH', }, + insufficientBalance: true, + isSpeedUp: false, + txId: 34, + } + const baseMockOwnProps = { transaction: { id: 34 } } + const tests = [ + { mockState: baseMockState, expectedResult: baseExpectedResult, mockOwnProps: baseMockOwnProps }, + { + mockState: Object.assign({}, baseMockState, { + metamask: { ...baseMockState.metamask, balance: '0xfffffffffffffffffffff' }, + }), + expectedResult: Object.assign({}, baseExpectedResult, { insufficientBalance: false }), + mockOwnProps: baseMockOwnProps, + }, + { + mockState: baseMockState, + mockOwnProps: Object.assign({}, baseMockOwnProps, { + transaction: { id: 34, status: 'submitted' }, + }), + expectedResult: Object.assign({}, baseExpectedResult, { isSpeedUp: true }), + }, + ] + + let result + tests.forEach(({ mockState, mockOwnProps, expectedResult}) => { + result = mapStateToProps(mockState, mockOwnProps) + assert.deepEqual(result, expectedResult) }) }) @@ -230,9 +257,21 @@ describe('gas-modal-page-container container', () => { setGasData: sinon.spy(), updateConfirmTxGasAndCalculate: sinon.spy(), someOtherDispatchProp: sinon.spy(), + createSpeedUpTransaction: sinon.spy(), + hideSidebar: sinon.spy(), } ownProps = { someOwnProp: 123 } }) + + afterEach(() => { + dispatchProps.updateCustomGasPrice.resetHistory() + dispatchProps.hideGasButtonGroup.resetHistory() + dispatchProps.setGasData.resetHistory() + dispatchProps.updateConfirmTxGasAndCalculate.resetHistory() + dispatchProps.someOtherDispatchProp.resetHistory() + dispatchProps.createSpeedUpTransaction.resetHistory() + dispatchProps.hideSidebar.resetHistory() + }) it('should return the expected props when isConfirm is true', () => { const result = mergeProps(stateProps, dispatchProps, ownProps) @@ -289,6 +328,19 @@ describe('gas-modal-page-container container', () => { result.someOtherDispatchProp() assert.equal(dispatchProps.someOtherDispatchProp.callCount, 1) }) + + it('should dispatch the expected actions from obSubmit when isConfirm is false and isSpeedUp is true', () => { + const result = mergeProps(Object.assign({}, stateProps, { isSpeedUp: true, isConfirm: false }), dispatchProps, ownProps) + + result.onSubmit() + + assert.equal(dispatchProps.updateConfirmTxGasAndCalculate.callCount, 0) + assert.equal(dispatchProps.setGasData.callCount, 0) + assert.equal(dispatchProps.hideGasButtonGroup.callCount, 0) + + assert.equal(dispatchProps.createSpeedUpTransaction.callCount, 1) + assert.equal(dispatchProps.hideSidebar.callCount, 1) + }) }) }) -- cgit