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:
11
AGENTS.md
11
AGENTS.md
@@ -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.
|
||||
|
||||
## 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
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
colors: {
|
||||
primary: 'amber',
|
||||
neutral: 'stone'
|
||||
primary: 'red',
|
||||
neutral: 'zinc'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,11 +1,169 @@
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<div class="relative min-h-dvh bg-default 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" />
|
||||
|
||||
<div class="relative min-h-dvh bg-muted text-default">
|
||||
<template v-if="auth.user.value">
|
||||
<Transition
|
||||
enter-active-class="transition-opacity duration-200 ease-out"
|
||||
@@ -30,11 +28,11 @@
|
||||
>
|
||||
<aside
|
||||
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 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" />
|
||||
</div>
|
||||
|
||||
@@ -64,11 +62,11 @@
|
||||
:key="item.to"
|
||||
:to="item.to"
|
||||
: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)"
|
||||
>
|
||||
<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)"
|
||||
>
|
||||
<UIcon :name="item.icon" class="size-5" />
|
||||
@@ -79,9 +77,9 @@
|
||||
</nav>
|
||||
|
||||
<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 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" />
|
||||
</div>
|
||||
|
||||
@@ -109,7 +107,7 @@
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
icon="i-lucide-house"
|
||||
class="w-full justify-start rounded-2xl"
|
||||
class="w-full justify-start rounded-lg"
|
||||
@click="mobileMenuOpen = false"
|
||||
/>
|
||||
|
||||
@@ -118,7 +116,7 @@
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
icon="i-lucide-languages"
|
||||
class="w-full justify-start rounded-2xl"
|
||||
class="w-full justify-start rounded-lg"
|
||||
:aria-label="t('common.switchLanguage')"
|
||||
@click="toggleLocale"
|
||||
/>
|
||||
@@ -129,7 +127,7 @@
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
icon="i-lucide-log-out"
|
||||
class="w-full justify-start rounded-2xl"
|
||||
class="w-full justify-start rounded-lg"
|
||||
@click="logout"
|
||||
/>
|
||||
</div>
|
||||
@@ -138,9 +136,9 @@
|
||||
|
||||
<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">
|
||||
<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 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" />
|
||||
</div>
|
||||
|
||||
@@ -161,11 +159,11 @@
|
||||
:key="item.to"
|
||||
:to="item.to"
|
||||
: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)"
|
||||
>
|
||||
<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)"
|
||||
>
|
||||
<UIcon :name="item.icon" class="size-5" />
|
||||
@@ -179,9 +177,9 @@
|
||||
</nav>
|
||||
|
||||
<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 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" />
|
||||
</div>
|
||||
|
||||
@@ -210,7 +208,7 @@
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
icon="i-lucide-house"
|
||||
class="justify-start rounded-2xl"
|
||||
class="justify-start rounded-lg"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
@@ -218,7 +216,7 @@
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
icon="i-lucide-languages"
|
||||
class="justify-start rounded-2xl"
|
||||
class="justify-start rounded-lg"
|
||||
:aria-label="t('common.switchLanguage')"
|
||||
@click="toggleLocale"
|
||||
/>
|
||||
@@ -229,19 +227,19 @@
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
icon="i-lucide-log-out"
|
||||
class="justify-start rounded-2xl"
|
||||
class="justify-start rounded-lg"
|
||||
@click="logout"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<UContainer class="py-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 size-10 shrink-0 items-center justify-center rounded-2xl bg-primary/12 text-primary">
|
||||
<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-lg bg-primary/12 text-primary">
|
||||
<UIcon name="i-lucide-grid-2x2" class="size-5" />
|
||||
</div>
|
||||
|
||||
@@ -286,8 +284,8 @@
|
||||
<header class="relative border-b border-default/80 bg-default/92 backdrop-blur-xl">
|
||||
<UContainer class="py-4 md:py-5">
|
||||
<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 size-10 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
||||
<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-lg bg-primary/10 text-primary">
|
||||
<UIcon name="i-lucide-grid-2x2" class="size-5" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<UContainer class="py-6">
|
||||
<div class="mx-auto max-w-7xl space-y-4">
|
||||
<div class="flex flex-col gap-2 lg:flex-row lg:items-end lg:justify-between">
|
||||
<UContainer class="page-shell">
|
||||
<div class="space-y-5">
|
||||
<div class="page-header lg:flex-row lg:items-end lg:justify-between">
|
||||
<div class="space-y-1">
|
||||
<UBadge label="Bookings" color="primary" variant="soft" size="sm" class="rounded-full" />
|
||||
<h1 class="text-2xl font-bold tracking-tight text-highlighted">
|
||||
<UBadge label="Bookings" color="primary" variant="soft" size="sm" class="page-eyebrow" />
|
||||
<h1 class="page-title">
|
||||
Booking list
|
||||
</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.' }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -31,7 +31,7 @@
|
||||
/>
|
||||
|
||||
<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>
|
||||
<div class="space-y-1">
|
||||
<h2 class="text-base font-semibold text-highlighted">
|
||||
@@ -75,7 +75,7 @@
|
||||
</UForm>
|
||||
</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>
|
||||
<div class="space-y-1">
|
||||
<h2 class="text-base font-semibold text-highlighted">
|
||||
@@ -88,7 +88,7 @@
|
||||
</template>
|
||||
|
||||
<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">
|
||||
Pending bookings
|
||||
</p>
|
||||
@@ -97,7 +97,7 @@
|
||||
</p>
|
||||
</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">
|
||||
Pending seats
|
||||
</p>
|
||||
@@ -113,7 +113,7 @@
|
||||
<UCard
|
||||
v-for="item in inventoryCards"
|
||||
:key="item.label"
|
||||
class="border border-default bg-default shadow-sm"
|
||||
class="metric-card rounded-lg"
|
||||
:ui="{ body: 'px-4 py-3' }"
|
||||
>
|
||||
<div class="space-y-0.5">
|
||||
@@ -128,7 +128,7 @@
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<p class="text-[11px] font-medium uppercase tracking-wide text-muted">
|
||||
Total Seats
|
||||
@@ -139,7 +139,7 @@
|
||||
</div>
|
||||
</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">
|
||||
<p class="text-[11px] font-medium uppercase tracking-wide text-muted">
|
||||
Total bookings
|
||||
@@ -150,7 +150,7 @@
|
||||
</div>
|
||||
</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">
|
||||
<p class="text-[11px] font-medium uppercase tracking-wide text-muted">
|
||||
Booking status
|
||||
@@ -163,7 +163,7 @@
|
||||
</div>
|
||||
|
||||
<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' }"
|
||||
>
|
||||
<template #header>
|
||||
@@ -197,7 +197,7 @@
|
||||
:empty="searchQuery.trim() ? 'No matching bookings found.' : 'No bookings available yet.'"
|
||||
sticky="header"
|
||||
caption="Bookings"
|
||||
class="min-w-[1120px]"
|
||||
class="compact-table min-w-[1120px]"
|
||||
>
|
||||
<template #customerName-cell="{ row }">
|
||||
<div class="min-w-0 space-y-0.5 py-0.5">
|
||||
@@ -363,7 +363,7 @@
|
||||
class="space-y-4"
|
||||
@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">
|
||||
{{ detailsBooking.customerName }}
|
||||
</p>
|
||||
@@ -426,12 +426,12 @@
|
||||
:disabled="savingDetails || !ticketCatalogItems.length"
|
||||
:ui="{
|
||||
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>
|
||||
|
||||
<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">
|
||||
<span class="text-sm font-medium text-muted">Updated total</span>
|
||||
<div class="text-right">
|
||||
@@ -490,7 +490,7 @@
|
||||
>
|
||||
<template #body>
|
||||
<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">
|
||||
{{ editingBooking.customerName }}
|
||||
</p>
|
||||
@@ -543,7 +543,7 @@
|
||||
>
|
||||
<template #body>
|
||||
<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">
|
||||
{{ transferringBooking.customerName }}
|
||||
</p>
|
||||
|
||||
@@ -160,20 +160,20 @@ async function cancelBookingConfirmation() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContainer class="py-8">
|
||||
<div class="mx-auto max-w-3xl space-y-5">
|
||||
<div class="space-y-2 text-center">
|
||||
<UBadge :label="t('confirm.badge')" color="primary" variant="soft" class="rounded-full" />
|
||||
<h1 class="text-2xl font-bold tracking-tight text-highlighted sm:text-3xl">
|
||||
<UContainer class="page-shell-compact">
|
||||
<div class="space-y-5">
|
||||
<div class="page-header text-center sm:items-center">
|
||||
<UBadge :label="t('confirm.badge')" color="primary" variant="soft" class="page-eyebrow" />
|
||||
<h1 class="page-title">
|
||||
{{ t('confirm.title') }}
|
||||
</h1>
|
||||
<p class="text-sm text-muted">
|
||||
<p class="page-description">
|
||||
{{ t('confirm.description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<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' }"
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
|
||||
<div class="overflow-hidden rounded-xl border border-default">
|
||||
<div class="overflow-hidden rounded-lg border border-default">
|
||||
<div
|
||||
v-for="row in detailRows"
|
||||
:key="row.label"
|
||||
@@ -221,7 +221,7 @@ async function cancelBookingConfirmation() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse gap-3 sm:flex-row sm:justify-end">
|
||||
<div class="action-row">
|
||||
<UButton
|
||||
to="/"
|
||||
:label="t('confirm.backToForm')"
|
||||
|
||||
@@ -234,30 +234,58 @@ async function bookTicket(event: FormSubmitEvent<typeof form>) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContainer class="py-8">
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<div class="mb-8 text-center">
|
||||
<h1 class="text-3xl font-extrabold tracking-tight text-highlighted sm:text-4xl">
|
||||
{{ bookingConfig.event.title }}
|
||||
</h1>
|
||||
</div>
|
||||
<UContainer class="page-shell-narrow">
|
||||
<div class="grid gap-8 xl:grid-cols-[minmax(0,1fr)_34rem] xl:items-start">
|
||||
<section class="space-y-6 xl:sticky xl:top-6">
|
||||
<div class="page-header">
|
||||
<UBadge :label="t('layout.brand')" color="primary" variant="soft" class="page-eyebrow" />
|
||||
<h1 class="page-title">
|
||||
{{ bookingConfig.event.title }}
|
||||
</h1>
|
||||
<p class="page-description">
|
||||
{{ t('booking.seatGeneration', { count: seatCount, seatLabel: locale === 'zh' ? '座位' : `seat${seatCount === 1 ? '' : 's'}` }) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<UCard id="booking-form" class="border border-default bg-default" :ui="{
|
||||
header: 'space-y-4',
|
||||
body: 'space-y-6'
|
||||
}">
|
||||
<template #header>
|
||||
<div class="space-y-3">
|
||||
<div v-for="detail in eventDetails" :key="detail.label"
|
||||
class="flex items-center gap-3 text-sm text-default">
|
||||
<UIcon :name="detail.icon" class="size-4 text-muted" />
|
||||
<span>{{ detail.value }}</span>
|
||||
<div class="grid gap-3 sm:grid-cols-3 xl:grid-cols-1">
|
||||
<div
|
||||
v-for="detail in eventDetails"
|
||||
:key="detail.label"
|
||||
class="surface-card rounded-lg p-4"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex size-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||
<UIcon :name="detail.icon" class="size-5" />
|
||||
</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>
|
||||
</template>
|
||||
</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">
|
||||
<div class="space-y-5">
|
||||
<div class="grid gap-5 sm:grid-cols-2">
|
||||
<UFormField name="name" :label="t('booking.name')" required>
|
||||
<UInput v-model="form.name" size="xl" class="w-full" :placeholder="t('booking.namePlaceholder')" />
|
||||
</UFormField>
|
||||
@@ -268,47 +296,68 @@ async function bookTicket(event: FormSubmitEvent<typeof form>) {
|
||||
</div>
|
||||
|
||||
<UFormField :label="t('booking.bookingMode')" name="bookingMode">
|
||||
<URadioGroup v-model="form.bookingMode" orientation="horizontal" variant="card" indicator="hidden"
|
||||
:items="bookingModeOptions" :ui="{
|
||||
<URadioGroup
|
||||
v-model="form.bookingMode"
|
||||
orientation="horizontal"
|
||||
variant="card"
|
||||
indicator="hidden"
|
||||
:items="bookingModeOptions"
|
||||
: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>
|
||||
|
||||
<UFormField :label="quantityLabel" name="quantity">
|
||||
<UInputNumber v-model="form.quantity" size="xl" class="w-full" :min="1" :step="1" />
|
||||
<template #help>
|
||||
{{ t('booking.seatGeneration', { count: seatCount, seatLabel: locale === 'zh' ? '座位' : `seat${seatCount === 1 ? '' : 's'}` }) }}
|
||||
</template>
|
||||
</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')">
|
||||
<USelect
|
||||
v-model="selectedPersonInCharge"
|
||||
size="xl"
|
||||
class="w-full"
|
||||
:items="personInCharge"
|
||||
:disabled="!personInCharge.length"
|
||||
item: 'rounded-lg border border-default bg-default p-3 transition-colors data-[state=checked]:border-primary data-[state=checked]:bg-primary/5'
|
||||
}"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<UButton id="getTicketBtn" type="submit" :label="t('booking.bookNow')" size="xl"
|
||||
class="w-full justify-center" :disabled="!selectedPersonInCharge || !selectedBookingMode || !selectedTicket" :loading="submittingBooking" />
|
||||
<div class="grid gap-5 sm:grid-cols-[minmax(0,1fr)_minmax(0,1.1fr)]">
|
||||
<UFormField :label="quantityLabel" name="quantity">
|
||||
<UInputNumber v-model="form.quantity" size="xl" class="w-full" :min="1" :step="1" />
|
||||
<template #help>
|
||||
{{ t('booking.seatGeneration', { count: seatCount, seatLabel: locale === 'zh' ? '座位' : `seat${seatCount === 1 ? '' : 's'}` }) }}
|
||||
</template>
|
||||
</UFormField>
|
||||
|
||||
<UFormField :label="t('booking.personInCharge')">
|
||||
<USelect
|
||||
v-model="selectedPersonInCharge"
|
||||
size="xl"
|
||||
class="w-full"
|
||||
:items="personInCharge"
|
||||
:disabled="!personInCharge.length"
|
||||
/>
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
<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-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>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,59 @@
|
||||
<template>
|
||||
<UContainer class="py-10 lg:py-16">
|
||||
<div class="mx-auto max-w-md">
|
||||
<UCard class="border border-default bg-default shadow-sm">
|
||||
<UContainer class="page-shell-narrow">
|
||||
<div class="grid gap-8 lg:grid-cols-[minmax(0,1fr)_27rem] lg:items-center">
|
||||
<section class="page-header">
|
||||
<UBadge :label="t('login.badge')" color="primary" variant="soft" class="page-eyebrow" />
|
||||
<h1 class="page-title">
|
||||
{{ t('login.title') }}
|
||||
</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-2">
|
||||
<UBadge :label="t('login.badge')" color="primary" variant="soft" class="rounded-full" />
|
||||
<h1 class="text-3xl font-bold text-highlighted">
|
||||
{{ t('login.title') }}
|
||||
</h1>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
@@ -45,22 +91,20 @@
|
||||
|
||||
<div class="my-6 flex items-center gap-3">
|
||||
<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>
|
||||
|
||||
<div class="space-y-4">
|
||||
<UButton
|
||||
:label="t('login.passkey')"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="xl"
|
||||
class="w-full justify-center"
|
||||
icon="i-lucide-fingerprint"
|
||||
:loading="passkeyPending"
|
||||
@click="loginWithPasskey"
|
||||
/>
|
||||
</div>
|
||||
<UButton
|
||||
:label="t('login.passkey')"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="xl"
|
||||
class="w-full justify-center"
|
||||
icon="i-lucide-fingerprint"
|
||||
:loading="passkeyPending"
|
||||
@click="loginWithPasskey"
|
||||
/>
|
||||
</UCard>
|
||||
</div>
|
||||
</UContainer>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<UContainer class="py-8">
|
||||
<div class="mx-auto max-w-6xl space-y-4">
|
||||
<div class="space-y-1.5">
|
||||
<UBadge label="Super Admin" color="primary" variant="soft" class="rounded-full" />
|
||||
<h1 class="text-2xl font-bold text-highlighted sm:text-3xl">
|
||||
<UContainer class="page-shell">
|
||||
<div class="space-y-5">
|
||||
<div class="page-header">
|
||||
<UBadge label="Super Admin" color="primary" variant="soft" class="page-eyebrow" />
|
||||
<h1 class="page-title">
|
||||
User management
|
||||
</h1>
|
||||
</div>
|
||||
@@ -16,7 +16,7 @@
|
||||
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>
|
||||
<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">
|
||||
@@ -49,8 +49,8 @@
|
||||
</template>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<div class="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="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-3 text-xs font-semibold uppercase text-muted">
|
||||
<div>Order</div>
|
||||
<div>Display Name</div>
|
||||
<div>PIC Phone</div>
|
||||
|
||||
@@ -365,23 +365,23 @@ async function openBatchShare() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContainer class="py-6 sm:py-8">
|
||||
<div class="mx-auto max-w-5xl space-y-5">
|
||||
<div class="space-y-1 text-center">
|
||||
<UBadge :label="t('receipt.badge')" color="primary" variant="soft" class="rounded-full" />
|
||||
<h1 class="text-2xl font-bold tracking-tight text-highlighted sm:text-3xl">
|
||||
<UContainer class="page-shell-narrow">
|
||||
<div class="space-y-6">
|
||||
<div class="page-header text-center sm:items-center">
|
||||
<UBadge :label="t('receipt.badge')" color="primary" variant="soft" class="page-eyebrow" />
|
||||
<h1 class="page-title">
|
||||
{{ eventDetails.title }}
|
||||
</h1>
|
||||
</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">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
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="activeTab === tab.id
|
||||
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
|
||||
? 'bg-primary text-inverted shadow-sm'
|
||||
: 'bg-elevated text-default hover:bg-muted'"
|
||||
@click="activeTab = tab.id"
|
||||
@@ -394,17 +394,17 @@ async function openBatchShare() {
|
||||
|
||||
<UCard
|
||||
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' }"
|
||||
>
|
||||
<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" />
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<p class="text-[11px] font-medium uppercase tracking-wide text-muted">
|
||||
<div class="surface-panel rounded-lg px-4 py-3 text-left">
|
||||
<p class="text-[11px] font-medium uppercase tracking-wide text-muted">
|
||||
{{ t('common.category') }}
|
||||
</p>
|
||||
<p class="mt-1 text-sm font-semibold text-highlighted">
|
||||
@@ -412,8 +412,8 @@ async function openBatchShare() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-default bg-elevated px-4 py-3 text-left">
|
||||
<p class="text-[11px] font-medium uppercase tracking-wide text-muted">
|
||||
<div class="surface-panel rounded-lg px-4 py-3 text-left">
|
||||
<p class="text-[11px] font-medium uppercase tracking-wide text-muted">
|
||||
{{ t('common.seats') }}
|
||||
</p>
|
||||
<p class="mt-1 text-sm font-semibold text-highlighted">
|
||||
@@ -421,8 +421,8 @@ async function openBatchShare() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-default bg-elevated px-4 py-3 text-left">
|
||||
<p class="text-[11px] font-medium uppercase tracking-wide text-muted">
|
||||
<div class="surface-panel rounded-lg px-4 py-3 text-left">
|
||||
<p class="text-[11px] font-medium uppercase tracking-wide text-muted">
|
||||
{{ t('common.totalPrice') }}
|
||||
</p>
|
||||
<p class="mt-1 text-sm font-semibold text-highlighted">
|
||||
@@ -443,10 +443,10 @@ async function openBatchShare() {
|
||||
|
||||
<UCard
|
||||
v-else-if="activeTab === 'status'"
|
||||
class="border border-default bg-default shadow-sm"
|
||||
class="surface-card overflow-hidden rounded-lg"
|
||||
:ui="{ body: 'p-0' }"
|
||||
>
|
||||
<div class="overflow-hidden rounded-2xl">
|
||||
<div class="overflow-hidden rounded-lg">
|
||||
<div
|
||||
v-for="row in statusRows"
|
||||
:key="row.label"
|
||||
@@ -467,7 +467,7 @@ async function openBatchShare() {
|
||||
|
||||
<UCard
|
||||
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' }"
|
||||
>
|
||||
<template #header>
|
||||
@@ -494,7 +494,7 @@ async function openBatchShare() {
|
||||
:data="receipt.seats"
|
||||
:columns="seatColumns"
|
||||
: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 }">
|
||||
<div class="min-w-0 space-y-0.5 py-0.5">
|
||||
@@ -502,7 +502,7 @@ async function openBatchShare() {
|
||||
<p class="text-sm font-semibold leading-tight text-highlighted sm:text-base">
|
||||
{{ getSeatLabel(row.original.seatNumber, locale) }}
|
||||
</p>
|
||||
<UBadge
|
||||
<UBadge
|
||||
:label="row.original.sharedAt ? t('receipt.sharedStatus') : t('receipt.availableStatus')"
|
||||
:color="row.original.sharedAt ? 'primary' : 'neutral'"
|
||||
variant="soft"
|
||||
@@ -586,7 +586,7 @@ async function openBatchShare() {
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<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">
|
||||
{{ t('receipt.seatsToShare') }}
|
||||
</p>
|
||||
@@ -600,7 +600,7 @@ async function openBatchShare() {
|
||||
/>
|
||||
</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">
|
||||
{{ t('receipt.nextSeats') }}
|
||||
</p>
|
||||
|
||||
@@ -32,20 +32,20 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContainer class="py-8">
|
||||
<div class="mx-auto max-w-4xl space-y-6">
|
||||
<div class="space-y-2 text-center">
|
||||
<UBadge label="Seat Ticket" color="primary" variant="soft" class="rounded-full" />
|
||||
<h1 class="text-2xl font-bold tracking-tight text-highlighted sm:text-3xl">
|
||||
<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="text-sm text-muted">
|
||||
<p class="page-description">
|
||||
{{ eventDetails.title }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<p class="text-sm font-semibold text-highlighted">
|
||||
QR Code
|
||||
@@ -59,7 +59,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
|
||||
<QrCodeSvg :value="receipt.seat.seatUrl" :size="220" />
|
||||
</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">
|
||||
{{ receipt.seat.recipientName || receipt.booking.customerName }}
|
||||
</p>
|
||||
@@ -72,7 +72,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
|
||||
</div>
|
||||
</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">
|
||||
<h2 class="text-xl font-semibold text-highlighted">
|
||||
Booking Details
|
||||
@@ -83,7 +83,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
|
||||
</div>
|
||||
|
||||
<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">
|
||||
Guest / Organizer
|
||||
</p>
|
||||
@@ -91,7 +91,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
|
||||
{{ receipt.booking.customerName }}
|
||||
</p>
|
||||
</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">
|
||||
Ticket Category
|
||||
</p>
|
||||
@@ -99,7 +99,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
|
||||
{{ ticketLabel }}
|
||||
</p>
|
||||
</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">
|
||||
Total Seats
|
||||
</p>
|
||||
@@ -107,7 +107,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
|
||||
{{ receipt.booking.seatCount }} seats
|
||||
</p>
|
||||
</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">
|
||||
Total Booking Value
|
||||
</p>
|
||||
@@ -117,7 +117,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
|
||||
</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">
|
||||
<UIcon name="i-lucide-calendar-days" class="size-4 text-muted" />
|
||||
<span>{{ eventDetails.dateLabel }}</span>
|
||||
@@ -132,7 +132,7 @@ const totalFormatted = computed(() => formatBookingCurrency(receipt.value.bookin
|
||||
</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">
|
||||
Seat shared {{ receipt.seat.sharedAt ? formatDateTime(receipt.seat.sharedAt) : 'recently' }}
|
||||
</p>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<UContainer class="py-8">
|
||||
<div class="mx-auto max-w-5xl space-y-6">
|
||||
<div class="space-y-2">
|
||||
<UBadge label="Security" color="primary" variant="soft" class="rounded-full" />
|
||||
<h1 class="text-3xl font-bold text-highlighted">
|
||||
<UContainer class="page-shell-narrow">
|
||||
<div class="space-y-6">
|
||||
<div class="page-header">
|
||||
<UBadge label="Security" color="primary" variant="soft" class="page-eyebrow" />
|
||||
<h1 class="page-title">
|
||||
Password and passkey settings
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<h2 class="text-xl font-semibold text-highlighted">
|
||||
<h2 class="text-lg font-semibold text-highlighted">
|
||||
Change password
|
||||
</h2>
|
||||
</template>
|
||||
@@ -39,15 +39,15 @@
|
||||
</UForm>
|
||||
</UCard>
|
||||
|
||||
<UCard class="border border-default bg-default">
|
||||
<UCard class="surface-card overflow-hidden rounded-lg">
|
||||
<template #header>
|
||||
<h2 class="text-xl font-semibold text-highlighted">
|
||||
<h2 class="text-lg font-semibold text-highlighted">
|
||||
Passkeys
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<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>
|
||||
<div class="text-sm font-semibold text-highlighted">
|
||||
@@ -79,7 +79,7 @@
|
||||
<div
|
||||
v-for="passkey in passkeys"
|
||||
: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>
|
||||
|
||||
Reference in New Issue
Block a user