122 lines
3.2 KiB
TypeScript
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,
|
|
};
|
|
};
|