# Quick Links Editor Design

**Date:** 2025-12-09
**Status:** Approved

## Overview

A "Quick Links" editor that allows dealers (Growth tier and above) to add up to 10 custom links displayed on their public dealer page. Links appear below the dealer info card in a two-column grid, auto-balanced by CSS, stacking to a single column on mobile.

### Key Characteristics

- **Flat list** - No hierarchy, drag-and-drop reordering
- **Max 10 links** - Auto-balanced across 2 columns
- **Both internal and external** - Same link type selector as nav editor
- **Open in new tab** - Defaults to checked
- **No default items** - Dealers start with empty list
- **Fixed header** - "Quick Links" displayed above the grid

### Editor Location

`/dashboard/cms/links`

## Database Schema

New `DealerLink` table:

```prisma
model DealerLink {
  id          String   @id @default(cuid())
  dealerId    String
  dealer      Dealer   @relation(fields: [dealerId], references: [id], onDelete: Cascade)

  label       String   @db.VarChar(50)
  linkType    String   @db.VarChar(20)  // "page" | "external"
  pageId      String?
  page        Page?    @relation(fields: [pageId], references: [id], onDelete: SetNull)
  externalUrl String?  @db.VarChar(500)
  openInNewTab Boolean @default(true)

  sortOrder   Int      @default(0)

  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  @@index([dealerId])
}
```

### Relations to Add

- `Dealer` model: `links DealerLink[]`
- `Page` model: `dealerLinks DealerLink[]`

## API Endpoints

### `/api/cms/links`

**GET** - Fetch dealer's links

- Auth required, Growth+ tier check
- Returns links ordered by `sortOrder`
- Includes page slug/title for internal links

**PUT** - Save all links

- Receives full array of links
- Validates: max 10 items, label length, URL format, link type
- Deletes removed items, upserts existing/new items
- Updates `sortOrder` based on array position

### Response Shape (GET)

```typescript
{
  links: Array<{
    id: string;
    label: string;
    linkType: 'page' | 'external';
    pageId: string | null;
    pageSlug: string | null;
    externalUrl: string | null;
    openInNewTab: boolean;
    sortOrder: number;
  }>;
}
```

## Editor UI

Split-screen layout with live preview:

```
┌────────────────────────────────────────────────────────────────┐
│  Quick Links                                         [Save]    │
│  Manage links displayed on your dealer page                    │
├─────────────────────────────────┬──────────────────────────────┤
│  EDITOR                         │  PREVIEW                     │
│                                 │                              │
│  ┌───────────────────────────┐  │  Quick Links                 │
│  │ ☰ ▲▼  Link 1    [Edit][×] │  │  ┌─────────┬─────────┐      │
│  ├───────────────────────────┤  │  │ • Link 1│ • Link 4│      │
│  │ ☰ ▲▼  Link 2    [Edit][×] │  │  │ • Link 2│ • Link 5│      │
│  ├───────────────────────────┤  │  │ • Link 3│ • Link 6│      │
│  │ ☰ ▲▼  Link 3    [Edit][×] │  │  └─────────┴─────────┘      │
│  └───────────────────────────┘  │                              │
│                                 │  [Desktop ▼]                 │
│  [+ Add Link]          3/10    │                              │
│                                 │                              │
│  Distribution: [3 | 3]         │                              │
└─────────────────────────────────┴──────────────────────────────┘
```

### Editor Features

- **Single sortable list** - dnd-kit with `verticalListSortingStrategy`
- **Up/down arrow buttons** - WCAG 2.2 compliant keyboard alternative
- **Live preview panel** - Shows two-column balanced output in real-time
- **Distribution indicator** - Shows "3 | 3" so users understand the column split
- **Responsive preview toggle** - Desktop (2-col) vs Mobile (1-col) view
- **Counter** - "X/10" links used

### Edit Modal Fields

- Link Type selector: Page | External URL
- Page dropdown (if page) or URL input (if external)
- Label text input
- "Open in new tab" checkbox (checked by default)

### Accessibility

- Drag handle + arrow buttons for reordering
- Keyboard: Tab to focus, Space to grab, Arrow keys to move, Space to drop
- Screen reader announcements via live region

## Public Display

Location: Below dealer info card on dealer's public page.

### Desktop (2 columns, CSS balanced)

```
┌─────────────────────────────────────────────────┐
│                  Quick Links                     │
├────────────────────────┬────────────────────────┤
│  • Shop AMSOIL Online  │  • Preferred Customer  │
│  • Product Lookup      │  • Free Shipping       │
│  • Equipment Guide     │  • Contact Me          │
└────────────────────────┴────────────────────────┘
```

### Mobile (single column)

```
┌─────────────────────┐
│    Quick Links      │
├─────────────────────┤
│ • Shop AMSOIL Online│
│ • Product Lookup    │
│ • Equipment Guide   │
│ • Preferred Customer│
│ • Free Shipping     │
│ • Contact Me        │
└─────────────────────┘
```

### CSS Implementation

```css
.quick-links-grid {
  column-count: 2;
  column-fill: balance;
  column-gap: 2rem;
}

@media (max-width: 640px) {
  .quick-links-grid {
    column-count: 1;
  }
}
```

### Display Rules

- Section only renders if dealer has at least 1 link
- Matches existing dealer page typography/colors
- Links inherit site's link styling

## Validation Rules

| Rule         | Constraint                           |
| ------------ | ------------------------------------ |
| Max links    | 10 total                             |
| Label        | 1-50 characters, required            |
| Link type    | "page" or "external" (no "none")     |
| External URL | Valid format, max 500 chars          |
| Page link    | Must reference dealer's own page     |
| Duplicates   | Warn on duplicate URLs (don't block) |

## File Structure

```
app/
  dashboard/cms/links/
    page.tsx              # Main editor page
  api/cms/links/
    route.ts              # GET/PUT endpoints

components/cms/
  LinkEditor.tsx          # Sortable list component
  LinkEditModal.tsx       # Add/edit modal
  LinkPreview.tsx         # Live preview panel

lib/cms/
  link-validation.ts      # Validation logic

prisma/
  schema.prisma           # DealerLink model (migration)
```

## Access Control

- **Tier requirement:** Growth and above
- Checked at: API route and page load
- Lower tiers see upgrade prompt

## Implementation Notes

### Patterns to Reuse from Nav Editor

- dnd-kit setup (simpler - no tree, just sortable list)
- Modal-based editing with link type selector
- Page fetching for internal link dropdown
- Real-time validation
- Save/publish flow

### Key Differences from Nav Editor

- No hierarchy (flat list vs. tree)
- No default items to protect
- No depth constraints
- Simpler validation rules
- Live preview panel (new)

### UX Research Findings

Based on research from 30+ sources including WordPress Gutenberg, Webflow, Shopify, and Nielsen Norman Group:

- **Auto-balance is industry standard** - CSS `column-fill: balance` prevents uneven layouts
- **"Guardrails, not cages"** - Smart defaults with option to reorder
- **Live preview is table stakes** - Users need to see what they're getting
- **Accessibility is critical** - WCAG 2.2 requires non-drag alternatives (arrow buttons)
