fix(branding): theme-aware logo across all login / auth entry pages

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.
This commit is contained in:
Luca
2026-06-03 16:56:15 +02:00
parent 05c1e8d18b
commit 0b4b5cf46e
5 changed files with 46 additions and 10 deletions
+12 -4
View File
@@ -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 (
<div className="min-h-screen" style={{ backgroundColor: 'var(--color-background, #fafafa)' }}>
<div className="min-h-screen flex flex-col">
{settingsData?.branding_logo_url && (
{brandLogo && (
<div className="p-8 text-center">
<img
src={buildResourceUrl(settingsData.branding_logo_url)}
src={buildResourceUrl(brandLogo)}
alt={settingsData.branding_company_name || 'Company Logo'}
className="h-16 w-auto object-contain mx-auto"
/>
@@ -103,10 +111,10 @@ export const ClientAccessPage: React.FC = () => {
<div className="min-h-screen" style={{ backgroundColor: 'var(--color-background, #fafafa)' }}>
<div className="min-h-screen flex flex-col">
{/* Logo */}
{settingsData?.branding_logo_url && (
{brandLogo && (
<div className="p-8 text-center">
<img
src={buildResourceUrl(settingsData.branding_logo_url)}
src={buildResourceUrl(brandLogo)}
alt={settingsData.branding_company_name || 'Company Logo'}
className="h-16 w-auto object-contain mx-auto"
/>
+7 -3
View File
@@ -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
@@ -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
@@ -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<string | null>(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
@@ -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