diff options
Diffstat (limited to 'frontend/src/components')
| -rw-r--r-- | frontend/src/components/Settings.css | 74 | ||||
| -rw-r--r-- | frontend/src/components/Settings.tsx | 64 |
2 files changed, 134 insertions, 4 deletions
diff --git a/frontend/src/components/Settings.css b/frontend/src/components/Settings.css index 171dcad..ec6fc83 100644 --- a/frontend/src/components/Settings.css +++ b/frontend/src/components/Settings.css @@ -84,4 +84,78 @@ .delete-btn:disabled { background: #ffcdd2; cursor: not-allowed; +} + +.import-export-section { + display: flex; + gap: 2rem; + margin-bottom: 2rem; +} + +@media (max-width: 600px) { + .import-export-section { + flex-direction: column; + } +} + +.import-section, +.export-section { + flex: 1; + background: var(--sidebar-bg); + padding: 1.5rem; + border-radius: 8px; + border: 1px solid var(--border-color); +} + +.import-form { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.file-input { + font-size: 0.9rem; + max-width: 100%; +} + +.export-buttons { + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.export-btn { + display: inline-block; + padding: 0.5rem 1rem; + background: var(--bg-color); + color: var(--link-color); + text-decoration: none; + border: 1px solid var(--border-color); + border-radius: 4px; + font-weight: bold; + text-align: center; + min-width: 70px; +} + +.export-btn:hover { + background: var(--sidebar-bg); +} + +button { + cursor: pointer; + padding: 0.5rem 1rem; + border-radius: 4px; + border: 1px solid var(--border-color); + background: var(--bg-color); + color: var(--text-color); + font-weight: bold; +} + +button:hover:not(:disabled) { + background: var(--sidebar-bg); +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; }
\ No newline at end of file diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index 3f508e9..6b6dab1 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -9,6 +9,8 @@ export default function Settings() { const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); + const [importFile, setImportFile] = useState<File | null>(null); + const fetchFeeds = () => { setLoading(true); apiFetch('/api/feed/') @@ -30,8 +32,6 @@ export default function Settings() { fetchFeeds(); }, []); - - const handleAddFeed = (e: React.FormEvent) => { e.preventDefault(); if (!newFeedUrl) return; @@ -48,7 +48,7 @@ export default function Settings() { }) .then(() => { setNewFeedUrl(''); - fetchFeeds(); // Refresh list (or we could append if server returns full feed object) + fetchFeeds(); }) .catch((err) => { setError(err.message); @@ -74,6 +74,34 @@ export default function Settings() { }); }; + const handleImport = (e: React.FormEvent) => { + e.preventDefault(); + if (!importFile) return; + + setLoading(true); + const formData = new FormData(); + formData.append('file', importFile); + formData.append('format', 'opml'); + + apiFetch('/api/import', { + method: 'POST', + body: formData, + }) + .then((res) => { + if (!res.ok) throw new Error('Failed to import feeds'); + return res.json(); + }) + .then(() => { + setImportFile(null); + fetchFeeds(); + alert('Import successful!'); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }; + return ( <div className="settings-page"> <h2>Settings</h2> @@ -94,9 +122,37 @@ export default function Settings() { Add Feed </button> </form> - {error && <p className="error-message">{error}</p>} </div> + <div className="import-export-section"> + <div className="import-section"> + <h3>Import Feeds (OPML)</h3> + <form onSubmit={handleImport} className="import-form"> + <input + type="file" + accept=".opml,.xml,.txt" + onChange={(e) => setImportFile(e.target.files?.[0] || null)} + className="file-input" + disabled={loading} + /> + <button type="submit" disabled={!importFile || loading}> + Import + </button> + </form> + </div> + + <div className="export-section"> + <h3>Export Feeds</h3> + <div className="export-buttons"> + <a href="/api/export/opml" className="export-btn">OPML</a> + <a href="/api/export/text" className="export-btn">Text</a> + <a href="/api/export/json" className="export-btn">JSON</a> + </div> + </div> + </div> + + {error && <p className="error-message">{error}</p>} + <div className="feed-list-section"> <h3>Manage Feeds</h3> {loading && <p>Loading...</p>} |
