import { useEffect, useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { setOnlineStatus, setSyncInProgress, removePendingAction, incrementRetryCount, updateLastSyncTime, } from '@/store/slices/offlineSlice'; import apiClient from '@/lib/api/client'; interface RootState { offline: { isOnline: boolean; pendingActions: any[]; syncInProgress: boolean; }; } const MAX_RETRY_ATTEMPTS = 3; export const useOfflineSync = () => { const dispatch = useDispatch(); const { isOnline, pendingActions, syncInProgress } = useSelector( (state: RootState) => state.offline ); // Monitor online/offline status useEffect(() => { const handleOnline = () => { dispatch(setOnlineStatus(true)); }; const handleOffline = () => { dispatch(setOnlineStatus(false)); }; // Set initial status dispatch(setOnlineStatus(navigator.onLine)); window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, [dispatch]); // Sync pending actions when online const syncPendingActions = useCallback(async () => { if (!isOnline || pendingActions.length === 0 || syncInProgress) { return; } dispatch(setSyncInProgress(true)); for (const action of pendingActions) { try { // Attempt to replay the action await replayAction(action); // Remove from pending actions on success dispatch(removePendingAction(action.id)); } catch (error) { console.error(`Failed to sync action ${action.id}:`, error); // Increment retry count dispatch(incrementRetryCount(action.id)); // If max retries exceeded, remove the action if (action.retryCount >= MAX_RETRY_ATTEMPTS) { console.warn(`Max retries exceeded for action ${action.id}, removing from queue`); dispatch(removePendingAction(action.id)); } } } dispatch(setSyncInProgress(false)); dispatch(updateLastSyncTime()); }, [isOnline, pendingActions, syncInProgress, dispatch]); // Trigger sync when coming online useEffect(() => { if (isOnline && pendingActions.length > 0) { syncPendingActions(); } }, [isOnline, pendingActions.length, syncPendingActions]); // Replay a specific action const replayAction = async (action: any) => { const { type, payload } = action; switch (type) { case 'CREATE_ACTIVITY': return await apiClient.post('/api/v1/activities', payload); case 'UPDATE_ACTIVITY': return await apiClient.put(`/api/v1/activities/${payload.id}`, payload); case 'DELETE_ACTIVITY': return await apiClient.delete(`/api/v1/activities/${payload.id}`); case 'CREATE_CHILD': return await apiClient.post('/api/v1/children', payload); case 'UPDATE_CHILD': return await apiClient.put(`/api/v1/children/${payload.id}`, payload); default: throw new Error(`Unknown action type: ${type}`); } }; return { isOnline, pendingActionsCount: pendingActions.length, syncInProgress, syncPendingActions, }; };