fix(events): preserve branding inheritance when saving events with null color_theme

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).
This commit is contained in:
Paul Nothaft
2026-05-27 15:44:41 +02:00
parent d5823c79d9
commit d5a37df2c4
+27 -4
View File
@@ -372,6 +372,12 @@ export const EventDetailsPage: React.FC = () => {
const [logoUploading, setLogoUploading] = useState(false);
const [currentTheme, setCurrentTheme] = useState<ThemeConfig | null>(null);
const [currentPresetName, setCurrentPresetName] = useState<string>('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<EnabledTemplate[]>([]);
// 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}