Auth Pages Redesign — Steer Document¶
Date: 2026-04-09
Author: Srikanth Donthi (CPO/CTO) + Claude Code session 35
Status: Implemented — frontend PR #21 merged
Companion spec: auth-pages-redesign-feature.md
Repo: curaway-health-navigator (frontend)
1. Problem Statement¶
Today's /sign-in and /sign-up pages use Clerk's hosted <SignIn> /
<SignUp> React components inside a custom split-screen layout
(SignInPage.tsx, SignUpPage.tsx). Three problems:
- Off-brand: the form half is Clerk's default UI — not Montserrat, not the storefront's teal/coral system, no Curaway navigation or footer. The patient lands in what looks like a different app.
- Voice rule violations: the marketing copy on the left panel
(
"World-class care","world-class providers") is on the forbidden-phrase list (world-classis severity:warning invoice_rules.yaml). - No unified footer/header: every other page on the storefront —
Treatments, Destinations, Hospitals, How It Works — wraps content
in
StorefrontLayout(Navigation + Footer). Auth doesn't, so the navigation links disappear at the worst moment for trust.
The target screenshot from SD shows what the fix looks like: same
storefront chrome (header with logo + nav + Free Consultation CTA, teal
footer with Quick Links and Contact Us), centered card with Welcome to
Curaway heading, Google button, OR-divider, Sign In / Sign Up tabs,
email + password fields, primary teal Sign In button, and a
Terms of Service / Privacy Policy line.
2. Decision: One Unified /auth Page, Headless Clerk Hooks¶
Decision: Replace SignInPage.tsx and SignUpPage.tsx with a
single Auth.tsx page mounted at /auth. Both /sign-in and
/sign-up redirect to /auth with a tab query param
(?tab=signin / ?tab=signup). The page wraps in the existing
StorefrontLayout so it inherits the same Navigation + Footer as
the rest of the storefront.
The auth itself uses Clerk's headless hooks (useSignIn,
useSignUp, useClerk) instead of the prebuilt <SignIn> /
<SignUp> components. This is the only way to build a fully
Curaway-branded form while keeping Clerk as the IdP, sessions,
rate limiting, and security.
Why one page with tabs over two pages¶
- Patient mental model: the screenshot deliberately shows tabs. Patients arriving via "Free Consultation" don't know whether they have an account yet. Tabs let them switch without a navigation step.
- One layout to maintain. Two pages would duplicate the layout, the Google button, the OR divider, the footer disclaimer, and the voice rules.
- Smaller bundle. One Clerk hook setup, one form ref, one validation pattern.
Why headless hooks over <SignIn> / <SignUp>¶
- The prebuilt components ship Clerk's CSS and DOM, which fights our
Tailwind/shadcn system. Theming via the
appearanceprop only goes so far — we already learned this with the avatar identicon in #19/#20. - Headless hooks (
signIn.create({...}),signIn.authenticateWithRedirect({...})) give us 100% control of the markup. The form is just shadcnCard+Tabs+Input+Button— same primitives as everything else. - One-line model swap if we ever change IdP (Auth0, Supabase, Stytch). The form stays identical; only the hook implementation changes.
3. UX Decisions¶
3.1 Tab default¶
Default to Sign In. /auth?tab=signup switches to Sign Up.
/sign-up redirects to /auth?tab=signup.
3.2 Google button placement¶
Google above the email form, with an OR CONTINUE WITH EMAIL
divider — matches the screenshot and matches industry pattern.
Patients who use Google never see the email fields visually
prioritized.
3.3 Verification step¶
Clerk requires email verification on Sign Up (code sent via email).
The form reveals a third state — "Check your email" — with a 6-digit
code input and a Resend code link. Same card, different content.
No new route.
If the Clerk dashboard is set to magic link instead of verification
code, the third state shows "We sent you a sign-in link" with a
"Resend" button. The form auto-detects the strategy from
signUp.unverifiedFields.
3.4 Error states¶
Field-level errors appear inline below each input
(red border + red text), driven by err.errors[0].longMessage from
Clerk. No toast spam. Submit button disables while the request is
in flight and shows a spinner.
3.5 Voice rules¶
The new copy is plain and on-brand: - Heading: "Welcome to Curaway" - Subhead: "Sign in or create an account to get your free consultation" - OR divider: "OR CONTINUE WITH EMAIL" - Submit (Sign In): "Sign In →" - Submit (Sign Up): "Create Account →" - Footer line: "By continuing, you agree to our Terms of Service and Privacy Policy"
No world-class, no peace of mind, no we'll find you the perfect,
nothing on the forbidden list. The auth-page CI scan
(tests/test_voice_compliance.py) will catch regressions.
3.6 Layout¶
Wrapped in StorefrontLayout so the page inherits:
- Navigation (logo + Home / Treatments / Destinations / About / Blog / Contact + Free Consultation CTA + search icon)
- Footer (4 columns: Curaway, Quick Links, Popular Services, Contact Us)
- Light teal background gradient
- 8px grid spacing
- Montserrat font
The card is centered horizontally, max-width 420px, vertically
centered between the header and the footer (min-height calc).
3.7 Mobile¶
Single column. Card grows to fill min(100% - 32px, 420px). Tab
labels stay horizontal. Footer columns stack into one column under
640px (existing storefront responsive behavior).
4. Out of Scope¶
- Forgot password. Clerk provides this as a separate flow
(
useSignIn().create({ strategy: 'reset_password_email_code' })). Add as a follow-up PR — for the first ship, the existingForgot password?link in the prebuilt UI is replaced with a link to/auth/forgotwhich falls back to Clerk's hosted reset for now. - Social providers beyond Google. Apple/Microsoft/Facebook — decided post-seed based on patient demand.
- Magic-link-only flow. Some auth pages skip passwords entirely in favor of an emailed sign-in link. Not for v1 — keep the password field per the screenshot.
- Multi-factor auth (TOTP/SMS). Clerk handles this when enabled in dashboard, but the headless form needs an MFA challenge state. Defer until we turn MFA on (post-Series A).
- Tenant-aware sign-up. All sign-ups go to the default Curaway tenant. Multi-tenant org switching is post-seed.
- Backend changes. Zero. This is a frontend-only PR. Clerk JWTs, CORS, and the existing patient row creation logic continue unchanged.
5. Risks & Mitigations¶
| Risk | Mitigation |
|---|---|
| Clerk dashboard is set to a strategy the form doesn't know about (e.g., passkeys) | The form reads signIn.supportedFirstFactors on mount and only renders what's configured. Anything we haven't built falls through to a <SignIn> fallback render in a flag-gated escape hatch (VITE_AUTH_HEADLESS=false). |
| Verification code email goes to spam during demo | Clerk's "verification code" defaults work in production. For the demo, switch the test account to magic link (one dashboard toggle). |
| Patient Clerk session collides with existing storefront session state | Session is owned by Clerk's useUser() hook, same as today. No change in storage. |
Form fights the existing creatingCaseRef redirect logic in ConversationApp |
After successful auth, redirect to redirectUrlComplete which defaults to /app. Same default as today. |
| Voice rule scanner doesn't run on the frontend repo | The scanner is backend-only. Mitigation: add a tiny vitest test (Auth.test.tsx) that asserts the rendered card text contains none of the forbidden phrases. List lives in a small TS module so we can keep it in sync with voice_rules.yaml manually. Not enforced as tightly as the backend scanner — acceptable risk because the auth page text is small and reviewed. |
6. Connection to Other Specs¶
- Clerk dual-email investigation (Session 33 finding): unrelated. Headless hooks don't change which strategies the dashboard sends. The fix lives in the Clerk dashboard.
- Medical advice remediation (PR #84): the auth page contains
no clinical content. Not in scope of
test_no_medical_advice.py. - Curaway brand voice (
config/voice_rules.yaml): the new strings are reviewed against this file before merge. Theworld-class/peace of mind/take the complexity outphrases on the current Sign In page get deleted, not replaced. CurawayUserMenu(frontend PR #19/#20): same lesson — when Clerk's prebuilt UI fights our brand, build it ourselves with headless primitives. This is the same pattern, applied to the auth surface.
7. Success Criteria¶
Measured at PR review and the first week post-merge:
- Visual parity with the screenshot at
/auth,/auth?tab=signup, and the email-verification state. - Zero new voice rule violations — manual audit of every string in
Auth.tsxagainstvoice_rules.yaml. - Same auth success rate as today (Clerk dashboard metric). Headless hooks call the same APIs as the prebuilt UI; the rate should be identical or better.
- Lighthouse score ≥90 on Performance, Accessibility, Best
Practices for
/auth(the prebuilt Clerk UI today scores in the 70s on Performance because of its bundled CSS). - Mobile responsive at 375px / 414px / 768px / 1280px / 1920px.
Validated via Playwright
e2e/auth.spec.ts.
8. Rollback¶
Two layers, each independently safe:
VITE_AUTH_HEADLESS=falsein Vercel env. TheAuth.tsxcomponent falls back to rendering<SignIn appearance={...} />/<SignUp />in the sameStorefrontLayoutshell. No code redeploy.- Revert the PR. The old
SignInPage.tsx/SignUpPage.tsxfiles are deleted in this PR but recoverable from git.
9. References¶
- Companion feature spec:
auth-pages-redesign-feature.md - Clerk headless docs: https://clerk.com/docs/custom-flows/overview
- Existing files (will be deleted/replaced):
src/pages/SignInPage.tsx(split-screen with<SignIn>)src/pages/SignUpPage.tsx(split-screen with<SignUp>)- Layout to wrap in:
src/layouts/StorefrontLayout.tsx - Reference screenshot: SD-provided 2026-04-09 (Welcome to Curaway centered card with Google + tabs + email/password)