From 03b06e7bde4f21b746a843d9d0d6cfc3d8cc2d85 Mon Sep 17 00:00:00 2001 From: waleed Date: Wed, 8 Apr 2026 13:18:31 -0700 Subject: [PATCH 1/8] fix(whitelabeling): eliminate logo flash by fetching org settings server-side --- .../app/workspace/[workspaceId]/layout.tsx | 20 ++-- .../[workspaceId]/settings/[section]/page.tsx | 1 + .../components/branding-provider.tsx | 98 ++++--------------- 3 files changed, 27 insertions(+), 92 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/layout.tsx b/apps/sim/app/workspace/[workspaceId]/layout.tsx index e1f9d815bb3..83f1630942e 100644 --- a/apps/sim/app/workspace/[workspaceId]/layout.tsx +++ b/apps/sim/app/workspace/[workspaceId]/layout.tsx @@ -1,5 +1,5 @@ -import { cookies } from 'next/headers' import { ToastProvider } from '@/components/emcn' +import { getSession } from '@/lib/auth' import { NavTour } from '@/app/workspace/[workspaceId]/components/product-tour' import { ImpersonationBanner } from '@/app/workspace/[workspaceId]/impersonation-banner' import { GlobalCommandsProvider } from '@/app/workspace/[workspaceId]/providers/global-commands-provider' @@ -8,22 +8,16 @@ import { SettingsLoader } from '@/app/workspace/[workspaceId]/providers/settings import { WorkspacePermissionsProvider } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' import { WorkspaceScopeSync } from '@/app/workspace/[workspaceId]/providers/workspace-scope-sync' import { Sidebar } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar' -import { - BRAND_COOKIE_NAME, - type BrandCache, - BrandingProvider, -} from '@/ee/whitelabeling/components/branding-provider' +import { BrandingProvider } from '@/ee/whitelabeling/components/branding-provider' +import { getOrgWhitelabelSettings } from '@/ee/whitelabeling/org-branding' export default async function WorkspaceLayout({ children }: { children: React.ReactNode }) { - const cookieStore = await cookies() - let initialCache: BrandCache | null = null - try { - const raw = cookieStore.get(BRAND_COOKIE_NAME)?.value - if (raw) initialCache = JSON.parse(decodeURIComponent(raw)) - } catch {} + const session = await getSession() + const orgId = session?.session?.activeOrganizationId + const initialOrgSettings = orgId ? await getOrgWhitelabelSettings(orgId) : null return ( - + diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx index 36c1f97867a..ca48abef01b 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx @@ -16,6 +16,7 @@ const SECTION_TITLES: Record = { subscription: 'Subscription', team: 'Team', sso: 'Single Sign-On', + whitelabeling: 'Whitelabeling', copilot: 'Copilot Keys', mcp: 'MCP Tools', 'custom-tools': 'Custom Tools', diff --git a/apps/sim/ee/whitelabeling/components/branding-provider.tsx b/apps/sim/ee/whitelabeling/components/branding-provider.tsx index d0c4faf1771..08436397089 100644 --- a/apps/sim/ee/whitelabeling/components/branding-provider.tsx +++ b/apps/sim/ee/whitelabeling/components/branding-provider.tsx @@ -1,37 +1,12 @@ 'use client' -import { createContext, useContext, useEffect, useMemo, useState } from 'react' -import type { BrandConfig } from '@/lib/branding/types' +import { createContext, useContext, useMemo } from 'react' +import type { BrandConfig, OrganizationWhitelabelSettings } from '@/lib/branding/types' import { getBrandConfig } from '@/ee/whitelabeling/branding' import { useWhitelabelSettings } from '@/ee/whitelabeling/hooks/whitelabel' import { generateOrgThemeCSS, mergeOrgBrandConfig } from '@/ee/whitelabeling/org-branding-utils' import { useOrganizations } from '@/hooks/queries/organization' -export const BRAND_COOKIE_NAME = 'sim-wl' -const BRAND_COOKIE_MAX_AGE = 30 * 24 * 60 * 60 - -/** - * Brand assets and theme CSS cached in a cookie between page loads. - * Written client-side after org settings resolve; read server-side in the - * workspace layout so the correct branding is baked into the initial HTML. - */ -export interface BrandCache { - logoUrl?: string - wordmarkUrl?: string - /** Pre-generated `:root { ... }` CSS from the last resolved org settings. */ - themeCSS?: string -} - -function writeBrandCookie(cache: BrandCache | null): void { - try { - if (cache && Object.keys(cache).length > 0) { - document.cookie = `${BRAND_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(cache))}; path=/; max-age=${BRAND_COOKIE_MAX_AGE}; SameSite=Lax` - } else { - document.cookie = `${BRAND_COOKIE_NAME}=; path=/; max-age=0; SameSite=Lax` - } - } catch {} -} - interface BrandingContextValue { config: BrandConfig } @@ -43,69 +18,34 @@ const BrandingContext = createContext({ interface BrandingProviderProps { children: React.ReactNode /** - * Brand cache read server-side from the `sim-wl` cookie by the workspace - * layout. When present, the server renders the correct org branding from the - * first byte — no flash of any kind on page load or hard refresh. + * Org whitelabel settings fetched server-side from the DB by the workspace layout. + * Used as the source of truth until the React Query result becomes available, + * ensuring the correct org logo appears in the initial server HTML — no flash. */ - initialCache?: BrandCache | null + initialOrgSettings?: OrganizationWhitelabelSettings | null } /** * Provides merged branding (instance env vars + org DB settings) to the workspace. * Injects a `