**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 /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