Drops 17 KB of embedded code templates that had drifted from actual implementations in apps/app/src/ (auth store template still used localStorage; portal router guards still showed dual-mode logic that was consolidated to /portal/* routes within apps/app in PR-B1/B2a). Slim rewrite: principles + file structure + pointers to actual reference code in apps/app/src/. Globs narrowed to apps/app/**/* since apps/portal/ no longer exists. Vuexy component selection deferred to dev-docs/VUEXY_COMPONENTS.md as canonical registry. Net: ~17 KB -> ~3 KB, less drift surface, points at living code instead of duplicating it.
86 lines
3.5 KiB
Plaintext
86 lines
3.5 KiB
Plaintext
---
|
|
description: Vue 3, TypeScript, and Vuexy patterns for Crewli
|
|
globs: ["apps/app/**/*.{vue,ts,tsx}"]
|
|
alwaysApply: true
|
|
---
|
|
|
|
# Vue + Vuexy Rules
|
|
|
|
## Core Principles
|
|
|
|
1. **Composition API only** — always `<script setup lang="ts">`, never Options API
|
|
2. **No `any` types** — use proper typing or `unknown` + narrowing
|
|
3. **TanStack Query for API** — never raw axios in components
|
|
4. **Pinia for cross-component client state** — server data lives in TanStack Query, never duplicated in stores
|
|
5. **Vuetify components first** — custom CSS only when no Vuetify class fits the use case
|
|
6. **VeeValidate + Zod** for all form validation
|
|
7. **Mobile-first** — minimum 375px width, responsive at every breakpoint
|
|
|
|
## File structure
|
|
|
|
```
|
|
apps/app/src/
|
|
├── lib/axios.ts # Single axios instance (do not duplicate)
|
|
├── composables/api/use*.ts # TanStack Query composables (one per resource)
|
|
├── stores/use*Store.ts # Pinia stores — client state only
|
|
├── types/*.ts # TypeScript interfaces (mirror backend Resources)
|
|
├── pages/ # File-based routing via unplugin-vue-router
|
|
├── layouts/ # Layout components
|
|
├── components/ # Reusable components
|
|
└── @core/ # Vuexy core — DO NOT MODIFY
|
|
```
|
|
|
|
## Reference patterns (read these for templates)
|
|
|
|
For working examples in the actual codebase:
|
|
|
|
- **Composable pattern:** `apps/app/src/composables/api/useEvents.ts`
|
|
- **Pinia store pattern:** `apps/app/src/stores/useAuthStore.ts`
|
|
- **Page pattern:** `apps/app/src/pages/events/index.vue`
|
|
- **Form pattern:** `apps/app/src/components/events/CreateEventDialog.vue`
|
|
- **Layout pattern:** `apps/app/src/layouts/OrganizerLayout.vue`
|
|
|
|
For Vuexy component selection, consult `dev-docs/VUEXY_COMPONENTS.md` — the registry of @core wrappers and patterns. Always check that registry before writing a custom component.
|
|
|
|
For auth and routing, see `dev-docs/AUTH_ARCHITECTURE.md` (httpOnly cookies, dual-axios for portal-token routes, route guard logic).
|
|
|
|
## Strict rules
|
|
|
|
### TypeScript
|
|
- Use `import type { ... }` for type-only imports
|
|
- Mirror backend PHP Enums as const objects with `as const` in `apps/app/src/types/`
|
|
- Generic API response shape: `{ data: T, meta?: PaginationMeta }`
|
|
|
|
### Architecture
|
|
- Components never import axios directly — always via composables
|
|
- Composables call axios via the singleton in `apps/app/src/lib/axios.ts`
|
|
- Mutations invalidate query keys after success
|
|
- No prop drilling — use Pinia stores when state crosses two component boundaries
|
|
|
|
### UI
|
|
- Three states for every list view: **loading** (VSkeletonLoader), **error** (VAlert with retry button), **empty** (helpful message explaining what action to take)
|
|
- Custom CSS forbidden when a Vuetify utility class exists
|
|
- Tables on mobile (<768px) collapse to VList or card view — never horizontal scroll without a visual indicator
|
|
|
|
### Forms
|
|
- Zod schema mirrors backend FormRequest validation
|
|
- Errors shown inline via VeeValidate's `errors` object
|
|
- Submit button disabled while `isPending`
|
|
|
|
### Routing
|
|
- File-based routing via unplugin-vue-router
|
|
- Guards in `apps/app/src/plugins/1.router/guards.ts`
|
|
- Portal routes are at `/portal/*` (within apps/app), NOT a separate SPA
|
|
- Platform admin routes are at `/platform/*`, gated by `super_admin` role
|
|
|
|
## Avoid
|
|
|
|
- Options API (`export default { ... }`)
|
|
- `any` types
|
|
- Raw axios calls in components
|
|
- Inline styles
|
|
- Direct DOM manipulation
|
|
- Mutating props
|
|
- Custom CSS when a Vuetify class exists
|
|
- Hardcoded URLs or string-literal status values
|