Booking Widget Internals
3-step state machine, react-hook-form, slot computation
Booking Widget Internals
The booking widget is the core client-facing component for scheduling appointments in Odys. It orchestrates user interaction, form validation, time slot generation, and backend communication—all within a streamlined, three-step flow. Understanding its internals reveals how Odys balances usability, performance, and spam resistance in a real-world scheduling context.
Overview
The widget is a React Client Component powered by react-hook-form and zod for form management and validation. It integrates with a backend API (/api/booking) to fetch availability and submit bookings, leveraging the availability, professionals, and appointments database tables. The state machine drives the UX through three phases: picking a date and time, entering client details, and displaying success. Slot computation happens client-side using rules derived from the availability table and existing appointments, ensuring real-time responsiveness.
State Machine and User Flow
The widget uses a simple but effective state machine with three states: "pick", "form", and "success". This pattern keeps the UI focused and reduces cognitive load. The transition from date selection to form submission is gated by selectedDate and selectedSlot, ensuring data integrity before proceeding. The success screen not only confirms the booking but also conditionally renders a Pix QR code for immediate payment, based on paymentType and pixKey—a key feature for Brazil’s financial landscape.
Slot Generation and Availability Logic
Slot computation is handled by generateSlots in src/lib/slots.ts, a pure function that takes a date, availability rules, existing appointments, and session duration. It generates time slots in HH:mm format, skipping those in the past or conflicting with existing appointments. The logic respects sessionDuration from the professionals table and uses availability.startTime and endTime to define the working window. This client-side approach reduces server load and enables instant feedback when users navigate the calendar. Additionally, the calendar UI itself prevents selection of past dates via the disabled prop (src/app/p/[slug]/booking-widget.tsx:204), ensuring users cannot even attempt to pick a historical day.
Design Decisions
The use of a honeypot field (website) in the form is a lightweight anti-bot measure—simple yet effective. By positioning it off-screen and dropping submissions server-side if filled, it avoids CAPTCHA friction. The two-column layout on larger screens (grid-cols-1 md:grid-cols-2) prioritizes the calendar and slots, aligning with user goals. Grouping slots into morning, afternoon, and evening improves scannability, even though it’s not required by the data model.
Potential Improvements
-
BookingWidget— Avoid re-fetching slots on every render: TheuseEffectinBookingWidgetcorrectly fetches slots whenselectedDatechanges. WhileslugandsessionDurationare static props and included in the dependency array, they do not cause unnecessary re-execution of the effect. -
generateSlots— Cache or memoize results: SincegenerateSlotsis called on every date change and performs array filtering and date math, it could benefit from memoization, especially on low-end devices. Caching bydate.toISOString()andsessionDurationwould prevent recomputation when users toggle back and forth. -
SlotButton— Accessibility enhancement: The time buttons lackaria-pressedor role attributes. Addingaria-pressed={selected}would improve screen reader experience, making it clear which slot is selected without relying solely on visual cues.
References
src/app/p/[slug]/booking-widget.tsxsrc/app/p/[slug]/page.tsxsrc/lib/slots.tssrc/lib/db/schema.ts(foravailability,appointments,professionals)