mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
Implementing save button on advance gas fee modal (#12854)
This commit is contained in:
parent
8e6a0ff879
commit
a020281c96
@ -25,7 +25,7 @@ const AdvancedGasFeeInputSubtext = ({ latest, historical }) => {
|
||||
};
|
||||
|
||||
AdvancedGasFeeInputSubtext.propTypes = {
|
||||
latest: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
latest: PropTypes.string,
|
||||
historical: PropTypes.string,
|
||||
};
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
align-items: center;
|
||||
margin-top: 2px;
|
||||
color: $ui-4;
|
||||
font-size: $font-size-h7;
|
||||
font-size: $font-size-h8;
|
||||
|
||||
&__label {
|
||||
font-weight: bold;
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
import Box from '../../../ui/box';
|
||||
import BasefeeInput from './basefee-input';
|
||||
import PriorityFeeInput from './priorityfee-input';
|
||||
import BaseFeeInput from './base-fee-input';
|
||||
import PriorityFeeInput from './priority-fee-input';
|
||||
|
||||
const AdvancedGasFeeInputs = () => {
|
||||
return (
|
||||
<Box className="advanced-gas-fee-input" margin={4}>
|
||||
<BasefeeInput />
|
||||
<div className="advanced-gas-fee-input__separator" />
|
||||
<Box className="advanced-gas-fee-inputs" margin={4}>
|
||||
<BaseFeeInput />
|
||||
<div className="advanced-gas-fee-inputs__separator" />
|
||||
<PriorityFeeInput />
|
||||
</Box>
|
||||
);
|
||||
|
@ -0,0 +1,155 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { PRIORITY_LEVELS } from '../../../../../../shared/constants/gas';
|
||||
import {
|
||||
divideCurrencies,
|
||||
multiplyCurrencies,
|
||||
} from '../../../../../../shared/modules/conversion.utils';
|
||||
import { PRIMARY, SECONDARY } from '../../../../../helpers/constants/common';
|
||||
import { decGWEIToHexWEI } from '../../../../../helpers/utils/conversions.util';
|
||||
import { getAdvancedGasFeeValues } from '../../../../../selectors';
|
||||
import { useGasFeeContext } from '../../../../../contexts/gasFee';
|
||||
import { useI18nContext } from '../../../../../hooks/useI18nContext';
|
||||
import { useUserPreferencedCurrency } from '../../../../../hooks/useUserPreferencedCurrency';
|
||||
import { useCurrencyDisplay } from '../../../../../hooks/useCurrencyDisplay';
|
||||
import Button from '../../../../ui/button';
|
||||
import Box from '../../../../ui/box';
|
||||
import FormField from '../../../../ui/form-field';
|
||||
import I18nValue from '../../../../ui/i18n-value';
|
||||
|
||||
import { useAdvanceGasFeePopoverContext } from '../../context';
|
||||
import AdvancedGasFeeInputSubtext from '../../advanced-gas-fee-input-subtext';
|
||||
|
||||
const divideCurrencyValues = (value, baseFee) => {
|
||||
if (baseFee === 0) {
|
||||
return 0;
|
||||
}
|
||||
return divideCurrencies(value, baseFee, {
|
||||
numberOfDecimals: 2,
|
||||
dividendBase: 10,
|
||||
divisorBase: 10,
|
||||
}).toNumber();
|
||||
};
|
||||
|
||||
const multiplyCurrencyValues = (baseFee, value, numberOfDecimals) =>
|
||||
multiplyCurrencies(baseFee, value, {
|
||||
numberOfDecimals,
|
||||
multiplicandBase: 10,
|
||||
multiplierBase: 10,
|
||||
}).toNumber();
|
||||
|
||||
const BaseFeeInput = () => {
|
||||
const t = useI18nContext();
|
||||
const { gasFeeEstimates, estimateUsed, maxFeePerGas } = useGasFeeContext();
|
||||
const { setDirty, setMaxFeePerGas } = useAdvanceGasFeePopoverContext();
|
||||
const { estimatedBaseFee } = gasFeeEstimates;
|
||||
const {
|
||||
numberOfDecimals: numberOfDecimalsPrimary,
|
||||
} = useUserPreferencedCurrency(PRIMARY);
|
||||
const {
|
||||
currency,
|
||||
numberOfDecimals: numberOfDecimalsFiat,
|
||||
} = useUserPreferencedCurrency(SECONDARY);
|
||||
|
||||
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
|
||||
|
||||
const [editingInGwei, setEditingInGwei] = useState(false);
|
||||
|
||||
const [maxBaseFeeGWEI, setMaxBaseFeeGWEI] = useState(() => {
|
||||
if (
|
||||
estimateUsed !== PRIORITY_LEVELS.CUSTOM &&
|
||||
advancedGasFeeValues?.maxBaseFee
|
||||
) {
|
||||
return multiplyCurrencyValues(
|
||||
estimatedBaseFee,
|
||||
advancedGasFeeValues.maxBaseFee,
|
||||
numberOfDecimalsPrimary,
|
||||
);
|
||||
}
|
||||
return maxFeePerGas;
|
||||
});
|
||||
|
||||
const [maxBaseFeeMultiplier, setMaxBaseFeeMultiplier] = useState(() => {
|
||||
if (
|
||||
estimateUsed !== PRIORITY_LEVELS.CUSTOM &&
|
||||
advancedGasFeeValues?.maxBaseFee
|
||||
) {
|
||||
return advancedGasFeeValues.maxBaseFee;
|
||||
}
|
||||
return divideCurrencyValues(maxFeePerGas, estimatedBaseFee);
|
||||
});
|
||||
|
||||
const [, { value: baseFeeInFiat }] = useCurrencyDisplay(
|
||||
decGWEIToHexWEI(maxBaseFeeGWEI),
|
||||
{ currency, numberOfDecimalsFiat },
|
||||
);
|
||||
|
||||
const updateBaseFee = useCallback(
|
||||
(value) => {
|
||||
let baseFeeInGWEI;
|
||||
let baseFeeMultiplierValue;
|
||||
if (editingInGwei) {
|
||||
baseFeeInGWEI = value;
|
||||
baseFeeMultiplierValue = divideCurrencyValues(value, estimatedBaseFee);
|
||||
} else {
|
||||
baseFeeInGWEI = multiplyCurrencyValues(
|
||||
estimatedBaseFee,
|
||||
value,
|
||||
numberOfDecimalsPrimary,
|
||||
);
|
||||
baseFeeMultiplierValue = value;
|
||||
}
|
||||
setMaxBaseFeeGWEI(baseFeeInGWEI);
|
||||
setMaxBaseFeeMultiplier(baseFeeMultiplierValue);
|
||||
setDirty(true);
|
||||
},
|
||||
[
|
||||
editingInGwei,
|
||||
estimatedBaseFee,
|
||||
numberOfDecimalsPrimary,
|
||||
setMaxBaseFeeGWEI,
|
||||
setMaxBaseFeeMultiplier,
|
||||
setDirty,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setMaxFeePerGas(maxBaseFeeGWEI);
|
||||
}, [maxBaseFeeGWEI, setMaxFeePerGas]);
|
||||
|
||||
return (
|
||||
<Box className="base-fee-input">
|
||||
<FormField
|
||||
onChange={updateBaseFee}
|
||||
titleText={t('maxBaseFee')}
|
||||
titleUnit={editingInGwei ? 'GWEI' : `(${t('multiplier')})`}
|
||||
tooltipText={t('advancedBaseGasFeeToolTip')}
|
||||
titleDetail={
|
||||
<Button
|
||||
className="base-fee-input__edit-link"
|
||||
type="link"
|
||||
onClick={() => setEditingInGwei(!editingInGwei)}
|
||||
>
|
||||
<I18nValue
|
||||
messageKey={editingInGwei ? 'editInMultiplier' : 'editInGwei'}
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
value={editingInGwei ? maxBaseFeeGWEI : maxBaseFeeMultiplier}
|
||||
detailText={
|
||||
editingInGwei
|
||||
? `${maxBaseFeeMultiplier}x ${`≈ ${baseFeeInFiat}`}`
|
||||
: `${maxBaseFeeGWEI} GWEI ${`≈ ${baseFeeInFiat}`}`
|
||||
}
|
||||
numeric
|
||||
/>
|
||||
<AdvancedGasFeeInputSubtext
|
||||
latest={estimatedBaseFee}
|
||||
historical="23-359 GWEI"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseFeeInput;
|
@ -1,21 +1,23 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
|
||||
import mockEstimates from '../../../../../test/data/mock-estimates.json';
|
||||
import mockState from '../../../../../test/data/mock-state.json';
|
||||
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
|
||||
import configureStore from '../../../../store/store';
|
||||
import { GasFeeContextProvider } from '../../../../contexts/gasFee';
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../../../shared/constants/gas';
|
||||
import { renderWithProvider } from '../../../../../../test/lib/render-helpers';
|
||||
import mockEstimates from '../../../../../../test/data/mock-estimates.json';
|
||||
import mockState from '../../../../../../test/data/mock-state.json';
|
||||
import { GasFeeContextProvider } from '../../../../../contexts/gasFee';
|
||||
import configureStore from '../../../../../store/store';
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../../shared/constants/gas';
|
||||
import BasefeeInput from './basefee-input';
|
||||
import { AdvanceGasFeePopoverContextProvider } from '../../context';
|
||||
import BaseFeeInput from './base-fee-input';
|
||||
|
||||
jest.mock('../../../../store/actions', () => ({
|
||||
jest.mock('../../../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
removePollingTokenFromAppState: jest.fn(),
|
||||
}));
|
||||
|
||||
const render = (txProps) => {
|
||||
@ -42,13 +44,15 @@ const render = (txProps) => {
|
||||
...txProps,
|
||||
}}
|
||||
>
|
||||
<BasefeeInput />
|
||||
<AdvanceGasFeePopoverContextProvider>
|
||||
<BaseFeeInput />
|
||||
</AdvanceGasFeePopoverContextProvider>
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('BasefeeInput', () => {
|
||||
describe('BaseFeeInput', () => {
|
||||
it('should renders advancedGasFee.baseFee value if current estimate used is not custom', () => {
|
||||
render({
|
||||
userFeeLevel: 'high',
|
@ -0,0 +1 @@
|
||||
export { default } from './base-fee-input';
|
@ -0,0 +1,8 @@
|
||||
.base-fee-input {
|
||||
a.base-fee-input__edit-link {
|
||||
display: inline;
|
||||
font-size: $font-size-h7;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { PRIORITY_LEVELS } from '../../../../../shared/constants/gas';
|
||||
import {
|
||||
divideCurrencies,
|
||||
multiplyCurrencies,
|
||||
} from '../../../../../shared/modules/conversion.utils';
|
||||
import { PRIMARY, SECONDARY } from '../../../../helpers/constants/common';
|
||||
import { decGWEIToHexWEI } from '../../../../helpers/utils/conversions.util';
|
||||
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import { useUserPreferencedCurrency } from '../../../../hooks/useUserPreferencedCurrency';
|
||||
import { useCurrencyDisplay } from '../../../../hooks/useCurrencyDisplay';
|
||||
import Button from '../../../ui/button';
|
||||
import FormField from '../../../ui/form-field';
|
||||
import I18nValue from '../../../ui/i18n-value';
|
||||
|
||||
import AdvancedGasFeeInputSubtext from '../advanced-gas-fee-input-subtext';
|
||||
import { getAdvancedGasFeeValues } from '../../../../selectors';
|
||||
|
||||
const divideCurrencyValues = (value, baseFee) => {
|
||||
if (baseFee === 0) {
|
||||
return 0;
|
||||
}
|
||||
return divideCurrencies(value, baseFee, {
|
||||
numberOfDecimals: 2,
|
||||
dividendBase: 10,
|
||||
divisorBase: 10,
|
||||
}).toNumber();
|
||||
};
|
||||
|
||||
const multiplyCurrencyValues = (baseFee, value, numberOfDecimals) =>
|
||||
multiplyCurrencies(baseFee, value, {
|
||||
numberOfDecimals,
|
||||
multiplicandBase: 10,
|
||||
multiplierBase: 10,
|
||||
}).toNumber();
|
||||
|
||||
const BasefeeInput = () => {
|
||||
const t = useI18nContext();
|
||||
const { gasFeeEstimates, estimateUsed, maxFeePerGas } = useGasFeeContext();
|
||||
const { estimatedBaseFee } = gasFeeEstimates;
|
||||
const {
|
||||
numberOfDecimals: numberOfDecimalsPrimary,
|
||||
} = useUserPreferencedCurrency(PRIMARY);
|
||||
const {
|
||||
currency,
|
||||
numberOfDecimals: numberOfDecimalsFiat,
|
||||
} = useUserPreferencedCurrency(SECONDARY);
|
||||
|
||||
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
|
||||
|
||||
const [editingInGwei, setEditingInGwei] = useState(false);
|
||||
|
||||
const [maxBaseFeeGWEI, setMaxBaseFeeGWEI] = useState(() => {
|
||||
if (
|
||||
estimateUsed !== PRIORITY_LEVELS.CUSTOM &&
|
||||
advancedGasFeeValues?.maxBaseFee
|
||||
) {
|
||||
return multiplyCurrencyValues(
|
||||
estimatedBaseFee,
|
||||
advancedGasFeeValues.maxBaseFee,
|
||||
numberOfDecimalsPrimary,
|
||||
);
|
||||
}
|
||||
return maxFeePerGas;
|
||||
});
|
||||
|
||||
const [maxBaseFeeMultiplier, setMaxBaseFeeMultiplier] = useState(() => {
|
||||
if (
|
||||
estimateUsed !== PRIORITY_LEVELS.CUSTOM &&
|
||||
advancedGasFeeValues?.maxBaseFee
|
||||
) {
|
||||
return advancedGasFeeValues.maxBaseFee;
|
||||
}
|
||||
return divideCurrencyValues(maxFeePerGas, estimatedBaseFee);
|
||||
});
|
||||
|
||||
const [, { value: baseFeeInFiat }] = useCurrencyDisplay(
|
||||
decGWEIToHexWEI(maxBaseFeeGWEI),
|
||||
{ currency, numberOfDecimalsFiat },
|
||||
);
|
||||
|
||||
const updateBaseFee = useCallback(
|
||||
(value) => {
|
||||
if (editingInGwei) {
|
||||
setMaxBaseFeeGWEI(value);
|
||||
setMaxBaseFeeMultiplier(divideCurrencyValues(value, estimatedBaseFee));
|
||||
} else {
|
||||
setMaxBaseFeeMultiplier(value);
|
||||
setMaxBaseFeeGWEI(
|
||||
multiplyCurrencyValues(
|
||||
estimatedBaseFee,
|
||||
value,
|
||||
numberOfDecimalsPrimary,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
[
|
||||
editingInGwei,
|
||||
estimatedBaseFee,
|
||||
numberOfDecimalsPrimary,
|
||||
setMaxBaseFeeGWEI,
|
||||
setMaxBaseFeeMultiplier,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<FormField
|
||||
onChange={updateBaseFee}
|
||||
titleText={t('maxBaseFee')}
|
||||
titleUnit={editingInGwei ? 'GWEI' : `(${t('multiplier')})`}
|
||||
tooltipText={t('advancedBaseGasFeeToolTip')}
|
||||
titleDetail={
|
||||
<Button
|
||||
className="advanced-gas-fee-input__edit-link"
|
||||
type="link"
|
||||
onClick={() => setEditingInGwei(!editingInGwei)}
|
||||
>
|
||||
<I18nValue
|
||||
messageKey={editingInGwei ? 'editInMultiplier' : 'editInGwei'}
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
value={editingInGwei ? maxBaseFeeGWEI : maxBaseFeeMultiplier}
|
||||
detailText={
|
||||
editingInGwei
|
||||
? `${maxBaseFeeMultiplier}x ${`≈ ${baseFeeInFiat}`}`
|
||||
: `${maxBaseFeeGWEI} GWEI ${`≈ ${baseFeeInFiat}`}`
|
||||
}
|
||||
numeric
|
||||
inputDetails={
|
||||
<AdvancedGasFeeInputSubtext
|
||||
latest={estimatedBaseFee}
|
||||
historical="23-359 GWEI"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BasefeeInput;
|
@ -1,20 +1,12 @@
|
||||
.advanced-gas-fee-input {
|
||||
a.advanced-gas-fee-input__edit-link {
|
||||
display: inline;
|
||||
font-size: $font-size-h7;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
.advanced-gas-fee-inputs {
|
||||
.form-field {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.form-field__heading-title > h6 {
|
||||
font-size: $font-size-h7;
|
||||
}
|
||||
|
||||
&__border {
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid $Grey-100;
|
||||
}
|
||||
|
||||
&__separator {
|
||||
border-top: 1px solid $ui-grey;
|
||||
margin: 24px 0 16px 0;
|
||||
|
@ -0,0 +1 @@
|
||||
export { default } from './priority-fee-input';
|
@ -0,0 +1,67 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { PRIORITY_LEVELS } from '../../../../../../shared/constants/gas';
|
||||
import { SECONDARY } from '../../../../../helpers/constants/common';
|
||||
import { decGWEIToHexWEI } from '../../../../../helpers/utils/conversions.util';
|
||||
import { getAdvancedGasFeeValues } from '../../../../../selectors';
|
||||
import { useCurrencyDisplay } from '../../../../../hooks/useCurrencyDisplay';
|
||||
import { useGasFeeContext } from '../../../../../contexts/gasFee';
|
||||
import { useI18nContext } from '../../../../../hooks/useI18nContext';
|
||||
import { useUserPreferencedCurrency } from '../../../../../hooks/useUserPreferencedCurrency';
|
||||
import FormField from '../../../../ui/form-field';
|
||||
|
||||
import { useAdvanceGasFeePopoverContext } from '../../context';
|
||||
import AdvancedGasFeeInputSubtext from '../../advanced-gas-fee-input-subtext';
|
||||
|
||||
const PriorityFeeInput = () => {
|
||||
const t = useI18nContext();
|
||||
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
|
||||
const {
|
||||
setDirty,
|
||||
setMaxPriorityFeePerGas,
|
||||
} = useAdvanceGasFeePopoverContext();
|
||||
const { estimateUsed, maxPriorityFeePerGas } = useGasFeeContext();
|
||||
|
||||
const [priorityFee, setPriorityFee] = useState(() => {
|
||||
if (
|
||||
estimateUsed !== PRIORITY_LEVELS.CUSTOM &&
|
||||
advancedGasFeeValues?.priorityFee
|
||||
)
|
||||
return advancedGasFeeValues.priorityFee;
|
||||
return maxPriorityFeePerGas;
|
||||
});
|
||||
|
||||
const { currency, numberOfDecimals } = useUserPreferencedCurrency(SECONDARY);
|
||||
|
||||
const [, { value: priorityFeeInFiat }] = useCurrencyDisplay(
|
||||
decGWEIToHexWEI(priorityFee),
|
||||
{ currency, numberOfDecimals },
|
||||
);
|
||||
|
||||
const updatePriorityFee = (value) => {
|
||||
setPriorityFee(value);
|
||||
setDirty(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setMaxPriorityFeePerGas(priorityFee);
|
||||
}, [priorityFee, setMaxPriorityFeePerGas]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
onChange={updatePriorityFee}
|
||||
titleText={t('priorityFee')}
|
||||
titleUnit="(GWEI)"
|
||||
tooltipText={t('advancedPriorityFeeToolTip')}
|
||||
value={priorityFee}
|
||||
detailText={`≈ ${priorityFeeInFiat}`}
|
||||
numeric
|
||||
/>
|
||||
<AdvancedGasFeeInputSubtext latest="1-18 GWEI" historical="23-359 GWEI" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PriorityFeeInput;
|
@ -1,20 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import mockEstimates from '../../../../../test/data/mock-estimates.json';
|
||||
import mockState from '../../../../../test/data/mock-state.json';
|
||||
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
|
||||
import configureStore from '../../../../store/store';
|
||||
import { GasFeeContextProvider } from '../../../../contexts/gasFee';
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../../../shared/constants/gas';
|
||||
import { renderWithProvider } from '../../../../../../test/lib/render-helpers';
|
||||
import mockEstimates from '../../../../../../test/data/mock-estimates.json';
|
||||
import mockState from '../../../../../../test/data/mock-state.json';
|
||||
import { GasFeeContextProvider } from '../../../../../contexts/gasFee';
|
||||
import configureStore from '../../../../../store/store';
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../../shared/constants/gas';
|
||||
import PriprityfeeInput from './priorityfee-input';
|
||||
import { AdvanceGasFeePopoverContextProvider } from '../../context';
|
||||
import PriorityfeeInput from './priority-fee-input';
|
||||
|
||||
jest.mock('../../../../store/actions', () => ({
|
||||
jest.mock('../../../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
removePollingTokenFromAppState: jest.fn(),
|
||||
}));
|
||||
|
||||
const render = (txProps) => {
|
||||
@ -41,7 +43,9 @@ const render = (txProps) => {
|
||||
...txProps,
|
||||
}}
|
||||
>
|
||||
<PriprityfeeInput />
|
||||
<AdvanceGasFeePopoverContextProvider>
|
||||
<PriorityfeeInput />
|
||||
</AdvanceGasFeePopoverContextProvider>
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
@ -1,57 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { PRIORITY_LEVELS } from '../../../../../shared/constants/gas';
|
||||
import { SECONDARY } from '../../../../helpers/constants/common';
|
||||
import { decGWEIToHexWEI } from '../../../../helpers/utils/conversions.util';
|
||||
import { getAdvancedGasFeeValues } from '../../../../selectors';
|
||||
import { useCurrencyDisplay } from '../../../../hooks/useCurrencyDisplay';
|
||||
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import { useUserPreferencedCurrency } from '../../../../hooks/useUserPreferencedCurrency';
|
||||
import FormField from '../../../ui/form-field';
|
||||
|
||||
import AdvancedGasFeeInputSubtext from '../advanced-gas-fee-input-subtext';
|
||||
|
||||
const PriorityFeeInput = () => {
|
||||
const t = useI18nContext();
|
||||
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
|
||||
|
||||
const { estimateUsed, maxPriorityFeePerGas } = useGasFeeContext();
|
||||
|
||||
const [priorityFee, setPriorityFee] = useState(() => {
|
||||
if (
|
||||
estimateUsed !== PRIORITY_LEVELS.CUSTOM &&
|
||||
advancedGasFeeValues?.priorityFee
|
||||
)
|
||||
return advancedGasFeeValues.priorityFee;
|
||||
return maxPriorityFeePerGas;
|
||||
});
|
||||
|
||||
const { currency, numberOfDecimals } = useUserPreferencedCurrency(SECONDARY);
|
||||
|
||||
const [, { value: priorityFeeInFiat }] = useCurrencyDisplay(
|
||||
decGWEIToHexWEI(priorityFee),
|
||||
{ currency, numberOfDecimals },
|
||||
);
|
||||
|
||||
return (
|
||||
<FormField
|
||||
onChange={setPriorityFee}
|
||||
titleText={t('priorityFee')}
|
||||
titleUnit="(GWEI)"
|
||||
tooltipText={t('advancedPriorityFeeToolTip')}
|
||||
value={priorityFee}
|
||||
detailText={`≈ ${priorityFeeInFiat}`}
|
||||
numeric
|
||||
inputDetails={
|
||||
<AdvancedGasFeeInputSubtext
|
||||
latest="1-18 GWEI"
|
||||
historical="23-359 GWEI"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default PriorityFeeInput;
|
@ -2,13 +2,12 @@ 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';
|
||||
|
||||
import { AdvanceGasFeePopoverContextProvider } from './context';
|
||||
import AdvancedGasFeeInputs from './advanced-gas-fee-inputs';
|
||||
import AdvancedGasFeeSaveButton from './advanced-gas-fee-save';
|
||||
|
||||
const AdvancedGasFeePopover = () => {
|
||||
const t = useI18nContext();
|
||||
@ -21,21 +20,19 @@ const AdvancedGasFeePopover = () => {
|
||||
if (currentModal !== 'advancedGasFee') return null;
|
||||
|
||||
return (
|
||||
<Popover
|
||||
className="advanced-gas-fee-popover"
|
||||
title={t('advancedGasFeeModalTitle')}
|
||||
onBack={() => closeModal('advancedGasFee')}
|
||||
onClose={closeAllModals}
|
||||
footer={
|
||||
<Button type="primary">
|
||||
<I18nValue messageKey="save" />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Box className="advanced-gas-fee-popover__wrapper">
|
||||
<AdvancedGasFeeInputs />
|
||||
</Box>
|
||||
</Popover>
|
||||
<AdvanceGasFeePopoverContextProvider>
|
||||
<Popover
|
||||
className="advanced-gas-fee-popover"
|
||||
title={t('advancedGasFeeModalTitle')}
|
||||
onBack={() => closeModal('advancedGasFee')}
|
||||
onClose={closeAllModals}
|
||||
footer={<AdvancedGasFeeSaveButton />}
|
||||
>
|
||||
<Box className="advanced-gas-fee-popover__wrapper">
|
||||
<AdvancedGasFeeInputs />
|
||||
</Box>
|
||||
</Popover>
|
||||
</AdvanceGasFeePopoverContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import mockEstimates from '../../../../test/data/mock-estimates.json';
|
||||
import mockState from '../../../../test/data/mock-state.json';
|
||||
import { GasFeeContextProvider } from '../../../contexts/gasFee';
|
||||
import configureStore from '../../../store/store';
|
||||
|
||||
import AdvancedGasFeePopover from './advanced-gas-fee-popover';
|
||||
|
||||
jest.mock('../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
removePollingTokenFromAppState: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../contexts/transaction-modal', () => ({
|
||||
useTransactionModalContext: () => ({
|
||||
closeModal: () => undefined,
|
||||
currentModal: 'advancedGasFee',
|
||||
}),
|
||||
}));
|
||||
|
||||
const render = () => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
accounts: {
|
||||
[mockState.metamask.selectedAddress]: {
|
||||
address: mockState.metamask.selectedAddress,
|
||||
balance: '0x1F4',
|
||||
},
|
||||
},
|
||||
advancedGasFee: { priorityFee: 100 },
|
||||
featureFlags: { advancedInlineGas: true },
|
||||
gasFeeEstimates:
|
||||
mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET].gasFeeEstimates,
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(
|
||||
<GasFeeContextProvider
|
||||
transaction={{
|
||||
userFeeLevel: 'custom',
|
||||
}}
|
||||
>
|
||||
<AdvancedGasFeePopover />
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('AdvancedGasFeePopover', () => {
|
||||
it('should renders save button disabled by default', () => {
|
||||
render();
|
||||
expect(screen.queryByRole('button', { name: 'Save' })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should enable save button as input value is changed', () => {
|
||||
render();
|
||||
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||
target: { value: 4 },
|
||||
});
|
||||
expect(screen.queryByRole('button', { name: 'Save' })).not.toBeDisabled();
|
||||
});
|
||||
});
|
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PRIORITY_LEVELS } from '../../../../../shared/constants/gas';
|
||||
import { useTransactionModalContext } from '../../../../contexts/transaction-modal';
|
||||
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
||||
import Button from '../../../ui/button';
|
||||
import I18nValue from '../../../ui/i18n-value';
|
||||
|
||||
import { useAdvanceGasFeePopoverContext } from '../context';
|
||||
import { decGWEIToHexWEI } from '../../../../../shared/modules/conversion.utils';
|
||||
|
||||
const AdvancedGasFeeSaveButton = () => {
|
||||
const { closeModal } = useTransactionModalContext();
|
||||
const { updateTransaction } = useGasFeeContext();
|
||||
const {
|
||||
isDirty,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
} = useAdvanceGasFeePopoverContext();
|
||||
|
||||
const onSave = () => {
|
||||
updateTransaction(
|
||||
PRIORITY_LEVELS.CUSTOM,
|
||||
decGWEIToHexWEI(maxFeePerGas),
|
||||
decGWEIToHexWEI(maxPriorityFeePerGas),
|
||||
);
|
||||
closeModal('advancedGasFee');
|
||||
};
|
||||
|
||||
return (
|
||||
<Button type="primary" disabled={!isDirty} onClick={onSave}>
|
||||
<I18nValue messageKey="save" />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvancedGasFeeSaveButton;
|
@ -0,0 +1 @@
|
||||
export { default } from './advanced-gas-fee-save';
|
@ -0,0 +1,33 @@
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const AdvanceGasFeePopoverContext = createContext({});
|
||||
|
||||
export const AdvanceGasFeePopoverContextProvider = ({ children }) => {
|
||||
const [maxFeePerGas, setMaxFeePerGas] = useState();
|
||||
const [maxPriorityFeePerGas, setMaxPriorityFeePerGas] = useState();
|
||||
const [isDirty, setDirty] = useState();
|
||||
|
||||
return (
|
||||
<AdvanceGasFeePopoverContext.Provider
|
||||
value={{
|
||||
isDirty,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
setDirty,
|
||||
setMaxPriorityFeePerGas,
|
||||
setMaxFeePerGas,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AdvanceGasFeePopoverContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useAdvanceGasFeePopoverContext() {
|
||||
return useContext(AdvanceGasFeePopoverContext);
|
||||
}
|
||||
|
||||
AdvanceGasFeePopoverContextProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './advanceGasFeePopover';
|
@ -53,4 +53,5 @@
|
||||
@import 'loading-network-screen/index';
|
||||
@import 'advanced-gas-fee-popover/index';
|
||||
@import 'advanced-gas-fee-popover/advanced-gas-fee-inputs/index';
|
||||
@import 'advanced-gas-fee-popover/advanced-gas-fee-inputs/base-fee-input/index';
|
||||
@import 'advanced-gas-fee-popover/advanced-gas-fee-input-subtext/index';
|
||||
|
@ -11,7 +11,7 @@
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
|
||||
&--selected {
|
||||
&-selected {
|
||||
background-color: $ui-1;
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,7 @@ const render = ({ componentProps, contextProps } = {}) => {
|
||||
console.log('on edit');
|
||||
}}
|
||||
rows={[]}
|
||||
userAcknowledgedGasMissing
|
||||
{...componentProps}
|
||||
/>
|
||||
</GasFeeContextProvider>,
|
||||
|
@ -30,7 +30,6 @@ export default function FormField({
|
||||
password,
|
||||
allowDecimals,
|
||||
disabled,
|
||||
inputDetails,
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
@ -108,7 +107,6 @@ export default function FormField({
|
||||
{error}
|
||||
</Typography>
|
||||
)}
|
||||
{inputDetails}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
@ -129,7 +127,6 @@ FormField.propTypes = {
|
||||
password: PropTypes.bool,
|
||||
allowDecimals: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
inputDetails: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
};
|
||||
|
||||
FormField.defaultProps = {
|
||||
@ -146,5 +143,4 @@ FormField.defaultProps = {
|
||||
password: false,
|
||||
allowDecimals: true,
|
||||
disabled: false,
|
||||
inputDetails: '',
|
||||
};
|
||||
|
@ -4,7 +4,8 @@
|
||||
.typography {
|
||||
@include design-system.Paragraph;
|
||||
|
||||
& b {
|
||||
& b,
|
||||
& strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,9 @@ import { useMaxPriorityFeePerGasInput } from './useMaxPriorityFeePerGasInput';
|
||||
import { useGasEstimates } from './useGasEstimates';
|
||||
import { useTransactionFunctions } from './useTransactionFunctions';
|
||||
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
const EIP_1559_V2 = process.env.EIP_1559_V2;
|
||||
|
||||
/**
|
||||
* @typedef {Object} GasFeeInputReturnType
|
||||
* @property {DecGweiString} [maxFeePerGas] - the maxFeePerGas input value.
|
||||
@ -118,10 +121,16 @@ export function useGasFeeInputs(
|
||||
* so that transaction is source of truth whenever possible.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (transaction?.userFeeLevel) {
|
||||
if (EIP_1559_V2 && transaction?.userFeeLevel) {
|
||||
setEstimateUsed(transaction?.userFeeLevel);
|
||||
setInternalEstimateToUse(transaction?.userFeeLevel);
|
||||
}
|
||||
}, [setEstimateUsed, transaction]);
|
||||
}, [
|
||||
setEstimateUsed,
|
||||
setInternalEstimateToUse,
|
||||
transaction,
|
||||
userPrefersAdvancedGas,
|
||||
]);
|
||||
|
||||
const [gasLimit, setGasLimit] = useState(() =>
|
||||
Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0')),
|
||||
@ -143,6 +152,7 @@ export function useGasFeeInputs(
|
||||
maxFeePerGasFiat,
|
||||
setMaxFeePerGas,
|
||||
} = useMaxFeePerGasInput({
|
||||
EIP_1559_V2,
|
||||
estimateToUse,
|
||||
gasEstimateType,
|
||||
gasFeeEstimates,
|
||||
@ -156,6 +166,7 @@ export function useGasFeeInputs(
|
||||
maxPriorityFeePerGasFiat,
|
||||
setMaxPriorityFeePerGas,
|
||||
} = useMaxPriorityFeePerGasInput({
|
||||
EIP_1559_V2,
|
||||
estimateToUse,
|
||||
gasEstimateType,
|
||||
gasFeeEstimates,
|
||||
@ -214,8 +225,12 @@ export function useGasFeeInputs(
|
||||
}
|
||||
}, [minimumGasLimit, gasErrors.gasLimit, transaction]);
|
||||
|
||||
const { updateTransactionUsingGasFeeEstimates } = useTransactionFunctions({
|
||||
const {
|
||||
updateTransaction,
|
||||
updateTransactionUsingGasFeeEstimates,
|
||||
} = useTransactionFunctions({
|
||||
defaultEstimateToUse,
|
||||
gasFeeEstimates,
|
||||
gasLimit,
|
||||
transaction,
|
||||
});
|
||||
@ -302,6 +317,22 @@ export function useGasFeeInputs(
|
||||
hasSimulationError,
|
||||
supportsEIP1559,
|
||||
supportsEIP1559V2: supportsEIP1559 && EIP_1559_V2_ENABLED,
|
||||
updateTransaction,
|
||||
updateTransactionUsingGasFeeEstimates,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* In EIP_1559_V2 implementation as used by useGasfeeInputContext() the use of this hook is evolved.
|
||||
* It is no longer used to keep transient state of advance gas fee inputs.
|
||||
* Transient state of inputs is maintained locally in /ui/components/app/advance-gas-fee-popover component.
|
||||
*
|
||||
* This hook is used now as source of shared data about transaction, it shares details of gas fee in transaction,
|
||||
* estimate used, is EIP-1559 supported and other details. It also have methods to update transaction.
|
||||
*
|
||||
* Transaction is used as single source of truth and as transaction is updated the fields shared by hook are
|
||||
* also updated using useEffect hook.
|
||||
*
|
||||
* It will be useful to plan a task to create a new hook of this shared information from this hook.
|
||||
* Methods like setEstimateToUse, onManualChange are deprecated in context of EIP_1559_V2 implementation.
|
||||
*/
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
|
||||
@ -34,6 +34,7 @@ const getMaxFeePerGasFromTransaction = (transaction) => {
|
||||
* method to update the setMaxFeePerGas.
|
||||
*/
|
||||
export function useMaxFeePerGasInput({
|
||||
EIP_1559_V2,
|
||||
estimateToUse,
|
||||
gasEstimateType,
|
||||
gasFeeEstimates,
|
||||
@ -65,6 +66,12 @@ export function useMaxFeePerGasInput({
|
||||
return null;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (EIP_1559_V2) {
|
||||
setMaxFeePerGas(maxFeePerGasFromTransaction);
|
||||
}
|
||||
}, [EIP_1559_V2, maxFeePerGasFromTransaction, setMaxFeePerGas]);
|
||||
|
||||
let gasSettings = {
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { addHexPrefix } from 'ethereumjs-util';
|
||||
|
||||
@ -34,6 +34,7 @@ const getMaxPriorityFeePerGasFromTransaction = (transaction) => {
|
||||
* method to update the maxPriorityFeePerGas.
|
||||
*/
|
||||
export function useMaxPriorityFeePerGasInput({
|
||||
EIP_1559_V2,
|
||||
estimateToUse,
|
||||
gasEstimateType,
|
||||
gasFeeEstimates,
|
||||
@ -61,6 +62,16 @@ export function useMaxPriorityFeePerGasInput({
|
||||
return null;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (EIP_1559_V2) {
|
||||
setMaxPriorityFeePerGas(maxPriorityFeePerGasFromTransaction);
|
||||
}
|
||||
}, [
|
||||
EIP_1559_V2,
|
||||
maxPriorityFeePerGasFromTransaction,
|
||||
setMaxPriorityFeePerGas,
|
||||
]);
|
||||
|
||||
const maxPriorityFeePerGasToUse =
|
||||
maxPriorityFeePerGas ??
|
||||
getGasFeeEstimate(
|
||||
|
@ -2,11 +2,15 @@ import { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { PRIORITY_LEVELS } from '../../../shared/constants/gas';
|
||||
import { decimalToHex } from '../../helpers/utils/conversions.util';
|
||||
import {
|
||||
decimalToHex,
|
||||
decGWEIToHexWEI,
|
||||
} from '../../helpers/utils/conversions.util';
|
||||
import { updateTransaction as updateTransactionFn } from '../../store/actions';
|
||||
|
||||
export const useTransactionFunctions = ({
|
||||
defaultEstimateToUse,
|
||||
gasFeeEstimates,
|
||||
gasLimit,
|
||||
transaction,
|
||||
}) => {
|
||||
@ -54,11 +58,19 @@ export const useTransactionFunctions = ({
|
||||
maxPriorityFeePerGas,
|
||||
);
|
||||
} else {
|
||||
updateTransaction(gasFeeEstimateToUse);
|
||||
const {
|
||||
suggestedMaxFeePerGas,
|
||||
suggestedMaxPriorityFeePerGas,
|
||||
} = gasFeeEstimates[gasFeeEstimateToUse];
|
||||
updateTransaction(
|
||||
gasFeeEstimateToUse,
|
||||
decGWEIToHexWEI(suggestedMaxFeePerGas),
|
||||
decGWEIToHexWEI(suggestedMaxPriorityFeePerGas),
|
||||
);
|
||||
}
|
||||
},
|
||||
[transaction?.dappSuggestedGasFees, updateTransaction],
|
||||
[gasFeeEstimates, transaction?.dappSuggestedGasFees, updateTransaction],
|
||||
);
|
||||
|
||||
return { updateTransactionUsingGasFeeEstimates };
|
||||
return { updateTransaction, updateTransactionUsingGasFeeEstimates };
|
||||
};
|
||||
|
@ -516,9 +516,9 @@ export default class ConfirmTransactionBase extends Component {
|
||||
subText={
|
||||
!isMultiLayerFeeNetwork && (
|
||||
<>
|
||||
<b key="editGasSubTextFeeLabel">
|
||||
<strong key="editGasSubTextFeeLabel">
|
||||
{t('editGasSubTextFeeLabel')}
|
||||
</b>
|
||||
</strong>
|
||||
<div
|
||||
key="editGasSubTextFeeValue"
|
||||
className="confirm-page-container-content__currency-container"
|
||||
@ -611,9 +611,9 @@ export default class ConfirmTransactionBase extends Component {
|
||||
subTitle={t('transactionDetailGasTotalSubtitle')}
|
||||
subText={
|
||||
<>
|
||||
<b key="editGasSubTextAmountLabel">
|
||||
<strong key="editGasSubTextAmountLabel">
|
||||
{t('editGasSubTextAmountLabel')}
|
||||
</b>
|
||||
</strong>
|
||||
{renderTotalMaxAmount()}
|
||||
</>
|
||||
}
|
||||
|
@ -102,10 +102,10 @@ const GasDetailsItem = ({
|
||||
})}
|
||||
>
|
||||
<Box marginRight={1}>
|
||||
<b>
|
||||
<strong>
|
||||
{estimateUsed === 'high' && '⚠ '}
|
||||
<I18nValue messageKey="editGasSubTextFeeLabel" />
|
||||
</b>
|
||||
</strong>
|
||||
</Box>
|
||||
<div
|
||||
key="editGasSubTextFeeValue"
|
||||
|
@ -11,10 +11,6 @@
|
||||
color: $secondary-1;
|
||||
}
|
||||
|
||||
&__gasfee-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__currency-container {
|
||||
position: relative;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user