Add Theme Settings tool + polish remaining radio detail pages
- New /admin/theme: recolour the whole site from housekeeping. 6 atom- faithful presets (Atom/Midnight/Ocean/Forest/Sunset/Candy) + per-colour pickers for the 12 settings ThemeVars injects + border radius. Writes to website_settings, busts the siteSettings cache, and revalidates the layout so the new palette applies live with no rebuild. Constants live in src/lib/theme-presets.ts (a "use server" file can't export objects). Added to the admin sidebar (System). - radio/contests/[id] + giveaways/[id] wrapped in ContentCard to match the public design system. - Skipped a separate VPN page: /admin/ip already manages the IP white/blacklist, so it would only duplicate it. Verified live (prod, amx_test): applied the Ocean preset → home renders --color-primary #0ea5e9 site-wide; reverted the test rows. tsc 0, vitest 49/49, next build 0.
This commit is contained in:
1 parent
6f15e0c8a3
commit
1199176883
6 files changed
+322
-10
No files matched your search
@@ -0,0 +1,65 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { requireStaff } from "@/lib/admin/guard";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||
import { PRESETS, THEME_COLOR_KEYS } from "@/lib/theme-presets";
|
||||
|
||||
// Only hex/keyword colour values are accepted (matches ThemeVars' sanitiser).
|
||||
const COLOR_RE = /^[#a-zA-Z0-9(),.\s%-]+$/;
|
||||
|
||||
async function writeSetting(key: string, value: string): Promise<void> {
|
||||
await prisma.websiteSetting.upsert({
|
||||
where: { key },
|
||||
update: { value },
|
||||
create: { key, value, comment: "Theme (housekeeping)" },
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveTheme(formData: FormData): Promise<void> {
|
||||
const staff = await requireStaff();
|
||||
|
||||
try {
|
||||
for (const key of THEME_COLOR_KEYS) {
|
||||
const raw = String(formData.get(key) ?? "").trim();
|
||||
if (raw && COLOR_RE.test(raw)) await writeSetting(key, raw);
|
||||
}
|
||||
const radius = String(formData.get("border_radius") ?? "").trim();
|
||||
if (/^\d{1,3}$/.test(radius)) await writeSetting("border_radius", radius);
|
||||
|
||||
siteSettings.reload();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "theme_update",
|
||||
description: "Updated theme colours",
|
||||
});
|
||||
revalidatePath("/", "layout");
|
||||
} catch {
|
||||
// ignore — page re-renders current state
|
||||
}
|
||||
redirect("/admin/theme?saved=1");
|
||||
}
|
||||
|
||||
export async function applyPreset(formData: FormData): Promise<void> {
|
||||
const staff = await requireStaff();
|
||||
const name = String(formData.get("preset") ?? "");
|
||||
const preset = PRESETS[name];
|
||||
if (!preset) redirect("/admin/theme");
|
||||
|
||||
try {
|
||||
for (const [key, value] of Object.entries(preset)) await writeSetting(key, value);
|
||||
siteSettings.reload();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "theme_preset",
|
||||
description: `Applied theme preset "${name}"`,
|
||||
});
|
||||
revalidatePath("/", "layout");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
redirect(`/admin/theme?preset=${encodeURIComponent(name)}`);
|
||||
}
|
||||
Reference in new issue
Block a user