@wix/headless-bookings 0.0.56 → 0.0.58

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 (39) hide show
  1. package/cjs/dist/react/booking/Booking.js +1 -1
  2. package/cjs/dist/react/booking-form/BookingForm.d.ts +117 -17
  3. package/cjs/dist/react/booking-form/BookingForm.js +149 -15
  4. package/cjs/dist/react/core/booking/Booking.d.ts +3 -3
  5. package/cjs/dist/react/core/booking/Booking.js +2 -2
  6. package/cjs/dist/react/core/booking-form/BookingForm.d.ts +112 -10
  7. package/cjs/dist/react/core/booking-form/BookingForm.js +204 -64
  8. package/cjs/dist/react/core/payment/Payment.d.ts +84 -5
  9. package/cjs/dist/react/core/payment/Payment.js +102 -4
  10. package/cjs/dist/react/payment/Payment.d.ts +64 -58
  11. package/cjs/dist/react/payment/Payment.js +66 -80
  12. package/cjs/dist/services/booking-form/booking-form.d.ts +22 -13
  13. package/cjs/dist/services/booking-form/booking-form.js +67 -25
  14. package/cjs/dist/services/booking-form/utils.d.ts +52 -0
  15. package/cjs/dist/services/booking-form/utils.js +47 -0
  16. package/cjs/dist/services/index.d.ts +1 -1
  17. package/cjs/dist/services/payment/payment.d.ts +4 -4
  18. package/cjs/dist/services/payment/payment.def.d.ts +4 -4
  19. package/cjs/dist/services/payment/payment.js +26 -18
  20. package/dist/react/booking/Booking.js +1 -1
  21. package/dist/react/booking-form/BookingForm.d.ts +117 -17
  22. package/dist/react/booking-form/BookingForm.js +149 -15
  23. package/dist/react/core/booking/Booking.d.ts +3 -3
  24. package/dist/react/core/booking/Booking.js +2 -2
  25. package/dist/react/core/booking-form/BookingForm.d.ts +112 -10
  26. package/dist/react/core/booking-form/BookingForm.js +204 -64
  27. package/dist/react/core/payment/Payment.d.ts +84 -5
  28. package/dist/react/core/payment/Payment.js +102 -4
  29. package/dist/react/payment/Payment.d.ts +64 -58
  30. package/dist/react/payment/Payment.js +66 -80
  31. package/dist/services/booking-form/booking-form.d.ts +22 -13
  32. package/dist/services/booking-form/booking-form.js +67 -25
  33. package/dist/services/booking-form/utils.d.ts +52 -0
  34. package/dist/services/booking-form/utils.js +47 -0
  35. package/dist/services/index.d.ts +1 -1
  36. package/dist/services/payment/payment.d.ts +4 -4
  37. package/dist/services/payment/payment.def.d.ts +4 -4
  38. package/dist/services/payment/payment.js +26 -18
  39. package/package.json +3 -3
@@ -10,6 +10,8 @@
10
10
  import { defineService, implementService } from '@wix/services-definitions';
11
11
  import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
12
12
  import { FormServiceDefinition } from '@wix/headless-forms/services';
13
+ import { BookingServiceDefinition, } from '../booking/booking.js';
14
+ import { hasForm, extractFormIdFromConfig } from './utils.js';
13
15
  // ============================================================================
14
16
  // Constants
15
17
  // ============================================================================
@@ -18,22 +20,25 @@ import { FormServiceDefinition } from '@wix/headless-forms/services';
18
20
  * All booking forms must belong to this namespace.
19
21
  */
20
22
  export const BOOKING_FORM_NAMESPACE = 'wix.bookings.v2.bookings';
21
- // ============================================================================
22
- // Helper Types
23
- // ============================================================================
24
- /**
25
- * Type guard to check if config has formId
26
- */
27
- export function hasFormId(config) {
28
- return 'formId' in config;
29
- }
30
23
  /**
31
- * Type guard to check if config has pre-loaded form
24
+ * Error thrown when BookingFormService cannot be initialized due to missing configuration.
25
+ * This occurs when:
26
+ * - No config (formId or form) was provided
27
+ * - AND the component is not wrapped with Booking.Root (no BookingService available)
32
28
  */
33
- export function hasForm(config) {
34
- return 'form' in config;
29
+ export class BookingFormConfigurationError extends Error {
30
+ constructor(message) {
31
+ super(message ||
32
+ 'BookingFormService: Cannot initialize - no formId or form provided in config and no BookingService available. ' +
33
+ 'Either provide a formId/form in the config, or wrap with Booking.Root.');
34
+ this.name = 'BookingFormConfigurationError';
35
+ }
35
36
  }
36
37
  // ============================================================================
38
+ // Re-export utilities
39
+ // ============================================================================
40
+ export { hasFormId, hasForm, extractFormIdFromForm, extractFormIdFromConfig, } from './utils.js';
41
+ // ============================================================================
37
42
  // Service Definition
38
43
  // ============================================================================
39
44
  /**
@@ -64,20 +69,21 @@ function validateBookingFormNamespace(form) {
64
69
  }
65
70
  }
66
71
  /**
67
- * Extracts the form ID from either config type.
68
- * @param config The service config
72
+ * Extracts the form ID from either config type or from services.
73
+ * @param config The service config (optional)
74
+ * @param services The list of services to extract formId from if config is not provided
69
75
  * @returns The form ID
70
76
  */
71
- function extractFormId(config) {
72
- if ('formId' in config) {
73
- return config.formId;
77
+ function extractFormId(config, services) {
78
+ // Try to extract from config first
79
+ let formId = extractFormIdFromConfig(config);
80
+ // Fallback to extracting from services
81
+ if (!formId) {
82
+ formId = services.find((s) => s.form?._id)?.form?._id;
74
83
  }
75
- // Extract from form object - forms typically have an 'id' or '_id' property
76
- const form = config.form;
77
- const formId = form.id ||
78
- form._id;
79
84
  if (!formId) {
80
- throw new Error('BookingFormService: Could not extract form ID from provided form object.');
85
+ throw new BookingFormConfigurationError('BookingFormService: no formId found in config or services. ' +
86
+ 'Either provide a formId in the config, or ensure Booking.Root has selected services with a form.');
81
87
  }
82
88
  return formId;
83
89
  }
@@ -120,14 +126,38 @@ function extractFormId(config) {
120
126
  export const BookingFormService = implementService.withConfig()(BookingFormServiceDefinition, ({ getService, config }) => {
121
127
  const signalsService = getService(SignalsServiceDefinition);
122
128
  const formService = getService(FormServiceDefinition);
129
+ // BookingService is optional - may not be available if not wrapped with Booking.Root
130
+ let bookingService = null;
131
+ try {
132
+ bookingService = getService(BookingServiceDefinition);
133
+ }
134
+ catch {
135
+ // BookingService not available - that's ok if formId was provided explicitly
136
+ bookingService = null;
137
+ }
138
+ const getServices = () => {
139
+ if (!bookingService) {
140
+ return [];
141
+ }
142
+ const serviceSelections = bookingService.serviceSelections.peek();
143
+ return serviceSelections.length
144
+ ? serviceSelections.map((s) => s.service)
145
+ : [];
146
+ };
147
+ // Validate that we have either config or bookingService with serviceSelections
148
+ // If neither is available, we can't extract formId and can't initialize the service
149
+ const hasServiceSelections = getServices().length > 0;
150
+ if (!config && (!bookingService || !hasServiceSelections)) {
151
+ throw new BookingFormConfigurationError();
152
+ }
123
153
  // Determine if we have a pre-loaded form
124
- const hasPreloadedForm = 'form' in config;
154
+ const hasPreloadedForm = hasForm(config);
125
155
  // If form is provided, validate namespace
126
156
  if (hasPreloadedForm) {
127
157
  validateBookingFormNamespace(config.form);
128
158
  }
129
159
  // Extract formId from config (either directly or from form object)
130
- const formIdValue = extractFormId(config);
160
+ const formIdValue = extractFormId(config, getServices());
131
161
  // Initialize formSubmission signal (stores form data to be submitted)
132
162
  const formSubmission = signalsService.signal(null);
133
163
  // Initialize submissionId signal (for future use)
@@ -135,7 +165,7 @@ export const BookingFormService = implementService.withConfig()(BookingFormServi
135
165
  // formId signal - readonly
136
166
  const formId = signalsService.signal(formIdValue);
137
167
  // form signal - contains pre-loaded form if provided
138
- const form = signalsService.signal(hasPreloadedForm ? config.form : null);
168
+ const form = signalsService.signal(hasForm(config) ? config.form : null);
139
169
  // Internal: Store validate callback (set by core component, not exposed publicly)
140
170
  let _validateCallback = null;
141
171
  // Internal: Register validate callback (called by core component via closure)
@@ -155,6 +185,18 @@ export const BookingFormService = implementService.withConfig()(BookingFormServi
155
185
  }
156
186
  return _validateCallback();
157
187
  };
188
+ // Auto-sync formValues to BookingService.formSubmission (client-side only)
189
+ // This keeps Booking.Data.formSubmission updated as the user types
190
+ // and ensures the book action gets the latest form values
191
+ // Only set up sync if BookingService is available
192
+ if (bookingService) {
193
+ signalsService.effect(() => {
194
+ const formValues = formService.formValuesSignal.get();
195
+ if (formValues && Object.keys(formValues).length > 0) {
196
+ bookingService.actions.setFormSubmission(formValues);
197
+ }
198
+ });
199
+ }
158
200
  // Public API (what's exposed in BookingFormServiceAPI interface)
159
201
  const publicApi = {
160
202
  formValues: formService.formValuesSignal,
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Utility functions for BookingForm
3
+ *
4
+ * Note: This file should not import from booking-form.ts to avoid circular dependencies.
5
+ * Type guards use generic types that will be compatible with BookingFormServiceConfig.
6
+ */
7
+ import { forms } from '@wix/forms';
8
+ /**
9
+ * Type guard to check if config has formId
10
+ */
11
+ export declare function hasFormId<T extends {
12
+ formId: string;
13
+ serviceIds?: string[];
14
+ } | {
15
+ form: forms.Form;
16
+ }>(config: T | undefined): config is T & {
17
+ formId: string;
18
+ serviceIds?: string[];
19
+ };
20
+ /**
21
+ * Type guard to check if config has pre-loaded form
22
+ */
23
+ export declare function hasForm<T extends {
24
+ formId: string;
25
+ serviceIds?: string[];
26
+ } | {
27
+ form: forms.Form;
28
+ }>(config: T | undefined): config is T & {
29
+ form: forms.Form;
30
+ };
31
+ /**
32
+ * Extracts the form ID from a form object.
33
+ * @param form The form object to extract ID from
34
+ * @returns The form ID or undefined if not found
35
+ */
36
+ export declare function extractFormIdFromForm(form: forms.Form): string | undefined;
37
+ /**
38
+ * Configuration object that may contain formId or form.
39
+ * Used by extractFormIdFromConfig.
40
+ */
41
+ export type FormIdConfig = {
42
+ formId: string;
43
+ serviceIds?: string[];
44
+ } | {
45
+ form: forms.Form;
46
+ } | undefined;
47
+ /**
48
+ * Extracts the form ID from a config object (either formId directly or from form object).
49
+ * @param config The config object containing either formId or form
50
+ * @returns The form ID or undefined if not found/extractable
51
+ */
52
+ export declare function extractFormIdFromConfig(config: FormIdConfig): string | undefined;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Utility functions for BookingForm
3
+ *
4
+ * Note: This file should not import from booking-form.ts to avoid circular dependencies.
5
+ * Type guards use generic types that will be compatible with BookingFormServiceConfig.
6
+ */
7
+ // ============================================================================
8
+ // Type Guards
9
+ // ============================================================================
10
+ /**
11
+ * Type guard to check if config has formId
12
+ */
13
+ export function hasFormId(config) {
14
+ return config != null && 'formId' in config;
15
+ }
16
+ /**
17
+ * Type guard to check if config has pre-loaded form
18
+ */
19
+ export function hasForm(config) {
20
+ return config != null && 'form' in config;
21
+ }
22
+ // ============================================================================
23
+ // Form ID Extraction
24
+ // ============================================================================
25
+ /**
26
+ * Extracts the form ID from a form object.
27
+ * @param form The form object to extract ID from
28
+ * @returns The form ID or undefined if not found
29
+ */
30
+ export function extractFormIdFromForm(form) {
31
+ return (form.id ||
32
+ form._id);
33
+ }
34
+ /**
35
+ * Extracts the form ID from a config object (either formId directly or from form object).
36
+ * @param config The config object containing either formId or form
37
+ * @returns The form ID or undefined if not found/extractable
38
+ */
39
+ export function extractFormIdFromConfig(config) {
40
+ if (hasFormId(config)) {
41
+ return config.formId;
42
+ }
43
+ if (hasForm(config)) {
44
+ return extractFormIdFromForm(config.form);
45
+ }
46
+ return undefined;
47
+ }
@@ -8,6 +8,6 @@ export { TimeSlotListService, TimeSlotListServiceDefinition, TimeSlotService, Ti
8
8
  export { ServiceListService, ServiceListServiceDefinition, type ServiceListServiceAPI, type ServiceListServiceConfig, type ServiceListFilters, type ServiceListActions, type QueryOptions, loadServiceListServiceInitialData, type SuccessServiceListServiceConfigResult, type ErrorServiceListServiceConfigResult, } from './service-list/service-list.js';
9
9
  export { executeBookAction, canBook, buildBookingRequest, buildCheckoutRequest, type CanBookParams, type BookResult, type BookingError, type BookActionParams, type BookChildProps, type BookProps, type BuildCheckoutParams, } from './booking/book-action/index.js';
10
10
  export { BookingFormService, BookingFormServiceDefinition, type BookingFormServiceAPI, type BookingFormServiceConfig, type BookingFormServiceInternalAPI, type ValidationResult, } from './booking-form/booking-form.js';
11
- export { PaymentService, PaymentServiceDefinition, type PaymentServiceAPI, type PaymentServiceConfig, type PaymentDetails, type SlotService, type LineItemAdditionalInfo, loadPaymentConfig, type SuccessPaymentConfigResult, type ErrorPaymentConfigResult, } from './payment/payment.js';
11
+ export { PaymentService, PaymentServiceDefinition, type PaymentServiceAPI, type PaymentServiceConfig, type PaymentDetails, type PricingServiceSelection, type LineItemAdditionalInfo, loadPaymentConfig, type SuccessPaymentConfigResult, type ErrorPaymentConfigResult, } from './payment/payment.js';
12
12
  export { LocationListService, LocationListServiceDefinition, LocationType, loadLocationListServiceInitialData, locationListServiceBinding, type LocationListServiceAPI, type LocationListServiceConfig, type LocationListActions, type LocationData, type DisplayLocation, type LoadLocationListServiceResult, } from './location-list/location-list.js';
13
13
  export { StaffMemberListService, StaffMemberListServiceDefinition, loadStaffMemberListServiceInitialData, staffMemberListServiceBinding, type StaffMemberListServiceAPI, type StaffMemberListServiceConfig, type StaffMemberListActions, type StaffMemberData, type LoadStaffMemberListServiceResult, } from './staff-member-list/staff-member-list.js';
@@ -3,8 +3,8 @@
3
3
  * Handles calculating total prices, taxes, discounts, and deposit information
4
4
  * Supports two modes: SSR (config with pre-fetched data) and lazy loading (reactive)
5
5
  */
6
- import { type PaymentServiceConfig, type PaymentServiceInternalConfig, type SlotService } from './payment.def.js';
7
- export { PaymentServiceDefinition, type PaymentServiceConfig, type PaymentDetails, type LineItemAdditionalInfo, type SlotService, } from './payment.def.js';
6
+ import { type PaymentServiceConfig, type PaymentServiceInternalConfig, type PricingServiceSelection } from './payment.def.js';
7
+ export { PaymentServiceDefinition, type PaymentServiceConfig, type PaymentDetails, type LineItemAdditionalInfo, type PricingServiceSelection, } from './payment.def.js';
8
8
  export type { PaymentServiceAPI, PaymentServiceInternalConfig, } from './payment.def.js';
9
9
  export declare const PaymentService: import("@wix/services-definitions").ServiceFactory<string & {
10
10
  __api: import("./payment.def.js").PaymentServiceAPI;
@@ -23,10 +23,10 @@ export type ErrorPaymentConfigResult = {
23
23
  * Load payment configuration for Server-Side Rendering (SSR).
24
24
  * Calls the pricing APIs and returns pre-calculated payment details.
25
25
  *
26
- * @param params.slotServices - Slot/service inputs for calculation
26
+ * @param params.pricingServiceSelections - Pricing service selections for calculation
27
27
  * @param params.numberOfParticipants - Number of participants (default: 1)
28
28
  */
29
29
  export declare function loadPaymentConfig(params: {
30
- slotServices: SlotService[];
30
+ pricingServiceSelections: PricingServiceSelection[];
31
31
  numberOfParticipants?: number;
32
32
  }): Promise<SuccessPaymentConfigResult | ErrorPaymentConfigResult>;
@@ -30,9 +30,9 @@ export interface PaymentDetails {
30
30
  };
31
31
  }
32
32
  /**
33
- * Input for calculating payment for a slot/service
33
+ * Input for calculating payment for a booking service
34
34
  */
35
- export interface SlotService {
35
+ export interface PricingServiceSelection {
36
36
  service: Service;
37
37
  /** Optional line item ID. If not provided, one will be generated. */
38
38
  lineItemId?: string;
@@ -50,8 +50,8 @@ export interface PaymentServiceConfig {
50
50
  * Not exposed publicly - Core merges this with PaymentServiceConfig
51
51
  */
52
52
  export interface PaymentServiceInternalConfig extends PaymentServiceConfig {
53
- /** Slot services for lazy loading (internal use) */
54
- slotServices?: SlotService[];
53
+ /** Booking services for lazy loading (internal use) */
54
+ pricingServiceSelections?: PricingServiceSelection[];
55
55
  /** Number of participants for lazy loading (internal use) */
56
56
  numberOfParticipants?: number;
57
57
  }
@@ -60,11 +60,11 @@ function buildLineItem(service, numberOfParticipants, lineItemId) {
60
60
  paymentOption,
61
61
  };
62
62
  }
63
- async function calculatePaymentDetails(slotServices, numberOfParticipants) {
64
- // Ensure all slot services have a lineItemId
65
- const data = slotServices.map((slot) => ({
66
- ...slot,
67
- lineItemId: slot.lineItemId || generateLineItemId(),
63
+ async function calculatePaymentDetails(pricingServiceSelections, numberOfParticipants) {
64
+ // Ensure all booking services have a lineItemId
65
+ const data = pricingServiceSelections.map((selection) => ({
66
+ ...selection,
67
+ lineItemId: selection.lineItemId || generateLineItemId(),
68
68
  }));
69
69
  const lineItems = data.map((item) => buildLineItem(item.service, numberOfParticipants, item.lineItemId));
70
70
  const response = await calculateTotals({
@@ -96,15 +96,15 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
96
96
  const isLoadingSignal = signalsService.signal(false);
97
97
  const errorSignal = signalsService.signal(null);
98
98
  // Internal calculate function
99
- const calculate = async (slotServices, numberOfParticipants) => {
100
- if (!slotServices.length) {
99
+ const calculate = async (pricingServiceSelections, numberOfParticipants) => {
100
+ if (!pricingServiceSelections.length) {
101
101
  paymentDetailsSignal.set(null);
102
102
  return;
103
103
  }
104
104
  isLoadingSignal.set(true);
105
105
  errorSignal.set(null);
106
106
  try {
107
- const paymentDetails = await calculatePaymentDetails(slotServices, numberOfParticipants);
107
+ const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, numberOfParticipants);
108
108
  paymentDetailsSignal.set(paymentDetails);
109
109
  }
110
110
  catch (error) {
@@ -122,22 +122,30 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
122
122
  if (config.paymentDetails) {
123
123
  // No-op - data is pre-fetched
124
124
  }
125
- // Mode 2: Lazy loading - slotServices provided via internal config
126
- else if (config.slotServices?.length) {
127
- void calculate(config.slotServices, config.numberOfParticipants ?? 1);
125
+ // Mode 2: Lazy loading - pricingServiceSelections provided via internal config
126
+ else if (config.pricingServiceSelections?.length) {
127
+ void calculate(config.pricingServiceSelections, config.numberOfParticipants ?? 1);
128
128
  }
129
129
  // Mode 3: Reactive - observe BookingService signals (client-side only)
130
130
  else if (typeof window !== 'undefined' && bookingService) {
131
- signalsService.effect(() => {
131
+ // Computed signal that only changes when services change (not participants)
132
+ const servicesKey = signalsService.computed(() => {
132
133
  const selections = bookingService?.serviceSelections.get() || [];
134
+ return selections.map((s) => s.service._id).join(',');
135
+ });
136
+ signalsService.effect(() => {
137
+ // Subscribe to servicesKey to trigger effect only when services change
138
+ servicesKey.get();
139
+ // Read current selections without subscribing (peek)
140
+ const selections = bookingService?.serviceSelections.peek() || [];
133
141
  // For single service mode, take the first selection
134
142
  const firstSelection = selections[0];
135
143
  const numParticipants = firstSelection?.totalParticipants || 1;
136
- const services = selections.map((selection) => ({
144
+ const pricingServiceSelections = selections.map((selection) => ({
137
145
  service: selection.service,
138
146
  lineItemId: selection.instanceId,
139
147
  }));
140
- void calculate(services, numParticipants);
148
+ void calculate(pricingServiceSelections, numParticipants);
141
149
  });
142
150
  }
143
151
  return {
@@ -150,19 +158,19 @@ export const PaymentService = implementService.withConfig()(PaymentServiceDefini
150
158
  * Load payment configuration for Server-Side Rendering (SSR).
151
159
  * Calls the pricing APIs and returns pre-calculated payment details.
152
160
  *
153
- * @param params.slotServices - Slot/service inputs for calculation
161
+ * @param params.pricingServiceSelections - Pricing service selections for calculation
154
162
  * @param params.numberOfParticipants - Number of participants (default: 1)
155
163
  */
156
164
  export async function loadPaymentConfig(params) {
157
165
  try {
158
- const { slotServices, numberOfParticipants = 1 } = params;
159
- if (!slotServices.length) {
166
+ const { pricingServiceSelections, numberOfParticipants = 1 } = params;
167
+ if (!pricingServiceSelections.length) {
160
168
  return {
161
169
  type: 'success',
162
170
  config: { paymentDetails: undefined },
163
171
  };
164
172
  }
165
- const paymentDetails = await calculatePaymentDetails(slotServices, numberOfParticipants);
173
+ const paymentDetails = await calculatePaymentDetails(pricingServiceSelections, numberOfParticipants);
166
174
  return {
167
175
  type: 'success',
168
176
  config: { paymentDetails },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-bookings",
3
- "version": "0.0.56",
3
+ "version": "0.0.58",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -36,7 +36,7 @@
36
36
  "@wix/auto_sdk_bookings_bookings": "^1.0.103",
37
37
  "@wix/auto_sdk_bookings_event-time-slots": "^1.0.64",
38
38
  "@wix/auto_sdk_ecom_checkout": "^1.0.104",
39
- "@wix/form-public": "^0.64.0",
39
+ "@wix/form-public": "https://cdn.dev.wixpress.com/@wix/form-public/5e033ab77775b2277d596c4663a1446228270e3fc9afa7b2b85c4a94.tar.gz",
40
40
  "@wix/headless-forms": "0.0.20",
41
41
  "@wix/headless-media": "0.0.18",
42
42
  "@wix/headless-utils": "0.0.8",
@@ -72,5 +72,5 @@
72
72
  "groupId": "com.wixpress.headless-components"
73
73
  }
74
74
  },
75
- "falconPackageHash": "8792da90af7c78ece12ac4244e507751fbe186381decf6b266bee92a"
75
+ "falconPackageHash": "ccd30f51c7b5e50deedb808077fe5dfaebe97ef4f66da71d4f246455"
76
76
  }