import { useEffect, useState, useRef } from 'react'; import { useErrorHandler } from '../../hooks/useErrorHandler'; interface LoadingState { source: string; startTime: number; duration: number; } const loadingStates = new Map(); const listeners = new Set<(states: LoadingState[]) => void>(); export function trackLoading(source: string, isLoading: boolean) { if (isLoading) { loadingStates.set(source, { source, startTime: Date.now(), duration: 0, }); } else { loadingStates.delete(source); } listeners.forEach(listener => { const states = Array.from(loadingStates.values()).map(s => ({ ...s, duration: Date.now() - s.startTime, })); listener(states); }); } export default function LoadingStateMonitor() { const [localLoadingStates, setLocalLoadingStates] = useState([]); const { addError } = useErrorHandler('LoadingStateMonitor'); const reportedStuckStates = useRef>(new Set()); const addErrorRef = useRef(addError); // Keep addError ref updated useEffect(() => { addErrorRef.current = addError; }, [addError]); useEffect(() => { const updateStates = (statesFromListener: LoadingState[]) => { // Use states from listener (always provided by trackLoading) const states = statesFromListener.filter(s => s.duration < 60000); // Only show states less than 60 seconds old setLocalLoadingStates(states); // Detect stuck loading states (more than 5 seconds) - only report once per state const stuck = states.filter(s => s.duration > 5000 && !reportedStuckStates.current.has(s.source)); if (stuck.length > 0) { stuck.forEach(state => { reportedStuckStates.current.add(state.source); // Use ref to avoid dependency issues addErrorRef.current( new Error(`Loading state stuck: ${state.source} (${(state.duration / 1000).toFixed(1)}s)`), 'LoadingStateMonitor' ); }); } // Clean up reported states that are no longer stuck const noLongerStuck = Array.from(reportedStuckStates.current).filter( source => !states.find(s => s.source === source && s.duration > 5000) ); noLongerStuck.forEach(source => reportedStuckStates.current.delete(source)); }; // Initial update from global Map const initialStates = Array.from(loadingStates.values()).map(s => ({ ...s, duration: Date.now() - s.startTime, })); updateStates(initialStates); listeners.add(updateStates); // Periodic check (in case listener doesn't fire) const interval = setInterval(() => { const currentStates = Array.from(loadingStates.values()).map(s => ({ ...s, duration: Date.now() - s.startTime, })); updateStates(currentStates); }, 1000); return () => { listeners.delete(updateStates); clearInterval(interval); }; }, []); // Empty deps - updateStates reads from global Map via listeners // Auto-reset stuck loading states after 10 seconds useEffect(() => { const stuck = localLoadingStates.filter(s => s.duration > 10000); if (stuck.length > 0) { stuck.forEach(state => { console.warn(`Auto-resetting stuck loading state: ${state.source}`); trackLoading(state.source, false); reportedStuckStates.current.delete(state.source); }); } }, [localLoadingStates]); return null; // This component doesn't render anything visible }