import { Fragment, useEffect, useMemo, useState } from 'react' import { CheckCircleIcon, ArrowTopRightOnSquareIcon, InformationCircleIcon, XCircleIcon, } from '@heroicons/react/20/solid' import mangoStore, { CLUSTER } from '@store/mangoStore' import { Notification, notify } from '../../utils/notifications' import Loading from './Loading' import { Transition } from '@headlessui/react' import { TokenInstructions } from '@project-serum/serum' import { CLIENT_TX_TIMEOUT, NOTIFICATION_POSITION_KEY, PREFERRED_EXPLORER_KEY, } from '../../utils/constants' import useLocalStorageState from 'hooks/useLocalStorageState' import { EXPLORERS } from 'pages/settings' const setMangoStore = mangoStore.getState().set const NotificationList = () => { const notifications = mangoStore((s) => s.notifications) const walletTokens = mangoStore((s) => s.wallet.tokens) const notEnoughSoLMessage = 'Not enough SOL' const [notificationPosition] = useLocalStorageState( NOTIFICATION_POSITION_KEY, 'bottom-left' ) const [mounted, setMounted] = useState(false) // 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 (notifications.length) { const customErrorNotification = notifications.find( (n) => n.description && n.description.includes('"Custom":1') ) const notEnoughSolNotification = notifications.find( (n) => n.title && n.title.includes(notEnoughSoLMessage) ) const solBalance = walletTokens.find((t) => t.mint.equals(TokenInstructions.WRAPPED_SOL_MINT) )?.uiAmount if ( !notEnoughSolNotification && customErrorNotification && solBalance && solBalance < 0.04 ) { notify({ title: notEnoughSoLMessage, type: 'info', }) } } }, [notifications, walletTokens]) const reversedNotifications = [...notifications].reverse() const position: string = useMemo(() => { switch (notificationPosition) { case 'bottom-left': return 'bottom-0 left-0' case 'bottom-right': return 'bottom-0 right-0' case 'top-left': return 'top-0 left-0' case 'top-right': return 'top-0 right-0' default: return 'bottom-0 left-0' } }, [notificationPosition]) useEffect(() => setMounted(true), []) if (!mounted) return null return (
{reversedNotifications.map((n) => ( ))}
) } const Notification = ({ notification }: { notification: Notification }) => { 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' } } // 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.notifications.map((n) => n.txid === txid && n.type === 'confirm' ? { ...n, show: false } : n ) s.notifications = newNotifications }) } }, [type, txid]) const hideNotification = () => { setMangoStore((s) => { const newNotifications = s.notifications.map((n) => n.id === id ? { ...n, show: false } : n ) s.notifications = newNotifications }) } // auto hide a notification after 8 seconds unless it is a confirming or time out notification // if no status is provided for a tx notification after 90s, hide it useEffect(() => { const id = setTimeout( () => { if (show) { hideNotification() } }, parsedTitle || type === 'confirm' ? CLIENT_TX_TIMEOUT : type === 'error' ? 30000 : 8000 ) return () => { clearInterval(id) } }) const { enterFromClass, enterToClass, leaveFromClass, leaveToClass, }: { enterFromClass: string enterToClass: string leaveFromClass: string leaveToClass: string } = useMemo(() => { 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 (notificationPosition) { case 'bottom-left': return fromLeft case 'bottom-right': return fromRight case 'top-left': return fromLeft case 'top-right': return fromRight default: return fromLeft } }, [notificationPosition]) return (
{type === 'success' ? ( ) : null} {type === 'info' && ( )} {type === 'error' && ( )} {type === 'confirm' && ( )}

{parsedTitle || title}

{description ? (

{description}

) : null} {txid ? ( ) : null}
) } export default NotificationList