"use client";
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
CheckCircle,
XCircle,
AlertTriangle,
Info,
X,
} from 'lucide-react';
export type ToastType = 'success' | 'error' | 'warning' | 'info';
export interface Toast {
id: string;
type: ToastType;
title: string;
message: string;
duration?: number;
action?: {
label: string;
onClick: () => void;
};
}
interface ToastProps {
toast: Toast;
onRemove: (id: string) => void;
}
const ToastItem = ({ toast, onRemove }: ToastProps) => {
useEffect(() => {
if (toast.duration !== 0) {
const timer = setTimeout(() => {
setTimeout(() => onRemove(toast.id), 200);
}, toast.duration || 3000);
return () => clearTimeout(timer);
}
}, [toast.duration, toast.id, onRemove]);
const getIcon = () => {
switch (toast.type) {
case 'success':
return ;
case 'error':
return ;
case 'warning':
return ;
case 'info':
return ;
default:
return ;
}
};
const getColors = () => {
switch (toast.type) {
case 'success':
return 'bg-stone-50 border-green-300 text-green-900 shadow-md';
case 'error':
return 'bg-stone-50 border-red-200 text-red-800 shadow-md';
case 'warning':
return 'bg-stone-50 border-yellow-200 text-yellow-800 shadow-md';
case 'info':
return 'bg-stone-50 border-stone-200 text-stone-800 shadow-md';
default:
return 'bg-stone-50 border-stone-200 text-stone-800 shadow-md';
}
};
return (
{getIcon()}
{toast.title}
{toast.message}
{toast.action && (
)}
{/* Progress bar */}
{toast.duration !== 0 && (
)}
);
};
// Toast context and provider
import { createContext, useContext, useCallback } from 'react';
interface ToastContextType {
addToast: (toast: Omit) => void;
showToast: (toast: Omit) => void;
showSuccess: (title: string, message?: string) => void;
showError: (title: string, message?: string) => void;
showWarning: (title: string, message?: string) => void;
showInfo: (title: string, message?: string) => void;
showEmailSent: (email: string) => void;
showEmailError: (error: string) => void;
showProjectSaved: (title: string) => void;
showProjectDeleted: (title: string) => void;
showImportSuccess: (count: number) => void;
showImportError: (error: string) => void;
}
const ToastContext = createContext(undefined);
// No-op fallback for SSR or when outside provider
const noopToast: ToastContextType = {
addToast: () => {},
showToast: () => {},
showSuccess: () => {},
showError: () => {},
showWarning: () => {},
showInfo: () => {},
showEmailSent: () => {},
showEmailError: () => {},
showProjectSaved: () => {},
showProjectDeleted: () => {},
showImportSuccess: () => {},
showImportError: () => {},
};
export const useToast = () => {
const context = useContext(ToastContext);
// Return no-op fallback during SSR or if used outside provider
if (!context) {
return noopToast;
}
return context;
};
export const ToastProvider = ({ children }: { children: React.ReactNode }) => {
const [toasts, setToasts] = useState([]);
const addToast = useCallback((toast: Omit) => {
const id = Math.random().toString(36).substr(2, 9);
const newToast = { ...toast, id };
setToasts(prev => [...prev, newToast]);
}, []);
const removeToast = useCallback((id: string) => {
setToasts(prev => prev.filter(toast => toast.id !== id));
}, []);
const showToast = useCallback((toast: Omit) => {
addToast(toast);
}, [addToast]);
const showSuccess = useCallback((title: string, message?: string) => {
addToast({
type: 'success',
title,
message: message || '',
duration: 4000
});
}, [addToast]);
const showError = useCallback((title: string, message?: string) => {
addToast({
type: 'error',
title,
message: message || '',
duration: 4000 // Shorter duration
});
}, [addToast]);
const showWarning = useCallback((title: string, message?: string) => {
addToast({
type: 'warning',
title,
message: message || '',
duration: 5000
});
}, [addToast]);
const showInfo = useCallback((title: string, message?: string) => {
addToast({
type: 'info',
title,
message: message || '',
duration: 4000
});
}, [addToast]);
const showEmailSent = useCallback((email: string) => {
addToast({
type: 'success',
title: 'E-Mail gesendet! 📧',
message: `Deine Nachricht an ${email} wurde erfolgreich versendet.`,
duration: 5000,
});
}, [addToast]);
const showEmailError = useCallback((error: string) => {
addToast({
type: 'error',
title: 'E-Mail Fehler! ❌',
message: `Fehler beim Senden: ${error}`,
duration: 8000
});
}, [addToast]);
const showProjectSaved = useCallback((title: string) => {
addToast({
type: 'success',
title: 'Projekt gespeichert! 💾',
message: `"${title}" wurde erfolgreich in der Datenbank gespeichert.`,
duration: 4000,
});
}, [addToast]);
const showProjectDeleted = useCallback((title: string) => {
addToast({
type: 'warning',
title: 'Projekt gelöscht! 🗑️',
message: `"${title}" wurde aus der Datenbank entfernt.`,
duration: 4000,
});
}, [addToast]);
const showImportSuccess = useCallback((count: number) => {
addToast({
type: 'success',
title: 'Import erfolgreich! 📥',
message: `${count} Projekte wurden erfolgreich importiert.`,
duration: 5000,
});
}, [addToast]);
const showImportError = useCallback((error: string) => {
addToast({
type: 'error',
title: 'Import Fehler! ❌',
message: `Fehler beim Importieren: ${error}`,
duration: 8000,
});
}, [addToast]);
const contextValue: ToastContextType = {
addToast,
showToast,
showSuccess,
showError,
showWarning,
showInfo,
showEmailSent,
showEmailError,
showProjectSaved,
showProjectDeleted,
showImportSuccess,
showImportError
};
return (
{children}
{/* Toast Container */}
{toasts.map((toast) => (
))}
);
};
export { ToastItem as Toast };
export default ToastItem;