'use client'; import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react'; import { useEffectiveDriverId } from '@/lib/currentDriver'; import { getNotificationRepository, getMarkNotificationReadUseCase, } from '@/lib/di-container'; import type { Notification } from '@gridpilot/notifications/application'; import ToastNotification from './ToastNotification'; import ModalNotification from './ModalNotification'; interface NotificationContextValue { notifications: Notification[]; unreadCount: number; toastNotifications: Notification[]; modalNotification: Notification | null; markAsRead: (notification: Notification) => Promise; dismissToast: (notification: Notification) => void; respondToModal: (notification: Notification, actionId?: string) => Promise; } 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 default function NotificationProvider({ children }: NotificationProviderProps) { const [notifications, setNotifications] = useState([]); const [toastNotifications, setToastNotifications] = useState([]); const [modalNotification, setModalNotification] = useState(null); const [seenNotificationIds, setSeenNotificationIds] = useState>(new Set()); const currentDriverId = useEffectiveDriverId(); // Poll for new notifications useEffect(() => { const loadNotifications = async () => { try { const repo = getNotificationRepository(); const allNotifications = await repo.findByRecipientId(currentDriverId); setNotifications(allNotifications); // Check for new notifications that need toast/modal display allNotifications.forEach((notification) => { // Check both unread and action_required status for modals const shouldDisplay = (notification.isUnread() || notification.isActionRequired()) && !seenNotificationIds.has(notification.id); if (shouldDisplay) { // Mark as seen to prevent duplicate displays setSeenNotificationIds((prev) => new Set([...prev, notification.id])); // Handle based on urgency if (notification.isModal()) { // Modal takes priority - show immediately setModalNotification(notification); } else if (notification.isToast()) { // Add to toast queue setToastNotifications((prev) => [...prev, notification]); } // Silent notifications just appear in the notification center } }); } catch (error) { console.error('Failed to load notifications:', error); } }; loadNotifications(); // Poll every 2 seconds for responsiveness const interval = setInterval(loadNotifications, 2000); return () => clearInterval(interval); }, [currentDriverId, seenNotificationIds]); // Prevent body scroll when modal is open useEffect(() => { if (modalNotification) { document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = ''; }; } }, [modalNotification]); const markAsRead = useCallback(async (notification: Notification) => { try { const markRead = getMarkNotificationReadUseCase(); await markRead.execute({ notificationId: notification.id, recipientId: currentDriverId, }); setNotifications((prev) => prev.map((n) => (n.id === notification.id ? n.markAsRead() : n)) ); } catch (error) { console.error('Failed to mark notification as read:', error); } }, [currentDriverId]); const dismissToast = useCallback((notification: Notification) => { setToastNotifications((prev) => prev.filter((n) => n.id !== notification.id)); }, []); const respondToModal = useCallback(async (notification: Notification, actionId?: string) => { try { // Mark as responded const repo = getNotificationRepository(); const updated = notification.markAsResponded(actionId); await repo.update(updated); // Update local state setNotifications((prev) => prev.map((n) => (n.id === notification.id ? updated : n)) ); // Clear modal setModalNotification(null); } catch (error) { console.error('Failed to respond to notification:', error); } }, []); const unreadCount = notifications.filter((n) => n.isUnread() || n.isActionRequired()).length; const value: NotificationContextValue = { notifications, unreadCount, toastNotifications, modalNotification, markAsRead, dismissToast, respondToModal, }; return ( {children} {/* Toast notifications container */}
{toastNotifications.map((notification) => ( ))}
{/* Modal notification */} {modalNotification && ( )}
); }