diff --git a/ui/src/app/handler/[...stack]/AuthEnterpriseCTA.tsx b/ui/src/app/handler/[...stack]/AuthEnterpriseCTA.tsx new file mode 100644 index 00000000..3efeb0e9 --- /dev/null +++ b/ui/src/app/handler/[...stack]/AuthEnterpriseCTA.tsx @@ -0,0 +1,25 @@ +"use client"; + +// Bland-style enterprise call-to-action rendered inside the auth brand panel. +// Links out to the main marketing site's enterprise intake form rather than the +// in-app modal, since the visitor is not yet authenticated here. + +import { Button } from "@/components/ui/button"; + +export function AuthEnterpriseCTA() { + return ( + + + + ); +} diff --git a/ui/src/app/handler/[...stack]/AuthShell.tsx b/ui/src/app/handler/[...stack]/AuthShell.tsx new file mode 100644 index 00000000..b438ead1 --- /dev/null +++ b/ui/src/app/handler/[...stack]/AuthShell.tsx @@ -0,0 +1,82 @@ +// Dark two-column auth shell. LEFT (lg+ only): a brand/value panel with a +// CSS-only audio-waveform motif, proof points, and a Bland-style enterprise CTA +// block at the bottom (passed in as `enterpriseSlot`). RIGHT: a centered +// zinc-900 card that wraps the Stack Auth form (`children`). Mobile collapses to +// the single card column. Palette is the app's blacks/greys with one warm CTA +// accent on the waveform + focus. + +import type { ReactNode } from "react"; + +const PROOF_POINTS = [ + "Open source", + "7+ telephony providers", + "Open architecture", +]; + +export function AuthShell({ + children, + enterpriseSlot, +}: { + children: ReactNode; + enterpriseSlot?: ReactNode; +}) { + return ( +
+ {/* Brand / value panel — hidden on mobile */} + + + {/* Form column */} +
+
+ {/* Mobile-only wordmark (brand panel is hidden) */} +
+
+ +
+ Dograh +
+ {children} +
+
+
+ ); +} diff --git a/ui/src/app/handler/[...stack]/BackButton.tsx b/ui/src/app/handler/[...stack]/BackButton.tsx index f2d730ae..d36a4a03 100644 --- a/ui/src/app/handler/[...stack]/BackButton.tsx +++ b/ui/src/app/handler/[...stack]/BackButton.tsx @@ -9,16 +9,14 @@ export function BackButton() { const router = useRouter(); return ( -
- -
+ ); } diff --git a/ui/src/app/handler/[...stack]/page.tsx b/ui/src/app/handler/[...stack]/page.tsx index 2ae94489..17bd093c 100644 --- a/ui/src/app/handler/[...stack]/page.tsx +++ b/ui/src/app/handler/[...stack]/page.tsx @@ -1,18 +1,25 @@ -import { StackHandler } from "@stackframe/stack"; +import { StackHandler, StackTheme } from "@stackframe/stack"; import { getAuthProvider } from "@/lib/auth/config"; +import { AuthEnterpriseCTA } from "./AuthEnterpriseCTA"; +import { AuthShell } from "./AuthShell"; import { BackButton } from "./BackButton"; +import { stackAuthDarkTheme } from "./stack-theme"; export default async function Handler(props: unknown) { const authProvider = await getAuthProvider(); if (authProvider === "local") { return ( -
-

Local Auth Mode

-

Stack Auth handler is disabled when using local authentication.

-
+ }> +
+

Local Auth Mode

+

+ Stack Auth handler is disabled when using local authentication. +

+
+
); } @@ -21,15 +28,11 @@ export default async function Handler(props: unknown) { const app = await getStackServerApp(); return ( -
+ }> -
- -
-
+ + + + ); } diff --git a/ui/src/app/handler/[...stack]/stack-theme.ts b/ui/src/app/handler/[...stack]/stack-theme.ts new file mode 100644 index 00000000..58fe710e --- /dev/null +++ b/ui/src/app/handler/[...stack]/stack-theme.ts @@ -0,0 +1,35 @@ +// Dark token overrides for the embedded Stack Auth form so it blends into the +// auth card surface (zinc-900 background, zinc-100 foreground, the warm CTA +// accent on the primary button, zinc-800 borders/inputs). Kept in sync with the +// .dark tokens in globals.css. Values are CSS color strings; Stack applies them +// to its own CSS variables. + +import type { StackTheme } from "@stackframe/stack"; +import type { ComponentProps } from "react"; + +type ThemeConfig = NonNullable["theme"]>; + +export const stackAuthDarkTheme: ThemeConfig = { + dark: { + background: "oklch(0.205 0 0)", + foreground: "oklch(0.985 0 0)", + card: "oklch(0.205 0 0)", + cardForeground: "oklch(0.985 0 0)", + popover: "oklch(0.205 0 0)", + popoverForeground: "oklch(0.985 0 0)", + primary: "oklch(0.78 0.16 67)", + primaryForeground: "oklch(0.16 0.02 60)", + secondary: "oklch(0.269 0 0)", + secondaryForeground: "oklch(0.985 0 0)", + muted: "oklch(0.269 0 0)", + mutedForeground: "oklch(0.708 0 0)", + accent: "oklch(0.269 0 0)", + accentForeground: "oklch(0.985 0 0)", + destructive: "oklch(0.704 0.191 22.216)", + destructiveForeground: "oklch(0.985 0 0)", + border: "oklch(0.269 0 0)", + input: "oklch(0.269 0 0)", + ring: "oklch(0.78 0.16 67)", + }, + radius: "0.625rem", +}; diff --git a/ui/src/app/layout.tsx b/ui/src/app/layout.tsx index b7961425..3083bce1 100644 --- a/ui/src/app/layout.tsx +++ b/ui/src/app/layout.tsx @@ -9,6 +9,7 @@ import AppLayout from "@/components/layout/AppLayout"; import PostHogIdentify from "@/components/PostHogIdentify"; import { SentryErrorBoundary } from "@/components/SentryErrorBoundary"; import SpinLoader from "@/components/SpinLoader"; +import { ThemeProvider } from "@/components/ThemeProvider"; import { Toaster } from "@/components/ui/sonner"; import { AppConfigProvider } from "@/context/AppConfigContext"; import { OnboardingProvider } from "@/context/OnboardingContext"; @@ -39,21 +40,24 @@ export default function RootLayout({ }) { return ( - + - {/* Inline script to prevent flash of light theme - runs before React hydrates */} + {/* Inline script to prevent flash of light theme - runs before React hydrates. + Dark is the locked default: only an explicit stored 'light' opts out. */}