mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 17:24:12 +00:00
feat(BlogWoman): add Favorites, Series collections and content blocks
Add new collections and blocks for BlogWoman affiliate and video content: Collections: - Favorites: Affiliate products with categories, badges, and price ranges - Series: YouTube series with custom branding (logo, colors) Blocks: - FavoritesBlock: Grid/list/carousel display for affiliate products - SeriesBlock: Series overview with filtering - SeriesDetailBlock: Single series page with hero - VideoEmbedBlock: YouTube/Vimeo embed with privacy mode - FeaturedContentBlock: Curated mixed-content collections Also includes documentation updates for deployment and API guides. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
68032a4bf8
commit
3ccb8bd585
19 changed files with 1816 additions and 150 deletions
147
CLAUDE.md
147
CLAUDE.md
|
|
@ -704,12 +704,14 @@ SELECT * FROM audit_logs ORDER BY created_at DESC LIMIT 10;
|
|||
\dt -- Alle Tabellen
|
||||
```
|
||||
|
||||
## Blocks Übersicht
|
||||
## Blocks Übersicht (42 Blocks)
|
||||
|
||||
### Core Blocks
|
||||
| Block | Slug | Beschreibung |
|
||||
|-------|------|--------------|
|
||||
| HeroBlock | hero-block | Einzelner Hero mit Bild, Headline, CTA |
|
||||
| HeroSliderBlock | hero-slider-block | Hero-Slider mit mehreren Slides |
|
||||
| ImageSliderBlock | image-slider-block | Bildergalerie/Karussell |
|
||||
| TextBlock | text-block | Textinhalt mit Rich-Text |
|
||||
| ImageTextBlock | image-text-block | Bild + Text nebeneinander |
|
||||
| CardGridBlock | card-grid-block | Karten-Raster |
|
||||
|
|
@ -719,6 +721,10 @@ SELECT * FROM audit_logs ORDER BY created_at DESC LIMIT 10;
|
|||
| TimelineBlock | timeline-block | Timeline-Darstellung |
|
||||
| DividerBlock | divider-block | Trennlinie |
|
||||
| VideoBlock | video-block | Video einbetten |
|
||||
|
||||
### Content Blocks
|
||||
| Block | Slug | Beschreibung |
|
||||
|-------|------|--------------|
|
||||
| PostsListBlock | posts-list-block | Beitrags-Liste |
|
||||
| TestimonialsBlock | testimonials-block | Kundenbewertungen |
|
||||
| NewsletterBlock | newsletter-block | Newsletter-Anmeldung |
|
||||
|
|
@ -726,8 +732,54 @@ SELECT * FROM audit_logs ORDER BY created_at DESC LIMIT 10;
|
|||
| FAQBlock | faq-block | FAQ-Akkordeon |
|
||||
| TeamBlock | team-block | Team-Mitglieder |
|
||||
| ServicesBlock | services-block | Leistungen |
|
||||
|
||||
### Blogging Blocks
|
||||
| Block | Slug | Beschreibung |
|
||||
|-------|------|--------------|
|
||||
| AuthorBioBlock | author-bio-block | Autoren-Biografie |
|
||||
| RelatedPostsBlock | related-posts-block | Verwandte Beiträge |
|
||||
| ShareButtonsBlock | share-buttons-block | Social Share Buttons |
|
||||
| TableOfContentsBlock | table-of-contents-block | Inhaltsverzeichnis |
|
||||
|
||||
### Team Blocks
|
||||
| Block | Slug | Beschreibung |
|
||||
|-------|------|--------------|
|
||||
| TeamFilterBlock | team-filter-block | Team mit Filter-Funktion |
|
||||
| OrgChartBlock | org-chart-block | Organigramm |
|
||||
|
||||
### Feature Blocks
|
||||
| Block | Slug | Beschreibung |
|
||||
|-------|------|--------------|
|
||||
| LocationsBlock | locations-block | Standorte/Filialen |
|
||||
| LogoGridBlock | logo-grid-block | Partner/Kunden-Logos |
|
||||
| StatsBlock | stats-block | Statistiken/Zahlen |
|
||||
| JobsBlock | jobs-block | Stellenangebote |
|
||||
| DownloadsBlock | downloads-block | Download-Bereich |
|
||||
| MapBlock | map-block | Karten-Einbindung |
|
||||
|
||||
### Interactive Blocks
|
||||
| Block | Slug | Beschreibung |
|
||||
|-------|------|--------------|
|
||||
| EventsBlock | events-block | Veranstaltungen |
|
||||
| PricingBlock | pricing-block | Preistabellen |
|
||||
| TabsBlock | tabs-block | Tab-Navigation |
|
||||
| AccordionBlock | accordion-block | Akkordeon/Aufklappbar |
|
||||
| ComparisonBlock | comparison-block | Vergleichstabelle |
|
||||
|
||||
### Tenant-specific Blocks
|
||||
| Block | Slug | Beschreibung |
|
||||
|-------|------|--------------|
|
||||
| BeforeAfterBlock | before-after-block | Vorher/Nachher Bildvergleich (porwoll.de) |
|
||||
|
||||
### BlogWoman Blocks
|
||||
| Block | Slug | Beschreibung |
|
||||
|-------|------|--------------|
|
||||
| FavoritesBlock | favorites-block | Affiliate-Produkte Grid/Liste/Karussell |
|
||||
| SeriesBlock | series-block | YouTube-Serien Übersicht |
|
||||
| SeriesDetailBlock | series-detail-block | Serien-Einzelseite mit Hero |
|
||||
| VideoEmbedBlock | video-embed-block | YouTube/Vimeo Embed mit Privacy Mode |
|
||||
| FeaturedContentBlock | featured-content-block | Kuratierte Mixed-Content Sammlung |
|
||||
|
||||
### HeroSliderBlock Features
|
||||
|
||||
Vollwertiger Hero-Slider mit:
|
||||
|
|
@ -761,39 +813,95 @@ Vollwertiger Hero-Slider mit:
|
|||
- Separate Mobile-Höhe
|
||||
- Content-Breite
|
||||
|
||||
## Collections Übersicht
|
||||
## Collections Übersicht (40+ Collections)
|
||||
|
||||
### Core Collections
|
||||
| Collection | Slug | Beschreibung |
|
||||
|------------|------|--------------|
|
||||
| Users | users | Benutzer mit isSuperAdmin Flag |
|
||||
| Tenants | tenants | Mandanten mit E-Mail-Konfiguration |
|
||||
| Media | media | Medien mit 11 responsive Image Sizes |
|
||||
| Pages | pages | Seiten mit Blocks |
|
||||
|
||||
### Content Collections
|
||||
| Collection | Slug | Beschreibung |
|
||||
|------------|------|--------------|
|
||||
| Posts | posts | Blog/News/Presse mit Kategorien |
|
||||
| Categories | categories | Kategorien für Posts |
|
||||
| Portfolios | portfolios | Portfolio-Galerien (Fotografie) |
|
||||
| PortfolioCategories | portfolio-categories | Kategorien für Portfolios |
|
||||
| Tags | tags | Tags für Posts (Blogging) |
|
||||
| Authors | authors | Autoren für Posts |
|
||||
| Testimonials | testimonials | Kundenbewertungen |
|
||||
| FAQs | faqs | Häufig gestellte Fragen (FAQ) |
|
||||
| SocialLinks | social-links | Social Media Links |
|
||||
|
||||
### Team & Services
|
||||
| Collection | Slug | Beschreibung |
|
||||
|------------|------|--------------|
|
||||
| Team | team | Team-Mitglieder und Mitarbeiter |
|
||||
| ServiceCategories | service-categories | Kategorien für Leistungen |
|
||||
| Services | services | Leistungen und Dienstleistungen |
|
||||
| NewsletterSubscribers | newsletter-subscribers | Newsletter mit Double Opt-In |
|
||||
| SocialLinks | social-links | Social Media Links |
|
||||
| Forms | forms | Formular-Builder |
|
||||
| FormSubmissions | form-submissions | Formular-Einsendungen mit Status-Workflow |
|
||||
| EmailLogs | email-logs | E-Mail-Protokollierung |
|
||||
| AuditLogs | audit-logs | Security Audit Trail |
|
||||
| CookieConfigurations | cookie-configurations | Cookie-Banner Konfiguration |
|
||||
| CookieInventory | cookie-inventory | Cookie-Inventar |
|
||||
| ConsentLogs | consent-logs | Consent-Protokollierung |
|
||||
| Jobs | jobs | Stellenangebote |
|
||||
|
||||
### Portfolio & Media
|
||||
| Collection | Slug | Beschreibung |
|
||||
|------------|------|--------------|
|
||||
| Portfolios | portfolios | Portfolio-Galerien (Fotografie) |
|
||||
| PortfolioCategories | portfolio-categories | Kategorien für Portfolios |
|
||||
| Videos | videos | Video-Bibliothek mit YouTube/Vimeo/Uploads |
|
||||
| VideoCategories | video-categories | Kategorien für Videos |
|
||||
|
||||
### Products (E-Commerce)
|
||||
| Collection | Slug | Beschreibung |
|
||||
|------------|------|--------------|
|
||||
| Products | products | Produkte |
|
||||
| ProductCategories | product-categories | Produkt-Kategorien |
|
||||
|
||||
### Feature Collections
|
||||
| Collection | Slug | Beschreibung |
|
||||
|------------|------|--------------|
|
||||
| Locations | locations | Standorte/Filialen |
|
||||
| Partners | partners | Partner/Kunden |
|
||||
| Downloads | downloads | Download-Dateien |
|
||||
| Events | events | Veranstaltungen |
|
||||
| Timelines | timelines | Chronologische Events (Geschichte, Meilensteine) |
|
||||
| Workflows | workflows | Komplexe Prozesse mit Phasen und Schritten |
|
||||
|
||||
### Formulare & Newsletter
|
||||
| Collection | Slug | Beschreibung |
|
||||
|------------|------|--------------|
|
||||
| Forms | forms | Formular-Builder (Plugin) |
|
||||
| FormSubmissions | form-submissions | Formular-Einsendungen mit CRM-Workflow |
|
||||
| NewsletterSubscribers | newsletter-subscribers | Newsletter mit Double Opt-In |
|
||||
|
||||
### Consent & Privacy (DSGVO)
|
||||
| Collection | Slug | Beschreibung |
|
||||
|------------|------|--------------|
|
||||
| CookieConfigurations | cookie-configurations | Cookie-Banner Konfiguration |
|
||||
| CookieInventory | cookie-inventory | Cookie-Inventar |
|
||||
| ConsentLogs | consent-logs | Consent-Protokollierung (WORM) |
|
||||
| PrivacyPolicySettings | privacy-policy-settings | Datenschutz-Einstellungen |
|
||||
|
||||
### Tenant-specific Collections
|
||||
| Collection | Slug | Beschreibung |
|
||||
|------------|------|--------------|
|
||||
| Bookings | bookings | Fotografie-Buchungen (porwoll.de) |
|
||||
| Certifications | certifications | Zertifizierungen (C2S) |
|
||||
| Projects | projects | Game-Development-Projekte (gunshin.de) |
|
||||
| Videos | videos | Video-Bibliothek mit YouTube/Vimeo/Uploads |
|
||||
| VideoCategories | video-categories | Kategorien für Videos |
|
||||
|
||||
### BlogWoman Collections
|
||||
| Collection | Slug | Beschreibung |
|
||||
|------------|------|--------------|
|
||||
| Favorites | favorites | Affiliate-Produkte mit Kategorien und Badges |
|
||||
| Series | series | YouTube-Serien mit Branding (Logo, Farben) |
|
||||
|
||||
### System Collections
|
||||
| Collection | Slug | Beschreibung |
|
||||
|------------|------|--------------|
|
||||
| EmailLogs | email-logs | E-Mail-Protokollierung |
|
||||
| AuditLogs | audit-logs | Security Audit Trail |
|
||||
| SiteSettings | site-settings | Website-Einstellungen (pro Tenant) |
|
||||
| Navigations | navigations | Navigationsmenüs (pro Tenant) |
|
||||
| Redirects | redirects | URL-Weiterleitungen (Plugin) |
|
||||
|
||||
## Timeline Collection
|
||||
|
||||
|
|
@ -930,12 +1038,11 @@ Die FormSubmissions Collection wurde zu einem leichtgewichtigen CRM erweitert:
|
|||
|
||||
## Globals
|
||||
|
||||
> **Hinweis:** SiteSettings, Navigations und PrivacyPolicySettings wurden zu tenant-spezifischen Collections umgewandelt (siehe Collections Übersicht).
|
||||
|
||||
| Global | Slug | Beschreibung |
|
||||
|--------|------|--------------|
|
||||
| SiteSettings | site-settings | Allgemeine Website-Einstellungen |
|
||||
| Navigation | navigation | Navigationsmenü |
|
||||
| SEOSettings | seo-settings | SEO-Einstellungen |
|
||||
| PrivacyPolicySettings | privacy-policy-settings | Datenschutz-Einstellungen |
|
||||
| SEOSettings | seo-settings | Globale SEO-Einstellungen (systemweit) |
|
||||
|
||||
## Test Suite
|
||||
|
||||
|
|
@ -1070,4 +1177,4 @@ ssh payload@162.55.85.18
|
|||
### Scripts & Backup
|
||||
- `scripts/backup/README.md` - Backup-System Dokumentation
|
||||
|
||||
*Letzte Aktualisierung: 27.12.2025*
|
||||
*Letzte Aktualisierung: 29.12.2025*
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# Deployment Guide - Payload CMS Multi-Tenant
|
||||
|
||||
*Letzte Aktualisierung: 18. Dezember 2025*
|
||||
*Letzte Aktualisierung: 27. Dezember 2025*
|
||||
|
||||
> **Wichtig:** Für die vollständige Deployment-Strategie siehe [DEPLOYMENT_STRATEGY.md](./DEPLOYMENT_STRATEGY.md)
|
||||
|
||||
## Übersicht
|
||||
|
||||
|
|
@ -94,61 +96,59 @@ pm2 logs payload --lines 20
|
|||
|
||||
## Production Deployment (main → cms.c2sgmbh.de)
|
||||
|
||||
### Schritt 1: Merge zu main
|
||||
### Option A: Via GitHub Actions (Empfohlen)
|
||||
|
||||
```bash
|
||||
# Auf dem Development-Server oder lokal
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git merge develop
|
||||
git push origin main
|
||||
```
|
||||
1. **develop in main mergen und pushen**
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git merge develop
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### Schritt 2: Deploy auf Hetzner 3
|
||||
2. **GitHub Actions Workflow starten**
|
||||
- Gehe zu: Actions → "Deploy to Production" → "Run workflow"
|
||||
- Oder via CLI:
|
||||
```bash
|
||||
gh workflow run deploy-production.yml
|
||||
```
|
||||
|
||||
3. **Der Workflow führt automatisch aus:**
|
||||
- Pre-flight Checks
|
||||
- Tests (optional)
|
||||
- Datenbank-Backup
|
||||
- Deployment
|
||||
- Health Check
|
||||
- Bei Fehler: Automatischer Rollback
|
||||
|
||||
### Option B: Via Deploy-Script auf Server
|
||||
|
||||
```bash
|
||||
# SSH zum Production-Server
|
||||
ssh payload@162.55.85.18
|
||||
|
||||
# Deploy-Script ausführen
|
||||
~/deploy.sh
|
||||
# Deploy-Script ausführen (mit Backup, Health Check, Rollback-Fähigkeit)
|
||||
cd ~/payload-cms
|
||||
./scripts/deploy-production.sh
|
||||
|
||||
# Optionen:
|
||||
./scripts/deploy-production.sh -y # Ohne Bestätigung
|
||||
./scripts/deploy-production.sh --skip-backup # Ohne Backup (nicht empfohlen)
|
||||
./scripts/deploy-production.sh --skip-build # Nur Service-Neustart
|
||||
./scripts/deploy-production.sh --rollback # Rollback zur vorherigen Version
|
||||
./scripts/deploy-production.sh --dry-run # Zeigt was passieren würde
|
||||
```
|
||||
|
||||
### Deploy-Script (~/deploy.sh)
|
||||
### Rollback bei Problemen
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
# Automatischer Rollback zur vorherigen Version
|
||||
./scripts/deploy-production.sh --rollback
|
||||
|
||||
cd ~/payload-cms
|
||||
|
||||
echo "📥 Pulling latest changes..."
|
||||
git pull origin main
|
||||
|
||||
echo "📦 Installing dependencies..."
|
||||
pnpm install
|
||||
|
||||
echo "🔄 Running migrations..."
|
||||
pnpm payload migrate
|
||||
|
||||
echo "🏗️ Building..."
|
||||
pnpm build
|
||||
|
||||
echo "🔄 Restarting PM2..."
|
||||
pm2 restart payload
|
||||
|
||||
echo "✅ Deployment complete!"
|
||||
pm2 status
|
||||
```
|
||||
|
||||
### Manuelles Deployment
|
||||
|
||||
```bash
|
||||
ssh payload@162.55.85.18
|
||||
cd ~/payload-cms
|
||||
git pull origin main
|
||||
pnpm install
|
||||
pnpm payload migrate
|
||||
# Oder manuell zu spezifischem Commit
|
||||
git log --oneline -10
|
||||
git reset --hard <commit-sha>
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm build
|
||||
pm2 restart payload
|
||||
```
|
||||
|
|
@ -396,15 +396,17 @@ TRUST_PROXY=true
|
|||
|
||||
| Workflow | Trigger | Aktion |
|
||||
|----------|---------|--------|
|
||||
| `ci.yml` | Push/PR auf main, develop | Lint, Test, Build |
|
||||
| `ci.yml` | Push/PR auf main, develop | Lint, Test, Build, E2E |
|
||||
| `security.yml` | Push/PR, Schedule | Security Scanning |
|
||||
| `deploy-staging.yml` | Push auf develop | Auto-Deploy zu Staging |
|
||||
| `deploy-production.yml` | Manuell (workflow_dispatch) | Production Deployment |
|
||||
|
||||
### Secrets (GitHub)
|
||||
|
||||
| Secret | Beschreibung |
|
||||
|--------|--------------|
|
||||
| `STAGING_SSH_KEY` | SSH Private Key für sv-payload |
|
||||
| `PRODUCTION_SSH_KEY` | SSH Private Key für Hetzner 3 |
|
||||
|
||||
### Manuelles Deployment triggern
|
||||
|
||||
|
|
@ -522,4 +524,4 @@ pnpm payload migrate --name 20251216_073000_add_video_collections
|
|||
|
||||
---
|
||||
|
||||
*Dokumentation: Complex Care Solutions GmbH | 18.12.2025*
|
||||
*Dokumentation: Complex Care Solutions GmbH | 29.12.2025*
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# Deployment-Strategie: Dev → Production
|
||||
|
||||
*Erstellt: 27. Dezember 2025*
|
||||
*Erstellt: 27. Dezember 2025 | Aktualisiert: 29. Dezember 2025*
|
||||
|
||||
> **Siehe auch:** [STAGING-DEPLOYMENT.md](./STAGING-DEPLOYMENT.md) für detaillierte Staging-Workflow-Dokumentation
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Infrastruktur Dokumentation
|
||||
|
||||
*Letzte Aktualisierung: 18. Dezember 2025*
|
||||
*Letzte Aktualisierung: 29. Dezember 2025*
|
||||
|
||||
## Gesamtübersicht
|
||||
|
||||
|
|
@ -84,10 +84,10 @@
|
|||
### Software Stack
|
||||
- Node.js 22.x
|
||||
- pnpm
|
||||
- Next.js 16.0.10
|
||||
- Claude Code 2.0.72
|
||||
- Codex CLI 0.73.0
|
||||
- Gemini CLI 0.21.2
|
||||
- Next.js 15.5.9
|
||||
- Claude Code (aktuell)
|
||||
- Codex CLI (aktuell)
|
||||
- Gemini CLI (aktuell)
|
||||
|
||||
### Projekte & Ports
|
||||
|
||||
|
|
@ -239,4 +239,4 @@ systemctl start frontend-porwoll
|
|||
|
||||
---
|
||||
|
||||
*Dokumentation: Martin Porwoll | Complex Care Solutions GmbH | 18.12.2025*
|
||||
*Dokumentation: Martin Porwoll | Complex Care Solutions GmbH | 29.12.2025*
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Projekt Status - Dezember 2025
|
||||
|
||||
**Stand:** 27. Dezember 2025
|
||||
**Stand:** 29. Dezember 2025
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
|
|
@ -177,6 +177,15 @@ pm2 logs payload
|
|||
|
||||
## 📝 Änderungsprotokoll
|
||||
|
||||
### 29.12.2025
|
||||
- **Dokumentation konsolidiert und aktualisiert:**
|
||||
- CLAUDE.md: Collections (40+) und Blocks (37) vollständig dokumentiert
|
||||
- CLAUDE.md: Neue Collections (Products, Tags, Authors, Locations, Partners, Jobs, Downloads, Events)
|
||||
- CLAUDE.md: Neue Blocks (ImageSlider, Blogging, Team-Filter, Feature-Blocks, Interactive-Blocks)
|
||||
- CLAUDE.md: Globals-Abschnitt korrigiert (SiteSettings/Navigations sind jetzt Collections)
|
||||
- docs/INFRASTRUCTURE.md, docs/DEPLOYMENT.md aktualisiert
|
||||
- docs/anleitungen/*.md auf aktuelle URLs korrigiert
|
||||
|
||||
### 27.12.2025
|
||||
- Payload CMS Update 3.68.4 → 3.69.0
|
||||
- Bug-Fixes Admin Panel:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
# Staging Deployment
|
||||
|
||||
> **Staging URL:** https://pl.c2sgmbh.de
|
||||
> **Staging URL:** https://pl.porwoll.tech
|
||||
> **Server:** sv-payload (37.24.237.181)
|
||||
> **Branch:** `develop`
|
||||
|
||||
> **Siehe auch:** [DEPLOYMENT_STRATEGY.md](./DEPLOYMENT_STRATEGY.md) für die vollständige Deployment-Strategie (Dev → Prod)
|
||||
|
||||
---
|
||||
|
||||
## Übersicht
|
||||
|
|
@ -14,7 +16,7 @@
|
|||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────────┐│
|
||||
│ │ Developer │ │ GitHub │ │ Staging Server ││
|
||||
│ │ │ │ Actions │ │ pl.c2sgmbh.de ││
|
||||
│ │ │ │ Actions │ │ pl.porwoll.tech ││
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────────────┬───────────────┘│
|
||||
│ │ │ │ │
|
||||
│ │ git push │ │ │
|
||||
|
|
@ -80,7 +82,7 @@ Jobs: deploy
|
|||
|
||||
### 3. Verify Deployment
|
||||
|
||||
- HTTP-Status von https://pl.c2sgmbh.de/admin prüfen
|
||||
- HTTP-Status von https://pl.porwoll.tech/admin prüfen
|
||||
- Bei Fehler: Benachrichtigung im Workflow-Summary
|
||||
|
||||
---
|
||||
|
|
@ -233,7 +235,7 @@ main (Produktion)
|
|||
| Branch | Deployment | URL |
|
||||
|--------|------------|-----|
|
||||
| `main` | Produktion (manuell) | cms.c2sgmbh.de |
|
||||
| `develop` | Staging (automatisch) | pl.c2sgmbh.de |
|
||||
| `develop` | Staging (automatisch) | pl.porwoll.tech |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -249,4 +251,4 @@ gh run list --workflow=deploy-staging.yml --limit=5
|
|||
|
||||
---
|
||||
|
||||
*Letzte Aktualisierung: 14.12.2025*
|
||||
*Letzte Aktualisierung: 29.12.2025*
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
Das Payload CMS stellt eine REST-API und eine GraphQL-API bereit. Diese Anleitung beschreibt die Nutzung der REST-API für alle Collections.
|
||||
|
||||
**Base URL:** `https://pl.c2sgmbh.de/api`
|
||||
**Base URL:** `https://pl.porwoll.tech/api`
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ Das Payload CMS stellt eine REST-API und eine GraphQL-API bereit. Diese Anleitun
|
|||
### Login
|
||||
|
||||
```bash
|
||||
curl -X POST "https://pl.c2sgmbh.de/api/users/login" \
|
||||
curl -X POST "https://pl.porwoll.tech/api/users/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "admin@example.com",
|
||||
|
|
@ -38,7 +38,7 @@ curl -X POST "https://pl.c2sgmbh.de/api/users/login" \
|
|||
### Token verwenden
|
||||
|
||||
```bash
|
||||
curl "https://pl.c2sgmbh.de/api/posts" \
|
||||
curl "https://pl.porwoll.tech/api/posts" \
|
||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIs..."
|
||||
```
|
||||
|
||||
|
|
@ -50,13 +50,13 @@ Das CMS unterstützt Deutsch (de) und Englisch (en). Lokalisierte Felder können
|
|||
|
||||
```bash
|
||||
# Deutsche Inhalte (Standard)
|
||||
curl "https://pl.c2sgmbh.de/api/posts?locale=de"
|
||||
curl "https://pl.porwoll.tech/api/posts?locale=de"
|
||||
|
||||
# Englische Inhalte
|
||||
curl "https://pl.c2sgmbh.de/api/posts?locale=en"
|
||||
curl "https://pl.porwoll.tech/api/posts?locale=en"
|
||||
|
||||
# Alle Sprachen gleichzeitig
|
||||
curl "https://pl.c2sgmbh.de/api/posts?locale=all"
|
||||
curl "https://pl.porwoll.tech/api/posts?locale=all"
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -66,7 +66,7 @@ curl "https://pl.c2sgmbh.de/api/posts?locale=all"
|
|||
### Aktuellen User abrufen
|
||||
|
||||
```bash
|
||||
curl "https://pl.c2sgmbh.de/api/users/me" \
|
||||
curl "https://pl.porwoll.tech/api/users/me" \
|
||||
-H "Authorization: JWT your-token"
|
||||
```
|
||||
|
||||
|
|
@ -85,14 +85,14 @@ curl "https://pl.c2sgmbh.de/api/users/me" \
|
|||
### Alle Tenants abrufen (Auth + SuperAdmin erforderlich)
|
||||
|
||||
```bash
|
||||
curl "https://pl.c2sgmbh.de/api/tenants" \
|
||||
curl "https://pl.porwoll.tech/api/tenants" \
|
||||
-H "Authorization: JWT your-token"
|
||||
```
|
||||
|
||||
### Tenant erstellen (Auth + SuperAdmin erforderlich)
|
||||
|
||||
```bash
|
||||
curl -X POST "https://pl.c2sgmbh.de/api/tenants" \
|
||||
curl -X POST "https://pl.porwoll.tech/api/tenants" \
|
||||
-H "Authorization: JWT your-token" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
|
|
@ -121,47 +121,47 @@ curl -X POST "https://pl.c2sgmbh.de/api/tenants" \
|
|||
|
||||
```bash
|
||||
# Alle Posts
|
||||
curl "https://pl.c2sgmbh.de/api/posts"
|
||||
curl "https://pl.porwoll.tech/api/posts"
|
||||
|
||||
# Nur Blog-Artikel
|
||||
curl "https://pl.c2sgmbh.de/api/posts?where[type][equals]=blog"
|
||||
curl "https://pl.porwoll.tech/api/posts?where[type][equals]=blog"
|
||||
|
||||
# Nur News
|
||||
curl "https://pl.c2sgmbh.de/api/posts?where[type][equals]=news"
|
||||
curl "https://pl.porwoll.tech/api/posts?where[type][equals]=news"
|
||||
|
||||
# Nur veröffentlichte Posts
|
||||
curl "https://pl.c2sgmbh.de/api/posts?where[status][equals]=published"
|
||||
curl "https://pl.porwoll.tech/api/posts?where[status][equals]=published"
|
||||
|
||||
# Nur hervorgehobene Posts
|
||||
curl "https://pl.c2sgmbh.de/api/posts?where[isFeatured][equals]=true"
|
||||
curl "https://pl.porwoll.tech/api/posts?where[isFeatured][equals]=true"
|
||||
|
||||
# Mit Sortierung (neueste zuerst)
|
||||
curl "https://pl.c2sgmbh.de/api/posts?sort=-publishedAt"
|
||||
curl "https://pl.porwoll.tech/api/posts?sort=-publishedAt"
|
||||
|
||||
# Limitiert auf 10 Einträge
|
||||
curl "https://pl.c2sgmbh.de/api/posts?limit=10"
|
||||
curl "https://pl.porwoll.tech/api/posts?limit=10"
|
||||
|
||||
# Pagination (Seite 2)
|
||||
curl "https://pl.c2sgmbh.de/api/posts?limit=10&page=2"
|
||||
curl "https://pl.porwoll.tech/api/posts?limit=10&page=2"
|
||||
|
||||
# Mit Locale
|
||||
curl "https://pl.c2sgmbh.de/api/posts?locale=de"
|
||||
curl "https://pl.porwoll.tech/api/posts?locale=de"
|
||||
```
|
||||
|
||||
### Einzelnen Post abrufen
|
||||
|
||||
```bash
|
||||
# Nach ID
|
||||
curl "https://pl.c2sgmbh.de/api/posts/1"
|
||||
curl "https://pl.porwoll.tech/api/posts/1"
|
||||
|
||||
# Nach Slug (über Query)
|
||||
curl "https://pl.c2sgmbh.de/api/posts?where[slug][equals]=mein-erster-artikel"
|
||||
curl "https://pl.porwoll.tech/api/posts?where[slug][equals]=mein-erster-artikel"
|
||||
```
|
||||
|
||||
### Post erstellen (Auth erforderlich)
|
||||
|
||||
```bash
|
||||
curl -X POST "https://pl.c2sgmbh.de/api/posts" \
|
||||
curl -X POST "https://pl.porwoll.tech/api/posts" \
|
||||
-H "Authorization: JWT your-token" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
|
|
@ -189,7 +189,7 @@ curl -X POST "https://pl.c2sgmbh.de/api/posts" \
|
|||
### Post aktualisieren (Auth erforderlich)
|
||||
|
||||
```bash
|
||||
curl -X PATCH "https://pl.c2sgmbh.de/api/posts/1" \
|
||||
curl -X PATCH "https://pl.porwoll.tech/api/posts/1" \
|
||||
-H "Authorization: JWT your-token" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
|
|
@ -201,7 +201,7 @@ curl -X PATCH "https://pl.c2sgmbh.de/api/posts/1" \
|
|||
### Post löschen (Auth erforderlich)
|
||||
|
||||
```bash
|
||||
curl -X DELETE "https://pl.c2sgmbh.de/api/posts/1" \
|
||||
curl -X DELETE "https://pl.porwoll.tech/api/posts/1" \
|
||||
-H "Authorization: JWT your-token"
|
||||
```
|
||||
|
||||
|
|
@ -213,22 +213,22 @@ curl -X DELETE "https://pl.c2sgmbh.de/api/posts/1" \
|
|||
|
||||
```bash
|
||||
# Alle Testimonials
|
||||
curl "https://pl.c2sgmbh.de/api/testimonials"
|
||||
curl "https://pl.porwoll.tech/api/testimonials"
|
||||
|
||||
# Nur aktive Testimonials
|
||||
curl "https://pl.c2sgmbh.de/api/testimonials?where[isActive][equals]=true"
|
||||
curl "https://pl.porwoll.tech/api/testimonials?where[isActive][equals]=true"
|
||||
|
||||
# Sortiert nach Bewertung (beste zuerst)
|
||||
curl "https://pl.c2sgmbh.de/api/testimonials?sort=-rating"
|
||||
curl "https://pl.porwoll.tech/api/testimonials?sort=-rating"
|
||||
|
||||
# Sortiert nach eigener Reihenfolge
|
||||
curl "https://pl.c2sgmbh.de/api/testimonials?sort=order"
|
||||
curl "https://pl.porwoll.tech/api/testimonials?sort=order"
|
||||
```
|
||||
|
||||
### Testimonial erstellen (Auth erforderlich)
|
||||
|
||||
```bash
|
||||
curl -X POST "https://pl.c2sgmbh.de/api/testimonials" \
|
||||
curl -X POST "https://pl.porwoll.tech/api/testimonials" \
|
||||
-H "Authorization: JWT your-token" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
|
|
@ -252,7 +252,7 @@ curl -X POST "https://pl.c2sgmbh.de/api/testimonials" \
|
|||
|
||||
```bash
|
||||
# Einfache Anmeldung
|
||||
curl -X POST "https://pl.c2sgmbh.de/api/newsletter-subscribers" \
|
||||
curl -X POST "https://pl.porwoll.tech/api/newsletter-subscribers" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tenant": 1,
|
||||
|
|
@ -261,7 +261,7 @@ curl -X POST "https://pl.c2sgmbh.de/api/newsletter-subscribers" \
|
|||
}'
|
||||
|
||||
# Mit Namen und Interessen
|
||||
curl -X POST "https://pl.c2sgmbh.de/api/newsletter-subscribers" \
|
||||
curl -X POST "https://pl.porwoll.tech/api/newsletter-subscribers" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tenant": 1,
|
||||
|
|
@ -292,15 +292,15 @@ curl -X POST "https://pl.c2sgmbh.de/api/newsletter-subscribers" \
|
|||
|
||||
```bash
|
||||
# Alle Subscribers
|
||||
curl "https://pl.c2sgmbh.de/api/newsletter-subscribers" \
|
||||
curl "https://pl.porwoll.tech/api/newsletter-subscribers" \
|
||||
-H "Authorization: JWT your-token"
|
||||
|
||||
# Nur bestätigte Subscribers
|
||||
curl "https://pl.c2sgmbh.de/api/newsletter-subscribers?where[status][equals]=confirmed" \
|
||||
curl "https://pl.porwoll.tech/api/newsletter-subscribers?where[status][equals]=confirmed" \
|
||||
-H "Authorization: JWT your-token"
|
||||
|
||||
# Nach E-Mail suchen
|
||||
curl "https://pl.c2sgmbh.de/api/newsletter-subscribers?where[email][equals]=kunde@example.com" \
|
||||
curl "https://pl.porwoll.tech/api/newsletter-subscribers?where[email][equals]=kunde@example.com" \
|
||||
-H "Authorization: JWT your-token"
|
||||
```
|
||||
|
||||
|
|
@ -308,7 +308,7 @@ curl "https://pl.c2sgmbh.de/api/newsletter-subscribers?where[email][equals]=kund
|
|||
|
||||
```bash
|
||||
# Über Token bestätigen
|
||||
curl -X PATCH "https://pl.c2sgmbh.de/api/newsletter-subscribers/1" \
|
||||
curl -X PATCH "https://pl.porwoll.tech/api/newsletter-subscribers/1" \
|
||||
-H "Authorization: JWT your-token" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
|
|
@ -319,7 +319,7 @@ curl -X PATCH "https://pl.c2sgmbh.de/api/newsletter-subscribers/1" \
|
|||
### Subscriber abmelden
|
||||
|
||||
```bash
|
||||
curl -X PATCH "https://pl.c2sgmbh.de/api/newsletter-subscribers/1" \
|
||||
curl -X PATCH "https://pl.porwoll.tech/api/newsletter-subscribers/1" \
|
||||
-H "Authorization: JWT your-token" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
|
|
@ -335,16 +335,16 @@ curl -X PATCH "https://pl.c2sgmbh.de/api/newsletter-subscribers/1" \
|
|||
|
||||
```bash
|
||||
# Alle Seiten
|
||||
curl "https://pl.c2sgmbh.de/api/pages"
|
||||
curl "https://pl.porwoll.tech/api/pages"
|
||||
|
||||
# Seite nach Slug
|
||||
curl "https://pl.c2sgmbh.de/api/pages?where[slug][equals]=startseite"
|
||||
curl "https://pl.porwoll.tech/api/pages?where[slug][equals]=startseite"
|
||||
|
||||
# Nur veröffentlichte Seiten
|
||||
curl "https://pl.c2sgmbh.de/api/pages?where[status][equals]=published"
|
||||
curl "https://pl.porwoll.tech/api/pages?where[status][equals]=published"
|
||||
|
||||
# Mit Locale
|
||||
curl "https://pl.c2sgmbh.de/api/pages?locale=de&depth=2"
|
||||
curl "https://pl.porwoll.tech/api/pages?locale=de&depth=2"
|
||||
```
|
||||
|
||||
### Seite mit Blocks
|
||||
|
|
@ -389,7 +389,7 @@ Die Cookie-Konfiguration wird automatisch nach Domain gefiltert.
|
|||
|
||||
```bash
|
||||
# Für Frontend Cookie-Banner
|
||||
curl "https://pl.c2sgmbh.de/api/cookie-configurations?where[tenant][equals]=1"
|
||||
curl "https://pl.porwoll.tech/api/cookie-configurations?where[tenant][equals]=1"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
|
@ -432,13 +432,13 @@ Dokumentation aller verwendeten Cookies für die Datenschutzerklärung.
|
|||
|
||||
```bash
|
||||
# Alle Cookies eines Tenants
|
||||
curl "https://pl.c2sgmbh.de/api/cookie-inventory?where[tenant][equals]=1"
|
||||
curl "https://pl.porwoll.tech/api/cookie-inventory?where[tenant][equals]=1"
|
||||
|
||||
# Nur aktive Cookies
|
||||
curl "https://pl.c2sgmbh.de/api/cookie-inventory?where[tenant][equals]=1&where[isActive][equals]=true"
|
||||
curl "https://pl.porwoll.tech/api/cookie-inventory?where[tenant][equals]=1&where[isActive][equals]=true"
|
||||
|
||||
# Nach Kategorie filtern
|
||||
curl "https://pl.c2sgmbh.de/api/cookie-inventory?where[tenant][equals]=1&where[category][equals]=analytics"
|
||||
curl "https://pl.porwoll.tech/api/cookie-inventory?where[tenant][equals]=1&where[category][equals]=analytics"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
|
@ -473,7 +473,7 @@ curl "https://pl.c2sgmbh.de/api/cookie-inventory?where[tenant][equals]=1&where[c
|
|||
Consent-Logs sind ein WORM (Write-Once-Read-Many) Audit-Trail für DSGVO-Nachweise.
|
||||
|
||||
```bash
|
||||
curl -X POST "https://pl.c2sgmbh.de/api/consent-logs" \
|
||||
curl -X POST "https://pl.porwoll.tech/api/consent-logs" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: your-consent-api-key" \
|
||||
-d '{
|
||||
|
|
@ -531,7 +531,7 @@ curl -X POST "https://pl.c2sgmbh.de/api/consent-logs" \
|
|||
Konfiguration für die Datenschutzerklärungs-Seite (z.B. Alfright Integration).
|
||||
|
||||
```bash
|
||||
curl "https://pl.c2sgmbh.de/api/privacy-policy-settings?where[tenant][equals]=1"
|
||||
curl "https://pl.porwoll.tech/api/privacy-policy-settings?where[tenant][equals]=1"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
|
@ -693,7 +693,7 @@ curl "https://pl.c2sgmbh.de/api/privacy-policy-settings?where[tenant][equals]=1"
|
|||
1. **Admin Panel:** Unter "Settings → Tenants" die ID ablesen
|
||||
2. **API:**
|
||||
```bash
|
||||
curl "https://pl.c2sgmbh.de/api/tenants" \
|
||||
curl "https://pl.porwoll.tech/api/tenants" \
|
||||
-H "Authorization: JWT your-token"
|
||||
```
|
||||
|
||||
|
|
@ -705,7 +705,7 @@ Wenn ein Frontend über eine Tenant-Domain (z.B. `porwoll.de`) zugreift, wird de
|
|||
|
||||
Für direkte API-Zugriffe:
|
||||
```bash
|
||||
curl "https://pl.c2sgmbh.de/api/posts?where[tenant][equals]=1"
|
||||
curl "https://pl.porwoll.tech/api/posts?where[tenant][equals]=1"
|
||||
```
|
||||
|
||||
### Super Admin
|
||||
|
|
@ -720,7 +720,7 @@ User mit `isSuperAdmin: true` haben Zugriff auf alle Tenants und können neue Te
|
|||
|
||||
```typescript
|
||||
// lib/api.ts
|
||||
const API_BASE = 'https://pl.c2sgmbh.de/api'
|
||||
const API_BASE = 'https://pl.porwoll.tech/api'
|
||||
const TENANT_ID = 1
|
||||
|
||||
export async function getPosts(type?: string, limit = 10, locale = 'de') {
|
||||
|
|
@ -889,6 +889,6 @@ Aktuell gibt es kein Rate Limiting. Für Production-Umgebungen sollte ein Revers
|
|||
|
||||
## Weitere Ressourcen
|
||||
|
||||
- **Admin Panel:** https://pl.c2sgmbh.de/admin
|
||||
- **Admin Panel:** https://pl.porwoll.tech/admin
|
||||
- **Payload CMS Docs:** https://payloadcms.com/docs
|
||||
- **GraphQL Playground:** https://pl.c2sgmbh.de/api/graphql (wenn aktiviert)
|
||||
- **GraphQL Playground:** https://pl.porwoll.tech/api/graphql (wenn aktiviert)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Security-Richtlinien - Payload CMS Multi-Tenant
|
||||
|
||||
> Letzte Aktualisierung: 18.12.2025
|
||||
> Letzte Aktualisierung: 29.12.2025
|
||||
|
||||
## Übersicht
|
||||
|
||||
|
|
@ -323,29 +323,13 @@ email=admin@example.com&password=secret
|
|||
- Rate-Limiting verhindert Brute-Force-Angriffe
|
||||
- Open Redirect Prevention durch URL-Validierung
|
||||
|
||||
### Custom Admin Login Page
|
||||
|
||||
Eine optionale Custom Login-Seite ist verfügbar unter `src/app/(payload)/admin/login/`:
|
||||
|
||||
```
|
||||
src/app/(payload)/admin/login/
|
||||
├── page.tsx # Login-Formular mit Styling
|
||||
└── page.module.scss # Custom Styles
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Styled Login-Form passend zum Admin-Theme
|
||||
- Redirect-Parameter Support (`?redirect=/admin/...`)
|
||||
- Fehlerbehandlung mit User-Feedback
|
||||
- Kompatibel mit Payload's Session-Management
|
||||
|
||||
---
|
||||
|
||||
## Änderungshistorie
|
||||
|
||||
| Datum | Änderung |
|
||||
|-------|----------|
|
||||
| 18.12.2025 | **Custom Admin Login Page:** Styled Login-Formular, Browser-Redirect mit Safe-URL-Validierung, Open Redirect Prevention |
|
||||
| 29.12.2025 | **Dokumentation aktualisiert:** Custom Login Page Abschnitt entfernt (wurde am 27.12.2025 entfernt) |
|
||||
| 17.12.2025 | **Security-Audit Fixes:** TRUST_PROXY für IP-Header-Spoofing, CSRF_SECRET Pflicht in Production, IP-Allowlist Startup-Warnungen, Tests auf 177 erweitert |
|
||||
| 09.12.2025 | Custom Login Route Dokumentation, multipart/form-data _payload Support |
|
||||
| 08.12.2025 | Security Test Suite (143 Tests) |
|
||||
|
|
@ -362,8 +346,7 @@ src/app/(payload)/admin/login/
|
|||
| `src/lib/security/ip-allowlist.ts` | IP-basierte Zugriffskontrolle |
|
||||
| `src/lib/security/csrf.ts` | CSRF Token Generation & Validation |
|
||||
| `src/lib/security/data-masking.ts` | Sensitive Data Masking |
|
||||
| `src/app/(payload)/api/users/login/route.ts` | Custom Login API |
|
||||
| `src/app/(payload)/admin/login/page.tsx` | Custom Login Page |
|
||||
| `src/app/(payload)/api/users/login/route.ts` | Custom Login API mit Audit |
|
||||
| `scripts/detect-secrets.sh` | Pre-Commit Secret Detection |
|
||||
| `.github/workflows/security.yml` | CI Security Scanning |
|
||||
| `tests/unit/security/` | Security Unit Tests |
|
||||
|
|
|
|||
|
|
@ -222,13 +222,31 @@
|
|||
|
||||
---
|
||||
|
||||
*Letzte Aktualisierung: 27.12.2025*
|
||||
*Letzte Aktualisierung: 29.12.2025*
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
### 29.12.2025
|
||||
- **Dokumentation konsolidiert und aktualisiert:**
|
||||
- CLAUDE.md: Collections (40+) und Blocks (37) vollständig dokumentiert
|
||||
- CLAUDE.md: Neue Collections hinzugefügt (Products, Tags, Authors, Locations, Partners, Jobs, Downloads, Events)
|
||||
- CLAUDE.md: Neue Blocks hinzugefügt (ImageSlider, Blogging, Team-Filter, Feature, Interactive Blocks)
|
||||
- CLAUDE.md: Globals-Abschnitt korrigiert (SiteSettings/Navigations sind jetzt Collections)
|
||||
- docs/INFRASTRUCTURE.md aktualisiert
|
||||
- docs/DEPLOYMENT.md aktualisiert
|
||||
- docs/STAGING-DEPLOYMENT.md: URLs korrigiert (pl.porwoll.tech)
|
||||
- docs/anleitungen/API_ANLEITUNG.md: URLs korrigiert (pl.porwoll.tech)
|
||||
- docs/anleitungen/SECURITY.md: Custom Login Page Abschnitt entfernt
|
||||
|
||||
### 27.12.2025
|
||||
- **Production Deployment-Strategie implementiert:**
|
||||
- GitHub Actions Workflow `deploy-production.yml` für manuelles Production-Deployment
|
||||
- Deploy-Script `scripts/deploy-production.sh` mit Backup, Health Check, Rollback
|
||||
- Umfassende Dokumentation in `docs/DEPLOYMENT_STRATEGY.md`
|
||||
- Features: Pre-flight Checks, DB-Backup, automatischer Rollback bei Fehler
|
||||
- Neues GitHub Secret erforderlich: `PRODUCTION_SSH_KEY`
|
||||
- **Payload CMS Update 3.68.4 → 3.69.0:**
|
||||
- Login Redirect Loop behoben (formatAdminURL generiert keine absoluten URLs mehr)
|
||||
- Update aller @payloadcms/* Pakete
|
||||
|
|
|
|||
182
src/blocks/FavoritesBlock.ts
Normal file
182
src/blocks/FavoritesBlock.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Background color options (shared with other blocks)
|
||||
*/
|
||||
const backgroundColorOptions = [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Ivory', value: 'ivory' },
|
||||
{ label: 'Sand', value: 'sand' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
]
|
||||
|
||||
/**
|
||||
* Category options matching the Favorites collection
|
||||
*/
|
||||
const categoryFilterOptions = [
|
||||
{ label: 'Alle Kategorien', value: 'all' },
|
||||
{ label: 'Fashion', value: 'fashion' },
|
||||
{ label: 'Beauty', value: 'beauty' },
|
||||
{ label: 'Travel', value: 'travel' },
|
||||
{ label: 'Tech', value: 'tech' },
|
||||
{ label: 'Home', value: 'home' },
|
||||
]
|
||||
|
||||
/**
|
||||
* FavoritesBlock
|
||||
*
|
||||
* Zeigt Favoriten/Affiliate-Produkte aus der Favorites Collection.
|
||||
* Unterstützt verschiedene Layouts und Filteroptionen.
|
||||
*/
|
||||
export const FavoritesBlock: Block = {
|
||||
slug: 'favorites-block',
|
||||
labels: {
|
||||
singular: 'Favoriten',
|
||||
plural: 'Favoriten',
|
||||
},
|
||||
imageURL: '/assets/blocks/favorites.png',
|
||||
fields: [
|
||||
// Header
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
|
||||
// Filtering
|
||||
{
|
||||
name: 'category',
|
||||
type: 'select',
|
||||
defaultValue: 'all',
|
||||
options: categoryFilterOptions,
|
||||
label: 'Kategorie-Filter',
|
||||
admin: {
|
||||
description: 'Nur Favoriten dieser Kategorie anzeigen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showFeaturedOnly',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Nur Featured anzeigen',
|
||||
admin: {
|
||||
description: 'Nur als "Featured" markierte Favoriten anzeigen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
defaultValue: 8,
|
||||
min: 1,
|
||||
max: 50,
|
||||
label: 'Anzahl',
|
||||
admin: {
|
||||
description: 'Maximale Anzahl der angezeigten Favoriten',
|
||||
},
|
||||
},
|
||||
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'grid',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Grid', value: 'grid' },
|
||||
{ label: 'Liste', value: 'list' },
|
||||
{ label: 'Karussell', value: 'carousel' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
defaultValue: '4',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '2 Spalten', value: '2' },
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
{ label: '4 Spalten', value: '4' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.layout === 'grid' || siblingData?.layout === 'carousel',
|
||||
},
|
||||
},
|
||||
|
||||
// Display Options
|
||||
{
|
||||
name: 'showPrice',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Preis anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showBadge',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Badge anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showDescription',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Beschreibung anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showCategory',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Kategorie anzeigen',
|
||||
},
|
||||
|
||||
// Styling
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
options: backgroundColorOptions,
|
||||
label: 'Hintergrundfarbe',
|
||||
},
|
||||
|
||||
// CTA
|
||||
{
|
||||
name: 'cta',
|
||||
type: 'group',
|
||||
label: 'Call-to-Action',
|
||||
fields: [
|
||||
{
|
||||
name: 'showCta',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'CTA-Button anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'ctaText',
|
||||
type: 'text',
|
||||
label: 'Button-Text',
|
||||
defaultValue: 'Alle Favoriten ansehen',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showCta,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ctaUrl',
|
||||
type: 'text',
|
||||
label: 'Button-Link',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showCta,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
342
src/blocks/FeaturedContentBlock.ts
Normal file
342
src/blocks/FeaturedContentBlock.ts
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Background color options
|
||||
*/
|
||||
const backgroundColorOptions = [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Ivory', value: 'ivory' },
|
||||
{ label: 'Sand', value: 'sand' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
]
|
||||
|
||||
/**
|
||||
* Item Type options
|
||||
*/
|
||||
const itemTypeOptions = [
|
||||
{ label: 'Blog-Beitrag', value: 'post' },
|
||||
{ label: 'Video', value: 'video' },
|
||||
{ label: 'Serie', value: 'series' },
|
||||
{ label: 'Externer Link', value: 'external' },
|
||||
]
|
||||
|
||||
/**
|
||||
* FeaturedContentBlock
|
||||
*
|
||||
* Kuratierte Sammlung von verschiedenen Content-Typen.
|
||||
* Ermöglicht das Mischen von Posts, Videos, Serien und externen Links.
|
||||
*/
|
||||
export const FeaturedContentBlock: Block = {
|
||||
slug: 'featured-content-block',
|
||||
labels: {
|
||||
singular: 'Featured Content',
|
||||
plural: 'Featured Contents',
|
||||
},
|
||||
imageURL: '/assets/blocks/featured-content.png',
|
||||
fields: [
|
||||
// Header
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
|
||||
// Items Array
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
required: true,
|
||||
minRows: 1,
|
||||
maxRows: 12,
|
||||
label: 'Inhalte',
|
||||
admin: {
|
||||
description: 'Wähle verschiedene Content-Typen aus',
|
||||
},
|
||||
fields: [
|
||||
// Item Type Selection
|
||||
{
|
||||
name: 'itemType',
|
||||
type: 'select',
|
||||
required: true,
|
||||
options: itemTypeOptions,
|
||||
label: 'Typ',
|
||||
},
|
||||
|
||||
// Post Selection (condition: itemType === 'post')
|
||||
{
|
||||
name: 'post',
|
||||
type: 'relationship',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
relationTo: 'posts' as any,
|
||||
label: 'Beitrag auswählen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.itemType === 'post',
|
||||
},
|
||||
},
|
||||
|
||||
// Video Selection (condition: itemType === 'video')
|
||||
{
|
||||
name: 'video',
|
||||
type: 'relationship',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
relationTo: 'videos' as any,
|
||||
label: 'Video auswählen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.itemType === 'video',
|
||||
},
|
||||
},
|
||||
|
||||
// Series Selection (condition: itemType === 'series')
|
||||
{
|
||||
name: 'series',
|
||||
type: 'relationship',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
relationTo: 'series' as any,
|
||||
label: 'Serie auswählen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.itemType === 'series',
|
||||
},
|
||||
},
|
||||
|
||||
// External Content (condition: itemType === 'external')
|
||||
{
|
||||
name: 'externalTitle',
|
||||
type: 'text',
|
||||
label: 'Titel',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.itemType === 'external',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'externalUrl',
|
||||
type: 'text',
|
||||
label: 'URL',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.itemType === 'external',
|
||||
},
|
||||
validate: (
|
||||
value: string | undefined | null,
|
||||
{ siblingData }: { siblingData?: Record<string, unknown> }
|
||||
) => {
|
||||
if (siblingData?.itemType !== 'external') return true
|
||||
if (!value) return 'URL ist erforderlich'
|
||||
try {
|
||||
new URL(value)
|
||||
return true
|
||||
} catch {
|
||||
return 'Bitte eine gültige URL eingeben'
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'externalImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Bild',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.itemType === 'external',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'externalDescription',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
maxLength: 200,
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.itemType === 'external',
|
||||
},
|
||||
},
|
||||
|
||||
// Custom Label (for all types)
|
||||
{
|
||||
name: 'customLabel',
|
||||
type: 'text',
|
||||
label: 'Custom-Label',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Optionales Label (z.B. "NEU", "TRENDING", "MUSS-LESEN")',
|
||||
},
|
||||
},
|
||||
|
||||
// Custom Order/Featured
|
||||
{
|
||||
name: 'featured',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Hervorgehoben',
|
||||
admin: {
|
||||
description: 'In featured-grid Layout größer darstellen',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'grid',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Grid', value: 'grid' },
|
||||
{ label: 'Karussell', value: 'carousel' },
|
||||
{ label: 'Liste', value: 'list' },
|
||||
{ label: 'Featured Grid (erstes Element größer)', value: 'featured-grid' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
defaultValue: '3',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '2 Spalten', value: '2' },
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
{ label: '4 Spalten', value: '4' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.layout === 'grid' ||
|
||||
siblingData?.layout === 'carousel' ||
|
||||
siblingData?.layout === 'featured-grid',
|
||||
},
|
||||
},
|
||||
|
||||
// Display Options
|
||||
{
|
||||
name: 'showDates',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Datum anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showType',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Content-Typ anzeigen',
|
||||
admin: {
|
||||
description: 'Icon oder Label für Post/Video/Serie anzeigen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showDescription',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Beschreibung anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showCustomLabels',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Custom-Labels anzeigen',
|
||||
},
|
||||
|
||||
// Styling
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
options: backgroundColorOptions,
|
||||
label: 'Hintergrundfarbe',
|
||||
},
|
||||
|
||||
// Card Style
|
||||
{
|
||||
name: 'card',
|
||||
type: 'group',
|
||||
label: 'Karten-Stil',
|
||||
fields: [
|
||||
{
|
||||
name: 'bg',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
label: 'Karten-Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Transparent', value: 'transparent' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'shadow',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Schatten',
|
||||
},
|
||||
{
|
||||
name: 'border',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Rahmen',
|
||||
},
|
||||
{
|
||||
name: 'imgRatio',
|
||||
type: 'select',
|
||||
defaultValue: '16:9',
|
||||
label: 'Bild-Verhältnis',
|
||||
options: [
|
||||
{ label: '16:9', value: '16:9' },
|
||||
{ label: '4:3', value: '4:3' },
|
||||
{ label: '1:1 (Quadrat)', value: '1:1' },
|
||||
{ label: '3:2', value: '3:2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'hover',
|
||||
type: 'select',
|
||||
defaultValue: 'lift',
|
||||
label: 'Hover-Effekt',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Anheben', value: 'lift' },
|
||||
{ label: 'Zoom (Bild)', value: 'zoom' },
|
||||
{ label: 'Schatten verstärken', value: 'shadow' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// CTA
|
||||
{
|
||||
name: 'cta',
|
||||
type: 'group',
|
||||
label: 'Call-to-Action',
|
||||
fields: [
|
||||
{
|
||||
name: 'showCta',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'CTA-Button anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'ctaText',
|
||||
type: 'text',
|
||||
label: 'Button-Text',
|
||||
defaultValue: 'Alle ansehen',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showCta,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ctaUrl',
|
||||
type: 'text',
|
||||
label: 'Button-Link',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showCta,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
154
src/blocks/SeriesBlock.ts
Normal file
154
src/blocks/SeriesBlock.ts
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Background color options
|
||||
*/
|
||||
const backgroundColorOptions = [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Ivory', value: 'ivory' },
|
||||
{ label: 'Sand', value: 'sand' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
]
|
||||
|
||||
/**
|
||||
* SeriesBlock
|
||||
*
|
||||
* Zeigt Serien aus der Series Collection.
|
||||
* Verschiedene Layouts für Übersichtsseiten.
|
||||
*/
|
||||
export const SeriesBlock: Block = {
|
||||
slug: 'series-block',
|
||||
labels: {
|
||||
singular: 'Serien-Übersicht',
|
||||
plural: 'Serien-Übersichten',
|
||||
},
|
||||
imageURL: '/assets/blocks/series.png',
|
||||
fields: [
|
||||
// Header
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'grid',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Grid', value: 'grid' },
|
||||
{ label: 'Liste', value: 'list' },
|
||||
{ label: 'Featured (Hero + Grid)', value: 'featured' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
defaultValue: '3',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '2 Spalten', value: '2' },
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
{ label: '4 Spalten', value: '4' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.layout === 'grid' || siblingData?.layout === 'featured',
|
||||
},
|
||||
},
|
||||
|
||||
// Display Options
|
||||
{
|
||||
name: 'showDescription',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Beschreibung anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showLogo',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Logo anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showTagline',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Tagline anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'useBrandColors',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Markenfarben verwenden',
|
||||
admin: {
|
||||
description: 'Serien-spezifische Farben für Akzente nutzen',
|
||||
},
|
||||
},
|
||||
|
||||
// Filtering
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
defaultValue: 6,
|
||||
min: 1,
|
||||
max: 20,
|
||||
label: 'Anzahl',
|
||||
admin: {
|
||||
description: 'Maximale Anzahl der angezeigten Serien',
|
||||
},
|
||||
},
|
||||
|
||||
// Styling
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
options: backgroundColorOptions,
|
||||
label: 'Hintergrundfarbe',
|
||||
},
|
||||
|
||||
// CTA
|
||||
{
|
||||
name: 'cta',
|
||||
type: 'group',
|
||||
label: 'Call-to-Action',
|
||||
fields: [
|
||||
{
|
||||
name: 'showCta',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'CTA-Button anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'ctaText',
|
||||
type: 'text',
|
||||
label: 'Button-Text',
|
||||
defaultValue: 'Alle Serien ansehen',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showCta,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ctaUrl',
|
||||
type: 'text',
|
||||
label: 'Button-Link',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showCta,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
155
src/blocks/SeriesDetailBlock.ts
Normal file
155
src/blocks/SeriesDetailBlock.ts
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* SeriesDetailBlock
|
||||
*
|
||||
* Für Serien-Einzelseiten mit Hero und Content.
|
||||
* Zeigt die ausgewählte Serie mit Hero, Beschreibung und verwandten Posts.
|
||||
*/
|
||||
export const SeriesDetailBlock: Block = {
|
||||
slug: 'series-detail-block',
|
||||
labels: {
|
||||
singular: 'Serien-Detail',
|
||||
plural: 'Serien-Details',
|
||||
},
|
||||
imageURL: '/assets/blocks/series-detail.png',
|
||||
fields: [
|
||||
// Series Selection
|
||||
{
|
||||
name: 'series',
|
||||
type: 'relationship',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
relationTo: 'series' as any,
|
||||
required: true,
|
||||
label: 'Serie auswählen',
|
||||
admin: {
|
||||
description: 'Die anzuzeigende Serie',
|
||||
},
|
||||
},
|
||||
|
||||
// Display Options
|
||||
{
|
||||
name: 'showHero',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Hero-Bereich anzeigen',
|
||||
admin: {
|
||||
description: 'Cover-Bild mit Logo und Tagline',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showDescription',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Beschreibung anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showBrandColors',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Markenfarben verwenden',
|
||||
admin: {
|
||||
description: 'Serien-spezifische Farben für Akzente nutzen',
|
||||
},
|
||||
},
|
||||
|
||||
// Related Posts
|
||||
{
|
||||
name: 'showRelatedPosts',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Verwandte Beiträge anzeigen',
|
||||
admin: {
|
||||
description: 'Posts die mit dieser Serie verknüpft sind',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'relatedPostsLimit',
|
||||
type: 'number',
|
||||
defaultValue: 6,
|
||||
min: 1,
|
||||
max: 20,
|
||||
label: 'Anzahl verwandter Beiträge',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showRelatedPosts,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'relatedPostsTitle',
|
||||
type: 'text',
|
||||
label: 'Titel für verwandte Beiträge',
|
||||
defaultValue: 'Beiträge aus dieser Serie',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showRelatedPosts,
|
||||
},
|
||||
},
|
||||
|
||||
// YouTube Integration
|
||||
{
|
||||
name: 'showYoutubePlaylist',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'YouTube-Playlist anzeigen',
|
||||
admin: {
|
||||
description: 'Embed der YouTube-Playlist falls konfiguriert',
|
||||
},
|
||||
},
|
||||
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'full',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Vollständig', value: 'full' },
|
||||
{ label: 'Kompakt', value: 'compact' },
|
||||
],
|
||||
},
|
||||
|
||||
// Hero Options (only when showHero is true)
|
||||
{
|
||||
name: 'hero',
|
||||
type: 'group',
|
||||
label: 'Hero-Optionen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showHero,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'height',
|
||||
type: 'select',
|
||||
defaultValue: 'medium',
|
||||
label: 'Hero-Höhe',
|
||||
options: [
|
||||
{ label: 'Klein (300px)', value: 'small' },
|
||||
{ label: 'Mittel (400px)', value: 'medium' },
|
||||
{ label: 'Groß (500px)', value: 'large' },
|
||||
{ label: 'Vollbild', value: 'fullscreen' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'overlay',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Overlay anzeigen',
|
||||
admin: {
|
||||
description: 'Dunkles Overlay für bessere Lesbarkeit',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'textAlign',
|
||||
type: 'select',
|
||||
defaultValue: 'center',
|
||||
label: 'Text-Ausrichtung',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Mitte', value: 'center' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
294
src/blocks/VideoEmbedBlock.ts
Normal file
294
src/blocks/VideoEmbedBlock.ts
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Aspect ratio options
|
||||
*/
|
||||
const aspectRatioOptions = [
|
||||
{ label: '16:9 (Standard)', value: '16:9' },
|
||||
{ label: '4:3', value: '4:3' },
|
||||
{ label: '1:1 (Quadrat)', value: '1:1' },
|
||||
{ label: '9:16 (Vertikal)', value: '9:16' },
|
||||
]
|
||||
|
||||
/**
|
||||
* Max width options
|
||||
*/
|
||||
const maxWidthOptions = [
|
||||
{ label: 'Volle Breite', value: 'full' },
|
||||
{ label: 'Groß (1024px)', value: 'large' },
|
||||
{ label: 'Mittel (768px)', value: 'medium' },
|
||||
{ label: 'Klein (512px)', value: 'small' },
|
||||
]
|
||||
|
||||
/**
|
||||
* VideoEmbedBlock
|
||||
*
|
||||
* Einbettung von YouTube, Vimeo oder benutzerdefinierten Videos.
|
||||
* Unterstützt Privacy Mode, Lazy Loading und verschiedene Aspect Ratios.
|
||||
*
|
||||
* Verwendet die Video-Utils aus src/lib/video/video-utils.ts für URL-Parsing
|
||||
*/
|
||||
export const VideoEmbedBlock: Block = {
|
||||
slug: 'video-embed-block',
|
||||
labels: {
|
||||
singular: 'Video-Embed',
|
||||
plural: 'Video-Embeds',
|
||||
},
|
||||
imageURL: '/assets/blocks/video-embed.png',
|
||||
fields: [
|
||||
// Title (optional)
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Titel',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Optionaler Titel über dem Video',
|
||||
},
|
||||
},
|
||||
|
||||
// Video Source Selection
|
||||
{
|
||||
name: 'videoSource',
|
||||
type: 'select',
|
||||
required: true,
|
||||
defaultValue: 'youtube',
|
||||
label: 'Video-Quelle',
|
||||
options: [
|
||||
{ label: 'YouTube', value: 'youtube' },
|
||||
{ label: 'Vimeo', value: 'vimeo' },
|
||||
{ label: 'Eigene URL', value: 'custom' },
|
||||
],
|
||||
},
|
||||
|
||||
// YouTube URL
|
||||
{
|
||||
name: 'youtubeUrl',
|
||||
type: 'text',
|
||||
label: 'YouTube-URL',
|
||||
admin: {
|
||||
description: 'z.B. https://www.youtube.com/watch?v=VIDEO_ID oder https://youtu.be/VIDEO_ID',
|
||||
condition: (_, siblingData) => siblingData?.videoSource === 'youtube',
|
||||
},
|
||||
validate: (
|
||||
value: string | undefined | null,
|
||||
{ siblingData }: { siblingData?: Record<string, unknown> }
|
||||
) => {
|
||||
if (siblingData?.videoSource !== 'youtube') return true
|
||||
if (!value) return 'YouTube-URL ist erforderlich'
|
||||
|
||||
// Check for valid YouTube URL patterns
|
||||
const youtubePatterns = [
|
||||
/youtube\.com\/watch\?v=/,
|
||||
/youtu\.be\//,
|
||||
/youtube\.com\/embed\//,
|
||||
/youtube\.com\/shorts\//,
|
||||
/youtube-nocookie\.com\/embed\//,
|
||||
]
|
||||
|
||||
const isValidYouTube = youtubePatterns.some((pattern) => pattern.test(value))
|
||||
if (!isValidYouTube) {
|
||||
return 'Bitte eine gültige YouTube-URL eingeben'
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
|
||||
// Vimeo URL
|
||||
{
|
||||
name: 'vimeoUrl',
|
||||
type: 'text',
|
||||
label: 'Vimeo-URL',
|
||||
admin: {
|
||||
description: 'z.B. https://vimeo.com/VIDEO_ID',
|
||||
condition: (_, siblingData) => siblingData?.videoSource === 'vimeo',
|
||||
},
|
||||
validate: (
|
||||
value: string | undefined | null,
|
||||
{ siblingData }: { siblingData?: Record<string, unknown> }
|
||||
) => {
|
||||
if (siblingData?.videoSource !== 'vimeo') return true
|
||||
if (!value) return 'Vimeo-URL ist erforderlich'
|
||||
|
||||
// Check for valid Vimeo URL patterns
|
||||
const vimeoPatterns = [/vimeo\.com\/\d+/, /player\.vimeo\.com\/video\/\d+/]
|
||||
|
||||
const isValidVimeo = vimeoPatterns.some((pattern) => pattern.test(value))
|
||||
if (!isValidVimeo) {
|
||||
return 'Bitte eine gültige Vimeo-URL eingeben'
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
|
||||
// Custom URL
|
||||
{
|
||||
name: 'customUrl',
|
||||
type: 'text',
|
||||
label: 'Video-URL',
|
||||
admin: {
|
||||
description: 'Direkte URL zu einer MP4, WebM oder anderen Video-Datei',
|
||||
condition: (_, siblingData) => siblingData?.videoSource === 'custom',
|
||||
},
|
||||
validate: (
|
||||
value: string | undefined | null,
|
||||
{ siblingData }: { siblingData?: Record<string, unknown> }
|
||||
) => {
|
||||
if (siblingData?.videoSource !== 'custom') return true
|
||||
if (!value) return 'Video-URL ist erforderlich'
|
||||
|
||||
try {
|
||||
new URL(value)
|
||||
return true
|
||||
} catch {
|
||||
return 'Bitte eine gültige URL eingeben'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Custom Thumbnail
|
||||
{
|
||||
name: 'thumbnail',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Vorschaubild',
|
||||
admin: {
|
||||
description:
|
||||
'Optional: Eigenes Vorschaubild. Bei YouTube wird automatisch das Thumbnail verwendet.',
|
||||
},
|
||||
},
|
||||
|
||||
// Caption
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
label: 'Bildunterschrift',
|
||||
admin: {
|
||||
description: 'Optionaler Text unter dem Video',
|
||||
},
|
||||
},
|
||||
|
||||
// Privacy & Performance
|
||||
{
|
||||
name: 'privacyMode',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Privacy Mode',
|
||||
admin: {
|
||||
description: 'YouTube: Verwendet youtube-nocookie.com für besseren Datenschutz',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'lazyLoad',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Lazy Loading',
|
||||
admin: {
|
||||
description: 'Video erst laden, wenn es im Viewport sichtbar ist',
|
||||
},
|
||||
},
|
||||
|
||||
// Display Options
|
||||
{
|
||||
name: 'aspectRatio',
|
||||
type: 'select',
|
||||
defaultValue: '16:9',
|
||||
options: aspectRatioOptions,
|
||||
label: 'Seitenverhältnis',
|
||||
},
|
||||
{
|
||||
name: 'maxWidth',
|
||||
type: 'select',
|
||||
defaultValue: 'large',
|
||||
options: maxWidthOptions,
|
||||
label: 'Maximale Breite',
|
||||
},
|
||||
|
||||
// Playback Options
|
||||
{
|
||||
name: 'playbackOptions',
|
||||
type: 'group',
|
||||
label: 'Wiedergabe-Optionen',
|
||||
fields: [
|
||||
{
|
||||
name: 'autoplay',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Autoplay',
|
||||
admin: {
|
||||
description: 'Automatisch abspielen (erfordert Mute für die meisten Browser)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'muted',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Stumm',
|
||||
admin: {
|
||||
description: 'Video stumm abspielen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'loop',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Endlosschleife',
|
||||
},
|
||||
{
|
||||
name: 'showControls',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Steuerung anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'startTime',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
label: 'Startzeit (Sekunden)',
|
||||
admin: {
|
||||
description: 'Video ab dieser Sekunde starten',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Styling
|
||||
{
|
||||
name: 'style',
|
||||
type: 'group',
|
||||
label: 'Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'alignment',
|
||||
type: 'select',
|
||||
defaultValue: 'center',
|
||||
label: 'Ausrichtung',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Mitte', value: 'center' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'borderRadius',
|
||||
type: 'select',
|
||||
defaultValue: 'md',
|
||||
label: 'Eckenradius',
|
||||
options: [
|
||||
{ label: 'Keine', value: 'none' },
|
||||
{ label: 'Klein', value: 'sm' },
|
||||
{ label: 'Mittel', value: 'md' },
|
||||
{ label: 'Groß', value: 'lg' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'shadow',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Schatten',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -47,3 +47,10 @@ export { ComparisonBlock } from './ComparisonBlock'
|
|||
|
||||
// Tenant-specific Blocks
|
||||
export { BeforeAfterBlock } from './BeforeAfterBlock'
|
||||
|
||||
// BlogWoman Blocks
|
||||
export { FavoritesBlock } from './FavoritesBlock'
|
||||
export { SeriesBlock } from './SeriesBlock'
|
||||
export { SeriesDetailBlock } from './SeriesDetailBlock'
|
||||
export { VideoEmbedBlock } from './VideoEmbedBlock'
|
||||
export { FeaturedContentBlock } from './FeaturedContentBlock'
|
||||
|
|
|
|||
214
src/collections/Favorites.ts
Normal file
214
src/collections/Favorites.ts
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
// src/collections/Favorites.ts
|
||||
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { authenticatedOnly, tenantScopedPublicRead } from '../lib/tenantAccess'
|
||||
|
||||
/**
|
||||
* Category Options for Favorites
|
||||
*/
|
||||
const categoryOptions = [
|
||||
{ label: 'Fashion', value: 'fashion' },
|
||||
{ label: 'Beauty', value: 'beauty' },
|
||||
{ label: 'Travel', value: 'travel' },
|
||||
{ label: 'Tech', value: 'tech' },
|
||||
{ label: 'Home', value: 'home' },
|
||||
]
|
||||
|
||||
/**
|
||||
* Badge Options for Favorites
|
||||
*/
|
||||
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 for Favorites
|
||||
*/
|
||||
const priceRangeOptions = [
|
||||
{ label: 'Budget (< €50)', value: 'budget' },
|
||||
{ label: 'Mid (€50-150)', value: 'mid' },
|
||||
{ label: 'Premium (€150-500)', value: 'premium' },
|
||||
{ label: 'Luxury (> €500)', value: 'luxury' },
|
||||
]
|
||||
|
||||
/**
|
||||
* Affiliate Network Options
|
||||
*/
|
||||
const affiliateNetworkOptions = [
|
||||
{ label: 'Amazon', value: 'amazon' },
|
||||
{ label: 'Awin', value: 'awin' },
|
||||
{ label: 'LTK (RewardStyle)', value: 'ltk' },
|
||||
{ label: 'Direct', value: 'direct' },
|
||||
{ label: 'Other', value: 'other' },
|
||||
]
|
||||
|
||||
/**
|
||||
* Favorites Collection
|
||||
*
|
||||
* Affiliate-Produkte und Favoriten für BlogWoman.de
|
||||
* Tenant-scoped für Multi-Tenant-Betrieb.
|
||||
*/
|
||||
export const Favorites: CollectionConfig = {
|
||||
slug: 'favorites',
|
||||
admin: {
|
||||
group: 'Content',
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'category', 'featured', 'isActive'],
|
||||
description: 'Affiliate-Produkte und Favoriten',
|
||||
},
|
||||
access: {
|
||||
read: tenantScopedPublicRead,
|
||||
create: authenticatedOnly,
|
||||
update: authenticatedOnly,
|
||||
delete: authenticatedOnly,
|
||||
},
|
||||
fields: [
|
||||
// Main Content
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
maxLength: 200,
|
||||
label: 'Titel',
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
label: 'Slug',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'URL-freundlicher Name',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
maxLength: 300,
|
||||
label: 'Beschreibung',
|
||||
admin: {
|
||||
description: 'Kurze Produktbeschreibung (max. 300 Zeichen)',
|
||||
},
|
||||
},
|
||||
|
||||
// Categorization
|
||||
{
|
||||
name: 'category',
|
||||
type: 'select',
|
||||
required: true,
|
||||
options: categoryOptions,
|
||||
label: 'Kategorie',
|
||||
},
|
||||
{
|
||||
name: 'subcategory',
|
||||
type: 'text',
|
||||
maxLength: 100,
|
||||
label: 'Unterkategorie',
|
||||
admin: {
|
||||
description: 'z.B. "Taschen", "Hautpflege", "Laptops"',
|
||||
},
|
||||
},
|
||||
|
||||
// Pricing
|
||||
{
|
||||
name: 'price',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
label: 'Preis (€)',
|
||||
admin: {
|
||||
description: 'Aktueller Preis in Euro',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'priceRange',
|
||||
type: 'select',
|
||||
options: priceRangeOptions,
|
||||
label: 'Preiskategorie',
|
||||
},
|
||||
|
||||
// Affiliate
|
||||
{
|
||||
name: 'affiliateUrl',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Affiliate-URL',
|
||||
admin: {
|
||||
description: 'Vollständiger Affiliate-Link mit Tracking',
|
||||
},
|
||||
validate: (value: string | undefined | null) => {
|
||||
if (!value) return 'Affiliate-URL ist erforderlich'
|
||||
try {
|
||||
new URL(value)
|
||||
return true
|
||||
} catch {
|
||||
return 'Bitte eine gültige URL eingeben'
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'affiliateNetwork',
|
||||
type: 'select',
|
||||
options: affiliateNetworkOptions,
|
||||
label: 'Affiliate-Netzwerk',
|
||||
},
|
||||
|
||||
// Media
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
required: true,
|
||||
label: 'Produktbild',
|
||||
admin: {
|
||||
description: 'Hauptbild des Produkts',
|
||||
},
|
||||
},
|
||||
|
||||
// Display Options
|
||||
{
|
||||
name: 'badge',
|
||||
type: 'select',
|
||||
options: badgeOptions,
|
||||
label: 'Badge',
|
||||
admin: {
|
||||
description: 'Optionales Label/Abzeichen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'featured',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Featured',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'In der Featured-Sektion anzeigen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isActive',
|
||||
type: 'checkbox',
|
||||
required: true,
|
||||
defaultValue: true,
|
||||
label: 'Aktiv',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Inaktive Favoriten werden nicht angezeigt',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'order',
|
||||
type: 'number',
|
||||
defaultValue: 0,
|
||||
label: 'Sortierung',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Niedrigere Zahlen werden zuerst angezeigt',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -43,6 +43,12 @@ import {
|
|||
ComparisonBlock,
|
||||
// Tenant-specific Blocks
|
||||
BeforeAfterBlock,
|
||||
// BlogWoman Blocks
|
||||
FavoritesBlock,
|
||||
SeriesBlock,
|
||||
SeriesDetailBlock,
|
||||
VideoEmbedBlock,
|
||||
FeaturedContentBlock,
|
||||
} from '../blocks'
|
||||
import { pagesAccess } from '../lib/access'
|
||||
|
||||
|
|
@ -140,6 +146,12 @@ export const Pages: CollectionConfig = {
|
|||
ComparisonBlock,
|
||||
// Tenant-specific Blocks
|
||||
BeforeAfterBlock,
|
||||
// BlogWoman Blocks
|
||||
FavoritesBlock,
|
||||
SeriesBlock,
|
||||
SeriesDetailBlock,
|
||||
VideoEmbedBlock,
|
||||
FeaturedContentBlock,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
173
src/collections/Series.ts
Normal file
173
src/collections/Series.ts
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
// src/collections/Series.ts
|
||||
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { authenticatedOnly, tenantScopedPublicRead } from '../lib/tenantAccess'
|
||||
|
||||
/**
|
||||
* Series Collection
|
||||
*
|
||||
* YouTube-Serien mit eigenem Branding für BlogWoman.de
|
||||
* Unterstützt lokalisierte Inhalte und Custom Brand Colors.
|
||||
* Tenant-scoped für Multi-Tenant-Betrieb.
|
||||
*/
|
||||
export const Series: CollectionConfig = {
|
||||
slug: 'series',
|
||||
admin: {
|
||||
group: 'Content',
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'slug', 'brandColor', 'isActive'],
|
||||
description: 'YouTube-Serien und Content-Reihen',
|
||||
},
|
||||
access: {
|
||||
read: tenantScopedPublicRead,
|
||||
create: authenticatedOnly,
|
||||
update: authenticatedOnly,
|
||||
delete: authenticatedOnly,
|
||||
},
|
||||
fields: [
|
||||
// Main Content
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
label: 'Titel',
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
label: 'Slug',
|
||||
admin: {
|
||||
description: 'URL-freundlicher Name (z.B. "grfi-series")',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'tagline',
|
||||
type: 'text',
|
||||
maxLength: 150,
|
||||
localized: true,
|
||||
label: 'Tagline',
|
||||
admin: {
|
||||
description: 'Kurzer Slogan oder Untertitel (max. 150 Zeichen)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'richText',
|
||||
localized: true,
|
||||
label: 'Beschreibung',
|
||||
admin: {
|
||||
description: 'Ausführliche Beschreibung der Serie',
|
||||
},
|
||||
},
|
||||
|
||||
// Branding
|
||||
{
|
||||
name: 'logo',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Logo',
|
||||
admin: {
|
||||
description: 'Serien-Logo (empfohlen: transparent PNG)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'coverImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Cover-Bild',
|
||||
admin: {
|
||||
description: 'Hauptbild für die Serie (16:9 empfohlen)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'brandColor',
|
||||
type: 'text',
|
||||
label: 'Markenfarbe',
|
||||
admin: {
|
||||
description: 'Hex-Farbcode (z.B. #B08D57)',
|
||||
placeholder: '#B08D57',
|
||||
},
|
||||
validate: (value: string | undefined | null) => {
|
||||
if (!value) return true // Optional field
|
||||
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
|
||||
if (!hexRegex.test(value)) {
|
||||
return 'Bitte einen gültigen Hex-Farbcode eingeben (z.B. #B08D57)'
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'accentColor',
|
||||
type: 'text',
|
||||
label: 'Akzentfarbe',
|
||||
admin: {
|
||||
description: 'Sekundäre Farbe (z.B. #FFFFFF)',
|
||||
placeholder: '#FFFFFF',
|
||||
},
|
||||
validate: (value: string | undefined | null) => {
|
||||
if (!value) return true // Optional field
|
||||
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
|
||||
if (!hexRegex.test(value)) {
|
||||
return 'Bitte einen gültigen Hex-Farbcode eingeben (z.B. #FFFFFF)'
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
|
||||
// YouTube Integration
|
||||
{
|
||||
name: 'youtubePlaylistId',
|
||||
type: 'text',
|
||||
label: 'YouTube Playlist ID',
|
||||
admin: {
|
||||
description: 'Die Playlist-ID aus YouTube (z.B. PLxxxxxx)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'youtubePlaylistUrl',
|
||||
type: 'text',
|
||||
label: 'YouTube Playlist URL',
|
||||
admin: {
|
||||
description: 'Vollständiger Link zur YouTube-Playlist',
|
||||
},
|
||||
validate: (value: string | undefined | null) => {
|
||||
if (!value) return true // Optional field
|
||||
try {
|
||||
const url = new URL(value)
|
||||
if (!url.hostname.includes('youtube.com') && !url.hostname.includes('youtu.be')) {
|
||||
return 'Bitte eine gültige YouTube-URL eingeben'
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
return 'Bitte eine gültige URL eingeben'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Display Settings
|
||||
{
|
||||
name: 'order',
|
||||
type: 'number',
|
||||
defaultValue: 0,
|
||||
label: 'Sortierung',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Niedrigere Zahlen werden zuerst angezeigt',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isActive',
|
||||
type: 'checkbox',
|
||||
required: true,
|
||||
defaultValue: true,
|
||||
label: 'Aktiv',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Inaktive Serien werden nicht angezeigt',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -64,6 +64,10 @@ import { Bookings } from './collections/Bookings'
|
|||
import { Certifications } from './collections/Certifications'
|
||||
import { Projects } from './collections/Projects'
|
||||
|
||||
// BlogWoman Collections
|
||||
import { Favorites } from './collections/Favorites'
|
||||
import { Series } from './collections/Series'
|
||||
|
||||
// Consent Management Collections
|
||||
import { CookieConfigurations } from './collections/CookieConfigurations'
|
||||
import { CookieInventory } from './collections/CookieInventory'
|
||||
|
|
@ -197,6 +201,9 @@ export default buildConfig({
|
|||
Bookings,
|
||||
Certifications,
|
||||
Projects,
|
||||
// BlogWoman Collections
|
||||
Favorites,
|
||||
Series,
|
||||
// Consent Management
|
||||
CookieConfigurations,
|
||||
CookieInventory,
|
||||
|
|
@ -266,6 +273,9 @@ export default buildConfig({
|
|||
bookings: {},
|
||||
certifications: {},
|
||||
projects: {},
|
||||
// BlogWoman Collections
|
||||
favorites: {},
|
||||
series: {},
|
||||
// Consent Management Collections - customTenantField: true weil sie bereits ein tenant-Feld haben
|
||||
'cookie-configurations': { customTenantField: true },
|
||||
'cookie-inventory': { customTenantField: true },
|
||||
|
|
|
|||
Loading…
Reference in a new issue