101 lines
2.7 KiB
TypeScript
101 lines
2.7 KiB
TypeScript
"use client";
|
|
|
|
import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from "react";
|
|
import { useAuth } from "@/hooks/use-auth";
|
|
import { fetchAPI, putAPI } from "@/lib/api";
|
|
import type { NotificationItem, PageResponse } from "@/lib/types";
|
|
|
|
interface NotificationContextType {
|
|
unreadCount: number;
|
|
notifications: NotificationItem[];
|
|
markRead: (id: number) => Promise<void>;
|
|
markAllRead: () => Promise<void>;
|
|
refresh: () => void;
|
|
}
|
|
|
|
const NotificationContext = createContext<NotificationContextType>({
|
|
unreadCount: 0,
|
|
notifications: [],
|
|
markRead: async () => {},
|
|
markAllRead: async () => {},
|
|
refresh: () => {},
|
|
});
|
|
|
|
export function NotificationProvider({ children }: { children: ReactNode }) {
|
|
const { user } = useAuth();
|
|
const [unreadCount, setUnreadCount] = useState(0);
|
|
const [notifications, setNotifications] = useState<NotificationItem[]>([]);
|
|
|
|
const fetchUnreadCount = useCallback(async () => {
|
|
if (!user) return;
|
|
try {
|
|
const res = await fetchAPI<{ count: number }>("/api/notifications/unread-count");
|
|
setUnreadCount(res.count);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}, [user]);
|
|
|
|
const fetchNotifications = useCallback(async () => {
|
|
if (!user) return;
|
|
try {
|
|
const res = await fetchAPI<PageResponse<NotificationItem>>("/api/notifications/", { page_size: "10" });
|
|
setNotifications(res.items || []);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}, [user]);
|
|
|
|
const refresh = useCallback(() => {
|
|
void fetchUnreadCount();
|
|
void fetchNotifications();
|
|
}, [fetchUnreadCount, fetchNotifications]);
|
|
|
|
useEffect(() => {
|
|
if (!user) return;
|
|
|
|
const initialRefresh = window.setTimeout(() => {
|
|
void fetchUnreadCount();
|
|
}, 0);
|
|
const interval = setInterval(fetchUnreadCount, 30000);
|
|
return () => {
|
|
window.clearTimeout(initialRefresh);
|
|
clearInterval(interval);
|
|
};
|
|
}, [user, fetchUnreadCount]);
|
|
|
|
const markRead = useCallback(async (id: number) => {
|
|
try {
|
|
await putAPI(`/api/notifications/${id}/read`);
|
|
setNotifications((prev) =>
|
|
prev.map((n) => (n.id === id ? { ...n, is_read: true } : n))
|
|
);
|
|
setUnreadCount((c) => Math.max(0, c - 1));
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}, []);
|
|
|
|
const markAllRead = useCallback(async () => {
|
|
try {
|
|
await putAPI("/api/notifications/read-all");
|
|
setNotifications((prev) => prev.map((n) => ({ ...n, is_read: true })));
|
|
setUnreadCount(0);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}, []);
|
|
|
|
return (
|
|
<NotificationContext.Provider
|
|
value={{ unreadCount, notifications, markRead, markAllRead, refresh }}
|
|
>
|
|
{children}
|
|
</NotificationContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useNotifications() {
|
|
return useContext(NotificationContext);
|
|
}
|