Booking Experience
Source: Webmaster Brief — Booking System: “Experience & Add-ons” Step (2026-05-12) Doc Status: Excellent | ✓ All 6 checks passedOverview
Inline page section on/booking. No modal. BookingSection owns a phase state machine:
3-Card Experience Selection
Three vertical cards, single-select (radio button behavior). Selecting a card highlights it and updates the booking footer bar total.| Card | Price | Add-ons Available |
|---|---|---|
| Ticket Only | 650,000 VND / person | Signature Bundle + all bottles |
| Dinner Show | 990k Solo / 1,850k Duo / 3,500k Table of 4 | Bottles only |
| VIP | SOLD OUT | — |
Card 1 — Ticket Only
- Base price: 650,000 VND per person
- Quantity: Controlled by a quantity stepper on the card
- Add-ons below card: Checkable secondary cards — free multi-selection, no exclusivity
| Add-on | Price | Tag |
|---|---|---|
| Signature Bundle (1 signature cocktail + 1 signature snack) | 360,000 VND | Green: “Save 80,000 VND” |
| Chevalier Brut Blanc de Blancs (Champagne, France) | 950,000 VND | Gold: “-15% vs. menu price” |
| Francis Gillot Sauvignon Blanc 2024 | 750,000 VND | Gold: “-15% vs. menu price” |
| Chateau Les Martineau Bordeaux 2021 | 750,000 VND | Gold: “-15% vs. menu price” |
Note: “Menu prices” for bottles are not yet officially published. Display only the add-on price with the discount tag. Full prices to be confirmed with Hamza.
Card 2 — Dinner Show
Price tiers (per reservation, not per person):| Tier | Guest Count | Price |
|---|---|---|
| Solo | 1 | 990,000 VND |
| Duo | 2 | 1,850,000 VND |
| Table of 4 | 3-4 | 3,500,000 VND |
Cap: Dinner Show is capped at 4 guests maximum. If guest count > 4, Table of 4 is pre-selected.Auto-selection: The correct tier is pre-selected based on the guest count set via a stepper on the card:
- 1 guest — Solo pre-selected
- 2 guests — Duo pre-selected
- 3-4 guests — Table of 4 pre-selected
| Add-on | Price | Tag |
|---|---|---|
| Chevalier Brut Blanc de Blancs | 950,000 VND | Gold: “-15% vs. menu price” |
| Francis Gillot Sauvignon Blanc 2024 | 750,000 VND | Gold: “-15% vs. menu price” |
| Chateau Les Martineau Bordeaux 2021 | 750,000 VND | Gold: “-15% vs. menu price” |
| Benjamin Mendy Cabernet Sauvignon 2024 | 750,000 VND | Gold: “-15% vs. menu price” |
Card 3 — VIP
- Status: SOLD OUT — always disabled
- UI: Card is visible but non-selectable. Red “Sold Out” badge overlays the card.
- Action: “Join the Waiting List” button links to WhatsApp or an email form
- No add-ons shown
Discount Tags
Displayed as high-visibility badges on add-on cards:- Green badge for fixed savings (e.g., “Save 80,000 VND”)
- Gold badge for percentage discounts (e.g., “-15% vs. menu price”)
Add-on Filtering Rules
| Experience Selected | Add-ons Visible |
|---|---|
| Ticket Only | Signature Bundle + all bottles |
| Dinner Show | Bottles only (Signature Bundle hidden) |
| VIP | Nothing — user never reaches add-ons |
Pricing
Ticket Only
Dinner Show
No per-person pricing for Dinner Show. Price is all-inclusive (4-course dinner).
Surcharges
Day-of-week surcharges and small-party surcharges do not apply under this pricing model. (The brief supersedes the previous logic inpayments.ts.)
Booking Footer Bar
- Always visible at the bottom of the section when an event is selected
- Displays: selected experience name + guest/dinner tier count + real-time total (base + add-ons)
- Format:
"Dinner Show — 2 guests — 2,800,000 VND" - Updates instantly on add-on selection
- Becomes the checkout CTA in the Checkout phase
Page Architecture
| Phase | Component | Description |
|---|---|---|
| GRID | EventCard × N | Upcoming events, “Book Now” triggers EXPERIENCE |
| EXPERIENCE | ExperienceCards | 3-card layout |
| ADDONS | AddonCheckCard × N | Filtered by experienceType |
| CHECKOUT | CheckoutForm | Customer form + payment |
| CONFIRMATION | ConfirmationDisplay | QR code + success |
/booking is the single entry point. No route change during booking flow.
State Machine
Phases
Transitions
| From | To | Trigger |
|---|---|---|
| GRID | EXPERIENCE | Book Now click |
| EXPERIENCE | ADDONS | Continue |
| ADDONS | CHECKOUT | Continue/Skip |
| CHECKOUT | CONFIRMATION | Payment success |
| CONFIRMATION | GRID | Book another |
| any phase | previous phase | Back button |
State Ownership
BookingSection owns all state:
selectedEventId: string | nullcurrentPhase: BookingPhase- Draft data flows through
ReservationDraftContext(Convex-backed)
Component Inventory
New Components
| Component | File | Purpose |
|---|---|---|
ExperienceCards | components/booking/experience-cards.tsx | 3-card layout container |
DinnerTierSelector | components/booking/dinner-tier-selector.tsx | Solo / Duo / Table of 4 radio toggle |
VipSoldOutBadge | components/booking/vip-sold-out-badge.tsx | Red “Sold Out” overlay + waiting list CTA |
AddonCheckCard | components/booking/addon-check-card.tsx | Checkable add-on card with discount tag |
DiscountTag | components/booking/discount-tag.tsx | Green “Save X” / Gold “-X% vs. menu” badge |
BookingFooterBar | components/booking/booking-footer-bar.tsx | Always-visible price bar + CTA |
BottleCaption | components/booking/bottle-caption.tsx | ”Book your bottle in advance…” text |
Modified Components
| Component | Change |
|---|---|
BookingSection | Full redesign — owns phase state machine, renders inline booking flow |
ReservationDraftProvider | Add experienceType, dinnerTier to draft state |
StepAddons | Filter add-ons by experienceType using listByExperienceType |
CheckoutSummary | Show experienceType + dinnerTier in order summary |
ConfirmationDisplay | Show correct experience type in confirmation |
Removed Components
| Component | Reason |
|---|---|
BookingModal | Replaced by inline BookingSection |
BookingModalContext | Replaced by BookingSection state |
StepIndicator | No stepper in inline flow |
TicketTypeOption | Replaced by ExperienceCards layout |
GuestCountSelector | Replaced by DinnerTierSelector + inline stepper |
ExperienceOption | Redundant with new card layout |
Schema Changes
addOns — New Fields
experiences — VIP Fields
bookingDrafts — New Fields
reservations — New Fields
Backend API
New Query: listByExperienceType
Returns add-ons filtered by experience type (uses the availableFor index).
New Query: getEventWithVipStatus
Returns event with VIP status flags populated.
Updated Mutation: createPending
Updated Mutation: updateWithAddOns
No changes to signature — addOns already stored on reservation. The experienceType and dinnerTier are set during createPending.
Updated payments.ts: createAddon / updateAddon
Update args to include new fields:
Admin CRUD
| Operation | Endpoint | Auth |
|---|---|---|
| List all add-ons | payments.listAll | Admin |
| List by experience type | payments.listByExperienceType | Admin |
| Create add-on | payments.createAddon | Admin |
| Update add-on | payments.updateAddon | Admin |
| Toggle enabled | payments.disableAddon | Admin |
Admin UI: /admin/addons (new page)
- Table view: name, type, price, referencePrice, availableFor, displayOrder, enabled
- Inline edit for price, referencePrice, description
- Toggle enabled/disabled
- Create new add-on form
- Filter by
availableFor(Ticket Only / Dinner Show / All)
Admin UI: VIP Status on Experience
On/admin/experiences/[id]:
- Toggle
vipEnabled - Set
vipCapacity - Manual
vipSoldOuttoggle
Files to Change
Schema
packages/backend/convex/schema.ts— Add fields toaddOns,experiences,reservations,bookingDrafts
Backend
packages/backend/convex/domains/payments.ts—listByExperienceTypequery; updatecreateAddon/updateAddonargspackages/backend/convex/domains/reservations.ts— UpdatecreatePendingargs to acceptexperienceType+dinnerTier
Frontend Hooks
apps/frontend/hooks/booking/use-experience-selection.ts— ManagesexperienceType+dinnerTierselection
Frontend Components
apps/frontend/components/home/booking-section.tsx— Full redesign with phase state machineapps/frontend/components/booking/experience-cards.tsx— Newapps/frontend/components/booking/dinner-tier-selector.tsx— Newapps/frontend/components/booking/vip-sold-out-badge.tsx— Newapps/frontend/components/booking/addon-check-card.tsx— Newapps/frontend/components/booking/discount-tag.tsx— Newapps/frontend/components/booking/booking-footer-bar.tsx— Newapps/frontend/components/booking/bottle-caption.tsx— Newapps/frontend/components/booking/step-addons.tsx— Filter byexperienceTypeapps/frontend/contexts/reservation-draft-context.tsx— AddexperienceType,dinnerTierapps/frontend/components/booking/checkout-summary.tsx— ShowexperienceType+dinnerTierapps/frontend/components/booking/confirmation-display.tsx— Show correct experience type
Remove
apps/frontend/components/booking/booking-modal.tsxapps/frontend/components/booking/ticket-type-option.tsxapps/frontend/components/booking/guest-count-selector.tsxapps/frontend/components/booking/experience-option.tsxapps/frontend/components/booking/step-indicator.tsxapps/frontend/contexts/booking-modal-context.tsx
Admin Pages
apps/frontend/app/[locale]/dashboard/admin/addons/page.tsx— New admin add-on management
Out of Scope
- Seat / zone selection
- The French Mentalist separate flow
- Zoho CRM sync for add-ons
- WhatsApp waiting list integration (P2)
- Automatic VIP capacity tracking (VIP sold-out is manual toggle)
Implementation Notes
Schema Changes (2026-05-12)
The following schema changes were implemented inpackages/backend/convex/schema.ts:
addOns table
- Replaced
applicableTo(single value) withavailableFor(array) for multi-experience support - Added
referencePrice: numberfor displaying discount tags - Added
displayOrder: numberfor sorting within experience type - Added
WINEandBEVERAGEto thetypeenum
experiences table
- Added
vipCapacity: numberfor VIP seat capacity
experienceEvents table
- Added
vipSoldOut: booleanfor runtime sold-out flag (manual toggle)
reservations table
- Added
experienceType: TICKET_ONLY | DINNER_SHOW | VIP - Added
dinnerTier: SOLO | DUO | TABLE_OF_4
bookingDrafts table
- Added
experienceType: TICKET_ONLY | DINNER_SHOW | VIP - Added
dinnerTier: SOLO | DUO | TABLE_OF_4
Backend API Changes
payments.ts — listByExperienceType query
New query to filter add-ons by experience type using the availableFor array:
payments.ts — createAddon mutation
Updated to accept new fields:
availableFor: array(required)displayOrder: number(required)referencePrice: optional number
payments.ts — updateAddon mutation
Updated to accept new fields:
availableFor: array(optional)displayOrder: number(optional)referencePrice: optional number
reservations.ts — createPending mutation
Updated to accept new arguments:
experienceType: TICKET_ONLY | DINNER_SHOW | VIP(optional)dinnerTier: SOLO | DUO | TABLE_OF_4(optional)
Implementation Status
| Component | Status | Notes |
|---|---|---|
| Schema changes | ✅ Implemented | All new fields added |
listByExperienceType query | ✅ Implemented | Uses availableFor array |
createAddon/updateAddon | ✅ Implemented | New fields included |
createPending | ✅ Implemented | experienceType/dinnerTier args added |
| Frontend components | ✅ Implemented | 3-card UI built with inline flow |
| Booking modal removal | ✅ Implemented | Inline phase state machine replaces modal |
New Components Created
The following components were created to implement the 3-card experience selection:| Component | File | Purpose |
|---|---|---|
DiscountTag | components/booking/discount-tag.tsx | Green/gold badge for discount tags |
AddonCheckCard | components/booking/addon-check-card.tsx | Checkable add-on card |
BottleCaption | components/booking/bottle-caption.tsx | Informational text for bottle add-ons |
VipSoldOutBadge | components/booking/vip-sold-out-badge.tsx | Sold out overlay for VIP |
DinnerTierSelector | components/booking/dinner-tier-selector.tsx | Solo/Duo/Table of 4 radio toggle |
ExperienceCards | components/booking/experience-cards.tsx | 3-card experience selection container |
BookingFooterBar | components/booking/booking-footer-bar.tsx | Fixed price bar with CTA |
Modified Components
| Component | Change |
|---|---|
BookingSection | Redesigned with inline phase state machine (GRID → EXPERIENCE → ADDONS → CHECKOUT → CONFIRMATION) |
CheckoutForm | Added reservationId prop support |
ConfirmationDisplay | Added reservationId prop support |
Notes
- The
BookingSectionnow owns the complete booking flow inline — no modal - Phase state machine: GRID (event selection) → EXPERIENCE (3-card selection) → ADDONS (filtered add-ons) → CHECKOUT → CONFIRMATION
- VIP is displayed as sold out with waiting list CTA
createPendingis called when transitioning from EXPERIENCE to ADDONS phaseupdateWithAddOnsis called when transitioning from ADDONS to CHECKOUT phase