1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Implements the new EIP1559 UI components (#11384)

This commit is contained in:
David Walsh 2021-06-28 09:45:08 -05:00 committed by GitHub
parent e488f61a21
commit d68f8f27c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 502 additions and 131 deletions

View File

@ -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": "Weve 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"
},

View File

@ -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

View File

@ -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 (
<div className="advanced-gas-controls">
<AdvancedGasControlsRow
@ -24,52 +28,66 @@ export default function AdvancedGasControls() {
titleDetailText=""
value={gasLimit}
/>
<AdvancedGasControlsRow
titleText={t('maxPriorityFee')}
tooltipText=""
onChange={setMaxPriorityFee}
value={maxPriorityFee}
titleDetailText={
<>
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('gasFeeEstimate')}:
</Typography>{' '}
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
></Typography>
</>
}
/>
<AdvancedGasControlsRow
titleText={t('maxFee')}
tooltipText=""
onChange={setMaxFee}
value={maxFee}
titleDetailText={
<>
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('gasFeeEstimate')}:
</Typography>{' '}
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
></Typography>
</>
}
/>
{process.env.SHOW_EIP_1559_UI ? (
<>
<AdvancedGasControlsRow
titleText={t('maxPriorityFee')}
tooltipText=""
onChange={setMaxPriorityFee}
value={maxPriorityFee}
titleDetailText={
<>
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('gasFeeEstimate')}:
</Typography>{' '}
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
></Typography>
</>
}
/>
<AdvancedGasControlsRow
titleText={t('maxFee')}
tooltipText=""
onChange={setMaxFee}
value={maxFee}
titleDetailText={
<>
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('gasFeeEstimate')}:
</Typography>{' '}
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
></Typography>
</>
}
/>
</>
) : (
<>
<AdvancedGasControlsRow
titleText={t('gasPrice')}
onChange={setGasPrice}
tooltipText=""
titleDetailText=""
value={gasPrice}
/>
</>
)}
</div>
);
}

View File

@ -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';

View File

@ -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 (
<div className="edit-gas-display-education">
<Typography
color={COLORS.BLACK}
variant={TYPOGRAPHY.H4}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('editGasEducationModalTitle')}
</Typography>
<Typography tag="p" color={COLORS.UI4} variant={TYPOGRAPHY.H6}>
{t('editGasEducationModalIntro')}
</Typography>
<Typography
color={COLORS.BLACK}
variant={TYPOGRAPHY.h6}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('editGasHigh')}
</Typography>
<Typography tag="p" color={COLORS.UI4} variant={TYPOGRAPHY.H6}>
{t('editGasEducationHighExplanation')}
</Typography>
<Typography
color={COLORS.BLACK}
variant={TYPOGRAPHY.h6}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('editGasMedium')}
</Typography>
<Typography tag="p" color={COLORS.UI4} variant={TYPOGRAPHY.H6}>
{t('editGasEducationMediumExplanation')}
</Typography>
<Typography
color={COLORS.BLACK}
variant={TYPOGRAPHY.h6}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('editGasLow')}
</Typography>
<Typography tag="p" color={COLORS.UI4} variant={TYPOGRAPHY.H6}>
{t('editGasEducationLowExplanation')}
</Typography>
<Typography tag="p" variant={TYPOGRAPHY.H6}>
<a
style={{ color: COLORS.PRIMARY1 }}
href=""
target="_blank"
rel="noopener noreferrer"
>
{t('editGasEducationLearnMoreLinkText')}
</a>
</Typography>
</div>
);
}

View File

@ -0,0 +1,14 @@
import React from 'react';
import EditGasDisplayEducation from '.';
export default {
title: 'Edit Gas Display',
};
export const basic = () => {
return (
<div style={{ width: '600px' }}>
<EditGasDisplayEducation />
</div>
);
};

View File

@ -0,0 +1 @@
export { default } from './edit-gas-display-education.component';

View File

@ -0,0 +1,5 @@
.edit-gas-display-education {
a {
color: $primary-1;
}
}

View File

@ -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 (
<div className="edit-gas-display">
<div className="edit-gas-display__content">
{warning && (
<div className="edit-gas-display__warning">
<ActionableMessage
className="actionable-message--warning"
message="Swaps are time sensitive. “Medium” is not reccomended."
/>
</div>
)}
{type === 'speed-up' && (
<div className="edit-gas-display__top-tooltip">
<Typography
color={COLORS.BLACK}
variant={TYPOGRAPHY.H8}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('speedUpTooltipText')}{' '}
<InfoTooltip
position="top"
contentText={t('speedUpExplanation')}
/>
</Typography>
</div>
)}
<TransactionTotalBanner total="" detail="" timing="" />
<RadioGroup
name="gas-recommendation"
options={[
{ value: 'low', label: t('editGasLow'), recommended: false },
{ value: 'medium', label: t('editGasMedium'), recommended: false },
{ value: 'high', label: t('editGasHigh'), recommended: true },
]}
selectedValue="high"
/>
{!alwaysShowForm && (
<button
className="edit-gas-display__advanced-button"
onClick={() => setShowAdvancedForm(!showAdvancedForm)}
>
{t('advancedOptions')}{' '}
{showAdvancedForm ? (
<i className="fa fa-caret-up"></i>
) : (
<i className="fa fa-caret-down"></i>
)}
</button>
)}
{(alwaysShowForm || showAdvancedForm) && <AdvancedGasControls />}
</div>
{showEducationButton && (
<div className="edit-gas-display__education">
<button onClick={onEducationClick}>
{t('editGasEducationButtonText')}
</button>
</div>
)}
</div>
);
}
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,
};

View File

@ -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 (
<div className="edit-gas-display">
<TransactionTotalBanner
total="9.99"
detail="Up to $17.79 (0.01234 ETH)"
timing="Likely in < 30 seconds
"
/>
<RadioGroup
name="gas-recommendation"
options={[
{ value: 'low', label: 'Low', recommended: false },
{ value: 'medium', label: 'Medium', recommended: false },
{ value: 'high', label: 'High', recommended: true },
]}
selectedValue="high"
/>
<button
className="edit-gas-display__advanced-button"
onClick={() => setShowAdvancedForm(!showAdvancedForm)}
>
{t('advancedOptions')}{' '}
{showAdvancedForm ? (
<i className="fa fa-caret-up"></i>
) : (
<i className="fa fa-caret-down"></i>
)}
</button>
{showAdvancedForm && <AdvancedGasControls />}
</div>
);
}

View File

@ -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 (
<div style={{ width: '600px' }}>
<PopoverPortal
title="Edit gas fee"
onClose={() => console.log('Closing!')}
footer={
<>
<Button type="primary">Save</Button>
</>
}
>
<div style={{ padding: '20px' }}>
<EditGasDisplay />
</div>
</PopoverPortal>
<EditGasDisplay showEducationButton />
</div>
);
};

View File

@ -1 +1 @@
export { default } from './edit-gas-display';
export { default } from './edit-gas-display.component';

View File

@ -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;
}
}
}

View File

@ -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 (
<Popover
title={title}
onClose={closePopover}
onBack={
showEducationContent ? () => setShowEducationContent(false) : undefined
}
footer={
<>
<Button type="primary" onClick={closePopover}>
{footerButtonText}
</Button>
</>
}
>
<div style={{ padding: '0 20px 20px 20px' }}>
{showEducationContent ? (
<EditGasDisplayEducation />
) : (
<EditGasDisplay
{...editGasDisplayProps}
onEducationClick={() => setShowEducationContent(true)}
/>
)}
</div>
</Popover>
);
}
EditGasPopover.propTypes = {
popoverTitle: PropTypes.string,
editGasDisplayProps: PropTypes.object,
confirmButtonText: PropTypes.string,
showEducationButton: PropTypes.bool,
};
EditGasPopover.defaultProps = {
popoverTitle: '',
editGasDisplayProps: {},
confirmButtonText: '',
showEducationButton: false,
};

View File

@ -0,0 +1,30 @@
import React from 'react';
import EditGasPopover from '.';
export default {
title: 'Edit Gas Display Popover',
};
export const basic = () => {
return (
<div style={{ width: '600px' }}>
<EditGasPopover />
</div>
);
};
export const basicWithDifferentButtonText = () => {
return (
<div style={{ width: '600px' }}>
<EditGasPopover confirmButtonText="Custom Value" />
</div>
);
};
export const educationalContentFlow = () => {
return (
<div style={{ width: '600px' }}>
<EditGasPopover editGasDisplayProps={{ showEducationButton: true }} />
</div>
);
};

View File

@ -0,0 +1 @@
export { default } from './edit-gas-popover.component';

View File

@ -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: <ConfirmCustomizeGasModal />,
contents: process.env.SHOW_EIP_1559_UI ? (
<EditGasPopover />
) : (
<ConfirmCustomizeGasModal />
),
mobileModalStyle: {
width: '100vw',
height: '100vh',

View File

@ -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 (
<EditGasPopover
popoverTitle={t('speedUpPopoverTitle')}
editGasDisplayProps={{ alwaysShowForm: true, type: 'speed-up' }}
confirmButtonText={t('submit')}
/>
);
}
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 (
<div>
<ReactCSSTransitionGroup
@ -64,11 +88,9 @@ export default class Sidebar extends Component {
transitionEnterTimeout={MILLISECOND * 300}
transitionLeaveTimeout={MILLISECOND * 200}
>
{sidebarOpen && !sidebarShouldClose
? this.renderSidebarContent()
: null}
{showSidebar ? this.renderSidebarContent() : null}
</ReactCSSTransitionGroup>
{sidebarOpen && !sidebarShouldClose ? this.renderOverlay() : null}
{showSidebar ? this.renderOverlay() : null}
</div>
);
}

View File

@ -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 ? (
<AdvancedGasControls />
) : (
<AdvancedGasInputs
updateCustomGasPrice={(newGasPrice) =>
updateGasAndCalculate({ ...customGas, gasPrice: newGasPrice })
}
updateCustomGasLimit={(newGasLimit) =>
updateGasAndCalculate({ ...customGas, gasLimit: newGasLimit })
}
customGasPrice={customGas.gasPrice}
customGasLimit={customGas.gasLimit}
insufficientBalance={insufficientBalance}
customPriceIsSafe
isSpeedUp={false}
/>
);
return (
<div className="confirm-page-container-content__details">
<div className="confirm-page-container-content__gas-fee">
@ -304,23 +323,9 @@ export default class ConfirmTransactionBase extends Component {
hideFiatConversion ? t('noConversionRateAvailable') : ''
}
/>
{advancedInlineGasShown ||
notMainnetOrTest ||
gasPriceFetchFailure ? (
<AdvancedGasInputs
updateCustomGasPrice={(newGasPrice) =>
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 ? (
<div className="confirm-page-container-content__error-container">
<ErrorMessage errorKey={GAS_PRICE_FETCH_FAILURE_ERROR_KEY} />