'use client'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import type { Notification, NotificationAction } from './notificationTypes'; import { Bell, AlertTriangle, Shield, Vote, Trophy, Users, Flag, AlertCircle, Clock, TrendingUp, Award, Star, Medal, Target, Zap, X, } from 'lucide-react'; import Button from '@/ui/Button'; interface ModalNotificationProps { notification: Notification; onAction: (notification: Notification, actionId?: string) => void; onDismiss?: (notification: Notification) => void; } const notificationIcons: Record = { protest_filed: AlertTriangle, protest_defense_requested: Shield, protest_vote_required: Vote, penalty_issued: AlertTriangle, race_results_posted: Trophy, race_performance_summary: Medal, race_final_results: Star, league_invite: Users, race_reminder: Flag, }; const notificationColors: Record = { protest_filed: { bg: 'bg-red-500/10', border: 'border-red-500/50', text: 'text-red-400', glow: 'shadow-[0_0_60px_rgba(239,68,68,0.3)]', }, protest_defense_requested: { bg: 'bg-warning-amber/10', border: 'border-warning-amber/50', text: 'text-warning-amber', glow: 'shadow-[0_0_60px_rgba(245,158,11,0.3)]', }, protest_vote_required: { bg: 'bg-primary-blue/10', border: 'border-primary-blue/50', text: 'text-primary-blue', glow: 'shadow-[0_0_60px_rgba(25,140,255,0.3)]', }, penalty_issued: { bg: 'bg-red-500/10', border: 'border-red-500/50', text: 'text-red-400', glow: 'shadow-[0_0_60px_rgba(239,68,68,0.3)]', }, race_performance_summary: { bg: 'bg-gradient-to-br from-yellow-400/20 via-orange-500/20 to-red-500/20', border: 'border-yellow-400/60', text: 'text-yellow-400', glow: 'shadow-[0_0_80px_rgba(251,191,36,0.4)]', }, race_final_results: { bg: 'bg-gradient-to-br from-purple-500/20 via-pink-500/20 to-indigo-500/20', border: 'border-purple-400/60', text: 'text-purple-400', glow: 'shadow-[0_0_80px_rgba(168,85,247,0.4)]', }, }; export default function ModalNotification({ notification, onAction, onDismiss, }: ModalNotificationProps) { const [isVisible, setIsVisible] = useState(false); const router = useRouter(); useEffect(() => { // Animate in const timeout = setTimeout(() => setIsVisible(true), 10); return () => clearTimeout(timeout); }, []); // Handle ESC key to dismiss useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape' && onDismiss && !notification.requiresResponse) { onDismiss(notification); } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [notification, onDismiss]); const handleAction = (action: NotificationAction) => { onAction(notification, action.id); if (action.href) { router.push(action.href); } }; const handlePrimaryAction = () => { onAction(notification, 'primary'); if (notification.actionUrl) { router.push(notification.actionUrl); } }; const Icon = notificationIcons[notification.type] || AlertCircle; const colors = notificationColors[notification.type] || { bg: 'bg-warning-amber/10', border: 'border-warning-amber/50', text: 'text-warning-amber', glow: 'shadow-[0_0_60px_rgba(245,158,11,0.3)]', }; const data: Record = notification.data ?? {}; const getNumber = (value: unknown): number | null => { if (typeof value === 'number' && Number.isFinite(value)) return value; if (typeof value === 'string') { const parsed = Number(value); if (Number.isFinite(parsed)) return parsed; } return null; }; const getString = (value: unknown): string | null => { if (typeof value === 'string') return value; if (typeof value === 'number' && Number.isFinite(value)) return String(value); return null; }; const isValidDate = (value: unknown): value is Date => value instanceof Date && !Number.isNaN(value.getTime()); // Check if there's a deadline const deadlineValue = data.deadline; const deadline: Date | null = isValidDate(deadlineValue) ? deadlineValue : typeof deadlineValue === 'string' || typeof deadlineValue === 'number' ? new Date(deadlineValue) : null; const hasDeadline = !!deadline && !Number.isNaN(deadline.getTime()); // Special celebratory styling for race notifications const isRaceNotification = notification.type.startsWith('race_'); const isPerformanceSummary = notification.type === 'race_performance_summary'; const isFinalResults = notification.type === 'race_final_results'; const provisionalRatingChange = getNumber(data.provisionalRatingChange) ?? 0; const finalRatingChange = getNumber(data.finalRatingChange) ?? 0; const ratingChange = provisionalRatingChange || finalRatingChange; const protestId = getString(data.protestId); return (
{/* Header with pulse animation */}
{/* Subtle pulse ring */}

{isRaceNotification ? (isPerformanceSummary ? '🏁 Race Complete!' : '🏆 Championship Update') : 'Action Required'}

{notification.title}

{/* X button for dismissible notifications */} {onDismiss && !notification.requiresResponse && ( )}
{/* Body */}

{notification.message}

{/* Race performance stats */} {isRaceNotification && (
POSITION
{notification.data?.position === 'DNF' ? 'DNF' : `P${notification.data?.position || '?'}`}
RATING CHANGE
= 0 ? 'text-green-400' : 'text-red-400'}`}> {ratingChange >= 0 ? '+' : ''} {ratingChange}
)} {/* Deadline warning */} {hasDeadline && !isRaceNotification && (

Response Required

Please respond by {deadline ? deadline.toLocaleDateString() : ''} at {deadline ? deadline.toLocaleTimeString() : ''}

)} {/* Additional context from data */} {protestId && (

Related Protest

{protestId}

)}
{/* Actions */}
{notification.actions && notification.actions.length > 0 ? (
{notification.actions.map((action, index) => ( ))}
) : (
{isRaceNotification ? ( <> ) : ( )}
)}
{/* Cannot dismiss warning */} {notification.requiresResponse && !isRaceNotification && (

⚠️ This notification requires your action and cannot be dismissed

)}
); }