From 57880c8115e2dff940af33ebdcfbfe0b087eff8b Mon Sep 17 00:00:00 2001 From: parva3105 Date: Fri, 20 Mar 2026 16:47:04 -0400 Subject: [PATCH 1/2] fix(pre-M3): wrap auth() in try-catch on root page to prevent 500 in Clerk test mode Clerk test-mode (pk_test_* keys) throws when auth() is called in a Server Component without the dev-browser cookie. Separated auth() into a guarded try-catch so role is undefined on failure, letting the landing page render normally. redirect() calls remain outside the try-catch so NEXT_REDIRECT still propagates correctly. Co-Authored-By: Claude Sonnet 4.6 --- app/page.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index ee0e2a8..a875314 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -6,8 +6,16 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { cn } from "@/lib/utils"; export default async function Home() { - const { sessionClaims } = await auth(); - const role = (sessionClaims?.metadata as { role?: string })?.role; + // auth() throws in Clerk test mode when the dev-browser cookie is absent. + // Separate the auth call from the redirects so NEXT_REDIRECT errors still + // propagate correctly (redirect() must NOT be inside a try-catch). + let role: string | undefined; + try { + const { sessionClaims } = await auth(); + role = (sessionClaims?.metadata as { role?: string })?.role; + } catch { + // Fall through — unauthenticated landing page will be shown. + } if (role === "agency") redirect("/dashboard"); if (role === "creator") redirect("/creator/deals"); From 0a5e6c0e4f4975d00f5ea06245ffe4415fcd5fb0 Mon Sep 17 00:00:00 2001 From: parva3105 Date: Fri, 20 Mar 2026 16:54:34 -0400 Subject: [PATCH 2/2] fix(pre-M3): extract buttonVariants to lib/button-variants.ts for server component use buttonVariants was defined in components/ui/button.tsx which is 'use client' (needed for ButtonPrimitive from @base-ui/react/button). Turbopack enforces the RSC rule: calling a 'use client' function from a Server Component throws at runtime. Fix: move the CVA config to lib/button-variants.ts (no 'use client') and re-import it in button.tsx, app/page.tsx, and signup-role-picker.tsx. Co-Authored-By: Claude Sonnet 4.6 --- components/auth/signup-role-picker.tsx | 2 +- components/ui/button.tsx | 40 ++---------------------- lib/button-variants.ts | 43 ++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 39 deletions(-) create mode 100644 lib/button-variants.ts diff --git a/components/auth/signup-role-picker.tsx b/components/auth/signup-role-picker.tsx index cbe677d..8b79fd3 100644 --- a/components/auth/signup-role-picker.tsx +++ b/components/auth/signup-role-picker.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { buttonVariants } from "@/components/ui/button"; +import { buttonVariants } from "@/lib/button-variants"; import { cn } from "@/lib/utils"; /** diff --git a/components/ui/button.tsx b/components/ui/button.tsx index ded01b2..1ea976d 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -1,46 +1,10 @@ "use client" import { Button as ButtonPrimitive } from "@base-ui/react/button" -import { cva, type VariantProps } from "class-variance-authority" +import { type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" - -const buttonVariants = cva( - "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", - { - variants: { - variant: { - default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", - outline: - "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", - ghost: - "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", - destructive: - "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", - link: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: - "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", - xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", - sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", - lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3", - icon: "size-8", - "icon-xs": - "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3", - "icon-sm": - "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg", - "icon-lg": "size-9", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - } -) +import { buttonVariants } from "@/lib/button-variants" function Button({ className, diff --git a/lib/button-variants.ts b/lib/button-variants.ts new file mode 100644 index 0000000..e3e5641 --- /dev/null +++ b/lib/button-variants.ts @@ -0,0 +1,43 @@ +import { cva, type VariantProps } from "class-variance-authority" + +// Extracted from components/ui/button.tsx so Server Components can call +// buttonVariants() without crossing the "use client" boundary. +const buttonVariants = cva( + "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", + outline: + "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", + ghost: + "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", + destructive: + "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: + "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", + lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3", + icon: "size-8", + "icon-xs": + "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3", + "icon-sm": + "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg", + "icon-lg": "size-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export { buttonVariants } +export type { VariantProps }