Skip to content

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:

  1. 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.
  2. Voice rule violations: the marketing copy on the left panel ("World-class care", "world-class providers") is on the forbidden-phrase list (world-class is severity:warning in voice_rules.yaml).
  3. 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 appearance prop 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 shadcn Card + 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 existing Forgot password? link in the prebuilt UI is replaced with a link to /auth/forgot which 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. The world-class / peace of mind / take the complexity out phrases 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.tsx against voice_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:

  1. VITE_AUTH_HEADLESS=false in Vercel env. The Auth.tsx component falls back to rendering <SignIn appearance={...} /> / <SignUp /> in the same StorefrontLayout shell. No code redeploy.
  2. Revert the PR. The old SignInPage.tsx / SignUpPage.tsx files 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)