From 98f4b49edfc0628971393802e98a36a20fc2ef32 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sat, 17 Jan 2026 21:14:31 +0000 Subject: [PATCH] Generated by Spark: Create Redux persistence middleware to sync state with database automatically --- PERSISTENCE_MIDDLEWARE_DOCS.md | 372 +++++++++++++++++ PERSISTENCE_QUICK_START.md | 384 ++++++++++++++++++ PRD.md | 14 + component-registry.json | 18 + src/components/PersistenceDashboard.tsx | 288 +++++++++++++ src/components/PersistenceExample.tsx | 251 ++++++++++++ src/config/pages.json | 18 + src/hooks/use-persistence.ts | 111 +++++ src/store/index.ts | 8 +- src/store/middleware/autoSyncMiddleware.ts | 162 ++++++++ src/store/middleware/index.ts | 23 ++ src/store/middleware/persistenceMiddleware.ts | 231 +++++++++++ src/store/middleware/syncMonitorMiddleware.ts | 134 ++++++ 13 files changed, 2013 insertions(+), 1 deletion(-) create mode 100644 PERSISTENCE_MIDDLEWARE_DOCS.md create mode 100644 PERSISTENCE_QUICK_START.md create mode 100644 src/components/PersistenceDashboard.tsx create mode 100644 src/components/PersistenceExample.tsx create mode 100644 src/hooks/use-persistence.ts create mode 100644 src/store/middleware/autoSyncMiddleware.ts create mode 100644 src/store/middleware/index.ts create mode 100644 src/store/middleware/persistenceMiddleware.ts create mode 100644 src/store/middleware/syncMonitorMiddleware.ts diff --git a/PERSISTENCE_MIDDLEWARE_DOCS.md b/PERSISTENCE_MIDDLEWARE_DOCS.md new file mode 100644 index 0000000..340a4f5 --- /dev/null +++ b/PERSISTENCE_MIDDLEWARE_DOCS.md @@ -0,0 +1,372 @@ +# Redux Persistence Middleware Documentation + +## Overview + +The Redux Persistence Middleware system provides automatic synchronization between Redux state, IndexedDB (local storage), and Flask API (remote storage). This system ensures data consistency, provides offline-first capabilities, and enables seamless sync operations. + +## Architecture + +``` +┌─────────────────┐ +│ Redux Actions │ +└────────┬────────┘ + │ + ▼ +┌─────────────────────────────┐ +│ Persistence Middleware │ +│ • Debouncing (300ms) │ +│ • Batch Operations │ +│ • Queue Management │ +└────────┬────────────────────┘ + │ + ├──────────────────┬──────────────────┐ + ▼ ▼ ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ IndexedDB │ │ Flask API │ │ Metrics │ +│ (Local) │ │ (Remote) │ │ Tracking │ +└──────────────┘ └──────────────┘ └──────────────┘ +``` + +## Middleware Components + +### 1. Persistence Middleware + +**Purpose:** Automatically persists Redux state changes to IndexedDB and optionally syncs to Flask API. + +**Features:** +- Automatic persistence with configurable debouncing (default: 300ms) +- Batch operations for improved performance +- Per-slice configuration +- Support for both PUT and DELETE operations +- Queue management to prevent overwhelming storage systems + +**Configuration:** +```typescript +const config = { + storeName: 'files', // IndexedDB store name + enabled: true, // Enable/disable persistence + syncToFlask: true, // Sync to Flask API + debounceMs: 300, // Debounce time in milliseconds + batchSize: 10, // Max operations per batch +} +``` + +**Supported Actions:** +- `addItem`, `updateItem`, `removeItem` +- `saveFile`, `saveModel`, `saveComponent`, etc. +- `setFiles`, `setModels`, `setComponents`, etc. +- `deleteFile`, `deleteModel`, `deleteComponent`, etc. + +### 2. Sync Monitor Middleware + +**Purpose:** Tracks metrics and performance of sync operations. + +**Metrics Tracked:** +- Total operations count +- Successful operations count +- Failed operations count +- Last operation timestamp +- Average operation duration +- Operation time history (last 100 operations) + +**Usage:** +```typescript +import { getSyncMetrics, subscribeSyncMetrics } from '@/store/middleware' + +// Get current metrics +const metrics = getSyncMetrics() + +// Subscribe to metric updates +const unsubscribe = subscribeSyncMetrics((newMetrics) => { + console.log('Sync metrics updated:', newMetrics) +}) +``` + +### 3. Auto-Sync Middleware + +**Purpose:** Provides automatic periodic synchronization with Flask API. + +**Features:** +- Configurable sync interval (default: 30 seconds) +- Optional sync-on-change mode +- Change tracking with configurable queue size +- Automatic connection health checks +- Manual sync trigger support + +**Configuration:** +```typescript +import { configureAutoSync } from '@/store/middleware' + +configureAutoSync({ + enabled: true, // Enable auto-sync + intervalMs: 30000, // Sync every 30 seconds + syncOnChange: true, // Sync immediately when changes detected + maxQueueSize: 50, // Max changes before forcing sync +}) +``` + +## React Hooks + +### `usePersistence()` + +A comprehensive hook for interacting with the persistence system. + +**Returns:** +```typescript +{ + status: { + enabled: boolean + lastSyncTime: number | null + syncStatus: 'idle' | 'syncing' | 'success' | 'error' + error: string | null + flaskConnected: boolean + }, + metrics: { + totalOperations: number + successfulOperations: number + failedOperations: number + lastOperationTime: number + averageOperationTime: number + }, + autoSyncStatus: { + enabled: boolean + lastSyncTime: number + changeCounter: number + nextSyncIn: number | null + }, + flush: () => Promise + configure: (sliceName: string, config: any) => void + enable: (sliceName: string) => void + disable: (sliceName: string) => void + resetMetrics: () => void + configureAutoSync: (config: any) => void + syncNow: () => Promise +} +``` + +**Example:** +```typescript +import { usePersistence } from '@/hooks/use-persistence' + +function MyComponent() { + const { status, metrics, syncNow } = usePersistence() + + return ( +
+

Status: {status.syncStatus}

+

Success Rate: {(metrics.successfulOperations / metrics.totalOperations * 100)}%

+ +
+ ) +} +``` + +## Persistence Dashboard + +A visual dashboard component for monitoring and controlling the persistence system. + +**Features:** +- Real-time connection status +- Sync metrics visualization +- Auto-sync configuration +- Manual sync operations +- Error reporting + +**Usage:** +```typescript +import { PersistenceDashboard } from '@/components/PersistenceDashboard' + +function App() { + return +} +``` + +## API Reference + +### Persistence Control + +```typescript +// Flush all pending operations immediately +await flushPersistence() + +// Configure a specific slice +configurePersistence('files', { + debounceMs: 500, + syncToFlask: false, +}) + +// Enable/disable persistence for a slice +enablePersistence('models') +disablePersistence('theme') +``` + +### Sync Operations + +```typescript +import { syncToFlaskBulk, syncFromFlaskBulk } from '@/store/slices/syncSlice' + +// Push all data to Flask +dispatch(syncToFlaskBulk()) + +// Pull all data from Flask +dispatch(syncFromFlaskBulk()) + +// Check Flask connection +dispatch(checkFlaskConnection()) +``` + +### Metrics Management + +```typescript +import { getSyncMetrics, resetSyncMetrics } from '@/store/middleware' + +// Get current metrics +const metrics = getSyncMetrics() + +// Reset all metrics +resetSyncMetrics() +``` + +## Data Flow Examples + +### Create Operation +``` +User creates file + → Redux action dispatched (files/addItem) + → Persistence middleware intercepts action + → Item added to persistence queue with 300ms debounce + → After debounce, item saved to IndexedDB + → If syncToFlask enabled, item synced to Flask API + → Sync monitor tracks operation + → Metrics updated +``` + +### Update Operation +``` +User edits file + → Redux action dispatched (files/updateItem) + → Persistence middleware intercepts + → Existing debounce timer cleared + → New debounce timer started (300ms) + → After debounce, updated item saved to IndexedDB + → Item synced to Flask API + → Auto-sync change counter incremented +``` + +### Auto-Sync Operation +``` +Auto-sync timer fires (every 30s) + → Check if changes pending + → If changes > 0, trigger bulk sync + → Collect all items from IndexedDB + → Send bulk request to Flask API + → Update last sync timestamp + → Reset change counter + → Update connection status +``` + +## Performance Considerations + +### Debouncing +- Default 300ms debounce prevents excessive writes +- Each new action resets the timer for that item +- Independent timers per item (files:123, models:456) + +### Batching +- Operations batched for IndexedDB efficiency +- Maximum 10 operations per batch by default +- Failed operations don't block successful ones + +### Queue Management +- FIFO queue ensures order +- Latest operation for a key overwrites previous +- Automatic flush on component unmount + +## Error Handling + +### Connection Failures +- Automatically detected via health checks +- System gracefully degrades to local-only mode +- Queued operations retained for next successful connection + +### Operation Failures +- Failed operations logged to console +- Metrics track failure count +- Error messages displayed in dashboard +- Toast notifications for user feedback + +### Data Conflicts +- Detected during sync operations +- Timestamp comparison used for detection +- Manual resolution via conflict resolution UI +- Multiple resolution strategies available + +## Best Practices + +### 1. Enable Auto-Sync for Production +```typescript +configureAutoSync({ + enabled: true, + intervalMs: 30000, + syncOnChange: true, +}) +``` + +### 2. Monitor Metrics Regularly +```typescript +useEffect(() => { + const unsubscribe = subscribeSyncMetrics((metrics) => { + if (metrics.failedOperations > 10) { + console.warn('High failure rate detected') + } + }) + return unsubscribe +}, []) +``` + +### 3. Flush on Critical Operations +```typescript +async function saveProject() { + // ... save project data ... + await flushPersistence() + // Ensure all data is persisted before continuing +} +``` + +### 4. Configure Per-Slice Settings +```typescript +// Disable Flask sync for local-only data +configurePersistence('settings', { + syncToFlask: false, +}) + +// Increase debounce for frequently updated data +configurePersistence('theme', { + debounceMs: 1000, +}) +``` + +## Troubleshooting + +### Problem: Data not syncing to Flask +**Solution:** Check Flask connection status in dashboard. Verify Flask API is running and accessible. + +### Problem: High failure rate +**Solution:** Check network connection. Verify IndexedDB quota not exceeded. Check console for detailed error messages. + +### Problem: Slow performance +**Solution:** Increase debounce time. Reduce sync frequency. Disable Flask sync for non-critical data. + +### Problem: Auto-sync not working +**Solution:** Verify auto-sync is enabled in settings. Check that Flask is connected. Verify change counter is incrementing. + +## Future Enhancements + +- [ ] Optimistic updates with rollback +- [ ] Selective sync by data type +- [ ] Compression for large payloads +- [ ] Differential sync (only changed fields) +- [ ] WebSocket real-time sync +- [ ] Multi-device sync with conflict resolution +- [ ] Offline queue with retry logic +- [ ] Sync progress indicators diff --git a/PERSISTENCE_QUICK_START.md b/PERSISTENCE_QUICK_START.md new file mode 100644 index 0000000..1c107c7 --- /dev/null +++ b/PERSISTENCE_QUICK_START.md @@ -0,0 +1,384 @@ +# Redux Persistence Middleware - Quick Start Guide + +## What is it? + +The Redux Persistence Middleware automatically saves your Redux state changes to IndexedDB and optionally syncs them with a Flask API backend. **No manual database calls needed!** + +## Key Features + +✅ **Automatic Persistence** - State changes are automatically saved +✅ **Debounced Writes** - Efficient 300ms debouncing prevents excessive writes +✅ **Flask Sync** - Optional bidirectional sync with remote API +✅ **Metrics Tracking** - Real-time performance monitoring +✅ **Auto-Sync** - Periodic automatic synchronization +✅ **Zero Configuration** - Works out of the box + +## How It Works + +``` +Redux Action → Middleware Intercepts → Queue (300ms debounce) → IndexedDB + Flask API +``` + +## Basic Usage + +### 1. Just Use Redux Normally + +```typescript +import { useAppDispatch } from '@/store' +import { saveFile } from '@/store/slices/filesSlice' + +function MyComponent() { + const dispatch = useAppDispatch() + + const handleSave = () => { + // That's it! Middleware handles persistence automatically + dispatch(saveFile({ + id: 'file-1', + name: 'example.js', + content: 'console.log("Hello")', + language: 'javascript', + path: '/src/example.js', + updatedAt: Date.now() + })) + } + + return +} +``` + +### 2. Monitor Persistence Status + +```typescript +import { usePersistence } from '@/hooks/use-persistence' + +function StatusBar() { + const { status, metrics } = usePersistence() + + return ( +
+ Status: {status.syncStatus} + Operations: {metrics.totalOperations} + Success Rate: {(metrics.successfulOperations / metrics.totalOperations * 100)}% +
+ ) +} +``` + +### 3. Configure Auto-Sync + +```typescript +import { usePersistence } from '@/hooks/use-persistence' + +function Settings() { + const { configureAutoSync } = usePersistence() + + const enableAutoSync = () => { + configureAutoSync({ + enabled: true, + intervalMs: 30000, // Sync every 30 seconds + syncOnChange: true, // Sync when changes detected + maxQueueSize: 50 // Force sync after 50 changes + }) + } + + return +} +``` + +## Supported Redux Actions + +The middleware automatically handles these action patterns: + +### Files +- `files/addItem`, `files/updateItem`, `files/removeItem` +- `files/saveFile`, `files/deleteFile` +- `files/setFiles` + +### Models +- `models/addItem`, `models/updateItem`, `models/removeItem` +- `models/saveModel`, `models/deleteModel` +- `models/setModels` + +### Components +- `components/addItem`, `components/updateItem`, `components/removeItem` +- `components/saveComponent`, `components/deleteComponent` +- `components/setComponents` + +### Component Trees +- `componentTrees/addItem`, `componentTrees/updateItem`, `componentTrees/removeItem` +- `componentTrees/saveComponentTree`, `componentTrees/deleteComponentTree` +- `componentTrees/setComponentTrees` + +### Workflows, Lambdas, Theme +- Same pattern as above for `workflows/*`, `lambdas/*`, `theme/*` + +## Advanced Usage + +### Flush Pending Operations + +```typescript +import { usePersistence } from '@/hooks/use-persistence' + +function SaveButton() { + const { flush } = usePersistence() + + const handleCriticalSave = async () => { + // Force immediate persistence of all pending operations + await flush() + console.log('All data persisted!') + } + + return +} +``` + +### Configure Per-Slice Settings + +```typescript +import { configurePersistence } from '@/store/middleware' + +// Disable Flask sync for local-only data +configurePersistence('settings', { + syncToFlask: false +}) + +// Increase debounce for frequently updated data +configurePersistence('theme', { + debounceMs: 1000 +}) + +// Reduce debounce for critical data +configurePersistence('files', { + debounceMs: 100 +}) +``` + +### Disable/Enable Persistence + +```typescript +import { disablePersistence, enablePersistence } from '@/store/middleware' + +// Temporarily disable persistence (e.g., during bulk operations) +disablePersistence('files') + +// ... perform bulk operations ... + +// Re-enable persistence +enablePersistence('files') +``` + +### Manual Sync + +```typescript +import { useAppDispatch } from '@/store' +import { syncToFlaskBulk, syncFromFlaskBulk } from '@/store/slices/syncSlice' + +function SyncButtons() { + const dispatch = useAppDispatch() + + return ( + <> + + + + ) +} +``` + +## Monitoring & Debugging + +### View Dashboard + +Navigate to the **Persistence** page in the app to see: +- Real-time connection status +- Sync metrics and performance +- Auto-sync configuration +- Manual sync controls +- Error reporting + +### Subscribe to Metrics + +```typescript +import { subscribeSyncMetrics } from '@/store/middleware' + +useEffect(() => { + const unsubscribe = subscribeSyncMetrics((metrics) => { + console.log('Metrics updated:', metrics) + + if (metrics.failedOperations > 10) { + alert('High failure rate detected!') + } + }) + + return unsubscribe +}, []) +``` + +### Check Connection Status + +```typescript +import { useAppSelector } from '@/store' + +function ConnectionIndicator() { + const connected = useAppSelector((state) => state.sync.flaskConnected) + const status = useAppSelector((state) => state.sync.status) + + return ( +
+ {connected ? '🟢 Connected' : '🔴 Offline'} + | Status: {status} +
+ ) +} +``` + +## Performance Tips + +### 1. Use Appropriate Debounce Times +- **Fast UI updates**: 100-200ms (search, filters) +- **Standard forms**: 300ms (default, recommended) +- **Non-critical data**: 500-1000ms (preferences, settings) + +### 2. Batch Operations +```typescript +// ❌ Bad - Multiple individual operations +files.forEach(file => dispatch(saveFile(file))) + +// ✅ Good - Single batch operation +dispatch(setFiles(files)) +``` + +### 3. Disable During Bulk Operations +```typescript +import { disablePersistence, enablePersistence, flushPersistence } from '@/store/middleware' + +async function importData(largeDataset) { + disablePersistence('files') + + // Perform bulk operations without persistence overhead + largeDataset.forEach(item => dispatch(addItem(item))) + + enablePersistence('files') + + // Flush once after all operations + await flushPersistence() +} +``` + +## Common Patterns + +### Create with Auto-Save +```typescript +const handleCreate = () => { + dispatch(saveFile({ + id: generateId(), + name: 'new-file.js', + content: '', + language: 'javascript', + path: '/src/new-file.js', + updatedAt: Date.now() + })) + // Automatically persisted and synced! +} +``` + +### Update with Auto-Save +```typescript +const handleUpdate = (fileId, newContent) => { + dispatch(updateFile({ + id: fileId, + content: newContent, + updatedAt: Date.now() + })) + // Automatically persisted and synced! +} +``` + +### Delete with Auto-Save +```typescript +const handleDelete = (fileId) => { + dispatch(deleteFile(fileId)) + // Automatically removed from storage! +} +``` + +## Troubleshooting + +### Data not persisting? +1. Check that the slice is configured in `persistenceMiddleware.ts` +2. Verify the action matches supported patterns +3. Check console for errors + +### High failure rate? +1. Check Flask API is running +2. Verify network connection +3. Check IndexedDB quota not exceeded + +### Slow performance? +1. Increase debounce time +2. Reduce sync frequency +3. Disable Flask sync for non-critical data + +## Example: Complete CRUD Component + +```typescript +import { useAppDispatch, useAppSelector } from '@/store' +import { saveFile, deleteFile } from '@/store/slices/filesSlice' +import { usePersistence } from '@/hooks/use-persistence' + +function FileManager() { + const dispatch = useAppDispatch() + const files = useAppSelector((state) => state.files.files) + const { status } = usePersistence() + + const create = (name, content) => { + dispatch(saveFile({ + id: `file-${Date.now()}`, + name, + content, + language: 'javascript', + path: `/src/${name}`, + updatedAt: Date.now() + })) + } + + const update = (id, content) => { + const file = files.find(f => f.id === id) + if (file) { + dispatch(saveFile({ + ...file, + content, + updatedAt: Date.now() + })) + } + } + + const remove = (id) => { + dispatch(deleteFile(id)) + } + + return ( +
+
Status: {status.syncStatus}
+ {files.map(file => ( +
+ {file.name} + + +
+ ))} + +
+ ) +} +``` + +## Learn More + +- Full documentation: `PERSISTENCE_MIDDLEWARE_DOCS.md` +- View live dashboard: Navigate to **Persistence** page +- Try the demo: Navigate to **Persistence Demo** page diff --git a/PRD.md b/PRD.md index c56172a..dc5d150 100644 --- a/PRD.md +++ b/PRD.md @@ -43,6 +43,20 @@ A comprehensive state management system built with Redux Toolkit, seamlessly int - **Progression**: Sync attempt → Conflict detected → User notified → User reviews conflict details → User selects resolution strategy → Conflict resolved → Data synced - **Success criteria**: All conflicts detected accurately, side-by-side comparison of versions, multiple resolution strategies available, resolved data persists correctly +### Redux Persistence Middleware +- **Functionality**: Automatic synchronization between Redux state and storage systems (IndexedDB + Flask API) +- **Purpose**: Eliminate manual sync calls, ensure data consistency, provide seamless offline-first experience +- **Trigger**: Any Redux action that modifies persistable state (files, models, components, etc.) +- **Progression**: Redux action dispatched → Middleware intercepts → Debounced queue → Batch persist to IndexedDB → Optional Flask sync → Metrics updated +- **Success criteria**: All state changes automatically persisted within 300ms, zero data loss, transparent operation + +### Sync Monitoring System +- **Functionality**: Real-time tracking of all persistence and sync operations with comprehensive metrics +- **Purpose**: Provide visibility into system health, performance diagnostics, and debugging capabilities +- **Trigger**: Every persistence operation (successful or failed) +- **Progression**: Operation starts → Timestamp recorded → Operation completes → Duration calculated → Metrics updated → Listeners notified +- **Success criteria**: Sub-millisecond overhead, accurate metrics, real-time updates, 100-operation history + ### Auto-Sync System - **Functionality**: Configurable automatic synchronization at set intervals - **Purpose**: Reduce manual sync burden and ensure data is regularly backed up diff --git a/component-registry.json b/component-registry.json index 76502c4..36bc957 100644 --- a/component-registry.json +++ b/component-registry.json @@ -249,6 +249,24 @@ "category": "templates", "description": "Project template selection and preview" }, + { + "name": "PersistenceDashboard", + "path": "@/components/PersistenceDashboard", + "export": "PersistenceDashboard", + "type": "feature", + "preload": false, + "category": "database", + "description": "Redux persistence middleware monitoring and control dashboard" + }, + { + "name": "PersistenceExample", + "path": "@/components/PersistenceExample", + "export": "PersistenceExample", + "type": "feature", + "preload": false, + "category": "database", + "description": "Interactive example demonstrating automatic persistence" + }, { "name": "JSONUIShowcase", "path": "@/components/JSONUIShowcasePage", diff --git a/src/components/PersistenceDashboard.tsx b/src/components/PersistenceDashboard.tsx new file mode 100644 index 0000000..26550e4 --- /dev/null +++ b/src/components/PersistenceDashboard.tsx @@ -0,0 +1,288 @@ +import { useState, useEffect } from 'react' +import { Card } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { Separator } from '@/components/ui/separator' +import { Switch } from '@/components/ui/switch' +import { Label } from '@/components/ui/label' +import { + Database, + CloudArrowUp, + CloudArrowDown, + ArrowsClockwise, + CheckCircle, + XCircle, + Clock, + ChartLine, + Gauge +} from '@phosphor-icons/react' +import { usePersistence } from '@/hooks/use-persistence' +import { useAppDispatch, useAppSelector } from '@/store' +import { syncToFlaskBulk, syncFromFlaskBulk, checkFlaskConnection } from '@/store/slices/syncSlice' +import { toast } from 'sonner' + +export function PersistenceDashboard() { + const dispatch = useAppDispatch() + const { status, metrics, autoSyncStatus, syncNow, configureAutoSync } = usePersistence() + const [autoSyncEnabled, setAutoSyncEnabled] = useState(false) + const [syncing, setSyncing] = useState(false) + + useEffect(() => { + dispatch(checkFlaskConnection()) + const interval = setInterval(() => { + dispatch(checkFlaskConnection()) + }, 10000) + + return () => clearInterval(interval) + }, [dispatch]) + + const handleSyncToFlask = async () => { + setSyncing(true) + try { + await dispatch(syncToFlaskBulk()).unwrap() + toast.success('Successfully synced to Flask') + } catch (error: any) { + toast.error(`Sync failed: ${error}`) + } finally { + setSyncing(false) + } + } + + const handleSyncFromFlask = async () => { + setSyncing(true) + try { + await dispatch(syncFromFlaskBulk()).unwrap() + toast.success('Successfully synced from Flask') + } catch (error: any) { + toast.error(`Sync failed: ${error}`) + } finally { + setSyncing(false) + } + } + + const handleAutoSyncToggle = (enabled: boolean) => { + setAutoSyncEnabled(enabled) + configureAutoSync({ enabled, syncOnChange: true }) + toast.info(enabled ? 'Auto-sync enabled' : 'Auto-sync disabled') + } + + const handleManualSync = async () => { + try { + await syncNow() + toast.success('Manual sync completed') + } catch (error: any) { + toast.error(`Manual sync failed: ${error}`) + } + } + + const getStatusColor = () => { + if (!status.flaskConnected) return 'bg-destructive' + if (status.syncStatus === 'syncing') return 'bg-amber-500' + if (status.syncStatus === 'success') return 'bg-accent' + if (status.syncStatus === 'error') return 'bg-destructive' + return 'bg-muted' + } + + const getStatusText = () => { + if (!status.flaskConnected) return 'Disconnected' + if (status.syncStatus === 'syncing') return 'Syncing...' + if (status.syncStatus === 'success') return 'Synced' + if (status.syncStatus === 'error') return 'Error' + return 'Idle' + } + + const formatTime = (timestamp: number | null) => { + if (!timestamp) return 'Never' + const date = new Date(timestamp) + return date.toLocaleTimeString() + } + + const formatDuration = (ms: number) => { + if (ms < 1000) return `${ms}ms` + return `${(ms / 1000).toFixed(1)}s` + } + + const getSuccessRate = () => { + if (metrics.totalOperations === 0) return 0 + return Math.round((metrics.successfulOperations / metrics.totalOperations) * 100) + } + + return ( +
+
+

Persistence & Sync Dashboard

+ + {getStatusText()} + +
+ +
+ +
+

+ + Connection Status +

+ {status.flaskConnected ? ( + + ) : ( + + )} +
+ +
+
+ Local Storage: + IndexedDB +
+
+ Remote Storage: + + {status.flaskConnected ? 'Flask API' : 'Offline'} + +
+
+ Last Sync: + {formatTime(status.lastSyncTime)} +
+
+
+ + +
+

+ + Sync Metrics +

+ +
+ +
+
+ Total Operations: + {metrics.totalOperations} +
+
+ Success Rate: + {getSuccessRate()}% +
+
+ Avg Duration: + {formatDuration(metrics.averageOperationTime)} +
+
+ Failed: + {metrics.failedOperations} +
+
+
+ + +
+

+ + Auto-Sync +

+ +
+ +
+
+ Status: + + {autoSyncStatus.enabled ? 'Enabled' : 'Disabled'} + +
+
+ Changes Pending: + {autoSyncStatus.changeCounter} +
+
+ Next Sync: + + {autoSyncStatus.nextSyncIn !== null + ? formatDuration(autoSyncStatus.nextSyncIn) + : 'N/A'} + +
+
+
+
+ + +

+ + Manual Sync Operations +

+ +
+ + + + +
+
+ + {status.error && ( + +
+ +
+

Sync Error

+

{status.error}

+
+
+
+ )} + + +

How Persistence Works

+ +
+

+ Automatic Persistence: All Redux state changes are automatically persisted to IndexedDB with a 300ms debounce. +

+

+ Flask Sync: When connected, data is synced bidirectionally with the Flask API backend. +

+

+ Auto-Sync: Enable auto-sync to automatically push changes to Flask at regular intervals (default: 30s). +

+

+ Conflict Resolution: When conflicts are detected during sync, you'll be notified to resolve them manually. +

+
+
+
+ ) +} diff --git a/src/components/PersistenceExample.tsx b/src/components/PersistenceExample.tsx new file mode 100644 index 0000000..93460e5 --- /dev/null +++ b/src/components/PersistenceExample.tsx @@ -0,0 +1,251 @@ +import { useState } from 'react' +import { Card } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Badge } from '@/components/ui/badge' +import { Separator } from '@/components/ui/separator' +import { FloppyDisk, Trash, PencilSimple, CheckCircle, Clock } from '@phosphor-icons/react' +import { useAppDispatch, useAppSelector } from '@/store' +import { saveFile, deleteFile } from '@/store/slices/filesSlice' +import { toast } from 'sonner' + +export function PersistenceExample() { + const dispatch = useAppDispatch() + const files = useAppSelector((state) => state.files.files) + const [fileName, setFileName] = useState('') + const [fileContent, setFileContent] = useState('') + const [editingId, setEditingId] = useState(null) + + const handleSave = async () => { + if (!fileName.trim()) { + toast.error('File name is required') + return + } + + const fileItem = { + id: editingId || `file-${Date.now()}`, + name: fileName, + content: fileContent, + language: 'javascript', + path: `/src/${fileName}`, + updatedAt: Date.now(), + } + + try { + await dispatch(saveFile(fileItem)).unwrap() + toast.success(`File "${fileName}" saved automatically!`, { + description: 'Synced to IndexedDB and Flask API', + }) + setFileName('') + setFileContent('') + setEditingId(null) + } catch (error: any) { + toast.error('Failed to save file', { + description: error, + }) + } + } + + const handleEdit = (file: any) => { + setEditingId(file.id) + setFileName(file.name) + setFileContent(file.content) + } + + const handleDelete = async (fileId: string, name: string) => { + try { + await dispatch(deleteFile(fileId)).unwrap() + toast.success(`File "${name}" deleted`, { + description: 'Automatically synced to storage', + }) + } catch (error: any) { + toast.error('Failed to delete file', { + description: error, + }) + } + } + + const handleCancel = () => { + setFileName('') + setFileContent('') + setEditingId(null) + } + + return ( +
+
+

Persistence Middleware Example

+

+ Demonstrates automatic persistence of Redux state to IndexedDB and Flask API +

+
+ +
+ +
+

+ {editingId ? 'Edit File' : 'Create File'} +

+ {editingId && ( + + Editing + + )} +
+ + +
+
+ + setFileName(e.target.value)} + placeholder="example.js" + /> +
+ +
+ +