Merge remote-tracking branch 'gitlab/claude/src-branding' into dev-merge-batch1
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -44,6 +44,17 @@ export async function GET(request: NextRequest) {
|
||||
return configManager.get<T>(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<string>('appName', '') || process.env.NEXT_PUBLIC_APP_NAME || 'Webmail';
|
||||
const jmapServerUrl = configManager.get<string>('jmapServerUrl') || process.env.NEXT_PUBLIC_JMAP_SERVER_URL || '';
|
||||
@@ -68,8 +79,12 @@ export async function GET(request: NextRequest) {
|
||||
faviconUrl: branded<string>('faviconUrl', '/branding/Bulwark_Favicon.svg'),
|
||||
appLogoLightUrl: branded<string>('appLogoLightUrl', ''),
|
||||
appLogoDarkUrl: branded<string>('appLogoDarkUrl', ''),
|
||||
appLogoLightUrlIsCustom: isLogoOverridden('appLogoLightUrl'),
|
||||
appLogoDarkUrlIsCustom: isLogoOverridden('appLogoDarkUrl'),
|
||||
loginLogoLightUrl: branded<string>('loginLogoLightUrl', '/branding/Bulwark_Logo_Color.svg'),
|
||||
loginLogoDarkUrl: branded<string>('loginLogoDarkUrl', '/branding/Bulwark_Logo_White.svg'),
|
||||
loginLogoLightUrlIsCustom: isLogoOverridden('loginLogoLightUrl'),
|
||||
loginLogoDarkUrlIsCustom: isLogoOverridden('loginLogoDarkUrl'),
|
||||
loginCompanyName: branded<string>('loginCompanyName', ''),
|
||||
loginImprintUrl: branded<string>('loginImprintUrl', ''),
|
||||
loginPrivacyPolicyUrl: branded<string>('loginPrivacyPolicyUrl', ''),
|
||||
|
||||
@@ -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 ? (
|
||||
<div className="flex items-center justify-center py-3 px-1">
|
||||
<img
|
||||
|
||||
@@ -20,8 +20,12 @@ interface ConfigData {
|
||||
faviconUrl: string;
|
||||
appLogoLightUrl: string;
|
||||
appLogoDarkUrl: string;
|
||||
appLogoLightUrlIsCustom: boolean;
|
||||
appLogoDarkUrlIsCustom: boolean;
|
||||
loginLogoLightUrl: string;
|
||||
loginLogoDarkUrl: string;
|
||||
loginLogoLightUrlIsCustom: boolean;
|
||||
loginLogoDarkUrlIsCustom: boolean;
|
||||
loginCompanyName: string;
|
||||
loginImprintUrl: string;
|
||||
loginPrivacyPolicyUrl: string;
|
||||
@@ -105,8 +109,12 @@ export function useConfig(): AppConfig {
|
||||
faviconUrl: configCache?.faviconUrl || '/branding/Bulwark_Favicon.svg',
|
||||
appLogoLightUrl: configCache?.appLogoLightUrl || '',
|
||||
appLogoDarkUrl: configCache?.appLogoDarkUrl || '',
|
||||
appLogoLightUrlIsCustom: configCache?.appLogoLightUrlIsCustom || false,
|
||||
appLogoDarkUrlIsCustom: configCache?.appLogoDarkUrlIsCustom || false,
|
||||
loginLogoLightUrl: configCache?.loginLogoLightUrl || '/branding/Bulwark_Logo_Color.svg',
|
||||
loginLogoDarkUrl: configCache?.loginLogoDarkUrl || '/branding/Bulwark_Logo_White.svg',
|
||||
loginLogoLightUrlIsCustom: configCache?.loginLogoLightUrlIsCustom || false,
|
||||
loginLogoDarkUrlIsCustom: configCache?.loginLogoDarkUrlIsCustom || false,
|
||||
loginCompanyName: configCache?.loginCompanyName || '',
|
||||
loginImprintUrl: configCache?.loginImprintUrl || '',
|
||||
loginPrivacyPolicyUrl: configCache?.loginPrivacyPolicyUrl || '',
|
||||
@@ -146,8 +154,12 @@ export function useConfig(): AppConfig {
|
||||
faviconUrl: configCache.faviconUrl,
|
||||
appLogoLightUrl: configCache.appLogoLightUrl,
|
||||
appLogoDarkUrl: configCache.appLogoDarkUrl,
|
||||
appLogoLightUrlIsCustom: configCache.appLogoLightUrlIsCustom,
|
||||
appLogoDarkUrlIsCustom: configCache.appLogoDarkUrlIsCustom,
|
||||
loginLogoLightUrl: configCache.loginLogoLightUrl,
|
||||
loginLogoDarkUrl: configCache.loginLogoDarkUrl,
|
||||
loginLogoLightUrlIsCustom: configCache.loginLogoLightUrlIsCustom,
|
||||
loginLogoDarkUrlIsCustom: configCache.loginLogoDarkUrlIsCustom,
|
||||
loginCompanyName: configCache.loginCompanyName,
|
||||
loginImprintUrl: configCache.loginImprintUrl,
|
||||
loginPrivacyPolicyUrl: configCache.loginPrivacyPolicyUrl,
|
||||
@@ -188,8 +200,12 @@ export function useConfig(): AppConfig {
|
||||
faviconUrl: data.faviconUrl,
|
||||
appLogoLightUrl: data.appLogoLightUrl,
|
||||
appLogoDarkUrl: data.appLogoDarkUrl,
|
||||
appLogoLightUrlIsCustom: data.appLogoLightUrlIsCustom,
|
||||
appLogoDarkUrlIsCustom: data.appLogoDarkUrlIsCustom,
|
||||
loginLogoLightUrl: data.loginLogoLightUrl,
|
||||
loginLogoDarkUrl: data.loginLogoDarkUrl,
|
||||
loginLogoLightUrlIsCustom: data.loginLogoLightUrlIsCustom,
|
||||
loginLogoDarkUrlIsCustom: data.loginLogoDarkUrlIsCustom,
|
||||
loginCompanyName: data.loginCompanyName,
|
||||
loginImprintUrl: data.loginImprintUrl,
|
||||
loginPrivacyPolicyUrl: data.loginPrivacyPolicyUrl,
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ export interface ThemePolicy {
|
||||
export const DEFAULT_THEME_POLICY: ThemePolicy = {
|
||||
disabledBuiltinThemes: [],
|
||||
disabledThemes: [],
|
||||
defaultThemeId: 'builtin-vnclagoon',
|
||||
defaultThemeId: 'builtin-src',
|
||||
};
|
||||
|
||||
export interface SettingsPolicy {
|
||||
|
||||
@@ -1343,8 +1343,8 @@ export const BUILTIN_THEMES: InstalledTheme[] = [
|
||||
description: 'SRC Advisory brand theme — Swiss red on white, MD3 components, light-first',
|
||||
css: srcCSS,
|
||||
skin: srcSkin,
|
||||
logoLightUrl: '/branding/src-logo.svg',
|
||||
logoDarkUrl: '/branding/src-logo.svg',
|
||||
logoLightUrl: '/branding/SRC_Symbol.png',
|
||||
logoDarkUrl: '/branding/SRC_Symbol.png',
|
||||
variants: ['light', 'dark'],
|
||||
typography: { fontSans: '"DM Sans", system-ui, -apple-system, "Segoe UI", sans-serif' },
|
||||
enabled: true,
|
||||
|
||||
+20
-5
@@ -1,10 +1,21 @@
|
||||
import type { InstalledTheme } from './plugin-types';
|
||||
|
||||
/**
|
||||
* Resolve which logo to show. When the active theme carries brand logos
|
||||
* (logoLightUrl/logoDarkUrl), those win — so switching theme switches the whole
|
||||
* brand (VNClagoon wordmark ↔ SRC mark). Otherwise falls back to the globally
|
||||
* configured logo. Returns a raw path; the caller applies withBasePath().
|
||||
* Resolve which logo to show.
|
||||
*
|
||||
* Precedence:
|
||||
* 1. An admin-configured global logo (Branding tab / APP_LOGO_*_URL /
|
||||
* LOGIN_LOGO_*_URL) - set `hasGlobalOverride` when the caller's config
|
||||
* source for that field is 'admin' or 'env', not 'default'. This is
|
||||
* what makes the Branding tab's existing logo fields actually take
|
||||
* effect for customers who want their own mark regardless of theme.
|
||||
* 2. The active theme's own brand logo (logoLightUrl/logoDarkUrl) - so
|
||||
* switching theme still switches the whole brand (VNClagoon wordmark
|
||||
* vs SRC mark) for anyone who HASN'T set an explicit override.
|
||||
* 3. The fallback value itself (theme has no logo and nothing was
|
||||
* configured).
|
||||
*
|
||||
* Returns a raw path; the caller applies withBasePath().
|
||||
*/
|
||||
export function resolveThemeLogo(
|
||||
themes: InstalledTheme[],
|
||||
@@ -12,7 +23,11 @@ export function resolveThemeLogo(
|
||||
isDark: boolean,
|
||||
fallbackLight: string,
|
||||
fallbackDark: string,
|
||||
hasGlobalOverride = false,
|
||||
): string {
|
||||
const fallback = isDark ? (fallbackDark || fallbackLight) : (fallbackLight || fallbackDark);
|
||||
if (hasGlobalOverride && fallback) return fallback;
|
||||
|
||||
const theme = activeThemeId ? themes.find((t) => 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;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 48 KiB |
Reference in New Issue
Block a user