import { useCallback, useEffect, useState } from 'react' import { CheckCircleIcon, ArrowTopRightOnSquareIcon, InformationCircleIcon, XCircleIcon, XMarkIcon, } from '@heroicons/react/20/solid' import mangoStore, { CLUSTER } from '@store/mangoStore' import { TransactionNotification, notify } from '../../utils/notifications' import Loading from '@components/shared/Loading' import { Transition } from '@headlessui/react' import { CLIENT_TX_TIMEOUT, NOTIFICATION_POSITION_KEY, PREFERRED_EXPLORER_KEY, } from '../../utils/constants' import useLocalStorageState from 'hooks/useLocalStorageState' import { useTranslation } from 'next-i18next' import useSolBalance from 'hooks/useSolBalance' import { EXPLORERS } from '@components/settings/PreferredExplorerSettings' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' const setMangoStore = mangoStore.getState().set function parseDescription(description: string | null | undefined) { if ( description?.includes('{"err":{"InstructionError":[2,{"Custom":6001}]}}') ) { return 'Your max slippage tolerance was exceeded' } return description } const TransactionNotificationList = () => { const { t } = useTranslation() const transactionNotifications = mangoStore((s) => s.transactionNotifications) const walletTokens = mangoStore((s) => s.wallet.tokens) const notEnoughSoLMessage = t('deposit-more-sol') const [notificationPosition] = useLocalStorageState( NOTIFICATION_POSITION_KEY, 'bottom-left', ) const [mounted, setMounted] = useState(false) const { maxSolDeposit } = useSolBalance() // if a notification is shown with {"InstructionError":[0,{"Custom":1}]} then // add a notification letting the user know they may not have enough SOL useEffect(() => { if (transactionNotifications.length) { const customErrorNotification = transactionNotifications.find( (n) => n.description && n.description.includes('"Custom":1'), ) const notEnoughSolNotification = transactionNotifications.find( (n) => n.title && n.title.includes(notEnoughSoLMessage), ) if ( !notEnoughSolNotification && customErrorNotification && maxSolDeposit <= 0 ) { notify({ title: notEnoughSoLMessage, type: 'info', }) } } }, [transactionNotifications, walletTokens, maxSolDeposit]) const clearAll = useCallback(() => { setMangoStore((s) => { const newNotifications = s.transactionNotifications.map((n) => ({ ...n, show: false, })) s.transactionNotifications = newNotifications }) }, [transactionNotifications]) const reversedNotifications = [...transactionNotifications].reverse() const getPosition = (position: string) => { const sharedClasses = 'pointer-events-none fixed z-50 flex items-end p-4 text-th-fgd-1 md:p-6' switch (position) { case 'Bottom-Left': return 'flex-col bottom-0 left-0 ' + sharedClasses case 'Bottom-Right': return 'flex-col w-full bottom-0 right-0 ' + sharedClasses case 'Top-Left': return 'flex-col-reverse top-0 left-0 ' + sharedClasses case 'Top-Right': return 'flex-col-reverse w-full top-0 right-0 ' + sharedClasses default: return 'flex-col bottom-0 left-0 ' + sharedClasses } } useEffect(() => setMounted(true), []) if (!mounted) return null return (
{transactionNotifications.filter((n) => n.show).length > 1 ? ( ) : null} {reversedNotifications.map((n) => ( ))}
) } const TransactionNotification = ({ notification, }: { notification: TransactionNotification }) => { const [notificationPosition] = useLocalStorageState( NOTIFICATION_POSITION_KEY, 'Bottom-Left', ) const [preferredExplorer] = useLocalStorageState( PREFERRED_EXPLORER_KEY, EXPLORERS[0], ) const { type, title, description, txid, show, id } = notification // overwrite the title if of the error message if it is a time out error let parsedTitle: string | undefined if (description) { if ( description.includes('Timed out awaiting') || description.includes('was not confirmed') ) { parsedTitle = 'Transaction status unknown' } } const parsedDescription = parseDescription(description) // if the notification is a success, then hide the confirming tx notification with the same txid useEffect(() => { if ((type === 'error' || type === 'success') && txid) { setMangoStore((s) => { const newNotifications = s.transactionNotifications.map((n) => n.txid === txid && n.type === 'confirm' ? { ...n, show: false } : n, ) s.transactionNotifications = newNotifications }) } }, [type, txid]) const hideNotification = useCallback(() => { setMangoStore((s) => { const newNotifications = s.transactionNotifications.map((n) => n.id === id ? { ...n, show: false } : n, ) s.transactionNotifications = newNotifications }) }, [id]) // auto hide a notification // if no status is provided for a tx notification after 90s, hide it useEffect(() => { const timeoutInterval = parsedTitle || type === 'confirm' ? CLIENT_TX_TIMEOUT : type === 'error' ? 30000 : type === 'info' ? 8000 : 10000 const id = setTimeout(() => { if (show) { hideNotification() } }, timeoutInterval) return () => { clearInterval(id) } }, [hideNotification, parsedTitle, show, type]) const getTransformClasses = (position: string) => { const fromLeft = { enterFromClass: 'md:-translate-x-48', enterToClass: 'md:translate-x-100', leaveFromClass: 'md:translate-x-0', leaveToClass: 'md:-translate-x-48', } const fromRight = { enterFromClass: 'md:translate-x-48', enterToClass: 'md:-translate-x-100', leaveFromClass: 'md:translate-x-0', leaveToClass: 'md:translate-x-48', } switch (position) { case 'Bottom-Left': return fromLeft case 'Bottom-Right': return fromRight case 'Top-Left': return fromLeft case 'Top-Right': return fromRight default: return fromLeft } } return (
{type === 'success' ? ( ) : null} {type === 'info' && ( )} {type === 'error' && ( )} {type === 'confirm' && ( )}

{parsedTitle || title}

{parsedDescription ? (

{parsedDescription}

) : null} {txid ? ( ) : null}
) } export default TransactionNotificationList // eslint-disable-next-line @typescript-eslint/no-explicit-any function LinkRenderer(props: any) { return ( // eslint-disable-next-line react/prop-types {/* eslint-disable-next-line react/prop-types */} {props.children} ) }