# BlogWoman.de - Payload CMS Erweiterungen

**Projekt:** cms.c2sgmbh (Payload CMS Multi-Tenant)
**Ziel:** Neue Collections und Blocks für blogwoman.de
**Server:** sv-payload (10.10.181.100) / Production: Hetzner 3 (162.55.85.18)
**Repository:** github.com/complexcaresolutions/cms.c2sgmbh

---

## Kontext

Das Payload CMS Multi-Tenant-System wird um Features für blogwoman.de erweitert.
BlogWoman ist ein Lifestyle-Blog für berufstätige Frauen mit Fokus auf:
- Affiliate-Produkte (Favoriten)
- YouTube-Serien mit eigenem Branding
- Newsletter-Conversions
- SEO-optimierter Blog-Content

**Tenant:** blogwoman (muss in Tenants Collection angelegt werden, ID wird dynamisch)

---

## Bestehende Infrastruktur

### Vorhandene Collections (NICHT ändern)
- Users, Tenants, Media, Pages, Posts, Categories
- Testimonials, FAQs, Team, Services
- NewsletterSubscribers, Videos, VideoCategories
- CookieConfigurations, CookieInventory, ConsentLogs
- AuditLogs, FormSubmissions

### Vorhandene Blocks (NICHT ändern)
- HeroBlock, HeroSliderBlock, TextBlock, ImageTextBlock
- CardGridBlock, QuoteBlock, CTABlock, VideoBlock
- PostsListBlock, TestimonialsBlock, NewsletterBlock
- FAQBlock, TeamBlock, ServicesBlock, TimelineBlock
- ProcessStepsBlock, DividerBlock, ContactFormBlock

### Tech Stack
- Payload CMS 3.69.0
- Next.js 15.5.9
- React 19.2.3
- PostgreSQL 17
- TypeScript
- pnpm

---

## Phase 1: Favorites Collection & Block

### 1.1 Collection: `favorites`

**Datei:** `src/collections/Favorites.ts`

```typescript
// Struktur:
import type { CollectionConfig } from 'payload'

export const Favorites: CollectionConfig = {
  slug: 'favorites',
  admin: {
    group: 'Content',
    useAsTitle: 'title',
    defaultColumns: ['title', 'category', 'featured', 'isActive'],
  },
  access: {
    read: () => true, // Public, aber Tenant-gefiltert
    create: ({ req: { user } }) => !!user,
    update: ({ req: { user } }) => !!user,
    delete: ({ req: { user } }) => !!user,
  },
  fields: [
    // ... siehe Feld-Definition unten
  ],
}
```

**Felder:**

| Feld | Typ | Config | Pflicht |
|------|-----|--------|---------|
| `title` | text | maxLength: 200 | required |
| `slug` | text | unique, admin.position: 'sidebar' | required |
| `description` | textarea | maxLength: 300 | - |
| `category` | select | options: siehe unten | required |
| `subcategory` | text | maxLength: 100 | - |
| `price` | number | min: 0 | - |
| `priceRange` | select | budget, mid, premium, luxury | - |
| `affiliateUrl` | text | URL-Validierung | required |
| `affiliateNetwork` | select | amazon, awin, ltk, direct, other | - |
| `image` | upload | relationTo: 'media' | required |
| `badge` | select | options: siehe unten | - |
| `featured` | checkbox | defaultValue: false | - |
| `isActive` | checkbox | defaultValue: true | required |
| `order` | number | defaultValue: 0 | - |
| `tenant` | relationship | relationTo: 'tenants' | required |

**Category Options:**
```typescript
const categoryOptions = [
  { label: 'Fashion', value: 'fashion' },
  { label: 'Beauty', value: 'beauty' },
  { label: 'Travel', value: 'travel' },
  { label: 'Tech', value: 'tech' },
  { label: 'Home', value: 'home' },
]
```

**Badge Options:**
```typescript
const badgeOptions = [
  { label: 'Investment Piece', value: 'investment-piece' },
  { label: 'Daily Driver', value: 'daily-driver' },
  { label: 'GRFI Approved', value: 'grfi-approved' },
  { label: 'Neu', value: 'new' },
  { label: 'Bestseller', value: 'bestseller' },
]
```

**Price Range Options:**
```typescript
const priceRangeOptions = [
  { label: 'Budget (< €50)', value: 'budget' },
  { label: 'Mid (€50-150)', value: 'mid' },
  { label: 'Premium (€150-500)', value: 'premium' },
  { label: 'Luxury (> €500)', value: 'luxury' },
]
```

### 1.2 Block: `favorites-block`

**Datei:** `src/blocks/FavoritesBlock.ts`

```typescript
import type { Block } from 'payload'

export const FavoritesBlock: Block = {
  slug: 'favorites-block',
  labels: {
    singular: 'Favoriten',
    plural: 'Favoriten',
  },
  fields: [
    // ... siehe unten
  ],
}
```

**Block-Felder:**

| Feld | Typ | Default |
|------|-----|---------|
| `title` | text | - |
| `subtitle` | text | - |
| `category` | select | 'all' (+ alle category options) |
| `showFeaturedOnly` | checkbox | false |
| `limit` | number | 8 |
| `layout` | select | 'grid' (grid, list, carousel) |
| `columns` | select | '4' (2, 3, 4) |
| `showPrice` | checkbox | true |
| `showBadge` | checkbox | true |
| `backgroundColor` | select | 'white' |

---

## Phase 2: Series Collection & Blocks

### 2.1 Collection: `series`

**Datei:** `src/collections/Series.ts`

**Felder:**

| Feld | Typ | Config | Pflicht |
|------|-----|--------|---------|
| `title` | text | localized: true | required |
| `slug` | text | unique | required |
| `tagline` | text | localized: true, maxLength: 150 | - |
| `description` | richText | localized: true | - |
| `logo` | upload | relationTo: 'media' | - |
| `coverImage` | upload | relationTo: 'media' | - |
| `brandColor` | text | placeholder: '#B08D57' | - |
| `accentColor` | text | placeholder: '#FFFFFF' | - |
| `youtubePlaylistId` | text | - | - |
| `youtubePlaylistUrl` | text | URL-Validierung | - |
| `order` | number | defaultValue: 0 | - |
| `isActive` | checkbox | defaultValue: true | required |
| `tenant` | relationship | relationTo: 'tenants' | required |

**Admin Config:**
```typescript
admin: {
  group: 'Content',
  useAsTitle: 'title',
  defaultColumns: ['title', 'slug', 'brandColor', 'isActive'],
}
```

### 2.2 Block: `series-block`

**Datei:** `src/blocks/SeriesBlock.ts`

**Felder:**

| Feld | Typ | Default |
|------|-----|---------|
| `title` | text | - |
| `subtitle` | text | - |
| `layout` | select | 'grid' (grid, list, featured) |
| `showDescription` | checkbox | true |
| `showLogo` | checkbox | true |
| `limit` | number | 6 |
| `columns` | select | '3' (2, 3, 4) |
| `backgroundColor` | select | 'white' |

### 2.3 Block: `series-detail-block`

**Datei:** `src/blocks/SeriesDetailBlock.ts`

Für Serien-Einzelseiten mit Hero und Content.

**Felder:**

| Feld | Typ | Default |
|------|-----|---------|
| `series` | relationship | relationTo: 'series' |
| `showHero` | checkbox | true |
| `showDescription` | checkbox | true |
| `showRelatedPosts` | checkbox | true |
| `relatedPostsLimit` | number | 6 |
| `layout` | select | 'full' (full, compact) |

---

## Phase 3: Video Embed Block

### 3.1 Block: `video-embed-block`

**Datei:** `src/blocks/VideoEmbedBlock.ts`

**Felder:**

| Feld | Typ | Config |
|------|-----|--------|
| `title` | text | optional |
| `videoSource` | select | youtube, vimeo, custom |
| `youtubeUrl` | text | condition: videoSource === 'youtube' |
| `vimeoUrl` | text | condition: videoSource === 'vimeo' |
| `customUrl` | text | condition: videoSource === 'custom' |
| `thumbnail` | upload | relationTo: 'media', optional |
| `caption` | text | localized: true |
| `privacyMode` | checkbox | defaultValue: true |
| `aspectRatio` | select | '16:9' (16:9, 4:3, 1:1, 9:16) |
| `maxWidth` | select | 'large' (full, large, medium, small) |
| `lazyLoad` | checkbox | defaultValue: true |

**YouTube URL Parser (Utility):**
```typescript
// src/lib/utils/youtube.ts
export function extractYouTubeId(url: string): string | null {
  if (!url) return null
  const patterns = [
    /youtube\.com\/watch\?v=([^&]+)/,
    /youtu\.be\/([^?]+)/,
    /youtube\.com\/embed\/([^?]+)/,
    /youtube\.com\/shorts\/([^?]+)/,
  ]
  for (const pattern of patterns) {
    const match = url.match(pattern)
    if (match) return match[1]
  }
  return null
}

export function getYouTubeEmbedUrl(videoId: string, privacyMode = true): string {
  const domain = privacyMode ? 'www.youtube-nocookie.com' : 'www.youtube.com'
  return `https://${domain}/embed/${videoId}`
}

export function getYouTubeThumbnail(videoId: string, quality: 'default' | 'hq' | 'maxres' = 'hq'): string {
  const qualityMap = {
    default: 'default',
    hq: 'hqdefault',
    maxres: 'maxresdefault',
  }
  return `https://img.youtube.com/vi/${videoId}/${qualityMap[quality]}.jpg`
}
```

---

## Phase 4: Stats Block

### 4.1 Block: `stats-block`

**Datei:** `src/blocks/StatsBlock.ts`

**Felder:**

| Feld | Typ | Config |
|------|-----|--------|
| `title` | text | optional |
| `subtitle` | text | optional |
| `stats` | array | siehe Stats Item |
| `layout` | select | 'row' (row, grid, cards) |
| `columns` | select | '4' (2, 3, 4, 5) |
| `animated` | checkbox | defaultValue: false |
| `backgroundColor` | select | 'white' (white, ivory, sand, dark) |

**Stats Array Item:**

| Feld | Typ | Config |
|------|-----|--------|
| `value` | text | required, z.B. "42.000" |
| `prefix` | text | optional, z.B. "€", ">" |
| `suffix` | text | optional, z.B. "+", "%", "K" |
| `label` | text | required, localized |
| `description` | text | optional, localized |
| `icon` | text | optional, Lucide icon name |

---

## Phase 5: Featured Content Block

### 5.1 Block: `featured-content-block`

**Datei:** `src/blocks/FeaturedContentBlock.ts`

**Felder:**

| Feld | Typ | Config |
|------|-----|--------|
| `title` | text | localized |
| `subtitle` | text | localized |
| `items` | array | siehe Items Array |
| `layout` | select | 'grid' (grid, carousel, list, featured-grid) |
| `columns` | select | '3' (2, 3, 4) |
| `showDates` | checkbox | defaultValue: true |
| `backgroundColor` | select | 'white' |

**Items Array:**

| Feld | Typ | Config |
|------|-----|--------|
| `itemType` | select | required: post, video, series, external |
| `post` | relationship | relationTo: 'posts', condition: itemType === 'post' |
| `video` | relationship | relationTo: 'videos', condition: itemType === 'video' |
| `series` | relationship | relationTo: 'series', condition: itemType === 'series' |
| `externalTitle` | text | condition: itemType === 'external' |
| `externalUrl` | text | condition: itemType === 'external' |
| `externalImage` | upload | relationTo: 'media', condition: itemType === 'external' |
| `externalDescription` | textarea | condition: itemType === 'external' |
| `customLabel` | text | optional, z.B. "NEU", "TRENDING" |

---

## Implementierungs-Reihenfolge

### Schritt 1: Collections erstellen
1. `src/collections/Favorites.ts`
2. `src/collections/Series.ts`

### Schritt 2: Collections in payload.config.ts registrieren
```typescript
// In payload.config.ts
import { Favorites } from './collections/Favorites'
import { Series } from './collections/Series'

// In collections Array hinzufügen:
collections: [
  // ... bestehende
  Favorites,
  Series,
],
```

### Schritt 3: Blocks erstellen
1. `src/blocks/FavoritesBlock.ts`
2. `src/blocks/SeriesBlock.ts`
3. `src/blocks/SeriesDetailBlock.ts`
4. `src/blocks/VideoEmbedBlock.ts`
5. `src/blocks/StatsBlock.ts`
6. `src/blocks/FeaturedContentBlock.ts`

### Schritt 4: Blocks in Pages registrieren
```typescript
// In src/collections/Pages.ts -> layout field -> blocks array
import { FavoritesBlock } from '../blocks/FavoritesBlock'
import { SeriesBlock } from '../blocks/SeriesBlock'
import { SeriesDetailBlock } from '../blocks/SeriesDetailBlock'
import { VideoEmbedBlock } from '../blocks/VideoEmbedBlock'
import { StatsBlock } from '../blocks/StatsBlock'
import { FeaturedContentBlock } from '../blocks/FeaturedContentBlock'

// blocks: [
//   ... bestehende,
//   FavoritesBlock,
//   SeriesBlock,
//   SeriesDetailBlock,
//   VideoEmbedBlock,
//   StatsBlock,
//   FeaturedContentBlock,
// ]
```

### Schritt 5: Utility-Funktionen erstellen
1. `src/lib/utils/youtube.ts`

### Schritt 6: Migration erstellen
```bash
pnpm payload migrate:create blogwoman_collections
```

### Schritt 7: Build & Test
```bash
pnpm lint --fix
pnpm build
```

---

## Erfolgskriterien

### Funktional
- [ ] Favorites Collection: CRUD über Admin Panel funktioniert
- [ ] Favorites: Filterung nach Kategorie in API (`/api/favorites?where[category][equals]=fashion`)
- [ ] Favorites: Tenant-Isolation funktioniert
- [ ] Favorites Block: In Pages verwendbar
- [ ] Series Collection: CRUD funktioniert
- [ ] Series: Localization (de/en) funktioniert
- [ ] Series Block: In Pages verwendbar
- [ ] Series Detail Block: Zeigt Serie mit related Posts
- [ ] Video Embed Block: YouTube-URLs werden korrekt geparst
- [ ] Video Embed Block: Privacy Mode (youtube-nocookie) funktioniert
- [ ] Stats Block: Zeigt Statistiken in verschiedenen Layouts
- [ ] Featured Content Block: Verschiedene Content-Typen mischbar

### Technisch
- [ ] `pnpm lint` ohne Errors
- [ ] `pnpm build` erfolgreich
- [ ] Keine TypeScript-Fehler
- [ ] Migration erstellt und ausführbar
- [ ] API-Endpoints erreichbar:
  - GET /api/favorites
  - GET /api/series

### Admin UX
- [ ] Collections erscheinen unter "Content" Gruppe
- [ ] Blocks haben deutsche Labels
- [ ] Conditional Fields funktionieren (z.B. bei itemType)

---

## Escape Hatch

Falls nach 20+ Iterationen blockiert:

1. **Dokumentiere in BLOCKERS.md:**
   - Was genau nicht funktioniert
   - Fehlermeldungen (vollständig)
   - Versuchte Lösungsansätze

2. **Teile das Problem auf:**
   - Nur Collections ohne Blocks
   - Nur einen Block isoliert

3. **Prüfe Abhängigkeiten:**
   - Ist Payload-Version kompatibel?
   - Fehlen Import-Statements?
   - Gibt es Naming-Konflikte?

---

## Referenzen

### Bestehende Collection als Vorlage
- `src/collections/Posts.ts` (für Struktur)
- `src/collections/Testimonials.ts` (für einfache Collection)
- `src/collections/Videos.ts` (falls vorhanden)

### Bestehende Blocks als Vorlage
- `src/blocks/PostsListBlock.ts`
- `src/blocks/TestimonialsBlock.ts`
- `src/blocks/CardGridBlock.ts`

### Payload Docs
- Collections: https://payloadcms.com/docs/configuration/collections
- Blocks: https://payloadcms.com/docs/fields/blocks
- Conditional Logic: https://payloadcms.com/docs/fields/overview#conditional-logic

---

## Verzeichnis

```
src/
├── collections/
│   ├── Favorites.ts          # NEU
│   └── Series.ts             # NEU
├── blocks/
│   ├── FavoritesBlock.ts     # NEU
│   ├── SeriesBlock.ts        # NEU
│   ├── SeriesDetailBlock.ts  # NEU
│   ├── VideoEmbedBlock.ts    # NEU
│   ├── StatsBlock.ts         # NEU
│   └── FeaturedContentBlock.ts # NEU
├── lib/
│   └── utils/
│       └── youtube.ts        # NEU
└── migrations/
    └── YYYYMMDD_HHMMSS_blogwoman_collections.ts # NEU (auto-generiert)
```

---

## Fertig?

Wenn ALLE Erfolgskriterien erfüllt sind:
- Alle Collections erstellt und registriert
- Alle Blocks erstellt und in Pages registriert
- `pnpm lint` ohne Errors
- `pnpm build` erfolgreich
- Migration erstellt

Dann schreibe als letzte Zeile:

<promise>BLOGWOMAN_PAYLOAD_COMPLETE</promise>