diff --git a/frontend/src/components/ToastContainer.tsx b/frontend/src/components/ToastContainer.tsx index 2598b9e..331f37b 100644 --- a/frontend/src/components/ToastContainer.tsx +++ b/frontend/src/components/ToastContainer.tsx @@ -2,12 +2,18 @@ import { useEffect, useState } from 'react' import { CheckCircle, XCircle, Info, AlertCircle, X } from 'lucide-react' import clsx from 'clsx' +export interface ToastAction { + label: string + onClick: () => void +} + export interface Toast { id: string type: 'success' | 'error' | 'info' | 'warning' title: string message?: string duration?: number + action?: ToastAction } // Global toast state (in production, use Zustand or Context) @@ -15,22 +21,34 @@ let toastListeners: ((toasts: Toast[]) => void)[] = [] let toastList: Toast[] = [] export const toast = { - success: (title: string, message?: string) => addToast('success', title, message), - error: (title: string, message?: string) => addToast('error', title, message), - info: (title: string, message?: string) => addToast('info', title, message), - warning: (title: string, message?: string) => addToast('warning', title, message), + success: (title: string, message?: string, action?: ToastAction) => + addToast('success', title, message, 5000, action), + error: (title: string, message?: string, action?: ToastAction) => + addToast('error', title, message, 5000, action), + info: (title: string, message?: string, action?: ToastAction) => + addToast('info', title, message, 5000, action), + warning: (title: string, message?: string, action?: ToastAction) => + addToast('warning', title, message, 5000, action), } -function addToast(type: Toast['type'], title: string, message?: string, duration = 5000) { - const id = Date.now().toString() - const newToast: Toast = { id, type, title, message, duration } +function addToast( + type: Toast['type'], + title: string, + message?: string, + duration = 5000, + action?: ToastAction +) { + const id = Date.now().toString() + Math.random().toString(36).slice(2, 6) + const newToast: Toast = { id, type, title, message, duration, action } toastList = [...toastList, newToast] toastListeners.forEach(listener => listener(toastList)) - - // Auto-remove after duration + + // Auto-remove after duration. Toasts with an action get a longer window + // so the user has time to actually click Undo. + const removeAfter = action ? Math.max(duration, 8000) : duration setTimeout(() => { removeToast(id) - }, duration) + }, removeAfter) } function removeToast(id: string) { @@ -82,6 +100,17 @@ export function ToastContainer() {
{toast.message}
)} + {toast.action && ( + + )}