fix(branding): unify hero logo SIZE the same way as visibility (#756)

Follow-on to the visibility fix in this PR — hero_logo_size had the same
split-brain: GalleryLayout read the global branding_logo_size live while
the hero-header path used the per-event snapshot, so a hero logo could
render at different sizes on different layouts and the global size didn't
reach hero-header galleries.

Now mirrored on the visibility model: NULL per-event hero_logo_size =
inherit branding_logo_size; explicit = override.

- Migration 153: hero_logo_size nullable + backfill NULL so existing
  galleries inherit the global size (restores GalleryLayout's prior
  live-global behaviour and fixes the hero-header staleness).
- Creation stores NULL unless explicit; gallery.js resolves
  per-event ?? global and sends the effective size.
- GalleryLayout now consumes that resolved size for the hero logo (new
  heroLogoSize prop) instead of the global — both render paths match.
- Admin size control gains a 'Use branding default' (inherit) option.

Verified: migration on SQLite + PG; live resolution (inherit follows
global both ways, override wins); creation stores NULL on PG; tsc clean,
106 adminEvents+gallery tests pass, build green.
This commit is contained in:
Paul Nothaft
2026-07-06 11:44:33 +02:00
parent 96fe478bf8
commit 60b03b1728
9 changed files with 85 additions and 16 deletions
@@ -29,6 +29,11 @@ interface GalleryLayoutProps {
// backend (per-event override, else the global branding toggle) (#756).
// When provided it wins over brandingSettings.logo_display_hero.
heroLogoVisible?: boolean;
// Effective hero-logo SIZE, resolved the same way (per-event override, else
// the global branding_logo_size) (#756). When provided it wins over
// brandingSettings.logo_size for the hero logo — so both render paths
// (this layout and the hero-header) size the logo identically.
heroLogoSize?: 'small' | 'medium' | 'large' | 'xlarge' | 'custom';
brandingSettings?: {
company_name?: string;
company_tagline?: string;
@@ -113,6 +118,7 @@ export const GalleryLayout: React.FC<GalleryLayoutProps> = ({
event,
brandingSettings,
heroLogoVisible,
heroLogoSize,
showLogout = false,
onLogout,
showDownloadAll = false,
@@ -162,7 +168,10 @@ export const GalleryLayout: React.FC<GalleryLayoutProps> = ({
// Calculate logo size classes based on settings
const getLogoDimensions = (context: 'header' | 'hero'): { className: string; style?: React.CSSProperties } => {
const size = brandingSettings?.logo_size || 'medium';
// #756: the hero logo uses the backend-resolved per-event size (override,
// else global) so it matches the hero-header layout; the header logo keeps
// the global size.
const size = (context === 'hero' && heroLogoSize) ? heroLogoSize : (brandingSettings?.logo_size || 'medium');
const maxHeight = brandingSettings?.logo_max_height || 48;
if (size === 'custom') {
@@ -221,7 +230,7 @@ export const GalleryLayout: React.FC<GalleryLayoutProps> = ({
};
const headerLogoSize = getLogoDimensions('header');
const heroLogoSize = getLogoDimensions('hero');
const heroLogoDimensions = getLogoDimensions('hero');
// Footer overhaul (#441 + #440). All five socials are independent;
// empty string = hide just that icon. Per-event promo override:
@@ -607,9 +616,9 @@ export const GalleryLayout: React.FC<GalleryLayoutProps> = ({
'/picpeak-logo-transparent.png'
}
alt={brandingSettings?.company_name || 'PicPeak'}
className={`${heroLogoSize.className} w-auto object-contain mx-auto`}
className={`${heroLogoDimensions.className} w-auto object-contain mx-auto`}
style={{
...(heroLogoSize.style || {}),
...(heroLogoDimensions.style || {}),
// Only apply brightness/invert filter to default logo; custom logos display as-is
filter: brandLogoUrl
? 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3))'
@@ -821,6 +821,7 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
}}
brandingSettings={brandingSettings}
heroLogoVisible={data?.event?.hero_logo_visible !== false}
heroLogoSize={data?.event?.hero_logo_size || undefined}
headerStyle={data?.event?.header_style || theme.headerStyle}
showLogout={true}
onLogout={logout}