chore: align migrations, docs, and frontends with crewli.app setup
- Replace dated migrations with ordered 2026_04_07_* chain; fold users update into base migration - Update OrganisationScope, AppServiceProvider, seeders, api routes, and .env.example - Refresh Cursor rules, CLAUDE.md, Makefile, README, and docs (API, SCHEMA, SETUP) - Adjust admin/app/portal HTML, packages, api-client, events types, and theme config - Update docker-compose and VS Code settings; remove stray Office lock files from resources Made-with: Cursor
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# EventCrew - Architecture
|
||||
# Crewli - Architecture
|
||||
|
||||
> Multi-tenant SaaS platform for event- and festival management.
|
||||
> Source of truth: `/resources/design/EventCrew_Design_Document_v1.3.docx`
|
||||
> Source of truth: `/resources/design/Crewli_Design_Document_v1.3.docx`
|
||||
|
||||
## System Overview
|
||||
|
||||
@@ -432,6 +432,8 @@ Three frontend origins in `config/cors.php` (via env):
|
||||
| App | `http://localhost:5174` | `FRONTEND_APP_URL` |
|
||||
| Portal | `http://localhost:5175` | `FRONTEND_PORTAL_URL` |
|
||||
|
||||
Production (registered domain **crewli.app**): API `https://api.crewli.app` (`APP_URL`); SPAs `https://admin.crewli.app`, `https://app.crewli.app`, `https://portal.crewli.app` via the same env keys. Frontends use `VITE_API_URL=https://api.crewli.app/api/v1`. `SANCTUM_STATEFUL_DOMAINS` = comma-separated SPA hostnames only (e.g. `admin.crewli.app,app.crewli.app,portal.crewli.app`). **`crewli.nl`** is reserved for a future marketing site only — not used for this application stack.
|
||||
|
||||
---
|
||||
|
||||
## Real-time Events (WebSocket)
|
||||
@@ -446,4 +448,4 @@ Via Laravel Echo + Pusher/Soketi:
|
||||
|
||||
---
|
||||
|
||||
*Source: EventCrew Design Document v1.3, March 2026*
|
||||
*Source: Crewli Design Document v1.3, March 2026*
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
# EventCrew - Cursor AI Instructions
|
||||
# Crewli - Cursor AI Instructions
|
||||
|
||||
> Multi-tenant SaaS platform for event- and festival management.
|
||||
> Design Document: `/resources/design/EventCrew_Design_Document_v1.3.docx`
|
||||
> Dev Guide: `/resources/design/EventCrew_Dev_Guide_v1.0.docx`
|
||||
> Start Guide: `/resources/design/EventCrew_Start_Guide_v1.0.docx`
|
||||
> Design Document: `/resources/design/Crewli_Design_Document_v1.3.docx`
|
||||
> Dev Guide: `/resources/design/Crewli_Dev_Guide_v1.0.docx`
|
||||
> Start Guide: `/resources/design/Crewli_Start_Guide_v1.0.docx`
|
||||
|
||||
## Project Overview
|
||||
|
||||
**Name**: EventCrew
|
||||
**Name**: Crewli
|
||||
**Type**: Multi-tenant SaaS platform (API-first architecture)
|
||||
**Status**: Development
|
||||
|
||||
### Description
|
||||
|
||||
EventCrew is a multi-tenant SaaS platform for professional event and festival management. It supports the full operational cycle: artist booking and advancing, staff planning and volunteer management, accreditation, briefings, and real-time show-day operations (Mission Control). Built for a professional volunteer organisation, with SaaS expansion potential.
|
||||
Crewli is a multi-tenant SaaS platform for professional event and festival management. It supports the full operational cycle: artist booking and advancing, staff planning and volunteer management, accreditation, briefings, and real-time show-day operations (Mission Control). Built for a professional volunteer organisation, with SaaS expansion potential.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
@@ -130,7 +130,7 @@ Build auth flow in apps/app/:
|
||||
1. stores/useAuthStore.ts - token storage, isAuthenticated, me() loading
|
||||
2. pages/login.vue - use Vuexy login layout
|
||||
3. Router guard - redirect to login if not authenticated
|
||||
4. Replace Vuexy demo navigation with EventCrew structure
|
||||
4. Replace Vuexy demo navigation with Crewli structure
|
||||
5. CASL permissions: connect to Spatie roles from auth/me response
|
||||
```
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
description: Core workspace rules for EventCrew multi-tenant SaaS platform
|
||||
description: Core workspace rules for Crewli multi-tenant SaaS platform
|
||||
globs: ["**/*"]
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Workspace Rules
|
||||
|
||||
You are an expert full-stack developer working on EventCrew, a multi-tenant SaaS platform for event and festival management. The backend is a Laravel 12 REST API (JSON only, no Blade), and three Vue 3 SPA frontends communicate via CORS + Sanctum tokens.
|
||||
You are an expert full-stack developer working on Crewli, a multi-tenant SaaS platform for event and festival management. The backend is a Laravel 12 REST API (JSON only, no Blade), and three Vue 3 SPA frontends communicate via CORS + Sanctum tokens.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
@@ -36,7 +36,7 @@ You are an expert full-stack developer working on EventCrew, a multi-tenant SaaS
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
event-crew/
|
||||
crewli/
|
||||
├── api/ # Laravel 12 REST API (JSON only)
|
||||
│ ├── app/
|
||||
│ │ ├── Http/
|
||||
@@ -130,8 +130,19 @@ event-crew/
|
||||
| Redis | `localhost:6379` | - |
|
||||
| Mailpit | `http://localhost:8025` | - |
|
||||
|
||||
### Production URLs (crewli.app)
|
||||
|
||||
**Note:** `crewli.app` = this SaaS product. `crewli.nl` is for a future public marketing site only — never use `.nl` for API, SPAs, or app-originated mail in this project.
|
||||
|
||||
| Service | URL | Env variable |
|
||||
|---------|-----|--------------|
|
||||
| API | `https://api.crewli.app` | `APP_URL` |
|
||||
| Admin SPA | `https://admin.crewli.app` | `FRONTEND_ADMIN_URL` |
|
||||
| Organizer SPA | `https://app.crewli.app` | `FRONTEND_APP_URL` |
|
||||
| Portal SPA | `https://portal.crewli.app` | `FRONTEND_PORTAL_URL` |
|
||||
|
||||
### CORS
|
||||
Three frontend origins configured in `config/cors.php` via env variables. Each Vite dev server gets its own port for CORS isolation.
|
||||
Three frontend origins configured in `config/cors.php` via env variables. Each Vite dev server gets its own port for CORS isolation. In production, set the same env vars to the `https://…` origins above (see `api/.env.example`).
|
||||
|
||||
## Git Conventions
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
description: Laravel API development guidelines for EventCrew multi-tenant platform
|
||||
description: Laravel API development guidelines for Crewli multi-tenant platform
|
||||
globs: ["api/**/*.php"]
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
description: Vue 3, TypeScript, and Vuexy patterns for EventCrew platform
|
||||
description: Vue 3, TypeScript, and Vuexy patterns for Crewli platform
|
||||
globs: ["apps/**/*.{vue,ts,tsx}"]
|
||||
alwaysApply: true
|
||||
---
|
||||
@@ -23,7 +23,7 @@ alwaysApply: true
|
||||
- Minimal modifications needed
|
||||
|
||||
### `apps/app/` (Organizer - Main App)
|
||||
- Sidebar nav customized for EventCrew structure
|
||||
- Sidebar nav customized for Crewli structure
|
||||
- Remove Vuexy demo/customizer components
|
||||
- Full Vuetify component usage
|
||||
- 90% of development work happens here
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
description: Multi-tenancy and portal architecture rules for EventCrew
|
||||
description: Multi-tenancy and portal architecture rules for Crewli
|
||||
globs: ["api/**/*.php", "apps/portal/**/*.{vue,ts}"]
|
||||
alwaysApply: true
|
||||
---
|
||||
@@ -101,7 +101,7 @@ Route::middleware(['auth:sanctum', 'event.role:event_manager'])->group(...);
|
||||
|
||||
### Token-Based Authentication Flow
|
||||
```
|
||||
1. Artist/supplier receives email with link: https://portal.eventcrew.app/advance?token=01HQ3K...
|
||||
1. Artist/supplier receives email with link: https://portal.crewli.app/advance?token=01HQ3K...
|
||||
2. Portal detects token in URL query parameter
|
||||
3. POST /api/v1/portal/token-auth { token: '01HQ3K...' }
|
||||
4. Backend validates token against artists.portal_token or production_requests.token
|
||||
@@ -111,7 +111,7 @@ Route::middleware(['auth:sanctum', 'event.role:event_manager'])->group(...);
|
||||
|
||||
### Login-Based Authentication Flow
|
||||
```
|
||||
1. Volunteer navigates to https://portal.eventcrew.app/login
|
||||
1. Volunteer navigates to https://portal.crewli.app/login
|
||||
2. Enters email + password
|
||||
3. POST /api/v1/auth/login (same endpoint as apps/app/)
|
||||
4. Returns user + organisations + event roles
|
||||
@@ -196,6 +196,8 @@ class PortalTokenMiddleware
|
||||
'supports_credentials' => true,
|
||||
```
|
||||
|
||||
Production example (subdomains on **crewli.app**): `FRONTEND_ADMIN_URL=https://admin.crewli.app`, `FRONTEND_APP_URL=https://app.crewli.app`, `FRONTEND_PORTAL_URL=https://portal.crewli.app`, and `SANCTUM_STATEFUL_DOMAINS=admin.crewli.app,app.crewli.app,portal.crewli.app`.
|
||||
|
||||
## Shift Claiming & Approval Flow
|
||||
|
||||
### Three Assignment Strategies per Shift
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
description: Database schema conventions, ULID primary keys, JSON column rules, soft delete strategy, and index requirements for EventCrew
|
||||
description: Database schema conventions, ULID primary keys, JSON column rules, soft delete strategy, and index requirements for Crewli
|
||||
globs: ["api/database/**/*.php", "api/app/Models/**/*.php"]
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
description: Testing standards for EventCrew multi-tenant platform
|
||||
description: Testing standards for Crewli multi-tenant platform
|
||||
globs: ["**/tests/**", "**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/Test.php"]
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
32
.cursorrules
32
.cursorrules
@@ -1,21 +1,21 @@
|
||||
# EventCrew Cursor Rules
|
||||
|
||||
# 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
|
||||
|
||||
- Resource controllers, form requests, API resources — always
|
||||
- `HasUlids` on business models, `HasFactory`, `SoftDeletes` where documented
|
||||
- Global scope `OrganisationScope` on event-related models
|
||||
- Policies for authorization — never 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
|
||||
|
||||
- `<script setup lang="ts">` always
|
||||
- TanStack Query for API state, Pinia for UI state
|
||||
- Vuetify components first; custom CSS only as a last resort
|
||||
|
||||
## Naming
|
||||
- snake_case DB | camelCase JS | PascalCase Vue | `use*` composables | `use*Store` Pinia
|
||||
|
||||
## Tests
|
||||
- PHPUnit Feature Test per controller, minimaal: 200 + 401 + 403
|
||||
- PHPUnit feature test per controller, minimum: 200 + 401 + 403
|
||||
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -31,7 +31,7 @@
|
||||
|
||||
"eslint.workingDirectories": [
|
||||
{ "directory": "apps/admin", "changeProcessCWD": true },
|
||||
{ "directory": "apps/band", "changeProcessCWD": true },
|
||||
{ "directory": "apps/customers", "changeProcessCWD": true }
|
||||
{ "directory": "apps/app", "changeProcessCWD": true },
|
||||
{ "directory": "apps/portal", "changeProcessCWD": true }
|
||||
]
|
||||
}
|
||||
|
||||
171
CLAUDE.md
171
CLAUDE.md
@@ -1,129 +1,132 @@
|
||||
# EventCrew — Claude Code Instructies
|
||||
# Crewli — Claude Code Instructions
|
||||
|
||||
## Project Context
|
||||
## Project context
|
||||
|
||||
EventCrew is een multi-tenant SaaS platform voor event- en festivalbeheer.
|
||||
Gebouwd voor een professionele vrijwilligersorganisatie, met SaaS-uitbreidingspotentieel.
|
||||
Design Document: /docs/EventCrew_Design_Document_v1.3.docx
|
||||
Crewli is a multi-tenant SaaS platform for event and festival management.
|
||||
Built for a professional volunteer organisation, with potential to expand as SaaS.
|
||||
Design document: `/resources/design/Crewli_Design_Document_v1.3.docx`
|
||||
|
||||
## Tech Stack
|
||||
## 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
|
||||
## Repository layout
|
||||
|
||||
- api/ Laravel backend
|
||||
- apps/admin/ Super Admin SPA
|
||||
- apps/app/ Organizer SPA (hoofdapp)
|
||||
- apps/portal/ Externe portals (vrijwilliger, artiest, leverancier)
|
||||
- `api/` — Laravel backend
|
||||
- `apps/admin/` — Super Admin SPA
|
||||
- `apps/app/` — Organizer SPA (main product app)
|
||||
- `apps/portal/` — External portal (volunteers, artists, suppliers, etc.)
|
||||
|
||||
## Apps & Portal Architectuur
|
||||
## Apps and portal architecture
|
||||
|
||||
- apps/admin/ = Super Admin — platformbeheer, organisaties aanmaken
|
||||
- apps/app/ = Organizer — event management per organisatie
|
||||
- apps/portal/ = Externe gebruikers — één 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/admin/` — Super Admin: platform management, creating organisations
|
||||
- `apps/app/` — Organizer: event management per organisation
|
||||
- `apps/portal/` — External users: one app, two access modes:
|
||||
- Login-based (`auth:sanctum`): volunteers, crew — persons with `user_id`
|
||||
- Token-based (`portal.token` middleware): artists, suppliers, press — persons without `user_id`
|
||||
|
||||
### CORS
|
||||
|
||||
Drie frontend origins configureren in zowel Laravel (config/cors.php via env) als Vite dev server proxy:
|
||||
- admin: localhost:5173
|
||||
- app: localhost:5174
|
||||
- portal: localhost:5175
|
||||
Configure three frontend origins in both Laravel (`config/cors.php` via env) and the Vite dev server proxy:
|
||||
|
||||
## Backend Regels (STRIKT VOLGEN)
|
||||
- admin: `localhost:5173`
|
||||
- app: `localhost:5174`
|
||||
- portal: `localhost:5175`
|
||||
|
||||
**Production (`crewli.app`):** API `https://api.crewli.app`, SPAs `https://admin.crewli.app`, `https://app.crewli.app`, `https://portal.crewli.app` — see `api/.env.example` for `FRONTEND_*` and `SANCTUM_STATEFUL_DOMAINS`. **`crewli.nl`** is only for a future marketing site; this application stack uses **`crewli.app`** (not `.nl` for API, SPAs, or transactional mail).
|
||||
|
||||
## Backend rules (strict)
|
||||
|
||||
### 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
|
||||
- Every query on event data **must** scope on `organisation_id`
|
||||
- Use `OrganisationScope` as an Eloquent global scope on all event-related models
|
||||
- Never use raw ID checks in controllers — always use 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())
|
||||
- Use resource controllers (`index` / `show` / `store` / `update` / `destroy`)
|
||||
- Namespace: `App\Http\Controllers\Api\V1\`
|
||||
- Return all responses through API resources (never return raw model attributes)
|
||||
- Validate with form requests (never inline `validate()`)
|
||||
|
||||
### Modellen
|
||||
### Models
|
||||
|
||||
- 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
|
||||
- Use the `HasUlids` trait on all business models (not UUID v4)
|
||||
- Soft deletes on: Organisation, Event, FestivalSection, Shift, ShiftAssignment, Person, Artist
|
||||
- No soft deletes on: CheckIn, BriefingSend, MessageReply, ShiftWaitlist (audit records)
|
||||
- JSON columns **only** for opaque configuration — never for queryable/filterable 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
|
||||
- Primary keys: ULID via `HasUlids` (not UUID v4, not auto-increment on business tables)
|
||||
- Create migrations in dependency order: foundation first, then dependent tables
|
||||
- Always add composite indexes as documented in the design document (section 3.5)
|
||||
|
||||
### Rollen & Permissies
|
||||
### Roles and permissions
|
||||
|
||||
- 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.)
|
||||
- Use Spatie `laravel-permission`
|
||||
- Check roles via `$user->hasRole()` and policies — never hard-code role strings in controllers
|
||||
- Three levels: app (`super_admin`), organisation (`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
|
||||
- Write PHPUnit feature tests per controller
|
||||
- Minimum per endpoint: happy path + unauthenticated (401) + wrong organisation (403)
|
||||
- Use factories for all test data
|
||||
- After each module: `php artisan test --filter=ModuleName`
|
||||
|
||||
## Frontend Regels (STRIKT VOLGEN)
|
||||
## Frontend rules (strict)
|
||||
|
||||
### Vue Componenten
|
||||
### Vue components
|
||||
|
||||
- Altijd <script setup lang='ts'> — nooit Options API
|
||||
- Props altijd getypeerd met defineProps<{...}>()
|
||||
- Emits altijd gedeclareerd met defineEmits<{...}>()
|
||||
- Always `<script setup lang="ts">` — never the Options API
|
||||
- Type props with `defineProps<{...}>()`
|
||||
- Declare emits with `defineEmits<{...}>()`
|
||||
|
||||
### API Calls
|
||||
### 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
|
||||
- Use TanStack Query (`useQuery` / `useMutation`) for **all** API calls
|
||||
- Never call axios directly from a component — always via a composable under `composables/`
|
||||
- Use Pinia stores for cross-component state — no prop drilling
|
||||
|
||||
### Naamgeving
|
||||
### Naming
|
||||
|
||||
- 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)
|
||||
- DB columns: `snake_case`
|
||||
- TypeScript / JS variables: `camelCase`
|
||||
- Vue components: PascalCase (e.g. `ShiftAssignPanel.vue`)
|
||||
- Composables: `use` prefix (e.g. `useShifts.ts`)
|
||||
- Pinia stores: `use` prefix + `Store` suffix (e.g. `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
|
||||
- Always use Vuexy/Vuetify for layout, forms, tables, dialogs
|
||||
- Do not write custom CSS when a Vuetify utility class exists
|
||||
- Responsive: mobile-first, usable from 375px width
|
||||
|
||||
## Verboden Patronen
|
||||
## Forbidden patterns
|
||||
|
||||
- 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)
|
||||
- Never: `$user->role === 'admin'` (use policies)
|
||||
- Never: `Model::all()` without a `where` clause (always scope)
|
||||
- Never: leave `dd()` or `var_dump()` in code
|
||||
- Never: hard-code `.env` values in code
|
||||
- Never: use JSON columns for data you need to filter on
|
||||
- Never: UUID v4 as primary key (use `HasUlids`)
|
||||
|
||||
## Volgorde bij elke nieuwe module
|
||||
## Order of work for each new 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
|
||||
1. Create and run migration(s)
|
||||
2. Eloquent model with relationships, scopes, and `HasUlids`
|
||||
3. Factory for test data
|
||||
4. Policy for authorization
|
||||
5. Form request(s) for validation
|
||||
6. API resource for response shaping
|
||||
7. Resource controller
|
||||
8. Register routes in `api.php`
|
||||
9. Write and run PHPUnit feature tests
|
||||
10. Vue composable for API calls (e.g. `useShifts.ts`)
|
||||
11. Pinia store if cross-component state is needed
|
||||
12. Vue page component
|
||||
13. Add route in Vue Router
|
||||
|
||||
6
Makefile
6
Makefile
@@ -9,7 +9,7 @@ NC := \033[0m
|
||||
help:
|
||||
@echo ""
|
||||
@echo "$(GREEN)╔══════════════════════════════════════════════════════════════╗$(NC)"
|
||||
@echo "$(GREEN)║ EVENT CREW - Development Commands ║$(NC)"
|
||||
@echo "$(GREEN)║ CREWLI - Development Commands ║$(NC)"
|
||||
@echo "$(GREEN)╚══════════════════════════════════════════════════════════════╝$(NC)"
|
||||
@echo ""
|
||||
@echo " $(YELLOW)Services (Docker):$(NC)"
|
||||
@@ -33,7 +33,7 @@ services:
|
||||
@docker compose up -d
|
||||
@echo ""
|
||||
@echo "$(GREEN)Services:$(NC)"
|
||||
@echo " $(CYAN)MySQL:$(NC) localhost:3306 (event_crew / secret)"
|
||||
@echo " $(CYAN)MySQL:$(NC) localhost:3306 (crewli / secret)"
|
||||
@echo " $(CYAN)Redis:$(NC) localhost:6379"
|
||||
@echo " $(CYAN)Mailpit:$(NC) http://localhost:8025"
|
||||
@echo ""
|
||||
@@ -68,4 +68,4 @@ fresh:
|
||||
@cd api && php artisan migrate:fresh --seed
|
||||
|
||||
db-shell:
|
||||
@docker exec -it bm_mysql mysql -u event_crew -psecret event_crew
|
||||
@docker exec -it bm_mysql mysql -u crewli -psecret crewli
|
||||
|
||||
23
README.md
23
README.md
@@ -1,10 +1,10 @@
|
||||
# EventCrew
|
||||
# Crewli
|
||||
|
||||
Multi-tenant SaaS platform for **event and festival operations**: planning, people, accreditation, artist advancing, volunteer shifts, briefings, and show-day tooling (Mission Control). The backend is a **JSON-only** Laravel API; all user interfaces are **Vue 3** single-page apps.
|
||||
|
||||
---
|
||||
|
||||
## What EventCrew covers
|
||||
## What Crewli covers
|
||||
|
||||
- **Organisations & events** — Multi-tenant data with organisation-scoped access; events move through a defined lifecycle (draft → published → registration → buildup → show day → teardown → closed).
|
||||
- **Festival structure** — Sections, time slots, shifts, assignments, claiming/approval flows for volunteers and crew.
|
||||
@@ -45,7 +45,7 @@ All apps talk to the API over **CORS** with **Laravel Sanctum** tokens.
|
||||
## Project structure
|
||||
|
||||
```
|
||||
event-crew/
|
||||
crewli/
|
||||
├── api/ # Laravel 12 REST API (JSON only)
|
||||
├── apps/
|
||||
│ ├── admin/ # Super Admin SPA
|
||||
@@ -94,7 +94,20 @@ Detailed setup: **[docs/SETUP.md](docs/SETUP.md)**.
|
||||
| Portal | http://localhost:5175 | `FRONTEND_PORTAL_URL` |
|
||||
| Mailpit | http://localhost:8025 | Local mail capture |
|
||||
|
||||
Production hostnames are placeholders until you deploy; configure `config/cors.php` via `.env`.
|
||||
### Production (crewli.app)
|
||||
|
||||
**Domains:** **`crewli.app`** is this product (API + organizer/admin/portal SPAs, transactional email from the app, seeds, etc.). **`crewli.nl`** is reserved for a future **public marketing site** only — do not point this codebase’s `APP_URL`, CORS, Sanctum, or app mail at `crewli.nl`.
|
||||
|
||||
Typical layout (configure the same values in `api/.env` — see `api/.env.example`):
|
||||
|
||||
| Service | URL | Env variable |
|
||||
|---------|-----|----------------|
|
||||
| API | `https://api.crewli.app` | `APP_URL` |
|
||||
| Admin | `https://admin.crewli.app` | `FRONTEND_ADMIN_URL` |
|
||||
| Organizer | `https://app.crewli.app` | `FRONTEND_APP_URL` |
|
||||
| Portal | `https://portal.crewli.app` | `FRONTEND_PORTAL_URL` |
|
||||
|
||||
Frontends: set `VITE_API_URL=https://api.crewli.app/api/v1` in each app’s env for production builds. `SANCTUM_STATEFUL_DOMAINS` must list the **hostnames only** of the three SPAs (e.g. `admin.crewli.app,app.crewli.app,portal.crewli.app`).
|
||||
|
||||
---
|
||||
|
||||
@@ -118,7 +131,7 @@ make db-shell
|
||||
|
||||
| Resource | Contents |
|
||||
|----------|----------|
|
||||
| [resources/design/](resources/design/) | **Canonical product specs** — place design documents here (often Word). Referenced by `.cursor` as source of truth for features and data model. Expected names include `EventCrew_Design_Document_v1.3.docx`, `EventCrew_Dev_Guide_v1.0.docx`, `EventCrew_Start_Guide_v1.0.docx` (adjust versions if you update files). |
|
||||
| [resources/design/](resources/design/) | **Canonical product specs** — place design documents here (often Word). Referenced by `.cursor` as source of truth for features and data model. Expected names include `Crewli_Design_Document_v1.3.docx`, `Crewli_Dev_Guide_v1.0.docx`, `Crewli_Start_Guide_v1.0.docx` (adjust versions if you update files). |
|
||||
| [.cursor/ARCHITECTURE.md](.cursor/ARCHITECTURE.md) | System diagram, apps, multi-tenancy, roles, event lifecycle, API route map, core schema overview (summarises `resources/design` when present) |
|
||||
| [.cursor/instructions.md](.cursor/instructions.md) | Quick reference, phased roadmap, module build order |
|
||||
| [.cursor/rules/](.cursor/rules/) | Workspace, Laravel, Vue, testing conventions |
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
APP_NAME="Event Crew"
|
||||
APP_NAME="Crewli"
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
# Local API origin (no path suffix). Production: https://api.crewli.app
|
||||
APP_URL=http://localhost:8000
|
||||
|
||||
APP_LOCALE=en
|
||||
@@ -20,8 +21,8 @@ LOG_LEVEL=debug
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=event_crew
|
||||
DB_USERNAME=event_crew
|
||||
DB_DATABASE=crewli
|
||||
DB_USERNAME=crewli
|
||||
DB_PASSWORD=secret
|
||||
|
||||
SESSION_DRIVER=database
|
||||
@@ -47,11 +48,19 @@ MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="noreply@eventcrew.nl"
|
||||
# App / transactional mail: use crewli.app. (crewli.nl = future marketing site only, not this stack.)
|
||||
MAIL_FROM_ADDRESS="noreply@crewli.app"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
# CORS - Frontend SPAs
|
||||
FRONTEND_URL=http://localhost:5173
|
||||
# CORS + Sanctum — SPA origins (no trailing slash; must match the browser URL)
|
||||
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
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
# --- Production (crewli.app) — uncomment and adjust hostnames if you use this layout:
|
||||
# APP_URL=https://api.crewli.app
|
||||
# FRONTEND_ADMIN_URL=https://admin.crewli.app
|
||||
# FRONTEND_APP_URL=https://app.crewli.app
|
||||
# FRONTEND_PORTAL_URL=https://portal.crewli.app
|
||||
# SANCTUM_STATEFUL_DOMAINS=admin.crewli.app,app.crewli.app,portal.crewli.app
|
||||
|
||||
@@ -8,6 +8,12 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
|
||||
/**
|
||||
* Explicit organisation filter for queries ({@see Builder::withGlobalScope()}).
|
||||
* Event data is currently scoped via the {@see Organisation} relationship and policies;
|
||||
* when you add child models (persons, shifts, …), consider a global scope driven by
|
||||
* the authenticated user’s active organisation (see `.cursor/rules/102_multi_tenancy.mdc`).
|
||||
*/
|
||||
final class OrganisationScope implements Scope
|
||||
{
|
||||
public function __construct(
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'name' => env('APP_NAME', 'Laravel'),
|
||||
'name' => env('APP_NAME', 'Crewli'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -14,19 +14,14 @@ return new class extends Migration
|
||||
$table->ulid('id')->primary();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->string('phone', 20)->nullable();
|
||||
$table->text('bio')->nullable();
|
||||
$table->json('instruments')->nullable();
|
||||
$table->string('avatar_path')->nullable();
|
||||
$table->enum('type', ['member', 'customer'])->default('member');
|
||||
$table->enum('role', ['admin', 'booking_agent', 'music_manager', 'member'])->nullable();
|
||||
$table->enum('status', ['active', 'inactive'])->default('active');
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->string('timezone')->default('Europe/Amsterdam');
|
||||
$table->string('locale')->default('nl');
|
||||
$table->string('avatar')->nullable();
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['type', 'status']);
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
// Remove old band-management columns
|
||||
$table->dropIndex(['type', 'status']);
|
||||
$table->dropColumn(['phone', 'bio', 'instruments', 'avatar_path', 'type', 'role', 'status']);
|
||||
});
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
// Add EventCrew columns per SCHEMA.md
|
||||
$table->string('timezone')->default('Europe/Amsterdam')->after('password');
|
||||
$table->string('locale')->default('nl')->after('timezone');
|
||||
$table->string('avatar')->nullable()->after('locale');
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
$table->dropColumn(['timezone', 'locale', 'avatar']);
|
||||
});
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('phone', 20)->nullable();
|
||||
$table->text('bio')->nullable();
|
||||
$table->json('instruments')->nullable();
|
||||
$table->string('avatar_path')->nullable();
|
||||
$table->enum('type', ['member', 'customer'])->default('member');
|
||||
$table->enum('role', ['admin', 'booking_agent', 'music_manager', 'member'])->nullable();
|
||||
$table->enum('status', ['active', 'inactive'])->default('active');
|
||||
$table->index(['type', 'status']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@@ -17,7 +17,7 @@ class DatabaseSeeder extends Seeder
|
||||
|
||||
$admin = User::create([
|
||||
'name' => 'Super Admin',
|
||||
'email' => 'admin@eventcrew.nl',
|
||||
'email' => 'admin@crewli.app',
|
||||
'password' => Hash::make('password'),
|
||||
]);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ use Illuminate\Support\Facades\Route;
|
||||
// Health check
|
||||
Route::get('/', fn () => response()->json([
|
||||
'success' => true,
|
||||
'message' => 'EventCrew API v1',
|
||||
'message' => 'Crewli API v1',
|
||||
'timestamp' => now()->toIso8601String(),
|
||||
]));
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ pnpm install
|
||||
|
||||
```env
|
||||
VITE_API_URL=http://localhost:8000/api/v1
|
||||
VITE_APP_NAME="Event Crew Admin"
|
||||
VITE_APP_NAME="Crewli Admin"
|
||||
```
|
||||
|
||||
4. Start development:
|
||||
@@ -31,4 +31,6 @@ pnpm dev
|
||||
|
||||
## Port
|
||||
|
||||
Runs on http://localhost:5173 (Vite default)
|
||||
Runs on http://localhost:5173 (Vite default).
|
||||
|
||||
**Production:** point `VITE_API_URL` at your API, e.g. `https://api.crewli.app/api/v1`, with DNS/TLS for `admin.crewli.app` (and matching Laravel `FRONTEND_ADMIN_URL` / CORS / Sanctum settings — see repo `README.md` and `api/.env.example`).
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Event Crew Admin</title>
|
||||
<title>Crewli Admin</title>
|
||||
<link rel="stylesheet" type="text/css" href="/loader.css" />
|
||||
</head>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "eventcrew-admin",
|
||||
"name": "crewli-admin",
|
||||
"version": "9.5.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios'
|
||||
/**
|
||||
* Single axios instance for the real Laravel API (VITE_API_URL).
|
||||
* Auth: Bearer token from cookie 'accessToken' (set by login).
|
||||
* Use this for all event-crew API calls; useApi (composables/useApi) stays for Vuexy demo/mock endpoints.
|
||||
* Use this for all Crewli API calls; useApi (composables/useApi) stays for Vuexy demo/mock endpoints.
|
||||
*/
|
||||
const apiClient: AxiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useEvents } from '@/composables/useEvents'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import type { EventCrewEventStatus, UpdateEventData } from '@/types/events'
|
||||
import type { CrewliEventStatus, UpdateEventData } from '@/types/events'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
@@ -17,7 +17,7 @@ const eventId = computed(() => route.params.id as string)
|
||||
|
||||
const formData = ref<UpdateEventData>({})
|
||||
|
||||
const statusOptions: { title: string; value: EventCrewEventStatus }[] = [
|
||||
const statusOptions: { title: string; value: CrewliEventStatus }[] = [
|
||||
{ title: 'Draft', value: 'draft' },
|
||||
{ title: 'Published', value: 'published' },
|
||||
{ title: 'Registration open', value: 'registration_open' },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useEvents } from '@/composables/useEvents'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { CreateEventData, EventCrewEventStatus } from '@/types/events'
|
||||
import type { CreateEventData, CrewliEventStatus } from '@/types/events'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
@@ -21,7 +21,7 @@ const formData = ref<CreateEventData>({
|
||||
status: 'draft',
|
||||
})
|
||||
|
||||
const statusOptions: { title: string; value: EventCrewEventStatus }[] = [
|
||||
const statusOptions: { title: string; value: CrewliEventStatus }[] = [
|
||||
{ title: 'Draft', value: 'draft' },
|
||||
{ title: 'Published', value: 'published' },
|
||||
{ title: 'Registration open', value: 'registration_open' },
|
||||
|
||||
@@ -17,8 +17,8 @@ export interface Pagination {
|
||||
to: number | null
|
||||
}
|
||||
|
||||
/** EventCrew festival / multi-day event (API resource). */
|
||||
export type EventCrewEventStatus =
|
||||
/** Crewli festival / multi-day event (API resource). */
|
||||
export type CrewliEventStatus =
|
||||
| 'draft'
|
||||
| 'published'
|
||||
| 'registration_open'
|
||||
@@ -41,7 +41,7 @@ export interface Event {
|
||||
start_date: string
|
||||
end_date: string
|
||||
timezone: string
|
||||
status: EventCrewEventStatus
|
||||
status: CrewliEventStatus
|
||||
created_at: string
|
||||
updated_at: string
|
||||
organisation?: OrganisationSummary
|
||||
@@ -53,7 +53,7 @@ export interface CreateEventData {
|
||||
start_date: string
|
||||
end_date: string
|
||||
timezone?: string
|
||||
status?: EventCrewEventStatus
|
||||
status?: CrewliEventStatus
|
||||
}
|
||||
|
||||
export interface UpdateEventData extends Partial<CreateEventData> {}
|
||||
|
||||
@@ -1,40 +1,23 @@
|
||||
# Band Portal
|
||||
# Crewli — Organizer SPA
|
||||
|
||||
This folder will contain the Band Member Portal using Vuexy Vue.
|
||||
Main product UI for organisation and event staff (Vue 3 + Vuexy + Vuetify). Lives in this repo; only re-copy from Vuexy when upgrading the template.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Copy Vuexy Vue **starter-kit** (TypeScript) here:
|
||||
|
||||
```bash
|
||||
cp -r /path/to/vuexy/typescript-version/starter-kit/* .
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. Create `.env.local`:
|
||||
2. Create `.env.local`:
|
||||
|
||||
```env
|
||||
VITE_API_URL=http://localhost:8000/api/v1
|
||||
VITE_APP_NAME="Band Portal"
|
||||
VITE_APP_NAME="Crewli Organizer"
|
||||
```
|
||||
|
||||
4. Update `vite.config.ts` to use port 5174:
|
||||
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
// ... existing config
|
||||
server: {
|
||||
port: 5174,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
5. Start development:
|
||||
3. Dev server uses port **5174** (see `vite.config.ts` or run from repo root: `make app`).
|
||||
|
||||
```bash
|
||||
pnpm dev --port 5174
|
||||
@@ -43,3 +26,5 @@ pnpm dev --port 5174
|
||||
## Port
|
||||
|
||||
Runs on http://localhost:5174
|
||||
|
||||
**Production:** e.g. `VITE_API_URL=https://api.crewli.app/api/v1` and host the SPA at `https://app.crewli.app` (see `api/.env.example` for `FRONTEND_APP_URL` and `SANCTUM_STATEFUL_DOMAINS`).
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Event Crew - App</title>
|
||||
<title>Crewli — Organizer</title>
|
||||
<link rel="stylesheet" type="text/css" href="/loader.css" />
|
||||
</head>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "eventcrew-app",
|
||||
"name": "crewli-app",
|
||||
"version": "9.5.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -2,6 +2,11 @@ import axios from 'axios'
|
||||
import { parse } from 'cookie-es'
|
||||
import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios'
|
||||
|
||||
/**
|
||||
* Single axios instance for the Laravel API (`VITE_API_URL`, e.g. …/api/v1).
|
||||
* Auth: Bearer token from cookie `accessToken` (set by login).
|
||||
* Use composables built on this client for real API calls; Vuexy `useApi` remains for demos/mocks.
|
||||
*/
|
||||
const apiClient: AxiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL,
|
||||
headers: {
|
||||
@@ -14,9 +19,7 @@ const apiClient: AxiosInstance = axios.create({
|
||||
function getAccessToken(): string | null {
|
||||
if (typeof document === 'undefined') return null
|
||||
const cookies = parse(document.cookie)
|
||||
const token = cookies.accessToken
|
||||
|
||||
return token ?? null
|
||||
return cookies.accessToken ?? null
|
||||
}
|
||||
|
||||
apiClient.interceptors.request.use(
|
||||
@@ -25,11 +28,9 @@ apiClient.interceptors.request.use(
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`🚀 ${config.method?.toUpperCase()} ${config.url}`, config.data)
|
||||
}
|
||||
|
||||
return config
|
||||
},
|
||||
error => Promise.reject(error),
|
||||
@@ -40,26 +41,20 @@ apiClient.interceptors.response.use(
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`✅ ${response.status} ${response.config.url}`, response.data)
|
||||
}
|
||||
|
||||
return response
|
||||
},
|
||||
error => {
|
||||
if (import.meta.env.DEV) {
|
||||
console.error(
|
||||
`❌ ${error.response?.status} ${error.config?.url}`,
|
||||
error.response?.data,
|
||||
)
|
||||
console.error(`❌ ${error.response?.status} ${error.config?.url}`, error.response?.data)
|
||||
}
|
||||
|
||||
if (error.response?.status === 401) {
|
||||
document.cookie = 'accessToken=; path=/; max-age=0'
|
||||
document.cookie = 'userData=; path=/; max-age=0'
|
||||
document.cookie = 'userAbilityRules=; path=/; max-age=0'
|
||||
if (window.location.pathname !== '/login') {
|
||||
if (typeof window !== 'undefined' && window.location.pathname !== '/login') {
|
||||
window.location.href = '/login'
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -13,8 +13,8 @@ export interface Pagination {
|
||||
to: number | null
|
||||
}
|
||||
|
||||
/** EventCrew festival / multi-day event (aligned with API `EventResource`). */
|
||||
export type EventCrewEventStatus =
|
||||
/** Crewli festival / multi-day event (aligned with API `EventResource`). */
|
||||
export type CrewliEventStatus =
|
||||
| 'draft'
|
||||
| 'published'
|
||||
| 'registration_open'
|
||||
@@ -37,7 +37,7 @@ export interface Event {
|
||||
start_date: string
|
||||
end_date: string
|
||||
timezone: string
|
||||
status: EventCrewEventStatus
|
||||
status: CrewliEventStatus
|
||||
created_at: string
|
||||
updated_at: string
|
||||
organisation?: OrganisationSummary
|
||||
|
||||
@@ -10,7 +10,7 @@ import { AppContentLayoutNav, ContentWidth, FooterType, NavbarType } from '@layo
|
||||
|
||||
export const { themeConfig, layoutConfig } = defineThemeConfig({
|
||||
app: {
|
||||
title: 'Band',
|
||||
title: 'Organizer',
|
||||
logo: h('div', { innerHTML: logo, style: 'line-height:0; color: rgb(var(--v-global-theme-primary))' }),
|
||||
contentWidth: ContentWidth.Boxed,
|
||||
contentLayoutNav: AppContentLayoutNav.Vertical,
|
||||
|
||||
@@ -1,40 +1,23 @@
|
||||
# Customer Portal
|
||||
# Crewli — Portal SPA
|
||||
|
||||
This folder will contain the Customer Portal using Vuexy Vue.
|
||||
External-facing app for volunteers (login) and artists/suppliers (token links). Stripped Vuexy layout; same stack as other apps.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Copy Vuexy Vue **starter-kit** (TypeScript) here:
|
||||
|
||||
```bash
|
||||
cp -r /path/to/vuexy/typescript-version/starter-kit/* .
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. Create `.env.local`:
|
||||
2. Create `.env.local`:
|
||||
|
||||
```env
|
||||
VITE_API_URL=http://localhost:8000/api/v1
|
||||
VITE_APP_NAME="Customer Portal"
|
||||
VITE_APP_NAME="Crewli Portal"
|
||||
```
|
||||
|
||||
4. Update `vite.config.ts` to use port 5175:
|
||||
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
// ... existing config
|
||||
server: {
|
||||
port: 5175,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
5. Start development:
|
||||
3. Dev server uses port **5175** (see `vite.config.ts` or run from repo root: `make portal`).
|
||||
|
||||
```bash
|
||||
pnpm dev --port 5175
|
||||
@@ -43,3 +26,5 @@ pnpm dev --port 5175
|
||||
## Port
|
||||
|
||||
Runs on http://localhost:5175
|
||||
|
||||
**Production:** e.g. `VITE_API_URL=https://api.crewli.app/api/v1` and host the SPA at `https://portal.crewli.app` (portal links in emails use this host; see `api/.env.example`).
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Event Crew - Customer Portal</title>
|
||||
<title>Crewli — Portal</title>
|
||||
<link rel="stylesheet" type="text/css" href="/loader.css" />
|
||||
</head>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "eventcrew-portal",
|
||||
"name": "crewli-portal",
|
||||
"version": "9.5.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -10,7 +10,7 @@ import { AppContentLayoutNav, ContentWidth, FooterType, NavbarType } from '@layo
|
||||
|
||||
export const { themeConfig, layoutConfig } = defineThemeConfig({
|
||||
app: {
|
||||
title: 'Customer',
|
||||
title: 'Portal',
|
||||
logo: h('div', { innerHTML: logo, style: 'line-height:0; color: rgb(var(--v-global-theme-primary))' }),
|
||||
contentWidth: ContentWidth.Boxed,
|
||||
contentLayoutNav: AppContentLayoutNav.Vertical,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Event Crew - Development Services
|
||||
# Crewli - Development Services
|
||||
# PHP/Node run natively for best Cursor IDE performance
|
||||
|
||||
services:
|
||||
@@ -9,8 +9,8 @@ services:
|
||||
- "3306:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: event_crew
|
||||
MYSQL_USER: event_crew
|
||||
MYSQL_DATABASE: crewli
|
||||
MYSQL_USER: crewli
|
||||
MYSQL_PASSWORD: secret
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
|
||||
68
docs/API.md
68
docs/API.md
@@ -1,56 +1,56 @@
|
||||
# EventCrew API Contract
|
||||
# Crewli API Contract
|
||||
|
||||
# Base: /api/v1/
|
||||
Base path: `/api/v1/`
|
||||
|
||||
# Auth: Bearer token (Sanctum)
|
||||
Auth: Bearer token (Sanctum)
|
||||
|
||||
## Auth
|
||||
|
||||
POST /auth/login
|
||||
POST /auth/logout
|
||||
GET /auth/me
|
||||
- `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
|
||||
- `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
|
||||
POST /organisations/{org}/events
|
||||
GET /organisations/{org}/events/{event}
|
||||
PUT /organisations/{org}/events/{event}
|
||||
- `GET /organisations/{org}/events`
|
||||
- `POST /organisations/{org}/events`
|
||||
- `GET /organisations/{org}/events/{event}`
|
||||
- `PUT /organisations/{org}/events/{event}`
|
||||
|
||||
## Festival Sections
|
||||
## Festival sections
|
||||
|
||||
GET /events/{event}/sections
|
||||
POST /events/{event}/sections
|
||||
GET /events/{event}/sections/{section}
|
||||
- `GET /events/{event}/sections`
|
||||
- `POST /events/{event}/sections`
|
||||
- `GET /events/{event}/sections/{section}`
|
||||
|
||||
## Time Slots
|
||||
## Time slots
|
||||
|
||||
GET /events/{event}/time-slots
|
||||
POST /events/{event}/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
|
||||
- `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
|
||||
- `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)
|
||||
_(Extend this contract per module as endpoints are implemented.)_
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# EventCrew — Core Database Schema
|
||||
# Crewli — Core Database Schema
|
||||
|
||||
> Source: Design Document v1.3 — Section 3.5
|
||||
> All 12 findings from the database review (v1.3) are incorporated.
|
||||
|
||||
195
docs/SETUP.md
195
docs/SETUP.md
@@ -1,6 +1,6 @@
|
||||
# Event Crew - Setup Guide
|
||||
# Crewli - Setup Guide
|
||||
|
||||
This guide walks you through setting up the Event Crew project from scratch.
|
||||
This guide walks you through setting up the Crewli project from scratch.
|
||||
|
||||
## Cursor AI Configuration
|
||||
|
||||
@@ -60,7 +60,7 @@ docker -v # Should show Docker version
|
||||
## Step 1: Start Docker Services
|
||||
|
||||
```bash
|
||||
cd event-crew
|
||||
cd crewli
|
||||
make services
|
||||
```
|
||||
|
||||
@@ -85,8 +85,8 @@ Requirements:
|
||||
- Set up CORS for localhost:5173, localhost:5174, localhost:5175
|
||||
- Use MySQL with these credentials:
|
||||
- Host: 127.0.0.1
|
||||
- Database: event_crew
|
||||
- Username: event_crew
|
||||
- Database: crewli
|
||||
- Username: crewli
|
||||
- Password: secret
|
||||
|
||||
Follow the conventions in .cursor/rules for code style.
|
||||
@@ -95,7 +95,7 @@ Follow the conventions in .cursor/rules for code style.
|
||||
### Manual Alternative
|
||||
|
||||
```bash
|
||||
cd event-crew
|
||||
cd crewli
|
||||
composer create-project laravel/laravel api
|
||||
cd api
|
||||
composer require laravel/sanctum
|
||||
@@ -107,46 +107,38 @@ Then configure `api/.env`:
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=event_crew
|
||||
DB_USERNAME=event_crew
|
||||
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: Copy Vuexy Files
|
||||
## Step 3: Vuexy frontends (this repo)
|
||||
|
||||
### Admin SPA (Full Version)
|
||||
This monorepo already contains three SPAs under `apps/`:
|
||||
|
||||
1. Extract your Vuexy download
|
||||
2. Navigate to: `vuexy-vuejs-admin-template/typescript-version/full-version/`
|
||||
3. Copy ALL contents to: `apps/admin/`
|
||||
| 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 (adjust path to your Vuexy download)
|
||||
# Example only — adjust to your Vuexy download path
|
||||
cp -r ~/Downloads/vuexy/typescript-version/full-version/* apps/admin/
|
||||
```
|
||||
|
||||
### Band Portal (Starter Kit)
|
||||
|
||||
1. Navigate to: `vuexy-vuejs-admin-template/typescript-version/starter-kit/`
|
||||
2. Copy ALL contents to: `apps/band/`
|
||||
|
||||
```bash
|
||||
cp -r ~/Downloads/vuexy/typescript-version/starter-kit/* apps/band/
|
||||
```
|
||||
|
||||
### Customer Portal (Starter Kit)
|
||||
|
||||
1. Copy starter-kit to: `apps/customers/`
|
||||
|
||||
```bash
|
||||
cp -r ~/Downloads/vuexy/typescript-version/starter-kit/* apps/customers/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Configure SPAs
|
||||
@@ -155,8 +147,8 @@ cp -r ~/Downloads/vuexy/typescript-version/starter-kit/* apps/customers/
|
||||
|
||||
```bash
|
||||
cd apps/admin && pnpm install
|
||||
cd ../band && pnpm install
|
||||
cd ../customers && pnpm install
|
||||
cd ../app && pnpm install
|
||||
cd ../portal && pnpm install
|
||||
```
|
||||
|
||||
### Create Environment Files
|
||||
@@ -164,139 +156,96 @@ cd ../customers && pnpm install
|
||||
**apps/admin/.env.local**
|
||||
```env
|
||||
VITE_API_URL=http://localhost:8000/api/v1
|
||||
VITE_APP_NAME="Event Crew Admin"
|
||||
VITE_APP_NAME="Crewli Admin"
|
||||
```
|
||||
|
||||
**apps/band/.env.local**
|
||||
**apps/app/.env.local**
|
||||
```env
|
||||
VITE_API_URL=http://localhost:8000/api/v1
|
||||
VITE_APP_NAME="Band Portal"
|
||||
VITE_APP_NAME="Crewli Organizer"
|
||||
```
|
||||
|
||||
**apps/customers/.env.local**
|
||||
**apps/portal/.env.local**
|
||||
```env
|
||||
VITE_API_URL=http://localhost:8000/api/v1
|
||||
VITE_APP_NAME="Customer Portal"
|
||||
VITE_APP_NAME="Crewli Portal"
|
||||
```
|
||||
|
||||
### Update Vite Ports
|
||||
### Dev server ports
|
||||
|
||||
**apps/band/vite.config.ts** - Add port 5174:
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
// ... existing config
|
||||
server: {
|
||||
port: 5174,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**apps/customers/vite.config.ts** - Add port 5175:
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
// ... existing config
|
||||
server: {
|
||||
port: 5175,
|
||||
},
|
||||
})
|
||||
```
|
||||
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 app’s `vite.config.ts` under `server.port`.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Set Up API Client in SPAs
|
||||
## Step 5: API client in SPAs
|
||||
|
||||
Use Cursor to add the API client. Prompt:
|
||||
|
||||
```
|
||||
Add an API client to apps/admin/src/lib/api-client.ts that:
|
||||
1. Uses axios with base URL from VITE_API_URL
|
||||
2. Adds auth token from localStorage to requests
|
||||
3. Handles 401 responses by redirecting to /login
|
||||
4. Logs requests and responses in development
|
||||
|
||||
Follow the pattern in .cursor/rules
|
||||
```
|
||||
`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
|
||||
## Step 6: Create database schema
|
||||
|
||||
Use Cursor to create migrations. Prompt:
|
||||
Implement migrations from the canonical schema, not a legacy intranet model:
|
||||
|
||||
```
|
||||
Create Laravel migrations for all tables defined in .cursor/rules:
|
||||
- users (with roles and types)
|
||||
- customers
|
||||
- locations
|
||||
- events
|
||||
- event_invitations
|
||||
- music_numbers
|
||||
- music_attachments
|
||||
- setlists
|
||||
- setlist_items
|
||||
- booking_requests
|
||||
- notifications
|
||||
- activity_logs
|
||||
- **`docs/SCHEMA.md`** — table list, columns, indexes
|
||||
- **`.cursor/ARCHITECTURE.md`** — overview and relationships
|
||||
- **`.cursor/rules/103_database.mdc`** — ULIDs, soft deletes, index rules
|
||||
|
||||
Use ULIDs for primary keys and follow Laravel conventions.
|
||||
```
|
||||
**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
|
||||
## Step 7: Start development
|
||||
|
||||
Open 4 terminal tabs:
|
||||
Open separate terminals (or use the Makefile from the repo root):
|
||||
|
||||
```bash
|
||||
# Tab 1: Services (already running)
|
||||
# Tab 1: Services (Docker)
|
||||
make services
|
||||
|
||||
# Tab 2: Laravel API
|
||||
make api
|
||||
|
||||
# Tab 3: Admin SPA
|
||||
# Tab 3: Admin SPA (optional)
|
||||
make admin
|
||||
|
||||
# Tab 4: Band Portal (optional)
|
||||
make band
|
||||
# Tab 4: Organizer SPA (optional)
|
||||
make app
|
||||
|
||||
# Tab 5: Portal SPA (optional)
|
||||
make portal
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Building Features
|
||||
## Building features
|
||||
|
||||
Use these Cursor prompts to build features:
|
||||
Use Cursor with **`CLAUDE.md`** and **`.cursor/instructions.md`**. Example directions:
|
||||
|
||||
### Authentication
|
||||
|
||||
```
|
||||
Create the authentication system:
|
||||
1. AuthController with login, logout, register, user endpoints
|
||||
2. Form requests for validation
|
||||
3. API resources for user responses
|
||||
4. Update Vuexy's auth to use our API instead of fake backend
|
||||
Wire Sanctum API auth: login, logout, me; form requests; API resources; Vue apps use axios + token storage (see .cursor/rules).
|
||||
```
|
||||
|
||||
### Events Module
|
||||
### Events module (Crewli)
|
||||
|
||||
```
|
||||
Create the Events module:
|
||||
1. Event model with relationships (location, customer, setlist, invitations)
|
||||
2. EventController with CRUD + invite and RSVP endpoints
|
||||
3. Form requests and API resources
|
||||
4. Event policy for authorization
|
||||
Events nested under organisations: ULID PK, OrganisationScope, policies, EventResource, feature tests (200/401/403/422).
|
||||
```
|
||||
|
||||
### RSVP System
|
||||
### Portal token flow
|
||||
|
||||
```
|
||||
Create the RSVP system:
|
||||
1. EventInvitation model
|
||||
2. Endpoints for members to respond to invitations
|
||||
3. Admin view of all RSVPs per event
|
||||
4. Email notifications for new invitations
|
||||
Portal token middleware and routes for artist/supplier contexts; document links on https://portal.crewli.app/... (see .cursor/rules/102_multi_tenancy.mdc).
|
||||
```
|
||||
|
||||
---
|
||||
@@ -325,15 +274,13 @@ pnpm type-check
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
## Next steps
|
||||
|
||||
1. ✅ Services running
|
||||
2. ✅ Laravel API created
|
||||
3. ✅ Vuexy copied to SPAs
|
||||
4. ✅ Environment configured
|
||||
5. 🔲 Build authentication
|
||||
6. 🔲 Build events module
|
||||
7. 🔲 Build members module
|
||||
8. 🔲 Build music catalog
|
||||
9. 🔲 Build setlist manager
|
||||
10. 🔲 Build customer portal
|
||||
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`
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user