'use client'; import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId'; import { useNotifications } from '@/components/notifications/NotificationProvider'; import type { NotificationVariant } from '@/components/notifications/notificationTypes'; import { Wrench, ChevronDown, ChevronUp, X, MessageSquare, Activity, LogIn, 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 { LoginSection } from './sections/LoginSection'; // Import types import type { DemoNotificationType, DemoUrgency, LoginMode } 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); const [loginMode, setLoginMode] = useState('none'); const [loggingIn, setLoggingIn] = useState(false); // 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(); // Sync login mode with actual session state on mount useEffect(() => { if (typeof document !== 'undefined') { // Check for actual session cookie first const cookies = document.cookie.split(';'); const sessionCookie = cookies.find(c => c.trim().startsWith('gp_session=')); if (sessionCookie) { // User has a session cookie, check if it's valid by calling the API fetch('/api/auth/session', { method: 'GET', credentials: 'include' }) .then(res => { if (res.ok) { return res.json(); } throw new Error('No valid session'); }) .then(session => { if (session && session.user) { // Determine login mode based on user email patterns const email = session.user.email?.toLowerCase() || ''; const displayName = session.user.displayName?.toLowerCase() || ''; const role = (session.user as any).role; let mode: LoginMode = 'none'; // First check session.role if available if (role) { if (role === 'sponsor') mode = 'sponsor'; else if (role === 'league-owner') mode = 'league-owner'; else if (role === 'league-steward') mode = 'league-steward'; else if (role === 'league-admin') mode = 'league-admin'; else if (role === 'system-owner') mode = 'system-owner'; else if (role === 'super-admin') mode = 'super-admin'; else if (role === 'driver') mode = 'driver'; } // Fallback to email patterns else if (email.includes('sponsor') || displayName.includes('sponsor')) { mode = 'sponsor'; } else if (email.includes('league-owner') || displayName.includes('owner')) { mode = 'league-owner'; } else if (email.includes('league-steward') || displayName.includes('steward')) { mode = 'league-steward'; } else if (email.includes('league-admin') || displayName.includes('admin')) { mode = 'league-admin'; } else if (email.includes('system-owner') || displayName.includes('system owner')) { mode = 'system-owner'; } else if (email.includes('super-admin') || displayName.includes('super admin')) { mode = 'super-admin'; } else if (email.includes('driver') || displayName.includes('demo')) { mode = 'driver'; } setLoginMode(mode); } else { setLoginMode('none'); } }) .catch(() => { // Session invalid or expired setLoginMode('none'); }); } else { // No session cookie means not logged in setLoginMode('none'); } } }, []); // 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', }); } }; const handleDemoLogin = async (role: LoginMode) => { if (role === 'none') return; setLoggingIn(true); try { // Use the demo login API const response = await fetch('/api/auth/demo-login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ role }), }); if (!response.ok) { throw new Error('Demo login failed'); } setLoginMode(role); // Navigate based on role if (role === 'sponsor') { window.location.href = '/sponsor/dashboard'; } else { // For driver and league roles, go to dashboard window.location.href = '/dashboard'; } } catch (error) { alert('Demo login failed. Please check the API server status.'); } finally { setLoggingIn(false); } }; const handleLogout = async () => { setLoggingIn(true); try { // Call logout API await fetch('/api/auth/logout', { method: 'POST' }); setLoginMode('none'); // Refresh to update all components window.location.href = '/'; } catch (error) { alert('Logout failed. Please check the API server status.'); } finally { setLoggingIn(false); } }; // 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')} > {/* Login Section - Accordion */} } isOpen={openAccordion === 'login'} onToggle={() => setOpenAccordion(openAccordion === 'login' ? null : 'login')} > {/* 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
)}
); }