Notifications

This commit is contained in:
2026-02-01 20:54:13 -06:00
parent 6c02bd5448
commit 48e0884bf7
14 changed files with 1628 additions and 7 deletions

View File

@@ -0,0 +1,183 @@
import { useState, useEffect, useCallback, useRef } from 'react';
import * as notificationsApi from '../api/notifications';
import type { Notification, NotificationFilters } from '../api/notifications';
interface UseNotificationsReturn {
notifications: Notification[];
unreadCount: number;
loading: boolean;
error: string | null;
hasMore: boolean;
page: number;
fetchNotifications: (filters?: NotificationFilters) => Promise<void>;
fetchMore: () => Promise<void>;
refreshUnreadCount: () => Promise<void>;
markAsRead: (id: string) => Promise<void>;
markAllAsRead: () => Promise<void>;
deleteNotification: (id: string) => Promise<void>;
refresh: () => Promise<void>;
}
/**
* Custom hook for managing notifications
* @param autoRefreshInterval - Interval in milliseconds to auto-refresh unread count (default: 30000ms)
* @returns Object with notifications data and methods
*/
export function useNotifications(autoRefreshInterval: number = 30000): UseNotificationsReturn {
const [notifications, setNotifications] = useState<Notification[]>([]);
const [unreadCount, setUnreadCount] = useState<number>(0);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [hasMore, setHasMore] = useState<boolean>(false);
const [page, setPage] = useState<number>(1);
const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null);
const fetchNotifications = useCallback(async (filters?: NotificationFilters) => {
try {
setLoading(true);
setError(null);
const response = await notificationsApi.fetchNotifications({
page: 1,
limit: 20,
...filters,
});
setNotifications(response.data);
setHasMore(response.pagination.hasNextPage);
setPage(response.pagination.page);
} catch (err) {
console.error('Error fetching notifications:', err);
setError(err instanceof Error ? err.message : 'Failed to fetch notifications');
} finally {
setLoading(false);
}
}, []);
const fetchMore = useCallback(async () => {
if (!hasMore || loading) return;
try {
setLoading(true);
const response = await notificationsApi.fetchNotifications({
page: page + 1,
limit: 20,
});
setNotifications(prev => [...prev, ...response.data]);
setHasMore(response.pagination.hasNextPage);
setPage(response.pagination.page);
} catch (err) {
console.error('Error fetching more notifications:', err);
setError(err instanceof Error ? err.message : 'Failed to fetch more notifications');
} finally {
setLoading(false);
}
}, [hasMore, loading, page]);
const refreshUnreadCount = useCallback(async () => {
try {
const count = await notificationsApi.getUnreadCount();
setUnreadCount(count);
} catch (err) {
console.error('Error fetching unread count:', err);
}
}, []);
const markAsRead = useCallback(async (id: string) => {
try {
await notificationsApi.markAsRead(id);
setNotifications(prev =>
prev.map(notification =>
notification.id === id
? { ...notification, is_read: true, read_at: new Date().toISOString() }
: notification
)
);
await refreshUnreadCount();
} catch (err) {
console.error('Error marking notification as read:', err);
throw err;
}
}, [refreshUnreadCount]);
const markAllAsRead = useCallback(async () => {
try {
await notificationsApi.markAllAsRead();
setNotifications(prev =>
prev.map(notification => ({
...notification,
is_read: true,
read_at: new Date().toISOString(),
}))
);
setUnreadCount(0);
} catch (err) {
console.error('Error marking all notifications as read:', err);
throw err;
}
}, []);
const deleteNotification = useCallback(async (id: string) => {
try {
await notificationsApi.deleteNotification(id);
const deletedNotification = notifications.find(n => n.id === id);
setNotifications(prev => prev.filter(notification => notification.id !== id));
if (deletedNotification && !deletedNotification.is_read) {
setUnreadCount(prev => Math.max(0, prev - 1));
}
} catch (err) {
console.error('Error deleting notification:', err);
throw err;
}
}, [notifications]);
const refresh = useCallback(async () => {
await Promise.all([
fetchNotifications(),
refreshUnreadCount(),
]);
}, [fetchNotifications, refreshUnreadCount]);
useEffect(() => {
refreshUnreadCount();
if (autoRefreshInterval > 0) {
refreshIntervalRef.current = setInterval(() => {
refreshUnreadCount();
}, autoRefreshInterval);
}
return () => {
if (refreshIntervalRef.current) {
clearInterval(refreshIntervalRef.current);
}
};
}, [autoRefreshInterval, refreshUnreadCount]);
return {
notifications,
unreadCount,
loading,
error,
hasMore,
page,
fetchNotifications,
fetchMore,
refreshUnreadCount,
markAsRead,
markAllAsRead,
deleteNotification,
refresh,
};
}