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
| Prop | Type | Description | |
|---|---|---|---|
industry | string | Pre-selected industry | |
zip | string | Pre-filled ZIP code | |
theme | 'light' \ | 'dark' | Color theme |
accentColor | string | Custom accent color (hex) | |
hideHeader | boolean | Hide the Otesse branding | |
onBookingCreated | function | Callback when booking is confirmed | |
onStepChange | function | Callback on each step transition | |
onError | function | Error callback | |
prefill | object | Pre-fill customer information | |
className | string | CSS 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.
On this page