plans
2026-05-12
2026 05 12 Booking Section Enhancement

Booking Section Enhancement Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Enhance BookingSection on /booking page to match the spec's conversion requirements — proper pricing display, scarcity signals, and integrated 4-step booking flow.

Architecture: Booking section serves as the entry point. Clicking "Book Now" opens the modal with the full 4-step flow (tickets → addons → checkout → confirmation). Scarcity signals and countdown timer maintain urgency throughout.

Tech Stack: Next.js 16, Tailwind CSS v4, Radix UI, Convex, paraglide-js i18n


File Structure

apps/frontend/
├── app/[locale]/(landing)/booking/
│   └── page.tsx                        # Booking page (receives BookingSection)
├── components/home/
│   └── booking-section.tsx             # Grid of ExperienceCards with availability
├── components/booking/
│   ├── experience-card.tsx             # Already exists - card with price + scarcity
│   ├── booking-modal.tsx               # Already exists - 4-step modal flow
│   ├── countdown-timer.tsx             # Already exists - 10-minute timer
│   ├── sticky-cart.tsx                 # Already exists - persistent cart sidebar
│   └── sticky-cart-footer.tsx          # Mobile sticky footer
├── contexts/booking-modal-context.tsx   # Already exists - modal state
└── lib/utils/availability.ts           # Already exists - computeAvailability

Task 1: Enhance ExperienceCard with Price Display

Files:

  • Modify: apps/frontend/components/booking/experience-card.tsx
  • Modify: apps/frontend/components/home/booking-section.tsx

The spec requires: Price displayed as "From [amount] VND", availability badge, and two buttons ("Discover" / "Book Now").

  • Step 1: Update ExperienceCardData type to include price
// In experience-card.tsx, add price field
export type ExperienceCardShow = {
  // ... existing fields
  defaultDinnerPrice?: number;
  defaultShowOnlyPrice?: number; // NEW
};
  • Step 2: Update useBookingData to map price from event
// In booking-section.tsx
const shows: ExperienceCardData[] = useMemo(() => {
  if (!rawEvents) return [];
  return rawEvents.map((event) => ({
    event: {
      /* existing */
    },
    show: {
      _id: event.experienceCode,
      title: event.experienceTitle,
      slug: event.experienceSlug,
      gallery: [] as string[],
      defaultDinnerPrice: event.dinnerPrice, // NEW - already there
      defaultShowOnlyPrice: event.showOnlyPrice, // NEW
    },
  }));
}, [rawEvents]);
  • Step 3: Add price display to default variant card

In the default variant's render, add price line:

<div className="flex items-center gap-2">
  <span className="font-sans text-sm text-[var(--color-muted-foreground)]">
    {formatShowDateWithDay(event.date)} at {formatShowTime(event.time)}
  </span>
  {showData.defaultDinnerPrice && (
    <span className="font-sans text-sm text-[var(--color-gold)] font-semibold ml-auto">
      From {formatPrice(showData.defaultDinnerPrice)} VND
    </span>
  )}
</div>
  • Step 4: Add formatPrice utility
// In lib/utils/format.ts
export function formatPrice(amount: number): string {
  return new Intl.NumberFormat("vi-VN").format(amount);
}
  • Step 5: Update badge colors per spec

Add gold/green/orange/grey badges matching spec:

  • Green: Available (>10 seats)
  • Orange: Few left (1-10 seats)
  • Grey: Sold out (0 seats)

Task 2: Add Scarcity Signals

Files:

  • Modify: apps/frontend/components/booking/experience-card.tsx

Per spec: "Only X seats left for this date" if <10 seats, "This date is filling up fast"

  • Step 1: Add scarcity messaging to card
// In the card, after availability badge
{
  remaining <= 10 && remaining > 0 && (
    <p className="text-sm text-orange-500 font-semibold animate-pulse">
      Only {remaining} seats left!
    </p>
  );
}
{
  remaining > 10 && remaining <= 20 && (
    <p className="text-sm text-[var(--color-gold)]">Selling fast</p>
  );
}
  • Step 2: Add orange-500 to tailwind config if not exists

Check tailwind.config.ts for custom colors.


Task 3: Integrate Countdown Timer

Files:

  • Modify: apps/frontend/components/booking/booking-modal.tsx
  • Check: apps/frontend/components/booking/countdown-timer.tsx

Per spec: "From step 1 onwards, start a 10-minute timer: 'Your seats are reserved for 09:54'"

  • Step 1: Verify countdown-timer exists and works

Read components/booking/countdown-timer.tsx to understand implementation.

  • Step 2: Import and render countdown in modal header
// In booking-modal.tsx
import { CountdownTimer } from "~/components/booking/countdown-timer";
 
// In BookingFlow component, add after StepIndicator
<div className="px-6 py-3 bg-surface/50">
  <CountdownTimer />
</div>;
  • Step 3: Verify timer resets on modal open

The timer should start when step 1 (tickets) renders. Verify in ReservationDraftProvider or step-tickets.


Task 4: Verify Sticky Cart Integration

Files:

  • Check: apps/frontend/components/booking/sticky-cart.tsx
  • Check: apps/frontend/components/booking/sticky-cart-footer.tsx

Per spec: "Visible at all steps 1-2-3. On mobile: fixed bar at bottom. On desktop: right-hand sidebar."

  • Step 1: Verify sticky-cart renders in modal

Read booking-modal.tsx to see if sticky-cart is included.

  • Step 2: Verify mobile footer variant exists

Check sticky-cart-footer.tsx for mobile-specific implementation.


Task 5: Add Trust Signals to Checkout

Files:

  • Modify: apps/frontend/components/booking/checkout-form.tsx

Per spec: Payment logos (VNPay, OnePay, Visa, Mastercard), "Secure encrypted payment" badge.

  • Step 1: Read checkout-form.tsx

Check current implementation.

  • Step 2: Add trust signals above payment button
// Add before the pay button
<div className="flex items-center justify-center gap-4 py-4">
  <Image src="/images/payment/vnpay.svg" alt="VNPay" width={40} height={24} />
  <Image src="/images/payment/visacard.svg" alt="Visa" width={40} height={24} />
  <Image
    src="/images/payment/mastercard.svg"
    alt="Mastercard"
    width={40}
    height={24}
  />
  <span className="text-xs text-muted-foreground flex items-center gap-1">
    <Lock className="w-3 h-3" />
    Secure payment
  </span>
</div>
  • Step 3: Create payment logo assets if missing

Check if /public/images/payment/ exists with vnpay.svg, visacard.svg, mastercard.svg.


Task 6: Add Paraglide Messages

Files:

  • Modify: apps/src/paraglide/messages.ts
  • Add translations to apps/src/paraglide/messages.en.ts and apps/src/paraglide/messages.vi.ts

Messages needed:

  • booking_title() — "Book Your Experience"

  • booking_subtitle() — "Select an upcoming show to book"

  • booking_from_price() — "From {price} VND"

  • booking_only_seats_left() — "Only {count} seats left!"

  • booking_selling_fast() — "Selling fast"

  • booking_secure_payment() — "Secure encrypted payment"

  • Step 1: Add messages to paraglide

// In messages.ts
booking_title: () => string;
booking_subtitle: () => string;
booking_from_price: (params: { price: string }) => string;
booking_only_seats_left: (params: { count: number }) => string;
booking_selling_fast: () => string;
booking_secure_payment: () => string;
  • Step 2: Add English translations
// In messages.en.ts
booking_title: () => "Book Your Experience",
booking_subtitle: () => "Select an upcoming show to book",
booking_from_price: ({ price }) => `From ${price} VND`,
booking_only_seats_left: ({ count }) => `Only ${count} seats left!`,
booking_selling_fast: () => "Selling fast",
booking_secure_payment: () => "Secure encrypted payment",
  • Step 3: Add Vietnamese translations
// In messages.vi.ts
booking_title: () => "Đặt Vé Trải Nghiệm",
booking_subtitle: () => "Chọn một suất diễn sắp tới để đặt",
booking_from_price: ({ price }) => `Từ ${price} VND`,
booking_only_seats_left: ({ count }) => `Chỉ còn ${count} chỗ!`,
booking_selling_fast: () => "Đang bán nhanh",
booking_secure_payment: () => "Thanh toán bảo mật",

Task 7: Test the Flow

Files:

  • Run: Development server

  • Step 1: Start dev server

cd apps/frontend && npm run dev
  • Step 2: Navigate to /booking page

Verify:

  • Cards display with "From X VND" price

  • Availability badges show correctly (green/orange/grey)

  • "Only X seats left" appears when <10 seats

  • "Book Now" button opens modal

  • Step 3: Complete booking flow in modal

  1. Select ticket type and quantity
  2. Click Continue to Add-ons
  3. Skip or select add-ons
  4. Fill checkout form
  5. Verify trust signals visible
  6. Check countdown timer running

Verification Checklist

Per spec requirements:

  • Cards show "From [price] VND"
  • Availability badge: Available (>10 seats) / Few left (1-10) / Sold out
  • Scarcity signal: "Only X seats left" when <10
  • "Selling fast" when 10-20 seats
  • Countdown timer starts on step 1
  • Sticky cart visible on mobile (bottom) and desktop (sidebar)
  • Trust signals in checkout (logos + "Secure payment")
  • No page reload between steps (SPA)
  • Mobile-first: touch targets ≥44px

Execution Options

Plan complete and saved to docs/superpowers/plans/2026-05-12-booking-section-enhancement.md. Two execution options:

1. Subagent-Driven (recommended) - I dispatch a fresh subagent per task, review between tasks, fast iteration

2. Inline Execution - Execute tasks in this session using executing-plans, batch execution with checkpoints

Which approach?