@wix/headless-restaurants-olo 0.0.22 → 0.0.24

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.
@@ -71,7 +71,7 @@ export declare const Variants: React.ForwardRefExoticComponent<ItemDetailsVarian
71
71
  export interface SpecialRequestProps {
72
72
  asChild?: boolean;
73
73
  children?: AsChildChildren<{
74
- description: string;
74
+ value: string;
75
75
  onChange: (value: string) => void;
76
76
  }>;
77
77
  /** Placeholder text for the textarea */
@@ -108,6 +108,7 @@ export interface AddToCartButtonProps {
108
108
  /** Text label for the button */
109
109
  label: React.ReactNode;
110
110
  formattedPrice: string;
111
+ modifierGroupHasError: boolean;
111
112
  }) => React.ReactNode;
112
113
  }
113
114
  export declare const AddToCartButton: React.ForwardRefExoticComponent<AddToCartButtonProps & React.RefAttributes<HTMLElement>>;
@@ -39,7 +39,7 @@ export const Variants = React.forwardRef(({ children, className, asChild, varian
39
39
  });
40
40
  Variants.displayName = 'ItemDetails.Variants';
41
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, }) => {
42
+ return (_jsx(CoreItemDetails.LineItemComponent, { addToCartLabelMap: addToCartLabelMap, children: ({ lineItem, buttonState, addToCartButtonDisabled, loadingState, labelText, formattedPrice, modifierGroupHasError, }) => {
43
43
  const label = (_jsxs(_Fragment, { children: [_jsx("span", { children: labelText }), " ", _jsx("span", { children: " | " }), _jsx("span", { children: formattedPrice })] }));
44
44
  return (_jsx(AsChildSlot, { asChild: asChild, className: className, customElement: children, customElementProps: {
45
45
  buttonState,
@@ -49,6 +49,7 @@ export const AddToCartButton = React.forwardRef(({ asChild, children, className,
49
49
  lineItems: [lineItem],
50
50
  label,
51
51
  formattedPrice,
52
+ modifierGroupHasError,
52
53
  }, ref: ref, ...props, children: _jsx(Commerce.Actions.AddToCart, { asChild: false, label: label, className: className, lineItems: [lineItem], ...props, children: children({
53
54
  lineItem,
54
55
  buttonState,
@@ -56,6 +57,7 @@ export const AddToCartButton = React.forwardRef(({ asChild, children, className,
56
57
  loadingState,
57
58
  label,
58
59
  formattedPrice,
60
+ modifierGroupHasError,
59
61
  }) }) }));
60
62
  } }));
61
63
  });
@@ -44,12 +44,12 @@ export const Description = React.forwardRef(({ ruleTypeMap, className, asChild,
44
44
  } }));
45
45
  });
46
46
  Description.displayName = 'ItemDetailsModifierGroup.Description';
47
- export const Error = React.forwardRef(({ ruleTypeMap, className, asChild, children }, ref) => {
47
+ export const Error = React.forwardRef(({ ruleTypeMap, className, asChild, children, ...otherProps }, ref) => {
48
48
  return (_jsx(GroupError, { ruleTypeMap: ruleTypeMap, children: ({ error }) => {
49
49
  if (!error) {
50
50
  return null;
51
51
  }
52
- return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, testId: TestIds.modifierGroupError, className: className, customElement: children, customElementProps: { error }, children: _jsx("p", { className: className, children: error }) }));
52
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, "data-testid": TestIds.modifierGroupError, className: className, customElement: children, customElementProps: { error }, content: error, ...otherProps, children: _jsx("p", { className: className, children: error }) }));
53
53
  } }));
54
54
  });
55
55
  Error.displayName = 'ItemDetailsModifierGroup.Error';
@@ -26,6 +26,7 @@ interface ItemDetailsLineItemProps {
26
26
  addToCartButtonDisabled: boolean;
27
27
  labelText: string;
28
28
  formattedPrice: string;
29
+ modifierGroupHasError: boolean;
29
30
  }) => React.ReactNode;
30
31
  }
31
32
  export declare const LineItemComponent: React.FC<ItemDetailsLineItemProps>;
@@ -24,11 +24,14 @@ export const Root = ({ children, itemDetailsServiceConfig, }) => {
24
24
  return (_jsx(WixServices, { servicesMap: createServicesMap().addService(ItemServiceDefinition, ItemService, config), children: children({ item: itemDetailsServiceConfig?.item ?? selectedItem }) }));
25
25
  };
26
26
  export const SpecialRequest = ({ children, }) => {
27
- const [value, setValue] = useState('');
28
27
  const service = useService(ItemServiceDefinition);
28
+ const initialSpecialRequest = service.specialRequest?.get?.() ?? '';
29
+ const [value, setValue] = useState(initialSpecialRequest);
29
30
  const onChange = (newValue) => {
30
- setValue(newValue);
31
- service.updateSpecialRequest(newValue);
31
+ if (typeof newValue === 'string') {
32
+ setValue(newValue);
33
+ service.updateSpecialRequest(newValue);
34
+ }
32
35
  };
33
36
  return children({
34
37
  value,
@@ -48,6 +51,7 @@ export const LineItemComponent = ({ addToCartLabelMap, children, }) => {
48
51
  const formatCurrency = oloSettingsService.formatCurrency;
49
52
  const formattedPrice = formatCurrency(price);
50
53
  const labelText = addToCartLabelMap[buttonState];
54
+ const modifierGroupHasError = service.doesModifierGroupHaveError?.get?.() ?? false;
51
55
  return children({
52
56
  loadingState,
53
57
  lineItem,
@@ -55,6 +59,7 @@ export const LineItemComponent = ({ addToCartLabelMap, children, }) => {
55
59
  addToCartButtonDisabled,
56
60
  labelText,
57
61
  formattedPrice,
62
+ modifierGroupHasError,
58
63
  });
59
64
  };
60
65
  export const QuantityComponent = ({ children, }) => {
@@ -49,8 +49,7 @@ export const ModifiersComponent = ({ children, }) => {
49
49
  };
50
50
  export const Description = ({ ruleTypeMap, children, }) => {
51
51
  const { ruleType, modifierGroup } = useModifiersContext();
52
- const modifierGroupName = modifierGroup.name || '';
53
- const description = getRuleTypeMapValue(ruleTypeMap, ruleType, modifierGroupName, modifierGroup.rule);
52
+ const description = getRuleTypeMapValue(ruleTypeMap, ruleType, modifierGroup.rule);
54
53
  return children({ description });
55
54
  };
56
55
  export const GroupError = ({ ruleTypeMap, children, }) => {
@@ -58,10 +57,9 @@ export const GroupError = ({ ruleTypeMap, children, }) => {
58
57
  const { ruleType, modifierGroup } = useModifiersContext();
59
58
  const groupId = modifierGroup._id || '';
60
59
  const modifierGroupErrors = service.modifierGroupError?.get() || {};
61
- const modifierGroupName = modifierGroup.name || '';
62
60
  let error;
63
61
  if (modifierGroupErrors[groupId]) {
64
- error = getRuleTypeMapValue(ruleTypeMap, ruleType, modifierGroupName, modifierGroup.rule);
62
+ error = getRuleTypeMapValue(ruleTypeMap, ruleType, modifierGroup.rule);
65
63
  }
66
64
  return children({ error });
67
65
  };
@@ -11,13 +11,13 @@ export declare enum RuleType {
11
11
  CHOOSE_BETWEEN_X_AND_Y = "CHOOSE_BETWEEN_X_AND_Y"
12
12
  }
13
13
  export interface RuleTypeMap {
14
- [RuleType.NO_LIMIT]?: (modifierGroupName: string) => string;
15
- [RuleType.CHOOSE_ONE]?: (modifierGroupName: string) => string;
16
- [RuleType.CHOOSE_X]?: (modifierGroupName: string, x: number) => string;
17
- [RuleType.CHOOSE_AT_LEAST_ONE]?: (modifierGroupName: string) => string;
18
- [RuleType.CHOOSE_AT_LEAST_X]?: (modifierGroupName: string, x: number) => string;
19
- [RuleType.CHOOSE_UP_TO_X]?: (modifierGroupName: string, x: number) => string;
20
- [RuleType.CHOOSE_BETWEEN_X_AND_Y]?: (modifierGroupName: string, x: number, y: number) => string;
14
+ [RuleType.NO_LIMIT]?: () => string;
15
+ [RuleType.CHOOSE_ONE]?: () => string;
16
+ [RuleType.CHOOSE_X]?: (x: number) => string;
17
+ [RuleType.CHOOSE_AT_LEAST_ONE]?: () => string;
18
+ [RuleType.CHOOSE_AT_LEAST_X]?: (x: number) => string;
19
+ [RuleType.CHOOSE_UP_TO_X]?: (x: number) => string;
20
+ [RuleType.CHOOSE_BETWEEN_X_AND_Y]?: (x: number, y: number) => string;
21
21
  }
22
22
  export declare enum AvailabilityStatus {
23
23
  AVAILABLE = 0,
@@ -43,6 +43,7 @@ export interface ItemServiceAPI {
43
43
  endDate?: Date;
44
44
  weeklyAvailabilitySummary?: WeeklyAvailability;
45
45
  };
46
+ doesModifierGroupHaveError: ReadOnlySignal<boolean>;
46
47
  }
47
48
  /**
48
49
  * Service definition for the Item service.
@@ -67,6 +68,12 @@ export interface ItemServiceConfig {
67
68
  operationId?: string;
68
69
  availabilityStatus?: AvailabilityStatus;
69
70
  editItemMode?: boolean;
71
+ editingItemValues?: {
72
+ quantity?: number;
73
+ specialRequest?: string;
74
+ selectedVariantId?: string;
75
+ selectedModifiers?: Record<string, Array<string>>;
76
+ };
70
77
  menuId?: string;
71
78
  sectionId?: string;
72
79
  futureAvailability?: FutureAvailability;
@@ -1,7 +1,7 @@
1
1
  import { defineService, implementService } from '@wix/services-definitions';
2
2
  import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
3
3
  import { AddToCartButtonState, AvailabilityStatus, } from './common-types.js';
4
- import { getAreNotEnoughModifiersOfMandatoryModifierGroupInStock, getModifiersInitState, getLineItemModifiers, getPriceVariantOptions, getSelectedModifierPrices, getSelectedVariantPrice, calculateItemPrice, } from './utils.js';
4
+ import { getAreNotEnoughModifiersOfMandatoryModifierGroupInStock, getModifiersInitState, getLineItemModifiers, getPriceVariantOptions, getSelectedModifierPrices, getSelectedVariantPrice, calculateItemPrice, checkModifiersValidation, } from './utils.js';
5
5
  import { OLOSettingsServiceDefinition } from './olo-settings-service.js';
6
6
  /**
7
7
  * Service definition for the Item service.
@@ -56,13 +56,27 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
56
56
  const item = signalsService.signal(config.item);
57
57
  const isLoading = signalsService.signal(!!config.item);
58
58
  const error = signalsService.signal(config.item ? null : 'Item not found');
59
- const quantity = signalsService.signal(1);
60
- const specialRequest = signalsService.signal('');
59
+ const quantity = signalsService.signal(config.editItemMode ? (config.editingItemValues?.quantity ?? 1) : 1);
60
+ const specialRequest = signalsService.signal(config.editItemMode
61
+ ? (config.editingItemValues?.specialRequest ?? '')
62
+ : '');
61
63
  const priceVariants = config.item?.priceVariants || [];
62
- const initialVariant = priceVariants.length > 0 ? priceVariants[0] : undefined;
64
+ let initialVariant;
65
+ if (!config.editItemMode) {
66
+ initialVariant = priceVariants.length > 0 ? priceVariants[0] : undefined;
67
+ }
68
+ else {
69
+ if (config.editingItemValues?.selectedVariantId) {
70
+ initialVariant = priceVariants.find((variant) => variant._id === config.editingItemValues?.selectedVariantId);
71
+ }
72
+ else {
73
+ initialVariant =
74
+ priceVariants.length > 0 ? priceVariants[0] : undefined;
75
+ }
76
+ }
63
77
  const selectedVariant = signalsService.signal(initialVariant);
64
78
  const modifierGroups = config.item?.modifierGroups || [];
65
- const initialSelectedModifiers = getModifiersInitState(modifierGroups);
79
+ const initialSelectedModifiers = getModifiersInitState(modifierGroups, config.editItemMode, config.editingItemValues?.selectedModifiers);
66
80
  const selectedModifiers = signalsService.signal(initialSelectedModifiers);
67
81
  const initialModifierGroupError = modifierGroups.reduce((acc, group) => {
68
82
  if (group._id) {
@@ -121,7 +135,9 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
121
135
  quantity.set(_quantity);
122
136
  };
123
137
  const updateSpecialRequest = (_specialRequest) => {
124
- specialRequest.set(_specialRequest);
138
+ if (typeof _specialRequest === 'string') {
139
+ specialRequest.set(_specialRequest);
140
+ }
125
141
  };
126
142
  const updateSelectedVariant = (variant) => {
127
143
  selectedVariant.set(variant);
@@ -148,6 +164,20 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
148
164
  }
149
165
  }
150
166
  };
167
+ const doesModifierGroupHaveError = signalsService.computed(() => {
168
+ const currentSelectedModifiers = selectedModifiers.get();
169
+ const errors = {};
170
+ modifierGroups.forEach((group) => {
171
+ if (group._id) {
172
+ const selectedCount = (currentSelectedModifiers[group._id] || [])
173
+ .length;
174
+ const isValid = checkModifiersValidation(group.rule, selectedCount);
175
+ errors[group._id] = !isValid;
176
+ }
177
+ });
178
+ modifierGroupError.set(errors);
179
+ return Object.values(errors).some((error) => error);
180
+ });
151
181
  const toggleModifier = (modifierGroupId, modifierId, singleSelect = false) => {
152
182
  const currentSelectedModifiers = selectedModifiers.get();
153
183
  const modifierIds = getModifierIds(modifierGroupId, modifierId, singleSelect);
@@ -155,6 +185,11 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
155
185
  ...currentSelectedModifiers,
156
186
  [modifierGroupId]: modifierIds,
157
187
  });
188
+ const currentModifierGroupError = modifierGroupError.get();
189
+ modifierGroupError.set({
190
+ ...currentModifierGroupError,
191
+ [modifierGroupId]: false,
192
+ });
158
193
  };
159
194
  const getSelectedModifiers = (modifierGroupId) => {
160
195
  const currentSelectedModifiers = selectedModifiers.get();
@@ -180,6 +215,7 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
180
215
  addToCartButtonDisabled,
181
216
  price,
182
217
  futureAvailability,
218
+ doesModifierGroupHaveError,
183
219
  };
184
220
  });
185
221
  /**
@@ -5,7 +5,7 @@ interface ruleUtilsArgs {
5
5
  minSelections: number;
6
6
  maxSelections?: number | null;
7
7
  }
8
- export declare const getModifiersInitState: (modifierGroups: EnhancedModifierGroup[]) => Record<string, string[]>;
8
+ export declare const getModifiersInitState: (modifierGroups: EnhancedModifierGroup[], editingItemMode?: boolean, editItemSelectedModifiers?: Record<string, Array<string>>) => Record<string, string[]>;
9
9
  export declare const isSingleSelectRule: (rule: NonNullable<EnhancedModifierGroup["rule"]>) => boolean | null | undefined;
10
10
  export declare const getFirstPreSelectedModifier: (modifiers: EnhancedModifier[]) => string | null | undefined;
11
11
  export declare const getPreSelectedModifiers: (modifiers: EnhancedModifier[]) => string[];
@@ -33,7 +33,8 @@ export declare const hasToChooseAtLeastOne: ({ required, minSelections, maxSelec
33
33
  export declare const hasToChooseAtLeastX: ({ required, minSelections, maxSelections, }: ruleUtilsArgs) => boolean;
34
34
  export declare const chooseUpToX: ({ required, minSelections, maxSelections, }: ruleUtilsArgs) => boolean;
35
35
  export declare const hasToChooseBetweenXAndY: ({ required, minSelections, maxSelections, }: ruleUtilsArgs) => boolean;
36
- export declare const getRuleTypeMapValue: (ruleTypeMap: RuleTypeMap, ruleType: RuleType, modifierGroupName: string, rule: EnhancedModifierGroup["rule"]) => string | undefined;
36
+ export declare const getRuleTypeMapValue: (ruleTypeMap: RuleTypeMap, ruleType: RuleType, rule: EnhancedModifierGroup["rule"]) => string | undefined;
37
+ export declare const checkModifiersValidation: (rule: EnhancedModifierGroup["rule"], selectedCount: number) => boolean;
37
38
  export declare const getAreNotEnoughModifiersOfMandatoryModifierGroupInStock: (modifierGroups: EnhancedModifierGroup[]) => boolean;
38
39
  export declare const getLineItemModifiers: (selectedModifiers: Record<string, Array<string>>, modifierGroups: EnhancedModifierGroup[], formatCurrency: (price?: number) => string) => {
39
40
  id: string;
@@ -1,10 +1,17 @@
1
1
  import { AvailabilityStatus, RuleType, } from './common-types.js';
2
- export const getModifiersInitState = (modifierGroups) => {
2
+ export const getModifiersInitState = (modifierGroups, editingItemMode, editItemSelectedModifiers) => {
3
3
  const initialSelectedModifiers = {};
4
4
  modifierGroups.forEach((group) => {
5
5
  if (group._id) {
6
- const isMultiSelectItem = !isSingleSelectRule(group.rule ?? {});
6
+ if (editingItemMode) {
7
+ const selectedModifiers = editItemSelectedModifiers?.[group._id ?? ''];
8
+ if (selectedModifiers) {
9
+ initialSelectedModifiers[group._id] = selectedModifiers;
10
+ }
11
+ return;
12
+ }
7
13
  const formModifiers = group.modifiers.map(convertModifierToFormModifier);
14
+ const isMultiSelectItem = !isSingleSelectRule(group.rule ?? {});
8
15
  if (isMultiSelectItem) {
9
16
  const preSelectedModifiers = getPreSelectedModifiers(formModifiers);
10
17
  initialSelectedModifiers[group._id] = preSelectedModifiers;
@@ -80,62 +87,77 @@ export const hasToChooseBetweenXAndY = ({ required, minSelections, maxSelections
80
87
  ? minSelections > 0 && maxSelections > minSelections
81
88
  : false
82
89
  : false;
83
- export const getRuleTypeMapValue = (ruleTypeMap, ruleType, modifierGroupName, rule) => {
90
+ export const getRuleTypeMapValue = (ruleTypeMap, ruleType, rule) => {
84
91
  const minSelections = rule?.minSelections ?? 0;
85
92
  const maxSelections = rule?.maxSelections;
86
93
  switch (ruleType) {
87
94
  case RuleType.NO_LIMIT: {
88
95
  const callback = ruleTypeMap[RuleType.NO_LIMIT];
89
96
  if (callback) {
90
- return callback(modifierGroupName);
97
+ return callback();
91
98
  }
92
99
  break;
93
100
  }
94
101
  case RuleType.CHOOSE_ONE: {
95
102
  const callback = ruleTypeMap[RuleType.CHOOSE_ONE];
96
103
  if (callback) {
97
- return callback(modifierGroupName);
104
+ return callback();
98
105
  }
99
106
  break;
100
107
  }
101
108
  case RuleType.CHOOSE_X: {
102
109
  const callback = ruleTypeMap[RuleType.CHOOSE_X];
103
110
  if (callback) {
104
- return callback(modifierGroupName, minSelections);
111
+ return callback(minSelections);
105
112
  }
106
113
  break;
107
114
  }
108
115
  case RuleType.CHOOSE_AT_LEAST_ONE: {
109
116
  const callback = ruleTypeMap[RuleType.CHOOSE_AT_LEAST_ONE];
110
117
  if (callback) {
111
- return callback(modifierGroupName);
118
+ return callback();
112
119
  }
113
120
  break;
114
121
  }
115
122
  case RuleType.CHOOSE_AT_LEAST_X: {
116
123
  const callback = ruleTypeMap[RuleType.CHOOSE_AT_LEAST_X];
117
124
  if (callback) {
118
- return callback(modifierGroupName, minSelections);
125
+ return callback(minSelections);
119
126
  }
120
127
  break;
121
128
  }
122
129
  case RuleType.CHOOSE_UP_TO_X: {
123
130
  const callback = ruleTypeMap[RuleType.CHOOSE_UP_TO_X];
124
131
  if (callback && maxSelections) {
125
- return callback(modifierGroupName, maxSelections);
132
+ return callback(maxSelections);
126
133
  }
127
134
  break;
128
135
  }
129
136
  case RuleType.CHOOSE_BETWEEN_X_AND_Y: {
130
137
  const callback = ruleTypeMap[RuleType.CHOOSE_BETWEEN_X_AND_Y];
131
138
  if (callback && maxSelections) {
132
- return callback(modifierGroupName, minSelections, maxSelections);
139
+ return callback(minSelections, maxSelections);
133
140
  }
134
141
  break;
135
142
  }
136
143
  }
137
144
  return undefined;
138
145
  };
146
+ export const checkModifiersValidation = (rule, selectedCount) => {
147
+ const required = rule?.required ?? false;
148
+ const minSelections = rule?.minSelections ?? 0;
149
+ const maxSelections = rule?.maxSelections;
150
+ if (required && selectedCount === 0) {
151
+ return false;
152
+ }
153
+ if (selectedCount < minSelections) {
154
+ return false;
155
+ }
156
+ if (maxSelections && selectedCount > maxSelections) {
157
+ return false;
158
+ }
159
+ return true;
160
+ };
139
161
  export const getAreNotEnoughModifiersOfMandatoryModifierGroupInStock = (modifierGroups) => modifierGroups.some((modifierGroup) => {
140
162
  const isModifierGroupMandatory = modifierGroup.rule?.required;
141
163
  const minimumRequiredModifiers = modifierGroup.rule?.minSelections ?? 0;
@@ -71,7 +71,7 @@ export declare const Variants: React.ForwardRefExoticComponent<ItemDetailsVarian
71
71
  export interface SpecialRequestProps {
72
72
  asChild?: boolean;
73
73
  children?: AsChildChildren<{
74
- description: string;
74
+ value: string;
75
75
  onChange: (value: string) => void;
76
76
  }>;
77
77
  /** Placeholder text for the textarea */
@@ -108,6 +108,7 @@ export interface AddToCartButtonProps {
108
108
  /** Text label for the button */
109
109
  label: React.ReactNode;
110
110
  formattedPrice: string;
111
+ modifierGroupHasError: boolean;
111
112
  }) => React.ReactNode;
112
113
  }
113
114
  export declare const AddToCartButton: React.ForwardRefExoticComponent<AddToCartButtonProps & React.RefAttributes<HTMLElement>>;
@@ -39,7 +39,7 @@ export const Variants = React.forwardRef(({ children, className, asChild, varian
39
39
  });
40
40
  Variants.displayName = 'ItemDetails.Variants';
41
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, }) => {
42
+ return (_jsx(CoreItemDetails.LineItemComponent, { addToCartLabelMap: addToCartLabelMap, children: ({ lineItem, buttonState, addToCartButtonDisabled, loadingState, labelText, formattedPrice, modifierGroupHasError, }) => {
43
43
  const label = (_jsxs(_Fragment, { children: [_jsx("span", { children: labelText }), " ", _jsx("span", { children: " | " }), _jsx("span", { children: formattedPrice })] }));
44
44
  return (_jsx(AsChildSlot, { asChild: asChild, className: className, customElement: children, customElementProps: {
45
45
  buttonState,
@@ -49,6 +49,7 @@ export const AddToCartButton = React.forwardRef(({ asChild, children, className,
49
49
  lineItems: [lineItem],
50
50
  label,
51
51
  formattedPrice,
52
+ modifierGroupHasError,
52
53
  }, ref: ref, ...props, children: _jsx(Commerce.Actions.AddToCart, { asChild: false, label: label, className: className, lineItems: [lineItem], ...props, children: children({
53
54
  lineItem,
54
55
  buttonState,
@@ -56,6 +57,7 @@ export const AddToCartButton = React.forwardRef(({ asChild, children, className,
56
57
  loadingState,
57
58
  label,
58
59
  formattedPrice,
60
+ modifierGroupHasError,
59
61
  }) }) }));
60
62
  } }));
61
63
  });
@@ -44,12 +44,12 @@ export const Description = React.forwardRef(({ ruleTypeMap, className, asChild,
44
44
  } }));
45
45
  });
46
46
  Description.displayName = 'ItemDetailsModifierGroup.Description';
47
- export const Error = React.forwardRef(({ ruleTypeMap, className, asChild, children }, ref) => {
47
+ export const Error = React.forwardRef(({ ruleTypeMap, className, asChild, children, ...otherProps }, ref) => {
48
48
  return (_jsx(GroupError, { ruleTypeMap: ruleTypeMap, children: ({ error }) => {
49
49
  if (!error) {
50
50
  return null;
51
51
  }
52
- return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, testId: TestIds.modifierGroupError, className: className, customElement: children, customElementProps: { error }, children: _jsx("p", { className: className, children: error }) }));
52
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, "data-testid": TestIds.modifierGroupError, className: className, customElement: children, customElementProps: { error }, content: error, ...otherProps, children: _jsx("p", { className: className, children: error }) }));
53
53
  } }));
54
54
  });
55
55
  Error.displayName = 'ItemDetailsModifierGroup.Error';
@@ -26,6 +26,7 @@ interface ItemDetailsLineItemProps {
26
26
  addToCartButtonDisabled: boolean;
27
27
  labelText: string;
28
28
  formattedPrice: string;
29
+ modifierGroupHasError: boolean;
29
30
  }) => React.ReactNode;
30
31
  }
31
32
  export declare const LineItemComponent: React.FC<ItemDetailsLineItemProps>;
@@ -24,11 +24,14 @@ export const Root = ({ children, itemDetailsServiceConfig, }) => {
24
24
  return (_jsx(WixServices, { servicesMap: createServicesMap().addService(ItemServiceDefinition, ItemService, config), children: children({ item: itemDetailsServiceConfig?.item ?? selectedItem }) }));
25
25
  };
26
26
  export const SpecialRequest = ({ children, }) => {
27
- const [value, setValue] = useState('');
28
27
  const service = useService(ItemServiceDefinition);
28
+ const initialSpecialRequest = service.specialRequest?.get?.() ?? '';
29
+ const [value, setValue] = useState(initialSpecialRequest);
29
30
  const onChange = (newValue) => {
30
- setValue(newValue);
31
- service.updateSpecialRequest(newValue);
31
+ if (typeof newValue === 'string') {
32
+ setValue(newValue);
33
+ service.updateSpecialRequest(newValue);
34
+ }
32
35
  };
33
36
  return children({
34
37
  value,
@@ -48,6 +51,7 @@ export const LineItemComponent = ({ addToCartLabelMap, children, }) => {
48
51
  const formatCurrency = oloSettingsService.formatCurrency;
49
52
  const formattedPrice = formatCurrency(price);
50
53
  const labelText = addToCartLabelMap[buttonState];
54
+ const modifierGroupHasError = service.doesModifierGroupHaveError?.get?.() ?? false;
51
55
  return children({
52
56
  loadingState,
53
57
  lineItem,
@@ -55,6 +59,7 @@ export const LineItemComponent = ({ addToCartLabelMap, children, }) => {
55
59
  addToCartButtonDisabled,
56
60
  labelText,
57
61
  formattedPrice,
62
+ modifierGroupHasError,
58
63
  });
59
64
  };
60
65
  export const QuantityComponent = ({ children, }) => {
@@ -49,8 +49,7 @@ export const ModifiersComponent = ({ children, }) => {
49
49
  };
50
50
  export const Description = ({ ruleTypeMap, children, }) => {
51
51
  const { ruleType, modifierGroup } = useModifiersContext();
52
- const modifierGroupName = modifierGroup.name || '';
53
- const description = getRuleTypeMapValue(ruleTypeMap, ruleType, modifierGroupName, modifierGroup.rule);
52
+ const description = getRuleTypeMapValue(ruleTypeMap, ruleType, modifierGroup.rule);
54
53
  return children({ description });
55
54
  };
56
55
  export const GroupError = ({ ruleTypeMap, children, }) => {
@@ -58,10 +57,9 @@ export const GroupError = ({ ruleTypeMap, children, }) => {
58
57
  const { ruleType, modifierGroup } = useModifiersContext();
59
58
  const groupId = modifierGroup._id || '';
60
59
  const modifierGroupErrors = service.modifierGroupError?.get() || {};
61
- const modifierGroupName = modifierGroup.name || '';
62
60
  let error;
63
61
  if (modifierGroupErrors[groupId]) {
64
- error = getRuleTypeMapValue(ruleTypeMap, ruleType, modifierGroupName, modifierGroup.rule);
62
+ error = getRuleTypeMapValue(ruleTypeMap, ruleType, modifierGroup.rule);
65
63
  }
66
64
  return children({ error });
67
65
  };
@@ -11,13 +11,13 @@ export declare enum RuleType {
11
11
  CHOOSE_BETWEEN_X_AND_Y = "CHOOSE_BETWEEN_X_AND_Y"
12
12
  }
13
13
  export interface RuleTypeMap {
14
- [RuleType.NO_LIMIT]?: (modifierGroupName: string) => string;
15
- [RuleType.CHOOSE_ONE]?: (modifierGroupName: string) => string;
16
- [RuleType.CHOOSE_X]?: (modifierGroupName: string, x: number) => string;
17
- [RuleType.CHOOSE_AT_LEAST_ONE]?: (modifierGroupName: string) => string;
18
- [RuleType.CHOOSE_AT_LEAST_X]?: (modifierGroupName: string, x: number) => string;
19
- [RuleType.CHOOSE_UP_TO_X]?: (modifierGroupName: string, x: number) => string;
20
- [RuleType.CHOOSE_BETWEEN_X_AND_Y]?: (modifierGroupName: string, x: number, y: number) => string;
14
+ [RuleType.NO_LIMIT]?: () => string;
15
+ [RuleType.CHOOSE_ONE]?: () => string;
16
+ [RuleType.CHOOSE_X]?: (x: number) => string;
17
+ [RuleType.CHOOSE_AT_LEAST_ONE]?: () => string;
18
+ [RuleType.CHOOSE_AT_LEAST_X]?: (x: number) => string;
19
+ [RuleType.CHOOSE_UP_TO_X]?: (x: number) => string;
20
+ [RuleType.CHOOSE_BETWEEN_X_AND_Y]?: (x: number, y: number) => string;
21
21
  }
22
22
  export declare enum AvailabilityStatus {
23
23
  AVAILABLE = 0,
@@ -43,6 +43,7 @@ export interface ItemServiceAPI {
43
43
  endDate?: Date;
44
44
  weeklyAvailabilitySummary?: WeeklyAvailability;
45
45
  };
46
+ doesModifierGroupHaveError: ReadOnlySignal<boolean>;
46
47
  }
47
48
  /**
48
49
  * Service definition for the Item service.
@@ -67,6 +68,12 @@ export interface ItemServiceConfig {
67
68
  operationId?: string;
68
69
  availabilityStatus?: AvailabilityStatus;
69
70
  editItemMode?: boolean;
71
+ editingItemValues?: {
72
+ quantity?: number;
73
+ specialRequest?: string;
74
+ selectedVariantId?: string;
75
+ selectedModifiers?: Record<string, Array<string>>;
76
+ };
70
77
  menuId?: string;
71
78
  sectionId?: string;
72
79
  futureAvailability?: FutureAvailability;
@@ -1,7 +1,7 @@
1
1
  import { defineService, implementService } from '@wix/services-definitions';
2
2
  import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
3
3
  import { AddToCartButtonState, AvailabilityStatus, } from './common-types.js';
4
- import { getAreNotEnoughModifiersOfMandatoryModifierGroupInStock, getModifiersInitState, getLineItemModifiers, getPriceVariantOptions, getSelectedModifierPrices, getSelectedVariantPrice, calculateItemPrice, } from './utils.js';
4
+ import { getAreNotEnoughModifiersOfMandatoryModifierGroupInStock, getModifiersInitState, getLineItemModifiers, getPriceVariantOptions, getSelectedModifierPrices, getSelectedVariantPrice, calculateItemPrice, checkModifiersValidation, } from './utils.js';
5
5
  import { OLOSettingsServiceDefinition } from './olo-settings-service.js';
6
6
  /**
7
7
  * Service definition for the Item service.
@@ -56,13 +56,27 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
56
56
  const item = signalsService.signal(config.item);
57
57
  const isLoading = signalsService.signal(!!config.item);
58
58
  const error = signalsService.signal(config.item ? null : 'Item not found');
59
- const quantity = signalsService.signal(1);
60
- const specialRequest = signalsService.signal('');
59
+ const quantity = signalsService.signal(config.editItemMode ? (config.editingItemValues?.quantity ?? 1) : 1);
60
+ const specialRequest = signalsService.signal(config.editItemMode
61
+ ? (config.editingItemValues?.specialRequest ?? '')
62
+ : '');
61
63
  const priceVariants = config.item?.priceVariants || [];
62
- const initialVariant = priceVariants.length > 0 ? priceVariants[0] : undefined;
64
+ let initialVariant;
65
+ if (!config.editItemMode) {
66
+ initialVariant = priceVariants.length > 0 ? priceVariants[0] : undefined;
67
+ }
68
+ else {
69
+ if (config.editingItemValues?.selectedVariantId) {
70
+ initialVariant = priceVariants.find((variant) => variant._id === config.editingItemValues?.selectedVariantId);
71
+ }
72
+ else {
73
+ initialVariant =
74
+ priceVariants.length > 0 ? priceVariants[0] : undefined;
75
+ }
76
+ }
63
77
  const selectedVariant = signalsService.signal(initialVariant);
64
78
  const modifierGroups = config.item?.modifierGroups || [];
65
- const initialSelectedModifiers = getModifiersInitState(modifierGroups);
79
+ const initialSelectedModifiers = getModifiersInitState(modifierGroups, config.editItemMode, config.editingItemValues?.selectedModifiers);
66
80
  const selectedModifiers = signalsService.signal(initialSelectedModifiers);
67
81
  const initialModifierGroupError = modifierGroups.reduce((acc, group) => {
68
82
  if (group._id) {
@@ -121,7 +135,9 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
121
135
  quantity.set(_quantity);
122
136
  };
123
137
  const updateSpecialRequest = (_specialRequest) => {
124
- specialRequest.set(_specialRequest);
138
+ if (typeof _specialRequest === 'string') {
139
+ specialRequest.set(_specialRequest);
140
+ }
125
141
  };
126
142
  const updateSelectedVariant = (variant) => {
127
143
  selectedVariant.set(variant);
@@ -148,6 +164,20 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
148
164
  }
149
165
  }
150
166
  };
167
+ const doesModifierGroupHaveError = signalsService.computed(() => {
168
+ const currentSelectedModifiers = selectedModifiers.get();
169
+ const errors = {};
170
+ modifierGroups.forEach((group) => {
171
+ if (group._id) {
172
+ const selectedCount = (currentSelectedModifiers[group._id] || [])
173
+ .length;
174
+ const isValid = checkModifiersValidation(group.rule, selectedCount);
175
+ errors[group._id] = !isValid;
176
+ }
177
+ });
178
+ modifierGroupError.set(errors);
179
+ return Object.values(errors).some((error) => error);
180
+ });
151
181
  const toggleModifier = (modifierGroupId, modifierId, singleSelect = false) => {
152
182
  const currentSelectedModifiers = selectedModifiers.get();
153
183
  const modifierIds = getModifierIds(modifierGroupId, modifierId, singleSelect);
@@ -155,6 +185,11 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
155
185
  ...currentSelectedModifiers,
156
186
  [modifierGroupId]: modifierIds,
157
187
  });
188
+ const currentModifierGroupError = modifierGroupError.get();
189
+ modifierGroupError.set({
190
+ ...currentModifierGroupError,
191
+ [modifierGroupId]: false,
192
+ });
158
193
  };
159
194
  const getSelectedModifiers = (modifierGroupId) => {
160
195
  const currentSelectedModifiers = selectedModifiers.get();
@@ -180,6 +215,7 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
180
215
  addToCartButtonDisabled,
181
216
  price,
182
217
  futureAvailability,
218
+ doesModifierGroupHaveError,
183
219
  };
184
220
  });
185
221
  /**
@@ -5,7 +5,7 @@ interface ruleUtilsArgs {
5
5
  minSelections: number;
6
6
  maxSelections?: number | null;
7
7
  }
8
- export declare const getModifiersInitState: (modifierGroups: EnhancedModifierGroup[]) => Record<string, string[]>;
8
+ export declare const getModifiersInitState: (modifierGroups: EnhancedModifierGroup[], editingItemMode?: boolean, editItemSelectedModifiers?: Record<string, Array<string>>) => Record<string, string[]>;
9
9
  export declare const isSingleSelectRule: (rule: NonNullable<EnhancedModifierGroup["rule"]>) => boolean | null | undefined;
10
10
  export declare const getFirstPreSelectedModifier: (modifiers: EnhancedModifier[]) => string | null | undefined;
11
11
  export declare const getPreSelectedModifiers: (modifiers: EnhancedModifier[]) => string[];
@@ -33,7 +33,8 @@ export declare const hasToChooseAtLeastOne: ({ required, minSelections, maxSelec
33
33
  export declare const hasToChooseAtLeastX: ({ required, minSelections, maxSelections, }: ruleUtilsArgs) => boolean;
34
34
  export declare const chooseUpToX: ({ required, minSelections, maxSelections, }: ruleUtilsArgs) => boolean;
35
35
  export declare const hasToChooseBetweenXAndY: ({ required, minSelections, maxSelections, }: ruleUtilsArgs) => boolean;
36
- export declare const getRuleTypeMapValue: (ruleTypeMap: RuleTypeMap, ruleType: RuleType, modifierGroupName: string, rule: EnhancedModifierGroup["rule"]) => string | undefined;
36
+ export declare const getRuleTypeMapValue: (ruleTypeMap: RuleTypeMap, ruleType: RuleType, rule: EnhancedModifierGroup["rule"]) => string | undefined;
37
+ export declare const checkModifiersValidation: (rule: EnhancedModifierGroup["rule"], selectedCount: number) => boolean;
37
38
  export declare const getAreNotEnoughModifiersOfMandatoryModifierGroupInStock: (modifierGroups: EnhancedModifierGroup[]) => boolean;
38
39
  export declare const getLineItemModifiers: (selectedModifiers: Record<string, Array<string>>, modifierGroups: EnhancedModifierGroup[], formatCurrency: (price?: number) => string) => {
39
40
  id: string;
@@ -1,10 +1,17 @@
1
1
  import { AvailabilityStatus, RuleType, } from './common-types.js';
2
- export const getModifiersInitState = (modifierGroups) => {
2
+ export const getModifiersInitState = (modifierGroups, editingItemMode, editItemSelectedModifiers) => {
3
3
  const initialSelectedModifiers = {};
4
4
  modifierGroups.forEach((group) => {
5
5
  if (group._id) {
6
- const isMultiSelectItem = !isSingleSelectRule(group.rule ?? {});
6
+ if (editingItemMode) {
7
+ const selectedModifiers = editItemSelectedModifiers?.[group._id ?? ''];
8
+ if (selectedModifiers) {
9
+ initialSelectedModifiers[group._id] = selectedModifiers;
10
+ }
11
+ return;
12
+ }
7
13
  const formModifiers = group.modifiers.map(convertModifierToFormModifier);
14
+ const isMultiSelectItem = !isSingleSelectRule(group.rule ?? {});
8
15
  if (isMultiSelectItem) {
9
16
  const preSelectedModifiers = getPreSelectedModifiers(formModifiers);
10
17
  initialSelectedModifiers[group._id] = preSelectedModifiers;
@@ -80,62 +87,77 @@ export const hasToChooseBetweenXAndY = ({ required, minSelections, maxSelections
80
87
  ? minSelections > 0 && maxSelections > minSelections
81
88
  : false
82
89
  : false;
83
- export const getRuleTypeMapValue = (ruleTypeMap, ruleType, modifierGroupName, rule) => {
90
+ export const getRuleTypeMapValue = (ruleTypeMap, ruleType, rule) => {
84
91
  const minSelections = rule?.minSelections ?? 0;
85
92
  const maxSelections = rule?.maxSelections;
86
93
  switch (ruleType) {
87
94
  case RuleType.NO_LIMIT: {
88
95
  const callback = ruleTypeMap[RuleType.NO_LIMIT];
89
96
  if (callback) {
90
- return callback(modifierGroupName);
97
+ return callback();
91
98
  }
92
99
  break;
93
100
  }
94
101
  case RuleType.CHOOSE_ONE: {
95
102
  const callback = ruleTypeMap[RuleType.CHOOSE_ONE];
96
103
  if (callback) {
97
- return callback(modifierGroupName);
104
+ return callback();
98
105
  }
99
106
  break;
100
107
  }
101
108
  case RuleType.CHOOSE_X: {
102
109
  const callback = ruleTypeMap[RuleType.CHOOSE_X];
103
110
  if (callback) {
104
- return callback(modifierGroupName, minSelections);
111
+ return callback(minSelections);
105
112
  }
106
113
  break;
107
114
  }
108
115
  case RuleType.CHOOSE_AT_LEAST_ONE: {
109
116
  const callback = ruleTypeMap[RuleType.CHOOSE_AT_LEAST_ONE];
110
117
  if (callback) {
111
- return callback(modifierGroupName);
118
+ return callback();
112
119
  }
113
120
  break;
114
121
  }
115
122
  case RuleType.CHOOSE_AT_LEAST_X: {
116
123
  const callback = ruleTypeMap[RuleType.CHOOSE_AT_LEAST_X];
117
124
  if (callback) {
118
- return callback(modifierGroupName, minSelections);
125
+ return callback(minSelections);
119
126
  }
120
127
  break;
121
128
  }
122
129
  case RuleType.CHOOSE_UP_TO_X: {
123
130
  const callback = ruleTypeMap[RuleType.CHOOSE_UP_TO_X];
124
131
  if (callback && maxSelections) {
125
- return callback(modifierGroupName, maxSelections);
132
+ return callback(maxSelections);
126
133
  }
127
134
  break;
128
135
  }
129
136
  case RuleType.CHOOSE_BETWEEN_X_AND_Y: {
130
137
  const callback = ruleTypeMap[RuleType.CHOOSE_BETWEEN_X_AND_Y];
131
138
  if (callback && maxSelections) {
132
- return callback(modifierGroupName, minSelections, maxSelections);
139
+ return callback(minSelections, maxSelections);
133
140
  }
134
141
  break;
135
142
  }
136
143
  }
137
144
  return undefined;
138
145
  };
146
+ export const checkModifiersValidation = (rule, selectedCount) => {
147
+ const required = rule?.required ?? false;
148
+ const minSelections = rule?.minSelections ?? 0;
149
+ const maxSelections = rule?.maxSelections;
150
+ if (required && selectedCount === 0) {
151
+ return false;
152
+ }
153
+ if (selectedCount < minSelections) {
154
+ return false;
155
+ }
156
+ if (maxSelections && selectedCount > maxSelections) {
157
+ return false;
158
+ }
159
+ return true;
160
+ };
139
161
  export const getAreNotEnoughModifiersOfMandatoryModifierGroupInStock = (modifierGroups) => modifierGroups.some((modifierGroup) => {
140
162
  const isModifierGroupMandatory = modifierGroup.rule?.required;
141
163
  const minimumRequiredModifiers = modifierGroup.rule?.minSelections ?? 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-restaurants-olo",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
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.29",
59
+ "@wix/headless-components": "0.0.30",
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": "22c7edc50a34f26aaa424a9e2d3195386db4cb0a84e96dc40950fca2"
79
+ "falconPackageHash": "8a5140aec5569bc45df3f464fbf17f0cdcbc5a879f3cb33e3acfbe36"
80
80
  }