"use client"; import { useCallback } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { api } from "@/lib/api"; export interface Notification { id: string; userId: string; type: string; title: string; message?: string; metadata?: Record; read: boolean; cleared: boolean; createdAt: string; } export interface DownloadHistoryItem { id: string; subject: string; type: string; status: string; error?: string; createdAt: string; updatedAt: string; completedAt?: string; metadata?: Record; } /** * Hook for managing notifications using React Query as single source of truth. * All components using this hook share the same cache and update together. */ export function useNotifications() { const queryClient = useQueryClient(); // Single source of truth - React Query cache const { data: notifications = [], isLoading, error, refetch, } = useQuery({ queryKey: ["notifications"], queryFn: () => api.get("/notifications"), refetchInterval: 30000, }); // Derive unread count from data (computed, not stored) const unreadCount = notifications.filter((n) => !n.read).length; // Mark as read mutation with optimistic update const markAsReadMutation = useMutation({ mutationFn: (id: string) => api.post(`/notifications/${id}/read`), onMutate: async (id: string) => { await queryClient.cancelQueries({ queryKey: ["notifications"] }); const previous = queryClient.getQueryData(["notifications"]); queryClient.setQueryData(["notifications"], (old) => old?.map((n) => (n.id === id ? { ...n, read: true } : n)) || [] ); return { previous }; }, onError: (_err, _id, context) => { if (context?.previous) { queryClient.setQueryData(["notifications"], context.previous); } }, }); // Mark all as read mutation with optimistic update const markAllAsReadMutation = useMutation({ mutationFn: () => api.post("/notifications/read-all"), onMutate: async () => { await queryClient.cancelQueries({ queryKey: ["notifications"] }); const previous = queryClient.getQueryData(["notifications"]); queryClient.setQueryData(["notifications"], (old) => old?.map((n) => ({ ...n, read: true })) || [] ); return { previous }; }, onError: (_err, _vars, context) => { if (context?.previous) { queryClient.setQueryData(["notifications"], context.previous); } }, }); // Clear notification mutation with optimistic update const clearMutation = useMutation({ mutationFn: (id: string) => api.post(`/notifications/${id}/clear`), onMutate: async (id: string) => { await queryClient.cancelQueries({ queryKey: ["notifications"] }); const previous = queryClient.getQueryData(["notifications"]); queryClient.setQueryData(["notifications"], (old) => old?.filter((n) => n.id !== id) || [] ); return { previous }; }, onError: (_err, _id, context) => { if (context?.previous) { queryClient.setQueryData(["notifications"], context.previous); } }, }); // Clear all mutation with optimistic update const clearAllMutation = useMutation({ mutationFn: () => api.post("/notifications/clear-all"), onMutate: async () => { await queryClient.cancelQueries({ queryKey: ["notifications"] }); const previous = queryClient.getQueryData(["notifications"]); queryClient.setQueryData(["notifications"], []); return { previous }; }, onError: (_err, _vars, context) => { if (context?.previous) { queryClient.setQueryData(["notifications"], context.previous); } }, }); return { notifications, unreadCount, isLoading, error: error instanceof Error ? error.message : null, refetch, markAsRead: (id: string) => markAsReadMutation.mutate(id), markAllAsRead: () => markAllAsReadMutation.mutate(), clearNotification: (id: string) => clearMutation.mutate(id), clearAll: () => clearAllMutation.mutate(), }; } /** * Hook for download history - unchanged from original */ export function useDownloadHistory() { const fetchHistory = useCallback(async () => { return api.get("/notifications/downloads/history"); }, []); const { data: history = [], isLoading, error, refetch, } = useQuery({ queryKey: ["download-history"], queryFn: fetchHistory, refetchInterval: 10000, }); const queryClient = useQueryClient(); const clearDownload = useCallback(async (id: string) => { try { await api.post(`/notifications/downloads/${id}/clear`); queryClient.setQueryData( ["download-history"], (old) => old?.filter((d) => d.id !== id) || [] ); } catch (err: unknown) { console.error("Failed to clear download:", err); } }, [queryClient]); const clearAll = useCallback(async () => { try { await api.post("/notifications/downloads/clear-all"); queryClient.setQueryData(["download-history"], []); } catch (err: unknown) { console.error("Failed to clear all:", err); } }, [queryClient]); const retryDownload = useCallback(async (id: string) => { try { await api.post(`/notifications/downloads/${id}/retry`); queryClient.setQueryData( ["download-history"], (old) => old?.filter((d) => d.id !== id) || [] ); } catch (err: unknown) { console.error("Failed to retry download:", err); } }, [queryClient]); return { history, isLoading, error: error instanceof Error ? error.message : null, refetch, clearDownload, clearAll, retryDownload, }; } /** * Hook for active downloads - unchanged from original */ export function useActiveDownloads() { const fetchDownloads = useCallback(async () => { return api.get("/notifications/downloads/active"); }, []); const { data: downloads = [], isLoading, error, refetch, } = useQuery({ queryKey: ["active-downloads"], queryFn: fetchDownloads, refetchInterval: 3000, }); return { downloads, isLoading, error: error instanceof Error ? error.message : null, refetch, }; }