diff --git a/src/components/Footer/Footer.jsx b/src/components/Footer/Footer.jsx
deleted file mode 100644
index 058b82a..0000000
--- a/src/components/Footer/Footer.jsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { createPortal } from 'react-dom';
-import { fetchDatabaseVersion, triggerFullImport } from '../../services/api';
-
-function Footer({ onImportComplete }) {
- const [dbVersion, setDbVersion] = useState(null);
- const [importing, setImporting] = useState(false);
- const [modalMessage, setModalMessage] = useState('');
- const [showModal, setShowModal] = useState(false);
-
- useEffect(() => {
- fetchDatabaseVersion()
- .then(data => setDbVersion(data.database_version))
- .catch(() => setDbVersion('N/A'));
- }, []);
-
- const handleImport = async () => {
- setImporting(true);
- try {
- const result = await triggerFullImport();
- const data = await fetchDatabaseVersion();
- setDbVersion(data.database_version);
- setModalMessage(result.message || 'Import completed');
- if (onImportComplete) await onImportComplete();
- } catch (err) {
- setModalMessage(`Import failed: ${err.message}`);
- } finally {
- setImporting(false);
- setShowModal(true);
- }
- };
-
- return (
- <>
-
setShowModal(false)} />
-
-
{modalMessage}
-
-
- >,
- document.body
- )}
- >
- );
-}
-
-export default Footer;
diff --git a/src/components/ImportButton/ImportButton.jsx b/src/components/ImportButton/ImportButton.jsx
new file mode 100644
index 0000000..b20d12e
--- /dev/null
+++ b/src/components/ImportButton/ImportButton.jsx
@@ -0,0 +1,125 @@
+import React, { useEffect, useState } from 'react';
+import { createPortal } from 'react-dom';
+import { fetchDatabaseVersion } from '../../services/api';
+
+function ImportButton({ onImportComplete }) {
+ const [dbVersion, setDbVersion] = useState(null);
+ const [importing, setImporting] = useState(false);
+ const [showModal, setShowModal] = useState(false);
+ const [progress, setProgress] = useState(0);
+ const [progressMessage, setProgressMessage] = useState('');
+ const [done, setDone] = useState(false);
+ const [resultMessage, setResultMessage] = useState('');
+ const [isError, setIsError] = useState(false);
+
+ useEffect(() => {
+ fetchDatabaseVersion()
+ .then(data => setDbVersion(data.database_version))
+ .catch(() => setDbVersion('N/A'));
+ }, []);
+
+ const handleImport = async () => {
+ setImporting(true);
+ setDone(false);
+ setIsError(false);
+ setProgress(0);
+ setProgressMessage('Starting…');
+ setResultMessage('');
+ setShowModal(true);
+
+ try {
+ const response = await fetch('/api/import/full-import', { method: 'POST' });
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder();
+ let buffer = '';
+
+ while (true) {
+ const { done: streamDone, value } = await reader.read();
+ if (streamDone) break;
+ buffer += decoder.decode(value, { stream: true });
+ const parts = buffer.split('\n\n');
+ buffer = parts.pop();
+ for (const part of parts) {
+ if (!part.startsWith('data: ')) continue;
+ const data = JSON.parse(part.slice(6));
+ if (data.progress !== undefined) setProgress(data.progress);
+ if (data.message) setProgressMessage(data.message);
+ if (data.done) {
+ setDone(true);
+ setImporting(false);
+ setIsError(!!data.error);
+ if (data.version) setDbVersion(data.version);
+ if (data.result) {
+ const r = data.result;
+ setResultMessage(
+ `${r.cards_added} added · ${r.cards_removed ?? 0} removed · ${r.total_cards.toLocaleString()} total · ${r.duration_seconds}s`
+ );
+ }
+ if (!data.error && onImportComplete) onImportComplete();
+ }
+ }
+ }
+ } catch (err) {
+ setProgressMessage(`Error: ${err.message}`);
+ setDone(true);
+ setIsError(true);
+ setImporting(false);
+ }
+ };
+
+ return (
+ <>
+
+ DB: {dbVersion ?? '…'}
+
+
+
+ {showModal && createPortal(
+ <>
+
done && setShowModal(false)} />
+
+
+ {progressMessage}
+
+
+ {!done && (
+
+ {Math.round(progress * 100)}%
+
+ )}
+ {done && (
+ <>
+ {resultMessage && (
+
{resultMessage}
+ )}
+
+ >
+ )}
+
+ >,
+ document.body
+ )}
+ >
+ );
+}
+
+export default ImportButton;
diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx
index 896d4cb..e17ae1b 100644
--- a/src/pages/HomePage.jsx
+++ b/src/pages/HomePage.jsx
@@ -8,7 +8,7 @@ import { fuzzyMatch } from '../utils/search';
import CardRow from '../components/CardRow/CardRow';
import SearchBar from '../components/SearchBar/SearchBar';
import FilterBar from '../components/FilterBar/FilterBar';
-import Footer from '../components/Footer/Footer';
+import ImportButton from '../components/ImportButton/ImportButton';
import PrintingRow from '../components/PrintingRow/PrintingRow';
const BADGE = {
@@ -234,7 +234,7 @@ function HomePage() {
onClick={exportCollection}
style={{ background: '#2a2a2a', color: '#ccc', border: '1px solid #3a3a3a', borderRadius: '5px', padding: '3px 10px', fontSize: '12px', cursor: 'pointer' }}
>Export
-
+
diff --git a/src/services/api.js b/src/services/api.js
index 3309ecf..b39d66b 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -37,13 +37,6 @@ export async function fetchDatabaseVersion() {
return await response.json();
}
-export async function triggerFullImport() {
- const response = await fetch(`${API_BASE}/import/full-import`, {
- method: 'POST'
- });
- if (!response.ok) throw new Error('Failed to trigger full import');
- return await response.json();
-}
export async function fetchSets() {
const response = await fetch(`${API_BASE}/sets`);