feat: extend theme builder with block visibility, layout, effects, media, and custom CSS tabs
- Add theme-blocks.ts registry with 24 themeable blocks across 4 categories - Extend resolver to resolve blocks, layout, effects, media, and custom CSS per scope - Add data-theme-block attributes to all site layout blocks - Extend ScopedThemeVars to generate CSS for block visibility, layout vars, effect vars, media vars, and custom CSS - Rewrite admin UI with 6 tabs: Colors, Blocks, Layout, Effects, Media, Custom - Extend server actions to save/load all new setting types - No database migration needed - uses existing theme_scope_values table with prefixed keys
This commit is contained in:
1 parent
3efd6c90f0
commit
adf0658916
7 files changed
+1334
-75
No files matched your search
@@ -8,6 +8,13 @@ import { requirePermission } from "@/lib/admin/guard";
|
||||
import { db, ThemeScope, ThemeScopeValue } from "@/lib/db";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||
import {
|
||||
BLOCK_KEY_PREFIX,
|
||||
EFFECT_KEYS,
|
||||
LAYOUT_KEYS,
|
||||
MEDIA_KEYS,
|
||||
THEME_BLOCKS,
|
||||
} from "@/lib/theme-blocks";
|
||||
import { allModuleIds } from "@/lib/theme-modules";
|
||||
import { THEME_COLOR_KEYS } from "@/lib/theme-presets";
|
||||
import { getAllScopes, getScopeValues } from "@/lib/theme-resolver";
|
||||
@@ -143,6 +150,7 @@ export async function saveScopeValues(formData: FormData): Promise<void> {
|
||||
if (!scopeId) redirect("/admin/theme-builder");
|
||||
|
||||
try {
|
||||
// Save color values (light + dark)
|
||||
for (const mode of ["light", "dark"] as const) {
|
||||
for (const key of THEME_COLOR_KEYS) {
|
||||
const fieldKey = `${key}_${mode}`;
|
||||
@@ -165,6 +173,154 @@ export async function saveScopeValues(formData: FormData): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Save block visibility
|
||||
for (const block of THEME_BLOCKS) {
|
||||
const key = `${BLOCK_KEY_PREFIX}${block.id}`;
|
||||
const val = formData.get(key);
|
||||
const visible = val === "on" || val === "true" ? "true" : "false";
|
||||
|
||||
// Only save if different from default
|
||||
if (visible !== String(block.defaultVisible)) {
|
||||
await db
|
||||
.insert(ThemeScopeValue)
|
||||
.values({
|
||||
scopeId: BigInt(scopeId),
|
||||
settingKey: key,
|
||||
settingVal: visible,
|
||||
mode: "light",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.onDuplicateKeyUpdate({
|
||||
set: { settingVal: visible, updatedAt: new Date() },
|
||||
});
|
||||
} else {
|
||||
// Remove if set back to default
|
||||
await db
|
||||
.delete(ThemeScopeValue)
|
||||
.where(
|
||||
and(
|
||||
sql`${ThemeScopeValue.scopeId} = ${scopeId}`,
|
||||
eq(ThemeScopeValue.settingKey, key),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Save layout settings
|
||||
for (const key of LAYOUT_KEYS) {
|
||||
const raw = String(formData.get(key) ?? "").trim();
|
||||
if (!raw) continue;
|
||||
|
||||
await db
|
||||
.insert(ThemeScopeValue)
|
||||
.values({
|
||||
scopeId: BigInt(scopeId),
|
||||
settingKey: key,
|
||||
settingVal: raw,
|
||||
mode: "light",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.onDuplicateKeyUpdate({
|
||||
set: { settingVal: raw, updatedAt: new Date() },
|
||||
});
|
||||
}
|
||||
|
||||
// Save effect settings
|
||||
for (const key of EFFECT_KEYS) {
|
||||
const raw = String(formData.get(key) ?? "").trim();
|
||||
if (!raw) continue;
|
||||
|
||||
await db
|
||||
.insert(ThemeScopeValue)
|
||||
.values({
|
||||
scopeId: BigInt(scopeId),
|
||||
settingKey: key,
|
||||
settingVal: raw,
|
||||
mode: "light",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.onDuplicateKeyUpdate({
|
||||
set: { settingVal: raw, updatedAt: new Date() },
|
||||
});
|
||||
}
|
||||
|
||||
// Save media settings
|
||||
for (const key of MEDIA_KEYS) {
|
||||
const raw = String(formData.get(key) ?? "").trim();
|
||||
if (!raw) continue;
|
||||
|
||||
await db
|
||||
.insert(ThemeScopeValue)
|
||||
.values({
|
||||
scopeId: BigInt(scopeId),
|
||||
settingKey: key,
|
||||
settingVal: raw,
|
||||
mode: "light",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.onDuplicateKeyUpdate({
|
||||
set: { settingVal: raw, updatedAt: new Date() },
|
||||
});
|
||||
}
|
||||
|
||||
// Save custom CSS
|
||||
const customCss = String(formData.get("custom:css") ?? "").trim();
|
||||
if (customCss) {
|
||||
await db
|
||||
.insert(ThemeScopeValue)
|
||||
.values({
|
||||
scopeId: BigInt(scopeId),
|
||||
settingKey: "custom:css",
|
||||
settingVal: customCss,
|
||||
mode: "light",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.onDuplicateKeyUpdate({
|
||||
set: { settingVal: customCss, updatedAt: new Date() },
|
||||
});
|
||||
} else {
|
||||
await db
|
||||
.delete(ThemeScopeValue)
|
||||
.where(
|
||||
and(
|
||||
sql`${ThemeScopeValue.scopeId} = ${scopeId}`,
|
||||
eq(ThemeScopeValue.settingKey, "custom:css"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Save custom HTML
|
||||
const customHtml = String(formData.get("custom:html") ?? "").trim();
|
||||
if (customHtml) {
|
||||
await db
|
||||
.insert(ThemeScopeValue)
|
||||
.values({
|
||||
scopeId: BigInt(scopeId),
|
||||
settingKey: "custom:html",
|
||||
settingVal: customHtml,
|
||||
mode: "light",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.onDuplicateKeyUpdate({
|
||||
set: { settingVal: customHtml, updatedAt: new Date() },
|
||||
});
|
||||
} else {
|
||||
await db
|
||||
.delete(ThemeScopeValue)
|
||||
.where(
|
||||
and(
|
||||
sql`${ThemeScopeValue.scopeId} = ${scopeId}`,
|
||||
eq(ThemeScopeValue.settingKey, "custom:html"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const staff = await requirePermission(PERMS.SETTINGS_EDIT);
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
|
||||
Reference in new issue
Block a user