import { useState, createContext, SetStateAction, Dispatch, FC, ReactNode, useEffect, } from 'react' import { Provider as ToastProvider } from '@radix-ui/react-toast' import Toast, { ToastViewport } from 'components/primitives/Toast' import { useReservoirClient } from '@reservoir0x/reservoir-kit-ui' import { Anchor, Flex } from 'components/primitives' import { v4 as uuidv4 } from 'uuid' import { Execute } from '@reservoir0x/reservoir-sdk' import { useNetwork } from 'wagmi' type ToastType = { id?: string title?: string description?: string action?: ReactNode status?: 'success' | 'error' } type Step = Execute['steps'][0] export const ToastContext = createContext<{ toasts: Array setToasts: Dispatch>> | null addToast: ((toast: ToastType) => void) | null }>({ toasts: [], setToasts: null, addToast: null, }) const ToastContextProvider: FC = ({ children }) => { const [toasts, setToasts] = useState>([]) const { chains } = useNetwork() const addToast = (toast: ToastType) => { toast.id = uuidv4() setToasts([...toasts, toast]) } const client = useReservoirClient() useEffect(() => { let eventListener: any if (client) { eventListener = client.addEventListener((event, chainId) => { switch (event.name) { case 'purchase_error': addToast?.({ title: 'Purchase Failure', status: 'error', description: event.data.error.length < 100 ? event.data.error : 'Failed to complete purchase', }) break case 'accept_offer_error': addToast?.({ title: 'Failed to sell', status: 'error', description: event.data.error.length < 100 ? event.data.error : 'Failed to accept offer(s)', }) break case 'purchase_complete': case 'accept_offer_complete': const chain = chains.find((chain) => chain.id === chainId) const blockExplorerBaseUrl = chain?.blockExplorers?.default?.url || 'https://etherscan.io' const executableSteps: Execute['steps'] = event?.data?.steps.filter( (step: Step) => step.items && step.items.length > 0 ) let stepCount = executableSteps.length let currentStepItem: NonNullable[0] | undefined const currentStepIndex = executableSteps.findIndex((step: Step) => { currentStepItem = step.items?.find( (item) => item.status === 'incomplete' ) return currentStepItem }) const currentStep = currentStepIndex > -1 ? executableSteps[currentStepIndex] : executableSteps[stepCount - 1] const purchaseTxHashes = currentStep?.items?.reduce((txHashes, item) => { item.transfersData?.forEach((transferData) => { if (transferData.txHash) { txHashes.add(transferData.txHash) } }) return txHashes }, new Set()) || [] const totalPurchases = Array.from(purchaseTxHashes).length const failedPurchases = totalPurchases - (currentStep?.items?.length || 0) const action = event.name === 'accept_offer_complete' ? 'sold' : 'purchased' addToast?.({ title: failedPurchases ? `${totalPurchases} ${ totalPurchases > 1 ? 'items' : 'item' } ${action}, ${failedPurchases} ${ failedPurchases > 1 ? 'items' : 'item' } ${action}` : `${totalPurchases} ${ totalPurchases > 1 ? 'items' : 'item' } ${action}.`, status: failedPurchases ? 'error' : 'success', action: ( {currentStep.items?.map((item) => item.txHashes?.map((txHash) => { const formattedTxHash = txHash ? `${txHash.slice(0, 4)}...${txHash.slice(-4)}` : '' return ( View transaction: {formattedTxHash} ) }) )} ), }) break } }) } return () => { client?.removeEventListener(eventListener) } }, [client]) return ( {children} {toasts.map((toast, idx) => { return ( ) })} ) } export default ToastContextProvider