# Basic Dealer Editor Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Build a single-page editor at `/dashboard/editor` for dealers to update their business information post-registration.

**Architecture:** Server component fetches dealer data, client form handles edits, API endpoint validates and persists changes. Shared dashboard layout provides consistent navigation.

**Tech Stack:** Next.js 14 App Router, Prisma ORM, PostgreSQL, React, TypeScript, Tailwind CSS

---

## Task 1: Database Schema Migration

**Files:**

- Modify: `prisma/schema.prisma:47-92`
- Create: `prisma/migrations/YYYYMMDDHHMMSS_add_dealer_contact_fields/migration.sql`

**Step 1: Add new fields to Dealer model**

Edit `prisma/schema.prisma` and add these fields to the Dealer model (after line 69):

```prisma
model Dealer {
  // ... existing fields ...
  country             String   // "US" or "CA"

  // NEW: Contact information for public display
  contactName         String?  // Contact person name
  contactEmail        String?  // Display email (separate from User.email)
  description         String?  @db.Text // Business description

  // CMS Configuration (for future dealer page customization)
  contactData         Json?    // Business hours, alternate phones, secondary emails
  // ... rest of model ...
}
```

**Step 2: Create migration**

Run:

```bash
npx prisma migrate dev --name add_dealer_contact_fields
```

Expected: Migration file created and database updated

**Step 3: Generate Prisma client**

Run:

```bash
npx prisma generate
```

Expected: TypeScript types updated with new fields

**Step 4: Verify migration**

Run:

```bash
npx prisma studio
```

Open Dealer table and verify new columns exist: `contactName`, `contactEmail`, `description`

**Step 5: Commit**

```bash
git add prisma/schema.prisma prisma/migrations/
git commit -m "feat(db): add dealer contact fields for basic editor"
```

---

## Task 2: API Endpoint - Validation Utilities

**Files:**

- Create: `lib/dealer-validation.ts`
- Create: `lib/__tests__/dealer-validation.test.ts`

**Step 1: Write failing test for validation**

Create `lib/__tests__/dealer-validation.test.ts`:

```typescript
import { validateDealerUpdate } from '../dealer-validation';

describe('validateDealerUpdate', () => {
  it('should validate required fields', () => {
    const result = validateDealerUpdate({});
    expect(result.isValid).toBe(false);
    expect(result.errors).toContain('businessName is required');
  });

  it('should validate state is 2 letters', () => {
    const result = validateDealerUpdate({
      businessName: 'Test',
      state: 'Minnesota',
    });
    expect(result.isValid).toBe(false);
    expect(result.errors).toContain('state must be 2-letter code');
  });

  it('should validate email format', () => {
    const result = validateDealerUpdate({
      businessName: 'Test',
      contactEmail: 'invalid-email',
    });
    expect(result.isValid).toBe(false);
    expect(result.errors).toContain('contactEmail must be valid email');
  });

  it('should pass valid data', () => {
    const result = validateDealerUpdate({
      businessName: 'Test Business',
      address: '123 Main St',
      city: 'Minneapolis',
      state: 'MN',
      zip: '55401',
      country: 'United States',
      phone: '(612) 555-1234',
    });
    expect(result.isValid).toBe(true);
    expect(result.errors).toHaveLength(0);
  });
});
```

**Step 2: Run test to verify it fails**

Run:

```bash
npm test -- lib/__tests__/dealer-validation.test.ts
```

Expected: FAIL - "Cannot find module '../dealer-validation'"

**Step 3: Implement validation utility**

Create `lib/dealer-validation.ts`:

```typescript
export interface DealerUpdateData {
  dealerNumber?: string;
  businessName?: string;
  contactName?: string;
  address?: string;
  city?: string;
  state?: string;
  zip?: string;
  country?: string;
  phone?: string;
  contactEmail?: string;
  customDomain?: string;
  description?: string;
}

export interface ValidationResult {
  isValid: boolean;
  errors: string[];
  fieldErrors: Record<string, string>;
}

export function validateDealerUpdate(data: DealerUpdateData): ValidationResult {
  const errors: string[] = [];
  const fieldErrors: Record<string, string> = {};

  // Required fields
  const required = ['businessName', 'address', 'city', 'state', 'zip', 'country', 'phone'];
  for (const field of required) {
    if (!data[field as keyof DealerUpdateData]) {
      const error = `${field} is required`;
      errors.push(error);
      fieldErrors[field] = error;
    }
  }

  // State must be 2-letter code
  if (data.state && data.state.length !== 2) {
    const error = 'state must be 2-letter code';
    errors.push(error);
    fieldErrors.state = error;
  }

  // Email validation (if provided)
  if (data.contactEmail && !isValidEmail(data.contactEmail)) {
    const error = 'contactEmail must be valid email';
    errors.push(error);
    fieldErrors.contactEmail = error;
  }

  // Phone validation (basic check)
  if (data.phone && data.phone.length < 10) {
    const error = 'phone must be at least 10 digits';
    errors.push(error);
    fieldErrors.phone = error;
  }

  return {
    isValid: errors.length === 0,
    errors,
    fieldErrors,
  };
}

function isValidEmail(email: string): boolean {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}
```

**Step 4: Run tests to verify they pass**

Run:

```bash
npm test -- lib/__tests__/dealer-validation.test.ts
```

Expected: PASS (4 tests)

**Step 5: Commit**

```bash
git add lib/dealer-validation.ts lib/__tests__/dealer-validation.test.ts
git commit -m "feat(validation): add dealer update validation utility"
```

---

## Task 3: API Endpoint - Update Route

**Files:**

- Create: `app/api/dealer/update/route.ts`
- Create: `app/api/dealer/update/__tests__/route.test.ts`

**Step 1: Write failing API test**

Create `app/api/dealer/update/__tests__/route.test.ts`:

```typescript
import { POST } from '../route';
import { NextRequest } from 'next/server';
import { getServerSession } from 'next-auth/next';

jest.mock('next-auth/next');
jest.mock('@/lib/prisma');

describe('POST /api/dealer/update', () => {
  it('should require authentication', async () => {
    (getServerSession as jest.Mock).mockResolvedValue(null);

    const request = new NextRequest('http://localhost:3000/api/dealer/update', {
      method: 'POST',
    });

    const response = await POST(request);
    expect(response.status).toBe(401);
  });

  it('should validate required fields', async () => {
    (getServerSession as jest.Mock).mockResolvedValue({
      user: { id: 'user-123', email: 'test@example.com' },
    });

    const request = new NextRequest('http://localhost:3000/api/dealer/update', {
      method: 'POST',
      body: JSON.stringify({}),
    });

    const response = await POST(request);
    expect(response.status).toBe(400);
    const data = await response.json();
    expect(data.error).toBeDefined();
  });
});
```

**Step 2: Run test to verify it fails**

Run:

```bash
npm test -- app/api/dealer/update/__tests__/route.test.ts
```

Expected: FAIL - "Cannot find module '../route'"

**Step 3: Implement API endpoint**

Create `app/api/dealer/update/route.ts`:

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth/next';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { validateDealerUpdate } from '@/lib/dealer-validation';
import { logger } from '@/lib/logger';

export const dynamic = 'force-dynamic';

/**
 * POST /api/dealer/update
 *
 * Updates dealer information for authenticated user.
 * All fields except subdomain can be updated.
 */
export async function POST(request: NextRequest) {
  try {
    // Step 1: Verify authentication
    const session = await getServerSession(authOptions);

    if (!session?.user?.id) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
    }

    // Step 2: Parse request body
    const body = await request.json();

    // Step 3: Validate data
    const validation = validateDealerUpdate(body);
    if (!validation.isValid) {
      return NextResponse.json(
        {
          error: 'Validation failed',
          fields: validation.fieldErrors,
        },
        { status: 400 }
      );
    }

    // Step 4: Check dealer exists and belongs to user
    const existingDealer = await prisma.dealer.findFirst({
      where: { userId: session.user.id },
    });

    if (!existingDealer) {
      return NextResponse.json({ error: 'Dealer not found' }, { status: 404 });
    }

    // Step 5: Check custom domain uniqueness (if changed)
    if (body.customDomain && body.customDomain !== existingDealer.customDomain) {
      const domainExists = await prisma.dealer.findFirst({
        where: {
          customDomain: body.customDomain,
          id: { not: existingDealer.id },
        },
      });

      if (domainExists) {
        return NextResponse.json(
          {
            error: 'Custom domain already in use',
            fields: { customDomain: 'This domain is already taken' },
          },
          { status: 400 }
        );
      }
    }

    // Step 6: Update dealer record (exclude subdomain)
    const updatedDealer = await prisma.dealer.update({
      where: { id: existingDealer.id },
      data: {
        dealerNumber: body.dealerNumber,
        businessName: body.businessName,
        contactName: body.contactName,
        address: body.address,
        city: body.city,
        state: body.state?.toUpperCase(),
        zip: body.zip,
        country: body.country,
        phone: body.phone,
        contactEmail: body.contactEmail,
        customDomain: body.customDomain || null,
        description: body.description,
      },
    });

    logger.info({ dealerId: updatedDealer.id }, 'Dealer updated via basic editor');

    return NextResponse.json({
      success: true,
      dealer: updatedDealer,
    });
  } catch (error) {
    logger.error({ err: error }, 'Error updating dealer');
    return NextResponse.json({ error: 'Failed to update dealer information' }, { status: 500 });
  }
}
```

**Step 4: Run tests to verify they pass**

Run:

```bash
npm test -- app/api/dealer/update/__tests__/route.test.ts
```

Expected: PASS (2 tests)

**Step 5: Commit**

```bash
git add app/api/dealer/update/
git commit -m "feat(api): add dealer update endpoint"
```

---

## Task 4: Shared Dashboard Layout

**Files:**

- Create: `app/dashboard/layout.tsx`
- Modify: `app/dashboard/page.tsx:1-148`

**Step 1: Extract header from dashboard page**

Review `app/dashboard/page.tsx` lines 69-98 (header section) - this will be moved to layout

**Step 2: Create dashboard layout component**

Create `app/dashboard/layout.tsx`:

```typescript
'use client';

import { usePathname } from 'next/navigation';
import Link from 'next/link';
import Image from 'next/image';
import { useSession } from 'next-auth/react';
import { SignOutButton } from '@/components/SignOutButton';
import Button from '@/components/ui/Button';
import styles from './dashboard.module.css';

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  const { data: session } = useSession();
  const pathname = usePathname();
  const isDebugMode = process.env.NEXT_PUBLIC_DEBUG === 'true';

  // Create mock session for debug mode
  const testSession = {
    user: {
      id: 'test-user-id',
      name: 'Test User',
      email: 'test@example.com',
      image: null,
    },
  };

  const displaySession = session || (isDebugMode ? testSession : null);

  return (
    <div className={styles.dashboard}>
      {/* Shared Header */}
      <header className={styles.header}>
        <div className={styles.header__left}>
          <h1 className={styles.header__title}>AMSOIL Dealer Lead Pages</h1>
          <p className={styles.header__subtitle}>
            Welcome back{displaySession?.user?.name ? `, ${displaySession.user.name}` : ''}.
            {isDebugMode && !session && (
              <span className={styles.header__debug}>(Debug Mode)</span>
            )}
          </p>
        </div>
        <div className={styles.header__right}>
          {/* Navigation buttons */}
          <Link href="/dashboard/editor">
            <Button
              variant={pathname === '/dashboard/editor' ? 'primary' : 'secondary'}
              size="md"
            >
              Basic Editor
            </Button>
          </Link>

          <Link href="/dashboard">
            <Button
              variant={pathname === '/dashboard' ? 'primary' : 'secondary'}
              size="md"
            >
              Dashboard
            </Button>
          </Link>

          {displaySession?.user?.image && (
            <Image
              src={displaySession.user.image}
              alt={displaySession.user?.name ?? 'User avatar'}
              width={48}
              height={48}
              className={styles.header__avatar}
            />
          )}
          {session && <SignOutButton />}
        </div>
      </header>

      {/* Page content */}
      {children}
    </div>
  );
}
```

**Step 3: Update dashboard page to use layout**

Modify `app/dashboard/page.tsx` - remove header section (lines 69-98) and slider editor button, simplify to:

```typescript
'use client';

import { useEffect, Suspense } from 'react';
import { useSearchParams } from 'next/navigation';
import Alert from '@/components/ui/Alert';
import { Card, CardBody } from '@/components/ui/Card';
import styles from './dashboard.module.css';

const leadPages: Array<{ id: string; name: string; description: string }> = [];

function DashboardContent() {
  const searchParams = useSearchParams();
  const [showPublishedMessage, setShowPublishedMessage] = useState(false);

  useEffect(() => {
    if (searchParams.get('published') === 'true') {
      setShowPublishedMessage(true);
      const timer = setTimeout(() => setShowPublishedMessage(false), 5000);
      return () => clearTimeout(timer);
    }
  }, [searchParams]);

  return (
    <>
      {showPublishedMessage && (
        <div className={styles.successMessage}>
          <Alert
            variant="success"
            title="Success!"
            description="Congratulations! Your dealer site is now live!"
            dismissible={true}
            onClose={() => setShowPublishedMessage(false)}
          />
        </div>
      )}

      <section className={styles.section}>
        <div className={styles.content}>
          {leadPages.length === 0 ? (
            <Card variant="surface">
              <CardBody>
                <div className={styles.emptyState}>
                  <h2 className={styles.emptyState__title}>No lead pages yet</h2>
                  <p className={styles.emptyState__description}>
                    As soon as you create lead pages for your AMSOIL dealership, they will
                    appear here with quick access links and performance metrics.
                  </p>
                </div>
              </CardBody>
            </Card>
          ) : (
            <ul className={styles.leadPagesList}>
              {leadPages.map((page) => (
                <li key={page.id}>
                  <Card variant="default">
                    <CardBody>
                      <h3 className={styles.leadPage__title}>{page.name}</h3>
                      <p className={styles.leadPage__description}>{page.description}</p>
                    </CardBody>
                  </Card>
                </li>
              ))}
            </ul>
          )}
        </div>
      </section>
    </>
  );
}

export default function DashboardPage() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <DashboardContent />
    </Suspense>
  );
}
```

**Step 4: Test dashboard still renders**

Run:

```bash
npm run dev
```

Visit `http://localhost:3000/dashboard` and verify header appears with "Basic Editor" and "Dashboard" buttons

**Step 5: Commit**

```bash
git add app/dashboard/layout.tsx app/dashboard/page.tsx
git commit -m "feat(dashboard): extract shared layout with navigation"
```

---

## Task 5: Editor Form Component

**Files:**

- Create: `app/dashboard/editor/EditorForm.tsx`

**Step 1: Create editor form component**

Create `app/dashboard/editor/EditorForm.tsx`:

```typescript
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';

interface EditorFormProps {
  initialData: {
    dealerId: string;
    subdomain: string;
    domainPrefix: string;
    domain: string;
    customDomain: string | null;
    dealerNumber: string | null;
    businessName: string | null;
    contactName: string | null;
    address: string;
    city: string;
    state: string;
    zip: string;
    country: string;
    phone: string;
    contactEmail: string | null;
    description: string | null;
  };
}

export default function EditorForm({ initialData }: EditorFormProps) {
  const router = useRouter();
  const [formData, setFormData] = useState({
    customDomain: initialData.customDomain || '',
    dealerNumber: initialData.dealerNumber || '',
    businessName: initialData.businessName || '',
    contactName: initialData.contactName || '',
    address: initialData.address,
    city: initialData.city,
    state: initialData.state,
    zip: initialData.zip,
    country: initialData.country,
    phone: initialData.phone,
    contactEmail: initialData.contactEmail || '',
    description: initialData.description || '',
  });

  const [saving, setSaving] = useState(false);
  const [success, setSuccess] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});

  const fullSubdomain = `${initialData.subdomain}.${initialData.domainPrefix}.${initialData.domain}`;

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setSaving(true);
    setSuccess(false);
    setError(null);
    setFieldErrors({});

    try {
      const response = await fetch('/api/dealer/update', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(formData),
      });

      const data = await response.json();

      if (!response.ok) {
        if (data.fields) {
          setFieldErrors(data.fields);
        }
        throw new Error(data.error || 'Failed to save changes');
      }

      setSuccess(true);
      setTimeout(() => setSuccess(false), 5000);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'An error occurred');
    } finally {
      setSaving(false);
    }
  };

  return (
    <div
      className="min-h-screen flex flex-col"
      style={{ backgroundColor: 'var(--reg-bg-navy)' }}
    >
      <div className="flex-1 flex items-center justify-center p-8">
        <div className="w-full max-w-3xl bg-white rounded-lg shadow-lg p-8">
          <h1 className="text-3xl font-bold text-gray-900 mb-6">Edit Dealer Information</h1>

          {/* Success Message */}
          {success && (
            <div className="mb-6 p-4 bg-green-50 border border-green-200 rounded-md">
              <p className="text-green-800 font-medium">Changes saved successfully!</p>
            </div>
          )}

          {/* Error Message */}
          {error && (
            <div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-md">
              <p className="text-red-800 font-medium">{error}</p>
            </div>
          )}

          <form onSubmit={handleSubmit} className="space-y-8">
            {/* Domain Settings */}
            <div className="space-y-4">
              <h2 className="text-xl font-semibold text-gray-800 border-b pb-2">
                Domain Settings
              </h2>

              {/* Subdomain - Read Only */}
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-1">
                  Subdomain (Cannot be changed)
                </label>
                <input
                  type="text"
                  value={fullSubdomain}
                  disabled
                  className="w-full px-4 py-2 border border-gray-300 rounded-md bg-gray-50 text-gray-500 cursor-not-allowed"
                />
                <p className="mt-1 text-xs text-gray-500">
                  Your subdomain cannot be changed after initial setup
                </p>
              </div>

              {/* Custom Domain */}
              <div>
                <label htmlFor="customDomain" className="block text-sm font-medium text-gray-700 mb-1">
                  Custom Domain (Optional)
                </label>
                <input
                  type="text"
                  id="customDomain"
                  value={formData.customDomain}
                  onChange={(e) =>
                    setFormData({ ...formData, customDomain: e.target.value.toLowerCase() })
                  }
                  placeholder="www.yourdomain.com"
                  className={`w-full px-4 py-2 border rounded-md focus:ring-2 focus:ring-blue-500 ${
                    fieldErrors.customDomain ? 'border-red-500' : 'border-gray-300'
                  }`}
                />
                {fieldErrors.customDomain && (
                  <p className="mt-1 text-sm text-red-600">{fieldErrors.customDomain}</p>
                )}
              </div>
            </div>

            {/* Business Information */}
            <div className="space-y-4">
              <h2 className="text-xl font-semibold text-gray-800 border-b pb-2">
                Business Information
              </h2>

              <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                {/* ZO Account Number */}
                <div className="md:col-span-2">
                  <label htmlFor="dealerNumber" className="block text-sm font-medium text-gray-700 mb-1">
                    ZO Account Number
                  </label>
                  <input
                    type="text"
                    id="dealerNumber"
                    value={formData.dealerNumber}
                    onChange={(e) =>
                      setFormData({ ...formData, dealerNumber: e.target.value })
                    }
                    className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
                  />
                </div>

                {/* Business Name */}
                <div className="md:col-span-2">
                  <label htmlFor="businessName" className="block text-sm font-medium text-gray-700 mb-1">
                    Business Name *
                  </label>
                  <input
                    type="text"
                    id="businessName"
                    value={formData.businessName}
                    onChange={(e) =>
                      setFormData({ ...formData, businessName: e.target.value })
                    }
                    required
                    className={`w-full px-4 py-2 border rounded-md focus:ring-2 focus:ring-blue-500 ${
                      fieldErrors.businessName ? 'border-red-500' : 'border-gray-300'
                    }`}
                  />
                  {fieldErrors.businessName && (
                    <p className="mt-1 text-sm text-red-600">{fieldErrors.businessName}</p>
                  )}
                </div>

                {/* Contact Name */}
                <div className="md:col-span-2">
                  <label htmlFor="contactName" className="block text-sm font-medium text-gray-700 mb-1">
                    Contact Name
                  </label>
                  <input
                    type="text"
                    id="contactName"
                    value={formData.contactName}
                    onChange={(e) =>
                      setFormData({ ...formData, contactName: e.target.value })
                    }
                    className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
                  />
                </div>

                {/* Street Address */}
                <div className="md:col-span-2">
                  <label htmlFor="address" className="block text-sm font-medium text-gray-700 mb-1">
                    Street Address *
                  </label>
                  <input
                    type="text"
                    id="address"
                    value={formData.address}
                    onChange={(e) =>
                      setFormData({ ...formData, address: e.target.value })
                    }
                    required
                    className={`w-full px-4 py-2 border rounded-md focus:ring-2 focus:ring-blue-500 ${
                      fieldErrors.address ? 'border-red-500' : 'border-gray-300'
                    }`}
                  />
                  {fieldErrors.address && (
                    <p className="mt-1 text-sm text-red-600">{fieldErrors.address}</p>
                  )}
                </div>

                {/* City */}
                <div>
                  <label htmlFor="city" className="block text-sm font-medium text-gray-700 mb-1">
                    City *
                  </label>
                  <input
                    type="text"
                    id="city"
                    value={formData.city}
                    onChange={(e) => setFormData({ ...formData, city: e.target.value })}
                    required
                    className={`w-full px-4 py-2 border rounded-md focus:ring-2 focus:ring-blue-500 ${
                      fieldErrors.city ? 'border-red-500' : 'border-gray-300'
                    }`}
                  />
                  {fieldErrors.city && (
                    <p className="mt-1 text-sm text-red-600">{fieldErrors.city}</p>
                  )}
                </div>

                {/* State */}
                <div>
                  <label htmlFor="state" className="block text-sm font-medium text-gray-700 mb-1">
                    State/Province *
                  </label>
                  <input
                    type="text"
                    id="state"
                    value={formData.state}
                    onChange={(e) =>
                      setFormData({ ...formData, state: e.target.value.toUpperCase() })
                    }
                    placeholder="MN or ON"
                    maxLength={2}
                    required
                    className={`w-full px-4 py-2 border rounded-md focus:ring-2 focus:ring-blue-500 ${
                      fieldErrors.state ? 'border-red-500' : 'border-gray-300'
                    }`}
                  />
                  {fieldErrors.state && (
                    <p className="mt-1 text-sm text-red-600">{fieldErrors.state}</p>
                  )}
                  <p className="mt-1 text-xs text-gray-500">
                    2-letter code (e.g., MN for Minnesota, ON for Ontario)
                  </p>
                </div>

                {/* Zipcode */}
                <div>
                  <label htmlFor="zip" className="block text-sm font-medium text-gray-700 mb-1">
                    ZIP/Postal Code *
                  </label>
                  <input
                    type="text"
                    id="zip"
                    value={formData.zip}
                    onChange={(e) => setFormData({ ...formData, zip: e.target.value })}
                    placeholder="12345 or K1A 0B1"
                    required
                    className={`w-full px-4 py-2 border rounded-md focus:ring-2 focus:ring-blue-500 ${
                      fieldErrors.zip ? 'border-red-500' : 'border-gray-300'
                    }`}
                  />
                  {fieldErrors.zip && (
                    <p className="mt-1 text-sm text-red-600">{fieldErrors.zip}</p>
                  )}
                </div>

                {/* Country */}
                <div>
                  <label htmlFor="country" className="block text-sm font-medium text-gray-700 mb-1">
                    Country *
                  </label>
                  <input
                    type="text"
                    id="country"
                    value={formData.country}
                    onChange={(e) =>
                      setFormData({ ...formData, country: e.target.value })
                    }
                    required
                    className={`w-full px-4 py-2 border rounded-md focus:ring-2 focus:ring-blue-500 ${
                      fieldErrors.country ? 'border-red-500' : 'border-gray-300'
                    }`}
                  />
                  {fieldErrors.country && (
                    <p className="mt-1 text-sm text-red-600">{fieldErrors.country}</p>
                  )}
                </div>

                {/* Phone */}
                <div className="md:col-span-2">
                  <label htmlFor="phone" className="block text-sm font-medium text-gray-700 mb-1">
                    Phone *
                  </label>
                  <input
                    type="tel"
                    id="phone"
                    value={formData.phone}
                    onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
                    placeholder="(555) 123-4567"
                    required
                    className={`w-full px-4 py-2 border rounded-md focus:ring-2 focus:ring-blue-500 ${
                      fieldErrors.phone ? 'border-red-500' : 'border-gray-300'
                    }`}
                  />
                  {fieldErrors.phone && (
                    <p className="mt-1 text-sm text-red-600">{fieldErrors.phone}</p>
                  )}
                </div>

                {/* Email */}
                <div className="md:col-span-2">
                  <label htmlFor="contactEmail" className="block text-sm font-medium text-gray-700 mb-1">
                    Email
                  </label>
                  <input
                    type="email"
                    id="contactEmail"
                    value={formData.contactEmail}
                    onChange={(e) =>
                      setFormData({ ...formData, contactEmail: e.target.value })
                    }
                    className={`w-full px-4 py-2 border rounded-md focus:ring-2 focus:ring-blue-500 ${
                      fieldErrors.contactEmail ? 'border-red-500' : 'border-gray-300'
                    }`}
                  />
                  {fieldErrors.contactEmail && (
                    <p className="mt-1 text-sm text-red-600">{fieldErrors.contactEmail}</p>
                  )}
                  <p className="mt-1 text-xs text-gray-500">
                    Public contact email (displayed on your site)
                  </p>
                </div>

                {/* Description */}
                <div className="md:col-span-2">
                  <label htmlFor="description" className="block text-sm font-medium text-gray-700 mb-1">
                    Business Description
                  </label>
                  <textarea
                    id="description"
                    value={formData.description}
                    onChange={(e) =>
                      setFormData({ ...formData, description: e.target.value })
                    }
                    rows={5}
                    placeholder="Tell visitors about your business..."
                    className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
                  />
                  <p className="mt-1 text-xs text-gray-500">
                    This will be displayed on your dealer site
                  </p>
                </div>
              </div>
            </div>

            {/* Submit Button */}
            <div className="flex justify-end pt-4 border-t">
              <button
                type="submit"
                disabled={saving}
                className="px-6 py-3 bg-blue-600 text-white font-medium rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
              >
                {saving ? 'Saving...' : 'Save Changes'}
              </button>
            </div>
          </form>
        </div>
      </div>
    </div>
  );
}
```

**Step 2: Commit**

```bash
git add app/dashboard/editor/EditorForm.tsx
git commit -m "feat(editor): create editor form component"
```

---

## Task 6: Editor Page (Server Component)

**Files:**

- Create: `app/dashboard/editor/page.tsx`

**Step 1: Create editor page server component**

Create `app/dashboard/editor/page.tsx`:

```typescript
import { redirect } from 'next/navigation';
import { getServerSession } from 'next-auth/next';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import EditorForm from './EditorForm';

/**
 * Basic Dealer Editor Page
 *
 * Server component that fetches dealer data and renders the editor form.
 * Only accessible to authenticated dealers with an active account.
 */
export default async function DealerEditorPage() {
  // Step 1: Verify authentication
  const session = await getServerSession(authOptions);

  if (!session?.user?.id) {
    redirect('/api/auth/signin?callbackUrl=/dashboard/editor');
  }

  // Step 2: Fetch dealer data
  const dealer = await prisma.dealer.findFirst({
    where: { userId: session.user.id },
    include: { user: true },
  });

  // Step 3: Handle missing dealer
  if (!dealer) {
    // If no dealer found, redirect to onboarding
    redirect('/registration/onboarding');
  }

  // Step 4: Prepare initial form data
  const initialData = {
    dealerId: dealer.id,
    subdomain: dealer.subdomain || 'not-set',
    domainPrefix: dealer.domainPrefix,
    domain: dealer.domain,
    customDomain: dealer.customDomain,
    dealerNumber: dealer.dealerNumber,
    businessName: dealer.businessName,
    contactName: dealer.contactName,
    address: dealer.address,
    city: dealer.city,
    state: dealer.state,
    zip: dealer.zip,
    country: dealer.country,
    phone: dealer.phone,
    contactEmail: dealer.contactEmail,
    description: dealer.description,
  };

  // Step 5: Render form with pre-filled data
  return <EditorForm initialData={initialData} />;
}
```

**Step 2: Test editor page loads**

Run:

```bash
npm run dev
```

Visit `http://localhost:3000/dashboard/editor` (while signed in)
Expected: Form loads with pre-filled dealer data

**Step 3: Commit**

```bash
git add app/dashboard/editor/page.tsx
git commit -m "feat(editor): create editor page server component"
```

---

## Task 7: End-to-End Testing

**Files:**

- Create: `app/dashboard/editor/__tests__/integration.test.tsx`

**Step 1: Write integration test**

Create `app/dashboard/editor/__tests__/integration.test.tsx`:

```typescript
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { useSession } from 'next-auth/react';
import EditorForm from '../EditorForm';

jest.mock('next-auth/react');
jest.mock('next/navigation', () => ({
  useRouter: () => ({
    push: jest.fn(),
  }),
}));

describe('Editor Integration', () => {
  const mockInitialData = {
    dealerId: 'dealer-123',
    subdomain: 'testdealer',
    domainPrefix: 'myamsoil',
    domain: 'com',
    customDomain: null,
    dealerNumber: '1234567',
    businessName: 'Test Business',
    contactName: 'John Doe',
    address: '123 Main St',
    city: 'Minneapolis',
    state: 'MN',
    zip: '55401',
    country: 'United States',
    phone: '(612) 555-1234',
    contactEmail: 'john@test.com',
    description: 'Test description',
  };

  beforeEach(() => {
    (useSession as jest.Mock).mockReturnValue({
      data: { user: { id: 'user-123', email: 'test@example.com' } },
    });
  });

  it('should render form with pre-filled data', () => {
    render(<EditorForm initialData={mockInitialData} />);

    expect(screen.getByLabelText(/Business Name/i)).toHaveValue('Test Business');
    expect(screen.getByLabelText(/Contact Name/i)).toHaveValue('John Doe');
    expect(screen.getByLabelText(/City/i)).toHaveValue('Minneapolis');
  });

  it('should disable subdomain field', () => {
    render(<EditorForm initialData={mockInitialData} />);

    const subdomainInput = screen.getByDisplayValue('testdealer.myamsoil.com');
    expect(subdomainInput).toBeDisabled();
  });

  it('should submit form successfully', async () => {
    global.fetch = jest.fn(() =>
      Promise.resolve({
        ok: true,
        json: () => Promise.resolve({ success: true }),
      })
    ) as jest.Mock;

    render(<EditorForm initialData={mockInitialData} />);

    const businessNameInput = screen.getByLabelText(/Business Name/i);
    fireEvent.change(businessNameInput, { target: { value: 'Updated Business' } });

    const submitButton = screen.getByText('Save Changes');
    fireEvent.click(submitButton);

    await waitFor(() => {
      expect(screen.getByText(/Changes saved successfully!/i)).toBeInTheDocument();
    });
  });

  it('should display validation errors', async () => {
    global.fetch = jest.fn(() =>
      Promise.resolve({
        ok: false,
        json: () =>
          Promise.resolve({
            error: 'Validation failed',
            fields: { businessName: 'Business name is required' },
          }),
      })
    ) as jest.Mock;

    render(<EditorForm initialData={mockInitialData} />);

    const businessNameInput = screen.getByLabelText(/Business Name/i);
    fireEvent.change(businessNameInput, { target: { value: '' } });

    const submitButton = screen.getByText('Save Changes');
    fireEvent.click(submitButton);

    await waitFor(() => {
      expect(screen.getByText(/Business name is required/i)).toBeInTheDocument();
    });
  });
});
```

**Step 2: Run integration tests**

Run:

```bash
npm test -- app/dashboard/editor/__tests__/integration.test.tsx
```

Expected: PASS (4 tests)

**Step 3: Manual end-to-end test**

1. Start dev server: `npm run dev`
2. Sign in at `/api/auth/signin`
3. Navigate to `/dashboard`
4. Click "Basic Editor" button
5. Verify form loads with dealer data
6. Edit a field (e.g., business name)
7. Click "Save Changes"
8. Verify success message appears
9. Refresh page - verify changes persisted

**Step 4: Commit**

```bash
git add app/dashboard/editor/__tests__/
git commit -m "test(editor): add integration tests"
```

---

## Task 8: Documentation & Cleanup

**Files:**

- Create: `app/dashboard/editor/README.md`
- Modify: `CLAUDE.md`

**Step 1: Create editor README**

Create `app/dashboard/editor/README.md`:

````markdown
# Basic Dealer Editor

## Overview

The Basic Dealer Editor allows authenticated dealers to update their business information after completing initial registration.

## Route

`/dashboard/editor`

## Features

- Pre-filled form with existing dealer data
- Subdomain display (read-only - cannot be changed)
- Custom domain editing (with uniqueness validation)
- All business contact fields editable
- Client-side and server-side validation
- Real-time error feedback
- Success confirmation messages

## Architecture

### Components

- `page.tsx` - Server component that fetches dealer data
- `EditorForm.tsx` - Client component with form logic and submission

### API Endpoint

- `POST /api/dealer/update` - Validates and updates dealer record

### Validation

Handled by `lib/dealer-validation.ts`:

- Required fields: businessName, address, city, state, zip, country, phone
- State must be 2-letter code
- Email format validation
- Custom domain uniqueness check

## Security

- User must be authenticated (enforced by middleware)
- Users can only update their own dealer record (verified by userId)
- Subdomain cannot be changed (excluded from update)

## Testing

Run tests:

```bash
npm test -- app/dashboard/editor
npm test -- lib/__tests__/dealer-validation.test.ts
npm test -- app/api/dealer/update
```
````

## Future Enhancements

- Image upload for business logo
- Rich text editor for description
- Change history / audit log
- Email verification for contactEmail changes

````

**Step 2: Update CLAUDE.md with editor documentation**

Add to `CLAUDE.md` (after Global Styling System section):

```markdown
## Basic Dealer Editor

**IMPORTANT**: Dealers can edit their business information post-registration via the Basic Dealer Editor.

### Route

`/dashboard/editor` - Single-page form for updating dealer information

### Editable Fields

All fields EXCEPT subdomain:
- ZO Account Number (`dealerNumber`)
- Business Name
- Contact Name (NEW field)
- Street Address
- City
- State/Province
- Zipcode
- Country
- Phone
- Email (display email, separate from OAuth login email)
- Description (long text for business description)
- Custom Domain (optional)

### Subdomain Policy

**CRITICAL**: Subdomain is READ-ONLY after initial setup.
- Displayed but cannot be edited
- Prevents broken links, SEO issues, customer confusion
- Custom domain can be changed anytime

### Email Separation

- **Login Email** (`User.email`) - OAuth email, authentication only
- **Display Email** (`Dealer.contactEmail`) - Public contact email, editable

### API Endpoint

- **Route**: `POST /api/dealer/update`
- **Authentication**: Required (enforced by middleware)
- **Authorization**: User can only update their own dealer record
- **Validation**: Via `lib/dealer-validation.ts`

### DO NOT:

- Allow subdomain editing after initial setup
- Skip validation on state (must be 2-letter code)
- Allow custom domain duplicates
- Forget email format validation
````

**Step 3: Commit**

```bash
git add app/dashboard/editor/README.md CLAUDE.md
git commit -m "docs: add basic dealer editor documentation"
```

---

## Task 9: Final Verification & PR Preparation

**Step 1: Run full test suite**

Run:

```bash
npm test
```

Expected: All tests passing

**Step 2: Run type checking**

Run:

```bash
npx tsc --noEmit
```

Expected: No type errors

**Step 3: Run build**

Run:

```bash
npm run build
```

Expected: Build succeeds

**Step 4: Manual verification checklist**

Test in dev environment:

- [ ] Dashboard loads with "Basic Editor" button
- [ ] Clicking "Basic Editor" navigates to `/dashboard/editor`
- [ ] Form pre-fills with existing dealer data
- [ ] Subdomain field is disabled
- [ ] Editing and saving works
- [ ] Success message appears on successful save
- [ ] Validation errors display correctly
- [ ] Custom domain uniqueness is enforced
- [ ] Navigation between dashboard and editor works

**Step 5: Create PR**

```bash
git push origin feature/basic-dealer-editor
gh pr create --title "feat: add basic dealer editor" --body "$(cat <<'EOF'
## Summary

Adds a Basic Dealer Editor at `/dashboard/editor` for dealers to update their business information after registration.

## Changes

- **Database**: Added `contactName`, `contactEmail`, `description` fields to Dealer model
- **API**: New `POST /api/dealer/update` endpoint with validation
- **UI**: Single-page editor form with all dealer fields
- **Layout**: Shared dashboard layout with navigation
- **Validation**: Server-side and client-side field validation

## Features

- Pre-filled form with existing data
- Subdomain read-only (cannot be changed)
- Custom domain editable with uniqueness check
- Success/error messaging
- Field-level validation feedback

## Testing

- Unit tests for validation logic
- Integration tests for form component
- API endpoint tests
- Manual end-to-end testing completed

## Documentation

- Added README in `app/dashboard/editor/`
- Updated `CLAUDE.md` with editor documentation
- Detailed implementation plan in `docs/plans/`

Closes #[issue-number]
EOF
)"
```

---

## Success Criteria

- ✅ Database migration adds new fields
- ✅ API endpoint validates and updates dealer data
- ✅ Shared dashboard layout provides consistent navigation
- ✅ Editor form pre-fills with existing data
- ✅ Subdomain displayed but not editable
- ✅ Custom domain can be changed (with uniqueness check)
- ✅ Form validation prevents invalid data
- ✅ Success/error messages provide clear feedback
- ✅ All tests passing
- ✅ Type checking passes
- ✅ Build succeeds
- ✅ Documentation complete

---

## Notes

- Follow @superpowers:test-driven-development for all new functions
- Use @superpowers:verification-before-completion before claiming tasks complete
- Commit after each logical step (frequent commits)
- DRY: Reuse validation logic, don't duplicate
- YAGNI: Don't add features not in requirements
