178 lines
7.4 KiB
Markdown
178 lines
7.4 KiB
Markdown
# Crewli — Cursor Master Prompt
|
|
# Plak dit BOVEN elke task. Vervang [TASK] onderaan.
|
|
|
|
## MANDATORY PREAMBLE
|
|
|
|
Read `@CLAUDE.md` and `@dev-docs/SCHEMA.md` before starting. Use `@workspace`
|
|
for full codebase context. These documents are your source of truth.
|
|
|
|
If modifying existing code, read the current implementation first. Understand
|
|
the patterns already in use before writing anything new.
|
|
|
|
## PROJECT CONTEXT
|
|
|
|
Crewli — multi-tenant SaaS for event/festival management.
|
|
|
|
- **Frontend:** Vue 3 + TypeScript + Vuexy 9.5 (Vuetify 3.10) + Pinia +
|
|
TanStack Vue Query + VeeValidate + Zod
|
|
- **Three SPAs:**
|
|
- `apps/admin/` — Super Admin (port 5173)
|
|
- `apps/app/` — Organizer main app (port 5174)
|
|
- `apps/portal/` — Dual-mode portal (port 5175)
|
|
- **API base:** `VITE_API_URL` from `.env.local` (default `http://localhost:8000`)
|
|
- **Axios instance:** `src/lib/axios.ts` — this is the ONLY axios instance.
|
|
Never create another. Never import axios directly in components.
|
|
- **Backend Enums:** PHP Enums in `api/app/Enums/` define all valid status/type
|
|
values. TypeScript equivalents must mirror these exactly in `src/types/`.
|
|
|
|
### Frontend file structure (per app)
|
|
|
|
```
|
|
src/
|
|
├── lib/axios.ts # Singleton axios instance (DO NOT DUPLICATE)
|
|
├── types/ # TypeScript interfaces per module
|
|
│ └── [module].ts
|
|
├── composables/api/ # TanStack Query composables per module
|
|
│ └── use[Module].ts
|
|
├── stores/ # Pinia stores (cross-component state only)
|
|
│ └── use[Module]Store.ts
|
|
├── pages/ # Page components
|
|
│ └── [module]/
|
|
│ ├── index.vue # List view
|
|
│ └── [id].vue # Detail view (or side panel)
|
|
└── router/ # Vue Router config
|
|
```
|
|
|
|
---
|
|
|
|
## ZERO-COMPROMISE RULES — VIOLATIONS ARE BLOCKERS
|
|
|
|
### TypeScript & typing
|
|
|
|
1. **NO `any` TYPES. EVER.** Every variable, prop, emit, return type, ref,
|
|
reactive, computed, and API response is fully typed. If you don't know the
|
|
type, check the backend API Resource in `api/app/Http/Resources/` — that
|
|
defines the contract. Create matching interfaces in `src/types/[module].ts`.
|
|
|
|
2. **TYPES FIRST.** Before writing any composable or component for a new
|
|
module, create the TypeScript interfaces in `src/types/[module].ts`. These
|
|
mirror the backend API Resource shape. Include:
|
|
- Entity interface (e.g., `Shift`, `Person`, `Event`)
|
|
- Create/Update DTOs (matching backend Form Request fields)
|
|
- Enum-like union types or const objects matching backend PHP Enums
|
|
- Paginated response wrapper if applicable
|
|
|
|
3. **ENUMS AS CONST OBJECTS.** Mirror backend PHP Enums as TypeScript const
|
|
objects with `as const`, not as loose string unions scattered across files:
|
|
```typescript
|
|
// src/types/shift.ts
|
|
export const ShiftAssignmentStatus = {
|
|
PENDING_APPROVAL: 'pending_approval',
|
|
APPROVED: 'approved',
|
|
REJECTED: 'rejected',
|
|
CANCELLED: 'cancelled',
|
|
COMPLETED: 'completed',
|
|
} as const
|
|
export type ShiftAssignmentStatus = typeof ShiftAssignmentStatus[keyof typeof ShiftAssignmentStatus]
|
|
```
|
|
|
|
### Architecture & patterns
|
|
|
|
4. **NO DIRECT AXIOS IN COMPONENTS.** All API calls go through composables in
|
|
`composables/api/use[Module].ts` using TanStack Query (`useQuery` /
|
|
`useMutation`). Components never import axios or `src/lib/axios.ts`.
|
|
The composable is the only layer that knows about HTTP.
|
|
|
|
5. **NO OPTIONS API.** Always `<script setup lang="ts">`. Props via
|
|
`defineProps<{...}>()`, emits via `defineEmits<{...}>()`, expose via
|
|
`defineExpose()`. No `export default { ... }`.
|
|
|
|
6. **NO PROP DRILLING.** If state needs to cross more than one component
|
|
boundary, use a Pinia store (`src/stores/use[Module]Store.ts`). Access
|
|
reactive state via `storeToRefs()`. Mutations only through store actions.
|
|
|
|
7. **DELETE OVER ADAPT.** If you find duplicate composables, overlapping
|
|
stores, or conflicting patterns: delete the worse version. Do not build
|
|
alongside existing code that does the same thing. One implementation per
|
|
concern.
|
|
|
|
8. **CONSISTENCY OVER CLEVERNESS.** Look at how existing modules handle the
|
|
same pattern (table views, form dialogs, detail panels, error handling).
|
|
Use the exact same approach. Never introduce a "better" alternative without
|
|
refactoring ALL existing modules to match.
|
|
|
|
9. **SINGLE SOURCE OF TRUTH.** Don't duplicate:
|
|
- Validation logic → Zod schema mirrors backend Form Request. One schema
|
|
per form, referenced by VeeValidate.
|
|
- Enum values → const objects in `src/types/`, matching backend Enums.
|
|
Never use raw string literals like `'approved'` in templates or logic.
|
|
- API URLs → constructed in composable from module prefix, never
|
|
hardcoded in components.
|
|
|
|
### UI & UX
|
|
|
|
10. **NO CUSTOM CSS WHERE VUETIFY HAS A SOLUTION.** Before writing any CSS:
|
|
check if a Vuetify utility class, component prop, or slot achieves the
|
|
result. Custom CSS is a last resort, must be `<style scoped>`, and must
|
|
have a comment explaining why Vuetify couldn't handle it.
|
|
|
|
11. **EVERY PAGE HAS THREE STATES:**
|
|
- **Loading:** Vuetify skeleton loader or progress indicator
|
|
- **Error:** User-facing message with retry action (`v-alert` with retry
|
|
button). Show what went wrong in user terms, not technical jargon.
|
|
- **Empty:** Helpful message explaining why the list is empty and what
|
|
action to take (not a blank white screen).
|
|
Never show only the happy path.
|
|
|
|
12. **MOBILE-FIRST.** Every component must be usable at 375px width. Use
|
|
Vuetify's responsive props (`cols`, `sm`, `md`, `lg` on `v-col`). Never
|
|
use fixed pixel widths for layout. Tables on mobile → card views or
|
|
horizontal scroll with visual indicator.
|
|
|
|
13. **FORMS USE VEEVALIDATE + ZOD.** Define the Zod schema matching the
|
|
backend Form Request rules. No inline validation logic in templates. Use
|
|
`useForm()` and `useField()` from VeeValidate with the Zod resolver.
|
|
|
|
### Code quality
|
|
|
|
14. **NO TODO / FIXME / HACK.** Complete the implementation or report the
|
|
blocker. No stubs, no "implement later", no placeholder components.
|
|
|
|
15. **NO ORPHANED IMPORTS OR UNUSED VARIABLES.** Run `npx tsc --noEmit`
|
|
after every change. Zero errors, zero warnings. Dead code is deleted
|
|
immediately, not commented out.
|
|
|
|
16. **NO GENERIC NAMES.** Component and composable names must be specific:
|
|
- ❌ `DataTable.vue`, `useApi.ts`, `helpers.ts`, `utils.ts`
|
|
- ✅ `ShiftAssignmentTable.vue`, `useShifts.ts`,
|
|
`formatShiftTimeRange.ts`
|
|
|
|
### Routing & auth
|
|
|
|
17. **ROUTER GUARDS.** Protected routes check auth state via navigation
|
|
guard. Portal routes validate token. No unguarded routes to
|
|
authenticated content. Redirect to login on 401.
|
|
|
|
### Process
|
|
|
|
18. **COMPONENT CREATION ORDER.** For every new page/feature:
|
|
1. TypeScript types in `src/types/[module].ts`
|
|
2. API composable in `src/composables/api/use[Module].ts`
|
|
3. Pinia store in `src/stores/use[Module]Store.ts` (if cross-component)
|
|
4. Vue page component in `src/pages/[module]/`
|
|
5. Router entry in `src/router/`
|
|
|
|
19. **VERIFY BEFORE DONE:**
|
|
- `npx tsc --noEmit` — zero errors
|
|
- No `any` types anywhere (search: `grep -rn ": any\|as any" src/`)
|
|
- No TODO/FIXME/HACK
|
|
- All three states (loading, error, empty) implemented
|
|
- Mobile responsive at 375px
|
|
- Consistent with existing module patterns
|
|
|
|
---
|
|
|
|
## TASK
|
|
|
|
[INSERT SPECIFIC TASK HERE]
|