NeahStable/hooks/use-notifications.ts
2026-01-11 23:01:31 +01:00

172 lines
5.6 KiB
TypeScript

import { useState, useEffect, useCallback, useRef } from 'react';
import { useSession } from 'next-auth/react';
import { Notification, NotificationCount } from '@/lib/types/notification';
import { useUnifiedRefresh } from './use-unified-refresh';
import { REFRESH_INTERVALS } from '@/lib/constants/refresh-intervals';
import { requestDeduplicator } from '@/lib/utils/request-deduplication';
// Default empty notification count
const defaultNotificationCount: NotificationCount = {
total: 0,
unread: 0,
sources: {}
};
export function useNotifications() {
const { data: session, status } = useSession();
const [notifications, setNotifications] = useState<Notification[]>([]);
const [notificationCount, setNotificationCount] = useState<NotificationCount>(defaultNotificationCount);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const isMountedRef = useRef<boolean>(false);
// Fetch notification count with request deduplication
const fetchNotificationCount = useCallback(async (force = false) => {
if (!session?.user || !isMountedRef.current) return;
try {
setError(null);
console.log('[useNotifications] Fetching notification count', { force });
// Use request deduplication to prevent duplicate calls
const requestKey = `notifications-count-${session.user.id}`;
const url = force
? `/api/notifications/count?_t=${Date.now()}`
: '/api/notifications/count';
const data = await requestDeduplicator.execute(
requestKey,
async () => {
const response = await fetch(url, {
credentials: 'include',
cache: force ? 'no-store' : 'default',
});
if (!response.ok) {
const errorText = await response.text();
console.error('Failed to fetch notification count:', {
status: response.status,
body: errorText
});
throw new Error(errorText || 'Failed to fetch notification count');
}
return response.json();
},
2000 // 2 second deduplication window
);
if (isMountedRef.current) {
console.log('[useNotifications] Received notification count:', data);
setNotificationCount(data);
}
} catch (err: any) {
console.error('Error fetching notification count:', err);
if (isMountedRef.current) {
setError(err.message || 'Failed to fetch notification count');
}
}
}, [session?.user]);
// Fetch notifications with request deduplication
const fetchNotifications = useCallback(async (page = 1, limit = 20) => {
if (!session?.user || !isMountedRef.current) return;
setLoading(true);
setError(null);
try {
console.log('[useNotifications] Fetching notifications', { page, limit });
// Use request deduplication to prevent duplicate calls
const requestKey = `notifications-${session.user.id}-${page}-${limit}`;
const data = await requestDeduplicator.execute(
requestKey,
async () => {
const response = await fetch(`/api/notifications?page=${page}&limit=${limit}`, {
credentials: 'include'
});
if (!response.ok) {
const errorText = await response.text();
console.error('Failed to fetch notifications:', {
status: response.status,
body: errorText
});
throw new Error(errorText || 'Failed to fetch notifications');
}
return response.json();
},
2000 // 2 second deduplication window
);
if (isMountedRef.current) {
setNotifications(data.notifications);
}
} catch (err: any) {
console.error('Error fetching notifications:', err);
if (isMountedRef.current) {
setError(err.message || 'Failed to fetch notifications');
}
} finally {
if (isMountedRef.current) {
setLoading(false);
}
}
}, [session?.user]);
// Use unified refresh system for notification count
const { refresh: refreshCount } = useUnifiedRefresh({
resource: 'notifications-count',
interval: REFRESH_INTERVALS.NOTIFICATIONS_COUNT,
enabled: status === 'authenticated',
onRefresh: async () => {
await fetchNotificationCount(true); // Force refresh to bypass cache
},
priority: 'high',
});
// Listen for custom events to trigger immediate refresh
useEffect(() => {
if (status !== 'authenticated') return;
const handleNotificationTrigger = () => {
console.log('[useNotifications] Received notification trigger event');
fetchNotificationCount(true);
};
// Listen for custom event from widgets
window.addEventListener('trigger-notification-refresh', handleNotificationTrigger);
return () => {
window.removeEventListener('trigger-notification-refresh', handleNotificationTrigger);
};
}, [status, fetchNotificationCount]);
// Initialize fetching on component mount and cleanup on unmount
useEffect(() => {
isMountedRef.current = true;
if (status === 'authenticated' && session?.user) {
// Initial fetches
fetchNotificationCount(true);
fetchNotifications();
}
return () => {
isMountedRef.current = false;
};
}, [status, session?.user, fetchNotificationCount, fetchNotifications]);
return {
notifications,
notificationCount,
loading,
error,
fetchNotifications,
fetchNotificationCount: () => fetchNotificationCount(true),
};
}