Unified Booking Flow 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: Replace all booking entry point patterns with a single unified BookingModalContext.open(eventId) pattern. Remove ExperiencePicker modal and dead StickyCart component.
Architecture: All booking entry points call useBookingModal().open(eventId). The BookingModal is rendered once in the root layout via BookingModalByContext. No local isOpen/selectedEventId state in page components.
Tech Stack: React Context (BookingModalContext), Convex, Next.js App Router
File Inventory
Files to DELETE (dead code)
components/booking/experience-picker.tsx— unnecessary intermediate modalcomponents/booking/sticky-cart.tsx— dead code, not usedcontexts/calendar-modal-context.tsx— calendar becomes display-only (no booking integration)components/home/calendar-modal.tsx— replaced byUpcomingExperiencesSection
Files to MODIFY (entry points — standardize on context)
components/home/cta-section.tsx— replace ExperiencePicker withuseBookingModal().open()components/home/journey-section.tsx— replace ExperiencePicker withuseBookingModal().open()components/home/hero-bottom.tsx— replace ExperiencePicker withuseBookingModal().open()components/marketing/visit-cta-section.tsx— replace ExperiencePicker withuseBookingModal().open()components/schedule/schedule-list.tsx— replace local BookingModal withuseBookingModal().open()components/schedule/schedule-page.tsx— remove pickerOpen statecomponents/home/experience-schedule-section.tsx— replace local BookingModal with contextcomponents/ui/upcoming-shows-section.tsx— replace local BookingModal with contextcomponents/shows/experience-event-list.tsx— replace local BookingModal with context per rowcomponents/shows/event-row.tsx— if exists, same patterncomponents/audience/table-content.tsx— replace local BookingModal with context
Files to MODIFY (core infrastructure)
contexts/booking-modal-context.tsx— already correct, verifyopen(eventId)signaturecomponents/ui/booking-modal.tsx— verify it reads eventId from contextapp/layout.tsx— ensureBookingModalProviderwraps app (already correct)
Files to CREATE (if missing)
hooks/use-booking-modal.ts— typed hook wrapper foruseContextSelector(BookingModalContext, ...)
Task 1: Verify Core Infrastructure
Goal: Ensure BookingModalContext and BookingModal work correctly with open(eventId) pattern.
Files:
-
Modify:
contexts/booking-modal-context.tsx -
Modify:
components/ui/booking-modal.tsx -
Step 1: Read current BookingModalContext
Read contexts/booking-modal-context.tsx to verify it has:
interface BookingModalActions {
open: (eventId: string) => void;
close: () => void;
setEventId: (eventId: string | null) => void;
}- Step 2: Read BookingModal
Read components/ui/booking-modal.tsx to verify it reads eventId from context, not props.
- Step 3: Create useBookingModal hook
Create hooks/use-booking-modal.ts:
import { useContextSelector } from "use-context-selector";
import { BookingModalContext } from "~/contexts/booking-modal-context";
export function useBookingModal() {
const isOpen = useContextSelector(BookingModalContext, (s) => s.isOpen);
const eventId = useContextSelector(BookingModalContext, (s) => s.eventId);
const open = useContextSelector(BookingModalContext, (s) => s.open);
const close = useContextSelector(BookingModalContext, (s) => s.close);
const setEventId = useContextSelector(
BookingModalContext,
(s) => s.setEventId,
);
return { isOpen, eventId, open, close, setEventId };
}- Step 4: Commit
git add hooks/use-booking-modal.ts
git commit -m "feat(booking): add useBookingModal hook for unified entry point"Task 2: Remove ExperiencePicker from CTA Sections
Goal: Replace all ExperiencePicker usages with useBookingModal().open(eventId).
Files:
- Delete:
components/booking/experience-picker.tsx - Modify:
components/home/cta-section.tsx - Modify:
components/home/journey-section.tsx - Modify:
components/home/hero-bottom.tsx - Modify:
components/marketing/visit-cta-section.tsx
cta-section.tsx
- Step 1: Read current implementation
Read components/home/cta-section.tsx to find the ExperiencePicker usage pattern.
- Step 2: Remove pickerOpen state and ExperiencePicker import
Replace:
const [pickerOpen, setPickerOpen] = useState(false);
// ...
<ExperiencePicker onClose={() => setPickerOpen(false)} />With:
const { open } = useBookingModal();
// In button onClick:
open(eventId); // Use a hardcoded or derived eventId, or pass eventId prop- Step 3: Import useBookingModal
Add:
import { useBookingModal } from "~/hooks/use-booking-modal";- Step 4: Apply same pattern to journey-section.tsx
Read and modify components/home/journey-section.tsx — same pattern.
- Step 5: Apply same pattern to hero-bottom.tsx
Read and modify components/home/hero-bottom.tsx — same pattern.
- Step 6: Apply same pattern to visit-cta-section.tsx
Read and modify components/marketing/visit-cta-section.tsx — same pattern (may have multiple CTA variants).
- Step 7: Delete ExperiencePicker
Delete components/booking/experience-picker.tsx.
- Step 8: Commit
git add -A
git commit -m "refactor(booking): remove ExperiencePicker, use useBookingModal() everywhere"Task 3: Remove Local BookingModal State from Schedule Page
Files:
-
Modify:
components/schedule/schedule-list.tsx(or wherever the schedule list with booking is) -
Modify:
components/schedule/schedule-page.tsx -
Step 1: Read schedule page
Read app/[locale]/(landing)/schedule/page.tsx to understand how it currently opens BookingModal.
- Step 2: Remove pickerOpen state
Remove local useState for pickerOpen.
- Step 3: Import and use useBookingModal
import { useBookingModal } from "~/hooks/use-booking-modal";
// ...
const { open } = useBookingModal();
// In per-show "Book Now" handler:
open(occurrenceId);- Step 4: Commit
git add components/schedule/schedule-list.tsx components/schedule/schedule-page.tsx
git commit -m "refactor(schedule): use BookingModalContext for booking"Task 4: Remove Local BookingModal State from Experience Sections
Files:
-
Modify:
components/home/experience-schedule-section.tsx -
Modify:
components/ui/upcoming-shows-section.tsx -
Step 1: Read experience-schedule-section.tsx
Find where it opens BookingModal with local selectedEventId state.
- Step 2: Replace with useBookingModal
const { open } = useBookingModal();
// Per-row onClick:
open(occurrenceId);- Step 3: Read upcoming-shows-section.tsx
Same pattern — replace local state with context.
- Step 4: Commit
git add components/home/experience-schedule-section.tsx components/ui/upcoming-shows-section.tsx
git commit -m "refactor: unify booking entry points in experience sections"Task 5: Remove Local BookingModal State from Show Pages
Files:
-
Modify:
components/shows/experience-event-list.tsx -
Modify:
components/audience/table-content.tsx -
Step 1: Read experience-event-list.tsx
Find per-row modalOpen state and <BookingModal> usage.
- Step 2: Replace with useBookingModal per row
// Instead of local state per row, use eventId in the click handler
const { open } = useBookingModal();
// onClick on "Book" button for row with id:
open(occurrenceId);- Step 3: Read audience/table-content.tsx
Same pattern — replace local bookingEventId state with context.
- Step 4: Commit
git add components/shows/experience-event-list.tsx components/audience/table-content.tsx
git commit -m "refactor: use BookingModalContext in show and audience pages"Task 6: Remove StickyCart (Dead Code)
Files:
-
Delete:
components/booking/sticky-cart.tsx -
Step 1: Verify no imports exist
grep -r "sticky-cart" apps/frontend/components --include="*.tsx"Expected: Only the file itself or no results.
- Step 2: Delete the file
rm components/booking/sticky-cart.tsx- Step 3: Commit
git rm components/booking/sticky-cart.tsx
git commit -m "chore: remove dead StickyCart component"Task 7: Simplify CalendarModal (Display-Only)
Files:
- Modify:
components/home/calendar-modal.tsx - Modify:
contexts/calendar-modal-context.tsx
The CalendarModal becomes purely a date display tool — it no longer has any booking integration. The selected day highlights shows on the page but does NOT open BookingModal.
-
Step 1: Read current calendar-modal-context.tsx
-
Step 2: Simplify context if needed
If CalendarModalContext has more than just navigation state (isOpen, currentMonth, selectedDay, prevMonth, nextMonth, setSelectedDay), trim it. It should NOT have open or any booking-related actions.
- Step 3: Ensure CalendarModal doesn't call booking
Read components/home/calendar-modal.tsx. Verify clicking a day does NOT call useBookingModal().open(). If it does, remove that behavior — calendar is display-only.
- Step 4: Commit
git add components/home/calendar-modal.tsx contexts/calendar-modal-context.tsx
git commit -m "refactor(calendar): simplify to display-only, no booking integration"Task 8: Verify All Entry Points Converted
Goal: Ensure no local BookingModal instantiation remains.
- Step 1: Search for remaining local BookingModal usage
grep -rn "<BookingModal" apps/frontend/components --include="*.tsx" | grep -v "booking-modal.tsx"Expected: Only BookingModalByContext in layout, or zero results.
- Step 2: Search for ExperiencePicker import
grep -rn "experience-picker" apps/frontend --include="*.tsx"Expected: Zero results after deletion.
- Step 3: Commit any remaining fixes
Final Architecture After Refactor
┌─────────────────────────────────────────────────────────────────────────────┐
│ UNIFIED BOOKING FLOW (AFTER) │
└─────────────────────────────────────────────────────────────────────────────┘
Any Page Component Root Layout
│ │
│ const { open } = useBookingModal() │
│ │
│ open(eventId) │
│ │ │
└────────►│ │
│ ▼
│ ┌─────────────────────────────────────────┐
│ │ BookingModalContext │
│ │ (isOpen, eventId, open, close, etc.) │
│ └─────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────────────────┐
│ BookingModal │
│ (reads eventId from │
│ context, not props) │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ BookingFlow │
│ (StepTickets → Addons → │
│ Checkout → Confirmation) │
└─────────────────────────────┘Entry points are now just:
const { open } = useBookingModal();
open(eventId);No more:
- Local
selectedEventIdstate <BookingModal isOpen={...} eventId={...} />- ExperiencePicker intermediate modal
- StickyCart dead code
Testing Checklist
- Homepage CTA opens BookingModal with correct event
- Journey section CTA opens BookingModal
- Hero bottom CTA opens BookingModal
- Schedule page "Book Now" per show opens BookingModal
- Experience schedule section "Book" button opens BookingModal
- Upcoming shows section opens BookingModal
- Show detail page "Book" opens BookingModal
- CalendarModal no longer triggers booking
- No ExperiencePicker modal exists
- No local BookingModal state in any page component