diff --git a/frontend/src/hooks/useNotifications.ts b/frontend/src/hooks/useNotifications.ts index 23589fa..f8b47b6 100644 --- a/frontend/src/hooks/useNotifications.ts +++ b/frontend/src/hooks/useNotifications.ts @@ -1,65 +1,63 @@ -import { useState, useEffect, useCallback, useRef } from 'react' +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { useCallback } from 'react' import api from '@/services/api' import type { Notification, NotificationList } from '@/types' -const POLL_INTERVAL = 60_000 // 60 seconds +const NOTIFICATIONS_KEY = ['notifications'] as const + +async function fetchNotifications(): Promise { + const res = await api.get('/notifications') + return res.data +} export function useNotifications() { - const [notifications, setNotifications] = useState([]) - const [unreadCount, setUnreadCount] = useState(0) - const [loading, setLoading] = useState(true) - const intervalRef = useRef | null>(null) + const queryClient = useQueryClient() - const fetchNotifications = useCallback(async () => { - try { - const res = await api.get('/notifications') - setNotifications(res.data.items) - setUnreadCount(res.data.unread_count) - } catch { - // Silently fail for polling - } finally { - setLoading(false) - } - }, []) + const { data, isLoading } = useQuery({ + queryKey: NOTIFICATIONS_KEY, + queryFn: fetchNotifications, + refetchInterval: 60_000, + }) - useEffect(() => { - fetchNotifications() - intervalRef.current = setInterval(fetchNotifications, POLL_INTERVAL) - return () => { - if (intervalRef.current) { - clearInterval(intervalRef.current) - } - } - }, [fetchNotifications]) - - const markAsRead = useCallback(async (id: number) => { - try { + const markAsReadMutation = useMutation({ + mutationFn: async (id: number) => { await api.put(`/notifications/${id}/read`) - setNotifications((prev) => - prev.map((n) => (n.id === id ? { ...n, is_read: true } : n)) - ) - setUnreadCount((prev) => Math.max(0, prev - 1)) - } catch { - // ignore - } - }, []) + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: NOTIFICATIONS_KEY }) + }, + }) - const markAllAsRead = useCallback(async () => { - try { + const markAllAsReadMutation = useMutation({ + mutationFn: async () => { await api.put<{ marked_read: number }>('/notifications/read-all') - setNotifications((prev) => prev.map((n) => ({ ...n, is_read: true }))) - setUnreadCount(0) - } catch { - // ignore - } - }, []) + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: NOTIFICATIONS_KEY }) + }, + }) + + const markAsRead = useCallback( + (id: number) => { + markAsReadMutation.mutate(id) + }, + [markAsReadMutation], + ) + + const markAllAsRead = useCallback(() => { + markAllAsReadMutation.mutate() + }, [markAllAsReadMutation]) + + const refresh = useCallback(() => { + queryClient.invalidateQueries({ queryKey: NOTIFICATIONS_KEY }) + }, [queryClient]) return { - notifications, - unreadCount, - loading, + notifications: data?.items ?? [], + unreadCount: data?.unread_count ?? 0, + loading: isLoading, markAsRead, markAllAsRead, - refresh: fetchNotifications, + refresh, } }