UNPKG

@tebuto/react-booking-widget

Version:

React Component for the Tebuto Booking Widget

498 lines (486 loc) 14.4 kB
import * as react from 'react'; import { JSX, ReactNode } from 'react'; /** * Theme configuration for the Tebuto Booking Widget. * All colors should be valid CSS color strings (e.g., '#00B4A9', 'rgb(0, 180, 169)'). * * Note: The widget automatically generates color variations (light, dark, etc.) * from the primaryColor, so only the base colors need to be configured. */ type TebutoWidgetTheme = { /** Primary brand color - used for buttons, highlights, selected states */ primaryColor?: string; /** Main widget background color */ backgroundColor?: string; /** Main text color */ textPrimary?: string; /** Secondary/muted text color */ textSecondary?: string; /** General border color */ borderColor?: string; /** Widget font family */ fontFamily?: string; /** Inherit font from parent page instead of using widget font */ inheritFont?: boolean; }; /** * Configuration options for the Tebuto Booking Widget. */ type TebutoBookingWidgetConfiguration = { /** UUID of the therapist whose calendar should be displayed (required) */ therapistUUID: string; /** Background color of the widget (shorthand for theme.backgroundColor) */ backgroundColor?: string; /** Array of category IDs to filter available appointments */ categories?: number[]; /** Whether to display a border around the widget (default: true) */ border?: boolean; /** Include subuser appointments in the calendar (default: false) */ includeSubusers?: boolean; /** Show quick filter buttons for time slots (default: false) */ showQuickFilters?: boolean; /** Inherit font from parent page instead of using widget font (default: false) */ inheritFont?: boolean; /** Theme configuration for customizing colors and fonts */ theme?: TebutoWidgetTheme; }; type TebutoBookingWidgetProps = { noScriptText?: string; } & TebutoBookingWidgetConfiguration; declare function TebutoBookingWidget({ noScriptText, ...config }: TebutoBookingWidgetProps): JSX.Element; type CustomBookingExampleProps = { therapistUUID: string; categories?: number[]; }; /** * CustomBookingExample - A modern, fully-featured booking interface * * This example demonstrates how to use the Tebuto hooks to build * a completely custom booking experience. * * @example * ```tsx * import { CustomBookingExample } from '@tebuto/react-booking-widget' * * function BookingPage() { * return <CustomBookingExample therapistUUID="your-uuid" /> * } * ``` */ declare function CustomBookingExample({ therapistUUID, categories }: Readonly<CustomBookingExampleProps>): react.JSX.Element; /** * API Types for Tebuto Booking Hooks * These types represent the data structures returned by the Tebuto API. */ /** Location type for appointments */ type AppointmentLocation = 'virtual' | 'onsite' | 'not-fixed'; /** Address structure for therapist/location */ type Address = { streetAndNumber: string; additionalInformation?: string; city: { name: string; zip: string; }; }; /** Therapist information */ type Therapist = { name: string; firstName: string; lastName: string; address: Address; showWatermark: boolean; }; /** Therapist reference in events */ type TherapistReference = { id: number; uuid: string; name: string; }; /** Available time slot / event */ type TimeSlot = { title: string; start: string; end: string; location: AppointmentLocation; color: string; price: string; taxRate: string; outageFeeEnabled: boolean; outageFeeHours: number; outageFeePrice: number; eventRuleId: number; eventCategoryId: number; paymentEnabled: boolean; paymentDuringBooking: boolean; therapist: TherapistReference; }; /** Response from claim endpoint */ type ClaimResponse = { isAvailable: boolean; requirePhoneNumber: boolean; requireAddress: boolean; requireBirthdate: boolean; }; /** Payment configuration */ type PaymentConfiguration = { paymentTypes: string[]; onlinePaymentMethods: string[]; }; /** Client information for booking */ type ClientInfo = { firstName: string; lastName: string; email: string; phone?: string; address?: { streetAndNumber: string; additionalInformation?: string; city: string; zip: string; }; birthdate?: string; notes?: string; }; /** Booking request payload */ type BookingRequest = { start: string; end: string; eventRuleId: number; locationSelection: AppointmentLocation; client: ClientInfo; }; /** Booking response */ type BookingResponse = { id: number; createdAt: string; locationSelection: AppointmentLocation; isConfirmed: boolean; isOutage: boolean; ics: string; }; /** Hook state for async operations */ type AsyncState<T> = { data: T | null; isLoading: boolean; error: Error | null; }; /** Category for filtering */ type EventCategory = { id: number; name: string; color: string; price: string; location: AppointmentLocation; }; /** Grouped time slots by date */ type SlotsByDate = { [date: string]: TimeSlot[]; }; /** Time slot with additional computed properties */ type EnrichedTimeSlot = TimeSlot & { dateKey: string; timeString: string; durationMinutes: number; formattedPrice: string; isToday: boolean; isPast: boolean; }; type TebutoConfig = { therapistUUID: string; apiBaseUrl: string; categories?: number[]; includeSubusers?: boolean; }; type TebutoContextValue = TebutoConfig & { buildUrl: (path: string) => string; fingerprint: string; }; type TebutoProviderProps = { therapistUUID: string; apiBaseUrl?: string; categories?: number[]; includeSubusers?: boolean; children: ReactNode; }; /** * TebutoProvider - Context provider for Tebuto booking hooks * * Wrap your booking components with this provider to share configuration * across all Tebuto hooks. * * @example * ```tsx * <TebutoProvider therapistUUID="your-uuid"> * <YourBookingComponent /> * </TebutoProvider> * ``` */ declare function TebutoProvider({ therapistUUID, apiBaseUrl, categories, includeSubusers, children }: Readonly<TebutoProviderProps>): react.JSX.Element; /** * useTebutoContext - Access the Tebuto configuration context * * Must be used within a TebutoProvider. * * @throws Error if used outside of TebutoProvider */ declare function useTebutoContext(): TebutoContextValue; type UseTherapistReturn = AsyncState<Therapist> & { refetch: () => Promise<void>; }; /** * useTherapist - Fetch therapist information * * Automatically fetches the therapist data when the component mounts. * Uses the therapistUUID from the TebutoProvider context. * * @example * ```tsx * const { data: therapist, isLoading, error } = useTherapist() * * if (isLoading) return <Spinner /> * if (error) return <Error message={error.message} /> * * return <h1>Book with {therapist.name}</h1> * ``` */ declare function useTherapist(): UseTherapistReturn; type UseAvailableSlotsOptions = { /** Auto-fetch on mount (default: true) */ autoFetch?: boolean; /** Filter by specific category IDs */ categories?: number[]; }; type UseAvailableSlotsReturn = AsyncState<TimeSlot[]> & { /** Refetch available slots */ refetch: () => Promise<void>; /** Slots grouped by date (YYYY-MM-DD) */ slotsByDate: SlotsByDate; /** All unique dates with available slots */ availableDates: Date[]; /** Get enriched slots for a specific date */ getSlotsForDate: (date: Date) => EnrichedTimeSlot[]; /** All unique categories from available slots */ categories: Array<{ id: number; name: string; color: string; }>; /** Total count of available slots */ totalSlots: number; }; /** * useAvailableSlots - Fetch and manage available time slots * * Provides available appointment slots with helpers for grouping, * filtering, and date navigation. * * @example * ```tsx * const { * slotsByDate, * availableDates, * getSlotsForDate, * isLoading * } = useAvailableSlots() * * const [selectedDate, setSelectedDate] = useState<Date | null>(null) * * return ( * <div> * <DatePicker dates={availableDates} onSelect={setSelectedDate} /> * {selectedDate && ( * <TimeSlotList slots={getSlotsForDate(selectedDate)} /> * )} * </div> * ) * ``` */ declare function useAvailableSlots(options?: UseAvailableSlotsOptions): UseAvailableSlotsReturn; type ClaimState = { claimedSlot: TimeSlot | null; claimResponse: ClaimResponse | null; isLoading: boolean; error: Error | null; }; type UseClaimSlotReturn = ClaimState & { /** Claim a time slot to reserve it temporarily */ claim: (slot: TimeSlot) => Promise<ClaimResponse | null>; /** Release the currently claimed slot */ unclaim: () => Promise<void>; /** Check if a specific slot is currently claimed */ isClaimed: (slot: TimeSlot) => boolean; /** Clear error state */ clearError: () => void; }; /** * useClaimSlot - Temporarily claim a time slot * * Claims a time slot to reserve it while the user fills out * their booking information. Automatically handles unclaiming * when claiming a new slot. * * @example * ```tsx * const { claim, unclaim, claimedSlot, claimResponse, isLoading } = useClaimSlot() * * const handleSlotSelect = async (slot: TimeSlot) => { * const response = await claim(slot) * if (response?.isAvailable) { * setStep('booking-form') * } * } * * const handleCancel = () => { * unclaim() * setStep('slot-selection') * } * ``` */ declare function useClaimSlot(): UseClaimSlotReturn; type BookingRequestState = { isLoading: boolean; error: Error | null; isSuccess: boolean; }; type BookAppointmentParams = { slot: TimeSlot; client: ClientInfo; locationSelection?: AppointmentLocation; }; type UseBookAppointmentReturn = BookingRequestState & { /** Submit booking request - returns true on success */ book: (params: BookAppointmentParams) => Promise<boolean>; /** Reset the state */ reset: () => void; }; /** * useBookAppointment - Submit a public booking request * * Submits a booking request with client information. For public bookings, * the client will receive a confirmation email to verify the appointment. * * @example * ```tsx * const { book, isLoading, isSuccess, error, reset } = useBookAppointment() * * const handleSubmit = async (clientInfo: ClientInfo) => { * const success = await book({ * slot: claimedSlot, * client: clientInfo, * locationSelection: selectedLocation * }) * * if (success) { * setStep('confirmation') * } * } * ``` */ declare function useBookAppointment(): UseBookAppointmentReturn; type BookingStep = 'loading' | 'date-selection' | 'time-selection' | 'booking-form' | 'confirmation' | 'error'; type UseBookingFlowOptions = { /** Categories to filter by */ categories?: number[]; /** Callback when booking request is submitted successfully */ onBookingComplete?: () => void; /** Callback on any error */ onError?: (error: Error) => void; }; type UseBookingFlowReturn = { /** Current booking step */ step: BookingStep; /** Go to a specific step */ goToStep: (step: BookingStep) => void; /** Therapist information */ therapist: ReturnType<typeof useTherapist>; /** Available slots management */ slots: ReturnType<typeof useAvailableSlots>; /** Selected date */ selectedDate: Date | null; /** Select a date */ selectDate: (date: Date | null) => void; /** Slots for the selected date */ selectedDateSlots: EnrichedTimeSlot[]; /** Selected time slot */ selectedSlot: TimeSlot | null; /** Select a time slot (claims it) */ selectSlot: (slot: TimeSlot | null) => Promise<boolean>; /** Selected location (for not-fixed appointments) */ selectedLocation: AppointmentLocation | null; /** Set the location selection */ setLocation: (location: AppointmentLocation) => void; /** Claim state */ claim: ReturnType<typeof useClaimSlot>; /** Booking state */ booking: ReturnType<typeof useBookAppointment>; /** Submit booking with client info */ submitBooking: (client: ClientInfo) => Promise<boolean>; /** Start over from the beginning */ reset: () => void; /** Overall loading state */ isLoading: boolean; /** Current error if any */ error: Error | null; }; /** * useBookingFlow - Complete booking flow orchestration * * A convenience hook that combines all booking hooks and manages * the booking flow state. Perfect for quickly building a booking UI. * * @example * ```tsx * function BookingPage() { * const { * step, * therapist, * slots, * selectedDate, * selectDate, * selectedDateSlots, * selectSlot, * submitBooking, * booking, * reset * } = useBookingFlow() * * switch (step) { * case 'loading': * return <LoadingSpinner /> * * case 'date-selection': * return ( * <DatePicker * availableDates={slots.availableDates} * onSelect={selectDate} * /> * ) * * case 'time-selection': * return ( * <TimeSlotPicker * slots={selectedDateSlots} * onSelect={selectSlot} * /> * ) * * case 'booking-form': * return ( * <BookingForm onSubmit={submitBooking} /> * ) * * case 'confirmation': * return ( * <Confirmation * booking={booking.booking} * onReset={reset} * /> * ) * } * } * ``` */ declare function useBookingFlow(options?: UseBookingFlowOptions): UseBookingFlowReturn; export { CustomBookingExample, TebutoBookingWidget, TebutoProvider, useAvailableSlots, useBookAppointment, useBookingFlow, useClaimSlot, useTebutoContext, useTherapist }; export type { Address, AppointmentLocation, AsyncState, BookingRequest, BookingResponse, ClaimResponse, ClientInfo, EnrichedTimeSlot, EventCategory, PaymentConfiguration, SlotsByDate, TebutoBookingWidgetConfiguration, TebutoWidgetTheme, Therapist, TherapistReference, TimeSlot };