"use client"; import { useEffect, useRef, useState } from "react"; import { Link } from "@/i18n/navigation"; import { apiFetch } from "@/app/lib/api-client"; interface NotificationPreview { id: string; title: string; body: string; category: string; icon?: string; read: boolean; muted?: boolean; created?: string; actionUrl?: string; } const CATEGORY_LABELS: Record = { system: "系统", meetup: "活动", gig: "赏金", match: "匹配", service: "服务", community: "社区", city: "城市", }; function formatTime(value?: string): string { if (!value) return "刚刚"; const date = new Date(value); if (Number.isNaN(date.getTime())) return "刚刚"; const diff = Date.now() - date.getTime(); const minutes = Math.floor(diff / 60000); if (minutes < 1) return "刚刚"; if (minutes < 60) return `${minutes} 分钟前`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours} 小时前`; return `${date.getMonth() + 1}月${date.getDate()}日`; } export default function NotificationBell() { const [open, setOpen] = useState(false); const [unread, setUnread] = useState(0); const [items, setItems] = useState([]); const ref = useRef(null); const load = () => { apiFetch<{ items: NotificationPreview[]; unread: number }>("/api/notifications?status=active&muted=exclude") .then((data) => { setItems((data.items || []).slice(0, 5)); setUnread(Number(data.unread || 0)); }) .catch(() => { setItems([]); setUnread(0); }); }; useEffect(() => { load(); const timer = window.setInterval(load, 30000); return () => window.clearInterval(timer); }, []); useEffect(() => { if (!open) return; const onClick = (event: MouseEvent) => { if (ref.current && !ref.current.contains(event.target as Node)) setOpen(false); }; document.addEventListener("mousedown", onClick); return () => document.removeEventListener("mousedown", onClick); }, [open]); const markAllRead = async () => { await apiFetch("/api/notifications/mark-all-read", { method: "POST" }); load(); }; const markRead = (id: string) => { apiFetch(`/api/notifications/${id}/read`, { method: "POST" }) .then(load) .catch(() => undefined); }; return (
{open && (

站内消息

{unread} 条未读

{items.map((item) => ( { markRead(item.id); setOpen(false); }} className="block border-b border-gray-100 px-4 py-3 transition-colors hover:bg-gray-50 dark:border-gray-800 dark:hover:bg-gray-800" >
{item.icon || "🔔"}

{item.title}

{!item.read && }

{item.body}

{CATEGORY_LABELS[item.category] || item.category} · {formatTime(item.created)}

))}
{items.length === 0 && (
📭

暂时没有新消息

)} setOpen(false)} className="block bg-gray-50 px-4 py-3 text-center text-sm font-semibold text-gray-800 hover:bg-gray-100 dark:bg-gray-950 dark:text-gray-200 dark:hover:bg-gray-800" > 打开消息中心 →
)}
); }