# Registration Flow - Architecture Diagram

> **Related:** registration requires a valid ZO number (`dealerNumber`),
> enforced server-side in `validateRegistrationData`. See
> [ZO_NUMBER_COMPLIANCE.md](./ZO_NUMBER_COMPLIANCE.md).

## Registration Flow Overview

```mermaid
flowchart TD
    subgraph Landing["Landing Page"]
        LP["public/index.html"]
        OAUTH["OAuth Sign-in<br/>(Google/Apple)"]
        CHECKOUT["Stripe Checkout"]
    end

    subgraph Welcome["Welcome Page"]
        WELCOME["/registration/welcome"]
        POLL["Poll for dealer creation"]
    end

    subgraph Onboarding["Onboarding Flow"]
        STEP1["Step 1: Subdomain Selection"]
        STEP2["Step 2: Business Profile"]
        STEP3["Step 3: Review & Publish"]
    end

    subgraph Dashboard["Dashboard"]
        DASH["/dashboard"]
    end

    LP --> OAUTH --> CHECKOUT
    CHECKOUT -->|"Stripe webhook creates dealer"| WELCOME
    WELCOME --> POLL
    POLL -->|"Dealer found"| STEP1
    STEP1 -->|"Subdomain available"| STEP2
    STEP2 -->|"Profile saved"| STEP3
    STEP3 -->|"Site published"| DASH

    classDef stripe fill:#635bff,stroke:#635bff,color:#fff
    classDef onboard fill:#d4edda,stroke:#28a745
    classDef complete fill:#28a745,stroke:#28a745,color:#fff
    class CHECKOUT stripe
    class STEP1,STEP2,STEP3 onboard
    class DASH complete
```

## Visual Component Tree

```
Landing Page (index.html)
    ↓
Stripe Checkout
    ↓ (checkout success callback)
/registration/welcome [SERVER PAGE]
├─ AutoReload (CLIENT)
├─ RefreshButton (CLIENT)
└─ Display: Success message + checklist
    ↓ "Continue to setup" button
    ↓
/registration/onboarding [SERVER PAGE]
├─ Validates: Stripe session + Dealer exists
├─ Fetches: Dealer data from DB
└─ Renders: OnboardingForm (CLIENT)
    ↓
OnboardingForm [CLIENT COMPONENT - 493 lines]
├─ State: currentStep, formData, subdomainStatus, etc.
├─ Step 1: SubdomainSelection
│  ├─ Input: subdomain text
│  ├─ API: GET /api/check-subdomain (debounced 500ms)
│  └─ Display: Availability status (green/red)
│
├─ Step 2: BusinessProfile
│  ├─ Inputs: name, phone, address, city, state, zip
│  ├─ API: POST /api/register/complete (on submit)
│  └─ Updates DB: Sets subdomain + contact info
│
└─ Step 3: ReviewPublish
   ├─ Display: Read-only summary
   ├─ API: POST /api/dealers/{id}/publish (on click)
   └─ Updates DB: Sets status to "active"
       ↓
/dashboard [WITH published=true PARAM]
```

## Component Structure Breakdown

### Page Components (Server-Side)

```typescript
// welcome/page.tsx (SERVER)
export default async function RegistrationWelcome(props) {
  // Verify Stripe session
  // Poll for dealer
  // Show UI or loading/error states
}

// onboarding/page.tsx (SERVER)
export default async function RegistrationOnboarding(props) {
  // Verify Stripe session
  // Load dealer data
  // Determine initial step
  // Render <OnboardingForm {...props} />
}
```

### Form Component (Client-Side)

```typescript
// OnboardingForm.tsx (CLIENT)
export default function OnboardingForm(props) {
  const [currentStep, setCurrentStep] = useState()
  const [formData, setFormData] = useState()
  const [subdomainStatus, setSubdomainStatus] = useState()

  // STEP 1: Subdomain Selection
  if (currentStep === 'subdomain') {
    return <SubdomainForm
      onCheckAvailability={() => fetch('/api/check-subdomain')}
      onContinue={() => setCurrentStep('profile')}
    />
  }

  // STEP 2: Business Profile
  if (currentStep === 'profile') {
    return <ProfileForm
      onSubmit={() => fetch('/api/register/complete')}
      onBack={() => setCurrentStep('subdomain')}
      onContinue={() => setCurrentStep('review')}
    />
  }

  // STEP 3: Review & Publish
  return <ReviewPublish
    onBack={() => setCurrentStep('profile')}
    onPublish={() => fetch('/api/dealers/{id}/publish')}
  />
}
```

## Data Flow Diagram

```
INITIALIZATION
├─ User completes Stripe checkout
│  ├─ Stripe webhook: checkout.session.completed
│  ├─ Database: INSERT Dealer (skeleton)
│  ├─ Status: registration_pending
│  └─ Subdomain: null
│
├─ Redirect: /registration/welcome?session_id=cs_xxx
│  ├─ Server: Verify session is paid
│  ├─ Server: Query Dealer by Stripe customer ID
│  ├─ If not found: Show loading + AutoReload (3s intervals)
│  └─ If found: Display welcome page
│
└─ User clicks "Continue to setup"
   └─ Navigate: /registration/onboarding?session_id=cs_xxx

STEP 1: SUBDOMAIN SELECTION
├─ User types subdomain: "bobsoil"
├─ Client: Debounce 500ms
├─ Client: POST to /api/check-subdomain?name=bobsoil
├─ Server: Check format (3-63 chars, lowercase, no leading/trailing hyphen)
├─ Server: Check reserved list (www, admin, api, dashboard, etc.)
├─ Server: Query DB for conflict (.com and .ca domains)
├─ Client: Display status (available ✓ or taken ✗)
└─ User clicks "Continue" → Step 2

STEP 2: BUSINESS PROFILE
├─ Form pre-filled with: Dealer data from server
├─ User edits: phone, address, city, state, zip, name (optional)
├─ User clicks "Continue to Review"
├─ Client: POST /api/register/complete {
│    dealerId, sessionId, subdomain,
│    name, phone, address, city, state, zip
│  }
├─ Server: Verify Stripe session is paid
├─ Server: Verify dealer ownership (customer ID match)
├─ Server: Validate subdomain available (final check)
├─ Server: Transaction: UPDATE Dealer {
│    subdomain, phone, address, city, state, zip, businessName,
│    status: registration_complete (CHANGED),
│    migrationStatus: registration_complete
│  }
├─ Server: Return success
└─ Client: Move to Step 3 → Review & Publish

STEP 3: REVIEW & PUBLISH
├─ Display: Summary (read-only)
│  ├─ Site URL: bobsoil.myamsoil.com
│  ├─ Business info: name, email, phone, address
│  └─ All fields from Step 2
├─ User clicks "Publish Site"
├─ Client: POST /api/dealers/{dealerId}/publish { sessionId }
├─ Server: Verify NextAuth session (401 if not)
├─ Server: Rate limit check: 5 req/min per user
├─ Server: Verify session is paid
├─ Server: Verify dealer ownership (customer ID + user ID)
├─ Server: Verify subdomain is set
├─ Server: UPDATE Dealer {
│    status: active (CHANGED),
│    migrationStatus: completed
│  }
├─ Server: Trigger static site generation
├─ Server: Send "Published" email
├─ Server: Return { success: true, siteUrl }
└─ Client: Redirect /dashboard?published=true

FINAL STATE
└─ Dealer record:
   ├─ status: active ✓
   ├─ subdomain: "bobsoil"
   ├─ domain: "com"
   ├─ businessName: "Bob's Oil Company"
   ├─ phone, address, city, state, zip: populated
   ├─ migrationStatus: completed
   └─ Site live at: https://bobsoil.myamsoil.com
```

## API Route Decision Tree

### GET /api/check-subdomain?name=X&domainPrefix=Y&domain=Z

Query parameters:

- `name` (required) - the subdomain to check
- `domainPrefix` (optional, default `myamsoil`) - valid values: `myamsoil` | `shopamsoil`
- `domain` (optional, default `com`) - valid values: `com` | `ca`

The check validates availability against the **exact tuple** `(subdomain, domainPrefix, domain)`.
Two dealers can share the same subdomain on different parent domains (e.g., `bobsoil` on
`myamsoil-com` and `shopamsoil-ca`). Sibling dealers on other parent domains are **not**
surfaced in the public signup flow.

```
YES: Request has 'name' param?
  │
  ├─ NO → Return 400: "Subdomain parameter required"
  │
  ├─ YES: domainPrefix valid? (myamsoil | shopamsoil)
  │  │
  │  ├─ NO → Return 400: "Invalid domainPrefix"
  │  │
  │  ├─ YES: domain valid? (com | ca)
  │  │  │
  │  │  ├─ NO → Return 400: "Invalid domain"
  │  │  │
  │  │  ├─ YES: Format valid? (3-63 chars, lowercase, no leading/trailing hyphen)
  │  │  │  │
  │  │  │  ├─ NO → Return 400: "Invalid format"
  │  │  │  │
  │  │  │  ├─ YES: Is reserved? (admin, api, www, etc.)
  │  │  │  │  │
  │  │  │  │  ├─ YES → Return 400: "Reserved subdomain"
  │  │  │  │  │
  │  │  │  │  ├─ NO: Check exact tuple (subdomain, domainPrefix, domain) in DB
  │  │  │  │  │  │
  │  │  │  │  │  ├─ FOUND → Return 409: "Already registered"
  │  │  │  │  │  │
  │  │  │  │  │  └─ NOT FOUND → Return 200: { subdomain, available: true }
```

Response shape:

- Available: `{ subdomain, available: true }`
- Unavailable: `{ subdomain, available: false, reason: "..." }`
  (`reason` is only present when `available` is false)

### POST /api/register/complete

```
YES: sessionId provided?
  │
  ├─ NO → Return 400: "Session ID required"
  │
  ├─ YES: Stripe session valid and paid?
  │  │
  │  ├─ NO → Return 400: "Payment not completed"
  │  │
  │  ├─ YES: Dealer found?
  │  │  │
  │  │  ├─ NO → Return 404: "Dealer not found"
  │  │  │
  │  │  ├─ YES: Customer ID matches?
  │  │  │  │
  │  │  │  ├─ NO → Return 403: "Session doesn't belong to dealer"
  │  │  │  │
  │  │  │  ├─ YES: All required fields provided?
  │  │  │  │  │
  │  │  │  │  ├─ NO → Return 400: ["errors": [...]]
  │  │  │  │  │
  │  │  │  │  ├─ YES: Subdomain still available?
  │  │  │  │  │  │
  │  │  │  │  │  ├─ NO → Return 409: "Subdomain not available"
  │  │  │  │  │  │
  │  │  │  │  │  ├─ YES: TRANSACTION {
  │  │  │  │  │  │   UPDATE dealer WITH all fields
  │  │  │  │  │  │   VERIFY no concurrent claim
  │  │  │  │  │  │   RETURN 200: success + dealer
  │  │  │  │  │  │ }
```

### POST /api/dealers/{id}/publish

```
YES: User logged in? (NextAuth)
  │
  ├─ NO → Return 401: "Unauthorized"
  │
  ├─ YES: Rate limit OK? (5/min per user)
  │  │
  │  ├─ NO → Return 429: "Too many attempts"
  │  │
  │  ├─ YES: sessionId provided?
  │  │  │
  │  │  ├─ NO → Return 400: "Session ID required"
  │  │  │
  │  │  ├─ YES: Stripe session valid and paid?
  │  │  │  │
  │  │  │  ├─ NO → Return 400: "Payment not completed"
  │  │  │  │
  │  │  │  ├─ YES: Dealer found?
  │  │  │  │  │
  │  │  │  │  ├─ NO → Return 404: "Dealer not found"
  │  │  │  │  │
  │  │  │  │  ├─ YES: User owns dealer? (userId match)
  │  │  │  │  │  │
  │  │  │  │  │  ├─ NO → Return 403: "Forbidden"
  │  │  │  │  │  │
  │  │  │  │  │  ├─ YES: Customer ID matches session?
  │  │  │  │  │  │  │
  │  │  │  │  │  │  ├─ NO → Return 403: "Unauthorized"
  │  │  │  │  │  │  │
  │  │  │  │  │  │  ├─ YES: Subdomain set?
  │  │  │  │  │  │  │  │
  │  │  │  │  │  │  │  ├─ NO → Return 400: "Subdomain required"
  │  │  │  │  │  │  │  │
  │  │  │  │  │  │  │  ├─ YES: Already active?
  │  │  │  │  │  │  │  │  │
  │  │  │  │  │  │  │  │  ├─ YES → Return 200: "Already published"
  │  │  │  │  │  │  │  │  │
  │  │  │  │  │  │  │  │  └─ NO → UPDATE dealer {
  │  │  │  │  │  │  │  │      status: active,
  │  │  │  │  │  │  │  │      migrationStatus: completed
  │  │  │  │  │  │  │  │      Return 200: success + siteUrl
  │  │  │  │  │  │  │  │    }
```

## State Transitions

```mermaid
flowchart TD
    NULL["No Dealer<br/>(Before checkout)"]
    PENDING["registration_pending<br/>subdomain: null"]
    READY["registration_pending<br/>subdomain: set<br/>contact: populated"]
    ACTIVE["active<br/>(Site is live)"]

    NULL -->|"Stripe webhook:<br/>checkout.session.completed"| PENDING
    PENDING -->|"Step 2 complete:<br/>/api/register/complete"| READY
    READY -->|"Step 3 publish:<br/>/api/dealers/id/publish"| ACTIVE

    classDef empty fill:#f8d7da,stroke:#dc3545
    classDef pending fill:#fff3cd,stroke:#ffc107
    classDef live fill:#d4edda,stroke:#28a745
    class NULL empty
    class PENDING,READY pending
    class ACTIVE live
```

```
┌─────────────────────────────────────────────────────────────┐
│ DEALER RECORD STATE MACHINE                                 │
└─────────────────────────────────────────────────────────────┘

         ┌──────────────────┐
         │      [NULL]      │  (Before checkout)
         │   No dealer      │
         └────────┬─────────┘
                  │
                  │ Stripe webhook: checkout.session.completed
                  ↓
         ┌──────────────────┐
         │ registration_    │  (Webhook creates)
         │   pending        │  subdomain: null
         │   Skeleton       │  contact: null
         └────────┬─────────┘
                  │
                  │ User completes Step 2: /api/register/complete
                  ↓
         ┌──────────────────┐
         │ registration_    │  (Profile filled in)
         │   pending        │  subdomain: "bobsoil"
         │   Ready to Pub   │  contact: populated
         └────────┬─────────┘
                  │
                  │ User completes Step 3: /api/dealers/{id}/publish
                  ↓
         ┌──────────────────┐
         │      active      │  (Site is live)
         │   Published      │  subdomain: "bobsoil"
         │   Gone live      │  contact: populated
         └──────────────────┘

Other state transitions:
  ├─ active → ? (customer cancels = not in current flow)
  └─ Any state: Can be re-visited if not yet published
```

## Route & Component Matrix

| Route                      | File                            | Type   | Render            | Purpose                    |
| -------------------------- | ------------------------------- | ------ | ----------------- | -------------------------- |
| `/registration/welcome`    | `welcome/page.tsx`              | Server | HTML + AutoReload | Post-checkout confirmation |
| `/registration/onboarding` | `onboarding/page.tsx`           | Server | OnboardingForm    | Form container             |
| (client state)             | `onboarding/OnboardingForm.tsx` | Client | Step 1\|2\|3      | Multi-step form logic      |

## Security Checkpoints

| Checkpoint        | Location                    | Check                                                                       |
| ----------------- | --------------------------- | --------------------------------------------------------------------------- |
| Welcome page      | `welcome/page.tsx`          | Stripe session paid                                                         |
| Onboarding page   | `onboarding/page.tsx`       | Stripe session paid + dealer exists                                         |
| Step 1 validation | `/api/check-subdomain`      | Rate limit (10/min)                                                         |
| Step 2 submission | `/api/register/complete`    | Stripe session paid + customer ID match                                     |
| Step 3 submission | `/api/dealers/{id}/publish` | NextAuth session + user ID match + Stripe verification + rate limit (5/min) |

---

**Quick Links:**

- Full details: `REGISTRATION_FLOW_MAP.md`
- Quick reference: `REGISTRATION_SUMMARY.md`
