import { useState, useEffect, useCallback, useRef } from 'react'; import { notificationsApi, Notification } from '@/lib/api/notifications'; const POLL_INTERVAL = 30000; // 30 seconds const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes interface UseNotificationsReturn { notifications: Notification[]; unreadCount: number; loading: boolean; error: string | null; refresh: () => Promise; markAsRead: (notificationId: string) => Promise; markAllAsRead: () => Promise; dismiss: (notificationId: string) => Promise; } export function useNotifications(options?: { limit?: number; autoRefresh?: boolean; }): UseNotificationsReturn { const { limit = 10, autoRefresh = true } = options || {}; const [notifications, setNotifications] = useState([]); const [unreadCount, setUnreadCount] = useState(0); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const lastFetchRef = useRef(0); const pollIntervalRef = useRef(null); const fetchNotifications = useCallback(async (force = false) => { const now = Date.now(); // Use cache if data is fresh and not forced if (!force && now - lastFetchRef.current < CACHE_DURATION) { return; } try { setError(null); const response = await notificationsApi.getNotifications({ limit, includeRead: false, // Only fetch unread notifications }); setNotifications(response.notifications); setUnreadCount(response.unreadCount); lastFetchRef.current = now; } catch (err: any) { console.error('❌ Failed to fetch notifications:', err); setError(err.response?.data?.message || 'Failed to load notifications'); } finally { setLoading(false); } }, [limit]); const refresh = useCallback(async () => { setLoading(true); await fetchNotifications(true); }, [fetchNotifications]); const markAsRead = useCallback(async (notificationId: string) => { try { // Optimistic update setNotifications((prev) => prev.map((n) => (n.id === notificationId ? { ...n, isRead: true, readAt: new Date().toISOString() } : n)) ); setUnreadCount((prev) => Math.max(0, prev - 1)); // API call await notificationsApi.markAsRead(notificationId); } catch (err: any) { console.error('❌ Failed to mark notification as read:', err); // Revert optimistic update on error await refresh(); } }, [refresh]); const markAllAsRead = useCallback(async () => { try { // Optimistic update setNotifications((prev) => prev.map((n) => ({ ...n, isRead: true, readAt: new Date().toISOString() })) ); setUnreadCount(0); // API call await notificationsApi.markAllAsRead(); } catch (err: any) { console.error('❌ Failed to mark all notifications as read:', err); // Revert optimistic update on error await refresh(); } }, [refresh]); const dismiss = useCallback(async (notificationId: string) => { try { // Optimistic update setNotifications((prev) => prev.filter((n) => n.id !== notificationId)); setUnreadCount((prev) => { const notification = notifications.find((n) => n.id === notificationId); return notification && !notification.isRead ? Math.max(0, prev - 1) : prev; }); // API call await notificationsApi.dismiss(notificationId); } catch (err: any) { console.error('❌ Failed to dismiss notification:', err); // Revert optimistic update on error await refresh(); } }, [notifications, refresh]); // Initial fetch useEffect(() => { fetchNotifications(true); }, [fetchNotifications]); // Setup polling useEffect(() => { if (!autoRefresh) return; pollIntervalRef.current = setInterval(() => { fetchNotifications(false); }, POLL_INTERVAL); return () => { if (pollIntervalRef.current) { clearInterval(pollIntervalRef.current); } }; }, [autoRefresh, fetchNotifications]); // Refresh on tab focus useEffect(() => { const handleVisibilityChange = () => { if (!document.hidden) { fetchNotifications(false); } }; document.addEventListener('visibilitychange', handleVisibilityChange); return () => { document.removeEventListener('visibilitychange', handleVisibilityChange); }; }, [fetchNotifications]); return { notifications, unreadCount, loading, error, refresh, markAsRead, markAllAsRead, dismiss, }; }