@wix/headless-bookings 0.0.63 → 0.0.65

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.
@@ -12,9 +12,10 @@ import type { Service } from '@wix/auto_sdk_bookings_services';
12
12
  */
13
13
  export interface TimeSlotListRootRenderProps {
14
14
  /** Function to navigate to the booking form with the selected time slot */
15
- navigateToBookingForm: ({ timeSlot, service, }: {
15
+ navigateToBookingForm: ({ timeSlot, service, timezone, }: {
16
16
  timeSlot?: ServiceSelectionTimeSlot;
17
17
  service?: Service;
18
+ timezone?: string;
18
19
  }) => Promise<void>;
19
20
  }
20
21
  /**
@@ -44,9 +45,10 @@ export interface RootProps {
44
45
  * </Root>
45
46
  * ```
46
47
  */
47
- navigateToBookingForm: ({ timeSlot, service, }: {
48
+ navigateToBookingForm: ({ timeSlot, service, timezone, }: {
48
49
  timeSlot?: ServiceSelectionTimeSlot;
49
50
  service?: Service;
51
+ timezone?: string;
50
52
  }) => Promise<void>;
51
53
  }
52
54
  /**
@@ -46,11 +46,16 @@ function RootContent({ navigateToBookingForm: providedNavigateToBookingForm, chi
46
46
  catch {
47
47
  bookingService = null;
48
48
  }
49
- const navigateToBookingForm = async ({ timeSlot, service, }) => {
49
+ const navigateToBookingForm = async ({ timeSlot, service, timezone, }) => {
50
50
  let resolvedTimeSlot = timeSlot;
51
51
  let resolvedService = service;
52
- if ((!resolvedTimeSlot || !resolvedService) && bookingService) {
52
+ let resolvedTimezone = timezone;
53
+ if ((!resolvedTimeSlot || !resolvedService || !resolvedTimezone) &&
54
+ bookingService) {
53
55
  const serviceSelections = bookingService.serviceSelections.get();
56
+ if (!resolvedTimezone) {
57
+ resolvedTimezone = bookingService.timezone.get();
58
+ }
54
59
  if (serviceSelections.length > 0) {
55
60
  const firstSelection = serviceSelections[0];
56
61
  if (firstSelection) {
@@ -68,6 +73,7 @@ function RootContent({ navigateToBookingForm: providedNavigateToBookingForm, chi
68
73
  return providedNavigateToBookingForm({
69
74
  timeSlot: resolvedTimeSlot,
70
75
  service: resolvedService,
76
+ timezone: resolvedTimezone,
71
77
  });
72
78
  };
73
79
  return _jsx(_Fragment, { children: children({ navigateToBookingForm }) });
@@ -52,21 +52,23 @@ export declare const Timezone: React.ForwardRefExoticComponent<TimezoneProps & R
52
52
  */
53
53
  export interface RootProps {
54
54
  children: React.ReactNode | ((data: {
55
- navigateToBookingForm: ({ timeSlot, service, }: {
55
+ navigateToBookingForm: ({ timeSlot, service, timezone, }: {
56
56
  timeSlot?: ServiceSelectionTimeSlot;
57
57
  service?: Service;
58
+ timezone?: string;
58
59
  }) => Promise<void>;
59
60
  }) => React.ReactNode);
60
61
  config: TimeSlotListServiceConfig;
61
62
  /**
62
63
  * Function to navigate to the booking form.
63
- * The timeSlot and service parameters are optional - if not provided,
64
+ * The timeSlot, service, and timezone parameters are optional - if not provided,
64
65
  * BookingService will be used to get them from the current service selections.
65
66
  * If not provided, will use the default navigation utility.
66
67
  */
67
- navigateToBookingForm?: ({ timeSlot, service, }: {
68
+ navigateToBookingForm?: ({ timeSlot, service, timezone, }: {
68
69
  timeSlot?: ServiceSelectionTimeSlot;
69
70
  service?: Service;
71
+ timezone?: string;
70
72
  }) => Promise<void>;
71
73
  className?: string;
72
74
  variant?: ListVariant;
@@ -87,11 +87,11 @@ Timezone.displayName = 'TimeSlotList.Timezone';
87
87
  export const Root = React.forwardRef((props, ref) => {
88
88
  const { children, config, className, variant, navigateToBookingForm: providedNavigateToBookingForm, } = props;
89
89
  const navigateToBookingForm = providedNavigateToBookingForm ||
90
- (async ({ timeSlot, service, }) => {
90
+ (async ({ timeSlot, service, timezone, }) => {
91
91
  if (!timeSlot || !service) {
92
92
  throw new Error('timeSlot and service are required when using default navigation. Either provide them or ensure BookingService has service selections.');
93
93
  }
94
- return defaultNavigateToBookingForm({ timeSlot, service });
94
+ return defaultNavigateToBookingForm({ timeSlot, service, timezone });
95
95
  });
96
96
  return (_jsx(CoreTimeSlotList.Root, { config: config, navigateToBookingForm: navigateToBookingForm, children: (coreData) => (_jsx(RootContent, { className: className, ref: ref, variant: variant, children: typeof children === 'function'
97
97
  ? children({
@@ -71,6 +71,16 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
71
71
  const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
72
72
  const formSubmissionSignal = signalsService.signal(config.formSubmission || null);
73
73
  // ========================================================================
74
+ // Helpers
75
+ // ========================================================================
76
+ function getTargetItem(instanceId) {
77
+ const current = serviceSelectionsSignal.get();
78
+ const targetId = instanceId ?? current[0]?.instanceId;
79
+ if (!targetId)
80
+ return undefined;
81
+ return current.find((item) => item.instanceId === targetId);
82
+ }
83
+ // ========================================================================
74
84
  // Actions Implementation
75
85
  // ========================================================================
76
86
  const actions = {
@@ -116,45 +126,23 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
116
126
  serviceSelectionsSignal.set([]);
117
127
  },
118
128
  setResource(resource, instanceId) {
119
- const current = serviceSelectionsSignal.get();
120
- // If no instanceId provided, use the first item
121
- const targetId = instanceId ?? current[0]?.instanceId;
122
- if (!targetId)
129
+ const targetItem = getTargetItem(instanceId);
130
+ if (!targetItem)
123
131
  return;
124
- const updated = current.map((item) => {
125
- if (item.instanceId !== targetId)
126
- return item;
127
- const newResources = item.resources.filter((r) => r.type !== resource.type);
128
- newResources.push(resource);
129
- return { ...item, resources: newResources };
130
- });
131
- serviceSelectionsSignal.set(updated);
132
+ const newResources = targetItem.resources
133
+ .filter((r) => r.type !== resource.type)
134
+ .concat(resource);
135
+ this.updateItem({ resources: newResources, timeSlot: undefined }, targetItem.instanceId);
132
136
  },
133
137
  getResourceByType(resourceType, instanceId) {
134
- const current = serviceSelectionsSignal.get();
135
- // If no instanceId provided, use the first item
136
- const targetId = instanceId ?? current[0]?.instanceId;
137
- if (!targetId)
138
- return undefined;
139
- const targetItem = current.find((item) => item.instanceId === targetId);
140
- if (!targetItem)
141
- return undefined;
142
- return targetItem.resources.find((r) => r.type === resourceType);
138
+ return getTargetItem(instanceId)?.resources.find((r) => r.type === resourceType);
143
139
  },
144
140
  clearResourceByType(resourceType, instanceId) {
145
- const current = serviceSelectionsSignal.get();
146
- // If no instanceId provided, use the first item
147
- const targetId = instanceId ?? current[0]?.instanceId;
148
- if (!targetId)
141
+ const targetItem = getTargetItem(instanceId);
142
+ if (!targetItem)
149
143
  return;
150
- const updated = current.map((item) => {
151
- if (item.instanceId !== targetId)
152
- return item;
153
- // Filter out the resource with matching type
154
- const newResources = item.resources.filter((r) => r.type !== resourceType);
155
- return { ...item, resources: newResources };
156
- });
157
- serviceSelectionsSignal.set(updated);
144
+ const newResources = targetItem.resources.filter((r) => r.type !== resourceType);
145
+ this.updateItem({ resources: newResources }, targetItem.instanceId);
158
146
  },
159
147
  // Location actions
160
148
  setLocation(location) {
@@ -178,9 +166,9 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
178
166
  },
179
167
  // Clear all
180
168
  clearAll() {
181
- serviceSelectionsSignal.set([]);
182
- locationSignal.set(null);
183
- formSubmissionSignal.set(null);
169
+ this.clearItems();
170
+ this.clearLocation();
171
+ this.clearFormSubmission();
184
172
  },
185
173
  // Book action - collects data from signals and delegates to executeBookAction
186
174
  // Loading/error state management is handled in Book.tsx component
@@ -1,7 +1,17 @@
1
1
  import type { ServiceSelectionTimeSlot } from '../services/booking/booking.js';
2
2
  import type { Service } from '@wix/auto_sdk_bookings_services';
3
3
  export declare function navigateToCheckout(checkoutId: string): Promise<void>;
4
- export declare function navigateToBookingForm({ timeSlot, service, }: {
4
+ /**
5
+ * Cleans a redirect URL by extracting the redirectUrl param and removing:
6
+ * - The 'origin' query parameter
7
+ * - Any query parameters with empty values
8
+ *
9
+ * @param fullUrl - The full URL containing the redirectUrl query parameter
10
+ * @returns The cleaned redirect URL
11
+ */
12
+ export declare function cleanRedirectUrl(fullUrl: string): string;
13
+ export declare function navigateToBookingForm({ timeSlot, service, timezone, }: {
5
14
  timeSlot: ServiceSelectionTimeSlot;
6
15
  service: Service;
16
+ timezone?: string;
7
17
  }): Promise<void>;
@@ -27,7 +27,32 @@ function mapTimeSlotLocationToBookingLocation(timeSlotLocationType) {
27
27
  return BookingsLocationType.UNDEFINED;
28
28
  }
29
29
  }
30
- export async function navigateToBookingForm({ timeSlot, service, }) {
30
+ /**
31
+ * Cleans a redirect URL by extracting the redirectUrl param and removing:
32
+ * - The 'origin' query parameter
33
+ * - Any query parameters with empty values
34
+ *
35
+ * @param fullUrl - The full URL containing the redirectUrl query parameter
36
+ * @returns The cleaned redirect URL
37
+ */
38
+ export function cleanRedirectUrl(fullUrl) {
39
+ const url = new URL(fullUrl);
40
+ const redirectUrl = url.searchParams.get('redirectUrl');
41
+ if (!redirectUrl) {
42
+ return fullUrl;
43
+ }
44
+ const redirectParsed = new URL(redirectUrl);
45
+ // Remove origin param
46
+ redirectParsed.searchParams.delete('origin');
47
+ // Remove empty params
48
+ for (const [key, value] of [...redirectParsed.searchParams.entries()]) {
49
+ if (!value) {
50
+ redirectParsed.searchParams.delete(key);
51
+ }
52
+ }
53
+ return redirectParsed.toString();
54
+ }
55
+ export async function navigateToBookingForm({ timeSlot, service, timezone, }) {
31
56
  if (!timeSlot) {
32
57
  throw new Error('No time slot provided');
33
58
  }
@@ -36,30 +61,36 @@ export async function navigateToBookingForm({ timeSlot, service, }) {
36
61
  }
37
62
  // Get the first resource (staff member) if available
38
63
  const selectedResource = timeSlot.resources?.[0];
64
+ // Destructure to exclude properties we handle separately (location has incompatible types)
65
+ const { location: _location, resources: _resources, ...restTimeSlot } = timeSlot;
66
+ // Build slot object, only including optional properties when they have values
67
+ const slot = {
68
+ ...restTimeSlot,
69
+ scheduleId: timeSlot.scheduleId,
70
+ startDate: timeSlot.startDate,
71
+ endDate: timeSlot.endDate,
72
+ ...(service._id && { serviceId: service._id }),
73
+ ...(timezone && { timezone }),
74
+ ...(selectedResource && {
75
+ resource: {
76
+ _id: selectedResource._id,
77
+ name: selectedResource.name,
78
+ },
79
+ }),
80
+ ...(timeSlot.location && {
81
+ location: {
82
+ _id: timeSlot.location._id,
83
+ name: timeSlot.location.name,
84
+ formattedAddress: timeSlot.location.formattedAddress,
85
+ locationType: mapTimeSlotLocationToBookingLocation(timeSlot.location.locationType),
86
+ },
87
+ }),
88
+ };
39
89
  const { redirectSession } = await redirects.createRedirectSession({
40
90
  bookingsCheckout: {
41
91
  slotAvailability: {
42
92
  bookable: true,
43
- slot: {
44
- ...timeSlot,
45
- serviceId: service._id ?? undefined,
46
- scheduleId: timeSlot.scheduleId,
47
- startDate: timeSlot.startDate,
48
- endDate: timeSlot.endDate,
49
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
50
- resource: selectedResource
51
- ? {
52
- _id: selectedResource._id,
53
- name: selectedResource.name,
54
- }
55
- : undefined,
56
- location: {
57
- _id: timeSlot.location?._id,
58
- name: timeSlot.location?.name,
59
- formattedAddress: timeSlot.location?.formattedAddress,
60
- locationType: mapTimeSlotLocationToBookingLocation(timeSlot.location?.locationType),
61
- },
62
- },
93
+ slot,
63
94
  },
64
95
  },
65
96
  callbacks: {
@@ -67,7 +98,7 @@ export async function navigateToBookingForm({ timeSlot, service, }) {
67
98
  },
68
99
  });
69
100
  if (redirectSession?.fullUrl) {
70
- window.location.href = redirectSession.fullUrl;
101
+ window.location.href = cleanRedirectUrl(redirectSession.fullUrl);
71
102
  }
72
103
  else {
73
104
  throw new Error('Failed to create redirect session');
@@ -12,9 +12,10 @@ import type { Service } from '@wix/auto_sdk_bookings_services';
12
12
  */
13
13
  export interface TimeSlotListRootRenderProps {
14
14
  /** Function to navigate to the booking form with the selected time slot */
15
- navigateToBookingForm: ({ timeSlot, service, }: {
15
+ navigateToBookingForm: ({ timeSlot, service, timezone, }: {
16
16
  timeSlot?: ServiceSelectionTimeSlot;
17
17
  service?: Service;
18
+ timezone?: string;
18
19
  }) => Promise<void>;
19
20
  }
20
21
  /**
@@ -44,9 +45,10 @@ export interface RootProps {
44
45
  * </Root>
45
46
  * ```
46
47
  */
47
- navigateToBookingForm: ({ timeSlot, service, }: {
48
+ navigateToBookingForm: ({ timeSlot, service, timezone, }: {
48
49
  timeSlot?: ServiceSelectionTimeSlot;
49
50
  service?: Service;
51
+ timezone?: string;
50
52
  }) => Promise<void>;
51
53
  }
52
54
  /**
@@ -46,11 +46,16 @@ function RootContent({ navigateToBookingForm: providedNavigateToBookingForm, chi
46
46
  catch {
47
47
  bookingService = null;
48
48
  }
49
- const navigateToBookingForm = async ({ timeSlot, service, }) => {
49
+ const navigateToBookingForm = async ({ timeSlot, service, timezone, }) => {
50
50
  let resolvedTimeSlot = timeSlot;
51
51
  let resolvedService = service;
52
- if ((!resolvedTimeSlot || !resolvedService) && bookingService) {
52
+ let resolvedTimezone = timezone;
53
+ if ((!resolvedTimeSlot || !resolvedService || !resolvedTimezone) &&
54
+ bookingService) {
53
55
  const serviceSelections = bookingService.serviceSelections.get();
56
+ if (!resolvedTimezone) {
57
+ resolvedTimezone = bookingService.timezone.get();
58
+ }
54
59
  if (serviceSelections.length > 0) {
55
60
  const firstSelection = serviceSelections[0];
56
61
  if (firstSelection) {
@@ -68,6 +73,7 @@ function RootContent({ navigateToBookingForm: providedNavigateToBookingForm, chi
68
73
  return providedNavigateToBookingForm({
69
74
  timeSlot: resolvedTimeSlot,
70
75
  service: resolvedService,
76
+ timezone: resolvedTimezone,
71
77
  });
72
78
  };
73
79
  return _jsx(_Fragment, { children: children({ navigateToBookingForm }) });
@@ -52,21 +52,23 @@ export declare const Timezone: React.ForwardRefExoticComponent<TimezoneProps & R
52
52
  */
53
53
  export interface RootProps {
54
54
  children: React.ReactNode | ((data: {
55
- navigateToBookingForm: ({ timeSlot, service, }: {
55
+ navigateToBookingForm: ({ timeSlot, service, timezone, }: {
56
56
  timeSlot?: ServiceSelectionTimeSlot;
57
57
  service?: Service;
58
+ timezone?: string;
58
59
  }) => Promise<void>;
59
60
  }) => React.ReactNode);
60
61
  config: TimeSlotListServiceConfig;
61
62
  /**
62
63
  * Function to navigate to the booking form.
63
- * The timeSlot and service parameters are optional - if not provided,
64
+ * The timeSlot, service, and timezone parameters are optional - if not provided,
64
65
  * BookingService will be used to get them from the current service selections.
65
66
  * If not provided, will use the default navigation utility.
66
67
  */
67
- navigateToBookingForm?: ({ timeSlot, service, }: {
68
+ navigateToBookingForm?: ({ timeSlot, service, timezone, }: {
68
69
  timeSlot?: ServiceSelectionTimeSlot;
69
70
  service?: Service;
71
+ timezone?: string;
70
72
  }) => Promise<void>;
71
73
  className?: string;
72
74
  variant?: ListVariant;
@@ -87,11 +87,11 @@ Timezone.displayName = 'TimeSlotList.Timezone';
87
87
  export const Root = React.forwardRef((props, ref) => {
88
88
  const { children, config, className, variant, navigateToBookingForm: providedNavigateToBookingForm, } = props;
89
89
  const navigateToBookingForm = providedNavigateToBookingForm ||
90
- (async ({ timeSlot, service, }) => {
90
+ (async ({ timeSlot, service, timezone, }) => {
91
91
  if (!timeSlot || !service) {
92
92
  throw new Error('timeSlot and service are required when using default navigation. Either provide them or ensure BookingService has service selections.');
93
93
  }
94
- return defaultNavigateToBookingForm({ timeSlot, service });
94
+ return defaultNavigateToBookingForm({ timeSlot, service, timezone });
95
95
  });
96
96
  return (_jsx(CoreTimeSlotList.Root, { config: config, navigateToBookingForm: navigateToBookingForm, children: (coreData) => (_jsx(RootContent, { className: className, ref: ref, variant: variant, children: typeof children === 'function'
97
97
  ? children({
@@ -71,6 +71,16 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
71
71
  const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
72
72
  const formSubmissionSignal = signalsService.signal(config.formSubmission || null);
73
73
  // ========================================================================
74
+ // Helpers
75
+ // ========================================================================
76
+ function getTargetItem(instanceId) {
77
+ const current = serviceSelectionsSignal.get();
78
+ const targetId = instanceId ?? current[0]?.instanceId;
79
+ if (!targetId)
80
+ return undefined;
81
+ return current.find((item) => item.instanceId === targetId);
82
+ }
83
+ // ========================================================================
74
84
  // Actions Implementation
75
85
  // ========================================================================
76
86
  const actions = {
@@ -116,45 +126,23 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
116
126
  serviceSelectionsSignal.set([]);
117
127
  },
118
128
  setResource(resource, instanceId) {
119
- const current = serviceSelectionsSignal.get();
120
- // If no instanceId provided, use the first item
121
- const targetId = instanceId ?? current[0]?.instanceId;
122
- if (!targetId)
129
+ const targetItem = getTargetItem(instanceId);
130
+ if (!targetItem)
123
131
  return;
124
- const updated = current.map((item) => {
125
- if (item.instanceId !== targetId)
126
- return item;
127
- const newResources = item.resources.filter((r) => r.type !== resource.type);
128
- newResources.push(resource);
129
- return { ...item, resources: newResources };
130
- });
131
- serviceSelectionsSignal.set(updated);
132
+ const newResources = targetItem.resources
133
+ .filter((r) => r.type !== resource.type)
134
+ .concat(resource);
135
+ this.updateItem({ resources: newResources, timeSlot: undefined }, targetItem.instanceId);
132
136
  },
133
137
  getResourceByType(resourceType, instanceId) {
134
- const current = serviceSelectionsSignal.get();
135
- // If no instanceId provided, use the first item
136
- const targetId = instanceId ?? current[0]?.instanceId;
137
- if (!targetId)
138
- return undefined;
139
- const targetItem = current.find((item) => item.instanceId === targetId);
140
- if (!targetItem)
141
- return undefined;
142
- return targetItem.resources.find((r) => r.type === resourceType);
138
+ return getTargetItem(instanceId)?.resources.find((r) => r.type === resourceType);
143
139
  },
144
140
  clearResourceByType(resourceType, instanceId) {
145
- const current = serviceSelectionsSignal.get();
146
- // If no instanceId provided, use the first item
147
- const targetId = instanceId ?? current[0]?.instanceId;
148
- if (!targetId)
141
+ const targetItem = getTargetItem(instanceId);
142
+ if (!targetItem)
149
143
  return;
150
- const updated = current.map((item) => {
151
- if (item.instanceId !== targetId)
152
- return item;
153
- // Filter out the resource with matching type
154
- const newResources = item.resources.filter((r) => r.type !== resourceType);
155
- return { ...item, resources: newResources };
156
- });
157
- serviceSelectionsSignal.set(updated);
144
+ const newResources = targetItem.resources.filter((r) => r.type !== resourceType);
145
+ this.updateItem({ resources: newResources }, targetItem.instanceId);
158
146
  },
159
147
  // Location actions
160
148
  setLocation(location) {
@@ -178,9 +166,9 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
178
166
  },
179
167
  // Clear all
180
168
  clearAll() {
181
- serviceSelectionsSignal.set([]);
182
- locationSignal.set(null);
183
- formSubmissionSignal.set(null);
169
+ this.clearItems();
170
+ this.clearLocation();
171
+ this.clearFormSubmission();
184
172
  },
185
173
  // Book action - collects data from signals and delegates to executeBookAction
186
174
  // Loading/error state management is handled in Book.tsx component
@@ -1,7 +1,17 @@
1
1
  import type { ServiceSelectionTimeSlot } from '../services/booking/booking.js';
2
2
  import type { Service } from '@wix/auto_sdk_bookings_services';
3
3
  export declare function navigateToCheckout(checkoutId: string): Promise<void>;
4
- export declare function navigateToBookingForm({ timeSlot, service, }: {
4
+ /**
5
+ * Cleans a redirect URL by extracting the redirectUrl param and removing:
6
+ * - The 'origin' query parameter
7
+ * - Any query parameters with empty values
8
+ *
9
+ * @param fullUrl - The full URL containing the redirectUrl query parameter
10
+ * @returns The cleaned redirect URL
11
+ */
12
+ export declare function cleanRedirectUrl(fullUrl: string): string;
13
+ export declare function navigateToBookingForm({ timeSlot, service, timezone, }: {
5
14
  timeSlot: ServiceSelectionTimeSlot;
6
15
  service: Service;
16
+ timezone?: string;
7
17
  }): Promise<void>;
@@ -27,7 +27,32 @@ function mapTimeSlotLocationToBookingLocation(timeSlotLocationType) {
27
27
  return BookingsLocationType.UNDEFINED;
28
28
  }
29
29
  }
30
- export async function navigateToBookingForm({ timeSlot, service, }) {
30
+ /**
31
+ * Cleans a redirect URL by extracting the redirectUrl param and removing:
32
+ * - The 'origin' query parameter
33
+ * - Any query parameters with empty values
34
+ *
35
+ * @param fullUrl - The full URL containing the redirectUrl query parameter
36
+ * @returns The cleaned redirect URL
37
+ */
38
+ export function cleanRedirectUrl(fullUrl) {
39
+ const url = new URL(fullUrl);
40
+ const redirectUrl = url.searchParams.get('redirectUrl');
41
+ if (!redirectUrl) {
42
+ return fullUrl;
43
+ }
44
+ const redirectParsed = new URL(redirectUrl);
45
+ // Remove origin param
46
+ redirectParsed.searchParams.delete('origin');
47
+ // Remove empty params
48
+ for (const [key, value] of [...redirectParsed.searchParams.entries()]) {
49
+ if (!value) {
50
+ redirectParsed.searchParams.delete(key);
51
+ }
52
+ }
53
+ return redirectParsed.toString();
54
+ }
55
+ export async function navigateToBookingForm({ timeSlot, service, timezone, }) {
31
56
  if (!timeSlot) {
32
57
  throw new Error('No time slot provided');
33
58
  }
@@ -36,30 +61,36 @@ export async function navigateToBookingForm({ timeSlot, service, }) {
36
61
  }
37
62
  // Get the first resource (staff member) if available
38
63
  const selectedResource = timeSlot.resources?.[0];
64
+ // Destructure to exclude properties we handle separately (location has incompatible types)
65
+ const { location: _location, resources: _resources, ...restTimeSlot } = timeSlot;
66
+ // Build slot object, only including optional properties when they have values
67
+ const slot = {
68
+ ...restTimeSlot,
69
+ scheduleId: timeSlot.scheduleId,
70
+ startDate: timeSlot.startDate,
71
+ endDate: timeSlot.endDate,
72
+ ...(service._id && { serviceId: service._id }),
73
+ ...(timezone && { timezone }),
74
+ ...(selectedResource && {
75
+ resource: {
76
+ _id: selectedResource._id,
77
+ name: selectedResource.name,
78
+ },
79
+ }),
80
+ ...(timeSlot.location && {
81
+ location: {
82
+ _id: timeSlot.location._id,
83
+ name: timeSlot.location.name,
84
+ formattedAddress: timeSlot.location.formattedAddress,
85
+ locationType: mapTimeSlotLocationToBookingLocation(timeSlot.location.locationType),
86
+ },
87
+ }),
88
+ };
39
89
  const { redirectSession } = await redirects.createRedirectSession({
40
90
  bookingsCheckout: {
41
91
  slotAvailability: {
42
92
  bookable: true,
43
- slot: {
44
- ...timeSlot,
45
- serviceId: service._id ?? undefined,
46
- scheduleId: timeSlot.scheduleId,
47
- startDate: timeSlot.startDate,
48
- endDate: timeSlot.endDate,
49
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
50
- resource: selectedResource
51
- ? {
52
- _id: selectedResource._id,
53
- name: selectedResource.name,
54
- }
55
- : undefined,
56
- location: {
57
- _id: timeSlot.location?._id,
58
- name: timeSlot.location?.name,
59
- formattedAddress: timeSlot.location?.formattedAddress,
60
- locationType: mapTimeSlotLocationToBookingLocation(timeSlot.location?.locationType),
61
- },
62
- },
93
+ slot,
63
94
  },
64
95
  },
65
96
  callbacks: {
@@ -67,7 +98,7 @@ export async function navigateToBookingForm({ timeSlot, service, }) {
67
98
  },
68
99
  });
69
100
  if (redirectSession?.fullUrl) {
70
- window.location.href = redirectSession.fullUrl;
101
+ window.location.href = cleanRedirectUrl(redirectSession.fullUrl);
71
102
  }
72
103
  else {
73
104
  throw new Error('Failed to create redirect session');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-bookings",
3
- "version": "0.0.63",
3
+ "version": "0.0.65",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -72,5 +72,5 @@
72
72
  "groupId": "com.wixpress.headless-components"
73
73
  }
74
74
  },
75
- "falconPackageHash": "96083b204b0065686724c6e3b5716295e35a12be6db784974a6c924d"
75
+ "falconPackageHash": "a7381beae4b122c7bdbd9d7b7f4079f31688f2a11cd3c6d246e683c5"
76
76
  }