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 && (
+
+ )}