@tebuto/react-booking-widget
Version:
React Component for the Tebuto Booking Widget
498 lines (486 loc) • 14.4 kB
TypeScript
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 };