feat(branding): derive logoUrl in useSettings hook (#12159)

This commit is contained in:
Raunak Bhagat authored and GitHub committed 2026-06-17 19:55:41 +00:00
1 parent ca2ea9d91b
commit f74788ac3e
4 files changed
+28 -34

No files matched your search

+12
View File
@@ -1,6 +1,7 @@
"use client";
import useSWR from "swr";
import { useMemo } from "react";
import useCCPairs from "@/hooks/useCCPairs";
import { errorHandlingFetcher } from "@/lib/fetcher";
import { SWR_KEYS } from "@/lib/swr-keys";
@@ -74,10 +75,21 @@ export function useSettings(): AppSettings {
}
);
// Cache-buster: the logo endpoint URL never changes, so the browser serves
// a cached image even after an admin uploads a new logo. Regenerating this
// timestamp whenever the enterprise settings reference changes forces a
// re-fetch. We use referential equality on the SWR data (compare: a===b),
// so this only fires when SWR actually receives new enterprise data.
// eslint-disable-next-line react-hooks/exhaustive-deps
const logoBuster = useMemo(() => Date.now(), [enterprise]);
return {
...core,
enterprise: enterprise ?? null,
appName: enterprise?.application_name?.trim() || "Onyx",
logoUrl: enterprise?.use_custom_logo
? `/api/enterprise-settings/logo?v=${logoBuster}`
: null,
vectorDbEnabled:
!settingsLoading && !settingsError && core.vector_db_enabled !== false,
isLoading:
+7
View File
@@ -176,6 +176,13 @@ export interface AppSettings extends Settings {
enterprise: EnterpriseSettings | null;
/** Resolved display name: enterprise.application_name || "Onyx". */
appName: string;
/**
* URL of the logo image to render, or `null` to use the default Onyx SVG.
* Includes a cache-buster that updates whenever enterprise settings are
* revalidated, forcing the browser to re-fetch after an admin uploads a
* new logo.
*/
logoUrl: string | null;
/** False when DISABLE_VECTOR_DB is set server-side. */
vectorDbEnabled: boolean;
isLoading: boolean;
+3 -15
View File
@@ -1,11 +1,10 @@
"use client";
import { useEffect, useMemo } from "react";
import { useEffect } from "react";
import { useSettings } from "@/lib/settings/hooks";
export default function DynamicMetadata() {
const settings = useSettings();
const { appName } = settings;
const { appName, logoUrl } = useSettings();
useEffect(() => {
if (document.title !== appName) {
@@ -13,16 +12,5 @@ export default function DynamicMetadata() {
}
}, [appName]);
// Cache-buster so the favicon re-fetches after an admin uploads a new logo.
const cacheBuster = useMemo(
() => Date.now(),
// eslint-disable-next-line react-hooks/exhaustive-deps
[settings.enterprise]
);
const favicon = settings.enterprise?.use_custom_logo
? `/api/enterprise-settings/logo?v=${cacheBuster}`
: "/onyx.ico";
return <link rel="icon" href={favicon} />;
return <link rel="icon" href={logoUrl ?? "/onyx.ico"} />;
}
+6 -19
View File
@@ -8,7 +8,6 @@ import {
import { cn } from "@opal/utils";
import Text from "@/refresh-components/texts/Text";
import Truncated from "@/refresh-components/texts/Truncated";
import { useMemo } from "react";
import { SvgOnyxLogo, SvgOnyxLogoTyped } from "@opal/logos";
export interface LogoProps {
@@ -27,21 +26,9 @@ export default function Logo({
onyxBranded,
}: LogoProps) {
const resolvedSize = size ?? DEFAULT_LOGO_SIZE_PX;
const settings = useSettings();
const enterpriseSettings = settings.enterprise;
const logoDisplayStyle = enterpriseSettings?.logo_display_style;
const applicationName = enterpriseSettings?.application_name;
// Cache-buster: the logo URL never changes (/api/enterprise-settings/logo)
// so the browser serves the in-memory cached image even after an admin
// uploads a new one. Generating a fresh timestamp each time enterprise
// settings are revalidated by SWR appends a unique query param to force
// the browser to re-fetch the image.
const logoBuster = useMemo(
() => Date.now(),
// eslint-disable-next-line react-hooks/exhaustive-deps
[enterpriseSettings]
);
const { enterprise, logoUrl } = useSettings();
const logoDisplayStyle = enterprise?.logo_display_style;
const applicationName = enterprise?.application_name;
if (onyxBranded) {
return folded ? (
@@ -51,7 +38,7 @@ export default function Logo({
);
}
const logo = enterpriseSettings?.use_custom_logo ? (
const logo = logoUrl ? (
<div
className={cn(
"aspect-square rounded-full overflow-hidden relative shrink-0",
@@ -62,7 +49,7 @@ export default function Logo({
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
alt="Logo"
src={`/api/enterprise-settings/logo?v=${logoBuster}`}
src={logoUrl}
className="object-cover object-center w-full h-full"
/>
</div>
@@ -84,7 +71,7 @@ export default function Logo({
<Truncated headingH3>{applicationName}</Truncated>
)}
{!NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED &&
!enterpriseSettings?.hide_onyx_branding && (
!enterprise?.hide_onyx_branding && (
<Text
secondaryBody
text03