maro 526a908c80
CI / test (push) Successful in 3m42s
Release / test (push) Successful in 3m44s
Release / build-and-deploy (push) Successful in 8m59s
Redesign: warmer, more editorial visual pass; remove stray em dashes
Replaces em dashes with en dashes across all hardcoded copy, emails, and
seed content, then reworks the site's visual language so it reads as
deliberately designed rather than assembled from generic components:

- New ArrowLink component replaces ad hoc "Mehr erfahren ->" links and
  ghost buttons; one shared FormField (TextField/TextAreaField) replaces
  7 near-duplicate field styles across every form
- Navbar loses its glassmorphism blur for a solid warm background, gets a
  subtle active-link underline and a softer, warmer mega-menu
- PageHeader supports per-page blob tone/position and a compact mode
  instead of an identical band on every interior page
- Homepage: the core offer is featured large instead of four equal cards,
  the philosophy section gets a single calmer accent blob, blog teasers
  now show their cover image like the real /aktuelles page does
- Booking calendar and widget: 44px touch targets throughout (month nav,
  day cells, time slots, cancel action), a visible "today" ring, and
  responsive padding so the calendar isn't cramped on small screens
- Quieter, warmer empty states on /termine and /aktuelles

No functional changes: all interaction logic (nav menus, forms, booking,
CMS data fetching) is untouched, only markup and styling.
2026-09-06 22:00:28 +02:00
2026-08-26 00:45:59 +00:00

Anouma

Die Website von Anouma — Next.js (App Router) mit einem eingebauten Payload CMS (PostgreSQL) für Termine, Beiträge, Angebote und Seiteninhalte, plus Online-Termine mit P2P-Video-Call (WebRTC), Anmeldungen und E-Mail-Erinnerungen.

Projektstruktur

  • app/(frontend)/ — die öffentliche Website (bestehendes ANOUMA-Design, eigener Root-Layout)
  • app/(call)/ — die Video-Call-Oberfläche (/termine/[slug]/call), eigenes minimalistisches dunkles Layout ohne Navbar/Footer
  • app/(payload)/ — der Admin-Bereich unter /admin, Payloads REST/GraphQL-API sowie die Meeting-/Cron-API-Routen (api/meetings/..., api/cron/...)
  • app/global-not-found.tsx — statische 404-Seite (siehe unten, warum sie nötig ist)
  • collections/, globals/, access/, fields/, payload.config.ts — die CMS-Konfiguration
  • lib/payload/ — Local-API-Zugriffe für die öffentliche Website (mit React.cache pro Request memoisiert)
  • lib/meeting/ — Meeting-Passwort, Zeitfenster/Status, signierte Beitritts-Tokens, WebSocket-Signaling, Reminder-Logik
  • lib/email/ — SMTP-Versand (nodemailer) und das E-Mail-Template für Reminder
  • lib/texte.ts — die ursprünglichen Anouma-Texte, nur noch als Seed-Quelle verwendet
  • scripts/seed.ts — überträgt die vorhandenen Inhalte ins CMS
  • server.ts — eigener Node-Server (statt next start), weil daran der WebRTC-Signaling-WebSocket hängt

Zwei bzw. drei Root-Layouts ((frontend), (payload), (call)) bedeuten: Next.js kann daraus keine einzelne 404-Seite komponieren, deshalb gibt es app/global-not-found.tsx (siehe experimental.globalNotFound in next.config.ts).

Online-Termine & Video-Call

  • Ein Termin wird per Häkchen „Online-Termin“ zu einem Video-Call-Termin. Payload generiert dabei automatisch ein zufälliges Meeting-Passwort (nie aus der Meeting-ID abgeleitet); die Admin kann jederzeit ein neues erzeugen lassen.
  • Öffentliche Beitrittsseite: /termine/[slug]/beitreten (Name + Passwort, beide Pflicht) → bei Erfolg /termine/[slug]/call.
  • Ist die aufrufende Person im selben Browser als Admin eingeloggt, wird sie automatisch als Host erkannt (kein Passwort nötig, größeres Beitritts-Zeitfenster, exklusive Steuerung: Bildschirmfreigabe, Teilnehmer entfernen).
  • Die Zeitfenster (wie früh Host/Teilnehmer beitreten dürfen, wie lange das Meeting nach Ende offen bleibt) sind zentral im CMS unter Video-Termin-Einstellungen konfigurierbar.
  • Server-seitige Validierung: /api/meetings/[slug]/join prüft Termin, Zeitfenster und Passwort und stellt danach erst ein kurzlebiges, signiertes Sitzungs-Token aus (MEETING_SESSION_SECRET). Das WebSocket-Signaling (/ws/signaling, siehe server.ts) prüft dieses Token erneut, bevor jemand einem Raum beitreten darf.
  • Reines P2P-WebRTC: Der Server relayt nur kleine Signaling-Nachrichten (Angebot/Antwort/ICE), niemals Audio/Video/Bildschirmfreigabe. Es wird nichts aufgezeichnet oder gespeichert.
  • Teilnehmer-Anmeldungen laufen über die Collection Anmeldungen (event-registrations), öffentlich erreichbar über /api/meetings/[slug]/register.

E-Mail-Erinnerungen

Erinnerungs-Mails (60/30 Minuten vorher, pro Termin im CMS einzeln an-/abschaltbar) werden nicht automatisch im Hintergrund verschickt, sondern müssen von einem externen Cron-Job ausgelöst werden, z. B. alle 5 Minuten:

curl -H "Authorization: Bearer $CRON_SECRET" https://anouma.org/api/cron/event-reminders

Der Versand ist idempotent (reminder60Sent/reminder30Sent je Anmeldung, hostReminder60Sent/hostReminder30Sent je Termin) — ein häufiger laufender Cron verschickt also nie doppelt.

Setup (Produktion / Deployment)

Produktion läuft komplett über Gitea Actions CI/CD — der Server baut nichts selbst, er zieht ein fertiges, getestetes Image aus der Registry. Einmalig auf dem Server:

git clone https://git.maro.run/maro/anouma.git
cd anouma
chmod +x setup.sh
./setup.sh

setup.sh installiert bei Bedarf Docker, legt /opt/anouma an, generiert alle Secrets automatisch (nie erneut, wenn schon vorhanden), richtet einen eingeschränkten Deployment-User samt SSH-Key für Gitea Actions ein und zeigt am Ende genau die Werte an, die als Gitea Secrets hinterlegt werden müssen. ./setup.sh --non-interactive läuft ohne Rückfragen.

Jedes weitere Deployment läuft danach ausschließlich über einen Git-Tag:

git tag v1.2.0 && git push origin v1.2.0

Gitea Actions testet, baut das Image, pusht es in die Registry und deployt es per SSH auf den Server — inklusive Backup, Healthcheck und automatischem Rollback bei Fehlschlag. Es gibt kein separates Update-Script.

Für alle Details (Architektur, Gitea Secrets, CI/Release-Workflows, NPM-Reverse-Proxy-Einrichtung, Environment-Variablen, WebRTC/STUN/TURN, Backups, Rollback, Security) siehe DEPLOYMENT.md.

Lokale Entwicklung (ohne Docker für die App)

npm install
cp .env.example .env
# .env ausfüllen: DATABASE_URI auf 127.0.0.1 statt "postgres" setzen, PAYLOAD_SECRET (z. B. mit `openssl rand -base64 48`)

Datenbank

Nur Postgres per Docker, die App läuft direkt auf dem Host:

docker compose up -d postgres

(oder eine gehostete Postgres-Instanz, z. B. Neon/Supabase — einfach DATABASE_URI in .env entsprechend setzen).

Entwicklung

npm run dev

Vorhandene Inhalte ins CMS übertragen

npm run seed

Überträgt die 7 Angebote sowie die Texte für Startseite, Über mich, Angebote-Einleitung, Aktuelles-Einleitung, Kontakt und Termin buchen aus lib/texte.ts ins CMS. Kann gefahrlos mehrfach ausgeführt werden. Kontaktdaten (E-Mail/Telefon/Region) werden zunächst als Platzhalter gesetzt — bitte im Admin unter „Kontakt“ durch die echten Angaben ersetzen.

Produktions-Build ohne Docker

npm run build
npm run migrate   # wendet Datenbank-Migrationen an
npm run start

Für einen vollständig dockerisierten Produktionsbetrieb (App + Datenbank, Healthchecks, Reverse-Proxy-Integration) siehe oben bzw. DEPLOYMENT.md.

Weitere Skripte

Skript Zweck
npm run generate:types payload-types.ts aus der Config neu erzeugen
npm run generate:importmap Admin-Importmap neu erzeugen (nach neuen Feldtypen)
npm run migrate:create Neue Datenbank-Migration aus Config-Änderungen bauen
npm run lint ESLint
npm test Test-Suite (aktuell ein Platzhalter — noch keine Tests vorhanden)
npm run seo:check Optionale SEO-Prüfung (doppelte Slugs, fehlende Alt-Texte) — nicht produktionskritisch

Design

Siehe app/(frontend)/globals.css für das Farbsystem und AGENTS.md für die Next.js-Version-16-Hinweise, die für jede Code-Änderung in app/ gelten.

S
Description
No description provided
Readme
781 KiB
Languages
TypeScript 92%
Shell 4.8%
CSS 2.8%
Dockerfile 0.3%