From 0b4b5cf46ee5c99e440dbad08b1e42d337dc1d57 Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:56:15 +0200 Subject: [PATCH] fix(branding): theme-aware logo across all login / auth entry pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the dark-logo fix to the customer login, customer accept-invite, customer reset-password, and gallery client-access pages — they all rendered only the light logo on the themed (possibly dark) surface. Also makes the login-page pick frame-aware: a framed login logo sits on a fixed cream plate, so the light (dark-ink) logo always reads there; only the frameless logo sits on the themed page background and uses the dark variant. This corrects the admin login too (was unconditionally swapping when dark). Customer/gallery pages read isDark from usePublicDarkMode (branding_force_ color_mode + OS fallback), matching CustomerLayout. --- frontend/src/pages/ClientAccessPage.tsx | 16 ++++++++++++---- frontend/src/pages/admin/AdminLoginPage.tsx | 10 +++++++--- .../pages/customer/CustomerAcceptInvitePage.tsx | 9 ++++++++- .../src/pages/customer/CustomerLoginPage.tsx | 12 +++++++++++- .../pages/customer/CustomerResetPasswordPage.tsx | 9 ++++++++- 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/frontend/src/pages/ClientAccessPage.tsx b/frontend/src/pages/ClientAccessPage.tsx index 4048eee2..f27e0530 100644 --- a/frontend/src/pages/ClientAccessPage.tsx +++ b/frontend/src/pages/ClientAccessPage.tsx @@ -7,6 +7,7 @@ import { Card, CardContent, Input, Button, Loading } from '../components/common' import { useGalleryAuth } from '../contexts'; import { useGalleryInfo } from '../hooks/useGallery'; import { usePublicSettings } from '../hooks/usePublicSettings'; +import { usePublicDarkMode } from '../hooks/usePublicDarkMode'; import { buildResourceUrl } from '../utils/url'; export const ClientAccessPage: React.FC = () => { @@ -22,6 +23,13 @@ export const ClientAccessPage: React.FC = () => { const { data: galleryInfo, isLoading: isLoadingInfo, error: infoError } = useGalleryInfo(slug); const { data: settingsData } = usePublicSettings(); + // Theme-aware logo: the page background follows the themed + // --color-background (dark when branding_force_color_mode / OS is dark), + // so pick the dark logo variant accordingly. + const { isDark } = usePublicDarkMode(); + const lightLogo = settingsData?.branding_logo_url?.trim(); + const darkLogo = settingsData?.branding_logo_url_dark?.trim(); + const brandLogo = isDark ? (darkLogo || lightLogo) : (lightLogo || darkLogo); // If already authenticated as client, redirect to gallery React.useEffect(() => { @@ -76,10 +84,10 @@ export const ClientAccessPage: React.FC = () => { return (
- {settingsData?.branding_logo_url && ( + {brandLogo && (
{settingsData.branding_company_name @@ -103,10 +111,10 @@ export const ClientAccessPage: React.FC = () => {
{/* Logo */} - {settingsData?.branding_logo_url && ( + {brandLogo && (
{settingsData.branding_company_name diff --git a/frontend/src/pages/admin/AdminLoginPage.tsx b/frontend/src/pages/admin/AdminLoginPage.tsx index 7afe1385..7fe2bc33 100644 --- a/frontend/src/pages/admin/AdminLoginPage.tsx +++ b/frontend/src/pages/admin/AdminLoginPage.tsx @@ -32,11 +32,15 @@ export const AdminLoginPage: React.FC = () => { const companyName = settingsData?.branding_company_name?.trim() || 'PicPeak'; // Theme-aware logo: the login page honours the admin dark-mode preference - // (and any branding_force_color_mode), so a dark-text logo isn't shown on - // the dark background. Falls back to whichever variant exists. + // (and any branding_force_color_mode). NOTE the frame nuance — a framed + // logo sits on a fixed cream plate (see render), so the light (dark-ink) + // logo always reads there; only the frameless logo sits on the themed + // (possibly dark) page background and needs the dark variant. const lightLogo = settingsData?.branding_logo_url?.trim(); const darkLogo = settingsData?.branding_logo_url_dark?.trim(); - const logoUrl = isDark ? (darkLogo || lightLogo) : (lightLogo || darkLogo); + const loginFrameEnabled = settingsData?.branding_login_logo_frame_enabled !== false; + const themedLogo = isDark ? (darkLogo || lightLogo) : (lightLogo || darkLogo); + const logoUrl = loginFrameEnabled ? (lightLogo || darkLogo) : themedLogo; const resolvedLogoUrl = logoUrl || '/picpeak-logo-transparent.png'; // Check for session expired message diff --git a/frontend/src/pages/customer/CustomerAcceptInvitePage.tsx b/frontend/src/pages/customer/CustomerAcceptInvitePage.tsx index 8deca301..fcd179eb 100644 --- a/frontend/src/pages/customer/CustomerAcceptInvitePage.tsx +++ b/frontend/src/pages/customer/CustomerAcceptInvitePage.tsx @@ -28,6 +28,7 @@ import { type CustomerProfilePrefill, } from '../../services/customer.service'; import { usePublicSettings } from '../../hooks/usePublicSettings'; +import { usePublicDarkMode } from '../../hooks/usePublicDarkMode'; interface FormState { display_name: string; @@ -76,8 +77,14 @@ export const CustomerAcceptInvitePage: React.FC = () => { const [isSubmitting, setIsSubmitting] = useState(false); const { data: settingsData } = usePublicSettings(); + // Theme-aware logo: the page renders on the themed customer surface + // (dark when branding_force_color_mode is dark / OS dark), so pick the + // dark logo variant accordingly. No frame here — logo sits on the page bg. + const { isDark } = usePublicDarkMode(); const companyName = settingsData?.branding_company_name?.trim() || 'PicPeak'; - const logoUrl = settingsData?.branding_logo_url?.trim(); + const lightLogo = settingsData?.branding_logo_url?.trim(); + const darkLogo = settingsData?.branding_logo_url_dark?.trim(); + const logoUrl = isDark ? (darkLogo || lightLogo) : (lightLogo || darkLogo); const resolvedLogoUrl = logoUrl || '/picpeak-logo-transparent.png'; // Pre-flight invitation lookup. The response carries any prefill data diff --git a/frontend/src/pages/customer/CustomerLoginPage.tsx b/frontend/src/pages/customer/CustomerLoginPage.tsx index de6c4852..391c719d 100644 --- a/frontend/src/pages/customer/CustomerLoginPage.tsx +++ b/frontend/src/pages/customer/CustomerLoginPage.tsx @@ -14,6 +14,7 @@ import { Button, Input, Card, ReCaptcha } from '../../components/common'; import { useCustomerAuth } from '../../contexts/CustomerAuthContext'; import { customerService } from '../../services/customer.service'; import { usePublicSettings } from '../../hooks/usePublicSettings'; +import { usePublicDarkMode } from '../../hooks/usePublicDarkMode'; import { resolveLoginLogoClasses } from '../../utils/loginLogoSize'; export const CustomerLoginPage: React.FC = () => { @@ -28,8 +29,17 @@ export const CustomerLoginPage: React.FC = () => { const [recaptchaToken, setRecaptchaToken] = useState(null); const { data: settingsData } = usePublicSettings(); + // Customer surface follows branding_force_color_mode (+ OS fallback); isDark + // drives the theme-aware logo pick. A framed logo sits on a fixed cream + // plate (see render), so the light (dark-ink) logo always reads there; + // only the frameless logo sits on the themed (possibly dark) page bg. + const { isDark } = usePublicDarkMode(); const companyName = settingsData?.branding_company_name?.trim() || 'PicPeak'; - const logoUrl = settingsData?.branding_logo_url?.trim(); + const lightLogo = settingsData?.branding_logo_url?.trim(); + const darkLogo = settingsData?.branding_logo_url_dark?.trim(); + const loginFrameEnabled = settingsData?.branding_login_logo_frame_enabled !== false; + const themedLogo = isDark ? (darkLogo || lightLogo) : (lightLogo || darkLogo); + const logoUrl = loginFrameEnabled ? (lightLogo || darkLogo) : themedLogo; const resolvedLogoUrl = logoUrl || '/picpeak-logo-transparent.png'; // After /accept-invite the user is redirected here with ?accepted=1 diff --git a/frontend/src/pages/customer/CustomerResetPasswordPage.tsx b/frontend/src/pages/customer/CustomerResetPasswordPage.tsx index f003b59a..3be8995f 100644 --- a/frontend/src/pages/customer/CustomerResetPasswordPage.tsx +++ b/frontend/src/pages/customer/CustomerResetPasswordPage.tsx @@ -18,6 +18,7 @@ import { useTranslation } from 'react-i18next'; import { Button, Input, Card, Loading } from '../../components/common'; import { customerService } from '../../services/customer.service'; import { usePublicSettings } from '../../hooks/usePublicSettings'; +import { usePublicDarkMode } from '../../hooks/usePublicDarkMode'; export const CustomerResetPasswordPage: React.FC = () => { const { t } = useTranslation(); @@ -33,8 +34,14 @@ export const CustomerResetPasswordPage: React.FC = () => { const [isSubmitting, setIsSubmitting] = useState(false); const { data: settingsData } = usePublicSettings(); + // Theme-aware logo: page renders on the themed customer surface (dark when + // branding_force_color_mode is dark / OS dark). No frame — logo sits on the + // page bg, so pick the dark variant when dark. + const { isDark } = usePublicDarkMode(); const companyName = settingsData?.branding_company_name?.trim() || 'PicPeak'; - const logoUrl = settingsData?.branding_logo_url?.trim(); + const lightLogo = settingsData?.branding_logo_url?.trim(); + const darkLogo = settingsData?.branding_logo_url_dark?.trim(); + const logoUrl = isDark ? (darkLogo || lightLogo) : (lightLogo || darkLogo); const resolvedLogoUrl = logoUrl || '/picpeak-logo-transparent.png'; // Pre-flight token validation. Same pattern as the invite page — if the