import DoneIcon from '@mui/icons-material/Done'; import ErrorRoundedIcon from '@mui/icons-material/ErrorRounded'; import InfoRoundedIcon from '@mui/icons-material/InfoRounded'; import WarningRoundedIcon from '@mui/icons-material/WarningRounded'; import { Box, Button, Typography } from '@mui/material'; import { useQueryClient } from '@tanstack/react-query'; import { useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import type { BottomSheetBase } from '../../components/BottomSheet'; import { BottomSheet } from '../../components/BottomSheet'; import { Token } from '../../components/Token'; import { getProcessMessage, useAvailableChains, useNavigateBack, useTokenBalance, } from '../../hooks'; import { useWidgetConfig } from '../../providers'; import type { RouteExecution } from '../../stores'; import { RouteExecutionStatus, getSourceTxHash, useFieldActions, } from '../../stores'; import { formatTokenAmount, hasEnumFlag, navigationRoutes, shortenAddress, } from '../../utils'; import { CenterContainer, IconCircle } from './StatusBottomSheet.style'; export const StatusBottomSheet: React.FC = ({ status, route, }) => { const { t } = useTranslation(); const { navigateBack, navigate } = useNavigateBack(); const ref = useRef(null); const queryClient = useQueryClient(); const { setFieldValue } = useFieldActions(); const { subvariant, contractComponent, contractSecondaryComponent, contractCompactComponent, } = useWidgetConfig(); const { getChainById } = useAvailableChains(); const toToken = { ...(route.steps.at(-1)?.execution?.toToken ?? route.toToken), amount: BigInt( route.steps.at(-1)?.execution?.toAmount ?? route.steps.at(-1)?.estimate.toAmount ?? route.toAmount, ), }; const toChain = getChainById(toToken.chainId); const { token, refetch, refetchNewBalance, refetchAllBalances } = useTokenBalance(route.toAddress, toToken, toChain); const invalidateQueries = () => { refetchAllBalances(); setFieldValue('fromAmount', ''); setFieldValue('toAmount', ''); queryClient.invalidateQueries({ queryKey: ['transaction-history'] }); }; const handleDone = () => { invalidateQueries(); navigateBack(); }; const handlePartialDone = () => { invalidateQueries(); if ( toToken.chainId !== route.toToken.chainId && toToken.address !== route.toToken.address ) { 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(); ref.current?.close(); }; const handleSeeDetails = () => { handleClose(); const transactionHash = getSourceTxHash(route); 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 === 'nft' ? t('success.title.purchaseSuccessful') : 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; } useEffect(() => { const hasSuccessFlag = hasEnumFlag(status, RouteExecutionStatus.Done); if ( (hasSuccessFlag || hasEnumFlag(status, RouteExecutionStatus.Failed)) && !ref.current?.isOpen() ) { if (hasSuccessFlag) { refetchNewBalance(); refetch(); } ref.current?.open(); } }, [refetch, refetchNewBalance, status]); const showContractComponent = subvariant === 'nft' && hasEnumFlag(status, RouteExecutionStatus.Done); 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 || contractComponent ) : ( {hasEnumFlag(status, RouteExecutionStatus.Done) ? ( ) : null} )} {!showContractComponent ? ( {primaryMessage} ) : null} {secondaryMessage ? ( {secondaryMessage} ) : null} {hasEnumFlag(status, RouteExecutionStatus.Done) ? ( ) : null} ); };