diff --git a/app/pages/bookings/index.vue b/app/pages/bookings/index.vue index f6426bf..cc22c28 100644 --- a/app/pages/bookings/index.vue +++ b/app/pages/bookings/index.vue @@ -606,6 +606,12 @@ definePageMeta({ middleware: 'auth' }) +useSeoMeta({ + title: 'Bookings', + description: 'Manage dinner ticket bookings, confirmations, receipts, and seat allocation.', + robots: 'noindex,nofollow' +}) + const toast = useToast() const apiClient = useApiClient() const auth = useAuth() diff --git a/app/pages/confirmation/[token].vue b/app/pages/confirmation/[token].vue index 1978ea0..f0616bf 100644 --- a/app/pages/confirmation/[token].vue +++ b/app/pages/confirmation/[token].vue @@ -36,6 +36,15 @@ const statusColor = computed(() => booking.value.status === 'confirmed' ? 'succe const ticketLabel = computed(() => booking.value.ticketLabel || booking.value.ticketType.toUpperCase()) const totalFormatted = computed(() => formatBookingCurrency(booking.value.totalPrice, locale.value)) const receiptPath = computed(() => `/receipt/${booking.value.receiptToken}`) + +useSeoMeta({ + title: () => `${t('confirm.title')} - ${booking.value.customerName}`, + description: () => t('confirm.description'), + ogTitle: () => `${t('confirm.title')} - ${booking.value.customerName}`, + ogDescription: () => t('confirm.description'), + robots: 'noindex,nofollow' +}) + const detailRows = computed(() => { const rows = [ { diff --git a/app/pages/index.vue b/app/pages/index.vue index 0c7108e..014e8fe 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -27,6 +27,20 @@ const [bookingConfig, contactsResponse] = await Promise.all([ apiClient<{ contacts: PublicContact[] }>('/api/public/contacts') ]) +const seoDescription = computed(() => { + return `${bookingConfig.event.dateLabel} · ${bookingConfig.event.timeLabel} · ${bookingConfig.event.venue}` +}) + +useSeoMeta({ + title: () => bookingConfig.event.title, + description: () => seoDescription.value, + ogTitle: () => bookingConfig.event.title, + ogDescription: () => seoDescription.value, + twitterTitle: () => bookingConfig.event.title, + twitterDescription: () => seoDescription.value, + robots: 'index,follow' +}) + const eventDetails = computed(() => [ { label: t('common.date'), diff --git a/app/pages/login/index.vue b/app/pages/login/index.vue index a3e5c05..b0a866d 100644 --- a/app/pages/login/index.vue +++ b/app/pages/login/index.vue @@ -121,6 +121,12 @@ definePageMeta({ middleware: 'guest' }) +useSeoMeta({ + title: 'Staff Login', + description: 'Staff login for the dinner ticket management system.', + robots: 'noindex,nofollow' +}) + const toast = useToast() const router = useRouter() const auth = useAuth() diff --git a/app/pages/management/users/index.vue b/app/pages/management/users/index.vue index 732b181..0a0339b 100644 --- a/app/pages/management/users/index.vue +++ b/app/pages/management/users/index.vue @@ -255,6 +255,12 @@ definePageMeta({ middleware: 'super-admin' }) +useSeoMeta({ + title: 'User Management', + description: 'Manage staff access for the dinner ticket system.', + robots: 'noindex,nofollow' +}) + const toast = useToast() const apiClient = useApiClient() const auth = useAuth() diff --git a/app/pages/receipt/[token].vue b/app/pages/receipt/[token].vue index 9d37ecd..046a82c 100644 --- a/app/pages/receipt/[token].vue +++ b/app/pages/receipt/[token].vue @@ -52,6 +52,15 @@ const ticketLabel = computed(() => receipt.value.booking.ticketLabel || receipt. const statusColor = computed(() => receipt.value.booking.status === 'confirmed' ? 'success' : 'warning') const sharedSeats = computed(() => receipt.value.seats.filter((seat) => Boolean(seat.sharedAt))) const availableSeats = computed(() => receipt.value.seats.filter((seat) => !seat.sharedAt)) + +useSeoMeta({ + title: () => `${t('receipt.badge')} - ${eventDetails.value.title}`, + description: () => `${receipt.value.booking.customerName} · ${ticketLabel.value} · ${receipt.value.booking.seatCount} ${t('common.seats')}`, + ogTitle: () => `${t('receipt.badge')} - ${eventDetails.value.title}`, + ogDescription: () => `${receipt.value.booking.customerName} · ${ticketLabel.value}`, + robots: 'noindex,nofollow' +}) + const maxShareCount = computed(() => Math.max(availableSeats.value.length, 1)) const normalizedShareCount = computed(() => { return Math.max(1, Math.min(Math.trunc(Number(shareForm.count) || 1), maxShareCount.value)) diff --git a/app/pages/seat/[token].vue b/app/pages/seat/[token].vue index e75b785..a7d9c83 100644 --- a/app/pages/seat/[token].vue +++ b/app/pages/seat/[token].vue @@ -29,6 +29,14 @@ 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)) + +useSeoMeta({ + title: () => `${getSeatLabel(receipt.value.seat.seatNumber)} - ${eventDetails.value.title}`, + description: () => `${receipt.value.seat.recipientName || receipt.value.booking.customerName} · ${ticketLabel.value}`, + ogTitle: () => `${getSeatLabel(receipt.value.seat.seatNumber)} - ${eventDetails.value.title}`, + ogDescription: () => `${receipt.value.seat.recipientName || receipt.value.booking.customerName} · ${ticketLabel.value}`, + robots: 'noindex,nofollow' +})