Files
maternal-app/maternal-web/store/slices/networkSlice.ts
Andrei 7cb2ff97de
Some checks failed
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
feat: Implement offline-first Redux architecture with optimistic updates
Implemented comprehensive offline-first state management:

Redux Store Setup:
- Configure Redux Toolkit with @redux-offline/redux-offline
- Setup redux-persist with IndexedDB (localforage)
- Custom offline config with exponential backoff retry
- Normalized state with entity adapters

State Slices:
- activitiesSlice: Normalized activities with optimistic CRUD
- childrenSlice: Normalized children with optimistic CRUD
- networkSlice: Network status and connection quality
- offlineSlice: Sync queue and pending actions

Middleware:
- offlineMiddleware: Queue actions when offline
- syncMiddleware: Process pending actions when online
- Conflict resolution strategies (SERVER_WINS, CLIENT_WINS, LAST_WRITE_WINS, MERGE)
- Version-based conflict detection

Features:
- Optimistic updates for immediate UI feedback
- Automatic sync queue with retry logic (5 retries max)
- Network detection (browser events + periodic checks)
- Connection quality monitoring (excellent/good/poor/offline)
- Latency tracking
- Conflict resolution with multiple strategies
- Entity versioning for optimistic updates

Components:
- NetworkStatusIndicator: Full-screen status banner
- NetworkStatusBadge: Compact app bar badge
- ReduxProvider: Provider with network detection setup

Custom Hooks:
- useAppDispatch/useAppSelector: Typed Redux hooks
- useIsOnline: Check online status
- useHasPendingSync: Check for pending actions
- useSyncStatus: Get sync progress info
- useOptimisticAction: Combine optimistic + actual actions
- useNetworkQuality: Get connection quality
- useIsOptimistic: Check if entity is being synced

Documentation:
- Comprehensive README with usage examples
- Architecture overview
- Best practices guide
- Troubleshooting section

State Structure:
- Normalized entities with byId/allIds
- Optimistic metadata (_optimistic, _localId, _version)
- Entity adapters with memoized selectors
- Offline queue persistence to IndexedDB

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-01 19:24:46 +00:00

65 lines
1.8 KiB
TypeScript

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface NetworkState {
isOnline: boolean;
isConnected: boolean; // API server reachability
lastOnlineTime: string | null;
lastOfflineTime: string | null;
connectionQuality: 'excellent' | 'good' | 'poor' | 'offline';
latency: number | null;
}
const initialState: NetworkState = {
isOnline: typeof navigator !== 'undefined' ? navigator.onLine : true,
isConnected: true,
lastOnlineTime: null,
lastOfflineTime: null,
connectionQuality: 'excellent',
latency: null,
};
const networkSlice = createSlice({
name: 'network',
initialState,
reducers: {
setOnlineStatus: (state, action: PayloadAction<boolean>) => {
const wasOnline = state.isOnline;
state.isOnline = action.payload;
if (action.payload && !wasOnline) {
// Just came online
state.lastOnlineTime = new Date().toISOString();
} else if (!action.payload && wasOnline) {
// Just went offline
state.lastOfflineTime = new Date().toISOString();
}
},
setServerConnection: (state, action: PayloadAction<boolean>) => {
state.isConnected = action.payload;
},
setConnectionQuality: (state, action: PayloadAction<NetworkState['connectionQuality']>) => {
state.connectionQuality = action.payload;
},
setLatency: (state, action: PayloadAction<number>) => {
state.latency = action.payload;
// Update connection quality based on latency
if (action.payload < 100) {
state.connectionQuality = 'excellent';
} else if (action.payload < 300) {
state.connectionQuality = 'good';
} else {
state.connectionQuality = 'poor';
}
},
},
});
export const {
setOnlineStatus,
setServerConnection,
setConnectionQuality,
setLatency,
} = networkSlice.actions;
export default networkSlice.reducer;