diff options
author | Dan Miller <danjm.com@gmail.com> | 2018-10-26 13:50:36 +0800 |
---|---|---|
committer | Dan Miller <danjm.com@gmail.com> | 2018-12-04 11:36:22 +0800 |
commit | e3f015c88f30fb4243ebbb3d2f109be8f3d68196 (patch) | |
tree | 62f96055f085624acf1a5fe91365f207daee2f0a /ui | |
parent | 9b9a2cc2e00618167d5fac8103e928fc16153b2d (diff) | |
download | tangerine-wallet-browser-e3f015c88f30fb4243ebbb3d2f109be8f3d68196.tar tangerine-wallet-browser-e3f015c88f30fb4243ebbb3d2f109be8f3d68196.tar.gz tangerine-wallet-browser-e3f015c88f30fb4243ebbb3d2f109be8f3d68196.tar.bz2 tangerine-wallet-browser-e3f015c88f30fb4243ebbb3d2f109be8f3d68196.tar.lz tangerine-wallet-browser-e3f015c88f30fb4243ebbb3d2f109be8f3d68196.tar.xz tangerine-wallet-browser-e3f015c88f30fb4243ebbb3d2f109be8f3d68196.tar.zst tangerine-wallet-browser-e3f015c88f30fb4243ebbb3d2f109be8f3d68196.zip |
Adds speed up slide-in gas customization sidebar
Diffstat (limited to 'ui')
19 files changed, 306 insertions, 58 deletions
diff --git a/ui/app/actions.js b/ui/app/actions.js index b747a97fb..cd24aed0a 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -325,6 +325,8 @@ var actions = { clearPendingTokens, createCancelTransaction, + createSpeedUpTransaction, + approveProviderRequest, rejectProviderRequest, clearApprovedOrigins, @@ -1837,6 +1839,28 @@ function createCancelTransaction (txId, customGasPrice) { } } +function createSpeedUpTransaction (txId, customGasPrice) { + log.debug('background.createSpeedUpTransaction') + let newTx + + return dispatch => { + return new Promise((resolve, reject) => { + background.createSpeedUpTransaction(txId, customGasPrice, (err, newState) => { + if (err) { + dispatch(actions.displayWarning(err.message)) + reject(err) + } + + const { selectedAddressTxList } = newState + newTx = selectedAddressTxList[selectedAddressTxList.length - 1] + resolve(newState) + }) + }) + .then(newState => dispatch(actions.updateMetamaskState(newState))) + .then(() => newTx) + } +} + // // config // @@ -1937,12 +1961,13 @@ function hideModal (payload) { } } -function showSidebar ({ transitionName, type }) { +function showSidebar ({ transitionName, type, props }) { return { type: actions.SIDEBAR_OPEN, value: { transitionName, type, + props, }, } } diff --git a/ui/app/app.js b/ui/app/app.js index 5405f8495..10428d1fd 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -43,6 +43,10 @@ const Alert = require('./components/alert') import AppHeader from './components/app-header' import UnlockPage from './components/pages/unlock-page' +import { + submittedPendingTransactionsSelector, +} from './selectors/transactions' + // Routes const { DEFAULT_ROUTE, @@ -106,12 +110,20 @@ class App extends Component { currentView, setMouseUserState, sidebar, + submittedPendingTransactions, } = this.props const isLoadingNetwork = network === 'loading' && currentView.name !== 'config' const loadMessage = loadingMessage || isLoadingNetwork ? this.getConnectingLabel(loadingMessage) : null log.debug('Main ui render function') + const { + isOpen: sidebarIsOpen, + transitionName: sidebarTransitionName, + type: sidebarType, + props: { transaction: sidebarTransaction }, + } = sidebar + return ( h('.flex-column.full-height', { className: classnames({ 'mouse-user-styles': isMouseUser }), @@ -139,10 +151,12 @@ class App extends Component { // sidebar h(Sidebar, { - sidebarOpen: sidebar.isOpen, + sidebarOpen: sidebarIsOpen, + sidebarShouldClose: sidebarTransaction && !submittedPendingTransactions.find(({ id }) => id === sidebarTransaction.id), hideSidebar: this.props.hideSidebar, - transitionName: sidebar.transitionName, - type: sidebar.type, + transitionName: sidebarTransitionName, + type: sidebarType, + sidebarProps: sidebar.props, }), // network dropdown @@ -254,6 +268,7 @@ App.propTypes = { activeAddress: PropTypes.string, unapprovedTxs: PropTypes.object, seedWords: PropTypes.string, + submittedPendingTransactions: PropTypes.array, unapprovedMsgCount: PropTypes.number, unapprovedPersonalMsgCount: PropTypes.number, unapprovedTypedMessagesCount: PropTypes.number, @@ -313,6 +328,7 @@ function mapStateToProps (state) { isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized), isPopup: state.metamask.isPopup, seedWords: state.metamask.seedWords, + submittedPendingTransactions: submittedPendingTransactionsSelector(state), unapprovedTxs, unapprovedMsgs: state.metamask.unapprovedMsgs, unapprovedMsgCount, 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 ( <div className="advanced-tab__gas-edit-row__input-wrapper"> <input - className="advanced-tab__gas-edit-row__input" + className={classnames('advanced-tab__gas-edit-row__input', { + 'advanced-tab__gas-edit-row__input--error': insufficientBalance, + })} type="number" value={value} min={min} precision={precision} onChange={event => onChange(Number(event.target.value))} /> - <div className="advanced-tab__gas-edit-row__input-arrows"> + <div className={classnames('advanced-tab__gas-edit-row__input-arrows', { + 'advanced-tab__gas-edit-row__input-arrows--error': insufficientBalance, + })}> <div className="advanced-tab__gas-edit-row__input-arrows__i-wrap"><i className="fa fa-sm fa-angle-up" onClick={() => onChange(value + 1)} /></div> <div className="advanced-tab__gas-edit-row__input-arrows__i-wrap"><i className="fa fa-sm fa-angle-down" onClick={() => onChange(value - 1)} /></div> </div> + {insufficientBalance && <div className="advanced-tab__gas-edit-row__insufficient-balance"> + Insufficient Balance + </div>} </div> ) } @@ -70,11 +79,11 @@ export default class AdvancedTabContent extends Component { ) } - renderGasEditRows (customGasPrice, updateCustomGasPrice, customGasLimit, updateCustomGasLimit) { + renderGasEditRows (customGasPrice, updateCustomGasPrice, customGasLimit, updateCustomGasLimit, insufficientBalance) { return ( <div className="advanced-tab__gas-edit-rows"> - { 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) } </div> ) } @@ -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 ) } <div className="advanced-tab__fee-chart__title">Live Gas Price Predictions</div> <GasPriceChart {...gasChartProps} updateCustomGasPrice={updateCustomGasPrice} /> 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, @@ -35,6 +38,9 @@ import { getRenderableBasicEstimateData, } from '../../../selectors/custom-gas' import { + submittedPendingTransactionsSelector, +} from '../../../selectors/transactions' +import { formatCurrency, } from '../../../helpers/confirm-transaction/util' import { @@ -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) + }) }) }) diff --git a/ui/app/components/page-container/page-container.component.js b/ui/app/components/page-container/page-container.component.js index d618dae4f..45dfff517 100644 --- a/ui/app/components/page-container/page-container.component.js +++ b/ui/app/components/page-container/page-container.component.js @@ -112,17 +112,19 @@ export default class PageContainer extends PureComponent { tabs={this.renderTabs()} headerCloseText={headerCloseText} /> - <div className="page-container__content"> - { this.renderContent() } + <div className="page-container__bottom"> + <div className="page-container__content"> + { this.renderContent() } + </div> + <PageContainerFooter + onCancel={onCancel} + cancelText={cancelText} + hideCancel={hideCancel} + onSubmit={onSubmit} + submitText={submitText} + disabled={disabled} + /> </div> - <PageContainerFooter - onCancel={onCancel} - cancelText={cancelText} - hideCancel={hideCancel} - onSubmit={onSubmit} - submitText={submitText} - disabled={disabled} - /> </div> ) } diff --git a/ui/app/components/sidebars/index.scss b/ui/app/components/sidebars/index.scss index 5ab0664df..6fcd93e99 100644 --- a/ui/app/components/sidebars/index.scss +++ b/ui/app/components/sidebars/index.scss @@ -1,3 +1,5 @@ +@import './sidebar-content'; + .sidebar-right-enter { transition: transform 300ms ease-in-out; transform: translateX(-100%); diff --git a/ui/app/components/sidebars/sidebar-content.scss b/ui/app/components/sidebars/sidebar-content.scss new file mode 100644 index 000000000..c0973be2c --- /dev/null +++ b/ui/app/components/sidebars/sidebar-content.scss @@ -0,0 +1,40 @@ +.sidebar-left { + .gas-modal-page-container { + .page-container { + max-width: 100%; + } + + .gas-price-chart { + margin-left: 10px; + } + + .page-container__bottom { + display: flex; + flex-direction: column; + flex-flow: space-between; + height: 100%; + } + + .page-container__content { + overflow-y: inherit; + } + + .basic-tab-content { + height: 377px; + margin-bottom: 0px; + border-bottom: 1px solid #d2d8dd; + } + + .advanced-tab__fee-chart { + height: 320px; + } + + .advanced-tab__fee-chart__speed-buttons { + bottom: 77px; + } + + .gas-modal-content__info-row { + height: 170px; + } + } +}
\ No newline at end of file diff --git a/ui/app/components/sidebars/sidebar.component.js b/ui/app/components/sidebars/sidebar.component.js index 57cdd7111..f68515ad6 100644 --- a/ui/app/components/sidebars/sidebar.component.js +++ b/ui/app/components/sidebars/sidebar.component.js @@ -3,14 +3,17 @@ import PropTypes from 'prop-types' import ReactCSSTransitionGroup from 'react-addons-css-transition-group' import WalletView from '../wallet-view' import { WALLET_VIEW_SIDEBAR } from './sidebar.constants' +import CustomizeGas from '../gas-customization/gas-modal-page-container/' export default class Sidebar extends Component { static propTypes = { sidebarOpen: PropTypes.bool, hideSidebar: PropTypes.func, + sidebarShouldClose: PropTypes.bool, transitionName: PropTypes.string, type: PropTypes.string, + sidebarProps: PropTypes.object, }; renderOverlay () { @@ -18,19 +21,27 @@ export default class Sidebar extends Component { } renderSidebarContent () { - const { type } = this.props - + const { type, sidebarProps = {} } = this.props + const { transaction = {} } = sidebarProps switch (type) { case WALLET_VIEW_SIDEBAR: return <WalletView responsiveDisplayClassname={'sidebar-right' } /> + case 'customize-gas': + return <div className={'sidebar-left'}><CustomizeGas transaction={transaction} /></div> default: return null } } + componentDidUpdate (prevProps) { + if (!prevProps.sidebarShouldClose && this.props.sidebarShouldClose) { + this.props.hideSidebar() + } + } + render () { - const { transitionName, sidebarOpen } = this.props + const { transitionName, sidebarOpen, sidebarShouldClose } = this.props return ( <div> @@ -39,9 +50,9 @@ export default class Sidebar extends Component { transitionEnterTimeout={300} transitionLeaveTimeout={200} > - { sidebarOpen ? this.renderSidebarContent() : null } + { sidebarOpen && !sidebarShouldClose ? this.renderSidebarContent() : null } </ReactCSSTransitionGroup> - { sidebarOpen ? this.renderOverlay() : null } + { sidebarOpen && !sidebarShouldClose ? this.renderOverlay() : null } </div> ) } diff --git a/ui/app/components/sidebars/tests/sidebars-component.test.js b/ui/app/components/sidebars/tests/sidebars-component.test.js index e2d77518a..cee22aca8 100644 --- a/ui/app/components/sidebars/tests/sidebars-component.test.js +++ b/ui/app/components/sidebars/tests/sidebars-component.test.js @@ -6,6 +6,7 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group' import Sidebar from '../sidebar.component.js' import WalletView from '../../wallet-view' +import CustomizeGas from '../../gas-customization/gas-modal-page-container/' const propsMethodSpies = { hideSidebar: sinon.spy(), @@ -59,6 +60,14 @@ describe('Sidebar Component', function () { assert.equal(renderSidebarContent.props.responsiveDisplayClassname, 'sidebar-right') }) + it('should render sidebar content with the correct props', () => { + wrapper.setProps({ type: 'customize-gas' }) + renderSidebarContent = wrapper.instance().renderSidebarContent() + const renderedSidebarContent = shallow(renderSidebarContent) + assert(renderedSidebarContent.hasClass('sidebar-left')) + assert(renderedSidebarContent.childAt(0).is(CustomizeGas)) + }) + it('should not render with an unrecognized type', () => { wrapper.setProps({ type: 'foobar' }) renderSidebarContent = wrapper.instance().renderSidebarContent() diff --git a/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js b/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js index a4f28fd63..a79213ace 100644 --- a/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js +++ b/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js @@ -26,6 +26,7 @@ export default class TransactionListItemDetails extends PureComponent { const prefix = prefixForNetwork(metamaskNetworkId) const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}` + global.platform.openWindow({ url: etherscanUrl }) this.setState({ showTransactionDetails: true }) } diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js index 696634fe0..d34dba210 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -27,6 +27,8 @@ export default class TransactionListItem extends PureComponent { tokenData: PropTypes.object, transaction: PropTypes.object, value: PropTypes.string, + fetchBasicGasEstimates: PropTypes.func, + fetchGasEstimates: PropTypes.func, } state = { @@ -69,9 +71,12 @@ export default class TransactionListItem extends PureComponent { } resubmit () { - const { transaction: { id }, retryTransaction, history } = this.props - return retryTransaction(id) - .then(id => history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`)) + const { transaction, retryTransaction, fetchBasicGasEstimates, fetchGasEstimates } = this.props + fetchBasicGasEstimates().then(basicEstimates => { + fetchGasEstimates(basicEstimates.blockTime) + }).then(() => { + retryTransaction(transaction) + }) } renderPrimaryCurrency () { diff --git a/ui/app/components/transaction-list-item/transaction-list-item.container.js b/ui/app/components/transaction-list-item/transaction-list-item.container.js index 62ed7a73f..df408b36b 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.container.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.container.js @@ -3,10 +3,16 @@ import { withRouter } from 'react-router-dom' import { compose } from 'recompose' import withMethodData from '../../higher-order-components/with-method-data' import TransactionListItem from './transaction-list-item.component' -import { setSelectedToken, retryTransaction, showModal } from '../../actions' +import { setSelectedToken, showModal, showSidebar } from '../../actions' import { hexToDecimal } from '../../helpers/conversions.util' import { getTokenData } from '../../helpers/transactions.util' import { formatDate } from '../../util' +import { + fetchBasicGasEstimates, + fetchGasEstimates, + setCustomGasPrice, + setCustomGasLimit, +} from '../../ducks/gas.duck' const mapStateToProps = (state, ownProps) => { const { transaction: { txParams: { value, nonce, data } = {}, time } = {} } = ownProps @@ -23,8 +29,18 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = dispatch => { return { + fetchBasicGasEstimates: () => dispatch(fetchBasicGasEstimates()), + fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)), setSelectedToken: tokenAddress => dispatch(setSelectedToken(tokenAddress)), - retryTransaction: transactionId => dispatch(retryTransaction(transactionId)), + retryTransaction: (transaction) => { + dispatch(setCustomGasPrice(transaction.txParams.gasPrice)) + dispatch(setCustomGasLimit(transaction.txParams.gas)) + dispatch(showSidebar({ + transitionName: 'sidebar-left', + type: 'customize-gas', + props: { transaction }, + })) + }, showCancelModal: (transactionId, originalGasPrice) => { return dispatch(showModal({ name: 'CANCEL_TRANSACTION', transactionId, originalGasPrice })) }, diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 5c86d397d..ea25b8693 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -52,6 +52,7 @@ function reduceApp (state, action) { isOpen: false, transitionName: '', type: '', + props: {}, }, alertOpen: false, alertMessage: null, diff --git a/ui/app/selectors.js b/ui/app/selectors.js index f99f14aa8..8259bb052 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -35,6 +35,7 @@ const selectors = { getTotalUnapprovedCount, preferencesSelector, getMetaMaskAccounts, + getCurrentEthBalance, } module.exports = selectors @@ -137,6 +138,10 @@ function getCurrentAccountWithSendEtherInfo (state) { return accounts.find(({ address }) => address === currentAddress) } +function getCurrentEthBalance (state) { + return getCurrentAccountWithSendEtherInfo(state).balance +} + function getGasIsLoading (state) { return state.appState.gasIsLoading } |