Files
2026-07-06 13:37:08 +02:00

144 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# place4bees vereinte Webseite
Selbst-hostbare Architektur: **Medusa.js** (Shop-Backend + Admin) + **Astro** (handgebautes Frontend).
**place4bees ist die neue, einzige Unternehmensmarke.** "Imkerei Björn Schumann" / "hhonig" taucht im Auftritt nirgends mehr auf (außer ggf. später im Impressum aus rechtlichen Gründen).
## Struktur
```
backend/ Medusa-Backend (Node.js, Turborepo), self-hosted via Docker
→ apps/backend enthält die eigentliche Medusa-Instanz
frontend/ Astro-Frontend
docker-compose.yml Postgres + Redis + Medusa-Backend (self-hosted)
```
## Eine Website, zwei Domains, zwei Bereiche
Es gibt **keine zwei Marken mehr**, sondern eine Website mit zwei Bereichen ("Sections"), gesteuert über `frontend/src/lib/sections.ts`:
- **Privatkunden** (`private`): Start, Shop, Über uns, Kontakt
- **Business** (`business`): Start, Angebot, Anfrage (nur Formular, kein Checkout)
Ein **persistenter Tab-Umschalter im Header** ("Privatkunden" / "Business") ist auf jeder Seite sichtbar und wechselt zwischen den Bereichen unabhängig davon, auf welcher Domain man sich befindet:
- **hhonig.de** → `/` zeigt automatisch den Privatkunden-Bereich
- **place4bees.com** → `/` zeigt automatisch den Business-Bereich
- Auf beiden Domains sind **beide Bereiche vollständig erreichbar** über die stabilen Pfade `/privatkunden` und `/business` (die Tabs verlinken immer auf diese festen Pfade, nie auf das host-abhängige `/`, damit kein Klick-Loop entsteht).
Die Zuordnung passiert in `frontend/src/lib/sections.ts` (`resolveSection`, `isBusinessHost`) anhand des `Host`-Headers rein serverseitig, keine Cookies/Redirects nötig.
## SEO-Konzept
- **Canonical-Konsolidierung:** Da dieselben Inhalte technisch auf zwei Domains erreichbar sind, zeigt jede Seite ihren `<link rel="canonical">` fest auf `https://place4bees.com/<pfad>` (in `Layout.astro`), unabhängig davon, über welche Domain sie aufgerufen wurde. Damit bündelt Google die Rankings auf einer Domain, statt sie zwischen hhonig.de und place4bees.com aufzuteilen. hhonig.de bleibt für Bestandsbesucher/Backlinks nutzbar, ohne hart per 301 umgeleitet zu werden.
- **Wichtig vor Go-Live:** Bestehende URLs von hhonig.de, shop.hhonig.de und place4bees.com erfassen und auf die neuen Pfade (`/`, `/shop`, `/business`, …) mappen als 301-Redirects dort, wo sich die URL-Struktur ändert.
- Google Search Console für beide Domains einrichten/aktualisieren, aktualisierte Sitemap einreichen.
- Produktseiten sollten vor Launch noch strukturierte Daten (schema.org `Product`) bekommen; der Privatkunden-Bereich zusätzlich `LocalBusiness`.
## Design / Theming
Farben sind als semantische Tokens in `frontend/src/styles/global.css` definiert (`--color-brand-primary`, `--color-brand-ink`, …) statt als rohe Tailwind-Farben in den Komponenten. Ein komplettes Reskinning bedeutet: Werte in dieser einen Datei ändern Komponenten bleiben unangetastet. Für einen eigenen Akzent nur im Business-Bereich gibt es bereits den Haken `[data-section="business"] { ... }` in derselben Datei.
**Aktuelles Look & Feel (2. Iteration, bewusst kontrastreicher):** dunkle Ink-Hero- und Footer-Flächen mit gestaffelter Hexagon-Grafik (`.hex-shape` in `global.css`, mehrere überlappende Sechsecke statt einer einzelnen Fläche), große Fraunces-Serifen-Headlines in Creme auf Dunkelbraun, Honiggold als Primärfarbe im Privatkunden-Bereich, Petrolgrün im Business-Bereich (automatisch über `[data-section="business"]`). Shop-Karten mit Schatten, Hover-Lift-Animation und Kategorie-Badges.
## Checkout-Flow
Voll funktionsfähig gegen die Medusa Store API, End-to-End getestet:
1. **Produktseite** (`/shop/[handle]`) → `AddToCartButton` legt bei Bedarf einen Cart an (Cookie `cart_id`) und fügt die gewählte Variante hinzu.
2. **Warenkorb** (`/warenkorb`) → Menge ändern / Position entfernen.
3. **Kasse** (`/kasse`) → Adresse, Versandart (aus Medusa geladen), Zahlung.
4. **Bestellbestätigung** (`/bestellung/danke`).
Zahlung läuft im Frontend aktuell noch über Medusas eingebauten `pp_system_default`-Provider (kein echtes Zahlungs-Gateway). Das PayPal-Backend ist bereits vorbereitet (siehe unten), die Frontend-Anbindung fehlt noch.
Alle Cart/Checkout-Aufrufe laufen direkt vom Browser aus gegen die Medusa Store API (`frontend/src/lib/cart.ts`), ohne eigene Backend-Route dazwischen.
## Zahlungsanbieter: PayPal
**Hinweis zur Wahl:** Medusa pflegt offiziell nur eine Stripe-Integration. Für PayPal gibt es kein offizielles Plugin, nur Community-Pakete einzelner kleiner Anbieter hier verwendet: [`@easypayment/medusa-payment-paypal`](https://www.npmjs.com/package/@easypayment/medusa-payment-paypal) (aktuell das am aktivsten gepflegte). Das ist ein bewusst in Kauf genommenes Risiko (Wartung/Sicherheit liegen bei einem kleinen Drittanbieter, nicht bei Medusa selbst) wurde nach Rücksprache trotzdem gewünscht.
**Bereits erledigt (Backend):**
- Plugin installiert, in `medusa-config.ts` registriert (Plugin + zwei Payment-Provider: `paypal` für Smart Buttons/Wallet, `paypal_card` für Karten-Zahlung über PayPal).
- Datenbank-Migration ausgeführt.
- Beide Provider (`pp_paypal_paypal`, `pp_paypal_card_paypal_card`) für die Europe-Region aktiviert.
- Server startet fehlerfrei mit der neuen Konfiguration.
**Noch offen (braucht dich):**
1. **PayPal-Konto verbinden:** Medusa Admin → Settings → PayPal → PayPal Connection → zunächst **Sandbox** wählen (Test-Modus, kostenlos und ohne Geschäftskonto-Prüfung sofort über [developer.paypal.com](https://developer.paypal.com/dashboard/) anlegbar) → "Connect to PayPal" oder Client-ID/Secret manuell eintragen.
2. **Frontend-Anbindung fehlt noch:** Das mitgelieferte UI-Paket (`@easypayment/medusa-paypal-ui`) ist explizit für Next.js-Storefronts gebaut und passt nicht zu unserem Astro-Frontend. Die PayPal-Buttons müssen wir selbst gegen PayPal's JS-SDK und die Medusa-Payment-Session bauen das kann ich erst sinnvoll umsetzen und testen, sobald ein Sandbox-Konto verbunden ist (sonst baue ich ungetesteten Zahlungscode).
3. Sobald Sandbox läuft: echte End-to-End-Bestellung testen, dann auf **Live** umschalten (im selben Admin-Screen, ohne Server-Neustart laut Plugin-Doku).
4. Optional: `PAYPAL_ENCRYPTION_KEY` (beliebiger starker Zufallsstring) in `.env` setzen, damit die gespeicherten PayPal-Zugangsdaten in der Datenbank verschlüsselt statt im Klartext liegen.
## Produktdaten: Preis, MwSt., Grundpreis, Lieferzeit
- **Preise sind inkl. MwSt.** und werden auch so angezeigt (Pflicht im B2C-Verkauf in Deutschland). Technisch über Medusas "Price Preferences" gelöst (`/admin/price-preferences`, `is_tax_inclusive: true` für Währung UND Region beide müssen übereinstimmen, siehe Bugfix unten).
- **Zwei Steuersätze eingerichtet** für die Deutschland-Steuerregion: 7% ermäßigt für alle 13 Honig-Produkte (Lebensmittel), 19% Standard für alles andere (Bienenprodukte, Süßigkeiten, Kurse, Gutscheine, Patenschaft). **Bitte mit einem Steuerberater gegenprüfen** insbesondere Gutscheine und Kurse/Patenschaften können abweichende umsatzsteuerliche Behandlung erfordern, das habe ich nur nach bestem Wissen als Entwickler, nicht als Steuerexperte eingerichtet.
- **Grundpreis (Preis pro kg)** wird auf Produktseite und in der Shop-Übersicht angezeigt, wo vorhanden berechnet aus dem tatsächlichen Nettofüllgewicht (z.B. "500 Gramm" aus dem Variantennamen), nicht aus dem Versandgewicht. Gespeichert als `metadata.net_weight_grams` pro Variante. Nur bei den 13 Honigsorten vorhanden (einzige Produkte, die nach Gewicht verkauft werden für Gutscheine, Kurse, Kerzen etc. ist kein Grundpreis vorgeschrieben).
- **Lieferzeit** ("2-3 Werktage") als `metadata.delivery_time` pro Produkt hinterlegt und auf der Produktseite separat angezeigt (vorher stand das nur unstrukturiert im Beschreibungstext).
- **Kategorie-Badge** (Honig, Bienenprodukte, Süßigkeiten mit Honig, Geschenkgutschein, Kurse, Bienenpatenschaften) aus den ursprünglichen Shopify-Tags als `metadata.category` übernommen, auf Shop-Karte und Produktseite sichtbar.
**Wichtiger Bugfix:** Medusa hatte Produktpreise und Versandkosten inkonsistent besteuert Produkte inkl. MwSt. (Bruttopreis blieb gleich), Versand exkl. MwSt. (Steuer wurde zusätzlich draufgerechnet, 10€ Versand wurden im Warenkorb zu 11,90€). Ursache: zwei widersprüchliche Price-Preference-Einträge (`currency_code: eur` → inklusiv, `region_id` Deutschland → exklusiv). Behoben, indem die Region-Preference ebenfalls auf `is_tax_inclusive: true` gesetzt wurde. End-to-End getestet: 7,90€ Honig + 10€ Versand = 17,90€ Gesamtsumme, keine versteckte Aufpreisung mehr.
## Lokal starten
```bash
# Postgres + Redis + Backend
docker compose up -d
# oder Backend lokal für Entwicklung (schneller iterieren):
cd backend/apps/backend && npm run dev # läuft auf Port 9001 (9000 war lokal belegt)
# Frontend
cd frontend && npm run dev # läuft auf Port 4321
```
Admin-Oberfläche für Produktpflege: **http://localhost:9001/app**
(Login wurde angelegt: `admin@hhonig.de` / `ChangeMe123!` bitte nach dem ersten Login ändern.)
**Host-abhängiges Verhalten lokal testen:**
```bash
curl -H "Host: hhonig.de" http://localhost:4321/ # → Privatkunden
curl -H "Host: place4bees.com" http://localhost:4321/ # → Business
```
(`server.allowedHosts` in `astro.config.mjs` erlaubt diese Test-Hostnamen gegen den Dev-Server.)
## Bereits erledigt
- Medusa-Backend lokal lauffähig (Postgres + Redis via Docker), Admin-User angelegt, Admin-UI getestet.
- Astro-Frontend mit Tailwind, Node-SSR-Adapter, zentralen Design-Tokens.
- Eine Website, zwei Bereiche (Privatkunden/Business) mit persistentem Tab-Umschalter, host-abhängigem Default, konsistent auf beiden Domains erreichbar.
- Shop-Anbindung an Medusa Store API funktioniert End-to-End (Produktliste + Produktdetail).
- Kompletter Checkout-Flow (Cart → Adresse → Versand → Zahlung → Bestellung) gebaut und End-to-End getestet, Bestellung erscheint im Admin.
- **Produktkatalog von shop.hhonig.de migriert:** alle 23 Produkte (Honig, Bienenprodukte, Patenschaften, Kurse, Gutscheine, Süßigkeiten) über Shopifys öffentliche `products.json`-API ausgelesen und per Medusa Admin API angelegt inkl. Varianten (Größen/Nennwerte), Preisen und Bildern. Migrations-Skript: siehe unten.
- Preis-Darstellung im Frontend korrigiert: Medusa v2 speichert Beträge als Dezimalwert (10 = 10,00 €, keine Cent-Umrechnung) frühere `/100`-Division in Warenkorb/Kasse/Produktseite entfernt.
- Produktseite zeigt bei mehreren Größen/Varianten jetzt den zur aktuellen Auswahl passenden Preis (aktualisiert sich beim Wechsel der Variante).
- Produktbilder selbst gehostet (nicht mehr auf Shopify-CDN verlinkt), siehe Produktmigration unten.
- PayPal-Backend vorbereitet (Plugin, Provider, Region-Aktivierung) Frontend-Buttons folgen, siehe "Nächste Schritte".
- MwSt. (7%/19%, inkl.-Anzeige), Grundpreis (€/kg) und Lieferzeit je Produkt ergänzt, siehe eigener Abschnitt unten.
- Redesign: dunkle Hero-/Footer-Flächen mit gestaffelter Hexagon-Grafik, überarbeitete Shop-Karten mit Preis/Grundpreis/Kategorie-Badge.
## Produktmigration (Shopify → Medusa)
Einmalig ausgeführtes Node-Skript, kein Teil der laufenden App: holt den Katalog über Shopifys öffentliche `https://shop.hhonig.de/products.json`-API und legt jedes Produkt per Medusa Admin API an (Varianten, Preise, Bilder). Nicht im Repo abgelegt (lief aus dem Scratchpad); bei Bedarf einfach neu schreiben oder erneut anfragen.
Bewusste Vereinfachungen beim Import, die noch nachgezogen werden sollten:
- ~~Bilder waren extern auf `cdn.shopify.com` verlinkt~~ **erledigt:** alle 52 Bilder heruntergeladen und über Medusas lokales File-Modul selbst gehostet (`/static/...` auf dem Medusa-Server). Konfiguriert in `medusa-config.ts` (`@medusajs/file-local`, `backend_url` aus `MEDUSA_BACKEND_URL`). Für Produktion: `MEDUSA_BACKEND_URL` auf die echte Backend-Domain setzen, sonst zeigen die Bild-URLs auf `localhost`.
- **Tags/Kategorien wurden nicht übernommen** (Medusas Tag-API verlangt vorab angelegte Tag-IDs). Die Shopify-Kategorien (Honig, Bienenprodukte, Bienenpatenschaften, Kurse, Geschenkgutschein, Süßigkeiten) liegen aber in den Rohdaten vor und lassen sich bei Bedarf nachträglich als Medusa-Kategorien/Collections anlegen, sobald es eine Kategorie-Navigation im Frontend gibt.
- **Varianten sind auf `manage_inventory: false`** gesetzt (immer bestellbar, keine Lagerbestandsprüfung) für den Start pragmatisch, sollte aber vor Go-Live durch echte Lagerbestände ersetzt werden, damit nichts verkauft wird, was nicht vorrätig ist.
- Ein Medusa-Demo-Produkt ("Medusa T-Shirt") ließ sich nicht löschen, da eine Testbestellung eine Lagerreservierung dafür hält unschädlich, kann später im Admin entfernt werden, sobald die Testbestellung storniert/gelöscht ist.
## Nächste Schritte
1. PayPal-Sandbox-Konto verbinden (Medusa Admin → Settings → PayPal) und danach die PayPal-Buttons im Frontend bauen (siehe oben).
2. Steuersätze/-zuordnung von einem Steuerberater gegenprüfen lassen (siehe oben), bevor echte Bestellungen laufen.
3. Echte Lagerbestände pflegen statt `manage_inventory: false` (aktuell immer bestellbar, keine Bestandsprüfung).
4. Kontakt-/Anfrage-Formulare (`/kontakt`, `/business/anfrage`) an einen Versand-Endpunkt anbinden (z.B. E-Mail-Versand über eigenes API-Route in Astro).
5. Produktions-Deployment: Reverse-Proxy/Server so einrichten, dass beide Domains (hhonig.de, place4bees.com) auf dieselbe Astro-Instanz zeigen (Host-Header muss unverändert durchgereicht werden) und 301-Redirect-Mapping für geänderte Alt-URLs erstellen.
6. Redis-gestützte Event-Bus/Cache-Module für Produktionsbetrieb ergänzen (aktuell nutzt Medusa lokal In-Memory-Fallback).
7. Echte Medusa-Kategorien/Collections für die 6 Produktgruppen anlegen, sobald eine Kategorie-Navigation im Shop gewünscht ist (aktuell nur als Badge über `metadata.category`, keine Filter-Navigation).
8. Impressum/Rechtliches ergänzen (dort ggf. "Imkerei Björn Schumann" als rechtlicher Betreibername, falls weiterhin relevant).