tahamajs/IE / Projects /front /src /context /NotificationContext.tsx
tahamajs's picture
download
raw
7.54 kB
// 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.