Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show banner for delayed bridge tx #28849

Merged
merged 5 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion shared/types/bridge-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export type BridgeHistoryItem = {
txMetaId: string; // Need this to handle STX that might not have a txHash immediately
quote: Quote;
status: StatusResponse;
startTime?: number;
startTime?: number; // timestamp in ms
estimatedProcessingTimeInSeconds: number;
slippagePercentage: number;
completionTime?: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {
BridgeHistoryItem,
StatusTypes,
} from '../../../../shared/types/bridge-status';
import { getIsDelayed } from './transaction-details';

describe('transaction-details', () => {
describe('getIsDelayed', () => {
it('returns false when status is not PENDING', () => {
const result = getIsDelayed(StatusTypes.COMPLETE, {
startTime: Date.now(),
estimatedProcessingTimeInSeconds: 60,
} as BridgeHistoryItem);
expect(result).toBe(false);
});

it('returns false when bridgeHistoryItem is undefined', () => {
const result = getIsDelayed(StatusTypes.PENDING, undefined);
expect(result).toBe(false);
});

it('returns false when startTime is undefined', () => {
const result = getIsDelayed(StatusTypes.PENDING, {
startTime: undefined,
estimatedProcessingTimeInSeconds: 60,
} as BridgeHistoryItem);
expect(result).toBe(false);
});

it('returns false when current time is less than estimated completion time', () => {
const result = getIsDelayed(StatusTypes.PENDING, {
startTime: Date.now() - 1000,
estimatedProcessingTimeInSeconds: 60,
} as BridgeHistoryItem);

expect(result).toBe(false);
});

it('returns true when current time exceeds estimated completion time', () => {
const startTime = Date.now() - 61 * 1000;

const result = getIsDelayed(StatusTypes.PENDING, {
startTime,
estimatedProcessingTimeInSeconds: 60,
} as BridgeHistoryItem);

expect(result).toBe(true);
});
});
});
71 changes: 69 additions & 2 deletions ui/pages/bridge/transaction-details/transaction-details.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import React from 'react';
import React, { useContext } from 'react';
import { useSelector } from 'react-redux';
import { useHistory, useParams } from 'react-router-dom';
import { NetworkConfiguration } from '@metamask/network-controller';
import { TransactionMeta } from '@metamask/transaction-controller';
import {
AvatarNetwork,
AvatarNetworkSize,
BannerAlert,
BannerAlertSeverity,
Box,
ButtonIcon,
ButtonIconSize,
ButtonLink,
IconName,
Text,
} from '../../../components/component-library';
Expand All @@ -21,7 +24,10 @@ import { MetaMaskReduxState } from '../../../store/store';
import { hexToDecimal } from '../../../../shared/modules/conversion.utils';
import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display/user-preferenced-currency-display.component';
import { EtherDenomination } from '../../../../shared/constants/common';
import { PRIMARY } from '../../../helpers/constants/common';
import {
PRIMARY,
SUPPORT_REQUEST_LINK,
} from '../../../helpers/constants/common';
import CurrencyDisplay from '../../../components/ui/currency-display/currency-display.component';
import {
BridgeHistoryItem,
Expand All @@ -39,6 +45,12 @@ import { ConfirmInfoRowDivider as Divider } from '../../../components/app/confir
import { useI18nContext } from '../../../hooks/useI18nContext';
import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../shared/constants/network';
import { selectedAddressTxListSelector } from '../../../selectors';
import {
MetaMetricsContextProp,
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import TransactionDetailRow from './transaction-detail-row';
import BridgeExplorerLinks from './bridge-explorer-links';
import BridgeStepList from './bridge-step-list';
Expand Down Expand Up @@ -79,6 +91,24 @@ const getBridgeAmount = ({
return undefined;
};

/**
* @param status - The status of the bridge history item
* @param bridgeHistoryItem - The bridge history item
* @returns Whether the bridge history item is delayed
*/
export const getIsDelayed = (
status: StatusTypes,
bridgeHistoryItem?: BridgeHistoryItem,
) => {
return Boolean(
status === StatusTypes.PENDING &&
bridgeHistoryItem?.startTime &&
Date.now() >
bridgeHistoryItem.startTime +
bridgeHistoryItem.estimatedProcessingTimeInSeconds * 1000,
);
};

const StatusToColorMap: Record<StatusTypes, TextColor> = {
[StatusTypes.PENDING]: TextColor.warningDefault,
[StatusTypes.COMPLETE]: TextColor.successDefault,
Expand All @@ -88,6 +118,7 @@ const StatusToColorMap: Record<StatusTypes, TextColor> = {

const CrossChainSwapTxDetails = () => {
const t = useI18nContext();
const trackEvent = useContext(MetaMetricsContext);
const rootState = useSelector((state) => state);
const history = useHistory();
const { srcTxMetaId } = useParams<{ srcTxMetaId: string }>();
Expand Down Expand Up @@ -141,6 +172,7 @@ const CrossChainSwapTxDetails = () => {
: undefined;

const bridgeAmount = getBridgeAmount({ bridgeHistoryItem });
const isDelayed = getIsDelayed(status, bridgeHistoryItem);

return (
<div className="bridge">
Expand All @@ -164,6 +196,41 @@ const CrossChainSwapTxDetails = () => {
flexDirection={FlexDirection.Column}
gap={4}
>
{isDelayed && (
<BannerAlert
title={t('bridgeTxDetailsDelayedTitle')}
severity={BannerAlertSeverity.Warning}
>
<Text display={Display.Flex} alignItems={AlignItems.center}>
{t('bridgeTxDetailsDelayedDescription')}&nbsp;
<ButtonLink
externalLink
href={SUPPORT_REQUEST_LINK}
onClick={() => {
trackEvent(
{
category: MetaMetricsEventCategory.Home,
event: MetaMetricsEventName.SupportLinkClicked,
properties: {
url: SUPPORT_REQUEST_LINK,
location: 'Bridge Tx Details',
},
},
{
contextPropsIntoEventProperties: [
MetaMetricsContextProp.PageTitle,
],
},
);
}}
>
{t('bridgeTxDetailsDelayedDescriptionSupport')}
</ButtonLink>
.
</Text>
</BannerAlert>
)}

{status !== StatusTypes.COMPLETE &&
(bridgeHistoryItem || srcChainTxMeta) && (
<BridgeStepList
Expand Down
Loading