From d68f8f27c6c2c363ce29b3acc5ffd7412928396e Mon Sep 17 00:00:00 2001 From: David Walsh Date: Mon, 28 Jun 2021 09:45:08 -0500 Subject: [PATCH] Implements the new EIP1559 UI components (#11384) --- app/_locales/en/messages.json | 42 +++++++ development/build/scripts.js | 4 +- .../advanced-gas-controls.component.js | 110 ++++++++++-------- ui/components/app/app-components.scss | 1 + .../edit-gas-display-education.component.js | 69 +++++++++++ .../edit-gas-display-education.stories.js | 14 +++ .../app/edit-gas-display-education/index.js | 1 + .../app/edit-gas-display-education/index.scss | 5 + .../edit-gas-display.component.js | 104 +++++++++++++++++ .../app/edit-gas-display/edit-gas-display.js | 45 ------- .../edit-gas-display.stories.js | 18 +-- ui/components/app/edit-gas-display/index.js | 2 +- ui/components/app/edit-gas-display/index.scss | 28 +++++ .../edit-gas-popover.component.js | 83 +++++++++++++ .../edit-gas-popover.stories.js | 30 +++++ ui/components/app/edit-gas-popover/index.js | 1 + ui/components/app/modals/modal.js | 7 +- .../app/sidebars/sidebar.component.js | 30 ++++- .../confirm-transaction-base.component.js | 39 ++++--- 19 files changed, 502 insertions(+), 131 deletions(-) create mode 100644 ui/components/app/edit-gas-display-education/edit-gas-display-education.component.js create mode 100644 ui/components/app/edit-gas-display-education/edit-gas-display-education.stories.js create mode 100644 ui/components/app/edit-gas-display-education/index.js create mode 100644 ui/components/app/edit-gas-display-education/index.scss create mode 100644 ui/components/app/edit-gas-display/edit-gas-display.component.js delete mode 100644 ui/components/app/edit-gas-display/edit-gas-display.js create mode 100644 ui/components/app/edit-gas-popover/edit-gas-popover.component.js create mode 100644 ui/components/app/edit-gas-popover/edit-gas-popover.stories.js create mode 100644 ui/components/app/edit-gas-popover/index.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 521cebef2..14dd7ba9f 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -617,6 +617,39 @@ "editContact": { "message": "Edit Contact" }, + "editGasEducationButtonText": { + "message": "How should I choose?" + }, + "editGasEducationHighExplanation": { + "message": "This is best for swaps or other time sensitive transactions. If a swap takes too long to process it will often fail and you may lose funds." + }, + "editGasEducationLearnMoreLinkText": { + "message": "Learn more about customizing gas." + }, + "editGasEducationLowExplanation": { + "message": "Low A lower gas fee should only be selected for transactions where processing time is less important. With a lower fee, it can be be hard to predict when (or if) your transaction with be successful." + }, + "editGasEducationMediumExplanation": { + "message": "A medium gas fee is good for sending, withdrawing or other non-time sensitive but important transactions." + }, + "editGasEducationModalIntro": { + "message": "The right gas amount to select depends on the type of transaction and how important it is." + }, + "editGasEducationModalTitle": { + "message": "How to choose?" + }, + "editGasHigh": { + "message": "High" + }, + "editGasLow": { + "message": "Low" + }, + "editGasMedium": { + "message": "Medium" + }, + "editGasTitle": { + "message": "Edit gas fee" + }, "editNonceField": { "message": "Edit Nonce" }, @@ -1817,6 +1850,15 @@ "speedUpCancellation": { "message": "Speed up this cancellation" }, + "speedUpExplanation": { + "message": "We’ve restimated the gas fee based on current network conditions and have increased it by at least 10% (required by the network)." + }, + "speedUpPopoverTitle": { + "message": "Speed up transaction" + }, + "speedUpTooltipText": { + "message": "New gas fee" + }, "speedUpTransaction": { "message": "Speed up this transaction" }, diff --git a/development/build/scripts.js b/development/build/scripts.js index 7fa0595e1..50fa4f8db 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -433,7 +433,9 @@ function getEnvironmentVariables({ devMode, testing }) { PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '', PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '', CONF: devMode ? metamaskrc : {}, - SHOW_EIP_1559_UI: process.env.SHOW_EIP_1559_UI === '1', + SHOW_EIP_1559_UI: + process.env.SHOW_EIP_1559_UI === '1' || + metamaskrc.SHOW_EIP_1559_UI === '1', SENTRY_DSN: process.env.SENTRY_DSN, SENTRY_DSN_DEV: metamaskrc.SENTRY_DSN_DEV, INFURA_PROJECT_ID: testing diff --git a/ui/components/app/advanced-gas-controls/advanced-gas-controls.component.js b/ui/components/app/advanced-gas-controls/advanced-gas-controls.component.js index 7231d67a0..5ba93e06c 100644 --- a/ui/components/app/advanced-gas-controls/advanced-gas-controls.component.js +++ b/ui/components/app/advanced-gas-controls/advanced-gas-controls.component.js @@ -1,4 +1,5 @@ import React, { useContext, useState } from 'react'; + import { I18nContext } from '../../../contexts/i18n'; import Typography from '../../ui/typography/typography'; import { @@ -15,6 +16,9 @@ export default function AdvancedGasControls() { const [maxPriorityFee, setMaxPriorityFee] = useState(0); const [maxFee, setMaxFee] = useState(0); + // Used in legacy version + const [gasPrice, setGasPrice] = useState(0); + return (
- - - {t('gasFeeEstimate')}: - {' '} - - - } - /> - - - {t('gasFeeEstimate')}: - {' '} - - - } - /> + {process.env.SHOW_EIP_1559_UI ? ( + <> + + + {t('gasFeeEstimate')}: + {' '} + + + } + /> + + + {t('gasFeeEstimate')}: + {' '} + + + } + /> + + ) : ( + <> + + + )}
); } diff --git a/ui/components/app/app-components.scss b/ui/components/app/app-components.scss index 8f1e3926c..ab36b4b96 100644 --- a/ui/components/app/app-components.scss +++ b/ui/components/app/app-components.scss @@ -12,6 +12,7 @@ @import 'connected-sites-list/index'; @import 'connected-status-indicator/index'; @import 'edit-gas-display/index'; +@import 'edit-gas-display-education/index'; @import 'gas-customization/gas-modal-page-container/index'; @import 'gas-customization/gas-price-button-group/index'; @import 'gas-customization/index'; diff --git a/ui/components/app/edit-gas-display-education/edit-gas-display-education.component.js b/ui/components/app/edit-gas-display-education/edit-gas-display-education.component.js new file mode 100644 index 000000000..3b0ce0c17 --- /dev/null +++ b/ui/components/app/edit-gas-display-education/edit-gas-display-education.component.js @@ -0,0 +1,69 @@ +import React, { useContext } from 'react'; + +import Typography from '../../ui/typography/typography'; +import { + COLORS, + TYPOGRAPHY, + FONT_WEIGHT, +} from '../../../helpers/constants/design-system'; + +import { I18nContext } from '../../../contexts/i18n'; + +export default function EditGasDisplayEducation() { + const t = useContext(I18nContext); + + return ( +
+ + {t('editGasEducationModalTitle')} + + + {t('editGasEducationModalIntro')} + + + {t('editGasHigh')} + + + {t('editGasEducationHighExplanation')} + + + {t('editGasMedium')} + + + {t('editGasEducationMediumExplanation')} + + + {t('editGasLow')} + + + {t('editGasEducationLowExplanation')} + + + + {t('editGasEducationLearnMoreLinkText')} + + +
+ ); +} diff --git a/ui/components/app/edit-gas-display-education/edit-gas-display-education.stories.js b/ui/components/app/edit-gas-display-education/edit-gas-display-education.stories.js new file mode 100644 index 000000000..aed0fa9c8 --- /dev/null +++ b/ui/components/app/edit-gas-display-education/edit-gas-display-education.stories.js @@ -0,0 +1,14 @@ +import React from 'react'; +import EditGasDisplayEducation from '.'; + +export default { + title: 'Edit Gas Display', +}; + +export const basic = () => { + return ( +
+ +
+ ); +}; diff --git a/ui/components/app/edit-gas-display-education/index.js b/ui/components/app/edit-gas-display-education/index.js new file mode 100644 index 000000000..dec57af8b --- /dev/null +++ b/ui/components/app/edit-gas-display-education/index.js @@ -0,0 +1 @@ +export { default } from './edit-gas-display-education.component'; diff --git a/ui/components/app/edit-gas-display-education/index.scss b/ui/components/app/edit-gas-display-education/index.scss new file mode 100644 index 000000000..dc8d39fd8 --- /dev/null +++ b/ui/components/app/edit-gas-display-education/index.scss @@ -0,0 +1,5 @@ +.edit-gas-display-education { + a { + color: $primary-1; + } +} diff --git a/ui/components/app/edit-gas-display/edit-gas-display.component.js b/ui/components/app/edit-gas-display/edit-gas-display.component.js new file mode 100644 index 000000000..710bc1323 --- /dev/null +++ b/ui/components/app/edit-gas-display/edit-gas-display.component.js @@ -0,0 +1,104 @@ +import React, { useState, useContext } from 'react'; +import PropTypes from 'prop-types'; + +import Typography from '../../ui/typography/typography'; +import { + COLORS, + TYPOGRAPHY, + FONT_WEIGHT, +} from '../../../helpers/constants/design-system'; + +import InfoTooltip from '../../ui/info-tooltip'; +import TransactionTotalBanner from '../transaction-total-banner/transaction-total-banner.component'; +import RadioGroup from '../../ui/radio-group/radio-group.component'; +import AdvancedGasControls from '../advanced-gas-controls/advanced-gas-controls.component'; + +import { I18nContext } from '../../../contexts/i18n'; +import ActionableMessage from '../../../pages/swaps/actionable-message'; + +export default function EditGasDisplay({ + alwaysShowForm, + type, + showEducationButton, + onEducationClick, +}) { + const t = useContext(I18nContext); + + const [warning] = useState(null); + const [showAdvancedForm, setShowAdvancedForm] = useState(false); + + return ( +
+
+ {warning && ( +
+ +
+ )} + {type === 'speed-up' && ( +
+ + {t('speedUpTooltipText')}{' '} + + +
+ )} + + + {!alwaysShowForm && ( + + )} + {(alwaysShowForm || showAdvancedForm) && } +
+ {showEducationButton && ( +
+ +
+ )} +
+ ); +} + +EditGasDisplay.propTypes = { + alwaysShowForm: PropTypes.bool, + type: PropTypes.oneOf(['customize-gas', 'speed-up']), + showEducationButton: PropTypes.bool, + onEducationClick: PropTypes.func, +}; + +EditGasDisplay.defaultProps = { + alwaysShowForm: false, + type: 'customize-gas', + showEducationButton: false, + onEducationClick: undefined, +}; diff --git a/ui/components/app/edit-gas-display/edit-gas-display.js b/ui/components/app/edit-gas-display/edit-gas-display.js deleted file mode 100644 index 4911633e2..000000000 --- a/ui/components/app/edit-gas-display/edit-gas-display.js +++ /dev/null @@ -1,45 +0,0 @@ -import React, { useState, useContext } from 'react'; - -import TransactionTotalBanner from '../transaction-total-banner/transaction-total-banner.component'; -import RadioGroup from '../../ui/radio-group/radio-group.component'; - -import AdvancedGasControls from '../advanced-gas-controls/advanced-gas-controls.component'; - -import { I18nContext } from '../../../contexts/i18n'; - -export default function EditGasDisplay() { - const t = useContext(I18nContext); - const [showAdvancedForm, setShowAdvancedForm] = useState(false); - - return ( -
- - - - {showAdvancedForm && } -
- ); -} diff --git a/ui/components/app/edit-gas-display/edit-gas-display.stories.js b/ui/components/app/edit-gas-display/edit-gas-display.stories.js index a3f85ba5a..35947b93d 100644 --- a/ui/components/app/edit-gas-display/edit-gas-display.stories.js +++ b/ui/components/app/edit-gas-display/edit-gas-display.stories.js @@ -1,6 +1,4 @@ import React from 'react'; -import PopoverPortal from '../../ui/popover/popover.component'; -import Button from '../../ui/button'; import EditGasDisplay from '.'; export default { @@ -15,22 +13,10 @@ export const basic = () => { ); }; -export const insidePopover = () => { +export const withEducation = () => { return (
- console.log('Closing!')} - footer={ - <> - - - } - > -
- -
-
+
); }; diff --git a/ui/components/app/edit-gas-display/index.js b/ui/components/app/edit-gas-display/index.js index 05c4488a9..d2f752891 100644 --- a/ui/components/app/edit-gas-display/index.js +++ b/ui/components/app/edit-gas-display/index.js @@ -1 +1 @@ -export { default } from './edit-gas-display'; +export { default } from './edit-gas-display.component'; diff --git a/ui/components/app/edit-gas-display/index.scss b/ui/components/app/edit-gas-display/index.scss index f8422eee8..95326868c 100644 --- a/ui/components/app/edit-gas-display/index.scss +++ b/ui/components/app/edit-gas-display/index.scss @@ -1,4 +1,21 @@ .edit-gas-display { + & .actionable-message--warning { + margin-top: 0; + } + + &__top-tooltip { + text-align: center; + + .info-tooltip { + display: inline-block; + + img { + height: 10px; + width: 10px; + } + } + } + .radio-group { margin: 20px auto; } @@ -14,4 +31,15 @@ .advanced-gas-controls { margin-top: 20px; } + + &__education { + margin-top: 20px; + + button { + display: block; + margin: 0 auto; + background: transparent; + color: $primary-1; + } + } } diff --git a/ui/components/app/edit-gas-popover/edit-gas-popover.component.js b/ui/components/app/edit-gas-popover/edit-gas-popover.component.js new file mode 100644 index 000000000..76810dcfc --- /dev/null +++ b/ui/components/app/edit-gas-popover/edit-gas-popover.component.js @@ -0,0 +1,83 @@ +import React, { useCallback, useContext, useState } from 'react'; +import PropTypes from 'prop-types'; + +import { useDispatch, useSelector } from 'react-redux'; +import Popover from '../../ui/popover'; +import Button from '../../ui/button'; +import EditGasDisplay from '../edit-gas-display'; +import EditGasDisplayEducation from '../edit-gas-display-education'; + +import { I18nContext } from '../../../contexts/i18n'; +import { hideModal, hideSidebar } from '../../../store/actions'; + +export default function EditGasPopover({ + popoverTitle, + confirmButtonText, + editGasDisplayProps, +}) { + const t = useContext(I18nContext); + const dispatch = useDispatch(); + const showSidebar = useSelector((state) => state.appState.sidebar.isOpen); + const [showEducationContent, setShowEducationContent] = useState(false); + + /** + * Temporary placeholder, this should be managed by the parent component but + * we will be extracting this component from the hard to maintain modal/ + * sidebar component. For now this is just to be able to appropriately close + * the modal in testing + */ + const closePopover = useCallback(() => { + if (showSidebar) { + dispatch(hideSidebar()); + } else { + dispatch(hideModal()); + } + }, [showSidebar, dispatch]); + + const title = showEducationContent + ? t('editGasEducationModalTitle') + : popoverTitle || t('editGasTitle'); + const footerButtonText = confirmButtonText || t('save'); + + return ( + setShowEducationContent(false) : undefined + } + footer={ + <> + + + } + > +
+ {showEducationContent ? ( + + ) : ( + setShowEducationContent(true)} + /> + )} +
+
+ ); +} + +EditGasPopover.propTypes = { + popoverTitle: PropTypes.string, + editGasDisplayProps: PropTypes.object, + confirmButtonText: PropTypes.string, + showEducationButton: PropTypes.bool, +}; + +EditGasPopover.defaultProps = { + popoverTitle: '', + editGasDisplayProps: {}, + confirmButtonText: '', + showEducationButton: false, +}; diff --git a/ui/components/app/edit-gas-popover/edit-gas-popover.stories.js b/ui/components/app/edit-gas-popover/edit-gas-popover.stories.js new file mode 100644 index 000000000..76c7dafae --- /dev/null +++ b/ui/components/app/edit-gas-popover/edit-gas-popover.stories.js @@ -0,0 +1,30 @@ +import React from 'react'; +import EditGasPopover from '.'; + +export default { + title: 'Edit Gas Display Popover', +}; + +export const basic = () => { + return ( +
+ +
+ ); +}; + +export const basicWithDifferentButtonText = () => { + return ( +
+ +
+ ); +}; + +export const educationalContentFlow = () => { + return ( +
+ +
+ ); +}; diff --git a/ui/components/app/edit-gas-popover/index.js b/ui/components/app/edit-gas-popover/index.js new file mode 100644 index 000000000..98a908824 --- /dev/null +++ b/ui/components/app/edit-gas-popover/index.js @@ -0,0 +1 @@ +export { default } from './edit-gas-popover.component'; diff --git a/ui/components/app/modals/modal.js b/ui/components/app/modals/modal.js index ce4282df3..96352bc19 100644 --- a/ui/components/app/modals/modal.js +++ b/ui/components/app/modals/modal.js @@ -11,6 +11,7 @@ import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; // Modal Components import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container'; import SwapsGasCustomizationModal from '../../../pages/swaps/swaps-gas-customization-modal'; +import EditGasPopover from '../edit-gas-popover/edit-gas-popover.component'; import DepositEtherModal from './deposit-ether-modal'; import AccountDetailsModal from './account-details-modal'; import ExportPrivateKeyModal from './export-private-key-modal'; @@ -246,7 +247,11 @@ const MODALS = { }, CUSTOMIZE_GAS: { - contents: , + contents: process.env.SHOW_EIP_1559_UI ? ( + + ) : ( + + ), mobileModalStyle: { width: '100vw', height: '100vh', diff --git a/ui/components/app/sidebars/sidebar.component.js b/ui/components/app/sidebars/sidebar.component.js index 45be23b50..1efd03c41 100644 --- a/ui/components/app/sidebars/sidebar.component.js +++ b/ui/components/app/sidebars/sidebar.component.js @@ -4,6 +4,8 @@ import ReactCSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'; import CustomizeGas from '../gas-customization/gas-modal-page-container'; import { MILLISECOND } from '../../../../shared/constants/time'; +import EditGasPopover from '../edit-gas-popover/edit-gas-popover.component'; + export default class Sidebar extends Component { static propTypes = { sidebarOpen: PropTypes.bool, @@ -15,6 +17,10 @@ export default class Sidebar extends Component { onOverlayClose: PropTypes.func, }; + static contextTypes = { + t: PropTypes.func, + }; + renderOverlay() { const { onOverlayClose } = this.props; @@ -48,6 +54,18 @@ export default class Sidebar extends Component { } } + renderGasPopover() { + const { t } = this.context; + + return ( + + ); + } + componentDidUpdate(prevProps) { if (!prevProps.sidebarShouldClose && this.props.sidebarShouldClose) { this.props.hideSidebar(); @@ -57,6 +75,12 @@ export default class Sidebar extends Component { render() { const { transitionName, sidebarOpen, sidebarShouldClose } = this.props; + const showSidebar = sidebarOpen && !sidebarShouldClose; + + if (showSidebar && process.env.SHOW_EIP_1559_UI) { + return this.renderGasPopover(); + } + return (
- {sidebarOpen && !sidebarShouldClose - ? this.renderSidebarContent() - : null} + {showSidebar ? this.renderSidebarContent() : null} - {sidebarOpen && !sidebarShouldClose ? this.renderOverlay() : null} + {showSidebar ? this.renderOverlay() : null}
); } diff --git a/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js index 6c631b921..cb820a874 100644 --- a/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -22,6 +22,7 @@ import { PRIMARY, SECONDARY } from '../../helpers/constants/common'; import { hexToDecimal } from '../../helpers/utils/conversions.util'; import AdvancedGasInputs from '../../components/app/gas-customization/advanced-gas-inputs'; import TextField from '../../components/ui/text-field'; +import AdvancedGasControls from '../../components/app/advanced-gas-controls'; import { TRANSACTION_TYPES, TRANSACTION_STATUSES, @@ -283,6 +284,24 @@ export default class ConfirmTransactionBase extends Component { const notMainnetOrTest = !(isMainnet || process.env.IN_TEST); const gasPriceFetchFailure = isEthGasPrice || noGasPrice; + const inlineGasControls = process.env.SHOW_EIP_1559_UI ? ( + + ) : ( + + updateGasAndCalculate({ ...customGas, gasPrice: newGasPrice }) + } + updateCustomGasLimit={(newGasLimit) => + updateGasAndCalculate({ ...customGas, gasLimit: newGasLimit }) + } + customGasPrice={customGas.gasPrice} + customGasLimit={customGas.gasLimit} + insufficientBalance={insufficientBalance} + customPriceIsSafe + isSpeedUp={false} + /> + ); + return (
@@ -304,23 +323,9 @@ export default class ConfirmTransactionBase extends Component { hideFiatConversion ? t('noConversionRateAvailable') : '' } /> - {advancedInlineGasShown || - notMainnetOrTest || - gasPriceFetchFailure ? ( - - updateGasAndCalculate({ ...customGas, gasPrice: newGasPrice }) - } - updateCustomGasLimit={(newGasLimit) => - updateGasAndCalculate({ ...customGas, gasLimit: newGasLimit }) - } - customGasPrice={customGas.gasPrice} - customGasLimit={customGas.gasLimit} - insufficientBalance={insufficientBalance} - customPriceIsSafe - isSpeedUp={false} - /> - ) : null} + {advancedInlineGasShown || notMainnetOrTest || gasPriceFetchFailure + ? inlineGasControls + : null} {noGasPrice ? (