From d5a37df2c41425511dc8a1f974088bebb768f0d5 Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Wed, 27 May 2026 15:44:41 +0200 Subject: [PATCH] fix(events): preserve branding inheritance when saving events with null color_theme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API-created events (and any event whose `color_theme` is NULL) had two visible bugs in the admin edit page (#550 follow-up — PR #552 fixed the v1 POST write path, this fixes the read/save path): 1. The theme picker initialised to the hardcoded `GALLERY_THEME_PRESETS .default.config` ("Classic Grid", green) — which had nothing to do with the admin's actual branding palette, while the gallery itself was rendering with the branding theme. Confusing visual mismatch. 2. Saving the event for ANY reason (changing the date, password, etc.) wrote `color_theme = 'default'` back to the row because the save handler always emitted the picker's initial preset name. That silently replaced "inherit from branding" with the literal Classic Grid preset, so the gallery's visuals jumped. Two fixes, both in EventDetailsPage: - Add a `themeChanged` flag, defaulted false. Flip in the picker's onChange / onPresetChange / onSyncFromBranding callbacks. The save handler now only writes `updateData.color_theme` when the flag is true, so saving without touching the picker preserves NULL. - When `event.color_theme` is null and `publicSettings.theme_config` (the site branding) is available, initialise `currentTheme` from branding instead of the Classic Grid preset, with currentPresetName set to 'custom' (since inherited branding isn't a named preset). Falls back to the Classic Grid preset only when no branding theme exists either. Combined effect: opening an API-created event shows the same palette the gallery uses, and saving without changing the theme preserves the inheritance. Existing events with a stored color_theme are unaffected (themeChanged stays false → no write, just like before for the common no-change-to-theme save). --- frontend/src/pages/admin/EventDetailsPage.tsx | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/admin/EventDetailsPage.tsx b/frontend/src/pages/admin/EventDetailsPage.tsx index d49992ae..711e2498 100644 --- a/frontend/src/pages/admin/EventDetailsPage.tsx +++ b/frontend/src/pages/admin/EventDetailsPage.tsx @@ -372,6 +372,12 @@ export const EventDetailsPage: React.FC = () => { const [logoUploading, setLogoUploading] = useState(false); const [currentTheme, setCurrentTheme] = useState(null); const [currentPresetName, setCurrentPresetName] = useState('default'); + // Tracks whether the admin actually interacted with the theme picker + // during this edit session. Prevents the save handler from writing the + // initial display state back to `events.color_theme`, which silently + // overwrote branding inheritance on events with a NULL color_theme + // (API-created events — #550 follow-up). + const [themeChanged, setThemeChanged] = useState(false); const [cssTemplates, setCssTemplates] = useState([]); // Fetch CSS templates when component mounts or editing starts @@ -643,10 +649,20 @@ export const EventDetailsPage: React.FC = () => { setCurrentPresetName('default'); } } else { - setCurrentTheme(GALLERY_THEME_PRESETS.default.config); - setCurrentPresetName('default'); + // No color_theme stored — the gallery renders with the site + // branding theme as a fallback. Mirror that here so the picker + // shows the same palette the admin sees on the gallery, rather + // than the hardcoded Classic Grid preset that has nothing to do + // with their branding (#550 follow-up). currentPresetName=custom + // because the inherited config isn't a named preset; combined + // with themeChanged=false below, saving without touching the + // picker leaves color_theme NULL and preserves inheritance. + const branding = publicSettings?.theme_config as ThemeConfig | undefined; + setCurrentTheme(branding ?? GALLERY_THEME_PRESETS.default.config); + setCurrentPresetName(branding ? 'custom' : 'default'); } - + setThemeChanged(false); + setIsEditing(true); }; @@ -765,7 +781,11 @@ export const EventDetailsPage: React.FC = () => { if (editForm.welcome_message !== undefined && editForm.welcome_message !== null) { updateData.welcome_message = editForm.welcome_message; } - if (themeToSave) { + // Only persist color_theme when the admin actually interacted with + // the picker. Writing the initial display state back to the row + // silently overwrote NULL (= "inherit branding") with the picker's + // default preset on any save (#550 follow-up). + if (themeChanged && themeToSave) { updateData.color_theme = themeToSave; } if (editForm.upload_category_id !== undefined) { @@ -2210,10 +2230,12 @@ export const EventDetailsPage: React.FC = () => { onChange={(theme) => { setCurrentTheme(theme); setEditForm(prev => ({ ...prev, color_theme: JSON.stringify(theme) })); + setThemeChanged(true); }} presetName={currentPresetName} onPresetChange={(presetName) => { setCurrentPresetName(presetName); + setThemeChanged(true); if (presetName !== 'custom') { const preset = GALLERY_THEME_PRESETS[presetName]; if (preset) { @@ -2248,6 +2270,7 @@ export const EventDetailsPage: React.FC = () => { setCurrentTheme(merged); setCurrentPresetName('custom'); setEditForm(prev => ({ ...prev, color_theme: JSON.stringify(merged) })); + setThemeChanged(true); toast.success(t('toast.brandingPaletteSynced', 'Palette synced from Branding.')); }} isPreviewMode={true}