diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 1c78aeb0..05eb400f 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,4 +1,5 @@
import React, { Suspense } from 'react';
+import { ToastProvider } from './components/ui';
import { Routes, Route } from 'react-router-dom';
import { AuthGuard } from './components/auth/AuthGuard';
@@ -23,7 +24,8 @@ function PageLoader() {
export default function App() {
return (
- }>
+
+ }>
} />
} />
@@ -52,3 +54,4 @@ export default function App() {
);
}
+
diff --git a/frontend/src/components/ui/Toast.tsx b/frontend/src/components/ui/Toast.tsx
new file mode 100644
index 00000000..34c6db20
--- /dev/null
+++ b/frontend/src/components/ui/Toast.tsx
@@ -0,0 +1,97 @@
+import React, { createContext, useContext, useState, useCallback } from 'react';
+import { AnimatePresence, motion } from 'framer-motion';
+import { X, CheckCircle, AlertCircle, AlertTriangle, Info } from 'lucide-react';
+
+type ToastType = 'success' | 'error' | 'warning' | 'info';
+
+interface Toast {
+ id: string;
+ type: ToastType;
+ message: string;
+}
+
+interface ToastContextValue {
+ showToast: (type: ToastType, message: string) => void;
+}
+
+const ToastContext = createContext({ showToast: () => {} });
+
+export const useToast = () => useContext(ToastContext);
+
+const TOAST_STYLES: Record = {
+ success: {
+ bg: 'bg-emerald-950/90',
+ border: 'border-emerald-500',
+ icon:
+ },
+ error: {
+ bg: 'bg-red-950/90',
+ border: 'border-red-500',
+ icon:
+ },
+ warning: {
+ bg: 'bg-amber-950/90',
+ border: 'border-amber-500',
+ icon:
+ },
+ info: {
+ bg: 'bg-blue-950/90',
+ border: 'border-blue-500',
+ icon:
+ }
+};
+
+function ToastItem({ toast, onRemove }: { toast: Toast; onRemove: (id: string) => void }) {
+ const { bg, border, icon } = TOAST_STYLES[toast.type];
+
+ React.useEffect(() => {
+ const timer = setTimeout(() => onRemove(toast.id), 5000);
+ return () => clearTimeout(timer);
+ }, [toast.id, onRemove]);
+
+ return (
+
+ {icon}
+ {toast.message}
+
+
+ );
+}
+
+export function ToastProvider({ children }: { children: React.ReactNode }) {
+ const [toasts, setToasts] = useState([]);
+
+ const showToast = useCallback((type: ToastType, message: string) => {
+ const id = Date.now().toString() + Math.random().toString(36);
+ setToasts(prev => [...prev, { id, type, message }]);
+ }, []);
+
+ const removeToast = useCallback((id: string) => {
+ setToasts(prev => prev.filter(t => t.id !== id));
+ }, []);
+
+ return (
+
+ {children}
+
+
+ {toasts.map(toast => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts
new file mode 100644
index 00000000..90446338
--- /dev/null
+++ b/frontend/src/components/ui/index.ts
@@ -0,0 +1 @@
+export { ToastProvider, useToast } from './Toast';