# Clerk Phase 0 + 0.5 Fix — Design Doc

**Date:** 2026-02-26
**Branch:** worktree-new-login
**Status:** Approved

---

## Problem

The `clerk-login` and `clerk-signup` pages exist but have two blockers:

1. **Missing catch-all route** — Clerk's `<SignIn>/<SignUp>` require a `[[...clerk]]/page.tsx`
   sub-route to handle OAuth callbacks, email verification links, and multi-step flows.
   Without it, Clerk logs an error and OAuth/email flows fail silently.

2. **Visual mismatch** — The current pages show:
   - Logo floating in a grey box (not the existing login card pattern)
   - Apple + Facebook social buttons (existing login: Google only)
   - Double-card: Clerk's card chrome inside our wrapper card
   - "Secured by Clerk / Development mode" banner
   - Full nav/footer showing (auth pages should be distraction-free)

3. **CSP missing Clerk domains** — `script-src`, `connect-src`, `img-src`, `frame-src`
   do not include `https://*.clerk.accounts.dev`. In dev, CSP is report-only so it
   doesn't block, but production will fail.

4. **Nonce not threaded into ClerkProvider** — Clerk injects inline styles. The nonce
   exists in `app/layout.tsx` but is not passed to `<ClerkProvider>`, causing CSP
   style violations in strict mode.

---

## Goal

Make `clerk-login` and `clerk-signup` visually identical to the existing `/login` page:

- Clean full-page `bg-slate-50` background, no nav/footer
- Mawidi logo centered above a white card
- White card: `bg-white rounded-xl shadow-sm border border-slate-200 p-8`
- Google-only social button + email form
- Privacy + Terms links below
- Fully functional OAuth + email verification flows
- CSP-compliant in production

The existing `/login` page is **not touched**.

---

## Changes (5 total)

### Change 1: Catch-All Route Restructure

**Files:**

- DELETE: `app/[lang]/clerk-login/page.tsx`
- CREATE: `app/[lang]/clerk-login/[[...clerk]]/page.tsx`
- DELETE: `app/[lang]/clerk-signup/page.tsx`
- CREATE: `app/[lang]/clerk-signup/[[...clerk]]/page.tsx`

All component logic moves one folder deeper. URLs remain `/en/clerk-login` and `/en/clerk-signup`.

### Change 2: Visual Redesign — Match Existing Login Page

Clerk `<SignIn>` + `<SignUp>` appearance configuration:

```typescript
appearance={{
  layout: {
    logoPlacement: 'none',        // We render our own logo above the card
    socialButtonsVariant: 'blockButton',
  },
  elements: {
    // Strip Clerk's card chrome — we wrap with our own card
    card: 'shadow-none border-0 p-0 w-full',
    // Hide Apple + Facebook
    socialButtonsBlockButton__apple: 'hidden',
    socialButtonsBlockButton__facebook: 'hidden',
    // Match existing login field styles
    formFieldInput: '!border-slate-300 focus:!ring-2 focus:!ring-[#0F9972] focus:!border-[#0F9972]',
    formButtonPrimary: '!bg-[#0F9972] hover:!bg-[#0D8761] font-medium transition-colors',
    footerActionLink: '!text-[#0F9972] hover:underline',
    socialButtonsBlockButton: '!border-slate-300 hover:!bg-slate-50 transition-colors',
  },
  variables: {
    colorPrimary: '#0F9972',
    colorText: '#1e293b',
    colorTextSecondary: '#64748b',
    colorBackground: '#ffffff',
    colorInputBackground: '#ffffff',
    colorInputText: '#1e293b',
    borderRadius: '0.5rem',
    fontFamily: 'var(--font-en), Inter, system-ui',  // RTL: var(--font-ar), Cairo
  },
}}
```

Page wrapper (identical to existing login):

```tsx
<div className="min-h-screen flex items-center justify-center bg-slate-50 px-4 py-12" dir={dir}>
  <div className="w-full max-w-md">
    {/* Logo */}
    <div className="text-center mb-6">
      <Link href={`/${lang}`}><Image src="/brand/logo-horizontal.jpeg" ... /></Link>
    </div>
    {/* Card */}
    <div className="bg-white rounded-xl shadow-sm border border-slate-200 p-8">
      <SignIn fallbackRedirectUrl={`/${lang}/dashboard`} signUpUrl={`/${lang}/clerk-signup`} appearance={...} />
    </div>
    {/* Footer */}
    <p className="text-center text-sm text-slate-600 mt-6">
      Don't have an account? <Link href={`/${lang}/clerk-signup`}>Sign up</Link>
    </p>
    <div className="flex items-center justify-center gap-4 text-xs text-slate-500 mt-4">
      <Link href={`/${lang}/privacy`}>Privacy</Link>
      <span>•</span>
      <Link href={`/${lang}/terms`}>Terms</Link>
    </div>
  </div>
</div>
```

### Change 3: Hide Nav/Footer on Clerk Auth Pages

**File:** `components/LayoutContent.tsx`

Add to `ROUTES_WITHOUT_NAV_FOOTER`:

```typescript
'/clerk-login',
'/clerk-signup',
```

### Change 4: CSP — Add Clerk Domains

**File:** `middleware.ts` — update `buildCsp()`:

- `script-src`: add `https://*.clerk.accounts.dev`
- `connect-src`: add `https://*.clerk.accounts.dev https://clerk.shared.global`
- `img-src`: add `https://*.clerk.accounts.dev`
- `frame-src`: add `https://*.clerk.accounts.dev`

### Change 5: Nonce Threading into ClerkProvider

**File:** `app/layout.tsx`

```tsx
// Before:
<ClerkProvider>

// After:
<ClerkProvider nonce={nonce}>
```

The `nonce` is already computed via `getCspNonce()` in the same component.

---

## Layout + Metadata Files

**Files to create:**

- `app/[lang]/clerk-login/layout.tsx` — `noindex: true`, title "Login | Mawidi"
- `app/[lang]/clerk-signup/layout.tsx` — `noindex: true`, title "Sign Up | Mawidi"

Copy pattern from existing `app/[lang]/login/layout.tsx`.

---

## Verification

1. Navigate to `localhost:9000/en/clerk-login` — Clerk widget renders, no nav/footer
2. Widget matches existing login visual: white card, logo above, Google-only social, green button
3. Navigate to `localhost:9000/ar/clerk-login` — RTL layout, Arabic font
4. Click "Sign up" → goes to `/en/clerk-signup`, same clean design
5. Console: zero Clerk catch-all errors
6. Console: zero CSP violations for Clerk domains

---

## What Is NOT Changed

- `app/[lang]/login/page.tsx` — untouched
- `app/[lang]/signup/page.tsx` — untouched
- `lib/auth.ts` (NextAuth) — untouched
- `middleware.ts` auth logic — untouched (only CSP directives updated)
- All Convex/data-source infrastructure — untouched
