first commit
Made-with: Cursor
This commit is contained in:
105
frontend/app/admin/settings/page.tsx
Normal file
105
frontend/app/admin/settings/page.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { api } from "@/lib/api";
|
||||
import { Save } from "lucide-react";
|
||||
|
||||
const settingFields = [
|
||||
{ key: "site_title", label: "Site Title" },
|
||||
{ key: "site_tagline", label: "Site Tagline" },
|
||||
{ key: "telegram_link", label: "Telegram Link" },
|
||||
{ key: "nostr_link", label: "Nostr Link" },
|
||||
{ key: "x_link", label: "X Link" },
|
||||
{ key: "youtube_link", label: "YouTube Link" },
|
||||
{ key: "discord_link", label: "Discord Link" },
|
||||
{ key: "linkedin_link", label: "LinkedIn Link" },
|
||||
];
|
||||
|
||||
export default function SettingsPage() {
|
||||
const [settings, setSettings] = useState<Record<string, string>>({});
|
||||
const [original, setOriginal] = useState<Record<string, string>>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [success, setSuccess] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
try {
|
||||
const data = await api.getSettings();
|
||||
setSettings(data);
|
||||
setOriginal(data);
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
load();
|
||||
}, []);
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true);
|
||||
setError("");
|
||||
setSuccess("");
|
||||
try {
|
||||
const changed = Object.entries(settings).filter(
|
||||
([key, value]) => value !== (original[key] || "")
|
||||
);
|
||||
await Promise.all(
|
||||
changed.map(([key, value]) => api.updateSetting(key, value))
|
||||
);
|
||||
setOriginal({ ...settings });
|
||||
setSuccess("Settings saved successfully.");
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const hasChanges = Object.entries(settings).some(
|
||||
([key, value]) => value !== (original[key] || "")
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<div className="text-on-surface/50">Loading settings...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-2xl font-bold text-on-surface">Site Settings</h1>
|
||||
|
||||
{error && <p className="text-error text-sm">{error}</p>}
|
||||
{success && <p className="text-green-400 text-sm">{success}</p>}
|
||||
|
||||
<div className="bg-surface-container-low rounded-xl p-6 space-y-4">
|
||||
{settingFields.map((field) => (
|
||||
<div key={field.key}>
|
||||
<label className="block text-on-surface/70 text-sm mb-1">{field.label}</label>
|
||||
<input
|
||||
value={settings[field.key] || ""}
|
||||
onChange={(e) =>
|
||||
setSettings({ ...settings, [field.key]: e.target.value })
|
||||
}
|
||||
className="bg-surface-container-highest text-on-surface rounded-lg px-4 py-3 w-full focus:outline-none focus:ring-1 focus:ring-primary/40"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving || !hasChanges}
|
||||
className="flex items-center gap-2 px-6 py-3 rounded-lg bg-gradient-to-r from-primary to-primary-container text-on-primary font-semibold text-sm hover:opacity-90 transition-opacity disabled:opacity-50 mt-2"
|
||||
>
|
||||
<Save size={16} />
|
||||
{saving ? "Saving..." : "Save Settings"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user