Wire guest branding theme

This commit is contained in:
Codex Agent
2026-01-15 08:06:21 +01:00
parent 33e46b448d
commit 81446b37c3
14 changed files with 478 additions and 123 deletions

View File

@@ -9,9 +9,8 @@ import { useTranslation } from '../i18n/useTranslation';
import { DEFAULT_LOCALE, isLocaleCode } from '../i18n/messages';
import { AlertTriangle, Download, Loader2, Share, X } from 'lucide-react';
import { createPhotoShareLink } from '../services/photosApi';
import { Share } from 'lucide-react';
import { createPhotoShareLink } from '../services/photosApi';
import { getContrastingTextColor } from '../lib/color';
import { applyGuestTheme } from '../lib/guestTheme';
interface GalleryState {
meta: GalleryMetaResponse | null;
@@ -95,28 +94,34 @@ export default function PublicGalleryPage(): React.ReactElement | null {
loadInitial();
}, [loadInitial]);
const resolvedBranding = useMemo(() => {
if (!state.meta) {
return null;
}
const palette = state.meta.branding.palette ?? {};
const primary = palette.primary ?? state.meta.branding.primary_color ?? '#f43f5e';
const secondary = palette.secondary ?? state.meta.branding.secondary_color ?? '#fb7185';
const background = palette.background ?? state.meta.branding.background_color ?? '#ffffff';
const surface = palette.surface ?? state.meta.branding.surface_color ?? background;
const mode = state.meta.branding.mode ?? 'auto';
return {
primary,
secondary,
background,
surface,
mode,
};
}, [state.meta]);
useEffect(() => {
const mode = state.meta?.branding.mode;
if (!mode || typeof document === 'undefined') {
if (!resolvedBranding) {
return;
}
const wasDark = document.documentElement.classList.contains('dark');
if (mode === 'dark') {
document.documentElement.classList.add('dark');
} else if (mode === 'light') {
document.documentElement.classList.remove('dark');
}
return () => {
if (wasDark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
};
}, [state.meta?.branding.mode]);
return applyGuestTheme(resolvedBranding);
}, [resolvedBranding]);
const loadMore = useCallback(async () => {
if (!token || !state.cursor || state.loadingMore) {
@@ -164,55 +169,46 @@ export default function PublicGalleryPage(): React.ReactElement | null {
}, [state.cursor, loadMore]);
const themeStyles = useMemo(() => {
if (!state.meta) {
if (!resolvedBranding) {
return {} as React.CSSProperties;
}
const palette = state.meta.branding.palette ?? {};
const primary = palette.primary ?? state.meta.branding.primary_color;
const secondary = palette.secondary ?? state.meta.branding.secondary_color;
const background = palette.background ?? state.meta.branding.background_color;
const surface = palette.surface ?? state.meta.branding.surface_color ?? background;
return {
'--gallery-primary': primary,
'--gallery-secondary': secondary,
'--gallery-background': background,
'--gallery-surface': surface,
'--gallery-primary': resolvedBranding.primary,
'--gallery-secondary': resolvedBranding.secondary,
'--gallery-background': resolvedBranding.background,
'--gallery-surface': resolvedBranding.surface,
} as React.CSSProperties & Record<string, string>;
}, [state.meta]);
}, [resolvedBranding]);
const headerStyle = useMemo(() => {
if (!state.meta) {
if (!resolvedBranding) {
return {};
}
const palette = state.meta.branding.palette ?? {};
const primary = palette.primary ?? state.meta.branding.primary_color;
const secondary = palette.secondary ?? state.meta.branding.secondary_color ?? primary;
const textColor = getContrastingTextColor(primary ?? '#f43f5e', '#0f172a', '#ffffff');
const textColor = getContrastingTextColor(resolvedBranding.primary, '#0f172a', '#ffffff');
return {
background: `linear-gradient(135deg, ${primary}, ${secondary})`,
background: `linear-gradient(135deg, ${resolvedBranding.primary}, ${resolvedBranding.secondary})`,
color: textColor,
} satisfies React.CSSProperties;
}, [state.meta]);
}, [resolvedBranding]);
const accentStyle = useMemo(() => {
if (!state.meta) {
if (!resolvedBranding) {
return {};
}
return {
color: (state.meta.branding.palette?.primary ?? state.meta.branding.primary_color),
color: resolvedBranding.primary,
} satisfies React.CSSProperties;
}, [state.meta]);
}, [resolvedBranding]);
const backgroundStyle = useMemo(() => {
if (!state.meta) {
if (!resolvedBranding) {
return {};
}
return {
backgroundColor: state.meta.branding.palette?.background ?? state.meta.branding.background_color,
backgroundColor: resolvedBranding.background,
} satisfies React.CSSProperties;
}, [state.meta]);
}, [resolvedBranding]);
const openLightbox = useCallback((photo: GalleryPhotoResource) => {
setSelectedPhoto(photo);