@wix/headless-bookings 0.0.57 → 0.0.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/cjs/dist/__mocks__/booking.d.ts +4 -5
  2. package/cjs/dist/__mocks__/booking.js +6 -3
  3. package/cjs/dist/react/booking/Booking.js +1 -1
  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/payment/Payment.d.ts +84 -5
  7. package/cjs/dist/react/core/payment/Payment.js +102 -4
  8. package/cjs/dist/react/core/staff-member/StaffMember.js +2 -1
  9. package/cjs/dist/react/payment/Payment.d.ts +64 -58
  10. package/cjs/dist/react/payment/Payment.js +66 -80
  11. package/cjs/dist/react/staff-member/StaffMemberList.js +1 -1
  12. package/cjs/dist/services/booking/booking.d.ts +15 -8
  13. package/cjs/dist/services/booking/booking.js +8 -16
  14. package/cjs/dist/services/index.d.ts +1 -1
  15. package/cjs/dist/services/payment/payment.d.ts +4 -4
  16. package/cjs/dist/services/payment/payment.def.d.ts +4 -4
  17. package/cjs/dist/services/payment/payment.js +26 -18
  18. package/cjs/dist/services/time-slot-list/time-slot-list.js +1 -1
  19. package/dist/__mocks__/booking.d.ts +4 -5
  20. package/dist/__mocks__/booking.js +6 -3
  21. package/dist/react/booking/Booking.js +1 -1
  22. package/dist/react/core/booking/Booking.d.ts +3 -3
  23. package/dist/react/core/booking/Booking.js +2 -2
  24. package/dist/react/core/payment/Payment.d.ts +84 -5
  25. package/dist/react/core/payment/Payment.js +102 -4
  26. package/dist/react/core/staff-member/StaffMember.js +2 -1
  27. package/dist/react/payment/Payment.d.ts +64 -58
  28. package/dist/react/payment/Payment.js +66 -80
  29. package/dist/react/staff-member/StaffMemberList.js +1 -1
  30. package/dist/services/booking/booking.d.ts +15 -8
  31. package/dist/services/booking/booking.js +8 -16
  32. package/dist/services/index.d.ts +1 -1
  33. package/dist/services/payment/payment.d.ts +4 -4
  34. package/dist/services/payment/payment.def.d.ts +4 -4
  35. package/dist/services/payment/payment.js +26 -18
  36. package/dist/services/time-slot-list/time-slot-list.js +1 -1
  37. package/package.json +2 -2
@@ -19,8 +19,8 @@ var TestIds;
19
19
  TestIds["paymentLineItems"] = "payment-line-items";
20
20
  TestIds["paymentLineItemsList"] = "payment-line-items-list";
21
21
  TestIds["paymentLineItemRepeater"] = "payment-line-item-repeater";
22
- TestIds["paymentLineItemRoot"] = "payment-line-item-root";
23
- TestIds["paymentLineItemPrice"] = "payment-line-item-price";
22
+ TestIds["paymentLineItemTotal"] = "payment-line-item-total";
23
+ TestIds["paymentLineItemSubtotal"] = "payment-line-item-subtotal";
24
24
  TestIds["paymentLineItemDiscount"] = "payment-line-item-discount";
25
25
  TestIds["paymentLineItemDeposit"] = "payment-line-item-deposit";
26
26
  })(TestIds || (TestIds = {}));
@@ -28,7 +28,7 @@ var TestIds;
28
28
  * Root component that provides payment context to the entire app.
29
29
  * Supports 3 modes:
30
30
  * 1. SSR: paymentServiceConfig.paymentDetails - pre-fetched data
31
- * 2. Lazy loading: slotServices + numberOfParticipants props - calls API via useEffect
31
+ * 2. Lazy loading: pricingServiceSelections + numberOfParticipants props - calls API via useEffect
32
32
  * 3. Reactive: no config/props - reads from BookingService signals
33
33
  *
34
34
  * @order 1
@@ -57,24 +57,36 @@ var TestIds;
57
57
  * ```
58
58
  */
59
59
  export const Root = React.forwardRef((props, ref) => {
60
- const { children, paymentServiceConfig, slotServices, numberOfParticipants, asChild, className, ...attrs } = props;
61
- return (_jsx(CorePayment.Root, { paymentServiceConfig: paymentServiceConfig, slotServices: slotServices, numberOfParticipants: numberOfParticipants, children: ({ isLoading, hasError }) => {
60
+ const { children, paymentServiceConfig, pricingServiceSelections, numberOfParticipants, asChild, className, ...attrs } = props;
61
+ return (_jsx(CorePayment.Root, { paymentServiceConfig: paymentServiceConfig, pricingServiceSelections: pricingServiceSelections, numberOfParticipants: numberOfParticipants, children: ({ isLoading, hasError }) => {
62
62
  return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentRoot, "data-loading": isLoading, "data-has-error": hasError, customElement: children, customElementProps: { isLoading, hasError }, ...attrs, children: children }));
63
63
  } }));
64
64
  });
65
65
  Root.displayName = 'Payment.Root';
66
66
  /**
67
67
  * Displays the payment subtotal.
68
+ * Always displays the monetary value but exposes priceText, hasPriceText, and isFree for custom rendering.
68
69
  *
69
70
  * @component
70
71
  * @example
71
72
  * ```tsx
72
73
  * <Payment.Subtotal className="text-lg" />
73
74
  * ```
75
+ * @example
76
+ * ```tsx
77
+ * <Payment.Subtotal asChild>
78
+ * {({ subtotal, priceText, hasPriceText, isFree }) => (
79
+ * <div>
80
+ * <Money money={subtotal} />
81
+ * {hasPriceText && <span className="text-sm">({priceText})</span>}
82
+ * </div>
83
+ * )}
84
+ * </Payment.Subtotal>
85
+ * ```
74
86
  */
75
87
  export const Subtotal = React.forwardRef((props, ref) => {
76
88
  const { asChild, children, className } = props;
77
- return (_jsx(CorePayment.Data, { children: ({ subtotal }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentSubtotal, customElement: children, customElementProps: { subtotal }, children: subtotal ? _jsx(Money, { money: subtotal }) : null })) }));
89
+ return (_jsx(CorePayment.Data, { children: ({ subtotal, priceText, hasPriceText, isFree }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentSubtotal, "data-has-price-text": hasPriceText, "data-is-free": isFree, customElement: children, customElementProps: { subtotal, priceText, hasPriceText, isFree }, children: subtotal && _jsx(Money, { money: subtotal }) })) }));
78
90
  });
79
91
  Subtotal.displayName = 'Payment.Subtotal';
80
92
  /**
@@ -89,10 +101,7 @@ Subtotal.displayName = 'Payment.Subtotal';
89
101
  */
90
102
  export const Tax = React.forwardRef((props, ref) => {
91
103
  const { asChild, children, className } = props;
92
- return (_jsx(CorePayment.Data, { children: ({ tax }) => {
93
- const hasTax = tax !== null && tax.amount > 0;
94
- return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentTax, "data-has-tax": hasTax, customElement: children, customElementProps: { tax }, children: tax ? _jsx(Money, { money: tax }) : null }));
95
- } }));
104
+ return (_jsx(CorePayment.Data, { children: ({ tax, hasTax }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentTax, "data-has-tax": hasTax, customElement: children, customElementProps: { tax }, children: hasTax && _jsx(Money, { money: tax }) })) }));
96
105
  });
97
106
  Tax.displayName = 'Payment.Tax';
98
107
  /**
@@ -107,24 +116,37 @@ Tax.displayName = 'Payment.Tax';
107
116
  */
108
117
  export const Discount = React.forwardRef((props, ref) => {
109
118
  const { asChild, children, className } = props;
110
- return (_jsx(CorePayment.Data, { children: ({ discount }) => {
111
- const hasDiscount = discount !== null && discount.amount > 0;
112
- return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentDiscount, "data-has-discount": hasDiscount, customElement: children, customElementProps: { discount }, children: discount ? _jsx(Money, { money: discount }) : null }));
113
- } }));
119
+ return (_jsx(CorePayment.Data, { children: ({ discount, hasDiscount }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentDiscount, "data-has-discount": hasDiscount, customElement: children, customElementProps: { discount }, children: hasDiscount && _jsx(Money, { money: discount }) })) }));
114
120
  });
115
121
  Discount.displayName = 'Payment.Discount';
116
122
  /**
117
123
  * Displays the payment total.
124
+ * Always displays the monetary value but exposes priceText, hasPriceText, and isFree for custom rendering.
118
125
  *
119
126
  * @component
120
127
  * @example
121
128
  * ```tsx
122
129
  * <Payment.Total className="text-2xl font-bold" />
123
130
  * ```
131
+ * @example
132
+ * ```tsx
133
+ * <Payment.Total asChild>
134
+ * {({ total, priceText, hasPriceText, isFree }) => (
135
+ * <div>
136
+ * {isFree ? (
137
+ * <span className="text-green-500">Free</span>
138
+ * ) : (
139
+ * <Money money={total} />
140
+ * )}
141
+ * {hasPriceText && <span className="text-sm">({priceText})</span>}
142
+ * </div>
143
+ * )}
144
+ * </Payment.Total>
145
+ * ```
124
146
  */
125
147
  export const Total = React.forwardRef((props, ref) => {
126
148
  const { asChild, children, className } = props;
127
- return (_jsx(CorePayment.Data, { children: ({ total }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentTotal, customElement: children, customElementProps: { total }, children: total ? _jsx(Money, { money: total }) : null })) }));
149
+ return (_jsx(CorePayment.Data, { children: ({ total, priceText, hasPriceText, isFree }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentTotal, "data-has-price-text": hasPriceText, "data-is-free": isFree, customElement: children, customElementProps: { total, priceText, hasPriceText, isFree }, children: total && _jsx(Money, { money: total }) })) }));
128
150
  });
129
151
  Total.displayName = 'Payment.Total';
130
152
  /**
@@ -139,10 +161,7 @@ Total.displayName = 'Payment.Total';
139
161
  */
140
162
  export const PayNow = React.forwardRef((props, ref) => {
141
163
  const { asChild, children, className } = props;
142
- return (_jsx(CorePayment.Data, { children: ({ payNow, total }) => {
143
- const isDeposit = payNow !== null && total !== null && payNow.amount < total.amount;
144
- return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentPayNow, "data-has-deposit": isDeposit, customElement: children, customElementProps: { payNow }, children: payNow ? _jsx(Money, { money: payNow }) : null }));
145
- } }));
164
+ return (_jsx(CorePayment.Data, { children: ({ payNow, isDeposit }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentPayNow, "data-has-deposit": isDeposit, customElement: children, customElementProps: { payNow }, children: payNow && payNow.amount > 0 && _jsx(Money, { money: payNow }) })) }));
146
165
  });
147
166
  PayNow.displayName = 'Payment.PayNow';
148
167
  /**
@@ -157,10 +176,7 @@ PayNow.displayName = 'Payment.PayNow';
157
176
  */
158
177
  export const PayLater = React.forwardRef((props, ref) => {
159
178
  const { asChild, children, className } = props;
160
- return (_jsx(CorePayment.Data, { children: ({ payLater, payNow, total }) => {
161
- const isDeposit = payNow !== null && total !== null && payNow.amount < total.amount;
162
- return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentPayLater, "data-has-deposit": isDeposit, customElement: children, customElementProps: { payLater }, children: payLater ? _jsx(Money, { money: payLater }) : null }));
163
- } }));
179
+ return (_jsx(CorePayment.Data, { children: ({ payLater, isDeposit }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentPayLater, "data-has-deposit": isDeposit, customElement: children, customElementProps: { payLater }, children: payLater && payLater.amount > 0 && _jsx(Money, { money: payLater }) })) }));
164
180
  });
165
181
  PayLater.displayName = 'Payment.PayLater';
166
182
  /**
@@ -219,84 +235,60 @@ export const LineItemsList = React.forwardRef((props, ref) => {
219
235
  });
220
236
  LineItemsList.displayName = 'Payment.LineItemsList';
221
237
  /**
222
- * Repeater component that renders LineItemRoot for each line item.
223
- * Supports asChild pattern for custom rendering.
238
+ * Internal component that wraps each line item with context provider.
239
+ */
240
+ const LineItemWrapper = ({ item, currency, children, }) => {
241
+ return (_jsx(CorePayment.LineItemProvider, { lineItem: item, currency: currency, children: children }));
242
+ };
243
+ /**
244
+ * Repeater component that maps over line items and provides context per item.
245
+ * Uses GenericList.Repeater internally with itemWrapper that provides LineItemContext.
224
246
  *
225
247
  * @component
226
248
  * @example
227
249
  * ```tsx
228
- * // Standard usage
229
250
  * <Payment.LineItemRepeater>
230
- * <Payment.LineItemPrice />
231
- * </Payment.LineItemRepeater>
232
- *
233
- * // AsChild usage with custom list
234
- * <Payment.LineItemRepeater asChild>
235
- * {({ items, itemWrapper }) => (
236
- * <ol>
237
- * {items.map((item, index) => (
238
- * <li key={item.id}>
239
- * {itemWrapper({ item, index, children: <Payment.LineItemPrice /> })}
240
- * </li>
241
- * ))}
242
- * </ol>
243
- * )}
251
+ * <Payment.LineItemTotal />
252
+ * <Payment.LineItemDiscount />
244
253
  * </Payment.LineItemRepeater>
245
254
  * ```
246
255
  */
247
256
  export const LineItemRepeater = React.forwardRef((props, ref) => {
248
- const { children, asChild } = props;
257
+ const { children } = props;
249
258
  const { currency } = useLineItemsContext();
250
- // Custom itemWrapper that renders LineItemRoot for each line item
251
- const lineItemWrapper = ({ item, children: itemChildren, }) => {
252
- return (_jsx(LineItemRoot, { lineItem: item, currency: currency, "data-testid": TestIds.paymentLineItemRepeater, children: itemChildren || undefined }, item.id));
253
- };
254
- return (_jsx(GenericList.Repeater, { ref: ref, itemWrapper: lineItemWrapper, asChild: asChild, children: children }));
259
+ return (_jsx(GenericList.Repeater, { ref: ref, itemWrapper: ({ item }) => (_jsx(LineItemWrapper, { item: item, currency: currency, children: children }, item.id)), children: undefined }));
255
260
  });
256
261
  LineItemRepeater.displayName = 'Payment.LineItemRepeater';
257
- const LineItemContext = React.createContext(null);
258
- function useLineItemContext() {
259
- const context = React.useContext(LineItemContext);
260
- if (!context) {
261
- throw new Error('useLineItemContext must be used within a Payment.LineItemRoot component');
262
- }
263
- return context;
264
- }
265
262
  /**
266
- * Root component for a single line item.
267
- * Provides context for child components.
263
+ * Displays the line item total (total price after tax).
264
+ * Shows priceText for CUSTOM rate type services, otherwise shows totalPrice.
268
265
  *
269
266
  * @component
270
267
  * @example
271
268
  * ```tsx
272
- * <Payment.LineItemRoot lineItem={item} currency="USD">
273
- * <Payment.LineItemPrice />
274
- * <Payment.LineItemDiscount />
275
- * </Payment.LineItemRoot>
269
+ * <Payment.LineItemTotal className="text-2xl font-bold" />
276
270
  * ```
277
271
  */
278
- export const LineItemRoot = React.forwardRef((props, ref) => {
279
- const { lineItem, currency, children, asChild, className, ...attrs } = props;
280
- return (_jsx(LineItemContext.Provider, { value: { lineItem, currency }, children: _jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": attrs['data-testid'] || TestIds.paymentLineItemRoot, customElement: children, customElementProps: { lineItem, currency }, ...attrs, children: _jsx("div", { children: React.isValidElement(children) ? children : null }) }) }));
272
+ export const LineItemTotal = React.forwardRef((props, ref) => {
273
+ const { asChild, children, className } = props;
274
+ return (_jsx(CorePayment.LineItemInfo, { children: ({ totalPrice, priceText, hasPriceText, isFree }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentLineItemTotal, "data-has-price-text": hasPriceText, "data-is-free": isFree, customElement: children, customElementProps: { totalPrice, priceText, hasPriceText, isFree }, children: priceText ? (_jsx("span", { children: priceText })) : (totalPrice && _jsx(Money, { money: totalPrice })) })) }));
281
275
  });
282
- LineItemRoot.displayName = 'Payment.LineItemRoot';
276
+ LineItemTotal.displayName = 'Payment.LineItemTotal';
283
277
  /**
284
- * Displays the line item price.
285
- * Shows priceText for CUSTOM rate type services, otherwise shows totalPrice.
278
+ * Displays the line item subtotal (full price before discounts).
279
+ * Shows priceText for CUSTOM rate type services, otherwise shows subtotal.
286
280
  *
287
281
  * @component
288
282
  * @example
289
283
  * ```tsx
290
- * <Payment.LineItemPrice className="text-lg font-bold" />
284
+ * <Payment.LineItemSubtotal className="text-lg" />
291
285
  * ```
292
286
  */
293
- export const LineItemPrice = React.forwardRef((props, ref) => {
287
+ export const LineItemSubtotal = React.forwardRef((props, ref) => {
294
288
  const { asChild, children, className } = props;
295
- const { lineItem } = useLineItemContext();
296
- const { totalPrice, priceText } = lineItem;
297
- return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentLineItemPrice, "data-has-price-text": !!priceText, customElement: children, customElementProps: { totalPrice, priceText }, children: priceText ? (_jsx("span", { children: priceText })) : totalPrice ? (_jsx(Money, { money: totalPrice })) : null }));
289
+ return (_jsx(CorePayment.LineItemInfo, { children: ({ subtotal, priceText, hasPriceText, isFree }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentLineItemSubtotal, "data-has-price-text": hasPriceText, "data-is-free": isFree, customElement: children, customElementProps: { subtotal, priceText, hasPriceText, isFree }, children: priceText ? (_jsx("span", { children: priceText })) : (subtotal && _jsx(Money, { money: subtotal })) })) }));
298
290
  });
299
- LineItemPrice.displayName = 'Payment.LineItemPrice';
291
+ LineItemSubtotal.displayName = 'Payment.LineItemSubtotal';
300
292
  /**
301
293
  * Displays the line item discount.
302
294
  * Uses data-has-discount attribute for conditional visibility.
@@ -309,10 +301,7 @@ LineItemPrice.displayName = 'Payment.LineItemPrice';
309
301
  */
310
302
  export const LineItemDiscount = React.forwardRef((props, ref) => {
311
303
  const { asChild, children, className } = props;
312
- const { lineItem } = useLineItemContext();
313
- const { discount } = lineItem;
314
- const hasDiscount = discount !== null && discount.amount > 0;
315
- return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentLineItemDiscount, "data-has-discount": hasDiscount, customElement: children, customElementProps: { discount }, children: discount ? _jsx(Money, { money: discount }) : null }));
304
+ return (_jsx(CorePayment.LineItemInfo, { children: ({ discount, hasDiscount }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentLineItemDiscount, "data-has-discount": hasDiscount, customElement: children, customElementProps: { discount }, children: hasDiscount && _jsx(Money, { money: discount }) })) }));
316
305
  });
317
306
  LineItemDiscount.displayName = 'Payment.LineItemDiscount';
318
307
  /**
@@ -327,9 +316,6 @@ LineItemDiscount.displayName = 'Payment.LineItemDiscount';
327
316
  */
328
317
  export const LineItemDeposit = React.forwardRef((props, ref) => {
329
318
  const { asChild, children, className } = props;
330
- const { lineItem } = useLineItemContext();
331
- const { deposit } = lineItem;
332
- const hasDeposit = deposit !== null && deposit.amount > 0;
333
- return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentLineItemDeposit, "data-has-deposit": hasDeposit, customElement: children, customElementProps: { deposit }, children: deposit ? _jsx(Money, { money: deposit }) : null }));
319
+ return (_jsx(CorePayment.LineItemInfo, { children: ({ deposit, hasDeposit }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.paymentLineItemDeposit, "data-has-deposit": hasDeposit, customElement: children, customElementProps: { deposit }, children: hasDeposit && _jsx(Money, { money: deposit }) })) }));
334
320
  });
335
321
  LineItemDeposit.displayName = 'Payment.LineItemDeposit';
@@ -152,7 +152,7 @@ const Clear = React.forwardRef((props, ref) => {
152
152
  // BookingService not available - likely no ServiceManagerProvider
153
153
  }
154
154
  const handleClick = () => {
155
- bookingService?.actions.clearResourceById(STAFF_MEMBER_RESOURCE_TYPE_ID);
155
+ bookingService?.actions.clearResourceByType(STAFF_MEMBER_RESOURCE_TYPE_ID);
156
156
  onClicked?.();
157
157
  };
158
158
  return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.staffMemberListActionClear, customElement: children, customElementProps: {
@@ -9,6 +9,13 @@ import type { Resource, EventInfo, Location as TimeSlotLocationType } from '@wix
9
9
  import type { FormValues } from '@wix/form-public';
10
10
  import type { BookResult } from './book-action/types.js';
11
11
  export declare const ANY_STAFF_MEMBER_ID = "__any_staff_member__";
12
+ /**
13
+ * Extended Resource type with a type field for categorization
14
+ */
15
+ export interface ExtendedResource extends Resource {
16
+ /** Resource type identifier (e.g., 'staff', 'room', 'equipment') */
17
+ type: string;
18
+ }
12
19
  /**
13
20
  * Minimal time slot data for service selections
14
21
  */
@@ -32,8 +39,8 @@ export interface ServiceSelection {
32
39
  instanceId: string;
33
40
  /** The selected service */
34
41
  service: Service;
35
- /** Selected resources (staff members, rooms, etc.) */
36
- resources: Resource[];
42
+ /** Selected resources (staff members, rooms, etc.) with type information */
43
+ resources: ExtendedResource[];
37
44
  /** Per-item time slot data */
38
45
  timeSlot?: ServiceSelectionTimeSlot;
39
46
  /** Number of participants for this service selection */
@@ -53,12 +60,12 @@ export interface BookingServiceActions {
53
60
  removeItem(instanceId?: string): void;
54
61
  /** Clears all service selections */
55
62
  clearItems(): void;
56
- /** Sets a resource by ID within a service selection. If no instanceId provided, uses the first item. */
57
- setResourceById(resourceId: string, resource: Resource, instanceId?: string): void;
58
- /** Gets a resource by ID from a service selection. If no instanceId provided, uses the first item. */
59
- getResourceById(resourceId: string, instanceId?: string): Resource | undefined;
60
- /** Clears a resource by ID from a service selection. If no instanceId provided, uses the first item. */
61
- clearResourceById(resourceId: string, instanceId?: string): void;
63
+ /** Sets a resource by type within a service selection. If no instanceId provided, uses the first item. Only one resource per type is allowed. The resourceType parameter specifies which type to set. */
64
+ setResource(resource: ExtendedResource, instanceId?: string): void;
65
+ /** Gets a resource by type from a service selection. If no instanceId provided, uses the first item. */
66
+ getResourceByType(resourceType: string, instanceId?: string): Resource | undefined;
67
+ /** Clears a resource by type from a service selection. If no instanceId provided, uses the first item. */
68
+ clearResourceByType(resourceType: string, instanceId?: string): void;
62
69
  setLocation(location: ServiceLocationType | null): void;
63
70
  clearLocation(): void;
64
71
  setTimezone(timezone: string): void;
@@ -115,8 +115,7 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
115
115
  clearItems() {
116
116
  serviceSelectionsSignal.set([]);
117
117
  },
118
- // Resource helpers
119
- setResourceById(resourceId, resource, instanceId) {
118
+ setResource(resource, instanceId) {
120
119
  const current = serviceSelectionsSignal.get();
121
120
  // If no instanceId provided, use the first item
122
121
  const targetId = instanceId ?? current[0]?.instanceId;
@@ -125,20 +124,13 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
125
124
  const updated = current.map((item) => {
126
125
  if (item.instanceId !== targetId)
127
126
  return item;
128
- // Find and replace resource by ID, or add if not found
129
- const existingIndex = item.resources.findIndex((r) => r._id === resourceId);
130
- const newResources = [...item.resources];
131
- if (existingIndex >= 0) {
132
- newResources[existingIndex] = resource;
133
- }
134
- else {
135
- newResources.push(resource);
136
- }
127
+ const newResources = item.resources.filter((r) => r.type !== resource.type);
128
+ newResources.push(resource);
137
129
  return { ...item, resources: newResources };
138
130
  });
139
131
  serviceSelectionsSignal.set(updated);
140
132
  },
141
- getResourceById(resourceId, instanceId) {
133
+ getResourceByType(resourceType, instanceId) {
142
134
  const current = serviceSelectionsSignal.get();
143
135
  // If no instanceId provided, use the first item
144
136
  const targetId = instanceId ?? current[0]?.instanceId;
@@ -147,9 +139,9 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
147
139
  const targetItem = current.find((item) => item.instanceId === targetId);
148
140
  if (!targetItem)
149
141
  return undefined;
150
- return targetItem.resources.find((r) => r._id === resourceId);
142
+ return targetItem.resources.find((r) => r.type === resourceType);
151
143
  },
152
- clearResourceById(resourceId, instanceId) {
144
+ clearResourceByType(resourceType, instanceId) {
153
145
  const current = serviceSelectionsSignal.get();
154
146
  // If no instanceId provided, use the first item
155
147
  const targetId = instanceId ?? current[0]?.instanceId;
@@ -158,8 +150,8 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
158
150
  const updated = current.map((item) => {
159
151
  if (item.instanceId !== targetId)
160
152
  return item;
161
- // Filter out the resource with matching ID
162
- const newResources = item.resources.filter((r) => r._id !== resourceId);
153
+ // Filter out the resource with matching type
154
+ const newResources = item.resources.filter((r) => r.type !== resourceType);
163
155
  return { ...item, resources: newResources };
164
156
  });
165
157
  serviceSelectionsSignal.set(updated);
@@ -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 },
@@ -195,7 +195,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
195
195
  dateRangeSignal.set({ start: newStartDate, end: newEndDate });
196
196
  };
197
197
  const preSelectedStaffMemberId = signalsService.computed(() => {
198
- const serviceSelections = bookingService.serviceSelections.peek();
198
+ const serviceSelections = bookingService.serviceSelections.get();
199
199
  return serviceSelections.length
200
200
  ? (serviceSelections[0]?.resources?.[0]?._id ?? undefined)
201
201
  : undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-bookings",
3
- "version": "0.0.57",
3
+ "version": "0.0.59",
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": "94e57cfc23b5dddf7ab80117e454a526cb7834e1fff7e8b473d4899e"
75
+ "falconPackageHash": "61ca8eb4d4959dd03911f50f6d490b55be609f85f03f0aff55af5948"
76
76
  }