diff --git a/src/components/ReduxIntegrationDemo.tsx b/src/components/ReduxIntegrationDemo.tsx index 9ec3826..3c60706 100644 --- a/src/components/ReduxIntegrationDemo.tsx +++ b/src/components/ReduxIntegrationDemo.tsx @@ -1,39 +1,29 @@ import { useEffect } from 'react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Badge } from '@/components/ui/badge' -import { Separator } from '@/components/ui/separator' import { toast } from 'sonner' -import { - ArrowsClockwise, - Database, - CloudArrowUp, - CloudArrowDown, - CheckCircle, - XCircle, - Clock, - Trash, - FilePlus -} from '@phosphor-icons/react' import { useReduxFiles } from '@/hooks/use-redux-files' import { useReduxComponentTrees } from '@/hooks/use-redux-component-trees' import { useReduxSync } from '@/hooks/use-redux-sync' import { useAppSelector } from '@/store' +import { ComponentTreesCard } from '@/components/redux-integration/ComponentTreesCard' +import { DangerZoneCard } from '@/components/redux-integration/DangerZoneCard' +import { FilesCard } from '@/components/redux-integration/FilesCard' +import { ReduxIntegrationHeader } from '@/components/redux-integration/ReduxIntegrationHeader' +import { StatusCardsSection } from '@/components/redux-integration/StatusCardsSection' +import reduxIntegrationCopy from '@/data/redux-integration-demo.json' export function ReduxIntegrationDemo() { const { files, load: loadFiles, save: saveFile, remove: removeFile } = useReduxFiles() const { trees, load: loadTrees } = useReduxComponentTrees() - const { - status, - lastSyncedAt, - flaskConnected, + const { + status, + lastSyncedAt, + flaskConnected, flaskStats, - syncToFlask, + syncToFlask, syncFromFlask, checkConnection, - clearFlaskData + clearFlaskData, } = useReduxSync() - const settings = useAppSelector((state) => state.settings.settings) useEffect(() => { @@ -51,284 +41,49 @@ export function ReduxIntegrationDemo() { updatedAt: Date.now(), } saveFile(newFile) - toast.success('Test file created and saved to IndexedDB') + toast.success(reduxIntegrationCopy.toast.createTestFile) } const handleDeleteFile = (fileId: string) => { removeFile(fileId) - toast.success('File deleted from IndexedDB') + toast.success(reduxIntegrationCopy.toast.deleteFile) } const handleSyncUp = () => { syncToFlask() - toast.info('Syncing to Flask API...') + toast.info(reduxIntegrationCopy.toast.syncUp) } const handleSyncDown = () => { syncFromFlask() - toast.info('Syncing from Flask API...') + toast.info(reduxIntegrationCopy.toast.syncDown) } const handleClearFlask = () => { clearFlaskData() - toast.warning('Clearing Flask storage...') - } - - const getSyncStatusBadge = () => { - switch (status) { - case 'idle': - return Idle - case 'syncing': - return Syncing... - case 'success': - return Success - case 'error': - return Error - } - } - - const getConnectionBadge = () => { - return flaskConnected ? ( - - Connected - - ) : ( - - Disconnected - - ) + toast.warning(reduxIntegrationCopy.toast.clearFlask) } return (
-
-

Redux Integration Demo

-

- Comprehensive Redux Toolkit integration with IndexedDB and Flask API synchronization -

-
- -
- - - - - IndexedDB Status - - Local browser storage - - -
- Files - {files.length} -
-
- Component Trees - {trees.length} -
- - -
-
- - - - - - Flask API Status - - Remote server connection - - -
- Connection - {getConnectionBadge()} -
- {flaskStats && ( - <> -
- Total Keys - {flaskStats.totalKeys} -
-
- Storage Size - - {(flaskStats.totalSizeBytes / 1024).toFixed(2)} KB - -
- - )} - - -
-
- - - - - - Sync Status - - Data synchronization - - -
- Status - {getSyncStatusBadge()} -
-
- Auto Sync - - {settings.autoSync ? 'Enabled' : 'Disabled'} - -
- {lastSyncedAt && ( -
- Last Sync - - - {new Date(lastSyncedAt).toLocaleTimeString()} - -
- )} - -
- - -
-
-
-
- - - - Files in Redux Store - - Files managed by Redux and synced with IndexedDB/Flask - - - - {files.length === 0 ? ( -
- -

No files yet. Create a test file to get started.

-
- ) : ( -
- {files.map((file) => ( -
-
-
{file.name}
-
- {file.path} • Updated {new Date(file.updatedAt).toLocaleString()} -
-
- -
- ))} -
- )} -
-
- - - - Component Trees in Redux Store - - JSON component trees loaded from components.json - - - - {trees.length === 0 ? ( -
- -

No component trees loaded yet.

-
- ) : ( -
- {trees.map((tree) => ( -
-
-
{tree.name}
- {tree.description && ( -
{tree.description}
- )} -
- - {tree.root.type} - -
- ))} -
- )} -
-
- - - - Danger Zone - - Irreversible operations - use with caution - - - - - - + + + + +
) diff --git a/src/components/redux-integration/ComponentTreesCard.tsx b/src/components/redux-integration/ComponentTreesCard.tsx new file mode 100644 index 0000000..e07bb1c --- /dev/null +++ b/src/components/redux-integration/ComponentTreesCard.tsx @@ -0,0 +1,45 @@ +import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Database } from '@phosphor-icons/react' +import reduxIntegrationCopy from '@/data/redux-integration-demo.json' +import { ComponentTree } from '@/store/slices/componentTreesSlice' + +type ComponentTreesCardProps = { + trees: ComponentTree[] +} + +export function ComponentTreesCard({ trees }: ComponentTreesCardProps) { + return ( + + + {reduxIntegrationCopy.componentTrees.title} + {reduxIntegrationCopy.componentTrees.description} + + + {trees.length === 0 ? ( +
+ +

{reduxIntegrationCopy.componentTrees.empty}

+
+ ) : ( +
+ {trees.map((tree) => ( +
+
+
{tree.name}
+ {tree.description && ( +
{tree.description}
+ )} +
+ {tree.root.type} +
+ ))} +
+ )} +
+
+ ) +} diff --git a/src/components/redux-integration/DangerZoneCard.tsx b/src/components/redux-integration/DangerZoneCard.tsx new file mode 100644 index 0000000..acc53b7 --- /dev/null +++ b/src/components/redux-integration/DangerZoneCard.tsx @@ -0,0 +1,26 @@ +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Trash } from '@phosphor-icons/react' +import reduxIntegrationCopy from '@/data/redux-integration-demo.json' + +type DangerZoneCardProps = { + flaskConnected: boolean + onClearFlask: () => void +} + +export function DangerZoneCard({ flaskConnected, onClearFlask }: DangerZoneCardProps) { + return ( + + + {reduxIntegrationCopy.danger.title} + {reduxIntegrationCopy.danger.description} + + + + + + ) +} diff --git a/src/components/redux-integration/FilesCard.tsx b/src/components/redux-integration/FilesCard.tsx new file mode 100644 index 0000000..41d9464 --- /dev/null +++ b/src/components/redux-integration/FilesCard.tsx @@ -0,0 +1,50 @@ +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Database, Trash } from '@phosphor-icons/react' +import reduxIntegrationCopy from '@/data/redux-integration-demo.json' +import { FileItem } from '@/store/slices/filesSlice' + +type FilesCardProps = { + files: FileItem[] + onDeleteFile: (fileId: string) => void +} + +export function FilesCard({ files, onDeleteFile }: FilesCardProps) { + return ( + + + {reduxIntegrationCopy.files.title} + {reduxIntegrationCopy.files.description} + + + {files.length === 0 ? ( +
+ +

{reduxIntegrationCopy.files.empty}

+
+ ) : ( +
+ {files.map((file) => ( +
+
+
{file.name}
+
+ {file.path} • {reduxIntegrationCopy.files.updatedLabel}{' '} + {new Date(file.updatedAt).toLocaleString()} +
+
+ +
+ ))} +
+ )} +
+
+ ) +} diff --git a/src/components/redux-integration/FlaskStatusCard.tsx b/src/components/redux-integration/FlaskStatusCard.tsx new file mode 100644 index 0000000..d261292 --- /dev/null +++ b/src/components/redux-integration/FlaskStatusCard.tsx @@ -0,0 +1,76 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { Separator } from '@/components/ui/separator' +import { ArrowsClockwise, CheckCircle, CloudArrowUp, XCircle } from '@phosphor-icons/react' +import reduxIntegrationCopy from '@/data/redux-integration-demo.json' +type FlaskStats = { + totalKeys: number + totalSizeBytes: number +} | null + +type FlaskStatusCardProps = { + flaskConnected: boolean + flaskStats: FlaskStats + onCheckConnection: () => void +} + +export function FlaskStatusCard({ flaskConnected, flaskStats, onCheckConnection }: FlaskStatusCardProps) { + const connectionLabel = flaskConnected + ? reduxIntegrationCopy.cards.flask.status.connected + : reduxIntegrationCopy.cards.flask.status.disconnected + + return ( + + + + + {reduxIntegrationCopy.cards.flask.title} + + {reduxIntegrationCopy.cards.flask.description} + + +
+ + {reduxIntegrationCopy.cards.flask.labels.connection} + + {flaskConnected ? ( + + + {connectionLabel} + + ) : ( + + + {connectionLabel} + + )} +
+ {flaskStats && ( + <> +
+ + {reduxIntegrationCopy.cards.flask.labels.totalKeys} + + {flaskStats.totalKeys} +
+
+ + {reduxIntegrationCopy.cards.flask.labels.storageSize} + + + {(flaskStats.totalSizeBytes / 1024).toFixed(2)}{' '} + {reduxIntegrationCopy.cards.flask.labels.storageUnit} + +
+ + )} + + +
+
+ ) +} diff --git a/src/components/redux-integration/IndexedDbStatusCard.tsx b/src/components/redux-integration/IndexedDbStatusCard.tsx new file mode 100644 index 0000000..914b375 --- /dev/null +++ b/src/components/redux-integration/IndexedDbStatusCard.tsx @@ -0,0 +1,49 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { Separator } from '@/components/ui/separator' +import { Database, FilePlus } from '@phosphor-icons/react' +import reduxIntegrationCopy from '@/data/redux-integration-demo.json' + +type IndexedDbStatusCardProps = { + filesCount: number + treesCount: number + onCreateTestFile: () => void +} + +export function IndexedDbStatusCard({ + filesCount, + treesCount, + onCreateTestFile, +}: IndexedDbStatusCardProps) { + return ( + + + + + {reduxIntegrationCopy.cards.indexedDb.title} + + {reduxIntegrationCopy.cards.indexedDb.description} + + +
+ + {reduxIntegrationCopy.cards.indexedDb.labels.files} + + {filesCount} +
+
+ + {reduxIntegrationCopy.cards.indexedDb.labels.componentTrees} + + {treesCount} +
+ + +
+
+ ) +} diff --git a/src/components/redux-integration/ReduxIntegrationHeader.tsx b/src/components/redux-integration/ReduxIntegrationHeader.tsx new file mode 100644 index 0000000..1e9573b --- /dev/null +++ b/src/components/redux-integration/ReduxIntegrationHeader.tsx @@ -0,0 +1,10 @@ +import reduxIntegrationCopy from '@/data/redux-integration-demo.json' + +export function ReduxIntegrationHeader() { + return ( +
+

{reduxIntegrationCopy.page.title}

+

{reduxIntegrationCopy.page.description}

+
+ ) +} diff --git a/src/components/redux-integration/StatusCardsSection.tsx b/src/components/redux-integration/StatusCardsSection.tsx new file mode 100644 index 0000000..dd3010f --- /dev/null +++ b/src/components/redux-integration/StatusCardsSection.tsx @@ -0,0 +1,60 @@ +import { IndexedDbStatusCard } from '@/components/redux-integration/IndexedDbStatusCard' +import { FlaskStatusCard } from '@/components/redux-integration/FlaskStatusCard' +import { SyncStatusCard } from '@/components/redux-integration/SyncStatusCard' +import { SyncStatus } from '@/store/slices/syncSlice' + +type FlaskStats = { + totalKeys: number + totalSizeBytes: number +} | null + +type StatusCardsSectionProps = { + filesCount: number + treesCount: number + flaskConnected: boolean + flaskStats: FlaskStats + status: SyncStatus + lastSyncedAt: number | null + autoSyncEnabled: boolean + onCreateTestFile: () => void + onCheckConnection: () => void + onSyncUp: () => void + onSyncDown: () => void +} + +export function StatusCardsSection({ + filesCount, + treesCount, + flaskConnected, + flaskStats, + status, + lastSyncedAt, + autoSyncEnabled, + onCreateTestFile, + onCheckConnection, + onSyncUp, + onSyncDown, +}: StatusCardsSectionProps) { + return ( +
+ + + +
+ ) +} diff --git a/src/components/redux-integration/SyncStatusCard.tsx b/src/components/redux-integration/SyncStatusCard.tsx new file mode 100644 index 0000000..50673b4 --- /dev/null +++ b/src/components/redux-integration/SyncStatusCard.tsx @@ -0,0 +1,116 @@ +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Separator } from '@/components/ui/separator' +import { ArrowsClockwise, CheckCircle, Clock, CloudArrowDown, CloudArrowUp, XCircle } from '@phosphor-icons/react' +import reduxIntegrationCopy from '@/data/redux-integration-demo.json' +import { SyncStatus } from '@/store/slices/syncSlice' + +type SyncStatusCardProps = { + status: SyncStatus + lastSyncedAt: number | null + autoSyncEnabled: boolean + flaskConnected: boolean + onSyncUp: () => void + onSyncDown: () => void +} + +export function SyncStatusCard({ + status, + lastSyncedAt, + autoSyncEnabled, + flaskConnected, + onSyncUp, + onSyncDown, +}: SyncStatusCardProps) { + const getSyncStatusBadge = () => { + switch (status) { + case 'idle': + return {reduxIntegrationCopy.cards.sync.status.idle} + case 'syncing': + return ( + + {reduxIntegrationCopy.cards.sync.status.syncing} + + ) + case 'success': + return ( + + + {reduxIntegrationCopy.cards.sync.status.success} + + ) + case 'error': + return ( + + + {reduxIntegrationCopy.cards.sync.status.error} + + ) + } + } + + return ( + + + + + {reduxIntegrationCopy.cards.sync.title} + + {reduxIntegrationCopy.cards.sync.description} + + +
+ + {reduxIntegrationCopy.cards.sync.labels.status} + + {getSyncStatusBadge()} +
+
+ + {reduxIntegrationCopy.cards.sync.labels.autoSync} + + + {autoSyncEnabled + ? reduxIntegrationCopy.cards.sync.autoSync.enabled + : reduxIntegrationCopy.cards.sync.autoSync.disabled} + +
+ {lastSyncedAt && ( +
+ + {reduxIntegrationCopy.cards.sync.labels.lastSync} + + + + {new Date(lastSyncedAt).toLocaleTimeString()} + +
+ )} + +
+ + +
+
+
+ ) +} diff --git a/src/data/redux-integration-demo.json b/src/data/redux-integration-demo.json new file mode 100644 index 0000000..fa42cb1 --- /dev/null +++ b/src/data/redux-integration-demo.json @@ -0,0 +1,76 @@ +{ + "page": { + "title": "Redux Integration Demo", + "description": "Comprehensive Redux Toolkit integration with IndexedDB and Flask API synchronization" + }, + "cards": { + "indexedDb": { + "title": "IndexedDB Status", + "description": "Local browser storage", + "labels": { + "files": "Files", + "componentTrees": "Component Trees", + "createTestFile": "Create Test File" + } + }, + "flask": { + "title": "Flask API Status", + "description": "Remote server connection", + "labels": { + "connection": "Connection", + "totalKeys": "Total Keys", + "storageSize": "Storage Size", + "storageUnit": "KB", + "checkConnection": "Check Connection" + }, + "status": { + "connected": "Connected", + "disconnected": "Disconnected" + } + }, + "sync": { + "title": "Sync Status", + "description": "Data synchronization", + "labels": { + "status": "Status", + "autoSync": "Auto Sync", + "lastSync": "Last Sync", + "push": "Push", + "pull": "Pull" + }, + "status": { + "idle": "Idle", + "syncing": "Syncing...", + "success": "Success", + "error": "Error" + }, + "autoSync": { + "enabled": "Enabled", + "disabled": "Disabled" + } + } + }, + "files": { + "title": "Files in Redux Store", + "description": "Files managed by Redux and synced with IndexedDB/Flask", + "empty": "No files yet. Create a test file to get started.", + "updatedLabel": "Updated" + }, + "componentTrees": { + "title": "Component Trees in Redux Store", + "description": "JSON component trees loaded from components.json", + "empty": "No component trees loaded yet." + }, + "danger": { + "title": "Danger Zone", + "description": "Irreversible operations - use with caution", + "clearButton": "Clear Flask Storage" + }, + "toast": { + "createTestFile": "Test file created and saved to IndexedDB", + "deleteFile": "File deleted from IndexedDB", + "syncUp": "Syncing to Flask API...", + "syncDown": "Syncing from Flask API...", + "clearFlask": "Clearing Flask storage..." + } +}