1101 lines
36 KiB
Markdown
1101 lines
36 KiB
Markdown
# 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', '<HASH_AUS_OBIGEM_BEFEHL>');
|
||
```
|
||
|
||
- [ ] **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 */}
|
||
<div className="inline-flex items-center gap-2 px-5 py-2 rounded-full border border-orange-500/40 bg-orange-500/10 text-orange-400 text-xs font-black tracking-[0.3em] uppercase mb-8">
|
||
<span className="w-1.5 h-1.5 bg-orange-400 rounded-full animate-pulse" />
|
||
{eyebrowText}
|
||
</div>
|
||
|
||
{/* Headline */}
|
||
<h1 className="text-5xl sm:text-6xl lg:text-7xl font-black text-slate-900 dark:text-white leading-tight mb-6 tracking-tight">
|
||
{headline1}{" "}
|
||
<span className="text-gradient">
|
||
{typed}
|
||
<span className="animate-pulse">|</span>
|
||
</span>
|
||
</h1>
|
||
|
||
{/* Subheadline */}
|
||
{subtext1 && (
|
||
<p className="text-xl sm:text-2xl text-slate-600 dark:text-slate-400 max-w-3xl mx-auto mb-4 leading-relaxed">
|
||
{subtext1}
|
||
</p>
|
||
)}
|
||
{subtext2 && (
|
||
<p className="text-xl sm:text-2xl text-slate-600 dark:text-slate-400 max-w-3xl mx-auto mb-4 leading-relaxed">
|
||
{subtext2}
|
||
</p>
|
||
)}
|
||
|
||
{/* Tech pills (aus hero_badges) */}
|
||
<div className="flex flex-wrap items-center justify-center gap-3 mb-10">
|
||
{badges.map((badge) => (
|
||
<span
|
||
key={badge}
|
||
className="px-3 py-1 rounded-md bg-slate-100 dark:bg-gray-900 border border-slate-200 dark:border-gray-700 text-slate-700 dark:text-slate-300 text-sm font-mono"
|
||
>
|
||
{badge}
|
||
</span>
|
||
))}
|
||
</div>
|
||
|
||
{/* CTAs */}
|
||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||
<a href={cta1Href} className="btn-primary w-full sm:w-auto px-8 py-4 text-lg">
|
||
{cta1Text}
|
||
</a>
|
||
<a href={cta2Href} className="btn-secondary w-full sm:w-auto px-8 py-4 text-lg">
|
||
{cta2Text}
|
||
</a>
|
||
</div>
|
||
```
|
||
|
||
- [ ] **Schritt 10.4: Hintergrundbild optional**
|
||
|
||
Das Background-`<div>` mit bedingtem Bild ergänzen (falls `bgImagePath` gesetzt):
|
||
|
||
```tsx
|
||
{bgImagePath && (
|
||
<div
|
||
className="absolute inset-0 bg-cover bg-center opacity-20"
|
||
style={{ backgroundImage: `url(${process.env.NEXT_PUBLIC_SUPABASE_URL}/storage/v1/object/public/hero-bilder/${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
|
||
<span className="text-orange-400 font-mono text-xs font-bold tracking-[0.25em] uppercase">
|
||
{eyebrowText}
|
||
</span>
|
||
|
||
<p className="text-slate-600 dark:text-slate-400 text-lg leading-relaxed mb-6">
|
||
{absatz1}
|
||
</p>
|
||
<p className="text-slate-600 dark:text-slate-400 text-lg leading-relaxed mb-8">
|
||
{absatz2}
|
||
</p>
|
||
```
|
||
|
||
Falls `stats` befüllt ist, die Statistiken rendern (als Ergänzung zu den bestehenden `highlights`):
|
||
|
||
```tsx
|
||
{stats && stats.length > 0 && (
|
||
<div className="flex gap-8 mt-4">
|
||
{stats.map((s) => (
|
||
<div key={s.label}>
|
||
<div className="text-3xl font-black text-orange-400">{s.wert}</div>
|
||
<div className="text-sm text-slate-500 dark:text-slate-400">{s.label}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
```
|
||
|
||
- [ ] **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: <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
|
||
</svg>,
|
||
label: "Telefon",
|
||
value: telefon,
|
||
href: `tel:${telefon.replace(/\s/g, "")}`,
|
||
},
|
||
{
|
||
icon: <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||
</svg>,
|
||
label: "E-Mail",
|
||
value: email,
|
||
href: `mailto:${email}`,
|
||
},
|
||
{
|
||
icon: <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||
</svg>,
|
||
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 && (
|
||
<div className="mt-6 pt-6 border-t border-slate-200 dark:border-slate-700">
|
||
<h4 className="text-sm font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-3">
|
||
Öffnungszeiten
|
||
</h4>
|
||
<ul className="space-y-1">
|
||
{oeffnungszeiten.map((oz) => (
|
||
<li key={oz.tag} className="flex justify-between text-sm text-slate-600 dark:text-slate-400">
|
||
<span>{oz.tag}</span>
|
||
<span>{oz.von} – {oz.bis}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
)}
|
||
```
|
||
|
||
- [ ] **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 (
|
||
<main className="min-h-screen bg-[#f0f4f8] dark:bg-[#18212f]">
|
||
<Header />
|
||
<Hero
|
||
eyebrowText={hero?.eyebrow_text}
|
||
headline1={hero?.headline1}
|
||
subtext1={hero?.subtext1}
|
||
subtext2={hero?.subtext2}
|
||
cta1Text={hero?.cta1_text}
|
||
cta1Href={hero?.cta1_href}
|
||
cta2Text={hero?.cta2_text}
|
||
cta2Href={hero?.cta2_href}
|
||
bgImagePath={hero?.bg_image_path}
|
||
badges={badges.length > 0 ? badges.map((b) => b.text) : undefined}
|
||
/>
|
||
<TaglineBanner />
|
||
<StatsBar />
|
||
<Services />
|
||
<TaglineBanner />
|
||
<PaperlessSection />
|
||
<DataSovereignty />
|
||
<TaglineBanner />
|
||
<Technologies />
|
||
<About
|
||
eyebrowText={ueberUns?.eyebrow_text}
|
||
absatz1={ueberUns?.absatz1}
|
||
absatz2={ueberUns?.absatz2}
|
||
stats={stats.length > 0 ? stats : undefined}
|
||
/>
|
||
{galerieBilder.length > 0 && (
|
||
<>
|
||
<TaglineBanner />
|
||
<section className="py-16 px-4 sm:px-6 lg:px-8">
|
||
<div className="max-w-7xl mx-auto">
|
||
<GalerieAnzeige bilder={galerieBilder} />
|
||
</div>
|
||
</section>
|
||
</>
|
||
)}
|
||
<TaglineBanner />
|
||
<Contact
|
||
telefon={kontakt?.telefon}
|
||
email={kontakt?.email}
|
||
adresseZeile1={kontakt?.adresse_zeile1}
|
||
adresseZeile2={kontakt?.adresse_zeile2}
|
||
oeffnungszeiten={oeffnung.length > 0 ? oeffnung : undefined}
|
||
/>
|
||
<Footer />
|
||
</main>
|
||
);
|
||
}
|
||
```
|
||
|
||
- [ ] **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)
|
||
```
|