@tebuto/react-booking-widget 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/types.d.ts CHANGED
@@ -1,10 +1,49 @@
1
- import { JSX } from 'react';
1
+ import { JSX, ReactNode } from 'react';
2
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
3
 
4
+ /**
5
+ * Theme configuration for the Tebuto Booking Widget.
6
+ * All colors should be valid CSS color strings (e.g., '#00B4A9', 'rgb(0, 180, 169)').
7
+ *
8
+ * Note: The widget automatically generates color variations (light, dark, etc.)
9
+ * from the primaryColor, so only the base colors need to be configured.
10
+ */
11
+ type TebutoWidgetTheme = {
12
+ /** Primary brand color - used for buttons, highlights, selected states */
13
+ primaryColor?: string;
14
+ /** Main widget background color */
15
+ backgroundColor?: string;
16
+ /** Main text color */
17
+ textPrimary?: string;
18
+ /** Secondary/muted text color */
19
+ textSecondary?: string;
20
+ /** General border color */
21
+ borderColor?: string;
22
+ /** Widget font family */
23
+ fontFamily?: string;
24
+ /** Inherit font from parent page instead of using widget font */
25
+ inheritFont?: boolean;
26
+ };
27
+ /**
28
+ * Configuration options for the Tebuto Booking Widget.
29
+ */
3
30
  type TebutoBookingWidgetConfiguration = {
31
+ /** UUID of the therapist whose calendar should be displayed (required) */
4
32
  therapistUUID: string;
33
+ /** Background color of the widget (shorthand for theme.backgroundColor) */
5
34
  backgroundColor?: string;
35
+ /** Array of category IDs to filter available appointments */
6
36
  categories?: number[];
37
+ /** Whether to display a border around the widget (default: true) */
7
38
  border?: boolean;
39
+ /** Include subuser appointments in the calendar (default: false) */
40
+ includeSubusers?: boolean;
41
+ /** Show quick filter buttons for time slots (default: false) */
42
+ showQuickFilters?: boolean;
43
+ /** Inherit font from parent page instead of using widget font (default: false) */
44
+ inheritFont?: boolean;
45
+ /** Theme configuration for customizing colors and fonts */
46
+ theme?: TebutoWidgetTheme;
8
47
  };
9
48
 
10
49
  type TebutoBookingWidgetProps = {
@@ -12,5 +51,456 @@ type TebutoBookingWidgetProps = {
12
51
  } & TebutoBookingWidgetConfiguration;
13
52
  declare function TebutoBookingWidget({ noScriptText, ...config }: TebutoBookingWidgetProps): JSX.Element;
14
53
 
15
- export { TebutoBookingWidget };
16
- export type { TebutoBookingWidgetConfiguration };
54
+ type CustomBookingExampleProps = {
55
+ therapistUUID: string;
56
+ categories?: number[];
57
+ };
58
+ /**
59
+ * CustomBookingExample - A modern, fully-featured booking interface
60
+ *
61
+ * This example demonstrates how to use the Tebuto hooks to build
62
+ * a completely custom booking experience.
63
+ *
64
+ * @example
65
+ * ```tsx
66
+ * import { CustomBookingExample } from '@tebuto/react-booking-widget'
67
+ *
68
+ * function BookingPage() {
69
+ * return <CustomBookingExample therapistUUID="your-uuid" />
70
+ * }
71
+ * ```
72
+ */
73
+ declare function CustomBookingExample({ therapistUUID, categories }: CustomBookingExampleProps): react_jsx_runtime.JSX.Element;
74
+
75
+ /**
76
+ * API Types for Tebuto Booking Hooks
77
+ * These types represent the data structures returned by the Tebuto API.
78
+ */
79
+ /** Location type for appointments */
80
+ type AppointmentLocation = 'virtual' | 'onsite' | 'not-fixed';
81
+ /** Address structure for therapist/location */
82
+ type Address = {
83
+ streetAndNumber: string;
84
+ additionalInformation?: string;
85
+ city: {
86
+ name: string;
87
+ zip: string;
88
+ };
89
+ };
90
+ /** Therapist information */
91
+ type Therapist = {
92
+ name: string;
93
+ firstName: string;
94
+ lastName: string;
95
+ address: Address;
96
+ showWatermark: boolean;
97
+ };
98
+ /** Therapist reference in events */
99
+ type TherapistReference = {
100
+ id: number;
101
+ uuid: string;
102
+ name: string;
103
+ };
104
+ /** Available time slot / event */
105
+ type TimeSlot = {
106
+ title: string;
107
+ start: string;
108
+ end: string;
109
+ location: AppointmentLocation;
110
+ color: string;
111
+ price: string;
112
+ taxRate: string;
113
+ outageFeeEnabled: boolean;
114
+ outageFeeHours: number;
115
+ outageFeePrice: number;
116
+ eventRuleId: number;
117
+ eventCategoryId: number;
118
+ paymentEnabled: boolean;
119
+ paymentDuringBooking: boolean;
120
+ therapist: TherapistReference;
121
+ };
122
+ /** Response from claim endpoint */
123
+ type ClaimResponse = {
124
+ isAvailable: boolean;
125
+ requirePhoneNumber: boolean;
126
+ requireAddress: boolean;
127
+ };
128
+ /** Payment configuration */
129
+ type PaymentConfiguration = {
130
+ paymentTypes: string[];
131
+ onlinePaymentMethods: string[];
132
+ };
133
+ /** Client information for booking */
134
+ type ClientInfo = {
135
+ firstName: string;
136
+ lastName: string;
137
+ email: string;
138
+ phone?: string;
139
+ address?: {
140
+ streetAndNumber: string;
141
+ additionalInformation?: string;
142
+ city: string;
143
+ zip: string;
144
+ };
145
+ notes?: string;
146
+ };
147
+ /** Booking request payload */
148
+ type BookingRequest = {
149
+ start: string;
150
+ end: string;
151
+ eventRuleId: number;
152
+ locationSelection: AppointmentLocation;
153
+ client: ClientInfo;
154
+ };
155
+ /** Booking response */
156
+ type BookingResponse = {
157
+ id: number;
158
+ createdAt: string;
159
+ locationSelection: AppointmentLocation;
160
+ isConfirmed: boolean;
161
+ isOutage: boolean;
162
+ ics: string;
163
+ };
164
+ /** Hook state for async operations */
165
+ type AsyncState<T> = {
166
+ data: T | null;
167
+ isLoading: boolean;
168
+ error: Error | null;
169
+ };
170
+ /** Category for filtering */
171
+ type EventCategory = {
172
+ id: number;
173
+ name: string;
174
+ color: string;
175
+ price: string;
176
+ location: AppointmentLocation;
177
+ };
178
+ /** Grouped time slots by date */
179
+ type SlotsByDate = {
180
+ [date: string]: TimeSlot[];
181
+ };
182
+ /** Time slot with additional computed properties */
183
+ type EnrichedTimeSlot = TimeSlot & {
184
+ dateKey: string;
185
+ timeString: string;
186
+ durationMinutes: number;
187
+ formattedPrice: string;
188
+ isToday: boolean;
189
+ isPast: boolean;
190
+ };
191
+
192
+ type TebutoConfig = {
193
+ therapistUUID: string;
194
+ apiBaseUrl: string;
195
+ categories?: number[];
196
+ includeSubusers?: boolean;
197
+ };
198
+ type TebutoContextValue = TebutoConfig & {
199
+ buildUrl: (path: string) => string;
200
+ };
201
+ type TebutoProviderProps = {
202
+ therapistUUID: string;
203
+ apiBaseUrl?: string;
204
+ categories?: number[];
205
+ includeSubusers?: boolean;
206
+ children: ReactNode;
207
+ };
208
+ /**
209
+ * TebutoProvider - Context provider for Tebuto booking hooks
210
+ *
211
+ * Wrap your booking components with this provider to share configuration
212
+ * across all Tebuto hooks.
213
+ *
214
+ * @example
215
+ * ```tsx
216
+ * <TebutoProvider therapistUUID="your-uuid">
217
+ * <YourBookingComponent />
218
+ * </TebutoProvider>
219
+ * ```
220
+ */
221
+ declare function TebutoProvider({ therapistUUID, apiBaseUrl, categories, includeSubusers, children }: TebutoProviderProps): react_jsx_runtime.JSX.Element;
222
+ /**
223
+ * useTebutoContext - Access the Tebuto configuration context
224
+ *
225
+ * Must be used within a TebutoProvider.
226
+ *
227
+ * @throws Error if used outside of TebutoProvider
228
+ */
229
+ declare function useTebutoContext(): TebutoContextValue;
230
+
231
+ type UseTherapistReturn = AsyncState<Therapist> & {
232
+ refetch: () => Promise<void>;
233
+ };
234
+ /**
235
+ * useTherapist - Fetch therapist information
236
+ *
237
+ * Automatically fetches the therapist data when the component mounts.
238
+ * Uses the therapistUUID from the TebutoProvider context.
239
+ *
240
+ * @example
241
+ * ```tsx
242
+ * const { data: therapist, isLoading, error } = useTherapist()
243
+ *
244
+ * if (isLoading) return <Spinner />
245
+ * if (error) return <Error message={error.message} />
246
+ *
247
+ * return <h1>Book with {therapist.name}</h1>
248
+ * ```
249
+ */
250
+ declare function useTherapist(): UseTherapistReturn;
251
+
252
+ type UseAvailableSlotsOptions = {
253
+ /** Auto-fetch on mount (default: true) */
254
+ autoFetch?: boolean;
255
+ /** Filter by specific category IDs */
256
+ categories?: number[];
257
+ };
258
+ type UseAvailableSlotsReturn = AsyncState<TimeSlot[]> & {
259
+ /** Refetch available slots */
260
+ refetch: () => Promise<void>;
261
+ /** Slots grouped by date (YYYY-MM-DD) */
262
+ slotsByDate: SlotsByDate;
263
+ /** All unique dates with available slots */
264
+ availableDates: Date[];
265
+ /** Get enriched slots for a specific date */
266
+ getSlotsForDate: (date: Date) => EnrichedTimeSlot[];
267
+ /** All unique categories from available slots */
268
+ categories: Array<{
269
+ id: number;
270
+ name: string;
271
+ color: string;
272
+ }>;
273
+ /** Total count of available slots */
274
+ totalSlots: number;
275
+ };
276
+ /**
277
+ * useAvailableSlots - Fetch and manage available time slots
278
+ *
279
+ * Provides available appointment slots with helpers for grouping,
280
+ * filtering, and date navigation.
281
+ *
282
+ * @example
283
+ * ```tsx
284
+ * const {
285
+ * slotsByDate,
286
+ * availableDates,
287
+ * getSlotsForDate,
288
+ * isLoading
289
+ * } = useAvailableSlots()
290
+ *
291
+ * const [selectedDate, setSelectedDate] = useState<Date | null>(null)
292
+ *
293
+ * return (
294
+ * <div>
295
+ * <DatePicker dates={availableDates} onSelect={setSelectedDate} />
296
+ * {selectedDate && (
297
+ * <TimeSlotList slots={getSlotsForDate(selectedDate)} />
298
+ * )}
299
+ * </div>
300
+ * )
301
+ * ```
302
+ */
303
+ declare function useAvailableSlots(options?: UseAvailableSlotsOptions): UseAvailableSlotsReturn;
304
+
305
+ type ClaimState = {
306
+ claimedSlot: TimeSlot | null;
307
+ claimResponse: ClaimResponse | null;
308
+ isLoading: boolean;
309
+ error: Error | null;
310
+ };
311
+ type UseClaimSlotReturn = ClaimState & {
312
+ /** Claim a time slot to reserve it temporarily */
313
+ claim: (slot: TimeSlot) => Promise<ClaimResponse | null>;
314
+ /** Release the currently claimed slot */
315
+ unclaim: () => Promise<void>;
316
+ /** Check if a specific slot is currently claimed */
317
+ isClaimed: (slot: TimeSlot) => boolean;
318
+ /** Clear error state */
319
+ clearError: () => void;
320
+ };
321
+ /**
322
+ * useClaimSlot - Temporarily claim a time slot
323
+ *
324
+ * Claims a time slot to reserve it while the user fills out
325
+ * their booking information. Automatically handles unclaiming
326
+ * when claiming a new slot.
327
+ *
328
+ * @example
329
+ * ```tsx
330
+ * const { claim, unclaim, claimedSlot, claimResponse, isLoading } = useClaimSlot()
331
+ *
332
+ * const handleSlotSelect = async (slot: TimeSlot) => {
333
+ * const response = await claim(slot)
334
+ * if (response?.isAvailable) {
335
+ * setStep('booking-form')
336
+ * }
337
+ * }
338
+ *
339
+ * const handleCancel = () => {
340
+ * unclaim()
341
+ * setStep('slot-selection')
342
+ * }
343
+ * ```
344
+ */
345
+ declare function useClaimSlot(): UseClaimSlotReturn;
346
+
347
+ type BookingState = {
348
+ booking: BookingResponse | null;
349
+ isLoading: boolean;
350
+ error: Error | null;
351
+ isSuccess: boolean;
352
+ };
353
+ type BookAppointmentParams = {
354
+ slot: TimeSlot;
355
+ client: ClientInfo;
356
+ locationSelection?: AppointmentLocation;
357
+ };
358
+ type UseBookAppointmentReturn = BookingState & {
359
+ /** Book the appointment */
360
+ book: (params: BookAppointmentParams) => Promise<BookingResponse | null>;
361
+ /** Reset the booking state */
362
+ reset: () => void;
363
+ /** Download the calendar file (.ics) */
364
+ downloadCalendar: () => void;
365
+ };
366
+ /**
367
+ * useBookAppointment - Complete the booking process
368
+ *
369
+ * Submits the booking with client information and handles
370
+ * the response including calendar download.
371
+ *
372
+ * @example
373
+ * ```tsx
374
+ * const { book, booking, isLoading, isSuccess, downloadCalendar, reset } = useBookAppointment()
375
+ *
376
+ * const handleSubmit = async (clientInfo: ClientInfo) => {
377
+ * const result = await book({
378
+ * slot: claimedSlot,
379
+ * client: clientInfo,
380
+ * locationSelection: selectedLocation
381
+ * })
382
+ *
383
+ * if (result) {
384
+ * setStep('confirmation')
385
+ * }
386
+ * }
387
+ *
388
+ * if (isSuccess) {
389
+ * return (
390
+ * <SuccessMessage>
391
+ * <button onClick={downloadCalendar}>Add to Calendar</button>
392
+ * <button onClick={reset}>Book Another</button>
393
+ * </SuccessMessage>
394
+ * )
395
+ * }
396
+ * ```
397
+ */
398
+ declare function useBookAppointment(): UseBookAppointmentReturn;
399
+
400
+ type BookingStep = 'loading' | 'date-selection' | 'time-selection' | 'booking-form' | 'confirmation' | 'error';
401
+ type UseBookingFlowOptions = {
402
+ /** Categories to filter by */
403
+ categories?: number[];
404
+ /** Callback when booking is complete */
405
+ onBookingComplete?: (booking: BookingResponse) => void;
406
+ /** Callback on any error */
407
+ onError?: (error: Error) => void;
408
+ };
409
+ type UseBookingFlowReturn = {
410
+ /** Current booking step */
411
+ step: BookingStep;
412
+ /** Go to a specific step */
413
+ goToStep: (step: BookingStep) => void;
414
+ /** Therapist information */
415
+ therapist: ReturnType<typeof useTherapist>;
416
+ /** Available slots management */
417
+ slots: ReturnType<typeof useAvailableSlots>;
418
+ /** Selected date */
419
+ selectedDate: Date | null;
420
+ /** Select a date */
421
+ selectDate: (date: Date | null) => void;
422
+ /** Slots for the selected date */
423
+ selectedDateSlots: EnrichedTimeSlot[];
424
+ /** Selected time slot */
425
+ selectedSlot: TimeSlot | null;
426
+ /** Select a time slot (claims it) */
427
+ selectSlot: (slot: TimeSlot | null) => Promise<boolean>;
428
+ /** Selected location (for not-fixed appointments) */
429
+ selectedLocation: AppointmentLocation | null;
430
+ /** Set the location selection */
431
+ setLocation: (location: AppointmentLocation) => void;
432
+ /** Claim state */
433
+ claim: ReturnType<typeof useClaimSlot>;
434
+ /** Booking state */
435
+ booking: ReturnType<typeof useBookAppointment>;
436
+ /** Submit booking with client info */
437
+ submitBooking: (client: ClientInfo) => Promise<boolean>;
438
+ /** Start over from the beginning */
439
+ reset: () => void;
440
+ /** Overall loading state */
441
+ isLoading: boolean;
442
+ /** Current error if any */
443
+ error: Error | null;
444
+ };
445
+ /**
446
+ * useBookingFlow - Complete booking flow orchestration
447
+ *
448
+ * A convenience hook that combines all booking hooks and manages
449
+ * the booking flow state. Perfect for quickly building a booking UI.
450
+ *
451
+ * @example
452
+ * ```tsx
453
+ * function BookingPage() {
454
+ * const {
455
+ * step,
456
+ * therapist,
457
+ * slots,
458
+ * selectedDate,
459
+ * selectDate,
460
+ * selectedDateSlots,
461
+ * selectSlot,
462
+ * submitBooking,
463
+ * booking,
464
+ * reset
465
+ * } = useBookingFlow()
466
+ *
467
+ * switch (step) {
468
+ * case 'loading':
469
+ * return <LoadingSpinner />
470
+ *
471
+ * case 'date-selection':
472
+ * return (
473
+ * <DatePicker
474
+ * availableDates={slots.availableDates}
475
+ * onSelect={selectDate}
476
+ * />
477
+ * )
478
+ *
479
+ * case 'time-selection':
480
+ * return (
481
+ * <TimeSlotPicker
482
+ * slots={selectedDateSlots}
483
+ * onSelect={selectSlot}
484
+ * />
485
+ * )
486
+ *
487
+ * case 'booking-form':
488
+ * return (
489
+ * <BookingForm onSubmit={submitBooking} />
490
+ * )
491
+ *
492
+ * case 'confirmation':
493
+ * return (
494
+ * <Confirmation
495
+ * booking={booking.booking}
496
+ * onReset={reset}
497
+ * />
498
+ * )
499
+ * }
500
+ * }
501
+ * ```
502
+ */
503
+ declare function useBookingFlow(options?: UseBookingFlowOptions): UseBookingFlowReturn;
504
+
505
+ export { CustomBookingExample, TebutoBookingWidget, TebutoProvider, useAvailableSlots, useBookAppointment, useBookingFlow, useClaimSlot, useTebutoContext, useTherapist };
506
+ export type { Address, AppointmentLocation, AsyncState, BookingRequest, BookingResponse, ClaimResponse, ClientInfo, EnrichedTimeSlot, EventCategory, PaymentConfiguration, SlotsByDate, TebutoBookingWidgetConfiguration, TebutoWidgetTheme, Therapist, TherapistReference, TimeSlot };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tebuto/react-booking-widget",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "description": "React Component for the Tebuto Booking Widget",
5
5
  "author": "Tebuto GmbH",
6
6
  "homepage": "https://tebuto.de",
@@ -9,6 +9,8 @@
9
9
  "module": "dist/esm/index.js",
10
10
  "types": "dist/types.d.ts",
11
11
  "scripts": {
12
+ "dev": "storybook dev -p 6006",
13
+ "build:storybook": "storybook build",
12
14
  "test": "jest",
13
15
  "build": "rollup -c --bundleConfigAsCjs",
14
16
  "prepare": "husky",
@@ -21,30 +23,39 @@
21
23
  "react-dom": "^19.0.0"
22
24
  },
23
25
  "devDependencies": {
24
- "@babel/core": "7.27.4",
25
- "@babel/preset-env": "7.27.2",
26
- "@babel/preset-react": "7.27.1",
27
- "@babel/preset-typescript": "7.27.1",
28
- "@biomejs/biome": "1.9.4",
29
- "@rollup/plugin-commonjs": "28.0.3",
30
- "@rollup/plugin-node-resolve": "16.0.1",
26
+ "@babel/core": "7.28.5",
27
+ "@babel/preset-env": "7.28.5",
28
+ "@babel/preset-react": "7.28.5",
29
+ "@babel/preset-typescript": "7.28.5",
30
+ "@biomejs/biome": "2.3.10",
31
+ "@rollup/plugin-commonjs": "29.0.0",
32
+ "@rollup/plugin-node-resolve": "16.0.3",
31
33
  "@rollup/plugin-terser": "0.4.4",
32
- "@rollup/plugin-typescript": "12.1.2",
33
- "@testing-library/react": "16.3.0",
34
- "@types/jest": "29.5.14",
35
- "@types/react": "19.1.6",
36
- "babel-jest": "29.7.0",
34
+ "@rollup/plugin-typescript": "12.3.0",
35
+ "@storybook/react": "^10.1.10",
36
+ "@storybook/react-vite": "^10.1.10",
37
+ "@testing-library/react": "16.3.1",
38
+ "@types/jest": "30.0.0",
39
+ "@types/react": "19.2.7",
40
+ "@vitejs/plugin-react": "^5.1.2",
41
+ "babel-jest": "30.2.0",
37
42
  "husky": "9.1.7",
38
- "jest": "29.7.0",
39
- "jest-environment-jsdom": "29.7.0",
40
- "rollup": "4.41.1",
41
- "rollup-plugin-dts": "6.2.1",
43
+ "jest": "30.2.0",
44
+ "jest-environment-jsdom": "30.2.0",
45
+ "msw": "^2.12.6",
46
+ "rollup": "4.54.0",
47
+ "rollup-plugin-dts": "6.3.0",
42
48
  "rollup-plugin-peer-deps-external": "2.2.4",
49
+ "storybook": "^10.1.10",
43
50
  "tslib": "2.8.1",
44
- "typescript": "5.8.3"
51
+ "typescript": "5.9.3",
52
+ "vite": "^7.3.0"
45
53
  },
46
54
  "jest": {
47
- "testEnvironment": "jsdom"
55
+ "testEnvironment": "jsdom",
56
+ "setupFilesAfterEnv": [
57
+ "<rootDir>/src/setupTests.ts"
58
+ ]
48
59
  },
49
60
  "babel": {
50
61
  "presets": [
@@ -66,10 +77,25 @@
66
77
  "url": "https://github.com/tebuto/react-booking-widget/issues"
67
78
  },
68
79
  "engines": {
69
- "node": ">=22"
80
+ "node": ">=24"
70
81
  },
71
- "keywords": ["appointment", "booking", "calendar", "react", "tebuto", "widget", "online", "therapist", "psychologist"],
82
+ "keywords": [
83
+ "appointment",
84
+ "booking",
85
+ "calendar",
86
+ "react",
87
+ "tebuto",
88
+ "widget",
89
+ "online",
90
+ "therapist",
91
+ "psychologist"
92
+ ],
72
93
  "publishConfig": {
73
94
  "access": "public"
95
+ },
96
+ "msw": {
97
+ "workerDirectory": [
98
+ "public"
99
+ ]
74
100
  }
75
101
  }
@@ -0,0 +1,2 @@
1
+ onlyBuiltDependencies:
2
+ - unrs-resolver
package/vite.config.ts ADDED
@@ -0,0 +1,6 @@
1
+ import react from '@vitejs/plugin-react'
2
+ import { defineConfig } from 'vite'
3
+
4
+ export default defineConfig({
5
+ plugins: [react()]
6
+ })