feat: decouple hero header from gallery layouts (#158)

- Add separate header_style setting (hero/standard/minimal/none) that can
  be combined with any layout type (grid/masonry/carousel/timeline/mosaic)
- Create HeroHeader and HeroDivider components for reusable hero section
- Add hero_divider_style setting (wave/straight/angle/curve/none)
- Add database migration for header_style and hero_divider_style columns
- Remove deprecated HeroGalleryLayout component
- Fix various TypeScript errors across the codebase:
  - Add missing type properties (css_template_id, updatedAt, justified settings)
  - Fix null handling for event_date and expires_at fields
  - Fix translation function calls and i18n config
  - Remove unused imports and variables
This commit is contained in:
Paul Nothaft
2026-02-01 22:44:28 +01:00
parent 397d33a95a
commit 7b8d8bd92b
35 changed files with 907 additions and 533 deletions
+61
View File
@@ -0,0 +1,61 @@
import type { ThemeConfig, HeaderStyleType, HeroDividerStyle, GalleryLayoutType } from '../types/theme.types';
/**
* Migrates legacy theme configurations that used 'hero' as a galleryLayout
* to the new decoupled headerStyle + galleryLayout system.
*
* This ensures backward compatibility with existing events that have
* 'hero' set as their galleryLayout.
*/
export function migrateThemeConfig(theme: ThemeConfig): ThemeConfig {
if (!theme) return theme;
// Check if this theme uses the legacy 'hero' layout
if ((theme.galleryLayout as string) === 'hero') {
return {
...theme,
headerStyle: 'hero' as HeaderStyleType,
galleryLayout: 'grid' as GalleryLayoutType,
heroDividerStyle: (theme.heroDividerStyle || 'wave') as HeroDividerStyle,
};
}
// If headerStyle is not set but galleryLayout is valid, default to 'standard'
if (!theme.headerStyle && theme.galleryLayout) {
return {
...theme,
headerStyle: 'standard' as HeaderStyleType,
};
}
return theme;
}
/**
* Parses and migrates a color_theme JSON string from the database.
* Handles both JSON strings and legacy preset names.
*/
export function parseAndMigrateTheme(colorTheme: string | null | undefined): ThemeConfig | null {
if (!colorTheme) return null;
try {
// Check if it's a JSON string
if (colorTheme.startsWith('{')) {
const parsed = JSON.parse(colorTheme);
return migrateThemeConfig(parsed);
}
// Legacy preset name - return null to let the caller handle preset lookup
return null;
} catch {
// Invalid JSON
return null;
}
}
/**
* Checks if a theme configuration needs migration from legacy hero layout.
*/
export function needsMigration(theme: ThemeConfig): boolean {
return (theme.galleryLayout as string) === 'hero';
}