import { useState, useRef } from 'react'; import { toast } from 'sonner'; import { Database, Upload, AlertTriangle, Loader2 } from 'lucide-react'; import { api } from '@/api'; import { errMessage } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { SectionCard, CountPill, fmt, importErrorState, type SectionCardProps, type ImportErrorState } from './dataShared'; interface ImportSummaryEntry { create?: number; skip?: number; conflict?: number; } interface PreviewData { import_session_id?: string; counts?: Record; summary?: Record; metadata?: { exported_at?: string | null }; source_filename?: string | null; warnings?: string[]; } interface ApplyResult { rows_created?: number; rows_skipped?: number; rows_conflicted?: number; rows_errored?: number; } interface PreviewState { status: 'idle' | 'loading' | 'ready' | 'error'; data: PreviewData | null; error: ImportErrorState | null; } interface ApplyState { status: 'idle' | 'loading' | 'done' | 'error'; result: ApplyResult | null; error: ImportErrorState | null; } export default function ImportMyDataSection({ onHistoryRefresh, cardProps = {} }: { onHistoryRefresh?: () => void; cardProps?: Partial; }) { const fileRef = useRef(null); const [file, setFile] = useState(null); const [preview, setPreview] = useState({ status: 'idle', data: null, error: null }); const [applyState, setApplyState] = useState({ status: 'idle', result: null, error: null }); const [confirmOpen, setConfirmOpen] = useState(false); const reset = () => { setFile(null); setPreview({ status: 'idle', data: null, error: null }); setApplyState({ status: 'idle', result: null, error: null }); if (fileRef.current) fileRef.current.value = ''; }; const handlePreview = async () => { if (!file) { toast.error('Choose a SQLite data export first.'); return; } setPreview({ status: 'loading', data: null, error: null }); setApplyState({ status: 'idle', result: null, error: null }); try { const data = await api.previewUserDbImport(file) as PreviewData; setPreview({ status: 'ready', data, error: null }); toast.success('SQLite export preview ready.'); } catch (err) { setPreview({ status: 'error', data: null, error: importErrorState(err, 'SQLite import preview failed.') }); toast.error(errMessage(err, 'SQLite import preview failed.')); } }; const handleApply = () => { if (!preview.data?.import_session_id) return; setConfirmOpen(true); }; const handleConfirmImport = async () => { if (!preview.data?.import_session_id) return; setConfirmOpen(false); setApplyState({ status: 'loading', result: null, error: null }); try { const result = await api.applyUserDbImport({ import_session_id: preview.data.import_session_id, options: { overwrite: false }, }) as ApplyResult; setApplyState({ status: 'done', result, error: null }); toast.success('SQLite data import applied.'); onHistoryRefresh?.(); } catch (err) { setApplyState({ status: 'error', result: null, error: importErrorState(err, 'SQLite import apply failed.') }); toast.error(errMessage(err, 'SQLite import apply failed.')); } }; const counts = preview.data?.counts || {}; const summary = preview.data?.summary || {}; return ( <>

Import a SQLite data export created by this app.

This is not a full system restore. Existing records are skipped by default, and admin/system data is never imported.

{preview.status === 'error' && (
{preview.error?.message || 'SQLite import preview failed.'} {(preview.error?.details?.length ?? 0) > 0 && (
    {preview.error?.details.map((d, i) => { const dd = d as { message?: string; table?: string }; return
  • {dd.message || dd.table || JSON.stringify(d)}
  • ; })}
)}
)} {preview.status === 'ready' && preview.data && (

Preview ready

Exported {fmt(preview.data.metadata?.exported_at)} · {preview.data.source_filename || 'SQLite export'}

User data only
{Object.entries(summary).filter(([, v]) => v && typeof v === 'object').map(([key, value]) => (

{key.replace(/_/g, ' ')}

create {value.create || 0} · skip {value.skip || 0} · conflict {value.conflict || 0}

))}
{(preview.data.warnings?.length ?? 0) > 0 && (
{preview.data.warnings?.map((warning, i) => (

{warning}

))}
)}

Review the preview before applying. Nothing is imported until you confirm.

)} {applyState.status === 'done' && applyState.result && (

SQLite import applied

)} {applyState.status === 'error' && (
{applyState.error?.message || 'SQLite import apply failed.'}
)}
{/* Import confirmation dialog */} Import SQLite data export? Import this SQLite data export into your account? Existing records will be skipped by default. Cancel Confirm Import ); }