mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 19:44: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
|
\dt -- Alle Tabellen
|
||||||
```
|
```
|
||||||
|
|
||||||
## Blocks Übersicht
|
## Blocks Übersicht (42 Blocks)
|
||||||
|
|
||||||
|
### Core Blocks
|
||||||
| Block | Slug | Beschreibung |
|
| Block | Slug | Beschreibung |
|
||||||
|-------|------|--------------|
|
|-------|------|--------------|
|
||||||
| HeroBlock | hero-block | Einzelner Hero mit Bild, Headline, CTA |
|
| HeroBlock | hero-block | Einzelner Hero mit Bild, Headline, CTA |
|
||||||
| HeroSliderBlock | hero-slider-block | Hero-Slider mit mehreren Slides |
|
| HeroSliderBlock | hero-slider-block | Hero-Slider mit mehreren Slides |
|
||||||
|
| ImageSliderBlock | image-slider-block | Bildergalerie/Karussell |
|
||||||
| TextBlock | text-block | Textinhalt mit Rich-Text |
|
| TextBlock | text-block | Textinhalt mit Rich-Text |
|
||||||
| ImageTextBlock | image-text-block | Bild + Text nebeneinander |
|
| ImageTextBlock | image-text-block | Bild + Text nebeneinander |
|
||||||
| CardGridBlock | card-grid-block | Karten-Raster |
|
| 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 |
|
| TimelineBlock | timeline-block | Timeline-Darstellung |
|
||||||
| DividerBlock | divider-block | Trennlinie |
|
| DividerBlock | divider-block | Trennlinie |
|
||||||
| VideoBlock | video-block | Video einbetten |
|
| VideoBlock | video-block | Video einbetten |
|
||||||
|
|
||||||
|
### Content Blocks
|
||||||
|
| Block | Slug | Beschreibung |
|
||||||
|
|-------|------|--------------|
|
||||||
| PostsListBlock | posts-list-block | Beitrags-Liste |
|
| PostsListBlock | posts-list-block | Beitrags-Liste |
|
||||||
| TestimonialsBlock | testimonials-block | Kundenbewertungen |
|
| TestimonialsBlock | testimonials-block | Kundenbewertungen |
|
||||||
| NewsletterBlock | newsletter-block | Newsletter-Anmeldung |
|
| 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 |
|
| FAQBlock | faq-block | FAQ-Akkordeon |
|
||||||
| TeamBlock | team-block | Team-Mitglieder |
|
| TeamBlock | team-block | Team-Mitglieder |
|
||||||
| ServicesBlock | services-block | Leistungen |
|
| 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) |
|
| 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
|
### HeroSliderBlock Features
|
||||||
|
|
||||||
Vollwertiger Hero-Slider mit:
|
Vollwertiger Hero-Slider mit:
|
||||||
|
|
@ -761,39 +813,95 @@ Vollwertiger Hero-Slider mit:
|
||||||
- Separate Mobile-Höhe
|
- Separate Mobile-Höhe
|
||||||
- Content-Breite
|
- Content-Breite
|
||||||
|
|
||||||
## Collections Übersicht
|
## Collections Übersicht (40+ Collections)
|
||||||
|
|
||||||
|
### Core Collections
|
||||||
| Collection | Slug | Beschreibung |
|
| Collection | Slug | Beschreibung |
|
||||||
|------------|------|--------------|
|
|------------|------|--------------|
|
||||||
| Users | users | Benutzer mit isSuperAdmin Flag |
|
| Users | users | Benutzer mit isSuperAdmin Flag |
|
||||||
| Tenants | tenants | Mandanten mit E-Mail-Konfiguration |
|
| Tenants | tenants | Mandanten mit E-Mail-Konfiguration |
|
||||||
| Media | media | Medien mit 11 responsive Image Sizes |
|
| Media | media | Medien mit 11 responsive Image Sizes |
|
||||||
| Pages | pages | Seiten mit Blocks |
|
| Pages | pages | Seiten mit Blocks |
|
||||||
|
|
||||||
|
### Content Collections
|
||||||
|
| Collection | Slug | Beschreibung |
|
||||||
|
|------------|------|--------------|
|
||||||
| Posts | posts | Blog/News/Presse mit Kategorien |
|
| Posts | posts | Blog/News/Presse mit Kategorien |
|
||||||
| Categories | categories | Kategorien für Posts |
|
| Categories | categories | Kategorien für Posts |
|
||||||
| Portfolios | portfolios | Portfolio-Galerien (Fotografie) |
|
| Tags | tags | Tags für Posts (Blogging) |
|
||||||
| PortfolioCategories | portfolio-categories | Kategorien für Portfolios |
|
| Authors | authors | Autoren für Posts |
|
||||||
| Testimonials | testimonials | Kundenbewertungen |
|
| Testimonials | testimonials | Kundenbewertungen |
|
||||||
| FAQs | faqs | Häufig gestellte Fragen (FAQ) |
|
| FAQs | faqs | Häufig gestellte Fragen (FAQ) |
|
||||||
|
| SocialLinks | social-links | Social Media Links |
|
||||||
|
|
||||||
|
### Team & Services
|
||||||
|
| Collection | Slug | Beschreibung |
|
||||||
|
|------------|------|--------------|
|
||||||
| Team | team | Team-Mitglieder und Mitarbeiter |
|
| Team | team | Team-Mitglieder und Mitarbeiter |
|
||||||
| ServiceCategories | service-categories | Kategorien für Leistungen |
|
| ServiceCategories | service-categories | Kategorien für Leistungen |
|
||||||
| Services | services | Leistungen und Dienstleistungen |
|
| Services | services | Leistungen und Dienstleistungen |
|
||||||
| NewsletterSubscribers | newsletter-subscribers | Newsletter mit Double Opt-In |
|
| Jobs | jobs | Stellenangebote |
|
||||||
| SocialLinks | social-links | Social Media Links |
|
|
||||||
| Forms | forms | Formular-Builder |
|
### Portfolio & Media
|
||||||
| FormSubmissions | form-submissions | Formular-Einsendungen mit Status-Workflow |
|
| Collection | Slug | Beschreibung |
|
||||||
| EmailLogs | email-logs | E-Mail-Protokollierung |
|
|------------|------|--------------|
|
||||||
| AuditLogs | audit-logs | Security Audit Trail |
|
| Portfolios | portfolios | Portfolio-Galerien (Fotografie) |
|
||||||
| CookieConfigurations | cookie-configurations | Cookie-Banner Konfiguration |
|
| PortfolioCategories | portfolio-categories | Kategorien für Portfolios |
|
||||||
| CookieInventory | cookie-inventory | Cookie-Inventar |
|
| Videos | videos | Video-Bibliothek mit YouTube/Vimeo/Uploads |
|
||||||
| ConsentLogs | consent-logs | Consent-Protokollierung |
|
| 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) |
|
| Timelines | timelines | Chronologische Events (Geschichte, Meilensteine) |
|
||||||
| Workflows | workflows | Komplexe Prozesse mit Phasen und Schritten |
|
| 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) |
|
| Bookings | bookings | Fotografie-Buchungen (porwoll.de) |
|
||||||
| Certifications | certifications | Zertifizierungen (C2S) |
|
| Certifications | certifications | Zertifizierungen (C2S) |
|
||||||
| Projects | projects | Game-Development-Projekte (gunshin.de) |
|
| 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
|
## Timeline Collection
|
||||||
|
|
||||||
|
|
@ -930,12 +1038,11 @@ Die FormSubmissions Collection wurde zu einem leichtgewichtigen CRM erweitert:
|
||||||
|
|
||||||
## Globals
|
## Globals
|
||||||
|
|
||||||
|
> **Hinweis:** SiteSettings, Navigations und PrivacyPolicySettings wurden zu tenant-spezifischen Collections umgewandelt (siehe Collections Übersicht).
|
||||||
|
|
||||||
| Global | Slug | Beschreibung |
|
| Global | Slug | Beschreibung |
|
||||||
|--------|------|--------------|
|
|--------|------|--------------|
|
||||||
| SiteSettings | site-settings | Allgemeine Website-Einstellungen |
|
| SEOSettings | seo-settings | Globale SEO-Einstellungen (systemweit) |
|
||||||
| Navigation | navigation | Navigationsmenü |
|
|
||||||
| SEOSettings | seo-settings | SEO-Einstellungen |
|
|
||||||
| PrivacyPolicySettings | privacy-policy-settings | Datenschutz-Einstellungen |
|
|
||||||
|
|
||||||
## Test Suite
|
## Test Suite
|
||||||
|
|
||||||
|
|
@ -1070,4 +1177,4 @@ ssh payload@162.55.85.18
|
||||||
### Scripts & Backup
|
### Scripts & Backup
|
||||||
- `scripts/backup/README.md` - Backup-System Dokumentation
|
- `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
|
# 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
|
## Übersicht
|
||||||
|
|
||||||
|
|
@ -94,61 +96,59 @@ pm2 logs payload --lines 20
|
||||||
|
|
||||||
## Production Deployment (main → cms.c2sgmbh.de)
|
## Production Deployment (main → cms.c2sgmbh.de)
|
||||||
|
|
||||||
### Schritt 1: Merge zu main
|
### Option A: Via GitHub Actions (Empfohlen)
|
||||||
|
|
||||||
```bash
|
1. **develop in main mergen und pushen**
|
||||||
# Auf dem Development-Server oder lokal
|
```bash
|
||||||
git checkout main
|
git checkout main
|
||||||
git pull origin main
|
git pull origin main
|
||||||
git merge develop
|
git merge develop
|
||||||
git push origin main
|
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
|
```bash
|
||||||
# SSH zum Production-Server
|
# SSH zum Production-Server
|
||||||
ssh payload@162.55.85.18
|
ssh payload@162.55.85.18
|
||||||
|
|
||||||
# Deploy-Script ausführen
|
# Deploy-Script ausführen (mit Backup, Health Check, Rollback-Fähigkeit)
|
||||||
~/deploy.sh
|
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
|
```bash
|
||||||
#!/bin/bash
|
# Automatischer Rollback zur vorherigen Version
|
||||||
set -e
|
./scripts/deploy-production.sh --rollback
|
||||||
|
|
||||||
cd ~/payload-cms
|
# Oder manuell zu spezifischem Commit
|
||||||
|
git log --oneline -10
|
||||||
echo "📥 Pulling latest changes..."
|
git reset --hard <commit-sha>
|
||||||
git pull origin main
|
pnpm install --frozen-lockfile
|
||||||
|
|
||||||
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
|
|
||||||
pnpm build
|
pnpm build
|
||||||
pm2 restart payload
|
pm2 restart payload
|
||||||
```
|
```
|
||||||
|
|
@ -396,15 +396,17 @@ TRUST_PROXY=true
|
||||||
|
|
||||||
| Workflow | Trigger | Aktion |
|
| 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 |
|
| `security.yml` | Push/PR, Schedule | Security Scanning |
|
||||||
| `deploy-staging.yml` | Push auf develop | Auto-Deploy zu Staging |
|
| `deploy-staging.yml` | Push auf develop | Auto-Deploy zu Staging |
|
||||||
|
| `deploy-production.yml` | Manuell (workflow_dispatch) | Production Deployment |
|
||||||
|
|
||||||
### Secrets (GitHub)
|
### Secrets (GitHub)
|
||||||
|
|
||||||
| Secret | Beschreibung |
|
| Secret | Beschreibung |
|
||||||
|--------|--------------|
|
|--------|--------------|
|
||||||
| `STAGING_SSH_KEY` | SSH Private Key für sv-payload |
|
| `STAGING_SSH_KEY` | SSH Private Key für sv-payload |
|
||||||
|
| `PRODUCTION_SSH_KEY` | SSH Private Key für Hetzner 3 |
|
||||||
|
|
||||||
### Manuelles Deployment triggern
|
### 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
|
# 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
|
## Zusammenfassung
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Infrastruktur Dokumentation
|
# Infrastruktur Dokumentation
|
||||||
|
|
||||||
*Letzte Aktualisierung: 18. Dezember 2025*
|
*Letzte Aktualisierung: 29. Dezember 2025*
|
||||||
|
|
||||||
## Gesamtübersicht
|
## Gesamtübersicht
|
||||||
|
|
||||||
|
|
@ -84,10 +84,10 @@
|
||||||
### Software Stack
|
### Software Stack
|
||||||
- Node.js 22.x
|
- Node.js 22.x
|
||||||
- pnpm
|
- pnpm
|
||||||
- Next.js 16.0.10
|
- Next.js 15.5.9
|
||||||
- Claude Code 2.0.72
|
- Claude Code (aktuell)
|
||||||
- Codex CLI 0.73.0
|
- Codex CLI (aktuell)
|
||||||
- Gemini CLI 0.21.2
|
- Gemini CLI (aktuell)
|
||||||
|
|
||||||
### Projekte & Ports
|
### 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
|
# Projekt Status - Dezember 2025
|
||||||
|
|
||||||
**Stand:** 27. Dezember 2025
|
**Stand:** 29. Dezember 2025
|
||||||
|
|
||||||
## Zusammenfassung
|
## Zusammenfassung
|
||||||
|
|
||||||
|
|
@ -177,6 +177,15 @@ pm2 logs payload
|
||||||
|
|
||||||
## 📝 Änderungsprotokoll
|
## 📝 Ä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
|
### 27.12.2025
|
||||||
- Payload CMS Update 3.68.4 → 3.69.0
|
- Payload CMS Update 3.68.4 → 3.69.0
|
||||||
- Bug-Fixes Admin Panel:
|
- Bug-Fixes Admin Panel:
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
# Staging Deployment
|
# Staging Deployment
|
||||||
|
|
||||||
> **Staging URL:** https://pl.c2sgmbh.de
|
> **Staging URL:** https://pl.porwoll.tech
|
||||||
> **Server:** sv-payload (37.24.237.181)
|
> **Server:** sv-payload (37.24.237.181)
|
||||||
> **Branch:** `develop`
|
> **Branch:** `develop`
|
||||||
|
|
||||||
|
> **Siehe auch:** [DEPLOYMENT_STRATEGY.md](./DEPLOYMENT_STRATEGY.md) für die vollständige Deployment-Strategie (Dev → Prod)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Übersicht
|
## Übersicht
|
||||||
|
|
@ -14,7 +16,7 @@
|
||||||
│ │
|
│ │
|
||||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────────┐│
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────────┐│
|
||||||
│ │ Developer │ │ GitHub │ │ Staging Server ││
|
│ │ Developer │ │ GitHub │ │ Staging Server ││
|
||||||
│ │ │ │ Actions │ │ pl.c2sgmbh.de ││
|
│ │ │ │ Actions │ │ pl.porwoll.tech ││
|
||||||
│ └──────┬───────┘ └──────┬───────┘ └──────────────┬───────────────┘│
|
│ └──────┬───────┘ └──────┬───────┘ └──────────────┬───────────────┘│
|
||||||
│ │ │ │ │
|
│ │ │ │ │
|
||||||
│ │ git push │ │ │
|
│ │ git push │ │ │
|
||||||
|
|
@ -80,7 +82,7 @@ Jobs: deploy
|
||||||
|
|
||||||
### 3. Verify Deployment
|
### 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
|
- Bei Fehler: Benachrichtigung im Workflow-Summary
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -233,7 +235,7 @@ main (Produktion)
|
||||||
| Branch | Deployment | URL |
|
| Branch | Deployment | URL |
|
||||||
|--------|------------|-----|
|
|--------|------------|-----|
|
||||||
| `main` | Produktion (manuell) | cms.c2sgmbh.de |
|
| `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.
|
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
|
### Login
|
||||||
|
|
||||||
```bash
|
```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" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"email": "admin@example.com",
|
"email": "admin@example.com",
|
||||||
|
|
@ -38,7 +38,7 @@ curl -X POST "https://pl.c2sgmbh.de/api/users/login" \
|
||||||
### Token verwenden
|
### Token verwenden
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://pl.c2sgmbh.de/api/posts" \
|
curl "https://pl.porwoll.tech/api/posts" \
|
||||||
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIs..."
|
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIs..."
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -50,13 +50,13 @@ Das CMS unterstützt Deutsch (de) und Englisch (en). Lokalisierte Felder können
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Deutsche Inhalte (Standard)
|
# Deutsche Inhalte (Standard)
|
||||||
curl "https://pl.c2sgmbh.de/api/posts?locale=de"
|
curl "https://pl.porwoll.tech/api/posts?locale=de"
|
||||||
|
|
||||||
# Englische Inhalte
|
# Englische Inhalte
|
||||||
curl "https://pl.c2sgmbh.de/api/posts?locale=en"
|
curl "https://pl.porwoll.tech/api/posts?locale=en"
|
||||||
|
|
||||||
# Alle Sprachen gleichzeitig
|
# 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
|
### Aktuellen User abrufen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://pl.c2sgmbh.de/api/users/me" \
|
curl "https://pl.porwoll.tech/api/users/me" \
|
||||||
-H "Authorization: JWT your-token"
|
-H "Authorization: JWT your-token"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -85,14 +85,14 @@ curl "https://pl.c2sgmbh.de/api/users/me" \
|
||||||
### Alle Tenants abrufen (Auth + SuperAdmin erforderlich)
|
### Alle Tenants abrufen (Auth + SuperAdmin erforderlich)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://pl.c2sgmbh.de/api/tenants" \
|
curl "https://pl.porwoll.tech/api/tenants" \
|
||||||
-H "Authorization: JWT your-token"
|
-H "Authorization: JWT your-token"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tenant erstellen (Auth + SuperAdmin erforderlich)
|
### Tenant erstellen (Auth + SuperAdmin erforderlich)
|
||||||
|
|
||||||
```bash
|
```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 "Authorization: JWT your-token" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
|
|
@ -121,47 +121,47 @@ curl -X POST "https://pl.c2sgmbh.de/api/tenants" \
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Alle Posts
|
# Alle Posts
|
||||||
curl "https://pl.c2sgmbh.de/api/posts"
|
curl "https://pl.porwoll.tech/api/posts"
|
||||||
|
|
||||||
# Nur Blog-Artikel
|
# 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
|
# 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
|
# 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
|
# 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)
|
# 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
|
# 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)
|
# 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
|
# Mit Locale
|
||||||
curl "https://pl.c2sgmbh.de/api/posts?locale=de"
|
curl "https://pl.porwoll.tech/api/posts?locale=de"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Einzelnen Post abrufen
|
### Einzelnen Post abrufen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Nach ID
|
# Nach ID
|
||||||
curl "https://pl.c2sgmbh.de/api/posts/1"
|
curl "https://pl.porwoll.tech/api/posts/1"
|
||||||
|
|
||||||
# Nach Slug (über Query)
|
# 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)
|
### Post erstellen (Auth erforderlich)
|
||||||
|
|
||||||
```bash
|
```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 "Authorization: JWT your-token" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
|
|
@ -189,7 +189,7 @@ curl -X POST "https://pl.c2sgmbh.de/api/posts" \
|
||||||
### Post aktualisieren (Auth erforderlich)
|
### Post aktualisieren (Auth erforderlich)
|
||||||
|
|
||||||
```bash
|
```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 "Authorization: JWT your-token" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
|
|
@ -201,7 +201,7 @@ curl -X PATCH "https://pl.c2sgmbh.de/api/posts/1" \
|
||||||
### Post löschen (Auth erforderlich)
|
### Post löschen (Auth erforderlich)
|
||||||
|
|
||||||
```bash
|
```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"
|
-H "Authorization: JWT your-token"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -213,22 +213,22 @@ curl -X DELETE "https://pl.c2sgmbh.de/api/posts/1" \
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Alle Testimonials
|
# Alle Testimonials
|
||||||
curl "https://pl.c2sgmbh.de/api/testimonials"
|
curl "https://pl.porwoll.tech/api/testimonials"
|
||||||
|
|
||||||
# Nur aktive 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)
|
# 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
|
# 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)
|
### Testimonial erstellen (Auth erforderlich)
|
||||||
|
|
||||||
```bash
|
```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 "Authorization: JWT your-token" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
|
|
@ -252,7 +252,7 @@ curl -X POST "https://pl.c2sgmbh.de/api/testimonials" \
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Einfache Anmeldung
|
# 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" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"tenant": 1,
|
"tenant": 1,
|
||||||
|
|
@ -261,7 +261,7 @@ curl -X POST "https://pl.c2sgmbh.de/api/newsletter-subscribers" \
|
||||||
}'
|
}'
|
||||||
|
|
||||||
# Mit Namen und Interessen
|
# 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" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"tenant": 1,
|
"tenant": 1,
|
||||||
|
|
@ -292,15 +292,15 @@ curl -X POST "https://pl.c2sgmbh.de/api/newsletter-subscribers" \
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Alle Subscribers
|
# Alle Subscribers
|
||||||
curl "https://pl.c2sgmbh.de/api/newsletter-subscribers" \
|
curl "https://pl.porwoll.tech/api/newsletter-subscribers" \
|
||||||
-H "Authorization: JWT your-token"
|
-H "Authorization: JWT your-token"
|
||||||
|
|
||||||
# Nur bestätigte Subscribers
|
# 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"
|
-H "Authorization: JWT your-token"
|
||||||
|
|
||||||
# Nach E-Mail suchen
|
# 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"
|
-H "Authorization: JWT your-token"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -308,7 +308,7 @@ curl "https://pl.c2sgmbh.de/api/newsletter-subscribers?where[email][equals]=kund
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Über Token bestätigen
|
# Ü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 "Authorization: JWT your-token" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
|
|
@ -319,7 +319,7 @@ curl -X PATCH "https://pl.c2sgmbh.de/api/newsletter-subscribers/1" \
|
||||||
### Subscriber abmelden
|
### Subscriber abmelden
|
||||||
|
|
||||||
```bash
|
```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 "Authorization: JWT your-token" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
|
|
@ -335,16 +335,16 @@ curl -X PATCH "https://pl.c2sgmbh.de/api/newsletter-subscribers/1" \
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Alle Seiten
|
# Alle Seiten
|
||||||
curl "https://pl.c2sgmbh.de/api/pages"
|
curl "https://pl.porwoll.tech/api/pages"
|
||||||
|
|
||||||
# Seite nach Slug
|
# 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
|
# 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
|
# 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
|
### Seite mit Blocks
|
||||||
|
|
@ -389,7 +389,7 @@ Die Cookie-Konfiguration wird automatisch nach Domain gefiltert.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Für Frontend Cookie-Banner
|
# 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:**
|
**Response:**
|
||||||
|
|
@ -432,13 +432,13 @@ Dokumentation aller verwendeten Cookies für die Datenschutzerklärung.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Alle Cookies eines Tenants
|
# 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
|
# 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
|
# 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:**
|
**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.
|
Consent-Logs sind ein WORM (Write-Once-Read-Many) Audit-Trail für DSGVO-Nachweise.
|
||||||
|
|
||||||
```bash
|
```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 "Content-Type: application/json" \
|
||||||
-H "X-API-Key: your-consent-api-key" \
|
-H "X-API-Key: your-consent-api-key" \
|
||||||
-d '{
|
-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).
|
Konfiguration für die Datenschutzerklärungs-Seite (z.B. Alfright Integration).
|
||||||
|
|
||||||
```bash
|
```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:**
|
**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
|
1. **Admin Panel:** Unter "Settings → Tenants" die ID ablesen
|
||||||
2. **API:**
|
2. **API:**
|
||||||
```bash
|
```bash
|
||||||
curl "https://pl.c2sgmbh.de/api/tenants" \
|
curl "https://pl.porwoll.tech/api/tenants" \
|
||||||
-H "Authorization: JWT your-token"
|
-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:
|
Für direkte API-Zugriffe:
|
||||||
```bash
|
```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
|
### Super Admin
|
||||||
|
|
@ -720,7 +720,7 @@ User mit `isSuperAdmin: true` haben Zugriff auf alle Tenants und können neue Te
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// lib/api.ts
|
// lib/api.ts
|
||||||
const API_BASE = 'https://pl.c2sgmbh.de/api'
|
const API_BASE = 'https://pl.porwoll.tech/api'
|
||||||
const TENANT_ID = 1
|
const TENANT_ID = 1
|
||||||
|
|
||||||
export async function getPosts(type?: string, limit = 10, locale = 'de') {
|
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
|
## Weitere Ressourcen
|
||||||
|
|
||||||
- **Admin Panel:** https://pl.c2sgmbh.de/admin
|
- **Admin Panel:** https://pl.porwoll.tech/admin
|
||||||
- **Payload CMS Docs:** https://payloadcms.com/docs
|
- **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
|
# Security-Richtlinien - Payload CMS Multi-Tenant
|
||||||
|
|
||||||
> Letzte Aktualisierung: 18.12.2025
|
> Letzte Aktualisierung: 29.12.2025
|
||||||
|
|
||||||
## Übersicht
|
## Übersicht
|
||||||
|
|
||||||
|
|
@ -323,29 +323,13 @@ email=admin@example.com&password=secret
|
||||||
- Rate-Limiting verhindert Brute-Force-Angriffe
|
- Rate-Limiting verhindert Brute-Force-Angriffe
|
||||||
- Open Redirect Prevention durch URL-Validierung
|
- 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
|
## Änderungshistorie
|
||||||
|
|
||||||
| Datum | Änderung |
|
| 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 |
|
| 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 |
|
| 09.12.2025 | Custom Login Route Dokumentation, multipart/form-data _payload Support |
|
||||||
| 08.12.2025 | Security Test Suite (143 Tests) |
|
| 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/ip-allowlist.ts` | IP-basierte Zugriffskontrolle |
|
||||||
| `src/lib/security/csrf.ts` | CSRF Token Generation & Validation |
|
| `src/lib/security/csrf.ts` | CSRF Token Generation & Validation |
|
||||||
| `src/lib/security/data-masking.ts` | Sensitive Data Masking |
|
| `src/lib/security/data-masking.ts` | Sensitive Data Masking |
|
||||||
| `src/app/(payload)/api/users/login/route.ts` | Custom Login API |
|
| `src/app/(payload)/api/users/login/route.ts` | Custom Login API mit Audit |
|
||||||
| `src/app/(payload)/admin/login/page.tsx` | Custom Login Page |
|
|
||||||
| `scripts/detect-secrets.sh` | Pre-Commit Secret Detection |
|
| `scripts/detect-secrets.sh` | Pre-Commit Secret Detection |
|
||||||
| `.github/workflows/security.yml` | CI Security Scanning |
|
| `.github/workflows/security.yml` | CI Security Scanning |
|
||||||
| `tests/unit/security/` | Security Unit Tests |
|
| `tests/unit/security/` | Security Unit Tests |
|
||||||
|
|
|
||||||
|
|
@ -222,13 +222,31 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Letzte Aktualisierung: 27.12.2025*
|
*Letzte Aktualisierung: 29.12.2025*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Changelog
|
## 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
|
### 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:**
|
- **Payload CMS Update 3.68.4 → 3.69.0:**
|
||||||
- Login Redirect Loop behoben (formatAdminURL generiert keine absoluten URLs mehr)
|
- Login Redirect Loop behoben (formatAdminURL generiert keine absoluten URLs mehr)
|
||||||
- Update aller @payloadcms/* Pakete
|
- 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
|
// Tenant-specific Blocks
|
||||||
export { BeforeAfterBlock } from './BeforeAfterBlock'
|
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,
|
ComparisonBlock,
|
||||||
// Tenant-specific Blocks
|
// Tenant-specific Blocks
|
||||||
BeforeAfterBlock,
|
BeforeAfterBlock,
|
||||||
|
// BlogWoman Blocks
|
||||||
|
FavoritesBlock,
|
||||||
|
SeriesBlock,
|
||||||
|
SeriesDetailBlock,
|
||||||
|
VideoEmbedBlock,
|
||||||
|
FeaturedContentBlock,
|
||||||
} from '../blocks'
|
} from '../blocks'
|
||||||
import { pagesAccess } from '../lib/access'
|
import { pagesAccess } from '../lib/access'
|
||||||
|
|
||||||
|
|
@ -140,6 +146,12 @@ export const Pages: CollectionConfig = {
|
||||||
ComparisonBlock,
|
ComparisonBlock,
|
||||||
// Tenant-specific Blocks
|
// Tenant-specific Blocks
|
||||||
BeforeAfterBlock,
|
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 { Certifications } from './collections/Certifications'
|
||||||
import { Projects } from './collections/Projects'
|
import { Projects } from './collections/Projects'
|
||||||
|
|
||||||
|
// BlogWoman Collections
|
||||||
|
import { Favorites } from './collections/Favorites'
|
||||||
|
import { Series } from './collections/Series'
|
||||||
|
|
||||||
// Consent Management Collections
|
// Consent Management Collections
|
||||||
import { CookieConfigurations } from './collections/CookieConfigurations'
|
import { CookieConfigurations } from './collections/CookieConfigurations'
|
||||||
import { CookieInventory } from './collections/CookieInventory'
|
import { CookieInventory } from './collections/CookieInventory'
|
||||||
|
|
@ -197,6 +201,9 @@ export default buildConfig({
|
||||||
Bookings,
|
Bookings,
|
||||||
Certifications,
|
Certifications,
|
||||||
Projects,
|
Projects,
|
||||||
|
// BlogWoman Collections
|
||||||
|
Favorites,
|
||||||
|
Series,
|
||||||
// Consent Management
|
// Consent Management
|
||||||
CookieConfigurations,
|
CookieConfigurations,
|
||||||
CookieInventory,
|
CookieInventory,
|
||||||
|
|
@ -266,6 +273,9 @@ export default buildConfig({
|
||||||
bookings: {},
|
bookings: {},
|
||||||
certifications: {},
|
certifications: {},
|
||||||
projects: {},
|
projects: {},
|
||||||
|
// BlogWoman Collections
|
||||||
|
favorites: {},
|
||||||
|
series: {},
|
||||||
// Consent Management Collections - customTenantField: true weil sie bereits ein tenant-Feld haben
|
// Consent Management Collections - customTenantField: true weil sie bereits ein tenant-Feld haben
|
||||||
'cookie-configurations': { customTenantField: true },
|
'cookie-configurations': { customTenantField: true },
|
||||||
'cookie-inventory': { customTenantField: true },
|
'cookie-inventory': { customTenantField: true },
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue