# Dealer Logo Implementation Plan

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

**Goal:** Add custom dealer logo upload to the Basic Information editor and display it in the dealer page header.

**Architecture:** Add `logoUrl` field to Dealer model, extend the editor with a 2-column layout (domain settings + logo upload), and conditionally render the logo in the header Row 2 next to the dealer's business name.

**Tech Stack:** Next.js, React, Prisma, Cloudflare Images, Tailwind CSS

---

## Task 1: Add logoUrl field to Prisma schema

**Files:**
- Modify: `prisma/schema.prisma:100-110`

**Step 1: Add the logoUrl field**

Add after line 109 (after `contactEmail`):

```prisma
  logoUrl              String?
```

The full context around line 109:

```prisma
  contactName          String?
  contactEmail         String?
  logoUrl              String?
  description          String?
```

**Step 2: Sync the database schema**

Run: `npm run sync-db`
Expected: Schema synced successfully

**Step 3: Verify schema change**

Run: `npx prisma validate`
Expected: "The schema at `prisma/schema.prisma` is valid"

**Step 4: Commit**

```bash
git add prisma/schema.prisma
git commit -m "Add logoUrl field to Dealer model"
```

---

## Task 2: Update DealerInfo type to include logoUrl

**Files:**
- Modify: `types/dealer.ts:23-43`

**Step 1: Add logoUrl to DealerInfo interface**

Add after `showContactName` (line 42):

```typescript
  logoUrl?: string; // Optional - dealer's custom logo URL
```

**Step 2: Commit**

```bash
git add types/dealer.ts
git commit -m "Add logoUrl to DealerInfo type"
```

---

## Task 3: Update dealer validation to accept logoUrl

**Files:**
- Modify: `lib/dealer-validation.ts:1-18`
- Modify: `lib/dealer-validation.ts:72-90`

**Step 1: Add logoUrl to DealerUpdateData interface**

Add after `showContactName` (line 17):

```typescript
  logoUrl?: string;
```

**Step 2: Add logoUrl max length constant**

Add to FIELD_MAX_LENGTHS object (around line 89, before the closing brace):

```typescript
  logoUrl: 500, // Cloudflare Images URLs
```

**Step 3: Commit**

```bash
git add lib/dealer-validation.ts
git commit -m "Add logoUrl to dealer validation"
```

---

## Task 4: Update dealer update API to handle logoUrl

**Files:**
- Modify: `app/api/dealer/update/route.ts:246-258`

**Step 1: Add logoUrl handling to updateData**

Add after the `showContactName` block (around line 258):

```typescript
    if (sanitizedBody.logoUrl !== undefined) {
      updateData.logoUrl = sanitizedBody.logoUrl || null;
    }
```

**Step 2: Commit**

```bash
git add app/api/dealer/update/route.ts
git commit -m "Add logoUrl handling to dealer update API"
```

---

## Task 5: Add 'logo' context to useImageUpload

**Files:**
- Modify: `hooks/useImageUpload.ts:3`

**Step 1: Add 'logo' to UploadContext type**

Change line 3 from:

```typescript
export type UploadContext = 'slider' | 'profile' | 'product' | 'page' | 'general';
```

To:

```typescript
export type UploadContext = 'slider' | 'profile' | 'product' | 'page' | 'general' | 'logo';
```

**Step 2: Commit**

```bash
git add hooks/useImageUpload.ts
git commit -m "Add logo context to image upload hook"
```

---

## Task 6: Create LogoUpload component

**Files:**
- Create: `app/dashboard/editor/LogoUpload.tsx`

**Step 1: Create the LogoUpload component**

```tsx
'use client';

import { useState, useRef } from 'react';
import Image from 'next/image';
import { useImageUpload } from '@/hooks/useImageUpload';

interface LogoUploadProps {
  currentLogoUrl: string | null;
  onLogoChange: (url: string | null) => void;
}

export default function LogoUpload({ currentLogoUrl, onLogoChange }: LogoUploadProps) {
  const fileInputRef = useRef<HTMLInputElement>(null);
  const { uploadImage, isUploading, error } = useImageUpload();
  const [imageError, setImageError] = useState(false);

  const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    // Validate file type
    const validTypes = ['image/png', 'image/jpeg', 'image/webp'];
    if (!validTypes.includes(file.type)) {
      alert('Please upload a PNG, JPG, or WebP image');
      return;
    }

    // Validate file size (5MB max)
    if (file.size > 5 * 1024 * 1024) {
      alert('Image must be under 5MB');
      return;
    }

    try {
      const url = await uploadImage(file, 'logo');
      onLogoChange(url);
      setImageError(false);
    } catch {
      // Error is already handled by the hook
    }

    // Reset file input
    if (fileInputRef.current) {
      fileInputRef.current.value = '';
    }
  };

  const handleRemove = () => {
    onLogoChange(null);
    setImageError(false);
  };

  return (
    <div className="space-y-3">
      <label className="block text-sm font-medium text-white/70">
        Dealer Logo (optional)
      </label>

      <div className="flex items-start gap-4">
        {/* Preview box */}
        <div className="w-20 h-20 bg-gray-800/50 border border-gray-600 rounded-md flex items-center justify-center overflow-hidden flex-shrink-0">
          {isUploading ? (
            <div className="animate-spin h-6 w-6 border-2 border-sky-400 border-t-transparent rounded-full" />
          ) : currentLogoUrl && !imageError ? (
            <Image
              src={currentLogoUrl}
              alt="Dealer logo"
              width={80}
              height={80}
              className="object-contain w-full h-full"
              onError={() => setImageError(true)}
            />
          ) : (
            <span className="text-gray-500 text-xs text-center px-2">No logo</span>
          )}
        </div>

        {/* Buttons and info */}
        <div className="flex flex-col gap-2">
          <div className="flex gap-2">
            <button
              type="button"
              onClick={() => fileInputRef.current?.click()}
              disabled={isUploading}
              className="px-3 py-1.5 text-sm bg-gray-700 hover:bg-gray-600 text-white rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
            >
              {currentLogoUrl ? 'Replace' : 'Choose File'}
            </button>
            {currentLogoUrl && (
              <button
                type="button"
                onClick={handleRemove}
                disabled={isUploading}
                className="px-3 py-1.5 text-sm bg-red-900/50 hover:bg-red-800/50 text-red-300 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
              >
                Remove
              </button>
            )}
          </div>
          <p className="text-xs text-white/50">PNG, JPG, or WebP. Max 5MB.</p>
          {error && <p className="text-xs text-red-400">{error}</p>}
        </div>
      </div>

      <input
        ref={fileInputRef}
        type="file"
        accept="image/png,image/jpeg,image/webp"
        onChange={handleFileSelect}
        className="hidden"
      />
    </div>
  );
}
```

**Step 2: Commit**

```bash
git add app/dashboard/editor/LogoUpload.tsx
git commit -m "Add LogoUpload component for dealer logo management"
```

---

## Task 7: Update EditorForm with 2-column layout and logo upload

**Files:**
- Modify: `app/dashboard/editor/EditorForm.tsx`

**Step 1: Add logoUrl to initialData interface (line 10-30)**

Add after `showContactName` in the interface:

```typescript
    logoUrl: string | null;
```

**Step 2: Add logoUrl to formData state (line 39-56)**

Add to the useState object:

```typescript
    logoUrl: initialData.logoUrl,
```

**Step 3: Add import for LogoUpload component (line 1-8)**

Add after the existing imports:

```typescript
import LogoUpload from './LogoUpload';
```

**Step 4: Update Domain Settings section to 2-column layout (line 249-361)**

Replace the Domain Settings section with a 2-column grid layout:

```tsx
          {/* Domain Settings - 2 Column Layout */}
          <div className="space-y-4">
            <h2 className="text-xl font-semibold text-white border-b border-white/10 pb-2">
              Domain Settings
            </h2>

            <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
              {/* Left Column: Domain fields */}
              <div className="space-y-4">
                {/* Subdomain field - keep existing code */}
                <div>
                  <label htmlFor="subdomain" className="block text-sm font-medium text-white/70 mb-1">
                    Subdomain {canEditSubdomain ? '*' : '(Cannot be changed)'}
                  </label>
                  {/* ... existing subdomain input code ... */}
                </div>

                {/* Custom Domain - keep existing code */}
                {canUseCustomDomain && (
                  <div>
                    {/* ... existing custom domain code ... */}
                  </div>
                )}
              </div>

              {/* Right Column: Logo Upload */}
              <div>
                <LogoUpload
                  currentLogoUrl={formData.logoUrl}
                  onLogoChange={(url) => setFormData({ ...formData, logoUrl: url })}
                />
              </div>
            </div>
          </div>
```

**Step 5: Add logoUrl to request data (around line 147-153)**

Ensure logoUrl is included in the request. The existing spread of formData should include it automatically since we added it to state.

**Step 6: Commit**

```bash
git add app/dashboard/editor/EditorForm.tsx
git commit -m "Add 2-column layout with logo upload to EditorForm"
```

---

## Task 8: Update DealerTemplate to display logo in header

**Files:**
- Modify: `components/DealerTemplate.tsx:351-356`

**Step 1: Add logoUrl to DealerInfo usage**

Update the header Row 2 section (around line 351-356). Replace:

```tsx
            {/* DYNAMIC: Dealer name */}
            <h1 className="dealer-name">{dealer.name}</h1>
```

With:

```tsx
            {/* DYNAMIC: Dealer logo and name */}
            <div className="dealer-name-row">
              {dealer.logoUrl && (
                <img
                  src={dealer.logoUrl}
                  alt=""
                  className="dealer-logo"
                  onError={(e) => {
                    // Hide broken image gracefully
                    (e.target as HTMLImageElement).style.display = 'none';
                  }}
                />
              )}
              <h1 className="dealer-name">{dealer.name}</h1>
            </div>
```

**Step 2: Add logoUrl to DealerInfo type import check**

The type already includes logoUrl from Task 2, so this should work automatically.

**Step 3: Commit**

```bash
git add components/DealerTemplate.tsx
git commit -m "Display dealer logo in header next to business name"
```

---

## Task 9: Add CSS styles for dealer logo in header

**Files:**
- Modify: `public/assets/dealer-template.css`

**Step 1: Add styles for dealer-name-row and dealer-logo**

Add after the `.dealer-page-wireframe .dealer-name` rule (around line 35):

```css
.dealer-page-wireframe .dealer-name-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.dealer-page-wireframe .dealer-logo {
  max-height: 48px;
  width: auto;
  object-fit: contain;
}
```

**Step 2: Commit**

```bash
git add public/assets/dealer-template.css
git commit -m "Add CSS styles for dealer logo in header"
```

---

## Task 10: Update dealer page data fetching to include logoUrl

**Files:**
- Check: `app/page.tsx` or wherever dealer data is fetched for the template

**Step 1: Verify logoUrl is included in dealer fetch**

Search for where dealer data is transformed into DealerInfo. The logoUrl field should be passed through automatically if the Prisma query selects all fields.

Run: `grep -r "DealerInfo" app/ --include="*.ts" --include="*.tsx" -l`

Check the relevant file and ensure logoUrl is mapped from the database record to the DealerInfo object.

**Step 2: Commit if changes needed**

```bash
git add [modified files]
git commit -m "Include logoUrl in dealer page data fetching"
```

---

## Task 11: Test the complete flow

**Step 1: Start the development server**

```bash
cd /home/momentary/repos/amsoil-dlp/.worktrees/dealer-logo
npm run power-cycle
```

**Step 2: Manual testing checklist**

- [ ] Login as test dealer (test-starter@claude.dev)
- [ ] Navigate to Basic Information editor
- [ ] Verify 2-column layout (domain settings left, logo upload right)
- [ ] Upload a logo image
- [ ] Verify preview displays correctly
- [ ] Click Publish
- [ ] View the public dealer page
- [ ] Verify logo appears left of business name in header Row 2
- [ ] Verify logo is left-aligned with AMSOIL logo in Row 1
- [ ] Test removing the logo
- [ ] Verify page displays correctly without logo

**Step 3: Final commit**

```bash
git add -A
git commit -m "Complete dealer logo feature implementation"
```

---

## Summary of Changes

| File | Change |
|------|--------|
| `prisma/schema.prisma` | Add `logoUrl String?` field |
| `types/dealer.ts` | Add `logoUrl` to DealerInfo interface |
| `lib/dealer-validation.ts` | Add `logoUrl` to DealerUpdateData and FIELD_MAX_LENGTHS |
| `app/api/dealer/update/route.ts` | Handle `logoUrl` in update logic |
| `hooks/useImageUpload.ts` | Add `'logo'` to UploadContext type |
| `app/dashboard/editor/LogoUpload.tsx` | New component for logo upload UI |
| `app/dashboard/editor/EditorForm.tsx` | 2-column layout, integrate LogoUpload |
| `components/DealerTemplate.tsx` | Display logo in header Row 2 |
| `public/assets/dealer-template.css` | Styles for `.dealer-name-row` and `.dealer-logo` |
