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

npm install

2. Environment vorbereiten

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

npm run setup

Das führt die Prisma-Migration aus und legt die zwei Beispiel-Wohnungen samt Admin-Account an.

4. Dev-Server

npm run dev

http://localhost:3000
→ Admin: 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.

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.

Description
No description provided
Readme 2.7 MiB
Languages
TypeScript 96%
CSS 2.4%
Dockerfile 1.2%
JavaScript 0.4%