# Spreewaldzeit — MVP Private Ferienwohnungs-Website mit zwei Wohnungen. Ruhig, editorial, wartungsarm. Technik-Stack bewusst schlank gehalten — kein Hotel-PMS-Overhead, aber vorbereitet für spätere Erweiterungen (iCal-Sync, Direktbuchung, Zahlungsabwicklung). ## Tech-Stack - **Next.js 14** (App Router, Server Components) - **TypeScript**, strict - **Prisma** + **SQLite** (einfach portierbar nach Postgres/MySQL) - **Tailwind CSS** mit eigener Naturpalette - **React Hook Form** + **Zod** für Formulare & Validierung - **jose** für signierte Session-Cookies (kein NextAuth-Overhead) - **nodemailer** für Mailversand (mit Dev-Fallback ins Terminal) - **react-day-picker** für den Verfügbarkeitskalender ## Projektstruktur ``` spreewaldzeit/ ├── app/ │ ├── layout.tsx # Root-Layout + Google Fonts │ ├── globals.css # Tailwind-Layer + globale Styles │ ├── page.tsx # Startseite │ ├── not-found.tsx │ ├── wohnungen/[slug]/ │ │ └── page.tsx # Wohnungsdetail │ ├── anfrage/ │ │ └── page.tsx # Anfrageformular │ ├── datenschutz/page.tsx │ ├── impressum/page.tsx │ ├── admin/ │ │ ├── layout.tsx │ │ ├── page.tsx # Redirect → Anfragen │ │ ├── login/page.tsx │ │ ├── anfragen/page.tsx # Anfragen-Liste │ │ ├── kalender/page.tsx # Blocks sperren/freigeben │ │ └── wohnungen/page.tsx # Wohnungen pflegen │ └── api/ │ ├── inquiries/route.ts # POST (öffentlich) │ ├── availability/[slug]/route.ts # GET (öffentlich) │ └── admin/ │ ├── login/route.ts │ ├── logout/route.ts │ ├── inquiries/[id]/route.ts # PATCH, DELETE │ ├── blocks/route.ts # POST │ ├── blocks/[id]/route.ts # DELETE │ └── apartments/[id]/route.ts # PATCH ├── components/ │ ├── layout/ (Header, Footer) │ ├── home/ (Hero, About, ApartmentPreview, Location) │ ├── apartment/ (Gallery, Features, AvailabilityCalendar) │ ├── inquiry/ (InquiryForm) │ ├── admin/ (AdminNav, InquiryRow, CalendarManager, ApartmentEditor) │ └── ui/ (Button, Input, Label, Textarea, FieldError) ├── lib/ │ ├── db.ts # Prisma-Singleton │ ├── auth.ts # JWT-Session via jose │ ├── email.ts # Nodemailer + Mail-Templates │ ├── validations.ts # Zod-Schemas │ └── utils.ts # Preis-/Datumsformatierung ├── prisma/ │ ├── schema.prisma # Apartment, Inquiry, Block, Admin │ └── seed.ts # 2 Wohnungen + Admin + 1 Beispiel-Block ├── types/ ├── middleware.ts # schützt /admin und /api/admin ├── tailwind.config.ts ├── next.config.js ├── tsconfig.json ├── .env.example └── package.json ``` ## Setup ### 1. Abhängigkeiten installieren ```bash npm install ``` ### 2. Environment vorbereiten ```bash cp .env.example .env ``` Dann `.env` öffnen und **mindestens** setzen: - `AUTH_SECRET` — zufällige, mind. 32 Zeichen lange Zeichenkette (z. B. `openssl rand -base64 48`) - `ADMIN_EMAIL` und `ADMIN_PASSWORD` — initialer Admin-Account - `OWNER_EMAIL` — wohin sollen neue Anfragen gehen? Die SMTP-Variablen sind **optional**. Ohne sie werden Mails ins Terminal geloggt (praktisch fürs lokale Entwickeln). ### 3. Datenbank + Seed ```bash npm run setup ``` Das führt die Prisma-Migration aus und legt die zwei Beispiel-Wohnungen samt Admin-Account an. ### 4. Dev-Server ```bash npm run dev ``` → [http://localhost:3000](http://localhost:3000) → Admin: [http://localhost:3000/admin/login](http://localhost:3000/admin/login) ## Nützliche Scripts | Script | Zweck | |---------------------|----------------------------------------------------| | `npm run dev` | Dev-Server mit Hot-Reload | | `npm run build` | Production-Build (inkl. `prisma generate + migrate`)| | `npm run start` | Production-Server | | `npm run db:push` | Schema ohne Migration in DB pushen (prototyping) | | `npm run db:migrate`| Neue Migration erzeugen | | `npm run db:seed` | Seed erneut laufen lassen | | `npm run db:studio` | Prisma Studio öffnen (DB-Browser) | ## Design-System - **Farben:** `parchment` (Hintergrund), `ink` (Text), `moss` & `sand` (Akzente) - **Typografie:** *Fraunces* (Display) + *Figtree* (Body), geladen via `next/font` - **Layout:** Container mit großzügigem Whitespace, asymmetrische Grids - **Bildsprache:** Große Bilder, ruhige Kompositionen, dezente Hover-Zooms Alle Tokens in `tailwind.config.ts`. ## Wichtige Designentscheidungen ### Warum SQLite? Ein Reiseobjekt mit zwei Wohnungen erzeugt wenig Schreiblast. SQLite ist schnell, wartungsarm und das Schema ist 1:1 auf Postgres/MySQL portierbar, sobald gebraucht. ### Warum JWT-Cookie statt NextAuth? Für einen Admin-User + zwei Gastgeber ist NextAuth Overkill. `jose` liefert signierte Cookies in ~80 Zeilen Code, die Middleware-kompatibel (Edge) funktionieren. ### Warum `Block.source` trotz MVP? Damit der spätere iCal-Importer einfach zusätzliche Einträge mit `source: "airbnb"` / `source: "booking"` schreiben kann — ohne Schema-Änderung. ### Honeypot statt Captcha Das Anfrageformular hat ein verstecktes `website`-Feld als Bot-Falle. Für den MVP ausreichend; falls Spam zunimmt, lässt sich hCaptcha o. ä. ergänzen. ## Erweiterungspfad Der Code ist bewusst so strukturiert, dass die folgenden Features **additiv** eingebaut werden können — ohne Refactoring: ### iCal-Sync (Airbnb / Booking.com) - Cron-Job (z. B. via Vercel Cron oder externer Scheduler), der pro Wohnung eine `ics_import_url` lädt, parst und Blocks mit `source: "airbnb"` / `source: "booking"` anlegt. Schema muss ggf. um `Apartment.icalUrls` erweitert werden. - Umgekehrt: GET-Endpoint `/api/ical/[slug].ics`, der alle Blocks als ICS ausliefert, damit Airbnb/Booking wiederum eure Belegung sehen. ### Direktbuchung - Stripe/Mollie-Integration im `/api/bookings/route.ts`. - `Booking`-Tabelle einführen; erfolgreiche Zahlungen legen automatisch einen `Block` mit `source: "direct"` an. ### Mehr Inhalte - Tagespreise / Saisonpreise (neues `PriceRule`-Modell). - Blog/Storys (neues `Post`-Modell + `/journal`-Route). ### Bildupload - Aktuell pflegt der Editor Bilder als URLs. Für echtes Hochladen: S3/R2/Cloudinary-Adapter in `lib/storage.ts` und Drag-and-Drop-Komponente im `ApartmentEditor`. ## DSGVO-Notizen - Keine Marketing-Cookies, kein Tracking. - Nur technisch notwendige Session-Cookies (httpOnly, SameSite=Lax, Secure in Prod). - Anfragen werden mit ausdrücklicher Einwilligung verarbeitet (Checkbox im Formular). - Datenschutz- und Impressums-Seite als Platzhalter angelegt — **vor Go-Live rechtlich prüfen lassen**. ## Lizenz Privat / intern. Anpassen wie benötigt.