Introduce structural CSS classes for page shells, headers, and surface cards Update primary theme color to red and neutral to zinc across the application
169 lines
6.1 KiB
Vue
169 lines
6.1 KiB
Vue
<script lang="ts" setup>
|
|
import type { PublicSeatReceipt } from '~~/shared/booking'
|
|
|
|
import {
|
|
formatBookingCurrency,
|
|
getSeatLabel
|
|
} from '~~/shared/booking'
|
|
|
|
import { formatDateTime } from '../../utils/formatters'
|
|
|
|
const route = useRoute()
|
|
const apiClient = useApiClient()
|
|
|
|
const token = String(route.params.token || '')
|
|
|
|
let initialReceipt: PublicSeatReceipt
|
|
|
|
try {
|
|
initialReceipt = await apiClient<PublicSeatReceipt>(`/api/public/seats/${token}`)
|
|
} catch (error: any) {
|
|
throw createError({
|
|
statusCode: error?.statusCode || error?.data?.statusCode || 404,
|
|
statusMessage: error?.data?.statusMessage || error?.message || 'Seat ticket not found'
|
|
})
|
|
}
|
|
|
|
const receipt = ref(initialReceipt)
|
|
|
|
const eventDetails = computed(() => receipt.value.booking.event)
|
|
const ticketLabel = computed(() => receipt.value.booking.ticketLabel || receipt.value.booking.ticketType.toUpperCase())
|
|
const totalFormatted = computed(() => formatBookingCurrency(receipt.value.booking.totalPrice))
|
|
</script>
|
|
|
|
<template>
|
|
<UContainer class="page-shell-narrow">
|
|
<div class="space-y-6">
|
|
<div class="page-header text-center sm:items-center">
|
|
<UBadge label="Seat Ticket" color="primary" variant="soft" class="page-eyebrow" />
|
|
<h1 class="page-title">
|
|
{{ getSeatLabel(receipt.seat.seatNumber) }}
|
|
</h1>
|
|
<p class="page-description">
|
|
{{ eventDetails.title }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="grid gap-6 lg:grid-cols-[18rem_minmax(0,1fr)]">
|
|
<UCard class="surface-card overflow-hidden rounded-lg" :ui="{ body: 'space-y-4 p-4 sm:p-5' }">
|
|
<div class="space-y-1 text-center">
|
|
<p class="text-sm font-semibold text-highlighted">
|
|
QR Code
|
|
</p>
|
|
<p class="text-xs text-muted">
|
|
Present this QR code at check-in.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex justify-center">
|
|
<QrCodeSvg :value="receipt.seat.seatUrl" :size="220" />
|
|
</div>
|
|
|
|
<div class="surface-panel rounded-lg p-4 text-sm text-default">
|
|
<p class="font-medium text-highlighted">
|
|
{{ receipt.seat.recipientName || receipt.booking.customerName }}
|
|
</p>
|
|
<p v-if="receipt.seat.recipientPhone" class="mt-1 text-muted">
|
|
{{ receipt.seat.recipientPhone }}
|
|
</p>
|
|
<p class="mt-3 text-muted">
|
|
{{ receipt.booking.status === 'confirmed' ? 'Booking confirmed' : 'Booking pending confirmation' }}
|
|
</p>
|
|
</div>
|
|
</UCard>
|
|
|
|
<UCard class="surface-card overflow-hidden rounded-lg" :ui="{ body: 'space-y-5 p-4 sm:p-5' }">
|
|
<div class="space-y-1">
|
|
<h2 class="text-xl font-semibold text-highlighted">
|
|
Booking Details
|
|
</h2>
|
|
<p class="text-sm text-muted">
|
|
Linked back to the main booking receipt.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="grid gap-3 sm:grid-cols-2">
|
|
<div class="surface-panel rounded-lg p-4">
|
|
<p class="text-xs uppercase tracking-wide text-muted">
|
|
Guest / Organizer
|
|
</p>
|
|
<p class="mt-2 font-semibold text-highlighted">
|
|
{{ receipt.booking.customerName }}
|
|
</p>
|
|
</div>
|
|
<div class="surface-panel rounded-lg p-4">
|
|
<p class="text-xs uppercase tracking-wide text-muted">
|
|
Ticket Category
|
|
</p>
|
|
<p class="mt-2 font-semibold text-highlighted">
|
|
{{ ticketLabel }}
|
|
</p>
|
|
</div>
|
|
<div class="surface-panel rounded-lg p-4">
|
|
<p class="text-xs uppercase tracking-wide text-muted">
|
|
Total Seats
|
|
</p>
|
|
<p class="mt-2 font-semibold text-highlighted">
|
|
{{ receipt.booking.seatCount }} seats
|
|
</p>
|
|
</div>
|
|
<div class="surface-panel rounded-lg p-4">
|
|
<p class="text-xs uppercase tracking-wide text-muted">
|
|
Total Booking Value
|
|
</p>
|
|
<p class="mt-2 font-semibold text-highlighted">
|
|
{{ totalFormatted }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="surface-panel space-y-3 rounded-lg p-4">
|
|
<div class="flex items-center gap-3 text-sm text-default">
|
|
<UIcon name="i-lucide-calendar-days" class="size-4 text-muted" />
|
|
<span>{{ eventDetails.dateLabel }}</span>
|
|
</div>
|
|
<div class="flex items-center gap-3 text-sm text-default">
|
|
<UIcon name="i-lucide-clock-6" class="size-4 text-muted" />
|
|
<span>{{ eventDetails.timeLabel }}</span>
|
|
</div>
|
|
<div class="flex items-center gap-3 text-sm text-default">
|
|
<UIcon name="i-lucide-map-pin" class="size-4 text-muted" />
|
|
<span>{{ eventDetails.venue }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="surface-panel rounded-lg p-4 text-sm text-default">
|
|
<p class="font-medium text-highlighted">
|
|
Seat shared {{ receipt.seat.sharedAt ? formatDateTime(receipt.seat.sharedAt) : 'recently' }}
|
|
</p>
|
|
<p class="mt-1 text-muted">
|
|
Submitted {{ formatDateTime(receipt.booking.createdAt) }}
|
|
</p>
|
|
<p v-if="receipt.booking.confirmedAt" class="mt-1 text-muted">
|
|
Confirmed {{ formatDateTime(receipt.booking.confirmedAt) }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-2 sm:flex-row">
|
|
<UButton
|
|
:to="receipt.receiptUrl"
|
|
label="Open Main Receipt"
|
|
icon="i-lucide-receipt"
|
|
class="flex-1 justify-center"
|
|
/>
|
|
<UButton
|
|
:to="receipt.seat.seatUrl"
|
|
target="_blank"
|
|
label="Open Ticket Link"
|
|
color="neutral"
|
|
variant="outline"
|
|
icon="i-lucide-external-link"
|
|
class="flex-1 justify-center"
|
|
/>
|
|
</div>
|
|
</UCard>
|
|
</div>
|
|
</div>
|
|
</UContainer>
|
|
</template>
|