Files
maternal-app/maternal-web/hooks/useOfflineSync.ts
2025-10-01 19:01:52 +00:00

122 lines
3.2 KiB
TypeScript

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,
};
};