cms.c2sgmbh/prompts/blogwoman-payload-development_1.md
Martin Porwoll 15f3fa2481 feat(BlogWoman): add tenant seed scripts and block migrations
- Add migration for BlogWoman page blocks (favorites-block, series-block,
  series-detail-block, featured-content-block) with all required columns
- Add seed scripts for BlogWoman tenant creation with full content:
  - 10 pages (Startseite, Über mich, Newsletter, etc.)
  - 7 blog posts
  - 9 series (GRFI, Investment-Piece, Pleasure P&L, etc.)
  - 4 categories, 10 tags, 1 author
  - Navigation, social links, cookie configuration
- Add Konzept-KI guide for AI-assisted tenant creation
- Add BlogWoman tenant prompt template

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 11:13:32 +00:00

14 KiB

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

// 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:

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

Badge Options:

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:

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

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:

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):

// 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

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

// 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

// 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

pnpm payload migrate:create blogwoman_collections

Schritt 7: Build & Test

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


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:

BLOGWOMAN_PAYLOAD_COMPLETE