aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Miller <danjm.com@gmail.com>2018-10-26 13:50:36 +0800
committerDan Miller <danjm.com@gmail.com>2018-12-04 11:36:22 +0800
commite3f015c88f30fb4243ebbb3d2f109be8f3d68196 (patch)
tree62f96055f085624acf1a5fe91365f207daee2f0a
parent9b9a2cc2e00618167d5fac8103e928fc16153b2d (diff)
downloadtangerine-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
-rw-r--r--ui/app/actions.js27
-rw-r--r--ui/app/app.js22
-rw-r--r--ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js25
-rw-r--r--ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/index.scss12
-rw-r--r--ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js18
-rw-r--r--ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js4
-rw-r--r--ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js57
-rw-r--r--ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js1
-rw-r--r--ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js66
-rw-r--r--ui/app/components/page-container/page-container.component.js22
-rw-r--r--ui/app/components/sidebars/index.scss2
-rw-r--r--ui/app/components/sidebars/sidebar-content.scss40
-rw-r--r--ui/app/components/sidebars/sidebar.component.js21
-rw-r--r--ui/app/components/sidebars/tests/sidebars-component.test.js9
-rw-r--r--ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js1
-rw-r--r--ui/app/components/transaction-list-item/transaction-list-item.component.js11
-rw-r--r--ui/app/components/transaction-list-item/transaction-list-item.container.js20
-rw-r--r--ui/app/reducers/app.js1
-rw-r--r--ui/app/selectors.js5
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
}