- Replace location.reload() with proper window.location.reload() function calls - Remove unused isReady variable in ChatPage.vue - Add handleRetry functions in ChatPage.vue and EventsPage.vue - Ensures successful production builds with vue-tsc type checking 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
217 lines
9.1 KiB
Vue
217 lines
9.1 KiB
Vue
<!-- eslint-disable vue/multi-word-component-names -->
|
|
<script setup lang="ts">
|
|
import { ref, computed } from 'vue'
|
|
import { useModuleReady } from '@/composables/useModuleReady'
|
|
import { useEvents } from '../composables/useEvents'
|
|
import { useAuth } from '@/composables/useAuth'
|
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
import { ScrollArea } from '@/components/ui/scroll-area'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { format } from 'date-fns'
|
|
import PurchaseTicketDialog from '../components/PurchaseTicketDialog.vue'
|
|
import { RefreshCw, User, LogIn } from 'lucide-vue-next'
|
|
import { formatEventPrice } from '@/lib/utils/formatting'
|
|
|
|
// Simple reactive module loading
|
|
const { isReady: moduleReady, isLoading: moduleLoading, error: moduleError } = useModuleReady('events')
|
|
|
|
// Only call services when module is ready - prevents service injection errors
|
|
const eventsData = computed(() => moduleReady.value ? useEvents() : null)
|
|
const authData = computed(() => moduleReady.value ? useAuth() : null)
|
|
|
|
// Reactive service data
|
|
const upcomingEvents = computed(() => eventsData.value?.upcomingEvents.value ?? [])
|
|
const pastEvents = computed(() => eventsData.value?.pastEvents.value ?? [])
|
|
const isLoading = computed(() => eventsData.value?.isLoading.value ?? false)
|
|
const error = computed(() => eventsData.value?.error.value ?? null)
|
|
const refresh = () => eventsData.value?.refresh()
|
|
const isAuthenticated = computed(() => authData.value?.isAuthenticated.value ?? false)
|
|
const userDisplay = computed(() => authData.value?.userDisplay.value ?? null)
|
|
const showPurchaseDialog = ref(false)
|
|
const selectedEvent = ref<{
|
|
id: string
|
|
name: string
|
|
price_per_ticket: number
|
|
currency: string
|
|
} | null>(null)
|
|
|
|
function formatDate(dateStr: string) {
|
|
if (!dateStr) return 'Date not available'
|
|
|
|
const date = new Date(dateStr)
|
|
if (isNaN(date.getTime())) {
|
|
return 'Invalid date'
|
|
}
|
|
|
|
// Format like "October 5th, 2025" to match the clean UI
|
|
return format(date, 'MMMM do, yyyy')
|
|
}
|
|
|
|
function handlePurchaseClick(event: {
|
|
id: string
|
|
name: string
|
|
price_per_ticket: number
|
|
currency: string
|
|
}) {
|
|
if (!isAuthenticated.value) {
|
|
// Show login prompt or redirect to login
|
|
// You could emit an event to show login dialog here
|
|
return
|
|
}
|
|
|
|
selectedEvent.value = event
|
|
showPurchaseDialog.value = true
|
|
}
|
|
|
|
function handleRetry() {
|
|
window.location.reload()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<!-- Module Loading State -->
|
|
<div v-if="moduleLoading" class="flex flex-col items-center justify-center min-h-screen">
|
|
<div class="flex flex-col items-center space-y-4">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
|
|
<div class="text-center space-y-2">
|
|
<h2 class="text-xl font-semibold">Loading Events...</h2>
|
|
<p class="text-sm text-muted-foreground">Loading event management and ticketing system...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Module Error State -->
|
|
<div v-else-if="moduleError" class="flex flex-col items-center justify-center min-h-screen">
|
|
<div class="text-center space-y-4">
|
|
<h2 class="text-xl font-semibold text-red-600">Failed to load events</h2>
|
|
<p class="text-muted-foreground">{{ moduleError }}</p>
|
|
<button @click="handleRetry" class="px-4 py-2 bg-primary text-primary-foreground rounded">
|
|
Retry
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Events Content - Only render when module is ready -->
|
|
<div v-else class="container mx-auto py-8 px-4">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<div class="space-y-1">
|
|
<h1 class="text-3xl font-bold text-foreground">Events</h1>
|
|
<div v-if="isAuthenticated && userDisplay" class="flex items-center gap-2 text-sm text-muted-foreground">
|
|
<User class="w-4 h-4" />
|
|
<span>Logged in as {{ userDisplay.name }}</span>
|
|
<Badge variant="outline" class="text-xs">{{ userDisplay.shortId }}</Badge>
|
|
</div>
|
|
<div v-else class="flex items-center gap-2 text-sm text-muted-foreground">
|
|
<LogIn class="w-4 h-4" />
|
|
<span>Please log in to purchase tickets</span>
|
|
</div>
|
|
</div>
|
|
<Button variant="secondary" size="sm" @click="refresh" :disabled="isLoading">
|
|
<RefreshCw class="w-4 h-4 mr-2" :class="{ 'animate-spin': isLoading }" />
|
|
Refresh
|
|
</Button>
|
|
</div>
|
|
|
|
<Tabs default-value="upcoming" class="w-full">
|
|
<TabsList class="grid w-full grid-cols-2">
|
|
<TabsTrigger value="upcoming">Upcoming Events</TabsTrigger>
|
|
<TabsTrigger value="past">Past Events</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<div v-if="error" class="mt-4 p-4 bg-destructive/15 text-destructive rounded-lg">
|
|
{{ error.message }}
|
|
</div>
|
|
|
|
<TabsContent value="upcoming">
|
|
<!-- {{ upcomingEvents }} -->
|
|
<ScrollArea class="h-[600px] w-full pr-4" v-if="upcomingEvents.length">
|
|
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
<Card v-for="event in upcomingEvents" :key="event.id" class="flex flex-col">
|
|
<CardHeader>
|
|
<CardTitle class="text-foreground">{{ event.name }}</CardTitle>
|
|
<CardDescription>{{ event.info }}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent class="flex-grow">
|
|
<div class="space-y-2">
|
|
<div class="flex justify-between">
|
|
<span class="text-muted-foreground">Start Date:</span>
|
|
<span class="text-foreground">{{ formatDate(event.event_start_date) }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-muted-foreground">End Date:</span>
|
|
<span class="text-foreground">{{ formatDate(event.event_end_date) }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-muted-foreground">Tickets Available:</span>
|
|
<span class="text-foreground">{{ event.amount_tickets - event.sold }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-muted-foreground">Price:</span>
|
|
<span class="text-foreground">{{ formatEventPrice(event.price_per_ticket, event.currency) }}</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
<CardFooter>
|
|
<Button
|
|
class="w-full"
|
|
variant="default"
|
|
:disabled="event.amount_tickets <= event.sold || !isAuthenticated"
|
|
@click="handlePurchaseClick(event)"
|
|
>
|
|
<span v-if="!isAuthenticated" class="flex items-center gap-2">
|
|
<LogIn class="w-4 h-4" />
|
|
Login to Purchase
|
|
</span>
|
|
<span v-else>Buy Ticket</span>
|
|
</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
</div>
|
|
</ScrollArea>
|
|
<div v-else-if="!isLoading" class="text-center py-8 text-muted-foreground">
|
|
No upcoming events found
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="past">
|
|
<ScrollArea class="h-[600px] w-full pr-4" v-if="pastEvents.length">
|
|
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
<Card v-for="event in pastEvents" :key="event.id" class="flex flex-col opacity-75">
|
|
<CardHeader>
|
|
<CardTitle class="text-foreground">{{ event.name }}</CardTitle>
|
|
<CardDescription>{{ event.info }}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent class="flex-grow">
|
|
<div class="space-y-2">
|
|
<div class="flex justify-between">
|
|
<span class="text-muted-foreground">Start Date:</span>
|
|
<span class="text-foreground">{{ formatDate(event.event_start_date) }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-muted-foreground">End Date:</span>
|
|
<span class="text-foreground">{{ formatDate(event.event_end_date) }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-muted-foreground">Total Tickets:</span>
|
|
<span class="text-foreground">{{ event.amount_tickets }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-muted-foreground">Tickets Sold:</span>
|
|
<span class="text-foreground">{{ event.sold }}</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</ScrollArea>
|
|
<div v-else-if="!isLoading" class="text-center py-8 text-muted-foreground">
|
|
No past events found
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
<PurchaseTicketDialog v-if="selectedEvent" :event="selectedEvent" v-model:is-open="showPurchaseDialog" />
|
|
</div>
|
|
</template>
|