diff --git a/apps/web/src/components/ToastProvider.tsx b/apps/web/src/components/ToastProvider.tsx new file mode 100644 index 0000000..1cf9ef9 --- /dev/null +++ b/apps/web/src/components/ToastProvider.tsx @@ -0,0 +1,64 @@ +import React, { createContext, useContext, useState, useCallback } from 'react'; +import Toast from './Toast'; + +export type ToastType = 'success' | 'error' | 'warning' | 'info'; + +export interface ToastMessage { + id: string; + type: ToastType; + message: string; + duration?: number; +} + +interface ToastContextType { + toasts: ToastMessage[]; + showToast: (type: ToastType, message: string, duration?: number) => void; + removeToast: (id: string) => void; +} + +const ToastContext = createContext(undefined); + +export function ToastProvider({ children }: { children: React.ReactNode }) { + const [toasts, setToasts] = useState([]); + + const showToast = useCallback((type: ToastType, message: string, duration = 5000) => { + const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + const toast: ToastMessage = { id, type, message, duration }; + + setToasts((prev) => [...prev, toast]); + + if (duration > 0) { + setTimeout(() => { + removeToast(id); + }, duration); + } + }, []); + + const removeToast = useCallback((id: string) => { + setToasts((prev) => prev.filter((toast) => toast.id !== id)); + }, []); + + return ( + + {children} +
+ {toasts.map((toast) => ( + removeToast(toast.id)} + /> + ))} +
+
+ ); +} + +export function useToast() { + const context = useContext(ToastContext); + if (!context) { + throw new Error('useToast must be used within ToastProvider'); + } + return context; +}