feat(seo): add meta tags and page titles
Configure default head meta and title template in nuxt.config.ts Add dynamic SEO meta tags and robots directives to all pages
This commit is contained in:
@@ -606,6 +606,12 @@ definePageMeta({
|
|||||||
middleware: 'auth'
|
middleware: 'auth'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useSeoMeta({
|
||||||
|
title: 'Bookings',
|
||||||
|
description: 'Manage dinner ticket bookings, confirmations, receipts, and seat allocation.',
|
||||||
|
robots: 'noindex,nofollow'
|
||||||
|
})
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const apiClient = useApiClient()
|
const apiClient = useApiClient()
|
||||||
const auth = useAuth()
|
const auth = useAuth()
|
||||||
|
|||||||
@@ -36,6 +36,15 @@ const statusColor = computed(() => booking.value.status === 'confirmed' ? 'succe
|
|||||||
const ticketLabel = computed(() => booking.value.ticketLabel || booking.value.ticketType.toUpperCase())
|
const ticketLabel = computed(() => booking.value.ticketLabel || booking.value.ticketType.toUpperCase())
|
||||||
const totalFormatted = computed(() => formatBookingCurrency(booking.value.totalPrice, locale.value))
|
const totalFormatted = computed(() => formatBookingCurrency(booking.value.totalPrice, locale.value))
|
||||||
const receiptPath = computed(() => `/receipt/${booking.value.receiptToken}`)
|
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 detailRows = computed(() => {
|
||||||
const rows = [
|
const rows = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,6 +27,20 @@ const [bookingConfig, contactsResponse] = await Promise.all([
|
|||||||
apiClient<{ contacts: PublicContact[] }>('/api/public/contacts')
|
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(() => [
|
const eventDetails = computed(() => [
|
||||||
{
|
{
|
||||||
label: t('common.date'),
|
label: t('common.date'),
|
||||||
|
|||||||
@@ -121,6 +121,12 @@ definePageMeta({
|
|||||||
middleware: 'guest'
|
middleware: 'guest'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useSeoMeta({
|
||||||
|
title: 'Staff Login',
|
||||||
|
description: 'Staff login for the dinner ticket management system.',
|
||||||
|
robots: 'noindex,nofollow'
|
||||||
|
})
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const auth = useAuth()
|
const auth = useAuth()
|
||||||
|
|||||||
@@ -255,6 +255,12 @@ definePageMeta({
|
|||||||
middleware: 'super-admin'
|
middleware: 'super-admin'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useSeoMeta({
|
||||||
|
title: 'User Management',
|
||||||
|
description: 'Manage staff access for the dinner ticket system.',
|
||||||
|
robots: 'noindex,nofollow'
|
||||||
|
})
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const apiClient = useApiClient()
|
const apiClient = useApiClient()
|
||||||
const auth = useAuth()
|
const auth = useAuth()
|
||||||
|
|||||||
@@ -52,6 +52,15 @@ const ticketLabel = computed(() => receipt.value.booking.ticketLabel || receipt.
|
|||||||
const statusColor = computed(() => receipt.value.booking.status === 'confirmed' ? 'success' : 'warning')
|
const statusColor = computed(() => receipt.value.booking.status === 'confirmed' ? 'success' : 'warning')
|
||||||
const sharedSeats = computed(() => receipt.value.seats.filter((seat) => Boolean(seat.sharedAt)))
|
const sharedSeats = computed(() => receipt.value.seats.filter((seat) => Boolean(seat.sharedAt)))
|
||||||
const availableSeats = computed(() => receipt.value.seats.filter((seat) => !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 maxShareCount = computed(() => Math.max(availableSeats.value.length, 1))
|
||||||
const normalizedShareCount = computed(() => {
|
const normalizedShareCount = computed(() => {
|
||||||
return Math.max(1, Math.min(Math.trunc(Number(shareForm.count) || 1), maxShareCount.value))
|
return Math.max(1, Math.min(Math.trunc(Number(shareForm.count) || 1), maxShareCount.value))
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ const receipt = ref(initialReceipt)
|
|||||||
const eventDetails = computed(() => receipt.value.booking.event)
|
const eventDetails = computed(() => receipt.value.booking.event)
|
||||||
const ticketLabel = computed(() => receipt.value.booking.ticketLabel || receipt.value.booking.ticketType.toUpperCase())
|
const ticketLabel = computed(() => receipt.value.booking.ticketLabel || receipt.value.booking.ticketType.toUpperCase())
|
||||||
const totalFormatted = computed(() => formatBookingCurrency(receipt.value.booking.totalPrice))
|
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'
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -114,6 +114,12 @@ definePageMeta({
|
|||||||
middleware: 'auth'
|
middleware: 'auth'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useSeoMeta({
|
||||||
|
title: 'Security Settings',
|
||||||
|
description: 'Manage password and passkey settings for the dinner ticket system.',
|
||||||
|
robots: 'noindex,nofollow'
|
||||||
|
})
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const auth = useAuth()
|
const auth = useAuth()
|
||||||
|
|||||||
@@ -6,6 +6,21 @@ export default defineNuxtConfig({
|
|||||||
devtools: { enabled: true },
|
devtools: { enabled: true },
|
||||||
modules: ['@nuxt/ui'],
|
modules: ['@nuxt/ui'],
|
||||||
css: ['~/assets/css/main.css'],
|
css: ['~/assets/css/main.css'],
|
||||||
|
app: {
|
||||||
|
head: {
|
||||||
|
title: 'Dinner Ticket System',
|
||||||
|
titleTemplate: (titleChunk) => titleChunk && titleChunk !== 'Dinner Ticket System'
|
||||||
|
? `${titleChunk} · Dinner Ticket System`
|
||||||
|
: 'Dinner Ticket System',
|
||||||
|
meta: [
|
||||||
|
{ name: 'description', content: 'Dinner ticket booking, confirmation, receipt, and seat sharing system.' },
|
||||||
|
{ property: 'og:site_name', content: 'Dinner Ticket System' },
|
||||||
|
{ property: 'og:type', content: 'website' },
|
||||||
|
{ name: 'twitter:card', content: 'summary' },
|
||||||
|
{ name: 'theme-color', content: '#dc2626' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
databaseUrl: '',
|
databaseUrl: '',
|
||||||
redisUrl: '',
|
redisUrl: '',
|
||||||
|
|||||||
Reference in New Issue
Block a user