"use client"; import { useState, useEffect, useCallback } from "react"; import { Bell, Zap, BookOpen, Crown, ShoppingBag, MessageCircle, Trophy, Users, X, CheckCheck, } from "lucide-react"; interface NotificationItem { id: string; type: string; title: string; body: string | null; data: string | null; read: boolean; createdAt: string; } interface NotificationPanelProps { token: string | null; onClose: () => void; onCountChange: (count: number) => void; } const typeIcons: Record = { streak_reminder: { icon: Zap, color: "text-yellow-400" }, daily_verse: { icon: BookOpen, color: "text-emerald-400" }, premium_expiring: { icon: Crown, color: "text-amber-400" }, new_listing: { icon: ShoppingBag, color: "text-blue-400" }, forum_reply: { icon: MessageCircle, color: "text-purple-400" }, achievement: { icon: Trophy, color: "text-yellow-500" }, referral_bonus: { icon: Users, color: "text-green-400" }, }; function timeAgo(dateStr: string): string { const now = Date.now(); const then = new Date(dateStr).getTime(); const diffMs = now - then; const mins = Math.floor(diffMs / 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`; const days = Math.floor(hrs / 24); if (days < 7) return `${days}d ago`; return new Date(dateStr).toLocaleDateString(); } export default function NotificationPanel({ token, onClose, onCountChange, }: NotificationPanelProps) { const [notifications, setNotifications] = useState([]); const [loading, setLoading] = useState(true); const fetchNotifications = useCallback(async () => { if (!token) return; try { const res = await fetch("/api/notifications", { headers: { Authorization: `Bearer ${token}` }, }); if (res.ok) { const data = await res.json(); setNotifications(data.notifications); } } catch { // ignore } finally { setLoading(false); } }, [token]); useEffect(() => { fetchNotifications(); }, [fetchNotifications]); const markAsRead = async (id: string) => { if (!token) return; try { const res = await fetch("/api/notifications", { method: "PATCH", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ id }), }); if (res.ok) { setNotifications((prev) => prev.map((n) => (n.id === id ? { ...n, read: true } : n)) ); await refreshCount(); } } catch { // ignore } }; const markAllAsRead = async () => { if (!token) return; try { const res = await fetch("/api/notifications", { method: "PATCH", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ all: true }), }); if (res.ok) { setNotifications((prev) => prev.map((n) => ({ ...n, read: true })) ); onCountChange(0); } } catch { // ignore } }; const refreshCount = async () => { if (!token) return; try { const res = await fetch("/api/notifications/unread-count", { headers: { Authorization: `Bearer ${token}` }, }); if (res.ok) { const data = await res.json(); onCountChange(data.count); } } catch { // ignore } }; const unreadCount = notifications.filter((n) => !n.read).length; return (
{/* Header */}

Notifications

{unreadCount > 0 && ( )}
{/* List */}
{loading ? (
) : notifications.length === 0 ? (

No notifications yet

) : ( notifications.map((notif) => { const iconConfig = typeIcons[notif.type] || { icon: Bell, color: "text-gray-400" }; const IconComponent = iconConfig.icon; return ( ); }) )}
); }