From 1c7e2e57b43b0ed7ab7ac55bdfd9a71ff4f157d0 Mon Sep 17 00:00:00 2001 From: Shellishack <40737228+Shellishack@users.noreply.github.com> Date: Mon, 6 Apr 2026 04:57:13 +0800 Subject: [PATCH] feat(branding): add logo URL support for tenant branding across the application --- packages/app/api/routes/auth.ts | 2 +- packages/app/api/routes/tenant.ts | 1 + packages/app/shared/index.ts | 2 ++ packages/app/web/app/layouts/DashboardLayout.tsx | 12 ++++++++++-- packages/app/web/app/pages/Login.tsx | 11 +++++++++-- packages/app/web/app/pages/Register.tsx | 6 ++++-- packages/app/web/app/pages/Settings.tsx | 13 +++++++++++++ packages/app/web/index.html | 5 ++++- 8 files changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/app/api/routes/auth.ts b/packages/app/api/routes/auth.ts index 2094834..3d581b3 100644 --- a/packages/app/api/routes/auth.ts +++ b/packages/app/api/routes/auth.ts @@ -146,7 +146,7 @@ authRouter.get('/status', async (_req, res) => { const tenant = await db.tenant.findFirst({ select: { name: true, settings: true } }); const settings = tenant?.settings as TenantSettings | null; const allowRegistration = settings?.allowRegistration !== false; - res.json({ ok: true, data: { hasProject: !!tenant, projectName: tenant?.name ?? null, allowRegistration } }); + res.json({ ok: true, data: { hasProject: !!tenant, projectName: tenant?.name ?? null, allowRegistration, logoUrl: settings?.logoUrl ?? null } }); }); // ── POST /auth/login ───────────────────────────────────────────────────────── diff --git a/packages/app/api/routes/tenant.ts b/packages/app/api/routes/tenant.ts index cce868b..01baca7 100644 --- a/packages/app/api/routes/tenant.ts +++ b/packages/app/api/routes/tenant.ts @@ -10,6 +10,7 @@ const updateSettingsSchema = z.object({ settings: z .object({ siteTitle: z.string().max(100).optional(), + logoUrl: z.string().max(500).nullable().optional(), personaName: z.string().min(1).max(80).optional(), personaPrompt: z.string().max(4000).optional(), endUserAccess: z.enum(['anonymous', 'whitelist', 'blacklist']).optional(), diff --git a/packages/app/shared/index.ts b/packages/app/shared/index.ts index 0d13efa..8932f42 100644 --- a/packages/app/shared/index.ts +++ b/packages/app/shared/index.ts @@ -51,6 +51,8 @@ export interface OnboardingBranding { export interface TenantSettings { /** Custom browser tab title (falls back to tenant name if not set) */ siteTitle?: string; + /** Project logo URL — used in the sidebar, login/register pages, and as the browser favicon */ + logoUrl?: string; /** Display name for the AI persona shown to end-users */ personaName: string; /** System prompt that defines the bot's behaviour */ diff --git a/packages/app/web/app/layouts/DashboardLayout.tsx b/packages/app/web/app/layouts/DashboardLayout.tsx index adc6004..d872e44 100644 --- a/packages/app/web/app/layouts/DashboardLayout.tsx +++ b/packages/app/web/app/layouts/DashboardLayout.tsx @@ -3,6 +3,7 @@ import { Link, Outlet, useNavigate, useLocation } from 'react-router-dom'; import { LayoutDashboard, Users, UserCheck, Radio, Settings, LogOut, MessageSquare, BotMessageSquare, Globe } from 'lucide-react'; import { isAuthenticated, clearAuth, getUser, getTenant } from '@/lib/auth'; import { cn } from '@/lib/utils'; +import type { TenantSettings } from '@clawscale/shared'; const navItems: { href: string; icon: typeof LayoutDashboard; label: string; exact?: boolean }[] = [ { href: '/dashboard', icon: LayoutDashboard, label: 'Dashboard', exact: true }, @@ -26,7 +27,12 @@ export default function DashboardLayout() { } else { setReady(true); const t = getTenant(); - if (t) document.title = t.settings?.siteTitle || `${t.name} — ClawScale`; + if (t) { + const s = t.settings as TenantSettings | undefined; + document.title = s?.siteTitle || `${t.name} — ClawScale`; + const favicon = document.querySelector('link[rel="icon"]'); + if (favicon) favicon.href = s?.logoUrl || '/logo.png'; + } } }, [navigate]); @@ -34,6 +40,8 @@ export default function DashboardLayout() { const user = getUser(); const tenant = getTenant(); + const tenantSettings = tenant?.settings as TenantSettings | undefined; + const projectLogo = tenantSettings?.logoUrl || '/logo.png'; function handleLogout() { clearAuth(); @@ -44,7 +52,7 @@ export default function DashboardLayout() {