notification system implemented
need refinements
This commit is contained in:
1 parent
612259f020
commit
1143f8ca02
33 files changed
+2221
-222
No files matched your search
@@ -0,0 +1,200 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { useAuth } from "@clerk/clerk-expo";
|
||||
import {
|
||||
fetchNotifications,
|
||||
fetchUnreadCount,
|
||||
markAsRead,
|
||||
markAllAsRead,
|
||||
deleteNotification,
|
||||
type Notification,
|
||||
} from "../api/notifications";
|
||||
import log from "../utils/logger";
|
||||
|
||||
interface NotificationsContextType {
|
||||
notifications: Notification[];
|
||||
unreadCount: number;
|
||||
loading: boolean;
|
||||
refetchNotifications: () => Promise<void>;
|
||||
markNotificationAsRead: (id: string) => Promise<void>;
|
||||
markAllAsReadAction: () => Promise<void>;
|
||||
deleteNotificationAction: (id: string) => Promise<void>;
|
||||
}
|
||||
|
||||
const NotificationsContext = createContext<
|
||||
NotificationsContextType | undefined
|
||||
>(undefined);
|
||||
|
||||
export function NotificationsProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { getToken, isSignedIn } = useAuth();
|
||||
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||||
const [unreadCount, setUnreadCount] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const fetchInProgressRef = useRef(false);
|
||||
const lastFetchTimeRef = useRef(0);
|
||||
|
||||
const refetchNotifications = useCallback(async () => {
|
||||
if (!isSignedIn) return;
|
||||
|
||||
const now = Date.now();
|
||||
const timeSinceLastFetch = now - lastFetchTimeRef.current;
|
||||
|
||||
// Prevent duplicate concurrent fetches
|
||||
if (fetchInProgressRef.current) {
|
||||
log.debug("Skipping duplicate notification fetch (already in progress)");
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounce: prevent fetches within 1 second of the last fetch
|
||||
if (timeSinceLastFetch < 1000) {
|
||||
log.debug("Skipping duplicate notification fetch (debounced)", {
|
||||
timeSinceLastFetch: `${timeSinceLastFetch}ms`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
fetchInProgressRef.current = true;
|
||||
lastFetchTimeRef.current = now;
|
||||
setLoading(true);
|
||||
const token = await getToken();
|
||||
|
||||
// Fetch notifications and unread count in parallel
|
||||
const [notificationsData, count] = await Promise.all([
|
||||
fetchNotifications(token),
|
||||
fetchUnreadCount(token),
|
||||
]);
|
||||
|
||||
setNotifications(notificationsData);
|
||||
setUnreadCount(count);
|
||||
|
||||
log.debug("Notifications fetched", {
|
||||
total: notificationsData.length,
|
||||
unread: count,
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("Failed to fetch notifications", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
fetchInProgressRef.current = false;
|
||||
}
|
||||
}, [getToken, isSignedIn]);
|
||||
|
||||
const markNotificationAsRead = useCallback(
|
||||
async (id: string) => {
|
||||
try {
|
||||
const token = await getToken();
|
||||
await markAsRead(id, token);
|
||||
|
||||
// Update local state
|
||||
setNotifications((prev) =>
|
||||
prev.map((n) => (n.id === id ? { ...n, read: true } : n)),
|
||||
);
|
||||
|
||||
setUnreadCount((prev) => Math.max(0, prev - 1));
|
||||
|
||||
log.debug("Notification marked as read", { id });
|
||||
} catch (error) {
|
||||
log.error("Failed to mark notification as read", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[getToken],
|
||||
);
|
||||
|
||||
const markAllAsReadAction = useCallback(async () => {
|
||||
try {
|
||||
const token = await getToken();
|
||||
await markAllAsRead(token);
|
||||
|
||||
// Update local state
|
||||
setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
|
||||
setUnreadCount(0);
|
||||
|
||||
log.debug("All notifications marked as read");
|
||||
} catch (error) {
|
||||
log.error("Failed to mark all notifications as read", error);
|
||||
throw error;
|
||||
}
|
||||
}, [getToken]);
|
||||
|
||||
const deleteNotificationAction = useCallback(
|
||||
async (id: string) => {
|
||||
try {
|
||||
const token = await getToken();
|
||||
await deleteNotification(id, token);
|
||||
|
||||
// Update local state
|
||||
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));
|
||||
}
|
||||
|
||||
log.debug("Notification deleted", { id });
|
||||
} catch (error) {
|
||||
log.error("Failed to delete notification", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[getToken, notifications],
|
||||
);
|
||||
|
||||
// Initial fetch on mount
|
||||
useEffect(() => {
|
||||
if (isSignedIn) {
|
||||
refetchNotifications();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isSignedIn]); // Only run when sign-in state changes
|
||||
|
||||
// Periodic refresh every 30 seconds
|
||||
useEffect(() => {
|
||||
if (!isSignedIn) return;
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
refetchNotifications();
|
||||
}, 30000);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isSignedIn]); // Only run when sign-in state changes
|
||||
|
||||
const value: NotificationsContextType = {
|
||||
notifications,
|
||||
unreadCount,
|
||||
loading,
|
||||
refetchNotifications,
|
||||
markNotificationAsRead,
|
||||
markAllAsReadAction,
|
||||
deleteNotificationAction,
|
||||
};
|
||||
|
||||
return (
|
||||
<NotificationsContext.Provider value={value}>
|
||||
{children}
|
||||
</NotificationsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useNotifications() {
|
||||
const context = useContext(NotificationsContext);
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
"useNotifications must be used within a NotificationsProvider",
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in new issue
Block a user