# Modul-Integration & Supabase-Anbindung — Implementierungsplan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Alle 6 Module vollständig einbinden — DB-Tabellen anlegen, CMS-Code integrieren, öffentliche Sektionen aus Supabase laden. **Architecture:** Next.js 15 App Router + selbst-gehostetes Supabase (Docker VM). Öffentliche Seiten sind async Server Components; Client-Komponenten (Hero, Contact) nehmen Props entgegen. Alle Supabase-Aufrufe in `try/catch` mit hartkodiertem Fallback. **Tech Stack:** Next.js 15, Supabase (PostgreSQL + Storage + Auth), Nodemailer, bcryptjs, `@supabase/ssr` --- ## Dateiübersicht **Neue Dateien (Modul 06-CMS kopieren):** ``` app/api/admin/hero/route.ts app/api/admin/hero/bild/route.ts app/api/admin/hero/logo/route.ts app/api/admin/hero/favicon/route.ts app/api/admin/hero/badges/route.ts app/api/admin/hero/badges/[id]/route.ts app/api/admin/galerie/route.ts app/api/admin/galerie/sort/route.ts app/api/admin/ueber-uns/route.ts app/api/admin/ueber-uns/upload/route.ts app/api/admin/ueber-uns/stats/route.ts app/api/admin/ueber-uns/stats/[id]/route.ts app/api/admin/kontakt/route.ts app/api/admin/kontakt/oeffnungszeiten/route.ts app/api/admin/kontakt/oeffnungszeiten/[id]/route.ts app/api/admin/kontakt/social/route.ts app/api/admin/kontakt/social/[id]/route.ts app/api/admin/passwort/route.ts app/admin/hero/page.tsx app/admin/galerie/page.tsx app/admin/ueber-uns/page.tsx app/admin/kontakt/page.tsx app/admin/passwort/page.tsx components/GalerieAnzeige.tsx components/admin/HeroVerwaltung.tsx components/admin/GalerieVerwaltung.tsx components/admin/UeberUnsVerwaltung.tsx components/admin/KontaktVerwaltung.tsx components/admin/PasswortAendern.tsx ``` **Geänderte Dateien:** ``` lib/supabase.ts — CMS-Tabellentypen ergänzen app/page.tsx — async, lädt aus Supabase components/Hero.tsx — Props entgegennehmen components/About.tsx — Props entgegennehmen components/Contact.tsx — Props entgegennehmen components/admin/AdminNav.tsx — CMS-Links ergänzen app/api/admin/passwort/route.ts — 'admins' → 'admin_users' ``` **Bereits vorhanden (kein Handlungsbedarf):** ``` instrumentation.ts ✅ Email-Queue-Worker bereits konfiguriert app/layout.tsx ✅ PageTracker bereits eingebunden lib/mailer.ts, lib/email-queue.ts, lib/admin-auth.ts ✅ ``` --- ## Task 1: DB-Migrationen — Basis-Tabellen **Auszuführen im Supabase SQL-Editor** (`https://supabase.mbo-tech-it.de`) - [ ] **Schritt 1.1: email_queue anlegen** ```sql CREATE TABLE IF NOT EXISTS email_queue ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), mail_from text NOT NULL, mail_to text NOT NULL, reply_to text, subject text NOT NULL, html text NOT NULL, body_text text NOT NULL, status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'sent', 'failed')), retry_count int NOT NULL DEFAULT 0, next_retry_at timestamptz NOT NULL DEFAULT now(), error_last text, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_email_queue_pending ON email_queue(status, next_retry_at) WHERE status = 'pending'; ALTER TABLE email_queue ENABLE ROW LEVEL SECURITY; CREATE POLICY "service_role_email_queue" ON email_queue USING (true) WITH CHECK (true); ``` - [ ] **Schritt 1.2: admin_users anlegen** ```sql CREATE TABLE IF NOT EXISTS admin_users ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), email text UNIQUE NOT NULL, name text, password_hash text NOT NULL, aktiv boolean NOT NULL DEFAULT true, created_at timestamptz NOT NULL DEFAULT now() ); ALTER TABLE admin_users ENABLE ROW LEVEL SECURITY; CREATE POLICY "service_role_admin_users" ON admin_users USING (true) WITH CHECK (true); ``` - [ ] **Schritt 1.3: anfragen anlegen** ```sql CREATE TABLE IF NOT EXISTS anfragen ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name text NOT NULL, email text NOT NULL, betreff text NOT NULL, nachricht text, status text NOT NULL DEFAULT 'offen', admin_notizen text, kunde_id uuid, created_at timestamptz NOT NULL DEFAULT now() ); ALTER TABLE anfragen ENABLE ROW LEVEL SECURITY; CREATE POLICY "service_role_anfragen" ON anfragen USING (true) WITH CHECK (true); CREATE POLICY "kunden_eigene_anfragen" ON anfragen FOR SELECT USING (auth.uid() = kunde_id); ``` - [ ] **Schritt 1.4: Commit** ```bash git commit --allow-empty -m "chore: DB-Migrationen 1-3 im Supabase SQL-Editor ausgeführt" ``` --- ## Task 2: DB-Migrationen — Module 02 & 03 **Auszuführen im Supabase SQL-Editor** - [ ] **Schritt 2.1: Modul 02 — Audit-Logs** Inhalt von `modules/02-admin-auth/migrations/MIGRATIONS_AUDIT_LOGS.sql` ausführen (Tabelle `admin_audit_logs`). - [ ] **Schritt 2.2: Modul 02 — Token-Blacklist** Inhalt von `modules/02-admin-auth/migrations/MIGRATIONS_TOKEN_BLACKLIST.sql` ausführen (Tabellen `admin_session_blacklist`, `action_token_blacklist`). - [ ] **Schritt 2.3: Modul 03 — Page Views** Inhalt von `modules/03-analytics/migrations/MIGRATIONS_PAGE_VIEWS.sql` ausführen (Tabelle `page_views`). - [ ] **Schritt 2.4: Modul 03 — Phone Clicks** Inhalt von `modules/03-analytics/migrations/MIGRATIONS_PHONE_CLICKS.sql` ausführen (Tabelle `phone_clicks`). - [ ] **Schritt 2.5: Commit** ```bash git commit --allow-empty -m "chore: DB-Migrationen Modul 02+03 ausgeführt" ``` --- ## Task 3: DB-Migration — Modul 06-CMS **Auszuführen im Supabase SQL-Editor** - [ ] **Schritt 3.1: CMS-Migration ausführen** Vollständigen Inhalt von `modules/06-website-cms/migrations/MIGRATIONS_WEBSITE_CMS.sql` im SQL-Editor ausführen. Erstellt diese Tabellen: - `hero_content`, `hero_badges` - `ueber_uns_content`, `ueber_uns_stats` - `galerie_bilder` - `kontakt_info`, `kontakt_oeffnungszeiten`, `kontakt_social` - [ ] **Schritt 3.2: Ersten Admin-User anlegen** > **Hinweis:** Dieser Schritt erfordert `bcryptjs` im node_modules. Task 4 (npm install) zuerst ausführen, dann hierher zurückkehren. > Alternativ: Online-Bcrypt-Generator (z.B. https://bcrypt.online) mit Cost Factor 12 nutzen. Bcrypt-Hash erzeugen (nach Task 4): ```bash node -e "const b = require('bcryptjs'); b.hash('DEIN_PASSWORT', 12).then(h => console.log(h))" ``` Dann im SQL-Editor: ```sql INSERT INTO admin_users (email, name, password_hash) VALUES ('jonny@mbo-tech-it.de', 'Admin', ''); ``` - [ ] **Schritt 3.3: Commit** ```bash git commit --allow-empty -m "chore: DB-Migration CMS + Admin-User angelegt" ``` --- ## Task 4: npm-Pakete installieren - [ ] **Schritt 4.1: Pakete installieren** ```bash npm install nodemailer bcryptjs npm install -D @types/nodemailer @types/bcryptjs ``` - [ ] **Schritt 4.2: Überprüfen** ```bash npx tsc --noEmit ``` Erwartet: Keine neuen Fehler (nur ggf. bestehende, die mit fehlenden DB-Typen zusammenhängen). - [ ] **Schritt 4.3: Commit** ```bash git add package.json package-lock.json git commit -m "chore: add nodemailer and bcryptjs dependencies" ``` --- ## Task 5: `lib/supabase.ts` — CMS-Tabellentypen ergänzen **Datei:** `lib/supabase.ts` - [ ] **Schritt 5.1: CMS-Typen in `Database.public.Tables` einfügen** Den bestehenden Abschluss-Block `};` des `Tables`-Objekts vor dem Schließen um folgende Typen erweitern: ```typescript hero_content: { Row: { id: string; site_name: string; site_tagline: string; logo_path: string | null; favicon_path: string | null; 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; updated_at: string; }; Insert: { site_name?: string; site_tagline?: string; logo_path?: string | null; favicon_path?: string | null; 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; updated_at?: string; }; Update: { site_name?: string; site_tagline?: string; logo_path?: string | null; favicon_path?: string | null; 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; updated_at?: string; }; Relationships: []; }; hero_badges: { Row: { id: string; text: string; reihenfolge: number }; Insert: { text: string; reihenfolge?: number }; Update: { text?: string; reihenfolge?: number }; Relationships: []; }; ueber_uns_content: { Row: { id: string; eyebrow_text: string; absatz1: string; absatz2: string; bild_url: string | null; updated_at: string; }; Insert: { eyebrow_text?: string; absatz1?: string; absatz2?: string; bild_url?: string | null; updated_at?: string; }; Update: { eyebrow_text?: string; absatz1?: string; absatz2?: string; bild_url?: string | null; updated_at?: string; }; Relationships: []; }; ueber_uns_stats: { Row: { id: string; wert: string; label: string; reihenfolge: number }; Insert: { wert: string; label: string; reihenfolge?: number }; Update: { wert?: string; label?: string; reihenfolge?: number }; Relationships: []; }; galerie_bilder: { Row: { id: string; storage_path: string; alt_text: string; reihenfolge: number; }; Insert: { storage_path: string; alt_text?: string; reihenfolge?: number }; Update: { storage_path?: string; alt_text?: string; reihenfolge?: number }; Relationships: []; }; kontakt_info: { Row: { id: string; telefon: string; email: string; adresse_zeile1: string; adresse_zeile2: string; formular_empfaenger: string; updated_at: string; }; Insert: { telefon?: string; email?: string; adresse_zeile1?: string; adresse_zeile2?: string; formular_empfaenger?: string; updated_at?: string; }; Update: { telefon?: string; email?: string; adresse_zeile1?: string; adresse_zeile2?: string; formular_empfaenger?: string; updated_at?: string; }; Relationships: []; }; kontakt_oeffnungszeiten: { Row: { id: string; tag: string; von: string; bis: string; reihenfolge: number }; Insert: { tag: string; von: string; bis: string; reihenfolge?: number }; Update: { tag?: string; von?: string; bis?: string; reihenfolge?: number }; Relationships: []; }; kontakt_social: { Row: { id: string; platform: string; url: string; reihenfolge: number }; Insert: { platform: string; url: string; reihenfolge?: number }; Update: { platform?: string; url?: string; reihenfolge?: number }; Relationships: []; }; ``` - [ ] **Schritt 5.2: TypeScript-Check** ```bash npx tsc --noEmit ``` Erwartet: Keine Fehler in `lib/supabase.ts`. - [ ] **Schritt 5.3: Commit** ```bash git add lib/supabase.ts git commit -m "feat: add CMS table types to supabase.ts" ``` --- ## Task 6: Modul 06-CMS Dateien kopieren - [ ] **Schritt 6.1: Reguläre Dateien kopieren (PowerShell)** ```powershell $src = "modules\06-website-cms\files" $dst = "." # API-Routen (ohne id/-Ordner) Copy-Item "$src\app\api\admin\hero\route.ts" -Destination "app\api\admin\hero\" -Force Copy-Item "$src\app\api\admin\hero\bild\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\hero\bild").FullName Copy-Item "$src\app\api\admin\hero\logo\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\hero\logo").FullName Copy-Item "$src\app\api\admin\hero\favicon\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\hero\favicon").FullName Copy-Item "$src\app\api\admin\hero\badges\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\hero\badges").FullName Copy-Item "$src\app\api\admin\galerie\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\galerie").FullName Copy-Item "$src\app\api\admin\galerie\sort\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\galerie\sort").FullName Copy-Item "$src\app\api\admin\ueber-uns\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\ueber-uns").FullName Copy-Item "$src\app\api\admin\ueber-uns\upload\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\ueber-uns\upload").FullName Copy-Item "$src\app\api\admin\ueber-uns\stats\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\ueber-uns\stats").FullName Copy-Item "$src\app\api\admin\kontakt\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\kontakt").FullName Copy-Item "$src\app\api\admin\kontakt\oeffnungszeiten\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\kontakt\oeffnungszeiten").FullName Copy-Item "$src\app\api\admin\kontakt\social\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\kontakt\social").FullName Copy-Item "$src\app\api\admin\passwort\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\passwort").FullName # Dynamic-Route-Ordner [id] (Ordner heißt im Modul "id", muss "[id]" heißen) Copy-Item "$src\app\api\admin\hero\badges\id\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\hero\badges\[id]").FullName Copy-Item "$src\app\api\admin\ueber-uns\stats\id\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\ueber-uns\stats\[id]").FullName Copy-Item "$src\app\api\admin\kontakt\oeffnungszeiten\id\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\kontakt\oeffnungszeiten\[id]").FullName Copy-Item "$src\app\api\admin\kontakt\social\id\route.ts" -Destination (New-Item -ItemType Directory -Force "app\api\admin\kontakt\social\[id]").FullName # Admin-Seiten Copy-Item "$src\app\admin\hero\page.tsx" -Destination (New-Item -ItemType Directory -Force "app\admin\hero").FullName Copy-Item "$src\app\admin\galerie\page.tsx" -Destination (New-Item -ItemType Directory -Force "app\admin\galerie").FullName Copy-Item "$src\app\admin\ueber-uns\page.tsx" -Destination (New-Item -ItemType Directory -Force "app\admin\ueber-uns").FullName Copy-Item "$src\app\admin\kontakt\page.tsx" -Destination (New-Item -ItemType Directory -Force "app\admin\kontakt").FullName Copy-Item "$src\app\admin\passwort\page.tsx" -Destination (New-Item -ItemType Directory -Force "app\admin\passwort").FullName # Komponenten Copy-Item "$src\components\GalerieAnzeige.tsx" -Destination "components\" -Force Copy-Item "$src\components\admin\HeroVerwaltung.tsx" -Destination "components\admin\" -Force Copy-Item "$src\components\admin\GalerieVerwaltung.tsx" -Destination "components\admin\" -Force Copy-Item "$src\components\admin\UeberUnsVerwaltung.tsx" -Destination "components\admin\" -Force Copy-Item "$src\components\admin\KontaktVerwaltung.tsx" -Destination "components\admin\" -Force Copy-Item "$src\components\admin\PasswortAendern.tsx" -Destination "components\admin\" -Force ``` - [ ] **Schritt 6.2: Verify** ```bash ls app/api/admin/hero/ ls app/api/admin/hero/badges/ ``` Erwartet: `route.ts`, `bild/`, `logo/`, `favicon/`, `badges/` (mit `route.ts` und `[id]/route.ts`) - [ ] **Schritt 6.3: Commit** ```bash git add app/api/admin/ app/admin/hero/ app/admin/galerie/ app/admin/ueber-uns/ app/admin/kontakt/ app/admin/passwort/ components/GalerieAnzeige.tsx components/admin/HeroVerwaltung.tsx components/admin/GalerieVerwaltung.tsx components/admin/UeberUnsVerwaltung.tsx components/admin/KontaktVerwaltung.tsx components/admin/PasswortAendern.tsx git commit -m "feat: add website-cms module files" ``` --- ## Task 7: `app/api/admin/passwort/route.ts` anpassen Die kopierte Datei referenziert `db.from('admins')` — muss `admin_users` sein. **Datei:** `app/api/admin/passwort/route.ts` - [ ] **Schritt 7.1: Tabellenname korrigieren** Beide Vorkommen von `'admins'` durch `'admin_users'` ersetzen: ```typescript // Zeile ~19: vorher const { data: admin } = await db.from('admins').select('password_hash').eq('id', session.id).single() // nachher const { data: admin } = await db.from('admin_users').select('password_hash').eq('id', session.id).single() // Zeile ~25: vorher const { error } = await db.from('admins').update({ password_hash: hash }).eq('id', session.id) // nachher const { error } = await db.from('admin_users').update({ password_hash: hash }).eq('id', session.id) ``` - [ ] **Schritt 7.2: TypeScript-Check** ```bash npx tsc --noEmit ``` - [ ] **Schritt 7.3: Commit** ```bash git add app/api/admin/passwort/route.ts git commit -m "fix: use admin_users table in passwort route" ``` --- ## Task 8: `components/admin/HeroVerwaltung.tsx` — Standardwerte anpassen - [ ] **Schritt 8.1: DEFAULTS im Code anpassen** In `components/admin/HeroVerwaltung.tsx` den `DEFAULTS`-Block suchen (ca. Zeile 33) und ersetzen: ```typescript const DEFAULTS: HeroContent = { site_name: 'MBO-Tech-IT', site_tagline: 'IT-Service Crailsheim', eyebrow_text: 'Digital Denken. Lokal Handeln.', headline1: 'Ihre IT-Infrastruktur.', headline2: 'Professionell. Zuverlässig.', subtext1: 'Ihr IT-Dienstleister in Crailsheim — egal ob zu Hause oder im Büro.', subtext2: '', cta1_text: 'Jetzt anfragen', cta1_href: '#contact', cta2_text: 'Unsere Leistungen', cta2_href: '#services', bg_image_path: null, } ``` - [ ] **Schritt 8.2: Gleiche Defaults in API-Route setzen** In `app/api/admin/hero/route.ts` den `fields`-Block im PATCH-Handler anpassen: ```typescript const fields = { site_name: body.site_name ?? 'MBO-Tech-IT', site_tagline: body.site_tagline ?? 'IT-Service Crailsheim', eyebrow_text: body.eyebrow_text ?? 'Digital Denken. Lokal Handeln.', headline1: body.headline1 ?? 'Ihre IT-Infrastruktur.', headline2: body.headline2 ?? 'Professionell. Zuverlässig.', subtext1: body.subtext1 ?? 'Ihr IT-Dienstleister in Crailsheim', subtext2: body.subtext2 ?? '', cta1_text: body.cta1_text ?? 'Jetzt anfragen', cta1_href: body.cta1_href ?? '#contact', cta2_text: body.cta2_text ?? 'Unsere Leistungen', cta2_href: body.cta2_href ?? '#services', updated_at: new Date().toISOString(), } ``` - [ ] **Schritt 8.3: Commit** ```bash git add components/admin/HeroVerwaltung.tsx app/api/admin/hero/route.ts git commit -m "feat: set MBO-Tech-IT defaults in hero verwaltung" ``` --- ## Task 9: `components/admin/AdminNav.tsx` — CMS-Links ergänzen **Datei:** `components/admin/AdminNav.tsx` - [ ] **Schritt 9.1: `navLinks`-Array erweitern** Den bestehenden `navLinks`-Array ersetzen: ```typescript const navLinks = [ { href: "/admin/hero", label: "Hero" }, { href: "/admin/ueber-uns", label: "Über uns" }, { href: "/admin/galerie", label: "Galerie" }, { href: "/admin/kontakt", label: "Kontakt" }, { href: "/admin/passwort", label: "Passwort" }, { href: "/admin/analytics", label: "Analytics" }, { href: "/admin/statistik", label: "Statistik" }, { href: "/admin/audit-logs", label: "Audit-Logs" }, ]; ``` - [ ] **Schritt 9.2: TypeScript-Check** ```bash npx tsc --noEmit ``` - [ ] **Schritt 9.3: Commit** ```bash git add components/admin/AdminNav.tsx git commit -m "feat: add CMS links to admin navigation" ``` --- ## Task 10: `components/Hero.tsx` — Props entgegennehmen Die Komponente bleibt `"use client"` (wegen `useTypewriter`), nimmt aber Inhalte als Props. **Datei:** `components/Hero.tsx` - [ ] **Schritt 10.1: Props-Interface + Defaults hinzufügen** Am Anfang der Datei nach dem Import-Block einfügen: ```typescript interface HeroProps { eyebrowText?: string; headline1?: string; subtext1?: string; subtext2?: string; cta1Text?: string; cta1Href?: string; cta2Text?: string; cta2Href?: string; bgImagePath?: string | null; badges?: string[]; } ``` - [ ] **Schritt 10.2: Funktionssignatur anpassen** ```typescript export default function Hero({ eyebrowText = "Digital Denken. Lokal Handeln.", headline1 = "Ihre IT-Infrastruktur.", subtext1 = "Ihr IT-Dienstleister in Crailsheim — egal ob zu Hause oder im Büro. Von Hard- & Software über Netzwerk & WLAN bis hin zu Webseiten & Webanwendungen: Wir lösen Ihre IT-Probleme persönlich und zum fairen Preis.", subtext2 = "", cta1Text = "Jetzt anfragen", cta1Href = "#contact", cta2Text = "Unsere Leistungen", cta2Href = "#services", bgImagePath = null, badges = ["Docker", "Kubernetes", "Proxmox", "Hetzner Cloud", "Linux"], }: HeroProps = {}) { ``` - [ ] **Schritt 10.3: JSX anpassen — Badge, Headline, Subtext, Buttons** Im JSX-Block die hardkodierten Werte durch Props ersetzen: ```tsx {/* Tagline badge */}
{eyebrowText}
{/* Headline */}

{headline1}{" "} {typed} |

{/* Subheadline */} {subtext1 && (

{subtext1}

)} {subtext2 && (

{subtext2}

)} {/* Tech pills (aus hero_badges) */}
{badges.map((badge) => ( {badge} ))}
{/* CTAs */}
{cta1Text} {cta2Text}
``` - [ ] **Schritt 10.4: Hintergrundbild optional** Das Background-`
` mit bedingtem Bild ergänzen (falls `bgImagePath` gesetzt): ```tsx {bgImagePath && (
)} ``` - [ ] **Schritt 10.5: TypeScript-Check** ```bash npx tsc --noEmit ``` - [ ] **Schritt 10.6: Commit** ```bash git add components/Hero.tsx git commit -m "feat: hero component accepts cms props" ``` --- ## Task 11: `components/About.tsx` — Props entgegennehmen **Datei:** `components/About.tsx` - [ ] **Schritt 11.1: Props-Interface hinzufügen** Am Dateianfang (vor dem `highlights`-Array): ```typescript interface AboutProps { eyebrowText?: string; absatz1?: string; absatz2?: string; stats?: { wert: string; label: string }[]; } ``` - [ ] **Schritt 11.2: Funktionssignatur anpassen** ```typescript export default function About({ eyebrowText = "Über uns", absatz1 = "MBO-Tech-IT steht für über 30 Jahre IT-Erfahrung — angefangen bei der einfachen Hardware-Wartung, über den Aufbau großer Client-Server-Netzwerke, bis hin zu mehr als 20 Jahren Spezialisierung in der IT-Security.", absatz2 = "Heute liegt der Fokus auf modernen Container-Technologien, Cloud-nativer Infrastruktur und Virtualisierung. Dieses breite Fundament ermöglicht es, Lösungen zu entwickeln, die nicht nur funktionieren — sondern auch sicher und langfristig tragfähig sind.", stats, }: AboutProps = {}) { ``` - [ ] **Schritt 11.3: JSX anpassen** Die hartkodierten Texte im JSX durch Props ersetzen: ```tsx {eyebrowText}

{absatz1}

{absatz2}

``` Falls `stats` befüllt ist, die Statistiken rendern (als Ergänzung zu den bestehenden `highlights`): ```tsx {stats && stats.length > 0 && (
{stats.map((s) => (
{s.wert}
{s.label}
))}
)} ``` - [ ] **Schritt 11.4: Commit** ```bash git add components/About.tsx git commit -m "feat: about component accepts cms props" ``` --- ## Task 12: `components/Contact.tsx` — Props entgegennehmen **Datei:** `components/Contact.tsx` - [ ] **Schritt 12.1: Props-Interface hinzufügen** Am Dateianfang (nach dem `"use client"` Directive): ```typescript interface OeffnungsZeit { tag: string; von: string; bis: string } interface ContactProps { telefon?: string; email?: string; adresseZeile1?: string; adresseZeile2?: string; oeffnungszeiten?: OeffnungsZeit[]; } ``` - [ ] **Schritt 12.2: Datei-level `contactItems`-Array entfernen und Funktionssignatur anpassen** Die bestehende `const contactItems = [...]` auf Datei-Ebene (Zeilen 3–37) vollständig entfernen. Dann die Funktionssignatur ersetzen und `contactItems` in den Funktionskörper verschieben: ```typescript export default function Contact({ telefon = "+49 171 9345193", email = "kontakt@mbo-tech-it.de", adresseZeile1 = "Mörikestr. 2", adresseZeile2 = "74564 Crailsheim", oeffnungszeiten, }: ContactProps = {}) { const contactItems = [ { icon: , label: "Telefon", value: telefon, href: `tel:${telefon.replace(/\s/g, "")}`, }, { icon: , label: "E-Mail", value: email, href: `mailto:${email}`, }, { icon: , label: "Standort", value: `${adresseZeile1}, ${adresseZeile2}`, href: `https://maps.google.com/?q=${encodeURIComponent(adresseZeile1 + " " + adresseZeile2)}`, }, ] ``` - [ ] **Schritt 12.3: Öffnungszeiten-Block im JSX ergänzen** Unterhalb der `contactItems`-Liste im JSX (nach der letzten `contactItems.map`-Ausgabe): ```tsx {oeffnungszeiten && oeffnungszeiten.length > 0 && (

Öffnungszeiten

    {oeffnungszeiten.map((oz) => (
  • {oz.tag} {oz.von} – {oz.bis}
  • ))}
)} ``` - [ ] **Schritt 12.4: Commit** ```bash git add components/Contact.tsx git commit -m "feat: contact component accepts cms props" ``` --- ## Task 13: `app/page.tsx` dynamisieren **Datei:** `app/page.tsx` - [ ] **Schritt 13.1: Datei vollständig ersetzen** ```typescript import { createServiceClient } from "@/lib/supabase"; import Header from "@/components/Header"; import Hero from "@/components/Hero"; import TaglineBanner from "@/components/TaglineBanner"; import StatsBar from "@/components/StatsBar"; import Services from "@/components/Services"; import Technologies from "@/components/Technologies"; import DataSovereignty from "@/components/DataSovereignty"; import PaperlessSection from "@/components/PaperlessSection"; import About from "@/components/About"; import Contact from "@/components/Contact"; import Footer from "@/components/Footer"; import { GalerieAnzeige } from "@/components/GalerieAnzeige"; export const dynamic = "force-dynamic"; export default async function Home() { const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL ?? ""; let hero = null, badges: { text: string }[] = [], ueberUns = null, stats: { wert: string; label: string }[] = [], galerie: { id: string; storage_path: string; alt_text: string }[] = [], kontakt = null, oeffnung: { tag: string; von: string; bis: string }[] = []; try { const db = createServiceClient(); const [ heroResult, badgesResult, ueberUnsResult, statsResult, galerieResult, kontaktResult, oeffnungResult, ] = await Promise.allSettled([ db.from("hero_content").select("*").limit(1).single(), db.from("hero_badges").select("*").order("reihenfolge"), db.from("ueber_uns_content").select("*").limit(1).single(), db.from("ueber_uns_stats").select("*").order("reihenfolge"), db.from("galerie_bilder").select("*").order("reihenfolge"), db.from("kontakt_info").select("*").limit(1).single(), db.from("kontakt_oeffnungszeiten").select("*").order("reihenfolge"), ]); hero = heroResult.status === "fulfilled" ? heroResult.value.data : null; badges = badgesResult.status === "fulfilled" ? (badgesResult.value.data ?? []) : []; ueberUns = ueberUnsResult.status === "fulfilled" ? ueberUnsResult.value.data : null; stats = statsResult.status === "fulfilled" ? (statsResult.value.data ?? []) : []; galerie = galerieResult.status === "fulfilled" ? (galerieResult.value.data ?? []) : []; kontakt = kontaktResult.status === "fulfilled" ? kontaktResult.value.data : null; oeffnung = oeffnungResult.status === "fulfilled" ? (oeffnungResult.value.data ?? []) : []; } catch { // Supabase nicht konfiguriert — Fallback-Werte greifen } const galerieBilder = galerie.map((b) => ({ id: b.id, src: `${supabaseUrl}/storage/v1/object/public/galerie-bilder/${b.storage_path}`, alt: b.alt_text, })); return (
0 ? badges.map((b) => b.text) : undefined} /> 0 ? stats : undefined} /> {galerieBilder.length > 0 && ( <>
)} 0 ? oeffnung : undefined} />
); } ``` - [ ] **Schritt 13.2: TypeScript-Check** ```bash npx tsc --noEmit ``` Erwartet: Keine Fehler. - [ ] **Schritt 13.3: Dev-Server starten und Seite prüfen** ```bash npm run dev ``` Prüfen unter `http://localhost:3000`: - Seite lädt ohne Fehler (Fallback-Werte werden gezeigt solange DB-Tabellen leer sind) - Kein Absturz, kein 500-Error in der Konsole - [ ] **Schritt 13.4: Commit** ```bash git add app/page.tsx git commit -m "feat: load homepage sections from supabase cms" ``` --- ## Task 14: Build-Check & Abschluss - [ ] **Schritt 14.1: TypeScript vollständig prüfen** ```bash npx tsc --noEmit ``` Erwartet: 0 Fehler. - [ ] **Schritt 14.2: Produktions-Build prüfen** ```bash npm run build ``` Erwartet: Kein Build-Fehler. Nur Warnungen zu `force-dynamic` sind akzeptabel. - [ ] **Schritt 14.3: Admin-Bereich testen** Dev-Server starten und manuell testen: 1. `http://localhost:3000/admin/login` → Login mit dem angelegten Admin-User 2. `/admin/hero` → Texte anpassen, speichern, Startseite neu laden → Änderung sichtbar 3. `/admin/galerie` → Bild hochladen (Storage Bucket muss existieren) 4. `/admin/ueber-uns` → Text + Statistik speichern 5. `/admin/kontakt` → Öffnungszeiten + Kontaktdaten speichern 6. `/admin/passwort` → Passwort ändern, neu einloggen - [ ] **Schritt 14.4: Supabase-Infrastructure (manuell im Dashboard)** Im Supabase-Dashboard `https://supabase.mbo-tech-it.de`: **Storage → New Bucket** (alle vier anlegen, Public = ✓): - `hero-bilder` - `site-assets` - `ueber-uns-bilder` - `galerie-bilder` **Authentication → URL Configuration:** - Site URL: `https://mbo-tech-it.de` - Redirect URLs: `https://mbo-tech-it.de/auth/callback` - Email Confirmations: aktivieren - [ ] **Schritt 14.5: git push** ```bash git push ``` --- ## Abhängigkeiten zwischen Tasks ``` Task 1 (email_queue, admin_users, anfragen) └── Task 2 (audit_logs, token_blacklist, page_views, phone_clicks) └── Task 3 (CMS + Admin-User) └── Task 4 (npm install) └── Task 5 (supabase.ts Typen) └── Task 6 (CMS-Dateien kopieren) ├── Task 7 (passwort route fix) ├── Task 8 (Hero-Defaults) └── Task 9 (AdminNav) └── Task 10 (Hero.tsx props) └── Task 11 (About.tsx props) └── Task 12 (Contact.tsx props) └── Task 13 (page.tsx dynamisieren) ← benötigt Tasks 10-12 └── Task 14 (Build-Check) ```