import React, { useState, useEffect } from 'react' import { Card, CardContent } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Wifi, WifiOff, RefreshCw, AlertCircle, CheckCircle, Clock, Activity } from 'lucide-react' interface OfflineDetectorState { isOnline: boolean lastOnlineTime?: Date offlineDuration: number retryCount: number isRetrying: boolean connectionQuality: 'good' | 'poor' | 'offline' apiStatus: 'connected' | 'disconnected' | 'checking' } interface OfflineDetectorProps { children: React.ReactNode apiEndpoint?: string checkInterval?: number onConnectionChange?: (isOnline: boolean) => void } export function OfflineDetector({ children, apiEndpoint = '/api/status', checkInterval = 30000, onConnectionChange }: OfflineDetectorProps) { const [state, setState] = useState({ isOnline: navigator.onLine, offlineDuration: 0, retryCount: 0, isRetrying: false, connectionQuality: navigator.onLine ? 'good' : 'offline', apiStatus: 'checking' }) const [showOfflineCard, setShowOfflineCard] = useState(false) const [offlineActions, setOfflineActions] = useState>([]) // Monitor browser online/offline events useEffect(() => { const handleOnline = () => { console.log('🟢 Browser reports: ONLINE') setState(prev => ({ ...prev, isOnline: true, connectionQuality: 'good', retryCount: 0, lastOnlineTime: new Date() })) onConnectionChange?.(true) setShowOfflineCard(false) // Process offline actions when coming back online if (offlineActions.length > 0) { processOfflineActions() } } const handleOffline = () => { console.log('🔴 Browser reports: OFFLINE') setState(prev => ({ ...prev, isOnline: false, connectionQuality: 'offline', lastOnlineTime: prev.lastOnlineTime || new Date() })) onConnectionChange?.(false) setShowOfflineCard(true) } window.addEventListener('online', handleOnline) window.addEventListener('offline', handleOffline) return () => { window.removeEventListener('online', handleOnline) window.removeEventListener('offline', handleOffline) } }, [onConnectionChange, offlineActions]) // Monitor offline duration useEffect(() => { if (!state.isOnline && state.lastOnlineTime) { const interval = setInterval(() => { setState(prev => ({ ...prev, offlineDuration: Date.now() - (prev.lastOnlineTime?.getTime() || Date.now()) })) }, 1000) return () => clearInterval(interval) } }, [state.isOnline, state.lastOnlineTime]) // Periodic API connectivity check useEffect(() => { const checkApiConnection = async () => { if (!state.isOnline) return setState(prev => ({ ...prev, apiStatus: 'checking' })) try { const startTime = Date.now() const response = await fetch(apiEndpoint, { method: 'HEAD', cache: 'no-cache', signal: AbortSignal.timeout(5000) }) const responseTime = Date.now() - startTime if (response.ok) { setState(prev => ({ ...prev, apiStatus: 'connected', connectionQuality: responseTime > 2000 ? 'poor' : 'good' })) } else { throw new Error(`API returned ${response.status}`) } } catch (error) { // Only log non-timeout errors to reduce console noise if (error.name !== 'TimeoutError') { console.warn('🔶 API connectivity check failed:', error) } setState(prev => ({ ...prev, apiStatus: 'disconnected', connectionQuality: 'poor' })) } } const interval = setInterval(checkApiConnection, checkInterval) checkApiConnection() // Initial check return () => clearInterval(interval) }, [apiEndpoint, checkInterval, state.isOnline]) const formatDuration = (ms: number): string => { const seconds = Math.floor(ms / 1000) const minutes = Math.floor(seconds / 60) const hours = Math.floor(minutes / 60) if (hours > 0) return `${hours}h ${minutes % 60}m` if (minutes > 0) return `${minutes}m ${seconds % 60}s` return `${seconds}s` } const handleRetryConnection = async () => { setState(prev => ({ ...prev, isRetrying: true, retryCount: prev.retryCount + 1 })) try { const response = await fetch(apiEndpoint, { method: 'HEAD', cache: 'no-cache', signal: AbortSignal.timeout(10000) }) if (response.ok) { setState(prev => ({ ...prev, isOnline: true, connectionQuality: 'good', apiStatus: 'connected', isRetrying: false })) setShowOfflineCard(false) onConnectionChange?.(true) } else { throw new Error('API not responding') } } catch (error) { console.warn('🔄 Retry failed:', error) setState(prev => ({ ...prev, isRetrying: false })) } } const queueOfflineAction = (action: string, data: any) => { const offlineAction = { id: `offline-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, action, timestamp: new Date(), data } setOfflineActions(prev => [...prev, offlineAction]) console.log('📝 Queued offline action:', offlineAction) } const processOfflineActions = async () => { console.log('⚡ Processing offline actions:', offlineActions.length) for (const action of offlineActions) { try { // Process each offline action based on type console.log(`Processing offline action: ${action.action}`) // Implementation would depend on specific action types } catch (error) { console.error('❌ Failed to process offline action:', action, error) } } setOfflineActions([]) } const getConnectionStatusColor = () => { if (!state.isOnline) return 'text-red-400 border-red-400' if (state.apiStatus === 'disconnected') return 'text-orange-400 border-orange-400' if (state.connectionQuality === 'poor') return 'text-yellow-400 border-yellow-400' return 'text-green-400 border-green-400' } const getConnectionIcon = () => { if (!state.isOnline || state.apiStatus === 'disconnected') return return } const getConnectionText = () => { if (!state.isOnline) return 'Offline' if (state.apiStatus === 'disconnected') return 'API Disconnected' if (state.apiStatus === 'checking') return 'Checking...' if (state.connectionQuality === 'poor') return 'Poor Connection' return 'Connected' } return ( <> {/* Connection Status Badge - Always visible in corner */}
{getConnectionIcon()} {getConnectionText()} {state.apiStatus === 'checking' && ( )}
{/* Offline Card - Shows when offline */} {showOfflineCard && (

You're Offline

Your internet connection has been lost.

Offline for: {formatDuration(state.offlineDuration)}

{offlineActions.length > 0 && (
{offlineActions.length} action(s) queued for when you're back online
)}
Some features may be unavailable while offline
)} {/* Main Content */} {children} ) } // Hook for components to use offline functionality export function useOfflineSupport() { const [isOnline, setIsOnline] = useState(navigator.onLine) const [offlineQueue, setOfflineQueue] = useState([]) useEffect(() => { const handleOnline = () => setIsOnline(true) const handleOffline = () => setIsOnline(false) window.addEventListener('online', handleOnline) window.addEventListener('offline', handleOffline) return () => { window.removeEventListener('online', handleOnline) window.removeEventListener('offline', handleOffline) } }, []) const queueAction = (action: any) => { if (!isOnline) { setOfflineQueue(prev => [...prev, action]) return false // Action was queued } return true // Action can be executed immediately } const processQueue = async () => { if (isOnline && offlineQueue.length > 0) { console.log('Processing offline queue:', offlineQueue.length, 'items') // Process queue items here setOfflineQueue([]) } } return { isOnline, queueAction, processQueue, queueSize: offlineQueue.length } }