'use client'; import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { v4 as uuid } from 'uuid'; import { ModalNotification } from './ModalNotification'; import { ToastNotification } from './ToastNotification'; import { Box } from '@/ui/Box'; import { Stack } from '@/ui/Stack'; import type { Notification, NotificationAction, NotificationVariant } from './notificationTypes'; interface AddNotificationInput { id?: string; type: string; title?: string; message: string; createdAt?: Date; variant?: NotificationVariant; actionUrl?: string; requiresResponse?: boolean; actions?: NotificationAction[]; data?: Record; } interface NotificationContextValue { notifications: Notification[]; unreadCount: number; addNotification: (input: AddNotificationInput) => string; dismissNotification: (id: string) => void; clearNotifications: () => void; markAsRead: (id: string) => void; markAllAsRead: () => void; } const NotificationContext = createContext(null); export function useNotifications() { const context = useContext(NotificationContext); if (!context) { throw new Error('useNotifications must be used within NotificationProvider'); } return context; } interface NotificationProviderProps { children: ReactNode; } export function NotificationProvider({ children }: NotificationProviderProps) { const [notifications, setNotifications] = useState([]); const addNotification = useCallback((input: AddNotificationInput): string => { const id = input.id ?? uuid(); const notification: Notification = { id, type: input.type, title: input.title, message: input.message, createdAt: input.createdAt ?? new Date(), variant: input.variant ?? 'toast', actionUrl: input.actionUrl, requiresResponse: input.requiresResponse, actions: input.actions, data: input.data, read: false, }; setNotifications((prev) => [notification, ...prev]); return id; }, []); const dismissNotification = useCallback((id: string) => { setNotifications((prev) => prev.filter((notification) => notification.id !== id)); }, []); const clearNotifications = useCallback(() => { setNotifications([]); }, []); const markAsRead = useCallback((id: string) => { setNotifications((prev) => prev.map((notification) => notification.id === id ? { ...notification, read: true } : notification, ), ); }, []); const markAllAsRead = useCallback(() => { setNotifications((prev) => prev.map((notification) => ({ ...notification, read: true }))); }, []); const unreadCount = useMemo( () => notifications.filter((notification) => !notification.read).length, [notifications], ); const modalNotification = useMemo( () => notifications.find((notification) => notification.variant === 'modal' && !notification.read) ?? null, [notifications], ); const toastNotifications = useMemo( () => notifications.filter((notification) => notification.variant === 'toast' && !notification.read), [notifications], ); useEffect(() => { if (!modalNotification) { return; } const previousOverflow = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = previousOverflow; }; }, [modalNotification]); const value: NotificationContextValue = { notifications, unreadCount, addNotification, dismissNotification, clearNotifications, markAsRead, markAllAsRead, }; return ( {children} {/* Toast notifications container */} {toastNotifications.map((notification) => ( dismissNotification(notification.id)} onRead={() => markAsRead(notification.id)} /> ))} {/* Modal notification */} {modalNotification && ( { // For now we just mark as read and optionally navigate via ModalNotification markAsRead(notification.id); if (actionId === 'dismiss') { dismissNotification(notification.id); } }} onDismiss={(notification: Notification) => { markAsRead(notification.id); dismissNotification(notification.id); }} /> )} ); }