Add named custom theme presets (save/load/rename/delete) in admin theme editor
Local Build and Deploy / deploy (push) Successful in 58s

This commit is contained in:
openhands committed 2026-07-13 20:42:40 +02:00
1 parent 01b70c2369
commit e6d7f2280b
4 files changed
+321 -2

No files matched your search

+77
View File
@@ -8,6 +8,12 @@ import { siteSettings } from "@/lib/services/site-settings";
import { logStaffActivity } from "@/lib/services/staff-activity";
import { FONTS, PRESETS, THEME_COLOR_KEYS } from "@/lib/theme-presets";
import { presetSettings, settingKey } from "@/lib/theme-settings";
import {
deleteCustomThemeStore,
getCustomTheme,
snapshotCurrentTheme,
upsertCustomTheme,
} from "@/lib/theme-custom-store";
// Only hex/keyword colour values are accepted (matches ThemeVars' sanitiser).
const COLOR_RE = /^[#a-zA-Z0-9(),.\s%-]+$/;
@@ -92,3 +98,74 @@ export async function applyPreset(formData: FormData): Promise<void> {
}
redirect(`/admin/theme?preset=${encodeURIComponent(name)}`);
}
export async function saveCustomTheme(formData: FormData): Promise<void> {
const staff = await requireStaff();
const name = String(formData.get("name") ?? "").normalize("NFC").trim();
if (!name) redirect("/admin/theme");
const snapshot = await snapshotCurrentTheme();
try {
await upsertCustomTheme(name, snapshot);
await logStaffActivity({
staffId: staff.id,
action: "theme_preset",
description: `Saved custom theme "${name}"`,
});
revalidatePath("/admin/theme");
} catch {
// ignore
}
redirect("/admin/theme?savedTheme=1");
}
export async function applyCustomTheme(formData: FormData): Promise<void> {
const staff = await requireStaff();
const id = String(formData.get("id") ?? "").normalize("NFC").trim();
if (!id) redirect("/admin/theme");
const theme = await getCustomTheme(id);
if (!theme) redirect("/admin/theme");
try {
for (const [key, value] of Object.entries(theme.settings)) {
if (value) await writeSetting(key, value);
}
await writeSetting("theme_preset", theme.name);
siteSettings.reload();
await logStaffActivity({
staffId: staff.id,
action: "theme_preset",
description: `Applied custom theme "${theme.name}"`,
});
revalidatePath("/", "layout");
} catch {
// ignore
}
redirect(`/admin/theme?theme=${encodeURIComponent(theme.name)}`);
}
export async function renameCustomTheme(formData: FormData): Promise<void> {
await requireStaff();
const id = String(formData.get("id") ?? "").normalize("NFC").trim();
const name = String(formData.get("name") ?? "").normalize("NFC").trim();
if (!id || !name) redirect("/admin/theme");
const snapshot = await snapshotCurrentTheme();
try {
await upsertCustomTheme(name, snapshot, id);
revalidatePath("/admin/theme");
} catch {
// ignore
}
redirect("/admin/theme?renamed=1");
}
export async function deleteCustomTheme(formData: FormData): Promise<void> {
await requireStaff();
const id = String(formData.get("id") ?? "").normalize("NFC").trim();
if (!id) redirect("/admin/theme");
try {
await deleteCustomThemeStore(id);
revalidatePath("/admin/theme");
} catch {
// ignore
}
redirect("/admin/theme?deletedTheme=1");
}