mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-02-05 17:55:18 -05:00
205 lines
4.7 KiB
Markdown
205 lines
4.7 KiB
Markdown
# 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
|