"use client"; import { formatDistanceToNow } from "date-fns"; import { AlertCircle, AtSign, Bell, Cable, CheckCheck, CheckCircle2, FileText, Loader2, } from "lucide-react"; import { useRouter } from "next/navigation"; import { convertRenderedToDisplay } from "@/components/chat-comments/comment-item/comment-item"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Separator } from "@/components/ui/separator"; import type { Notification, NotificationTypeEnum } from "@/hooks/use-notifications"; import { cn } from "@/lib/utils"; /** * Filter configuration for notification types */ const NOTIFICATION_FILTERS = { new_mention: { label: "Mentions", icon: AtSign }, connector_indexing: { label: "Connectors", icon: Cable }, document_processing: { label: "Documents", icon: FileText }, } as const; /** * Get initials from name or email for avatar fallback */ function getInitials(name: string | null | undefined, email: string | null | undefined): string { if (name) { return name .split(" ") .map((n) => n[0]) .join("") .toUpperCase() .slice(0, 2); } if (email) { const localPart = email.split("@")[0]; return localPart.slice(0, 2).toUpperCase(); } return "U"; } interface NotificationPopupProps { notifications: Notification[]; unreadCount: number; loading: boolean; markAsRead: (id: number) => Promise; markAllAsRead: () => Promise; onClose?: () => void; activeFilter: NotificationTypeEnum | null; onFilterChange: (filter: NotificationTypeEnum | null) => void; } export function NotificationPopup({ notifications, unreadCount, loading, markAsRead, markAllAsRead, onClose, activeFilter, onFilterChange, }: NotificationPopupProps) { const router = useRouter(); const handleMarkAllAsRead = async () => { await markAllAsRead(); }; const handleNotificationClick = async (notification: Notification) => { if (!notification.read) { await markAsRead(notification.id); } if (notification.type === "new_mention") { const metadata = notification.metadata as { thread_id?: number; comment_id?: number; }; const searchSpaceId = notification.search_space_id; const threadId = metadata?.thread_id; const commentId = metadata?.comment_id; if (searchSpaceId && threadId) { const url = commentId ? `/dashboard/${searchSpaceId}/new-chat/${threadId}?commentId=${commentId}` : `/dashboard/${searchSpaceId}/new-chat/${threadId}`; onClose?.(); router.push(url); } } }; const formatTime = (dateString: string) => { try { return formatDistanceToNow(new Date(dateString), { addSuffix: true }); } catch { return "Recently"; } }; const getStatusIcon = (notification: Notification) => { // For mentions, show the author's avatar with initials fallback if (notification.type === "new_mention") { const metadata = notification.metadata as { author_name?: string; author_avatar_url?: string | null; author_email?: string; }; const authorName = metadata?.author_name; const avatarUrl = metadata?.author_avatar_url; const authorEmail = metadata?.author_email; return ( {avatarUrl && } {getInitials(authorName, authorEmail)} ); } // For other notification types, show status icons const status = notification.metadata?.status as string | undefined; switch (status) { case "in_progress": return ; case "completed": return ; case "failed": return ; default: return ; } }; return (
{/* Header */}

Notifications

{unreadCount > 0 && ( )}
{/* Filter Pills */}
{( Object.entries(NOTIFICATION_FILTERS) as [ NotificationTypeEnum, (typeof NOTIFICATION_FILTERS)[keyof typeof NOTIFICATION_FILTERS], ][] ).map(([key, { label, icon: Icon }]) => { const isActive = activeFilter === key; return ( ); })}
{/* Notifications List */} {loading ? (
) : notifications.length === 0 ? (

No notifications

) : (
{notifications.map((notification, index) => (
{index < notifications.length - 1 && }
))}
)}
); }