feat(branding): symmetric light/dark logo fallback + customer surface

Pick the logo by the active color mode with a symmetric fallback: a
single uploaded logo serves both modes (dark uses dark||light, light
uses light||dark). Apply to admin header, customer gallery, and the
customer portal (follows branding_force_color_mode). PDFs already use
the light branding logo with the business-profile PDF logo as override
(resolveLogoFile) — unchanged.
This commit is contained in:
Luca
2026-06-03 13:33:27 +02:00
parent 4790ea5ccd
commit 44590b8c0b
3 changed files with 20 additions and 8 deletions
@@ -33,10 +33,11 @@ export const AdminHeader: React.FC<AdminHeaderProps> = ({ onMenuClick }) => {
const { data: brandingSettings } = usePublicSettings(); const { data: brandingSettings } = usePublicSettings();
const companyName = brandingSettings?.branding_company_name?.trim() || 'PicPeak'; const companyName = brandingSettings?.branding_company_name?.trim() || 'PicPeak';
// Dark-mode logo variant: use it when the admin theme is dark and a dark // Dark-mode logo variant. Symmetric fallback: if only one logo is set,
// logo was uploaded; otherwise fall back to the light logo. // use it for both modes (dark → dark||light, light → light||dark).
const logoUrl = (isDark && brandingSettings?.branding_logo_url_dark?.trim()) const lightLogo = brandingSettings?.branding_logo_url?.trim();
|| brandingSettings?.branding_logo_url?.trim(); const darkLogo = brandingSettings?.branding_logo_url_dark?.trim();
const logoUrl = isDark ? (darkLogo || lightLogo) : (lightLogo || darkLogo);
const logoDisplayMode = brandingSettings?.branding_logo_display_mode || 'logo_and_text'; const logoDisplayMode = brandingSettings?.branding_logo_display_mode || 'logo_and_text';
// Logo placement honours the same Branding > Logo Position setting // Logo placement honours the same Branding > Logo Position setting
// the gallery does. 'sidepanel' moves the logo into the AdminSidebar // the gallery does. 'sidepanel' moves the logo into the AdminSidebar
@@ -123,9 +123,11 @@ export const GalleryLayout: React.FC<GalleryLayoutProps> = ({
const { t } = useTranslation(); const { t } = useTranslation();
const { format } = useLocalizedDate(); const { format } = useLocalizedDate();
const { theme } = useTheme(); const { theme } = useTheme();
// Dark-mode logo variant: prefer it on dark gallery themes, else light. // Dark-mode logo variant. Symmetric fallback: a single uploaded logo
const brandLogoUrl = (theme.colorMode === 'dark' && brandingSettings?.logo_url_dark) // serves both modes (dark → dark||light, light → light||dark).
|| brandingSettings?.logo_url; const brandLogoUrl = theme.colorMode === 'dark'
? (brandingSettings?.logo_url_dark || brandingSettings?.logo_url)
: (brandingSettings?.logo_url || brandingSettings?.logo_url_dark);
const guestIdentity = useGuestIdentityOptional(); const guestIdentity = useGuestIdentityOptional();
// Footer legal-link config. Cached aggressively because the toggle state // Footer legal-link config. Cached aggressively because the toggle state
+10 -1
View File
@@ -65,7 +65,16 @@ export const CustomerLayout: React.FC = () => {
const [sidebarOpen, setSidebarOpen] = useState(false); const [sidebarOpen, setSidebarOpen] = useState(false);
const companyName = settingsData?.branding_company_name?.trim() || 'PicPeak'; const companyName = settingsData?.branding_company_name?.trim() || 'PicPeak';
const logoUrl = settingsData?.branding_logo_url?.trim(); // Theme-aware logo: the customer surface follows branding_force_color_mode
// ('auto' → OS preference). Symmetric fallback so a single uploaded logo
// serves both modes.
const forceMode = settingsData?.branding_force_color_mode;
const customerIsDark = forceMode === 'dark'
|| (forceMode === 'auto' && typeof window !== 'undefined'
&& window.matchMedia?.('(prefers-color-scheme: dark)').matches);
const lightLogo = settingsData?.branding_logo_url?.trim();
const darkLogo = settingsData?.branding_logo_url_dark?.trim();
const logoUrl = customerIsDark ? (darkLogo || lightLogo) : (lightLogo || darkLogo);
const resolvedLogoUrl = logoUrl || '/picpeak-logo-transparent.png'; const resolvedLogoUrl = logoUrl || '/picpeak-logo-transparent.png';
// Filter out feature-gated entries the customer can't see. Galleries + // Filter out feature-gated entries the customer can't see. Galleries +