From a19e7c40a200ff822c947a83349ed07ccf4e1b01 Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Tue, 3 Feb 2026 17:12:56 +0100 Subject: [PATCH] fix: sync header_style DB column with theme editor selections (#158) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The frontend never sent header_style/hero_divider_style as separate fields when creating or updating events, so the database columns always kept their default value of 'standard' — making the hero header impossible to enable through the admin UI. - Extract headerStyle/heroDividerStyle from theme config and include in create and update payloads (CreateEventPage, EventDetailsPage) - Add backend fallback to extract values from color_theme JSON when not explicitly provided, ensuring older clients stay in sync --- .gitignore | 8 ++++ backend/src/routes/adminEvents.js | 45 ++++++++++++++++++- frontend/src/pages/admin/CreateEventPage.tsx | 2 + frontend/src/pages/admin/EventDetailsPage.tsx | 3 ++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7a1c752e..4a6603f5 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,14 @@ docs/*_PLAN.md docs/test-*.md docs/feature-*.md +# Scaffolding documentation (local development reference) +docs/DATABASE_SCHEMA.md +docs/BACKEND_SERVICES.md +docs/API_ROUTES.md +docs/FRONTEND_ARCHITECTURE.md +docs/DEVELOPER_ONBOARDING.md +docs/ENVIRONMENT_VARIABLES.md + # Local backup directory (from testing) backup/ diff --git a/backend/src/routes/adminEvents.js b/backend/src/routes/adminEvents.js index 25845e6f..d1d90bd9 100644 --- a/backend/src/routes/adminEvents.js +++ b/backend/src/routes/adminEvents.js @@ -365,6 +365,26 @@ router.post('/', adminAuth, requirePermission('events.create'), [ await fs.mkdir(path.join(eventPath, 'collages'), { recursive: true }); await fs.mkdir(path.join(eventPath, 'individual'), { recursive: true }); + // Sync header_style / hero_divider_style from color_theme JSON when not + // explicitly provided in the request body (#158). + let effectiveHeaderStyle = header_style; + let effectiveDividerStyle = hero_divider_style; + if (color_theme && (!req.body.header_style || !req.body.hero_divider_style)) { + try { + if (typeof color_theme === 'string' && color_theme.startsWith('{')) { + const parsed = JSON.parse(color_theme); + if (!req.body.header_style && parsed.headerStyle) { + effectiveHeaderStyle = parsed.headerStyle; + } + if (!req.body.hero_divider_style && parsed.heroDividerStyle) { + effectiveDividerStyle = parsed.heroDividerStyle; + } + } + } catch (_) { + // color_theme is not JSON – nothing to extract + } + } + // Insert into database const insertResult = await db('events').insert({ slug, @@ -394,8 +414,8 @@ router.post('/', adminAuth, requirePermission('events.create'), [ hero_logo_visible: formatBoolean(hero_logo_visible !== undefined ? hero_logo_visible : true), hero_logo_size: hero_logo_size || 'medium', hero_logo_position: hero_logo_position || 'top', - header_style: header_style || 'standard', - hero_divider_style: hero_divider_style || 'wave', + header_style: effectiveHeaderStyle || 'standard', + hero_divider_style: effectiveDividerStyle || 'wave', hero_image_anchor: hero_image_anchor || 'center' }).returning('id'); @@ -810,6 +830,27 @@ router.put('/:id', adminAuth, requirePermission('events.edit'), [ updates.hero_logo_visible = formatBoolean(updates.hero_logo_visible); } + // Sync header_style / hero_divider_style from color_theme JSON when not + // explicitly provided in the request body (#158). This ensures the + // database columns stay in sync even if the frontend only sends the + // serialised theme object. + if (updates.color_theme && !Object.prototype.hasOwnProperty.call(updates, 'header_style')) { + try { + const themeStr = typeof updates.color_theme === 'string' ? updates.color_theme : ''; + if (themeStr.startsWith('{')) { + const parsed = JSON.parse(themeStr); + if (parsed.headerStyle) { + updates.header_style = parsed.headerStyle; + } + if (parsed.heroDividerStyle && !Object.prototype.hasOwnProperty.call(updates, 'hero_divider_style')) { + updates.hero_divider_style = parsed.heroDividerStyle; + } + } + } catch (_) { + // color_theme is not JSON (e.g. preset name) – nothing to extract + } + } + // Update event await db('events') .where('id', id) diff --git a/frontend/src/pages/admin/CreateEventPage.tsx b/frontend/src/pages/admin/CreateEventPage.tsx index 456ef153..a90e4e74 100644 --- a/frontend/src/pages/admin/CreateEventPage.tsx +++ b/frontend/src/pages/admin/CreateEventPage.tsx @@ -292,6 +292,8 @@ export const CreateEventPage: React.FC = () => { password: formData.require_password ? formData.password : undefined, welcome_message: formData.welcome_message || '', color_theme: JSON.stringify(formData.theme_config), + header_style: formData.theme_config.headerStyle || 'standard', + hero_divider_style: formData.theme_config.heroDividerStyle || 'wave', expiration_days: requireExpiration ? formData.expires_in_days : undefined, allow_user_uploads: formData.allow_user_uploads, upload_category_id: formData.upload_category_id, diff --git a/frontend/src/pages/admin/EventDetailsPage.tsx b/frontend/src/pages/admin/EventDetailsPage.tsx index 3eef09a0..297c93c7 100644 --- a/frontend/src/pages/admin/EventDetailsPage.tsx +++ b/frontend/src/pages/admin/EventDetailsPage.tsx @@ -550,6 +550,9 @@ export const EventDetailsPage: React.FC = () => { hero_logo_position: editForm.hero_logo_position, // Hero image anchor position (#162) hero_image_anchor: editForm.hero_image_anchor, + // Header style settings (decoupled from layout, #158) + header_style: currentTheme?.headerStyle || 'standard', + hero_divider_style: currentTheme?.heroDividerStyle || 'wave', }; // Only include fields that have defined values