Add mobile-optimized card view for seat lists on smaller screens Increase minimum height for buttons and form items for better touch interaction Adjust grid layouts, padding, and spacing across pages for mobile devices
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-5 sm: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-4 lg:grid-cols-[18rem_minmax(0,1fr)] lg:gap-6">
|
|
<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-2 sm:grid-cols-2 sm:gap-3">
|
|
<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="min-h-12 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="min-h-12 flex-1 justify-center"
|
|
/>
|
|
</div>
|
|
</UCard>
|
|
</div>
|
|
</div>
|
|
</UContainer>
|
|
</template>
|