@wix/headless-bookings 1.0.0 → 1.0.2

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.
Files changed (37) hide show
  1. package/cjs/dist/__mocks__/booking/booking-mocks.d.ts +1 -1
  2. package/cjs/dist/__mocks__/services/services-mocks.d.ts +23 -0
  3. package/cjs/dist/__mocks__/services/services-mocks.js +65 -1
  4. package/cjs/dist/api/query-services/index.js +2 -2
  5. package/cjs/dist/react/core/booking/Booking.d.ts +1 -1
  6. package/cjs/dist/react/core/booking/Booking.js +18 -5
  7. package/cjs/dist/react/core/location/Location.d.ts +1 -1
  8. package/cjs/dist/react/core/location/Location.js +5 -5
  9. package/cjs/dist/react/core/service/Service.d.ts +10 -1
  10. package/cjs/dist/react/core/service/Service.js +5 -4
  11. package/cjs/dist/react/service/Service.d.ts +68 -1
  12. package/cjs/dist/react/service/Service.js +81 -1
  13. package/cjs/dist/services/booking/booking.d.ts +1 -1
  14. package/cjs/dist/services/location-list/location-list.def.d.ts +1 -1
  15. package/cjs/dist/services/location-list/location-list.def.js +1 -1
  16. package/cjs/dist/services/location-list/location-list.js +7 -7
  17. package/cjs/dist/services/payment/payment.def.d.ts +6 -0
  18. package/cjs/dist/services/payment/payment.js +56 -5
  19. package/dist/__mocks__/booking/booking-mocks.d.ts +1 -1
  20. package/dist/__mocks__/services/services-mocks.d.ts +23 -0
  21. package/dist/__mocks__/services/services-mocks.js +64 -0
  22. package/dist/api/query-services/index.js +1 -1
  23. package/dist/react/core/booking/Booking.d.ts +1 -1
  24. package/dist/react/core/booking/Booking.js +18 -5
  25. package/dist/react/core/location/Location.d.ts +1 -1
  26. package/dist/react/core/location/Location.js +1 -1
  27. package/dist/react/core/service/Service.d.ts +10 -1
  28. package/dist/react/core/service/Service.js +5 -4
  29. package/dist/react/service/Service.d.ts +68 -1
  30. package/dist/react/service/Service.js +81 -1
  31. package/dist/services/booking/booking.d.ts +1 -1
  32. package/dist/services/location-list/location-list.def.d.ts +1 -1
  33. package/dist/services/location-list/location-list.def.js +1 -1
  34. package/dist/services/location-list/location-list.js +1 -1
  35. package/dist/services/payment/payment.def.d.ts +6 -0
  36. package/dist/services/payment/payment.js +57 -6
  37. package/package.json +5 -3
@@ -10,6 +10,7 @@ exports.loadPaymentConfig = loadPaymentConfig;
10
10
  const services_definitions_1 = require("@wix/services-definitions");
11
11
  const signals_1 = require("@wix/services-definitions/core-services/signals");
12
12
  const auto_sdk_ecom_totals_calculator_1 = require("@wix/auto_sdk_ecom_totals-calculator");
13
+ const auto_sdk_bookings_pricing_1 = require("@wix/auto_sdk_bookings_pricing");
13
14
  const booking_js_1 = require("../booking/booking.js");
14
15
  const auto_sdk_bookings_services_1 = require("@wix/auto_sdk_bookings_services");
15
16
  const payment_def_js_1 = require("./payment.def.js");
@@ -91,13 +92,56 @@ function buildLineItem(service, totalParticipants, lineItemId) {
91
92
  paymentOption,
92
93
  };
93
94
  }
95
+ // durationRange is an @internal field on AvailabilityConstraints (bookings services SDK v1.0.226+).
96
+ // It signals that the service uses flexible duration pricing (hourly/daily rentals),
97
+ // where the correct price must be fetched via previewPrice() rather than the fixed unit price.
98
+ function getDurationRange(service) {
99
+ return service.schedule?.availabilityConstraints?.durationRange;
100
+ }
101
+ async function getPreviewedPrice(service, localStartDate, localEndDate, timeZone) {
102
+ if (!service._id)
103
+ return null;
104
+ const lineItemInput = {
105
+ serviceId: service._id,
106
+ localStartDate,
107
+ localEndDate,
108
+ ...(timeZone ? { timeZone } : {}),
109
+ };
110
+ const result = await (0, auto_sdk_bookings_pricing_1.previewPrice)([lineItemInput]);
111
+ return result.priceInfo?.calculatedPrice ?? null;
112
+ }
113
+ async function buildLineItemWithPreview(item, totalParticipants) {
114
+ if (item.localStartDate &&
115
+ item.localEndDate &&
116
+ getDurationRange(item.service)) {
117
+ const previewedPrice = await getPreviewedPrice(item.service, item.localStartDate, item.localEndDate, item.timeZone).catch(() => null);
118
+ if (previewedPrice != null) {
119
+ const depositValue = item.service.payment?.fixed?.deposit?.value;
120
+ const hasDeposit = !!depositValue && Number(depositValue) > 0;
121
+ const deposit = hasDeposit
122
+ ? Number(depositValue) * totalParticipants
123
+ : undefined;
124
+ return {
125
+ _id: item.lineItemId,
126
+ catalogReference: {
127
+ catalogItemId: item.service._id || '',
128
+ appId: constants_js_1.BOOKING_APP_ID,
129
+ },
130
+ price: String(previewedPrice),
131
+ ...(deposit ? { depositAmount: String(deposit) } : {}),
132
+ quantity: 1,
133
+ paymentOption: getPaymentOption(item.service, hasDeposit),
134
+ };
135
+ }
136
+ }
137
+ return buildLineItem(item.service, totalParticipants, item.lineItemId);
138
+ }
94
139
  async function calculatePaymentDetails(pricingServiceSelections, totalParticipants) {
95
- // Ensure all booking services have a lineItemId
96
140
  const data = pricingServiceSelections.map((selection) => ({
97
141
  ...selection,
98
142
  lineItemId: selection.lineItemId || (0, guid_js_1.generateLineItemId)(),
99
143
  }));
100
- const lineItems = data.map((item) => buildLineItem(item.service, totalParticipants, item.lineItemId));
144
+ const lineItems = await Promise.all(data.map((item) => buildLineItemWithPreview(item, totalParticipants)));
101
145
  const response = await (0, auto_sdk_ecom_totals_calculator_1.calculateTotals)({
102
146
  lineItems,
103
147
  });
@@ -168,22 +212,29 @@ exports.PaymentService = services_definitions_1.implementService.withConfig()(pa
168
212
  }
169
213
  // Mode 3: Reactive - observe BookingService signals (client-side only)
170
214
  else if (typeof window !== 'undefined' && bookingService) {
171
- // Computed signal that only changes when services change (not participants)
215
+ // Computed signal that changes when services or selected time slots change (not participants)
172
216
  const servicesKey = signalsService.computed(() => {
173
217
  const selections = bookingService?.serviceSelections.get() || [];
174
- return selections.map((s) => s.service._id).join(',');
218
+ const timezone = bookingService?.timezone.get();
219
+ return (selections
220
+ .map((s) => `${s.service._id}:${s.timeSlot?.startDate ?? ''}:${s.timeSlot?.endDate ?? ''}`)
221
+ .join(',') + `:${timezone ?? ''}`);
175
222
  });
176
223
  signalsService.effect(() => {
177
- // Subscribe to servicesKey to trigger effect only when services change
224
+ // Subscribe to servicesKey to trigger effect when service or time slot changes
178
225
  servicesKey.get();
179
226
  // Read current selections without subscribing (peek)
180
227
  const selections = bookingService?.serviceSelections.peek() || [];
228
+ const timezone = bookingService?.timezone.peek();
181
229
  // For single service mode, take the first selection
182
230
  const firstSelection = selections[0];
183
231
  const numParticipants = firstSelection?.totalParticipants || 1;
184
232
  const pricingServiceSelections = selections.map((selection) => ({
185
233
  service: selection.service,
186
234
  lineItemId: selection.instanceId,
235
+ localStartDate: selection.timeSlot?.startDate,
236
+ localEndDate: selection.timeSlot?.endDate,
237
+ timeZone: timezone,
187
238
  }));
188
239
  if (pricingServiceSelections.length) {
189
240
  serviceOptionsSignal.set(deriveServiceOptions(pricingServiceSelections));
@@ -1,5 +1,5 @@
1
1
  import type { FormValues } from '@wix/forms/components';
2
- import type { Location } from '@wix/auto_sdk_bookings_services';
2
+ import type { V2Location as Location } from '@wix/auto_sdk_bookings_services';
3
3
  import type { ServiceSelection, ServiceSelectionTimeSlot, ExtendedResource } from '../../services/booking/booking.js';
4
4
  export declare function createMockResource(overrides?: Partial<ExtendedResource>): ExtendedResource;
5
5
  export declare function createMockLocation(overrides?: Partial<Location>): Location;
@@ -35,6 +35,29 @@ export declare const mockServiceCourse: Service;
35
35
  * Mock service without optional fields (minimal)
36
36
  */
37
37
  export declare const mockServiceMinimal: Service;
38
+ /**
39
+ * Mock service driving the fixed-duration path: a single value in
40
+ * `sessionDurations`, no `durationRange`. Drives `Service.DurationInMinutes`;
41
+ * `Service.DurationRange` renders nothing for this shape.
42
+ *
43
+ * `mockService` already carries this shape — this is a renamed copy for
44
+ * tests/examples that want to name the case they're exercising.
45
+ */
46
+ export declare const mockServiceFixedDuration: Service;
47
+ /**
48
+ * Mock service driving the HOUR-unit DurationRange path. Mirrors the server
49
+ * payload shape: empty `sessionDurations`, range pre-computed at
50
+ * `availabilityConstraints.durationRange`. `durationRange` is not yet in the
51
+ * SDK's `AvailabilityConstraints` typing (runtime is ahead of types), so the
52
+ * service object is cast — matches the read-side cast in
53
+ * `core/service/Service.tsx`.
54
+ */
55
+ export declare const mockServiceHourRange: Service;
56
+ /**
57
+ * Mock service driving the DAY-unit DurationRange path. Same server shape as
58
+ * {@link mockServiceHourRange}.
59
+ */
60
+ export declare const mockServiceDayRange: Service;
38
61
  /**
39
62
  * Array of mock services for list tests
40
63
  */
@@ -170,6 +170,70 @@ export const mockServiceMinimal = {
170
170
  name: 'Minimal Service',
171
171
  description: '',
172
172
  };
173
+ /**
174
+ * Mock service driving the fixed-duration path: a single value in
175
+ * `sessionDurations`, no `durationRange`. Drives `Service.DurationInMinutes`;
176
+ * `Service.DurationRange` renders nothing for this shape.
177
+ *
178
+ * `mockService` already carries this shape — this is a renamed copy for
179
+ * tests/examples that want to name the case they're exercising.
180
+ */
181
+ export const mockServiceFixedDuration = {
182
+ ...mockService,
183
+ _id: 'test-service-fixed-duration',
184
+ name: 'Fixed Duration Service',
185
+ schedule: {
186
+ availabilityConstraints: {
187
+ sessionDurations: [60],
188
+ },
189
+ },
190
+ };
191
+ /**
192
+ * Mock service driving the HOUR-unit DurationRange path. Mirrors the server
193
+ * payload shape: empty `sessionDurations`, range pre-computed at
194
+ * `availabilityConstraints.durationRange`. `durationRange` is not yet in the
195
+ * SDK's `AvailabilityConstraints` typing (runtime is ahead of types), so the
196
+ * service object is cast — matches the read-side cast in
197
+ * `core/service/Service.tsx`.
198
+ */
199
+ export const mockServiceHourRange = {
200
+ ...mockService,
201
+ _id: 'test-service-hour-range',
202
+ name: 'Hourly Range Service',
203
+ schedule: {
204
+ availabilityConstraints: {
205
+ sessionDurations: [],
206
+ durationRange: {
207
+ unitType: 'HOUR',
208
+ hourOptions: {
209
+ minDurationInMinutes: 120,
210
+ maxDurationInMinutes: 540,
211
+ },
212
+ },
213
+ },
214
+ },
215
+ };
216
+ /**
217
+ * Mock service driving the DAY-unit DurationRange path. Same server shape as
218
+ * {@link mockServiceHourRange}.
219
+ */
220
+ export const mockServiceDayRange = {
221
+ ...mockService,
222
+ _id: 'test-service-day-range',
223
+ name: 'Daily Range Service',
224
+ schedule: {
225
+ availabilityConstraints: {
226
+ sessionDurations: [],
227
+ durationRange: {
228
+ unitType: 'DAY',
229
+ dayOptions: {
230
+ minDurationInDays: 2,
231
+ maxDurationInDays: 5,
232
+ },
233
+ },
234
+ },
235
+ },
236
+ };
173
237
  /**
174
238
  * Array of mock services for list tests
175
239
  */
@@ -2,7 +2,7 @@
2
2
  * Query Services API
3
3
  * Fetches single or multiple services with pagination and sorting
4
4
  */
5
- import { queryServices as queryServicesApi, LocationType, } from '@wix/auto_sdk_bookings_services';
5
+ import { queryServices as queryServicesApi, LocationTypeEnumLocationType as LocationType, } from '@wix/auto_sdk_bookings_services';
6
6
  import { BOOKING_APP_ID } from '../../services/constants.js';
7
7
  /**
8
8
  * Synthetic location ID constants for non-business locations
@@ -6,7 +6,7 @@ import React from 'react';
6
6
  import { type BookingServiceConfig, type ServiceSelection, type BookingServiceActions } from '../../../services/booking/booking.js';
7
7
  import type { FormValues } from '@wix/forms/components';
8
8
  import type { TimeSlot, Resource } from '@wix/auto_sdk_bookings_availability-time-slots';
9
- import { type Service, type Location as ServiceLocationType } from '@wix/auto_sdk_bookings_services';
9
+ import { type Service, type V2Location as ServiceLocationType } from '@wix/auto_sdk_bookings_services';
10
10
  import type { PricingServiceSelection } from '../../../services/payment/payment.js';
11
11
  /**
12
12
  * Props for Booking.Root component
@@ -7,7 +7,7 @@ import React from 'react';
7
7
  import { WixServices, useService } from '@wix/services-manager-react';
8
8
  import { createServicesMap } from '@wix/services-manager';
9
9
  import { BookingServiceDefinition, BookingService, } from '../../../services/booking/booking.js';
10
- import { LocationType, } from '@wix/auto_sdk_bookings_services';
10
+ import { LocationTypeEnumLocationType as LocationType, } from '@wix/auto_sdk_bookings_services';
11
11
  /**
12
12
  * Root component that provides booking context to the entire app.
13
13
  *
@@ -53,7 +53,16 @@ export function Root(props) {
53
53
  function mapTimeSlotLocationToServiceLocation(timeSlotLocation) {
54
54
  // TimeSlot location has flat name/formattedAddress at top level
55
55
  // Convert to nested business/custom structure that CoreLocation expects
56
- const locationType = timeSlotLocation.locationType;
56
+ const isCustomer = timeSlotLocation.locationType === LocationType.CUSTOMER;
57
+ const isCustom = timeSlotLocation.locationType === LocationType.CUSTOM;
58
+ // The two SDKs share BUSINESS/CUSTOM/CUSTOMER but the time-slots enum also
59
+ // exposes UNKNOWN_LOCATION_TYPE, which V2Location.type does not accept.
60
+ // Default to BUSINESS for any unrecognized value.
61
+ const type = isCustomer
62
+ ? LocationType.CUSTOMER
63
+ : isCustom
64
+ ? LocationType.CUSTOM
65
+ : LocationType.BUSINESS;
57
66
  const location = {
58
67
  _id: timeSlotLocation._id ?? undefined,
59
68
  name: timeSlotLocation.name ?? undefined,
@@ -65,10 +74,10 @@ function mapTimeSlotLocationToServiceLocation(timeSlotLocation) {
65
74
  };
66
75
  return {
67
76
  _id: timeSlotLocation._id ?? undefined,
68
- type: locationType,
69
- ...(locationType === LocationType.CUSTOMER
77
+ type,
78
+ ...(isCustomer
70
79
  ? null
71
- : locationType === LocationType.CUSTOM
80
+ : isCustom
72
81
  ? { custom: location }
73
82
  : { business: location }),
74
83
  };
@@ -275,9 +284,13 @@ export function PaymentData(props) {
275
284
  const { children } = props;
276
285
  const bookingService = useService(BookingServiceDefinition);
277
286
  const serviceSelections = bookingService.serviceSelections.get();
287
+ const timezone = bookingService.timezone.get();
278
288
  const pricingServiceSelections = serviceSelections.map((selection) => ({
279
289
  service: selection.service,
280
290
  lineItemId: selection.instanceId,
291
+ localStartDate: selection.timeSlot?.startDate,
292
+ localEndDate: selection.timeSlot?.endDate,
293
+ timeZone: timezone,
281
294
  }));
282
295
  return children({ pricingServiceSelections });
283
296
  }
@@ -6,7 +6,7 @@
6
6
  * @module Core/Location
7
7
  */
8
8
  import React from 'react';
9
- import { LocationType } from '@wix/auto_sdk_bookings_services';
9
+ import { LocationTypeEnumLocationType as LocationType } from '@wix/auto_sdk_bookings_services';
10
10
  import type { AddressData as AddressDataType } from '@wix/headless-components/react';
11
11
  import type { Location } from '../../../services/location-list/location-list.def.js';
12
12
  /**
@@ -8,7 +8,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
8
8
  */
9
9
  import React, { createContext, useContext } from 'react';
10
10
  import { useService } from '@wix/services-manager-react';
11
- import { LocationType } from '@wix/auto_sdk_bookings_services';
11
+ import { LocationTypeEnumLocationType as LocationType } from '@wix/auto_sdk_bookings_services';
12
12
  import { BookingServiceDefinition } from '../../../services/booking/booking.js';
13
13
  import { LocationListServiceDefinition } from '../../../services/location-list/location-list.def.js';
14
14
  // ==========================================
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import React from 'react';
6
6
  import { type ServiceServiceConfig } from '../../../services/service/service.js';
7
- import type { Service, Payment, BookingPolicy, Schedule, StaffMember } from '@wix/auto_sdk_bookings_services';
7
+ import type { Service, Payment, BookingPolicy, Schedule, StaffMember, DurationRange } from '@wix/auto_sdk_bookings_services';
8
8
  import type { MoneyData } from '@wix/headless-components/react';
9
9
  /**
10
10
  * Props for Service.Root component
@@ -50,6 +50,15 @@ export interface InfoProps {
50
50
  description: string;
51
51
  tagline?: string;
52
52
  duration?: number;
53
+ /**
54
+ * Typed range of supported session durations, derived from
55
+ * `service.schedule.availabilityConstraints.sessionDurations`. Set when the
56
+ * service supports two or more distinct durations; otherwise undefined and
57
+ * the single-value `duration` field above should be used. Consumers branch
58
+ * on `range.unitType` (the SDK `UnitType` enum) and read the matching
59
+ * `range.hourOptions` / `range.dayOptions` config.
60
+ */
61
+ durationRange?: DurationRange;
53
62
  type?: string;
54
63
  category?: {
55
64
  name?: string;
@@ -87,9 +87,9 @@ export function Info(props) {
87
87
  currency,
88
88
  };
89
89
  }
90
- // Extract duration from schedule.availabilityConstraints.sessionDurations
91
- // Use the first duration if multiple are available
92
- const duration = service.schedule?.availabilityConstraints?.sessionDurations?.[0];
90
+ const constraints = service.schedule?.availabilityConstraints;
91
+ const duration = constraints?.sessionDurations?.[0];
92
+ const durationRange = constraints?.durationRange;
93
93
  // Get staff members from staffMemberDetails.staffMembers
94
94
  // (available when using conditionalFields: ['STAFF_MEMBER_DETAILS'] in queryServices)
95
95
  const staff = service.staffMembers || [];
@@ -119,7 +119,8 @@ export function Info(props) {
119
119
  name: service.name || '',
120
120
  description: service.description || '',
121
121
  tagline: service.tagLine || undefined,
122
- duration: duration,
122
+ duration,
123
+ durationRange,
123
124
  type: service.type,
124
125
  category: service.category
125
126
  ? {
@@ -5,7 +5,7 @@
5
5
  import React from 'react';
6
6
  import { AsChildChildren } from '@wix/headless-utils/react';
7
7
  import { MoneyData } from '@wix/headless-components/react';
8
- import { type Service, type Payment } from '@wix/auto_sdk_bookings_services';
8
+ import { type Service, type Payment, type DurationRange as DurationRangeData } from '@wix/auto_sdk_bookings_services';
9
9
  import * as ServiceMediaModule from './ServiceMedia.js';
10
10
  export declare const TestIds: {
11
11
  readonly serviceRoot: "service-root";
@@ -14,6 +14,7 @@ export declare const TestIds: {
14
14
  readonly servicePrice: "service-price";
15
15
  readonly serviceTagline: "service-tagline";
16
16
  readonly serviceDurationInMinutes: "service-duration-in-minutes";
17
+ readonly serviceDurationRange: "service-duration-range";
17
18
  readonly serviceType: "service-type";
18
19
  readonly serviceCategory: "service-category";
19
20
  readonly serviceLocations: "service-locations";
@@ -340,6 +341,72 @@ export interface DurationInMinutesProps extends Omit<React.HTMLAttributes<HTMLEl
340
341
  * ```
341
342
  */
342
343
  export declare const DurationInMinutes: React.ForwardRefExoticComponent<DurationInMinutesProps & React.RefAttributes<HTMLElement>>;
344
+ /**
345
+ * Props for Service DurationRange component
346
+ */
347
+ export interface DurationRangeProps extends Omit<React.HTMLAttributes<HTMLElement>, 'children'> {
348
+ asChild?: boolean;
349
+ children?: AsChildChildren<{
350
+ range: DurationRangeData;
351
+ }>;
352
+ }
353
+ /**
354
+ * Provides the service's supported session duration range as raw SDK data.
355
+ * Renders nothing when the service has fewer than two distinct supported
356
+ * durations — use {@link DurationInMinutes} for that case.
357
+ *
358
+ * The component is intentionally UI-agnostic: it does not pick formatting,
359
+ * units, or labels for you. The default render outputs only the raw
360
+ * `min - max` numbers (no unit text). Consumers wanting unit-aware UI use
361
+ * `asChild` with a custom render and narrow on the SDK `range` payload
362
+ * (`range.hourOptions` / `range.dayOptions` / `range.unitType`, or any
363
+ * other unit configuration the SDK adds in the future).
364
+ *
365
+ * @component
366
+ * @example
367
+ * ```tsx
368
+ * // Default usage - displays raw min/max, e.g. "120 - 540" or "2 - 5"
369
+ * <Service.DurationRange className="text-base text-secondary-foreground" />
370
+ * ```
371
+ *
372
+ * @example
373
+ * ```tsx
374
+ * // asChild with primitive element
375
+ * <Service.DurationRange asChild>
376
+ * <span className="font-semibold" />
377
+ * </Service.DurationRange>
378
+ * ```
379
+ *
380
+ * @example
381
+ * ```tsx
382
+ * // asChild with custom component - narrow on the SDK range payload to
383
+ * // decide unit-specific formatting.
384
+ * <Service.DurationRange asChild>
385
+ * {React.forwardRef(({ range, ...props }, ref) => {
386
+ * if (range.hourOptions) {
387
+ * const { minDurationInMinutes: min, maxDurationInMinutes: max } =
388
+ * range.hourOptions;
389
+ * return (
390
+ * <span ref={ref} {...props}>
391
+ * {min}-{max} min
392
+ * </span>
393
+ * );
394
+ * }
395
+ * if (range.dayOptions) {
396
+ * const { minDurationInDays: min, maxDurationInDays: max } =
397
+ * range.dayOptions;
398
+ * return (
399
+ * <span ref={ref} {...props}>
400
+ * {min}-{max} days
401
+ * </span>
402
+ * );
403
+ * }
404
+ * return <span ref={ref} {...props} />;
405
+ * })}
406
+ * </Service.DurationRange>
407
+ * ```
408
+ */
409
+ export declare const DurationRange: React.ForwardRefExoticComponent<DurationRangeProps & React.RefAttributes<HTMLElement>>;
343
410
  /**
344
411
  * Props for Service Type component
345
412
  */
@@ -9,7 +9,7 @@ import * as CoreServiceMediaGallery from '../core/service/ServiceMediaGallery.js
9
9
  import { AsChildSlot } from '@wix/headless-utils/react';
10
10
  import { Money } from '@wix/headless-components/react';
11
11
  import { MediaGallery } from '@wix/headless-media/react';
12
- import { RateType, } from '@wix/auto_sdk_bookings_services';
12
+ import { RateType, UnitType, } from '@wix/auto_sdk_bookings_services';
13
13
  import * as LocationList from '../location/LocationList.js';
14
14
  // Note: Location components are used inside LocationList.LocationRepeater
15
15
  // Users should import { Location } from '@wix/headless-bookings/react' for Location.Name, Location.Address, etc.
@@ -24,6 +24,7 @@ export const TestIds = {
24
24
  servicePrice: 'service-price',
25
25
  serviceTagline: 'service-tagline',
26
26
  serviceDurationInMinutes: 'service-duration-in-minutes',
27
+ serviceDurationRange: 'service-duration-range',
27
28
  serviceType: 'service-type',
28
29
  serviceCategory: 'service-category',
29
30
  serviceLocations: 'service-locations',
@@ -302,6 +303,85 @@ export const DurationInMinutes = React.forwardRef((props, ref) => {
302
303
  } }));
303
304
  });
304
305
  DurationInMinutes.displayName = 'Service.DurationInMinutes';
306
+ /**
307
+ * Provides the service's supported session duration range as raw SDK data.
308
+ * Renders nothing when the service has fewer than two distinct supported
309
+ * durations — use {@link DurationInMinutes} for that case.
310
+ *
311
+ * The component is intentionally UI-agnostic: it does not pick formatting,
312
+ * units, or labels for you. The default render outputs only the raw
313
+ * `min - max` numbers (no unit text). Consumers wanting unit-aware UI use
314
+ * `asChild` with a custom render and narrow on the SDK `range` payload
315
+ * (`range.hourOptions` / `range.dayOptions` / `range.unitType`, or any
316
+ * other unit configuration the SDK adds in the future).
317
+ *
318
+ * @component
319
+ * @example
320
+ * ```tsx
321
+ * // Default usage - displays raw min/max, e.g. "120 - 540" or "2 - 5"
322
+ * <Service.DurationRange className="text-base text-secondary-foreground" />
323
+ * ```
324
+ *
325
+ * @example
326
+ * ```tsx
327
+ * // asChild with primitive element
328
+ * <Service.DurationRange asChild>
329
+ * <span className="font-semibold" />
330
+ * </Service.DurationRange>
331
+ * ```
332
+ *
333
+ * @example
334
+ * ```tsx
335
+ * // asChild with custom component - narrow on the SDK range payload to
336
+ * // decide unit-specific formatting.
337
+ * <Service.DurationRange asChild>
338
+ * {React.forwardRef(({ range, ...props }, ref) => {
339
+ * if (range.hourOptions) {
340
+ * const { minDurationInMinutes: min, maxDurationInMinutes: max } =
341
+ * range.hourOptions;
342
+ * return (
343
+ * <span ref={ref} {...props}>
344
+ * {min}-{max} min
345
+ * </span>
346
+ * );
347
+ * }
348
+ * if (range.dayOptions) {
349
+ * const { minDurationInDays: min, maxDurationInDays: max } =
350
+ * range.dayOptions;
351
+ * return (
352
+ * <span ref={ref} {...props}>
353
+ * {min}-{max} days
354
+ * </span>
355
+ * );
356
+ * }
357
+ * return <span ref={ref} {...props} />;
358
+ * })}
359
+ * </Service.DurationRange>
360
+ * ```
361
+ */
362
+ export const DurationRange = React.forwardRef((props, ref) => {
363
+ const { asChild, children, ...rest } = props;
364
+ return (_jsx(CoreService.Info, { children: ({ durationRange }) => {
365
+ if (!durationRange)
366
+ return null;
367
+ // When `unitType` is missing or unrecognised, no default content is
368
+ // rendered — consumers can still read the raw `range` via asChild.
369
+ const { unitType, hourOptions, dayOptions } = durationRange;
370
+ let min;
371
+ let max;
372
+ if (unitType === UnitType.HOUR) {
373
+ min = hourOptions?.minDurationInMinutes;
374
+ max = hourOptions?.maxDurationInMinutes;
375
+ }
376
+ else if (unitType === UnitType.DAY) {
377
+ min = dayOptions?.minDurationInDays;
378
+ max = dayOptions?.maxDurationInDays;
379
+ }
380
+ const hasRange = min != null && max != null;
381
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, ...rest, "data-testid": TestIds.serviceDurationRange, customElement: children, customElementProps: { range: durationRange }, children: hasRange ? _jsx("span", { children: `${min} - ${max}` }) : null }));
382
+ } }));
383
+ });
384
+ DurationRange.displayName = 'Service.DurationRange';
305
385
  /**
306
386
  * Displays the service type (APPOINTMENT, CLASS, COURSE, etc.) with customizable rendering.
307
387
  *
@@ -4,7 +4,7 @@
4
4
  * Persists across page navigation in SPA until page refresh
5
5
  */
6
6
  import { type Signal } from '@wix/services-definitions/core-services/signals';
7
- import type { Service, Location as ServiceLocationType } from '@wix/auto_sdk_bookings_services';
7
+ import type { Service, V2Location as ServiceLocationType } from '@wix/auto_sdk_bookings_services';
8
8
  import type { Resource, EventInfo, Location as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
9
9
  import type { FormValues } from '@wix/forms/components';
10
10
  import type { BookResult } from './book-action/types.js';
@@ -5,7 +5,7 @@
5
5
  * @module Services/LocationList/Definition
6
6
  */
7
7
  import type { Signal, ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
8
- import { LocationType, type Service } from '@wix/auto_sdk_bookings_services';
8
+ import { LocationTypeEnumLocationType as LocationType, type Service } from '@wix/auto_sdk_bookings_services';
9
9
  /**
10
10
  * Location type from the SDK
11
11
  * Represents a service location (BUSINESS, CUSTOM, or CUSTOMER type)
@@ -5,7 +5,7 @@
5
5
  * @module Services/LocationList/Definition
6
6
  */
7
7
  import { defineService } from '@wix/services-definitions';
8
- import { LocationType } from '@wix/auto_sdk_bookings_services';
8
+ import { LocationTypeEnumLocationType as LocationType, } from '@wix/auto_sdk_bookings_services';
9
9
  // ============================================================================
10
10
  // Service Definition
11
11
  // ============================================================================
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { implementService } from '@wix/services-definitions';
8
8
  import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
9
- import { LocationType } from '@wix/auto_sdk_bookings_services';
9
+ import { LocationTypeEnumLocationType as LocationType } from '@wix/auto_sdk_bookings_services';
10
10
  import { LocationListServiceDefinition, } from './location-list.def.js';
11
11
  import { BookingServiceDefinition } from '../booking/booking.js';
12
12
  import { queryLocations } from '../../api/query-locations/index.js';
@@ -48,6 +48,12 @@ export interface PricingServiceSelection {
48
48
  service: Service;
49
49
  /** Optional line item ID. If not provided, one will be generated. */
50
50
  lineItemId?: string;
51
+ /** Local start date of the selected slot in ISO-8601 format (e.g. "2024-01-30T13:30:00"). */
52
+ localStartDate?: string;
53
+ /** Local end date of the selected slot in ISO-8601 format (e.g. "2024-01-30T14:30:00"). */
54
+ localEndDate?: string;
55
+ /** IANA time zone identifier for the selected slot (e.g. "America/New_York"). */
56
+ timeZone?: string;
51
57
  }
52
58
  /**
53
59
  * Public config for PaymentService (SSR mode only):