# Create Product — Complete Implementation Reference
## Heart of the Project — DO NOT MODIFY WITHOUT FULL UNDERSTANDING

**File:** `src/app/admin/products/page.tsx` (2143 lines)
**API Routes:** `src/app/api/products/route.ts` (148 lines), `src/app/api/products/upload/route.ts` (84 lines), `src/app/api/products/article/route.ts`
**Components:** `RichTextToolbar`, `ContentIntelligencePreview`

---

## 1. TYPE DEFINITIONS (Lines 7-34)

```typescript
type Faq = {
  question: string;
  answer: string;
};

type Product = {
  id: string;
  name: string;
  slug: string;
  description: string;       // Short description for cards
  price: number;
  image: string;             // URL string (local blob or server path)
  brand: string;
  isAffiliate: boolean;      // Always true for admin-created products
  affiliateLink: string;
  categoryId: string;        // 'cat_supps' | 'cat_equip'
  badge: string;             // '' | 'Most Popular' | 'New'
  productDescription: string; // Detailed description / SEO content
  publishStatus: string;     // 'Draft' | 'Published' | 'Disabled'
  commission: number;        // Default 10
  revenue: number;           // Default 0
  cookieLife: number;        // Default 30 (days)
  faqs: Faq[];               // Array of { question, answer }
  keywords: string;          // Comma-separated keywords
  partnerLink?: string;      // Affiliate URL
  articleContent?: string;   // Rich text article HTML
  category?: string;         // Optional display name
};
```

## 2. EMPTY PRODUCT DEFAULTS (Lines 36-55)

```typescript
const emptyProduct = {
  id: '', name: '', slug: '', description: '', price: 0, image: '',
  brand: '', isAffiliate: true, affiliateLink: '', categoryId: 'cat_supps',
  badge: '', productDescription: '', publishStatus: 'Draft',
  commission: 10, revenue: 0, cookieLife: 30, faqs: [], keywords: '',
};
```

## 3. ALL STATE VARIABLES (Lines 146-172)

| Variable | Type | Default | Purpose |
|---|---|---|---|
| `products` | `Product[]` | `[]` | Full product list from API |
| `editingProduct` | `Product \| null` | `null` | Product being edited (opens Edit modal) |
| `isCreating` | `boolean` | `false` | Toggles Create Product modal |
| `newProduct` | `Product` | `{...emptyProduct}` | Form data for new product |
| `isLoading` | `boolean` | `true` | Loading spinner for product table |
| `imagePreview` | `string` | `''` | Local blob URL for image preview |
| `isGenerating` | `boolean` | `false` | AI article generation loading state |
| `isUploading` | `boolean` | `false` | Image upload loading state |
| `uploadProgress` | `number` | `0` | Upload progress percentage (0-100) |
| `showCreatePreview` | `boolean` | `false` | Toggles Create Preview modal |
| `showEditPreview` | `boolean` | `false` | Toggles Edit Preview modal |
| `currentPage` | `number` | `1` | Pagination page number |
| `categoryFilter` | `string` | `'all'` | Filter: 'all' \| 'cat_supps' \| 'cat_equip' |
| `articleSaved` | `Record<string, boolean>` | `{}` | Per-product article save status |
| `isSavingArticle` | `boolean` | `false` | Article save loading state |
| `isSavingPreview` | `boolean` | `false` | Preview save loading state |
| `selectedProduct` | `Product \| null` | `null` | Product detail modal |
| `previewProductId` | `string \| null` | `null` | ID of product being previewed |
| `previewMode` | `'card' \| 'detail'` | `'card'` | Preview view toggle |
| `uploadedImages` | `string[]` | `[]` | URLs for RichTextToolbar image picker |
| `showContentIntelligence` | `boolean` | `false` | Toggles Content Intelligence modal |
| `localCreateSeoDesc` | `string` | `''` | Local SEO text for Create modal |
| `localEditSeoDesc` | `string` | `''` | Local SEO text for Edit modal |

**Constants:** `itemsPerPage = 6`

## 4. COMPUTED VALUES (Lines 174-187)

```typescript
filteredProducts = categoryFilter === 'all' ? products : products.filter(p => p.categoryId === categoryFilter)
totalPages = Math.ceil(filteredProducts.length / itemsPerPage)
paginatedProducts = filteredProducts.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage)
```

## 5. HELPER FUNCTIONS

### `getProductColor(id: string): string` (Lines 57-71)
Returns a Tailwind color class based on string hash. 8 colors: emerald, blue, amber, rose, indigo, teal, orange, pink.

### `getInitials(name: string): string` (Lines 73-75)
Returns first 2 uppercase letters from name. E.g., "Gold Standard Whey" → "GS".

### `generateSEOBlogPost(...)` (Lines 78-139)
**LEGACY — NOT USED BY "Generate Article" BUTTON.** This is a client-side template function that generates generic HTML. The "Generate Article" button now calls `generateArticleWithAI()` which uses the Content Intelligence API (DeepSeek + Tavily). Keep this function for backward compatibility but it is NOT the active generation path.

### `slugify(text: string): string` (Lines 141-143)
Converts text to URL-safe slug. E.g., "Gold Standard" → "gold-standard".

### `getStatusBadge(status: string): string` (Lines 756-767)
Returns Tailwind classes for status badges:
- `Published` → `bg-emerald-50 text-emerald-600`
- `Draft` → `bg-amber-50 text-amber-600`
- `Disabled` → `bg-red-50 text-red-600`

## 6. API FUNCTIONS

### `fetchProducts()` (Lines 211-224)
- `GET /api/products` → sets `products` state
- Called on mount via `useEffect([], [])`

### `handleCreate(e)` (Lines 226-257)
- **Trigger:** Submit of `#create-product-form`
- **Payload:** `{ ...newProduct, productDescription: localCreateSeoDesc, slug: slugify(newProduct.name), price: parseFloat, stock: 0, isAffiliate: true }`
- **Endpoint:** `POST /api/products`
- **On success:** Closes modal, resets `newProduct` to `emptyProduct`, clears `imagePreview`, hides preview, refetches products
- **On error:** Parses JSON error, shows alert

### `handleUpdate(e)` (Lines 259-286)
- **Trigger:** Submit of `#edit-product-form`
- **Payload:** `{ ...editingProduct, productDescription: localEditSeoDesc, slug: slugify(editingProduct.name), price: parseFloat }`
- **Endpoint:** `PUT /api/products`
- **On success:** Closes edit modal, refetches products

### `handleDelete(id)` (Lines 288-299)
- **Trigger:** Delete button click (with `confirm()` dialog)
- **Endpoint:** `DELETE /api/products?id=${id}`
- **On success:** Refetches products, clears edit modal if deleted product was being edited

### `uploadImage(file, isEdit)` (Lines 302-362)
- **Trigger:** Called by `handleImageUpload()` after file selection
- **Flow:**
  1. Sets `isUploading=true`, `uploadProgress=0`
  2. Starts interval simulating progress (0→80% in 1.6s)
  3. Creates `FormData` with file
  4. `POST /api/products/upload` with `formData`
  5. On success: sets `uploadProgress=100`, adds URL to `uploadedImages[]`, updates `editingProduct.image` or `newProduct.image` + `imagePreview`
  6. On error: tries JSON parse, falls back to text, shows alert
  7. Finally: `isUploading=false`, `uploadProgress=0`

### `handleImageUpload(e, isEdit)` (Lines 364-377)
- **Trigger:** File input `onChange`
- **Flow:**
  1. Gets file from `e.target.files[0]`
  2. Creates local blob URL via `URL.createObjectURL(file)` for instant preview
  3. Updates `editingProduct.image` or `newProduct.image` + `imagePreview` with blob URL
  4. Calls `uploadImage(file, isEdit)` to upload to server (so image appears in RichTextToolbar picker)

### `generateArticleWithAI(product, isEdit, mode)` (Lines 380-432)
- **Trigger:** "Generate Article" button click in Create or Edit modal
- **Flow:**
  1. Sets `isGenerating=true`
  2. `POST /api/content-intelligence/generate` with `{ productName, brand, category, shortDescription, keywords, faqs, preset, variant }`
  3. **Error handling:** Checks `!res.ok` first, tries JSON parse, falls back to text, throws meaningful error
  4. On success: extracts `data.content.articleHtml`, sets `productDescription` and `articleContent` on product, updates local SEO desc
  5. On error: shows alert with error message
  6. Finally: `isGenerating=false`

### `handleSaveArticle(productId, description)` (Lines 435-463)
- **Trigger:** Save Article button in RichTextToolbar
- **Endpoint:** `POST /api/products/article` with `{ productId, productDescription, richTextState }`
- Sets `articleSaved[productId] = true` on success

### `handleEditArticle(productId)` (Lines 465-467)
- Sets `articleSaved[productId] = false` to re-enable editing

### `handleContentIntelligenceGenerated(content, title)` (Lines 470-474)
- **Trigger:** Content Intelligence modal generates content
- Sets `localCreateSeoDesc` and `newProduct.productDescription`, closes Content Intelligence modal

### `handlePreview()` (Lines 477-547)
- **Trigger:** "Preview" button in Create modal
- **Flow:**
  1. Validates product name is not empty
  2. Creates payload with timestamped slug (to avoid UNIQUE constraint)
  3. If `newProduct.id` exists → `PUT /api/products` (UPDATE)
  4. If no `newProduct.id` → `POST /api/products` (INSERT)
  5. On success: updates `newProduct.id` with response ID, sets `previewProductId`, opens preview modal, refetches products
  6. On error: tries JSON parse, falls back to text, shows alert

## 7. EXPORT FUNCTIONS

### `resolveImageUrl(url)` (Lines 550-555)
Converts relative URLs to absolute by prepending `window.location.origin`.

### `resolveContentImages(html)` (Lines 558-562)
Regex replaces all `<img src="...">` with resolved absolute URLs.

### `exportAsPDF()` (Lines 565-669)
- Opens new window, writes HTML with:
  - Hero section: gradient background (emerald-900 to teal-900), product image, badge, name, brand, divider, description, price, "Free shipping"
  - Article content with ProseMirror-matching CSS (blockquotes, code blocks, lists, headings, links, bold/italic/underline)
  - Footer with "Generated from Marich Wellness Admin" + date
  - `window.print()` on load → browser's Save as PDF dialog

### `exportAsWord()` (Lines 671-754)
- Generates Word-compatible HTML with proper XML namespace headers
- Same hero + article layout as PDF
- Triggers `.doc` download via `document.execCommand` or blob download

## 8. PREVIEW COMPONENTS

### `ProductCardPreview({ product })` (Lines 769-812)
- Card view matching shop grid: image/initials, favorite icon, badge, name, brand, price, "Shop at Partner" button
- Hover effects: scale image, emerald overlay, shadow
- **Image sizing:** `aspect-[4/3]` with `object-contain p-4` (full image visible, not cropped)
- **Card width:** Constrained to `max-w-sm mx-auto` for proper proportions
- **Initials placeholder:** `w-20 h-24` (reduced from `w-24 h-32`)

### `ProductDetailPreview({ product })` (Lines 814-861)
- Detail view: split layout (image left, details right)
- Status badge, name, brand, divider, description, price, "Shop at Partner" button
- **Image sizing:** `md:w-2/5` column with `max-w-[240px] max-h-[240px] object-contain` centered in flex layout with padding
- **HTML rendering:** Uses `<div dangerouslySetInnerHTML={{ __html: product.productDescription }} />` so HTML tags render as formatted content (headings, lists, bold, italic, etc.) instead of showing raw tags
- **Text sizes:** h2 at `text-2xl`, price at `text-2xl`, brand at `text-base`, description at `text-sm`

## 9. UI LAYOUT — CREATE PRODUCT MODAL (Lines 1407-1693)

### Modal Container
- `fixed inset-0 z-50`, backdrop blur, `w-[95vw] max-w-[1600px]`, `height: 90vh`, `overflow: hidden`
- `rounded-[2rem]`, `animate-in fade-in zoom-in-95`

### Header (sticky, non-scrollable)
- "Create Product" title, close button (X)
- `border-b border-zinc-100`, `px-10 py-5`

### Form Body (scrollable)
- `overflow-y-auto`, `pb-4`, `space-y-6`
- **Grid:** `grid-cols-1 lg:grid-cols-5 gap-8`

#### LEFT COLUMN (lg:col-span-2) — Form Fields
| Field | Type | State | Notes |
|---|---|---|---|
| Product Name | text input | `newProduct.name` | Required |
| Brand | text input | `newProduct.brand` | Required |
| Category | select | `newProduct.categoryId` | 'cat_supps' \| 'cat_equip' |
| Price ($) | number input | `newProduct.price` | min=0, step=0.01, required |
| Commission (%) | number input | `newProduct.commission` | min=0, max=100 |
| Revenue ($) | number input | `newProduct.revenue` | min=0 |
| Cookie Life (days) | number input | `newProduct.cookieLife` | min=0 |
| Badge | select | `newProduct.badge` | '' \| 'Most Popular' \| 'New' |
| Link | url input | `newProduct.partnerLink` | Affiliate URL |
| Publish Status | select | `newProduct.publishStatus` | 'Draft' \| 'Published' \| 'Disabled' |
| Short Description | textarea | `newProduct.description` | rows=2 |
| Product Description (Detailed) | textarea | `newProduct.productDescription` | rows=3 |
| Key Words | textarea | `localCreateSeoDesc` | rows=3, comma-separated |

#### RIGHT COLUMN (lg:col-span-3)
**Row 1: Upload Image + FAQ (grid-cols-2)**
- **Upload Image:** `w-40 h-40` (aspect-ratio 1/1), dashed border, file input
  - States: empty (cloud_upload icon), uploading (spinner + progress bar), preview (object-contain p-2)
  - `onChange` → `handleImageUpload(e, false)`
- **FAQ:** textarea, one question per line, parsed into `{ question, answer: '' }` array
  - `rows=4`, dashed border, placeholder "Paste your FAQ questions here, one per line..."

**Row 2: Article Content (HTML) — full width**
- Label + "Generate Article" button (emerald-600, disabled when `isGenerating`)
- `RichTextToolbar` with `height: 400px`
  - Props: `value={newProduct.articleContent}`, `onChange`, `rows=16`, `uploadedImages`

### Footer (sticky bottom, non-scrollable)
- **Save Product** (primary bg, `form="create-product-form"`, `type="submit"`)
- **Preview** (emerald-600, calls `handlePreview()`)
- **Cancel** (border, resets all state)

## 10. UI LAYOUT — EDIT PRODUCT MODAL (Lines 1695-1963)

### Identical structure to Create modal with these differences:
- Title: "Edit Product"
- Form ID: `edit-product-form`
- State: `editingProduct` instead of `newProduct`
- SEO desc: `localEditSeoDesc` instead of `localCreateSeoDesc`
- Image upload: `handleImageUpload(e, true)` (isEdit=true)
- Generate Article: `generateArticleWithAI(editingProduct, true, 'detailed')`
- Footer: "Update Product" + "Cancel" (no Preview button)
- Close handler: `setEditingProduct(null)`, clears `imagePreview` and `localEditSeoDesc`

## 11. UI LAYOUT — PRODUCT TABLE (Lines 1280-1405)

### Header
- "Catalog Intelligence" title + "Curate and refine your affiliate wellness collection." subtitle
- Category filter dropdown (All Products / Supplements / Equipment) + Create Product button

### Table Columns
| Column | Content | Styling |
|---|---|---|
| Product ID | `PROD-001` format | `font-mono font-bold text-primary` |
| Product | Color avatar + name + brand | `product-name-cell`, `product-brand-cell` |
| Status | Badge pill | `getStatusBadge()` |
| Product Description | Truncated text | `line-clamp-1` |
| Price | `$XX` | `product-price-cell font-bold text-primary` |
| Commission% | `XX%` | `text-emerald-600 text-center` |
| Revenue | `$XX` | `text-primary text-center` |
| Cookie Life | `XXd` | `text-zinc-600 text-center` |
| Actions | Edit + Delete icons | `edit` / `delete` Material Symbols |

### Pagination
- "Showing X–Y of Z products"
- Previous/Next buttons + numbered page buttons
- Current page: `bg-primary text-on-primary`
- Other pages: `text-zinc-600 hover:bg-zinc-50`

## 12. UI LAYOUT — PREVIEW MODAL (Lines 2024-2119)

### Header
- "Product Preview" + product name
- Export buttons: PDF (red) + Word (blue)
- Close button (X)

### Body
- **Toggle:** Card View / Detail View buttons
- **Preview Content:** `ProductCardPreview` or `ProductDetailPreview`
- **Editable Article:** RichTextToolbar with `localCreateSeoDesc`
  - "Generate with AI" button → opens `ContentIntelligencePreview` modal

### Footer
- "Preview is based on saved draft data"
- "Back to Editor" button

## 13. UI LAYOUT — PRODUCT DETAIL MODAL (Lines 1965-2022)

- Image/initials (left) + details (right)
- Status badge, name, brand, divider, description, price
- Commission/Revenue/Cookie Life grid (3 columns)
- "Shop at Partner" link button

## 14. UI LAYOUT — CONTENT INTELLIGENCE MODAL (Lines 2121-2138)

```tsx
<ContentIntelligencePreview
  productName={newProduct.name}
  brand={newProduct.brand}
  category={newProduct.categoryId}
  shortDescription={newProduct.description}
  keywords={newProduct.keywords}
  faqs={newProduct.faqs}
  price={newProduct.price}
  image={newProduct.image || imagePreview}
  badge={newProduct.badge}
  productId={newProduct.id || previewProductId}
  existingContent={localCreateSeoDesc}
  onContentGenerated={handleContentIntelligenceGenerated}
  onClose={() => setShowContentIntelligence(false)}
/>
```

## 15. API ROUTE — `src/app/api/products/route.ts` (148 lines)

### `GET /api/products`
- **Query params:** `category`, `affiliate`, `page`, `limit`, `published`
- **SQL:** Joins `Product p` with `Category c`, filters by params, paginates with `LIMIT/OFFSET`
- **Returns:** `{ products: [...], pagination: { page, limit, total, totalPages } }`

### `POST /api/products`
- **Body:** `{ name, slug, description, price, image, brand, stock, categoryId, isAffiliate, affiliateLink, benefits, badge, productDescription, publishStatus, commission, revenue, cookieLife, faqs }`
- **SQL:** `INSERT INTO Product (...)` with all fields
- **ID generation:** UUID v4 (random hex pattern)
- **Returns:** `{ success: true, id }` (status 201)

### `PUT /api/products`
- **Body:** Same as POST + `id`
- **SQL:** `UPDATE Product SET ... WHERE id = ?`
- **Returns:** `{ success: true, id }`

### `DELETE /api/products`
- **Query params:** `id`
- **SQL:** `DELETE FROM Product WHERE id = ?`
- **Returns:** `{ success: true }`

## 16. API ROUTE — `src/app/api/products/upload/route.ts` (84 lines)

### `POST /api/products/upload`
- **Body:** `FormData` with `image` file
- **Validation:** File must be `image/*` type
- **Processing:**
  1. Try: Sharp resize to 800×800 (fit: 'inside', withoutEnlargement: true), convert to WebP (quality 75)
  2. Catch: Save original file with original extension (Sharp fallback for dev environments)
- **Storage:** `public/uploads/products/<uuid>.webp` (or original ext)
- **Returns:** `{ success: true, url: "/uploads/products/<uuid>.webp", filename, size }`

## 17. DATABASE SCHEMA — Product Table

```sql
CREATE TABLE Product (
  id VARCHAR(191) PRIMARY KEY,
  name VARCHAR(191) NOT NULL,
  slug VARCHAR(191) NOT NULL UNIQUE,
  description TEXT,
  price DECIMAL(10,2) NOT NULL DEFAULT 0,
  image VARCHAR(500),
  brand VARCHAR(191),
  stock INT DEFAULT 0,
  categoryId VARCHAR(191),
  isAffiliate BOOLEAN DEFAULT 0,
  affiliateLink VARCHAR(500),
  benefits TEXT,
  badge VARCHAR(50),
  productDescription TEXT,
  publishStatus VARCHAR(50) DEFAULT 'Draft',
  commission DECIMAL(5,2) DEFAULT 10.00,
  revenue DECIMAL(10,2) DEFAULT 0.00,
  cookieLife INT DEFAULT 30,
  faqs TEXT,  -- JSON stringified
  createdAt DATETIME,
  updatedAt DATETIME,
  FOREIGN KEY (categoryId) REFERENCES Category(id)
);
```

## 18. DATABASE SCHEMA — ProductArticle Table

```sql
CREATE TABLE ProductArticle (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  productId VARCHAR(191) UNIQUE,
  productDescription TEXT,
  richTextState TEXT,  -- JSON serialized
  createdAt DATETIME,
  updatedAt DATETIME,
  FOREIGN KEY (productId) REFERENCES Product(id)
);
```

## 19. DATA FLOW — Complete Create Product Lifecycle

```
1. User clicks "Create Product" → isCreating=true, modal opens
2. User fills form fields → newProduct state updates
3. User uploads image:
   a. handleImageUpload() → creates blob URL for instant preview
   b. uploadImage() → POST /api/products/upload → Sharp processes → returns URL
   c. URL added to uploadedImages[] for RichTextToolbar picker
4. User clicks "Generate Article":
   a. generateArticleWithAI() → POST /api/content-intelligence/generate
   b. DeepSeek crafts research query → 1 Tavily call → DeepSeek generates HTML
   c. HTML set to productDescription + articleContent
5. User clicks "Preview":
   a. handlePreview() → POST or PUT /api/products (saves draft)
   b. Preview modal opens with ProductCardPreview / ProductDetailPreview
   c. Editable RichTextToolbar with localCreateSeoDesc
   d. "Generate with AI" button opens ContentIntelligencePreview modal
6. User clicks "Save Product":
   a. handleCreate() → POST /api/products
   b. Modal closes, product list refreshes
```

## 20. CRITICAL RULES — DO NOT BREAK

1. **`localCreateSeoDesc` and `localEditSeoDesc`** exist to prevent parent re-renders on every keystroke. They sync to `newProduct.productDescription` / `editingProduct.productDescription` only on save/preview/generate.

2. **`handlePreview()` uses timestamped slugs** (`slugify(name) + '-' + Date.now()`) to avoid UNIQUE constraint violations on repeated preview clicks.

3. **Preview checks `newProduct.id`** — if it exists, uses PUT (UPDATE) instead of POST (INSERT) to avoid duplicate slug errors.

4. **Image upload creates BOTH a local blob URL** (for instant preview) AND uploads to server (for RichTextToolbar picker). Both paths must work.

5. **"Generate Article" button uses Content Intelligence API** (DeepSeek + Tavily), NOT the legacy `generateSEOBlogPost()` template function.

6. **Error handling in `generateArticleWithAI()`** checks `!res.ok` before `res.json()` to prevent `SyntaxError` crashes on non-JSON error responses.

7. **Modal uses `overflow: hidden`** on outer container and `overflow-y-auto` on inner form content. Action buttons are in a sticky bottom bar OUTSIDE the scrollable area.

8. **FAQ textarea** parses one question per line into `{ question, answer: '' }` array. Individual Q&A input fields were removed to prevent layout shift.

9. **`uploadedImages` prop** must be passed to ALL RichTextToolbar instances (Create, Edit, Preview) for the image picker to work.

10. **`onClose` in Create modal** resets `newProduct` to `emptyProduct`, clears `imagePreview` and `localCreateSeoDesc` to prevent stale data.

## 21. CATALOG INTELLIGENCE TABLE RULES — DO NOT BREAK

The Catalog Intelligence table (lines 1340-1395 in `src/app/admin/products/page.tsx`) has specific rendering rules that must be preserved:

### Column: Product (lines 1357-1370)
- **Thumbnail**: Shows `p.image` as `<img>` with `w-10 h-14 object-contain rounded-lg` if image exists
- **Fallback**: Shows colored initials square (`getProductColor(p.id)` + `getInitials(p.name)`) if no image
- **Category label**: Shows `p.categoryId === 'cat_supps' ? 'Supplement' : 'Equipment'` — NOT product name or brand

### Column: Product Description (lines 1376-1379)
- **Line 1**: Product Name (`p.name`) with `product-name-cell font-bold text-primary`
- **Line 2**: Brand Name (`p.brand`) with `product-brand-cell text-zinc-500 uppercase tracking-widest`
- **DO NOT** render `p.productDescription` or `p.description` here — they contain raw HTML

### Column: Revenue (lines 1384-1386)
- **Computed value**: `((p.price || 0) * ((p.commission || 0) / 100)).toFixed(2)`
- **DO NOT** use `p.revenue` — it's a static field that doesn't reflect actual earnings
- The calculation auto-updates when Price or Commission % changes in the edit modal

### Columns NOT to modify
- Product ID, Status, Price, Commission %, Cookie Life, Actions — must remain untouched
- Create/Edit modals — must remain untouched
- Preview modals — must remain untouched

---

*Last Updated: 2026-06-08 — This document is the canonical reference for the Create Product functionality.*
