2026-01-28 08:07:06 +08:00
|
|
|
import React, { useState, useEffect, useCallback, createContext, useContext } from 'react';
|
|
|
|
|
import { notificationService, Notification } from '../services/notificationService';
|
2026-01-27 00:29:00 +08:00
|
|
|
|
|
|
|
|
interface NotificationContextType {
|
|
|
|
|
notifications: Notification[];
|
|
|
|
|
unreadCount: number;
|
2026-01-28 08:07:06 +08:00
|
|
|
loading: boolean;
|
|
|
|
|
markAsRead: (id: number) => Promise<void>;
|
|
|
|
|
markAllAsRead: () => Promise<void>;
|
|
|
|
|
refreshNotifications: () => Promise<void>;
|
2026-01-27 00:29:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const NotificationContext = createContext<NotificationContextType | undefined>(undefined);
|
|
|
|
|
|
|
|
|
|
export const NotificationProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
|
|
|
const [notifications, setNotifications] = useState<Notification[]>([]);
|
2026-01-28 08:07:06 +08:00
|
|
|
const [unreadCount, setUnreadCount] = useState<number>(0);
|
|
|
|
|
const [loading, setLoading] = useState<boolean>(false);
|
|
|
|
|
|
|
|
|
|
const fetchNotifications = useCallback(async () => {
|
|
|
|
|
try {
|
|
|
|
|
// Fetch unread count
|
|
|
|
|
const countRes = await notificationService.getUnreadCount();
|
|
|
|
|
if (countRes.success) {
|
|
|
|
|
setUnreadCount(countRes.data.count);
|
|
|
|
|
}
|
2026-01-27 00:29:00 +08:00
|
|
|
|
2026-01-28 08:07:06 +08:00
|
|
|
// Fetch latest notifications (page 1, 10 items)
|
|
|
|
|
// You might want to load more on demand, but this is initial state
|
|
|
|
|
const listRes = await notificationService.getNotifications(1, 10);
|
|
|
|
|
if (listRes.success) {
|
|
|
|
|
setNotifications(listRes.data.notifications);
|
2026-01-27 00:29:00 +08:00
|
|
|
}
|
2026-01-28 08:07:06 +08:00
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to fetch notifications:', error);
|
2026-01-27 00:29:00 +08:00
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-01-28 08:07:06 +08:00
|
|
|
const refreshNotifications = useCallback(async () => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
await fetchNotifications();
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}, [fetchNotifications]);
|
|
|
|
|
|
|
|
|
|
// Initial fetch and polling
|
2026-01-27 00:29:00 +08:00
|
|
|
useEffect(() => {
|
2026-01-28 08:07:06 +08:00
|
|
|
fetchNotifications();
|
|
|
|
|
const interval = setInterval(fetchNotifications, 60000); // Poll every minute
|
|
|
|
|
return () => clearInterval(interval);
|
|
|
|
|
}, [fetchNotifications]);
|
|
|
|
|
|
|
|
|
|
const markAsRead = useCallback(async (id: number) => {
|
|
|
|
|
try {
|
|
|
|
|
await notificationService.markAsRead(id);
|
|
|
|
|
// Optimistic update
|
|
|
|
|
setNotifications(prev => prev.map(n => n.id === id ? { ...n, is_read: true } : n));
|
|
|
|
|
setUnreadCount(prev => Math.max(0, prev - 1));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to mark as read:', error);
|
2026-01-27 00:29:00 +08:00
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-01-28 08:07:06 +08:00
|
|
|
const markAllAsRead = useCallback(async () => {
|
|
|
|
|
try {
|
|
|
|
|
await notificationService.markAllAsRead();
|
|
|
|
|
// Optimistic update
|
|
|
|
|
setNotifications(prev => prev.map(n => ({ ...n, is_read: true })));
|
|
|
|
|
setUnreadCount(0);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to mark all as read:', error);
|
|
|
|
|
}
|
2026-01-27 00:29:00 +08:00
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<NotificationContext.Provider value={{
|
|
|
|
|
notifications,
|
|
|
|
|
unreadCount,
|
2026-01-28 08:07:06 +08:00
|
|
|
loading,
|
2026-01-27 00:29:00 +08:00
|
|
|
markAsRead,
|
|
|
|
|
markAllAsRead,
|
2026-01-28 08:07:06 +08:00
|
|
|
refreshNotifications
|
2026-01-27 00:29:00 +08:00
|
|
|
}}>
|
|
|
|
|
{children}
|
|
|
|
|
</NotificationContext.Provider>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useNotifications = () => {
|
|
|
|
|
const context = useContext(NotificationContext);
|
|
|
|
|
if (!context) {
|
|
|
|
|
throw new Error('useNotifications must be used within a NotificationProvider');
|
|
|
|
|
}
|
|
|
|
|
return context;
|
|
|
|
|
};
|