Private
Public Access
1
0

Fix branding page 500 error and make theme changes instant

1. Fix 500 on hard refresh: branding page load was querying tenant
   for logoUrl/logoText fields that may not exist in DB yet. Now
   uses locals.tenant data (from resolver) instead of a separate query.

2. Instant theme application: after saving, persistThemeCSS() generates
   CSS from the draft and writes it into the <style id="tenant-theme">
   element in the DOM. This means navigating to any page after saving
   immediately shows the new theme without a hard refresh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-27 14:58:32 -05:00
parent b1d994c9e4
commit 795e7c14ff
3 changed files with 100 additions and 9 deletions
+93
View File
@@ -0,0 +1,93 @@
import type { TenantThemeData } from '../../app.d.js';
/**
* Client-side CSS variable mapping — mirrors server-side theme-css.ts
*/
const FIELD_MAP: Record<string, { cssVar: string; mode: 'light' | 'dark' }> = {};
const vars = [
['PageBg', '--color-page-bg'],
['NavBg', '--color-nav-bg'],
['ColumnBg', '--color-column-bg'],
['ColumnBorder', '--color-column-border'],
['ColumnText', '--color-column-text'],
['CardBg', '--color-card-bg'],
['CardBorder', '--color-card-border'],
['CardText', '--color-card-text'],
['CardRadius', '--color-card-radius'],
['Primary', '--color-primary'],
['PrimaryHover', '--color-primary-hover'],
['Danger', '--color-danger'],
['DangerHover', '--color-danger-hover'],
['Success', '--color-success'],
['Warning', '--color-warning']
] as const;
for (const [suffix, cssVar] of vars) {
FIELD_MAP[`light${suffix}`] = { cssVar, mode: 'light' };
FIELD_MAP[`dark${suffix}`] = { cssVar, mode: 'dark' };
}
function hexToRgba(hex: string, opacity: number): string {
const h = hex.replace('#', '');
const r = parseInt(h.substring(0, 2), 16);
const g = parseInt(h.substring(2, 4), 16);
const b = parseInt(h.substring(4, 6), 16);
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
}
/**
* Generates CSS from theme data and writes it into the <style id="tenant-theme"> element.
* This mirrors what generateTenantCSS does server-side, but runs in the browser
* so theme changes take effect immediately without a full page reload.
*/
export function persistThemeCSS(theme: TenantThemeData) {
const lightVars: string[] = [];
const darkVars: string[] = [];
for (const [field, { cssVar, mode }] of Object.entries(FIELD_MAP)) {
const value = (theme as any)[field];
if (value == null) continue;
const target = mode === 'dark' ? darkVars : lightVars;
target.push(` ${cssVar}: ${value};`);
}
// Handle opacity for columns
if (theme.lightColumnOpacity != null && theme.lightColumnBg?.startsWith('#')) {
const idx = lightVars.findIndex((v) => v.includes('--color-column-bg:'));
if (idx !== -1) lightVars[idx] = ` --color-column-bg: ${hexToRgba(theme.lightColumnBg, theme.lightColumnOpacity)};`;
if (theme.lightColumnOpacity < 1) lightVars.push(` --column-backdrop-blur: blur(8px);`);
}
if (theme.lightCardOpacity != null && theme.lightCardBg?.startsWith('#')) {
const idx = lightVars.findIndex((v) => v.includes('--color-card-bg:'));
if (idx !== -1) lightVars[idx] = ` --color-card-bg: ${hexToRgba(theme.lightCardBg, theme.lightCardOpacity)};`;
if (theme.lightCardOpacity < 1) lightVars.push(` --card-backdrop-blur: blur(8px);`);
}
if (theme.darkColumnOpacity != null && theme.darkColumnBg?.startsWith('#')) {
const idx = darkVars.findIndex((v) => v.includes('--color-column-bg:'));
if (idx !== -1) darkVars[idx] = ` --color-column-bg: ${hexToRgba(theme.darkColumnBg, theme.darkColumnOpacity)};`;
if (theme.darkColumnOpacity < 1) darkVars.push(` --column-backdrop-blur: blur(8px);`);
}
if (theme.darkCardOpacity != null && theme.darkCardBg?.startsWith('#')) {
const idx = darkVars.findIndex((v) => v.includes('--color-card-bg:'));
if (idx !== -1) darkVars[idx] = ` --color-card-bg: ${hexToRgba(theme.darkCardBg, theme.darkCardOpacity)};`;
if (theme.darkCardOpacity < 1) darkVars.push(` --card-backdrop-blur: blur(8px);`);
}
let css = '';
if (lightVars.length > 0) {
css += `:root {\n${lightVars.join('\n')}\n}\n`;
}
if (darkVars.length > 0) {
css += `html.dark {\n${darkVars.join('\n')}\n}\n`;
}
// Find or create the <style id="tenant-theme"> element
let styleEl = document.getElementById('tenant-theme') as HTMLStyleElement | null;
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = 'tenant-theme';
document.head.appendChild(styleEl);
}
styleEl.textContent = css;
}