docs: design-document v1.8, dev-docs restructure, VitePress user docs scaffold, backlog update

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 11:16:43 +02:00
parent 03ca1a50a7
commit 83437378c8
53 changed files with 3924 additions and 114 deletions

165
dev-docs/API.md Normal file
View File

@@ -0,0 +1,165 @@
# Crewli API Contract
Base path: `/api/v1/`
Auth: Bearer token (Sanctum)
## Auth
- `POST /auth/login`
- `POST /auth/logout`
- `GET /auth/me`
## Organisations
- `GET /organisations` — list (super admin)
- `POST /organisations` — create
- `GET /organisations/{org}` — show
- `PUT /organisations/{org}` — update
- `GET /organisations/{org}/members` — members
- `POST /organisations/{org}/invite` — invite user
## Events
- `GET /organisations/{org}/events` — list (top-level only by default)
- `GET /organisations/{org}/events?include_children=true` — include sub-events nested in response
- `GET /organisations/{org}/events?type=festival` — filter by event_type (festival|series|event)
- `POST /organisations/{org}/events` — create (supports `parent_event_id` for sub-events)
- `GET /organisations/{org}/events/{event}` — detail (includes children and parent if loaded)
- `PUT /organisations/{org}/events/{event}` — update (does NOT accept `status` — use transition endpoint)
- `POST /organisations/{org}/events/{event}/transition` — change event status via state machine (see below)
- `GET /organisations/{org}/events/{event}/children` — list sub-events of a festival/series
### Event Status Transitions
`POST /organisations/{org}/events/{event}/transition`
Body: `{ "status": "published" }`
Enforces a state machine: only valid forward (and select backward) transitions are allowed.
Returns 422 with `errors`, `current_status`, `requested_status`, and `allowed_transitions` when the transition is invalid or prerequisites are missing.
**Prerequisites checked:**
- `→ published`: name, start_date, end_date required
- `→ registration_open`: at least one time slot and one section required
**Festival cascade:** Transitioning a festival parent to `showday`, `teardown`, or `closed` automatically cascades to all children in an earlier status.
**EventResource** includes `allowed_transitions` (array of valid next statuses) so the frontend knows which buttons to show.
## Crowd Types
- `GET /organisations/{org}/crowd-types`
- `POST /organisations/{org}/crowd-types`
- `PUT /organisations/{org}/crowd-types/{type}`
- `DELETE /organisations/{org}/crowd-types/{type}`
## Companies
- `GET /organisations/{org}/companies`
- `POST /organisations/{org}/companies`
- `PUT /organisations/{org}/companies/{company}`
- `DELETE /organisations/{org}/companies/{company}`
## Section Categories
- `GET /organisations/{org}/section-categories` — distinct categories used across the organisation's events (for autocomplete). Returns `{ "data": ["Bar", "Podium", ...] }`
## Festival Sections
- `GET /events/{event}/sections`
- `POST /events/{event}/sections`
- `PUT /events/{event}/sections/{section}`
- `DELETE /events/{event}/sections/{section}`
- `POST /events/{event}/sections/reorder`
> **Festival context:** `{event}` can be a festival parent or a sub-event.
> On a festival parent, sections are for operational planning (build-up, teardown).
> For sub-events, `GET` automatically includes `cross_event` sections from the parent festival.
> Shifts on cross_event sections must use the **parent festival's event_id** in API calls,
> since the section's `event_id` points to the parent.
## Time Slots
- `GET /events/{event}/time-slots`
- `POST /events/{event}/time-slots`
- `PUT /events/{event}/time-slots/{timeSlot}`
- `DELETE /events/{event}/time-slots/{timeSlot}`
> **Festival context:** `{event}` can be a festival parent or a sub-event.
> Festival-level time slots (operational: build-up, teardown, transitions) are separate
> from sub-event time slots (program-specific).
>
> `GET /events/{event}/time-slots` returns only the specified event's own time slots by
> default. For sub-events, pass `?include_parent=true` to also include the parent festival's
> time slots — each time slot is marked with a `source` field (`sub_event` or `festival`)
> and includes `event_name` for display grouping. This parameter has no effect on festivals
> or flat events.
>
> Shifts on sub-event sections may reference parent festival time slots (e.g. for build-up
> shifts). The `time_slot_id` validation accepts time slots from the sub-event itself or
> its parent festival.
## Shifts
- `GET /events/{event}/sections/{section}/shifts`
- `POST /events/{event}/sections/{section}/shifts`
- `PUT /events/{event}/sections/{section}/shifts/{shift}`
- `DELETE /events/{event}/sections/{section}/shifts/{shift}`
- `POST /events/{event}/sections/{section}/shifts/{shift}/assign`
- `POST /events/{event}/sections/{section}/shifts/{shift}/claim`
> **Festival context:** When managing shifts on a `cross_event` section, the `{event}`
> in the URL must be the parent festival's ID (matching `section.event_id`), not the
> sub-event's ID.
## Persons
- `GET /events/{event}/persons`
- `POST /events/{event}/persons`
- `GET /events/{event}/persons/{person}`
- `PUT /events/{event}/persons/{person}`
- `POST /events/{event}/persons/{person}/approve`
- `DELETE /events/{event}/persons/{person}`
## Crowd Lists
- `GET /events/{event}/crowd-lists`
- `POST /events/{event}/crowd-lists`
- `PUT /events/{event}/crowd-lists/{list}`
- `DELETE /events/{event}/crowd-lists/{list}`
- `POST /events/{event}/crowd-lists/{list}/persons`
- `DELETE /events/{event}/crowd-lists/{list}/persons/{person}`
## Locations
- `GET /events/{event}/locations`
- `POST /events/{event}/locations`
- `PUT /events/{event}/locations/{location}`
- `DELETE /events/{event}/locations/{location}`
## Person Tags (Organisation Settings)
- `GET /organisations/{org}/person-tags` — list active tags (ordered)
- `POST /organisations/{org}/person-tags` — create tag
- `PUT /organisations/{org}/person-tags/{tag}` — update tag
- `DELETE /organisations/{org}/person-tags/{tag}` — deactivate tag (soft: sets `is_active = false`)
- `GET /organisations/{org}/person-tag-categories` — distinct categories for autocomplete
## User Tag Assignments
- `GET /organisations/{org}/users/{user}/tags` — list all tags for user in organisation
- `POST /organisations/{org}/users/{user}/tags` — assign a tag
- `DELETE /organisations/{org}/users/{user}/tags/{tagAssignment}` — remove assignment
- `PUT /organisations/{org}/users/{user}/tags/sync` — sync tags by source
> **Sync endpoint:** Replaces tags of the specified `source` only.
> Body: `{ "tag_ids": ["ulid1", "ulid2"], "source": "self_reported" }`
> Removes `self_reported` tags not in the list, adds new ones, leaves `organiser_assigned` untouched (and vice versa).
### Person list tag filtering
- `GET /events/{event}/persons?tag={person_tag_id}` — filter persons by single tag
- `GET /events/{event}/persons?tags=ulid1,ulid2` — filter persons by multiple tags (AND logic: must have all)
_(Extend this contract per module as endpoints are implemented.)_

485
dev-docs/BACKLOG.md Normal file
View File

@@ -0,0 +1,485 @@
# Crewli — Product Backlog
> Gedocumenteerde wensen en features die bewust zijn uitgesteld.
> Bijgewerkt: April 2026
>
> **Gebruik:** Voeg nieuwe items toe als ze tijdens development ontstaan.
> Geef elk item een prioriteit en fase zodra je het gaat oppakken.
---
## Fase 3 — Geplande features
### ARCH-01 — Recurrence / Terugkerende events
**Aanleiding:** Schaatsbaan use case — 8 weken, elke za+zo openingsdagen.
**Wat:** Organisator definieert één template sub-event met RRULE.
Platform genereert automatisch alle instanties.
**Details:**
- RRULE formaat (RFC 5545): `FREQ=WEEKLY;BYDAY=SA,SU;UNTIL=20270126`
- `events.recurrence_rule` (string nullable) — al gereserveerd in schema
- `events.recurrence_exceptions` (JSON) — cancelled + modified dates
- UI: "Genereer openingsdagen" wizard
- Aanpassen van één instantie raakt template niet
- "Alleen deze dag" / "Alle volgende dagen" / "Alle dagen" (Google Calendar patroon)
**Schema:** Kolommen al aanwezig in v1.7. Alleen generator-logica ontbreekt.
---
### ARCH-02 — Min/max shifts per vrijwilliger per serie
**Aanleiding:** Schaatsbaan — eerlijke verdeling, minimum commitment.
**Wat:** Per festival/serie instelbaar minimum en maximum aantal shifts
dat een vrijwilliger kan claimen.
**Details:**
- `festivals.min_shifts_per_volunteer` (int nullable)
- `festivals.max_shifts_per_volunteer` (int nullable)
- Portal toont voortgang: "Jij hebt 2 van minimaal 4 shifts geclaimd"
- Bij bereiken maximum: verdere claims geblokkeerd
**Afhankelijk van:** ARCH-01 (recurrence), Portal self-service
---
### ARCH-03 — Sectie templates / kopiëren van vorig event
**Aanleiding:** Organisatoren die elk jaar dezelfde secties en shifts opzetten.
**Wat:** "Kopieer secties van vorig festival" functie in de UI.
Kopieert festival_sections + shifts structuur (zonder toewijzingen).
**Details:**
- UI: dropdown "Kopieer structuur van..." bij aanmaken festival
- Optie: kopieer alleen secties / secties + shifts / alles
- Tijden worden proportioneel aangepast aan nieuwe datums
**Prioriteit:** Hoog — bespaart veel handmatig werk bij terugkerende festivals
---
### ARCH-04 — Cross-festival conflictdetectie
**Aanleiding:** Vrijwilliger die bij twee festivals van dezelfde organisatie
op dezelfde dag ingepland staat.
**Wat:** Waarschuwing (geen blokkade) als iemand al actief is op een
ander festival van dezelfde organisatie op dezelfde datum.
**Details:**
- Soft check — waarschuwing tonen, niet blokkeren
- Relevant bij organisaties met meerdere festivals tegelijk
- Query: `shift_assignments` cross-festival op person_id + datum
---
### ARCH-05 — Shift fairness / prioriteitswachtrij
**Aanleiding:** Populaire shifts worden direct volgeboekt door snelle vrijwilligers.
**Wat:** Optionele wachtrij-modus waarbij het systeem eerlijk verdeelt
op basis van: reliability score, aantal uren al ingepland, aanmeldvolgorde.
**Details:**
- `shifts.assignment_mode` (enum: first_come | fair_queue | manual)
- Fair queue: systeem wijst toe op basis van algoritme
- Organisator keurt resultaat goed voor publicatie
**Prioriteit:** Middel — nice-to-have voor grote festivals
---
### ARCH-06 — Locatie-gebaseerd shift-overzicht
Cross sub-event filter op location_id. Toont alle shifts op een fysieke locatie
ongeacht programmaonderdeel.
**Schema:** `locations` tabel en `shifts.location_id` bestaan al.
**Prioriteit:** Laag
---
### ARCH-07 — Accreditatie-templates per sectie/dag combinatie
MUST-HAVE bij accreditatie build. Templates worden primaire toewijzingsmethode.
Per crowd_type + sectie + dag → automatisch voorgestelde accreditatie-items.
Handmatige per-persoon toewijzing is de uitzondering, niet de norm.
**Schema:** Nieuwe tabel `accreditation_templates` nodig.
**Prioriteit:** Hoog — direct meebouwen bij accreditatie-module
---
## Fase 3 — Communicatie & Notificaties
### COMM-01 — Real-time WebSocket notificaties
**Aanleiding:** Differentiator — geen van de concurrenten heeft dit.
**Wat:** Push notificaties via Laravel Echo + Soketi voor:
- Nieuwe vrijwilliger aanmelding
- Shift geclaimd
- Uitnodiging geaccepteerd
- Shift niet gevuld (waarschuwing)
- No-show alert op show-dag
**Tech:** Laravel Echo + Soketi (zelf-gehoste WebSocket server)
**Frontend:** Notificatie bell in topbar activeren
---
### COMM-02 — Topbar volledig activeren
**Aanleiding:** Vuexy topbar staat er maar is niet aangesloten op Crewli.
**Wat:**
- Zoekbalk (CTRL+K) aansluiten op Crewli-entiteiten
(personen, events, secties zoeken)
- Notificatie bell koppelen aan COMM-01
- App switcher: Organizer / Admin / Portal wisselen
- User avatar: gekoppeld aan ingelogde gebruiker (deels al gedaan)
**Prioriteit:** Middel — werkt zonder maar verbetert UX significant
---
### COMM-03 — Globale zoekfunctie (cmd+K)
**Aanleiding:** Differentiator — cross-entiteit zoeken.
**Wat:** Modal zoekbalk die zoekt over:
personen, events, artiesten, secties, shifts
**Tech:** Meilisearch of database full-text search
**Prioriteit:** Laag — Fase 4
---
### COMM-04 — SMS + WhatsApp campagnes via Zender
**Aanleiding:** WeezCrew heeft dit als sterk punt.
**Wat:** Bulk communicatie via Zender (zelf-gehoste SMS/WhatsApp gateway)
- Normal urgency → email
- Urgent → WhatsApp
- Emergency → SMS + WhatsApp parallel
**Tech:** ZenderService (al gedocumenteerd in dev guide)
**Afhankelijk van:** Communicatie module backend
---
## Fase 3 — Show Day & Operationeel
### OPS-01 — Mission Control
**Aanleiding:** In2Event's sterkste feature.
**Wat:** Real-time operationele hub op show-dag:
- Live check-in overzicht per sectie
- Artiest handling (aankomst, soundcheck, performance status)
- No-show alerts met automatische opvolging
- Inventaris uitgifte (portofoons, hesjes)
**Prioriteit:** Hoog voor show-dag gebruik
---
### OPS-02 — No-show automatisering
**Aanleiding:** 30-minuten alert voor niet-ingecheckte vrijwilligers.
**Wat:** Automatische WhatsApp/SMS via Zender als vrijwilliger
niet is ingecheckt 30 min na shift-starttijd.
**Schema:** `show_day_absence_alerts` al aanwezig ✅
**Afhankelijk van:** COMM-04 (Zender), OPS-01 (Mission Control)
---
### OPS-03 — Allocatiesheet PDF generator
**Aanleiding:** WeezCrew heeft branded PDF per crew.
**Wat:** Gepersonaliseerde PDF per vrijwilliger/crew:
taakbeschrijving, tijden, locatie, QR-code voor check-in.
**Tech:** DomPDF (al geïnstalleerd)
**Prioriteit:** Middel
---
### OPS-04 — Scanner infrastructuur
**Aanleiding:** QR check-in op locatie.
**Wat:** Scanstations configureren, koppelen aan hardware.
`scanners` tabel al aanwezig in schema ✅
**Prioriteit:** Laag — Fase 4
---
## Fase 3 — Vrijwilligers & Portal
### VOL-01 — apps/portal/ vrijwilliger self-service
**Aanleiding:** Vrijwilligers moeten zichzelf kunnen aanmelden en
shifts claimen zonder toegang tot de Organizer app.
**Wat:**
- Publiek registratieformulier (multi-step)
- Login portal voor vrijwilligers
- Beschikbaarheid opgeven (time slots kiezen)
- My Shifts overzicht
- Shift claimen met conflictdetectie
- "Ik kan toch niet komen" workflow
**Afhankelijk van:** Sections + Shifts backend (al klaar ✅)
---
### VOL-02 — Vrijwilliger paspoort + reliability score
**Aanleiding:** Platform-breed profiel dat accumuleert over jaren.
**Wat:**
- Festival-paspoort: visuele tijdlijn van deelgenomen festivals
- Reliability score (0.0-5.0): berekend via scheduled job
- Coordinator-beoordeling per festival (intern, nooit zichtbaar)
- "Would reinvite" indicator bij heruitnodiging
**Schema:** `volunteer_profiles`, `volunteer_festival_history` al aanwezig ✅
---
### VOL-03 — Post-festival evaluatie + retrospectief
**Aanleiding:** Automatische feedback na het festival.
**Wat:**
- 24u na laatste shift: evaluatiemail naar vrijwilligers
- Max 5 vragen (beleving, shift kwaliteit, terugkomen?)
- Gegenereerd retrospectief rapport per festival
- Coordinator-beoordeling parallel (intern)
**Schema:** `post_festival_evaluations`, `festival_retrospectives` al aanwezig ✅
---
### VOL-04 — Shift swap workflow (portal)
**Aanleiding:** Vrijwilliger wil shift ruilen met collega.
**Wat:**
- Open swap: iedereen mag reageren
- Persoonlijke swap: specifieke collega vragen
- Na akkoord beide: coordinator bevestigt (of auto-approve)
- Wachtlijst: bij uitval automatisch aanschrijven
**Schema:** `shift_swap_requests`, `shift_absences`, `shift_waitlist` al aanwezig ✅
---
## Fase 3 — Artiesten & Advancing
### ART-01 — Artist advancing portal (apps/portal/)
**Aanleiding:** Crescat's sterkste feature.
**Wat:**
- Sectie-gebaseerd advance portal via gesignde URL
- Per sectie onafhankelijk submitbaar (Guest List, Contacts, Production)
- Milestone pipeline: Offer In → Advance Received
- Per-artiest zichtbaarheidscontrole van advance secties
- Submission diff tracking (created/updated/untouched/deleted)
**Schema:** `advance_sections`, `advance_submissions` al aanwezig ✅
---
### ART-02 — Timetable (stage + drag-drop)
**Aanleiding:** FullCalendar timeline view voor podia-planning.
**Wat:**
- Timeline view per podium
- Drag-and-drop performances
- B2B detectie (twee artiesten op zelfde podium zelfde tijd)
**Tech:** FullCalendar (al in stack ✅)
---
## Fase 3 — Formulieren & Leveranciers
### FORM-01 — Formulierbouwer
**Aanleiding:** WeezCrew heeft een krachtige drag-sorteerbare builder.
**Wat:**
- Drag-sorteerbaar, conditionele logica
- Live preview
- Iframe embed voor externe websites
- Configureerbare velden per crowd type
**Schema:** `public_forms` al aanwezig ✅
---
### SUP-01 — Leveranciersportal + productieverzoeken
**Aanleiding:** Leveranciers moeten productie-informatie kunnen indienen.
**Wat:**
- Token-gebaseerde portal toegang (geen account nodig)
- Productieverzoek indienen (mensen, tech, stroom, voertuigen)
- Crowd list indienen voor hun crew
**Schema:** `production_requests`, `material_requests` al aanwezig ✅
---
## Fase 4 — Differentiators
### DIFF-01 — Cross-event crew pool + reliability score
**Aanleiding:** Vrijwilligers hergebruiken over events van dezelfde organisatie.
**Wat:** Eén klik heruitnodiging op basis van vorig jaar.
Reliability score zichtbaar naast naam in de lijst.
---
### DIFF-02 — Crew PWA (mobiel)
**Aanleiding:** On-site zelfservice voor crew op hun telefoon.
**Wat:** Progressive Web App voor:
shifts bekijken, briefing lezen, clock-in, push notificaties.
---
### DIFF-03 — Publieke REST API + webhooks
**Aanleiding:** Enterprise integraties.
**Wat:** Gedocumenteerde publieke API + webhook systeem
voor third-party integraties (ticketing, HR, etc.)
---
### DIFF-04 — CO2 / Duurzaamheidsrapportage
**Aanleiding:** Toenemende focus op duurzame events.
**Wat:** Emissieberekeningen op basis van transport en energieverbruik.
**Status:** Expliciet out of scope voor v1.x
---
## Apps & Platforms
### APPS-01 — apps/admin/ volledig bouwen
**Aanleiding:** Super Admin panel voor platform-beheer.
**Wat:**
- Alle organisaties beheren
- Billing status wijzigen
- Platform-gebruikers beheren
- Usage statistieken
---
### APPS-02 — OrganisationSwitcher ingeklapte staat fix
**Aanleiding:** Flikkering/hover-bug bij ingeklapte sidebar.
**Wat:** Correcte weergave en animatie in ingeklapte staat.
**Prioriteit:** Low — cosmetisch, werkt functioneel wel
---
## Technische schuld
### TECH-01 — Bestaande tests bijwerken na festival/event refactor
**Aanleiding:** Na toevoegen parent_event_id worden bestaande tests
mogelijk fragiel door gewijzigde factory-setup.
**Wat:** Alle Feature tests reviewen en bijwerken waar nodig.
---
### TECH-02 — scopeForFestival helper op Event model
**Aanleiding:** Queries die door parent/child heen moeten werken.
**Wat:** `Event::scopeWithChildren()` en `Event::scopeForFestival()`
helper scopes zodat queries automatisch parent + children bevatten.
---
### TECH-03 — DevSeeder uitbreiden met festival-structuur
**Aanleiding:** Na festival/event refactor heeft de DevSeeder
realistische testdata nodig met parent/child events.
**Wat:** DevSeeder aanpassen met:
- Test festival (parent)
- 2-3 sub-events (children)
- Personen op festival-niveau
---
### ~~TECH-04 — EventController.store() redundante ternary~~ ✅ OPGELOST
---
## Opgeloste items (april 2026)
De volgende items zijn geïmplementeerd en afgerond:
- ~~TECH-04: EventController.store() redundante ternary~~ ✅
- ~~Auth race condition (CTRL+R fix)~~ ✅
- ~~Section edit dialog bug~~ ✅
- ~~Time slot duplicate button~~ ✅
- ~~Browser autocomplete disabled op dialog form fields~~ ✅
- ~~Category + icon fields op festival_sections~~ ✅
- ~~IconPicker component~~ ✅
- ~~Crowd Types beheer-UI~~ ✅
- ~~Companies CRUD~~ ✅
- ~~Person tags backend (person_tags + user_organisation_tags)~~ ✅
- ~~Event status state machine (dedicated transition endpoint, prerequisites, festival cascade)~~ ✅
- ~~Festival tab-navigatie (uniform tabs, Programmaonderdelen tab)~~ ✅
- ~~SectionsShiftsPanel extractie als herbruikbaar component~~ ✅
- ~~Cross-event section auto-redirect~~ ✅
---
## Nieuwe backlog items
### ARCH-06 — Locatie-gebaseerd shift-overzicht
Cross sub-event filter op location_id. Toont alle shifts op een fysieke locatie
ongeacht programmaonderdeel.
**Prioriteit:** Laag
---
### ARCH-07 — Accreditatie-templates per sectie/dag combinatie
MUST-HAVE bij accreditatie build. Templates worden primaire toewijzingsmethode.
Per crowd_type + sectie + dag → automatisch voorgestelde accreditatie-items.
**Prioriteit:** Hoog — direct meebouwen bij accreditatie-module
---
### ARCH-08 — Recurrence voor time slots
Herhalingsfunctie: "genereer 5 time slots in één keer" voor opbouwdagen etc.
**Prioriteit:** Middel
---
### ART-03 — Artist profile met cross-event rider defaults
Organisatie-niveau artiest-profiel dat rider-defaults, contacten en interne
notities opslaat over events heen. "Importeer van vorig jaar" functie.
**Prioriteit:** Laag
---
### UX-01 — Festival setup checklist / onboarding wizard
Checklist widget op festival dashboard die door de configuratiestappen leidt.
Items worden groen als ze zijn afgerond.
**Prioriteit:** Middel
---
### UX-02 — Aandachtsmatrix dashboard
Dashboard widget: hoeveel personen approved maar zonder shift? Hoeveel
shift-claims wachten op goedkeuring? Hoeveel pending identity matches?
**Prioriteit:** Middel
---
### UX-03 — Personen-tab op sub-event niveau
Gefilterde view: alleen personen met shifts in dit programmaonderdeel.
Met link "Bekijk alle personen op festival-niveau".
**Prioriteit:** Middel
---
_Laatste update: April 2026_
_Voeg nieuwe items toe met prefix: ARCH-, COMM-, OPS-, VOL-, ART-, FORM-, SUP-, DIFF-, APPS-, TECH-, UX-_

1491
dev-docs/SCHEMA.md Normal file

File diff suppressed because it is too large Load Diff

286
dev-docs/SETUP.md Normal file
View File

@@ -0,0 +1,286 @@
# Crewli - Setup Guide
This guide walks you through setting up the Crewli project from scratch.
## Cursor AI Configuration
The project includes comprehensive AI instructions:
```
.cursor/
├── instructions.md # Quick start and common prompts
├── ARCHITECTURE.md # System design and database schema
└── rules/
├── 001_workspace.mdc # Project structure and conventions
├── 100_laravel.mdc # Laravel API patterns
├── 101_vue.mdc # Vue + Vuexy patterns
└── 200_testing.mdc # Testing strategies
```
**Read these files first!** They contain everything Cursor needs to generate code correctly.
## Prerequisites
Install these before starting:
### macOS (Homebrew)
```bash
# PHP 8.3
brew install php@8.3
brew link php@8.3
# Composer
brew install composer
# Node.js (via fnm)
brew install fnm
fnm install 20
fnm use 20
# pnpm
npm install -g pnpm
# Docker Desktop
# Download from: https://www.docker.com/products/docker-desktop/
```
### Verify Installation
```bash
php -v # Should show 8.3.x
composer -V # Should show 2.x
node -v # Should show v20.x
pnpm -v # Should show 8.x or 9.x
docker -v # Should show Docker version
```
---
## Step 1: Start Docker Services
```bash
cd crewli
make services
```
This starts:
- **MySQL 8.0** on port 3306
- **Redis** on port 6379
- **Mailpit** on port 8025 (email testing UI)
---
## Step 2: Create Laravel API
Open the project in Cursor and use this prompt:
```
Create a new Laravel 12 project in the api/ folder.
Requirements:
- Use the command: composer create-project laravel/laravel api
- After creation, install Sanctum: composer require laravel/sanctum
- Configure for API-only (we don't need web routes)
- Set up CORS for localhost:5173, localhost:5174, localhost:5175
- Use MySQL with these credentials:
- Host: 127.0.0.1
- Database: crewli
- Username: crewli
- Password: secret
Follow the conventions in .cursor/rules for code style.
```
### Manual Alternative
```bash
cd crewli
composer create-project laravel/laravel api
cd api
composer require laravel/sanctum
php artisan install:api
```
Then configure `api/.env`:
```env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=crewli
DB_USERNAME=crewli
DB_PASSWORD=secret
FRONTEND_ADMIN_URL=http://localhost:5173
FRONTEND_APP_URL=http://localhost:5174
FRONTEND_PORTAL_URL=http://localhost:5175
SANCTUM_STATEFUL_DOMAINS=localhost:5173,localhost:5174,localhost:5175
SESSION_DOMAIN=localhost
```
**Production (domain `crewli.app`):** set `APP_URL=https://api.crewli.app`, point `FRONTEND_ADMIN_URL` / `FRONTEND_APP_URL` / `FRONTEND_PORTAL_URL` to `https://admin.crewli.app`, `https://app.crewli.app`, and `https://portal.crewli.app`, and `SANCTUM_STATEFUL_DOMAINS=admin.crewli.app,app.crewli.app,portal.crewli.app` (hostnames only). Each SPA build should use `VITE_API_URL=https://api.crewli.app/api/v1`. Full template: `api/.env.example`. The product uses **`crewli.app`** only; **`crewli.nl`** is for a future public marketing site, not this API or SPAs.
---
## Step 3: Vuexy frontends (this repo)
This monorepo already contains three SPAs under `apps/`:
| Directory | Role | Typical Vuexy source |
|-----------|------|----------------------|
| `apps/admin/` | Super Admin | full-version (TypeScript) |
| `apps/app/` | Organizer (main product) | full-version or customized starter |
| `apps/portal/` | External portal (volunteers, token links) | stripped starter / custom layout |
If you ever need to re-copy from a Vuexy ZIP, use the paths above — not legacy `apps/band` or `apps/customers`.
```bash
# Example only — adjust to your Vuexy download path
cp -r ~/Downloads/vuexy/typescript-version/full-version/* apps/admin/
```
---
## Step 4: Configure SPAs
### Install Dependencies
```bash
cd apps/admin && pnpm install
cd ../app && pnpm install
cd ../portal && pnpm install
```
### Create Environment Files
**apps/admin/.env.local**
```env
VITE_API_URL=http://localhost:8000/api/v1
VITE_APP_NAME="Crewli Admin"
```
**apps/app/.env.local**
```env
VITE_API_URL=http://localhost:8000/api/v1
VITE_APP_NAME="Crewli Organizer"
```
**apps/portal/.env.local**
```env
VITE_API_URL=http://localhost:8000/api/v1
VITE_APP_NAME="Crewli Portal"
```
### Dev server ports
From the repo root, `make admin`, `make app`, and `make portal` start Vite on **5173**, **5174**, and **5175** respectively. If you run `pnpm dev` manually, configure the same ports in each apps `vite.config.ts` under `server.port`.
---
## Step 5: API client in SPAs
`apps/admin/src/lib/api-client.ts`, `apps/app/src/lib/api-client.ts`, and `apps/portal/src/lib/api-client.ts` share the same pattern: `VITE_API_URL` base, Bearer token from the `accessToken` cookie, 401 → clear cookies and redirect to `/login`. Build new composables on `apiClient`; keep Vuexy `useApi` for template demos only.
---
## Step 6: Create database schema
Implement migrations from the canonical schema, not a legacy intranet model:
- **`docs/SCHEMA.md`** — table list, columns, indexes
- **`.cursor/ARCHITECTURE.md`** — overview and relationships
- **`.cursor/rules/103_database.mdc`** — ULIDs, soft deletes, index rules
**Checked-in foundation (this repo):** Laravel defaults (`users`, `cache`, `jobs`) then `2026_04_07_*` migrations: Sanctum tokens → Spatie permission → activity log → `organisations``organisation_user``events``user_invitations``event_user_roles`. New modules should append migrations with a later timestamp in dependency order.
Typical next expansion order from `103_database.mdc`: festival sections, time slots, persons, shifts, …
Then run:
```bash
cd api && php artisan migrate
```
---
## Step 7: Start development
Open separate terminals (or use the Makefile from the repo root):
```bash
# Tab 1: Services (Docker)
make services
# Tab 2: Laravel API
make api
# Tab 3: Admin SPA (optional)
make admin
# Tab 4: Organizer SPA (optional)
make app
# Tab 5: Portal SPA (optional)
make portal
```
---
## Building features
Use Cursor with **`CLAUDE.md`** and **`.cursor/instructions.md`**. Example directions:
### Authentication
```
Wire Sanctum API auth: login, logout, me; form requests; API resources; Vue apps use axios + token storage (see .cursor/rules).
```
### Events module (Crewli)
```
Events nested under organisations: ULID PK, OrganisationScope, policies, EventResource, feature tests (200/401/403/422).
```
### Portal token flow
```
Portal token middleware and routes for artist/supplier contexts; document links on https://portal.crewli.app/... (see .cursor/rules/102_multi_tenancy.mdc).
```
---
## Troubleshooting
### MySQL Connection Refused
```bash
# Check if Docker is running
docker ps
# Restart services
make services-stop
make services
```
### CORS Errors
Check `api/config/cors.php` allows your frontend origins.
### Vuexy TypeScript Errors
```bash
cd apps/admin
pnpm install
pnpm type-check
```
---
## Next steps
1. Services running (Docker)
2. Laravel API configured and migrated
3. SPAs installed (`apps/admin`, `apps/app`, `apps/portal`)
4. Environment files for API + each SPA
5. Authentication and organisation switching
6. Events, sections, time slots, shifts
7. Persons, crowd types, portal flows
8. Accreditation, briefings, operational modules per roadmap in `.cursor/instructions.md`

122
dev-docs/TEST_SCENARIO.md Normal file
View File

@@ -0,0 +1,122 @@
# Crewli — End-to-End Testscenario Fase 2
## Scenario: "Test Festival BV organiseert Echt Feesten 2026"
Dit scenario valideert de complete operationele kern van Crewli.
Voer dit uit na elke Fase 2 module om regressie te voorkomen.
---
## Stap 1 — Voorbereiding (al werkend na Fase 1)
- [ ] Login als orgadmin@crewli.test / password
- [ ] Organisatie "Test Festival BV" is automatisch actief
- [ ] Navigeer naar Evenementen
- [ ] Maak aan: "Echt Feesten 2026" | slug: echt-feesten-2026
start: 10-07-2026 | eind: 12-07-2026 | timezone: Europe/Amsterdam
- [ ] Evenement verschijnt in lijst met status "draft"
- [ ] Klik door naar detail pagina — header toont naam, datum, status
---
## Stap 2 — Festival Secties (Fase 2 Module 1)
- [ ] Navigeer naar evenement detail > tab "Secties"
- [ ] Maak sectie aan: "Bar" (sort_order: 1)
- [ ] Maak sectie aan: "Security" (sort_order: 2)
- [ ] Maak sectie aan: "Hospitality" (sort_order: 3)
- [ ] Drie secties zichtbaar in lijst, correct gesorteerd
---
## Stap 3 — Time Slots (Fase 2 Module 1)
- [ ] Navigeer naar evenement detail > tab "Time Slots"
- [ ] Maak time slot aan:
Naam: "Vrijdag Avond" | type: VOLUNTEER
datum: 10-07-2026 | start: 18:00 | eind: 02:00
- [ ] Maak time slot aan:
Naam: "Zaterdag Dag" | type: VOLUNTEER
datum: 11-07-2026 | start: 10:00 | eind: 18:00
- [ ] Maak time slot aan:
Naam: "Zaterdag Avond" | type: VOLUNTEER
datum: 11-07-2026 | start: 18:00 | eind: 02:00
- [ ] Maak time slot aan:
Naam: "Zondag" | type: CREW
datum: 12-07-2026 | start: 10:00 | eind: 20:00
- [ ] Vier time slots zichtbaar, correct gesorteerd op datum/tijd
---
## Stap 4 — Shifts aanmaken (Fase 2 Module 2)
- [ ] Navigeer naar sectie "Bar"
- [ ] Maak shift aan: Time Slot "Vrijdag Avond" | slots_total: 4
slots_open_for_claiming: 3
- [ ] Maak shift aan: Time Slot "Zaterdag Dag" | slots_total: 5
slots_open_for_claiming: 4
- [ ] Maak shift aan: Time Slot "Zaterdag Avond" | slots_total: 4
slots_open_for_claiming: 3
- [ ] Navigeer naar sectie "Security"
- [ ] Maak shift aan: Time Slot "Vrijdag Avond" | slots_total: 3
slots_open_for_claiming: 2
- [ ] Maak shift aan: Time Slot "Zaterdag Avond" | slots_total: 3
slots_open_for_claiming: 2
- [ ] Shifts tonen fill_rate: 0/4, 0/5, etc.
---
## Stap 5 — Personen aanmaken (Fase 2 Module 3)
- [ ] Navigeer naar Personen (event-scoped)
- [ ] Maak 5 personen aan als crowd_type "Volunteer": 1. Jan de Vries | jan@test.nl 2. Lisa Bakker | lisa@test.nl 3. Ahmed Hassan | ahmed@test.nl 4. Sara Jansen | sara@test.nl 5. Tom Visser | tom@test.nl
- [ ] Vijf personen zichtbaar in lijst, status "pending"
---
## Stap 6 — Shift toewijzing (Fase 2 Module 2)
- [ ] Wijs Jan de Vries toe aan: Bar > Vrijdag Avond
- [ ] Wijs Jan de Vries toe aan: Bar > Zaterdag Dag
- [ ] Wijs Lisa Bakker toe aan: Bar > Vrijdag Avond
- [ ] Wijs Ahmed Hassan toe aan: Security > Vrijdag Avond
- [ ] Fill rate Bar > Vrijdag Avond: 2/4 ✓
---
## Stap 7 — Conflictdetectie (Fase 2 Module 2)
- [ ] Probeer Jan de Vries toe te wijzen aan:
Security > Vrijdag Avond (zelfde time slot!)
- [ ] Systeem weigert met foutmelding:
"Jan de Vries is al ingepland voor Vrijdag Avond"
- [ ] Conflictdetectie werkt op DB-niveau (UNIQUE constraint)
---
## Stap 8 — Overzicht validatie
- [ ] Event dashboard tiles tonen correcte aantallen:
Secties: 3 | Shifts: 5 | Personen: 5
- [ ] Sectie "Bar" toont 3 shifts, totaal 13 slots
- [ ] Persoon "Jan de Vries" toont 2 shift-toewijzingen
- [ ] Geen console errors in browser DevTools
---
## Regressie — controleer na elke nieuwe module
- [ ] Login werkt nog (admin@crewli.test en orgadmin@crewli.test)
- [ ] Organisatie switcher werkt nog
- [ ] Events lijst laadt zonder errors
- [ ] php artisan test → alle tests groen
## Openstaande FK constraints (worden toegevoegd bij persons module)
- shift_assignments.person_id → persons
- shift_check_ins.person_id → persons
- volunteer_availabilities.person_id → persons
person_id kolommen zonder FK constraint in:
- shift_assignments
- shift_check_ins
- volunteer_availabilities

1291
dev-docs/design-document.md Normal file

File diff suppressed because it is too large Load Diff

759
dev-docs/dev-guide.md Normal file
View File

@@ -0,0 +1,759 @@
# Crewli Development Guide
Cursor & Claude Code — Van Leeg Project naar Productie
**Versie:** 1.0 | **Datum:** Maart 2026 | **Stack:** Laravel 12 + Vue 3 | **AI Tools:** Cursor + Claude Code
## 1. Strategie & Mindset
Cursor vs Claude Code — wanneer gebruik je wat?
Voordat je begint met ontwikkelen is het belangrijk te begrijpen hoe Cursor en Claude Code zich tot elkaar verhouden. Ze zijn complementair — niet concurrerend.
| **Tool** | **Wanneer inzetten** |
|----|----|
| Cursor (IDE) | Dagelijks coderen. Inline autocomplete, context-aware suggesties, kleine refactors, code reviews, directe file-edits. Beste voor: een specifiek component bouwen, een bug fixen, een test schrijven. |
| Claude Code (Terminal) | Grote, multi-file taken. Scaffolding van een volledig module (migrations + model + controller + tests + Vue-pagina). Autonome agent die zelfstandig werkt, tests uitvoert en fouten corrigeert. Beste voor: 'Bouw het volledige shift-module end-to-end.' |
| Samen | Aanbevolen workflow: Claude Code genereert het skelet en alle bestanden. Cursor verfijnt, debugt en voegt details toe. Claude Code draait de test-suite. Cursor doet code review en stijlcorrecties. |
> **KERNPRINCIPE**
>
> Claude Code is je senior developer die grote blokken werk autonoom uitvoert.
>
> Cursor is je pair programmer die naast je zit terwijl jij zelf ook werkt.
>
> Jij bent de architect en product owner: jij beslist, zij bouwen.
## 2. De Eerste Stappen
Wat je vandaag doet voordat je één regel code schrijft
### 2.1 Repository structuur definitief maken
Controleer en bevestig de folderstructuur
Jouw huidige setup heeft al een goede basis. Bevestig of maak de volgende structuur:
```
crewli/ # Monorepo root
├── api/ # Laravel 12 backend
│ ├── app/
│ │ ├── Http/
│ │ │ ├── Controllers/Api/V1/
│ │ │ ├── Middleware/
│ │ │ └── Requests/ # Form Requests per endpoint
│ │ ├── Models/
│ │ ├── Policies/ # Laravel Policies per model
│ │ ├── Services/ # Business logic buiten controllers
│ │ ├── Events/ + Listeners/
│ │ └── Jobs/ # Queue jobs (briefings, PDF, notifs)
│ ├── database/
│ │ ├── migrations/
│ │ ├── factories/
│ │ └── seeders/
│ └── tests/Feature/Api/V1/ # PHPUnit feature tests per controller
├── apps/
│ ├── admin/ # Super Admin SPA (Vuexy)
│ ├── app/ # Organizer SPA (Vuexy) -- HOOFDAPP
│ └── portal/ # Externe portals (vrijwilliger, artiest, leverancier)
├── docs/ # Design document, API docs, ERD
│ ├── design-document.md
│ └── dev-guide.md
└── .cursorrules # Cursor workspace rules
```
### 2.2 Dependencies installeren
Backend en frontend klaarstomen
**Backend (api/)**
```bash
cd api
# Spatie permissions (rollen/permissies)
composer require spatie/laravel-permission
# Audit log
composer require spatie/laravel-activitylog
# Media library (bestandsbeheer)
composer require spatie/laravel-medialibrary
# PDF generatie
composer require barryvdh/laravel-dompdf
# QR codes
composer require endroid/qr-code
# Publiceer Spatie configs
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan vendor:publish --provider="Spatie\LaravelActivitylog\ActivitylogServiceProvider"
```
**Frontend — alle apps (apps/app/, apps/admin/, apps/portal/)**
```bash
# TanStack Query voor API state management
npm install @tanstack/vue-query
# Formuliervalidatie
npm install vee-validate zod @vee-validate/zod
# Drag-and-drop (form builder, timetable, prioriteitsranking)
npm install vuedraggable@next
# In apps/app/ en apps/admin/ ook:
npm install @fullcalendar/vue3 @fullcalendar/timeline @fullcalendar/resource-timeline
```
### 2.3 CLAUDE.md aanmaken
Het belangrijkste bestand in je hele repo
CLAUDE.md is de instructieset voor Claude Code. Het wordt automatisch geladen bij elke sessie. Dit bestand is de meest impactvolle investering die je doet — een uur hieraan besteden bespaart honderden uren aan correcties.
Maak aan: /crewli/CLAUDE.md (root niveau, zodat het voor alle sub-projecten geldt)
## 3. Helper Files — Volledige Inhoud
De exacte bestanden die je aanmaakt voor de eerste prompt
### 3.1 CLAUDE.md — Root niveau
Dit is de volledige, aanbevolen inhoud voor je CLAUDE.md. Kopieer dit letterlijk en pas aan waar nodig.
```
# Crewli — Claude Code Instructies
## Project Context
Crewli is een multi-tenant SaaS platform voor event- en festivalbeheer.
Gebouwd voor een professionele vrijwilligersorganisatie, met SaaS-uitbreidingspotentieel.
Design Document: /resources/design/design-document.md
## Tech Stack
- Backend: PHP 8.2+, Laravel 12, Sanctum, Spatie Permission, MySQL 8, Redis
- Frontend: TypeScript, Vue 3 (Composition API), Vuexy/Vuetify, Pinia, TanStack Query
- Testing: PHPUnit (backend), Vitest (frontend)
## Repository Structuur
- api/ Laravel backend
- apps/app/ Organizer SPA (hoofdapp)
- apps/admin/ Super Admin SPA
- apps/portal/ Externe portals (vrijwilliger, artiest, leverancier)
## Backend Regels (STRIKT VOLGEN)
### Multi-tenancy
- ELKE query op event-data MOET scoperen op organisation_id
- Gebruik OrganisationScope als Eloquent Global Scope op alle event-gerelateerde modellen
- Nooit directe id-checks in controllers — gebruik altijd Policies
### Controllers
- Gebruik Resource Controllers (index/show/store/update/destroy)
- Namespace: App\Http\Controllers\Api\V1\
- Alle responses via API Resources (nooit model-attributen direct teruggeven)
- Validatie via Form Requests (nooit inline validate())
### Modellen
- Gebruik HasUlids trait op alle business-modellen (GEEN UUID v4)
- Soft deletes op: Organisation, Event, FestivalSection, Shift, ShiftAssignment, Person, Artist
- GEEN soft deletes op: CheckIn, BriefingSend, MessageReply, ShiftWaitlist (audit-records)
- JSON kolommen ALLEEN voor opaque configuratie — nooit voor queryable data
### Database
- Primaire sleutels: ULID via HasUlids (niet UUID v4, niet auto-increment voor business tables)
- Elke migratie in volgorde aanmaken: eerst foundation, dan afhankelijke tabellen
- ALTIJD composite indexes toevoegen zoals gedocumenteerd in het design document sectie 3.5
### Rollen & Permissies
- Gebruik Spatie laravel-permission
- Check rollen via $user->hasRole() en Policies — nooit hardcoded role strings in controllers
- Drie niveaus: app (super_admin), organisatie (org_admin/org_member), event (event_manager etc.)
### Testing
- Schrijf PHPUnit Feature Tests per controller
- Minimaal per endpoint: happy path + unauthenticated (401) + wrong organisation (403)
- Gebruik factories voor alle test-data
- Draai tests NA elke module: php artisan test --filter=ModuleNaam
## Frontend Regels (STRIKT VOLGEN)
### Vue Componenten
- Altijd <script setup lang='ts'> — nooit Options API
- Props altijd getypeerd met defineProps<{...}>()
- Emits altijd gedeclareerd met defineEmits<{...}>()
### API Calls
- Gebruik TanStack Query (useQuery / useMutation) voor ALLE API calls
- Nooit direct axios in een component — altijd via een composable in composables/api/
- Pinia stores voor cross-component state — nooit prop drilling
### Naamgeving
- DB kolommen: snake_case
- TypeScript/JS variabelen: camelCase
- Vue componenten: PascalCase (bijv. ShiftAssignPanel.vue)
- Composables: use-prefix (bijv. useShifts.ts)
- Pinia stores: use-suffix store (bijv. useEventStore.ts)
### UI
- Gebruik ALTIJD Vuexy/Vuetify componenten voor layout, forms, tabellen, dialogen
- Nooit custom CSS schrijven als een Vuetify klasse bestaat
- Responsief: mobile-first, minimaal werkend op 375px breedte
## Verboden Patronen
- NOOIT: $user->role === 'admin' (gebruik policies)
- NOOIT: Model::all() zonder where-clausule (altijd scopen)
- NOOIT: dd() of var_dump() achterlaten in code
- NOOIT: .env waarden hardcoden in code
- NOOIT: JSON kolommen gebruiken voor data waarop gefilterd wordt
- NOOIT: UUID v4 als primaire sleutel (gebruik HasUlids)
## Volgorde bij elke nieuwe module
1. Migratie(s) aanmaken en draaien
2. Eloquent Model met relaties, scopes en HasUlids
3. Factory voor test-data
4. Policy voor autorisatie
5. Form Request(s) voor validatie
6. API Resource voor response transformatie
7. Resource Controller
8. Routes registreren in api.php
9. PHPUnit Feature Test schrijven en draaien
10. Vue composable voor API calls (useModuleNaam.ts)
11. Pinia store indien cross-component state nodig
12. Vue pagina component
13. Route toevoegen in Vue Router
```
### 3.2 .cursorrules — Root niveau
Dit is het equivalent van CLAUDE.md maar voor Cursor's autocomplete en inline AI. Korter en meer gefocust op directe code-stijl.
```
# Crewli Cursor Rules
## Stack
PHP 8.2 + Laravel 12 | TypeScript + Vue 3 + Vuexy/Vuetify | Pinia + TanStack Query
## Laravel
- Resource Controllers, Form Requests, API Resources — altijd
- HasUlids op business modellen, HasFactory, SoftDeletes waar gedocumenteerd
- Global Scope OrganisationScope op event-gerelateerde modellen
- Policies voor autorisatie, nooit inline role checks
## Vue 3
- <script setup lang='ts'> altijd
- TanStack Query voor API state, Pinia voor UI state
- Vuetify componenten eerst, custom CSS als laatste redmiddel
## Naamgeving
- snake_case DB | camelCase JS | PascalCase Vue | use* composables | use*Store Pinia
## Tests
- PHPUnit Feature Test per controller, minimaal: 200 + 401 + 403
```
### 3.3 dev-docs/SCHEMA.md — Levend schema-document
Maak een Markdown bestand aan in /dev-docs/ dat de tabel-definitie bevat als platte tekst. Claude Code gebruikt dit als primaire referentie bij het genereren van migraties.
```
# Crewli Database Schema
# Versie: 1.3 | Gegenereerd vanuit Design Document
## Regels
- Primaire sleutels: ULID via HasUlids (nooit UUID v4)
- Soft delete: zie lijst per tabel hieronder
- JSON kolommen: alleen voor opaque config
## Tabellen
### users
- id (ulid, PK)
- name (string)
- email (string, unique)
- password (string)
- timezone (string, default: Europe/Amsterdam)
- locale (string, default: nl)
- avatar (string, nullable)
- email_verified_at (timestamp, nullable)
- deleted_at (timestamp, nullable) -- soft delete
### organisations
- id (ulid, PK)
- name (string)
- slug (string, unique)
- billing_status (enum: trial|active|suspended|cancelled, default: trial)
- settings (json, nullable) -- UI display prefs only
- deleted_at (timestamp, nullable)
# ... (volledig schema uit Design Document sectie 3.5)
```
### 3.4 docs/API.md — API contract
Een simpele route-lijst die Claude Code gebruikt als referentie bij het genereren van controllers en Vue composables.
```
# Crewli API Contract
# Base: /api/v1/
# Auth: Bearer token (Sanctum)
## Auth
POST /auth/login
POST /auth/logout
GET /auth/me
## Organisations
GET /organisations -- lijst (super admin)
POST /organisations -- aanmaken
GET /organisations/{org} -- detail
PUT /organisations/{org} -- bijwerken
GET /organisations/{org}/members -- leden
POST /organisations/{org}/invite -- uitnodigen
## Events
GET /organisations/{org}/events
POST /organisations/{org}/events
GET /organisations/{org}/events/{event}
PUT /organisations/{org}/events/{event}
## Festival Sections
GET /events/{event}/sections
POST /events/{event}/sections
GET /events/{event}/sections/{section}
## Time Slots
GET /events/{event}/time-slots
POST /events/{event}/time-slots
## Shifts
GET /events/{event}/sections/{section}/shifts
POST /events/{event}/sections/{section}/shifts
PUT /events/{event}/sections/{section}/shifts/{shift}
POST /events/{event}/sections/{section}/shifts/{shift}/assign
POST /events/{event}/sections/{section}/shifts/{shift}/claim
## Persons
GET /events/{event}/persons
POST /events/{event}/persons
GET /events/{event}/persons/{person}
PUT /events/{event}/persons/{person}
POST /events/{event}/persons/{person}/approve
# ... (volledig API contract uitbreiden per module)
```
## 4. Development Workflow
Hoe je van leeg project naar werkende feature gaat
Elke feature volgt dezelfde drielaagse workflow. Commit altijd per voltooide laag — nooit halfafgebouwde code in main.
| **Laag** | **Wat je doet en met welk tool** |
|----|----|
| Laag 1 — Backend (API) | Claude Code genereert: migratie + model + factory + policy + form request + resource + controller + test. Jij reviewt en draait tests. |
| Laag 2 — Frontend (Vue) | Claude Code genereert: composable + Pinia store + Vue pagina + router entry. Cursor verfijnt de UI met Vuexy componenten. |
| Laag 3 — Integration | Cursor: verbind frontend met backend. Test end-to-end. Fix type-errors. Review mobile weergave. |
### 4.1 De Module-generatie volgorde
Altijd in deze volgorde. Nooit stappen overslaan — later toevoegen kost meer tijd dan nu correct doen.
| **Stap** | **Commando / Actie** |
|----|----|
| 1. Migratie | php artisan make:migration create_shifts_table |
| 2. Model | php artisan make:model Shift -mfp (migration + factory + policy) |
| 3. Form Request | php artisan make:request StoreShiftRequest + UpdateShiftRequest |
| 4. API Resource | php artisan make:resource ShiftResource + ShiftCollection |
| 5. Controller | php artisan make:controller Api/V1/ShiftController --api |
| 6. Registreer routes | In api/routes/api.php toevoegen |
| 7. Test | php artisan make:test ShiftControllerTest + draaien |
| 8. Composable | apps/app/src/composables/api/useShifts.ts aanmaken |
| 9. Store (indien nodig) | apps/app/src/stores/useShiftStore.ts |
| 10. Vue pagina | apps/app/src/pages/sections/[id]/shifts.vue |
| 11. Route | apps/app/src/router/index.ts |
### 4.2 Fase-planning: wat bouw je wanneer
| **Fase** | **Inhoud** |
|----|----|
| Fase 1 — Foundation (nu) | Auth (login/logout/me), Organisations CRUD, Events CRUD, User invitations, Multi-tenant scope, Roles & permissions setup, Basis dashboard shell |
| Fase 2 — Core Operations | Persons & Crowd Types, Festival Sections + Time Slots + Shifts, Shift claiming + goedkeuring, Vrijwilligers registratie + portaal, Accreditatie engine, Basis briefings |
| Fase 3 — Advancing & Show Day | Artist advancing + portaal, Timetable, Mission Control, Formulierbouwer, Post-festival evaluatie, PDF allocatiesheet, Campagnes (email + WhatsApp via Zender) |
| Fase 4 — Differentiators | Real-time WebSockets, Show Day Mode, Vrijwilligersprofiel + festival-paspoort, Shift swap & wachtlijst, Retrospectief rapport, Leveranciersportaal uitgebreid |
## 5. Prompt Bibliotheek
Kant-en-klare prompts voor elke ontwikkelstap
Gebruik deze prompts letterlijk of als basis. De meest effectieve prompts zijn: specifiek, contextueel en taak-gebaseerd. Verwijs altijd naar de docs/ bestanden die je hebt aangemaakt.
### 5.1 Kickstart prompts
> **Fase 1 kickstart — Alles genereren in een sweep**
>
> Lees /resources/design/design-document.md sectie 3.5 (schema) en /CLAUDE.md.
>
> Genereer alle Fase 1 componenten in de juiste volgorde:
>
> 1. Migrations voor: users (update), organisations, organisation_user, user_invitations, events, event_user_roles
> 2. Eloquent modellen met HasUlids, relaties, OrganisationScope global scope waar van toepassing
> 3. Factories met realistic test data
> 4. Spatie Permission seeder: maak rollen aan (super_admin, org_admin, org_member, event_manager, staff_coordinator, volunteer_coordinator)
> 5. Auth controller (login/logout/me) met Sanctum
> 6. Organisations controller (CRUD) met Policy en Feature Test
> 7. Events controller (CRUD) met Policy en Feature Test
>
> Draai na elke stap: php artisan test. Los fouten op voor je verder gaat.
> **Module genereren — Shifts als voorbeeld**
>
> Lees /CLAUDE.md en /dev-docs/SCHEMA.md voor de shifts tabel definitie.
>
> Bouw het volledige Shifts module in de volgorde uit CLAUDE.md sectie 'Volgorde bij elke nieuwe module'.
>
> Specifieke eisen voor Shifts:
>
> - time_slot_id MOET gedenormaliseerd worden in shift_assignments voor de UNIQUE(person_id, time_slot_id) constraint
> - ShiftAssignment heeft een status machine: pending_approval > approved/rejected/cancelled/completed
> - Auto-approve is configureerbaar per shift (auto_approved bool op shift niveau)
> - Bij approve: stuur notificatie naar vrijwilliger (queued job, gebruik ZenderService voor WhatsApp)
> - ShiftResource moet slots_filled (count van approved assignments) en fill_rate (percentage) berekend teruggeven
>
> Eindig met: php artisan test --filter=Shift
### 5.2 Backend prompts
> **Migration genereren**
>
> Genereer een Laravel migratie voor de tabel [TABELNAAM] op basis van /dev-docs/SCHEMA.md.
>
> Gebruik $table->ulid('id')->primary() als PK.
>
> Voeg alle indexes toe zoals gedocumenteerd (composite indexes, unique constraints).
>
> Voeg timestamps() en softDeletes() toe indien van toepassing per CLAUDE.md.
>
> Gebruik constrained() op alle foreign keys voor cascade-gedrag.
> **Model met alle features**
>
> Genereer het Eloquent model voor [MODELNAAM].
>
> Gebruik: HasUlids, HasFactory, SoftDeletes (indien van toepassing).
>
> Voeg toe: OrganisationScope global scope, alle relaties (hasMany, belongsTo, belongsToMany),
> computed accessors (fill_rate, available_slots), status-gerelateerde scopes (scopePending, scopeApproved),
> en $fillable of $guarded array.
>
> Schrijf ook de factory met realistic Nederlandse testdata.
> **API Resource met computed velden**
>
> Genereer een Laravel API Resource voor [MODELNAAM].
>
> Voeg toe: alle relevante velden, computed velden (fill_rate, status_label),
> conditioneel geladen relaties (whenLoaded), en wanneer van toepassing: when() voor permissie-afhankelijke velden.
>
> De Resource mag NOOIT model-attributen direct weggeven zonder transformatie.
> **Feature test schrijven**
>
> Schrijf een PHPUnit Feature Test voor [CONTROLLERNAAM].
>
> Dek minimaal af: index (200), show (200), store (201), update (200), destroy (204),
> unauthenticated (401 op alle routes), wrong organisation (403), validatiefouten (422).
>
> Gebruik RefreshDatabase, ActingAs met correcte rol via Spatie Permission.
>
> Maak test data via factories — nooit hardcoded IDs.
> **ZenderService aanmaken (WhatsApp/SMS)**
>
> Maak app/Services/ZenderService.php aan.
>
> Zender is een self-hosted SMS/WhatsApp gateway (CodeCanyon product).
>
> Config: ZENDER_API_URL en ZENDER_API_KEY uit .env.
>
> Methoden: sendSms(string $to, string $message): bool
> sendWhatsApp(string $to, string $message): bool
> sendByUrgency(string $to, string $message, string $urgency): bool
>
> urgency: normal=email only, urgent=whatsapp, emergency=sms+whatsapp parallel
>
> Gebruik Laravel HTTP Client (Http::). Log alle sends via activitylog.
>
> Schrijf ook een ZenderServiceTest met HTTP fake.
### 5.3 Frontend prompts
> **Vue pagina voor een lijst-overzicht**
>
> Maak apps/app/src/pages/[module]/index.vue.
>
> Gebruik \<script setup lang='ts'\>.
>
> API calls via useQuery() uit TanStack Query — niet direct axios.
>
> Tabel via VDataTable van Vuetify — niet custom HTML.
>
> Bovenaan: status KPI-tiles (totaal, goedgekeurd, pending) als klikbare VCard componenten.
>
> Rij klik: opent een side panel (niet navigeert naar nieuwe pagina) met detail-informatie.
>
> Loading state: VSkeleton loader. Error state: VAlert met retry knop.
>
> Mobiel: tabel collapst naar een VList op viewport < 768px.
> **Composable voor API calls**
>
> Maak apps/app/src/composables/api/use[Module].ts.
>
> Exporteer: use[Module]List (useQuery), use[Module]Detail (useQuery met id param),
> useCreate[Module] (useMutation), useUpdate[Module] (useMutation), useDelete[Module] (useMutation).
>
> Gebruik axios via de centrale api.ts instance (met Sanctum CSRF en auth header).
>
> Mutations invalideren automatisch de relevante query keys na succes.
>
> Alle response types volledig getypeerd via TypeScript interfaces in types/[module].ts.
> **Pinia store aanmaken**
>
> Maak apps/app/src/stores/use[Module]Store.ts.
>
> Gebruik defineStore met Setup syntax (niet Options syntax).
>
> Sla op: geselecteerde IDs, UI state (open sidepanel, actief tab), filters.
>
> NIET in Pinia: server data (dat zit in TanStack Query). Pinia is alleen voor UI state.
>
> Exporteer: alle state als readonly via storeToRefs.
> **Shift claim workflow — volledig end-to-end**
>
> Bouw de volledige shift claim workflow:
>
> Backend: POST /shifts/{shift}/claim endpoint in ShiftController.
>
> - Valideer: shift heeft slots_open_for_claiming beschikbaar
> - Valideer: geen bestaande approved assignment voor zelfde time_slot_id voor deze person
> - Maak ShiftAssignment aan met status=pending_approval
> - Dispatch NotifyCoordinatorOfClaimJob (queued)
> - Return ShiftAssignmentResource
>
> Frontend: 'Claim' knop in portal/shifts/index.vue.
>
> - Disable knop als conflict of geen slots beschikbaar
> - Na claim: toon 'Wachten op goedkeuring' badge
> - Optioneel: 'Op wachtlijst' knop als shift vol is
### 5.4 Agent-aanstuurprompts
> **Grote module — een prompt voor alles**
>
> Je bent een senior fullstack developer die werkt aan Crewli. Lees /CLAUDE.md volledig.
>
> Bouw het volledige [MODULE] module:
>
> Backend (in volgorde):
>
> 1. Migrations voor alle tabellen uit /dev-docs/SCHEMA.md sectie [X.X]
> 2. Models met alle relaties, scopes en accessors
> 3. Factories
> 4. Policies
> 5. Form Requests
> 6. API Resources
> 7. Controllers
> 8. Routes
> 9. Feature tests — draai ze, los fouten op
>
> Frontend:
>
> 10. TypeScript types in apps/app/src/types/[module].ts
> 11. Composables in apps/app/src/composables/api/
> 12. Vue pagina's (lijst + detail side panel)
> 13. Router entries
>
> Stop na elke laag en vraag bevestiging voor je doorgaat.
>
> Als een test faalt: los het op voor je verdergaat — nooit overslaan.
> **Bug fix prompt**
>
> Er is een probleem met [BESCHRIJVING VAN HET PROBLEEM].
>
> Relevante bestanden: [BESTANDSPADEN].
>
> Foutmelding: [PLAK EXACTE ERROR].
>
> Verwacht gedrag: [WAT ZOU ER MOETEN GEBEUREN].
>
> Analyseer de oorzaak, schrijf een failing test die het probleem reproduceert,
> fix het probleem, en bevestig dat de test slaagt.
> **Code review prompt**
>
> Review de code in [BESTANDSPAD] als senior Laravel/Vue developer.
>
> Check specifiek op:
>
> - Multi-tenancy: wordt organisation_id correct gescopeerd?
> - Security: worden Policies gebruikt? Geen directe role-checks?
> - Performance: ontbrekende eager loading (N+1), ontbrekende indexes?
> - Conventies: volgt het CLAUDE.md regels?
> - Types: zijn alle TypeScript types volledig (geen any)?
>
> Geef concrete verbeterpunten met codevoorbeelden.
## 6. Agents — Autonome Ontwikkeling
Hoe je Claude Code en Cursor agents effectief inzet
### 6.1 Claude Code als Agent
Claude Code kan volledig autonoom werken: bestanden lezen, aanmaken, aanpassen, tests draaien en fouten corrigeren — zonder dat jij elke stap bevestigt. Dit is het krachtigste en snelste werkmode.
| **Mode** | **Wanneer gebruiken** |
|----|----|
| Interactief (standaard) | Als je wil meekijken en goedkeuren. Claude Code stelt elke actie voor en wacht. Gebruik voor: eerste keer een module bouwen, complexe refactors. |
| Autonoom (--dangerously-skip-permissions) | Als je een grote taak wil delegeren en wegloopt. Claude Code werkt door tot klaar. Gebruik voor: routine-modules die je eerder hebt gebouwd, test-driven fixes. |
| Aanbevolen aanpak | Start autonoom voor scaffolding. Schakel naar interactief bij UI-componenten of business-logica die project-specifieke kennis vereist. |
**Claude Code opstarten**
```bash
# Installeer Claude Code (eenmalig)
npm install -g @anthropic-ai/claude-code
# Start in je project root
cd /pad/naar/crewli
claude
# Of: direct met een taak
claude --print 'Genereer de Shift migration op basis van CLAUDE.md'
# Autonoom mode (voorzichtig gebruiken)
claude --dangerously-skip-permissions
```
### 6.2 Cursor Agent Mode
Cursor heeft een ingebouwde Agent mode die vergelijkbaar is met Claude Code maar geintegreerd in de IDE. Activeer via Cmd+Shift+P > 'Cursor: Open Agent'.
| **Feature** | **Gebruik** |
|----|----|
| @workspace | Geeft de agent toegang tot je hele codebase als context. Altijd meegeven bij module-niveau taken. |
| @file | Verwijs naar een specifiek bestand. Bijv: @CLAUDE.md @api/app/Models/Shift.php |
| @docs | Verwijs naar externe documentatie (Laravel docs, Vuetify docs). Cursor indexeert deze. |
| Composer mode | Meerdere bestanden tegelijk bewerken. Ideaal voor: tegelijk model + controller + test aanpassen. |
### 6.3 De optimale agent-workflow per dag
> **DAGELIJKSE ROUTINE**
>
> Ochtend: Open Claude Code. Geef de taak voor die dag: 'Bouw het volledig Persons module op basis van CLAUDE.md en SCHEMA.md.' Laat autonoom draaien.
>
> Middag: Review de gegenereerde code in Cursor. Check: volgt het de conventies? Zijn de tests groen? Zijn de TypeScript types compleet?
>
> Namiddag: Cursor voor UI fijnafstelling, Vuexy componenten integratie, visuele correcties.
>
> Einde dag: php artisan test (alle tests groen). Commit alles met duidelijke commit messages.
### 6.4 Context window management
Claude Code heeft een beperkt context window. Bij grote taken verliest het de context van eerdere bestanden. Beheer dit proactief:
- Begin elke nieuwe sessie met: 'Lees /CLAUDE.md voor je begint.'
- Verwijs expliciet naar relevante bestanden: 'Zie /dev-docs/SCHEMA.md voor de tabel definitie.'
- Splits grote modules: 'Bouw eerst alleen de backend (stappen 1-9). Stop dan.'
- Na een context-verlies: geef een samenvatting: 'We bouwen Crewli. Tot nu toe klaar: auth, organisations, events. Nu: Shifts module backend.'
- Gebruik /dev-docs/API.md als levend document — houd bij wat er al gebouwd is.
## 7. Tips, Valkuilen & Best Practices
Geleerd van ervaring — lees dit voordat je begint
### 7.1 De grootste tijdverspillers
| **Valkuil** | **Oplossing** |
|----|----|
| Te brede prompts: 'Bouw de hele app' | Altijd per module. Per module maximaal 1 laag tegelijk. Breed = vaag = slechte output. |
| CLAUDE.md niet up-to-date houden | Na elke architectuurbeslissing: update CLAUDE.md. Dit is je bron van waarheid. Verouderde regels leiden tot inconsistente code. |
| Tests overslaan 'want het werkt toch' | Schrijf tests altijd. AI-gegenereerde code heeft subtiele bugs die pas later opduiken. Tests vangen dit vroeg. |
| Alle gegenereerde code blindelings accepteren | Review altijd: check multi-tenancy scoping, check indexes, check error handling. AI mist soms subtiele business logica. |
| Frontend en backend tegelijk bouwen | Backend eerst, compleet en getest. Dan frontend. Nooit parallel — je verliest overzicht. |
| Geen versiecontrole per module | Commit na elke voltooide module (backend + frontend + tests). Kleine commits = makkelijk terugdraaien. |
### 7.2 Prompts die altijd goed werken
- **Geef altijd context: 'Crewli is een multi-tenant SaaS voor festival-organisatie...'**
- **Verwijs naar bestanden: 'Op basis van CLAUDE.md en SCHEMA.md...'**
- **Specificeer de output: 'Genereer X, Y en Z. Niets anders.'**
- **Vraag om uitleg: 'Leg uit waarom je deze aanpak kiest voor de shift conflict-check.'**
- **Gebruik iteratief: 'Dit klopt niet omdat... Pas aan en draai tests opnieuw.'**
### 7.3 Kwaliteitscontrole checklist
Gebruik dit als checklist voor elke voltooide module voordat je verder gaat:
| **Check** | **Wat je controleert** |
|----|----|
| Tests | php artisan test draait groen. Minimaal: 200, 401, 403 per endpoint. |
| Multi-tenancy | Elke query heeft organisation_id scope. Controleer via tinker. |
| N+1 queries | Gebruik Laravel Debugbar of query logging. Geen N+1 in lijst-endpoints. |
| TypeScript | Geen 'any' types in Vue composables en components. npx tsc --noEmit groen. |
| Mobile | Pagina is bruikbaar op 375px. Open Chrome DevTools en check. |
| Error states | Wat ziet een gebruiker bij: lege lijst, API fout, netwerk timeout? |
| CLAUDE.md | Geen verboden patronen (Model::all, hardcoded roles, UUID v4). |
### 7.4 Handige Laravel commando's
```bash
# Alles in een keer voor een nieuwe model
php artisan make:model Shift -a # model + migration + factory + seeder + policy + controller + resource
# Tests draaien
php artisan test # alle tests
php artisan test --filter=ShiftTest # specifieke test class
php artisan test --coverage # met coverage rapport
# Database
php artisan migrate:fresh --seed # reset + migreer + seed
php artisan tinker # REPL voor quick checks
# Routes inspecteren
php artisan route:list --path=api/v1 # alle API routes
# Queue (voor briefings, notificaties)
php artisan queue:work --queue=notifications,briefings,default
```
### 7.5 Eerste dag: exacte volgorde
| **#** | **Actie** |
|----|----|
| 1 | Repository structuur controleren (sectie 2, stap 01) |
| 2 | Dependencies installeren (sectie 2, stap 02) |
| 3 | CLAUDE.md aanmaken en invullen (sectie 3.1) |
| 4 | .cursorrules aanmaken (sectie 3.2) |
| 5 | docs/SCHEMA.md aanmaken met volledig schema uit design document |
| 6 | docs/API.md aanmaken met initiiele routes |
| 7 | Claude Code starten: 'Lees CLAUDE.md. Daarna: genereer Fase 1 — auth, organisations, events.' |
| 8 | Tests draaien: php artisan test — los fouten op |
| 9 | Commit: 'feat: fase 1 foundation — auth, organisations, events' |
| 10 | Morgen: Fase 2 starten met Persons & Crowd Types |
---
Crewli Development Guide v1.0 — Maart 2026

457
dev-docs/start-guide.md Normal file
View File

@@ -0,0 +1,457 @@
**Crewli**
Start — Definitieve Actielijst
Architectuur + Technologie + Stap-voor-stap naar eerste werkende code
**Versie:** 1.0 — Definitief | **Datum:** Maart 2026 | **Status:** Klaar om te starten
## 1 — Definitieve Architectuur
Dit is de volledige, vastgestelde architectuur van Crewli. Alle beslissingen hierin zijn definitief — verwerk ze in CLAUDE.md en .cursorrules zodat Claude Code dit altijd als context heeft.
### 1.1 Systeemoverzicht
| **apps/admin/** | **apps/app/** | **apps/portal/** |
|---|---|---|
| Super Admin SPA | Organizer SPA | Portal SPA |
Vuexy + Vue 3 + TypeScript | Pinia + TanStack Query | Axios → CORS → Sanctum Token
Alle drie apps zijn Vue 3 SPA's — Vuexy template — communiceren uitsluitend via REST API
**api/ — Laravel 12 REST API (ENIGE backend — geen Blade views)**
PHP 8.2 | Sanctum | Spatie Permission | MySQL 8 | Redis | Queue Workers
### 1.2 Laravel vs Vue — de harde scheiding
> **GOUDEN REGEL**
>
> Laravel doet NIKS met HTML of UI. Geen Blade views, geen Mix, geen Inertia.
>
> Laravel is uitsluitend een JSON REST API. Elke response is application/json.
>
> Vue doet ALLES met de gebruikersinterface. De drie SPA's communiceren via HTTPS met de API.
| **App / Laag** | **Technology** | **Gebruik & verantwoordelijkheid** |
|----|----|----|
| api/ | Laravel 12 + Sanctum | REST API, authenticatie, business logic, database, queue workers, e-mail, PDF-generatie. Geen enkele HTML pagina. |
| apps/admin/ | Vue 3 + Vuexy (vol) | Super Admin SPA: organisations beheren, billing, platform-gebruikers. Klein en eenvoudig. |
| apps/app/ | Vue 3 + Vuexy (vol) | Organizer SPA: de hoofdapp. Events, shifts, persons, artists, briefings, Mission Control. 90% van je werk. |
| apps/portal/ | Vue 3 + Vuexy (gestript) | Portal SPA: twee toegangsmodi. Login voor vrijwilligers/crew. Token voor artiesten/leveranciers/pers. |
### 1.3 Vuexy — waar en hoe
| **App / Laag** | **Technology** | **Gebruik & verantwoordelijkheid** |
|----|----|----|
| apps/admin/ | Vuexy volledig | Admin template ongewijzigd: sidebar, dark mode, customizer. Weinig aanpassingen nodig. |
| apps/app/ | Vuexy volledig | Sidebar nav aanpassen voor Crewli-structuur. Customizer/demo-componenten verwijderen. Full Vuetify component gebruik. |
| apps/portal/ | Vuexy gestript | Geen sidebar nav, geen customizer, geen dark mode toggle. Wel: Vuetify componenten, Vuexy SCSS variabelen, Vuexy fonts. Eigen layout: top-bar met event-logo + naam. Mobile-first. |
### 1.4 Portal: twee toegangsmodi
Eén portal app met twee modi — op basis van hoe de gebruiker binnenkomt:
| **Gebruiker** | **Identiteit** | **Toegang** | **Waarom** |
|----|:--:|:--:|----|
| **Vrijwilliger** | Langdurig | **Login** | Festival-paspoort, reliability score, shift-historie accumuleren over jaren |
| **Crew / Staff** | Langdurig | **Login** | Kan ook organizer-rechten hebben; organisatie-medewerker |
| **Artiest** | Per event | **Token** | Eenmalige booking-relatie; advancing via gesignde URL |
| **Tour manager** | Per event | **Token** | Namens artiest; geen platform-account nodig |
| **Leverancier** | Per event | **Token** | Productieaanvraag is event-specifiek; token via production_requests.token |
| **Pers / Media** | Per event | **Token** | Accreditatie per event; geen terugkerende relatie |
**Hoe de router dit afhandelt (apps/portal/)**
```ts
// apps/portal/src/router/guards.ts
export const accessMode = computed(() => {
const token = route.query.token as string | undefined
const isAuth = authStore.isAuthenticated
if (token) return 'token' // artiest, leverancier, pers → token-flow
if (isAuth) return 'login' // vrijwilliger, crew → login-flow
return 'unauthenticated' // → redirect naar /login
})
// Token-based: geen login nodig, token gevalideerd via API
// POST /api/v1/portal/token-auth { token: '...' } → person context terug
```
### 1.5 Hoe de API authorisatie werkt — backend
```php
// api/routes/api.php
Route::prefix('v1')->group(function () {
// Publiek (login, token-auth)
Route::post('auth/login', [AuthController::class, 'login']);
Route::post('portal/token-auth', [PortalTokenController::class, 'auth']);
Route::post('portal/form-submit', [PublicFormController::class, 'submit']);
// Login-based (Sanctum)
Route::middleware('auth:sanctum')->group(function () {
Route::post('auth/logout', [AuthController::class, 'logout']);
Route::get('auth/me', [AuthController::class, 'me']);
// ... alle organizer + portal-login routes
});
// Token-based portal (eigen middleware)
Route::middleware('portal.token')->group(function () {
Route::get('portal/artist', [ArtistPortalController::class, 'index']);
Route::post('portal/advancing', [AdvancingController::class, 'submit']);
// ... alle token-portal routes
});
});
```
> **LET OP — CORS**
>
> Laravel CORS config (config/cors.php) moet drie origins toestaan:
>
> admin.crewli.app | app.crewli.app | portal.crewli.app
>
> In development: http://localhost:5173 | :5174 | :5175
## 2 — Actielijst: Wat Je Nu Doet
Voer deze stappen uit in volgorde. Sla niets over — elke stap is input voor de volgende. Tijdsinschatting totaal: 2-4 uur. Daarna kun je de eerste Claude Code prompt sturen.
### Stap 1 — Repository herstructureren **NU**
apps/band/ hernoemen, demo-rommel verwijderen
-**Hernoem apps/band/ naar apps/portal/**
```
mv apps/band apps/portal
# Update package.json naam in apps/portal/package.json:
# "name": "crewli-portal"
```
- ☐ **Verwijder Vuexy demo-bestanden uit apps/app/src/**
- Te verwijderen uit apps/app/src/components/dialogs/:
- AddAuthenticatorAppDialog.vue, AddPaymentMethodDialog.vue, CardAddEditDialog.vue
- PricingPlanDialog.vue, ReferAndEarnDialog.vue, UserUpgradePlanDialog.vue
- Te verwijderen uit apps/app/src/@core/components/:
- BuyNow.vue — 'buy template' knop
- TheCustomizer.vue — theme demo-customizer (niet nodig in productie)
- Te verwijderen uit apps/app/src/pages/:
- second-page.vue — demo pagina
- Te verwijderen uit apps/app/public/:
- mockServiceWorker.js — MSW service worker (alleen in dev nodig, niet committen)
### Stap 2 — API-laag opruimen in apps/app/ **NU**
Een centrale axios instance — dubbele laag verwijderen
- **!** apps/app/ heeft nu drie overlappende API-bestanden: src/lib/api-client.ts, src/utils/api.ts, en src/composables/useApi.ts. Dit moet worden een bestand.
- ☐ **Bepaal: src/lib/axios.ts wordt de ENIGE axios instance**
```ts
// apps/app/src/lib/axios.ts — de ENIGE axios instance
import axios from 'axios'
import { useAuthStore } from '@/stores/useAuthStore'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL + '/api/v1',
withCredentials: true,
headers: { 'Accept': 'application/json' },
})
// Request interceptor: voeg Bearer token toe
api.interceptors.request.use(config => {
const auth = useAuthStore()
if (auth.token) config.headers.Authorization = `Bearer ${auth.token}`
return config
})
// Response interceptor: 401 → redirect naar login
api.interceptors.response.use(
res => res,
err => {
if (err.response?.status === 401) useAuthStore().logout()
return Promise.reject(err)
}
)
export default api
```
- ☐ Verwijder daarna: src/lib/api-client.ts en src/utils/api.ts
- ☐ Hernoem src/composables/useApi.ts → src/composables/useApiHelpers.ts (algemene helpers)
- ☐ Doe hetzelfde voor apps/portal/ en apps/admin/ (zelfde patroon)
### Stap 3 — TanStack Query installeren **NU**
Ontbreekt nog — vereist voor alle API state management
- ☐ **Installeer in alle drie apps (app/, admin/, portal/)**
```sh
cd apps/app && pnpm add @tanstack/vue-query
cd ../admin && pnpm add @tanstack/vue-query
cd ../portal && pnpm add @tanstack/vue-query
```
- ☐ Registreer in main.ts van elke app
```ts
// apps/app/src/main.ts — voeg toe
import { VueQueryPlugin } from '@tanstack/vue-query'
app.use(VueQueryPlugin, {
queryClientConfig: {
defaultOptions: {
queries: { staleTime: 1000 * 60 * 5, retry: 1 },
},
},
})
```
- ☐ Installeer ook formuliervalidatie (alle apps)
```sh
pnpm add vee-validate zod @vee-validate/zod
```
### Stap 4 — Backend dependencies installeren **NU**
Spatie packages zijn vereist voor fase 1
- ☐ **Installeer Spatie packages in api/**
```sh
cd api
composer require spatie/laravel-permission
composer require spatie/laravel-activitylog
composer require spatie/laravel-medialibrary
composer require barryvdh/laravel-dompdf
composer require endroid/qr-code
# Publiceer configs
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan vendor:publish --provider="Spatie\LaravelActivitylog\ActivitylogServiceProvider"
# Voeg HasUlids toe als trait — Laravel native, geen extra package nodig
# use Illuminate\Database\Eloquent\Concerns\HasUlids;
```
- ☐ Voeg PortalToken middleware toe (skeleton voor later)
```sh
php artisan make:middleware PortalTokenMiddleware
# Registreer in bootstrap/app.php als 'portal.token'
```
- ☐ Update config/cors.php voor drie frontend origins
```php
// config/cors.php
'allowed_origins' => [
env('FRONTEND_ADMIN_URL', 'http://localhost:5173'),
env('FRONTEND_APP_URL', 'http://localhost:5174'),
env('FRONTEND_PORTAL_URL','http://localhost:5175'),
],
```
### Stap 5 — Helper bestanden aanmaken **NU**
CLAUDE.md, .cursorrules, docs/ — dit is het belangrijkste wat je doet
- **!** Dit zijn de vier bestanden die Claude Code altijd laadt als context. Een uur hieraan besteden bespaart honderden uren aan correcties. Gebruik de volledige inhoud uit Dev Guide sectie 3.
- ☐ **Maak aan in de root van je project:**
```sh
# Root van het project (naast api/ en apps/)
touch CLAUDE.md # Dev Guide sectie 3.1 — volledig invullen
touch .cursorrules # Dev Guide sectie 3.2 — volledig invullen
mkdir -p docs
touch docs/SCHEMA.md # Schema uit Design Document v1.3 sectie 3.5
touch docs/API.md # API contract — begin met auth + organisations + events
```
- ☐ Voeg toe aan CLAUDE.md (update ten opzichte van Dev Guide v1.0):
- Portal architectuur: een app, twee toegangsmodi
- Login-based (auth:sanctum): vrijwilligers, crew — persons met user_id
- Token-based (portal.token middleware): artiesten, leveranciers, pers — persons zonder user_id
- apps/ mapping: admin/ = Super Admin, app/ = Organizer, portal/ = Externe gebruikers
- CORS: drie origins configureren in zowel Laravel als Vite dev server
### Stap 6 — Vite dev ports configureren **NU**
Elk frontend-app een eigen port zodat CORS werkt
- ☐ **Pas vite.config.ts aan in elke app**
```ts
// apps/admin/vite.config.ts → port: 5173
// apps/app/vite.config.ts → port: 5174
// apps/portal/vite.config.ts → port: 5175
server: {
port: 5174, // aanpassen per app
proxy: { // optioneel: proxy API calls in dev
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
}
}
}
```
- ☐ Voeg .env.local toe aan elke app
```sh
# apps/app/.env.local
VITE_API_URL=http://localhost:8000
VITE_APP_NAME=Crewli
# apps/portal/.env.local
VITE_API_URL=http://localhost:8000
VITE_APP_NAME=Crewli Portal
```
### Stap 7 — Eerste Claude Code prompt sturen **NU**
Je bent klaar om de eerste module te laten genereren
- **!** Na stappen 1-6 ben je klaar. Onderstaande prompt kun je letterlijk in Claude Code plakken.
- ☐ **Start Claude Code vanuit je project root**
```sh
cd /pad/naar/crewli
claude
```
- ☐ **Plak deze prompt als eerste bericht:**
```
Lees eerst volledig /CLAUDE.md en /dev-docs/SCHEMA.md.
Bouw daarna Fase 1 — Foundation — in deze volgorde:
1. Migrations in api/database/migrations/:
- update users tabel (voeg timezone, locale, deleted_at toe)
- organisations (ULID, name, slug, billing_status, settings JSON, deleted_at)
- organisation_user pivot (int PK, user_id, organisation_id, role)
- user_invitations (ULID, email, invited_by, organisation_id, event_id nullable,
token ULID unique, status enum, expires_at)
- events (ULID, organisation_id, name, slug, start_date, end_date,
timezone, status enum, deleted_at)
- event_user_roles pivot
2. Models: User (update), Organisation, UserInvitation, Event
- HasUlids op alle business modellen
- SoftDeletes op Organisation, Event
- OrganisationScope global scope op Event
- Alle relaties (hasMany, belongsToMany)
3. Spatie Permission setup:
- RoleSeeder: super_admin, org_admin, org_member,
event_manager, staff_coordinator, volunteer_coordinator
4. Auth: LoginController, LogoutController, MeController
- Sanctum token-based (geen session)
- MeController geeft user + organisations + active event roles terug
5. Organisations: OrganisationController (index/show/store/update)
- OrganisationPolicy
- OrganisationRequest (store + update)
- OrganisationResource
6. Events: EventController (index/show/store/update) genest onder organisations
- EventPolicy
- EventRequest
- EventResource
7. Feature tests voor alles bovenstaande:
- Happy path (200/201)
- Unauthenticated (401)
- Wrong organisation (403)
Draai na elke stap: php artisan test
Los fouten op voor je verdergaat.
Stop na stap 7 en rapporteer wat er gebouwd is en of alle tests groen zijn.
```
## 3 — Daarna: Frontend Fase 1 + Fase 2 Planning
### Stap 8 — Frontend Fase 1 — Auth + Shell **DAARNA**
Na groene backend tests
- Auth flow bouwen in apps/app/
- stores/useAuthStore.ts — token opslaan, isAuthenticated, me() laden
- pages/login.vue — Vuexy login layout gebruiken (al aanwezig als basis)
- router guard — redirect naar login als niet authenticated
- Navigatiestructuur Crewli invullen
- src/navigation/vertical/index.ts — verwijder Vuexy demo-items, voeg Crewli items toe
- Events → Sections → Shifts, Persons, Artists, Briefings, Rapportage
- CASL permissions setup
- src/plugins/casl.ts koppelen aan Spatie roles vanuit auth/me response
- useAbility() gebruiken voor conditionele UI-elementen (aanpassen-knop tonen/verbergen)
### Stap 9 — Fase 2 — Core module volgorde **DAARNA**
Na werkende auth en shell
- **Bouw in deze volgorde — altijd eerst backend, dan frontend:**
1. Crowd Types + Persons + Crowd Lists (basis guest management)
2. Festival Sections + Time Slots + Shifts (het hart van het platform)
3. Shift Assignments — claim workflow met approval flow
4. Vrijwilligers registratie (public form → portal login flow)
5. Accreditatie engine + Access Zones
6. Basis briefings (template + send + track)
- **!** Elke module: gebruik de module-prompt uit Dev Guide sectie 5.2. Altijd: migrations → model → factory → policy → resource → controller → test → composable → pagina.
### Stap 10 — Portal app inrichten **DAARNA**
Na werkende apps/app/ basis
- apps/portal/ strippen tot portal-layout
- Verwijder: sidebar nav, customizer, dark mode toggle, demo-dialogen
- Maak: PortalLayout.vue — top-bar met event-logo, naam, hamburger menu
- Maak: twee router guards — loginGuard en tokenGuard
- Login-flow (vrijwilligers/crew): zelfde /api/v1/auth/login endpoint als app/
- Token-flow (artiesten/leveranciers): POST /api/v1/portal/token-auth
- Routing op basis van accessMode computed + person.crowd_type
## 4 — Checklist: Ben Je Klaar om te Starten?
| **#** | **Actie** | **Status** |
|:--:|----|:--:|
| **1** | apps/band/ hernoemd naar apps/portal/ | ☐ Klaar |
| **2** | Demo-rommel verwijderd uit apps/app/ | ☐ Klaar |
| **3** | Dubbele API-laag opgeruimd → een src/lib/axios.ts per app | ☐ Klaar |
| **4** | TanStack Query geinstalleerd in alle drie apps + geregistreerd in main.ts | ☐ Klaar |
| **5** | VeeValidate + Zod geinstalleerd in alle drie apps | ☐ Klaar |
| **6** | Spatie packages geinstalleerd in api/ + configs gepubliceerd | ☐ Klaar |
| **7** | PortalTokenMiddleware skeleton aangemaakt | ☐ Klaar |
| **8** | config/cors.php: drie frontend origins geconfigureerd | ☐ Klaar |
| **9** | Vite dev ports: admin=5173, app=5174, portal=5175 | ☐ Klaar |
| **10** | .env.local aangemaakt per app met VITE_API_URL | ☐ Klaar |
| **11** | CLAUDE.md aangemaakt en volledig ingevuld (Dev Guide sectie 3.1 + portal architectuur) | ☐ Klaar |
| **12** | .cursorrules aangemaakt (Dev Guide sectie 3.2) | ☐ Klaar |
| **13** | docs/SCHEMA.md aangemaakt (schema uit Design Document v1.3 sectie 3.5) | ☐ Klaar |
| **14** | docs/API.md aangemaakt met initiele route-lijst | ☐ Klaar |
| **15** | claude (Claude Code) gestart en Fase 1 prompt ingevoerd | ☐ Klaar |
| **16** | php artisan test — alle tests groen | ☐ Klaar |
Crewli Start Guide v1.0 — Maart 2026 | Architectuur + Actielijst