import {
Done,
ErrorRounded,
InfoRounded,
WarningRounded,
} from '@mui/icons-material';
import { Box, Button, Skeleton, Typography } from '@mui/material';
import { useQueryClient } from '@tanstack/react-query';
import { useCallback, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { BottomSheet } from '../../components/BottomSheet/BottomSheet.js';
import type { BottomSheetBase } from '../../components/BottomSheet/types.js';
import { Token } from '../../components/Token/Token.js';
import { useAccount } from '../../hooks/useAccount.js';
import { useAvailableChains } from '../../hooks/useAvailableChains.js';
import { useNavigateBack } from '../../hooks/useNavigateBack.js';
import { getProcessMessage } from '../../hooks/useProcessMessage.js';
import { useSetContentHeight } from '../../hooks/useSetContentHeight.js';
import { useTokenBalance } from '../../hooks/useTokenBalance.js';
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js';
import { useFieldActions } from '../../stores/form/useFieldActions.js';
import {
RouteExecutionStatus,
type RouteExecution,
} from '../../stores/routes/types.js';
import { getSourceTxHash } from '../../stores/routes/utils.js';
import { hasEnumFlag } from '../../utils/enum.js';
import { formatTokenAmount } from '../../utils/format.js';
import { navigationRoutes } from '../../utils/navigationRoutes.js';
import { shortenAddress } from '../../utils/wallet.js';
import { safeBigInt } from '../../utils/utils.js';
import {
CenterContainer,
IconCircle,
MessageSkeletonContainer,
} from './StatusBottomSheet.style.js';
interface StatusBottomSheetContentProps extends RouteExecution {
onClose(): void;
}
const MessageSkeleton = () => (
);
export const StatusBottomSheet: React.FC = ({
status,
route,
}) => {
const ref = useRef(null);
const onClose = useCallback(() => {
ref.current?.close();
}, []);
useEffect(() => {
const hasSuccessFlag = hasEnumFlag(status, RouteExecutionStatus.Done);
const hasFailedFlag = hasEnumFlag(status, RouteExecutionStatus.Failed);
if ((hasSuccessFlag || hasFailedFlag) && !ref.current?.isOpen()) {
ref.current?.open();
}
}, [status]);
return (
);
};
export const StatusBottomSheetContent: React.FC<
StatusBottomSheetContentProps
> = ({ status, route, onClose }) => {
const { t } = useTranslation();
const { navigateBack, navigate } = useNavigateBack();
const queryClient = useQueryClient();
const { setFieldValue } = useFieldActions();
const {
subvariant,
subvariantOptions,
contractSecondaryComponent,
contractCompactComponent,
feeConfig,
handleSwapLog,
} = useWidgetConfig();
const { getChainById } = useAvailableChains();
const { account } = useAccount();
const ref = useRef();
useSetContentHeight(ref);
const lastStep = route?.steps?.at(-1);
const toToken = {
...(lastStep?.execution?.toToken ?? route?.toToken),
amount: safeBigInt(
lastStep?.execution?.toAmount ??
lastStep?.estimate?.toAmount ??
route?.toAmount,
),
};
const toChain = getChainById(toToken.chainId);
const { token, refetch, refetchNewBalance, refetchAllBalances } =
useTokenBalance(route.toAddress, toToken, toChain);
const invalidateQueries = () => {
Promise.resolve().then(() => {
refetchAllBalances();
setFieldValue('fromAmount', '');
setFieldValue('toAmount', '');
queryClient.invalidateQueries({ queryKey: ['transaction-history'] });
queryClient.invalidateQueries({ queryKey: ['token-balances'] });
});
};
const handleDone = () => {
invalidateQueries();
Promise.resolve().then(() => {
navigateBack();
});
};
const handlePartialDone = () => {
invalidateQueries();
if (
toToken.chainId !== route?.toToken?.chainId &&
toToken.address !== route?.toToken?.address
) {
Promise.resolve().then(() => {
setFieldValue(
'fromAmount',
formatTokenAmount(toToken.amount, toToken.decimals),
{ isTouched: true },
);
setFieldValue('fromChain', toToken.chainId, { isTouched: true });
setFieldValue('fromToken', toToken.address, { isTouched: true });
setFieldValue('toChain', route?.toToken?.chainId, {
isTouched: true,
});
setFieldValue('toToken', route?.toToken?.address, {
isTouched: true,
});
});
}
navigateBack();
};
const handleClose = () => {
invalidateQueries();
Promise.resolve().then(() => {
onClose();
});
};
const handleSeeDetails = () => {
handleClose();
const transactionHash = getSourceTxHash(route);
Promise.resolve().then(() => {
navigate(navigationRoutes.transactionDetails, {
state: {
routeId: route.id,
transactionHash,
},
replace: true,
});
});
};
const transactionType =
route.fromChainId === route.toChainId ? 'swap' : 'bridge';
let title: string | undefined;
let primaryMessage;
let secondaryMessage;
let handlePrimaryButton = handleDone;
switch (status) {
case RouteExecutionStatus.Done: {
title =
subvariant === 'custom'
? t(
`success.title.${subvariantOptions?.custom ?? 'checkout'}Successful`,
)
: t(`success.title.${transactionType}Successful`);
if (token) {
primaryMessage = t('success.message.exchangeSuccessful', {
amount: formatTokenAmount(token.amount, token.decimals),
tokenSymbol: token.symbol,
chainName: toChain?.name,
walletAddress: shortenAddress(route.toAddress),
});
}
handlePrimaryButton = handleDone;
break;
}
case RouteExecutionStatus.Done | RouteExecutionStatus.Partial: {
title = t(`success.title.${transactionType}PartiallySuccessful`);
primaryMessage = t('success.message.exchangePartiallySuccessful', {
tool: route.steps.at(-1)?.toolDetails.name,
tokenSymbol: route.steps.at(-1)?.action.toToken.symbol,
});
if (token) {
secondaryMessage = t('success.message.exchangeSuccessful', {
amount: formatTokenAmount(token.amount, token.decimals),
tokenSymbol: token.symbol,
chainName: toChain?.name,
walletAddress: shortenAddress(route.toAddress),
});
}
handlePrimaryButton = handlePartialDone;
break;
}
case RouteExecutionStatus.Done | RouteExecutionStatus.Refunded: {
title = t('success.title.refundIssued');
primaryMessage = t('success.message.exchangePartiallySuccessful', {
tool: route.steps.at(-1)?.toolDetails.name,
tokenSymbol: route.steps.at(-1)?.action.toToken.symbol,
});
if (token) {
secondaryMessage = t('success.message.exchangeSuccessful', {
amount: formatTokenAmount(token.amount, token.decimals),
tokenSymbol: token.symbol,
chainName: toChain?.name,
walletAddress: shortenAddress(route.toAddress),
});
}
break;
}
case RouteExecutionStatus.Failed: {
const step = route.steps.find(
(step) => step.execution?.status === 'FAILED',
);
const process = step?.execution?.process.find(
(process) => process.status === 'FAILED',
);
if (!step || !process) {
break;
}
const processMessage = getProcessMessage(t, getChainById, step, process);
title = processMessage.title;
primaryMessage = processMessage.message;
handlePrimaryButton = handleClose;
break;
}
default:
break;
}
const handleTrackingLogFail = async () => {
try {
const step = route.steps.find(
(step) => step.execution?.status === 'FAILED',
);
const process = step?.execution?.process.find(
(process) => process.status === 'FAILED',
);
if (!step || !process) {
return;
}
const agg = route?.steps?.[0]?.integrator?.toLowerCase();
const processMessage = getProcessMessage(t, getChainById, step, process);
const transactionHash = getSourceTxHash(route);
typeof handleSwapLog === 'function' &&
(await handleSwapLog({
title: processMessage.title,
message: processMessage.message,
address: account?.address,
chainName: getChainById(step.action.fromChainId)?.name ?? '',
transactionHash,
amount: formatTokenAmount(
BigInt(step?.action?.fromAmount || 0),
step.action.fromToken.decimals,
),
symbol: step.action.fromToken.symbol,
agg,
}));
} catch (e) {
console.error(e);
}
};
useEffect(() => {
if (RouteExecutionStatus.Failed) {
handleTrackingLogFail();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [status]);
useEffect(() => {
const hasSuccessFlag = hasEnumFlag(status, RouteExecutionStatus.Done);
if (hasSuccessFlag) {
refetchNewBalance();
refetch();
}
}, [refetch, refetchNewBalance, status]);
const showContractComponent =
subvariant === 'custom' &&
hasEnumFlag(status, RouteExecutionStatus.Done) &&
(contractCompactComponent || contractSecondaryComponent);
const VcComponent =
status === RouteExecutionStatus.Done ? feeConfig?._vcComponent : undefined;
return (
{!showContractComponent ? (
{status === RouteExecutionStatus.Idle ? (
) : null}
{status === RouteExecutionStatus.Done ? (
) : null}
{hasEnumFlag(status, RouteExecutionStatus.Partial) ||
hasEnumFlag(status, RouteExecutionStatus.Refunded) ? (
) : null}
{hasEnumFlag(status, RouteExecutionStatus.Failed) ? (
) : null}
) : null}
{title}
{showContractComponent ? (
contractCompactComponent || contractSecondaryComponent
) : (
{hasEnumFlag(status, RouteExecutionStatus.Done) ? (
) : null}
)}
{!showContractComponent ? (
primaryMessage ? (
{primaryMessage}
) : (
)
) : null}
{secondaryMessage ? (
{secondaryMessage}
) : null}
{VcComponent ? : null}
{hasEnumFlag(status, RouteExecutionStatus.Done) ? (
) : null}
);
};