Adding edit gas fee modal (#12624)
Edit transaction screen changes for EIP-1559 V2feature/default_network_editable
parent
cce2dda8e4
commit
0daefe9ea0
@ -0,0 +1,49 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext'; |
||||
import Popover from '../../ui/popover'; |
||||
import I18nValue from '../../ui/i18n-value'; |
||||
import LoadingHeartBeat from '../../ui/loading-heartbeat'; |
||||
|
||||
import EditGasItem from './edit-gas-item'; |
||||
|
||||
const EditGasFeePopover = ({ onClose }) => { |
||||
const t = useI18nContext(); |
||||
|
||||
return ( |
||||
<Popover |
||||
title={t('editGasFeeModalTitle')} |
||||
onClose={onClose} |
||||
className="edit-gas-fee-popover" |
||||
> |
||||
<> |
||||
{process.env.IN_TEST === 'true' ? null : <LoadingHeartBeat />} |
||||
<div className="edit-gas-fee-popover__wrapper"> |
||||
<div className="edit-gas-fee-popover__content"> |
||||
<div className="edit-gas-fee-popover__content__header"> |
||||
<span className="edit-gas-fee-popover__content__header-option"> |
||||
<I18nValue messageKey="gasOption" /> |
||||
</span> |
||||
<span className="edit-gas-fee-popover__content__header-time"> |
||||
<I18nValue messageKey="time" /> |
||||
</span> |
||||
<span className="edit-gas-fee-popover__content__header-max-fee"> |
||||
<I18nValue messageKey="maxFee" /> |
||||
</span> |
||||
</div> |
||||
<EditGasItem estimateType="low" onClose={onClose} /> |
||||
<EditGasItem estimateType="medium" onClose={onClose} /> |
||||
<EditGasItem estimateType="high" onClose={onClose} /> |
||||
</div> |
||||
</div> |
||||
</> |
||||
</Popover> |
||||
); |
||||
}; |
||||
|
||||
EditGasFeePopover.propTypes = { |
||||
onClose: PropTypes.func, |
||||
}; |
||||
|
||||
export default EditGasFeePopover; |
@ -0,0 +1,93 @@ |
||||
import React from 'react'; |
||||
import { screen } from '@testing-library/react'; |
||||
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers'; |
||||
import { ETH } from '../../../helpers/constants/common'; |
||||
import configureStore from '../../../store/store'; |
||||
import { GasFeeContextProvider } from '../../../contexts/gasFee'; |
||||
|
||||
import EditGasFeePopover from './edit-gas-fee-popover'; |
||||
|
||||
jest.mock('../../../store/actions', () => ({ |
||||
disconnectGasFeeEstimatePoller: jest.fn(), |
||||
getGasFeeEstimatesAndStartPolling: jest |
||||
.fn() |
||||
.mockImplementation(() => Promise.resolve()), |
||||
addPollingTokenToAppState: jest.fn(), |
||||
})); |
||||
|
||||
const MOCK_FEE_ESTIMATE = { |
||||
low: { |
||||
minWaitTimeEstimate: 360000, |
||||
maxWaitTimeEstimate: 300000, |
||||
suggestedMaxPriorityFeePerGas: '3', |
||||
suggestedMaxFeePerGas: '53', |
||||
}, |
||||
medium: { |
||||
minWaitTimeEstimate: 30000, |
||||
maxWaitTimeEstimate: 60000, |
||||
suggestedMaxPriorityFeePerGas: '7', |
||||
suggestedMaxFeePerGas: '70', |
||||
}, |
||||
high: { |
||||
minWaitTimeEstimate: 15000, |
||||
maxWaitTimeEstimate: 15000, |
||||
suggestedMaxPriorityFeePerGas: '10', |
||||
suggestedMaxFeePerGas: '100', |
||||
}, |
||||
estimatedBaseFee: '50', |
||||
}; |
||||
|
||||
const renderComponent = () => { |
||||
const store = configureStore({ |
||||
metamask: { |
||||
nativeCurrency: ETH, |
||||
provider: {}, |
||||
cachedBalances: {}, |
||||
accounts: { |
||||
'0xAddress': { |
||||
address: '0xAddress', |
||||
balance: '0x176e5b6f173ebe66', |
||||
}, |
||||
}, |
||||
selectedAddress: '0xAddress', |
||||
featureFlags: { advancedInlineGas: true }, |
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE, |
||||
}, |
||||
}); |
||||
|
||||
return renderWithProvider( |
||||
<GasFeeContextProvider transaction={{ txParams: { gas: '0x5208' } }}> |
||||
<EditGasFeePopover /> |
||||
</GasFeeContextProvider>, |
||||
store, |
||||
); |
||||
}; |
||||
|
||||
describe('EditGasFeePopover', () => { |
||||
it('should renders low / medium / high options', () => { |
||||
renderComponent(); |
||||
|
||||
expect(screen.queryByText('🐢')).toBeInTheDocument(); |
||||
expect(screen.queryByText('🦊')).toBeInTheDocument(); |
||||
expect(screen.queryByText('🦍')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Low')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Market')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Aggressive')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should show time estimates', () => { |
||||
renderComponent(); |
||||
console.log(document.body.innerHTML); |
||||
expect(screen.queryByText('6 min')).toBeInTheDocument(); |
||||
expect(screen.queryByText('30 sec')).toBeInTheDocument(); |
||||
expect(screen.queryByText('15 sec')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should show gas fee estimates', () => { |
||||
renderComponent(); |
||||
expect(screen.queryByTitle('0.001113 ETH')).toBeInTheDocument(); |
||||
expect(screen.queryByTitle('0.00147 ETH')).toBeInTheDocument(); |
||||
expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument(); |
||||
}); |
||||
}); |
@ -0,0 +1,84 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import classNames from 'classnames'; |
||||
|
||||
import { getMaximumGasTotalInHexWei } from '../../../../../shared/modules/gas.utils'; |
||||
import { PRIORITY_LEVEL_ICON_MAP } from '../../../../helpers/constants/gas'; |
||||
import { PRIMARY } from '../../../../helpers/constants/common'; |
||||
import { |
||||
decGWEIToHexWEI, |
||||
decimalToHex, |
||||
} from '../../../../helpers/utils/conversions.util'; |
||||
import { toHumanReadableTime } from '../../../../helpers/utils/util'; |
||||
import { useGasFeeContext } from '../../../../contexts/gasFee'; |
||||
import { useI18nContext } from '../../../../hooks/useI18nContext'; |
||||
import I18nValue from '../../../ui/i18n-value'; |
||||
import InfoTooltip from '../../../ui/info-tooltip'; |
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'; |
||||
|
||||
const EditGasItem = ({ estimateType, onClose }) => { |
||||
const { |
||||
estimateUsed, |
||||
gasFeeEstimates, |
||||
gasLimit, |
||||
setEstimateToUse, |
||||
updateTransaction, |
||||
} = useGasFeeContext(); |
||||
const t = useI18nContext(); |
||||
|
||||
const { minWaitTimeEstimate, suggestedMaxFeePerGas } = |
||||
gasFeeEstimates[estimateType] || {}; |
||||
const hexMaximumTransactionFee = suggestedMaxFeePerGas |
||||
? getMaximumGasTotalInHexWei({ |
||||
gasLimit: decimalToHex(gasLimit), |
||||
maxFeePerGas: decGWEIToHexWEI(suggestedMaxFeePerGas), |
||||
}) |
||||
: null; |
||||
|
||||
const onOptionSelect = () => { |
||||
setEstimateToUse(estimateType); |
||||
updateTransaction(estimateType); |
||||
onClose(); |
||||
}; |
||||
|
||||
return ( |
||||
<div |
||||
className={classNames('edit-gas-item', { |
||||
'edit-gas-item--selected': estimateType === estimateUsed, |
||||
})} |
||||
role="button" |
||||
onClick={onOptionSelect} |
||||
> |
||||
<span className="edit-gas-item__name"> |
||||
<span className="edit-gas-item__icon"> |
||||
{PRIORITY_LEVEL_ICON_MAP[estimateType]} |
||||
</span> |
||||
<I18nValue messageKey={estimateType} /> |
||||
</span> |
||||
<span |
||||
className={`edit-gas-item__time-estimate edit-gas-item__time-estimate-${estimateType}`} |
||||
> |
||||
{minWaitTimeEstimate && toHumanReadableTime(t, minWaitTimeEstimate)} |
||||
</span> |
||||
<span |
||||
className={`edit-gas-item__fee-estimate edit-gas-item__fee-estimate-${estimateType}`} |
||||
> |
||||
<UserPreferencedCurrencyDisplay |
||||
key="editGasSubTextFeeAmount" |
||||
type={PRIMARY} |
||||
value={hexMaximumTransactionFee} |
||||
/> |
||||
</span> |
||||
<span className="edit-gas-item__tooltip"> |
||||
<InfoTooltip position="top" /> |
||||
</span> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
EditGasItem.propTypes = { |
||||
estimateType: PropTypes.string, |
||||
onClose: PropTypes.func, |
||||
}; |
||||
|
||||
export default EditGasItem; |
@ -0,0 +1,93 @@ |
||||
import React from 'react'; |
||||
import { screen } from '@testing-library/react'; |
||||
|
||||
import { renderWithProvider } from '../../../../../test/lib/render-helpers'; |
||||
import { ETH } from '../../../../helpers/constants/common'; |
||||
import configureStore from '../../../../store/store'; |
||||
import { GasFeeContextProvider } from '../../../../contexts/gasFee'; |
||||
|
||||
import EditGasItem from './edit-gas-item'; |
||||
|
||||
jest.mock('../../../../store/actions', () => ({ |
||||
disconnectGasFeeEstimatePoller: jest.fn(), |
||||
getGasFeeEstimatesAndStartPolling: jest |
||||
.fn() |
||||
.mockImplementation(() => Promise.resolve()), |
||||
addPollingTokenToAppState: jest.fn(), |
||||
})); |
||||
|
||||
const MOCK_FEE_ESTIMATE = { |
||||
low: { |
||||
minWaitTimeEstimate: 360000, |
||||
maxWaitTimeEstimate: 300000, |
||||
suggestedMaxPriorityFeePerGas: '3', |
||||
suggestedMaxFeePerGas: '53', |
||||
}, |
||||
medium: { |
||||
minWaitTimeEstimate: 30000, |
||||
maxWaitTimeEstimate: 60000, |
||||
suggestedMaxPriorityFeePerGas: '7', |
||||
suggestedMaxFeePerGas: '70', |
||||
}, |
||||
high: { |
||||
minWaitTimeEstimate: 15000, |
||||
maxWaitTimeEstimate: 15000, |
||||
suggestedMaxPriorityFeePerGas: '10', |
||||
suggestedMaxFeePerGas: '100', |
||||
}, |
||||
estimatedBaseFee: '50', |
||||
}; |
||||
|
||||
const renderComponent = (props) => { |
||||
const store = configureStore({ |
||||
metamask: { |
||||
nativeCurrency: ETH, |
||||
provider: {}, |
||||
cachedBalances: {}, |
||||
accounts: { |
||||
'0xAddress': { |
||||
address: '0xAddress', |
||||
balance: '0x176e5b6f173ebe66', |
||||
}, |
||||
}, |
||||
selectedAddress: '0xAddress', |
||||
featureFlags: { advancedInlineGas: true }, |
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE, |
||||
}, |
||||
}); |
||||
|
||||
return renderWithProvider( |
||||
<GasFeeContextProvider transaction={{ txParams: { gas: '0x5208' } }}> |
||||
<EditGasItem estimateType="low" {...props} /> |
||||
</GasFeeContextProvider>, |
||||
store, |
||||
); |
||||
}; |
||||
|
||||
describe('EditGasItem', () => { |
||||
it('should renders low gas estimate options for estimateType low', () => { |
||||
renderComponent({ estimateType: 'low' }); |
||||
|
||||
expect(screen.queryByText('🐢')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Low')).toBeInTheDocument(); |
||||
expect(screen.queryByText('6 min')).toBeInTheDocument(); |
||||
expect(screen.queryByTitle('0.001113 ETH')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should renders market gas estimate options for estimateType medium', () => { |
||||
renderComponent({ estimateType: 'medium' }); |
||||
|
||||
expect(screen.queryByText('🦊')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Market')).toBeInTheDocument(); |
||||
expect(screen.queryByText('30 sec')).toBeInTheDocument(); |
||||
expect(screen.queryByTitle('0.00147 ETH')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should renders aggressive gas estimate options for estimateType high', () => { |
||||
renderComponent({ estimateType: 'high' }); |
||||
|
||||
expect(screen.queryByText('🦍')).toBeInTheDocument(); |
||||
expect(screen.queryByText('15 sec')).toBeInTheDocument(); |
||||
expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument(); |
||||
}); |
||||
}); |
@ -0,0 +1 @@ |
||||
export { default } from './edit-gas-item'; |
@ -0,0 +1,56 @@ |
||||
.edit-gas-item { |
||||
border-radius: 24px; |
||||
color: $ui-4; |
||||
cursor: pointer; |
||||
font-size: 12px; |
||||
margin: 12px 0; |
||||
padding: 4px 12px; |
||||
height: 32px; |
||||
|
||||
&--selected { |
||||
background-color: $ui-1; |
||||
} |
||||
|
||||
&__name { |
||||
display: inline-block; |
||||
color: $ui-black; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
width: 40%; |
||||
} |
||||
|
||||
&__icon { |
||||
margin-right: 4px; |
||||
} |
||||
|
||||
&__time-estimate { |
||||
display: inline-block; |
||||
width: 20%; |
||||
} |
||||
|
||||
&__fee-estimate { |
||||
display: inline-block; |
||||
width: 30%; |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
&__tooltip { |
||||
display: inline-block; |
||||
text-align: right; |
||||
width: 10%; |
||||
|
||||
.info-tooltip { |
||||
display: inline-block; |
||||
} |
||||
} |
||||
|
||||
&__time-estimate-low, |
||||
&__fee-estimate-high { |
||||
color: $secondary-1; |
||||
} |
||||
|
||||
&__time-estimate-medium, |
||||
&__time-estimate-high { |
||||
color: $success-3; |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
export { default } from './edit-gas-fee-popover'; |
@ -0,0 +1,35 @@ |
||||
.edit-gas-fee-popover { |
||||
@media screen and (min-width: $break-large) { |
||||
max-height: 84vh; |
||||
} |
||||
|
||||
&__wrapper { |
||||
border-top: 1px solid $ui-grey; |
||||
} |
||||
|
||||
&__content { |
||||
padding: 16px 12px; |
||||
|
||||
&__header { |
||||
color: $ui-4; |
||||
font-size: 10px; |
||||
font-weight: 700; |
||||
margin: 0 12px; |
||||
|
||||
&-option { |
||||
display: inline-block; |
||||
width: 40%; |
||||
} |
||||
|
||||
&-time { |
||||
display: inline-block; |
||||
width: 20%; |
||||
} |
||||
|
||||
&-max-fee { |
||||
display: inline-block; |
||||
width: 30%; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,49 @@ |
||||
import { useCallback } from 'react'; |
||||
import { useDispatch } from 'react-redux'; |
||||
|
||||
import { |
||||
decGWEIToHexWEI, |
||||
decimalToHex, |
||||
} from '../../helpers/utils/conversions.util'; |
||||
import { updateTransaction as updateTransactionFn } from '../../store/actions'; |
||||
|
||||
export const useTransactionFunctions = ({ |
||||
defaultEstimateToUse, |
||||
gasLimit, |
||||
gasFeeEstimates, |
||||
transaction, |
||||
}) => { |
||||
const dispatch = useDispatch(); |
||||
|
||||
const updateTransaction = useCallback( |
||||
(estimateType) => { |
||||
const newGasSettings = { |
||||
gas: decimalToHex(gasLimit), |
||||
gasLimit: decimalToHex(gasLimit), |
||||
estimateSuggested: defaultEstimateToUse, |
||||
estimateUsed: estimateType, |
||||
}; |
||||
|
||||
newGasSettings.maxFeePerGas = decGWEIToHexWEI( |
||||
gasFeeEstimates[estimateType].suggestedMaxFeePerGas, |
||||
); |
||||
newGasSettings.maxPriorityFeePerGas = decGWEIToHexWEI( |
||||
gasFeeEstimates[estimateType].suggestedMaxPriorityFeePerGas, |
||||
); |
||||
|
||||
const updatedTxMeta = { |
||||
...transaction, |
||||
userFeeLevel: estimateType || 'custom', |
||||
txParams: { |
||||
...transaction.txParams, |
||||
...newGasSettings, |
||||
}, |
||||
}; |
||||
|
||||
dispatch(updateTransactionFn(updatedTxMeta)); |
||||
}, |
||||
[defaultEstimateToUse, dispatch, gasLimit, gasFeeEstimates, transaction], |
||||
); |
||||
|
||||
return { updateTransaction }; |
||||
}; |
Loading…
Reference in new issue