|
|
|
@@ -1,5 +1,11 @@
|
|
|
|
|
import { getTranslations } from "next-intl/server";
|
|
|
|
|
import { applyCustomTheme, applyPreset, deleteCustomTheme, saveCustomTheme, saveTheme } from "@/actions/admin-theme";
|
|
|
|
|
import {
|
|
|
|
|
applyCustomTheme,
|
|
|
|
|
applyPreset,
|
|
|
|
|
deleteCustomTheme,
|
|
|
|
|
saveCustomTheme,
|
|
|
|
|
saveTheme,
|
|
|
|
|
} from "@/actions/admin-theme";
|
|
|
|
|
import { siteSettings } from "@/lib/services/site-settings";
|
|
|
|
|
import { FONTS, PRESETS } from "@/lib/theme-presets";
|
|
|
|
|
import { listCustomThemes } from "@/lib/theme-custom-store";
|
|
|
|
@@ -10,15 +16,58 @@ export const dynamic = "force-dynamic";
|
|
|
|
|
export const metadata = { title: "Theme" };
|
|
|
|
|
|
|
|
|
|
const COLOR_FIELDS: { key: string; label: string; def: string; desc: string; bgKey?: string }[] = [
|
|
|
|
|
{ key: "color_background", label: "Page background", def: "#f8fafc", desc: "Main page background behind all content" },
|
|
|
|
|
{ key: "color_surface", label: "Card / surface", def: "#ffffff", desc: "Cards, panels, and elevated surfaces" },
|
|
|
|
|
{ key: "color_dropdown", label: "Dropdown menu", def: "#ffffff", desc: "Dropdown menus and input backgrounds" },
|
|
|
|
|
{
|
|
|
|
|
key: "color_background",
|
|
|
|
|
label: "Page background",
|
|
|
|
|
def: "#f8fafc",
|
|
|
|
|
desc: "Main page background behind all content",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "color_surface",
|
|
|
|
|
label: "Card / surface",
|
|
|
|
|
def: "#ffffff",
|
|
|
|
|
desc: "Cards, panels, and elevated surfaces",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "color_dropdown",
|
|
|
|
|
label: "Dropdown menu",
|
|
|
|
|
def: "#ffffff",
|
|
|
|
|
desc: "Dropdown menus and input backgrounds",
|
|
|
|
|
},
|
|
|
|
|
{ key: "color_navbar", label: "Navbar bar", def: "#ffffff", desc: "Top navigation bar background" },
|
|
|
|
|
{ key: "color_navbar_text", label: "Navbar text", def: "#1e293b", desc: "Text color in the navigation bar", bgKey: "color_navbar" },
|
|
|
|
|
{ key: "color_text", label: "Body text", def: "#0f172a", desc: "Main body text — must contrast with background/surface", bgKey: "color_surface" },
|
|
|
|
|
{ key: "color_text_muted", label: "Muted text", def: "#64748b", desc: "Secondary text, labels, hints — lighter than body text", bgKey: "color_surface" },
|
|
|
|
|
{ key: "color_primary", label: "Primary accent", def: "#f59e0b", desc: "Main brand color, highlights, active elements" },
|
|
|
|
|
{ key: "color_accent", label: "Secondary accent", def: "#10b981", desc: "Secondary brand color, success-oriented highlights" },
|
|
|
|
|
{
|
|
|
|
|
key: "color_navbar_text",
|
|
|
|
|
label: "Navbar text",
|
|
|
|
|
def: "#1e293b",
|
|
|
|
|
desc: "Text color in the navigation bar",
|
|
|
|
|
bgKey: "color_navbar",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "color_text",
|
|
|
|
|
label: "Body text",
|
|
|
|
|
def: "#0f172a",
|
|
|
|
|
desc: "Main body text — must contrast with background/surface",
|
|
|
|
|
bgKey: "color_surface",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "color_text_muted",
|
|
|
|
|
label: "Muted text",
|
|
|
|
|
def: "#64748b",
|
|
|
|
|
desc: "Secondary text, labels, hints — lighter than body text",
|
|
|
|
|
bgKey: "color_surface",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "color_primary",
|
|
|
|
|
label: "Primary accent",
|
|
|
|
|
def: "#f59e0b",
|
|
|
|
|
desc: "Main brand color, highlights, active elements",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "color_accent",
|
|
|
|
|
label: "Secondary accent",
|
|
|
|
|
def: "#10b981",
|
|
|
|
|
desc: "Secondary brand color, success-oriented highlights",
|
|
|
|
|
},
|
|
|
|
|
{ key: "border_color", label: "Border (golden)", def: "#eeb425", desc: "Borders, dividers, outlines" },
|
|
|
|
|
{ key: "color_success", label: "Success", def: "#16a34a", desc: "Positive status (green)" },
|
|
|
|
|
{ key: "color_warning", label: "Warning", def: "#eab308", desc: "Warning status (yellow)" },
|
|
|
|
@@ -27,21 +76,81 @@ const COLOR_FIELDS: { key: string; label: string; def: string; desc: string; bgK
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const BUTTON_FIELDS: { key: string; label: string; def: string; desc: string; bgKey?: string }[] = [
|
|
|
|
|
{ key: "button_primary_color", label: "Primary button", def: "#f59e0b", desc: "Main call-to-action button background" },
|
|
|
|
|
{ key: "button_text_color", label: "Button text", def: "#1e293b", desc: "Text on primary buttons — must contrast with button color", bgKey: "button_primary_color" },
|
|
|
|
|
{ key: "button_secondary_color", label: "Secondary button", def: "#22c55e", desc: "Secondary action button background" },
|
|
|
|
|
{ key: "button_secondary_text_color", label: "Secondary text", def: "#ffffff", desc: "Text on secondary buttons", bgKey: "button_secondary_color" },
|
|
|
|
|
{ key: "button_danger_color", label: "Danger button", def: "#ef4444", desc: "Destructive action button (delete, ban)" },
|
|
|
|
|
{ key: "button_danger_text_color", label: "Danger text", def: "#ffffff", desc: "Text on danger buttons", bgKey: "button_danger_color" },
|
|
|
|
|
{ key: "button_outline_color", label: "Outline border", def: "#eeb425", desc: "Outline button border color" },
|
|
|
|
|
{ key: "button_outline_text_color", label: "Outline text", def: "#1a1a2e", desc: "Text on outline buttons", bgKey: "color_surface" },
|
|
|
|
|
{
|
|
|
|
|
key: "button_primary_color",
|
|
|
|
|
label: "Primary button",
|
|
|
|
|
def: "#f59e0b",
|
|
|
|
|
desc: "Main call-to-action button background",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "button_text_color",
|
|
|
|
|
label: "Button text",
|
|
|
|
|
def: "#1e293b",
|
|
|
|
|
desc: "Text on primary buttons — must contrast with button color",
|
|
|
|
|
bgKey: "button_primary_color",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "button_secondary_color",
|
|
|
|
|
label: "Secondary button",
|
|
|
|
|
def: "#22c55e",
|
|
|
|
|
desc: "Secondary action button background",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "button_secondary_text_color",
|
|
|
|
|
label: "Secondary text",
|
|
|
|
|
def: "#ffffff",
|
|
|
|
|
desc: "Text on secondary buttons",
|
|
|
|
|
bgKey: "button_secondary_color",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "button_danger_color",
|
|
|
|
|
label: "Danger button",
|
|
|
|
|
def: "#ef4444",
|
|
|
|
|
desc: "Destructive action button (delete, ban)",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "button_danger_text_color",
|
|
|
|
|
label: "Danger text",
|
|
|
|
|
def: "#ffffff",
|
|
|
|
|
desc: "Text on danger buttons",
|
|
|
|
|
bgKey: "button_danger_color",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "button_outline_color",
|
|
|
|
|
label: "Outline border",
|
|
|
|
|
def: "#eeb425",
|
|
|
|
|
desc: "Outline button border color",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "button_outline_text_color",
|
|
|
|
|
label: "Outline text",
|
|
|
|
|
def: "#1a1a2e",
|
|
|
|
|
desc: "Text on outline buttons",
|
|
|
|
|
bgKey: "color_surface",
|
|
|
|
|
},
|
|
|
|
|
{ key: "link_color", label: "Link", def: "#eeb425", desc: "Hyperlink text color", bgKey: "color_surface" },
|
|
|
|
|
{ key: "link_hover_color", label: "Link hover", def: "#cf9d15", desc: "Hyperlink text on hover", bgKey: "color_surface" },
|
|
|
|
|
{
|
|
|
|
|
key: "link_hover_color",
|
|
|
|
|
label: "Link hover",
|
|
|
|
|
def: "#cf9d15",
|
|
|
|
|
desc: "Hyperlink text on hover",
|
|
|
|
|
bgKey: "color_surface",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const GRADIENT_FIELDS: { key: string; label: string; def: string; desc: string }[] = [
|
|
|
|
|
{ key: "gradient_from", label: "Gradient start", def: "#f59e0b", desc: "Left/top color of card header and hero gradients" },
|
|
|
|
|
{ key: "gradient_to", label: "Gradient end", def: "#10b981", desc: "Right/bottom color of card header and hero gradients" },
|
|
|
|
|
{
|
|
|
|
|
key: "gradient_from",
|
|
|
|
|
label: "Gradient start",
|
|
|
|
|
def: "#f59e0b",
|
|
|
|
|
desc: "Left/top color of card header and hero gradients",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "gradient_to",
|
|
|
|
|
label: "Gradient end",
|
|
|
|
|
def: "#10b981",
|
|
|
|
|
desc: "Right/bottom color of card header and hero gradients",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const HEADINGS: { key: string; label: string; def: string }[] = [
|
|
|
|
@@ -52,9 +161,26 @@ const HEADINGS: { key: string; label: string; def: string }[] = [
|
|
|
|
|
|
|
|
|
|
const ADMIN_FIELDS: { key: string; label: string; def: string; desc: string; bgKey?: string }[] = [
|
|
|
|
|
{ key: "admin_canvas", label: "Page background", def: "#0f172a", desc: "Main admin page background" },
|
|
|
|
|
{ key: "admin_surface", label: "Card / panel", def: "#1e293b", desc: "Admin cards, tables, and panel backgrounds" },
|
|
|
|
|
{ key: "admin_text", label: "Body text", def: "#f1f5f9", desc: "Main admin text — must contrast with canvas/surface", bgKey: "admin_canvas" },
|
|
|
|
|
{ key: "admin_text_muted", label: "Muted text", def: "#94a3b8", desc: "Secondary labels, hints, descriptions", bgKey: "admin_canvas" },
|
|
|
|
|
{
|
|
|
|
|
key: "admin_surface",
|
|
|
|
|
label: "Card / panel",
|
|
|
|
|
def: "#1e293b",
|
|
|
|
|
desc: "Admin cards, tables, and panel backgrounds",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "admin_text",
|
|
|
|
|
label: "Body text",
|
|
|
|
|
def: "#f1f5f9",
|
|
|
|
|
desc: "Main admin text — must contrast with canvas/surface",
|
|
|
|
|
bgKey: "admin_canvas",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: "admin_text_muted",
|
|
|
|
|
label: "Muted text",
|
|
|
|
|
def: "#94a3b8",
|
|
|
|
|
desc: "Secondary labels, hints, descriptions",
|
|
|
|
|
bgKey: "admin_canvas",
|
|
|
|
|
},
|
|
|
|
|
{ key: "admin_border", label: "Border", def: "#334155", desc: "Table borders, dividers, input outlines" },
|
|
|
|
|
{ key: "admin_sidebar_bg", label: "Sidebar", def: "#0f172a", desc: "Left navigation sidebar background" },
|
|
|
|
|
];
|
|
|
|
@@ -73,11 +199,7 @@ interface ThemeField {
|
|
|
|
|
bgKey?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function colorFields(
|
|
|
|
|
fields: ThemeField[],
|
|
|
|
|
current: Record<string, string>,
|
|
|
|
|
suffix = "",
|
|
|
|
|
) {
|
|
|
|
|
function colorFields(fields: ThemeField[], current: Record<string, string>, suffix = "") {
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
@@ -108,7 +230,14 @@ function colorFields(
|
|
|
|
|
export default async function AdminTheme({
|
|
|
|
|
searchParams,
|
|
|
|
|
}: {
|
|
|
|
|
searchParams: Promise<{ saved?: string; preset?: string; savedTheme?: string; theme?: string; renamed?: string; deletedTheme?: string }>;
|
|
|
|
|
searchParams: Promise<{
|
|
|
|
|
saved?: string;
|
|
|
|
|
preset?: string;
|
|
|
|
|
savedTheme?: string;
|
|
|
|
|
theme?: string;
|
|
|
|
|
renamed?: string;
|
|
|
|
|
deletedTheme?: string;
|
|
|
|
|
}>;
|
|
|
|
|
}) {
|
|
|
|
|
const t = await getTranslations("pages.admin.theme");
|
|
|
|
|
|
|
|
|
@@ -170,9 +299,7 @@ export default async function AdminTheme({
|
|
|
|
|
<div className="mb-4 p-3 rounded-lg theme-status-success text-xs">Theme saved as preset.</div>
|
|
|
|
|
) : null}
|
|
|
|
|
{sp.theme ? (
|
|
|
|
|
<div className="mb-4 p-3 rounded-lg theme-status-success text-xs">
|
|
|
|
|
Applied theme “{sp.theme}”.
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mb-4 p-3 rounded-lg theme-status-success text-xs">Applied theme “{sp.theme}”.</div>
|
|
|
|
|
) : null}
|
|
|
|
|
{sp.renamed ? (
|
|
|
|
|
<div className="mb-4 p-3 rounded-lg theme-status-success text-xs">Theme renamed.</div>
|
|
|
|
@@ -213,8 +340,8 @@ export default async function AdminTheme({
|
|
|
|
|
<section className="mt-6">
|
|
|
|
|
<h2 className="admin-section-title">Your saved themes</h2>
|
|
|
|
|
<p className="text-xs text-[var(--admin-text-muted)] mb-3">
|
|
|
|
|
Save the current configuration as a named preset, then load it anytime. Perfect for keeping multiple brand
|
|
|
|
|
themes ready to switch.
|
|
|
|
|
Save the current configuration as a named preset, then load it anytime. Perfect for keeping multiple
|
|
|
|
|
brand themes ready to switch.
|
|
|
|
|
</p>
|
|
|
|
|
<form action={saveCustomTheme} className="flex flex-wrap items-center gap-2 mb-4">
|
|
|
|
|
<input
|
|
|
|
@@ -277,14 +404,15 @@ export default async function AdminTheme({
|
|
|
|
|
<form action={saveTheme}>
|
|
|
|
|
<h2 className="mt-6 text-lg font-extrabold">☀ Light mode</h2>
|
|
|
|
|
<p className="text-xs text-[var(--admin-text-muted)] mb-3 mt-1">
|
|
|
|
|
These colors are used when visitors view your site in light mode (default). Make sure body text contrasts well
|
|
|
|
|
with both the page background and card surface.
|
|
|
|
|
These colors are used when visitors view your site in light mode (default). Make sure body text
|
|
|
|
|
contrasts well with both the page background and card surface.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<section className="mt-6">
|
|
|
|
|
<h2 className="admin-section-title">Page & text colors</h2>
|
|
|
|
|
<p className="text-xs text-[var(--admin-text-muted)] mb-2">
|
|
|
|
|
Backgrounds, surfaces, and text colors. Text colors must contrast with their backgrounds for readability.
|
|
|
|
|
Backgrounds, surfaces, and text colors. Text colors must contrast with their backgrounds for
|
|
|
|
|
readability.
|
|
|
|
|
</p>
|
|
|
|
|
<div className="admin-card">{colorFields(COLOR_FIELDS, current)}</div>
|
|
|
|
|
</section>
|
|
|
|
@@ -292,7 +420,8 @@ export default async function AdminTheme({
|
|
|
|
|
<section className="mt-6">
|
|
|
|
|
<h2 className="admin-section-title">Buttons & links</h2>
|
|
|
|
|
<p className="text-xs text-[var(--admin-text-muted)] mb-2">
|
|
|
|
|
Button backgrounds, text on buttons, and link colors. Button text must contrast with its button background.
|
|
|
|
|
Button backgrounds, text on buttons, and link colors. Button text must contrast with its button
|
|
|
|
|
background.
|
|
|
|
|
</p>
|
|
|
|
|
<div className="admin-card">{colorFields(BUTTON_FIELDS, current)}</div>
|
|
|
|
|
</section>
|
|
|
|
@@ -307,8 +436,8 @@ export default async function AdminTheme({
|
|
|
|
|
|
|
|
|
|
<h2 className="mt-8 text-lg font-extrabold">🛠 Admin panel (HK)</h2>
|
|
|
|
|
<p className="text-xs text-[var(--admin-text-muted)] mb-3 mt-1">
|
|
|
|
|
Override colors for the Housekeeping admin panel. These apply across both light and dark modes. Leave empty to
|
|
|
|
|
use auto-derived defaults.
|
|
|
|
|
Override colors for the Housekeeping admin panel. These apply across both light and dark modes.
|
|
|
|
|
Leave empty to use auto-derived defaults.
|
|
|
|
|
</p>
|
|
|
|
|
<section className="mt-4">
|
|
|
|
|
<div className="admin-card">{colorFields(ADMIN_FIELDS, current)}</div>
|
|
|
|
@@ -316,7 +445,8 @@ export default async function AdminTheme({
|
|
|
|
|
|
|
|
|
|
<h2 className="mt-8 text-lg font-extrabold">🌙 Dark mode</h2>
|
|
|
|
|
<p className="text-xs text-[var(--admin-text-muted)] mb-3 mt-1">
|
|
|
|
|
These colors are used when visitors switch to dark mode. Dark backgrounds need lighter text for good contrast.
|
|
|
|
|
These colors are used when visitors switch to dark mode. Dark backgrounds need lighter text for good
|
|
|
|
|
contrast.
|
|
|
|
|
</p>
|
|
|
|
|
<section className="mt-4">
|
|
|
|
|
<h3 className="admin-section-title">Page & text colors</h3>
|
|
|
|
|