From 747a6e948558ff10d4b50bf0ad174e76c9319e28 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 00:39:03 +0000 Subject: [PATCH] Refactor storage settings components --- src/components/StorageSettingsPanel.tsx | 495 ++++++++---------- src/components/molecules/StorageSettings.tsx | 392 +++++++------- .../storage/storageSettingsConfig.ts | 13 + .../storage/storageSettingsCopy.json | 106 ++++ .../storage/storageSettingsUtils.ts | 34 ++ .../storage/useStorageDataHandlers.ts | 57 ++ .../storage/useStorageSwitchHandlers.ts | 85 +++ 7 files changed, 699 insertions(+), 483 deletions(-) create mode 100644 src/components/storage/storageSettingsConfig.ts create mode 100644 src/components/storage/storageSettingsCopy.json create mode 100644 src/components/storage/storageSettingsUtils.ts create mode 100644 src/components/storage/useStorageDataHandlers.ts create mode 100644 src/components/storage/useStorageSwitchHandlers.ts diff --git a/src/components/StorageSettingsPanel.tsx b/src/components/StorageSettingsPanel.tsx index 41e7a48..7b62368 100644 --- a/src/components/StorageSettingsPanel.tsx +++ b/src/components/StorageSettingsPanel.tsx @@ -1,12 +1,186 @@ +import { useState } 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 { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { useStorageBackend } from '@/hooks/use-unified-storage' -import { Database, HardDrive, Cloud, Download, Upload, CircleNotch, CloudArrowUp } from '@phosphor-icons/react' -import { toast } from 'sonner' -import { useState } from 'react' +import { + Database, + HardDrive, + Cloud, + Download, + Upload, + CircleNotch, + CloudArrowUp, +} from '@phosphor-icons/react' +import { storageSettingsCopy, getBackendCopy, type StorageBackendKey } from '@/components/storage/storageSettingsConfig' +import { useStorageSwitchHandlers } from '@/components/storage/useStorageSwitchHandlers' +import { useStorageDataHandlers } from '@/components/storage/useStorageDataHandlers' + +const getBackendIcon = (backend: StorageBackendKey | null) => { + switch (backend) { + case 'flask': + return + case 'sqlite': + return + case 'indexeddb': + return + case 'sparkkv': + return + default: + return + } +} + +const StorageSettingsPanelHeader = ({ description }: { description: string }) => ( + + + + {storageSettingsCopy.panel.title} + + {description} + +) + +const BackendStatusSummary = ({ backend }: { backend: StorageBackendKey | null }) => { + const backendCopy = getBackendCopy(backend) + + return ( +
+
+
+ {storageSettingsCopy.panel.currentBackendLabel} + {getBackendIcon(backend)} + {backendCopy.panelLabel} +
+ {backend?.toUpperCase() || backendCopy.badgeLabel.toUpperCase()} +
+

{backendCopy.panelDescription}

+
+ ) +} + +type BackendSwitcherProps = { + backend: StorageBackendKey | null + flaskUrl: string + isSwitching: boolean + onFlaskUrlChange: (value: string) => void + onSwitchToIndexedDB: () => void + onSwitchToFlask: () => void + onSwitchToSQLite: () => void +} + +const BackendSwitcher = ({ + backend, + flaskUrl, + isSwitching, + onFlaskUrlChange, + onSwitchToIndexedDB, + onSwitchToFlask, + onSwitchToSQLite, +}: BackendSwitcherProps) => ( +
+

{storageSettingsCopy.panel.switchTitle}

+ +
+
+ + onFlaskUrlChange(e.target.value)} + placeholder={storageSettingsCopy.panel.flaskUrlPlaceholder} + className="mt-1" + /> +

{storageSettingsCopy.panel.flaskUrlHelp}

+
+
+ +
+ + + +
+

{storageSettingsCopy.panel.switchHelp}

+
+) + +type DataManagementSectionProps = { + isExporting: boolean + isImporting: boolean + onExport: () => void + onImport: () => void +} + +const DataManagementSection = ({ + isExporting, + isImporting, + onExport, + onImport, +}: DataManagementSectionProps) => ( +
+

{storageSettingsCopy.panel.dataTitle}

+
+ + +
+

{storageSettingsCopy.panel.dataHelp}

+
+) export function StorageSettingsPanel() { const { @@ -19,168 +193,33 @@ export function StorageSettingsPanel() { importData, } = useStorageBackend() - const [isSwitching, setIsSwitching] = useState(false) - const [isExporting, setIsExporting] = useState(false) - const [isImporting, setIsImporting] = useState(false) - const [flaskUrl, setFlaskUrl] = useState(localStorage.getItem('codeforge-flask-url') || 'http://localhost:5001') + const [flaskUrl, setFlaskUrl] = useState( + localStorage.getItem('codeforge-flask-url') || storageSettingsCopy.panel.flaskUrlPlaceholder + ) - const handleSwitchToFlask = async () => { - if (backend === 'flask') { - toast.info('Already using Flask backend') - return - } + const { isSwitching, handleSwitchToFlask, handleSwitchToSQLite, handleSwitchToIndexedDB } = + useStorageSwitchHandlers({ + backend, + flaskUrl, + switchToFlask, + switchToSQLite, + switchToIndexedDB, + }) - if (!flaskUrl) { - toast.error('Please enter a Flask backend URL') - return - } - - setIsSwitching(true) - try { - await switchToFlask(flaskUrl) - toast.success('Switched to Flask backend') - } catch (error) { - toast.error(`Failed to switch to Flask: ${error instanceof Error ? error.message : 'Unknown error'}`) - } finally { - setIsSwitching(false) - } - } - - const handleSwitchToSQLite = async () => { - if (backend === 'sqlite') { - toast.info('Already using SQLite') - return - } - - setIsSwitching(true) - try { - await switchToSQLite() - toast.success('Switched to SQLite storage') - } catch (error) { - toast.error(`Failed to switch to SQLite: ${error instanceof Error ? error.message : 'Unknown error'}`) - } finally { - setIsSwitching(false) - } - } - - const handleSwitchToIndexedDB = async () => { - if (backend === 'indexeddb') { - toast.info('Already using IndexedDB') - return - } - - setIsSwitching(true) - try { - await switchToIndexedDB() - toast.success('Switched to IndexedDB storage (default)') - } catch (error) { - toast.error(`Failed to switch to IndexedDB: ${error instanceof Error ? error.message : 'Unknown error'}`) - } finally { - setIsSwitching(false) - } - } - - const handleExport = async () => { - setIsExporting(true) - try { - const data = await exportData() - const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }) - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = `codeforge-data-${new Date().toISOString().split('T')[0]}.json` - document.body.appendChild(a) - a.click() - document.body.removeChild(a) - URL.revokeObjectURL(url) - toast.success('Data exported successfully') - } catch (error) { - toast.error(`Failed to export data: ${error instanceof Error ? error.message : 'Unknown error'}`) - } finally { - setIsExporting(false) - } - } - - const handleImport = async () => { - const input = document.createElement('input') - input.type = 'file' - input.accept = 'application/json' - - input.onchange = async (e) => { - const file = (e.target as HTMLInputElement).files?.[0] - if (!file) return - - setIsImporting(true) - try { - const text = await file.text() - const data = JSON.parse(text) - await importData(data) - toast.success('Data imported successfully') - } catch (error) { - toast.error(`Failed to import data: ${error instanceof Error ? error.message : 'Unknown error'}`) - } finally { - setIsImporting(false) - } - } - - input.click() - } - - const getBackendIcon = () => { - switch (backend) { - case 'flask': - return - case 'sqlite': - return - case 'indexeddb': - return - case 'sparkkv': - return - default: - return - } - } - - const getBackendLabel = () => { - switch (backend) { - case 'flask': - return 'Flask Backend (Remote)' - case 'sqlite': - return 'SQLite (On-disk)' - case 'indexeddb': - return 'IndexedDB (Browser) - Default' - case 'sparkkv': - return 'Spark KV (Cloud)' - default: - return 'Unknown' - } - } - - const getBackendDescription = () => { - switch (backend) { - case 'flask': - return 'Data stored on Flask server with SQLite (cross-device sync)' - case 'sqlite': - return 'Data stored in SQLite database persisted to localStorage' - case 'indexeddb': - return 'Data stored in browser IndexedDB (default, recommended for most users)' - case 'sparkkv': - return 'Data stored in Spark cloud key-value store' - default: - return 'No storage backend detected' - } - } + const { isExporting, isImporting, handleExport, handleImport } = useStorageDataHandlers({ + exportData, + importData, + exportFilename: () => { + const dateStamp = new Date().toISOString().split('T')[0] + return `${storageSettingsCopy.panel.exportFilenamePrefix}-${dateStamp}.json` + }, + importAccept: 'application/json', + }) if (isLoading && !backend) { return ( - - - - Storage Settings - - Detecting storage backend... - +
@@ -192,128 +231,24 @@ export function StorageSettingsPanel() { return ( - - - - Storage Settings - - - Manage your local data storage preferences - - + -
-
-
- Current Backend: - {getBackendIcon()} - {getBackendLabel()} -
- {backend?.toUpperCase() || 'UNKNOWN'} -
-

{getBackendDescription()}

-
- -
-

Switch Storage Backend

- -
-
- - setFlaskUrl(e.target.value)} - placeholder="http://localhost:5001" - className="mt-1" - /> -

- Enter Flask backend URL or set VITE_FLASK_BACKEND_URL environment variable -

-
-
- -
- - - -
-

- IndexedDB is the default and works offline. Flask backend enables cross-device sync. -

-
- -
-

Data Management

-
- - -
-

- Export your data as a JSON file or import from a previous backup -

-
+ + +
) diff --git a/src/components/molecules/StorageSettings.tsx b/src/components/molecules/StorageSettings.tsx index f50a166..20fa6b0 100644 --- a/src/components/molecules/StorageSettings.tsx +++ b/src/components/molecules/StorageSettings.tsx @@ -5,8 +5,165 @@ import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Badge } from '@/components/ui/badge' import { useStorageBackend } from '@/hooks/use-unified-storage' -import { toast } from 'sonner' import { Database, HardDrive, Cloud, Cpu, Download, Upload } from '@phosphor-icons/react' +import { + storageSettingsCopy, + getBackendCopy, + type StorageBackendKey, +} from '@/components/storage/storageSettingsConfig' +import { useStorageSwitchHandlers } from '@/components/storage/useStorageSwitchHandlers' +import { useStorageDataHandlers } from '@/components/storage/useStorageDataHandlers' + +const getBackendIcon = (backend: StorageBackendKey | null) => { + switch (backend) { + case 'flask': + return + case 'indexeddb': + return + case 'sqlite': + return + case 'sparkkv': + return + default: + return + } +} + +type BackendCardProps = { + backend: StorageBackendKey | null + isLoading: boolean + flaskUrl: string + isSwitching: boolean + onFlaskUrlChange: (value: string) => void + onSwitchToFlask: () => void + onSwitchToIndexedDB: () => void + onSwitchToSQLite: () => void +} + +const BackendCard = ({ + backend, + isLoading, + flaskUrl, + isSwitching, + onFlaskUrlChange, + onSwitchToFlask, + onSwitchToIndexedDB, + onSwitchToSQLite, +}: BackendCardProps) => { + const backendCopy = getBackendCopy(backend) + + return ( + + + + {getBackendIcon(backend)} + {storageSettingsCopy.molecule.title} + + {storageSettingsCopy.molecule.description} + + +
+ {storageSettingsCopy.molecule.currentBackendLabel} + + {getBackendIcon(backend)} + {backendCopy.moleculeLabel} + +
+ +
+
+ +
+ onFlaskUrlChange(e.target.value)} + placeholder={storageSettingsCopy.molecule.flaskUrlPlaceholder} + disabled={isSwitching || isLoading} + /> + +
+

{storageSettingsCopy.molecule.flaskHelp}

+
+ +
+ + +
+ +
+

{storageSettingsCopy.molecule.backendDetails.indexeddb}

+

{storageSettingsCopy.molecule.backendDetails.sqlite}

+

{storageSettingsCopy.molecule.backendDetails.flask}

+
+
+
+
+ ) +} + +type DataManagementCardProps = { + isExporting: boolean + isImporting: boolean + onExport: () => void + onImport: () => void +} + +const DataManagementCard = ({ + isExporting, + isImporting, + onExport, + onImport, +}: DataManagementCardProps) => ( + + + {storageSettingsCopy.molecule.dataTitle} + {storageSettingsCopy.molecule.dataDescription} + + +
+ + +
+

{storageSettingsCopy.molecule.dataHelp}

+
+
+) export function StorageSettings() { const { @@ -20,214 +177,43 @@ export function StorageSettings() { } = useStorageBackend() const [flaskUrl, setFlaskUrl] = useState( - localStorage.getItem('codeforge-flask-url') || 'http://localhost:5001' + localStorage.getItem('codeforge-flask-url') || storageSettingsCopy.molecule.flaskUrlPlaceholder ) - const [isSwitching, setIsSwitching] = useState(false) - const handleSwitchToFlask = async () => { - setIsSwitching(true) - try { - await switchToFlask(flaskUrl) - toast.success('Switched to Flask backend') - } catch (error) { - toast.error(`Failed to switch to Flask: ${error}`) - } finally { - setIsSwitching(false) - } - } + const { isSwitching, handleSwitchToFlask, handleSwitchToSQLite, handleSwitchToIndexedDB } = + useStorageSwitchHandlers({ + backend, + flaskUrl, + switchToFlask, + switchToSQLite, + switchToIndexedDB, + }) - const handleSwitchToIndexedDB = async () => { - setIsSwitching(true) - try { - await switchToIndexedDB() - toast.success('Switched to IndexedDB') - } catch (error) { - toast.error(`Failed to switch to IndexedDB: ${error}`) - } finally { - setIsSwitching(false) - } - } - - const handleSwitchToSQLite = async () => { - setIsSwitching(true) - try { - await switchToSQLite() - toast.success('Switched to SQLite') - } catch (error) { - toast.error(`Failed to switch to SQLite: ${error}`) - } finally { - setIsSwitching(false) - } - } - - const handleExport = async () => { - try { - const data = await exportData() - const json = JSON.stringify(data, null, 2) - const blob = new Blob([json], { type: 'application/json' }) - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = `codeforge-backup-${Date.now()}.json` - document.body.appendChild(a) - a.click() - document.body.removeChild(a) - URL.revokeObjectURL(url) - toast.success('Data exported successfully') - } catch (error) { - toast.error(`Failed to export data: ${error}`) - } - } - - const handleImport = () => { - const input = document.createElement('input') - input.type = 'file' - input.accept = '.json' - input.onchange = async (e) => { - const file = (e.target as HTMLInputElement).files?.[0] - if (!file) return - - try { - const text = await file.text() - const data = JSON.parse(text) - await importData(data) - toast.success('Data imported successfully') - } catch (error) { - toast.error(`Failed to import data: ${error}`) - } - } - input.click() - } - - const getBackendIcon = (backendType: string | null) => { - switch (backendType) { - case 'flask': - return - case 'indexeddb': - return - case 'sqlite': - return - case 'sparkkv': - return - default: - return - } - } - - const getBackendLabel = (backendType: string | null) => { - switch (backendType) { - case 'flask': - return 'Flask Backend' - case 'indexeddb': - return 'IndexedDB' - case 'sqlite': - return 'SQLite' - case 'sparkkv': - return 'Spark KV' - default: - return 'Unknown' - } - } + const { isExporting, isImporting, handleExport, handleImport } = useStorageDataHandlers({ + exportData, + importData, + exportFilename: () => `${storageSettingsCopy.molecule.exportFilenamePrefix}-${Date.now()}.json`, + importAccept: '.json', + }) return (
- - - - {getBackendIcon(backend)} - Storage Backend - - - Choose where your data is stored - - - -
- Current backend: - - {getBackendIcon(backend)} - {getBackendLabel(backend)} - -
- -
-
- -
- setFlaskUrl(e.target.value)} - placeholder="http://localhost:5001" - disabled={isSwitching || isLoading} - /> - -
-

- Store data on a Flask server (persistent across devices) -

-
- -
- - -
- -
-

IndexedDB (Default): Browser storage, large capacity, works offline

-

SQLite: Browser storage with SQL queries, requires sql.js package

-

Flask: Server storage, persistent across devices, requires backend

-
-
-
-
- - - - Data Management - - Export or import your data - - - -
- - -
-

- Backup your data to a JSON file or restore from a previous backup -

-
-
+ +
) } diff --git a/src/components/storage/storageSettingsConfig.ts b/src/components/storage/storageSettingsConfig.ts new file mode 100644 index 0000000..34e28f7 --- /dev/null +++ b/src/components/storage/storageSettingsConfig.ts @@ -0,0 +1,13 @@ +import copy from './storageSettingsCopy.json' + +export type StorageBackendKey = 'flask' | 'sqlite' | 'indexeddb' | 'sparkkv' + +export const storageSettingsCopy = copy + +export const getBackendCopy = (backend: StorageBackendKey | null) => { + if (!backend) { + return storageSettingsCopy.backends.unknown + } + + return storageSettingsCopy.backends[backend] ?? storageSettingsCopy.backends.unknown +} diff --git a/src/components/storage/storageSettingsCopy.json b/src/components/storage/storageSettingsCopy.json new file mode 100644 index 0000000..1f11e0d --- /dev/null +++ b/src/components/storage/storageSettingsCopy.json @@ -0,0 +1,106 @@ +{ + "panel": { + "title": "Storage Settings", + "description": "Manage your local data storage preferences", + "loadingDescription": "Detecting storage backend...", + "currentBackendLabel": "Current Backend:", + "switchTitle": "Switch Storage Backend", + "flaskUrlLabel": "Flask Backend URL (Optional)", + "flaskUrlPlaceholder": "http://localhost:5001", + "flaskUrlHelp": "Enter Flask backend URL or set VITE_FLASK_BACKEND_URL environment variable", + "switchHelp": "IndexedDB is the default and works offline. Flask backend enables cross-device sync.", + "dataTitle": "Data Management", + "dataHelp": "Export your data as a JSON file or import from a previous backup", + "buttons": { + "indexeddb": "IndexedDB (Default)", + "flask": "Flask Backend", + "sqlite": "SQLite", + "export": "Export Data", + "import": "Import Data" + }, + "exportFilenamePrefix": "codeforge-data" + }, + "molecule": { + "title": "Storage Backend", + "description": "Choose where your data is stored", + "currentBackendLabel": "Current backend:", + "flaskUrlLabel": "Flask Backend URL", + "flaskUrlPlaceholder": "http://localhost:5001", + "flaskHelp": "Store data on a Flask server (persistent across devices)", + "dataTitle": "Data Management", + "dataDescription": "Export or import your data", + "dataHelp": "Backup your data to a JSON file or restore from a previous backup", + "buttons": { + "flaskActive": "Active", + "flaskUse": "Use Flask", + "indexeddbActive": "Active", + "indexeddbUse": "Use IndexedDB", + "sqliteActive": "Active", + "sqliteUse": "Use SQLite", + "export": "Export Data", + "import": "Import Data" + }, + "backendDetails": { + "indexeddb": "IndexedDB (Default): Browser storage, large capacity, works offline", + "sqlite": "SQLite: Browser storage with SQL queries, requires sql.js package", + "flask": "Flask: Server storage, persistent across devices, requires backend" + }, + "exportFilenamePrefix": "codeforge-backup" + }, + "backends": { + "flask": { + "panelLabel": "Flask Backend (Remote)", + "panelDescription": "Data stored on Flask server with SQLite (cross-device sync)", + "moleculeLabel": "Flask Backend", + "badgeLabel": "Flask" + }, + "sqlite": { + "panelLabel": "SQLite (On-disk)", + "panelDescription": "Data stored in SQLite database persisted to localStorage", + "moleculeLabel": "SQLite", + "badgeLabel": "SQLite" + }, + "indexeddb": { + "panelLabel": "IndexedDB (Browser) - Default", + "panelDescription": "Data stored in browser IndexedDB (default, recommended for most users)", + "moleculeLabel": "IndexedDB", + "badgeLabel": "IndexedDB" + }, + "sparkkv": { + "panelLabel": "Spark KV (Cloud)", + "panelDescription": "Data stored in Spark cloud key-value store", + "moleculeLabel": "Spark KV", + "badgeLabel": "Spark KV" + }, + "unknown": { + "panelLabel": "Unknown", + "panelDescription": "No storage backend detected", + "moleculeLabel": "Unknown", + "badgeLabel": "Unknown" + } + }, + "toasts": { + "alreadyUsing": { + "flask": "Already using Flask backend", + "sqlite": "Already using SQLite", + "indexeddb": "Already using IndexedDB" + }, + "errors": { + "missingFlaskUrl": "Please enter a Flask backend URL" + }, + "success": { + "switchFlask": "Switched to Flask backend", + "switchSQLite": "Switched to SQLite storage", + "switchIndexedDB": "Switched to IndexedDB storage (default)", + "export": "Data exported successfully", + "import": "Data imported successfully" + }, + "failure": { + "switchFlask": "Failed to switch to Flask", + "switchSQLite": "Failed to switch to SQLite", + "switchIndexedDB": "Failed to switch to IndexedDB", + "export": "Failed to export data", + "import": "Failed to import data" + } + } +} diff --git a/src/components/storage/storageSettingsUtils.ts b/src/components/storage/storageSettingsUtils.ts new file mode 100644 index 0000000..cfe819f --- /dev/null +++ b/src/components/storage/storageSettingsUtils.ts @@ -0,0 +1,34 @@ +export const formatStorageError = (error: unknown) => { + if (error instanceof Error) { + return error.message + } + + return String(error) +} + +export const downloadJson = (data: unknown, filename: string) => { + const json = JSON.stringify(data, null, 2) + const blob = new Blob([json], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const anchor = document.createElement('a') + + anchor.href = url + anchor.download = filename + document.body.appendChild(anchor) + anchor.click() + document.body.removeChild(anchor) + URL.revokeObjectURL(url) +} + +export const createJsonFileInput = (accept: string, onFileLoaded: (file: File) => void) => { + const input = document.createElement('input') + input.type = 'file' + input.accept = accept + input.onchange = (event) => { + const file = (event.target as HTMLInputElement).files?.[0] + if (file) { + onFileLoaded(file) + } + } + input.click() +} diff --git a/src/components/storage/useStorageDataHandlers.ts b/src/components/storage/useStorageDataHandlers.ts new file mode 100644 index 0000000..6689bad --- /dev/null +++ b/src/components/storage/useStorageDataHandlers.ts @@ -0,0 +1,57 @@ +import { useCallback, useState } from 'react' +import { toast } from 'sonner' +import { createJsonFileInput, downloadJson, formatStorageError } from './storageSettingsUtils' +import { storageSettingsCopy } from './storageSettingsConfig' + +type DataHandlers = { + exportData: () => Promise + importData: (data: unknown) => Promise + exportFilename: () => string + importAccept: string +} + +export const useStorageDataHandlers = ({ + exportData, + importData, + exportFilename, + importAccept, +}: DataHandlers) => { + const [isExporting, setIsExporting] = useState(false) + const [isImporting, setIsImporting] = useState(false) + + const handleExport = useCallback(async () => { + setIsExporting(true) + try { + const data = await exportData() + downloadJson(data, exportFilename()) + toast.success(storageSettingsCopy.toasts.success.export) + } catch (error) { + toast.error(`${storageSettingsCopy.toasts.failure.export}: ${formatStorageError(error)}`) + } finally { + setIsExporting(false) + } + }, [exportData, exportFilename]) + + const handleImport = useCallback(() => { + createJsonFileInput(importAccept, async (file) => { + setIsImporting(true) + try { + const text = await file.text() + const data = JSON.parse(text) + await importData(data) + toast.success(storageSettingsCopy.toasts.success.import) + } catch (error) { + toast.error(`${storageSettingsCopy.toasts.failure.import}: ${formatStorageError(error)}`) + } finally { + setIsImporting(false) + } + }) + }, [importAccept, importData]) + + return { + isExporting, + isImporting, + handleExport, + handleImport, + } +} diff --git a/src/components/storage/useStorageSwitchHandlers.ts b/src/components/storage/useStorageSwitchHandlers.ts new file mode 100644 index 0000000..5f52c4c --- /dev/null +++ b/src/components/storage/useStorageSwitchHandlers.ts @@ -0,0 +1,85 @@ +import { useCallback, useState } from 'react' +import { toast } from 'sonner' +import { formatStorageError } from './storageSettingsUtils' +import { storageSettingsCopy, type StorageBackendKey } from './storageSettingsConfig' + +type SwitchHandlers = { + backend: StorageBackendKey | null + flaskUrl: string + switchToFlask: (url: string) => Promise + switchToSQLite: () => Promise + switchToIndexedDB: () => Promise +} + +export const useStorageSwitchHandlers = ({ + backend, + flaskUrl, + switchToFlask, + switchToSQLite, + switchToIndexedDB, +}: SwitchHandlers) => { + const [isSwitching, setIsSwitching] = useState(false) + + const handleSwitchToFlask = useCallback(async () => { + if (backend === 'flask') { + toast.info(storageSettingsCopy.toasts.alreadyUsing.flask) + return + } + + if (!flaskUrl) { + toast.error(storageSettingsCopy.toasts.errors.missingFlaskUrl) + return + } + + setIsSwitching(true) + try { + await switchToFlask(flaskUrl) + toast.success(storageSettingsCopy.toasts.success.switchFlask) + } catch (error) { + toast.error(`${storageSettingsCopy.toasts.failure.switchFlask}: ${formatStorageError(error)}`) + } finally { + setIsSwitching(false) + } + }, [backend, flaskUrl, switchToFlask]) + + const handleSwitchToSQLite = useCallback(async () => { + if (backend === 'sqlite') { + toast.info(storageSettingsCopy.toasts.alreadyUsing.sqlite) + return + } + + setIsSwitching(true) + try { + await switchToSQLite() + toast.success(storageSettingsCopy.toasts.success.switchSQLite) + } catch (error) { + toast.error(`${storageSettingsCopy.toasts.failure.switchSQLite}: ${formatStorageError(error)}`) + } finally { + setIsSwitching(false) + } + }, [backend, switchToSQLite]) + + const handleSwitchToIndexedDB = useCallback(async () => { + if (backend === 'indexeddb') { + toast.info(storageSettingsCopy.toasts.alreadyUsing.indexeddb) + return + } + + setIsSwitching(true) + try { + await switchToIndexedDB() + toast.success(storageSettingsCopy.toasts.success.switchIndexedDB) + } catch (error) { + toast.error(`${storageSettingsCopy.toasts.failure.switchIndexedDB}: ${formatStorageError(error)}`) + } finally { + setIsSwitching(false) + } + }, [backend, switchToIndexedDB]) + + return { + isSwitching, + handleSwitchToFlask, + handleSwitchToSQLite, + handleSwitchToIndexedDB, + } +}