@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
@@ -1,58 +1,42 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useMemo } from 'react';
2
+ /**
3
+ * Core BookingForm Component
4
+ *
5
+ * Provides low-level access to booking form functionality via render props.
6
+ * Wraps CoreForm.Root and provides merged field map with booking defaults.
7
+ *
8
+ * Supports two modes:
9
+ * 1. Client-side: Provide formId to load the form dynamically
10
+ * 2. SSR/SSG: Provide a pre-loaded form object for server-side rendering
11
+ */
12
+ import * as React from 'react';
13
+ import { useMemo, useRef, useCallback } from 'react';
3
14
  import { WixServices, useService } from '@wix/services-manager-react';
4
15
  import { createServicesMap } from '@wix/services-manager';
5
- import { BookingFormServiceDefinition, BookingFormService, BOOKING_FORM_NAMESPACE, } from '../../../services/booking-form/booking-form.js';
16
+ import { UniqueFieldSuffixContextProvider, } from '@wix/form-public';
17
+ import { BookingFormServiceDefinition, BookingFormService, BookingFormConfigurationError, BOOKING_FORM_NAMESPACE, extractFormIdFromConfig, } from '../../../services/booking-form/booking-form.js';
18
+ import { BookingServiceDefinition } from '../../../services/booking/booking.js';
6
19
  import { FormServiceDefinition, FormService, } from '@wix/headless-forms/services';
7
- // TODO: remove this when form will be updated to support optional field map
8
- const DEFAULT_BOOKING_FIELD_MAP = {
9
- TEXT_INPUT: () => null,
10
- TEXT_AREA: () => null,
11
- PHONE_INPUT: () => null,
12
- EMAIL_INPUT: () => null,
13
- NUMBER_INPUT: () => null,
14
- CHECKBOX: () => null,
15
- DROPDOWN: () => null,
16
- DATE_PICKER: () => null,
17
- SUBMIT_BUTTON: () => null,
18
- RADIO_GROUP: () => null,
19
- CHECKBOX_GROUP: () => null,
20
- DATE_INPUT: () => null,
21
- DATE_TIME_INPUT: () => null,
22
- TIME_INPUT: () => null,
23
- FILE_UPLOAD: () => null,
24
- SIGNATURE: () => null,
25
- TEXT: () => null,
26
- MULTILINE_ADDRESS: () => null,
27
- RATING_INPUT: () => null,
28
- TAGS: () => null,
29
- PRODUCT_LIST: () => null,
30
- FIXED_PAYMENT: () => null,
31
- PAYMENT_INPUT: () => null,
32
- DONATION: () => null,
33
- APPOINTMENT: () => null,
34
- IMAGE_CHOICE: () => null,
35
- };
36
20
  // ============================================================================
37
21
  // Component
38
22
  // ============================================================================
39
23
  /**
40
24
  * Extracts the form ID from props (either directly or from form object)
25
+ * Returns undefined if formId should be extracted from services by the service layer
41
26
  */
42
27
  function extractFormIdFromProps(props) {
43
- if ('formId' in props && props.formId) {
44
- return props.formId;
45
- }
46
- if ('form' in props && props.form) {
47
- const form = props.form;
48
- const formId = form.id ||
49
- form._id;
50
- if (!formId) {
51
- throw new Error('BookingForm: Could not extract form ID from provided form object.');
52
- }
53
- return formId;
28
+ // Convert props to config-like object for the utility
29
+ const config = props.form
30
+ ? { form: props.form }
31
+ : props.formId
32
+ ? { formId: props.formId }
33
+ : undefined;
34
+ const formId = extractFormIdFromConfig(config);
35
+ // If form was provided but we couldn't extract formId, that's an error
36
+ if (props.form && !formId) {
37
+ throw new Error('BookingForm: Could not extract form ID from provided form object.');
54
38
  }
55
- throw new Error('BookingForm: Either formId or form must be provided.');
39
+ return formId;
56
40
  }
57
41
  /**
58
42
  * Core component that provides booking form data via render props.
@@ -123,41 +107,75 @@ function extractFormIdFromProps(props) {
123
107
  * ```
124
108
  */
125
109
  export function Root(props) {
126
- const { additionalMetadata, fieldMap: userFieldMap, children } = props;
110
+ const { serviceIds, additionalMetadata, fieldMap, children } = props;
127
111
  // Determine if we have a pre-loaded form
128
112
  const hasPreloadedForm = 'form' in props && props.form;
129
113
  const preloadedForm = hasPreloadedForm ? props.form : null;
130
114
  // Extract formId from props
131
- const formId = extractFormIdFromProps(props);
132
- // Merge user's partial field map with booking defaults
133
- const fields = useMemo(() => ({
134
- // ...DEFAULT_BOOKING_FIELD_MAP, // TODO: Add default field map
135
- ...userFieldMap,
136
- }), [userFieldMap]);
115
+ const propsFormId = extractFormIdFromProps(props);
116
+ // Try to get BookingService if available (to extract formId and serviceIds from services)
117
+ let bookingService = null;
118
+ try {
119
+ bookingService = useService(BookingServiceDefinition);
120
+ }
121
+ catch {
122
+ // BookingService not available - that's ok if formId and serviceIds were provided explicitly
123
+ bookingService = null;
124
+ }
125
+ // Determine final formId: use props formId if provided, otherwise extract from services
126
+ let formId = propsFormId;
127
+ if (!formId && bookingService) {
128
+ const serviceSelections = bookingService.serviceSelections.get();
129
+ const services = serviceSelections.length
130
+ ? serviceSelections.map((s) => s.service)
131
+ : [];
132
+ formId = services.find((s) => s.form?._id)?.form?._id;
133
+ }
134
+ // Determine final serviceIds: use props serviceIds if provided, otherwise extract from services
135
+ let resolvedServiceIds = serviceIds;
136
+ if (!resolvedServiceIds && bookingService) {
137
+ const serviceSelections = bookingService.serviceSelections.get();
138
+ resolvedServiceIds = serviceSelections
139
+ .map((s) => s.service._id)
140
+ .filter((id) => Boolean(id));
141
+ }
142
+ // Merge serviceIds into additionalMetadata
143
+ const mergedMetadata = {
144
+ ...additionalMetadata,
145
+ ...(resolvedServiceIds &&
146
+ resolvedServiceIds.length > 0 && { serviceIds: resolvedServiceIds }),
147
+ };
137
148
  // Build formServiceConfig for FormService
138
- // If we have a pre-loaded form, pass it directly; otherwise pass formId
149
+ // If we have a pre-loaded form, pass it directly
150
+ // If we have formId, pass it with namespace and metadata
139
151
  const formServiceConfig = useMemo(() => {
140
152
  if (preloadedForm) {
141
153
  return { form: preloadedForm };
142
154
  }
143
- return {
144
- formId,
145
- namespace: BOOKING_FORM_NAMESPACE,
146
- ...(additionalMetadata && { additionalMetadata }),
147
- };
148
- }, [formId, additionalMetadata, preloadedForm]);
155
+ // Only provide config if formId is available
156
+ if (formId) {
157
+ return {
158
+ formId,
159
+ namespace: BOOKING_FORM_NAMESPACE,
160
+ ...(Object.keys(mergedMetadata).length > 0 && {
161
+ additionalMetadata: mergedMetadata,
162
+ }),
163
+ };
164
+ }
165
+ }, [formId, mergedMetadata, preloadedForm]);
149
166
  // Build bookingFormServiceConfig
150
167
  const bookingFormServiceConfig = useMemo(() => {
151
168
  if (preloadedForm) {
152
169
  return { form: preloadedForm };
153
170
  }
154
- return { formId };
155
- }, [formId, preloadedForm]);
171
+ // formId is optional - service will extract from services if not provided
172
+ return formId ? { formId, serviceIds } : undefined;
173
+ }, [formId, preloadedForm, serviceIds]);
156
174
  // Both FormService and BookingFormService are added to the same WixServices context
157
175
  // This allows BookingFormService to access FormService via getService internally
158
176
  return (_jsx(WixServices, { servicesMap: createServicesMap()
159
177
  .addService(FormServiceDefinition, FormService, formServiceConfig)
160
- .addService(BookingFormServiceDefinition, BookingFormService, bookingFormServiceConfig), children: _jsx(BookingFormContent, { formId: formId, fields: { ...DEFAULT_BOOKING_FIELD_MAP, ...fields }, children: children }) }));
178
+ .addService(BookingFormServiceDefinition, BookingFormService, bookingFormServiceConfig), children: _jsx(UniqueFieldSuffixContextProvider, { children: _jsx(BookingFormContent, { fields: fieldMap, children: children }) }) }));
161
179
  }
162
180
  /**
163
181
  * Internal component that consumes BookingFormService and provides render props.
@@ -165,17 +183,139 @@ export function Root(props) {
165
183
  *
166
184
  * Note: BookingFormService internally accesses FormService to get formValues,
167
185
  * so we only need to consume BookingFormService here.
186
+ *
187
+ * Also automatically syncs form submission to BookingService when available,
188
+ * so Booking.Actions.Book can check canBook() correctly.
189
+ *
190
+ * Note: BookingService must be available in a parent WixServices context
191
+ * (typically provided by Booking.Root) for the sync to work. If BookingService
192
+ * is not available, useService will throw. This is expected since Booking.Actions.Book
193
+ * requires Booking.Root to be in the component tree.
168
194
  */
169
- function BookingFormContent({ formId, fields, children, }) {
195
+ function BookingFormContent({ fields, children, }) {
170
196
  const bookingFormService = useService(BookingFormServiceDefinition);
197
+ // Try to get BookingService from parent context (provided by Booking.Root)
198
+ // Note: useService must be called unconditionally per React hooks rules.
199
+ // If BookingService is not available, useService will throw an error.
200
+ // This is expected - Booking.Actions.Book requires Booking.Root to be in the tree.
201
+ // We use a wrapper component pattern to handle this gracefully.
202
+ // For now, we'll always attempt to get it and let it throw if not available
203
+ // The user should wrap BookingForm with Booking.Root when using Booking.Actions.Book
204
+ const bookingService = useService(BookingServiceDefinition);
205
+ // Create internal ref for form handle
206
+ const formRef = useRef(null);
207
+ // Create validate action that uses the form ref
208
+ const validate = useCallback(() => {
209
+ return formRef.current?.validate() ?? Promise.resolve(false);
210
+ }, []);
211
+ // Wrap setFormSubmission to also sync to BookingService when available
212
+ const setFormSubmission = useCallback((formValues) => {
213
+ // Set in BookingFormService (always)
214
+ bookingFormService.setFormSubmission(formValues);
215
+ // Also set in BookingService if available (for canBook check)
216
+ // This allows Booking.Actions.Book to work correctly
217
+ if (bookingService) {
218
+ bookingService.actions.setFormSubmission(formValues);
219
+ }
220
+ }, [bookingFormService, bookingService]);
171
221
  const formSubmission = bookingFormService.formSubmission.get();
172
222
  const formValues = bookingFormService.formValues.get();
223
+ // Get formId from service (it extracts from services if not provided in config)
224
+ const resolvedFormId = bookingFormService.formId.get();
173
225
  return children({
174
- formId,
226
+ formId: resolvedFormId,
175
227
  formValues,
176
228
  formSubmission,
177
- setFormSubmission: bookingFormService.setFormSubmission,
178
- validateFormSubmission: bookingFormService.validateFormSubmission,
229
+ setFormSubmission,
230
+ validateFormSubmission: validate,
231
+ formRef,
179
232
  fields,
180
233
  });
181
234
  }
235
+ /**
236
+ * Core component that provides booking form actions via render props.
237
+ * Must be used within BookingForm.Root context.
238
+ *
239
+ * @component
240
+ * @example
241
+ * ```tsx
242
+ * <CoreBookingForm.Root formId="form-123">
243
+ * {({ fields }) => (
244
+ * <>
245
+ * <Form.Fields fieldMap={fields} />
246
+ * <CoreBookingForm.Actions>
247
+ * {({ validateFormSubmission, formId }) => (
248
+ * <button onClick={async () => {
249
+ * const result = await validateFormSubmission();
250
+ * console.log('Validation result:', result);
251
+ * }}>
252
+ * Validate Form
253
+ * </button>
254
+ * )}
255
+ * </CoreBookingForm.Actions>
256
+ * </>
257
+ * )}
258
+ * </CoreBookingForm.Root>
259
+ * ```
260
+ */
261
+ export function Actions(props) {
262
+ const bookingFormService = useService(BookingFormServiceDefinition);
263
+ const formSubmission = bookingFormService.formSubmission.get();
264
+ const formValues = bookingFormService.formValues.get();
265
+ const formId = bookingFormService.formId.get();
266
+ return props.children({
267
+ validateFormSubmission: bookingFormService.validateFormSubmission,
268
+ formId,
269
+ formValues,
270
+ formSubmission,
271
+ });
272
+ }
273
+ /**
274
+ * Component to display when BookingForm fails to load due to configuration errors.
275
+ * This component should be used in an error boundary to catch BookingFormConfigurationError.
276
+ *
277
+ * @component
278
+ * @example
279
+ * ```tsx
280
+ * import * as CoreBookingForm from '@wix/headless-bookings/react/core/booking-form';
281
+ *
282
+ * // Default usage with className
283
+ * <CoreBookingForm.LoadError error={error} className="text-destructive p-4" />
284
+ *
285
+ * // Custom content
286
+ * <CoreBookingForm.LoadError error={error}>
287
+ * <div className="error-container">
288
+ * <h3>Error Loading Form</h3>
289
+ * <p>Please try again later.</p>
290
+ * </div>
291
+ * </CoreBookingForm.LoadError>
292
+ *
293
+ * // With asChild for custom components
294
+ * <CoreBookingForm.LoadError error={error} asChild>
295
+ * <CustomErrorComponent />
296
+ * </CoreBookingForm.LoadError>
297
+ * ```
298
+ */
299
+ export const LoadError = React.forwardRef((props, ref) => {
300
+ const { error, asChild, children, className, ...otherProps } = props;
301
+ const errorData = { error, message: error.message };
302
+ if (asChild && React.isValidElement(children)) {
303
+ return React.cloneElement(children, {
304
+ ref,
305
+ className,
306
+ 'data-testid': 'booking-form-load-error',
307
+ role: 'alert',
308
+ ...errorData,
309
+ ...otherProps,
310
+ });
311
+ }
312
+ return (_jsx("div", { ref: ref, className: className, "data-testid": "booking-form-load-error", role: "alert", ...otherProps, children: children ?? error.message }));
313
+ });
314
+ /**
315
+ * Type guard to check if an error is a BookingFormConfigurationError
316
+ */
317
+ export function isBookingFormConfigurationError(error) {
318
+ return error instanceof BookingFormConfigurationError;
319
+ }
320
+ // Re-export the error class for convenience
321
+ export { BookingFormConfigurationError };
@@ -4,7 +4,7 @@
4
4
  * Note: Currently supports single service only
5
5
  */
6
6
  import React from 'react';
7
- import { type PaymentServiceConfig, type SlotService } from '../../../services/payment/payment.js';
7
+ import { type PaymentServiceConfig, type PricingServiceSelection } from '../../../services/payment/payment.js';
8
8
  import type { MoneyData } from '@wix/headless-components/react';
9
9
  /**
10
10
  * Props for Payment.Root component
@@ -16,8 +16,8 @@ export interface RootProps {
16
16
  }) => React.ReactNode);
17
17
  /** Service config - SSR only (paymentDetails) */
18
18
  paymentServiceConfig?: PaymentServiceConfig;
19
- /** Slot services for lazy loading (triggers API call) */
20
- slotServices?: SlotService[];
19
+ /** Pricing service selections for lazy loading (triggers API call) */
20
+ pricingServiceSelections?: PricingServiceSelection[];
21
21
  /** Number of participants for lazy loading */
22
22
  numberOfParticipants?: number;
23
23
  }
@@ -25,7 +25,7 @@ export interface RootProps {
25
25
  * Root component that provides payment context.
26
26
  * Supports 3 modes:
27
27
  * 1. SSR: paymentServiceConfig.paymentDetails - pre-fetched data
28
- * 2. Lazy loading: slotServices + numberOfParticipants props - calls API on init
28
+ * 2. Lazy loading: pricingServiceSelections + numberOfParticipants props - calls API on init
29
29
  * 3. Reactive: no config/props - reads from BookingService signals
30
30
  *
31
31
  * @component
@@ -37,7 +37,7 @@ export interface RootProps {
37
37
  * </Payment.Root>
38
38
  *
39
39
  * // Lazy loading mode
40
- * <Payment.Root slotServices={[...]} numberOfParticipants={2}>
40
+ * <Payment.Root pricingServiceSelections={[...]} numberOfParticipants={2}>
41
41
  * {({ isLoading }) => isLoading ? <div>Loading...</div> : <Payment.Total />}
42
42
  * </Payment.Root>
43
43
  *
@@ -59,6 +59,12 @@ export interface DataProps {
59
59
  discount: MoneyData | null;
60
60
  payNow: MoneyData | null;
61
61
  payLater: MoneyData | null;
62
+ priceText: string | null;
63
+ hasPriceText: boolean;
64
+ isFree: boolean;
65
+ hasTax: boolean;
66
+ hasDiscount: boolean;
67
+ isDeposit: boolean;
62
68
  currency: string;
63
69
  isLoading: boolean;
64
70
  error: string | null;
@@ -123,3 +129,76 @@ export interface LineItemsProps {
123
129
  * ```
124
130
  */
125
131
  export declare function LineItems(props: LineItemsProps): React.ReactNode;
132
+ /**
133
+ * Context value for LineItem components
134
+ */
135
+ export interface LineItemContextValue {
136
+ lineItem: LineItem;
137
+ currency: string;
138
+ }
139
+ /**
140
+ * Hook to access the current LineItem from context.
141
+ * Must be used within LineItemProvider.
142
+ *
143
+ * @throws Error if used outside of LineItemProvider context
144
+ */
145
+ export declare function useLineItemContext(): LineItemContextValue;
146
+ /**
147
+ * Props for LineItemProvider
148
+ */
149
+ export interface LineItemProviderProps {
150
+ children: React.ReactNode;
151
+ lineItem: LineItem;
152
+ currency: string;
153
+ }
154
+ /**
155
+ * Provider component used by repeaters to provide LineItem context.
156
+ * Can be used by core consumers to create their own repeater.
157
+ *
158
+ * @component
159
+ * @example
160
+ * ```tsx
161
+ * {lineItems.map((item) => (
162
+ * <LineItemProvider key={item.id} lineItem={item} currency={currency}>
163
+ * {children}
164
+ * </LineItemProvider>
165
+ * ))}
166
+ * ```
167
+ */
168
+ export declare function LineItemProvider(props: LineItemProviderProps): React.ReactNode;
169
+ /**
170
+ * Props for LineItemInfo render prop component
171
+ */
172
+ export interface LineItemInfoProps {
173
+ children: (data: {
174
+ lineItem: LineItem;
175
+ currency: string;
176
+ totalPrice: MoneyData | null;
177
+ subtotal: MoneyData | null;
178
+ tax: MoneyData | null;
179
+ discount: MoneyData | null;
180
+ deposit: MoneyData | null;
181
+ priceText: string | null;
182
+ hasPriceText: boolean;
183
+ isFree: boolean;
184
+ hasDiscount: boolean;
185
+ hasDeposit: boolean;
186
+ }) => React.ReactNode;
187
+ }
188
+ /**
189
+ * Core component that provides access to line item info via render props.
190
+ * Must be used within LineItemProvider.
191
+ *
192
+ * @component
193
+ * @example
194
+ * ```tsx
195
+ * <LineItemInfo>
196
+ * {({ totalPrice, priceText, lineItem }) => (
197
+ * <div>
198
+ * {priceText ? <span>{priceText}</span> : <Money money={totalPrice} />}
199
+ * </div>
200
+ * )}
201
+ * </LineItemInfo>
202
+ * ```
203
+ */
204
+ export declare function LineItemInfo(props: LineItemInfoProps): React.ReactNode;
@@ -1,4 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Core Payment Components
4
+ * Provides low-level access to PaymentService via render props
5
+ * Note: Currently supports single service only
6
+ */
7
+ import React from 'react';
2
8
  import { WixServices, useService } from '@wix/services-manager-react';
3
9
  import { createServicesMap } from '@wix/services-manager';
4
10
  import { PaymentServiceDefinition, PaymentService, } from '../../../services/payment/payment.js';
@@ -20,7 +26,7 @@ function RootContent({ children, }) {
20
26
  * Root component that provides payment context.
21
27
  * Supports 3 modes:
22
28
  * 1. SSR: paymentServiceConfig.paymentDetails - pre-fetched data
23
- * 2. Lazy loading: slotServices + numberOfParticipants props - calls API on init
29
+ * 2. Lazy loading: pricingServiceSelections + numberOfParticipants props - calls API on init
24
30
  * 3. Reactive: no config/props - reads from BookingService signals
25
31
  *
26
32
  * @component
@@ -32,7 +38,7 @@ function RootContent({ children, }) {
32
38
  * </Payment.Root>
33
39
  *
34
40
  * // Lazy loading mode
35
- * <Payment.Root slotServices={[...]} numberOfParticipants={2}>
41
+ * <Payment.Root pricingServiceSelections={[...]} numberOfParticipants={2}>
36
42
  * {({ isLoading }) => isLoading ? <div>Loading...</div> : <Payment.Total />}
37
43
  * </Payment.Root>
38
44
  *
@@ -43,11 +49,11 @@ function RootContent({ children, }) {
43
49
  * ```
44
50
  */
45
51
  export function Root(props) {
46
- const { children, paymentServiceConfig = {}, slotServices, numberOfParticipants, } = props;
52
+ const { children, paymentServiceConfig = {}, pricingServiceSelections, numberOfParticipants, } = props;
47
53
  // Merge public config with lazy loading props into internal config
48
54
  const internalConfig = {
49
55
  ...paymentServiceConfig,
50
- slotServices,
56
+ pricingServiceSelections,
51
57
  numberOfParticipants,
52
58
  };
53
59
  return (_jsx(WixServices, { servicesMap: createServicesMap().addService(PaymentServiceDefinition, PaymentService, internalConfig), children: _jsx(RootContent, { children: children }) }));
@@ -71,6 +77,16 @@ export function Data(props) {
71
77
  const discount = convertMultiCurrencyPriceToMoneyData(priceSummary?.discount, currency);
72
78
  const payNow = convertMultiCurrencyPriceToMoneyData(paymentDetails?.response?.payNow?.total, currency);
73
79
  const payLater = convertMultiCurrencyPriceToMoneyData(paymentDetails?.response?.payLater?.total, currency);
80
+ // Get priceText from first line item (single service mode)
81
+ const additionalInfoList = paymentDetails?.additionalInfo?.lineItems || [];
82
+ const firstLineItemInfo = additionalInfoList[0];
83
+ const priceText = firstLineItemInfo?.priceText || null;
84
+ const hasPriceText = !!priceText;
85
+ const isFree = total !== null && total.amount === 0 && !hasPriceText;
86
+ // Computed flags
87
+ const hasTax = tax !== null && tax.amount > 0;
88
+ const hasDiscount = discount !== null && discount.amount > 0;
89
+ const isDeposit = payNow !== null && total !== null && payNow.amount < total.amount;
74
90
  return props.children({
75
91
  subtotal,
76
92
  total,
@@ -78,6 +94,12 @@ export function Data(props) {
78
94
  discount,
79
95
  payNow,
80
96
  payLater,
97
+ priceText,
98
+ hasPriceText,
99
+ isFree,
100
+ hasTax,
101
+ hasDiscount,
102
+ isDeposit,
81
103
  currency,
82
104
  isLoading,
83
105
  error,
@@ -134,3 +156,79 @@ export function LineItems(props) {
134
156
  error,
135
157
  });
136
158
  }
159
+ const LineItemContext = React.createContext(null);
160
+ /**
161
+ * Hook to access the current LineItem from context.
162
+ * Must be used within LineItemProvider.
163
+ *
164
+ * @throws Error if used outside of LineItemProvider context
165
+ */
166
+ export function useLineItemContext() {
167
+ const context = React.useContext(LineItemContext);
168
+ if (!context) {
169
+ throw new Error('LineItem components must be used within LineItemProvider');
170
+ }
171
+ return context;
172
+ }
173
+ /**
174
+ * Provider component used by repeaters to provide LineItem context.
175
+ * Can be used by core consumers to create their own repeater.
176
+ *
177
+ * @component
178
+ * @example
179
+ * ```tsx
180
+ * {lineItems.map((item) => (
181
+ * <LineItemProvider key={item.id} lineItem={item} currency={currency}>
182
+ * {children}
183
+ * </LineItemProvider>
184
+ * ))}
185
+ * ```
186
+ */
187
+ export function LineItemProvider(props) {
188
+ const { children, lineItem, currency } = props;
189
+ const contextValue = {
190
+ lineItem,
191
+ currency,
192
+ };
193
+ return (_jsx(LineItemContext.Provider, { value: contextValue, children: children }));
194
+ }
195
+ /**
196
+ * Core component that provides access to line item info via render props.
197
+ * Must be used within LineItemProvider.
198
+ *
199
+ * @component
200
+ * @example
201
+ * ```tsx
202
+ * <LineItemInfo>
203
+ * {({ totalPrice, priceText, lineItem }) => (
204
+ * <div>
205
+ * {priceText ? <span>{priceText}</span> : <Money money={totalPrice} />}
206
+ * </div>
207
+ * )}
208
+ * </LineItemInfo>
209
+ * ```
210
+ */
211
+ export function LineItemInfo(props) {
212
+ const { lineItem, currency } = useLineItemContext();
213
+ // Computed flags
214
+ const hasPriceText = !!lineItem.priceText;
215
+ const isFree = lineItem.totalPrice !== null &&
216
+ lineItem.totalPrice.amount === 0 &&
217
+ !hasPriceText;
218
+ const hasDiscount = lineItem.discount !== null && lineItem.discount.amount > 0;
219
+ const hasDeposit = lineItem.deposit !== null && lineItem.deposit.amount > 0;
220
+ return props.children({
221
+ lineItem,
222
+ currency,
223
+ totalPrice: lineItem.totalPrice,
224
+ subtotal: lineItem.subtotal,
225
+ tax: lineItem.tax,
226
+ discount: lineItem.discount,
227
+ deposit: lineItem.deposit,
228
+ priceText: lineItem.priceText,
229
+ hasPriceText,
230
+ isFree,
231
+ hasDiscount,
232
+ hasDeposit,
233
+ });
234
+ }