diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..460e349 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,5 @@ +# Version bewusst festhalten, damit alle dieselbe Mailpit-Version verwenden. +FROM axllent/mailpit:v1.31.2 AS mailpit +FROM mcr.microsoft.com/devcontainers/javascript-node:24-bookworm + +COPY --from=mailpit /mailpit /usr/local/bin/mailpit diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c2df1da..1f24ae9 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,15 +1,18 @@ { "name": "Ernaehrungsberatung", - "image": "mcr.microsoft.com/devcontainers/javascript-node:24-bookworm", "remoteUser": "node", - "forwardPorts": [5173], + "forwardPorts": [5173, 8025], "portsAttributes": { "5173": { "label": "Webseite", "onAutoForward": "notify" + }, + "8025": { + "label": "Mailpit – Testmails", + "onAutoForward": "notify" } }, - "postCreateCommand": "if [ -f package-lock.json ]; then npm ci; else npm install; fi", + "postCreateCommand": "bash .devcontainer/setup.sh", "customizations": { "vscode": { "extensions": [ @@ -17,5 +20,8 @@ "EditorConfig.EditorConfig" ] } + }, + "build": { + "dockerfile": "Dockerfile" } } diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100644 index 0000000..0afa5c9 --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +npm ci +if [ ! -e .env ]; then + cp .env.example .env +fi + +# Buchungsdatei nicht vorab anlegen: Das Backend erstellt sie bei der ersten Buchung. +echo "Bereit. In drei Terminals starten: npm run dev:mail, npm run dev:server, npm run dev" diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..318f119 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# Lokal nur einen Mailfänger verwenden. Keine echten Zugangsdaten eintragen. +HOST=127.0.0.1 +PORT=3000 +SMTP_HOST=127.0.0.1 +SMTP_PORT=1025 +SMTP_SECURE=false +SMTP_USER= +SMTP_PASS= +SMTP_FROM="Tri-Hub " +# Für echtes SMTP erst nach ausdrücklicher Freigabe ändern. +SMTP_ALLOW_EXTERNAL=false diff --git a/.gitignore b/.gitignore index cc9ffb7..e872025 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,9 @@ dist/ .DS_Store Thumbs.db *.log + +# Private Buchungsdaten und lokale Testergebnisse +src/server/data/* +!src/server/data/.gitkeep +playwright-report/ +test-results/ diff --git a/.prettierignore b/.prettierignore index 320c107..790904b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,7 @@ node_modules/ dist/ package-lock.json + +src/server/data/ +playwright-report/ +test-results/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ca7144 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# Tri-Hub Ernährungsberatung + +## Im Dev Container starten + +1. Repository klonen und in VS Code „Dev Containers: Reopen in Container“ wählen. + Bei einem bereits vorhandenen Container einmal „Dev Containers: Rebuild Container“ + ausführen, damit Mailpit installiert wird. +2. Die Einrichtung installiert automatisch die npm-Abhängigkeiten und erstellt + `.env` aus `.env.example`, sofern noch keine `.env` vorhanden ist. +3. In drei getrennten Terminals in dieser Reihenfolge starten: + +```bash +npm run dev:mail +``` + +```bash +npm run dev:server +``` + +```bash +npm run dev +``` + +Startseite: http://localhost:5173 — über „Beratung kennenlernen“ zur +Angebotsübersicht und von dort zum Buchungsformular des gewählten Pakets. +Direkt: http://localhost:5173/angebotsuebersicht.html +Mailansicht: http://localhost:8025. Beide Web-Ports werden vom Dev Container +weitergeleitet. Mailpit läuft im selben Container auf SMTP-Port 1025 und fängt +Testmails lokal ab, ohne sie extern zu versenden. + +Die Container-Konfiguration installiert Mailpit v1.31.2 aus dem +[offiziellen Docker-Image](https://mailpit.axllent.org/docs/install/docker/). +Die Binärdatei, lokale Mails, `.env` und Buchungsdaten werden nicht committed. +Mailpit verwendet ohne Datenbankpfad einen temporären Speicher; Testmails müssen +nach einem Neustart nicht erhalten bleiben. + +Ohne Dev Container: Node.js 24 und Mailpit installieren, `npm ci` ausführen und +`.env.example` nach `.env` kopieren, falls `.env` fehlt. Danach dieselben drei +Startbefehle verwenden. + +Ohne Mailpit wird die Buchung gespeichert, aber der fehlgeschlagene Mailversand +angezeigt. Der Ablauf bleibt eine Simulation ohne Zahlung oder verbindliche +Beratung. Buchungen liegen lokal in `src/server/data/bookings.csv`. Diese Datei +nicht vorab leer anlegen: Das Backend erzeugt sie bei der ersten Buchung selbst. + +## Prüfen + +```bash +npm test +npm run build +npm run test:browser +``` + +Die Browsertests starten eigene Server auf 3000 und 5173; diese Ports vorher +freihalten. Sie verwenden eine temporäre CSV und simulierten Mailversand. +Falls Chromium fehlt: `npx playwright install chromium`. + +Der Build enthält Startseite, Angebotsübersicht und Buchungsseite. Für ein +Deployment zusätzlich das Node-Backend betreiben und `/api` dorthin weiterleiten. diff --git a/angebotsuebersicht.html b/angebotsuebersicht.html new file mode 100644 index 0000000..06d0290 --- /dev/null +++ b/angebotsuebersicht.html @@ -0,0 +1,34 @@ + + + + + + Ernährungs-Pakete – Tri-Hub + + + + + +
+
+
+

TRI-HUB · ERNÄHRUNG

+

Unsere Ernährungs-Pakete

+
+

+ Wähle das Paket, das zu deinem aktuellen Stand passt. Von + den Grundlagen bis zur Wettkampf-Vorbereitung. +

+
+
+
+ + + + diff --git a/booking-confirmation.html b/booking-confirmation.html new file mode 100644 index 0000000..5ad23bf --- /dev/null +++ b/booking-confirmation.html @@ -0,0 +1,161 @@ + + + + + + + + Buchungsbestätigung – Tri-Hub + + + +
+
+

Deine Buchungsübersicht

+

Keine Bestätigung verfügbar.

+

+ In diesem Tab liegt keine gespeicherte Buchungsbestätigung + vor. Öffne die Bestätigung im Tab deiner Buchung. Falls du + bereits gebucht hast, prüfe deine E-Mails, bevor du erneut + buchst. +

+ Zur Paketübersicht → +
+ + +
+ + + + + diff --git a/booking.html b/booking.html new file mode 100644 index 0000000..10fd45f --- /dev/null +++ b/booking.html @@ -0,0 +1,21 @@ + + + + + + + Paket buchen – Tri-Hub + + +
+ Zur Angebotsseite +

Deine Buchung

+ +
+
+ + + diff --git a/docs/booking.md b/docs/booking.md new file mode 100644 index 0000000..c6788e1 --- /dev/null +++ b/docs/booking.md @@ -0,0 +1,146 @@ +# Paketbuchung + +## Ablauf + +Die Angebotsübersicht führt zur Detailseite eines Pakets. Der Buchungsbutton +übergibt die Paket-ID und gegebenenfalls die gewählte Variante als URL-Parameter +an `booking.html`. Bestehende Links mit `#/buchen/:packageId` funktionieren weiterhin. + +Booking lädt die Bestellübersicht vom Server. Sie enthält Paket und Variante, +Laufzeit, Leistungen, Abrechnung, Nettopreis, MwSt. und Gesamtbetrag. Die Preise +stammen aus `src/shared/packagesdetails.js`; Preisangaben des Browsers werden +nicht übernommen. Ohne Varianten-ID wird die erste Variante des Pakets gewählt. + +Beim Absenden speichert der Server Kundendaten und den vollständigen Bestellstand +in der CSV. Anschließend sendet er eine Bestätigung als Text und HTML mit +Buchungsnummer, Datum, gebuchten Leistungen und Preisen. Änderungen am Katalog +verändern bereits gespeicherte Bestellungen nicht. + +Der Zahlungsvorgang ist weiterhin simuliert. Es erfolgt keine Abbuchung und +keine verbindliche Beratungsbuchung. Darauf weisen Formular und E-Mail hin. + +## Lokal starten + +Voraussetzung ist Node.js 24. Abhängigkeiten mit `npm ci` installieren und die +Werte aus `.env.example` in eine lokale `.env` übernehmen. Eine vorhandene +Konfiguration dabei erhalten. + +In separaten Terminals starten: + +- `npm run dev:mail`: Mailpit mit SMTP auf Port 1025 und Weboberfläche auf 8025. +- `npm run dev:server`: Buchungs-API auf Port 3000. +- `npm run dev`: Frontend auf Port 5173; `/api` wird an Node weitergeleitet. + +Die Angebotsübersicht liegt unter , +der Mailfänger unter . Mailpit ist im Dev Container enthalten. + +`npm run build` erzeugt die Seiten in `dist/`, einschließlich der Paketdetailseite. +Für ein Deployment muss der Webserver `/api` an den Node-Server weiterleiten. +`npm run start:server` startet die API ohne automatischen Neustart. + +## Konfiguration + +| Variable | Standardwert | Bedeutung | +| ------------------------ | -------------------------------- | --------------------------------------------------- | +| `HOST` | `127.0.0.1` | Bind-Adresse der API | +| `PORT` | `3000` | API-Port; bei Änderung auch den Vite-Proxy anpassen | +| `SMTP_HOST` | `127.0.0.1` | SMTP-Server | +| `SMTP_PORT` | `1025` lokal, sonst `587` | SMTP-Port | +| `SMTP_SECURE` | `false` | Direktes TLS aktivieren | +| `SMTP_USER`, `SMTP_PASS` | leer | SMTP-Zugangsdaten; beide gemeinsam setzen | +| `SMTP_FROM` | `Tri-Hub ` | Absender | +| `SMTP_ALLOW_EXTERNAL` | `false` | Versand über externe SMTP-Server aktivieren | + +Externe SMTP-Verbindungen erfordern TLS. Zugangsdaten gehören ausschließlich +in die Serverkonfiguration und dürfen kein `VITE_`-Präfix erhalten. + +## API + +Der vollständige Vertrag steht in `src/server/swagger.json`. + +| Endpunkt | Funktion | +| ---------------------------- | ----------------------------------------------------------------------------------- | +| `GET /api/angebote` | Bestehende Kurzübersicht mit `id`, `name` und `summary` | +| `GET /api/packages` | Kurzübersicht im Swagger-Format | +| `GET /api/packages/{id}` | Paketdetails einschließlich Varianten und Leistungen | +| `POST /api/bookings/preview` | Bestellübersicht für `packageId` und optionale `variantId`; speichert keine Buchung | +| `POST /api/bookings` | Buchung speichern und Bestätigung versenden | + +POST-Anfragen benötigen `Content-Type: application/json`. Beim Buchen ist +zusätzlich ein `Idempotency-Key` als UUID v4 erforderlich. Das Formular sendet +`packageId`, gegebenenfalls `variantId`, `name`, `email` und optional `phone`. +Die Telefonnummer darf höchstens 50 Zeichen und keine Steuerzeichen enthalten. +Sie wird in der CSV gespeichert und in der Bestätigungsmail ausgegeben. + +Alternativ akzeptiert die API das Swagger-Objekt `customer` mit `firstName`, +`lastName`, `email` sowie optional `phone`, `ageGroup` und `notes`. Dabei muss +`acceptedTerms` den Wert `true` haben. Diese Angaben werden ebenfalls gespeichert. + +Die Antwort enthält die Buchungsnummer, den Erstellungszeitpunkt, das gebuchte +Paket unter `bookedPackage` und den Versandstatus. Eine neue Buchung mit +SMTP-Annahme erhält HTTP 201, eine Wiederholung HTTP 200. Bei ausstehendem, +fehlgeschlagenem oder unklarem Versand wird HTTP 202 zurückgegeben. + +Ungültige Angaben, Pakete und Varianten ergeben HTTP 400; ein unbekanntes Paket +am Detail-Endpunkt HTTP 404. Derselbe Anfrageschlüssel mit anderen Daten ergibt +HTTP 409. JSON-Anfragen sind auf 8 KiB begrenzt. Fehlerantworten enthalten +`error.code`, `error.message` und gegebenenfalls `error.fields`. + +## Speicherung und Wiederholungen + +Die Buchungen liegen in `src/server/data/bookings.csv`. Neue Buchungsnummern +werden fortlaufend als `TH-000001`, `TH-000002` usw. vergeben. Der höchste +vorhandene Wert bestimmt die nächste Nummer. Die CSV muss deshalb vollständig +erhalten bleiben; ältere UUID-Buchungsnummern werden weiterhin unterstützt. + +`bookedPackage` speichert den Bestellstand als JSON, `customer` die optionalen +strukturierten Kundendaten und `acceptedTerms` die zugehörige Zustimmung. +Die optionale Telefonnummer steht zusätzlich in der Spalte `phone`. +CSV-Dateien mit dem früheren Spaltensatz bleiben lesbar und werden beim nächsten +Speichern erweitert. Für alte Buchungen ohne Bestellstand liefert die API +`bookedPackage: null`. + +Lesen, Schreiben und Versand werden innerhalb einer Warteschlange abgearbeitet. +Die Datei wird über eine temporäre Datei atomar ersetzt. Unterstützt wird ein +Backend-Prozess auf einem lokalen Dateisystem. Mehrere Serverinstanzen benötigen +einen gemeinsam abgesicherten Speicher. CSV-Zellen werden gegen die Auswertung +als Tabellenformeln geschützt; Dateien erhalten den Zugriffsmodus 0600. + +Wiederholte Anfragen mit demselben Schlüssel und denselben Daten liefern die +vorhandene Buchung. Es wird keine zweite Bestellung angelegt. Die Auswahl einer +anderen Variante mit demselben Schlüssel wird abgelehnt. + +Das Formular merkt sich Anfrage und Bestellübersicht je Paket und Variante im +`sessionStorage`. Nach einem Verbindungsfehler bleiben die Angaben gesperrt, +damit dieselbe Anfrage erneut geprüft werden kann. Ein neuer Tab oder gelöschter +Sitzungsspeicher liegt außerhalb dieser Absicherung. + +## Versandstatus + +| Status | Bedeutung | +| ---------- | ------------------------------------------------------------------------------------------------ | +| `pending` | Buchung gespeichert; Versand noch nicht gestartet. Dieselbe Anfrage kann den Versand fortsetzen. | +| `sending` | Versandabsicht gespeichert. Nach einem Prozessabbruch liefert die API `unknown`. | +| `accepted` | SMTP hat die Nachricht angenommen; die Zustellung ist noch nicht bestätigt. | +| `failed` | Versand fehlgeschlagen oder ausdrücklich abgelehnt. | +| `unknown` | SMTP-Annahme oder anschließende Statusspeicherung blieb unklar. | + +Bei `failed` und `unknown` wird nicht automatisch erneut versendet. Vor einem +manuellen Neuversand muss der Mailserver anhand der Buchungsnummer geprüft werden. +CSV und SMTP bilden keine gemeinsame Transaktion. + +## Tests + +`npm test` prüft Validierung, Preisberechnung, Varianten, CSV-Kompatibilität, +Wiederholungen und Versandfehler. `npm run test:browser` prüft den Weg von der +Angebotsübersicht über die Paketdetails bis zur Bestätigung, einschließlich +Premium-Jahresvariante, Tastaturbedienung und schmalem Bildschirm. + +Die Tests verwenden die regulären Paketdaten, temporäre CSV-Dateien und einen +simulierten Mailversand. Playwright benötigt Chromium; die Installation erfolgt +mit `npx playwright install --with-deps chromium`. Die Ports 3000 und 5173 müssen +für die automatisch gestarteten Testserver frei sein. + +Vor einem Commit außerdem `npm run format:check`, `npm run build` und +`git diff --check` ausführen. Für eine manuelle Mailprüfung eine Buchung mit einer +Adresse unter `example.test` anlegen und die Bestätigung in Mailpit kontrollieren. diff --git a/index.html b/index.html index dc976fa..a44a444 100644 --- a/index.html +++ b/index.html @@ -2,38 +2,1141 @@ + - 1:1-Ernährungsberatung + Ernährung · Tri-hub + + + + + + + + -
- Ernährungsberatung -
+ -
-

Ernährung, die zu dir passt.

-

- Individuelle 1:1-Beratung für deinen Alltag und deine - sportlichen Ziele. -

- Beratung kennenlernen +
+ +
+
+
+
TRI-HUB · ERNÄHRUNG
-
-

Deine persönliche Beratung

-

- Hier stellen wir bald unseren Ablauf und unser Angebot vor. -

+

+ Essen, Trinken
+ und Energie. +

+ +

+ Ernährung gehört zu deinem Training genauso wie + Schwimmen, Radfahren und Laufen. Hier findest du + Wissen und Orientierung rund um Sporternährung, + Hydration, Energieaufnahme und Magenmanagement – + passend zu deinem Triathlon ab 50. +

+ + +
+ + +
+
+ + +
+
+
+
+
TRI-HUB SPEKTRUM
+

+ Ernährung,
die mitdenkt. +

+
+ +

+ Die Ernährungsberatung ergänzt das Tri-hub-Angebot + um die Themen, die für Training und Wettkampf + besonders wichtig sind. Von den Grundlagen der + Sporternährung bis zur Frage, was dein Magen während + einer langen Einheit verträgt. +

+
+ +
+
+
01 · SPORTERNÄHRUNG
+

+ Die Grundlagen +

+

+ Verstehe, welche Rolle deine Ernährung vor, + während und nach dem Training spielen kann. +

+
+ +
+
02 · HYDRATION
+

+ Trinken & Energie +

+

+ Orientierung rund um Flüssigkeit und + Energieaufnahme – besonders dann, wenn die + Belastung länger wird. +

+
+ +
+
03 · MAGENMANAGEMENT
+

+ Was verträgst du? +

+

+ Ernährung soll nicht nur auf dem Papier + funktionieren. Sie muss auch unter Belastung zu + dir passen. +

+
+
+
+
+ + +
+
+
+
FACHBEITRÄGE
+

+ Wissen,
das dich
weiterbringt. +

+
+ +
+

+ Rund um Ernährung gibt es viele Empfehlungen. Bei + Tri-hub geht es darum, daraus verständliche + Orientierung für deinen Sport und deinen Alltag zu + machen. Die Inhalte dieser Seite bauen auf dem + Ernährungsspektrum von Tri-hub auf. +

+ +
    +
  • + +
    + Sporternährung
    + Grundlagen für Training und + Wettkampf. +
    +
  • +
  • + +
    + Hydration
    + Trinken und Flüssigkeitsversorgung im + Ausdauertraining. +
    +
  • +
  • + +
    + Energieaufnahme
    + Wie du Energie rund um Belastung + bewusst planen kannst. +
    +
  • +
  • + +
    + Magenmanagement
    + Verträglichkeit als Teil deiner + persönlichen Strategie. +
    +
  • +
+
+
+
+ + +
+
+
+
DEINE ERFAHRUNG
+

+ Was hat dir
geholfen? +

+

+ Deine Erfahrung kann anderen helfen, das Angebot + besser kennenzulernen. Teile, was du aus der + Ernährungsberatung mitgenommen hast und was sich für + dich im Training bewährt hat. +

+ + + Bewertung schreiben + +
+ +
+ +
+
+
+
T
+
+ Deine Erfahrung + Hier kann eine Kundenstimme + erscheinen +
+
+
+ ★★★★★ +
+
+ +
+ „Teile hier deine Erfahrung mit der + Ernährungsberatung – ehrlich, persönlich und so, + wie du sie erlebt hast.“ +
+
+ +

+ Hinweis: Die Bewertungen in dieser HTML-Demo werden + nicht dauerhaft gespeichert. Für den Live-Betrieb + sollte das Formular an das bestehende Backend oder + CMS von Tri-hub angebunden werden. +

+
+
+
+ + +
+
+
+
+
REZENSION VERFASSEN
+

+ Deine Stimme
zählt. +

+
+ +

+ Erzähle anderen von deiner persönlichen Erfahrung + mit dem Ernährungsangebot. Kurz und ehrlich reicht + völlig. +

+
+ +
+
+ + +
+ +
+ +
+ + + + + + + + + + + + + + +
+
+ +
+ + +
+ + + + + +
+
+
+ >>>>>>>>> Temporary merge branch 2
-