'use client'; import { AlertTriangle, Bell, CheckCheck, ExternalLink, Flag, Shield, Trophy, Users, Vote } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; import { Box } from '@/ui/Box'; import { Text } from '@/ui/Text'; import { Stack } from '@/ui/Stack'; import { Icon } from '@/ui/Icon'; const notificationIcons: Record = { protest_filed: AlertTriangle, protest_defense_requested: Shield, protest_vote_required: Vote, penalty_issued: AlertTriangle, race_results_posted: Trophy, league_invite: Users, race_reminder: Flag, }; const notificationColors: Record = { protest_filed: 'text-red-400 bg-red-400/10', protest_defense_requested: 'text-warning-amber bg-warning-amber/10', protest_vote_required: 'text-primary-blue bg-primary-blue/10', penalty_issued: 'text-red-400 bg-red-400/10', race_results_posted: 'text-performance-green bg-performance-green/10', league_invite: 'text-primary-blue bg-primary-blue/10', race_reminder: 'text-warning-amber bg-warning-amber/10', }; import { useNotifications } from './NotificationProvider'; import type { Notification } from './notificationTypes'; interface NotificationCenterProps { onNavigate?: (href: string) => void; } export function NotificationCenter({ onNavigate }: NotificationCenterProps) { const [isOpen, setIsOpen] = useState(false); const panelRef = useRef(null); const { notifications, unreadCount, markAsRead, markAllAsRead } = useNotifications(); // Close panel when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (panelRef.current && !panelRef.current.contains(event.target as Node)) { setIsOpen(false); } }; if (isOpen) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [isOpen]); const handleNotificationClick = (notification: Notification) => { markAsRead(notification.id); if (notification.actionUrl && onNavigate) { onNavigate(notification.actionUrl); setIsOpen(false); } }; const formatTime = (date: Date) => { const now = new Date(); const diff = now.getTime() - new Date(date).getTime(); const minutes = Math.floor(diff / (1000 * 60)); const hours = Math.floor(diff / (1000 * 60 * 60)); const days = Math.floor(diff / (1000 * 60 * 60 * 24)); if (minutes < 1) return 'Just now'; if (minutes < 60) return `${minutes}m ago`; if (hours < 24) return `${hours}h ago`; if (days < 7) return `${days}d ago`; return new Date(date).toLocaleDateString(); }; return ( {/* Bell button */} setIsOpen(!isOpen)} p={2} rounded="lg" transition bg={isOpen ? 'bg-primary-blue/10' : undefined} color={isOpen ? 'text-primary-blue' : 'text-gray-400'} hoverBg={!isOpen ? 'bg-iron-gray/50' : undefined} hoverTextColor={!isOpen ? 'text-white' : undefined} position="relative" > {unreadCount > 0 && ( {unreadCount > 99 ? '99+' : unreadCount} )} {/* Notification panel */} {isOpen && ( {/* Header */} Notifications {unreadCount > 0 && ( {unreadCount} new )} {unreadCount > 0 && ( Mark all read )} {/* Notifications list */} {notifications.length === 0 ? ( No notifications yet You'll be notified about protests, races, and more ) : ( {notifications.map((notification) => { const NotificationIcon = notificationIcons[notification.type] || Bell; const colorClass = notificationColors[notification.type] || 'text-gray-400 bg-gray-400/10'; return ( handleNotificationClick(notification)} w="full" textAlign="left" px={4} py={3} transition hoverBg="bg-iron-gray/30" bg={!notification.read ? 'bg-primary-blue/5' : undefined} > {notification.title} {!notification.read && ( )} {notification.message} {formatTime(notification.createdAt)} {notification.actionUrl && ( View )} ); })} )} {/* Footer */} {notifications.length > 0 && ( Showing {notifications.length} notification{notifications.length !== 1 ? 's' : ''} )} )} ); }