@wix/headless-restaurants-olo 0.0.17 → 0.0.19

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.
@@ -1,9 +1,9 @@
1
1
  import React from 'react';
2
- import { Commerce } from '@wix/ecom/components';
2
+ import { type LineItem } from '@wix/ecom/services';
3
3
  import { type AsChildChildren } from '@wix/headless-utils/react';
4
4
  import { ItemServiceConfig } from '../services/item-details-service.js';
5
5
  import { EnhancedVariant } from '@wix/headless-restaurants-menus/services';
6
- import { AvailabilityStatus, AvailabilityStatusMap } from '../services/common-types.js';
6
+ import { AvailabilityStatus, AvailabilityStatusMap, AddToCartButtonState } from '../services/common-types.js';
7
7
  /**
8
8
  * Root component for menu item display and interaction.
9
9
  * Provides context for all menu item-related components like name, price, description, image, etc.
@@ -72,6 +72,7 @@ export interface SpecialRequestProps {
72
72
  asChild?: boolean;
73
73
  children?: AsChildChildren<{
74
74
  description: string;
75
+ onChange: (value: string) => void;
75
76
  }>;
76
77
  /** Placeholder text for the textarea */
77
78
  placeholder?: string;
@@ -94,16 +95,35 @@ export interface SpecialRequestProps {
94
95
  * Usage:
95
96
  * <ItemDetails.AddToCartButton>Add to cart</ItemDetails.AddToCartButton>
96
97
  */
97
- export interface AddToCartButtonProps extends Omit<React.ComponentPropsWithoutRef<typeof Commerce.Actions.AddToCart>, 'lineItems'> {
98
+ export interface AddToCartButtonProps {
98
99
  asChild?: boolean;
99
- children?: React.ReactNode;
100
100
  className?: string;
101
- label?: string;
101
+ addToCartLabelMap: Record<AddToCartButtonState, string>;
102
+ children: (props: {
103
+ lineItem: LineItem;
104
+ buttonState: AddToCartButtonState;
105
+ addToCartButtonDisabled?: boolean;
106
+ /** Content to display when loading */
107
+ loadingState?: string | React.ReactNode;
108
+ /** Text label for the button */
109
+ label: React.ReactNode;
110
+ formattedPrice: string;
111
+ }) => React.ReactNode;
102
112
  }
113
+ export declare const AddToCartButton: React.ForwardRefExoticComponent<AddToCartButtonProps & React.RefAttributes<HTMLElement>>;
103
114
  export interface ItemDetailsQuantityProps {
104
- children: React.ReactNode;
115
+ asChild?: boolean;
116
+ className?: string;
117
+ children: (props: {
118
+ quantity: number;
119
+ increment: () => void;
120
+ decrement: () => void;
121
+ setQuantity: (quantity: number) => void;
122
+ canIncrement: boolean;
123
+ canDecrement: boolean;
124
+ onValueChange: (value: number) => void;
125
+ }) => React.ReactNode;
105
126
  }
106
- export declare const AddToCartButton: React.FC<AddToCartButtonProps>;
107
127
  export declare const Quantity: React.FC<ItemDetailsQuantityProps>;
108
128
  export declare const SpecialRequest: React.ForwardRefExoticComponent<SpecialRequestProps & React.RefAttributes<never>>;
109
129
  export interface ItemDetailsAvailabilityProps {
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { Commerce } from '@wix/ecom/components';
4
4
  import { AsChildSlot } from '@wix/headless-utils/react';
@@ -38,15 +38,42 @@ export const Variants = React.forwardRef(({ children, className, asChild, varian
38
38
  } }));
39
39
  });
40
40
  Variants.displayName = 'ItemDetails.Variants';
41
- export const AddToCartButton = ({ asChild, children, className, label = 'Add to cart', ...props }) => {
42
- return (_jsx(CoreItemDetails.LineItemComponent, { children: ({ lineItem }) => (_jsx(Commerce.Actions.AddToCart, { asChild: asChild, label: label, className: className, lineItems: [lineItem], ...props, children: children })) }));
43
- };
41
+ export const AddToCartButton = React.forwardRef(({ asChild, children, className, addToCartLabelMap, ...props }, ref) => {
42
+ return (_jsx(CoreItemDetails.LineItemComponent, { addToCartLabelMap: addToCartLabelMap, children: ({ lineItem, buttonState, addToCartButtonDisabled, loadingState, labelText, formattedPrice, }) => {
43
+ const label = (_jsxs(_Fragment, { children: [_jsx("span", { children: labelText }), " ", _jsx("span", { children: " | " }), _jsx("span", { children: formattedPrice })] }));
44
+ return (_jsx(AsChildSlot, { asChild: asChild, className: className, customElement: children, customElementProps: {
45
+ buttonState,
46
+ addToCartButtonDisabled,
47
+ loadingState,
48
+ lineItem,
49
+ lineItems: [lineItem],
50
+ label,
51
+ formattedPrice,
52
+ }, ref: ref, ...props, children: _jsx(Commerce.Actions.AddToCart, { asChild: false, label: label, className: className, lineItems: [lineItem], ...props, children: children({
53
+ lineItem,
54
+ buttonState,
55
+ addToCartButtonDisabled,
56
+ loadingState,
57
+ label,
58
+ formattedPrice,
59
+ }) }) }));
60
+ } }));
61
+ });
62
+ AddToCartButton.displayName = 'AddToCartButton';
44
63
  export const Quantity = ({ children }) => {
45
- return (_jsx(CoreItemDetails.QuantityComponent, { children: ({ quantity, onValueChange, }) => (_jsx(QuantityComponent.Root, { onValueChange: onValueChange, initialValue: quantity, children: children })) }));
64
+ return (_jsx(CoreItemDetails.QuantityComponent, { children: ({ quantity, onValueChange, increment, decrement, setQuantity, canIncrement, canDecrement, }) => (_jsx(QuantityComponent.Root, { onValueChange: onValueChange, initialValue: quantity, children: children({
65
+ quantity,
66
+ increment,
67
+ decrement,
68
+ setQuantity,
69
+ canIncrement,
70
+ canDecrement,
71
+ onValueChange,
72
+ }) })) }));
46
73
  };
47
74
  Quantity.displayName = 'Quantity';
48
75
  export const SpecialRequest = React.forwardRef(({ className, labelClassName, placeholder = 'Any special requests or dietary restrictions?', maxLength = 200, rows = 3, label = 'Special Requests', asChild, children, ...props }, ref) => {
49
- return (_jsx(CoreItemDetails.SpecialRequest, { children: ({ value, onChange, }) => (_jsxs(AsChildSlot, { ref: ref, asChild: asChild, className: className, onChange: onChange, "data-testid": TestIds.itemSpecialRequest, customElement: children, customElementProps: { label, value }, content: value, ...props, children: [label && _jsx("label", { className: labelClassName, children: label }), _jsx("textarea", { value: value, onChange: (e) => onChange(e.target.value), placeholder: placeholder, maxLength: maxLength, rows: rows, className: className, children: value })] })) }));
76
+ return (_jsx(CoreItemDetails.SpecialRequest, { children: ({ value, onChange, }) => (_jsxs(AsChildSlot, { ref: ref, asChild: asChild, className: className, onChange: onChange, "data-testid": TestIds.itemSpecialRequest, customElement: children, customElementProps: { label, value, onChange }, content: value, ...props, children: [label && _jsx("label", { className: labelClassName, children: label }), _jsx("textarea", { value: value, onChange: (e) => onChange(e.target.value), placeholder: placeholder, maxLength: maxLength, rows: rows, className: className, children: value })] })) }));
50
77
  });
51
78
  SpecialRequest.displayName = 'SpecialRequest';
52
79
  export const AvailabilityComponent = React.forwardRef(({ asChild, children, textClassName, buttonClassName, availabilityStatusMap, ...rest }, ref) => {
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { type LineItem } from '@wix/ecom/services';
3
3
  import { ItemServiceConfig } from '../../services/item-details-service.js';
4
4
  import { EnhancedVariant } from '@wix/headless-restaurants-menus/services';
5
- import { AvailabilityStatus, AvailabilityStatusMap } from '../../services/common-types.js';
5
+ import { AvailabilityStatus, AvailabilityStatusMap, AddToCartButtonState } from '../../services/common-types.js';
6
6
  interface ItemDetailsRootProps {
7
7
  children: (props: {
8
8
  item: unknown;
@@ -18,8 +18,14 @@ interface ItemDetailsSpecialRequestProps {
18
18
  }
19
19
  export declare const SpecialRequest: React.FC<ItemDetailsSpecialRequestProps>;
20
20
  interface ItemDetailsLineItemProps {
21
+ addToCartLabelMap: Record<AddToCartButtonState, string>;
21
22
  children: (props: {
23
+ loadingState: string | React.ReactNode;
22
24
  lineItem: LineItem;
25
+ buttonState: AddToCartButtonState;
26
+ addToCartButtonDisabled: boolean;
27
+ labelText: string;
28
+ formattedPrice: string;
23
29
  }) => React.ReactNode;
24
30
  }
25
31
  export declare const LineItemComponent: React.FC<ItemDetailsLineItemProps>;
@@ -5,11 +5,10 @@ import { createServicesMap } from '@wix/services-manager';
5
5
  import { ItemService, ItemServiceDefinition, loadItemServiceConfig, } from '../../services/item-details-service.js';
6
6
  import { OLOSettingsServiceDefinition } from '../../services/olo-settings-service.js';
7
7
  import { useItemContext } from '@wix/headless-restaurants-menus/react';
8
- import { AvailabilityStatus, } from '../../services/common-types.js';
8
+ import { AvailabilityStatus, AddToCartButtonState, } from '../../services/common-types.js';
9
9
  export const Root = ({ children, itemDetailsServiceConfig, }) => {
10
10
  const service = useService(OLOSettingsServiceDefinition);
11
11
  const selectedItem = service.selectedItem?.get();
12
- console.log('selectedItem', selectedItem, itemDetailsServiceConfig);
13
12
  let config = itemDetailsServiceConfig;
14
13
  if (!config) {
15
14
  config = loadItemServiceConfig({
@@ -37,10 +36,25 @@ export const SpecialRequest = ({ children, }) => {
37
36
  // maxLength: 200
38
37
  });
39
38
  };
40
- export const LineItemComponent = ({ children, }) => {
39
+ export const LineItemComponent = ({ addToCartLabelMap, children, }) => {
41
40
  const service = useService(ItemServiceDefinition);
41
+ const oloSettingsService = useService(OLOSettingsServiceDefinition);
42
42
  const lineItem = service.lineItem?.get?.() ?? {};
43
- return children({ lineItem });
43
+ const loadingState = service.isLoading?.get?.() ?? false;
44
+ const buttonState = service.buttonState?.get?.() ?? AddToCartButtonState.VALID_TO_CONTINUE;
45
+ const addToCartButtonDisabled = service.addToCartButtonDisabled?.get?.() ?? false;
46
+ const price = service.price?.get?.() ?? 0;
47
+ const formatCurrency = oloSettingsService.formatCurrency;
48
+ const formattedPrice = formatCurrency(price);
49
+ const labelText = addToCartLabelMap[buttonState];
50
+ return children({
51
+ loadingState,
52
+ lineItem,
53
+ buttonState,
54
+ addToCartButtonDisabled,
55
+ labelText,
56
+ formattedPrice,
57
+ });
44
58
  };
45
59
  export const QuantityComponent = ({ children, }) => {
46
60
  const service = useService(ItemServiceDefinition);
@@ -35,3 +35,11 @@ export type AvailabilityStatusObject = {
35
35
  text?: string;
36
36
  };
37
37
  export type AvailabilityStatusMap = Partial<Record<Exclude<AvailabilityStatus, NextAvailability>, AvailabilityStatusObject>> & Record<NextAvailability, AvailabilityStatusWithActionObject>;
38
+ export declare enum AddToCartButtonState {
39
+ ITEM_UNAVAILABLE = 0,
40
+ OUT_OF_STOCK = 1,
41
+ NOT_VALID = 2,
42
+ EDITING = 3,
43
+ VALID_TO_CONTINUE = 4
44
+ }
45
+ export type AddToCartButtonLabelMap = Record<AddToCartButtonState, string>;
@@ -16,3 +16,11 @@ export var AvailabilityStatus;
16
16
  AvailabilityStatus[AvailabilityStatus["NEXT_AVAILABILITY_PICKUP"] = 3] = "NEXT_AVAILABILITY_PICKUP";
17
17
  AvailabilityStatus[AvailabilityStatus["NEXT_AVAILABILITY_DELIVERY"] = 4] = "NEXT_AVAILABILITY_DELIVERY";
18
18
  })(AvailabilityStatus || (AvailabilityStatus = {}));
19
+ export var AddToCartButtonState;
20
+ (function (AddToCartButtonState) {
21
+ AddToCartButtonState[AddToCartButtonState["ITEM_UNAVAILABLE"] = 0] = "ITEM_UNAVAILABLE";
22
+ AddToCartButtonState[AddToCartButtonState["OUT_OF_STOCK"] = 1] = "OUT_OF_STOCK";
23
+ AddToCartButtonState[AddToCartButtonState["NOT_VALID"] = 2] = "NOT_VALID";
24
+ AddToCartButtonState[AddToCartButtonState["EDITING"] = 3] = "EDITING";
25
+ AddToCartButtonState[AddToCartButtonState["VALID_TO_CONTINUE"] = 4] = "VALID_TO_CONTINUE";
26
+ })(AddToCartButtonState || (AddToCartButtonState = {}));
@@ -1,4 +1,4 @@
1
1
  export { ItemService, ItemServiceDefinition, loadItemServiceConfig, ItemServiceConfig, } from './item-details-service.js';
2
2
  export { OLOSettingsService, OLOSettingsServiceDefinition, loadOLOSettingsServiceConfig, type OLOSettingsServiceConfig, type OLOSettingsServiceAPI, } from './olo-settings-service.js';
3
- export { AvailabilityStatus, AvailabilityStatusMap } from './common-types.js';
3
+ export { AvailabilityStatus, AvailabilityStatusMap, AddToCartButtonState, AddToCartButtonLabelMap, } from './common-types.js';
4
4
  export { FulfillmentsService, FulfillmentsServiceDefinition, loadFulfillmentsServiceConfig, } from './fulfillments-service.js';
@@ -1,4 +1,4 @@
1
1
  export { ItemService, ItemServiceDefinition, loadItemServiceConfig, } from './item-details-service.js';
2
2
  export { OLOSettingsService, OLOSettingsServiceDefinition, loadOLOSettingsServiceConfig, } from './olo-settings-service.js';
3
- export { AvailabilityStatus } from './common-types.js';
3
+ export { AvailabilityStatus, AddToCartButtonState, } from './common-types.js';
4
4
  export { FulfillmentsService, FulfillmentsServiceDefinition, loadFulfillmentsServiceConfig, } from './fulfillments-service.js';
@@ -1,8 +1,8 @@
1
- import { type Signal } from '@wix/services-definitions/core-services/signals';
1
+ import { type Signal, type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
2
2
  import { type LineItem } from '@wix/ecom/services';
3
3
  import { itemVariants } from '@wix/restaurants';
4
4
  import type { EnhancedItem } from '@wix/headless-restaurants-menus/services';
5
- import { AvailabilityStatus } from './common-types.js';
5
+ import { AddToCartButtonState, AvailabilityStatus } from './common-types.js';
6
6
  type Variant = itemVariants.Variant;
7
7
  /**
8
8
  * API interface for the Item Detailsservice, providing reactive item data management.
@@ -15,7 +15,10 @@ export interface ItemServiceAPI {
15
15
  item?: Signal<EnhancedItem | undefined>;
16
16
  quantity: Signal<number>;
17
17
  specialRequest: Signal<string>;
18
- lineItem: Signal<LineItem>;
18
+ lineItem: ReadOnlySignal<LineItem>;
19
+ buttonState: ReadOnlySignal<AddToCartButtonState | undefined>;
20
+ addToCartButtonDisabled: Signal<boolean>;
21
+ price: ReadOnlySignal<number>;
19
22
  selectedVariant: Signal<Variant | undefined>;
20
23
  selectedModifiers: Signal<Record<string, Array<string>>>;
21
24
  availabilityStatus: Signal<AvailabilityStatus>;
@@ -59,6 +62,9 @@ export interface ItemServiceConfig {
59
62
  itemId?: string;
60
63
  operationId?: string;
61
64
  availabilityStatus?: AvailabilityStatus;
65
+ editItemMode?: boolean;
66
+ menuId?: string;
67
+ sectionId?: string;
62
68
  }
63
69
  export declare const ItemService: import("@wix/services-definitions").ServiceFactory<string & {
64
70
  __api: ItemServiceAPI;
@@ -1,7 +1,8 @@
1
1
  import { defineService, implementService } from '@wix/services-definitions';
2
2
  import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
3
- import { AvailabilityStatus } from './common-types.js';
4
- import { getModifiersInitState } from './utils.js';
3
+ import { AddToCartButtonState, AvailabilityStatus } from './common-types.js';
4
+ import { getAreNotEnoughModifiersOfMandatoryModifierGroupInStock, getModifiersInitState, getLineItemModifiers, getPriceVariantOptions, getSelectedModifierPrices, getSelectedVariantPrice, calculateItemPrice, } from './utils.js';
5
+ import { OLOSettingsServiceDefinition } from './olo-settings-service.js';
5
6
  /**
6
7
  * Service definition for the Item service.
7
8
  * This defines the contract that the ItemService must implement.
@@ -45,13 +46,13 @@ export const ItemServiceDefinition = defineService('item');
45
46
  const APP_ID = '9a5d83fd-8570-482e-81ab-cfa88942ee60';
46
47
  export const ItemService = implementService.withConfig()(ItemServiceDefinition, ({ getService, config }) => {
47
48
  const signalsService = getService(SignalsServiceDefinition);
49
+ const oloSettingsService = getService(OLOSettingsServiceDefinition);
48
50
  const availabilityStatus = signalsService.signal(config.availabilityStatus ?? AvailabilityStatus.AVAILABLE);
49
51
  const item = signalsService.signal(config.item);
50
52
  const isLoading = signalsService.signal(!!config.item);
51
53
  const error = signalsService.signal(config.item ? null : 'Item not found');
52
54
  const quantity = signalsService.signal(1);
53
55
  const specialRequest = signalsService.signal('');
54
- const lineItem = signalsService.signal({});
55
56
  const priceVariants = config.item?.priceVariants || [];
56
57
  const initialVariant = priceVariants.length > 0 ? priceVariants[0] : undefined;
57
58
  const selectedVariant = signalsService.signal(initialVariant);
@@ -65,44 +66,57 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
65
66
  return acc;
66
67
  }, {});
67
68
  const modifierGroupError = signalsService.signal(initialModifierGroupError);
68
- if (config.item) {
69
- lineItem.set({
69
+ const price = signalsService.computed(() => {
70
+ const basePrice = Number(config?.item?.priceInfo?.price ?? 0);
71
+ const variantPrice = getSelectedVariantPrice(selectedVariant.get(), priceVariants);
72
+ const modifierPrices = getSelectedModifierPrices(selectedModifiers.get() || {}, modifierGroups);
73
+ const itemQuantity = quantity.get() ?? 1;
74
+ return calculateItemPrice(basePrice, variantPrice, modifierPrices, itemQuantity);
75
+ });
76
+ const areModifiersOfMandatoryModifierGroupOutOfStock = getAreNotEnoughModifiersOfMandatoryModifierGroupInStock(modifierGroups);
77
+ const canAcceptOrders = oloSettingsService.canAcceptOrders ?? true;
78
+ const isValidToContinue = canAcceptOrders &&
79
+ config?.item?.orderSettings?.inStock &&
80
+ config.availabilityStatus === AvailabilityStatus.AVAILABLE &&
81
+ !areModifiersOfMandatoryModifierGroupOutOfStock;
82
+ const buttonState = signalsService.computed(() => {
83
+ return !config?.item?.orderSettings?.inStock ||
84
+ areModifiersOfMandatoryModifierGroupOutOfStock
85
+ ? AddToCartButtonState.OUT_OF_STOCK
86
+ : availabilityStatus.get() !== AvailabilityStatus.AVAILABLE
87
+ ? AddToCartButtonState.ITEM_UNAVAILABLE
88
+ : !canAcceptOrders
89
+ ? AddToCartButtonState.NOT_VALID
90
+ : config.editItemMode
91
+ ? AddToCartButtonState.EDITING
92
+ : isValidToContinue
93
+ ? AddToCartButtonState.VALID_TO_CONTINUE
94
+ : AddToCartButtonState.NOT_VALID;
95
+ });
96
+ const addToCartButtonDisabled = signalsService.signal(!isValidToContinue);
97
+ const lineItem = signalsService.computed(() => {
98
+ const formatCurrency = oloSettingsService.formatCurrency;
99
+ return {
70
100
  quantity: quantity.get(),
71
101
  catalogReference: {
72
- // @ts-expect-error - item is not typed
73
- catalogItemId: config.item._id,
102
+ catalogItemId: config.item?._id ?? undefined,
74
103
  appId: APP_ID,
75
104
  options: {
76
105
  operationId: config.operationId,
77
- // @ts-expect-error - item is not typed
78
- menuId: config.item.menuId,
79
- // @ts-expect-error - item is not typed
80
- sectionId: config.item.sectionId,
106
+ menuId: config.menuId,
107
+ sectionId: config.sectionId,
108
+ priceVariant: getPriceVariantOptions(selectedVariant.get(), formatCurrency),
109
+ modifierGroups: getLineItemModifiers(selectedModifiers.get(), modifierGroups, formatCurrency),
110
+ specialRequests: specialRequest.get(),
81
111
  },
82
112
  },
83
- });
84
- }
113
+ };
114
+ });
85
115
  const updateQuantity = (_quantity) => {
86
116
  quantity.set(_quantity);
87
- const _lineItem = lineItem.get();
88
- lineItem.set({
89
- ..._lineItem,
90
- quantity: _quantity,
91
- });
92
117
  };
93
118
  const updateSpecialRequest = (_specialRequest) => {
94
119
  specialRequest.set(_specialRequest);
95
- const _lineItem = lineItem.get();
96
- lineItem.set({
97
- ..._lineItem,
98
- catalogReference: {
99
- ..._lineItem.catalogReference,
100
- options: {
101
- ..._lineItem.catalogReference?.options,
102
- specialRequest: _specialRequest,
103
- },
104
- },
105
- });
106
120
  };
107
121
  const updateSelectedVariant = (variant) => {
108
122
  selectedVariant.set(variant);
@@ -157,6 +171,9 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
157
171
  availabilityStatus,
158
172
  getSelectedModifiers,
159
173
  modifierGroupError,
174
+ buttonState,
175
+ addToCartButtonDisabled,
176
+ price,
160
177
  };
161
178
  });
162
179
  /**
@@ -11,12 +11,16 @@ export interface OLOSettingsServiceAPI {
11
11
  currentFulfillment: Signal<string>;
12
12
  currentTimeSlot: Signal<string>;
13
13
  filterMenus: (menus: MenusServiceConfig['menus']) => MenusServiceConfig['menus'];
14
+ canAcceptOrders: boolean;
15
+ formatCurrency: (price?: number) => string;
14
16
  }
15
17
  export interface OLOSettingsServiceConfig {
16
18
  operationGroup?: operationGroupsSDK.OperationGroup;
17
19
  operation?: operationsSDK.Operation;
18
20
  availabilityDispatchAction?: () => void;
19
21
  menuIdsByOperation?: string[];
22
+ canAcceptOrders?: boolean;
23
+ formattedPrice?: (price?: number) => string;
20
24
  }
21
25
  export declare const OLOSettingsServiceDefinition: string & {
22
26
  __api: OLOSettingsServiceAPI;
@@ -8,6 +8,8 @@ export const OLOSettingsService = implementService.withConfig()(OLOSettingsServi
8
8
  const availabilityDispatchAction = signalsService.signal(config.availabilityDispatchAction);
9
9
  const operationGroup = signalsService.signal(config.operationGroup);
10
10
  const operation = signalsService.signal(config.operation);
11
+ const formatCurrency = config.formattedPrice ??
12
+ ((price) => price?.toFixed(2)?.toString() ?? '');
11
13
  const selectedItem = signalsService.signal(null);
12
14
  const isLoading = signalsService.signal(false);
13
15
  const error = signalsService.signal(null);
@@ -27,6 +29,8 @@ export const OLOSettingsService = implementService.withConfig()(OLOSettingsServi
27
29
  currentFulfillment,
28
30
  currentTimeSlot,
29
31
  filterMenus,
32
+ canAcceptOrders: config.canAcceptOrders ?? true,
33
+ formatCurrency,
30
34
  };
31
35
  });
32
36
  export async function loadOLOSettingsServiceConfig() {
@@ -34,4 +34,32 @@ export declare const hasToChooseAtLeastX: ({ required, minSelections, maxSelecti
34
34
  export declare const chooseUpToX: ({ required, minSelections, maxSelections, }: ruleUtilsArgs) => boolean;
35
35
  export declare const hasToChooseBetweenXAndY: ({ required, minSelections, maxSelections, }: ruleUtilsArgs) => boolean;
36
36
  export declare const getRuleTypeMapValue: (ruleTypeMap: RuleTypeMap, ruleType: RuleType, modifierGroupName: string, rule: EnhancedModifierGroup["rule"]) => string | undefined;
37
+ export declare const getAreNotEnoughModifiersOfMandatoryModifierGroupInStock: (modifierGroups: EnhancedModifierGroup[]) => boolean;
38
+ export declare const getLineItemModifiers: (selectedModifiers: Record<string, Array<string>>, modifierGroups: EnhancedModifierGroup[], formatCurrency: (price?: number) => string) => {
39
+ id: string;
40
+ modifiers: {
41
+ id: string;
42
+ price: string | undefined;
43
+ formattedPrice: string | undefined;
44
+ }[];
45
+ }[] | undefined;
46
+ export declare const getPriceVariantOptions: (selectedPriceVariant: {
47
+ _id?: string | null;
48
+ priceInfo?: {
49
+ price?: string;
50
+ };
51
+ } | undefined, formatCurrency: (price?: number) => string) => {
52
+ id: string;
53
+ formattedPrice: string;
54
+ } | undefined;
55
+ export declare const getSelectedModifierPrices: (selectedModifiers: Record<string, Array<string>>, modifierGroups: EnhancedModifierGroup[]) => number;
56
+ export declare const getSelectedVariantPrice: (selectedVariant: {
57
+ _id?: string | null;
58
+ } | undefined, priceVariants: Array<{
59
+ _id?: string | null;
60
+ priceInfo?: {
61
+ price?: string;
62
+ };
63
+ }>) => number;
64
+ export declare const calculateItemPrice: (basePrice: number, variantPrice: number, modifierPrices: number, quantity: number) => number;
37
65
  export {};
@@ -136,3 +136,79 @@ export const getRuleTypeMapValue = (ruleTypeMap, ruleType, modifierGroupName, ru
136
136
  }
137
137
  return undefined;
138
138
  };
139
+ export const getAreNotEnoughModifiersOfMandatoryModifierGroupInStock = (modifierGroups) => modifierGroups.some((modifierGroup) => {
140
+ const isModifierGroupMandatory = modifierGroup.rule?.required;
141
+ const minimumRequiredModifiers = modifierGroup.rule?.minSelections ?? 0;
142
+ const inStockModifiers = modifierGroup.modifiers.filter((modifier) => modifier.inStock ?? true);
143
+ const areMinimumRequiredModifiersInStock = inStockModifiers.length >= minimumRequiredModifiers;
144
+ return isModifierGroupMandatory && !areMinimumRequiredModifiersInStock;
145
+ });
146
+ export const getLineItemModifiers = (selectedModifiers, modifierGroups, formatCurrency) => {
147
+ const modifierGroupsOptions = modifierGroups
148
+ .map((modifierGroup) => {
149
+ const selectedModifierIds = selectedModifiers[modifierGroup._id ?? ''];
150
+ if (!selectedModifierIds || selectedModifierIds.length === 0) {
151
+ return null;
152
+ }
153
+ const modifiers = selectedModifierIds
154
+ .map((modifierIdWithIndex) => {
155
+ const baseModifierId = modifierIdWithIndex.split('~')[0];
156
+ const modifier = modifierGroup.modifiers.find((mod) => mod._id === baseModifierId);
157
+ if (!modifier)
158
+ return null;
159
+ const price = Number(modifier?.additionalChargeInfo?.additionalCharge ?? 0);
160
+ return {
161
+ id: modifier._id ?? '',
162
+ price: modifier?.additionalChargeInfo?.additionalCharge,
163
+ formattedPrice: price > 0 ? formatCurrency(price) : undefined,
164
+ };
165
+ })
166
+ .filter((modifier) => modifier !== null);
167
+ if (modifiers.length === 0 || !modifierGroup._id) {
168
+ return null;
169
+ }
170
+ return {
171
+ id: modifierGroup._id,
172
+ modifiers,
173
+ };
174
+ })
175
+ .filter((modifierGroup) => modifierGroup !== null &&
176
+ modifierGroup.modifiers.length > 0 &&
177
+ !!modifierGroup?.id?.length);
178
+ return modifierGroupsOptions.length > 0 ? modifierGroupsOptions : undefined;
179
+ };
180
+ export const getPriceVariantOptions = (selectedPriceVariant, formatCurrency) => {
181
+ const variantId = selectedPriceVariant?._id;
182
+ const variantPrice = selectedPriceVariant?.priceInfo?.price;
183
+ if (!variantId) {
184
+ return undefined;
185
+ }
186
+ const price = variantPrice ? Number(variantPrice) : undefined;
187
+ return {
188
+ id: variantId,
189
+ formattedPrice: formatCurrency(price),
190
+ };
191
+ };
192
+ export const getSelectedModifierPrices = (selectedModifiers, modifierGroups) => {
193
+ return Object.entries(selectedModifiers).reduce((total, [modifierGroupId, modifierIds]) => {
194
+ const groupTotal = modifierIds.reduce((groupSum, modifierId) => {
195
+ const baseModifierId = modifierId.split('~')[0];
196
+ const modifier = modifierGroups
197
+ .find((group) => group._id === modifierGroupId)
198
+ ?.modifiers.find((mod) => mod._id === baseModifierId);
199
+ return (groupSum +
200
+ Number(modifier?.additionalChargeInfo?.additionalCharge ?? 0));
201
+ }, 0);
202
+ return total + groupTotal;
203
+ }, 0);
204
+ };
205
+ export const getSelectedVariantPrice = (selectedVariant, priceVariants) => {
206
+ if (!selectedVariant?._id) {
207
+ return 0;
208
+ }
209
+ const variant = priceVariants.find((variant) => variant._id === selectedVariant._id);
210
+ return Number(variant?.priceInfo?.price ?? 0);
211
+ };
212
+ export const calculateItemPrice = (basePrice, variantPrice, modifierPrices, quantity) => {
213
+ return (basePrice + variantPrice + modifierPrices) * quantity;
214
+ };
@@ -1,9 +1,9 @@
1
1
  import React from 'react';
2
- import { Commerce } from '@wix/ecom/components';
2
+ import { type LineItem } from '@wix/ecom/services';
3
3
  import { type AsChildChildren } from '@wix/headless-utils/react';
4
4
  import { ItemServiceConfig } from '../services/item-details-service.js';
5
5
  import { EnhancedVariant } from '@wix/headless-restaurants-menus/services';
6
- import { AvailabilityStatus, AvailabilityStatusMap } from '../services/common-types.js';
6
+ import { AvailabilityStatus, AvailabilityStatusMap, AddToCartButtonState } from '../services/common-types.js';
7
7
  /**
8
8
  * Root component for menu item display and interaction.
9
9
  * Provides context for all menu item-related components like name, price, description, image, etc.
@@ -72,6 +72,7 @@ export interface SpecialRequestProps {
72
72
  asChild?: boolean;
73
73
  children?: AsChildChildren<{
74
74
  description: string;
75
+ onChange: (value: string) => void;
75
76
  }>;
76
77
  /** Placeholder text for the textarea */
77
78
  placeholder?: string;
@@ -94,16 +95,35 @@ export interface SpecialRequestProps {
94
95
  * Usage:
95
96
  * <ItemDetails.AddToCartButton>Add to cart</ItemDetails.AddToCartButton>
96
97
  */
97
- export interface AddToCartButtonProps extends Omit<React.ComponentPropsWithoutRef<typeof Commerce.Actions.AddToCart>, 'lineItems'> {
98
+ export interface AddToCartButtonProps {
98
99
  asChild?: boolean;
99
- children?: React.ReactNode;
100
100
  className?: string;
101
- label?: string;
101
+ addToCartLabelMap: Record<AddToCartButtonState, string>;
102
+ children: (props: {
103
+ lineItem: LineItem;
104
+ buttonState: AddToCartButtonState;
105
+ addToCartButtonDisabled?: boolean;
106
+ /** Content to display when loading */
107
+ loadingState?: string | React.ReactNode;
108
+ /** Text label for the button */
109
+ label: React.ReactNode;
110
+ formattedPrice: string;
111
+ }) => React.ReactNode;
102
112
  }
113
+ export declare const AddToCartButton: React.ForwardRefExoticComponent<AddToCartButtonProps & React.RefAttributes<HTMLElement>>;
103
114
  export interface ItemDetailsQuantityProps {
104
- children: React.ReactNode;
115
+ asChild?: boolean;
116
+ className?: string;
117
+ children: (props: {
118
+ quantity: number;
119
+ increment: () => void;
120
+ decrement: () => void;
121
+ setQuantity: (quantity: number) => void;
122
+ canIncrement: boolean;
123
+ canDecrement: boolean;
124
+ onValueChange: (value: number) => void;
125
+ }) => React.ReactNode;
105
126
  }
106
- export declare const AddToCartButton: React.FC<AddToCartButtonProps>;
107
127
  export declare const Quantity: React.FC<ItemDetailsQuantityProps>;
108
128
  export declare const SpecialRequest: React.ForwardRefExoticComponent<SpecialRequestProps & React.RefAttributes<never>>;
109
129
  export interface ItemDetailsAvailabilityProps {
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { Commerce } from '@wix/ecom/components';
4
4
  import { AsChildSlot } from '@wix/headless-utils/react';
@@ -38,15 +38,42 @@ export const Variants = React.forwardRef(({ children, className, asChild, varian
38
38
  } }));
39
39
  });
40
40
  Variants.displayName = 'ItemDetails.Variants';
41
- export const AddToCartButton = ({ asChild, children, className, label = 'Add to cart', ...props }) => {
42
- return (_jsx(CoreItemDetails.LineItemComponent, { children: ({ lineItem }) => (_jsx(Commerce.Actions.AddToCart, { asChild: asChild, label: label, className: className, lineItems: [lineItem], ...props, children: children })) }));
43
- };
41
+ export const AddToCartButton = React.forwardRef(({ asChild, children, className, addToCartLabelMap, ...props }, ref) => {
42
+ return (_jsx(CoreItemDetails.LineItemComponent, { addToCartLabelMap: addToCartLabelMap, children: ({ lineItem, buttonState, addToCartButtonDisabled, loadingState, labelText, formattedPrice, }) => {
43
+ const label = (_jsxs(_Fragment, { children: [_jsx("span", { children: labelText }), " ", _jsx("span", { children: " | " }), _jsx("span", { children: formattedPrice })] }));
44
+ return (_jsx(AsChildSlot, { asChild: asChild, className: className, customElement: children, customElementProps: {
45
+ buttonState,
46
+ addToCartButtonDisabled,
47
+ loadingState,
48
+ lineItem,
49
+ lineItems: [lineItem],
50
+ label,
51
+ formattedPrice,
52
+ }, ref: ref, ...props, children: _jsx(Commerce.Actions.AddToCart, { asChild: false, label: label, className: className, lineItems: [lineItem], ...props, children: children({
53
+ lineItem,
54
+ buttonState,
55
+ addToCartButtonDisabled,
56
+ loadingState,
57
+ label,
58
+ formattedPrice,
59
+ }) }) }));
60
+ } }));
61
+ });
62
+ AddToCartButton.displayName = 'AddToCartButton';
44
63
  export const Quantity = ({ children }) => {
45
- return (_jsx(CoreItemDetails.QuantityComponent, { children: ({ quantity, onValueChange, }) => (_jsx(QuantityComponent.Root, { onValueChange: onValueChange, initialValue: quantity, children: children })) }));
64
+ return (_jsx(CoreItemDetails.QuantityComponent, { children: ({ quantity, onValueChange, increment, decrement, setQuantity, canIncrement, canDecrement, }) => (_jsx(QuantityComponent.Root, { onValueChange: onValueChange, initialValue: quantity, children: children({
65
+ quantity,
66
+ increment,
67
+ decrement,
68
+ setQuantity,
69
+ canIncrement,
70
+ canDecrement,
71
+ onValueChange,
72
+ }) })) }));
46
73
  };
47
74
  Quantity.displayName = 'Quantity';
48
75
  export const SpecialRequest = React.forwardRef(({ className, labelClassName, placeholder = 'Any special requests or dietary restrictions?', maxLength = 200, rows = 3, label = 'Special Requests', asChild, children, ...props }, ref) => {
49
- return (_jsx(CoreItemDetails.SpecialRequest, { children: ({ value, onChange, }) => (_jsxs(AsChildSlot, { ref: ref, asChild: asChild, className: className, onChange: onChange, "data-testid": TestIds.itemSpecialRequest, customElement: children, customElementProps: { label, value }, content: value, ...props, children: [label && _jsx("label", { className: labelClassName, children: label }), _jsx("textarea", { value: value, onChange: (e) => onChange(e.target.value), placeholder: placeholder, maxLength: maxLength, rows: rows, className: className, children: value })] })) }));
76
+ return (_jsx(CoreItemDetails.SpecialRequest, { children: ({ value, onChange, }) => (_jsxs(AsChildSlot, { ref: ref, asChild: asChild, className: className, onChange: onChange, "data-testid": TestIds.itemSpecialRequest, customElement: children, customElementProps: { label, value, onChange }, content: value, ...props, children: [label && _jsx("label", { className: labelClassName, children: label }), _jsx("textarea", { value: value, onChange: (e) => onChange(e.target.value), placeholder: placeholder, maxLength: maxLength, rows: rows, className: className, children: value })] })) }));
50
77
  });
51
78
  SpecialRequest.displayName = 'SpecialRequest';
52
79
  export const AvailabilityComponent = React.forwardRef(({ asChild, children, textClassName, buttonClassName, availabilityStatusMap, ...rest }, ref) => {
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { type LineItem } from '@wix/ecom/services';
3
3
  import { ItemServiceConfig } from '../../services/item-details-service.js';
4
4
  import { EnhancedVariant } from '@wix/headless-restaurants-menus/services';
5
- import { AvailabilityStatus, AvailabilityStatusMap } from '../../services/common-types.js';
5
+ import { AvailabilityStatus, AvailabilityStatusMap, AddToCartButtonState } from '../../services/common-types.js';
6
6
  interface ItemDetailsRootProps {
7
7
  children: (props: {
8
8
  item: unknown;
@@ -18,8 +18,14 @@ interface ItemDetailsSpecialRequestProps {
18
18
  }
19
19
  export declare const SpecialRequest: React.FC<ItemDetailsSpecialRequestProps>;
20
20
  interface ItemDetailsLineItemProps {
21
+ addToCartLabelMap: Record<AddToCartButtonState, string>;
21
22
  children: (props: {
23
+ loadingState: string | React.ReactNode;
22
24
  lineItem: LineItem;
25
+ buttonState: AddToCartButtonState;
26
+ addToCartButtonDisabled: boolean;
27
+ labelText: string;
28
+ formattedPrice: string;
23
29
  }) => React.ReactNode;
24
30
  }
25
31
  export declare const LineItemComponent: React.FC<ItemDetailsLineItemProps>;
@@ -5,11 +5,10 @@ import { createServicesMap } from '@wix/services-manager';
5
5
  import { ItemService, ItemServiceDefinition, loadItemServiceConfig, } from '../../services/item-details-service.js';
6
6
  import { OLOSettingsServiceDefinition } from '../../services/olo-settings-service.js';
7
7
  import { useItemContext } from '@wix/headless-restaurants-menus/react';
8
- import { AvailabilityStatus, } from '../../services/common-types.js';
8
+ import { AvailabilityStatus, AddToCartButtonState, } from '../../services/common-types.js';
9
9
  export const Root = ({ children, itemDetailsServiceConfig, }) => {
10
10
  const service = useService(OLOSettingsServiceDefinition);
11
11
  const selectedItem = service.selectedItem?.get();
12
- console.log('selectedItem', selectedItem, itemDetailsServiceConfig);
13
12
  let config = itemDetailsServiceConfig;
14
13
  if (!config) {
15
14
  config = loadItemServiceConfig({
@@ -37,10 +36,25 @@ export const SpecialRequest = ({ children, }) => {
37
36
  // maxLength: 200
38
37
  });
39
38
  };
40
- export const LineItemComponent = ({ children, }) => {
39
+ export const LineItemComponent = ({ addToCartLabelMap, children, }) => {
41
40
  const service = useService(ItemServiceDefinition);
41
+ const oloSettingsService = useService(OLOSettingsServiceDefinition);
42
42
  const lineItem = service.lineItem?.get?.() ?? {};
43
- return children({ lineItem });
43
+ const loadingState = service.isLoading?.get?.() ?? false;
44
+ const buttonState = service.buttonState?.get?.() ?? AddToCartButtonState.VALID_TO_CONTINUE;
45
+ const addToCartButtonDisabled = service.addToCartButtonDisabled?.get?.() ?? false;
46
+ const price = service.price?.get?.() ?? 0;
47
+ const formatCurrency = oloSettingsService.formatCurrency;
48
+ const formattedPrice = formatCurrency(price);
49
+ const labelText = addToCartLabelMap[buttonState];
50
+ return children({
51
+ loadingState,
52
+ lineItem,
53
+ buttonState,
54
+ addToCartButtonDisabled,
55
+ labelText,
56
+ formattedPrice,
57
+ });
44
58
  };
45
59
  export const QuantityComponent = ({ children, }) => {
46
60
  const service = useService(ItemServiceDefinition);
@@ -35,3 +35,11 @@ export type AvailabilityStatusObject = {
35
35
  text?: string;
36
36
  };
37
37
  export type AvailabilityStatusMap = Partial<Record<Exclude<AvailabilityStatus, NextAvailability>, AvailabilityStatusObject>> & Record<NextAvailability, AvailabilityStatusWithActionObject>;
38
+ export declare enum AddToCartButtonState {
39
+ ITEM_UNAVAILABLE = 0,
40
+ OUT_OF_STOCK = 1,
41
+ NOT_VALID = 2,
42
+ EDITING = 3,
43
+ VALID_TO_CONTINUE = 4
44
+ }
45
+ export type AddToCartButtonLabelMap = Record<AddToCartButtonState, string>;
@@ -16,3 +16,11 @@ export var AvailabilityStatus;
16
16
  AvailabilityStatus[AvailabilityStatus["NEXT_AVAILABILITY_PICKUP"] = 3] = "NEXT_AVAILABILITY_PICKUP";
17
17
  AvailabilityStatus[AvailabilityStatus["NEXT_AVAILABILITY_DELIVERY"] = 4] = "NEXT_AVAILABILITY_DELIVERY";
18
18
  })(AvailabilityStatus || (AvailabilityStatus = {}));
19
+ export var AddToCartButtonState;
20
+ (function (AddToCartButtonState) {
21
+ AddToCartButtonState[AddToCartButtonState["ITEM_UNAVAILABLE"] = 0] = "ITEM_UNAVAILABLE";
22
+ AddToCartButtonState[AddToCartButtonState["OUT_OF_STOCK"] = 1] = "OUT_OF_STOCK";
23
+ AddToCartButtonState[AddToCartButtonState["NOT_VALID"] = 2] = "NOT_VALID";
24
+ AddToCartButtonState[AddToCartButtonState["EDITING"] = 3] = "EDITING";
25
+ AddToCartButtonState[AddToCartButtonState["VALID_TO_CONTINUE"] = 4] = "VALID_TO_CONTINUE";
26
+ })(AddToCartButtonState || (AddToCartButtonState = {}));
@@ -1,4 +1,4 @@
1
1
  export { ItemService, ItemServiceDefinition, loadItemServiceConfig, ItemServiceConfig, } from './item-details-service.js';
2
2
  export { OLOSettingsService, OLOSettingsServiceDefinition, loadOLOSettingsServiceConfig, type OLOSettingsServiceConfig, type OLOSettingsServiceAPI, } from './olo-settings-service.js';
3
- export { AvailabilityStatus, AvailabilityStatusMap } from './common-types.js';
3
+ export { AvailabilityStatus, AvailabilityStatusMap, AddToCartButtonState, AddToCartButtonLabelMap, } from './common-types.js';
4
4
  export { FulfillmentsService, FulfillmentsServiceDefinition, loadFulfillmentsServiceConfig, } from './fulfillments-service.js';
@@ -1,4 +1,4 @@
1
1
  export { ItemService, ItemServiceDefinition, loadItemServiceConfig, } from './item-details-service.js';
2
2
  export { OLOSettingsService, OLOSettingsServiceDefinition, loadOLOSettingsServiceConfig, } from './olo-settings-service.js';
3
- export { AvailabilityStatus } from './common-types.js';
3
+ export { AvailabilityStatus, AddToCartButtonState, } from './common-types.js';
4
4
  export { FulfillmentsService, FulfillmentsServiceDefinition, loadFulfillmentsServiceConfig, } from './fulfillments-service.js';
@@ -1,8 +1,8 @@
1
- import { type Signal } from '@wix/services-definitions/core-services/signals';
1
+ import { type Signal, type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
2
2
  import { type LineItem } from '@wix/ecom/services';
3
3
  import { itemVariants } from '@wix/restaurants';
4
4
  import type { EnhancedItem } from '@wix/headless-restaurants-menus/services';
5
- import { AvailabilityStatus } from './common-types.js';
5
+ import { AddToCartButtonState, AvailabilityStatus } from './common-types.js';
6
6
  type Variant = itemVariants.Variant;
7
7
  /**
8
8
  * API interface for the Item Detailsservice, providing reactive item data management.
@@ -15,7 +15,10 @@ export interface ItemServiceAPI {
15
15
  item?: Signal<EnhancedItem | undefined>;
16
16
  quantity: Signal<number>;
17
17
  specialRequest: Signal<string>;
18
- lineItem: Signal<LineItem>;
18
+ lineItem: ReadOnlySignal<LineItem>;
19
+ buttonState: ReadOnlySignal<AddToCartButtonState | undefined>;
20
+ addToCartButtonDisabled: Signal<boolean>;
21
+ price: ReadOnlySignal<number>;
19
22
  selectedVariant: Signal<Variant | undefined>;
20
23
  selectedModifiers: Signal<Record<string, Array<string>>>;
21
24
  availabilityStatus: Signal<AvailabilityStatus>;
@@ -59,6 +62,9 @@ export interface ItemServiceConfig {
59
62
  itemId?: string;
60
63
  operationId?: string;
61
64
  availabilityStatus?: AvailabilityStatus;
65
+ editItemMode?: boolean;
66
+ menuId?: string;
67
+ sectionId?: string;
62
68
  }
63
69
  export declare const ItemService: import("@wix/services-definitions").ServiceFactory<string & {
64
70
  __api: ItemServiceAPI;
@@ -1,7 +1,8 @@
1
1
  import { defineService, implementService } from '@wix/services-definitions';
2
2
  import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
3
- import { AvailabilityStatus } from './common-types.js';
4
- import { getModifiersInitState } from './utils.js';
3
+ import { AddToCartButtonState, AvailabilityStatus } from './common-types.js';
4
+ import { getAreNotEnoughModifiersOfMandatoryModifierGroupInStock, getModifiersInitState, getLineItemModifiers, getPriceVariantOptions, getSelectedModifierPrices, getSelectedVariantPrice, calculateItemPrice, } from './utils.js';
5
+ import { OLOSettingsServiceDefinition } from './olo-settings-service.js';
5
6
  /**
6
7
  * Service definition for the Item service.
7
8
  * This defines the contract that the ItemService must implement.
@@ -45,13 +46,13 @@ export const ItemServiceDefinition = defineService('item');
45
46
  const APP_ID = '9a5d83fd-8570-482e-81ab-cfa88942ee60';
46
47
  export const ItemService = implementService.withConfig()(ItemServiceDefinition, ({ getService, config }) => {
47
48
  const signalsService = getService(SignalsServiceDefinition);
49
+ const oloSettingsService = getService(OLOSettingsServiceDefinition);
48
50
  const availabilityStatus = signalsService.signal(config.availabilityStatus ?? AvailabilityStatus.AVAILABLE);
49
51
  const item = signalsService.signal(config.item);
50
52
  const isLoading = signalsService.signal(!!config.item);
51
53
  const error = signalsService.signal(config.item ? null : 'Item not found');
52
54
  const quantity = signalsService.signal(1);
53
55
  const specialRequest = signalsService.signal('');
54
- const lineItem = signalsService.signal({});
55
56
  const priceVariants = config.item?.priceVariants || [];
56
57
  const initialVariant = priceVariants.length > 0 ? priceVariants[0] : undefined;
57
58
  const selectedVariant = signalsService.signal(initialVariant);
@@ -65,44 +66,57 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
65
66
  return acc;
66
67
  }, {});
67
68
  const modifierGroupError = signalsService.signal(initialModifierGroupError);
68
- if (config.item) {
69
- lineItem.set({
69
+ const price = signalsService.computed(() => {
70
+ const basePrice = Number(config?.item?.priceInfo?.price ?? 0);
71
+ const variantPrice = getSelectedVariantPrice(selectedVariant.get(), priceVariants);
72
+ const modifierPrices = getSelectedModifierPrices(selectedModifiers.get() || {}, modifierGroups);
73
+ const itemQuantity = quantity.get() ?? 1;
74
+ return calculateItemPrice(basePrice, variantPrice, modifierPrices, itemQuantity);
75
+ });
76
+ const areModifiersOfMandatoryModifierGroupOutOfStock = getAreNotEnoughModifiersOfMandatoryModifierGroupInStock(modifierGroups);
77
+ const canAcceptOrders = oloSettingsService.canAcceptOrders ?? true;
78
+ const isValidToContinue = canAcceptOrders &&
79
+ config?.item?.orderSettings?.inStock &&
80
+ config.availabilityStatus === AvailabilityStatus.AVAILABLE &&
81
+ !areModifiersOfMandatoryModifierGroupOutOfStock;
82
+ const buttonState = signalsService.computed(() => {
83
+ return !config?.item?.orderSettings?.inStock ||
84
+ areModifiersOfMandatoryModifierGroupOutOfStock
85
+ ? AddToCartButtonState.OUT_OF_STOCK
86
+ : availabilityStatus.get() !== AvailabilityStatus.AVAILABLE
87
+ ? AddToCartButtonState.ITEM_UNAVAILABLE
88
+ : !canAcceptOrders
89
+ ? AddToCartButtonState.NOT_VALID
90
+ : config.editItemMode
91
+ ? AddToCartButtonState.EDITING
92
+ : isValidToContinue
93
+ ? AddToCartButtonState.VALID_TO_CONTINUE
94
+ : AddToCartButtonState.NOT_VALID;
95
+ });
96
+ const addToCartButtonDisabled = signalsService.signal(!isValidToContinue);
97
+ const lineItem = signalsService.computed(() => {
98
+ const formatCurrency = oloSettingsService.formatCurrency;
99
+ return {
70
100
  quantity: quantity.get(),
71
101
  catalogReference: {
72
- // @ts-expect-error - item is not typed
73
- catalogItemId: config.item._id,
102
+ catalogItemId: config.item?._id ?? undefined,
74
103
  appId: APP_ID,
75
104
  options: {
76
105
  operationId: config.operationId,
77
- // @ts-expect-error - item is not typed
78
- menuId: config.item.menuId,
79
- // @ts-expect-error - item is not typed
80
- sectionId: config.item.sectionId,
106
+ menuId: config.menuId,
107
+ sectionId: config.sectionId,
108
+ priceVariant: getPriceVariantOptions(selectedVariant.get(), formatCurrency),
109
+ modifierGroups: getLineItemModifiers(selectedModifiers.get(), modifierGroups, formatCurrency),
110
+ specialRequests: specialRequest.get(),
81
111
  },
82
112
  },
83
- });
84
- }
113
+ };
114
+ });
85
115
  const updateQuantity = (_quantity) => {
86
116
  quantity.set(_quantity);
87
- const _lineItem = lineItem.get();
88
- lineItem.set({
89
- ..._lineItem,
90
- quantity: _quantity,
91
- });
92
117
  };
93
118
  const updateSpecialRequest = (_specialRequest) => {
94
119
  specialRequest.set(_specialRequest);
95
- const _lineItem = lineItem.get();
96
- lineItem.set({
97
- ..._lineItem,
98
- catalogReference: {
99
- ..._lineItem.catalogReference,
100
- options: {
101
- ..._lineItem.catalogReference?.options,
102
- specialRequest: _specialRequest,
103
- },
104
- },
105
- });
106
120
  };
107
121
  const updateSelectedVariant = (variant) => {
108
122
  selectedVariant.set(variant);
@@ -157,6 +171,9 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
157
171
  availabilityStatus,
158
172
  getSelectedModifiers,
159
173
  modifierGroupError,
174
+ buttonState,
175
+ addToCartButtonDisabled,
176
+ price,
160
177
  };
161
178
  });
162
179
  /**
@@ -11,12 +11,16 @@ export interface OLOSettingsServiceAPI {
11
11
  currentFulfillment: Signal<string>;
12
12
  currentTimeSlot: Signal<string>;
13
13
  filterMenus: (menus: MenusServiceConfig['menus']) => MenusServiceConfig['menus'];
14
+ canAcceptOrders: boolean;
15
+ formatCurrency: (price?: number) => string;
14
16
  }
15
17
  export interface OLOSettingsServiceConfig {
16
18
  operationGroup?: operationGroupsSDK.OperationGroup;
17
19
  operation?: operationsSDK.Operation;
18
20
  availabilityDispatchAction?: () => void;
19
21
  menuIdsByOperation?: string[];
22
+ canAcceptOrders?: boolean;
23
+ formattedPrice?: (price?: number) => string;
20
24
  }
21
25
  export declare const OLOSettingsServiceDefinition: string & {
22
26
  __api: OLOSettingsServiceAPI;
@@ -8,6 +8,8 @@ export const OLOSettingsService = implementService.withConfig()(OLOSettingsServi
8
8
  const availabilityDispatchAction = signalsService.signal(config.availabilityDispatchAction);
9
9
  const operationGroup = signalsService.signal(config.operationGroup);
10
10
  const operation = signalsService.signal(config.operation);
11
+ const formatCurrency = config.formattedPrice ??
12
+ ((price) => price?.toFixed(2)?.toString() ?? '');
11
13
  const selectedItem = signalsService.signal(null);
12
14
  const isLoading = signalsService.signal(false);
13
15
  const error = signalsService.signal(null);
@@ -27,6 +29,8 @@ export const OLOSettingsService = implementService.withConfig()(OLOSettingsServi
27
29
  currentFulfillment,
28
30
  currentTimeSlot,
29
31
  filterMenus,
32
+ canAcceptOrders: config.canAcceptOrders ?? true,
33
+ formatCurrency,
30
34
  };
31
35
  });
32
36
  export async function loadOLOSettingsServiceConfig() {
@@ -34,4 +34,32 @@ export declare const hasToChooseAtLeastX: ({ required, minSelections, maxSelecti
34
34
  export declare const chooseUpToX: ({ required, minSelections, maxSelections, }: ruleUtilsArgs) => boolean;
35
35
  export declare const hasToChooseBetweenXAndY: ({ required, minSelections, maxSelections, }: ruleUtilsArgs) => boolean;
36
36
  export declare const getRuleTypeMapValue: (ruleTypeMap: RuleTypeMap, ruleType: RuleType, modifierGroupName: string, rule: EnhancedModifierGroup["rule"]) => string | undefined;
37
+ export declare const getAreNotEnoughModifiersOfMandatoryModifierGroupInStock: (modifierGroups: EnhancedModifierGroup[]) => boolean;
38
+ export declare const getLineItemModifiers: (selectedModifiers: Record<string, Array<string>>, modifierGroups: EnhancedModifierGroup[], formatCurrency: (price?: number) => string) => {
39
+ id: string;
40
+ modifiers: {
41
+ id: string;
42
+ price: string | undefined;
43
+ formattedPrice: string | undefined;
44
+ }[];
45
+ }[] | undefined;
46
+ export declare const getPriceVariantOptions: (selectedPriceVariant: {
47
+ _id?: string | null;
48
+ priceInfo?: {
49
+ price?: string;
50
+ };
51
+ } | undefined, formatCurrency: (price?: number) => string) => {
52
+ id: string;
53
+ formattedPrice: string;
54
+ } | undefined;
55
+ export declare const getSelectedModifierPrices: (selectedModifiers: Record<string, Array<string>>, modifierGroups: EnhancedModifierGroup[]) => number;
56
+ export declare const getSelectedVariantPrice: (selectedVariant: {
57
+ _id?: string | null;
58
+ } | undefined, priceVariants: Array<{
59
+ _id?: string | null;
60
+ priceInfo?: {
61
+ price?: string;
62
+ };
63
+ }>) => number;
64
+ export declare const calculateItemPrice: (basePrice: number, variantPrice: number, modifierPrices: number, quantity: number) => number;
37
65
  export {};
@@ -136,3 +136,79 @@ export const getRuleTypeMapValue = (ruleTypeMap, ruleType, modifierGroupName, ru
136
136
  }
137
137
  return undefined;
138
138
  };
139
+ export const getAreNotEnoughModifiersOfMandatoryModifierGroupInStock = (modifierGroups) => modifierGroups.some((modifierGroup) => {
140
+ const isModifierGroupMandatory = modifierGroup.rule?.required;
141
+ const minimumRequiredModifiers = modifierGroup.rule?.minSelections ?? 0;
142
+ const inStockModifiers = modifierGroup.modifiers.filter((modifier) => modifier.inStock ?? true);
143
+ const areMinimumRequiredModifiersInStock = inStockModifiers.length >= minimumRequiredModifiers;
144
+ return isModifierGroupMandatory && !areMinimumRequiredModifiersInStock;
145
+ });
146
+ export const getLineItemModifiers = (selectedModifiers, modifierGroups, formatCurrency) => {
147
+ const modifierGroupsOptions = modifierGroups
148
+ .map((modifierGroup) => {
149
+ const selectedModifierIds = selectedModifiers[modifierGroup._id ?? ''];
150
+ if (!selectedModifierIds || selectedModifierIds.length === 0) {
151
+ return null;
152
+ }
153
+ const modifiers = selectedModifierIds
154
+ .map((modifierIdWithIndex) => {
155
+ const baseModifierId = modifierIdWithIndex.split('~')[0];
156
+ const modifier = modifierGroup.modifiers.find((mod) => mod._id === baseModifierId);
157
+ if (!modifier)
158
+ return null;
159
+ const price = Number(modifier?.additionalChargeInfo?.additionalCharge ?? 0);
160
+ return {
161
+ id: modifier._id ?? '',
162
+ price: modifier?.additionalChargeInfo?.additionalCharge,
163
+ formattedPrice: price > 0 ? formatCurrency(price) : undefined,
164
+ };
165
+ })
166
+ .filter((modifier) => modifier !== null);
167
+ if (modifiers.length === 0 || !modifierGroup._id) {
168
+ return null;
169
+ }
170
+ return {
171
+ id: modifierGroup._id,
172
+ modifiers,
173
+ };
174
+ })
175
+ .filter((modifierGroup) => modifierGroup !== null &&
176
+ modifierGroup.modifiers.length > 0 &&
177
+ !!modifierGroup?.id?.length);
178
+ return modifierGroupsOptions.length > 0 ? modifierGroupsOptions : undefined;
179
+ };
180
+ export const getPriceVariantOptions = (selectedPriceVariant, formatCurrency) => {
181
+ const variantId = selectedPriceVariant?._id;
182
+ const variantPrice = selectedPriceVariant?.priceInfo?.price;
183
+ if (!variantId) {
184
+ return undefined;
185
+ }
186
+ const price = variantPrice ? Number(variantPrice) : undefined;
187
+ return {
188
+ id: variantId,
189
+ formattedPrice: formatCurrency(price),
190
+ };
191
+ };
192
+ export const getSelectedModifierPrices = (selectedModifiers, modifierGroups) => {
193
+ return Object.entries(selectedModifiers).reduce((total, [modifierGroupId, modifierIds]) => {
194
+ const groupTotal = modifierIds.reduce((groupSum, modifierId) => {
195
+ const baseModifierId = modifierId.split('~')[0];
196
+ const modifier = modifierGroups
197
+ .find((group) => group._id === modifierGroupId)
198
+ ?.modifiers.find((mod) => mod._id === baseModifierId);
199
+ return (groupSum +
200
+ Number(modifier?.additionalChargeInfo?.additionalCharge ?? 0));
201
+ }, 0);
202
+ return total + groupTotal;
203
+ }, 0);
204
+ };
205
+ export const getSelectedVariantPrice = (selectedVariant, priceVariants) => {
206
+ if (!selectedVariant?._id) {
207
+ return 0;
208
+ }
209
+ const variant = priceVariants.find((variant) => variant._id === selectedVariant._id);
210
+ return Number(variant?.priceInfo?.price ?? 0);
211
+ };
212
+ export const calculateItemPrice = (basePrice, variantPrice, modifierPrices, quantity) => {
213
+ return (basePrice + variantPrice + modifierPrices) * quantity;
214
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-restaurants-olo",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -56,7 +56,7 @@
56
56
  "@radix-ui/react-slot": "^1.1.0",
57
57
  "@wix/auto_sdk_restaurants_items": "^1.0.48",
58
58
  "@wix/ecom": "^1.0.1461",
59
- "@wix/headless-components": "0.0.27",
59
+ "@wix/headless-components": "0.0.28",
60
60
  "@wix/headless-media": "0.0.17",
61
61
  "@wix/headless-restaurants-menus": "0.0.20",
62
62
  "@wix/headless-utils": "0.0.7",
@@ -76,5 +76,5 @@
76
76
  "groupId": "com.wixpress.headless-components"
77
77
  }
78
78
  },
79
- "falconPackageHash": "4642f5bdaedb6d472e41409ca88d2be3914e062b018972f37b44ea55"
79
+ "falconPackageHash": "d932daf9652014859c0211e27fa3c0342659bcf9665f9d9ae2e30173"
80
80
  }