'use client'; import { useState, useEffect } from 'react'; import { Box } from '@/ui/Box'; import { Text } from '@/ui/Text'; import { Heading } from '@/ui/Heading'; import { Icon } from '@/ui/Icon'; import { IconButton } from '@/ui/IconButton'; import type { Notification, NotificationAction } from './notificationTypes'; import { Bell, AlertTriangle, Shield, Vote, Trophy, Users, Flag, AlertCircle, Clock, Star, Medal, X, } from 'lucide-react'; import { Button } from '@/ui/Button'; interface ModalNotificationProps { notification: Notification; onAction: (notification: Notification, actionId?: string) => void; onDismiss?: (notification: Notification) => void; onNavigate?: (href: string) => 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 function ModalNotification({ notification, onAction, onDismiss, onNavigate, }: ModalNotificationProps) { const [isVisible, setIsVisible] = useState(false); 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 && onNavigate) { onNavigate(action.href); } }; const handlePrimaryAction = () => { onAction(notification, 'primary'); if (notification.actionUrl && onNavigate) { onNavigate(notification.actionUrl); } }; const NotificationIcon = 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 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 */} {isRaceNotification ? (isPerformanceSummary ? '🏁 Race Complete!' : '🏆 Championship Update') : 'Action Required'} {notification.title} {/* X button for dismissible notifications */} {onDismiss && !notification.requiresResponse && ( onDismiss(notification)} variant="ghost" size="md" color="text-gray-400" title="Dismiss notification" /> )} {/* 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'} block> {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 )} ); }