feat(cms): add recovery history and operational reliability tools
CI / check (push) Failing after 22s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped

This commit is contained in:
Simo committed 2026-09-09 21:57:56 +02:00
1 parent 89526af344
commit 1ef405be0a
92 files changed
+6384 -267

No files matched your search

+21 -3
View File
@@ -20,6 +20,7 @@ import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { useServerAction } from "@/hooks/use-server-action";
import { useUnsavedChanges } from "@/hooks/use-unsaved-changes";
import {
BOOLEAN_KEYS,
type ManagedField,
@@ -45,7 +46,13 @@ export function CmsSettingsForm({
canEdit: boolean;
}) {
const [values, setValues] = useState(initialValues);
const { isPending, run } = useServerAction();
const [savedValues, setSavedValues] = useState(initialValues);
const tAction = useTranslations("pages.admin.actions");
const dirty = Object.keys({ ...savedValues, ...values }).some(
(key) => savedValues[key] !== values[key],
);
useUnsavedChanges(canEdit && dirty, tAction("leaveUnsaved"));
const { isPending, run, fieldErrors } = useServerAction();
const t = useTranslations("pages.admin.settings");
function handleChange(key: string, value: string) {
@@ -55,6 +62,7 @@ export function CmsSettingsForm({
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!canEdit) return;
const submitted = { ...values };
const normalized = { ...values };
for (const key of BOOLEAN_KEYS) {
normalized[key] = values[key] === "1" ? "1" : "0";
@@ -62,24 +70,34 @@ export function CmsSettingsForm({
run(() => saveManagedSettings({ settings: normalized }), {
successMessage: t("saved"),
errorMessage: t("saveFailed"),
onSuccess: () => setSavedValues(submitted),
});
}
return (
<form onSubmit={handleSubmit} className="space-y-5">
{Object.keys(fieldErrors).length > 0 && (
<div role="alert" className="admin-card text-sm text-destructive">
{Object.values(fieldErrors).flat().join(" · ")}
</div>
)}
{SETTINGS_GROUPS.map((group) => (
<GroupSection
key={group.id}
group={group}
values={values}
canEdit={canEdit}
canEdit={canEdit && !isPending}
onChange={handleChange}
/>
))}
{canEdit ? (
<div className="sticky bottom-3 z-10 flex justify-end">
<Button type="submit" disabled={isPending} className="shadow-lg">
<Button
type="submit"
disabled={isPending || !dirty}
className="shadow-lg"
>
{isPending ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (