aboutsummaryrefslogtreecommitdiffstats
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/src/components/Settings.css74
-rw-r--r--frontend/src/components/Settings.tsx64
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>}