mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-02-05 16:35:18 -05:00
lol
This commit is contained in:
204
.agent/rules/nuxt-standards.md
Normal file
204
.agent/rules/nuxt-standards.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# ThrillWiki Nuxt 4 Frontend Standards
|
||||
|
||||
Rules for developing the ThrillWiki frontend with Nuxt 4.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── app.vue # Root component
|
||||
├── nuxt.config.ts # Nuxt configuration
|
||||
├── pages/ # File-based routing
|
||||
│ ├── index.vue # Homepage (/)
|
||||
│ ├── parks/
|
||||
│ │ ├── index.vue # /parks
|
||||
│ │ ├── nearby.vue # /parks/nearby
|
||||
│ │ └── [slug].vue # /parks/:slug
|
||||
│ └── ...
|
||||
├── components/
|
||||
│ ├── layout/ # Header, Footer, Sidebar
|
||||
│ ├── ui/ # Base components (Button, Card, Input)
|
||||
│ ├── entity/ # ParkCard, RideCard, ReviewCard
|
||||
│ └── forms/ # Form components
|
||||
├── composables/ # Shared logic (useAuth, useApi, useUnits)
|
||||
├── stores/ # Pinia stores
|
||||
├── types/ # TypeScript interfaces
|
||||
└── assets/
|
||||
└── css/ # Global styles, Tailwind config
|
||||
```
|
||||
|
||||
## Component Conventions
|
||||
|
||||
### Naming
|
||||
- Use PascalCase for component files: `ParkCard.vue`, `SearchAutocomplete.vue`
|
||||
- Use kebab-case in templates: `<park-card>`, `<search-autocomplete>`
|
||||
- Prefix base components with `Base`: `BaseButton.vue`, `BaseInput.vue`
|
||||
|
||||
### Component Structure
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// 1. Type imports
|
||||
import type { Park } from '~/types'
|
||||
|
||||
// 2. Component imports (auto-imported usually)
|
||||
|
||||
// 3. Props and emits
|
||||
const props = defineProps<{
|
||||
park: Park
|
||||
variant?: 'default' | 'compact'
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'select', park: Park): void
|
||||
}>()
|
||||
|
||||
// 4. Composables
|
||||
const { formatDistance } = useUnits()
|
||||
|
||||
// 5. Refs and reactive state
|
||||
const isExpanded = ref(false)
|
||||
|
||||
// 6. Computed properties
|
||||
const displayLocation = computed(() =>
|
||||
`${props.park.city}, ${props.park.country}`
|
||||
)
|
||||
|
||||
// 7. Functions
|
||||
function handleClick() {
|
||||
emit('select', props.park)
|
||||
}
|
||||
|
||||
// 8. Lifecycle hooks (if needed)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Template here -->
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Scoped styles, prefer Tailwind classes in template */
|
||||
</style>
|
||||
```
|
||||
|
||||
### TypeScript
|
||||
- Enable strict mode
|
||||
- Define interfaces for all data structures in `types/`
|
||||
- Use `defineProps<T>()` with TypeScript generics
|
||||
- No `any` types without explicit justification
|
||||
|
||||
## Routing
|
||||
|
||||
### File-Based Routes
|
||||
Follow Nuxt 4 file-based routing conventions:
|
||||
- `pages/index.vue` → `/`
|
||||
- `pages/parks/index.vue` → `/parks`
|
||||
- `pages/parks/[slug].vue` → `/parks/:slug`
|
||||
- `pages/parks/[park]/rides/[ride].vue` → `/parks/:park/rides/:ride`
|
||||
|
||||
### Navigation
|
||||
```typescript
|
||||
// Use navigateTo for programmatic navigation
|
||||
await navigateTo('/parks/cedar-point')
|
||||
|
||||
// Use NuxtLink for declarative navigation
|
||||
<NuxtLink to="/parks">All Parks</NuxtLink>
|
||||
```
|
||||
|
||||
## Data Fetching
|
||||
|
||||
### Use Composables
|
||||
```typescript
|
||||
// composables/useParks.ts
|
||||
export function useParks() {
|
||||
const { data, pending, error, refresh } = useFetch('/api/parks/')
|
||||
|
||||
return {
|
||||
parks: data,
|
||||
loading: pending,
|
||||
error,
|
||||
refresh
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### In Components
|
||||
```typescript
|
||||
// Use useAsyncData for page-level data
|
||||
const { data: park } = await useAsyncData(
|
||||
`park-${route.params.slug}`,
|
||||
() => $fetch(`/api/parks/${route.params.slug}/`)
|
||||
)
|
||||
```
|
||||
|
||||
## State Management (Pinia)
|
||||
|
||||
### Store Structure
|
||||
```typescript
|
||||
// stores/auth.ts
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const user = ref<User | null>(null)
|
||||
const isAuthenticated = computed(() => !!user.value)
|
||||
|
||||
async function login(credentials: LoginCredentials) {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
function logout() {
|
||||
user.value = null
|
||||
}
|
||||
|
||||
return { user, isAuthenticated, login, logout }
|
||||
})
|
||||
```
|
||||
|
||||
### Using Stores
|
||||
```typescript
|
||||
const authStore = useAuthStore()
|
||||
const { user, isAuthenticated } = storeToRefs(authStore)
|
||||
```
|
||||
|
||||
## API Integration
|
||||
|
||||
### Base API Composable
|
||||
```typescript
|
||||
// composables/useApi.ts
|
||||
export function useApi() {
|
||||
const config = useRuntimeConfig()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
return $fetch.create({
|
||||
baseURL: config.public.apiBase,
|
||||
headers: {
|
||||
...(authStore.token && { Authorization: `Bearer ${authStore.token}` })
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Page Errors
|
||||
```typescript
|
||||
// In page components
|
||||
const { data, error } = await useAsyncData(...)
|
||||
|
||||
if (error.value) {
|
||||
throw createError({
|
||||
statusCode: error.value.statusCode || 500,
|
||||
message: error.value.message
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Form Errors
|
||||
- Display validation errors inline with form fields
|
||||
- Use toast notifications for API errors
|
||||
- Provide clear user feedback
|
||||
|
||||
## Accessibility Requirements
|
||||
|
||||
- All interactive elements must be keyboard accessible
|
||||
- Provide proper ARIA labels
|
||||
- Ensure color contrast meets WCAG AA standards
|
||||
- Support `prefers-reduced-motion`
|
||||
- Use semantic HTML elements
|
||||
Reference in New Issue
Block a user