feat(activities): show event name (linked) on My tickets
Replace the "Event: <8-char-id>…" placeholder with the actual event title, sourced from the shared events store, and wrap it in a RouterLink to the event detail page. Card title truncates so long names don't push the per-event ticket-count badge out of the row. Subscribe to the events feed on mount so titles resolve as relay events stream in — the user can land on My tickets directly from a purchase flow without having to visit /events first to populate the store. Falls back to the old short-id label until the event arrives (or if it's been deleted upstream). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4b8fd34bba
commit
c8930aee3e
1 changed files with 60 additions and 6 deletions
|
|
@ -1,6 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref, watch } from 'vue'
|
import { onMounted, ref, watch } from 'vue'
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
import { useUserTickets } from '../composables/useUserTickets'
|
import { useUserTickets } from '../composables/useUserTickets'
|
||||||
|
import { useEvents } from '../composables/useEvents'
|
||||||
|
import { useEventsStore } from '../stores/events'
|
||||||
import { useAuth } from '@/composables/useAuthService'
|
import { useAuth } from '@/composables/useAuthService'
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
|
|
@ -22,6 +25,21 @@ const {
|
||||||
refresh
|
refresh
|
||||||
} = useUserTickets()
|
} = useUserTickets()
|
||||||
|
|
||||||
|
// Subscribe to the events feed so we can map ticket.eventId → event.title.
|
||||||
|
// The events store is shared (pinia), so if the user has already visited
|
||||||
|
// the feed this is a no-op fresh subscription; nothing depends on it
|
||||||
|
// being the canonical one.
|
||||||
|
const eventsStore = useEventsStore()
|
||||||
|
const { subscribe: subscribeToEvents } = useEvents()
|
||||||
|
|
||||||
|
function eventTitleFor(eventId: string): string | null {
|
||||||
|
return eventsStore.getEventById(eventId)?.title ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
function eventShortLabel(eventId: string): string {
|
||||||
|
return `Event: ${eventId.slice(0, 8)}…`
|
||||||
|
}
|
||||||
|
|
||||||
const qrCodes = ref<Record<string, string>>({})
|
const qrCodes = ref<Record<string, string>>({})
|
||||||
const currentTicketIndex = ref<Record<string, number>>({})
|
const currentTicketIndex = ref<Record<string, number>>({})
|
||||||
|
|
||||||
|
|
@ -110,6 +128,10 @@ watch(groupedTickets, async (newGroups) => {
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
// Kick off the events subscription so eventTitleFor() can resolve
|
||||||
|
// names as relay events stream in. Fire-and-forget — the QR cards
|
||||||
|
// render fine while the title is still loading.
|
||||||
|
subscribeToEvents()
|
||||||
if (isAuthenticated.value) {
|
if (isAuthenticated.value) {
|
||||||
await refresh()
|
await refresh()
|
||||||
}
|
}
|
||||||
|
|
@ -170,9 +192,17 @@ onMounted(async () => {
|
||||||
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
<Card v-for="group in groupedTickets" :key="group.eventId" class="flex flex-col">
|
<Card v-for="group in groupedTickets" :key="group.eventId" class="flex flex-col">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<CardTitle class="text-foreground">Event: {{ group.eventId.slice(0, 8) }}...</CardTitle>
|
<CardTitle class="text-foreground min-w-0 flex-1">
|
||||||
<Badge variant="outline">
|
<RouterLink
|
||||||
|
:to="{ name: 'event-detail', params: { id: group.eventId } }"
|
||||||
|
class="hover:underline truncate block"
|
||||||
|
:title="eventTitleFor(group.eventId) ?? eventShortLabel(group.eventId)"
|
||||||
|
>
|
||||||
|
{{ eventTitleFor(group.eventId) ?? eventShortLabel(group.eventId) }}
|
||||||
|
</RouterLink>
|
||||||
|
</CardTitle>
|
||||||
|
<Badge variant="outline" class="shrink-0">
|
||||||
{{ group.tickets.length }} ticket{{ group.tickets.length !== 1 ? 's' : '' }}
|
{{ group.tickets.length }} ticket{{ group.tickets.length !== 1 ? 's' : '' }}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -262,7 +292,15 @@ onMounted(async () => {
|
||||||
<div v-else class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div v-else class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
<Card v-for="group in groupedTickets.filter(g => g.paidCount > 0)" :key="group.eventId" class="flex flex-col">
|
<Card v-for="group in groupedTickets.filter(g => g.paidCount > 0)" :key="group.eventId" class="flex flex-col">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle class="text-foreground">Event: {{ group.eventId.slice(0, 8) }}...</CardTitle>
|
<CardTitle class="text-foreground min-w-0">
|
||||||
|
<RouterLink
|
||||||
|
:to="{ name: 'event-detail', params: { id: group.eventId } }"
|
||||||
|
class="hover:underline truncate block"
|
||||||
|
:title="eventTitleFor(group.eventId) ?? eventShortLabel(group.eventId)"
|
||||||
|
>
|
||||||
|
{{ eventTitleFor(group.eventId) ?? eventShortLabel(group.eventId) }}
|
||||||
|
</RouterLink>
|
||||||
|
</CardTitle>
|
||||||
<CardDescription>{{ group.paidCount }} paid ticket{{ group.paidCount !== 1 ? 's' : '' }}</CardDescription>
|
<CardDescription>{{ group.paidCount }} paid ticket{{ group.paidCount !== 1 ? 's' : '' }}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
@ -279,7 +317,15 @@ onMounted(async () => {
|
||||||
<div v-else class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div v-else class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
<Card v-for="group in groupedTickets.filter(g => g.pendingCount > 0)" :key="group.eventId" class="flex flex-col opacity-75">
|
<Card v-for="group in groupedTickets.filter(g => g.pendingCount > 0)" :key="group.eventId" class="flex flex-col opacity-75">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle class="text-foreground">Event: {{ group.eventId.slice(0, 8) }}...</CardTitle>
|
<CardTitle class="text-foreground min-w-0">
|
||||||
|
<RouterLink
|
||||||
|
:to="{ name: 'event-detail', params: { id: group.eventId } }"
|
||||||
|
class="hover:underline truncate block"
|
||||||
|
:title="eventTitleFor(group.eventId) ?? eventShortLabel(group.eventId)"
|
||||||
|
>
|
||||||
|
{{ eventTitleFor(group.eventId) ?? eventShortLabel(group.eventId) }}
|
||||||
|
</RouterLink>
|
||||||
|
</CardTitle>
|
||||||
<CardDescription>{{ group.pendingCount }} pending ticket{{ group.pendingCount !== 1 ? 's' : '' }}</CardDescription>
|
<CardDescription>{{ group.pendingCount }} pending ticket{{ group.pendingCount !== 1 ? 's' : '' }}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
@ -296,7 +342,15 @@ onMounted(async () => {
|
||||||
<div v-else class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div v-else class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
<Card v-for="group in groupedTickets.filter(g => g.registeredCount > 0)" :key="group.eventId" class="flex flex-col">
|
<Card v-for="group in groupedTickets.filter(g => g.registeredCount > 0)" :key="group.eventId" class="flex flex-col">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle class="text-foreground">Event: {{ group.eventId.slice(0, 8) }}...</CardTitle>
|
<CardTitle class="text-foreground min-w-0">
|
||||||
|
<RouterLink
|
||||||
|
:to="{ name: 'event-detail', params: { id: group.eventId } }"
|
||||||
|
class="hover:underline truncate block"
|
||||||
|
:title="eventTitleFor(group.eventId) ?? eventShortLabel(group.eventId)"
|
||||||
|
>
|
||||||
|
{{ eventTitleFor(group.eventId) ?? eventShortLabel(group.eventId) }}
|
||||||
|
</RouterLink>
|
||||||
|
</CardTitle>
|
||||||
<CardDescription>{{ group.registeredCount }} registered ticket{{ group.registeredCount !== 1 ? 's' : '' }}</CardDescription>
|
<CardDescription>{{ group.registeredCount }} registered ticket{{ group.registeredCount !== 1 ? 's' : '' }}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue