story-research-zapwall/lib/hooks/useSyncProgress.ts
2026-01-07 03:45:01 +01:00

108 lines
2.7 KiB
TypeScript

/**
* Custom hook for monitoring sync progress
* Centralizes the pattern of polling syncProgressManager and updating state
*/
import { useState, useEffect, useRef } from 'react'
import type { SyncProgress } from '../helpers/syncProgressHelper'
export interface UseSyncProgressOptions {
onComplete?: () => void | Promise<void>
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 { onComplete, pollInterval = 500, maxDuration = 60000 } = options
const [syncProgress, setSyncProgress] = useState<SyncProgress | null>(null)
const [isSyncing, setIsSyncing] = useState(false)
const intervalRef = useRef<NodeJS.Timeout | null>(null)
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
const onCompleteRef = useRef(onComplete)
const isMonitoringRef = useRef(false)
// Update onComplete ref when it changes
useEffect(() => {
onCompleteRef.current = onComplete
}, [onComplete])
const checkProgress = async (): Promise<void> => {
const { syncProgressManager } = await import('../syncProgressManager')
const currentProgress = syncProgressManager.getProgress()
if (currentProgress) {
setSyncProgress(currentProgress)
if (currentProgress.completed) {
setIsSyncing(false)
if (onCompleteRef.current) {
await onCompleteRef.current()
}
stopMonitoring()
}
}
}
const startMonitoring = (): void => {
if (isMonitoringRef.current) {
return
}
isMonitoringRef.current = true
setIsSyncing(true)
setSyncProgress({ currentStep: 0, totalSteps: 7, completed: false })
// Poll progress periodically
intervalRef.current = setInterval(() => {
void checkProgress()
}, pollInterval)
// Cleanup after max duration
timeoutRef.current = setTimeout(() => {
stopMonitoring()
}, maxDuration)
}
const stopMonitoring = (): void => {
if (!isMonitoringRef.current) {
return
}
isMonitoringRef.current = false
setIsSyncing(false)
if (intervalRef.current) {
clearInterval(intervalRef.current)
intervalRef.current = null
}
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
timeoutRef.current = null
}
}
// Cleanup on unmount
useEffect(() => {
return () => {
stopMonitoring()
}
}, [])
return {
syncProgress,
isSyncing,
startMonitoring,
stopMonitoring,
}
}