66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { createContext, useContext, useState, useCallback, ReactNode } from 'react';
|
||
|
|
import NotificationToast from './NotificationToast';
|
||
|
|
|
||
|
|
export type NotificationType = 'success' | 'error' | 'info' | 'warning';
|
||
|
|
|
||
|
|
export interface Notification {
|
||
|
|
id: string;
|
||
|
|
type: NotificationType;
|
||
|
|
message: string;
|
||
|
|
duration?: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface NotificationContextType {
|
||
|
|
showNotification: (type: NotificationType, message: string, duration?: number) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
const NotificationContext = createContext<NotificationContextType | undefined>(undefined);
|
||
|
|
|
||
|
|
export function useNotification() {
|
||
|
|
const context = useContext(NotificationContext);
|
||
|
|
if (!context) {
|
||
|
|
throw new Error('useNotification must be used within NotificationProvider');
|
||
|
|
}
|
||
|
|
return context;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function NotificationProvider({ children }: { children: ReactNode }) {
|
||
|
|
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||
|
|
|
||
|
|
const showNotification = useCallback(
|
||
|
|
(type: NotificationType, message: string, duration = 4000) => {
|
||
|
|
const id = Math.random().toString(36).substring(2, 9);
|
||
|
|
const notification: Notification = { id, type, message, duration };
|
||
|
|
|
||
|
|
setNotifications((prev) => [...prev, notification]);
|
||
|
|
|
||
|
|
// Auto-remove after duration
|
||
|
|
setTimeout(() => {
|
||
|
|
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
||
|
|
}, duration);
|
||
|
|
},
|
||
|
|
[]
|
||
|
|
);
|
||
|
|
|
||
|
|
const removeNotification = useCallback((id: string) => {
|
||
|
|
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<NotificationContext.Provider value={{ showNotification }}>
|
||
|
|
{children}
|
||
|
|
<div className="fixed top-20 right-4 z-50 flex flex-col gap-2 max-w-md w-full">
|
||
|
|
{notifications.map((notification) => (
|
||
|
|
<NotificationToast
|
||
|
|
key={notification.id}
|
||
|
|
notification={notification}
|
||
|
|
onClose={() => removeNotification(notification.id)}
|
||
|
|
/>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</NotificationContext.Provider>
|
||
|
|
);
|
||
|
|
}
|