Skip to content

Commit 66ea103

Browse files
committed
feat(web): match cheatcode V1's auth modal — native Clerk dark theme
Per V1 (which used baseTheme: dark): drop V2's custom light header/footer/card wrapper and render Clerk's native <SignIn>/<SignUp> with @clerk/themes' dark baseTheme inside a transparent shell with a single custom close X. Clerk's own chrome (header, Google, divider, fields, footer toggle) now shows, dark and native. Auth route page darkened to match so the modal sits on a dark surface. - add @clerk/themes dependency - clerk-auth-appearance: baseTheme dark + V1's minimal card overrides - auth-modal: minimal V1-exact structure, mode-driven (footer routes /sign-in↔/sign-up)
1 parent 8d1268d commit 66ea103

7 files changed

Lines changed: 58 additions & 144 deletions

File tree

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@cheatcode/types": "workspace:*",
1919
"@cheatcode/ui": "workspace:*",
2020
"@clerk/nextjs": "catalog:",
21+
"@clerk/themes": "^2.4.57",
2122
"@clerk/ui": "catalog:",
2223
"@hookform/resolvers": "catalog:",
2324
"@streamdown/code": "catalog:",

apps/web/src/components/auth/auth-modal.tsx

Lines changed: 26 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22

33
import { ModalShell } from "@cheatcode/ui";
44
import { SignIn, SignUp } from "@clerk/nextjs";
5-
import { useState, useSyncExternalStore } from "react";
6-
import { CheatcodeMark } from "@/components/ui/cheatcode-mark";
5+
import { useSyncExternalStore } from "react";
76
import { X } from "@/components/ui/icons";
8-
import { cn } from "@/lib/ui/cn";
97
import { clerkAuthAppearance } from "./clerk-auth-appearance";
108

119
export type AuthMode = "sign-in" | "sign-up";
@@ -32,96 +30,43 @@ export function AuthModal({
3230
return null;
3331
}
3432

35-
return (
36-
<AuthModalContent
37-
initialMode={mode}
38-
key={mode}
39-
onClose={onClose}
40-
redirectPath={redirectPath}
41-
titleId={titleId}
42-
/>
43-
);
44-
}
45-
46-
function AuthModalContent({
47-
initialMode,
48-
onClose,
49-
redirectPath,
50-
titleId,
51-
}: {
52-
initialMode: AuthMode;
53-
onClose: () => void;
54-
redirectPath: string;
55-
titleId: string;
56-
}) {
57-
const [activeMode, setActiveMode] = useState<AuthMode>(initialMode);
58-
const isSignIn = activeMode === "sign-in";
59-
33+
const isSignIn = mode === "sign-in";
6034
return (
6135
<ModalShell
62-
className="cheatcode-auth-dialog m-auto max-h-[calc(100vh-2rem)] w-[min(calc(100vw-2rem),442px)] max-w-[442px] overflow-visible border-0 bg-transparent p-0 text-[#1b1b1b] shadow-none backdrop:bg-black/25 backdrop:backdrop-blur-[8px]"
36+
className="cheatcode-auth-dialog m-auto max-h-[calc(100vh-2rem)] w-[min(calc(100vw-2rem),25rem)] max-w-[25rem] overflow-visible border-0 bg-transparent p-0 text-white shadow-none backdrop:bg-black/60 backdrop:backdrop-blur-[6px]"
6337
labelledBy={titleId}
6438
onClose={onClose}
6539
open={true}
6640
>
67-
<div className="cheatcode-auth-card relative max-h-[calc(100vh-2rem)] overflow-hidden rounded-[22px] bg-white shadow-[0_28px_80px_rgba(0,0,0,0.16)] ring-1 ring-[#eeeeee]">
41+
<div className="relative mx-auto max-h-[calc(100vh-2rem)] overflow-y-auto">
42+
<span className="sr-only" id={titleId}>
43+
{isSignIn ? "Sign in to Cheatcode" : "Create your account"}
44+
</span>
6845
<button
6946
aria-label="Close authentication modal"
70-
className="absolute top-3 right-3 z-10 flex h-8 w-8 items-center justify-center rounded-full text-[#707070] outline-none transition-colors hover:bg-[#f7f7f7] hover:text-[#1b1b1b] focus-visible:ring-2 focus-visible:ring-[#1b1b1b]/15"
47+
className="absolute top-4 right-6 z-10 flex h-8 w-8 items-center justify-center rounded-full text-white/60 outline-none transition-colors hover:bg-white/10 hover:text-white focus-visible:ring-2 focus-visible:ring-white/25"
7148
onClick={onClose}
7249
type="button"
7350
>
74-
<X aria-hidden="true" className="h-4 w-4" />
51+
<X aria-hidden="true" className="h-5 w-5" />
7552
</button>
76-
<div className="px-10 pt-9 pb-8 max-sm:px-6">
77-
<div className="mb-4 flex justify-center">
78-
<span className="flex size-12 items-center justify-center text-[#dfa94f] drop-shadow-[0_1px_0_rgba(255,255,255,0.65)]">
79-
<CheatcodeMark aria-hidden="true" className="size-10" />
80-
</span>
81-
</div>
82-
<div className="mb-6 text-center">
83-
<h2 className="font-semibold text-[#1b1b1b] text-[20px] leading-6" id={titleId}>
84-
{isSignIn ? "Sign in to Cheatcode" : "Create your account"}
85-
</h2>
86-
<p className="mt-2 text-[#707070] text-[14px] leading-5">
87-
{isSignIn
88-
? "Welcome back! Please sign in to continue"
89-
: "Welcome! Please fill in the details to get started."}
90-
</p>
91-
</div>
92-
{isSignIn ? (
93-
<SignIn
94-
appearance={clerkAuthAppearance}
95-
fallbackRedirectUrl={redirectPath}
96-
forceRedirectUrl={redirectPath}
97-
oauthFlow="popup"
98-
routing="hash"
99-
signUpUrl="#sign-up"
100-
/>
101-
) : (
102-
<SignUp
103-
appearance={clerkAuthAppearance}
104-
fallbackRedirectUrl={redirectPath}
105-
forceRedirectUrl={redirectPath}
106-
oauthFlow="popup"
107-
routing="hash"
108-
signInUrl="#sign-in"
109-
/>
110-
)}
111-
</div>
112-
<div className="flex justify-center border-[#eeeeee] border-t bg-[#fafafa] px-4 py-5 text-[#707070] text-[14px]">
113-
<span>{isSignIn ? "Don’t have an account?" : "Already have an account?"}</span>
114-
<button
115-
className={cn(
116-
"ml-1 font-medium text-[#d99b36] transition-colors hover:text-[#b87418]",
117-
"paper-focus-ring rounded-sm",
118-
)}
119-
onClick={() => setActiveMode(isSignIn ? "sign-up" : "sign-in")}
120-
type="button"
121-
>
122-
{isSignIn ? "Sign up" : "Sign in"}
123-
</button>
124-
</div>
53+
{isSignIn ? (
54+
<SignIn
55+
appearance={clerkAuthAppearance}
56+
fallbackRedirectUrl={redirectPath}
57+
forceRedirectUrl={redirectPath}
58+
oauthFlow="popup"
59+
routing="hash"
60+
/>
61+
) : (
62+
<SignUp
63+
appearance={clerkAuthAppearance}
64+
fallbackRedirectUrl={redirectPath}
65+
forceRedirectUrl={redirectPath}
66+
oauthFlow="popup"
67+
routing="hash"
68+
/>
69+
)}
12570
</div>
12671
</ModalShell>
12772
);

apps/web/src/components/auth/auth-route-page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ export function AuthRoutePage({ mode }: { mode: AuthMode }) {
1919
}, [isLoaded, isSignedIn, redirectPath, router]);
2020

2121
return (
22-
<main className="relative min-h-screen overflow-hidden bg-white text-[#1b1b1b]">
23-
<div className="fixed top-3.5 right-3.5 z-20 hidden h-7 items-center gap-2 rounded-full bg-[#f7f7f7] pr-3 pl-2.5 text-[14px] md:flex">
22+
<main className="relative min-h-screen overflow-hidden bg-[#0d0d0d] text-white">
23+
<div className="fixed top-3.5 right-3.5 z-20 hidden h-7 items-center gap-2 rounded-full bg-[#1a1a1a] pr-3 pl-2.5 text-[#d4d4d4] text-[14px] md:flex">
2424
<Monitor aria-hidden="true" className="h-3.5 w-3.5" />
2525
Computer
2626
</div>
@@ -29,9 +29,9 @@ export function AuthRoutePage({ mode }: { mode: AuthMode }) {
2929
className="mx-auto flex min-h-screen w-full max-w-[704px] flex-col items-center px-6 pt-[154px]"
3030
>
3131
<CheatcodeMark aria-hidden="true" className="mb-7 h-10 w-10 text-[#f8af2c]" />
32-
<div className="mb-2 h-4 w-40 rounded-full bg-[#f7f7f7]" />
33-
<div className="h-8 w-72 rounded-full bg-[#f1f1f1]" />
34-
<div className="mt-16 h-[132px] w-full rounded-[24px] border border-[#f1f1f1] bg-white shadow-[0_18px_70px_rgba(0,0,0,0.06)]" />
32+
<div className="mb-2 h-4 w-40 rounded-full bg-[#1a1a1a]" />
33+
<div className="h-8 w-72 rounded-full bg-[#161616]" />
34+
<div className="mt-16 h-[132px] w-full rounded-[24px] border border-[#1f1f1f] bg-[#111111] shadow-[0_18px_70px_rgba(0,0,0,0.4)]" />
3535
</section>
3636
<AuthModal
3737
id="auth-route-modal"
Lines changed: 9 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,16 @@
1+
import { dark } from "@clerk/themes";
2+
3+
// Exact parity with cheatcode V1's auth modal: Clerk's native dark theme with the card's own
4+
// border/shadow removed (the modal shell provides the surface). Keeping Clerk's default chrome
5+
// — header, social buttons, divider, fields, footer — so it looks and feels 100% native.
16
export const clerkAuthAppearance = {
7+
baseTheme: dark,
28
elements: {
3-
card: "!m-0 !flex !w-full !flex-col !gap-0 !rounded-none !border-0 !bg-transparent !p-0 !shadow-none",
4-
cardBox: "!w-full !bg-transparent !shadow-none",
5-
developmentMode: "!hidden",
6-
dividerLine: "!bg-[#eeeeee]",
7-
dividerRow: "!my-0 !w-full",
8-
dividerText: "!text-[#707070] !text-[13px]",
9-
footer: "!hidden",
10-
footerAction: "!hidden",
11-
footerActionLink: "!hidden",
12-
footerActionText: "!hidden",
13-
form: "!w-full !gap-5 !space-y-0",
14-
formButtonPrimary:
15-
"!flex !h-10 !min-h-10 !w-full !items-center !justify-center !gap-2 !rounded-[12px] !border-0 !bg-[#e3ac55] !px-4 !font-semibold !text-[14px] !text-white !shadow-none !transition-colors hover:!bg-[#d39a3f]",
16-
formField: "!w-full !space-y-2",
17-
formFieldInput:
18-
"!box-border !h-[38px] !w-full !rounded-[11px] !border !border-[#e6e6e6] !bg-white !px-3.5 !text-[#1b1b1b] !text-[14px] !shadow-none placeholder:!text-[#a3a3a3] focus:!border-[#d8d8d8] focus:!ring-2 focus:!ring-[#1b1b1b]/10",
19-
formFieldInputGroup:
20-
"!box-border !h-[38px] !w-full !rounded-[11px] !border !border-[#e6e6e6] !bg-white focus-within:!border-[#d8d8d8] focus-within:!ring-2 focus-within:!ring-[#1b1b1b]/10",
21-
formFieldInputShowPasswordButton: "!text-[#707070] hover:!text-[#1b1b1b]",
22-
formFieldLabel: "!text-[#1b1b1b] !text-[13px] !font-medium",
23-
formFieldLabelRow: "!mb-2",
24-
formFieldRow: "!w-full",
25-
header: "!hidden",
26-
headerSubtitle: "!hidden",
27-
headerTitle: "!hidden",
28-
logoBox: "!hidden",
29-
main: "!w-full !gap-5 !space-y-0",
30-
rootBox: "!w-full",
31-
socialButtons: "!w-full",
32-
socialButtonsBlockButtonText: "!text-[14px] !font-medium !text-[#5f5f5f]",
33-
socialButtonsBlockButton:
34-
"!flex !h-[38px] !w-full !items-center !justify-center !gap-2.5 !rounded-[11px] !border !border-[#e6e6e6] !bg-white !px-3.5 !text-[#5f5f5f] !shadow-none !transition-colors hover:!bg-[#f7f7f7]",
35-
socialButtonsRoot: "!mb-0 !w-full",
36-
},
37-
layout: {
38-
socialButtonsPlacement: "top",
39-
socialButtonsVariant: "blockButton",
9+
card: "shadow-none border-0",
10+
developmentMode: "hidden",
11+
rootBox: "mx-auto",
4012
},
4113
variables: {
42-
borderRadius: "10px",
43-
colorBackground: "#ffffff",
44-
colorInputBackground: "#ffffff",
45-
colorPrimary: "#e0aa57",
46-
colorText: "#1b1b1b",
47-
colorTextSecondary: "#707070",
4814
fontFamily: "var(--font-geist-sans), Arial, sans-serif",
4915
},
5016
};

apps/web/src/components/shell/app-sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ interface SidebarProject {
6969
}
7070

7171
const PRIMARY_NAV = navItems("primary");
72-
const WORKSPACE_SECTION_NAV = navItems("workspace").filter((item) => item.id !== "tools");
72+
const WORKSPACE_SECTION_NAV = navItems("workspace");
7373
const FOOTER_NAV = navItems("footer");
7474
const CREDIT_BAR_KEYS = Array.from({ length: 50 }, (_, index) => `credit-bar-${index}`);
7575
const BUD_ACCOUNT_FONT = 'circular, "circular Fallback", sans-serif';

apps/web/src/components/tools/tools-catalog.tsx

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -306,10 +306,9 @@ function BrandLogo({
306306
return (
307307
<span
308308
className={cn(
309-
"flex shrink-0 items-center justify-center overflow-hidden rounded-[14px]",
309+
"flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-white shadow-[0_0_0_1px_#ececec]",
310310
boxClass,
311311
)}
312-
style={{ backgroundImage: gradientForSlug(slug) }}
313312
>
314313
{failed ? (
315314
<span className="font-bold text-[#1b1b1b]/70 text-[10px]">{initials(displayName)}</span>
@@ -772,15 +771,3 @@ function initials(displayName: string): string {
772771
.toUpperCase();
773772
return joined || "?";
774773
}
775-
776-
// Deterministic soft gradient per toolkit slug — keeps each logo tile visually
777-
// distinct across the whole catalog without per-brand styling. Inline style (not a
778-
// Tailwind class) because the hue is computed at runtime.
779-
function gradientForSlug(slug: string): string {
780-
let hash = 0;
781-
for (let index = 0; index < slug.length; index += 1) {
782-
hash = (hash * 31 + slug.charCodeAt(index)) % 360;
783-
}
784-
const hue = hash;
785-
return `linear-gradient(135deg, hsl(${hue} 62% 93%), hsl(${(hue + 36) % 360} 58% 88%))`;
786-
}

pnpm-lock.yaml

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)