/** * NotificationList Component * Displays a list of notifications with infinite scroll */ import React, { useState, useEffect } from 'react'; import { NotificationItem } from './NotificationItem'; interface Notification { id: string; type: string; title: string; message: string; actionUrl?: string; read: boolean; createdAt: string; } interface NotificationListProps { userId?: string; onNotificationRead?: () => void; onAllRead?: () => void; compact?: boolean; showFilters?: boolean; } export function NotificationList({ userId = 'demo-user', onNotificationRead, onAllRead, compact = false, showFilters = false }: NotificationListProps) { const [notifications, setNotifications] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [filter, setFilter] = useState<'all' | 'unread'>('all'); const [hasMore, setHasMore] = useState(false); const [offset, setOffset] = useState(0); const limit = compact ? 5 : 20; useEffect(() => { fetchNotifications(true); }, [userId, filter]); async function fetchNotifications(reset = false) { try { setIsLoading(true); const currentOffset = reset ? 0 : offset; const unreadOnly = filter === 'unread'; const response = await fetch( `/api/notifications?userId=${userId}&limit=${limit}&offset=${currentOffset}&unreadOnly=${unreadOnly}` ); const data = await response.json(); if (data.success) { if (reset) { setNotifications(data.data.notifications); } else { setNotifications(prev => [...prev, ...data.data.notifications]); } setHasMore(data.data.pagination.hasMore); setOffset(currentOffset + limit); } else { setError(data.error); } } catch (err: any) { setError(err.message); } finally { setIsLoading(false); } } async function handleMarkAsRead(notificationId: string) { try { const response = await fetch(`/api/notifications/${notificationId}?userId=${userId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ read: true, userId }) }); if (response.ok) { setNotifications(prev => prev.map(n => (n.id === notificationId ? { ...n, read: true } : n)) ); onNotificationRead?.(); } } catch (error) { console.error('Failed to mark as read:', error); } } async function handleMarkAllAsRead() { try { const response = await fetch('/api/notifications/read-all', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId }) }); if (response.ok) { setNotifications(prev => prev.map(n => ({ ...n, read: true }))); onAllRead?.(); } } catch (error) { console.error('Failed to mark all as read:', error); } } async function handleDelete(notificationId: string) { try { const response = await fetch(`/api/notifications/${notificationId}?userId=${userId}`, { method: 'DELETE' }); if (response.ok) { setNotifications(prev => prev.filter(n => n.id !== notificationId)); } } catch (error) { console.error('Failed to delete notification:', error); } } if (error) { return (

Failed to load notifications

); } return (
{showFilters && (
)} {isLoading && notifications.length === 0 ? (

Loading notifications...

) : notifications.length === 0 ? (

No notifications yet

) : (
{notifications.map(notification => ( ))}
)} {hasMore && !isLoading && (
)} {isLoading && notifications.length > 0 && (
)}
); }