mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Confirm transaction page - onchain failure handling (#12743)
This commit is contained in:
parent
fb27e170ac
commit
7609841902
@ -1382,7 +1382,8 @@
|
|||||||
"message": "Want to $1 about gas?"
|
"message": "Want to $1 about gas?"
|
||||||
},
|
},
|
||||||
"learnCancelSpeeedup": {
|
"learnCancelSpeeedup": {
|
||||||
"message": "Learn how to $1"
|
"message": "Learn how to $1",
|
||||||
|
"description": "$1 is link to cancel or speed up transactions"
|
||||||
},
|
},
|
||||||
"learnMore": {
|
"learnMore": {
|
||||||
"message": "learn more"
|
"message": "learn more"
|
||||||
@ -1969,12 +1970,16 @@
|
|||||||
"pending": {
|
"pending": {
|
||||||
"message": "Pending"
|
"message": "Pending"
|
||||||
},
|
},
|
||||||
"pendingTransaction": {
|
|
||||||
"message": "You have ($1) pending transaction(s)."
|
|
||||||
},
|
|
||||||
"pendingTransactionInfo": {
|
"pendingTransactionInfo": {
|
||||||
"message": "This transaction will not process until that one is complete."
|
"message": "This transaction will not process until that one is complete."
|
||||||
},
|
},
|
||||||
|
"pendingTransactionMultiple": {
|
||||||
|
"message": "You have ($1) pending transactions."
|
||||||
|
},
|
||||||
|
"pendingTransactionSingle": {
|
||||||
|
"message": "You have (1) pending transaction.",
|
||||||
|
"description": "$1 is count of pending transactions"
|
||||||
|
},
|
||||||
"permissionCheckedIconDescription": {
|
"permissionCheckedIconDescription": {
|
||||||
"message": "You have approved this permission"
|
"message": "You have approved this permission"
|
||||||
},
|
},
|
||||||
@ -2020,6 +2025,9 @@
|
|||||||
"privateNetwork": {
|
"privateNetwork": {
|
||||||
"message": "Private Network"
|
"message": "Private Network"
|
||||||
},
|
},
|
||||||
|
"proceedWithTransaction": {
|
||||||
|
"message": "I want to proceed anyway"
|
||||||
|
},
|
||||||
"proposedApprovalLimit": {
|
"proposedApprovalLimit": {
|
||||||
"message": "Proposed Approval Limit"
|
"message": "Proposed Approval Limit"
|
||||||
},
|
},
|
||||||
@ -2407,6 +2415,9 @@
|
|||||||
"simulationErrorMessage": {
|
"simulationErrorMessage": {
|
||||||
"message": "This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended."
|
"message": "This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended."
|
||||||
},
|
},
|
||||||
|
"simulationErrorMessageV2": {
|
||||||
|
"message": "We were not able to estimate gas. There might be an error in the contract and this transaction may fail."
|
||||||
|
},
|
||||||
"skip": {
|
"skip": {
|
||||||
"message": "Skip"
|
"message": "Skip"
|
||||||
},
|
},
|
||||||
|
@ -36,10 +36,10 @@ export default class ConfirmPageContainerContent extends Component {
|
|||||||
onCancel: PropTypes.func,
|
onCancel: PropTypes.func,
|
||||||
cancelText: PropTypes.string,
|
cancelText: PropTypes.string,
|
||||||
onSubmit: PropTypes.func,
|
onSubmit: PropTypes.func,
|
||||||
onConfirmAnyways: PropTypes.func,
|
setUserAcknowledgedGasMissing: PropTypes.func,
|
||||||
submitText: PropTypes.string,
|
submitText: PropTypes.string,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
hideConfirmAnyways: PropTypes.bool,
|
hideUserAcknowledgedGasMissing: PropTypes.bool,
|
||||||
unapprovedTxCount: PropTypes.number,
|
unapprovedTxCount: PropTypes.number,
|
||||||
rejectNText: PropTypes.string,
|
rejectNText: PropTypes.string,
|
||||||
hideTitle: PropTypes.boolean,
|
hideTitle: PropTypes.boolean,
|
||||||
@ -99,15 +99,15 @@ export default class ConfirmPageContainerContent extends Component {
|
|||||||
origin,
|
origin,
|
||||||
ethGasPriceWarning,
|
ethGasPriceWarning,
|
||||||
hideTitle,
|
hideTitle,
|
||||||
onConfirmAnyways,
|
setUserAcknowledgedGasMissing,
|
||||||
hideConfirmAnyways,
|
hideUserAcknowledgedGasMissing,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const primaryAction = hideConfirmAnyways
|
const primaryAction = hideUserAcknowledgedGasMissing
|
||||||
? null
|
? null
|
||||||
: {
|
: {
|
||||||
label: this.context.t('tryAnywayOption'),
|
label: this.context.t('tryAnywayOption'),
|
||||||
onClick: onConfirmAnyways,
|
onClick: setUserAcknowledgedGasMissing,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -22,7 +22,7 @@ describe('Confirm Page Container Content', () => {
|
|||||||
const mockOnCancel = jest.fn();
|
const mockOnCancel = jest.fn();
|
||||||
const mockOnCancelAll = jest.fn();
|
const mockOnCancelAll = jest.fn();
|
||||||
const mockOnSubmit = jest.fn();
|
const mockOnSubmit = jest.fn();
|
||||||
const mockOnConfirmAnyways = jest.fn();
|
const mockSetUserAcknowledgedGasMissing = jest.fn();
|
||||||
props = {
|
props = {
|
||||||
action: ' Withdraw Stake',
|
action: ' Withdraw Stake',
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
@ -32,7 +32,7 @@ describe('Confirm Page Container Content', () => {
|
|||||||
onCancel: mockOnCancel,
|
onCancel: mockOnCancel,
|
||||||
cancelText: 'Reject',
|
cancelText: 'Reject',
|
||||||
onSubmit: mockOnSubmit,
|
onSubmit: mockOnSubmit,
|
||||||
onConfirmAnyways: mockOnConfirmAnyways,
|
setUserAcknowledgedGasMissing: mockSetUserAcknowledgedGasMissing,
|
||||||
submitText: 'Confirm',
|
submitText: 'Confirm',
|
||||||
disabled: true,
|
disabled: true,
|
||||||
origin: 'http://localhost:4200',
|
origin: 'http://localhost:4200',
|
||||||
@ -63,7 +63,7 @@ describe('Confirm Page Container Content', () => {
|
|||||||
|
|
||||||
const iWillTryButton = getByText('I will try anyway');
|
const iWillTryButton = getByText('I will try anyway');
|
||||||
fireEvent.click(iWillTryButton);
|
fireEvent.click(iWillTryButton);
|
||||||
expect(props.onConfirmAnyways).toHaveBeenCalledTimes(1);
|
expect(props.setUserAcknowledgedGasMissing).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
const cancelButton = getByText('Reject');
|
const cancelButton = getByText('Reject');
|
||||||
fireEvent.click(cancelButton);
|
fireEvent.click(cancelButton);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.edit-gas-fee-popover {
|
.edit-gas-fee-popover {
|
||||||
height: 540px;
|
height: 500px;
|
||||||
|
|
||||||
&__wrapper {
|
&__wrapper {
|
||||||
border-top: 1px solid $ui-grey;
|
border-top: 1px solid $ui-grey;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { toBigNumber } from '../../../../../shared/modules/conversion.utils';
|
|
||||||
import { COLORS } from '../../../../helpers/constants/design-system';
|
import { COLORS } from '../../../../helpers/constants/design-system';
|
||||||
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
||||||
import I18nValue from '../../../ui/i18n-value';
|
import I18nValue from '../../../ui/i18n-value';
|
||||||
@ -10,13 +9,6 @@ import StatusSlider from './status-slider';
|
|||||||
|
|
||||||
const NetworkStatus = () => {
|
const NetworkStatus = () => {
|
||||||
const { gasFeeEstimates } = useGasFeeContext();
|
const { gasFeeEstimates } = useGasFeeContext();
|
||||||
let estBaseFee = null;
|
|
||||||
if (gasFeeEstimates?.estimatedBaseFee) {
|
|
||||||
// estimatedBaseFee is not likely to be below 1, value .01 is used as test networks sometimes
|
|
||||||
// show have small values for it and more decimal places may cause UI to look broken.
|
|
||||||
estBaseFee = toBigNumber.dec(gasFeeEstimates?.estimatedBaseFee);
|
|
||||||
estBaseFee = estBaseFee.lessThan(0.01) ? 0.01 : estBaseFee.toFixed(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="network-status">
|
<div className="network-status">
|
||||||
@ -32,7 +24,8 @@ const NetworkStatus = () => {
|
|||||||
<div className="network-status__info">
|
<div className="network-status__info">
|
||||||
<div className="network-status__info__field">
|
<div className="network-status__info__field">
|
||||||
<span className="network-status__info__field-data">
|
<span className="network-status__info__field-data">
|
||||||
{estBaseFee !== null && `${estBaseFee} GWEI`}
|
{gasFeeEstimates?.estimatedBaseFee &&
|
||||||
|
`${gasFeeEstimates?.estimatedBaseFee} GWEI`}
|
||||||
</span>
|
</span>
|
||||||
<span className="network-status__info__field-label">Base fee</span>
|
<span className="network-status__info__field-label">Base fee</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,21 +57,10 @@ describe('NetworkStatus', () => {
|
|||||||
expect(screen.queryByText('Priority fee')).toBeInTheDocument();
|
expect(screen.queryByText('Priority fee')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should renders current base fee value rounded to 2 decimal places', () => {
|
it('should renders current base fee value', () => {
|
||||||
renderComponent();
|
renderComponent();
|
||||||
expect(
|
expect(
|
||||||
screen.queryByText(
|
screen.queryByText(`${MOCK_FEE_ESTIMATE.estimatedBaseFee} GWEI`),
|
||||||
`${parseFloat(MOCK_FEE_ESTIMATE.estimatedBaseFee).toFixed(2)} GWEI`,
|
|
||||||
),
|
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should .01 as estimates base fee if estimated base fee is < .01', () => {
|
|
||||||
renderComponent({
|
|
||||||
gasFeeEstimates: {
|
|
||||||
estimatedBaseFee: '0.0012',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(screen.queryByText('0.01 GWEI')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -36,7 +36,7 @@ export default function TransactionDetailItem({
|
|||||||
color={COLORS.BLACK}
|
color={COLORS.BLACK}
|
||||||
fontWeight={FONT_WEIGHT.BOLD}
|
fontWeight={FONT_WEIGHT.BOLD}
|
||||||
variant={TYPOGRAPHY.H6}
|
variant={TYPOGRAPHY.H6}
|
||||||
margin={[1, 1]}
|
margin={[1, 0, 1, 1]}
|
||||||
>
|
>
|
||||||
{detailTotal}
|
{detailTotal}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-edit-V2 {
|
&-edit-V2 {
|
||||||
margin-bottom: 10px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@ -66,4 +65,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-rows {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,18 @@ import { COLORS } from '../../../helpers/constants/design-system';
|
|||||||
import { PRIORITY_LEVEL_ICON_MAP } from '../../../helpers/constants/gas';
|
import { PRIORITY_LEVEL_ICON_MAP } from '../../../helpers/constants/gas';
|
||||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
|
|
||||||
export default function TransactionDetail({ rows = [], onEdit }) {
|
export default function TransactionDetail({
|
||||||
|
rows = [],
|
||||||
|
onEdit,
|
||||||
|
userAcknowledgedGasMissing,
|
||||||
|
}) {
|
||||||
// eslint-disable-next-line prefer-destructuring
|
// eslint-disable-next-line prefer-destructuring
|
||||||
const EIP_1559_V2 = process.env.EIP_1559_V2;
|
const EIP_1559_V2 = process.env.EIP_1559_V2;
|
||||||
|
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const {
|
const {
|
||||||
gasLimit,
|
gasLimit,
|
||||||
|
hasSimulationError,
|
||||||
estimateUsed,
|
estimateUsed,
|
||||||
maxFeePerGas,
|
maxFeePerGas,
|
||||||
maxPriorityFeePerGas,
|
maxPriorityFeePerGas,
|
||||||
@ -26,6 +31,9 @@ export default function TransactionDetail({ rows = [], onEdit }) {
|
|||||||
const { openModal } = useTransactionModalContext();
|
const { openModal } = useTransactionModalContext();
|
||||||
|
|
||||||
if (EIP_1559_V2 && estimateUsed) {
|
if (EIP_1559_V2 && estimateUsed) {
|
||||||
|
const editEnabled = !hasSimulationError || userAcknowledgedGasMissing;
|
||||||
|
if (!editEnabled) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="transaction-detail">
|
<div className="transaction-detail">
|
||||||
<div className="transaction-detail-edit-V2">
|
<div className="transaction-detail-edit-V2">
|
||||||
@ -88,4 +96,5 @@ export default function TransactionDetail({ rows = [], onEdit }) {
|
|||||||
TransactionDetail.propTypes = {
|
TransactionDetail.propTypes = {
|
||||||
rows: PropTypes.arrayOf(TransactionDetailItem).isRequired,
|
rows: PropTypes.arrayOf(TransactionDetailItem).isRequired,
|
||||||
onEdit: PropTypes.func,
|
onEdit: PropTypes.func,
|
||||||
|
userAcknowledgedGasMissing: PropTypes.bool.isRequired,
|
||||||
};
|
};
|
||||||
|
@ -16,7 +16,7 @@ jest.mock('../../../store/actions', () => ({
|
|||||||
addPollingTokenToAppState: jest.fn(),
|
addPollingTokenToAppState: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const render = (props) => {
|
const render = ({ componentProps, contextProps } = {}) => {
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
metamask: {
|
metamask: {
|
||||||
nativeCurrency: ETH,
|
nativeCurrency: ETH,
|
||||||
@ -37,13 +37,13 @@ const render = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return renderWithProvider(
|
return renderWithProvider(
|
||||||
<GasFeeContextProvider {...props}>
|
<GasFeeContextProvider {...contextProps}>
|
||||||
<TransactionDetail
|
<TransactionDetail
|
||||||
onEdit={() => {
|
onEdit={() => {
|
||||||
console.log('on edit');
|
console.log('on edit');
|
||||||
}}
|
}}
|
||||||
rows={[]}
|
rows={[]}
|
||||||
{...props}
|
{...componentProps}
|
||||||
/>
|
/>
|
||||||
</GasFeeContextProvider>,
|
</GasFeeContextProvider>,
|
||||||
store,
|
store,
|
||||||
@ -54,41 +54,79 @@ describe('TransactionDetail', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
process.env.EIP_1559_V2 = true;
|
process.env.EIP_1559_V2 = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
process.env.EIP_1559_V2 = false;
|
process.env.EIP_1559_V2 = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render edit link with text low if low gas estimates are selected', () => {
|
it('should render edit link with text low if low gas estimates are selected', () => {
|
||||||
render({ transaction: { userFeeLevel: 'low' } });
|
render({ contextProps: { transaction: { userFeeLevel: 'low' } } });
|
||||||
expect(screen.queryByText('🐢')).toBeInTheDocument();
|
expect(screen.queryByText('🐢')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Low')).toBeInTheDocument();
|
expect(screen.queryByText('Low')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render edit link with text markey if medium gas estimates are selected', () => {
|
it('should render edit link with text markey if medium gas estimates are selected', () => {
|
||||||
render({ transaction: { userFeeLevel: 'medium' } });
|
render({ contextProps: { transaction: { userFeeLevel: 'medium' } } });
|
||||||
expect(screen.queryByText('🦊')).toBeInTheDocument();
|
expect(screen.queryByText('🦊')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Market')).toBeInTheDocument();
|
expect(screen.queryByText('Market')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render edit link with text agressive if high gas estimates are selected', () => {
|
it('should render edit link with text agressive if high gas estimates are selected', () => {
|
||||||
render({ transaction: { userFeeLevel: 'high' } });
|
render({ contextProps: { transaction: { userFeeLevel: 'high' } } });
|
||||||
expect(screen.queryByText('🦍')).toBeInTheDocument();
|
expect(screen.queryByText('🦍')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Aggressive')).toBeInTheDocument();
|
expect(screen.queryByText('Aggressive')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render edit link with text Site suggested if site suggested estimated are used', () => {
|
it('should render edit link with text Site suggested if site suggested estimated are used', () => {
|
||||||
render({
|
render({
|
||||||
|
contextProps: {
|
||||||
transaction: {
|
transaction: {
|
||||||
dappSuggestedGasFees: { maxFeePerGas: 1, maxPriorityFeePerGas: 1 },
|
dappSuggestedGasFees: { maxFeePerGas: 1, maxPriorityFeePerGas: 1 },
|
||||||
txParams: { maxFeePerGas: 1, maxPriorityFeePerGas: 1 },
|
txParams: { maxFeePerGas: 1, maxPriorityFeePerGas: 1 },
|
||||||
},
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(screen.queryByText('🌐')).toBeInTheDocument();
|
expect(screen.queryByText('🌐')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Site suggested')).toBeInTheDocument();
|
expect(screen.queryByText('Site suggested')).toBeInTheDocument();
|
||||||
expect(document.getElementsByClassName('info-tooltip')).toHaveLength(1);
|
expect(document.getElementsByClassName('info-tooltip')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render edit link with text advance if custom gas estimates are used', () => {
|
it('should render edit link with text advance if custom gas estimates are used', () => {
|
||||||
render({
|
render({
|
||||||
|
contextProps: {
|
||||||
defaultEstimateToUse: 'custom',
|
defaultEstimateToUse: 'custom',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(screen.queryByText('⚙')).toBeInTheDocument();
|
expect(screen.queryByText('⚙')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Advanced')).toBeInTheDocument();
|
expect(screen.queryByText('Advanced')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Edit')).toBeInTheDocument();
|
expect(screen.queryByText('Edit')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not render edit link if transaction has simulation error and prop userAcknowledgedGasMissing is false', () => {
|
||||||
|
render({
|
||||||
|
contextProps: {
|
||||||
|
transaction: {
|
||||||
|
simulationFails: true,
|
||||||
|
userFeeLevel: 'low',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
componentProps: { userAcknowledgedGasMissing: false },
|
||||||
|
});
|
||||||
|
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Low')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render edit link if userAcknowledgedGasMissing is true even if transaction has simulation error', () => {
|
||||||
|
render({
|
||||||
|
contextProps: {
|
||||||
|
transaction: {
|
||||||
|
simulationFails: true,
|
||||||
|
userFeeLevel: 'low',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
componentProps: { userAcknowledgedGasMissing: true },
|
||||||
|
});
|
||||||
|
expect(screen.queryByRole('button')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Low')).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -19,6 +19,7 @@ const typeHash = {
|
|||||||
export default function ActionableMessage({
|
export default function ActionableMessage({
|
||||||
message = '',
|
message = '',
|
||||||
primaryAction = null,
|
primaryAction = null,
|
||||||
|
primaryActionV2 = null,
|
||||||
secondaryAction = null,
|
secondaryAction = null,
|
||||||
className = '',
|
className = '',
|
||||||
infoTooltipText = '',
|
infoTooltipText = '',
|
||||||
@ -50,6 +51,14 @@ export default function ActionableMessage({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="actionable-message__message">{message}</div>
|
<div className="actionable-message__message">{message}</div>
|
||||||
|
{primaryActionV2 && (
|
||||||
|
<button
|
||||||
|
className="actionable-message__action-v2"
|
||||||
|
onClick={primaryActionV2.onClick}
|
||||||
|
>
|
||||||
|
{primaryActionV2.label}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{(primaryAction || secondaryAction) && (
|
{(primaryAction || secondaryAction) && (
|
||||||
<div
|
<div
|
||||||
className={classnames('actionable-message__actions', {
|
className={classnames('actionable-message__actions', {
|
||||||
@ -61,6 +70,7 @@ export default function ActionableMessage({
|
|||||||
className={classnames(
|
className={classnames(
|
||||||
'actionable-message__action',
|
'actionable-message__action',
|
||||||
'actionable-message__action--primary',
|
'actionable-message__action--primary',
|
||||||
|
`actionable-message__action-${type}`,
|
||||||
{
|
{
|
||||||
'actionable-message__action--rounded': roundedButtons,
|
'actionable-message__action--rounded': roundedButtons,
|
||||||
},
|
},
|
||||||
@ -75,6 +85,7 @@ export default function ActionableMessage({
|
|||||||
className={classnames(
|
className={classnames(
|
||||||
'actionable-message__action',
|
'actionable-message__action',
|
||||||
'actionable-message__action--secondary',
|
'actionable-message__action--secondary',
|
||||||
|
`actionable-message__action-${type}`,
|
||||||
{
|
{
|
||||||
'actionable-message__action--rounded': roundedButtons,
|
'actionable-message__action--rounded': roundedButtons,
|
||||||
},
|
},
|
||||||
@ -96,6 +107,10 @@ ActionableMessage.propTypes = {
|
|||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
}),
|
}),
|
||||||
|
primaryActionV2: PropTypes.shape({
|
||||||
|
label: PropTypes.string,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
}),
|
||||||
secondaryAction: PropTypes.shape({
|
secondaryAction: PropTypes.shape({
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { fireEvent } from '@testing-library/react';
|
||||||
|
|
||||||
import { renderWithProvider } from '../../../../test/jest';
|
import { renderWithProvider } from '../../../../test/jest';
|
||||||
import ActionableMessage from '.';
|
import ActionableMessage from '.';
|
||||||
@ -19,4 +20,28 @@ describe('ActionableMessage', () => {
|
|||||||
expect(getByText(props.message)).toBeInTheDocument();
|
expect(getByText(props.message)).toBeInTheDocument();
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders button for primaryActionV2 prop', () => {
|
||||||
|
const props = createProps();
|
||||||
|
const { getByRole } = renderWithProvider(
|
||||||
|
<ActionableMessage
|
||||||
|
{...props}
|
||||||
|
primaryActionV2={{ label: 'primary-action-v2' }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
expect(getByRole('button')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders primaryActionV2.onClick is callen when primaryActionV2 button is clicked', () => {
|
||||||
|
const props = createProps();
|
||||||
|
const onClick = jest.fn();
|
||||||
|
const { getByRole } = renderWithProvider(
|
||||||
|
<ActionableMessage
|
||||||
|
{...props}
|
||||||
|
primaryActionV2={{ label: 'primary-action-v2', onClick }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
fireEvent.click(getByRole('button'));
|
||||||
|
expect(onClick).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -54,6 +54,21 @@
|
|||||||
&--rounded {
|
&--rounded {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-danger {
|
||||||
|
background: $Red-500;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__action-v2 {
|
||||||
|
color: $primary-1;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 12px;
|
||||||
|
align-self: baseline;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__info-tooltip-wrapper {
|
&__info-tooltip-wrapper {
|
||||||
@ -86,11 +101,6 @@
|
|||||||
color: $Black-100;
|
color: $Black-100;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
|
||||||
background: $Red-500;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&--info {
|
&--info {
|
||||||
|
@ -262,5 +262,6 @@ export function useGasFeeErrors({
|
|||||||
gasWarnings,
|
gasWarnings,
|
||||||
balanceError,
|
balanceError,
|
||||||
estimatesUnavailableWarning,
|
estimatesUnavailableWarning,
|
||||||
|
hasSimulationError: Boolean(transaction?.simulationFails),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -280,6 +280,21 @@ describe('useGasFeeErrors', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Simulation Error', () => {
|
||||||
|
it('is false if transaction has falsy values for simulationFails', () => {
|
||||||
|
configureEIP1559();
|
||||||
|
const { result } = renderUseGasFeeErrorsHook();
|
||||||
|
expect(result.current.hasSimulationError).toBe(false);
|
||||||
|
});
|
||||||
|
it('is true if transaction.simulationFails is true', () => {
|
||||||
|
configureEIP1559();
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
transaction: { simulationFails: true },
|
||||||
|
});
|
||||||
|
expect(result.current.hasSimulationError).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('estimatesUnavailableWarning', () => {
|
describe('estimatesUnavailableWarning', () => {
|
||||||
it('is false if supportsEIP1559 and gasEstimateType is fee-market', () => {
|
it('is false if supportsEIP1559 and gasEstimateType is fee-market', () => {
|
||||||
configureEIP1559();
|
configureEIP1559();
|
||||||
|
@ -186,6 +186,7 @@ export function useGasFeeInputs(
|
|||||||
gasErrors,
|
gasErrors,
|
||||||
gasWarnings,
|
gasWarnings,
|
||||||
hasGasErrors,
|
hasGasErrors,
|
||||||
|
hasSimulationError,
|
||||||
} = useGasFeeErrors({
|
} = useGasFeeErrors({
|
||||||
gasEstimateType,
|
gasEstimateType,
|
||||||
gasFeeEstimates,
|
gasFeeEstimates,
|
||||||
@ -301,6 +302,7 @@ export function useGasFeeInputs(
|
|||||||
gasErrors,
|
gasErrors,
|
||||||
gasWarnings,
|
gasWarnings,
|
||||||
hasGasErrors,
|
hasGasErrors,
|
||||||
|
hasSimulationError,
|
||||||
supportsEIP1559,
|
supportsEIP1559,
|
||||||
updateTransactionUsingGasFeeEstimates,
|
updateTransactionUsingGasFeeEstimates,
|
||||||
};
|
};
|
||||||
|
@ -145,7 +145,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
submitWarning: '',
|
submitWarning: '',
|
||||||
ethGasPriceWarning: '',
|
ethGasPriceWarning: '',
|
||||||
editingGas: false,
|
editingGas: false,
|
||||||
confirmAnyways: false,
|
userAcknowledgedGasMissing: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
@ -290,8 +290,8 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
this.setState({ editingGas: false });
|
this.setState({ editingGas: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleConfirmAnyways() {
|
setUserAcknowledgedGasMissing() {
|
||||||
this.setState({ confirmAnyways: true });
|
this.setState({ userAcknowledgedGasMissing: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDetails() {
|
renderDetails() {
|
||||||
@ -318,15 +318,16 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
nativeCurrency,
|
nativeCurrency,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { t } = this.context;
|
const { t } = this.context;
|
||||||
|
const { userAcknowledgedGasMissing } = this.state;
|
||||||
|
|
||||||
const { valid } = this.getErrorKey();
|
const { valid } = this.getErrorKey();
|
||||||
const isDisabled = () => {
|
const isDisabled = () => {
|
||||||
return this.state.confirmAnyways ? false : !valid;
|
return userAcknowledgedGasMissing ? false : !valid;
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasSimulationError = Boolean(txData.simulationFails);
|
const hasSimulationError = Boolean(txData.simulationFails);
|
||||||
const renderSimulationFailureWarning =
|
const renderSimulationFailureWarning =
|
||||||
hasSimulationError && !this.state.confirmAnyways;
|
hasSimulationError && !userAcknowledgedGasMissing;
|
||||||
|
|
||||||
const renderTotalMaxAmount = () => {
|
const renderTotalMaxAmount = () => {
|
||||||
if (
|
if (
|
||||||
@ -432,6 +433,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
useNativeCurrencyAsPrimaryCurrency={
|
useNativeCurrencyAsPrimaryCurrency={
|
||||||
useNativeCurrencyAsPrimaryCurrency
|
useNativeCurrencyAsPrimaryCurrency
|
||||||
}
|
}
|
||||||
|
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<TransactionDetailItem
|
<TransactionDetailItem
|
||||||
@ -559,7 +561,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
type="danger"
|
type="danger"
|
||||||
primaryAction={{
|
primaryAction={{
|
||||||
label: this.context.t('tryAnywayOption'),
|
label: this.context.t('tryAnywayOption'),
|
||||||
onClick: () => this.handleConfirmAnyways(),
|
onClick: () => this.setUserAcknowledgedGasMissing(),
|
||||||
}}
|
}}
|
||||||
message={this.context.t('simulationErrorMessage')}
|
message={this.context.t('simulationErrorMessage')}
|
||||||
roundedButtons
|
roundedButtons
|
||||||
@ -569,9 +571,17 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="confirm-page-container-content__details">
|
<div className="confirm-page-container-content__details">
|
||||||
{EIP_1559_V2 && <TransactionAlerts />}
|
{EIP_1559_V2 && (
|
||||||
|
<TransactionAlerts
|
||||||
|
setUserAcknowledgedGasMissing={() =>
|
||||||
|
this.setUserAcknowledgedGasMissing()
|
||||||
|
}
|
||||||
|
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<TransactionDetail
|
<TransactionDetail
|
||||||
disabled={isDisabled()}
|
disabled={isDisabled()}
|
||||||
|
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||||
onEdit={
|
onEdit={
|
||||||
renderSimulationFailureWarning ? null : () => this.handleEditGas()
|
renderSimulationFailureWarning ? null : () => this.handleEditGas()
|
||||||
}
|
}
|
||||||
@ -946,14 +956,14 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
submitWarning,
|
submitWarning,
|
||||||
ethGasPriceWarning,
|
ethGasPriceWarning,
|
||||||
editingGas,
|
editingGas,
|
||||||
confirmAnyways,
|
userAcknowledgedGasMissing,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const { name } = methodData;
|
const { name } = methodData;
|
||||||
const { valid, errorKey } = this.getErrorKey();
|
const { valid, errorKey } = this.getErrorKey();
|
||||||
const hasSimulationError = Boolean(txData.simulationFails);
|
const hasSimulationError = Boolean(txData.simulationFails);
|
||||||
const renderSimulationFailureWarning =
|
const renderSimulationFailureWarning =
|
||||||
hasSimulationError && !confirmAnyways;
|
hasSimulationError && !userAcknowledgedGasMissing;
|
||||||
const {
|
const {
|
||||||
totalTx,
|
totalTx,
|
||||||
positionOfCurrentTx,
|
positionOfCurrentTx,
|
||||||
@ -967,7 +977,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
} = this.getNavigateTxData();
|
} = this.getNavigateTxData();
|
||||||
|
|
||||||
const isDisabled = () => {
|
const isDisabled = () => {
|
||||||
return confirmAnyways ? false : !valid;
|
return userAcknowledgedGasMissing ? false : !valid;
|
||||||
};
|
};
|
||||||
|
|
||||||
let functionType = getMethodName(name);
|
let functionType = getMethodName(name);
|
||||||
@ -1017,7 +1027,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
lastTx={lastTx}
|
lastTx={lastTx}
|
||||||
ofText={ofText}
|
ofText={ofText}
|
||||||
requestsWaitingText={requestsWaitingText}
|
requestsWaitingText={requestsWaitingText}
|
||||||
hideConfirmAnyways={!isDisabled()}
|
hideUserAcknowledgedGasMissing={!isDisabled()}
|
||||||
disabled={
|
disabled={
|
||||||
renderSimulationFailureWarning ||
|
renderSimulationFailureWarning ||
|
||||||
!valid ||
|
!valid ||
|
||||||
@ -1029,7 +1039,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
onCancelAll={() => this.handleCancelAll()}
|
onCancelAll={() => this.handleCancelAll()}
|
||||||
onCancel={() => this.handleCancel()}
|
onCancel={() => this.handleCancel()}
|
||||||
onSubmit={() => this.handleSubmit()}
|
onSubmit={() => this.handleSubmit()}
|
||||||
onConfirmAnyways={() => this.handleConfirmAnyways()}
|
setUserAcknowledgedGasMissing={this.setUserAcknowledgedGasMissing}
|
||||||
hideSenderToRecipient={hideSenderToRecipient}
|
hideSenderToRecipient={hideSenderToRecipient}
|
||||||
origin={txData.origin}
|
origin={txData.origin}
|
||||||
ethGasPriceWarning={ethGasPriceWarning}
|
ethGasPriceWarning={ethGasPriceWarning}
|
||||||
|
@ -26,11 +26,14 @@ const GasDetailsItem = ({
|
|||||||
isMainnet,
|
isMainnet,
|
||||||
maxFeePerGas,
|
maxFeePerGas,
|
||||||
maxPriorityFeePerGas,
|
maxPriorityFeePerGas,
|
||||||
|
userAcknowledgedGasMissing,
|
||||||
txData,
|
txData,
|
||||||
useNativeCurrencyAsPrimaryCurrency,
|
useNativeCurrencyAsPrimaryCurrency,
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const { estimateUsed } = useGasFeeContext();
|
const { estimateUsed, hasSimulationError } = useGasFeeContext();
|
||||||
|
|
||||||
|
if (hasSimulationError && !userAcknowledgedGasMissing) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TransactionDetailItem
|
<TransactionDetailItem
|
||||||
@ -138,6 +141,7 @@ GasDetailsItem.propTypes = {
|
|||||||
isMainnet: PropTypes.bool,
|
isMainnet: PropTypes.bool,
|
||||||
maxFeePerGas: PropTypes.string,
|
maxFeePerGas: PropTypes.string,
|
||||||
maxPriorityFeePerGas: PropTypes.string,
|
maxPriorityFeePerGas: PropTypes.string,
|
||||||
|
userAcknowledgedGasMissing: PropTypes.bool.isRequired,
|
||||||
txData: PropTypes.object,
|
txData: PropTypes.object,
|
||||||
useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
|
useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
@ -17,7 +17,7 @@ jest.mock('../../../store/actions', () => ({
|
|||||||
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
|
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const render = (props) => {
|
const render = ({ componentProps, contextProps } = {}) => {
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
metamask: {
|
metamask: {
|
||||||
nativeCurrency: ETH,
|
nativeCurrency: ETH,
|
||||||
@ -37,8 +37,12 @@ const render = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return renderWithProvider(
|
return renderWithProvider(
|
||||||
<GasFeeContextProvider {...props}>
|
<GasFeeContextProvider {...contextProps}>
|
||||||
<GasDetailsItem txData={{ txParams: {} }} {...props} />
|
<GasDetailsItem
|
||||||
|
txData={{ txParams: {} }}
|
||||||
|
userAcknowledgedGasMissing={false}
|
||||||
|
{...componentProps}
|
||||||
|
/>
|
||||||
</GasFeeContextProvider>,
|
</GasFeeContextProvider>,
|
||||||
store,
|
store,
|
||||||
);
|
);
|
||||||
@ -56,16 +60,47 @@ describe('GasDetailsItem', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should show warning icon if estimates are high', async () => {
|
it('should show warning icon if estimates are high', async () => {
|
||||||
render({ defaultEstimateToUse: 'high' });
|
render({ contextProps: { defaultEstimateToUse: 'high' } });
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.queryByText('⚠ Max fee:')).toBeInTheDocument();
|
expect(screen.queryByText('⚠ Max fee:')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not show warning icon if estimates are not high', async () => {
|
it('should not show warning icon if estimates are not high', async () => {
|
||||||
render({ defaultEstimateToUse: 'low' });
|
render({ contextProps: { defaultEstimateToUse: 'low' } });
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
|
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return null if there is simulationError and user has not acknowledged gasMissing warning', () => {
|
||||||
|
const { container } = render({
|
||||||
|
contextProps: {
|
||||||
|
defaultEstimateToUse: 'low',
|
||||||
|
transaction: { simulationFails: true },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(container.innerHTML).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not return null even if there is simulationError if user acknowledged gasMissing warning', async () => {
|
||||||
|
render();
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText('Gas')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should should render gas fee details', async () => {
|
||||||
|
render({
|
||||||
|
componentProps: {
|
||||||
|
hexMinimumTransactionFee: '0x1ca62a4f7800',
|
||||||
|
hexMaximumTransactionFee: '0x290ee75e3d900',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByTitle('0.0000315 ETH')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('ETH')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTitle('0.0007223')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,26 +1,59 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { PRIORITY_LEVELS } from '../../../../shared/constants/gas';
|
||||||
import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../helpers/constants/error-keys';
|
import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../helpers/constants/error-keys';
|
||||||
import { submittedPendingTransactionsSelector } from '../../../selectors/transactions';
|
import { submittedPendingTransactionsSelector } from '../../../selectors/transactions';
|
||||||
import { useGasFeeContext } from '../../../contexts/gasFee';
|
import { useGasFeeContext } from '../../../contexts/gasFee';
|
||||||
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
import ActionableMessage from '../../../components/ui/actionable-message/actionable-message';
|
import ActionableMessage from '../../../components/ui/actionable-message/actionable-message';
|
||||||
import ErrorMessage from '../../../components/ui/error-message';
|
import ErrorMessage from '../../../components/ui/error-message';
|
||||||
import I18nValue from '../../../components/ui/i18n-value';
|
import I18nValue from '../../../components/ui/i18n-value';
|
||||||
|
import Typography from '../../../components/ui/typography';
|
||||||
|
|
||||||
const TransactionAlerts = () => {
|
const TransactionAlerts = ({
|
||||||
const { balanceError, estimateUsed } = useGasFeeContext();
|
userAcknowledgedGasMissing,
|
||||||
|
setUserAcknowledgedGasMissing,
|
||||||
|
}) => {
|
||||||
|
const { balanceError, estimateUsed, hasSimulationError } = useGasFeeContext();
|
||||||
const pendingTransactions = useSelector(submittedPendingTransactionsSelector);
|
const pendingTransactions = useSelector(submittedPendingTransactionsSelector);
|
||||||
|
const t = useI18nContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="transaction-alerts">
|
<div className="transaction-alerts">
|
||||||
|
{hasSimulationError && (
|
||||||
|
<ActionableMessage
|
||||||
|
message={<I18nValue messageKey="simulationErrorMessageV2" />}
|
||||||
|
useIcon
|
||||||
|
iconFillColor="#d73a49"
|
||||||
|
type="danger"
|
||||||
|
primaryActionV2={
|
||||||
|
userAcknowledgedGasMissing === true
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
label: t('proceedWithTransaction'),
|
||||||
|
onClick: setUserAcknowledgedGasMissing,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{pendingTransactions?.length > 0 && (
|
{pendingTransactions?.length > 0 && (
|
||||||
<ActionableMessage
|
<ActionableMessage
|
||||||
message={
|
message={
|
||||||
<div className="transaction-alerts__pending-transactions">
|
<Typography
|
||||||
|
className="transaction-alerts__pending-transactions"
|
||||||
|
align="left"
|
||||||
|
fontSize="12px"
|
||||||
|
margin={[0, 0]}
|
||||||
|
>
|
||||||
<strong>
|
<strong>
|
||||||
<I18nValue
|
<I18nValue
|
||||||
messageKey="pendingTransaction"
|
messageKey={
|
||||||
|
pendingTransactions?.length === 1
|
||||||
|
? 'pendingTransactionSingle'
|
||||||
|
: 'pendingTransactionMultiple'
|
||||||
|
}
|
||||||
options={[pendingTransactions?.length]}
|
options={[pendingTransactions?.length]}
|
||||||
/>
|
/>
|
||||||
</strong>{' '}
|
</strong>{' '}
|
||||||
@ -38,36 +71,33 @@ const TransactionAlerts = () => {
|
|||||||
</a>,
|
</a>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Typography>
|
||||||
}
|
}
|
||||||
useIcon
|
useIcon
|
||||||
iconFillColor="#f8c000"
|
iconFillColor="#f8c000"
|
||||||
type="warning"
|
type="warning"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{balanceError && (
|
{balanceError && <ErrorMessage errorKey={INSUFFICIENT_FUNDS_ERROR_KEY} />}
|
||||||
<>
|
{estimateUsed === PRIORITY_LEVELS.LOW && (
|
||||||
{pendingTransactions?.length > 0 && (
|
|
||||||
<div className="transaction-alerts--separator" />
|
|
||||||
)}
|
|
||||||
<ErrorMessage errorKey={INSUFFICIENT_FUNDS_ERROR_KEY} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{estimateUsed === 'low' && (
|
|
||||||
<>
|
|
||||||
{balanceError && (
|
|
||||||
<div className="transaction-alerts-message--separator" />
|
|
||||||
)}
|
|
||||||
<ActionableMessage
|
<ActionableMessage
|
||||||
message={<I18nValue messageKey="lowPriorityMessage" />}
|
message={
|
||||||
|
<Typography align="left" fontSize="12px" margin={[0, 0]}>
|
||||||
|
<I18nValue messageKey="lowPriorityMessage" />
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
useIcon
|
useIcon
|
||||||
iconFillColor="#f8c000"
|
iconFillColor="#f8c000"
|
||||||
type="warning"
|
type="warning"
|
||||||
/>
|
/>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TransactionAlerts.propTypes = {
|
||||||
|
userAcknowledgedGasMissing: PropTypes.bool,
|
||||||
|
setUserAcknowledgedGasMissing: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
export default TransactionAlerts;
|
export default TransactionAlerts;
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
.transaction-alerts {
|
.transaction-alerts {
|
||||||
margin-top: 20px;
|
text-align: left;
|
||||||
|
|
||||||
&--separator {
|
& > *:first-of-type {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > *:not(:first-of-type) {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ jest.mock('../../../store/actions', () => ({
|
|||||||
addPollingTokenToAppState: jest.fn(),
|
addPollingTokenToAppState: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const render = ({ props, state }) => {
|
const render = ({ props, state } = {}) => {
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
metamask: {
|
metamask: {
|
||||||
nativeCurrency: ETH,
|
nativeCurrency: ETH,
|
||||||
@ -95,7 +95,7 @@ describe('TransactionAlerts', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
screen.queryByText('You have (1) pending transaction(s).'),
|
screen.queryByText('You have (1) pending transaction.'),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user