Add missing pages with AppShell layout integration
- Created /track, /insights, /children, /family, /settings, /logout pages - Wrapped all authenticated pages with AppShell and ProtectedRoute - Updated AI assistant page to use AppShell layout - All pages now have proper header/navigation and footer/tabbar - Added responsive mobile and desktop layouts - Integrated with existing navigation system 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
121
maternal-web/hooks/useOfflineSync.ts
Normal file
121
maternal-web/hooks/useOfflineSync.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user