mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Add togglable advanced gas controls on send and confirm screens (#6112)
* Extract advanced gas input controls to their own component * Add advanced inline gas toggle to settings * Add optional advanced inline gas to send send screen * Adds optional advanced gas inputs to the confirm screen * Add info modals for advanced gas inputs. * Fix translation of advance gas toggle description. * Lint and unit test fixes for inline-advanced-gas-inputs * Increase margin above advanced options button on send screen * Move methods from constructor to property syntax in advanced-gas-inputs.component
This commit is contained in:
parent
c28fa31250
commit
38b91f63a2
@ -529,6 +529,9 @@
|
||||
"gasLimitCalculation": {
|
||||
"message": "We calculate the suggested gas limit based on network success rates."
|
||||
},
|
||||
"gasLimitInfoModalContent": {
|
||||
"message": "Gas limit is the maximum amount of units of gas you are willing to spend."
|
||||
},
|
||||
"gasLimitRequired": {
|
||||
"message": "Gas Limit Required"
|
||||
},
|
||||
@ -547,6 +550,9 @@
|
||||
"gasPriceExtremelyLow": {
|
||||
"message": "Gas Price Extremely Low"
|
||||
},
|
||||
"gasPriceInfoModalContent": {
|
||||
"message": "Gas price specifies the amount of Ether you are willing to pay for each unit of gas."
|
||||
},
|
||||
"gasPriceNoDenom": {
|
||||
"message": "Gas Price"
|
||||
},
|
||||
@ -1210,6 +1216,12 @@
|
||||
"shapeshiftBuy": {
|
||||
"message": "Buy with Shapeshift"
|
||||
},
|
||||
"showAdvancedGasInline": {
|
||||
"message": "Advanced gas controls"
|
||||
},
|
||||
"showAdvancedGasInlineDescription": {
|
||||
"message": "Select this to show gas price and limit controls directly on the send and confirm screens."
|
||||
},
|
||||
"showPrivateKeys": {
|
||||
"message": "Show Private Keys"
|
||||
},
|
||||
|
@ -43,4 +43,8 @@
|
||||
font-size: .625rem;
|
||||
}
|
||||
}
|
||||
|
||||
.advanced-gas-inputs__gas-edit-rows {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,10 @@
|
||||
|
||||
&__gas-fee {
|
||||
border-bottom: 1px solid $geyser;
|
||||
|
||||
.advanced-gas-inputs__gas-edit-rows {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&__function-type {
|
||||
|
@ -0,0 +1,146 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
export default class AdvancedTabContent extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
updateCustomGasPrice: PropTypes.func,
|
||||
updateCustomGasLimit: PropTypes.func,
|
||||
customGasPrice: PropTypes.number,
|
||||
customGasLimit: PropTypes.number,
|
||||
insufficientBalance: PropTypes.bool,
|
||||
customPriceIsSafe: PropTypes.bool,
|
||||
isSpeedUp: PropTypes.bool,
|
||||
showGasPriceInfoModal: PropTypes.func,
|
||||
showGasLimitInfoModal: PropTypes.func,
|
||||
}
|
||||
|
||||
debouncedGasLimitReset = debounce((dVal) => {
|
||||
if (dVal < 21000) {
|
||||
this.props.updateCustomGasLimit(21000)
|
||||
}
|
||||
}, 1000, { trailing: true })
|
||||
|
||||
onChangeGasLimit = (val) => {
|
||||
this.props.updateCustomGasLimit(val)
|
||||
this.debouncedGasLimitReset(val)
|
||||
}
|
||||
|
||||
gasInputError ({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value }) {
|
||||
const { t } = this.context
|
||||
let errorText
|
||||
let errorType
|
||||
let isInError = true
|
||||
|
||||
|
||||
if (insufficientBalance) {
|
||||
errorText = t('insufficientBalance')
|
||||
errorType = 'error'
|
||||
} else if (labelKey === 'gasPrice' && isSpeedUp && value === 0) {
|
||||
errorText = t('zeroGasPriceOnSpeedUpError')
|
||||
errorType = 'error'
|
||||
} else if (labelKey === 'gasPrice' && !customPriceIsSafe) {
|
||||
errorText = t('gasPriceExtremelyLow')
|
||||
errorType = 'warning'
|
||||
} else {
|
||||
isInError = false
|
||||
}
|
||||
|
||||
return {
|
||||
isInError,
|
||||
errorText,
|
||||
errorType,
|
||||
}
|
||||
}
|
||||
|
||||
gasInput ({ labelKey, value, onChange, insufficientBalance, showGWEI, customPriceIsSafe, isSpeedUp }) {
|
||||
const {
|
||||
isInError,
|
||||
errorText,
|
||||
errorType,
|
||||
} = this.gasInputError({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value })
|
||||
|
||||
return (
|
||||
<div className="advanced-gas-inputs__gas-edit-row__input-wrapper">
|
||||
<input
|
||||
className={classnames('advanced-gas-inputs__gas-edit-row__input', {
|
||||
'advanced-gas-inputs__gas-edit-row__input--error': isInError && errorType === 'error',
|
||||
'advanced-gas-inputs__gas-edit-row__input--warning': isInError && errorType === 'warning',
|
||||
})}
|
||||
type="number"
|
||||
value={value}
|
||||
onChange={event => onChange(Number(event.target.value))}
|
||||
/>
|
||||
<div className={classnames('advanced-gas-inputs__gas-edit-row__input-arrows', {
|
||||
'advanced-gas-inputs__gas-edit-row__input--error': isInError && errorType === 'error',
|
||||
'advanced-gas-inputs__gas-edit-row__input--warning': isInError && errorType === 'warning',
|
||||
})}>
|
||||
<div className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap" onClick={() => onChange(value + 1)}><i className="fa fa-sm fa-angle-up" /></div>
|
||||
<div className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap" onClick={() => onChange(value - 1)}><i className="fa fa-sm fa-angle-down" /></div>
|
||||
</div>
|
||||
{ isInError
|
||||
? <div className={`advanced-gas-inputs__gas-edit-row__${errorType}-text`}>
|
||||
{ errorText }
|
||||
</div>
|
||||
: null }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
infoButton (onClick) {
|
||||
return <i className="fa fa-info-circle" onClick={onClick} />
|
||||
}
|
||||
|
||||
renderGasEditRow (gasInputArgs) {
|
||||
return (
|
||||
<div className="advanced-gas-inputs__gas-edit-row">
|
||||
<div className="advanced-gas-inputs__gas-edit-row__label">
|
||||
{ this.context.t(gasInputArgs.labelKey) }
|
||||
{ this.infoButton(() => gasInputArgs.infoOnClick()) }
|
||||
</div>
|
||||
{ this.gasInput(gasInputArgs) }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
customGasPrice,
|
||||
updateCustomGasPrice,
|
||||
customGasLimit,
|
||||
insufficientBalance,
|
||||
customPriceIsSafe,
|
||||
isSpeedUp,
|
||||
showGasPriceInfoModal,
|
||||
showGasLimitInfoModal,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className="advanced-gas-inputs__gas-edit-rows">
|
||||
{ this.renderGasEditRow({
|
||||
labelKey: 'gasPrice',
|
||||
value: customGasPrice,
|
||||
onChange: updateCustomGasPrice,
|
||||
insufficientBalance,
|
||||
customPriceIsSafe,
|
||||
showGWEI: true,
|
||||
isSpeedUp,
|
||||
infoOnClick: showGasPriceInfoModal,
|
||||
}) }
|
||||
{ this.renderGasEditRow({
|
||||
labelKey: 'gasLimit',
|
||||
value: customGasLimit,
|
||||
onChange: this.onChangeGasLimit,
|
||||
insufficientBalance,
|
||||
customPriceIsSafe,
|
||||
infoOnClick: showGasLimitInfoModal,
|
||||
}) }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { showModal } from '../../../actions'
|
||||
import AdvancedGasInputs from './advanced-gas-inputs.component'
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
showGasPriceInfoModal: modalName => dispatch(showModal({ name: 'GAS_PRICE_INFO_MODAL' })),
|
||||
showGasLimitInfoModal: modalName => dispatch(showModal({ name: 'GAS_LIMIT_INFO_MODAL' })),
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(AdvancedGasInputs)
|
@ -0,0 +1 @@
|
||||
export { default } from './advanced-gas-inputs.container'
|
@ -0,0 +1,133 @@
|
||||
.advanced-gas-inputs {
|
||||
&__gas-edit-rows {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__gas-edit-row {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
width: 47.5%;
|
||||
|
||||
&__label {
|
||||
color: #313B5E;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (max-width: 576px) {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.fa-info-circle {
|
||||
color: $silver;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fa-info-circle:hover {
|
||||
color: $mid-gray;
|
||||
}
|
||||
}
|
||||
|
||||
&__error-text {
|
||||
font-size: 12px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
&__warning-text {
|
||||
font-size: 12px;
|
||||
color: orange;
|
||||
}
|
||||
|
||||
&__input-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__input {
|
||||
border: 1px solid $dusty-gray;
|
||||
border-radius: 4px;
|
||||
color: $mid-gray;
|
||||
font-size: 16px;
|
||||
height: 24px;
|
||||
width: 100%;
|
||||
padding-left: 8px;
|
||||
padding-top: 2px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
&__input--error {
|
||||
border: 1px solid $red;
|
||||
}
|
||||
|
||||
&__input--warning {
|
||||
border: 1px solid $orange;
|
||||
}
|
||||
|
||||
&__input-arrows {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 0px;
|
||||
width: 17px;
|
||||
height: 24px;
|
||||
border: 1px solid #dadada;
|
||||
border-top-right-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #9b9b9b;
|
||||
font-size: .8em;
|
||||
border-bottom-right-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&__i-wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__i-wrap:hover {
|
||||
background: #4EADE7;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
background: #4EADE7;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__input-arrows--error {
|
||||
border: 1px solid $red;
|
||||
}
|
||||
|
||||
&__input-arrows--warning {
|
||||
border: 1px solid $orange;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="number"]:hover::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__gwei-symbol {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 10px;
|
||||
color: $dusty-gray;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,3 +3,5 @@
|
||||
@import './gas-modal-page-container/index';
|
||||
|
||||
@import './gas-price-chart/index';
|
||||
|
||||
@import './advanced-gas-inputs/index';
|
||||
|
@ -230,6 +230,40 @@ const MODALS = {
|
||||
},
|
||||
},
|
||||
|
||||
GAS_PRICE_INFO_MODAL: {
|
||||
contents: [
|
||||
h(NotifcationModal, {
|
||||
header: 'gasPriceNoDenom',
|
||||
message: 'gasPriceInfoModalContent',
|
||||
}),
|
||||
],
|
||||
mobileModalStyle: {
|
||||
width: '95%',
|
||||
top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
|
||||
},
|
||||
laptopModalStyle: {
|
||||
width: '449px',
|
||||
top: 'calc(33% + 45px)',
|
||||
},
|
||||
},
|
||||
|
||||
GAS_LIMIT_INFO_MODAL: {
|
||||
contents: [
|
||||
h(NotifcationModal, {
|
||||
header: 'gasLimit',
|
||||
message: 'gasLimitInfoModalContent',
|
||||
}),
|
||||
],
|
||||
mobileModalStyle: {
|
||||
width: '95%',
|
||||
top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
|
||||
},
|
||||
laptopModalStyle: {
|
||||
width: '449px',
|
||||
top: 'calc(33% + 45px)',
|
||||
},
|
||||
},
|
||||
|
||||
CONFIRM_RESET_ACCOUNT: {
|
||||
contents: h(ConfirmResetAccount),
|
||||
mobileModalStyle: {
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../../constants/transactions'
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../../../constants/common'
|
||||
import AdvancedGasInputs from '../../gas-customization/advanced-gas-inputs'
|
||||
|
||||
export default class ConfirmTransactionBase extends Component {
|
||||
static contextTypes = {
|
||||
@ -81,6 +82,11 @@ export default class ConfirmTransactionBase extends Component {
|
||||
titleComponent: PropTypes.node,
|
||||
valid: PropTypes.bool,
|
||||
warning: PropTypes.string,
|
||||
advancedInlineGasShown: PropTypes.bool,
|
||||
gasPrice: PropTypes.number,
|
||||
gasLimit: PropTypes.number,
|
||||
insufficientBalance: PropTypes.bool,
|
||||
convertThenUpdateGasAndCalculate: PropTypes.func,
|
||||
}
|
||||
|
||||
state = {
|
||||
@ -165,6 +171,11 @@ export default class ConfirmTransactionBase extends Component {
|
||||
hexTransactionFee,
|
||||
hexTransactionTotal,
|
||||
hideDetails,
|
||||
advancedInlineGasShown,
|
||||
gasPrice,
|
||||
gasLimit,
|
||||
insufficientBalance,
|
||||
convertThenUpdateGasAndCalculate,
|
||||
} = this.props
|
||||
|
||||
if (hideDetails) {
|
||||
@ -182,6 +193,18 @@ export default class ConfirmTransactionBase extends Component {
|
||||
headerTextClassName="confirm-detail-row__header-text--edit"
|
||||
onHeaderClick={() => this.handleEditGas()}
|
||||
/>
|
||||
{advancedInlineGasShown
|
||||
? <AdvancedGasInputs
|
||||
updateCustomGasPrice={newGasPrice => convertThenUpdateGasAndCalculate({ gasPrice: newGasPrice, gasLimit })}
|
||||
updateCustomGasLimit={newGasLimit => convertThenUpdateGasAndCalculate({ gasLimit: newGasLimit, gasPrice })}
|
||||
customGasPrice={gasPrice}
|
||||
customGasLimit={gasLimit}
|
||||
insufficientBalance={insufficientBalance}
|
||||
customPriceIsSafe={true}
|
||||
isSpeedUp={false}
|
||||
/>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<ConfirmDetailRow
|
||||
|
@ -14,11 +14,17 @@ import {
|
||||
GAS_LIMIT_TOO_LOW_ERROR_KEY,
|
||||
} from '../../../constants/error-keys'
|
||||
import { getHexGasTotal } from '../../../helpers/confirm-transaction/util'
|
||||
import { isBalanceSufficient } from '../../send/send.utils'
|
||||
import {
|
||||
convertGasPriceForInputs,
|
||||
convertGasLimitForInputs,
|
||||
decimalToHex,
|
||||
decGWEIToHexWEI,
|
||||
} from '../../../helpers/conversions.util'
|
||||
import { isBalanceSufficient, calcGasTotal } from '../../send/send.utils'
|
||||
import { conversionGreaterThan } from '../../../conversion-util'
|
||||
import { MIN_GAS_LIMIT_DEC } from '../../send/send.constants'
|
||||
import { addressSlicer, valuesFor } from '../../../util'
|
||||
import { getMetaMaskAccounts } from '../../../selectors'
|
||||
import { getMetaMaskAccounts, getAdvancedInlineGasShown } from '../../../selectors'
|
||||
|
||||
const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
|
||||
return {
|
||||
@ -47,7 +53,13 @@ const mapStateToProps = (state, props) => {
|
||||
nonce,
|
||||
} = confirmTransaction
|
||||
const { txParams = {}, lastGasPrice, id: transactionId } = txData
|
||||
const { from: fromAddress, to: txParamsToAddress } = txParams
|
||||
const {
|
||||
from: fromAddress,
|
||||
to: txParamsToAddress,
|
||||
gasPrice,
|
||||
gas: gasLimit,
|
||||
value: amount,
|
||||
} = txParams
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
const {
|
||||
conversionRate,
|
||||
@ -84,6 +96,13 @@ const mapStateToProps = (state, props) => {
|
||||
)
|
||||
const unapprovedTxCount = valuesFor(currentNetworkUnapprovedTxs).length
|
||||
|
||||
const insufficientBalance = !isBalanceSufficient({
|
||||
amount,
|
||||
gasTotal: calcGasTotal(gasLimit, gasPrice),
|
||||
balance,
|
||||
conversionRate,
|
||||
})
|
||||
|
||||
return {
|
||||
balance,
|
||||
fromAddress,
|
||||
@ -113,9 +132,13 @@ const mapStateToProps = (state, props) => {
|
||||
unapprovedTxCount,
|
||||
currentNetworkUnapprovedTxs,
|
||||
customGas: {
|
||||
gasLimit: customGasLimit || txData.gasPrice,
|
||||
gasPrice: customGasPrice || txData.gasLimit,
|
||||
gasLimit: customGasLimit || gasPrice,
|
||||
gasPrice: customGasPrice || gasLimit,
|
||||
},
|
||||
advancedInlineGasShown: getAdvancedInlineGasShown(state),
|
||||
gasPrice: convertGasPriceForInputs(gasPrice),
|
||||
gasLimit: convertGasLimitForInputs(gasLimit),
|
||||
insufficientBalance,
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,6 +155,12 @@ const mapDispatchToProps = dispatch => {
|
||||
updateGasAndCalculate: ({ gasLimit, gasPrice }) => {
|
||||
return dispatch(updateGasAndCalculate({ gasLimit, gasPrice }))
|
||||
},
|
||||
convertThenUpdateGasAndCalculate: ({ gasLimit, gasPrice }) => {
|
||||
return dispatch(updateGasAndCalculate({
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
gasPrice: decGWEIToHexWEI(gasPrice),
|
||||
}))
|
||||
},
|
||||
showRejectTransactionsConfirmationModal: ({ onSubmit, unapprovedTxCount }) => {
|
||||
return dispatch(showModal({ name: 'REJECT_TRANSACTIONS', onSubmit, unapprovedTxCount }))
|
||||
},
|
||||
|
@ -59,6 +59,8 @@ export default class SettingsTab extends PureComponent {
|
||||
nativeCurrency: PropTypes.string,
|
||||
useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
|
||||
setUseNativeCurrencyAsPrimaryCurrencyPreference: PropTypes.func,
|
||||
setAdvancedInlineGasFeatureFlag: PropTypes.func,
|
||||
advancedInlineGas: PropTypes.bool,
|
||||
}
|
||||
|
||||
state = {
|
||||
@ -412,6 +414,32 @@ export default class SettingsTab extends PureComponent {
|
||||
)
|
||||
}
|
||||
|
||||
renderAdvancedGasInputInline () {
|
||||
const { t } = this.context
|
||||
const { advancedInlineGas, setAdvancedInlineGasFeatureFlag } = this.props
|
||||
|
||||
return (
|
||||
<div className="settings-page__content-row">
|
||||
<div className="settings-page__content-item">
|
||||
<span>{ t('showAdvancedGasInline') }</span>
|
||||
<div className="settings-page__content-description">
|
||||
{ t('showAdvancedGasInlineDescription') }
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-page__content-item">
|
||||
<div className="settings-page__content-item-col">
|
||||
<ToggleButton
|
||||
value={advancedInlineGas}
|
||||
onToggle={value => setAdvancedInlineGasFeatureFlag(!value)}
|
||||
activeLabel=""
|
||||
inactiveLabel=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderUsePrimaryCurrencyOptions () {
|
||||
const { t } = this.context
|
||||
const {
|
||||
@ -508,6 +536,7 @@ export default class SettingsTab extends PureComponent {
|
||||
{ this.renderClearApproval() }
|
||||
{ this.renderPrivacyOptIn() }
|
||||
{ this.renderHexDataOptIn() }
|
||||
{ this.renderAdvancedGasInputInline() }
|
||||
{ this.renderBlockieOptIn() }
|
||||
</div>
|
||||
)
|
||||
|
@ -25,6 +25,7 @@ const mapStateToProps = state => {
|
||||
featureFlags: {
|
||||
sendHexData,
|
||||
privacyMode,
|
||||
advancedInlineGas,
|
||||
} = {},
|
||||
provider = {},
|
||||
currentLocale,
|
||||
@ -39,6 +40,7 @@ const mapStateToProps = state => {
|
||||
nativeCurrency,
|
||||
useBlockie,
|
||||
sendHexData,
|
||||
advancedInlineGas,
|
||||
privacyMode,
|
||||
provider,
|
||||
useNativeCurrencyAsPrimaryCurrency,
|
||||
@ -54,6 +56,7 @@ const mapDispatchToProps = dispatch => {
|
||||
setUseBlockie: value => dispatch(setUseBlockie(value)),
|
||||
updateCurrentLocale: key => dispatch(updateCurrentLocale(key)),
|
||||
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
|
||||
setAdvancedInlineGasFeatureFlag: shouldShow => dispatch(setFeatureFlag('advancedInlineGas', shouldShow)),
|
||||
setPrivacyMode: enabled => dispatch(setFeatureFlag('privacyMode', enabled)),
|
||||
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
|
||||
setUseNativeCurrencyAsPrimaryCurrencyPreference: value => {
|
||||
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
|
||||
import SendRowWrapper from '../send-row-wrapper/'
|
||||
import GasFeeDisplay from './gas-fee-display/gas-fee-display.component'
|
||||
import GasPriceButtonGroup from '../../../gas-customization/gas-price-button-group'
|
||||
import AdvancedGasInputs from '../../../gas-customization/advanced-gas-inputs'
|
||||
|
||||
export default class SendGasRow extends Component {
|
||||
|
||||
@ -13,54 +14,94 @@ export default class SendGasRow extends Component {
|
||||
gasLoadingError: PropTypes.bool,
|
||||
gasTotal: PropTypes.string,
|
||||
showCustomizeGasModal: PropTypes.func,
|
||||
setGasPrice: PropTypes.func,
|
||||
setGasLimit: PropTypes.func,
|
||||
gasPriceButtonGroupProps: PropTypes.object,
|
||||
gasButtonGroupShown: PropTypes.bool,
|
||||
advancedInlineGasShown: PropTypes.bool,
|
||||
resetGasButtons: PropTypes.func,
|
||||
gasPrice: PropTypes.number,
|
||||
gasLimit: PropTypes.number,
|
||||
insufficientBalance: PropTypes.bool,
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
render () {
|
||||
renderAdvancedOptionsButton () {
|
||||
const { showCustomizeGasModal } = this.props
|
||||
return <div className="advanced-gas-options-btn" onClick={() => showCustomizeGasModal()}>
|
||||
{ this.context.t('advancedOptions') }
|
||||
</div>
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
const {
|
||||
conversionRate,
|
||||
convertedCurrency,
|
||||
gasLoadingError,
|
||||
gasTotal,
|
||||
gasFeeError,
|
||||
showCustomizeGasModal,
|
||||
gasPriceButtonGroupProps,
|
||||
gasButtonGroupShown,
|
||||
advancedInlineGasShown,
|
||||
resetGasButtons,
|
||||
setGasPrice,
|
||||
setGasLimit,
|
||||
gasPrice,
|
||||
gasLimit,
|
||||
insufficientBalance,
|
||||
} = this.props
|
||||
|
||||
const gasPriceButtonGroup = <div>
|
||||
<GasPriceButtonGroup
|
||||
className="gas-price-button-group--small"
|
||||
showCheck={false}
|
||||
{...gasPriceButtonGroupProps}
|
||||
/>
|
||||
{ this.renderAdvancedOptionsButton() }
|
||||
</div>
|
||||
const gasFeeDisplay = <GasFeeDisplay
|
||||
conversionRate={conversionRate}
|
||||
convertedCurrency={convertedCurrency}
|
||||
gasLoadingError={gasLoadingError}
|
||||
gasTotal={gasTotal}
|
||||
onReset={resetGasButtons}
|
||||
onClick={() => showCustomizeGasModal()}
|
||||
/>
|
||||
const advancedGasInputs = <div>
|
||||
<AdvancedGasInputs
|
||||
updateCustomGasPrice={newGasPrice => setGasPrice(newGasPrice, gasLimit)}
|
||||
updateCustomGasLimit={newGasLimit => setGasLimit(newGasLimit, gasPrice)}
|
||||
customGasPrice={gasPrice}
|
||||
customGasLimit={gasLimit}
|
||||
insufficientBalance={insufficientBalance}
|
||||
customPriceIsSafe={true}
|
||||
isSpeedUp={false}
|
||||
/>
|
||||
{ this.renderAdvancedOptionsButton() }
|
||||
</div>
|
||||
|
||||
if (advancedInlineGasShown) {
|
||||
return advancedGasInputs
|
||||
} else if (gasButtonGroupShown) {
|
||||
return gasPriceButtonGroup
|
||||
} else {
|
||||
return gasFeeDisplay
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { gasFeeError } = this.props
|
||||
|
||||
return (
|
||||
<SendRowWrapper
|
||||
label={`${this.context.t('transactionFee')}:`}
|
||||
showError={gasFeeError}
|
||||
errorType={'gasFee'}
|
||||
>
|
||||
{gasButtonGroupShown
|
||||
? <div>
|
||||
<GasPriceButtonGroup
|
||||
className="gas-price-button-group--small"
|
||||
showCheck={false}
|
||||
{...gasPriceButtonGroupProps}
|
||||
/>
|
||||
<div className="advanced-gas-options-btn" onClick={() => showCustomizeGasModal()}>
|
||||
{ this.context.t('advancedOptions') }
|
||||
</div>
|
||||
</div>
|
||||
: <GasFeeDisplay
|
||||
conversionRate={conversionRate}
|
||||
convertedCurrency={convertedCurrency}
|
||||
gasLoadingError={gasLoadingError}
|
||||
gasTotal={gasTotal}
|
||||
onReset={resetGasButtons}
|
||||
onClick={() => showCustomizeGasModal()}
|
||||
/>}
|
||||
|
||||
{ this.renderContent() }
|
||||
</SendRowWrapper>
|
||||
)
|
||||
}
|
||||
|
@ -4,12 +4,24 @@ import {
|
||||
getCurrentCurrency,
|
||||
getGasTotal,
|
||||
getGasPrice,
|
||||
getGasLimit,
|
||||
getSendAmount,
|
||||
} from '../../send.selectors.js'
|
||||
import {
|
||||
isBalanceSufficient,
|
||||
calcGasTotal,
|
||||
} from '../../send.utils.js'
|
||||
import {
|
||||
getBasicGasEstimateLoadingStatus,
|
||||
getRenderableEstimateDataForSmallButtonsFromGWEI,
|
||||
getDefaultActiveButtonIndex,
|
||||
} from '../../../../selectors/custom-gas'
|
||||
import {
|
||||
decGWEIToHexWEI,
|
||||
decimalToHex,
|
||||
convertGasPriceForInputs,
|
||||
convertGasLimitForInputs,
|
||||
} from '../../../../helpers/conversions.util'
|
||||
import {
|
||||
showGasButtonGroup,
|
||||
} from '../../../../ducks/send.duck'
|
||||
@ -17,19 +29,34 @@ import {
|
||||
resetCustomData,
|
||||
} from '../../../../ducks/gas.duck'
|
||||
import { getGasLoadingError, gasFeeIsInError, getGasButtonGroupShown } from './send-gas-row.selectors.js'
|
||||
import { showModal, setGasPrice } from '../../../../actions'
|
||||
import { showModal, setGasPrice, setGasLimit, setGasTotal } from '../../../../actions'
|
||||
import { getAdvancedInlineGasShown, getCurrentEthBalance } from '../../../../selectors'
|
||||
import SendGasRow from './send-gas-row.component'
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(SendGasRow)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const gasButtonInfo = getRenderableEstimateDataForSmallButtonsFromGWEI(state)
|
||||
const activeButtonIndex = getDefaultActiveButtonIndex(gasButtonInfo, getGasPrice(state))
|
||||
const gasPrice = getGasPrice(state)
|
||||
const activeButtonIndex = getDefaultActiveButtonIndex(gasButtonInfo, gasPrice)
|
||||
const renderableGasPrice = convertGasPriceForInputs(gasPrice)
|
||||
const renderableGasLimit = convertGasLimitForInputs(getGasLimit(state))
|
||||
|
||||
const gasTotal = getGasTotal(state)
|
||||
const conversionRate = getConversionRate(state)
|
||||
const balance = getCurrentEthBalance(state)
|
||||
|
||||
const insufficientBalance = !isBalanceSufficient({
|
||||
amount: getSendAmount(state),
|
||||
gasTotal,
|
||||
balance,
|
||||
conversionRate,
|
||||
})
|
||||
|
||||
return {
|
||||
conversionRate: getConversionRate(state),
|
||||
conversionRate,
|
||||
convertedCurrency: getCurrentCurrency(state),
|
||||
gasTotal: getGasTotal(state),
|
||||
gasTotal,
|
||||
gasFeeError: gasFeeIsInError(state),
|
||||
gasLoadingError: getGasLoadingError(state),
|
||||
gasPriceButtonGroupProps: {
|
||||
@ -39,13 +66,26 @@ function mapStateToProps (state) {
|
||||
gasButtonInfo,
|
||||
},
|
||||
gasButtonGroupShown: getGasButtonGroupShown(state),
|
||||
advancedInlineGasShown: getAdvancedInlineGasShown(state),
|
||||
gasPrice: renderableGasPrice,
|
||||
gasLimit: renderableGasLimit,
|
||||
insufficientBalance,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
showCustomizeGasModal: () => dispatch(showModal({ name: 'CUSTOMIZE_GAS', hideBasic: true })),
|
||||
setGasPrice: newPrice => dispatch(setGasPrice(newPrice)),
|
||||
setGasPrice: (newPrice, gasLimit) => {
|
||||
newPrice = decGWEIToHexWEI(newPrice)
|
||||
dispatch(setGasPrice(newPrice))
|
||||
dispatch(setGasTotal(calcGasTotal(gasLimit, newPrice)))
|
||||
},
|
||||
setGasLimit: (newLimit, gasPrice) => {
|
||||
newLimit = decimalToHex(newLimit)
|
||||
dispatch(setGasLimit(newLimit))
|
||||
dispatch(setGasTotal(calcGasTotal(newLimit, gasPrice)))
|
||||
},
|
||||
showGasButtonGroup: () => dispatch(showGasButtonGroup()),
|
||||
resetCustomData: () => dispatch(resetCustomData()),
|
||||
}
|
||||
@ -74,5 +114,6 @@ function mergeProps (stateProps, dispatchProps, ownProps) {
|
||||
dispatchSetGasPrice(gasButtonInfo[1].priceInHexWei)
|
||||
dispatchShowGasButtonGroup()
|
||||
},
|
||||
setGasPrice: dispatchSetGasPrice,
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ let mergeProps
|
||||
const actionSpies = {
|
||||
showModal: sinon.spy(),
|
||||
setGasPrice: sinon.spy(),
|
||||
setGasTotal: sinon.spy(),
|
||||
setGasLimit: sinon.spy(),
|
||||
}
|
||||
|
||||
const sendDuckSpies = {
|
||||
@ -28,11 +30,26 @@ proxyquire('../send-gas-row.container.js', {
|
||||
return () => ({})
|
||||
},
|
||||
},
|
||||
'../../../../selectors': {
|
||||
getCurrentEthBalance: (s) => `mockCurrentEthBalance:${s}`,
|
||||
getAdvancedInlineGasShown: (s) => `mockAdvancedInlineGasShown:${s}`,
|
||||
},
|
||||
'../../send.selectors.js': {
|
||||
getConversionRate: (s) => `mockConversionRate:${s}`,
|
||||
getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`,
|
||||
getGasTotal: (s) => `mockGasTotal:${s}`,
|
||||
getGasPrice: (s) => `mockGasPrice:${s}`,
|
||||
getGasLimit: (s) => `mockGasLimit:${s}`,
|
||||
getSendAmount: (s) => `mockSendAmount:${s}`,
|
||||
},
|
||||
'../../send.utils.js': {
|
||||
isBalanceSufficient: ({
|
||||
amount,
|
||||
gasTotal,
|
||||
balance,
|
||||
conversionRate,
|
||||
}) => `${amount}:${gasTotal}:${balance}:${conversionRate}`,
|
||||
calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice,
|
||||
},
|
||||
'./send-gas-row.selectors.js': {
|
||||
getGasLoadingError: (s) => `mockGasLoadingError:${s}`,
|
||||
@ -47,6 +64,12 @@ proxyquire('../send-gas-row.container.js', {
|
||||
},
|
||||
'../../../../ducks/send.duck': sendDuckSpies,
|
||||
'../../../../ducks/gas.duck': gasDuckSpies,
|
||||
'../../../../helpers/conversions.util': {
|
||||
convertGasPriceForInputs: str => str + '*',
|
||||
convertGasLimitForInputs: str => str + '**',
|
||||
decGWEIToHexWEI: str => '0x' + str + '000',
|
||||
decimalToHex: str => '0x' + str,
|
||||
},
|
||||
})
|
||||
|
||||
describe('send-gas-row container', () => {
|
||||
@ -67,6 +90,10 @@ describe('send-gas-row container', () => {
|
||||
gasButtonInfo: `mockGasButtonInfo:mockState`,
|
||||
},
|
||||
gasButtonGroupShown: `mockGetGasButtonGroupShown:mockState`,
|
||||
advancedInlineGasShown: 'mockAdvancedInlineGasShown:mockState',
|
||||
gasLimit: 'mockGasLimit:mockState**',
|
||||
gasPrice: 'mockGasPrice:mockState*',
|
||||
insufficientBalance: false,
|
||||
})
|
||||
})
|
||||
|
||||
@ -79,6 +106,7 @@ describe('send-gas-row container', () => {
|
||||
beforeEach(() => {
|
||||
dispatchSpy = sinon.spy()
|
||||
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
|
||||
actionSpies.setGasTotal.resetHistory()
|
||||
})
|
||||
|
||||
describe('showCustomizeGasModal()', () => {
|
||||
@ -94,10 +122,23 @@ describe('send-gas-row container', () => {
|
||||
|
||||
describe('setGasPrice()', () => {
|
||||
it('should dispatch an action', () => {
|
||||
mapDispatchToPropsObject.setGasPrice('mockNewPrice')
|
||||
assert(dispatchSpy.calledOnce)
|
||||
mapDispatchToPropsObject.setGasPrice('mockNewPrice', 'mockLimit')
|
||||
assert(dispatchSpy.calledTwice)
|
||||
assert(actionSpies.setGasPrice.calledOnce)
|
||||
assert.equal(actionSpies.setGasPrice.getCall(0).args[0], 'mockNewPrice')
|
||||
assert.equal(actionSpies.setGasPrice.getCall(0).args[0], '0xmockNewPrice000')
|
||||
assert(actionSpies.setGasTotal.calledOnce)
|
||||
assert.equal(actionSpies.setGasTotal.getCall(0).args[0], 'mockLimit0xmockNewPrice000')
|
||||
})
|
||||
})
|
||||
|
||||
describe('setGasLimit()', () => {
|
||||
it('should dispatch an action', () => {
|
||||
mapDispatchToPropsObject.setGasLimit('mockNewLimit', 'mockPrice')
|
||||
assert(dispatchSpy.calledTwice)
|
||||
assert(actionSpies.setGasLimit.calledOnce)
|
||||
assert.equal(actionSpies.setGasLimit.getCall(0).args[0], '0xmockNewLimit')
|
||||
assert(actionSpies.setGasTotal.calledOnce)
|
||||
assert.equal(actionSpies.setGasTotal.getCall(0).args[0], '0xmockNewLimitmockPrice')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -560,6 +560,7 @@
|
||||
&__form-field {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
max-width: 277px;
|
||||
|
||||
.currency-display {
|
||||
color: $tundora;
|
||||
@ -586,7 +587,7 @@
|
||||
font-family: Roboto;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
width: 88px;
|
||||
width: 95px;
|
||||
font-weight: 400;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
@ -934,6 +935,7 @@
|
||||
font-size: 14px;
|
||||
color: #2f9ae0;
|
||||
cursor: pointer;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.sliders-icon-container {
|
||||
|
@ -24,6 +24,7 @@ import {
|
||||
|
||||
import { getSymbolAndDecimals } from '../token-util'
|
||||
import { conversionUtil } from '../conversion-util'
|
||||
import { addHexPrefix } from 'ethereumjs-util'
|
||||
|
||||
// Actions
|
||||
const createActionType = action => `metamask/confirm-transaction/${action}`
|
||||
@ -256,6 +257,8 @@ export function setFetchingData (isFetching) {
|
||||
}
|
||||
|
||||
export function updateGasAndCalculate ({ gasLimit, gasPrice }) {
|
||||
gasLimit = addHexPrefix(gasLimit)
|
||||
gasPrice = addHexPrefix(gasPrice)
|
||||
return (dispatch, getState) => {
|
||||
const { confirmTransaction: { txData } } = getState()
|
||||
const newTxData = {
|
||||
|
@ -120,3 +120,11 @@ export function hexWEIToDecGWEI (decGWEI) {
|
||||
toDenomination: 'GWEI',
|
||||
})
|
||||
}
|
||||
|
||||
export function convertGasPriceForInputs (gasPriceInHexWEI) {
|
||||
return Number(hexWEIToDecGWEI(gasPriceInHexWEI))
|
||||
}
|
||||
|
||||
export function convertGasLimitForInputs (gasLimitInHexWEI) {
|
||||
return parseInt(gasLimitInHexWEI, 16)
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ const selectors = {
|
||||
getCurrentEthBalance,
|
||||
getNetworkIdentifier,
|
||||
isBalanceCached,
|
||||
getAdvancedInlineGasShown,
|
||||
}
|
||||
|
||||
module.exports = selectors
|
||||
@ -230,3 +231,7 @@ function getTotalUnapprovedCount ({ metamask }) {
|
||||
function preferencesSelector ({ metamask }) {
|
||||
return metamask.preferences
|
||||
}
|
||||
|
||||
function getAdvancedInlineGasShown (state) {
|
||||
return Boolean(state.metamask.featureFlags.advancedInlineGas)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user