Files
soroushasadi/components/admin/SectionEditor.tsx
T
soroush.asadi add78d8460
ci / build (push) Failing after 23s
deploy / deploy (push) Failing after 10m12s
first commit
2026-05-31 12:47:02 +03:30

124 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { JsonForm, type JsonValue } from './JsonForm';
type Status = 'idle' | 'saving' | 'saved' | 'error';
export function SectionEditor({
sectionKey,
title,
initial,
isOverridden,
}: {
sectionKey: string;
title: string;
initial: { fa: JsonValue; en: JsonValue };
isOverridden: boolean;
}) {
const router = useRouter();
const [data, setData] = useState<{ fa: JsonValue; en: JsonValue }>(initial);
const [tab, setTab] = useState<'fa' | 'en'>('fa');
const [status, setStatus] = useState<Status>('idle');
const [overridden, setOverridden] = useState(isOverridden);
async function save() {
setStatus('saving');
try {
const res = await fetch('/api/admin/section', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ key: sectionKey, data }),
});
if (!res.ok) throw new Error(String(res.status));
setStatus('saved');
setOverridden(true);
router.refresh();
setTimeout(() => setStatus('idle'), 2500);
} catch {
setStatus('error');
}
}
async function reset() {
if (!confirm('Revert this section to its built-in default? Your edits will be removed.')) return;
setStatus('saving');
try {
const res = await fetch(`/api/admin/section?key=${encodeURIComponent(sectionKey)}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error(String(res.status));
router.refresh();
window.location.reload();
} catch {
setStatus('error');
}
}
return (
<div className="flex flex-col gap-6">
{/* Toolbar */}
<div className="sticky top-0 z-10 -mx-6 flex flex-wrap items-center justify-between gap-3 border-b border-white/8 bg-base-900/80 px-6 py-3 backdrop-blur sm:-mx-8 sm:px-8">
<div className="flex items-center gap-1 rounded-full border border-white/10 bg-white/[0.02] p-1">
<TabBtn active={tab === 'fa'} onClick={() => setTab('fa')}>FA · فارسی</TabBtn>
<TabBtn active={tab === 'en'} onClick={() => setTab('en')}>EN · English</TabBtn>
</div>
<div className="flex items-center gap-2">
{status === 'saved' && <span className="text-sm text-emerald">Saved </span>}
{status === 'error' && <span className="text-sm text-magenta">Save failed</span>}
{overridden && (
<button
type="button"
onClick={reset}
className="rounded-lg border border-white/10 px-3 py-2 text-sm text-slate-300 transition-colors hover:bg-white/[0.05]"
>
Reset to default
</button>
)}
<button
type="button"
onClick={save}
disabled={status === 'saving'}
className="rounded-lg bg-electric px-4 py-2 text-sm font-semibold text-base-900 transition-opacity hover:opacity-90 disabled:opacity-50"
>
{status === 'saving' ? 'Saving…' : 'Save changes'}
</button>
</div>
</div>
{/* The form for the active locale. FA renders RTL. */}
<div dir={tab === 'fa' ? 'rtl' : 'ltr'} className="pb-24">
<JsonForm
key={tab}
value={data[tab]}
onChange={(nv) => setData((d) => ({ ...d, [tab]: nv }))}
/>
</div>
</div>
);
}
function TabBtn({
active,
onClick,
children,
}: {
active: boolean;
onClick: () => void;
children: React.ReactNode;
}) {
return (
<button
type="button"
onClick={onClick}
className={
'rounded-full px-4 py-1.5 text-sm font-medium transition-colors ' +
(active ? 'bg-electric text-base-900' : 'text-slate-300 hover:text-white')
}
>
{children}
</button>
);
}