"use client"; import { useState, useEffect, useCallback } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { Shield, TrendingUp, AlertTriangle, Sparkles, DollarSign, X, CheckCheck, Bell, RefreshCw } from "lucide-react"; import { notificationsApi, NotificationItem } from "@/lib/api"; import { useThemeStore } from "@/lib/stores/themeStore"; // ─── Type config ────────────────────────────────────────────────────────────── const typeConfig: Record = { alert: { icon: Shield, color: "text-red-500", darkBg: "bg-red-500/10 border-red-500/20", lightBg: "bg-red-50 border-red-200/70" }, insight: { icon: Sparkles, color: "text-blue-500", darkBg: "bg-blue-500/10 border-blue-500/20", lightBg: "bg-blue-50 border-blue-200/70" }, warning: { icon: AlertTriangle, color: "text-amber-500", darkBg: "bg-amber-500/10 border-amber-500/20", lightBg: "bg-amber-50 border-amber-200/70" }, success: { icon: TrendingUp, color: "text-emerald-500", darkBg: "bg-emerald-500/10 border-emerald-500/20", lightBg: "bg-emerald-50 border-emerald-200/70" }, default: { icon: DollarSign, color: "text-slate-500", darkBg: "bg-zinc-500/10 border-zinc-500/20", lightBg: "bg-slate-50 border-slate-200/70" }, }; function getTypeConfig(type: string) { return typeConfig[type] || typeConfig.default; } function timeAgo(dateStr: string): string { const diff = Date.now() - new Date(dateStr).getTime(); const mins = Math.floor(diff / 60000); if (mins < 1) return "Just now"; if (mins < 60) return `${mins}m ago`; const hrs = Math.floor(mins / 60); if (hrs < 24) return `${hrs}h ago`; return `${Math.floor(hrs / 24)}d ago`; } // ─── Notification Item ──────────────────────────────────────────────────────── function NotifItem({ notification, onDismiss, onRead, isLight, }: { notification: NotificationItem; onDismiss: (id: string) => void; onRead: (id: string) => void; isLight: boolean; }) { const config = getTypeConfig(notification.type); const Icon = config.icon; const bgClass = isLight ? config.lightBg : config.darkBg; return ( onRead(notification.id)} className={`relative flex gap-3 rounded-xl border p-3 cursor-pointer transition-all group ${bgClass} ${ !notification.read ? "ring-1 ring-inset" : "opacity-60" }`} > {!notification.read && (
)}

{notification.title}

{notification.message}

{timeAgo(notification.created_at)}

); } // ─── Notification Panel ─────────────────────────────────────────────────────── export function NotificationPanel({ isOpen, onClose, }: { isOpen: boolean; onClose: () => void; }) { const { theme } = useThemeStore(); const isLight = theme === "light"; const [notifications, setNotifications] = useState([]); const [unreadCount, setUnreadCount] = useState(0); const [filter, setFilter] = useState<"all" | "unread">("all"); const [loading, setLoading] = useState(false); const loadNotifications = useCallback(async () => { setLoading(true); try { const res = await notificationsApi.list(); setNotifications(res.notifications); setUnreadCount(res.unread_count); } catch { // backend offline — keep empty state } finally { setLoading(false); } }, []); useEffect(() => { if (isOpen) loadNotifications(); }, [isOpen, loadNotifications]); const dismiss = async (id: string) => { setNotifications((prev) => prev.filter((n) => n.id !== id)); try { await notificationsApi.dismiss(id); } catch { /* ignore */ } }; const markRead = async (id: string) => { setNotifications((prev) => prev.map((n) => n.id === id ? { ...n, read: true } : n)); setUnreadCount((c) => Math.max(0, c - 1)); try { await notificationsApi.markRead(id); } catch { /* ignore */ } }; const markAllRead = async () => { setNotifications((prev) => prev.map((n) => ({ ...n, read: true }))); setUnreadCount(0); try { await notificationsApi.markAllRead(); } catch { /* ignore */ } }; const displayed = filter === "unread" ? notifications.filter((n) => !n.read) : notifications; return ( {isOpen && ( <> {/* Header */}
Notifications {unreadCount > 0 && ( {unreadCount} )}
{unreadCount > 0 && ( )}
{/* Filter tabs */}
{(["all", "unread"] as const).map((f) => ( ))}
{/* List */}
{loading ? (
) : displayed.length === 0 ? (

No notifications

) : ( displayed.map((n) => ( )) )}

Real-time · AI-powered · Encrypted

)}
); } // ─── Notification Bell ──────────────────────────────────────────────────────── export function NotificationBell() { const { theme } = useThemeStore(); const isLight = theme === "light"; const [isOpen, setIsOpen] = useState(false); const [unreadCount, setUnreadCount] = useState(0); useEffect(() => { notificationsApi.list().then((res) => setUnreadCount(res.unread_count)).catch(() => {}); }, []); return (
setIsOpen((v) => !v)} className={`relative rounded-xl p-2.5 transition-all duration-200 focus:outline-none ${ isLight ? "bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800" : "bg-white/5 text-zinc-400 hover:text-white hover:bg-white/10" }`} > setIsOpen(false)} />
); }