From aa5832225da708cfb980f202f3ac51f52259c1b3 Mon Sep 17 00:00:00 2001 From: lobo Date: Wed, 30 Sep 2026 08:46:51 +0200 Subject: [PATCH] refactor(import): move the import parts to components/import --- README.md | 7 +- src/components/import/ImportHistory.jsx | 76 ++++ src/components/import/ImportInput.jsx | 71 ++++ src/components/import/ImportReview.jsx | 152 ++++++++ src/components/import/ImportView.jsx | 170 +++++++++ src/pages/ImportPage.jsx | 483 +----------------------- src/pages/ImportPage.test.jsx | 3 +- src/utils/imports.js | 39 +- 8 files changed, 520 insertions(+), 481 deletions(-) create mode 100644 src/components/import/ImportHistory.jsx create mode 100644 src/components/import/ImportInput.jsx create mode 100644 src/components/import/ImportReview.jsx create mode 100644 src/components/import/ImportView.jsx diff --git a/README.md b/README.md index 65cfac1..42314a2 100644 --- a/README.md +++ b/README.md @@ -153,9 +153,10 @@ flowchart LR ```text src/ -├── pages/ # One component per route (Dashboard, GameLibrary, GameDetail, ...) -├── components/ # Shared components; ui/ holds the base components -├── hooks/ # Data hooks (library sync, Steam profile, appearance, session expiry) +├── pages/ # One component per route (Dashboard, GameLibrary, GameDetail, ...): loads data, places cards +├── components/ # Cards and shared components; ui/ holds the base components, import/ and stats/ the parts +│ # of one page that share page-only code +├── hooks/ # Data hooks (game, dashboard, library sync, Steam profile, appearance, session) ├── context/ # PreferencesContext: theme, language, appearance ├── utils/ # Validation schemas and helpers └── test/ # Test setup (i18n mock) diff --git a/src/components/import/ImportHistory.jsx b/src/components/import/ImportHistory.jsx new file mode 100644 index 0000000..232d6a4 --- /dev/null +++ b/src/components/import/ImportHistory.jsx @@ -0,0 +1,76 @@ +import {useState} from 'react'; +import {useTranslation} from 'react-i18next'; +import {toast} from 'sonner'; +import {RotateCcw} from 'lucide-react'; + +import {Badge} from '@/components/ui/badge'; +import {Button} from '@/components/ui/button'; +import {Card} from '@/components/ui/card'; +import {checkUndo, undoImport} from '../../utils/imports.js'; + +export default function ImportHistory({imports, onOpen, onChanged}) { + const {t, i18n} = useTranslation(); + const [confirm, setConfirm] = useState(null); + const [busy, setBusy] = useState(false); + + const run = async (call, onOk) => { + setBusy(true); + try { + const response = await call(); + if (response.status === 401) return; + if (!response.ok) { + toast.error(t('IMPORT.UNDO_FAILED')); + return; + } + await onOk(response); + } catch (err) { + console.error('Import undo error:', err); + toast.error(t('INFO.ERROR.NETWORK_FAILURE')); + } finally { + setBusy(false); + } + }; + const askUndo = (id) => run(() => checkUndo(id), async (response) => setConfirm({id, ...await response.json()})); + const undo = () => run(() => undoImport(confirm.id), () => { + toast.success(t('IMPORT.UNDONE', {count: confirm.games})); + setConfirm(null); + onChanged(); + }); + + return ( + +

{t('IMPORT.HISTORY')}

+ +
+ ); +} diff --git a/src/components/import/ImportInput.jsx b/src/components/import/ImportInput.jsx new file mode 100644 index 0000000..ac46c52 --- /dev/null +++ b/src/components/import/ImportInput.jsx @@ -0,0 +1,71 @@ +import {useState} from 'react'; +import {useTranslation} from 'react-i18next'; +import {Loader2, Search} from 'lucide-react'; + +import {Button} from '@/components/ui/button'; +import {Card} from '@/components/ui/card'; +import {Input} from '@/components/ui/input'; +import {Textarea} from '@/components/ui/textarea'; +import useStartImport from '../../hooks/useStartImport.js'; +import {parseCsv, parseTextList} from '../../utils/imports.js'; +import {MAX_IMPORT_ROWS} from '../../utils/limits.js'; + +export default function ImportInput({onCreated}) { + const {t} = useTranslation(); + const [source, setSource] = useState('TEXT'); + const [text, setText] = useState(''); + const [csv, setCsv] = useState(null); + const [busy, setBusy] = useState(false); + const rows = source === 'TEXT' ? parseTextList(text) : csv?.rows ?? []; + const tooMany = rows.length > MAX_IMPORT_ROWS; + + const readFile = async (file) => setCsv(file ? parseCsv(await file.text()) : null); + + const startImport = useStartImport(); + const start = async () => { + setBusy(true); + const id = await startImport(source, rows); + setBusy(false); + if (id) onCreated(id); + }; + + return ( + +

{t('IMPORT.HINT')}

+
+ {['TEXT', 'CSV'].map((value) => ( + + ))} +
+ {source === 'TEXT' ? ( +