Notifications
This commit is contained in:
183
src/hooks/useNotifications.ts
Normal file
183
src/hooks/useNotifications.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user