---
description: Create a new page in ThrillWiki following project conventions
---

# New Page Workflow

Create a new page in ThrillWiki following all conventions and patterns.

## Information Gathering

Before creating the page, determine:

1. **Route**: What URL should this page have?
2. **Page Type**: 
   - List page (shows multiple items)
   - Detail page (shows single item)
   - Form page (create/edit content)
   - Static page (about, contact, etc.)
3. **Data Requirements**: What data does this page need?
4. **Authentication**: Public or authenticated only?
5. **Related Components**: What existing components can be reused?

## File Creation Steps

### 1. Create the Page Component

Location: `frontend/pages/[route].vue` or `frontend/pages/[folder]/[route].vue`

```vue
<script setup lang="ts">
// Define page metadata
definePageMeta({
  // middleware: ['auth'], // If authenticated only
  // layout: 'admin',      // If using special layout
})

// Set page head
useSeoMeta({
  title: 'Page Title | ThrillWiki',
  description: 'Page description for SEO',
})

// Fetch data
const { data, pending, error } = await useAsyncData('unique-key', () =>
  $fetch('/api/v1/endpoint/')
)

// Handle error
if (error.value) {
  throw createError({
    statusCode: error.value.statusCode || 500,
    message: error.value.message
  })
}
</script>

<template>
  <PageContainer>
    <!-- Breadcrumbs (if applicable) -->
    <Breadcrumbs :items="breadcrumbItems" />
    
    <!-- Page Header -->
    <div class="mb-8">
      <h1 class="text-3xl font-bold">Page Title</h1>
      <p class="text-muted-foreground mt-2">Page description</p>
    </div>
    
    <!-- Loading State -->
    <div v-if="pending" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
      <Skeleton v-for="i in 8" :key="i" class="h-64" />
    </div>
    
    <!-- Content -->
    <div v-else>
      <!-- Page content here -->
    </div>
  </PageContainer>
</template>
```

### 2. For List Pages - Add Filtering

```vue
<script setup lang="ts">
const route = useRoute()
const router = useRouter()

// Filter state from URL
const filters = computed(() => ({
  status: route.query.status as string || '',
  search: route.query.search as string || '',
  page: parseInt(route.query.page as string) || 1
}))

// Fetch with filters
const { data, pending, refresh } = await useAsyncData(
  `items-${JSON.stringify(filters.value)}`,
  () => $fetch('/api/v1/items/', { params: filters.value }),
  { watch: [filters] }
)

// Update filters
function updateFilter(key: string, value: string) {
  router.push({
    query: { ...route.query, [key]: value || undefined, page: 1 }
  })
}
</script>

<template>
  <!-- Filter Bar -->
  <div class="flex gap-4 mb-6">
    <Input 
      :model-value="filters.search"
      @update:model-value="updateFilter('search', $event)"
      placeholder="Search..."
    />
    <Select
      :model-value="filters.status"
      @update:model-value="updateFilter('status', $event)"
    >
      <SelectOption value="">All</SelectOption>
      <SelectOption value="operating">Operating</SelectOption>
      <SelectOption value="closed">Closed</SelectOption>
    </Select>
  </div>
  
  <!-- Results -->
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
    <ItemCard v-for="item in data?.results" :key="item.id" :item="item" />
  </div>
  
  <!-- Pagination -->
  <Pagination 
    :current-page="filters.page"
    :total-pages="Math.ceil((data?.count || 0) / 20)"
    @page-change="updateFilter('page', $event.toString())"
  />
</template>
```

### 3. For Detail Pages - Dynamic Route

File: `frontend/pages/items/[slug].vue`

```vue
<script setup lang="ts">
const route = useRoute()
const slug = route.params.slug as string

const { data: item, error } = await useAsyncData(
  `item-${slug}`,
  () => $fetch(`/api/v1/items/${slug}/`)
)

if (error.value) {
  throw createError({
    statusCode: 404,
    message: 'Item not found'
  })
}

useSeoMeta({
  title: `${item.value?.name} | ThrillWiki`,
  description: item.value?.description,
})
</script>
```

### 4. For Form Pages

```vue
<script setup lang="ts">
import { z } from 'zod'

const schema = z.object({
  name: z.string().min(1, 'Name is required'),
  description: z.string().optional(),
})

const form = reactive({
  name: '',
  description: '',
})

const errors = ref<Record<string, string[]>>({})
const isSubmitting = ref(false)

async function handleSubmit() {
  // Validate
  const result = schema.safeParse(form)
  if (!result.success) {
    errors.value = result.error.flatten().fieldErrors
    return
  }
  
  isSubmitting.value = true
  try {
    await $fetch('/api/v1/items/', {
      method: 'POST',
      body: form
    })
    await navigateTo('/items')
  } catch (e) {
    // Handle API errors
  } finally {
    isSubmitting.value = false
  }
}
</script>
```

## Checklist

After creating the page, verify:

- [ ] Page renders without errors
- [ ] SEO meta tags are set
- [ ] Loading states display correctly
- [ ] Error states are handled
- [ ] Page is responsive (mobile, tablet, desktop)
- [ ] Keyboard navigation works
- [ ] Data fetches efficiently (no N+1 issues)
- [ ] URL parameters persist correctly (for list pages)
- [ ] Authentication is enforced (if required)

## Output

Report what was created:
```
Created: frontend/pages/[path].vue
Route: /[route]
Type: [list/detail/form/static]
Features: [list of features implemented]
```
