From 44590b8c0b21f22cbf7857b71951d2747db5be98 Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Wed, 3 Jun 2026 13:33:27 +0200 Subject: [PATCH] feat(branding): symmetric light/dark logo fallback + customer surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- frontend/src/components/admin/AdminHeader.tsx | 9 +++++---- frontend/src/components/gallery/GalleryLayout.tsx | 8 +++++--- frontend/src/pages/customer/CustomerLayout.tsx | 11 ++++++++++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/admin/AdminHeader.tsx b/frontend/src/components/admin/AdminHeader.tsx index 0315bd77..605e8ed0 100644 --- a/frontend/src/components/admin/AdminHeader.tsx +++ b/frontend/src/components/admin/AdminHeader.tsx @@ -33,10 +33,11 @@ export const AdminHeader: React.FC = ({ onMenuClick }) => { const { data: brandingSettings } = usePublicSettings(); const companyName = brandingSettings?.branding_company_name?.trim() || 'PicPeak'; - // Dark-mode logo variant: use it when the admin theme is dark and a dark - // logo was uploaded; otherwise fall back to the light logo. - const logoUrl = (isDark && brandingSettings?.branding_logo_url_dark?.trim()) - || brandingSettings?.branding_logo_url?.trim(); + // Dark-mode logo variant. Symmetric fallback: if only one logo is set, + // use it for both modes (dark → dark||light, light → light||dark). + const lightLogo = 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'; // Logo placement honours the same Branding > Logo Position setting // the gallery does. 'sidepanel' moves the logo into the AdminSidebar diff --git a/frontend/src/components/gallery/GalleryLayout.tsx b/frontend/src/components/gallery/GalleryLayout.tsx index c4bb7d03..377ddfcb 100644 --- a/frontend/src/components/gallery/GalleryLayout.tsx +++ b/frontend/src/components/gallery/GalleryLayout.tsx @@ -123,9 +123,11 @@ export const GalleryLayout: React.FC = ({ const { t } = useTranslation(); const { format } = useLocalizedDate(); const { theme } = useTheme(); - // Dark-mode logo variant: prefer it on dark gallery themes, else light. - const brandLogoUrl = (theme.colorMode === 'dark' && brandingSettings?.logo_url_dark) - || brandingSettings?.logo_url; + // Dark-mode logo variant. Symmetric fallback: a single uploaded logo + // serves both modes (dark → dark||light, light → light||dark). + const brandLogoUrl = theme.colorMode === 'dark' + ? (brandingSettings?.logo_url_dark || brandingSettings?.logo_url) + : (brandingSettings?.logo_url || brandingSettings?.logo_url_dark); const guestIdentity = useGuestIdentityOptional(); // Footer legal-link config. Cached aggressively because the toggle state diff --git a/frontend/src/pages/customer/CustomerLayout.tsx b/frontend/src/pages/customer/CustomerLayout.tsx index a16ba1a3..536b8d60 100644 --- a/frontend/src/pages/customer/CustomerLayout.tsx +++ b/frontend/src/pages/customer/CustomerLayout.tsx @@ -65,7 +65,16 @@ export const CustomerLayout: React.FC = () => { const [sidebarOpen, setSidebarOpen] = useState(false); 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'; // Filter out feature-gated entries the customer can't see. Galleries +