| // src/context/NotificationContext.tsx | |
| import { createContext, useContext, useEffect, useState, useCallback, ReactNode } from "react"; | |
| import { useAuth } from "./AuthContext"; | |
| import { useWebSocket } from "./WebSocketContext"; | |
| import { useToast } from "./ToastContext"; | |
| import api from "@/lib/api"; | |
| // ============================================ | |
| // Types | |
| // ============================================ | |
| export interface Notification { | |
| id: number; | |
| type: "like" | "comment" | "follow" | "mention" | "system" | "alert" | "reminder"; | |
| title: string; | |
| body: string; | |
| read: boolean; | |
| createdAt: string; | |
| link?: string; | |
| actorUsername?: string; | |
| actorAvatar?: string; | |
| metadata?: Record<string, any>; | |
| } | |
| interface NotificationContextValue { | |
| notifications: Notification[]; | |
| unreadCount: number; | |
| loading: boolean; | |
| fetchNotifications: () => Promise<void>; | |
| markAsRead: (id: number) => Promise<void>; | |
| markAllAsRead: () => Promise<void>; | |
| deleteNotification: (id: number) => Promise<void>; | |
| clearAll: () => Promise<void>; | |
| hasMore: boolean; | |
| loadMore: () => Promise<void>; | |
| refresh: () => Promise<void>; | |
| } | |
| // ============================================ | |
| // Constants | |
| // ============================================ | |
| const PAGE_SIZE = 20; | |
| // ============================================ | |
| // Context | |
| // ============================================ | |
| const NotificationContext = createContext<NotificationContextValue | null>(null); | |
| export function NotificationProvider({ children }: { children: ReactNode }) { | |
| const { user } = useAuth(); | |
| const { subscribe } = useWebSocket(); | |
| const toast = useToast(); | |
| const [notifications, setNotifications] = useState<Notification[]>([]); | |
| const [unreadCount, setUnreadCount] = useState(0); | |
| const [loading, setLoading] = useState(false); | |
| const [hasMore, setHasMore] = useState(true); | |
| const [page, setPage] = useState(0); | |
| // ============================================ | |
| // Subscribe to WebSocket Notifications | |
| // ============================================ | |
| useEffect(() => { | |
| if (!user) return; | |
| const unsubscribe = subscribe("notification", (data: Notification) => { | |
| setNotifications(prev => [data, ...prev]); | |
| if (!data.read) setUnreadCount(prev => prev + 1); | |
| toast.info(`${data.title}${data.body ? `: ${data.body}` : ""}`, { autoClose: 5000 }); | |
| }); | |
| return unsubscribe; | |
| }, [user, subscribe, toast]); | |
| // ============================================ | |
| // Fetch Notifications on User Login | |
| // ============================================ | |
| useEffect(() => { | |
| if (user) { | |
| fetchNotifications(); | |
| } else { | |
| setNotifications([]); | |
| setUnreadCount(0); | |
| setHasMore(true); | |
| setPage(0); | |
| } | |
| }, [user]); | |
| // ============================================ | |
| // Fetch Notifications | |
| // ============================================ | |
| // src/context/NotificationContext.tsx - Fixed notification fetch | |
| const fetchNotifications = useCallback(async () => { | |
| if (!user) return; | |
| setLoading(true); | |
| try { | |
| // ✅ Try multiple endpoints | |
| let response; | |
| try { | |
| response = await api.get("/notifications", { | |
| params: { page: 0, size: PAGE_SIZE } | |
| }); | |
| } catch { | |
| // Fallback endpoint | |
| response = await api.get("/notifications/recent"); | |
| } | |
| const data = response.data; | |
| const list = data.notifications || data.content || data.result || []; | |
| setNotifications(list); | |
| setUnreadCount(data.unreadCount || 0); | |
| setHasMore(list.length === PAGE_SIZE); | |
| setPage(0); | |
| } catch (error) { | |
| // ✅ Silent fail - use empty array | |
| console.warn("Notifications endpoint not available, using empty data"); | |
| setNotifications([]); | |
| setUnreadCount(0); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }, [user]); // ============================================ | |
| // Load More | |
| // ============================================ | |
| const loadMore = useCallback(async () => { | |
| if (!user || !hasMore || loading) return; | |
| setLoading(true); | |
| try { | |
| const nextPage = page + 1; | |
| const response = await api.get("/notifications", { | |
| params: { page: nextPage, size: PAGE_SIZE } | |
| }); | |
| const data = response.data; | |
| const list = data.notifications || data.content || []; | |
| setNotifications(prev => [...prev, ...list]); | |
| setHasMore(list.length === PAGE_SIZE); | |
| setPage(nextPage); | |
| } catch (error) { | |
| console.error("Failed to load more notifications:", error); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }, [user, hasMore, loading, page]); | |
| // ============================================ | |
| // Mark as Read | |
| // ============================================ | |
| const markAsRead = useCallback(async (id: number) => { | |
| try { | |
| await api.put(`/notifications/${id}/read`); | |
| setNotifications(prev => | |
| prev.map(n => (n.id === id ? { ...n, read: true } : n)) | |
| ); | |
| setUnreadCount(prev => Math.max(0, prev - 1)); | |
| } catch (error) { | |
| console.error("Failed to mark notification as read:", error); | |
| } | |
| }, []); | |
| // ============================================ | |
| // Mark All as Read | |
| // ============================================ | |
| const markAllAsRead = useCallback(async () => { | |
| try { | |
| await api.put("/notifications/read-all"); | |
| setNotifications(prev => prev.map(n => ({ ...n, read: true }))); | |
| setUnreadCount(0); | |
| toast.success("All notifications marked as read"); | |
| } catch (error) { | |
| console.error("Failed to mark all as read:", error); | |
| toast.error("Could not mark all as read"); | |
| } | |
| }, [toast]); | |
| // ============================================ | |
| // Delete Notification | |
| // ============================================ | |
| const deleteNotification = useCallback(async (id: number) => { | |
| try { | |
| await api.delete(`/notifications/${id}`); | |
| const wasUnread = notifications.find(n => n.id === id)?.read === false; | |
| setNotifications(prev => prev.filter(n => n.id !== id)); | |
| if (wasUnread) setUnreadCount(prev => Math.max(0, prev - 1)); | |
| toast.success("Notification deleted"); | |
| } catch (error) { | |
| console.error("Failed to delete notification:", error); | |
| toast.error("Could not delete notification"); | |
| } | |
| }, [notifications, toast]); | |
| // ============================================ | |
| // Clear All | |
| // ============================================ | |
| const clearAll = useCallback(async () => { | |
| try { | |
| await api.delete("/notifications/delete-all"); | |
| setNotifications([]); | |
| setUnreadCount(0); | |
| toast.success("All notifications cleared"); | |
| } catch (error) { | |
| console.error("Failed to clear notifications:", error); | |
| toast.error("Could not clear notifications"); | |
| } | |
| }, [toast]); | |
| const refresh = useCallback(async () => { | |
| await fetchNotifications(); | |
| }, [fetchNotifications]); | |
| // ============================================ | |
| // Value | |
| // ============================================ | |
| const value: NotificationContextValue = { | |
| notifications, | |
| unreadCount, | |
| loading, | |
| fetchNotifications, | |
| markAsRead, | |
| markAllAsRead, | |
| deleteNotification, | |
| clearAll, | |
| hasMore, | |
| loadMore, | |
| refresh, | |
| }; | |
| return ( | |
| <NotificationContext.Provider value={value}> | |
| {children} | |
| </NotificationContext.Provider> | |
| ); | |
| } | |
| export const useNotifications = () => { | |
| const ctx = useContext(NotificationContext); | |
| if (!ctx) throw new Error("useNotifications must be used within NotificationProvider"); | |
| return ctx; | |
| }; |
Xet Storage Details
- Size:
- 7.54 kB
- Xet hash:
- e439cdd35195d1454603922aa4616112a7b108371fb498c00fda5492d11f9afb
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.