React Components

The @otesse/react package provides pre-built React components for common Otesse integrations.

Installation

npm install @otesse/react @otesse/sdk

Provider Setup

Wrap your application with the Otesse provider:

import { OtesseProvider } from '@otesse/react';

function App() {
  return (
    <OtesseProvider publicKey="pk_live_abc123">
      <YourApp />
    </OtesseProvider>
  );
}

Booking Widget

Drop-in booking form component:

import { BookingWidget } from '@otesse/react';

function BookingPage() {
  return (
    <BookingWidget
      industry="cleaning"
      zip="97401"
      theme="light"
      accentColor="#3B82F6"
      onBookingCreated={(booking) => {
        console.log('Booking created:', booking.id);
        router.push('/thank-you');
      }}
      onStepChange={(step) => {
        analytics.track('booking_step', { step });
      }}
      onError={(error) => {
        console.error('Booking error:', error);
      }}
      className="max-w-lg mx-auto"
    />
  );
}

Props

PropTypeDescription
industrystringPre-selected industry
zipstringPre-filled ZIP code
theme'light' \'dark'Color theme
accentColorstringCustom accent color (hex)
hideHeaderbooleanHide the Otesse branding
onBookingCreatedfunctionCallback when booking is confirmed
onStepChangefunctionCallback on each step transition
onErrorfunctionError callback
prefillobjectPre-fill customer information
classNamestringCSS class for the container

Availability Calendar

Show available dates and times:

import { AvailabilityCalendar } from '@otesse/react';

function SchedulePage() {
  const [selectedSlot, setSelectedSlot] = useState(null);

  return (
    <AvailabilityCalendar
      zip="97401"
      industry="cleaning"
      duration={120}
      onSlotSelected={(slot) => setSelectedSlot(slot)}
      selectedDate={selectedSlot?.date}
      minDate={new Date()}
      maxDate={addDays(new Date(), 30)}
    />
  );
}

Service Catalog

Display available services and pricing:

import { ServiceCatalog } from '@otesse/react';

function ServicesPage() {
  return (
    <ServiceCatalog
      industry="junk-removal"
      zip="97401"
      showPricing={true}
      onItemSelected={(item) => {
        console.log('Selected:', item.name, item.price);
      }}
      columns={3}
      className="gap-4"
    />
  );
}

Hooks

For custom UI, use the data hooks directly:

import { useAvailability, useIndustries, usePricing } from '@otesse/react';

function CustomBookingUI() {
  const { industries, loading: industriesLoading } = useIndustries();

  const { slots, loading: slotsLoading } = useAvailability({
    zip: '97401',
    industry: 'cleaning',
    date: '2026-03-15',
  });

  const { pricing, loading: pricingLoading } = usePricing({
    industry: 'cleaning',
    zip: '97401',
    configuration: {
      bedrooms: 3,
      bathrooms: 2,
      frequency: 'one-time',
    },
  });

  return (
    <div>
      <h2>Available Services</h2>
      {industries?.map(ind => (
        <div key={ind.id}>{ind.name}</div>
      ))}

      <h2>Available Times</h2>
      {slots?.map(slot => (
        <button key={slot.start}>{slot.label}: {slot.start}-{slot.end}</button>
      ))}

      <h2>Estimated Price</h2>
      {pricing && <p>Total: ${(pricing.total / 100).toFixed(2)}</p>}
    </div>
  );
}

Styling

All components accept a className prop and use CSS custom properties for theming:

:root {
  --otesse-primary: #3B82F6;
  --otesse-text: #1a1a1a;
  --otesse-bg: #ffffff;
  --otesse-border: #e5e7eb;
  --otesse-radius: 8px;
  --otesse-font: 'Inter', sans-serif;
}

Override these variables to match your brand without modifying component source code.