'use client'; import { useEffectiveDriverId } from "@/lib/hooks/useEffectiveDriverId"; import { useNotifications } from '@/components/notifications/NotificationProvider'; import type { NotificationVariant } from '@/components/notifications/notificationTypes'; import { Wrench, ChevronDown, ChevronUp, X, MessageSquare, Activity, AlertTriangle } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { ApiConnectionMonitor } from '@/lib/api/base/ApiConnectionMonitor'; import { CircuitBreakerRegistry } from '@/lib/api/base/RetryHandler'; import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler'; // Import our new components import { Accordion } from './Accordion'; import { NotificationTypeSection } from './sections/NotificationTypeSection'; import { UrgencySection } from './sections/UrgencySection'; import { NotificationSendSection } from './sections/NotificationSendSection'; import { APIStatusSection } from './sections/APIStatusSection'; // Import types import type { DemoNotificationType, DemoUrgency } from './types'; export default function DevToolbar() { const router = useRouter(); const { addNotification } = useNotifications(); const [isExpanded, setIsExpanded] = useState(false); const [isMinimized, setIsMinimized] = useState(false); const [selectedType, setSelectedType] = useState('protest_filed'); const [selectedUrgency, setSelectedUrgency] = useState('toast'); const [sending, setSending] = useState(false); const [lastSent, setLastSent] = useState(null); // API Status Monitoring State const [apiStatus, setApiStatus] = useState(() => ApiConnectionMonitor.getInstance().getStatus()); const [apiHealth, setApiHealth] = useState(() => ApiConnectionMonitor.getInstance().getHealth()); const [circuitBreakers, setCircuitBreakers] = useState(() => CircuitBreakerRegistry.getInstance().getStatus()); const [checkingHealth, setCheckingHealth] = useState(false); // Error Stats State const [errorStats, setErrorStats] = useState({ total: 0, byType: {} as Record }); // Accordion state - only one open at a time const [openAccordion, setOpenAccordion] = useState('notifications'); const currentDriverId = useEffectiveDriverId(); // API Status Monitoring Effects useEffect(() => { const monitor = ApiConnectionMonitor.getInstance(); const registry = CircuitBreakerRegistry.getInstance(); const updateStatus = () => { setApiStatus(monitor.getStatus()); setApiHealth(monitor.getHealth()); setCircuitBreakers(registry.getStatus()); }; // Initial update updateStatus(); // Listen for status changes monitor.on('connected', updateStatus); monitor.on('disconnected', updateStatus); monitor.on('degraded', updateStatus); monitor.on('success', updateStatus); monitor.on('failure', updateStatus); // Poll for updates every 2 seconds const interval = setInterval(updateStatus, 2000); return () => { monitor.off('connected', updateStatus); monitor.off('disconnected', updateStatus); monitor.off('degraded', updateStatus); monitor.off('success', updateStatus); monitor.off('failure', updateStatus); clearInterval(interval); }; }, []); // Error Stats Effect useEffect(() => { const updateErrorStats = () => { try { const handler = getGlobalErrorHandler(); const stats = handler.getStats(); setErrorStats(stats); } catch { // Handler might not be initialized yet setErrorStats({ total: 0, byType: {} }); } }; // Initial update updateErrorStats(); // Poll for updates every 3 seconds const interval = setInterval(updateErrorStats, 3000); return () => clearInterval(interval); }, []); // API Health Check Handler const handleApiHealthCheck = async () => { setCheckingHealth(true); try { const monitor = ApiConnectionMonitor.getInstance(); const result = await monitor.performHealthCheck(); addNotification({ type: result.healthy ? 'api_healthy' : 'api_unhealthy', title: result.healthy ? 'API Health Check Passed' : 'API Health Check Failed', message: result.healthy ? `API responded in ${result.responseTime}ms` : `Health check failed: ${result.error}`, variant: 'toast', }); } catch (error) { addNotification({ type: 'api_error', title: 'Health Check Error', message: 'Failed to perform health check', variant: 'toast', }); } finally { setCheckingHealth(false); } }; // Reset API Stats const handleResetApiStats = () => { ApiConnectionMonitor.getInstance().reset(); CircuitBreakerRegistry.getInstance().resetAll(); addNotification({ type: 'api_reset', title: 'API Stats Reset', message: 'All API connection statistics have been reset', variant: 'toast', }); }; // Test API Error const handleTestApiError = async () => { try { // This will intentionally fail to test error handling const response = await fetch('http://localhost:3001/api/nonexistent', { method: 'GET', }); if (!response.ok) { throw new Error(`Test error: ${response.status}`); } } catch (error) { addNotification({ type: 'api_test_error', title: 'Test Error Triggered', message: 'This is a test API error to demonstrate error handling', variant: 'toast', }); } }; // Only show in development if (process.env.NODE_ENV === 'production') { return null; } const handleSendNotification = async () => { setSending(true); try { const actionUrlByType: Record = { protest_filed: '/races', defense_requested: '/races', vote_required: '/leagues', race_performance_summary: '/races', race_final_results: '/races', }; const titleByType: Record = { protest_filed: 'Protest Filed Against You', defense_requested: 'Defense Requested', vote_required: 'Vote Required', race_performance_summary: 'Race Performance Summary', race_final_results: 'Race Final Results', }; const messageByType: Record = { protest_filed: 'A protest has been filed against you. Please review the incident details.', defense_requested: 'A steward requests your defense. Please respond within the deadline.', vote_required: 'A protest vote is pending. Please review and vote.', race_performance_summary: 'Your race is complete. View your provisional results.', race_final_results: 'Stewarding is closed. Your final results are available.', }; const notificationTypeByDemoType: Record = { protest_filed: 'protest_filed', defense_requested: 'protest_defense_requested', vote_required: 'protest_vote_required', race_performance_summary: 'race_performance_summary', race_final_results: 'race_final_results', }; const variant: NotificationVariant = selectedUrgency === 'modal' ? 'modal' : 'toast'; addNotification({ type: notificationTypeByDemoType[selectedType], title: titleByType[selectedType], message: messageByType[selectedType], variant, actionUrl: actionUrlByType[selectedType], data: { driverId: currentDriverId, demo: true, }, }); setLastSent(`${selectedType}-${selectedUrgency}`); setTimeout(() => setLastSent(null), 3000); } catch (error) { // Silent failure for demo notifications setSending(false); } }; if (isMinimized) { return ( ); } return (
{/* Header */}
Dev Toolbar DEMO
{/* Content */} {isExpanded && (
{/* Notification Section - Accordion */} } isOpen={openAccordion === 'notifications'} onToggle={() => setOpenAccordion(openAccordion === 'notifications' ? null : 'notifications')} >
{/* API Status Section - Accordion */} } isOpen={openAccordion === 'apiStatus'} onToggle={() => setOpenAccordion(openAccordion === 'apiStatus' ? null : 'apiStatus')} > {/* Error Stats Section - Accordion */} } isOpen={openAccordion === 'errors'} onToggle={() => setOpenAccordion(openAccordion === 'errors' ? null : 'errors')} >
Total Errors {errorStats.total}
{Object.keys(errorStats.byType).length > 0 ? (
{Object.entries(errorStats.byType).map(([type, count]) => (
{type} {count}
))}
) : (
No errors yet
)}
)} {/* Collapsed state hint */} {!isExpanded && (
Click ↑ to expand dev tools
)}
); }