feat(settings): add admin settings UI and Navbar link

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-16 20:10:41 +02:00
parent 364b1cd7e0
commit 078dc25b87
3 changed files with 73 additions and 0 deletions
+6
View File
@@ -25,6 +25,12 @@ export default function Navbar() {
>
Analyze
</Link>
{/* If you have an isAdmin helper, show Settings only for admins. Example:
{isAdmin(user) && (
<Link to="/settings" className="text-gray-600 hover:text-blue-600 font-medium transition-colors">Settings</Link>
)}
*/}
<a href="/settings" className="text-gray-600 hover:text-blue-600 font-medium transition-colors">Settings</a>
</div>
</div>
</nav>
+61
View File
@@ -0,0 +1,61 @@
import type { LoaderFunction } from '@remix-run/node';
import { json } from '@remix-run/node';
import React, { useEffect, useState } from 'react';
import { requireAdmin } from '~/lib/auth.server';
import { settingsService } from '~/lib/settings.server';
export const loader: LoaderFunction = async ({ request }) => {
await requireAdmin(request);
await settingsService.init?.();
const entries: any[] = [];
// @ts-ignore
for (const key of (settingsService as any).cache.keys()) {
entries.push({ key, value: await settingsService.get(key) });
}
return json({ entries });
};
export default function SettingsPage() {
const [items, setItems] = useState<Array<{ key: string; value: any }>>([]);
useEffect(() => {
fetch('/api/admin/settings')
.then(r => r.json())
.then(j => setItems(j));
}, []);
async function save(key: string, value: any) {
await fetch(`/api/admin/settings/${encodeURIComponent(key)}`, {
method: 'PUT',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ value }),
});
setItems(s => s.map(i => (i.key === key ? { ...i, value } : i)));
}
return (
<div className="p-6">
<h1 className="text-2xl font-bold mb-4">Settings</h1>
<ul>
{items.map(it => (
<li key={it.key} className="mb-3">
<div className="flex items-center gap-4">
<div className="font-medium">{it.key}</div>
<textarea
className="border p-2 w-2/3"
defaultValue={JSON.stringify(it.value, null, 2)}
onBlur={e => {
try {
const v = JSON.parse(e.currentTarget.value);
save(it.key, v);
} catch (err) {
alert('Invalid JSON');
}
}}
/>
</div>
</li>
))}
</ul>
</div>
);
}