@wix/headless-restaurants-olo 0.0.44 → 0.0.46

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.
@@ -63,17 +63,17 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
63
63
  : '');
64
64
  const priceVariants = config.item?.priceVariants || [];
65
65
  let initialVariant;
66
- if (!config.editItemMode) {
67
- initialVariant = priceVariants.length > 0 ? priceVariants[0] : undefined;
66
+ if (config.editItemMode && config.editingItemValues?.selectedVariantId) {
67
+ initialVariant = priceVariants.find((variant) => variant._id === config.editingItemValues?.selectedVariantId);
68
68
  }
69
69
  else {
70
- if (config.editingItemValues?.selectedVariantId) {
71
- initialVariant = priceVariants.find((variant) => variant._id === config.editingItemValues?.selectedVariantId);
72
- }
73
- else {
74
- initialVariant =
75
- priceVariants.length > 0 ? priceVariants[0] : undefined;
76
- }
70
+ initialVariant =
71
+ priceVariants.length > 0
72
+ ? priceVariants.reduce((cheapest, current) => Number(current.priceInfo?.price) <
73
+ Number(cheapest.priceInfo?.price)
74
+ ? current
75
+ : cheapest)
76
+ : undefined;
77
77
  }
78
78
  const selectedVariant = signalsService.signal(initialVariant);
79
79
  const modifierGroups = config.item?.modifierGroups || [];
@@ -381,6 +381,8 @@ interface AddressPickerProps extends Omit<React.ComponentPropsWithoutRef<'div'>,
381
381
  predictionSecondaryTextClassName?: string;
382
382
  /** Optional class name for the prediction description */
383
383
  predictionDescriptionClassName?: string;
384
+ /** Optional class name for the error message */
385
+ errorClassName?: string;
384
386
  /** Minimum input length before showing predictions (default: 2) */
385
387
  minInputLength?: number;
386
388
  /** Children render prop that receives the address picker props */
@@ -392,6 +394,7 @@ interface AddressPickerProps extends Omit<React.ComponentPropsWithoutRef<'div'>,
392
394
  onPredictionSelect: (prediction: AddressPrediction) => Promise<void>;
393
395
  isDelivery: boolean;
394
396
  dispatchType: DispatchType | null;
397
+ error: string | null;
395
398
  }> | React.ReactNode;
396
399
  }
397
400
  export declare const AddressPicker: React.ForwardRefExoticComponent<AddressPickerProps & React.RefAttributes<HTMLDivElement>>;
@@ -441,4 +444,74 @@ interface SaveButtonProps extends Omit<React.ComponentPropsWithoutRef<'button'>,
441
444
  * ```
442
445
  */
443
446
  export declare const SaveButton: React.ForwardRefExoticComponent<SaveButtonProps & React.RefAttributes<HTMLButtonElement>>;
447
+ interface DispatchTypeSelectorProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'children'> {
448
+ /** Child components that will have access to dispatch type context */
449
+ children: React.ReactNode;
450
+ }
451
+ /**
452
+ * Container component for dispatch type selection (pickup or delivery)
453
+ * Provides context for child components to access dispatch type data
454
+ * Does not render if no dispatch types are available
455
+ *
456
+ * @example
457
+ * ```tsx
458
+ * <FulfillmentDetails.DispatchTypeSelector>
459
+ * <FulfillmentDetails.DispatchTypeOptions emptyState={<div>No options</div>}>
460
+ * <FulfillmentDetails.DispatchTypeOptionRepeater>
461
+ * {({ type, isSelected, selectDispatchType }) => (
462
+ * <button onClick={() => selectDispatchType(type)}>
463
+ * {type} {isSelected && '✓'}
464
+ * </button>
465
+ * )}
466
+ * </FulfillmentDetails.DispatchTypeOptionRepeater>
467
+ * </FulfillmentDetails.DispatchTypeOptions>
468
+ * </FulfillmentDetails.DispatchTypeSelector>
469
+ * ```
470
+ */
471
+ export declare const DispatchTypeSelector: React.ForwardRefExoticComponent<DispatchTypeSelectorProps & React.RefAttributes<HTMLDivElement>>;
472
+ interface DispatchTypeOptionsProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'children'> {
473
+ /** Child components to render when dispatch types are available */
474
+ children: React.ReactNode;
475
+ /** Optional content to display when no dispatch types are available */
476
+ emptyState?: React.ReactNode;
477
+ }
478
+ /**
479
+ * Container for dispatch type options list with empty state support
480
+ *
481
+ * @example
482
+ * ```tsx
483
+ * <FulfillmentDetails.DispatchTypeOptions emptyState={<div>No options available</div>}>
484
+ * <FulfillmentDetails.DispatchTypeOptionRepeater>
485
+ * {({ type }) => <div>{type}</div>}
486
+ * </FulfillmentDetails.DispatchTypeOptionRepeater>
487
+ * </FulfillmentDetails.DispatchTypeOptions>
488
+ * ```
489
+ */
490
+ export declare const DispatchTypeOptions: React.ForwardRefExoticComponent<DispatchTypeOptionsProps & React.RefAttributes<HTMLDivElement>>;
491
+ interface DispatchTypeOptionRepeaterProps {
492
+ /** Render prop that receives each dispatch type option data */
493
+ children: (props: {
494
+ type: DispatchType;
495
+ isSelected: boolean;
496
+ selectDispatchType: (type: DispatchType) => void;
497
+ }) => React.ReactNode;
498
+ }
499
+ /**
500
+ * Repeater component that renders children for each available dispatch type
501
+ *
502
+ * @example
503
+ * ```tsx
504
+ * <FulfillmentDetails.DispatchTypeOptionRepeater>
505
+ * {({ type, isSelected, selectDispatchType }) => (
506
+ * <button
507
+ * onClick={() => selectDispatchType(type)}
508
+ * data-selected={isSelected}
509
+ * >
510
+ * {type}
511
+ * </button>
512
+ * )}
513
+ * </FulfillmentDetails.DispatchTypeOptionRepeater>
514
+ * ```
515
+ */
516
+ export declare const DispatchTypeOptionRepeater: React.FC<DispatchTypeOptionRepeaterProps>;
444
517
  export {};
@@ -13,6 +13,14 @@ function useTimeSlotContext() {
13
13
  }
14
14
  return context;
15
15
  }
16
+ const DispatchTypeContext = React.createContext(null);
17
+ function useDispatchTypeContext() {
18
+ const context = React.useContext(DispatchTypeContext);
19
+ if (!context) {
20
+ throw new Error('useDispatchTypeContext must be used within a FulfillmentDetails.DispatchTypeSelector component');
21
+ }
22
+ return context;
23
+ }
16
24
  // ========================================
17
25
  // FULFILLMENT DETAILS HEADLESS COMPONENTS
18
26
  // ========================================
@@ -32,6 +40,9 @@ var TestIds;
32
40
  TestIds["addressPickerPredictions"] = "fulfillment-address-picker-predictions";
33
41
  TestIds["addressPickerPrediction"] = "fulfillment-address-picker-prediction";
34
42
  TestIds["saveButton"] = "fulfillment-save-button";
43
+ TestIds["dispatchTypeSelector"] = "fulfillment-dispatch-type-selector";
44
+ TestIds["dispatchTypeOptions"] = "fulfillment-dispatch-type-options";
45
+ TestIds["dispatchTypeOption"] = "fulfillment-dispatch-type-option";
35
46
  })(TestIds || (TestIds = {}));
36
47
  /**
37
48
  * Root headless component for Fulfillment Details
@@ -482,17 +493,14 @@ const AddressPickerInput = React.memo(({ inputValue, onInputChange, predictions,
482
493
  prevProps.onPredictionSelect === nextProps.onPredictionSelect &&
483
494
  !predictionsChanged);
484
495
  });
485
- export const AddressPicker = React.forwardRef(({ children, asChild, className, placeholder = 'Enter delivery address', inputClassName, predictionsClassName, predictionClassName, predictionMainTextClassName, predictionSecondaryTextClassName, predictionDescriptionClassName, minInputLength = 2, ...rest }, ref) => {
486
- return (_jsx(CoreFulfillmentDetails.AddressPicker, { children: ({ inputValue, onInputChange, predictions, isLoading, onPredictionSelect, isDelivery, dispatchType, }) => {
487
- // Don't render anything if not delivery
488
- if (!isDelivery) {
489
- return null;
490
- }
496
+ export const AddressPicker = React.forwardRef(({ children, asChild, className, placeholder = 'Enter delivery address', inputClassName, predictionsClassName, predictionClassName, predictionMainTextClassName, predictionSecondaryTextClassName, predictionDescriptionClassName, errorClassName, minInputLength = 2, ...rest }, ref) => {
497
+ return (_jsx(CoreFulfillmentDetails.AddressPicker, { children: ({ inputValue, onInputChange, predictions, isLoading, onPredictionSelect, isDelivery, dispatchType, error, }) => {
491
498
  // Memoize defaultContent to prevent recreation when only predictions change
492
499
  // This ensures the input element maintains focus
493
500
  // Note: predictions is passed to AddressPickerInput which handles it internally,
494
501
  // so we don't need to recreate defaultContent when only predictions change
495
- const defaultContent = useMemo(() => (_jsx("div", { ref: ref, className: className, "data-testid": TestIds.addressPicker, ...rest, children: _jsx(AddressPickerInput, { inputValue: inputValue, onInputChange: onInputChange, predictions: predictions, isLoading: isLoading, placeholder: placeholder, inputClassName: inputClassName, predictionsClassName: predictionsClassName, predictionClassName: predictionClassName, predictionMainTextClassName: predictionMainTextClassName, predictionSecondaryTextClassName: predictionSecondaryTextClassName, predictionDescriptionClassName: predictionDescriptionClassName, onPredictionSelect: onPredictionSelect }) })),
502
+ // IMPORTANT: useMemo must be called before any conditional returns to follow Rules of Hooks
503
+ const defaultContent = useMemo(() => (_jsxs("div", { ref: ref, className: className, "data-testid": TestIds.addressPicker, ...rest, children: [_jsx(AddressPickerInput, { inputValue: inputValue, onInputChange: onInputChange, predictions: predictions, isLoading: isLoading, placeholder: placeholder, inputClassName: inputClassName, predictionsClassName: predictionsClassName, predictionClassName: predictionClassName, predictionMainTextClassName: predictionMainTextClassName, predictionSecondaryTextClassName: predictionSecondaryTextClassName, predictionDescriptionClassName: predictionDescriptionClassName, onPredictionSelect: onPredictionSelect }), error && (_jsx("div", { className: errorClassName, "data-testid": `${TestIds.addressPicker}-error`, children: error }))] })),
496
504
  // Include predictions in deps so AddressPickerInput receives updates
497
505
  // AddressPickerInput is memoized and will only re-render when necessary
498
506
  [
@@ -508,9 +516,15 @@ export const AddressPicker = React.forwardRef(({ children, asChild, className, p
508
516
  predictionMainTextClassName,
509
517
  predictionSecondaryTextClassName,
510
518
  predictionDescriptionClassName,
519
+ errorClassName,
520
+ error,
511
521
  className,
512
522
  // Note: rest and ref are intentionally excluded as they should be stable
513
523
  ]);
524
+ // Don't render anything if not delivery
525
+ if (!isDelivery) {
526
+ return null;
527
+ }
514
528
  return (_jsx(AsChildSlot, { asChild: asChild, customElement: children, customElementProps: {
515
529
  inputValue,
516
530
  onInputChange,
@@ -519,6 +533,7 @@ export const AddressPicker = React.forwardRef(({ children, asChild, className, p
519
533
  onPredictionSelect,
520
534
  isDelivery,
521
535
  dispatchType,
536
+ error,
522
537
  }, content: defaultContent, children: defaultContent }));
523
538
  } }));
524
539
  });
@@ -562,3 +577,89 @@ export const SaveButton = React.forwardRef(({ children, asChild, className, savi
562
577
  } }));
563
578
  });
564
579
  SaveButton.displayName = 'FulfillmentDetails.SaveButton';
580
+ /**
581
+ * Container component for dispatch type selection (pickup or delivery)
582
+ * Provides context for child components to access dispatch type data
583
+ * Does not render if no dispatch types are available
584
+ *
585
+ * @example
586
+ * ```tsx
587
+ * <FulfillmentDetails.DispatchTypeSelector>
588
+ * <FulfillmentDetails.DispatchTypeOptions emptyState={<div>No options</div>}>
589
+ * <FulfillmentDetails.DispatchTypeOptionRepeater>
590
+ * {({ type, isSelected, selectDispatchType }) => (
591
+ * <button onClick={() => selectDispatchType(type)}>
592
+ * {type} {isSelected && '✓'}
593
+ * </button>
594
+ * )}
595
+ * </FulfillmentDetails.DispatchTypeOptionRepeater>
596
+ * </FulfillmentDetails.DispatchTypeOptions>
597
+ * </FulfillmentDetails.DispatchTypeSelector>
598
+ * ```
599
+ */
600
+ export const DispatchTypeSelector = React.forwardRef(({ children, className, ...rest }, ref) => {
601
+ return (_jsx(CoreFulfillmentDetails.DispatchTypeSelector, { children: ({ availableTypes, selectedType, selectDispatchType, hasTypes }) => {
602
+ if (!hasTypes)
603
+ return null;
604
+ const contextValue = {
605
+ availableTypes: availableTypes.map(({ type, isSelected }) => ({
606
+ type,
607
+ isSelected,
608
+ })),
609
+ selectedType,
610
+ selectDispatchType,
611
+ hasTypes,
612
+ };
613
+ return (_jsx(DispatchTypeContext.Provider, { value: contextValue, children: _jsx("div", { ref: ref, className: className, "data-testid": TestIds.dispatchTypeSelector, ...rest, children: children }) }));
614
+ } }));
615
+ });
616
+ DispatchTypeSelector.displayName = 'FulfillmentDetails.DispatchTypeSelector';
617
+ /**
618
+ * Container for dispatch type options list with empty state support
619
+ *
620
+ * @example
621
+ * ```tsx
622
+ * <FulfillmentDetails.DispatchTypeOptions emptyState={<div>No options available</div>}>
623
+ * <FulfillmentDetails.DispatchTypeOptionRepeater>
624
+ * {({ type }) => <div>{type}</div>}
625
+ * </FulfillmentDetails.DispatchTypeOptionRepeater>
626
+ * </FulfillmentDetails.DispatchTypeOptions>
627
+ * ```
628
+ */
629
+ export const DispatchTypeOptions = React.forwardRef(({ children, emptyState, className, ...rest }, ref) => {
630
+ const { hasTypes } = useDispatchTypeContext();
631
+ if (!hasTypes) {
632
+ return emptyState || null;
633
+ }
634
+ return (_jsx("div", { ref: ref, className: className, "data-testid": TestIds.dispatchTypeOptions, ...rest, children: children }));
635
+ });
636
+ DispatchTypeOptions.displayName = 'FulfillmentDetails.DispatchTypeOptions';
637
+ /**
638
+ * Repeater component that renders children for each available dispatch type
639
+ *
640
+ * @example
641
+ * ```tsx
642
+ * <FulfillmentDetails.DispatchTypeOptionRepeater>
643
+ * {({ type, isSelected, selectDispatchType }) => (
644
+ * <button
645
+ * onClick={() => selectDispatchType(type)}
646
+ * data-selected={isSelected}
647
+ * >
648
+ * {type}
649
+ * </button>
650
+ * )}
651
+ * </FulfillmentDetails.DispatchTypeOptionRepeater>
652
+ * ```
653
+ */
654
+ export const DispatchTypeOptionRepeater = ({ children }) => {
655
+ const { availableTypes, selectDispatchType, hasTypes } = useDispatchTypeContext();
656
+ if (!hasTypes)
657
+ return null;
658
+ return (_jsx(_Fragment, { children: availableTypes.map(({ type, isSelected }) => (_jsx(React.Fragment, { children: children({
659
+ type,
660
+ isSelected,
661
+ selectDispatchType,
662
+ }) }, type))) }));
663
+ };
664
+ DispatchTypeOptionRepeater.displayName =
665
+ 'FulfillmentDetails.DispatchTypeOptionRepeater';
@@ -152,6 +152,7 @@ export interface AddToCartButtonProps {
152
152
  /** Text label for the button */
153
153
  label: React.ReactNode;
154
154
  formattedPrice: string;
155
+ price: number;
155
156
  }>;
156
157
  }
157
158
  /**
@@ -132,7 +132,7 @@ Variants.displayName = 'ItemDetails.Variants';
132
132
  * ```
133
133
  */
134
134
  export const AddToCartButton = React.forwardRef(({ asChild, children, className, addToCartLabelMap, ...props }, ref) => {
135
- return (_jsx(CoreItemDetails.LineItemComponent, { addToCartLabelMap: addToCartLabelMap, children: ({ onHandleAddToCart, buttonState, addToCartButtonDisabled, loadingState, labelText, formattedPrice, lineItem, }) => {
135
+ return (_jsx(CoreItemDetails.LineItemComponent, { addToCartLabelMap: addToCartLabelMap, children: ({ onHandleAddToCart, buttonState, addToCartButtonDisabled, loadingState, labelText, price, formattedPrice, lineItem, }) => {
136
136
  const label = (_jsxs(_Fragment, { children: [_jsx("span", { children: labelText }), " ", _jsx("span", { children: " | " }), _jsx("span", { children: formattedPrice })] }));
137
137
  return (_jsx(AsChildSlot, { asChild: asChild, className: className, customElement: children, customElementProps: {
138
138
  buttonState,
@@ -141,6 +141,7 @@ export const AddToCartButton = React.forwardRef(({ asChild, children, className,
141
141
  onHandleAddToCart,
142
142
  label,
143
143
  formattedPrice,
144
+ price,
144
145
  }, ref: ref, ...props, children: _jsx(Commerce.Actions.AddToCart, { asChild: false, label: label, className: className, lineItems: [lineItem], ...props }) }));
145
146
  } }));
146
147
  });
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { type FulfillmentDetailsServiceConfig } from '../../services/fulfillment-details-service.js';
3
3
  import { DispatchType, FulfillmentTypeEnum, TimeSlot } from '../../types/fulfillments-types.js';
4
+ import { StreetAddress } from '../../types/operation.js';
4
5
  export interface RootProps {
5
6
  children: React.ReactNode;
6
7
  fulfillmentDetailsServiceConfig?: FulfillmentDetailsServiceConfig;
@@ -97,6 +98,8 @@ export interface DeliveryAddressFields {
97
98
  country?: string;
98
99
  /** Formatted full address string */
99
100
  formattedAddress?: string;
101
+ /** Street address */
102
+ streetAddress?: StreetAddress;
100
103
  }
101
104
  export interface DeliveryAddressProps {
102
105
  children: (props: {
@@ -168,6 +171,8 @@ export interface AddressPickerProps {
168
171
  isDelivery: boolean;
169
172
  /** Current dispatch type */
170
173
  dispatchType: DispatchType | null;
174
+ /** Error message from the service */
175
+ error: string | null;
171
176
  }) => React.ReactNode;
172
177
  }
173
178
  /**
@@ -237,3 +242,38 @@ export interface SaveButtonProps {
237
242
  * ```
238
243
  */
239
244
  export declare const SaveButton: React.FC<SaveButtonProps>;
245
+ export interface DispatchTypeSelectorProps {
246
+ children: (props: {
247
+ availableTypes: Array<{
248
+ type: DispatchType;
249
+ isSelected: boolean;
250
+ }>;
251
+ selectedType: DispatchType | null;
252
+ selectDispatchType: (type: DispatchType) => void;
253
+ hasTypes: boolean;
254
+ }) => React.ReactNode;
255
+ }
256
+ /**
257
+ * Component that provides dispatch type selection (pickup or delivery)
258
+ * Uses FulfillmentDetailsService to manage dispatch type state
259
+ *
260
+ * @example
261
+ * ```tsx
262
+ * <CoreFulfillmentDetails.DispatchTypeSelector>
263
+ * {({ availableTypes, selectedType, selectDispatchType }) => (
264
+ * <div>
265
+ * {availableTypes.map(({ type, isSelected }) => (
266
+ * <button
267
+ * key={type}
268
+ * onClick={() => selectDispatchType(type)}
269
+ * disabled={isSelected}
270
+ * >
271
+ * {type} {isSelected && '(selected)'}
272
+ * </button>
273
+ * ))}
274
+ * </div>
275
+ * )}
276
+ * </CoreFulfillmentDetails.DispatchTypeSelector>
277
+ * ```
278
+ */
279
+ export declare const DispatchTypeSelector: React.FC<DispatchTypeSelectorProps>;
@@ -22,12 +22,19 @@ export const Root = ({ children, fulfillmentDetailsServiceConfig, }) => {
22
22
  if (!fulfillmentDetailsServiceConfig &&
23
23
  oloSettingsService.operation?.get()) {
24
24
  const operation = oloSettingsService.operation.get();
25
+ const availableDispatchTypes = fulfillmentsService.availableTypes?.get();
25
26
  if (operation) {
26
- const loadedConfig = loadFulfillmentDetailsServiceConfig(operation);
27
+ const loadedConfig = loadFulfillmentDetailsServiceConfig(operation, {
28
+ availableDispatchTypes,
29
+ });
27
30
  setConfig(loadedConfig);
28
31
  }
29
32
  }
30
- }, [fulfillmentDetailsServiceConfig, oloSettingsService.operation]);
33
+ }, [
34
+ fulfillmentDetailsServiceConfig,
35
+ oloSettingsService.operation,
36
+ fulfillmentsService.availableTypes,
37
+ ]);
31
38
  if (!config || isFulfillmentsLoading) {
32
39
  return null;
33
40
  }
@@ -41,7 +48,7 @@ export const Root = ({ children, fulfillmentDetailsServiceConfig, }) => {
41
48
  export const AddressName = ({ children }) => {
42
49
  const fulfillmentDetailsService = useService(FulfillmentDetailsServiceDefinition);
43
50
  const selectedAddress = fulfillmentDetailsService?.address?.get();
44
- console.log('selectedAddress', selectedAddress);
51
+ const error = fulfillmentDetailsService.error?.get() ?? null;
45
52
  const dispatchType = fulfillmentDetailsService.dispatchType?.get();
46
53
  const addressName = selectedAddress?.formatted;
47
54
  const fullAddress = selectedAddress
@@ -58,7 +65,9 @@ export const AddressName = ({ children }) => {
58
65
  .filter(Boolean)
59
66
  .join(', ')
60
67
  : null;
61
- const hasAddress = Boolean(selectedAddress);
68
+ console.log('error', error);
69
+ console.log('selectedAddress', selectedAddress);
70
+ const hasAddress = Boolean(selectedAddress && !error);
62
71
  return children({
63
72
  addressName,
64
73
  fullAddress,
@@ -209,6 +218,7 @@ export const DeliveryAddress = ({ children, }) => {
209
218
  : null;
210
219
  const hasAddress = Boolean(currentAddress);
211
220
  const onAddressChange = (newAddress) => {
221
+ console.log('onAddressChange', newAddress);
212
222
  fulfillmentDetailsService.setAddress(newAddress);
213
223
  };
214
224
  const onAddressClear = () => {
@@ -379,7 +389,9 @@ export const AddressPicker = ({ children }) => {
379
389
  subdivision: address.subdivision || '',
380
390
  postalCode: address.postalCode || '',
381
391
  country: address.country || '',
382
- formattedAddress,
392
+ // @ts-expect-error
393
+ formattedAddress: address.formattedAddress || '',
394
+ streetAddress: address.streetAddress,
383
395
  };
384
396
  // TODO - check first available time slot & available dates
385
397
  fulfillmentDetailsService.setAddress(addressFields);
@@ -402,6 +414,7 @@ export const AddressPicker = ({ children }) => {
402
414
  setIsLoading(false);
403
415
  }
404
416
  };
417
+ const error = fulfillmentDetailsService.error?.get() ?? null;
405
418
  return children({
406
419
  inputValue,
407
420
  onInputChange,
@@ -410,6 +423,7 @@ export const AddressPicker = ({ children }) => {
410
423
  onPredictionSelect,
411
424
  isDelivery,
412
425
  dispatchType,
426
+ error,
413
427
  });
414
428
  };
415
429
  /**
@@ -461,3 +475,48 @@ export const SaveButton = ({ children }) => {
461
475
  dispatchType,
462
476
  });
463
477
  };
478
+ /**
479
+ * Component that provides dispatch type selection (pickup or delivery)
480
+ * Uses FulfillmentDetailsService to manage dispatch type state
481
+ *
482
+ * @example
483
+ * ```tsx
484
+ * <CoreFulfillmentDetails.DispatchTypeSelector>
485
+ * {({ availableTypes, selectedType, selectDispatchType }) => (
486
+ * <div>
487
+ * {availableTypes.map(({ type, isSelected }) => (
488
+ * <button
489
+ * key={type}
490
+ * onClick={() => selectDispatchType(type)}
491
+ * disabled={isSelected}
492
+ * >
493
+ * {type} {isSelected && '(selected)'}
494
+ * </button>
495
+ * ))}
496
+ * </div>
497
+ * )}
498
+ * </CoreFulfillmentDetails.DispatchTypeSelector>
499
+ * ```
500
+ */
501
+ export const DispatchTypeSelector = ({ children, }) => {
502
+ const fulfillmentDetailsService = useService(FulfillmentDetailsServiceDefinition);
503
+ const selectedType = fulfillmentDetailsService.dispatchType?.get() ?? null;
504
+ const availableDispatchTypes = fulfillmentDetailsService.availableDispatchTypes?.get() ?? [];
505
+ // Map available dispatch types to the format needed
506
+ const availableTypes = availableDispatchTypes.map((type) => ({
507
+ type,
508
+ isSelected: type === selectedType,
509
+ }));
510
+ console.log('availableTypes', availableTypes);
511
+ const selectDispatchType = (type) => {
512
+ console.log('selectDispatchType', type);
513
+ fulfillmentDetailsService.setDispatchType(type);
514
+ };
515
+ const hasTypes = availableTypes.length > 0;
516
+ return children({
517
+ availableTypes,
518
+ selectedType,
519
+ selectDispatchType,
520
+ hasTypes,
521
+ });
522
+ };
@@ -28,6 +28,7 @@ interface ItemDetailsLineItemProps {
28
28
  addToCartButtonDisabled: boolean;
29
29
  labelText: string;
30
30
  formattedPrice: string;
31
+ price: number;
31
32
  }) => React.ReactNode;
32
33
  }
33
34
  export declare const LineItemComponent: React.FC<ItemDetailsLineItemProps>;
@@ -48,6 +48,7 @@ export const LineItemComponent = ({ addToCartLabelMap, children, }) => {
48
48
  labelText,
49
49
  formattedPrice,
50
50
  lineItem,
51
+ price,
51
52
  });
52
53
  };
53
54
  export const QuantityComponent = ({ children, }) => {
@@ -36,6 +36,8 @@ export interface FulfillmentDetailsServiceAPI {
36
36
  availableTimeSlotsForDate: Signal<TimeSlot[]>;
37
37
  /** Available time slots filtered by current dispatch type */
38
38
  availableTimeSlots: ReadOnlySignal<TimeSlot[]>;
39
+ /** Available dispatch types (PICKUP and/or DELIVERY) based on available time slots */
40
+ availableDispatchTypes: ReadOnlySignal<DispatchType[]>;
39
41
  /** Selected scheduling type (ASAP or PREORDER) */
40
42
  schedulingType: Signal<FulfillmentTypeEnum | undefined>;
41
43
  /**
@@ -101,6 +103,8 @@ export interface FulfillmentDetailsServiceConfig {
101
103
  initialAddress?: Address;
102
104
  /** Number of days ahead to calculate available dates (default: 30) */
103
105
  daysAhead?: number;
106
+ /** Available dispatch types from fulfillments service */
107
+ availableDispatchTypes?: DispatchType[];
104
108
  }
105
109
  export declare const FulfillmentDetailsServiceDefinition: string & {
106
110
  __api: FulfillmentDetailsServiceAPI;
@@ -124,4 +128,5 @@ export declare function loadFulfillmentDetailsServiceConfig(operation: Operation
124
128
  initialDispatchType?: DispatchType;
125
129
  initialAddress?: Address;
126
130
  daysAhead?: number;
131
+ availableDispatchTypes?: DispatchType[];
127
132
  }): FulfillmentDetailsServiceConfig;