"use client" import { useState, useEffect, useRef } from "react" import { Bell, X, CheckCheck, AlertCircle, CreditCard, FileText, Wrench, Info } from "lucide-react" import { formatDate } from "@/lib/utils" interface Notification { id: string type: string subject: string read: boolean sent_at: string metadata?: { body?: string } & Record } const typeIcon: Record = { rent_due: CreditCard, rent_overdue: AlertCircle, lease_expiry: FileText, maintenance_update: Wrench, general: Info, } const typeColor: Record = { rent_due: "text-amber-400 bg-amber-500/10", rent_overdue: "text-red-400 bg-red-500/10", lease_expiry: "text-orange-400 bg-orange-500/10", maintenance_update: "text-blue-400 bg-blue-500/10", general: "text-white/40 bg-white/5", } export function NotificationsBell() { const [open, setOpen] = useState(false) const [notifs, setNotifs] = useState([]) const [loading, setLoading] = useState(false) const ref = useRef(null) const unread = notifs.filter(n => !n.read).length async function load() { setLoading(true) try { const res = await fetch("/api/notifications") if (res.ok) setNotifs(await res.json()) } finally { setLoading(false) } } async function markAllRead() { const prev = [...notifs] setNotifs(n => n.map(x => ({ ...x, read: true }))) try { const res = await fetch("/api/notifications/read", { method: "PATCH" }) if (!res.ok) throw new Error() } catch { setNotifs(prev) // rollback on failure } } useEffect(() => { load() }, []) useEffect(() => { function onOutside(e: MouseEvent) { if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false) } document.addEventListener("mousedown", onOutside) return () => document.removeEventListener("mousedown", onOutside) }, []) return (
{open && (
{/* Header */}
Notifications {unread > 0 && ( {unread} )}
{unread > 0 && ( )}
{/* List */}
{loading ? (
Loading…
) : notifs.length === 0 ? (

No notifications yet

) : (
{notifs.map((n) => { const Icon = typeIcon[n.type] ?? Info const color = typeColor[n.type] ?? typeColor.general return (

{n.subject}

{typeof n.metadata?.body === "string" && n.metadata.body && (

{n.metadata.body}

)}

{formatDate(n.sent_at)}

{!n.read &&
}
) })}
)}
)}
) }