mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-02-05 15:15:18 -05:00
4.7 KiB
4.7 KiB
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
<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
anytypes without explicit justification
Routing
File-Based Routes
Follow Nuxt 4 file-based routing conventions:
pages/index.vue→/pages/parks/index.vue→/parkspages/parks/[slug].vue→/parks/:slugpages/parks/[park]/rides/[ride].vue→/parks/:park/rides/:ride
Navigation
// 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
// composables/useParks.ts
export function useParks() {
const { data, pending, error, refresh } = useFetch('/api/parks/')
return {
parks: data,
loading: pending,
error,
refresh
}
}
In Components
// 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
// 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
const authStore = useAuthStore()
const { user, isAuthenticated } = storeToRefs(authStore)
API Integration
Base API Composable
// 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
// 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