/** * Custom hook for monitoring sync progress * Centralizes the pattern of polling syncProgressManager and updating state */ import { useState, useEffect, useRef, useCallback, type MutableRefObject } from 'react' import type { SyncProgress } from '../helpers/syncProgressHelper' export interface UseSyncProgressOptions { onComplete?: () => void | Promise pollInterval?: number maxDuration?: number } export interface UseSyncProgressResult { syncProgress: SyncProgress | null isSyncing: boolean startMonitoring: () => void stopMonitoring: () => void } /** * Hook to monitor sync progress from syncProgressManager */ export function useSyncProgress(options: UseSyncProgressOptions = {}): UseSyncProgressResult { const config = normalizeSyncProgressOptions(options) const state = useSyncProgressState({ onComplete: config.onComplete }) const checkProgress = useCheckProgress({ onCompleteRef: state.onCompleteRef, setSyncProgress: state.setSyncProgress, setIsSyncing: state.setIsSyncing, stopMonitoring: state.stopMonitoring }) const startMonitoring = useStartMonitoring({ pollInterval: config.pollInterval, maxDuration: config.maxDuration, checkProgress, stopMonitoring: state.stopMonitoring, setIsSyncing: state.setIsSyncing, setSyncProgress: state.setSyncProgress, isMonitoringRef: state.isMonitoringRef, intervalRef: state.intervalRef, timeoutRef: state.timeoutRef }) useUnmountCleanup({ stopMonitoring: state.stopMonitoring }) return { syncProgress: state.syncProgress, isSyncing: state.isSyncing, startMonitoring, stopMonitoring: state.stopMonitoring } } function normalizeSyncProgressOptions(options: UseSyncProgressOptions): { onComplete: UseSyncProgressOptions['onComplete']; pollInterval: number; maxDuration: number } { return { onComplete: options.onComplete, pollInterval: options.pollInterval ?? 500, maxDuration: options.maxDuration ?? 60000 } } function useSyncProgressState(params: { onComplete: UseSyncProgressOptions['onComplete'] }): { syncProgress: SyncProgress | null setSyncProgress: (value: SyncProgress | null) => void isSyncing: boolean setIsSyncing: (value: boolean) => void intervalRef: MutableRefObject | null> timeoutRef: MutableRefObject | null> isMonitoringRef: MutableRefObject onCompleteRef: MutableRefObject stopMonitoring: () => void } { const [syncProgress, setSyncProgress] = useState(null) const [isSyncing, setIsSyncing] = useState(false) const intervalRef = useRef | null>(null) const timeoutRef = useRef | null>(null) const isMonitoringRef = useRef(false) const onCompleteRef = useRef(params.onComplete) useEffect(() => { onCompleteRef.current = params.onComplete }, [params.onComplete]) const stopMonitoring = useCallback((): void => { if (!isMonitoringRef.current) { return } isMonitoringRef.current = false setIsSyncing(false) intervalRef.current = clearIntervalAndReturnNull(intervalRef.current) timeoutRef.current = clearTimeoutAndReturnNull(timeoutRef.current) }, []) return { syncProgress, setSyncProgress, isSyncing, setIsSyncing, intervalRef, timeoutRef, isMonitoringRef, onCompleteRef, stopMonitoring } } function clearIntervalAndReturnNull(current: ReturnType | null): null { if (current) { clearInterval(current) } return null } function clearTimeoutAndReturnNull(current: ReturnType | null): null { if (current) { clearTimeout(current) } return null } function useCheckProgress(params: { onCompleteRef: MutableRefObject setSyncProgress: (value: SyncProgress | null) => void setIsSyncing: (value: boolean) => void stopMonitoring: () => void }): () => Promise { const { onCompleteRef, setSyncProgress, setIsSyncing, stopMonitoring } = params return useCallback(async (): Promise => { const { syncProgressManager } = await import('../syncProgressManager') const current = syncProgressManager.getProgress() if (!current) { return } setSyncProgress(current) if (!current.completed) { return } setIsSyncing(false) await onCompleteRef.current?.() stopMonitoring() }, [onCompleteRef, setIsSyncing, setSyncProgress, stopMonitoring]) } function useStartMonitoring(params: { pollInterval: number maxDuration: number checkProgress: () => Promise stopMonitoring: () => void setIsSyncing: (value: boolean) => void setSyncProgress: (value: SyncProgress | null) => void isMonitoringRef: MutableRefObject intervalRef: MutableRefObject | null> timeoutRef: MutableRefObject | null> }): () => void { const { pollInterval, maxDuration, checkProgress, stopMonitoring, setIsSyncing, setSyncProgress, isMonitoringRef, intervalRef, timeoutRef, } = params return useCallback((): void => { if (isMonitoringRef.current) { return } isMonitoringRef.current = true setIsSyncing(true) setSyncProgress({ currentStep: 0, totalSteps: 7, completed: false }) intervalRef.current = setInterval(() => void checkProgress(), pollInterval) timeoutRef.current = setTimeout(() => stopMonitoring(), maxDuration) }, [checkProgress, intervalRef, isMonitoringRef, maxDuration, pollInterval, setIsSyncing, setSyncProgress, stopMonitoring, timeoutRef]) } function useUnmountCleanup(params: { stopMonitoring: () => void }): void { const { stopMonitoring } = params useEffect(() => () => stopMonitoring(), [stopMonitoring]) }