import { createContext, useContext, useState, useCallback, useRef, useEffect, ReactNode, } from 'react'; interface ToastOptions { type?: 'success' | 'error' | 'info' | 'warning'; message: string; title?: string; icon?: ReactNode; duration?: number; onClick?: () => void; } interface Toast extends ToastOptions { id: number; } interface ToastContextType { showToast: (options: ToastOptions) => void; } const ToastContext = createContext(null); // eslint-disable-next-line react-refresh/only-export-components export function useToast() { const context = useContext(ToastContext); if (!context) { throw new Error('useToast must be used within ToastProvider'); } return context; } export function ToastProvider({ children }: { children: ReactNode }) { const [toasts, setToasts] = useState([]); const timersRef = useRef>>(new Map()); const showToast = useCallback((options: ToastOptions) => { const id = Date.now() + Math.random(); // Avoid ID collision const toast: Toast = { id, duration: 5000, type: 'info', ...options }; setToasts((prev) => [...prev, toast]); const timer = setTimeout(() => { setToasts((prev) => prev.filter((t) => t.id !== id)); timersRef.current.delete(id); }, toast.duration); timersRef.current.set(id, timer); }, []); const removeToast = useCallback((id: number) => { // Clear timer when manually removing const timer = timersRef.current.get(id); if (timer) { clearTimeout(timer); timersRef.current.delete(id); } setToasts((prev) => prev.filter((t) => t.id !== id)); }, []); // Cleanup all timers on unmount useEffect(() => { const timers = timersRef.current; return () => { timers.forEach((timer) => clearTimeout(timer)); timers.clear(); }; }, []); return ( {children} {/* Toast Container */}
{toasts.map((toast) => ( removeToast(toast.id)} /> ))}
); } function ToastItem({ toast, onClose }: { toast: Toast; onClose: () => void }) { const handleClick = () => { if (toast.onClick) { toast.onClick(); onClose(); } }; const typeStyles = { success: { bg: 'bg-dark-800', accent: 'bg-gradient-to-r from-success-500/50 to-transparent', border: 'border-success-500/70', icon: 'text-success-400', iconBg: 'bg-success-500/30', }, error: { bg: 'bg-dark-800', accent: 'bg-gradient-to-r from-error-500/50 to-transparent', border: 'border-error-500/70', icon: 'text-error-400', iconBg: 'bg-error-500/30', }, warning: { bg: 'bg-dark-800', accent: 'bg-gradient-to-r from-warning-500/50 to-transparent', border: 'border-warning-500/70', icon: 'text-warning-400', iconBg: 'bg-warning-500/30', }, info: { bg: 'bg-dark-800', accent: 'bg-gradient-to-r from-accent-500/50 to-transparent', border: 'border-accent-500/70', icon: 'text-accent-400', iconBg: 'bg-accent-500/30', }, }; const style = typeStyles[toast.type || 'info']; const defaultIcons = { success: ( ), error: ( ), warning: ( ), info: ( ), }; return (
{/* Color accent */}
{/* Icon */}
{toast.icon || defaultIcons[toast.type || 'info']}
{/* Content */}
{toast.title && (

{toast.title}

)}

{toast.message}

{/* Close button */}
{/* Progress bar */}
); }