diff --git a/app/(main)/[locale]/login/page.tsx b/app/(main)/[locale]/login/page.tsx index be9f2d8c..4d4a7c7b 100644 --- a/app/(main)/[locale]/login/page.tsx +++ b/app/(main)/[locale]/login/page.tsx @@ -134,12 +134,20 @@ export default function LoginPage() { const isMobileHandoff = Boolean(mobileRedirectUri); const { login, loginDemo, isLoading, error, clearError, isAuthenticated } = useAuthStore(); const { theme, setTheme, initializeTheme } = useThemeStore(useShallow((s) => ({ theme: s.theme, setTheme: s.setTheme, initializeTheme: s.initializeTheme }))); - const { appName, jmapServerUrl: configuredServerUrl, oauthEnabled, oauthOnly, oauthClientId: globalOauthClientId, oauthIssuerUrl: globalOauthIssuerUrl, oauthScopes, rememberMeEnabled, devMode, demoMode, loginLogoLightUrl, loginLogoDarkUrl, loginCompanyName, loginImprintUrl, loginPrivacyPolicyUrl, loginWebsiteUrl, loginLogoMaxHeight, loginLogoMaxWidth, loginShowHeading, loginShowSubtitle, loginShowTotp, loginShowVersion, isLoading: configLoading, error: configError, autoSsoEnabled, embeddedMode: _embeddedMode, allowCustomJmapEndpoint, jmapServers, jmapServerAutoPickByDomain } = useConfig(); + const { appName, jmapServerUrl: configuredServerUrl, oauthEnabled, oauthOnly, oauthClientId: globalOauthClientId, oauthIssuerUrl: globalOauthIssuerUrl, oauthScopes, rememberMeEnabled, devMode, demoMode, loginLogoLightUrl, loginLogoDarkUrl, loginLogoLightUrlIsCustom, loginLogoDarkUrlIsCustom, loginCompanyName, loginImprintUrl, loginPrivacyPolicyUrl, loginWebsiteUrl, loginLogoMaxHeight, loginLogoMaxWidth, loginShowHeading, loginShowSubtitle, loginShowTotp, loginShowVersion, isLoading: configLoading, error: configError, autoSsoEnabled, embeddedMode: _embeddedMode, allowCustomJmapEndpoint, jmapServers, jmapServerAutoPickByDomain } = useConfig(); const resolvedTheme = useThemeStore((s) => s.resolvedTheme); const { activeThemeId, installedThemes } = useThemeStore(useShallow((s) => ({ activeThemeId: s.activeThemeId, installedThemes: s.installedThemes }))); // Active theme may carry its own brand logo (VNClagoon wordmark, SRC mark); - // fall back to the globally configured login logo. - const effLoginLogo = resolveThemeLogo(installedThemes, activeThemeId, resolvedTheme === 'dark', loginLogoLightUrl, loginLogoDarkUrl); + // an explicitly-configured logo (Branding tab / LOGIN_LOGO_*_URL) wins + // over that, falling back to the theme's logo only when nothing was set. + const effLoginLogo = resolveThemeLogo( + installedThemes, + activeThemeId, + resolvedTheme === 'dark', + loginLogoLightUrl, + loginLogoDarkUrl, + loginLogoLightUrlIsCustom || loginLogoDarkUrlIsCustom, + ); // Login logo sizing: when a max height/width is configured, drop the fixed // 64×64 box so the logo (e.g. a wide wordmark) can render at its true size. diff --git a/app/api/config/route.ts b/app/api/config/route.ts index bf86013f..2c400ae5 100644 --- a/app/api/config/route.ts +++ b/app/api/config/route.ts @@ -44,6 +44,17 @@ export async function GET(request: NextRequest) { return configManager.get(key, fallback); }; + // Whether a logo field was actually set by an operator (Branding tab, + // an env var, or a per-domain override) rather than left at its default - + // consumed by resolveThemeLogo() so an explicit choice here wins over the + // active theme's own built-in logo, instead of being silently shadowed by + // it. See lib/theme-logo.ts. + const configSources = configManager.getAllWithSources(); + const isLogoOverridden = (key: BrandingOverrideKey): boolean => + typeof domainOverrides[key] === 'string' && domainOverrides[key]!.length > 0 + ? true + : configSources[key]?.source !== 'default'; + const appName = branded('appName', '') || process.env.NEXT_PUBLIC_APP_NAME || 'Webmail'; const jmapServerUrl = configManager.get('jmapServerUrl') || process.env.NEXT_PUBLIC_JMAP_SERVER_URL || ''; @@ -68,8 +79,12 @@ export async function GET(request: NextRequest) { faviconUrl: branded('faviconUrl', '/branding/Bulwark_Favicon.svg'), appLogoLightUrl: branded('appLogoLightUrl', ''), appLogoDarkUrl: branded('appLogoDarkUrl', ''), + appLogoLightUrlIsCustom: isLogoOverridden('appLogoLightUrl'), + appLogoDarkUrlIsCustom: isLogoOverridden('appLogoDarkUrl'), loginLogoLightUrl: branded('loginLogoLightUrl', '/branding/Bulwark_Logo_Color.svg'), loginLogoDarkUrl: branded('loginLogoDarkUrl', '/branding/Bulwark_Logo_White.svg'), + loginLogoLightUrlIsCustom: isLogoOverridden('loginLogoLightUrl'), + loginLogoDarkUrlIsCustom: isLogoOverridden('loginLogoDarkUrl'), loginCompanyName: branded('loginCompanyName', ''), loginImprintUrl: branded('loginImprintUrl', ''), loginPrivacyPolicyUrl: branded('loginPrivacyPolicyUrl', ''), diff --git a/components/layout/navigation-rail.tsx b/components/layout/navigation-rail.tsx index a6fb1923..0e81d78f 100644 --- a/components/layout/navigation-rail.tsx +++ b/components/layout/navigation-rail.tsx @@ -189,7 +189,7 @@ export function NavigationRail({ const t = useTranslations("sidebar"); const pathname = usePathname(); const router = useRouter(); - const { appLogoLightUrl, appLogoDarkUrl } = useConfig(); + const { appLogoLightUrl, appLogoDarkUrl, appLogoLightUrlIsCustom, appLogoDarkUrlIsCustom } = useConfig(); const resolvedTheme = useThemeStore((s) => s.resolvedTheme); const activeThemeId = useThemeStore((s) => s.activeThemeId); const installedThemes = useThemeStore((s) => s.installedThemes); @@ -465,7 +465,7 @@ export function NavigationRail({ )} > {(() => { - const logoUrl = withBasePath(resolveThemeLogo(installedThemes, activeThemeId, resolvedTheme === 'dark', appLogoLightUrl, appLogoDarkUrl)); + const logoUrl = withBasePath(resolveThemeLogo(installedThemes, activeThemeId, resolvedTheme === 'dark', appLogoLightUrl, appLogoDarkUrl, appLogoLightUrlIsCustom || appLogoDarkUrlIsCustom)); return logoUrl ? (
t.id === activeThemeId) : undefined; if (theme) { const themed = isDark @@ -20,5 +35,5 @@ export function resolveThemeLogo( : (theme.logoLightUrl ?? theme.logoDarkUrl); if (themed) return themed; } - return isDark ? (fallbackDark || fallbackLight) : (fallbackLight || fallbackDark); + return fallback; } diff --git a/public/branding/SRC_Symbol.png b/public/branding/SRC_Symbol.png new file mode 100644 index 00000000..f4d4d70a Binary files /dev/null and b/public/branding/SRC_Symbol.png differ diff --git a/public/icon-512x512.png b/public/icon-512x512.png index 8d21a585..22b9ebd6 100644 Binary files a/public/icon-512x512.png and b/public/icon-512x512.png differ