MBO-Tech-IT-Webseite/modules/06-website-cms/TEMPLATE.md

300 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Modul: Website-CMS (Hero · Über uns · Galerie · Kontakt · Passwort)
> Vollständiges CMS für eine öffentliche Website: Hero-Sektion (Texte, Badges, Hintergrundbild, Logo, Favicon), Über-uns-Sektion (Texte, Statistiken, Sektionsbild), Bildergalerie (Upload, Sortierung, animiertes Grid), Kontakt-Daten (Info, Öffnungszeiten, Social Media) und Admin-Passwort-Änderung. Alle Sektionen lesen direkt aus Supabase; bei DB-Fehler greift ein statischer Fallback.
> **Anfrageformular** (Kontaktformular mit E-Mail-Versand) → Modul 07
---
## Enthaltene Dateien
| Ziel im neuen Projekt | Inhalt |
|---|---|
| `app/api/admin/hero/route.ts` | GET + PATCH: hero_content (UPSERT) + hero_badges (lesen) |
| `app/api/admin/hero/bild/route.ts` | POST/DELETE: Hintergrundbild (`hero-bilder` Bucket) |
| `app/api/admin/hero/logo/route.ts` | POST/DELETE: Logo (`site-assets` Bucket) |
| `app/api/admin/hero/favicon/route.ts` | POST/DELETE: Favicon (`site-assets` Bucket) |
| `app/api/admin/hero/badges/route.ts` | POST: Badge hinzufügen |
| `app/api/admin/hero/badges/id/route.ts` | PATCH/DELETE: Badge bearbeiten/löschen |
| `app/api/admin/galerie/route.ts` | GET/POST/PATCH/DELETE: Galerie-Bilder + Storage |
| `app/api/admin/galerie/sort/route.ts` | POST: Reihenfolge speichern |
| `app/api/admin/ueber-uns/route.ts` | GET + PATCH: ueber_uns_content (UPSERT) + Stats (lesen) |
| `app/api/admin/ueber-uns/upload/route.ts` | POST: Sektionsbild hochladen (`ueber-uns-bilder` Bucket) |
| `app/api/admin/ueber-uns/stats/route.ts` | POST: Statistik hinzufügen |
| `app/api/admin/ueber-uns/stats/id/route.ts` | PATCH/DELETE: Statistik bearbeiten/löschen |
| `app/api/admin/kontakt/route.ts` | GET + PATCH: kontakt_info (UPSERT) + Listen lesen |
| `app/api/admin/kontakt/oeffnungszeiten/route.ts` | POST: Öffnungszeit hinzufügen |
| `app/api/admin/kontakt/oeffnungszeiten/id/route.ts` | PATCH/DELETE |
| `app/api/admin/kontakt/social/route.ts` | POST: Social-Link hinzufügen |
| `app/api/admin/kontakt/social/id/route.ts` | PATCH/DELETE |
| `app/api/admin/passwort/route.ts` | POST: Admin-Passwort ändern (bcrypt) |
| `app/admin/hero/page.tsx` | Admin-Page Hero |
| `app/admin/galerie/page.tsx` | Admin-Page Galerie |
| `app/admin/ueber-uns/page.tsx` | Admin-Page Über uns |
| `app/admin/kontakt/page.tsx` | Admin-Page Kontakt |
| `app/admin/passwort/page.tsx` | Admin-Page Passwort |
| `components/GalerieAnzeige.tsx` | Öffentliche Galerie mit 3D-Flip-Animation |
| `components/admin/HeroVerwaltung.tsx` | Admin-UI: Hero |
| `components/admin/GalerieVerwaltung.tsx` | Admin-UI: Galerie |
| `components/admin/UeberUnsVerwaltung.tsx` | Admin-UI: Über uns |
| `components/admin/KontaktVerwaltung.tsx` | Admin-UI: Kontakt |
| `components/admin/PasswortAendern.tsx` | Admin-UI: Passwort ändern |
**Hinweis:** Ordner `id/` in `files/` entsprechen Next.js Dynamic-Route-Ordnern `[id]`.
---
## Voraussetzungen
Benötigt:
- `lib/supabase.ts` mit `createPublicClient()` und `createServiceClient()`
- `lib/admin-auth.ts` (Modul 02): `requireAdmin()` und `getAdminSession()`
- Tabelle `admins` mit Spalten `id`, `email`, `password_hash` (Modul 02)
```bash
npm install bcryptjs
npm install -D @types/bcryptjs
```
---
## Umgebungsvariablen
```env
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co # Für Storage-URL-Konstruktion im Client
SUPABASE_INTERNAL_URL=http://supabase-kong:8000 # Nur Docker-intern; API-Routen verwenden diese
```
---
## Supabase Storage Buckets anlegen
Im Supabase Dashboard → Storage → New Bucket für jeden Bucket:
| Bucket-Name | Public | Verwendung |
|---|---|---|
| `hero-bilder` | ✓ | Hero-Hintergrundbild |
| `site-assets` | ✓ | Logo + Favicon |
| `ueber-uns-bilder` | ✓ | Über-uns-Sektionsbild |
| `galerie-bilder` | ✓ | Galerie-Fotos |
---
## Datenbank-Migrationen (Supabase)
Die vollständige SQL-Datei liegt unter `migrations/MIGRATIONS_WEBSITE_CMS.sql`.
Im Supabase SQL-Editor einmalig ausführen. Sie enthält alle Tabellen, Spalten und RLS-Policies.
Erstellt werden:
- `hero_content` — Singleton (Seitenname, Tagline, Überschriften, Buttons, Bildpfade)
- `hero_badges` — Liste der Trust-Badges
- `ueber_uns_content` — Singleton (Eyebrow, Absätze, Bildpfad)
- `ueber_uns_stats` — Liste der Kennzahlen/Statistiken
- `galerie_bilder` — Liste der Galeriefotos (storage_path, alt_text, reihenfolge)
- `kontakt_info` — Singleton (Telefon, E-Mail, Adresse, Formular-Empfänger)
- `kontakt_oeffnungszeiten` — Liste der Öffnungszeiten
- `kontakt_social` — Liste der Social-Media-Links
Alle Tabellen erhalten `public read` RLS-Policy (anonymer Lesezugriff).
### Supabase-Typen (`lib/supabase.ts`) ergänzen
In `Database.public.Tables` hinzufügen:
```ts
hero_content: { Row: {
id: string; site_name: string; site_tagline: string;
eyebrow_text: string; headline1: string; headline2: string;
subtext1: string; subtext2: string;
cta1_text: string; cta1_href: string; cta2_text: string; cta2_href: string;
bg_image_path: string | null; logo_path: string | null; favicon_path: string | null;
updated_at: string;
} }
hero_badges: { Row: { id: string; text: string; reihenfolge: number } }
ueber_uns_content: { Row: {
id: string; eyebrow_text: string; absatz1: string; absatz2: string;
bild_url: string | null; updated_at: string;
} }
ueber_uns_stats: { Row: { id: string; wert: string; label: string; reihenfolge: number } }
galerie_bilder: { Row: {
id: string; storage_path: string; alt_text: string; reihenfolge: number;
} }
kontakt_info: { Row: {
id: string; telefon: string; email: string;
adresse_zeile1: string; adresse_zeile2: string;
formular_empfaenger: string; updated_at: string;
} }
kontakt_oeffnungszeiten: { Row: { id: string; tag: string; von: string; bis: string; reihenfolge: number } }
kontakt_social: { Row: { id: string; platform: string; url: string; reihenfolge: number } }
```
---
## Architektur-Muster
### UPSERT-Singleton (Einzel-Zeilen-Tabellen)
`hero_content`, `ueber_uns_content`, `kontakt_info` haben immer genau eine Zeile.
API-Route: erst `select('id').limit(1).single()` → bei Treffer `update`, sonst `insert`.
### CRUD-Liste (Tabellen mit `reihenfolge`)
`hero_badges`, `ueber_uns_stats`, `galerie_bilder`, `kontakt_oeffnungszeiten`, `kontakt_social` — klassisches INSERT/PATCH/DELETE. Reihenfolge wird als Integer gespeichert.
### Server Component + Fallback
Öffentliche Website-Sektionen sind async Server Components. Alle Supabase-Aufrufe in `try/catch`; bei Fehler werden hartcodierte Fallback-Daten gerendert.
### force-dynamic
Alle Admin-Pages müssen `export const dynamic = 'force-dynamic'` haben (verhindert statisches Pre-Rendering beim Docker-Build ohne Env-Variablen).
### Galerie: Hydration-Mismatch vermeiden
`GalerieAnzeige` initialisiert `slots` ohne Shuffle im `useState()`-Initializer (Server und Client rendern identisch). Der Shuffle + Intervall-Start läuft nur im `useEffect([])` nach dem Mount.
---
## Einbindung Schritt für Schritt
### 1. Dateien kopieren (Dynamic-Route-Ordner umbenennen)
```
files/app/api/admin/hero/badges/id/ → app/api/admin/hero/badges/[id]/
files/app/api/admin/ueber-uns/stats/id/ → app/api/admin/ueber-uns/stats/[id]/
files/app/api/admin/kontakt/oeffnungszeiten/id/ → app/api/admin/kontakt/oeffnungszeiten/[id]/
files/app/api/admin/kontakt/social/id/ → app/api/admin/kontakt/social/[id]/
```
### 2. Standardwerte anpassen
In `components/admin/HeroVerwaltung.tsx`:
```tsx
const DEFAULTS: HeroContent = {
site_name: 'Musterfirma', // ← eigenen Firmennamen setzen
site_tagline: '',
eyebrow_text: 'Ihr Slogan hier',
headline1: 'Wir lieben Qualität.',
headline2: 'Ihre Kunden auch.',
// ...
}
```
In `app/api/admin/hero/route.ts` PATCH-Handler dieselben Defaults setzen.
### 3. Fallback-Bild konfigurieren (`components/admin/HeroVerwaltung.tsx`)
```tsx
src={bgPreview ?? bgUrl ?? '/hero.jpg'}
```
Das Fallback-Bild `/hero.jpg` muss im `public/`-Ordner liegen (oder durch eigenen Pfad ersetzen).
### 4. Öffentliche Website-Sektionen einbinden
Die Galerie-Anzeige-Komponente auf der Hauptseite:
```tsx
// In app/page.tsx (async Server Component):
import { GalerieAnzeige } from '@/components/GalerieAnzeige'
const { data: galerie } = await supabase.from('galerie_bilder').select('*').order('reihenfolge')
const bilder = (galerie ?? []).map(b => ({
id: b.id,
src: `${supabaseUrl}/storage/v1/object/public/galerie-bilder/${b.storage_path}`,
alt: b.alt_text,
}))
<GalerieAnzeige bilder={bilder} />
```
### 5. Admin-Navigation ergänzen
```tsx
<a href="/admin/hero">Hero</a>
<a href="/admin/ueber-uns">Über uns</a>
<a href="/admin/galerie">Galerie</a>
<a href="/admin/kontakt">Kontakt</a>
<a href="/admin/passwort">Passwort</a>
```
---
## Anpassungspunkte
| Was | Wo |
|---|---|
| Firmenname / Standardtexte | `components/admin/HeroVerwaltung.tsx``DEFAULTS` + `api/admin/hero/route.ts` |
| Fallback-Hero-Bild | `components/admin/HeroVerwaltung.tsx``bgPreview ?? bgUrl ?? '/hero.jpg'` |
| Galerie-Flip-Geschwindigkeit | `components/GalerieAnzeige.tsx``INTERVAL_MS` (Standard: 1500 ms) |
| Galerie-Grid-Layout | `components/GalerieAnzeige.tsx``VISIBLE`, `ROW_H`, `numGroups`-Berechnung |
| Bucket-Namen | Alle `app/api/admin/*/route.ts``const BUCKET = '...'` |
| Max. Upload-Größe | `app/api/admin/galerie/route.ts``MAX_SIZE` |
---
## Integrations-Prompt
Kopiere diesen Prompt in eine neue KI-Konversation, nachdem du die `files/` in dein Projekt kopiert und die SQL-Migration ausgeführt hast. Ersetze alle `[PLATZHALTER]`.
```
Ich integriere das Website-CMS-Modul (Hero, Über uns, Galerie, Kontakt, Passwort) in mein Next.js/Supabase-Projekt.
PROJEKT-KONTEXT:
- Firmenname: [FIRMENNAME]
- Tagline (optional): [TAGLINE oder leer lassen]
- Hero-Eyebrow-Text: [z. B. "Ihr Ansprechpartner seit 2010"]
- Hero-Überschrift 1 (weiß): [z. B. "Wir lieben Qualität."]
- Hero-Überschrift 2 (akzentfarbe): [z. B. "Ihre Kunden auch."]
- Modul 02 (Admin-Auth) ist bereits integriert (requireAdmin + getAdminSession verfügbar)
- Tabelle `admins` mit password_hash-Spalte existiert bereits
BEREITS KOPIERTE DATEIEN (aus modules/06-website-cms/files/):
Wichtig: Ordner "id/" umbenennen zu "[id]" — betrifft:
- app/api/admin/hero/badges/id/ → [id]/
- app/api/admin/ueber-uns/stats/id/ → [id]/
- app/api/admin/kontakt/oeffnungszeiten/id/ → [id]/
- app/api/admin/kontakt/social/id/ → [id]/
AUFGABEN führe sie der Reihe nach aus:
1. SQL-MIGRATION: Führe modules/06-website-cms/migrations/MIGRATIONS_WEBSITE_CMS.sql
im Supabase SQL-Editor aus (enthält alle Tabellen + RLS-Policies).
2. SUPABASE STORAGE BUCKETS anlegen (alle Public):
- hero-bilder
- site-assets
- ueber-uns-bilder
- galerie-bilder
3. BCRYPTJS installieren (für Passwort-Änderung):
npm install bcryptjs && npm install -D @types/bcryptjs
4. SUPABASE-TYPEN: Lies lib/supabase.ts.
Ergänze in Database.public.Tables die Typen für:
hero_content, hero_badges, ueber_uns_content, ueber_uns_stats,
galerie_bilder, kontakt_info, kontakt_oeffnungszeiten, kontakt_social
(Typen aus modules/06-website-cms/TEMPLATE.md kopieren)
5. STANDARDWERTE anpassen:
a) Lies components/admin/HeroVerwaltung.tsx.
Setze in DEFAULTS: site_name: '[FIRMENNAME]', site_tagline: '[TAGLINE]',
eyebrow_text: '[EYEBROW]', headline1: '[H1]', headline2: '[H2]'
b) Lies app/api/admin/hero/route.ts.
Setze dieselben Werte als Fallback in den PATCH-Handler-fields.
6. FALLBACK-BILD: Lege eine Datei hero.jpg (oder eigenen Namen) in public/ ab.
Lies components/admin/HeroVerwaltung.tsx und ersetze '/hero.jpg' durch deinen Pfad.
7. ÖFFENTLICHE SEITE einbinden:
a) Lies app/page.tsx (oder deine Hauptseite).
Importiere und verwende die Sektionskomponenten (Hero, UeberUns, GalerieAnzeige).
Für GalerieAnzeige: Daten aus galerie_bilder laden, Storage-URLs konstruieren,
als `bilder: GalerieBild[]`-Array übergeben.
b) Kontaktsektion mit Formular → Modul 07 einbinden.
8. ADMIN-NAVIGATION ergänzen:
Lies app/admin/AdminNav.tsx (oder deine Admin-Nav).
Füge Links zu /admin/hero, /admin/ueber-uns, /admin/galerie, /admin/kontakt, /admin/passwort hinzu.
Import-Icons: Home, Info, Grid2x2, Phone, KeyRound (aus lucide-react).
9. TEST:
a) /admin/hero → Seitenname + Texte + Hintergrundbild + Logo + Favicon speichern
b) /admin/ueber-uns → Texte + Statistik hinzufügen
c) /admin/galerie → Bild hochladen, Reihenfolge ändern
d) /admin/kontakt → Öffnungszeiten + Social-Links anlegen, Formular-Empfänger eintragen
e) /admin/passwort → Passwort ändern (aktuelles Passwort + neues Passwort)
f) Öffentliche Hauptseite → alle Sektionen zeigen Daten aus der DB
Lies jede Datei vor dem Bearbeiten. Melde wenn alle Schritte abgeschlossen sind.
```