1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 19:26:13 +02:00

Integrate gas buttons with the send screen.

This commit is contained in:
Dan Miller 2018-09-13 10:05:17 -02:30
parent 7de3f22d63
commit b567c78bca
14 changed files with 301 additions and 41 deletions

View File

@ -121,6 +121,9 @@
"available": {
"message": "Available"
},
"average": {
"message": "Average"
},
"back": {
"message": "Back"
},
@ -439,6 +442,9 @@
"failed": {
"message": "Failed"
},
"fast": {
"message": "Fast"
},
"feeChartTitle": {
"message": "Live Transaction Fee Predictions"
},
@ -1023,6 +1029,9 @@
"save": {
"message": "Save"
},
"slow": {
"message": "Slow"
},
"saveAsCsvFile": {
"message": "Save as CSV File"
},
@ -1277,6 +1286,9 @@
"transactionErrorNoContract": {
"message": "Trying to call a function on a non-contract address."
},
"transactionFee": {
"message": "Transaction Fee"
},
"transactionMemo": {
"message": "Transaction memo (optional)"
},

View File

@ -23,6 +23,12 @@ export default class ButtonGroup extends PureComponent {
: this.props.defaultActiveButtonIndex,
}
componentDidUpdate (prevProps, prevState) {
if (typeof this.props.newActiveButtonIndex === 'number' && prevState.activeButtonIndex !== this.props.newActiveButtonIndex) {
this.setState({ activeButtonIndex: prevProps.newActiveButtonIndex })
}
}
handleButtonClick (activeButtonIndex) {
this.setState({ activeButtonIndex })
}

View File

@ -89,16 +89,20 @@ export default class GasModalPageContainer extends Component {
},
{
gasPriceButtonGroupProps,
hideBasic,
...advancedTabProps
}) {
return (
<Tabs>
<Tab name={this.context.t('basic')}>
<div className="gas-modal-content">
{ this.renderBasicTabContent(gasPriceButtonGroupProps) }
{ this.renderInfoRows(originalTotalFiat, originalTotalEth, newTotalFiat, newTotalEth) }
</div>
</Tab>
{hideBasic
? null
: <Tab name={this.context.t('basic')}>
<div className="gas-modal-content">
{ this.renderBasicTabContent(gasPriceButtonGroupProps) }
{ this.renderInfoRows(originalTotalFiat, originalTotalEth, newTotalFiat, newTotalEth) }
</div>
</Tab>
}
<Tab name={this.context.t('advanced')}>
<div className="gas-modal-content">
{ this.renderAdvancedTabContent(advancedTabProps) }

View File

@ -10,6 +10,9 @@ import {
setCustomGasPrice,
setCustomGasLimit,
} from '../../../ducks/gas.duck'
import {
hideGasButtonGroup,
} from '../../../ducks/send.duck'
import {
updateGasAndCalculate,
} from '../../../ducks/confirm-transaction.duck'
@ -59,7 +62,10 @@ const mapStateToProps = state => {
const newTotalFiat = addHexWEIsToRenderableFiat(value, customGasTotal, currentCurrency, conversionRate)
const hideBasic = state.appState.modal.modalState.props.hideBasic
return {
hideBasic,
isConfirm: isConfirm(state),
customGasPriceInHex,
customGasLimitInHex,
@ -95,6 +101,7 @@ const mapDispatchToProps = dispatch => {
updateConfirmTxGasAndCalculate: (gasLimit, gasPrice) => {
return dispatch(updateGasAndCalculate({ gasLimit, gasPrice }))
},
hideGasButtonGroup: () => dispatch(hideGasButtonGroup()),
}
}
@ -102,6 +109,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { gasPriceButtonGroupProps, isConfirm } = stateProps
const {
updateCustomGasPrice: dispatchUpdateCustomGasPrice,
hideGasButtonGroup: dispatchHideGasButtonGroup,
setGasData: dispatchSetGasData,
updateConfirmTxGasAndCalculate: dispatchUpdateConfirmTxGasAndCalculate,
...otherDispatchProps
@ -111,7 +119,10 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
...stateProps,
...otherDispatchProps,
...ownProps,
onSubmit: isConfirm ? dispatchUpdateConfirmTxGasAndCalculate : dispatchSetGasData,
onSubmit: isConfirm ? dispatchUpdateConfirmTxGasAndCalculate : (newLimit, newPrice) => {
dispatchSetGasData(newLimit, newPrice)
dispatchHideGasButtonGroup()
},
gasPriceButtonGroupProps: {
...gasPriceButtonGroupProps,
handleGasPriceSelection: dispatchUpdateCustomGasPrice,

View File

@ -27,7 +27,7 @@ export default class GasPriceButtonGroup extends Component {
}
renderButtonContent ({
label,
labelKey,
feeInPrimaryCurrency,
feeInSecondaryCurrency,
timeEstimate,
@ -36,7 +36,7 @@ export default class GasPriceButtonGroup extends Component {
showCheck,
}) {
return (<div>
{ label && <div className={`${className}__label`}>{ label }</div> }
{ labelKey && <div className={`${className}__label`}>{ this.context.t(labelKey) }</div> }
{ feeInPrimaryCurrency && <div className={`${className}__primary-currency`}>{ feeInPrimaryCurrency }</div> }
{ feeInSecondaryCurrency && <div className={`${className}__secondary-currency`}>{ feeInSecondaryCurrency }</div> }
{ timeEstimate && <div className={`${className}__time-estimate`}>{ timeEstimate }</div> }
@ -57,9 +57,7 @@ export default class GasPriceButtonGroup extends Component {
onClick={() => handleGasPriceSelection(priceInHexWei)}
key={`gas-price-button-${index}`}
>
{buttonDataLoading
? 'Loading...'
: this.renderButtonContent(renderableGasInfo, buttonContentPropsAndFlags)}
{this.renderButtonContent(renderableGasInfo, buttonContentPropsAndFlags)}
</Button>
)
}
@ -68,18 +66,23 @@ export default class GasPriceButtonGroup extends Component {
const {
gasButtonInfo,
defaultActiveButtonIndex = 1,
newActiveButtonIndex,
noButtonActiveByDefault = false,
buttonDataLoading,
...buttonPropsAndFlags
} = this.props
return (
<ButtonGroup
className={buttonPropsAndFlags.className}
defaultActiveButtonIndex={defaultActiveButtonIndex}
noButtonActiveByDefault={noButtonActiveByDefault}
>
{ gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index)) }
</ButtonGroup>
!buttonDataLoading
? <ButtonGroup
className={buttonPropsAndFlags.className}
defaultActiveButtonIndex={defaultActiveButtonIndex}
newActiveButtonIndex={newActiveButtonIndex}
noButtonActiveByDefault={noButtonActiveByDefault}
>
{ gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index)) }
</ButtonGroup>
: <div className={`${buttonPropsAndFlags.className}__loading-container`}>Loading...</div>
)
}
}

View File

@ -18,6 +18,9 @@
height: 15.4px;
}
&__loading-container {
height: 130px;
}
.button-group__button, .button-group__button--active {
height: 130px;
@ -58,3 +61,67 @@
}
}
}
.gas-price-button-group--small {
display: flex;
justify-content: stretch;
max-width: 260px;
&__button-fiat-price {
font-size: 13px;
}
&__button-label {
font-size: 16px;
}
&__label {
font-weight: 500;
}
&__primary-currency {
font-size: 12px;
}
&__secondary-currency {
font-size: 12px;
}
&__loading-container {
height: 78px;
}
.button-group__button, .button-group__button--active {
height: 78px;
background: white;
color: $scorpion;
padding-top: 9px;
padding-left: 8.5px;
div {
display: flex;
flex-flow: column;
align-items: flex-start;
justify-content: flex-start;
}
i {
&:last-child {
display: none;
}
}
}
.button-group__button--active {
color: $white;
background: $dodger-blue;
i {
&:last-child {
display: flex;
color: $curious-blue;
margin-top: 10px
}
}
}
}

View File

@ -58,7 +58,8 @@ export default class PageContainer extends PureComponent {
renderActiveTabContent () {
const { tabsComponent } = this.props
const { children } = tabsComponent.props
let { children } = tabsComponent.props
children = children.filter(child => child)
const { activeTabIndex } = this.state
return children[activeTabIndex]

View File

@ -46,11 +46,10 @@ export default class GasFeeDisplay extends Component {
</div>
}
<button
className="sliders-icon-container"
onClick={onClick}
disabled={!gasTotal && !gasLoadingError}
className="gas-fee-reset"
onClick={showGasButtonGroup}
>
<i className="fa fa-sliders sliders-icon" />
Reset
</button>
</div>
)

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react'
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'
export default class SendGasRow extends Component {
@ -26,21 +27,37 @@ export default class SendGasRow extends Component {
gasTotal,
gasFeeError,
showCustomizeGasModal,
gasPriceButtonGroupProps,
gasButtonGroupShown,
showGasButtonGroup
} = this.props
return (
<SendRowWrapper
label={`${this.context.t('gasFee')}:`}
label={`${this.context.t('transactionFee')}:`}
showError={gasFeeError}
errorType={'gasFee'}
>
<GasFeeDisplay
conversionRate={conversionRate}
convertedCurrency={convertedCurrency}
gasLoadingError={gasLoadingError}
gasTotal={gasTotal}
onClick={() => showCustomizeGasModal()}
/>
{gasButtonGroupShown
? <div>
<GasPriceButtonGroup
className="gas-price-button-group--small"
showCheck={false}
{...this.props.gasPriceButtonGroupProps}
/>
<div className="advanced-gas-options-btn" onClick={() => showCustomizeGasModal()}>
Advanced Options
</div>
</div>
: <GasFeeDisplay
conversionRate={conversionRate}
convertedCurrency={convertedCurrency}
gasLoadingError={gasLoadingError}
gasTotal={gasTotal}
showGasButtonGroup={showGasButtonGroup}
onClick={() => showCustomizeGasModal()}
/>}
</SendRowWrapper>
)
}

View File

@ -3,25 +3,64 @@ import {
getConversionRate,
getCurrentCurrency,
getGasTotal,
getGasPrice,
} from '../../send.selectors.js'
import { getGasLoadingError, gasFeeIsInError } from './send-gas-row.selectors.js'
import { showModal } from '../../../../actions'
import{
getBasicGasEstimateLoadingStatus,
getRenderableEstimateDataForSmallButtons,
getDefaultActiveButtonIndex
} from '../../../../selectors/custom-gas'
import{
showGasButtonGroup
} from '../../../../ducks/send.duck'
import { getGasLoadingError, gasFeeIsInError, getGasButtonGroupShown } from './send-gas-row.selectors.js'
import { showModal, setGasPrice } from '../../../../actions'
import SendGasRow from './send-gas-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendGasRow)
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(SendGasRow)
function mapStateToProps (state) {
const gasButtonInfo = getRenderableEstimateDataForSmallButtons(state)
const activeButtonIndex = getDefaultActiveButtonIndex(gasButtonInfo, getGasPrice(state))
return {
conversionRate: getConversionRate(state),
convertedCurrency: getCurrentCurrency(state),
gasTotal: getGasTotal(state),
gasFeeError: gasFeeIsInError(state),
gasLoadingError: getGasLoadingError(state),
gasPriceButtonGroupProps: {
buttonDataLoading: getBasicGasEstimateLoadingStatus(state),
defaultActiveButtonIndex: 1,
newActiveButtonIndex: activeButtonIndex > -1 ? activeButtonIndex : null,
gasButtonInfo,
},
gasButtonGroupShown: getGasButtonGroupShown(state),
}
}
function mapDispatchToProps (dispatch) {
return {
showCustomizeGasModal: () => dispatch(showModal({ name: 'CUSTOMIZE_GAS' })),
showCustomizeGasModal: () => dispatch(showModal({ name: 'CUSTOMIZE_GAS', hideBasic: true })),
setGasPrice: newPrice => dispatch(setGasPrice(newPrice)),
showGasButtonGroup: () => dispatch(showGasButtonGroup())
}
}
function mergeProps (stateProps, dispatchProps, ownProps) {
const { gasPriceButtonGroupProps } = stateProps
const {
setGasPrice: dispatchSetGasPrice,
...otherDispatchProps
} = dispatchProps
return {
...stateProps,
...otherDispatchProps,
...ownProps,
gasPriceButtonGroupProps: {
...gasPriceButtonGroupProps,
handleGasPriceSelection: dispatchSetGasPrice,
},
}
}

View File

@ -1,6 +1,7 @@
const selectors = {
gasFeeIsInError,
getGasLoadingError,
getGasButtonGroupShown,
}
module.exports = selectors
@ -12,3 +13,7 @@ function getGasLoadingError (state) {
function gasFeeIsInError (state) {
return Boolean(state.send.errors.gasFee)
}
function getGasButtonGroupShown (state) {
return state.send.gasButtonGroupShown
}

View File

@ -579,7 +579,7 @@
font-family: Roboto;
font-size: 16px;
line-height: 22px;
width: 88px;
width: 112px;
font-weight: 400;
flex: 0 0 auto;
}
@ -622,6 +622,7 @@
&__to-autocomplete {
position: relative;
max-width: 260px;
&__down-caret {
z-index: 1026;
@ -684,6 +685,7 @@
display: flex;
align-items: center;
}
}
&__sliders-icon-container {
@ -917,6 +919,15 @@
display: none;
}
}
}
.advanced-gas-options-btn {
display: flex;
justify-content: flex-end;
font-size: 14px;
color: #2f9ae0;
cursor: pointer;
}
.sliders-icon-container {
@ -935,6 +946,23 @@
font-size: 1em;
}
.gas-fee-reset {
display: flex;
align-items: center;
justify-content: center;
height: 24px;
border-radius: 4px;
background-color: #fff;
position: absolute;
right: 12px;
top: 14px;
cursor: pointer;
font-size: 1em;
font-size: 14px;
color: #2f9ae0;
font-family: Roboto;
}
.sliders-icon {
color: $curious-blue;
}

View File

@ -7,11 +7,14 @@ const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN'
const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'
const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS'
const RESET_SEND_STATE = 'metamask/send/RESET_SEND_STATE'
const SHOW_GAS_BUTTON_GROUP = 'metamask/send/SHOW_GAS_BUTTON_GROUP'
const HIDE_GAS_BUTTON_GROUP = 'metamask/send/HIDE_GAS_BUTTON_GROUP'
// TODO: determine if this approach to initState is consistent with conventional ducks pattern
const initState = {
fromDropdownOpen: false,
toDropdownOpen: false,
gasButtonGroupShown: true,
errors: {},
}
@ -43,6 +46,14 @@ export default function reducer ({ send: sendState = initState }, action = {}) {
...action.value,
},
})
case SHOW_GAS_BUTTON_GROUP:
return extend(newState, {
gasButtonGroupShown: true,
})
case HIDE_GAS_BUTTON_GROUP:
return extend(newState, {
gasButtonGroupShown: false,
})
case RESET_SEND_STATE:
return extend({}, initState)
default:
@ -67,6 +78,14 @@ export function closeToDropdown () {
return { type: CLOSE_TO_DROPDOWN }
}
export function showGasButtonGroup () {
return { type: SHOW_GAS_BUTTON_GROUP }
}
export function hideGasButtonGroup () {
return { type: HIDE_GAS_BUTTON_GROUP }
}
export function updateSendErrors (errorObject) {
return {
type: UPDATE_SEND_ERRORS,

View File

@ -25,6 +25,7 @@ const selectors = {
getCustomGasLimit,
getCustomGasPrice,
getCustomGasTotal,
getRenderableEstimateDataForSmallButtons,
getRenderableBasicEstimateData,
getBasicGasEstimateLoadingStatus,
getAveragePriceEstimateInHexWEI,
@ -34,6 +35,8 @@ const selectors = {
module.exports = selectors
const NUMBER_OF_DECIMALS_SM_BTNS = 5
function getCustomGasErrors (state) {
return state.gas.errors
}
@ -60,7 +63,9 @@ function getAveragePriceEstimateInHexWEI (state) {
}
function getDefaultActiveButtonIndex (gasButtonInfo, customGasPriceInHex, gasPrice) {
console.log('gasButtonInfo', gasButtonInfo)
return gasButtonInfo.findIndex(({ priceInHexWei }) => {
console.log('priceInHexWei', priceInHexWei, '|', customGasPriceInHex)
return priceInHexWei === addHexPrefix(customGasPriceInHex || gasPrice)
})
}
@ -74,23 +79,24 @@ function apiEstimateModifiedToGWEI (estimate) {
})
}
function basicPriceEstimateToETHTotal (estimate, gasLimit) {
function basicPriceEstimateToETHTotal (estimate, gasLimit, numberOfDecimals = 9) {
return conversionUtil(calcGasTotal(gasLimit, estimate), {
fromNumericBase: 'hex',
toNumericBase: 'dec',
fromDenomination: 'GWEI',
numberOfDecimals: 9,
numberOfDecimals,
})
}
function getRenderableEthFee (estimate, gasLimit) {
function getRenderableEthFee (estimate, gasLimit, numberOfDecimals = 9) {
return pipe(
apiEstimateModifiedToGWEI,
partialRight(basicPriceEstimateToETHTotal, [gasLimit]),
partialRight(basicPriceEstimateToETHTotal, [gasLimit, numberOfDecimals]),
formatETHFee
)(estimate, gasLimit)
}
function getRenderableConvertedCurrencyFee (estimate, gasLimit, convertedCurrency, conversionRate) {
return pipe(
apiEstimateModifiedToGWEI,
@ -188,3 +194,46 @@ function getRenderableBasicEstimateData (state) {
},
]
}
function getRenderableEstimateDataForSmallButtons (state) {
if (getBasicGasEstimateLoadingStatus(state)) {
return []
}
const gasLimit = state.metamask.send.gasLimit || getCustomGasLimit(state)
const conversionRate = state.metamask.conversionRate
const currentCurrency = getCurrentCurrency(state)
const {
gas: {
basicEstimates: {
safeLow,
average,
fast,
blockTime,
safeLowWait,
avgWait,
fastWait,
},
},
} = state
return [
{
labelKey: 'fast',
feeInSecondaryCurrency: getRenderableConvertedCurrencyFee(fast, gasLimit, currentCurrency, conversionRate),
feeInPrimaryCurrency: getRenderableEthFee(fast, gasLimit, NUMBER_OF_DECIMALS_SM_BTNS),
priceInHexWei: getGasPriceInHexWei(fast),
},
{
labelKey: 'average',
feeInSecondaryCurrency: getRenderableConvertedCurrencyFee(average, gasLimit, currentCurrency, conversionRate),
feeInPrimaryCurrency: getRenderableEthFee(average, gasLimit, NUMBER_OF_DECIMALS_SM_BTNS),
priceInHexWei: getGasPriceInHexWei(average),
},
{
labelKey: 'slow',
feeInSecondaryCurrency: getRenderableConvertedCurrencyFee(safeLow, gasLimit, currentCurrency, conversionRate),
feeInPrimaryCurrency: getRenderableEthFee(safeLow, gasLimit, NUMBER_OF_DECIMALS_SM_BTNS),
priceInHexWei: getGasPriceInHexWei(safeLow),
},
]
}