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) 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-gradient-to-r from-success-500/20 to-success-600/10', border: 'border-success-500/30', icon: 'text-success-400', iconBg: 'bg-success-500/20', }, error: { bg: 'bg-gradient-to-r from-error-500/20 to-error-600/10', border: 'border-error-500/30', icon: 'text-error-400', iconBg: 'bg-error-500/20', }, warning: { bg: 'bg-gradient-to-r from-warning-500/20 to-warning-600/10', border: 'border-warning-500/30', icon: 'text-warning-400', iconBg: 'bg-warning-500/20', }, info: { bg: 'bg-gradient-to-r from-accent-500/20 to-accent-600/10', border: 'border-accent-500/30', icon: 'text-accent-400', iconBg: 'bg-accent-500/20', }, } const style = typeStyles[toast.type || 'info'] const defaultIcons = { success: ( ), error: ( ), warning: ( ), info: ( ), } return (
{/* Glow effect */}
{/* Icon */}
{toast.icon || defaultIcons[toast.type || 'info']}
{/* Content */}
{toast.title && (

{toast.title}

)}

{toast.message}

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