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>
65 lines
1.8 KiB
TypeScript
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;
|