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 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
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
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 iProductutan 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. Undviker float-drift.
Produktion – deploy med Docker
# 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:
- Ta bort
ports:idocker-compose.yml - Anslut containern till proxyns Docker-nätverk med
networks:i compose-filen - 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).
# 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 buildgrönt,prisma db pushskapar schemat, seed körs,GET /sv|/en|/admin/login200,POST /api/bookingsvaliderar och skapar bokning.
- Inga migrationer — vi kör
prisma db pushistället förprisma migrate dev. Bra för MVP, men byt tillmigrate devnär schemat är stabilt så historiken finns versionerad. - Mailjet-mall är inline HTML i 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
CANCELLEDmen 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.
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.