Files
thrillwiki_django_no_react/.agent/workflows/new-page.md
pacnpal 1adba1b804 lol
2026-01-02 07:58:58 -05:00

5.3 KiB

description
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

<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

<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

<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

<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]