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

13 KiB
Raw Blame History

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)
npm install bcryptjs
npm install -D @types/bcryptjs

Umgebungsvariablen

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:

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:

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)

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:

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

<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.tsxDEFAULTS + api/admin/hero/route.ts
Fallback-Hero-Bild components/admin/HeroVerwaltung.tsxbgPreview ?? bgUrl ?? '/hero.jpg'
Galerie-Flip-Geschwindigkeit components/GalerieAnzeige.tsxINTERVAL_MS (Standard: 1500 ms)
Galerie-Grid-Layout components/GalerieAnzeige.tsxVISIBLE, ROW_H, numGroups-Berechnung
Bucket-Namen Alle app/api/admin/*/route.tsconst BUCKET = '...'
Max. Upload-Größe app/api/admin/galerie/route.tsMAX_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.