diff --git a/apps/dokploy/components/layouts/onboarding-layout.tsx b/apps/dokploy/components/layouts/onboarding-layout.tsx
index 297a9d8a7..ed46c1930 100644
--- a/apps/dokploy/components/layouts/onboarding-layout.tsx
+++ b/apps/dokploy/components/layouts/onboarding-layout.tsx
@@ -8,8 +8,10 @@ import { Button } from "../ui/button";
interface Props {
children: React.ReactNode;
+ /** Overrides the default quote in the left panel (desktop only). */
+ leftPanel?: React.ReactNode;
}
-export const OnboardingLayout = ({ children }: Props) => {
+export const OnboardingLayout = ({ children, leftPanel }: Props) => {
const { config: whitelabeling } = useWhitelabelingPublic();
const appName = whitelabeling?.appName || "Dokploy";
const appDescription =
@@ -30,9 +32,11 @@ export const OnboardingLayout = ({ children }: Props) => {
{appName}
diff --git a/apps/dokploy/components/proprietary/auth/signup-showcase.tsx b/apps/dokploy/components/proprietary/auth/signup-showcase.tsx
new file mode 100644
index 000000000..e67edf608
--- /dev/null
+++ b/apps/dokploy/components/proprietary/auth/signup-showcase.tsx
@@ -0,0 +1,204 @@
+"use client";
+
+import {
+ Activity,
+ ArrowRight,
+ Bot,
+ Boxes,
+ Cloud,
+ Database,
+ Layers,
+ LayoutTemplate,
+ Rocket,
+ Shield,
+ Terminal,
+ Unlock,
+ Users,
+} from "lucide-react";
+import { useEffect, useRef, useState } from "react";
+import { cn } from "@/lib/utils";
+
+interface ShowcaseItem {
+ icon: typeof Users;
+ title: string;
+ description: string;
+}
+
+const SLIDES: ShowcaseItem[][] = [
+ [
+ {
+ icon: Users,
+ title: "Team access",
+ description:
+ "Anyone in your team can deploy simply and securely in a safe, governed environment.",
+ },
+ {
+ icon: Layers,
+ title: "Open source",
+ description:
+ "Deploy for free with the open-source alternative to Netlify, Vercel, and Heroku.",
+ },
+ {
+ icon: Bot,
+ title: "AI sandbox",
+ description:
+ "Unleash the power of AI and test AI-generated code in a sandbox before deploying to a live URL.",
+ },
+ {
+ icon: Shield,
+ title: "Enterprise ready",
+ description:
+ "Scale when you're ready with granular RBAC, SSO, audit logs, rollback and multi-tenancy.",
+ },
+ ],
+ [
+ {
+ icon: Rocket,
+ title: "Any stack",
+ description:
+ "Deploy any application using Nixpacks, Heroku Buildpacks, or your own Dockerfile.",
+ },
+ {
+ icon: Boxes,
+ title: "Docker Compose",
+ description:
+ "Deploy complex applications natively with full Docker Compose integration.",
+ },
+ {
+ icon: Cloud,
+ title: "Multi-server",
+ description:
+ "Effortlessly deploy your applications on remote servers, with zero configuration hassle.",
+ },
+ {
+ icon: LayoutTemplate,
+ title: "Ready templates",
+ description:
+ "Get started quickly with pre-configured templates for Supabase, Cal.com, PocketBase, and more.",
+ },
+ ],
+ [
+ {
+ icon: Database,
+ title: "Managed databases",
+ description:
+ "Manage and back up MySQL, PostgreSQL, MongoDB, MariaDB, and Redis directly from Dokploy.",
+ },
+ {
+ icon: Terminal,
+ title: "API & CLI",
+ description: "Full API and CLI access to fit any custom workflow.",
+ },
+ {
+ icon: Activity,
+ title: "Live monitoring",
+ description:
+ "Monitor CPU, memory, and network usage in real time across every deployment.",
+ },
+ {
+ icon: Unlock,
+ title: "No lock-in",
+ description:
+ "Modify, scale, and customize Dokploy however your project needs.",
+ },
+ ],
+];
+
+const AUTO_ADVANCE_MS = 6000;
+
+export const SignupShowcase = () => {
+ const [active, setActive] = useState(0);
+ const [paused, setPaused] = useState(false);
+ const [reduceMotion, setReduceMotion] = useState(false);
+
+ useEffect(() => {
+ const query = window.matchMedia("(prefers-reduced-motion: reduce)");
+ setReduceMotion(query.matches);
+ const listener = (e: MediaQueryListEvent) => setReduceMotion(e.matches);
+ query.addEventListener("change", listener);
+ return () => query.removeEventListener("change", listener);
+ }, []);
+
+ const timerRef = useRef
| null>(null);
+
+ useEffect(() => {
+ if (paused || reduceMotion) {
+ return;
+ }
+ timerRef.current = setInterval(() => {
+ setActive((prev) => (prev + 1) % SLIDES.length);
+ }, AUTO_ADVANCE_MS);
+ return () => {
+ if (timerRef.current) {
+ clearInterval(timerRef.current);
+ }
+ };
+ }, [paused, reduceMotion]);
+
+ const goTo = (index: number) => {
+ setActive(((index % SLIDES.length) + SLIDES.length) % SLIDES.length);
+ };
+
+ return (
+ setPaused(true)}
+ onMouseLeave={() => setPaused(false)}
+ onFocus={() => setPaused(true)}
+ onBlur={() => setPaused(false)}
+ >
+
+ {SLIDES[active]?.map((item) => (
+
+
+
+
+
+
{item.title}
+
+ {item.description}
+
+
+
+ ))}
+
+
+
+ {SLIDES.map((slide, index) => (
+
+
+
+
+ );
+};
diff --git a/apps/dokploy/pages/register.tsx b/apps/dokploy/pages/register.tsx
index d3c5511cb..524db4dba 100644
--- a/apps/dokploy/pages/register.tsx
+++ b/apps/dokploy/pages/register.tsx
@@ -11,6 +11,7 @@ import { z } from "zod";
import { OnboardingLayout } from "@/components/layouts/onboarding-layout";
import { SignInWithGithub } from "@/components/proprietary/auth/sign-in-with-github";
import { SignInWithGoogle } from "@/components/proprietary/auth/sign-in-with-google";
+import { SignupShowcase } from "@/components/proprietary/auth/signup-showcase";
import { AlertBlock } from "@/components/shared/alert-block";
import { Logo } from "@/components/shared/logo";
import { Button } from "@/components/ui/button";
@@ -296,7 +297,12 @@ const Register = ({ isCloud }: Props) => {
export default Register;
Register.getLayout = (page: ReactElement) => {
- return {page};
+ const isCloud = (page.props as Props).isCloud;
+ return (
+ : undefined}>
+ {page}
+
+ );
};
export async function getServerSideProps(context: GetServerSidePropsContext) {
if (IS_CLOUD) {