@wix/headless-bookings 0.0.21 → 0.0.23

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 (35) hide show
  1. package/cjs/dist/api/fetch-availability/index.d.ts +4 -3
  2. package/cjs/dist/api/fetch-availability/index.js +31 -14
  3. package/cjs/dist/react/booking/Booking.d.ts +2 -1
  4. package/cjs/dist/react/booking/Booking.js +1 -1
  5. package/cjs/dist/react/core/booking/Booking.d.ts +3 -2
  6. package/cjs/dist/react/core/booking/Booking.js +5 -2
  7. package/cjs/dist/react/core/time-slot-list/TimeSlot.d.ts +4 -4
  8. package/cjs/dist/react/core/time-slot-list/TimeSlot.js +7 -5
  9. package/cjs/dist/react/core/time-slot-list/TimeSlotList.js +3 -2
  10. package/cjs/dist/react/time-slot-list/TimeSlot.d.ts +6 -6
  11. package/cjs/dist/react/time-slot-list/TimeSlot.js +6 -6
  12. package/cjs/dist/react/time-slot-list/TimeSlotList.d.ts +4 -4
  13. package/cjs/dist/services/booking/booking.d.ts +5 -0
  14. package/cjs/dist/services/booking/booking.js +17 -1
  15. package/cjs/dist/services/service/service.js +4 -1
  16. package/cjs/dist/services/time-slot-list/time-slot-list.def.d.ts +12 -4
  17. package/cjs/dist/services/time-slot-list/time-slot-list.js +21 -30
  18. package/dist/api/fetch-availability/index.d.ts +4 -3
  19. package/dist/api/fetch-availability/index.js +31 -14
  20. package/dist/react/booking/Booking.d.ts +2 -1
  21. package/dist/react/booking/Booking.js +1 -1
  22. package/dist/react/core/booking/Booking.d.ts +3 -2
  23. package/dist/react/core/booking/Booking.js +5 -2
  24. package/dist/react/core/time-slot-list/TimeSlot.d.ts +4 -4
  25. package/dist/react/core/time-slot-list/TimeSlot.js +7 -5
  26. package/dist/react/core/time-slot-list/TimeSlotList.js +3 -2
  27. package/dist/react/time-slot-list/TimeSlot.d.ts +6 -6
  28. package/dist/react/time-slot-list/TimeSlot.js +6 -6
  29. package/dist/react/time-slot-list/TimeSlotList.d.ts +4 -4
  30. package/dist/services/booking/booking.d.ts +5 -0
  31. package/dist/services/booking/booking.js +17 -1
  32. package/dist/services/service/service.js +4 -1
  33. package/dist/services/time-slot-list/time-slot-list.def.d.ts +12 -4
  34. package/dist/services/time-slot-list/time-slot-list.js +21 -30
  35. package/package.json +2 -2
@@ -52,10 +52,10 @@ export interface FetchAvailabilityResponse {
52
52
  * // response.timeSlots are grouped by time
53
53
  * ```
54
54
  */
55
- export declare function fetchAvailability({ services: selectedServices, viewMode, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, }: {
55
+ export declare function fetchAvailability({ services: selectedServices, viewMode, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, timeSlotsPerDay, }: {
56
56
  services: Service[];
57
- fromLocalDate: string;
58
- toLocalDate: string;
57
+ fromLocalDate: Date;
58
+ toLocalDate: Date;
59
59
  timeZone?: string;
60
60
  locations?: Location[];
61
61
  staffMemberIds?: string[];
@@ -68,4 +68,5 @@ export declare function fetchAvailability({ services: selectedServices, viewMode
68
68
  bookOnlineDisabled?: boolean;
69
69
  };
70
70
  slotsPerPage?: number;
71
+ timeSlotsPerDay?: number;
71
72
  }): Promise<FetchAvailabilityResponse>;
@@ -5,6 +5,7 @@
5
5
  import { listAvailabilityTimeSlots, } from '@wix/auto_sdk_bookings_availability-time-slots';
6
6
  import { listEventTimeSlots } from '@wix/auto_sdk_bookings_event-time-slots';
7
7
  import { ServiceType } from '@wix/auto_sdk_bookings_services';
8
+ import { format } from 'date-fns';
8
9
  import { ANY_STAFF_MEMBER_ID } from '../../services/booking/booking.js';
9
10
  import { ViewMode, } from '../../services/time-slot-list/time-slot-list.def.js';
10
11
  // ============================================================================
@@ -18,6 +19,16 @@ var FlowType;
18
19
  })(FlowType || (FlowType = {}));
19
20
  const STAFF_RESOURCE_TYPE_ID = '1cd44cf8-756f-41c3-bd90-3e2ffcaf1155';
20
21
  // ============================================================================
22
+ // Helpers
23
+ // ============================================================================
24
+ /**
25
+ * Formats a Date to local ISO string (YYYY-MM-DDThh:mm:ss)
26
+ */
27
+ function toLocalDateString(date) {
28
+ const pattern = "yyyy-MM-dd'T'HH:mm:ss";
29
+ return format(date, pattern);
30
+ }
31
+ // ============================================================================
21
32
  // Grouping Functions
22
33
  // ============================================================================
23
34
  /**
@@ -104,9 +115,19 @@ function detectFlow(bookingServices, viewMode) {
104
115
  * // response.timeSlots are grouped by time
105
116
  * ```
106
117
  */
107
- export async function fetchAvailability({ services: selectedServices, viewMode = ViewMode.CLASSIC, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, }) {
118
+ export async function fetchAvailability({ services: selectedServices, viewMode = ViewMode.CLASSIC, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, timeSlotsPerDay, }) {
108
119
  const flow = detectFlow(selectedServices, viewMode);
109
120
  const serviceIds = selectedServices.map((s) => s._id);
121
+ // Convert Date objects to local ISO strings
122
+ const fromLocalDateStr = toLocalDateString(fromLocalDate);
123
+ const toLocalDateStr = toLocalDateString(toLocalDate);
124
+ const isFetchAvailabileDays = timeSlotsPerDay === 1;
125
+ const cursorPaging = isFetchAvailabileDays
126
+ ? undefined
127
+ : {
128
+ cursor,
129
+ limit: slotsPerPage,
130
+ };
110
131
  if (flow === FlowType.SINGLE_SERVICE) {
111
132
  const isAnyStaffMemberSelected = staffMemberIds?.length === 1 && staffMemberIds[0] === ANY_STAFF_MEMBER_ID;
112
133
  const resourceTypes = staffMemberIds?.length && !isAnyStaffMemberSelected
@@ -119,20 +140,18 @@ export async function fetchAvailability({ services: selectedServices, viewMode =
119
140
  : undefined;
120
141
  const response = await listAvailabilityTimeSlots({
121
142
  serviceId: serviceIds[0],
122
- fromLocalDate,
123
- toLocalDate,
143
+ fromLocalDate: fromLocalDateStr,
144
+ toLocalDate: toLocalDateStr,
124
145
  timeZone,
125
146
  locations,
126
- cursorPaging: {
127
- cursor,
128
- limit: slotsPerPage,
129
- },
147
+ cursorPaging,
130
148
  resourceTypes,
131
149
  bookable,
132
150
  bookingPolicyViolations,
133
- includeResourceTypeIds: isAnyStaffMemberSelected
151
+ includeResourceTypeIds: isAnyStaffMemberSelected || isFetchAvailabileDays
134
152
  ? undefined
135
153
  : [STAFF_RESOURCE_TYPE_ID],
154
+ timeSlotsPerDay,
136
155
  });
137
156
  return {
138
157
  timeSlots: groupTimeSlotsByTime(response.timeSlots || []),
@@ -154,17 +173,15 @@ export async function fetchAvailability({ services: selectedServices, viewMode =
154
173
  eventFilter['resources.id'] = { $hasSome: staffMemberIds };
155
174
  }
156
175
  const response = await listEventTimeSlots({
157
- fromLocalDate,
158
- toLocalDate,
176
+ fromLocalDate: fromLocalDateStr,
177
+ toLocalDate: toLocalDateStr,
159
178
  timeZone,
160
179
  serviceIds: serviceIds.length ? serviceIds : undefined,
161
- cursorPaging: {
162
- cursor,
163
- limit: slotsPerPage,
164
- },
180
+ cursorPaging,
165
181
  eventFilter: Object.keys(eventFilter).length ? eventFilter : undefined,
166
182
  includeNonBookable: bookable ? !bookable : undefined,
167
183
  bookingPolicyViolations,
184
+ maxSlotsPerDay: timeSlotsPerDay,
168
185
  });
169
186
  return {
170
187
  timeSlots: groupTimeSlotsByTime(response.timeSlots || []),
@@ -4,12 +4,13 @@
4
4
  */
5
5
  import React from 'react';
6
6
  import type { BookingServiceConfig } from '../../services/booking/booking.js';
7
+ export type { BookingData, DataProps } from '../core/booking/Booking.js';
7
8
  /**
8
9
  * Props for Booking.Root component
9
10
  */
10
11
  export interface BookingRootProps {
11
12
  children: React.ReactNode;
12
- bookingServiceConfig: BookingServiceConfig;
13
+ bookingServiceConfig?: BookingServiceConfig;
13
14
  }
14
15
  /**
15
16
  * Root component that provides booking context to the entire app.
@@ -19,7 +19,7 @@ import * as CoreBooking from '../core/booking/Booking.js';
19
19
  * ```
20
20
  */
21
21
  export const Root = (props) => {
22
- const { children, bookingServiceConfig } = props;
22
+ const { children, bookingServiceConfig = {} } = props;
23
23
  return (_jsx(CoreBooking.Root, { bookingServiceConfig: bookingServiceConfig, children: children }));
24
24
  };
25
25
  Root.displayName = 'Booking.Root';
@@ -10,7 +10,7 @@ import type { FormValues } from '@wix/form-public';
10
10
  */
11
11
  export interface RootProps {
12
12
  children: React.ReactNode;
13
- bookingServiceConfig: BookingServiceConfig;
13
+ bookingServiceConfig?: BookingServiceConfig;
14
14
  }
15
15
  /**
16
16
  * Root component that provides booking context to the entire app.
@@ -35,6 +35,7 @@ export declare function Root(props: RootProps): React.ReactNode;
35
35
  export interface BookingData {
36
36
  serviceSelections: ServiceSelection[];
37
37
  location: Location | null;
38
+ timezone: string | undefined;
38
39
  formSubmission: FormValues | null;
39
40
  actions: BookingServiceActions;
40
41
  }
@@ -52,7 +53,7 @@ export interface DataProps {
52
53
  * @example
53
54
  * ```tsx
54
55
  * <Booking.Data>
55
- * {({ serviceSelections, location, formSubmission, actions }) => (
56
+ * {({ serviceSelections, location, timezone, formSubmission, actions }) => (
56
57
  * serviceSelections.length > 0 ? (
57
58
  * <div>{serviceSelections.map(item => item.service.name).join(', ')}</div>
58
59
  * ) : (
@@ -18,7 +18,8 @@ import { BookingServiceDefinition, BookingService, } from '../../../services/boo
18
18
  * ```
19
19
  */
20
20
  export function Root(props) {
21
- return (_jsx(WixServices, { servicesMap: createServicesMap().addService(BookingServiceDefinition, BookingService, props.bookingServiceConfig), children: props.children }));
21
+ const { bookingServiceConfig = {}, children } = props;
22
+ return (_jsx(WixServices, { servicesMap: createServicesMap().addService(BookingServiceDefinition, BookingService, bookingServiceConfig), children: children }));
22
23
  }
23
24
  /**
24
25
  * Core component that provides access to booking data via render props.
@@ -28,7 +29,7 @@ export function Root(props) {
28
29
  * @example
29
30
  * ```tsx
30
31
  * <Booking.Data>
31
- * {({ serviceSelections, location, formSubmission, actions }) => (
32
+ * {({ serviceSelections, location, timezone, formSubmission, actions }) => (
32
33
  * serviceSelections.length > 0 ? (
33
34
  * <div>{serviceSelections.map(item => item.service.name).join(', ')}</div>
34
35
  * ) : (
@@ -43,11 +44,13 @@ export function Data(props) {
43
44
  const bookingService = useService(BookingServiceDefinition);
44
45
  const serviceSelections = bookingService.serviceSelections.get();
45
46
  const location = bookingService.location.get();
47
+ const timezone = bookingService.timezone.get();
46
48
  const formSubmission = bookingService.formSubmission.get();
47
49
  const { actions } = bookingService;
48
50
  return children({
49
51
  serviceSelections,
50
52
  location,
53
+ timezone,
51
54
  formSubmission,
52
55
  actions,
53
56
  });
@@ -88,10 +88,10 @@ export declare function StaffMemberProvider(props: StaffMemberProviderProps): Re
88
88
  */
89
89
  export interface InfoProps {
90
90
  children: (data: {
91
- /** Raw localStartDate string from timeSlot */
92
- startDate: string | undefined;
93
- /** Raw localEndDate string from timeSlot */
94
- endDate: string | undefined;
91
+ /** Parsed localStartDate as Date (constructed from the raw localStartDate string) */
92
+ startDate: Date | undefined;
93
+ /** Parsed localEndDate as Date (constructed from the raw localEndDate string) */
94
+ endDate: Date | undefined;
95
95
  /** Calculated duration in minutes (null if dates unavailable) */
96
96
  durationInMinutes: number | null;
97
97
  /** Whether the timeSlot is bookable */
@@ -116,15 +116,17 @@ export function StaffMemberProvider(props) {
116
116
  */
117
117
  export function Info(props) {
118
118
  const { timeSlot, isSelected } = useTimeSlotContext();
119
- const startDate = timeSlot.localStartDate ?? undefined;
120
- const endDate = timeSlot.localEndDate ?? undefined;
119
+ const startDate = timeSlot.localStartDate
120
+ ? new Date(timeSlot.localStartDate)
121
+ : undefined;
122
+ const endDate = timeSlot.localEndDate
123
+ ? new Date(timeSlot.localEndDate)
124
+ : undefined;
121
125
  const bookable = timeSlot.bookable ?? false;
122
126
  // Calculate duration from start and end dates
123
127
  let durationInMinutes = null;
124
128
  if (startDate && endDate) {
125
- const startTime = new Date(startDate).getTime();
126
- const endTime = new Date(endDate).getTime();
127
- durationInMinutes = Math.round((endTime - startTime) / (1000 * 60));
129
+ durationInMinutes = Math.round((endDate.getTime() - startDate.getTime()) / (1000 * 60));
128
130
  }
129
131
  return props.children({
130
132
  startDate,
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useService, WixServices } from '@wix/services-manager-react';
3
3
  import { createServicesMap } from '@wix/services-manager';
4
4
  import { TimeSlotListServiceDefinition, TimeSlotListService, } from '../../../services/time-slot-list/index.js';
5
+ import { BookingServiceDefinition } from '../../../services/booking/booking.js';
5
6
  /**
6
7
  * Root component that provides TimeSlotListService context to its children.
7
8
  *
@@ -54,8 +55,8 @@ function getTimezoneDisplayName(timezone) {
54
55
  * ```
55
56
  */
56
57
  export function Timezone(props) {
57
- const service = useService(TimeSlotListServiceDefinition);
58
- const timezone = service.timezone.get();
58
+ const bookingService = useService(BookingServiceDefinition);
59
+ const timezone = bookingService.timezone.get();
59
60
  const displayName = getTimezoneDisplayName(timezone);
60
61
  return props.children({ timezone, displayName });
61
62
  }
@@ -16,18 +16,18 @@ export type { LocationData, StaffMemberData, } from '../core/time-slot-list/Time
16
16
  export interface StartDateProps {
17
17
  asChild?: boolean;
18
18
  children?: AsChildChildren<{
19
- startDate: string | undefined;
19
+ startDate: Date | undefined;
20
20
  }>;
21
21
  className?: string;
22
22
  }
23
23
  /**
24
- * Displays the time slot start date/time as a raw string.
24
+ * Displays the time slot start date/time as a Date.
25
25
  * Consumer is responsible for formatting.
26
26
  *
27
27
  * @component
28
28
  * @example
29
29
  * ```tsx
30
- * // Default usage - displays raw localStartDate string
30
+ * // Default usage - displays Date.toString()
31
31
  * <TimeSlot.StartDate className="text-foreground" />
32
32
  *
33
33
  * // With custom formatting
@@ -45,18 +45,18 @@ export declare const StartDate: React.ForwardRefExoticComponent<StartDateProps &
45
45
  export interface EndDateProps {
46
46
  asChild?: boolean;
47
47
  children?: AsChildChildren<{
48
- endDate: string | undefined;
48
+ endDate: Date | undefined;
49
49
  }>;
50
50
  className?: string;
51
51
  }
52
52
  /**
53
- * Displays the time slot end date/time as a raw string.
53
+ * Displays the time slot end date/time as a Date.
54
54
  * Consumer is responsible for formatting.
55
55
  *
56
56
  * @component
57
57
  * @example
58
58
  * ```tsx
59
- * // Default usage - displays raw localEndDate string
59
+ * // Default usage - displays Date.toString()
60
60
  * <TimeSlot.EndDate className="text-foreground" />
61
61
  *
62
62
  * // With custom formatting
@@ -34,13 +34,13 @@ var TestIds;
34
34
  TestIds["timeSlotStaffMemberActionSelect"] = "time-slot-staff-member-action-select";
35
35
  })(TestIds || (TestIds = {}));
36
36
  /**
37
- * Displays the time slot start date/time as a raw string.
37
+ * Displays the time slot start date/time as a Date.
38
38
  * Consumer is responsible for formatting.
39
39
  *
40
40
  * @component
41
41
  * @example
42
42
  * ```tsx
43
- * // Default usage - displays raw localStartDate string
43
+ * // Default usage - displays Date.toString()
44
44
  * <TimeSlot.StartDate className="text-foreground" />
45
45
  *
46
46
  * // With custom formatting
@@ -53,17 +53,17 @@ var TestIds;
53
53
  */
54
54
  export const StartDate = React.forwardRef((props, ref) => {
55
55
  const { asChild, children, className } = props;
56
- return (_jsx(CoreTimeSlot.Info, { children: ({ startDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotStartDate, customElement: children, customElementProps: { startDate }, children: _jsx("span", { children: startDate }) })) }));
56
+ return (_jsx(CoreTimeSlot.Info, { children: ({ startDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotStartDate, customElement: children, customElementProps: { startDate }, children: _jsx("span", { children: startDate?.toString() }) })) }));
57
57
  });
58
58
  StartDate.displayName = 'TimeSlot.StartDate';
59
59
  /**
60
- * Displays the time slot end date/time as a raw string.
60
+ * Displays the time slot end date/time as a Date.
61
61
  * Consumer is responsible for formatting.
62
62
  *
63
63
  * @component
64
64
  * @example
65
65
  * ```tsx
66
- * // Default usage - displays raw localEndDate string
66
+ * // Default usage - displays Date.toString()
67
67
  * <TimeSlot.EndDate className="text-foreground" />
68
68
  *
69
69
  * // With custom formatting
@@ -76,7 +76,7 @@ StartDate.displayName = 'TimeSlot.StartDate';
76
76
  */
77
77
  export const EndDate = React.forwardRef((props, ref) => {
78
78
  const { asChild, children, className } = props;
79
- return (_jsx(CoreTimeSlot.Info, { children: ({ endDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotEndDate, customElement: children, customElementProps: { endDate }, children: _jsx("span", { children: endDate }) })) }));
79
+ return (_jsx(CoreTimeSlot.Info, { children: ({ endDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotEndDate, customElement: children, customElementProps: { endDate }, children: _jsx("span", { children: endDate?.toString() }) })) }));
80
80
  });
81
81
  EndDate.displayName = 'TimeSlot.EndDate';
82
82
  /**
@@ -113,10 +113,10 @@ export interface TimeSlotRepeaterChildProps {
113
113
  isSelected: boolean;
114
114
  /** Whether the slot can be booked */
115
115
  bookable: boolean;
116
- /** Start date/time as ISO string */
117
- startDate: string | undefined;
118
- /** End date/time as ISO string */
119
- endDate: string | undefined;
116
+ /** Start date/time as Date (constructed from the raw localStartDate string) */
117
+ startDate: Date | undefined;
118
+ /** End date/time as Date (constructed from the raw localEndDate string) */
119
+ endDate: Date | undefined;
120
120
  /** Duration in minutes */
121
121
  durationInMinutes: number | null;
122
122
  /** The raw time slot data */
@@ -58,6 +58,7 @@ export interface BookingServiceActions {
58
58
  getResourceById(resourceId: string, instanceId?: string): Resource | undefined;
59
59
  setLocation(location: Location | null): void;
60
60
  clearLocation(): void;
61
+ setTimezone(timezone: string): void;
61
62
  setFormSubmission(values: FormValues | null): void;
62
63
  clearFormSubmission(): void;
63
64
  clearAll(): void;
@@ -71,6 +72,8 @@ export interface BookingService {
71
72
  serviceSelections: Signal<ServiceSelection[]>;
72
73
  /** Signal containing the selected location */
73
74
  location: Signal<Location | null>;
75
+ /** Signal containing the selected timezone */
76
+ timezone: Signal<string | undefined>;
74
77
  /** Signal containing the form submission */
75
78
  formSubmission: Signal<FormValues | null>;
76
79
  /** Actions for modifying booking state */
@@ -93,6 +96,8 @@ export interface BookingServiceConfig {
93
96
  serviceSelections?: ServiceSelection[];
94
97
  /** Optional initial location */
95
98
  location?: Location | null;
99
+ /** Optional initial timezone */
100
+ timezone?: string;
96
101
  /** Optional initial form submission */
97
102
  formSubmission?: FormValues | null;
98
103
  }
@@ -10,7 +10,7 @@ import { SignalsServiceDefinition, } from '@wix/services-definitions/core-servic
10
10
  // ============================================================================
11
11
  export const ANY_STAFF_MEMBER_ID = '__any_staff_member__';
12
12
  // ============================================================================
13
- // Types
13
+ // Helpers
14
14
  // ============================================================================
15
15
  /**
16
16
  * Generates a unique instance ID for service selections
@@ -18,6 +18,14 @@ export const ANY_STAFF_MEMBER_ID = '__any_staff_member__';
18
18
  function generateInstanceId() {
19
19
  return crypto.randomUUID();
20
20
  }
21
+ /**
22
+ * Gets the client's timezone from Intl API
23
+ */
24
+ function getClientTimezone() {
25
+ return typeof Intl !== 'undefined'
26
+ ? Intl.DateTimeFormat().resolvedOptions().timeZone
27
+ : undefined;
28
+ }
21
29
  /**
22
30
  * Service definition for Booking
23
31
  */
@@ -59,6 +67,7 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
59
67
  }));
60
68
  const serviceSelectionsSignal = signalsService.signal(initialItemsWithIds);
61
69
  const locationSignal = signalsService.signal(config.location || null);
70
+ const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
62
71
  const formSubmissionSignal = signalsService.signal(config.formSubmission || null);
63
72
  // ========================================================================
64
73
  // Actions Implementation
@@ -146,6 +155,12 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
146
155
  clearLocation() {
147
156
  locationSignal.set(null);
148
157
  },
158
+ // Timezone actions
159
+ setTimezone(timezone) {
160
+ if (timezoneSignal.get() !== timezone) {
161
+ timezoneSignal.set(timezone);
162
+ }
163
+ },
149
164
  // Form Submission actions
150
165
  setFormSubmission(values) {
151
166
  formSubmissionSignal.set(values);
@@ -163,6 +178,7 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
163
178
  return {
164
179
  serviceSelections: serviceSelectionsSignal,
165
180
  location: locationSignal,
181
+ timezone: timezoneSignal,
166
182
  formSubmission: formSubmissionSignal,
167
183
  actions,
168
184
  };
@@ -185,7 +185,10 @@ export const ServiceService = implementService.withConfig()(ServiceServiceDefini
185
185
  if (!bookableSignal.get() || !bookingService)
186
186
  return;
187
187
  const currentService = serviceSignal.get();
188
- bookingService.actions.updateItem({ service: currentService });
188
+ bookingService.actions.setItem({
189
+ service: currentService,
190
+ resources: [],
191
+ });
189
192
  },
190
193
  add: () => {
191
194
  if (!bookableSignal.get() || !bookingService)
@@ -65,11 +65,21 @@ export interface TimeSlotsFilters {
65
65
  * Default: 1000. Max: 1000.
66
66
  * @example
67
67
  * ```ts
68
- * // Load 6 slots at a time
69
- * filters: { slotsPerPage: 6 }
68
+ * // Load 10 slots at a time
69
+ * filters: { slotsPerPage: 10 }
70
70
  * ```
71
71
  */
72
72
  slotsPerPage?: number;
73
+ /**
74
+ * Maximum number of time slots to return per day.
75
+ * Useful for fetching availability indicators (set to 1 to get one slot per day).
76
+ * @example
77
+ * ```ts
78
+ * // Get one slot per day for calendar indicators
79
+ * filters: { timeSlotsPerDay: 1 }
80
+ * ```
81
+ */
82
+ timeSlotsPerDay?: number;
73
83
  }
74
84
  export interface TimeSlotListServiceConfig {
75
85
  /** Services to fetch time slots for */
@@ -92,7 +102,6 @@ export interface TimeSlotListServiceActions {
92
102
  clearPreferences: () => void;
93
103
  loadMore: () => Promise<void>;
94
104
  setDateRange: (start: Date, end: Date) => void;
95
- setTimezone: (tz: string) => void;
96
105
  }
97
106
  export interface TimeSlotListServiceAPI {
98
107
  timeSlots: Signal<TimeSlot[]>;
@@ -102,7 +111,6 @@ export interface TimeSlotListServiceAPI {
102
111
  hasMore: Signal<boolean>;
103
112
  startDate: Signal<Date>;
104
113
  endDate: Signal<Date>;
105
- timezone: Signal<string | undefined>;
106
114
  actions: TimeSlotListServiceActions;
107
115
  }
108
116
  export declare const TimeSlotListServiceDefinition: string & {
@@ -4,7 +4,6 @@
4
4
  */
5
5
  import { implementService } from '@wix/services-definitions';
6
6
  import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
7
- import { format } from 'date-fns';
8
7
  import { TimeSlotListServiceDefinition, ViewMode, } from './time-slot-list.def.js';
9
8
  import { fetchAvailability } from '../../api/fetch-availability/index.js';
10
9
  import { BookingServiceDefinition, ANY_STAFF_MEMBER_ID, } from '../booking/booking.js';
@@ -19,13 +18,6 @@ function getClientTimezone() {
19
18
  ? Intl.DateTimeFormat().resolvedOptions().timeZone
20
19
  : undefined;
21
20
  }
22
- /**
23
- * Formats a Date to local ISO string (YYYY-MM-DDThh:mm:ss)
24
- */
25
- function toLocalDateString(date) {
26
- const pattern = "yyyy-MM-dd'T'HH:mm:ss";
27
- return format(date, pattern);
28
- }
29
21
  /**
30
22
  * Merges new grouped slots with existing ones
31
23
  * Slots with the same start time are merged
@@ -68,7 +60,6 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
68
60
  const viewModeSignal = signalsService.signal(ViewMode.CLASSIC);
69
61
  const startDateSignal = signalsService.signal(config.startDate || currentDate);
70
62
  const endDateSignal = signalsService.signal(config.endDate || getDefaultEndDate());
71
- const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
72
63
  const cursorSignal = signalsService.signal(undefined);
73
64
  /**
74
65
  * Helper: Get services from config or BookingService
@@ -86,7 +77,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
86
77
  const location = bookingService.location.get();
87
78
  const serviceSelections = bookingService.serviceSelections.get();
88
79
  const selectedStaffMemberIds = serviceSelections
89
- .map(({ resources }) => resources.map((r) => r._id))
80
+ .map((serviceSelection) => serviceSelection.resources.map((r) => r._id))
90
81
  .flat();
91
82
  const selectedLocation = location ? { _id: location.id } : null;
92
83
  // Merge config filters with BookingService selections (selections win)
@@ -114,9 +105,9 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
114
105
  try {
115
106
  const response = await fetchAvailability({
116
107
  services: selectedServices,
117
- fromLocalDate: toLocalDateString(startDateSignal.get()),
118
- toLocalDate: toLocalDateString(endDateSignal.get()),
119
- timeZone: timezoneSignal.get(),
108
+ fromLocalDate: startDateSignal.get(),
109
+ toLocalDate: endDateSignal.get(),
110
+ timeZone: bookingService.timezone.get(),
120
111
  viewMode: viewModeSignal.get(),
121
112
  cursor: cursorSignal.get(),
122
113
  ...filters,
@@ -312,16 +303,14 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
312
303
  if (!cursor || isLoadingSignal.get())
313
304
  return;
314
305
  const selectedServices = getServices();
315
- if (selectedServices.length === 0)
316
- return;
317
306
  const filters = getFilters();
318
307
  isLoadingSignal.set(true);
319
308
  try {
320
309
  const response = await fetchAvailability({
321
310
  services: selectedServices,
322
- fromLocalDate: toLocalDateString(startDateSignal.get()),
323
- toLocalDate: toLocalDateString(endDateSignal.get()),
324
- timeZone: timezoneSignal.get(),
311
+ fromLocalDate: startDateSignal.get(),
312
+ toLocalDate: endDateSignal.get(),
313
+ timeZone: bookingService.timezone.get(),
325
314
  cursor,
326
315
  viewMode: viewModeSignal.get(),
327
316
  ...filters,
@@ -357,17 +346,21 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
357
346
  endDateSignal.set(newEndDate);
358
347
  fetchTimeSlots();
359
348
  };
360
- /**
361
- * Action: Update timezone and refetch slots
362
- */
363
- const setTimezone = (newTimezone) => {
364
- timezoneSignal.set(newTimezone);
365
- fetchTimeSlots();
366
- };
367
349
  // Auto-fetch on initialization if no pre-loaded data provided
368
- if (!config.timeSlots && config.services && config.services.length > 0) {
350
+ if (!config.timeSlots) {
369
351
  fetchTimeSlots();
370
352
  }
353
+ // Subscribe to timezone changes from BookingService to auto-refetch
354
+ // NOTE: This should depend ONLY on bookingService.timezone.
355
+ let firstTimezoneEffectRun = true;
356
+ signalsService.effect(() => {
357
+ bookingService.timezone.get();
358
+ if (firstTimezoneEffectRun) {
359
+ firstTimezoneEffectRun = false;
360
+ return;
361
+ }
362
+ fetchTimeSlots();
363
+ });
371
364
  // Return service API
372
365
  return {
373
366
  // State Signals
@@ -379,7 +372,6 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
379
372
  // Configuration Signals (viewMode is internal only)
380
373
  startDate: startDateSignal,
381
374
  endDate: endDateSignal,
382
- timezone: timezoneSignal,
383
375
  // Actions
384
376
  actions: {
385
377
  selectSlot,
@@ -388,7 +380,6 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
388
380
  clearPreferences,
389
381
  loadMore,
390
382
  setDateRange,
391
- setTimezone,
392
383
  },
393
384
  };
394
385
  });
@@ -420,8 +411,8 @@ export async function loadTimeSlotListConfig(options) {
420
411
  try {
421
412
  const response = await fetchAvailability({
422
413
  services: options.services || [],
423
- fromLocalDate: toLocalDateString(startDate),
424
- toLocalDate: toLocalDateString(endDate),
414
+ fromLocalDate: startDate,
415
+ toLocalDate: endDate,
425
416
  timeZone: options.timezone ?? getClientTimezone(),
426
417
  ...options.filters,
427
418
  });
@@ -52,10 +52,10 @@ export interface FetchAvailabilityResponse {
52
52
  * // response.timeSlots are grouped by time
53
53
  * ```
54
54
  */
55
- export declare function fetchAvailability({ services: selectedServices, viewMode, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, }: {
55
+ export declare function fetchAvailability({ services: selectedServices, viewMode, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, timeSlotsPerDay, }: {
56
56
  services: Service[];
57
- fromLocalDate: string;
58
- toLocalDate: string;
57
+ fromLocalDate: Date;
58
+ toLocalDate: Date;
59
59
  timeZone?: string;
60
60
  locations?: Location[];
61
61
  staffMemberIds?: string[];
@@ -68,4 +68,5 @@ export declare function fetchAvailability({ services: selectedServices, viewMode
68
68
  bookOnlineDisabled?: boolean;
69
69
  };
70
70
  slotsPerPage?: number;
71
+ timeSlotsPerDay?: number;
71
72
  }): Promise<FetchAvailabilityResponse>;
@@ -5,6 +5,7 @@
5
5
  import { listAvailabilityTimeSlots, } from '@wix/auto_sdk_bookings_availability-time-slots';
6
6
  import { listEventTimeSlots } from '@wix/auto_sdk_bookings_event-time-slots';
7
7
  import { ServiceType } from '@wix/auto_sdk_bookings_services';
8
+ import { format } from 'date-fns';
8
9
  import { ANY_STAFF_MEMBER_ID } from '../../services/booking/booking.js';
9
10
  import { ViewMode, } from '../../services/time-slot-list/time-slot-list.def.js';
10
11
  // ============================================================================
@@ -18,6 +19,16 @@ var FlowType;
18
19
  })(FlowType || (FlowType = {}));
19
20
  const STAFF_RESOURCE_TYPE_ID = '1cd44cf8-756f-41c3-bd90-3e2ffcaf1155';
20
21
  // ============================================================================
22
+ // Helpers
23
+ // ============================================================================
24
+ /**
25
+ * Formats a Date to local ISO string (YYYY-MM-DDThh:mm:ss)
26
+ */
27
+ function toLocalDateString(date) {
28
+ const pattern = "yyyy-MM-dd'T'HH:mm:ss";
29
+ return format(date, pattern);
30
+ }
31
+ // ============================================================================
21
32
  // Grouping Functions
22
33
  // ============================================================================
23
34
  /**
@@ -104,9 +115,19 @@ function detectFlow(bookingServices, viewMode) {
104
115
  * // response.timeSlots are grouped by time
105
116
  * ```
106
117
  */
107
- export async function fetchAvailability({ services: selectedServices, viewMode = ViewMode.CLASSIC, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, }) {
118
+ export async function fetchAvailability({ services: selectedServices, viewMode = ViewMode.CLASSIC, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, timeSlotsPerDay, }) {
108
119
  const flow = detectFlow(selectedServices, viewMode);
109
120
  const serviceIds = selectedServices.map((s) => s._id);
121
+ // Convert Date objects to local ISO strings
122
+ const fromLocalDateStr = toLocalDateString(fromLocalDate);
123
+ const toLocalDateStr = toLocalDateString(toLocalDate);
124
+ const isFetchAvailabileDays = timeSlotsPerDay === 1;
125
+ const cursorPaging = isFetchAvailabileDays
126
+ ? undefined
127
+ : {
128
+ cursor,
129
+ limit: slotsPerPage,
130
+ };
110
131
  if (flow === FlowType.SINGLE_SERVICE) {
111
132
  const isAnyStaffMemberSelected = staffMemberIds?.length === 1 && staffMemberIds[0] === ANY_STAFF_MEMBER_ID;
112
133
  const resourceTypes = staffMemberIds?.length && !isAnyStaffMemberSelected
@@ -119,20 +140,18 @@ export async function fetchAvailability({ services: selectedServices, viewMode =
119
140
  : undefined;
120
141
  const response = await listAvailabilityTimeSlots({
121
142
  serviceId: serviceIds[0],
122
- fromLocalDate,
123
- toLocalDate,
143
+ fromLocalDate: fromLocalDateStr,
144
+ toLocalDate: toLocalDateStr,
124
145
  timeZone,
125
146
  locations,
126
- cursorPaging: {
127
- cursor,
128
- limit: slotsPerPage,
129
- },
147
+ cursorPaging,
130
148
  resourceTypes,
131
149
  bookable,
132
150
  bookingPolicyViolations,
133
- includeResourceTypeIds: isAnyStaffMemberSelected
151
+ includeResourceTypeIds: isAnyStaffMemberSelected || isFetchAvailabileDays
134
152
  ? undefined
135
153
  : [STAFF_RESOURCE_TYPE_ID],
154
+ timeSlotsPerDay,
136
155
  });
137
156
  return {
138
157
  timeSlots: groupTimeSlotsByTime(response.timeSlots || []),
@@ -154,17 +173,15 @@ export async function fetchAvailability({ services: selectedServices, viewMode =
154
173
  eventFilter['resources.id'] = { $hasSome: staffMemberIds };
155
174
  }
156
175
  const response = await listEventTimeSlots({
157
- fromLocalDate,
158
- toLocalDate,
176
+ fromLocalDate: fromLocalDateStr,
177
+ toLocalDate: toLocalDateStr,
159
178
  timeZone,
160
179
  serviceIds: serviceIds.length ? serviceIds : undefined,
161
- cursorPaging: {
162
- cursor,
163
- limit: slotsPerPage,
164
- },
180
+ cursorPaging,
165
181
  eventFilter: Object.keys(eventFilter).length ? eventFilter : undefined,
166
182
  includeNonBookable: bookable ? !bookable : undefined,
167
183
  bookingPolicyViolations,
184
+ maxSlotsPerDay: timeSlotsPerDay,
168
185
  });
169
186
  return {
170
187
  timeSlots: groupTimeSlotsByTime(response.timeSlots || []),
@@ -4,12 +4,13 @@
4
4
  */
5
5
  import React from 'react';
6
6
  import type { BookingServiceConfig } from '../../services/booking/booking.js';
7
+ export type { BookingData, DataProps } from '../core/booking/Booking.js';
7
8
  /**
8
9
  * Props for Booking.Root component
9
10
  */
10
11
  export interface BookingRootProps {
11
12
  children: React.ReactNode;
12
- bookingServiceConfig: BookingServiceConfig;
13
+ bookingServiceConfig?: BookingServiceConfig;
13
14
  }
14
15
  /**
15
16
  * Root component that provides booking context to the entire app.
@@ -19,7 +19,7 @@ import * as CoreBooking from '../core/booking/Booking.js';
19
19
  * ```
20
20
  */
21
21
  export const Root = (props) => {
22
- const { children, bookingServiceConfig } = props;
22
+ const { children, bookingServiceConfig = {} } = props;
23
23
  return (_jsx(CoreBooking.Root, { bookingServiceConfig: bookingServiceConfig, children: children }));
24
24
  };
25
25
  Root.displayName = 'Booking.Root';
@@ -10,7 +10,7 @@ import type { FormValues } from '@wix/form-public';
10
10
  */
11
11
  export interface RootProps {
12
12
  children: React.ReactNode;
13
- bookingServiceConfig: BookingServiceConfig;
13
+ bookingServiceConfig?: BookingServiceConfig;
14
14
  }
15
15
  /**
16
16
  * Root component that provides booking context to the entire app.
@@ -35,6 +35,7 @@ export declare function Root(props: RootProps): React.ReactNode;
35
35
  export interface BookingData {
36
36
  serviceSelections: ServiceSelection[];
37
37
  location: Location | null;
38
+ timezone: string | undefined;
38
39
  formSubmission: FormValues | null;
39
40
  actions: BookingServiceActions;
40
41
  }
@@ -52,7 +53,7 @@ export interface DataProps {
52
53
  * @example
53
54
  * ```tsx
54
55
  * <Booking.Data>
55
- * {({ serviceSelections, location, formSubmission, actions }) => (
56
+ * {({ serviceSelections, location, timezone, formSubmission, actions }) => (
56
57
  * serviceSelections.length > 0 ? (
57
58
  * <div>{serviceSelections.map(item => item.service.name).join(', ')}</div>
58
59
  * ) : (
@@ -18,7 +18,8 @@ import { BookingServiceDefinition, BookingService, } from '../../../services/boo
18
18
  * ```
19
19
  */
20
20
  export function Root(props) {
21
- return (_jsx(WixServices, { servicesMap: createServicesMap().addService(BookingServiceDefinition, BookingService, props.bookingServiceConfig), children: props.children }));
21
+ const { bookingServiceConfig = {}, children } = props;
22
+ return (_jsx(WixServices, { servicesMap: createServicesMap().addService(BookingServiceDefinition, BookingService, bookingServiceConfig), children: children }));
22
23
  }
23
24
  /**
24
25
  * Core component that provides access to booking data via render props.
@@ -28,7 +29,7 @@ export function Root(props) {
28
29
  * @example
29
30
  * ```tsx
30
31
  * <Booking.Data>
31
- * {({ serviceSelections, location, formSubmission, actions }) => (
32
+ * {({ serviceSelections, location, timezone, formSubmission, actions }) => (
32
33
  * serviceSelections.length > 0 ? (
33
34
  * <div>{serviceSelections.map(item => item.service.name).join(', ')}</div>
34
35
  * ) : (
@@ -43,11 +44,13 @@ export function Data(props) {
43
44
  const bookingService = useService(BookingServiceDefinition);
44
45
  const serviceSelections = bookingService.serviceSelections.get();
45
46
  const location = bookingService.location.get();
47
+ const timezone = bookingService.timezone.get();
46
48
  const formSubmission = bookingService.formSubmission.get();
47
49
  const { actions } = bookingService;
48
50
  return children({
49
51
  serviceSelections,
50
52
  location,
53
+ timezone,
51
54
  formSubmission,
52
55
  actions,
53
56
  });
@@ -88,10 +88,10 @@ export declare function StaffMemberProvider(props: StaffMemberProviderProps): Re
88
88
  */
89
89
  export interface InfoProps {
90
90
  children: (data: {
91
- /** Raw localStartDate string from timeSlot */
92
- startDate: string | undefined;
93
- /** Raw localEndDate string from timeSlot */
94
- endDate: string | undefined;
91
+ /** Parsed localStartDate as Date (constructed from the raw localStartDate string) */
92
+ startDate: Date | undefined;
93
+ /** Parsed localEndDate as Date (constructed from the raw localEndDate string) */
94
+ endDate: Date | undefined;
95
95
  /** Calculated duration in minutes (null if dates unavailable) */
96
96
  durationInMinutes: number | null;
97
97
  /** Whether the timeSlot is bookable */
@@ -116,15 +116,17 @@ export function StaffMemberProvider(props) {
116
116
  */
117
117
  export function Info(props) {
118
118
  const { timeSlot, isSelected } = useTimeSlotContext();
119
- const startDate = timeSlot.localStartDate ?? undefined;
120
- const endDate = timeSlot.localEndDate ?? undefined;
119
+ const startDate = timeSlot.localStartDate
120
+ ? new Date(timeSlot.localStartDate)
121
+ : undefined;
122
+ const endDate = timeSlot.localEndDate
123
+ ? new Date(timeSlot.localEndDate)
124
+ : undefined;
121
125
  const bookable = timeSlot.bookable ?? false;
122
126
  // Calculate duration from start and end dates
123
127
  let durationInMinutes = null;
124
128
  if (startDate && endDate) {
125
- const startTime = new Date(startDate).getTime();
126
- const endTime = new Date(endDate).getTime();
127
- durationInMinutes = Math.round((endTime - startTime) / (1000 * 60));
129
+ durationInMinutes = Math.round((endDate.getTime() - startDate.getTime()) / (1000 * 60));
128
130
  }
129
131
  return props.children({
130
132
  startDate,
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useService, WixServices } from '@wix/services-manager-react';
3
3
  import { createServicesMap } from '@wix/services-manager';
4
4
  import { TimeSlotListServiceDefinition, TimeSlotListService, } from '../../../services/time-slot-list/index.js';
5
+ import { BookingServiceDefinition } from '../../../services/booking/booking.js';
5
6
  /**
6
7
  * Root component that provides TimeSlotListService context to its children.
7
8
  *
@@ -54,8 +55,8 @@ function getTimezoneDisplayName(timezone) {
54
55
  * ```
55
56
  */
56
57
  export function Timezone(props) {
57
- const service = useService(TimeSlotListServiceDefinition);
58
- const timezone = service.timezone.get();
58
+ const bookingService = useService(BookingServiceDefinition);
59
+ const timezone = bookingService.timezone.get();
59
60
  const displayName = getTimezoneDisplayName(timezone);
60
61
  return props.children({ timezone, displayName });
61
62
  }
@@ -16,18 +16,18 @@ export type { LocationData, StaffMemberData, } from '../core/time-slot-list/Time
16
16
  export interface StartDateProps {
17
17
  asChild?: boolean;
18
18
  children?: AsChildChildren<{
19
- startDate: string | undefined;
19
+ startDate: Date | undefined;
20
20
  }>;
21
21
  className?: string;
22
22
  }
23
23
  /**
24
- * Displays the time slot start date/time as a raw string.
24
+ * Displays the time slot start date/time as a Date.
25
25
  * Consumer is responsible for formatting.
26
26
  *
27
27
  * @component
28
28
  * @example
29
29
  * ```tsx
30
- * // Default usage - displays raw localStartDate string
30
+ * // Default usage - displays Date.toString()
31
31
  * <TimeSlot.StartDate className="text-foreground" />
32
32
  *
33
33
  * // With custom formatting
@@ -45,18 +45,18 @@ export declare const StartDate: React.ForwardRefExoticComponent<StartDateProps &
45
45
  export interface EndDateProps {
46
46
  asChild?: boolean;
47
47
  children?: AsChildChildren<{
48
- endDate: string | undefined;
48
+ endDate: Date | undefined;
49
49
  }>;
50
50
  className?: string;
51
51
  }
52
52
  /**
53
- * Displays the time slot end date/time as a raw string.
53
+ * Displays the time slot end date/time as a Date.
54
54
  * Consumer is responsible for formatting.
55
55
  *
56
56
  * @component
57
57
  * @example
58
58
  * ```tsx
59
- * // Default usage - displays raw localEndDate string
59
+ * // Default usage - displays Date.toString()
60
60
  * <TimeSlot.EndDate className="text-foreground" />
61
61
  *
62
62
  * // With custom formatting
@@ -34,13 +34,13 @@ var TestIds;
34
34
  TestIds["timeSlotStaffMemberActionSelect"] = "time-slot-staff-member-action-select";
35
35
  })(TestIds || (TestIds = {}));
36
36
  /**
37
- * Displays the time slot start date/time as a raw string.
37
+ * Displays the time slot start date/time as a Date.
38
38
  * Consumer is responsible for formatting.
39
39
  *
40
40
  * @component
41
41
  * @example
42
42
  * ```tsx
43
- * // Default usage - displays raw localStartDate string
43
+ * // Default usage - displays Date.toString()
44
44
  * <TimeSlot.StartDate className="text-foreground" />
45
45
  *
46
46
  * // With custom formatting
@@ -53,17 +53,17 @@ var TestIds;
53
53
  */
54
54
  export const StartDate = React.forwardRef((props, ref) => {
55
55
  const { asChild, children, className } = props;
56
- return (_jsx(CoreTimeSlot.Info, { children: ({ startDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotStartDate, customElement: children, customElementProps: { startDate }, children: _jsx("span", { children: startDate }) })) }));
56
+ return (_jsx(CoreTimeSlot.Info, { children: ({ startDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotStartDate, customElement: children, customElementProps: { startDate }, children: _jsx("span", { children: startDate?.toString() }) })) }));
57
57
  });
58
58
  StartDate.displayName = 'TimeSlot.StartDate';
59
59
  /**
60
- * Displays the time slot end date/time as a raw string.
60
+ * Displays the time slot end date/time as a Date.
61
61
  * Consumer is responsible for formatting.
62
62
  *
63
63
  * @component
64
64
  * @example
65
65
  * ```tsx
66
- * // Default usage - displays raw localEndDate string
66
+ * // Default usage - displays Date.toString()
67
67
  * <TimeSlot.EndDate className="text-foreground" />
68
68
  *
69
69
  * // With custom formatting
@@ -76,7 +76,7 @@ StartDate.displayName = 'TimeSlot.StartDate';
76
76
  */
77
77
  export const EndDate = React.forwardRef((props, ref) => {
78
78
  const { asChild, children, className } = props;
79
- return (_jsx(CoreTimeSlot.Info, { children: ({ endDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotEndDate, customElement: children, customElementProps: { endDate }, children: _jsx("span", { children: endDate }) })) }));
79
+ return (_jsx(CoreTimeSlot.Info, { children: ({ endDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotEndDate, customElement: children, customElementProps: { endDate }, children: _jsx("span", { children: endDate?.toString() }) })) }));
80
80
  });
81
81
  EndDate.displayName = 'TimeSlot.EndDate';
82
82
  /**
@@ -113,10 +113,10 @@ export interface TimeSlotRepeaterChildProps {
113
113
  isSelected: boolean;
114
114
  /** Whether the slot can be booked */
115
115
  bookable: boolean;
116
- /** Start date/time as ISO string */
117
- startDate: string | undefined;
118
- /** End date/time as ISO string */
119
- endDate: string | undefined;
116
+ /** Start date/time as Date (constructed from the raw localStartDate string) */
117
+ startDate: Date | undefined;
118
+ /** End date/time as Date (constructed from the raw localEndDate string) */
119
+ endDate: Date | undefined;
120
120
  /** Duration in minutes */
121
121
  durationInMinutes: number | null;
122
122
  /** The raw time slot data */
@@ -58,6 +58,7 @@ export interface BookingServiceActions {
58
58
  getResourceById(resourceId: string, instanceId?: string): Resource | undefined;
59
59
  setLocation(location: Location | null): void;
60
60
  clearLocation(): void;
61
+ setTimezone(timezone: string): void;
61
62
  setFormSubmission(values: FormValues | null): void;
62
63
  clearFormSubmission(): void;
63
64
  clearAll(): void;
@@ -71,6 +72,8 @@ export interface BookingService {
71
72
  serviceSelections: Signal<ServiceSelection[]>;
72
73
  /** Signal containing the selected location */
73
74
  location: Signal<Location | null>;
75
+ /** Signal containing the selected timezone */
76
+ timezone: Signal<string | undefined>;
74
77
  /** Signal containing the form submission */
75
78
  formSubmission: Signal<FormValues | null>;
76
79
  /** Actions for modifying booking state */
@@ -93,6 +96,8 @@ export interface BookingServiceConfig {
93
96
  serviceSelections?: ServiceSelection[];
94
97
  /** Optional initial location */
95
98
  location?: Location | null;
99
+ /** Optional initial timezone */
100
+ timezone?: string;
96
101
  /** Optional initial form submission */
97
102
  formSubmission?: FormValues | null;
98
103
  }
@@ -10,7 +10,7 @@ import { SignalsServiceDefinition, } from '@wix/services-definitions/core-servic
10
10
  // ============================================================================
11
11
  export const ANY_STAFF_MEMBER_ID = '__any_staff_member__';
12
12
  // ============================================================================
13
- // Types
13
+ // Helpers
14
14
  // ============================================================================
15
15
  /**
16
16
  * Generates a unique instance ID for service selections
@@ -18,6 +18,14 @@ export const ANY_STAFF_MEMBER_ID = '__any_staff_member__';
18
18
  function generateInstanceId() {
19
19
  return crypto.randomUUID();
20
20
  }
21
+ /**
22
+ * Gets the client's timezone from Intl API
23
+ */
24
+ function getClientTimezone() {
25
+ return typeof Intl !== 'undefined'
26
+ ? Intl.DateTimeFormat().resolvedOptions().timeZone
27
+ : undefined;
28
+ }
21
29
  /**
22
30
  * Service definition for Booking
23
31
  */
@@ -59,6 +67,7 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
59
67
  }));
60
68
  const serviceSelectionsSignal = signalsService.signal(initialItemsWithIds);
61
69
  const locationSignal = signalsService.signal(config.location || null);
70
+ const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
62
71
  const formSubmissionSignal = signalsService.signal(config.formSubmission || null);
63
72
  // ========================================================================
64
73
  // Actions Implementation
@@ -146,6 +155,12 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
146
155
  clearLocation() {
147
156
  locationSignal.set(null);
148
157
  },
158
+ // Timezone actions
159
+ setTimezone(timezone) {
160
+ if (timezoneSignal.get() !== timezone) {
161
+ timezoneSignal.set(timezone);
162
+ }
163
+ },
149
164
  // Form Submission actions
150
165
  setFormSubmission(values) {
151
166
  formSubmissionSignal.set(values);
@@ -163,6 +178,7 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
163
178
  return {
164
179
  serviceSelections: serviceSelectionsSignal,
165
180
  location: locationSignal,
181
+ timezone: timezoneSignal,
166
182
  formSubmission: formSubmissionSignal,
167
183
  actions,
168
184
  };
@@ -185,7 +185,10 @@ export const ServiceService = implementService.withConfig()(ServiceServiceDefini
185
185
  if (!bookableSignal.get() || !bookingService)
186
186
  return;
187
187
  const currentService = serviceSignal.get();
188
- bookingService.actions.updateItem({ service: currentService });
188
+ bookingService.actions.setItem({
189
+ service: currentService,
190
+ resources: [],
191
+ });
189
192
  },
190
193
  add: () => {
191
194
  if (!bookableSignal.get() || !bookingService)
@@ -65,11 +65,21 @@ export interface TimeSlotsFilters {
65
65
  * Default: 1000. Max: 1000.
66
66
  * @example
67
67
  * ```ts
68
- * // Load 6 slots at a time
69
- * filters: { slotsPerPage: 6 }
68
+ * // Load 10 slots at a time
69
+ * filters: { slotsPerPage: 10 }
70
70
  * ```
71
71
  */
72
72
  slotsPerPage?: number;
73
+ /**
74
+ * Maximum number of time slots to return per day.
75
+ * Useful for fetching availability indicators (set to 1 to get one slot per day).
76
+ * @example
77
+ * ```ts
78
+ * // Get one slot per day for calendar indicators
79
+ * filters: { timeSlotsPerDay: 1 }
80
+ * ```
81
+ */
82
+ timeSlotsPerDay?: number;
73
83
  }
74
84
  export interface TimeSlotListServiceConfig {
75
85
  /** Services to fetch time slots for */
@@ -92,7 +102,6 @@ export interface TimeSlotListServiceActions {
92
102
  clearPreferences: () => void;
93
103
  loadMore: () => Promise<void>;
94
104
  setDateRange: (start: Date, end: Date) => void;
95
- setTimezone: (tz: string) => void;
96
105
  }
97
106
  export interface TimeSlotListServiceAPI {
98
107
  timeSlots: Signal<TimeSlot[]>;
@@ -102,7 +111,6 @@ export interface TimeSlotListServiceAPI {
102
111
  hasMore: Signal<boolean>;
103
112
  startDate: Signal<Date>;
104
113
  endDate: Signal<Date>;
105
- timezone: Signal<string | undefined>;
106
114
  actions: TimeSlotListServiceActions;
107
115
  }
108
116
  export declare const TimeSlotListServiceDefinition: string & {
@@ -4,7 +4,6 @@
4
4
  */
5
5
  import { implementService } from '@wix/services-definitions';
6
6
  import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
7
- import { format } from 'date-fns';
8
7
  import { TimeSlotListServiceDefinition, ViewMode, } from './time-slot-list.def.js';
9
8
  import { fetchAvailability } from '../../api/fetch-availability/index.js';
10
9
  import { BookingServiceDefinition, ANY_STAFF_MEMBER_ID, } from '../booking/booking.js';
@@ -19,13 +18,6 @@ function getClientTimezone() {
19
18
  ? Intl.DateTimeFormat().resolvedOptions().timeZone
20
19
  : undefined;
21
20
  }
22
- /**
23
- * Formats a Date to local ISO string (YYYY-MM-DDThh:mm:ss)
24
- */
25
- function toLocalDateString(date) {
26
- const pattern = "yyyy-MM-dd'T'HH:mm:ss";
27
- return format(date, pattern);
28
- }
29
21
  /**
30
22
  * Merges new grouped slots with existing ones
31
23
  * Slots with the same start time are merged
@@ -68,7 +60,6 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
68
60
  const viewModeSignal = signalsService.signal(ViewMode.CLASSIC);
69
61
  const startDateSignal = signalsService.signal(config.startDate || currentDate);
70
62
  const endDateSignal = signalsService.signal(config.endDate || getDefaultEndDate());
71
- const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
72
63
  const cursorSignal = signalsService.signal(undefined);
73
64
  /**
74
65
  * Helper: Get services from config or BookingService
@@ -86,7 +77,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
86
77
  const location = bookingService.location.get();
87
78
  const serviceSelections = bookingService.serviceSelections.get();
88
79
  const selectedStaffMemberIds = serviceSelections
89
- .map(({ resources }) => resources.map((r) => r._id))
80
+ .map((serviceSelection) => serviceSelection.resources.map((r) => r._id))
90
81
  .flat();
91
82
  const selectedLocation = location ? { _id: location.id } : null;
92
83
  // Merge config filters with BookingService selections (selections win)
@@ -114,9 +105,9 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
114
105
  try {
115
106
  const response = await fetchAvailability({
116
107
  services: selectedServices,
117
- fromLocalDate: toLocalDateString(startDateSignal.get()),
118
- toLocalDate: toLocalDateString(endDateSignal.get()),
119
- timeZone: timezoneSignal.get(),
108
+ fromLocalDate: startDateSignal.get(),
109
+ toLocalDate: endDateSignal.get(),
110
+ timeZone: bookingService.timezone.get(),
120
111
  viewMode: viewModeSignal.get(),
121
112
  cursor: cursorSignal.get(),
122
113
  ...filters,
@@ -312,16 +303,14 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
312
303
  if (!cursor || isLoadingSignal.get())
313
304
  return;
314
305
  const selectedServices = getServices();
315
- if (selectedServices.length === 0)
316
- return;
317
306
  const filters = getFilters();
318
307
  isLoadingSignal.set(true);
319
308
  try {
320
309
  const response = await fetchAvailability({
321
310
  services: selectedServices,
322
- fromLocalDate: toLocalDateString(startDateSignal.get()),
323
- toLocalDate: toLocalDateString(endDateSignal.get()),
324
- timeZone: timezoneSignal.get(),
311
+ fromLocalDate: startDateSignal.get(),
312
+ toLocalDate: endDateSignal.get(),
313
+ timeZone: bookingService.timezone.get(),
325
314
  cursor,
326
315
  viewMode: viewModeSignal.get(),
327
316
  ...filters,
@@ -357,17 +346,21 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
357
346
  endDateSignal.set(newEndDate);
358
347
  fetchTimeSlots();
359
348
  };
360
- /**
361
- * Action: Update timezone and refetch slots
362
- */
363
- const setTimezone = (newTimezone) => {
364
- timezoneSignal.set(newTimezone);
365
- fetchTimeSlots();
366
- };
367
349
  // Auto-fetch on initialization if no pre-loaded data provided
368
- if (!config.timeSlots && config.services && config.services.length > 0) {
350
+ if (!config.timeSlots) {
369
351
  fetchTimeSlots();
370
352
  }
353
+ // Subscribe to timezone changes from BookingService to auto-refetch
354
+ // NOTE: This should depend ONLY on bookingService.timezone.
355
+ let firstTimezoneEffectRun = true;
356
+ signalsService.effect(() => {
357
+ bookingService.timezone.get();
358
+ if (firstTimezoneEffectRun) {
359
+ firstTimezoneEffectRun = false;
360
+ return;
361
+ }
362
+ fetchTimeSlots();
363
+ });
371
364
  // Return service API
372
365
  return {
373
366
  // State Signals
@@ -379,7 +372,6 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
379
372
  // Configuration Signals (viewMode is internal only)
380
373
  startDate: startDateSignal,
381
374
  endDate: endDateSignal,
382
- timezone: timezoneSignal,
383
375
  // Actions
384
376
  actions: {
385
377
  selectSlot,
@@ -388,7 +380,6 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
388
380
  clearPreferences,
389
381
  loadMore,
390
382
  setDateRange,
391
- setTimezone,
392
383
  },
393
384
  };
394
385
  });
@@ -420,8 +411,8 @@ export async function loadTimeSlotListConfig(options) {
420
411
  try {
421
412
  const response = await fetchAvailability({
422
413
  services: options.services || [],
423
- fromLocalDate: toLocalDateString(startDate),
424
- toLocalDate: toLocalDateString(endDate),
414
+ fromLocalDate: startDate,
415
+ toLocalDate: endDate,
425
416
  timeZone: options.timezone ?? getClientTimezone(),
426
417
  ...options.filters,
427
418
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-bookings",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -70,5 +70,5 @@
70
70
  "groupId": "com.wixpress.headless-components"
71
71
  }
72
72
  },
73
- "falconPackageHash": "d2842c53f889bbbdfd049c74e533eef301d158412ec519b08c80c87c"
73
+ "falconPackageHash": "bd78009e38ad24f8630606b907ce144a0e0d1df6917d8dcb25994523"
74
74
  }