'use client'; import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react'; import { useTranslations } from 'next-intl'; import { CheckCircle2, AlertTriangle, Info, X } from 'lucide-react'; type ToastKind = 'success' | 'error' | 'info'; interface Toast { id: number; kind: ToastKind; message: string; } interface ToastContextValue { toast: (message: string, kind?: ToastKind) => void; success: (message: string) => void; error: (message: string) => void; info: (message: string) => void; } const ToastContext = createContext(undefined); const KIND_CLASSES: Record = { success: 'bg-emerald-50 text-emerald-900 ring-emerald-200', error: 'bg-red-50 text-red-900 ring-red-200', info: 'bg-slate-50 text-slate-900 ring-slate-200', }; const KIND_ICON = { success: CheckCircle2, error: AlertTriangle, info: Info, } as const; export function ToastProvider({ children }: { children: React.ReactNode }) { const tCommon = useTranslations('common'); const [toasts, setToasts] = useState([]); const dismiss = useCallback((id: number) => { setToasts((prev) => prev.filter((t) => t.id !== id)); }, []); const toast = useCallback( (message: string, kind: ToastKind = 'info') => { const id = Date.now() + Math.random(); setToasts((prev) => [...prev, { id, kind, message }]); setTimeout(() => dismiss(id), 4500); }, [dismiss], ); const value = useMemo( () => ({ toast, success: (m) => toast(m, 'success'), error: (m) => toast(m, 'error'), info: (m) => toast(m, 'info'), }), [toast], ); return ( {children}
{toasts.map((t) => { const Icon = KIND_ICON[t.kind]; return (
{t.message}
); })}
); } export function useToast(): ToastContextValue { const ctx = useContext(ToastContext); if (!ctx) { // Graceful fallback in case a consumer renders outside the provider return { toast: () => undefined, success: () => undefined, error: () => undefined, info: () => undefined, }; } return ctx; } // Re-export for callers that want a hook-less ergonomic check. export function useToastMount() { const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); return mounted; }