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 && (
})
@@ -103,10 +111,10 @@ export const ClientAccessPage: React.FC = () => {
{/* Logo */}
- {settingsData?.branding_logo_url && (
+ {brandLogo && (
})
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