From b567c78bcae73e9c73b69040d22e096e4f876a2b Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Thu, 13 Sep 2018 10:05:17 -0230 Subject: Integrate gas buttons with the send screen. --- app/_locales/en/messages.json | 12 ++++ .../button-group/button-group.component.js | 6 ++ .../gas-modal-page-container.component.js | 16 ++++-- .../gas-modal-page-container.container.js | 13 ++++- .../gas-price-button-group.component.js | 27 +++++---- .../gas-price-button-group/index.scss | 67 ++++++++++++++++++++++ .../page-container/page-container.component.js | 3 +- .../gas-fee-display/gas-fee-display.component.js | 7 +-- .../send-gas-row/send-gas-row.component.js | 33 ++++++++--- .../send-gas-row/send-gas-row.container.js | 47 +++++++++++++-- .../send-gas-row/send-gas-row.selectors.js | 5 ++ ui/app/css/itcss/components/send.scss | 30 +++++++++- ui/app/ducks/send.duck.js | 19 ++++++ ui/app/selectors/custom-gas.js | 57 ++++++++++++++++-- 14 files changed, 301 insertions(+), 41 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 017a8a380..7a79da2bd 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -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)" }, diff --git a/ui/app/components/button-group/button-group.component.js b/ui/app/components/button-group/button-group.component.js index 440b3c4f7..723f9b526 100644 --- a/ui/app/components/button-group/button-group.component.js +++ b/ui/app/components/button-group/button-group.component.js @@ -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 }) } 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 89065472d..db730458d 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 @@ -89,16 +89,20 @@ export default class GasModalPageContainer extends Component { }, { gasPriceButtonGroupProps, + hideBasic, ...advancedTabProps }) { return ( - -
- { this.renderBasicTabContent(gasPriceButtonGroupProps) } - { this.renderInfoRows(originalTotalFiat, originalTotalEth, newTotalFiat, newTotalEth) } -
-
+ {hideBasic + ? null + : +
+ { this.renderBasicTabContent(gasPriceButtonGroupProps) } + { this.renderInfoRows(originalTotalFiat, originalTotalEth, newTotalFiat, newTotalEth) } +
+
+ }
{ this.renderAdvancedTabContent(advancedTabProps) } 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 7e2a7e144..a150e5009 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 @@ -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, diff --git a/ui/app/components/gas-customization/gas-price-button-group/gas-price-button-group.component.js b/ui/app/components/gas-customization/gas-price-button-group/gas-price-button-group.component.js index 3873f54bc..8d6675e18 100644 --- a/ui/app/components/gas-customization/gas-price-button-group/gas-price-button-group.component.js +++ b/ui/app/components/gas-customization/gas-price-button-group/gas-price-button-group.component.js @@ -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 (
- { label &&
{ label }
} + { labelKey &&
{ this.context.t(labelKey) }
} { feeInPrimaryCurrency &&
{ feeInPrimaryCurrency }
} { feeInSecondaryCurrency &&
{ feeInSecondaryCurrency }
} { timeEstimate &&
{ timeEstimate }
} @@ -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)} ) } @@ -68,18 +66,23 @@ export default class GasPriceButtonGroup extends Component { const { gasButtonInfo, defaultActiveButtonIndex = 1, + newActiveButtonIndex, noButtonActiveByDefault = false, + buttonDataLoading, ...buttonPropsAndFlags } = this.props return ( - - { gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index)) } - + !buttonDataLoading + ? + { gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index)) } + + :
Loading...
) } } diff --git a/ui/app/components/gas-customization/gas-price-button-group/index.scss b/ui/app/components/gas-customization/gas-price-button-group/index.scss index e2670edd5..7647c42c0 100644 --- a/ui/app/components/gas-customization/gas-price-button-group/index.scss +++ b/ui/app/components/gas-customization/gas-price-button-group/index.scss @@ -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 + } + } + } +} diff --git a/ui/app/components/page-container/page-container.component.js b/ui/app/components/page-container/page-container.component.js index 3a2274a29..672255e27 100644 --- a/ui/app/components/page-container/page-container.component.js +++ b/ui/app/components/page-container/page-container.component.js @@ -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] diff --git a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js b/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js index 9bbb67506..9962f8028 100644 --- a/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js +++ b/ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js @@ -46,11 +46,10 @@ export default class GasFeeDisplay extends Component {
}
) diff --git a/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js index 6ad4499ff..64a5cd603 100644 --- a/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js @@ -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 ( - showCustomizeGasModal()} - /> + {gasButtonGroupShown + ?
+ +
showCustomizeGasModal()}> + Advanced Options +
+
+ : showCustomizeGasModal()} + />} +
) } diff --git a/ui/app/components/send/send-content/send-gas-row/send-gas-row.container.js b/ui/app/components/send/send-content/send-gas-row/send-gas-row.container.js index e44fe4ef1..19e49b391 100644 --- a/ui/app/components/send/send-content/send-gas-row/send-gas-row.container.js +++ b/ui/app/components/send/send-content/send-gas-row/send-gas-row.container.js @@ -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, + }, + } +} \ No newline at end of file diff --git a/ui/app/components/send/send-content/send-gas-row/send-gas-row.selectors.js b/ui/app/components/send/send-content/send-gas-row/send-gas-row.selectors.js index 96f6293c2..79c838543 100644 --- a/ui/app/components/send/send-content/send-gas-row/send-gas-row.selectors.js +++ b/ui/app/components/send/send-content/send-gas-row/send-gas-row.selectors.js @@ -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 +} diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss index c791a24c7..4535ced52 100644 --- a/ui/app/css/itcss/components/send.scss +++ b/ui/app/css/itcss/components/send.scss @@ -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; } diff --git a/ui/app/ducks/send.duck.js b/ui/app/ducks/send.duck.js index db01bbaa9..758916d48 100644 --- a/ui/app/ducks/send.duck.js +++ b/ui/app/ducks/send.duck.js @@ -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, diff --git a/ui/app/selectors/custom-gas.js b/ui/app/selectors/custom-gas.js index 61c0525df..f023b03b8 100644 --- a/ui/app/selectors/custom-gas.js +++ b/ui/app/selectors/custom-gas.js @@ -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), + }, + ] +} -- cgit v1.2.3