From 0b537a815dfe8c571416b9212b102edeb03614ef Mon Sep 17 00:00:00 2001 From: Yash Kumar Date: Sun, 11 Oct 2026 19:10:06 +0530 Subject: [PATCH 1/3] feat(enterprise): dual-mode branding with separate light/dark theme logos --- .../server/update-server-config.test.ts | 4 + .../impersonation/impersonation-bar.tsx | 1 + .../components/layouts/onboarding-layout.tsx | 10 +- .../whitelabeling/whitelabeling-settings.tsx | 112 ++++++++++++++++++ apps/dokploy/components/shared/logo.tsx | 17 ++- apps/dokploy/pages/_document.tsx | 24 +++- apps/dokploy/pages/_error.tsx | 3 +- apps/dokploy/pages/index.tsx | 7 +- apps/dokploy/pages/invitation.tsx | 7 +- apps/dokploy/pages/register.tsx | 7 +- apps/dokploy/pages/reset-password.tsx | 5 + apps/dokploy/pages/send-reset-password.tsx | 7 +- .../api/routers/proprietary/whitelabeling.ts | 4 + .../src/db/schema/web-server-settings.ts | 12 ++ .../src/services/proprietary/whitelabeling.ts | 14 +++ 15 files changed, 223 insertions(+), 11 deletions(-) diff --git a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts index d1a03fedc..f82fdeb6b 100644 --- a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts +++ b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts @@ -62,6 +62,10 @@ const baseSettings: WebServerSettings = { errorPageDescription: null, ogImageUrl: null, footerText: null, + logoLightUrl: null, + loginLogoLightUrl: null, + faviconLightUrl: null, + dualModeBranding: false, }, cleanupCacheApplications: false, cleanupCacheOnCompose: false, diff --git a/apps/dokploy/components/dashboard/impersonation/impersonation-bar.tsx b/apps/dokploy/components/dashboard/impersonation/impersonation-bar.tsx index 5ab262ec8..8a66b5270 100644 --- a/apps/dokploy/components/dashboard/impersonation/impersonation-bar.tsx +++ b/apps/dokploy/components/dashboard/impersonation/impersonation-bar.tsx @@ -186,6 +186,7 @@ export const ImpersonationBar = () => { {!isImpersonating ? (
diff --git a/apps/dokploy/components/layouts/onboarding-layout.tsx b/apps/dokploy/components/layouts/onboarding-layout.tsx index ed46c1930..3776c2db5 100644 --- a/apps/dokploy/components/layouts/onboarding-layout.tsx +++ b/apps/dokploy/components/layouts/onboarding-layout.tsx @@ -19,6 +19,10 @@ export const OnboardingLayout = ({ children, leftPanel }: Props) => { "\u201CThe Open Source alternative to Netlify, Vercel, Heroku.\u201D"; const logoUrl = whitelabeling?.loginLogoUrl || whitelabeling?.logoUrl || undefined; + const logoLightUrl = + whitelabeling?.loginLogoLightUrl || + whitelabeling?.logoLightUrl || + undefined; return (
@@ -28,7 +32,11 @@ export const OnboardingLayout = ({ children, leftPanel }: Props) => { href="/" className="relative z-20 flex items-center text-lg font-medium gap-4 text-primary" > - + {appName}
diff --git a/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-settings.tsx b/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-settings.tsx index 3ba9e80bd..c4ce523a7 100644 --- a/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-settings.tsx +++ b/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-settings.tsx @@ -26,6 +26,7 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; import { WhitelabelingPreview } from "./whitelabeling-preview"; @@ -49,6 +50,10 @@ const formSchema = z.object({ errorPageDescription: z.string(), footerText: z.string(), ogImageUrl: safeUrlField, + logoLightUrl: safeUrlField, + loginLogoLightUrl: safeUrlField, + faviconLightUrl: safeUrlField, + dualModeBranding: z.boolean(), }); type FormSchema = z.infer; @@ -195,6 +200,10 @@ export function WhitelabelingSettings() { errorPageDescription: "", footerText: "", ogImageUrl: "", + logoLightUrl: "", + loginLogoLightUrl: "", + faviconLightUrl: "", + dualModeBranding: false, }, resolver: zodResolver(formSchema), }); @@ -214,6 +223,10 @@ export function WhitelabelingSettings() { errorPageDescription: data.errorPageDescription ?? "", footerText: data.footerText ?? "", ogImageUrl: data.ogImageUrl ?? "", + logoLightUrl: data.logoLightUrl ?? "", + loginLogoLightUrl: data.loginLogoLightUrl ?? "", + faviconLightUrl: data.faviconLightUrl ?? "", + dualModeBranding: data.dualModeBranding ?? false, }); } }, [data, form]); @@ -244,6 +257,10 @@ export function WhitelabelingSettings() { errorPageDescription: values.errorPageDescription || null, footerText: values.footerText || null, ogImageUrl: values.ogImageUrl || null, + logoLightUrl: values.logoLightUrl || null, + loginLogoLightUrl: values.loginLogoLightUrl || null, + faviconLightUrl: values.faviconLightUrl || null, + dualModeBranding: values.dualModeBranding, }, }) .then(async () => { @@ -412,6 +429,101 @@ export function WhitelabelingSettings() { + {/* Light Mode Logos Section */} + + +
+
+ Light Mode Logos + + Use separate logos for light and dark themes. When enabled, + the logos in Branding are used for dark mode. + +
+ ( + + + + + + )} + /> +
+
+
+
+ + ( + + Logo URL + + + + + + )} + /> + + ( + + Login Page Logo URL + + + + + + )} + /> + + ( + + Favicon URL + + + + + Follows the browser/OS theme, not the website theme. + + + + )} + /> + +
+
+
+ {/* Appearance Section */} diff --git a/apps/dokploy/components/shared/logo.tsx b/apps/dokploy/components/shared/logo.tsx index 2c316e22f..500925df3 100644 --- a/apps/dokploy/components/shared/logo.tsx +++ b/apps/dokploy/components/shared/logo.tsx @@ -1,16 +1,25 @@ +import { useTheme } from "next-themes"; import { cn } from "@/lib/utils"; interface Props { className?: string; logoUrl?: string; + logoLightUrl?: string; } -export const Logo = ({ className = "size-14", logoUrl }: Props) => { - if (logoUrl) { +export const Logo = ({ + className = "size-14", + logoUrl, + logoLightUrl, +}: Props) => { + const { resolvedTheme } = useTheme(); + const effectiveUrl = + resolvedTheme === "light" && logoLightUrl ? logoLightUrl : logoUrl; + + if (effectiveUrl) { return ( - // biome-ignore lint/performance/noImgElement: this is for dynamic logo loading Organization Logo diff --git a/apps/dokploy/pages/_document.tsx b/apps/dokploy/pages/_document.tsx index a8f3b5931..5e6b6948b 100644 --- a/apps/dokploy/pages/_document.tsx +++ b/apps/dokploy/pages/_document.tsx @@ -13,6 +13,7 @@ interface WhitelabelingDocumentProps { appDescription: string | null; ogImageUrl: string | null; faviconHref: string | null; + faviconLightHref: string | null; customCss: string | null; baseUrl: string; } @@ -22,6 +23,7 @@ export default function Document({ appDescription, ogImageUrl, faviconHref, + faviconLightHref, customCss, baseUrl, }: WhitelabelingDocumentProps) { @@ -44,7 +46,22 @@ export default function Document({ - + {faviconLightHref ? ( + <> + + + + ) : ( + + )} {customCss && (