'use client' /** * @file Full notifications list page (View all). */ import { useEffect, useState } from 'react' import Link from 'next/link' import { useAuth } from '@/lib/auth/context' import { listNotifications, markNotificationRead, markAllNotificationsRead, type Notification, } from '@/lib/api/notifications' import { getAuthErrorMessage } from '@ciphera-net/ui' import { formatTimeAgo, getTypeIcon } from '@/lib/utils/notifications' import { Button, ArrowLeftIcon } from '@ciphera-net/ui' import { NotificationsListSkeleton, useMinimumLoading } from '@/components/skeletons' import { toast } from '@ciphera-net/ui' const PAGE_SIZE = 50 export default function NotificationsPage() { const { user } = useAuth() const [notifications, setNotifications] = useState([]) const [unreadCount, setUnreadCount] = useState(0) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [offset, setOffset] = useState(0) const [hasMore, setHasMore] = useState(true) const [loadingMore, setLoadingMore] = useState(false) const showSkeleton = useMinimumLoading(loading) const fetchPage = async (pageOffset: number, append: boolean) => { if (append) setLoadingMore(true) else setLoading(true) setError(null) try { const res = await listNotifications({ limit: PAGE_SIZE, offset: pageOffset }) const list = Array.isArray(res?.notifications) ? res.notifications : [] setNotifications((prev) => (append ? [...prev, ...list] : list)) setUnreadCount(typeof res?.unread_count === 'number' ? res.unread_count : 0) setHasMore(list.length === PAGE_SIZE) } catch (err) { setError(getAuthErrorMessage(err as Error) || 'Failed to load notifications') setNotifications((prev) => (append ? prev : [])) } finally { setLoading(false) setLoadingMore(false) } } useEffect(() => { if (!user?.org_id) { setLoading(false) return } fetchPage(0, false) }, [user?.org_id]) const handleLoadMore = () => { const next = offset + PAGE_SIZE setOffset(next) fetchPage(next, true) } const handleMarkRead = async (id: string) => { try { await markNotificationRead(id) setNotifications((prev) => prev.map((n) => (n.id === id ? { ...n, read: true } : n))) setUnreadCount((c) => Math.max(0, c - 1)) } catch { // Ignore } } const handleMarkAllRead = async () => { try { await markAllNotificationsRead() setNotifications((prev) => prev.map((n) => ({ ...n, read: true }))) setUnreadCount(0) toast.success('All notifications marked as read') } catch (err) { toast.error(getAuthErrorMessage(err as Error) || 'Failed to mark all as read') } } const handleNotificationClick = (n: Notification) => { if (!n.read) handleMarkRead(n.id) } if (!user?.org_id) { return (

Switch to an organization to view notifications.

Go to workspace
) } return (
Back {unreadCount > 0 && ( )}

Notifications

Manage which notifications you receive in{' '} Organization Settings → Notifications

{showSkeleton ? ( ) : error ? (
{error}
) : notifications.length === 0 ? (

No notifications yet

Manage which notifications you receive in{' '} Organization Settings → Notifications

) : (
{notifications.map((n) => (
{n.link_url ? ( handleNotificationClick(n)} className={`block p-4 rounded-xl border border-neutral-200 dark:border-neutral-800 hover:bg-neutral-50 dark:hover:bg-neutral-800/50 transition-colors ${!n.read ? 'bg-brand-orange/5 dark:bg-brand-orange/10' : ''}`} >
{getTypeIcon(n.type)}

{n.title}

{n.body && (

{n.body}

)}

{formatTimeAgo(n.created_at)}

) : (
handleNotificationClick(n)} onKeyDown={(e) => e.key === 'Enter' && handleNotificationClick(n)} className={`block p-4 rounded-xl border border-neutral-200 dark:border-neutral-800 hover:bg-neutral-50 dark:hover:bg-neutral-800/50 cursor-pointer ${!n.read ? 'bg-brand-orange/5 dark:bg-brand-orange/10' : ''}`} >
{getTypeIcon(n.type)}

{n.title}

{n.body && (

{n.body}

)}

{formatTimeAgo(n.created_at)}

)}
))} {hasMore && (
)}
)}
) }