1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

Adding the AdvancedGasFeePopover template (#12780)

This commit is contained in:
Niranjana Binoy 2021-11-23 12:46:33 -05:00 committed by GitHub
parent a931316a53
commit 582882b3ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 252 additions and 120 deletions

View File

@ -163,6 +163,9 @@
"advanced": {
"message": "Advanced"
},
"advancedGasFeeModalTitle": {
"message": "Advanced gas fee"
},
"advancedGasPriceTitle": {
"message": "Gas price"
},

View File

@ -0,0 +1,35 @@
import React from 'react';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { useTransactionModalContext } from '../../../contexts/transaction-modal';
import Box from '../../ui/box';
import Button from '../../ui/button';
import I18nValue from '../../ui/i18n-value';
import Popover from '../../ui/popover';
const AdvancedGasFeePopover = () => {
const t = useI18nContext();
const { closeModal, currentModal } = useTransactionModalContext();
if (currentModal !== 'advancedGasFee') return null;
// todo: align styles to edit gas fee modal
return (
<Popover
className="advanced-gas-fee-popover"
title={t('advancedGasFeeModalTitle')}
onBack={() => closeModal('advancedGasFee')}
onClose={() => closeModal('advancedGasFee')}
footer={
<Button type="primary">
<I18nValue messageKey="save" />
</Button>
}
>
<Box className="advanced-gas-fee-popover" margin={4}></Box>
</Popover>
);
};
export default AdvancedGasFeePopover;

View File

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

View File

@ -0,0 +1,8 @@
.advanced-gas-fee-popover {
.popover-header {
border-radius: 0;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border-bottom: 1px solid $Grey-200;
}
}

View File

@ -50,4 +50,5 @@
@import 'transaction-total-banner/index';
@import 'wallet-overview/index';
@import 'whats-new-popup/index';
@import 'loading-network-screen/index'
@import 'loading-network-screen/index';
@import 'advanced-gas-fee-popover/index';

View File

@ -8,7 +8,8 @@ import { GasFeeContextProvider } from '../../../contexts/gasFee';
import ErrorMessage from '../../ui/error-message';
import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction';
import Dialog from '../../ui/dialog';
import EditGasFeePopover from '../edit-gas-fee-popover/edit-gas-fee-popover';
import AdvancedGasFeePopover from '../advanced-gas-fee-popover';
import EditGasFeePopover from '../edit-gas-fee-popover';
import {
ConfirmPageContainerHeader,
ConfirmPageContainerContent,
@ -236,9 +237,8 @@ export default class ConfirmPageContainer extends Component {
transaction={currentTransaction}
/>
)}
{editingGas && EIP_1559_V2 && (
<EditGasFeePopover onClose={handleCloseEditGas} />
)}
<EditGasFeePopover />
<AdvancedGasFeePopover />
</div>
</GasFeeContextProvider>
);

View File

@ -1,8 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { PRIORITY_LEVELS } from '../../../../shared/constants/gas';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { useTransactionModalContext } from '../../../contexts/transaction-modal';
import I18nValue from '../../ui/i18n-value';
import LoadingHeartBeat from '../../ui/loading-heartbeat';
import Popover from '../../ui/popover';
@ -12,13 +13,16 @@ import { COLORS } from '../../../helpers/constants/design-system';
import EditGasItem from './edit-gas-item';
import NetworkStatus from './network-status';
const EditGasFeePopover = ({ onClose }) => {
const EditGasFeePopover = () => {
const t = useI18nContext();
const { closeModal, currentModal } = useTransactionModalContext();
if (currentModal !== 'editGasFee') return null;
return (
<Popover
title={t('editGasFeeModalTitle')}
onClose={onClose}
onClose={() => closeModal('editGasFee')}
className="edit-gas-fee-popover"
>
<>
@ -36,27 +40,12 @@ const EditGasFeePopover = ({ onClose }) => {
<I18nValue messageKey="maxFee" />
</span>
</div>
<EditGasItem
priorityLevel={PRIORITY_LEVELS.LOW}
onClose={onClose}
/>
<EditGasItem
priorityLevel={PRIORITY_LEVELS.MEDIUM}
onClose={onClose}
/>
<EditGasItem
priorityLevel={PRIORITY_LEVELS.HIGH}
onClose={onClose}
/>
<EditGasItem priorityLevel={PRIORITY_LEVELS.LOW} />
<EditGasItem priorityLevel={PRIORITY_LEVELS.MEDIUM} />
<EditGasItem priorityLevel={PRIORITY_LEVELS.HIGH} />
<div className="edit-gas-fee-popover__content__separator" />
<EditGasItem
priorityLevel={PRIORITY_LEVELS.DAPP_SUGGESTED}
onClose={onClose}
/>
<EditGasItem
priorityLevel={PRIORITY_LEVELS.CUSTOM}
onClose={onClose}
/>
<EditGasItem priorityLevel={PRIORITY_LEVELS.DAPP_SUGGESTED} />
<EditGasItem priorityLevel={PRIORITY_LEVELS.CUSTOM} />
<NetworkStatus />
<Typography
className="edit-gas-fee-popover__know-more"
@ -85,8 +74,4 @@ const EditGasFeePopover = ({ onClose }) => {
);
};
EditGasFeePopover.propTypes = {
onClose: PropTypes.func,
};
export default EditGasFeePopover;

View File

@ -16,6 +16,13 @@ jest.mock('../../../store/actions', () => ({
addPollingTokenToAppState: jest.fn(),
}));
jest.mock('../../../contexts/transaction-modal', () => ({
useTransactionModalContext: () => ({
closeModal: () => undefined,
currentModal: 'editGasFee',
}),
}));
const MOCK_FEE_ESTIMATE = {
low: {
minWaitTimeEstimate: 360000,

View File

@ -16,13 +16,14 @@ import { getAdvancedGasFeeValues } from '../../../../selectors';
import { toHumanReadableTime } from '../../../../helpers/utils/util';
import { useGasFeeContext } from '../../../../contexts/gasFee';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import { useTransactionModalContext } from '../../../../contexts/transaction-modal';
import I18nValue from '../../../ui/i18n-value';
import InfoTooltip from '../../../ui/info-tooltip';
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display';
import { useCustomTimeEstimate } from './useCustomTimeEstimate';
const EditGasItem = ({ priorityLevel, onClose }) => {
const EditGasItem = ({ priorityLevel }) => {
const {
estimateUsed,
gasFeeEstimates,
@ -34,6 +35,8 @@ const EditGasItem = ({ priorityLevel, onClose }) => {
} = useGasFeeContext();
const t = useI18nContext();
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
const { closeModal, openModal } = useTransactionModalContext();
let maxFeePerGas;
let maxPriorityFeePerGas;
let minWaitTime;
@ -83,11 +86,12 @@ const EditGasItem = ({ priorityLevel, onClose }) => {
: null;
const onOptionSelect = () => {
if (priorityLevel !== PRIORITY_LEVELS.CUSTOM) {
if (priorityLevel === PRIORITY_LEVELS.CUSTOM) {
openModal('advancedGasFee');
} else {
updateTransactionUsingGasFeeEstimates(priorityLevel);
closeModal('editGasFee');
}
// todo: open advance modal if priorityLevel is custom
onClose();
};
return (
@ -144,7 +148,6 @@ const EditGasItem = ({ priorityLevel, onClose }) => {
EditGasItem.propTypes = {
priorityLevel: PropTypes.string,
onClose: PropTypes.func,
};
export default EditGasItem;

View File

@ -13,7 +13,7 @@
display: flex;
flex-wrap: wrap;
justify-content: end;
width: 65%;
width: 55%;
}
.info-tooltip {

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { useGasFeeContext } from '../../../contexts/gasFee';
import { useTransactionModalContext } from '../../../contexts/transaction-modal';
import InfoTooltip from '../../ui/info-tooltip/info-tooltip';
import Typography from '../../ui/typography/typography';
@ -22,12 +23,13 @@ export default function TransactionDetail({ rows = [], onEdit }) {
maxPriorityFeePerGas,
transaction,
} = useGasFeeContext();
const { openModal } = useTransactionModalContext();
if (EIP_1559_V2 && estimateUsed) {
return (
<div className="transaction-detail">
<div className="transaction-detail-edit-V2">
<button onClick={onEdit}>
<button onClick={() => openModal('editGasFee')}>
<span className="transaction-detail-edit-V2__icon">
{`${PRIORITY_LEVEL_ICON_MAP[estimateUsed]} `}
</span>
@ -37,7 +39,9 @@ export default function TransactionDetail({ rows = [], onEdit }) {
<i className="fas fa-chevron-right asset-list-item__chevron-right" />
</button>
{estimateUsed === 'custom' && onEdit && (
<button onClick={onEdit}>{t('edit')}</button>
<button onClick={() => openModal('advancedGasFee')}>
{t('edit')}
</button>
)}
{estimateUsed === 'dappSuggested' && (
<InfoTooltip

View File

@ -3,6 +3,7 @@ import React, {
createContext,
useEffect,
useCallback,
useContext,
useState,
} from 'react';
import { useSelector } from 'react-redux';
@ -124,6 +125,10 @@ export function MetaMetricsProvider({ children }) {
MetaMetricsProvider.propTypes = { children: PropTypes.node };
export function useMetaMetricsContext() {
return useContext(MetaMetricsContext);
}
export class LegacyMetaMetricsProvider extends Component {
static propTypes = {
children: PropTypes.node,

View File

@ -0,0 +1,75 @@
import React, { createContext, useContext, useState } from 'react';
import PropTypes from 'prop-types';
import { TRANSACTION_TYPES } from '../../shared/constants/transaction';
import { getMethodName } from '../helpers/utils/metrics';
import { useGasFeeContext } from './gasFee';
import { useMetaMetricsContext } from './metametrics';
export const TransactionModalContext = createContext({});
export const TransactionModalContextProvider = ({
actionKey,
children,
methodData,
}) => {
const [openModals, setOpenModals] = useState([]);
const metricsEvent = useMetaMetricsContext();
const { transaction: { origin } = {} } = useGasFeeContext();
const captureEvent = () => {
metricsEvent({
eventOpts: {
category: 'Transactions',
action: 'Confirm Screen',
name: 'User clicks "Edit" on gas',
},
customVariables: {
recipientKnown: null,
functionType:
actionKey ||
getMethodName(methodData.name) ||
TRANSACTION_TYPES.CONTRACT_INTERACTION,
origin,
},
});
};
const closeModal = (modalName) => {
const index = openModals.indexOf(modalName);
if (openModals < 0) return;
const modals = [...openModals];
modals.splice(index, 1);
setOpenModals(modals);
};
const openModal = (modalName) => {
if (openModals.includes(modalName)) return;
captureEvent();
const modals = [...openModals];
modals.push(modalName);
setOpenModals(modals);
};
return (
<TransactionModalContext.Provider
value={{
closeModal,
currentModal: openModals[openModals.length - 1],
openModal,
}}
>
{children}
</TransactionModalContext.Provider>
);
};
export function useTransactionModalContext() {
return useContext(TransactionModalContext);
}
TransactionModalContextProvider.propTypes = {
actionKey: PropTypes.string,
children: PropTypes.node.isRequired,
methodData: PropTypes.object,
};

View File

@ -0,0 +1,13 @@
import { getMethodName } from './metrics';
describe('getMethodName', () => {
it('should get correct method names', () => {
expect(getMethodName(undefined)).toStrictEqual('');
expect(getMethodName({})).toStrictEqual('');
expect(getMethodName('confirm')).toStrictEqual('confirm');
expect(getMethodName('balanceOf')).toStrictEqual('balance Of');
expect(getMethodName('ethToTokenSwapInput')).toStrictEqual(
'eth To Token Swap Input',
);
});
});

View File

@ -0,0 +1,10 @@
export function getMethodName(camelCase) {
if (!camelCase || typeof camelCase !== 'string') {
return '';
}
return camelCase
.replace(/([a-z])([A-Z])/gu, '$1 $2')
.replace(/([A-Z])([a-z])/gu, ' $1$2')
.replace(/ +/gu, ' ');
}

View File

@ -25,9 +25,11 @@ import {
TRANSACTION_TYPES,
TRANSACTION_STATUSES,
} from '../../../shared/constants/transaction';
import { getMethodName } from '../../helpers/utils/metrics';
import { getTransactionTypeTitle } from '../../helpers/utils/transactions.util';
import { toBuffer } from '../../../shared/modules/buffer-utils';
import { TransactionModalContextProvider } from '../../contexts/transaction-modal';
import TransactionDetail from '../../components/app/transaction-detail/transaction-detail.component';
import TransactionDetailItem from '../../components/app/transaction-detail-item/transaction-detail-item.component';
import InfoTooltip from '../../components/ui/info-tooltip/info-tooltip';
@ -913,6 +915,7 @@ export default class ConfirmTransactionBase extends Component {
render() {
const { t } = this.context;
const {
actionKey,
fromName,
fromAddress,
toName,
@ -976,71 +979,65 @@ export default class ConfirmTransactionBase extends Component {
}
}
return (
<ConfirmPageContainer
fromName={fromName}
fromAddress={fromAddress}
showAccountInHeader={showAccountInHeader}
toName={toName}
toAddress={toAddress}
toEns={toEns}
toNickname={toNickname}
showEdit={Boolean(onEdit)}
action={functionType}
title={title}
titleComponent={this.renderTitleComponent()}
subtitleComponent={this.renderSubtitleComponent()}
hideSubtitle={hideSubtitle}
detailsComponent={this.renderDetails()}
dataComponent={this.renderData(functionType)}
contentComponent={contentComponent}
nonce={customNonceValue || nonce}
unapprovedTxCount={unapprovedTxCount}
identiconAddress={identiconAddress}
errorMessage={submitError}
errorKey={errorKey}
hasSimulationError={hasSimulationError}
warning={submitWarning}
totalTx={totalTx}
positionOfCurrentTx={positionOfCurrentTx}
nextTxId={nextTxId}
prevTxId={prevTxId}
showNavigation={showNavigation}
onNextTx={(txId) => this.handleNextTx(txId)}
firstTx={firstTx}
lastTx={lastTx}
ofText={ofText}
requestsWaitingText={requestsWaitingText}
hideConfirmAnyways={!isDisabled()}
disabled={
renderSimulationFailureWarning ||
!valid ||
submitting ||
hardwareWalletRequiresConnection ||
(gasIsLoading && !gasFeeIsCustom)
}
onEdit={() => this.handleEdit()}
onCancelAll={() => this.handleCancelAll()}
onCancel={() => this.handleCancel()}
onSubmit={() => this.handleSubmit()}
onConfirmAnyways={() => this.handleConfirmAnyways()}
hideSenderToRecipient={hideSenderToRecipient}
origin={txData.origin}
ethGasPriceWarning={ethGasPriceWarning}
editingGas={editingGas}
handleCloseEditGas={() => this.handleCloseEditGas()}
currentTransaction={txData}
/>
<TransactionModalContextProvider
actionKey={actionKey}
methodData={methodData}
>
<ConfirmPageContainer
fromName={fromName}
fromAddress={fromAddress}
showAccountInHeader={showAccountInHeader}
toName={toName}
toAddress={toAddress}
toEns={toEns}
toNickname={toNickname}
showEdit={Boolean(onEdit)}
action={functionType}
title={title}
titleComponent={this.renderTitleComponent()}
subtitleComponent={this.renderSubtitleComponent()}
hideSubtitle={hideSubtitle}
detailsComponent={this.renderDetails()}
dataComponent={this.renderData(functionType)}
contentComponent={contentComponent}
nonce={customNonceValue || nonce}
unapprovedTxCount={unapprovedTxCount}
identiconAddress={identiconAddress}
errorMessage={submitError}
errorKey={errorKey}
hasSimulationError={hasSimulationError}
warning={submitWarning}
totalTx={totalTx}
positionOfCurrentTx={positionOfCurrentTx}
nextTxId={nextTxId}
prevTxId={prevTxId}
showNavigation={showNavigation}
onNextTx={(txId) => this.handleNextTx(txId)}
firstTx={firstTx}
lastTx={lastTx}
ofText={ofText}
requestsWaitingText={requestsWaitingText}
hideConfirmAnyways={!isDisabled()}
disabled={
renderSimulationFailureWarning ||
!valid ||
submitting ||
hardwareWalletRequiresConnection ||
(gasIsLoading && !gasFeeIsCustom)
}
onEdit={() => this.handleEdit()}
onCancelAll={() => this.handleCancelAll()}
onCancel={() => this.handleCancel()}
onSubmit={() => this.handleSubmit()}
onConfirmAnyways={() => this.handleConfirmAnyways()}
hideSenderToRecipient={hideSenderToRecipient}
origin={txData.origin}
ethGasPriceWarning={ethGasPriceWarning}
editingGas={editingGas}
handleCloseEditGas={() => this.handleCloseEditGas()}
currentTransaction={txData}
/>
</TransactionModalContextProvider>
);
}
}
export function getMethodName(camelCase) {
if (!camelCase || typeof camelCase !== 'string') {
return '';
}
return camelCase
.replace(/([a-z])([A-Z])/gu, '$1 $2')
.replace(/([A-Z])([a-z])/gu, ' $1$2')
.replace(/ +/gu, ' ');
}

View File

@ -1,15 +0,0 @@
import { getMethodName } from './confirm-transaction-base.component';
describe('ConfirmTransactionBase Component', () => {
describe('getMethodName', () => {
it('should get correct method names', () => {
expect(getMethodName(undefined)).toStrictEqual('');
expect(getMethodName({})).toStrictEqual('');
expect(getMethodName('confirm')).toStrictEqual('confirm');
expect(getMethodName('balanceOf')).toStrictEqual('balance Of');
expect(getMethodName('ethToTokenSwapInput')).toStrictEqual(
'eth To Token Swap Input',
);
});
});
});