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} ); };