Chip-Auswahl im Gericht-Formular (Klick zum An-/Abwählen) statt Checkbox-Liste; Kürzel erscheinen hochgestellt neben dem Gerichtnamen in Speisekarte, mobiler Ansicht und Wochenplan-Editor. Editor bekommt außerdem einen Heute-Button.
Canteen Menu – Speisekarte für die Betriebskantine
Digitale Speisekarte für eine Firmenkantine (ca. 250 Mitarbeiter) mit öffentlicher Wochenansicht, Kantinen-Bereich zur Pflege der Gerichte und Admin-Bereich für Benutzerverwaltung und Branding.
Stack: Next.js 16 (App Router, TypeScript, Tailwind CSS) · Supabase (PostgreSQL, Auth inkl. Passkeys, Storage, Row Level Security)
Funktionen
| Bereich | Funktionen |
|---|---|
| Öffentlich (ohne Login) | Wochenplan Mo–Fr mit den Bereichen Brotzeit (09:00–10:00), Mittagessen, Mittagessen vegetarisch und Suppe (je 11:30–13:30); Navigation vorherige/nächste Woche; Logo zentriert über der Karte; Allergene & Zusatzstoffe unterhalb der Karte |
Kantinen-Team (/intern) |
Gerichte anlegen/bearbeiten/löschen; Gerichte Tagen/Bereichen zuweisen (mehrere pro Zelle) und wieder entfernen; globale Allergen-/Zusatzstofflisten pflegen; eigene Passkeys verwalten |
Admin (/admin) |
Kantinenmitarbeiter anlegen/bearbeiten/löschen (inkl. Passwort-Reset und Rollenvergabe); Farbgebung der Speisekarte anpassen; Logo hochladen/entfernen |
Anmeldung mit E-Mail + Passwort oder alternativ per Passkey (Registrierung im Bereich „Passkeys“ nach dem ersten Login).
Projektstruktur
app/ Next.js-Routen
page.tsx Öffentliche Speisekarte
login/ Login (Passwort + Passkey)
intern/ Kantinen-Bereich (Rolle: staff oder admin)
admin/ Verwaltung (Rolle: admin)
api/admin/users/ Benutzerverwaltung (serverseitig, Service-Role-Key)
components/ Client-Komponenten (WeekEditor, DishManager, …)
lib/supabase/ Supabase-Clients (Browser / Server / Admin)
lib/ Typen, Datums-/Formatierungshelfer
proxy.ts Session-Refresh + Schutz von /intern und /admin
supabase/migrations/ Schema & Seed-Daten (Referenz/Doku)
supabase/local/ Skripte + Dumps für die lokale PostgreSQL-Kopie
scripts/ Hilfsskripte (DB-Migration, lokaler Sync)
Entwicklung
Voraussetzung in beiden Varianten: .env.local mit den Werten aus
.env.example (liegt lokal bereits vor und ist per .gitignore vom
Repo ausgeschlossen).
Variante A – Docker (nichts vorinstallieren außer Docker):
docker compose --env-file .env.local --profile dev up dev
# http://localhost:3000 (Hot-Reload, Quellcode wird gemountet)
Variante B – lokal installiertes Node.js:
npm install
npm run dev # http://localhost:3000 (bindet an 0.0.0.0, auch im LAN erreichbar)
Umgebungsvariablen (Live-Betrieb)
Beim Webserver/Hoster müssen genau diese drei Variablen hinterlegt werden (Werte: Supabase-Dashboard → Project Settings → API Keys):
| Variable | Bedeutung | Geheim? |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
URL des Supabase-Projekts, z. B. https://hvzikwjoyhoakedlywhj.supabase.co |
nein |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Öffentlicher anon-Key (RLS schützt die Daten) | nein |
SUPABASE_SERVICE_ROLE_KEY |
Service-Role-Key für die Benutzerverwaltung | ja – niemals im Browser/Repo! |
APP_URL |
Öffentliche URL der App, z. B. https://menu.example.de (Origin-Check hinter Reverse-Proxy; zugleich Passkey-Domain) |
nein |
Build & Start auf dem Server – mit Docker (empfohlen, Node.js und PostgreSQL müssen nicht installiert sein):
docker compose --env-file .env.local up -d --build
# App: Port 3000 (hinter Reverse-Proxy mit HTTPS betreiben)
# Lokale DB-Kopie: Port 5433 (Dienst "db", optional)
Oder klassisch ohne Docker:
npm ci
npm run build
npm run start # Port 3000, bindet an 0.0.0.0 (hinter Reverse-Proxy betreiben)
Passkeys für die Produktions-Domain freischalten
Passkeys sind an die Domain gebunden. Aktuell ist localhost konfiguriert.
Vor dem Livegang: APP_URL auf die öffentliche URL setzen und einmalig
SUPABASE_ACCESS_TOKEN=<personal-access-token> node scripts/configure-passkeys.mjs
ausführen (liest APP_URL aus der Umgebung; die URL kann alternativ als
Argument übergeben werden). Ohne lokales Node.js geht es auch über Docker:
docker run --rm -v .:/app -w /app \
-e SUPABASE_ACCESS_TOKEN=<token> -e APP_URL=https://menu.example.de \
node:24-alpine node scripts/configure-passkeys.mjs
Achtung: Bereits registrierte Passkeys der alten Domain verlieren dabei ihre Gültigkeit und müssen neu registriert werden.
Datenbank
Supabase (führende Datenbank)
- Projekt-Ref:
hvzikwjoyhoakedlywhj(Regioneu-central-1, Frankfurt) - Schema und Seed-Daten:
supabase/migrations/0001_init.sqlund0002_seed.sql - Ausführen weiterer SQL-Dateien:
SUPABASE_ACCESS_TOKEN=<token> node scripts/db-apply.mjs <datei.sql>
Sicherheitsmodell (Row Level Security):
anondarf ausschließlich lesen (Speisekarte, Bereiche, Allergene, Zusatzstoffe, Einstellungen).staff/admin(Rolle improfiles-Eintrag) pflegen Gerichte, Wochenplan und Kennzeichnungen.- Nur
adminändert Einstellungen (Farben/Logo) und verwaltet Benutzer; das Anlegen/Löschen von Benutzern läuft serverseitig über den Service-Role-Key (app/api/admin/users). - Storage-Bucket
branding(Logo): öffentlich lesbar, Schreibzugriff nur Admin.
Lokale PostgreSQL-Kopie
Die Datenbank canteen_menu (Kopie von Schema + Daten aus Supabase, inkl.
Minimal-Stubs für auth/storage) gibt es in zwei Varianten:
Per Docker: Der Dienst db aus docker-compose.yml spielt die
Skripte aus supabase/local/ beim ersten Start automatisch ein
(PostgreSQL 17, Host-Port 5433 – 5432 ist meist durch eine native
PostgreSQL-Installation belegt –, Passwort über LOCAL_PG_PASSWORD).
Nativ: Auf dem Entwicklungsrechner läuft PostgreSQL 17 mit derselben Datenbank. Neu aufbauen bzw. aktualisieren:
$env:SUPABASE_DB_PASSWORD = "<Supabase-DB-Passwort>"
$env:LOCAL_PG_PASSWORD = "<lokales postgres-Passwort>"
powershell -File scripts\sync-local-db.ps1
Hinweis: Die lokale Kopie dient als Ablage/Backup und für Auswertungen. Logins (Passwort-Hashes, Passkeys) verbleiben ausschließlich in Supabase.
Zugangsdaten
Die Anmeldedaten (Admin, Demo-Mitarbeiter, Datenbank-Passwörter) stehen in
ZUGANGSDATEN.md – diese Datei ist bewusst nicht im Repository
(.gitignore). Nach dem ersten Login bitte die Startpasswörter ändern.