Files
boka-gasol247/README.md
2026-05-22 10:50:48 +02:00

201 lines
9.6 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.

# boka.gasol247
Beställningsplattform för gasoltuber till ett enskilt scoutläger (Jamboree).
Riktar sig till organisationer (scoutkårer, föreningar) som beställer för leverans/upphämtning
på plats. Bokningen genererar ett faktureringsunderlag som administratören exporterar
till CSV — själva faktureringen sker utanför systemet.
Tekniskt frikopplad från [gasol247.com](https://www.gasol247.com/) men ärver dess
färgspråk. Tänkt att deployas på egen subdomän.
## Funktioner i MVP
- **Publikt bokningsformulär** med fyra steg: produkter → uppgifter → upphämtning → granska
- **Språkstöd**: svenska (default) och engelska, växlas i header
- **Organisationsbeställning** med Luhn-validering av svenskt organisationsnummer
- **Upphämtningstider** (slots) — kunden väljer tid, kapacitet per slot
- **Bekräftelse-mejl** via Mailjet med responsiv HTML-template (mobil + desktop)
- **Admin-panel** bakom lösen: lista bokningar, filter, status, detaljvy, ändra status
- **CSV-export** av faktureringsunderlag (Excel-vänlig, semikolon + UTF-8 BOM)
- **Prissnapshot**: priser & namn fryses i bokningen när den skapas
- **Bokningsnummer** i formatet `JAM-YY-XXXX` (läsbart över telefon)
## Teknisk stack
| Lager | Val | Varför |
|---|---|---|
| Frontend + Backend | Next.js 15 (App Router) | En process, enkel deploy |
| Språk | TypeScript | Typsäkerhet över hela kodbasen |
| Styling | Tailwind CSS | Snabb iteration, små bundles |
| Databas | SQLite via Prisma 5 | En fil, ingen separat container för MVP |
| Auth | Auth.js v5 (NextAuth) Credentials + bcrypt | Lokal admin-tabell, ingen extern beroende |
| i18n | next-intl | Server- + client-translations, prefix-routing |
| Mejl | Mailjet (`node-mailjet`) | Bra leverans, designat HTML-mejl |
| Deploy | Docker (Next standalone) | Körs bakom egen reverse proxy |
## Snabbstart utveckling
```bash
cp .env.example .env
# Generera AUTH_SECRET:
# openssl rand -base64 32
npm install
npx prisma db push # skapa SQLite-schemat
npm run db:seed # admin + produkter + upphämtningstider
npm run dev # http://localhost:3000
```
Default-admin (från `.env`): `admin@example.com` / `change-me` — ändra direkt i `.env` innan deploy.
### Användbara kommandon
```bash
npm run dev # dev-server med hot reload
npm run build # produktionsbygge
npm run start # produktionsserver (kräver byggd app)
npm run db:studio # Prisma Studio — visuell DB-editor
npm run db:seed # idempotent seed (kör om för uppdaterade priser)
npx prisma db push # synka schemat till SQLite efter ändring i prisma/schema.prisma
```
### Projektstruktur
```
boka.gasol247/
├── prisma/
│ ├── schema.prisma Admin, Product, PickupSlot, Booking, BookingItem
│ └── seed.ts Första admin + P6/P11/P19 + exempel-slots
├── src/
│ ├── app/
│ │ ├── [locale]/ sv default, /en/* för engelska
│ │ │ ├── page.tsx Publikt bokningsformulär
│ │ │ ├── booking/[number]/ Bekräftelsesida
│ │ │ └── admin/ Login + dashboard + detaljvy
│ │ └── api/
│ │ ├── bookings/ POST: skapa bokning + skicka mejl
│ │ ├── admin/export/ GET: CSV-faktureringsunderlag
│ │ └── auth/[...nextauth]/
│ ├── components/ BookingForm, Header, LanguageSwitcher, StatusBadge
│ ├── lib/ prisma, money, mailjet, bookingNumber, orgNumber, validation
│ ├── i18n/ next-intl routing + request config
│ ├── auth.ts Auth.js v5 setup
│ └── middleware.ts Locale-routing
├── messages/{sv,en}.json Översättningar (UI + e-post)
├── docker/entrypoint.sh Kör prisma db push + ev. seed vid start
├── Dockerfile Multi-stage, Next standalone, non-root
├── docker-compose.yml Volym för SQLite
└── .env.example
```
## Datamodell snabb översikt
- **Admin** — `email`, `passwordHash` (bcrypt), `name`. Krediter för login.
- **Product** — `sku`, lokaliserade namn/beskrivningar, `priceOre` (öre, inte SEK), `vatBp` (basis-punkter), `active`, `sortOrder`.
- **PickupSlot** — `labelSv`/`labelEn`, `startsAt`/`endsAt`, `capacity`, `active`.
- **Booking** — bokningsnr, status, kontaktuppgifter, organisationsuppgifter, fakturaadress, snapshot av subtotal/vat/total, locale, valfri `pickupSlotId`.
- **BookingItem** — snapshot av `sku`/`nameSv`/`nameEn`/`unitPriceOre`/`vatBp`/`quantity`/`lineTotalOre`. Frysta vid bokningstillfället så priser kan ändras i `Product` utan att gamla bokningar påverkas.
### Bokningsstatus
`PENDING``CONFIRMED``DELIVERED``INVOICED` (eller `CANCELLED` när som helst). Lagras som sträng eftersom SQLite inte stöder enums.
### Priser i öre
All prismatte sker i heltal (öre). Konvertering till SEK vid presentation eller export via [`src/lib/money.ts`](src/lib/money.ts). Undviker float-drift.
## Produktion deploy med Docker
```bash
# 1. Skapa .env för produktion (på servern)
cp .env.example .env
# - Sätt AUTH_SECRET (openssl rand -base64 32)
# - Sätt MAILJET_API_KEY / SECRET / MAIL_FROM_EMAIL
# - Sätt AUTH_URL och NEXT_PUBLIC_SITE_URL till din domän (https://...)
# - Sätt SEED_ADMIN_EMAIL / PASSWORD för första admin
# 2. Bygg och starta
docker compose build
RUN_SEED=true docker compose up -d # första gången
# Vid omstarter: docker compose up -d (RUN_SEED är false som default)
# 3. Logga in
# https://din-domän/admin/login
```
### Bakom reverse proxy
`docker-compose.yml` exponerar port 3000 endast på `127.0.0.1` som standard. Om du
kör Caddy/Traefik/nginx som separat stack:
1. Ta bort `ports:` i `docker-compose.yml`
2. Anslut containern till proxyns Docker-nätverk med `networks:` i compose-filen
3. Pekande proxy → `boka-gasol247:3000`
Sätt `AUTH_TRUST_HOST=true` (redan satt) så Auth.js godkänner proxyns host-header.
### SQLite-volym & backup
Databasen ligger i Docker-volymen `app-data` (monterad som `/app/data/app.db`).
```bash
# Backup
docker run --rm \
-v boka.gasol247_app-data:/data \
-v "$PWD":/out alpine \
cp /data/app.db /out/app.backup-$(date +%F).db
# Restore (stoppa appen först)
docker compose down
docker run --rm \
-v boka.gasol247_app-data:/data \
-v "$PWD":/in alpine \
cp /in/app.backup-2026-05-22.db /data/app.db
docker compose up -d
```
Kör backup minst dagligen vid skarp användning.
## Konfiguration miljövariabler
| Variabel | Krav | Beskrivning |
|---|---|---|
| `DATABASE_URL` | ✅ | T.ex. `file:./data/app.db` lokalt, `file:/app/data/app.db` i Docker |
| `AUTH_SECRET` | ✅ | `openssl rand -base64 32` |
| `AUTH_URL` | ✅ | T.ex. `https://boka.gasol247.com` i produktion |
| `AUTH_TRUST_HOST` | ✅ | `true` bakom proxy |
| `MAILJET_API_KEY` | ⚠️ | Krävs för att skicka mejl. Utan: bokning skapas, mejl skippas |
| `MAILJET_API_SECRET` | ⚠️ | Som ovan |
| `MAIL_FROM_EMAIL` | ⚠️ | Verifierad avsändaradress i Mailjet |
| `MAIL_FROM_NAME` | | Visningsnamn |
| `NEXT_PUBLIC_SITE_URL` | | För absoluta länkar i framtida e-post |
| `NEXT_PUBLIC_EVENT_NAME` | | Visas i header och e-postmall |
| `SEED_ADMIN_EMAIL/PASSWORD/NAME` | | Bara för `npm run db:seed` / `RUN_SEED=true` |
## Aktuell status och kända begränsningar
> **Verifierat 2026-05-22:** `next build` grönt, `prisma db push` skapar schemat, seed körs, `GET /sv|/en|/admin/login` 200, `POST /api/bookings` validerar och skapar bokning.
- **Inga migrationer** — vi kör `prisma db push` istället för `prisma migrate dev`. Bra för MVP, men byt till `migrate dev` när schemat är stabilt så historiken finns versionerad.
- **Mailjet-mall är inline HTML** i [src/lib/mailjet.ts](src/lib/mailjet.ts). Funkar och är responsiv, men för att kunna ändra design utan deploy bör vi flytta till Mailjet Template-ID.
- **Ingen lagerbegränsning** — boka.gasol247 förhindrar inte överbokning av en produkt. Kapacitet hanteras bara per upphämtnings-slot.
- **Ingen kund-avbokning** — admin kan markera `CANCELLED` men kunden själv har ingen länk i mejlet.
- **Admin-tabellen finns** men det finns inget UI för att skapa nya admins. Lägg till manuellt via Prisma Studio eller kör seed med andra `SEED_ADMIN_*`-variabler.
- **Produkter & slots redigeras genom seed-filen** — inget admin-UI för det än.
- **Ingen rate-limit eller CAPTCHA** på publika `POST /api/bookings`. Lägg till om eventet annonseras brett.
- **Prisma VS Code-tillägget visar Prisma 7-varningar** för `datasource.url` — kosmetiskt; projektet kör Prisma 5.22 där det fortfarande är obligatoriskt.
Backlog och prioriterade förbättringar finns i [BACKLOG.md](./BACKLOG.md).
## Felsökning
**"Cannot find module './globals.css'"** — Cleared via `src/types/css.d.ts`. Om felet kommer tillbaka, kontrollera att filen finns kvar.
**Mejl skickas inte** — Kolla logs: `docker compose logs app | grep mailjet`. Vanliga orsaker: avsändaradress inte verifierad i Mailjet, fel API-key, eller `MAILJET_API_KEY` saknas (då skippas sändningen tyst och loggas).
**Bokning skapas inte** — Kolla validation: `POST /api/bookings` returnerar `{error: "validation", issues: {...}}` med fältnivå-detaljer. Org.nummer måste passera Luhn-check.
**Admin kan inte logga in** — Kör `npm run db:seed` igen, eller skapa admin manuellt via Prisma Studio. Lösenord bcrypt-hashas i seed-scriptet.
## Licens
Privat — för intern användning på Jamboree-eventet.