refactor(ui): standardize page layouts and component styling

Introduce structural CSS classes for page shells, headers, and surface cards
Update primary theme color to red and neutral to zinc across the application
This commit is contained in:
2026-05-08 16:25:42 +08:00
parent bc009cffda
commit 227c64d346
12 changed files with 454 additions and 194 deletions

View File

@@ -29,6 +29,17 @@ The Git history follows Conventional Commit-style messages, commonly `feat(scope
Create local configuration from `.env.example`. Do not commit secrets, session keys, database URLs with credentials, or WhatsApp tokens. Keep `NUXT_PUBLIC_APP_URL` aligned with the browser origin because WebAuthn requires a stable relying-party origin. Create local configuration from `.env.example`. Do not commit secrets, session keys, database URLs with credentials, or WhatsApp tokens. Keep `NUXT_PUBLIC_APP_URL` aligned with the browser origin because WebAuthn requires a stable relying-party origin.
## UI Safety Rules (CRITICAL)
User-facing UI must NEVER contain:
- prompts
- remarks
- planning notes
- debug messages
- explanations of what was changed
- internal field names like `debug`, `meta`, `internal`
## Agent-Specific Instructions ## Agent-Specific Instructions
Keep implementations concise and scoped to the requested behavior. Prefer the smallest code change that fits existing patterns, and avoid unrelated refactors, formatting churn, or broad rewrites. Keep implementations concise and scoped to the requested behavior. Prefer the smallest code change that fits existing patterns, and avoid unrelated refactors, formatting churn, or broad rewrites.

View File

@@ -1,8 +1,8 @@
export default defineAppConfig({ export default defineAppConfig({
ui: { ui: {
colors: { colors: {
primary: 'amber', primary: 'red',
neutral: 'stone' neutral: 'zinc'
} }
} }
}) })

View File

@@ -1,11 +1,169 @@
@import "tailwindcss"; @import "tailwindcss";
@import "@nuxt/ui"; @import "@nuxt/ui";
:root {
color-scheme: light dark;
}
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
body { body {
min-height: 100dvh; min-height: 100dvh;
background: var(--ui-bg); background:
linear-gradient(180deg, color-mix(in srgb, var(--ui-primary) 7%, transparent) 0, transparent 22rem),
var(--ui-bg-muted);
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
}
button,
a,
input,
textarea,
select {
touch-action: manipulation;
}
::selection {
background: color-mix(in srgb, var(--ui-primary) 18%, transparent);
}
.page-shell {
width: min(100%, 80rem);
margin-inline: auto;
padding: 1.5rem 1rem 2.5rem;
}
.page-shell-narrow {
width: min(100%, 64rem);
margin-inline: auto;
padding: 1.5rem 1rem 2.5rem;
}
.page-shell-compact {
width: min(100%, 48rem);
margin-inline: auto;
padding: 1.5rem 1rem 2.5rem;
}
.page-header {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-bottom: 1.25rem;
}
.page-eyebrow {
display: inline-flex;
width: fit-content;
align-items: center;
gap: 0.5rem;
border-radius: 999px;
border: 1px solid color-mix(in srgb, var(--ui-primary) 18%, var(--ui-border));
background: color-mix(in srgb, var(--ui-primary) 8%, var(--ui-bg));
padding: 0.25rem 0.625rem;
color: var(--ui-primary);
font-size: 0.75rem;
font-weight: 650;
}
.page-title {
max-width: 44rem;
color: var(--ui-text-highlighted);
font-size: clamp(1.875rem, 1.5rem + 1.25vw, 3rem);
font-weight: 760;
line-height: 1.05;
}
.page-description {
max-width: 42rem;
color: var(--ui-text-muted);
font-size: 1rem;
line-height: 1.65;
}
.surface-card {
border: 1px solid color-mix(in srgb, var(--ui-border) 86%, transparent);
background: color-mix(in srgb, var(--ui-bg) 95%, transparent);
box-shadow: 0 1px 2px color-mix(in srgb, black 4%, transparent), 0 18px 48px color-mix(in srgb, black 7%, transparent);
}
.surface-panel {
border: 1px solid color-mix(in srgb, var(--ui-border) 88%, transparent);
background: color-mix(in srgb, var(--ui-bg-elevated) 76%, var(--ui-bg));
box-shadow: inset 0 1px 0 color-mix(in srgb, white 48%, transparent);
}
.metric-card {
position: relative;
overflow: hidden;
border: 1px solid color-mix(in srgb, var(--ui-border) 82%, transparent);
background: linear-gradient(180deg, color-mix(in srgb, var(--ui-bg) 98%, white) 0, color-mix(in srgb, var(--ui-bg-elevated) 82%, var(--ui-bg)) 100%);
box-shadow: 0 1px 2px color-mix(in srgb, black 4%, transparent);
}
.metric-card::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 3px;
background: var(--ui-primary);
opacity: 0.82;
}
.compact-table {
border-radius: 1rem;
}
.compact-table :where(th) {
background: var(--ui-bg-muted);
color: var(--ui-text-muted);
font-size: 0.75rem;
font-weight: 650;
text-transform: uppercase;
}
.form-grid {
display: grid;
gap: 1rem;
}
.action-row {
display: flex;
flex-direction: column-reverse;
gap: 0.625rem;
}
@media (min-width: 640px) {
.action-row {
flex-direction: row;
justify-content: flex-end;
}
}
@media (min-width: 768px) {
.page-shell,
.page-shell-narrow,
.page-shell-compact {
padding: 2rem 1.5rem 3rem;
}
.page-header {
margin-bottom: 1.5rem;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
}
} }

View File

@@ -1,7 +1,5 @@
<template> <template>
<div class="relative min-h-dvh bg-default text-default"> <div class="relative min-h-dvh bg-muted text-default">
<div class="pointer-events-none absolute inset-x-0 top-0 h-72 bg-gradient-to-b from-primary/10 via-primary/0 to-transparent opacity-80" />
<template v-if="auth.user.value"> <template v-if="auth.user.value">
<Transition <Transition
enter-active-class="transition-opacity duration-200 ease-out" enter-active-class="transition-opacity duration-200 ease-out"
@@ -30,11 +28,11 @@
> >
<aside <aside
v-if="mobileMenuOpen" v-if="mobileMenuOpen"
class="fixed inset-y-2 left-2 z-50 flex w-[min(20rem,calc(100vw-1rem))] flex-col rounded-[28px] border border-default/80 bg-default/96 p-3 shadow-2xl backdrop-blur-xl lg:hidden" class="fixed inset-y-2 left-2 z-50 flex w-[min(20rem,calc(100vw-1rem))] flex-col rounded-lg border border-default/80 bg-default/96 p-3 shadow-2xl backdrop-blur-xl lg:hidden"
> >
<div class="flex items-start justify-between gap-3 rounded-3xl border border-default bg-gradient-to-br from-primary/12 via-default to-default px-4 py-4"> <div class="flex items-start justify-between gap-3 rounded-lg border border-default bg-elevated px-4 py-4">
<div class="flex min-w-0 items-center gap-3"> <div class="flex min-w-0 items-center gap-3">
<div class="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-primary/12 text-primary"> <div class="flex size-11 shrink-0 items-center justify-center rounded-lg bg-primary/12 text-primary">
<UIcon name="i-lucide-grid-2x2" class="size-5" /> <UIcon name="i-lucide-grid-2x2" class="size-5" />
</div> </div>
@@ -64,11 +62,11 @@
:key="item.to" :key="item.to"
:to="item.to" :to="item.to"
:aria-current="isMenuItemActive(item) ? 'page' : undefined" :aria-current="isMenuItemActive(item) ? 'page' : undefined"
class="group flex min-h-14 items-center gap-3 rounded-2xl border px-4 py-3 text-sm font-medium transition-all duration-200" class="group flex min-h-14 items-center gap-3 rounded-lg border px-4 py-3 text-sm font-medium transition-all duration-200"
:class="mobileMenuItemClasses(item)" :class="mobileMenuItemClasses(item)"
> >
<div <div
class="flex size-10 shrink-0 items-center justify-center rounded-2xl transition-colors duration-200" class="flex size-10 shrink-0 items-center justify-center rounded-lg transition-colors duration-200"
:class="mobileMenuIconClasses(item)" :class="mobileMenuIconClasses(item)"
> >
<UIcon :name="item.icon" class="size-5" /> <UIcon :name="item.icon" class="size-5" />
@@ -79,9 +77,9 @@
</nav> </nav>
<div class="mt-auto space-y-3 border-t border-default pt-3"> <div class="mt-auto space-y-3 border-t border-default pt-3">
<div class="rounded-3xl border border-default bg-default/90 px-4 py-4 shadow-sm"> <div class="rounded-lg border border-default bg-elevated px-4 py-4 shadow-sm">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-muted text-highlighted"> <div class="flex size-11 shrink-0 items-center justify-center rounded-lg bg-muted text-highlighted">
<UIcon name="i-lucide-user-round" class="size-5" /> <UIcon name="i-lucide-user-round" class="size-5" />
</div> </div>
@@ -109,7 +107,7 @@
color="neutral" color="neutral"
variant="ghost" variant="ghost"
icon="i-lucide-house" icon="i-lucide-house"
class="w-full justify-start rounded-2xl" class="w-full justify-start rounded-lg"
@click="mobileMenuOpen = false" @click="mobileMenuOpen = false"
/> />
@@ -118,7 +116,7 @@
color="neutral" color="neutral"
variant="ghost" variant="ghost"
icon="i-lucide-languages" icon="i-lucide-languages"
class="w-full justify-start rounded-2xl" class="w-full justify-start rounded-lg"
:aria-label="t('common.switchLanguage')" :aria-label="t('common.switchLanguage')"
@click="toggleLocale" @click="toggleLocale"
/> />
@@ -129,7 +127,7 @@
color="neutral" color="neutral"
variant="outline" variant="outline"
icon="i-lucide-log-out" icon="i-lucide-log-out"
class="w-full justify-start rounded-2xl" class="w-full justify-start rounded-lg"
@click="logout" @click="logout"
/> />
</div> </div>
@@ -138,9 +136,9 @@
<div class="relative lg:grid lg:min-h-dvh lg:grid-cols-[17.5rem_minmax(0,1fr)]"> <div class="relative lg:grid lg:min-h-dvh lg:grid-cols-[17.5rem_minmax(0,1fr)]">
<aside class="sticky top-0 hidden h-dvh flex-col border-r border-default/80 bg-default/92 px-5 py-5 backdrop-blur-xl lg:flex"> <aside class="sticky top-0 hidden h-dvh flex-col border-r border-default/80 bg-default/92 px-5 py-5 backdrop-blur-xl lg:flex">
<div class="rounded-[28px] border border-default bg-gradient-to-br from-primary/12 via-default to-default px-4 py-4 shadow-sm"> <div class="rounded-lg border border-default bg-elevated px-4 py-4 shadow-sm">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="flex size-12 shrink-0 items-center justify-center rounded-2xl bg-primary/12 text-primary"> <div class="flex size-12 shrink-0 items-center justify-center rounded-lg bg-primary/12 text-primary">
<UIcon name="i-lucide-grid-2x2" class="size-6" /> <UIcon name="i-lucide-grid-2x2" class="size-6" />
</div> </div>
@@ -161,11 +159,11 @@
:key="item.to" :key="item.to"
:to="item.to" :to="item.to"
:aria-current="isMenuItemActive(item) ? 'page' : undefined" :aria-current="isMenuItemActive(item) ? 'page' : undefined"
class="group flex min-h-14 items-center gap-3 rounded-2xl border px-4 py-3 text-sm font-medium transition-all duration-200" class="group flex min-h-14 items-center gap-3 rounded-lg border px-4 py-3 text-sm font-medium transition-all duration-200"
:class="desktopMenuItemClasses(item)" :class="desktopMenuItemClasses(item)"
> >
<div <div
class="flex size-10 shrink-0 items-center justify-center rounded-2xl transition-colors duration-200" class="flex size-10 shrink-0 items-center justify-center rounded-lg transition-colors duration-200"
:class="desktopMenuIconClasses(item)" :class="desktopMenuIconClasses(item)"
> >
<UIcon :name="item.icon" class="size-5" /> <UIcon :name="item.icon" class="size-5" />
@@ -179,9 +177,9 @@
</nav> </nav>
<div class="mt-auto space-y-3"> <div class="mt-auto space-y-3">
<div class="rounded-[28px] border border-default bg-default/90 px-4 py-4 shadow-sm"> <div class="rounded-lg border border-default bg-elevated px-4 py-4 shadow-sm">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-muted text-highlighted"> <div class="flex size-11 shrink-0 items-center justify-center rounded-lg bg-muted text-highlighted">
<UIcon name="i-lucide-user-round" class="size-5" /> <UIcon name="i-lucide-user-round" class="size-5" />
</div> </div>
@@ -210,7 +208,7 @@
color="neutral" color="neutral"
variant="ghost" variant="ghost"
icon="i-lucide-house" icon="i-lucide-house"
class="justify-start rounded-2xl" class="justify-start rounded-lg"
/> />
<UButton <UButton
@@ -218,7 +216,7 @@
color="neutral" color="neutral"
variant="ghost" variant="ghost"
icon="i-lucide-languages" icon="i-lucide-languages"
class="justify-start rounded-2xl" class="justify-start rounded-lg"
:aria-label="t('common.switchLanguage')" :aria-label="t('common.switchLanguage')"
@click="toggleLocale" @click="toggleLocale"
/> />
@@ -229,19 +227,19 @@
color="neutral" color="neutral"
variant="outline" variant="outline"
icon="i-lucide-log-out" icon="i-lucide-log-out"
class="justify-start rounded-2xl" class="justify-start rounded-lg"
@click="logout" @click="logout"
/> />
</div> </div>
</div> </div>
</aside> </aside>
<div class="min-w-0"> <div class="min-w-0 bg-muted">
<header class="sticky top-0 z-30 border-b border-default/80 bg-default/92 backdrop-blur-xl lg:hidden"> <header class="sticky top-0 z-30 border-b border-default/80 bg-default/92 backdrop-blur-xl lg:hidden">
<UContainer class="py-3"> <UContainer class="py-3">
<div class="flex items-center justify-between gap-3"> <div class="flex items-center justify-between gap-3">
<div class="flex min-w-0 items-center gap-3 rounded-2xl border border-default bg-default/80 px-3 py-2 shadow-sm"> <div class="flex min-w-0 items-center gap-3 rounded-lg border border-default bg-elevated px-3 py-2 shadow-sm">
<div class="flex size-10 shrink-0 items-center justify-center rounded-2xl bg-primary/12 text-primary"> <div class="flex size-10 shrink-0 items-center justify-center rounded-lg bg-primary/12 text-primary">
<UIcon name="i-lucide-grid-2x2" class="size-5" /> <UIcon name="i-lucide-grid-2x2" class="size-5" />
</div> </div>
@@ -286,8 +284,8 @@
<header class="relative border-b border-default/80 bg-default/92 backdrop-blur-xl"> <header class="relative border-b border-default/80 bg-default/92 backdrop-blur-xl">
<UContainer class="py-4 md:py-5"> <UContainer class="py-4 md:py-5">
<div class="flex items-center justify-between gap-3"> <div class="flex items-center justify-between gap-3">
<div class="flex min-w-0 items-center gap-3 rounded-2xl border border-default bg-default/80 px-3 py-2 shadow-sm"> <div class="flex min-w-0 items-center gap-3 rounded-lg border border-default bg-elevated px-3 py-2 shadow-sm">
<div class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary"> <div class="flex size-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
<UIcon name="i-lucide-grid-2x2" class="size-5" /> <UIcon name="i-lucide-grid-2x2" class="size-5" />
</div> </div>

View File

@@ -1,13 +1,13 @@
<template> <template>
<UContainer class="py-6"> <UContainer class="page-shell">
<div class="mx-auto max-w-7xl space-y-4"> <div class="space-y-5">
<div class="flex flex-col gap-2 lg:flex-row lg:items-end lg:justify-between"> <div class="page-header lg:flex-row lg:items-end lg:justify-between">
<div class="space-y-1"> <div class="space-y-1">
<UBadge label="Bookings" color="primary" variant="soft" size="sm" class="rounded-full" /> <UBadge label="Bookings" color="primary" variant="soft" size="sm" class="page-eyebrow" />
<h1 class="text-2xl font-bold tracking-tight text-highlighted"> <h1 class="page-title">
Booking list Booking list
</h1> </h1>
<p class="text-sm text-muted"> <p class="page-description">
{{ auth.isSuperAdmin.value ? 'All submitted bookings across every PIC.' : 'Bookings assigned to you as PIC.' }} {{ auth.isSuperAdmin.value ? 'All submitted bookings across every PIC.' : 'Bookings assigned to you as PIC.' }}
</p> </p>
</div> </div>
@@ -31,7 +31,7 @@
/> />
<div v-if="auth.isSuperAdmin.value" class="grid gap-4 xl:grid-cols-[minmax(0,1.2fr)_18rem]"> <div v-if="auth.isSuperAdmin.value" class="grid gap-4 xl:grid-cols-[minmax(0,1.2fr)_18rem]">
<UCard class="border border-default bg-default shadow-sm" :ui="{ header: 'px-4 py-3', body: 'px-4 py-3' }"> <UCard class="surface-card rounded-lg" :ui="{ header: 'px-4 py-3', body: 'px-4 py-3' }">
<template #header> <template #header>
<div class="space-y-1"> <div class="space-y-1">
<h2 class="text-base font-semibold text-highlighted"> <h2 class="text-base font-semibold text-highlighted">
@@ -75,7 +75,7 @@
</UForm> </UForm>
</UCard> </UCard>
<UCard class="border border-default bg-default shadow-sm" :ui="{ header: 'px-4 py-3', body: 'px-4 py-3' }"> <UCard class="surface-card rounded-lg" :ui="{ header: 'px-4 py-3', body: 'px-4 py-3' }">
<template #header> <template #header>
<div class="space-y-1"> <div class="space-y-1">
<h2 class="text-base font-semibold text-highlighted"> <h2 class="text-base font-semibold text-highlighted">
@@ -88,7 +88,7 @@
</template> </template>
<div class="grid gap-4 sm:grid-cols-2"> <div class="grid gap-4 sm:grid-cols-2">
<div class="rounded-lg border border-default bg-muted/20 p-3"> <div class="surface-panel rounded-lg p-3">
<p class="text-xs uppercase tracking-wide text-muted"> <p class="text-xs uppercase tracking-wide text-muted">
Pending bookings Pending bookings
</p> </p>
@@ -97,7 +97,7 @@
</p> </p>
</div> </div>
<div class="rounded-lg border border-default bg-muted/20 p-3"> <div class="surface-panel rounded-lg p-3">
<p class="text-xs uppercase tracking-wide text-muted"> <p class="text-xs uppercase tracking-wide text-muted">
Pending seats Pending seats
</p> </p>
@@ -113,7 +113,7 @@
<UCard <UCard
v-for="item in inventoryCards" v-for="item in inventoryCards"
:key="item.label" :key="item.label"
class="border border-default bg-default shadow-sm" class="metric-card rounded-lg"
:ui="{ body: 'px-4 py-3' }" :ui="{ body: 'px-4 py-3' }"
> >
<div class="space-y-0.5"> <div class="space-y-0.5">
@@ -128,7 +128,7 @@
</div> </div>
<div class="grid gap-3 md:grid-cols-3"> <div class="grid gap-3 md:grid-cols-3">
<UCard class="border border-default bg-default shadow-sm" :ui="{ body: 'px-4 py-3' }"> <UCard class="metric-card rounded-lg" :ui="{ body: 'px-4 py-3' }">
<div class="space-y-0.5"> <div class="space-y-0.5">
<p class="text-[11px] font-medium uppercase tracking-wide text-muted"> <p class="text-[11px] font-medium uppercase tracking-wide text-muted">
Total Seats Total Seats
@@ -139,7 +139,7 @@
</div> </div>
</UCard> </UCard>
<UCard class="border border-default bg-default shadow-sm" :ui="{ body: 'px-4 py-3' }"> <UCard class="metric-card rounded-lg" :ui="{ body: 'px-4 py-3' }">
<div class="space-y-0.5"> <div class="space-y-0.5">
<p class="text-[11px] font-medium uppercase tracking-wide text-muted"> <p class="text-[11px] font-medium uppercase tracking-wide text-muted">
Total bookings Total bookings
@@ -150,7 +150,7 @@
</div> </div>
</UCard> </UCard>
<UCard class="border border-default bg-default shadow-sm" :ui="{ body: 'px-4 py-3' }"> <UCard class="metric-card rounded-lg" :ui="{ body: 'px-4 py-3' }">
<div class="space-y-0.5"> <div class="space-y-0.5">
<p class="text-[11px] font-medium uppercase tracking-wide text-muted"> <p class="text-[11px] font-medium uppercase tracking-wide text-muted">
Booking status Booking status
@@ -163,7 +163,7 @@
</div> </div>
<UCard <UCard
class="border border-default bg-default shadow-sm" class="surface-card overflow-hidden rounded-lg"
:ui="{ header: 'px-4 py-3', body: 'p-0 sm:p-0' }" :ui="{ header: 'px-4 py-3', body: 'p-0 sm:p-0' }"
> >
<template #header> <template #header>
@@ -197,7 +197,7 @@
:empty="searchQuery.trim() ? 'No matching bookings found.' : 'No bookings available yet.'" :empty="searchQuery.trim() ? 'No matching bookings found.' : 'No bookings available yet.'"
sticky="header" sticky="header"
caption="Bookings" caption="Bookings"
class="min-w-[1120px]" class="compact-table min-w-[1120px]"
> >
<template #customerName-cell="{ row }"> <template #customerName-cell="{ row }">
<div class="min-w-0 space-y-0.5 py-0.5"> <div class="min-w-0 space-y-0.5 py-0.5">
@@ -363,7 +363,7 @@
class="space-y-4" class="space-y-4"
@submit="saveBookingDetails" @submit="saveBookingDetails"
> >
<div v-if="detailsBooking" class="rounded-lg border border-default bg-muted/20 px-3 py-2"> <div v-if="detailsBooking" class="surface-panel rounded-lg px-3 py-2">
<p class="text-sm font-medium text-highlighted"> <p class="text-sm font-medium text-highlighted">
{{ detailsBooking.customerName }} {{ detailsBooking.customerName }}
</p> </p>
@@ -426,12 +426,12 @@
:disabled="savingDetails || !ticketCatalogItems.length" :disabled="savingDetails || !ticketCatalogItems.length"
:ui="{ :ui="{
fieldset: 'grid grid-cols-2 gap-3', fieldset: 'grid grid-cols-2 gap-3',
item: 'rounded-lg border border-default bg-default p-3 data-[state=checked]:border-primary data-[state=checked]:bg-primary/5' item: 'rounded-lg border border-default bg-default p-3 transition-colors data-[state=checked]:border-primary data-[state=checked]:bg-primary/5'
}" }"
/> />
</UFormField> </UFormField>
<div class="rounded-lg border border-default bg-muted/30 px-4 py-3"> <div class="surface-panel rounded-lg px-4 py-3">
<div class="flex items-center justify-between gap-4"> <div class="flex items-center justify-between gap-4">
<span class="text-sm font-medium text-muted">Updated total</span> <span class="text-sm font-medium text-muted">Updated total</span>
<div class="text-right"> <div class="text-right">
@@ -490,7 +490,7 @@
> >
<template #body> <template #body>
<div class="space-y-4"> <div class="space-y-4">
<div v-if="editingBooking" class="rounded-lg border border-default bg-muted/20 px-3 py-2"> <div v-if="editingBooking" class="surface-panel rounded-lg px-3 py-2">
<p class="text-sm font-medium text-highlighted"> <p class="text-sm font-medium text-highlighted">
{{ editingBooking.customerName }} {{ editingBooking.customerName }}
</p> </p>
@@ -543,7 +543,7 @@
> >
<template #body> <template #body>
<div class="space-y-4"> <div class="space-y-4">
<div v-if="transferringBooking" class="rounded-lg border border-default bg-muted/20 px-3 py-2"> <div v-if="transferringBooking" class="surface-panel rounded-lg px-3 py-2">
<p class="text-sm font-medium text-highlighted"> <p class="text-sm font-medium text-highlighted">
{{ transferringBooking.customerName }} {{ transferringBooking.customerName }}
</p> </p>

View File

@@ -160,20 +160,20 @@ async function cancelBookingConfirmation() {
</script> </script>
<template> <template>
<UContainer class="py-8"> <UContainer class="page-shell-compact">
<div class="mx-auto max-w-3xl space-y-5"> <div class="space-y-5">
<div class="space-y-2 text-center"> <div class="page-header text-center sm:items-center">
<UBadge :label="t('confirm.badge')" color="primary" variant="soft" class="rounded-full" /> <UBadge :label="t('confirm.badge')" color="primary" variant="soft" class="page-eyebrow" />
<h1 class="text-2xl font-bold tracking-tight text-highlighted sm:text-3xl"> <h1 class="page-title">
{{ t('confirm.title') }} {{ t('confirm.title') }}
</h1> </h1>
<p class="text-sm text-muted"> <p class="page-description">
{{ t('confirm.description') }} {{ t('confirm.description') }}
</p> </p>
</div> </div>
<UCard <UCard
class="border border-default bg-default shadow-sm" class="surface-card overflow-hidden rounded-lg"
:ui="{ body: 'space-y-4 p-4 sm:p-5' }" :ui="{ body: 'space-y-4 p-4 sm:p-5' }"
> >
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
@@ -197,7 +197,7 @@ async function cancelBookingConfirmation() {
icon="i-lucide-badge-check" icon="i-lucide-badge-check"
/> />
<div class="overflow-hidden rounded-xl border border-default"> <div class="overflow-hidden rounded-lg border border-default">
<div <div
v-for="row in detailRows" v-for="row in detailRows"
:key="row.label" :key="row.label"
@@ -221,7 +221,7 @@ async function cancelBookingConfirmation() {
</div> </div>
</div> </div>
<div class="flex flex-col-reverse gap-3 sm:flex-row sm:justify-end"> <div class="action-row">
<UButton <UButton
to="/" to="/"
:label="t('confirm.backToForm')" :label="t('confirm.backToForm')"

View File

@@ -234,30 +234,58 @@ async function bookTicket(event: FormSubmitEvent<typeof form>) {
</script> </script>
<template> <template>
<UContainer class="py-8"> <UContainer class="page-shell-narrow">
<div class="mx-auto max-w-2xl"> <div class="grid gap-8 xl:grid-cols-[minmax(0,1fr)_34rem] xl:items-start">
<div class="mb-8 text-center"> <section class="space-y-6 xl:sticky xl:top-6">
<h1 class="text-3xl font-extrabold tracking-tight text-highlighted sm:text-4xl"> <div class="page-header">
<UBadge :label="t('layout.brand')" color="primary" variant="soft" class="page-eyebrow" />
<h1 class="page-title">
{{ bookingConfig.event.title }} {{ bookingConfig.event.title }}
</h1> </h1>
<p class="page-description">
{{ t('booking.seatGeneration', { count: seatCount, seatLabel: locale === 'zh' ? '座位' : `seat${seatCount === 1 ? '' : 's'}` }) }}
</p>
</div> </div>
<UCard id="booking-form" class="border border-default bg-default" :ui="{ <div class="grid gap-3 sm:grid-cols-3 xl:grid-cols-1">
header: 'space-y-4', <div
body: 'space-y-6' v-for="detail in eventDetails"
}"> :key="detail.label"
<template #header> class="surface-card rounded-lg p-4"
<div class="space-y-3"> >
<div v-for="detail in eventDetails" :key="detail.label" <div class="flex items-start gap-3">
class="flex items-center gap-3 text-sm text-default"> <div class="flex size-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
<UIcon :name="detail.icon" class="size-4 text-muted" /> <UIcon :name="detail.icon" class="size-5" />
<span>{{ detail.value }}</span> </div>
<div class="min-w-0">
<p class="text-xs font-semibold uppercase text-muted">
{{ detail.label }}
</p>
<p class="mt-1 text-sm font-semibold leading-6 text-highlighted break-words">
{{ detail.value }}
</p>
</div> </div>
</div> </div>
</template> </div>
</div>
</section>
<UCard
id="booking-form"
class="surface-card overflow-hidden rounded-lg"
:ui="{ body: 'space-y-6 p-5 sm:p-6' }"
>
<div class="space-y-1">
<p class="text-sm font-semibold text-primary">
{{ t('booking.bookNow') }}
</p>
<h2 class="text-xl font-semibold text-highlighted">
{{ t('booking.ticketCategory') }}
</h2>
</div>
<UForm :state="form" :validate="validateBooking" class="space-y-6" @submit="bookTicket"> <UForm :state="form" :validate="validateBooking" class="space-y-6" @submit="bookTicket">
<div class="space-y-5"> <div class="grid gap-5 sm:grid-cols-2">
<UFormField name="name" :label="t('booking.name')" required> <UFormField name="name" :label="t('booking.name')" required>
<UInput v-model="form.name" size="xl" class="w-full" :placeholder="t('booking.namePlaceholder')" /> <UInput v-model="form.name" size="xl" class="w-full" :placeholder="t('booking.namePlaceholder')" />
</UFormField> </UFormField>
@@ -268,13 +296,20 @@ async function bookTicket(event: FormSubmitEvent<typeof form>) {
</div> </div>
<UFormField :label="t('booking.bookingMode')" name="bookingMode"> <UFormField :label="t('booking.bookingMode')" name="bookingMode">
<URadioGroup v-model="form.bookingMode" orientation="horizontal" variant="card" indicator="hidden" <URadioGroup
:items="bookingModeOptions" :ui="{ v-model="form.bookingMode"
orientation="horizontal"
variant="card"
indicator="hidden"
:items="bookingModeOptions"
:ui="{
fieldset: 'grid grid-cols-2 gap-3', fieldset: 'grid grid-cols-2 gap-3',
item: 'rounded-xl border border-default bg-default p-3 data-[state=checked]:border-primary data-[state=checked]:bg-primary/5' item: 'rounded-lg border border-default bg-default p-3 transition-colors data-[state=checked]:border-primary data-[state=checked]:bg-primary/5'
}" /> }"
/>
</UFormField> </UFormField>
<div class="grid gap-5 sm:grid-cols-[minmax(0,1fr)_minmax(0,1.1fr)]">
<UFormField :label="quantityLabel" name="quantity"> <UFormField :label="quantityLabel" name="quantity">
<UInputNumber v-model="form.quantity" size="xl" class="w-full" :min="1" :step="1" /> <UInputNumber v-model="form.quantity" size="xl" class="w-full" :min="1" :step="1" />
<template #help> <template #help>
@@ -282,21 +317,6 @@ async function bookTicket(event: FormSubmitEvent<typeof form>) {
</template> </template>
</UFormField> </UFormField>
<UFormField :label="t('booking.ticketCategory')" name="ticketType">
<URadioGroup v-model="form.ticketType" orientation="horizontal" variant="card" indicator="hidden"
:items="ticketCatalogOptions" :ui="{
fieldset: 'grid grid-cols-2 gap-3',
item: 'rounded-xl border border-default bg-default p-3 data-[state=checked]:border-primary data-[state=checked]:bg-primary/5'
}" />
</UFormField>
<div class="rounded-xl border border-default bg-muted px-4 py-4">
<div class="flex items-center justify-between gap-4">
<span class="text-sm font-medium text-muted">{{ t('common.totalPrice') }}</span>
<span class="text-2xl font-bold text-highlighted">{{ totalFormatted }}</span>
</div>
</div>
<UFormField :label="t('booking.personInCharge')"> <UFormField :label="t('booking.personInCharge')">
<USelect <USelect
v-model="selectedPersonInCharge" v-model="selectedPersonInCharge"
@@ -306,9 +326,38 @@ async function bookTicket(event: FormSubmitEvent<typeof form>) {
:disabled="!personInCharge.length" :disabled="!personInCharge.length"
/> />
</UFormField> </UFormField>
</div>
<UButton id="getTicketBtn" type="submit" :label="t('booking.bookNow')" size="xl" <UFormField :label="t('booking.ticketCategory')" name="ticketType">
class="w-full justify-center" :disabled="!selectedPersonInCharge || !selectedBookingMode || !selectedTicket" :loading="submittingBooking" /> <URadioGroup
v-model="form.ticketType"
orientation="horizontal"
variant="card"
indicator="hidden"
:items="ticketCatalogOptions"
:ui="{
fieldset: 'grid grid-cols-1 gap-3 sm:grid-cols-2',
item: 'rounded-lg border border-default bg-default p-3 transition-colors data-[state=checked]:border-primary data-[state=checked]:bg-primary/5'
}"
/>
</UFormField>
<div class="surface-panel rounded-lg px-4 py-4">
<div class="flex items-center justify-between gap-4">
<span class="text-sm font-medium text-muted">{{ t('common.totalPrice') }}</span>
<span class="text-2xl font-bold tabular-nums text-highlighted">{{ totalFormatted }}</span>
</div>
</div>
<UButton
id="getTicketBtn"
type="submit"
:label="t('booking.bookNow')"
size="xl"
class="w-full justify-center"
:disabled="!selectedPersonInCharge || !selectedBookingMode || !selectedTicket"
:loading="submittingBooking"
/>
</UForm> </UForm>
</UCard> </UCard>
</div> </div>

View File

@@ -1,13 +1,59 @@
<template> <template>
<UContainer class="py-10 lg:py-16"> <UContainer class="page-shell-narrow">
<div class="mx-auto max-w-md"> <div class="grid gap-8 lg:grid-cols-[minmax(0,1fr)_27rem] lg:items-center">
<UCard class="border border-default bg-default shadow-sm"> <section class="page-header">
<template #header> <UBadge :label="t('login.badge')" color="primary" variant="soft" class="page-eyebrow" />
<div class="space-y-2"> <h1 class="page-title">
<UBadge :label="t('login.badge')" color="primary" variant="soft" class="rounded-full" />
<h1 class="text-3xl font-bold text-highlighted">
{{ t('login.title') }} {{ t('login.title') }}
</h1> </h1>
<p class="page-description">
{{ t('layout.brand') }}
</p>
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-1">
<div class="surface-card rounded-lg p-4">
<div class="flex items-center gap-3">
<div class="flex size-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
<UIcon name="i-lucide-lock-keyhole" class="size-5" />
</div>
<div>
<p class="text-sm font-semibold text-highlighted">
{{ t('login.signIn') }}
</p>
<p class="text-sm text-muted">
{{ t('login.remember') }}
</p>
</div>
</div>
</div>
<div class="surface-card rounded-lg p-4">
<div class="flex items-center gap-3">
<div class="flex size-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
<UIcon name="i-lucide-fingerprint" class="size-5" />
</div>
<div>
<p class="text-sm font-semibold text-highlighted">
{{ t('login.passkey') }}
</p>
<p class="text-sm text-muted">
{{ t('login.or') }}
</p>
</div>
</div>
</div>
</div>
</section>
<UCard class="surface-card overflow-hidden rounded-lg">
<template #header>
<div class="space-y-1">
<p class="text-sm font-semibold text-primary">
{{ t('login.badge') }}
</p>
<h2 class="text-xl font-semibold text-highlighted">
{{ t('login.signIn') }}
</h2>
</div> </div>
</template> </template>
@@ -45,11 +91,10 @@
<div class="my-6 flex items-center gap-3"> <div class="my-6 flex items-center gap-3">
<div class="h-px flex-1 bg-default" /> <div class="h-px flex-1 bg-default" />
<span class="text-xs font-semibold uppercase tracking-[0.2em] text-muted">{{ t('login.or') }}</span> <span class="text-xs font-semibold uppercase text-muted">{{ t('login.or') }}</span>
<div class="h-px flex-1 bg-default" /> <div class="h-px flex-1 bg-default" />
</div> </div>
<div class="space-y-4">
<UButton <UButton
:label="t('login.passkey')" :label="t('login.passkey')"
color="neutral" color="neutral"
@@ -60,7 +105,6 @@
:loading="passkeyPending" :loading="passkeyPending"
@click="loginWithPasskey" @click="loginWithPasskey"
/> />
</div>
</UCard> </UCard>
</div> </div>
</UContainer> </UContainer>

View File

@@ -1,9 +1,9 @@
<template> <template>
<UContainer class="py-8"> <UContainer class="page-shell">
<div class="mx-auto max-w-6xl space-y-4"> <div class="space-y-5">
<div class="space-y-1.5"> <div class="page-header">
<UBadge label="Super Admin" color="primary" variant="soft" class="rounded-full" /> <UBadge label="Super Admin" color="primary" variant="soft" class="page-eyebrow" />
<h1 class="text-2xl font-bold text-highlighted sm:text-3xl"> <h1 class="page-title">
User management User management
</h1> </h1>
</div> </div>
@@ -16,7 +16,7 @@
icon="i-lucide-key-round" icon="i-lucide-key-round"
/> />
<UCard class="border border-default bg-default shadow-sm" :ui="{ body: 'p-0 sm:p-0' }"> <UCard class="surface-card overflow-hidden rounded-lg" :ui="{ body: 'p-0 sm:p-0' }">
<template #header> <template #header>
<div class="flex flex-col gap-2.5 lg:flex-row lg:items-center lg:justify-between"> <div class="flex flex-col gap-2.5 lg:flex-row lg:items-center lg:justify-between">
<div class="flex flex-col gap-2 sm:flex-row sm:items-center"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center">
@@ -49,8 +49,8 @@
</template> </template>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<div class="min-w-[900px]" aria-label="Users"> <div class="compact-table min-w-[900px]" aria-label="Users">
<div class="grid grid-cols-[56px_minmax(190px,1.4fr)_minmax(150px,1fr)_120px_minmax(190px,1.2fr)_150px_minmax(230px,auto)] items-center gap-3 border-b border-default bg-muted px-4 py-2 text-xs font-semibold uppercase text-muted"> <div class="grid grid-cols-[56px_minmax(190px,1.4fr)_minmax(150px,1fr)_120px_minmax(190px,1.2fr)_150px_minmax(230px,auto)] items-center gap-3 border-b border-default bg-muted px-4 py-3 text-xs font-semibold uppercase text-muted">
<div>Order</div> <div>Order</div>
<div>Display Name</div> <div>Display Name</div>
<div>PIC Phone</div> <div>PIC Phone</div>

View File

@@ -365,22 +365,22 @@ async function openBatchShare() {
</script> </script>
<template> <template>
<UContainer class="py-6 sm:py-8"> <UContainer class="page-shell-narrow">
<div class="mx-auto max-w-5xl space-y-5"> <div class="space-y-6">
<div class="space-y-1 text-center"> <div class="page-header text-center sm:items-center">
<UBadge :label="t('receipt.badge')" color="primary" variant="soft" class="rounded-full" /> <UBadge :label="t('receipt.badge')" color="primary" variant="soft" class="page-eyebrow" />
<h1 class="text-2xl font-bold tracking-tight text-highlighted sm:text-3xl"> <h1 class="page-title">
{{ eventDetails.title }} {{ eventDetails.title }}
</h1> </h1>
</div> </div>
<div class="rounded-2xl border border-default bg-default p-1.5 shadow-sm"> <div class="surface-card rounded-lg p-1.5">
<div class="flex gap-1.5"> <div class="flex gap-1.5">
<button <button
v-for="tab in tabs" v-for="tab in tabs"
:key="tab.id" :key="tab.id"
type="button" type="button"
class="flex min-h-10 flex-1 items-center justify-center gap-1.5 rounded-xl px-3 py-2 text-xs font-medium transition sm:text-sm" class="flex min-h-10 flex-1 items-center justify-center gap-1.5 rounded-lg px-3 py-2 text-xs font-medium transition sm:text-sm"
:class="activeTab === tab.id :class="activeTab === tab.id
? 'bg-primary text-inverted shadow-sm' ? 'bg-primary text-inverted shadow-sm'
: 'bg-elevated text-default hover:bg-muted'" : 'bg-elevated text-default hover:bg-muted'"
@@ -394,16 +394,16 @@ async function openBatchShare() {
<UCard <UCard
v-if="activeTab === 'main'" v-if="activeTab === 'main'"
class="border border-default bg-default shadow-sm" class="surface-card overflow-hidden rounded-lg"
:ui="{ body: 'p-5 sm:p-8' }" :ui="{ body: 'p-5 sm:p-8' }"
> >
<div class="mx-auto flex max-w-sm flex-col items-center gap-5 text-center"> <div class="mx-auto flex max-w-sm flex-col items-center gap-5 text-center">
<div class="rounded-[1.75rem] border border-default bg-elevated p-5 shadow-sm"> <div class="surface-panel rounded-lg p-5 shadow-sm">
<QrCodeSvg :value="receipt.receiptUrl" :size="240" /> <QrCodeSvg :value="receipt.receiptUrl" :size="240" />
</div> </div>
<div class="grid w-full gap-2 sm:grid-cols-3"> <div class="grid w-full gap-2 sm:grid-cols-3">
<div class="rounded-2xl border border-default bg-elevated px-4 py-3 text-left"> <div class="surface-panel rounded-lg px-4 py-3 text-left">
<p class="text-[11px] font-medium uppercase tracking-wide text-muted"> <p class="text-[11px] font-medium uppercase tracking-wide text-muted">
{{ t('common.category') }} {{ t('common.category') }}
</p> </p>
@@ -412,7 +412,7 @@ async function openBatchShare() {
</p> </p>
</div> </div>
<div class="rounded-2xl border border-default bg-elevated px-4 py-3 text-left"> <div class="surface-panel rounded-lg px-4 py-3 text-left">
<p class="text-[11px] font-medium uppercase tracking-wide text-muted"> <p class="text-[11px] font-medium uppercase tracking-wide text-muted">
{{ t('common.seats') }} {{ t('common.seats') }}
</p> </p>
@@ -421,7 +421,7 @@ async function openBatchShare() {
</p> </p>
</div> </div>
<div class="rounded-2xl border border-default bg-elevated px-4 py-3 text-left"> <div class="surface-panel rounded-lg px-4 py-3 text-left">
<p class="text-[11px] font-medium uppercase tracking-wide text-muted"> <p class="text-[11px] font-medium uppercase tracking-wide text-muted">
{{ t('common.totalPrice') }} {{ t('common.totalPrice') }}
</p> </p>
@@ -443,10 +443,10 @@ async function openBatchShare() {
<UCard <UCard
v-else-if="activeTab === 'status'" v-else-if="activeTab === 'status'"
class="border border-default bg-default shadow-sm" class="surface-card overflow-hidden rounded-lg"
:ui="{ body: 'p-0' }" :ui="{ body: 'p-0' }"
> >
<div class="overflow-hidden rounded-2xl"> <div class="overflow-hidden rounded-lg">
<div <div
v-for="row in statusRows" v-for="row in statusRows"
:key="row.label" :key="row.label"
@@ -467,7 +467,7 @@ async function openBatchShare() {
<UCard <UCard
v-else v-else
class="border border-default bg-default shadow-sm" class="surface-card overflow-hidden rounded-lg"
:ui="{ header: 'px-4 py-3', body: 'p-0' }" :ui="{ header: 'px-4 py-3', body: 'p-0' }"
> >
<template #header> <template #header>
@@ -494,7 +494,7 @@ async function openBatchShare() {
:data="receipt.seats" :data="receipt.seats"
:columns="seatColumns" :columns="seatColumns"
:caption="t('common.seats')" :caption="t('common.seats')"
class="min-w-[560px] sm:min-w-[720px]" class="compact-table min-w-[560px] sm:min-w-[720px]"
> >
<template #seatNumber-cell="{ row }"> <template #seatNumber-cell="{ row }">
<div class="min-w-0 space-y-0.5 py-0.5"> <div class="min-w-0 space-y-0.5 py-0.5">
@@ -586,7 +586,7 @@ async function openBatchShare() {
<template #body> <template #body>
<div class="space-y-4"> <div class="space-y-4">
<div class="grid gap-3 sm:grid-cols-2"> <div class="grid gap-3 sm:grid-cols-2">
<div class="rounded-2xl border border-default bg-elevated p-4"> <div class="surface-panel rounded-lg p-4">
<p class="text-xs uppercase tracking-wide text-muted"> <p class="text-xs uppercase tracking-wide text-muted">
{{ t('receipt.seatsToShare') }} {{ t('receipt.seatsToShare') }}
</p> </p>
@@ -600,7 +600,7 @@ async function openBatchShare() {
/> />
</div> </div>
<div class="rounded-2xl border border-default bg-elevated p-4"> <div class="surface-panel rounded-lg p-4">
<p class="text-xs uppercase tracking-wide text-muted"> <p class="text-xs uppercase tracking-wide text-muted">
{{ t('receipt.nextSeats') }} {{ t('receipt.nextSeats') }}
</p> </p>

View File

@@ -32,20 +32,20 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
</script> </script>
<template> <template>
<UContainer class="py-8"> <UContainer class="page-shell-narrow">
<div class="mx-auto max-w-4xl space-y-6"> <div class="space-y-6">
<div class="space-y-2 text-center"> <div class="page-header text-center sm:items-center">
<UBadge label="Seat Ticket" color="primary" variant="soft" class="rounded-full" /> <UBadge label="Seat Ticket" color="primary" variant="soft" class="page-eyebrow" />
<h1 class="text-2xl font-bold tracking-tight text-highlighted sm:text-3xl"> <h1 class="page-title">
{{ getSeatLabel(receipt.seat.seatNumber) }} {{ getSeatLabel(receipt.seat.seatNumber) }}
</h1> </h1>
<p class="text-sm text-muted"> <p class="page-description">
{{ eventDetails.title }} {{ eventDetails.title }}
</p> </p>
</div> </div>
<div class="grid gap-6 lg:grid-cols-[18rem_minmax(0,1fr)]"> <div class="grid gap-6 lg:grid-cols-[18rem_minmax(0,1fr)]">
<UCard class="border border-default bg-default shadow-sm" :ui="{ body: 'space-y-4 p-4 sm:p-5' }"> <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"> <div class="space-y-1 text-center">
<p class="text-sm font-semibold text-highlighted"> <p class="text-sm font-semibold text-highlighted">
QR Code QR Code
@@ -59,7 +59,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
<QrCodeSvg :value="receipt.seat.seatUrl" :size="220" /> <QrCodeSvg :value="receipt.seat.seatUrl" :size="220" />
</div> </div>
<div class="rounded-2xl border border-default bg-elevated p-4 text-sm text-default"> <div class="surface-panel rounded-lg p-4 text-sm text-default">
<p class="font-medium text-highlighted"> <p class="font-medium text-highlighted">
{{ receipt.seat.recipientName || receipt.booking.customerName }} {{ receipt.seat.recipientName || receipt.booking.customerName }}
</p> </p>
@@ -72,7 +72,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
</div> </div>
</UCard> </UCard>
<UCard class="border border-default bg-default shadow-sm" :ui="{ body: 'space-y-5 p-4 sm:p-5' }"> <UCard class="surface-card overflow-hidden rounded-lg" :ui="{ body: 'space-y-5 p-4 sm:p-5' }">
<div class="space-y-1"> <div class="space-y-1">
<h2 class="text-xl font-semibold text-highlighted"> <h2 class="text-xl font-semibold text-highlighted">
Booking Details Booking Details
@@ -83,7 +83,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
</div> </div>
<div class="grid gap-3 sm:grid-cols-2"> <div class="grid gap-3 sm:grid-cols-2">
<div class="rounded-2xl border border-default bg-elevated p-4"> <div class="surface-panel rounded-lg p-4">
<p class="text-xs uppercase tracking-wide text-muted"> <p class="text-xs uppercase tracking-wide text-muted">
Guest / Organizer Guest / Organizer
</p> </p>
@@ -91,7 +91,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
{{ receipt.booking.customerName }} {{ receipt.booking.customerName }}
</p> </p>
</div> </div>
<div class="rounded-2xl border border-default bg-elevated p-4"> <div class="surface-panel rounded-lg p-4">
<p class="text-xs uppercase tracking-wide text-muted"> <p class="text-xs uppercase tracking-wide text-muted">
Ticket Category Ticket Category
</p> </p>
@@ -99,7 +99,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
{{ ticketLabel }} {{ ticketLabel }}
</p> </p>
</div> </div>
<div class="rounded-2xl border border-default bg-elevated p-4"> <div class="surface-panel rounded-lg p-4">
<p class="text-xs uppercase tracking-wide text-muted"> <p class="text-xs uppercase tracking-wide text-muted">
Total Seats Total Seats
</p> </p>
@@ -107,7 +107,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
{{ receipt.booking.seatCount }} seats {{ receipt.booking.seatCount }} seats
</p> </p>
</div> </div>
<div class="rounded-2xl border border-default bg-elevated p-4"> <div class="surface-panel rounded-lg p-4">
<p class="text-xs uppercase tracking-wide text-muted"> <p class="text-xs uppercase tracking-wide text-muted">
Total Booking Value Total Booking Value
</p> </p>
@@ -117,7 +117,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
</div> </div>
</div> </div>
<div class="space-y-3 rounded-2xl border border-default bg-elevated p-4"> <div class="surface-panel space-y-3 rounded-lg p-4">
<div class="flex items-center gap-3 text-sm text-default"> <div class="flex items-center gap-3 text-sm text-default">
<UIcon name="i-lucide-calendar-days" class="size-4 text-muted" /> <UIcon name="i-lucide-calendar-days" class="size-4 text-muted" />
<span>{{ eventDetails.dateLabel }}</span> <span>{{ eventDetails.dateLabel }}</span>
@@ -132,7 +132,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
</div> </div>
</div> </div>
<div class="rounded-2xl border border-default bg-elevated p-4 text-sm text-default"> <div class="surface-panel rounded-lg p-4 text-sm text-default">
<p class="font-medium text-highlighted"> <p class="font-medium text-highlighted">
Seat shared {{ receipt.seat.sharedAt ? formatDateTime(receipt.seat.sharedAt) : 'recently' }} Seat shared {{ receipt.seat.sharedAt ? formatDateTime(receipt.seat.sharedAt) : 'recently' }}
</p> </p>

View File

@@ -1,17 +1,17 @@
<template> <template>
<UContainer class="py-8"> <UContainer class="page-shell-narrow">
<div class="mx-auto max-w-5xl space-y-6"> <div class="space-y-6">
<div class="space-y-2"> <div class="page-header">
<UBadge label="Security" color="primary" variant="soft" class="rounded-full" /> <UBadge label="Security" color="primary" variant="soft" class="page-eyebrow" />
<h1 class="text-3xl font-bold text-highlighted"> <h1 class="page-title">
Password and passkey settings Password and passkey settings
</h1> </h1>
</div> </div>
<div class="grid gap-6 lg:grid-cols-[1.05fr_0.95fr]"> <div class="grid gap-6 lg:grid-cols-[1.05fr_0.95fr]">
<UCard class="border border-default bg-default"> <UCard class="surface-card overflow-hidden rounded-lg">
<template #header> <template #header>
<h2 class="text-xl font-semibold text-highlighted"> <h2 class="text-lg font-semibold text-highlighted">
Change password Change password
</h2> </h2>
</template> </template>
@@ -39,15 +39,15 @@
</UForm> </UForm>
</UCard> </UCard>
<UCard class="border border-default bg-default"> <UCard class="surface-card overflow-hidden rounded-lg">
<template #header> <template #header>
<h2 class="text-xl font-semibold text-highlighted"> <h2 class="text-lg font-semibold text-highlighted">
Passkeys Passkeys
</h2> </h2>
</template> </template>
<div class="space-y-5"> <div class="space-y-5">
<div class="rounded-2xl border border-default bg-muted/40 p-4"> <div class="surface-panel rounded-lg p-4">
<div class="flex items-center justify-between gap-3"> <div class="flex items-center justify-between gap-3">
<div> <div>
<div class="text-sm font-semibold text-highlighted"> <div class="text-sm font-semibold text-highlighted">
@@ -79,7 +79,7 @@
<div <div
v-for="passkey in passkeys" v-for="passkey in passkeys"
:key="passkey.id" :key="passkey.id"
class="rounded-2xl border border-default bg-default px-4 py-4" class="surface-panel rounded-lg px-4 py-4"
> >
<div class="flex items-center justify-between gap-3"> <div class="flex items-center justify-between gap-3">
<div> <div>