@wix/headless-restaurants-olo 0.0.31 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/cjs/dist/react/ClickableItem.d.ts +18 -3
  2. package/cjs/dist/react/ClickableItem.js +16 -9
  3. package/cjs/dist/react/FulfillmentDetails.d.ts +233 -0
  4. package/cjs/dist/react/FulfillmentDetails.js +255 -0
  5. package/cjs/dist/react/ItemDetails.d.ts +233 -36
  6. package/cjs/dist/react/ItemDetails.js +208 -0
  7. package/cjs/dist/react/OLO.d.ts +5 -160
  8. package/cjs/dist/react/OLO.js +6 -122
  9. package/cjs/dist/react/OLOMenus.d.ts +8 -2
  10. package/cjs/dist/react/OLOMenus.js +3 -4
  11. package/cjs/dist/react/Settings.d.ts +176 -48
  12. package/cjs/dist/react/Settings.js +276 -26
  13. package/cjs/dist/react/core/ClickableItem.d.ts +12 -5
  14. package/cjs/dist/react/core/ClickableItem.js +13 -14
  15. package/cjs/dist/react/core/FulfillmentDetails.d.ts +78 -0
  16. package/cjs/dist/react/core/FulfillmentDetails.js +177 -0
  17. package/cjs/dist/react/core/ItemDetails.js +2 -4
  18. package/cjs/dist/react/core/OLO.d.ts +6 -74
  19. package/cjs/dist/react/core/OLO.js +5 -44
  20. package/cjs/dist/react/core/OLOMenus.d.ts +1 -1
  21. package/cjs/dist/react/core/OLOMenus.js +2 -3
  22. package/cjs/dist/react/core/Settings.d.ts +138 -22
  23. package/cjs/dist/react/core/Settings.js +157 -34
  24. package/cjs/dist/react/core/index.d.ts +1 -0
  25. package/cjs/dist/react/core/index.js +1 -0
  26. package/cjs/dist/react/index.d.ts +2 -0
  27. package/cjs/dist/react/index.js +1 -0
  28. package/cjs/dist/services/fulfillments-service.js +80 -22
  29. package/cjs/dist/types/fulfillments-types.d.ts +49 -2
  30. package/cjs/dist/types/operation.d.ts +1 -1
  31. package/cjs/dist/utils/fulfillments-utils.d.ts +34 -1
  32. package/cjs/dist/utils/fulfillments-utils.js +209 -1
  33. package/dist/react/ClickableItem.d.ts +18 -3
  34. package/dist/react/ClickableItem.js +16 -9
  35. package/dist/react/FulfillmentDetails.d.ts +233 -0
  36. package/dist/react/FulfillmentDetails.js +255 -0
  37. package/dist/react/ItemDetails.d.ts +233 -36
  38. package/dist/react/ItemDetails.js +208 -0
  39. package/dist/react/OLO.d.ts +5 -160
  40. package/dist/react/OLO.js +6 -122
  41. package/dist/react/OLOMenus.d.ts +8 -2
  42. package/dist/react/OLOMenus.js +3 -4
  43. package/dist/react/Settings.d.ts +176 -48
  44. package/dist/react/Settings.js +276 -26
  45. package/dist/react/core/ClickableItem.d.ts +12 -5
  46. package/dist/react/core/ClickableItem.js +13 -14
  47. package/dist/react/core/FulfillmentDetails.d.ts +78 -0
  48. package/dist/react/core/FulfillmentDetails.js +177 -0
  49. package/dist/react/core/ItemDetails.js +2 -4
  50. package/dist/react/core/OLO.d.ts +6 -74
  51. package/dist/react/core/OLO.js +5 -44
  52. package/dist/react/core/OLOMenus.d.ts +1 -1
  53. package/dist/react/core/OLOMenus.js +2 -3
  54. package/dist/react/core/Settings.d.ts +138 -22
  55. package/dist/react/core/Settings.js +157 -34
  56. package/dist/react/core/index.d.ts +1 -0
  57. package/dist/react/core/index.js +1 -0
  58. package/dist/react/index.d.ts +2 -0
  59. package/dist/react/index.js +1 -0
  60. package/dist/services/fulfillments-service.js +80 -22
  61. package/dist/types/fulfillments-types.d.ts +49 -2
  62. package/dist/types/operation.d.ts +1 -1
  63. package/dist/utils/fulfillments-utils.d.ts +34 -1
  64. package/dist/utils/fulfillments-utils.js +209 -1
  65. package/package.json +2 -2
@@ -1,7 +1,17 @@
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 { CoreSettings } from './core/index.js';
4
+ import { DispatchType, } from '../types/fulfillments-types.js';
4
5
  import { AsChildSlot } from '@wix/headless-utils/react';
6
+ import { stringFormat } from '../utils/fulfillments-utils.js';
7
+ const DispatchTypeContext = React.createContext(null);
8
+ function useDispatchTypeContext() {
9
+ const context = React.useContext(DispatchTypeContext);
10
+ if (!context) {
11
+ throw new Error('useDispatchTypeContext must be used within a Settings.DispatchTypeSelector component');
12
+ }
13
+ return context;
14
+ }
5
15
  // ========================================
6
16
  // OLO SETTINGS HEADLESS COMPONENTS
7
17
  // ========================================
@@ -13,6 +23,9 @@ import { AsChildSlot } from '@wix/headless-utils/react';
13
23
  var TestIds;
14
24
  (function (TestIds) {
15
25
  TestIds["settingsRoot"] = "settings-root";
26
+ TestIds["dispatchTypeSelector"] = "dispatch-type-selector";
27
+ TestIds["dispatchTypeOptions"] = "dispatch-type-options";
28
+ TestIds["dispatchTypeOption"] = "dispatch-type-option";
16
29
  })(TestIds || (TestIds = {}));
17
30
  /**
18
31
  * Root headless component for OLO Settings
@@ -58,14 +71,177 @@ Root.displayName = 'Settings.Root';
58
71
  // export const CurrentTimeSlot2: React.FC<CurrentTimeSlotProps> = ({ children }) => {
59
72
  // return <CoreSettings.CurrentTimeSlot>{children}</CoreSettings.CurrentTimeSlot>;
60
73
  // };
61
- export const CurrentTimeSlot = React.forwardRef(({ asChild, children, className, ...rest }, ref) => {
62
- return (_jsx(CoreSettings.CurrentTimeSlot, { children: ({ timeSlot, hasDetails }) => (_jsxs(AsChildSlot, { ref: ref, asChild: asChild,
63
- // testId={TestIds.currentTimeSlot}
64
- className: className, customElement: children, customElementProps: { timeSlot, hasDetails }, content: timeSlot, ...rest, children: [timeSlot?.dispatchType, timeSlot?.startTime.toLocaleString() +
65
- ' - ' +
66
- timeSlot?.endTime.toLocaleString()] })) }));
74
+ // Time slot display type enum
75
+ var TimeSlotDisplayType;
76
+ (function (TimeSlotDisplayType) {
77
+ TimeSlotDisplayType["ASAP_TIME"] = "ASAP_TIME";
78
+ TimeSlotDisplayType["ASAP_TIME_RANGE"] = "ASAP_TIME_RANGE";
79
+ TimeSlotDisplayType["PREORDER_TODAY"] = "PREORDER_TODAY";
80
+ TimeSlotDisplayType["PREORDER_TOMORROW"] = "PREORDER_TOMORROW";
81
+ TimeSlotDisplayType["SCHEDULED"] = "SCHEDULED";
82
+ })(TimeSlotDisplayType || (TimeSlotDisplayType = {}));
83
+ // Time format options
84
+ const TIME_ONLY_FORMAT = {
85
+ hour: 'numeric',
86
+ minute: 'numeric',
87
+ };
88
+ // Helper to format a date as time only (e.g., "10:30 AM")
89
+ const formatTime = (date) => date?.toLocaleString('en-US', TIME_ONLY_FORMAT) ?? '';
90
+ const getTimeSlotDisplayType = (params) => {
91
+ const { asapTime, asapTimeRange, isToday, isTomorrow } = params;
92
+ if (asapTime)
93
+ return TimeSlotDisplayType.ASAP_TIME;
94
+ if (asapTimeRange)
95
+ return TimeSlotDisplayType.ASAP_TIME_RANGE;
96
+ if (isToday)
97
+ return TimeSlotDisplayType.PREORDER_TODAY;
98
+ if (isTomorrow)
99
+ return TimeSlotDisplayType.PREORDER_TOMORROW;
100
+ return TimeSlotDisplayType.SCHEDULED;
101
+ };
102
+ const formatTimeSlotText = (params, templates) => {
103
+ const displayType = getTimeSlotDisplayType(params);
104
+ const { asapTime, asapTimeRange, timeSlot } = params;
105
+ const { asapTimeText, asapTimeRangeText, preorderTodayText, preorderTomorrowText, fullDateOptions, } = templates;
106
+ // Pre-format start and end times
107
+ const startTimeStr = formatTime(timeSlot?.startTime);
108
+ const endTimeStr = formatTime(timeSlot?.endTime);
109
+ switch (displayType) {
110
+ case TimeSlotDisplayType.ASAP_TIME:
111
+ return stringFormat(asapTimeText, asapTime);
112
+ case TimeSlotDisplayType.ASAP_TIME_RANGE:
113
+ return stringFormat(asapTimeRangeText, asapTimeRange?.minDuration ?? 0, asapTimeRange?.maxDuration ?? 0);
114
+ case TimeSlotDisplayType.PREORDER_TODAY:
115
+ return stringFormat(preorderTodayText, startTimeStr, endTimeStr);
116
+ case TimeSlotDisplayType.PREORDER_TOMORROW:
117
+ return stringFormat(preorderTomorrowText, startTimeStr, endTimeStr);
118
+ case TimeSlotDisplayType.SCHEDULED:
119
+ default:
120
+ return `${timeSlot?.startTime.toLocaleString('en-US', fullDateOptions)} - ${timeSlot?.endTime.toLocaleString('en-US', fullDateOptions)}`;
121
+ }
122
+ };
123
+ export const CurrentTimeSlot = React.forwardRef(({ asChild, children, className, deliveryTypeText, pickupTypeText, asapTimeText = '', asapTimeRangeText = '', preorderTodayText = '', preorderTomorrowText = '', ...rest }, ref) => {
124
+ const fullDateOptions = {
125
+ month: 'short',
126
+ day: 'numeric',
127
+ hour: 'numeric',
128
+ minute: 'numeric',
129
+ };
130
+ return (_jsx(CoreSettings.CurrentTimeSlot, { children: ({ timeSlot, hasDetails, asapTime, asapTimeRange }) => {
131
+ const dispatchTypeText = timeSlot?.dispatchType === DispatchType.DELIVERY
132
+ ? deliveryTypeText
133
+ : pickupTypeText;
134
+ // Determine if the time slot is today or tomorrow
135
+ const now = new Date();
136
+ const isToday = timeSlot?.startTime &&
137
+ timeSlot.startTime.toDateString() === now.toDateString();
138
+ const tomorrow = new Date(now);
139
+ tomorrow.setDate(tomorrow.getDate() + 1);
140
+ const isTomorrow = timeSlot?.startTime &&
141
+ timeSlot.startTime.toDateString() === tomorrow.toDateString();
142
+ const timeSlotText = formatTimeSlotText({ asapTime, asapTimeRange, timeSlot, isToday, isTomorrow }, {
143
+ asapTimeText,
144
+ asapTimeRangeText,
145
+ preorderTodayText,
146
+ preorderTomorrowText,
147
+ fullDateOptions,
148
+ });
149
+ return hasDetails ? (_jsxs(AsChildSlot, { ref: ref, asChild: asChild,
150
+ // testId={TestIds.currentTimeSlot}
151
+ className: className, customElement: children, customElementProps: {
152
+ timeSlot,
153
+ hasDetails,
154
+ asapTime,
155
+ asapTimeRange,
156
+ }, content: timeSlot, ...rest, children: [dispatchTypeText, " ", timeSlotText] })) : undefined;
157
+ } }));
67
158
  });
68
159
  CurrentTimeSlot.displayName = 'Settings.CurrentTimeSlot';
160
+ /**
161
+ * Container component for dispatch type selection (pickup or delivery)
162
+ * Provides context for child components to access dispatch type data
163
+ * Does not render if no dispatch types are available
164
+ *
165
+ * @example
166
+ * ```tsx
167
+ * <Settings.DispatchTypeSelector>
168
+ * <Settings.DispatchTypeOptions emptyState={<div>No options</div>}>
169
+ * <Settings.DispatchTypeOptionRepeater>
170
+ * {({ type, isSelected, selectDispatchType }) => (
171
+ * <button onClick={() => selectDispatchType(type)}>
172
+ * {type} {isSelected && '✓'}
173
+ * </button>
174
+ * )}
175
+ * </Settings.DispatchTypeOptionRepeater>
176
+ * </Settings.DispatchTypeOptions>
177
+ * </Settings.DispatchTypeSelector>
178
+ * ```
179
+ */
180
+ export const DispatchTypeSelector = React.forwardRef(({ children, className, ...rest }, ref) => {
181
+ return (_jsx(CoreSettings.DispatchTypeSelector, { children: ({ availableTypes, selectedType, selectDispatchType, hasTypes }) => {
182
+ if (!hasTypes)
183
+ return null;
184
+ const contextValue = {
185
+ availableTypes: availableTypes.map(({ type, isSelected }) => ({
186
+ type,
187
+ isSelected,
188
+ })),
189
+ selectedType,
190
+ selectDispatchType,
191
+ hasTypes,
192
+ };
193
+ return (_jsx(DispatchTypeContext.Provider, { value: contextValue, children: _jsx("div", { ref: ref, className: className, "data-testid": TestIds.dispatchTypeSelector, ...rest, children: children }) }));
194
+ } }));
195
+ });
196
+ DispatchTypeSelector.displayName = 'Settings.DispatchTypeSelector';
197
+ /**
198
+ * Container for dispatch type options list with empty state support
199
+ *
200
+ * @example
201
+ * ```tsx
202
+ * <Settings.DispatchTypeOptions emptyState={<div>No options available</div>}>
203
+ * <Settings.DispatchTypeOptionRepeater>
204
+ * {({ type }) => <div>{type}</div>}
205
+ * </Settings.DispatchTypeOptionRepeater>
206
+ * </Settings.DispatchTypeOptions>
207
+ * ```
208
+ */
209
+ export const DispatchTypeOptions = React.forwardRef(({ children, emptyState, className, ...rest }, ref) => {
210
+ const { hasTypes } = useDispatchTypeContext();
211
+ if (!hasTypes) {
212
+ return emptyState || null;
213
+ }
214
+ return (_jsx("div", { ref: ref, className: className, "data-testid": TestIds.dispatchTypeOptions, ...rest, children: children }));
215
+ });
216
+ DispatchTypeOptions.displayName = 'Settings.DispatchTypeOptions';
217
+ /**
218
+ * Repeater component that renders children for each available dispatch type
219
+ *
220
+ * @example
221
+ * ```tsx
222
+ * <Settings.DispatchTypeOptionRepeater>
223
+ * {({ type, isSelected, selectDispatchType }) => (
224
+ * <button
225
+ * onClick={() => selectDispatchType(type)}
226
+ * data-selected={isSelected}
227
+ * >
228
+ * {type}
229
+ * </button>
230
+ * )}
231
+ * </Settings.DispatchTypeOptionRepeater>
232
+ * ```
233
+ */
234
+ export const DispatchTypeOptionRepeater = ({ children }) => {
235
+ const { availableTypes, selectDispatchType, hasTypes } = useDispatchTypeContext();
236
+ if (!hasTypes)
237
+ return null;
238
+ return (_jsx(_Fragment, { children: availableTypes.map(({ type, isSelected }) => (_jsx(React.Fragment, { children: children({
239
+ type,
240
+ isSelected,
241
+ selectDispatchType,
242
+ }) }, type))) }));
243
+ };
244
+ DispatchTypeOptionRepeater.displayName = 'Settings.DispatchTypeOptionRepeater';
69
245
  /**
70
246
  * Headless component for current location data
71
247
  * Provides access to store location and coordinates
@@ -94,31 +270,105 @@ export const CurrentLocation = ({ children, className, asChild, }) => {
94
270
  className: className, customElement: children, customElementProps: { location, hasLocation }, content: location.name, children: location.name })) }));
95
271
  };
96
272
  /**
97
- * Headless component for extra settings data
98
- * Provides access to ordering status, fees, and thresholds
273
+ * Headless component for minimum order amount information
99
274
  *
100
275
  * @example
101
276
  * ```tsx
102
- * <Settings.ExtraData>
103
- * {({ extraData, hasExtraData }) => (
104
- * hasExtraData ? (
105
- * <div>
106
- * <p>Status: {extraData.acceptingOrders ? 'Open' : 'Closed'}</p>
107
- * {extraData.orderingDisabledReason && (
108
- * <p>Reason: {extraData.orderingDisabledReason}</p>
109
- * )}
110
- * <p>Delivery Fee: ${extraData.deliveryFee}</p>
111
- * <p>Min Order: ${extraData.minOrderAmount}</p>
112
- * <p>Free Delivery: ${extraData.freeDeliveryThreshold}</p>
113
- * <p>Tax Rate: {extraData.taxRate}%</p>
114
- * </div>
277
+ * <Settings.MinOrderAmount>
278
+ * {({ minOrderAmount, hasMinOrderAmount }) => (
279
+ * hasMinOrderAmount ? (
280
+ * <div>Minimum order: ${minOrderAmount}</div>
281
+ * ) : null
282
+ * )}
283
+ * </Settings.MinOrderAmount>
284
+ * ```
285
+ */
286
+ export const MinOrderAmount = ({ children, asChild, className, ...rest }) => {
287
+ return (_jsx(CoreSettings.MinOrderAmount, { children: ({ minOrderAmount, hasMinOrderAmount }) => (_jsx(AsChildSlot, { asChild: asChild, className: className, customElement: children, customElementProps: { minOrderAmount, hasMinOrderAmount }, content: minOrderAmount?.toString() ?? '', ...rest, children: minOrderAmount && _jsxs("div", { children: ["$", minOrderAmount] }) })) }));
288
+ };
289
+ /**
290
+ * Headless component for accepting orders status
291
+ *
292
+ * @example
293
+ * ```tsx
294
+ * <Settings.AcceptingOrders>
295
+ * {({ acceptingOrders }) => (
296
+ * <div>
297
+ * Status: {acceptingOrders ? 'Accepting Orders' : 'Not Accepting Orders'}
298
+ * </div>
299
+ * )}
300
+ * </Settings.AcceptingOrders>
301
+ * ```
302
+ */
303
+ export const AcceptingOrders = ({ children, asChild, className, ...rest }) => {
304
+ return (_jsx(CoreSettings.AcceptingOrders, { children: ({ acceptingOrders }) => (_jsx(AsChildSlot, { asChild: asChild, className: className, customElement: children, customElementProps: { acceptingOrders }, content: acceptingOrders.toString(), ...rest, children: _jsx("div", { children: acceptingOrders ? 'Open' : 'Closed' }) })) }));
305
+ };
306
+ /**
307
+ * Headless component for selected address information
308
+ *
309
+ * @example
310
+ * ```tsx
311
+ * <Settings.SelectedAddress>
312
+ * {({ formattedAddress, selectedType, hasAddress }) => (
313
+ * <div>
314
+ * <p>{formattedAddress}</p>
315
+ * <p>{selectedType}</p>
316
+ * </div>
317
+ * )}
318
+ * </Settings.SelectedAddress>
319
+ * ```
320
+ */
321
+ export const SelectedAddress = ({ children, asChild, className, ...rest }) => {
322
+ return (_jsx(CoreSettings.SelectedAddress, { children: ({ selectedAddress, hasAddress, selectedType }) => {
323
+ const formattedAddress = selectedAddress?.formatted ||
324
+ [
325
+ selectedAddress?.addressLine,
326
+ selectedAddress?.city,
327
+ selectedAddress?.subdivision,
328
+ selectedAddress?.postalCode,
329
+ selectedAddress?.country,
330
+ ]
331
+ .filter(Boolean)
332
+ .join(', ');
333
+ return (_jsx(AsChildSlot, { asChild: asChild, className: className, customElement: children, customElementProps: { formattedAddress, selectedType, hasAddress }, content: formattedAddress, ...rest, children: hasAddress && _jsx("div", { children: formattedAddress }) }));
334
+ } }));
335
+ };
336
+ /**
337
+ * Headless component for delivery fee information
338
+ *
339
+ * @example
340
+ * ```tsx
341
+ * <Settings.DeliveryFee>
342
+ * {({ deliveryFee, hasDeliveryFee }) => (
343
+ * hasDeliveryFee ? (
344
+ * <div>Delivery Fee: ${deliveryFee}</div>
115
345
  * ) : (
116
- * <div>No additional settings available</div>
346
+ * <div>Free Delivery</div>
117
347
  * )
118
348
  * )}
119
- * </Settings.ExtraData>
349
+ * </Settings.DeliveryFee>
350
+ * ```
351
+ */
352
+ export const DeliveryFee = ({ children, asChild, className, ...rest }) => {
353
+ return (_jsx(CoreSettings.DeliveryFee, { children: ({ deliveryFee, hasDeliveryFee }) => (_jsx(AsChildSlot, { asChild: asChild, className: className, customElement: children, customElementProps: { deliveryFee, hasDeliveryFee }, content: deliveryFee ?? '', ...rest, children: deliveryFee && _jsx("div", { children: deliveryFee }) })) }));
354
+ };
355
+ /**
356
+ * Headless component for free delivery threshold information
357
+ *
358
+ * @example
359
+ * ```tsx
360
+ * <Settings.FreeDeliveryThreshold>
361
+ * {({ freeDeliveryThreshold, hasFreeDeliveryThreshold }) => (
362
+ * hasFreeDeliveryThreshold ? (
363
+ * <div>Free delivery on orders over ${freeDeliveryThreshold}</div>
364
+ * ) : null
365
+ * )}
366
+ * </Settings.FreeDeliveryThreshold>
120
367
  * ```
121
368
  */
122
- export const ExtraData = ({ children, asChild, className, ...rest }) => {
123
- return (_jsx(CoreSettings.ExtraData, { children: ({ extraData, hasExtraData }) => (_jsx(AsChildSlot, { asChild: asChild, className: className, customElement: children, customElementProps: { extraData, hasExtraData }, content: (!!extraData?.acceptingOrders).toString(), ...rest, children: _jsx("div", { children: (!!extraData?.acceptingOrders).toString() }) })) }));
369
+ export const FreeDeliveryThreshold = ({ children, asChild, className, ...rest }) => {
370
+ return (_jsx(CoreSettings.FreeDeliveryThreshold, { children: ({ freeDeliveryThreshold, hasFreeDeliveryThreshold }) => (_jsx(AsChildSlot, { asChild: asChild, className: className, customElement: children, customElementProps: {
371
+ freeDeliveryThreshold,
372
+ hasFreeDeliveryThreshold,
373
+ }, content: freeDeliveryThreshold ?? '', ...rest, children: freeDeliveryThreshold && _jsx("div", { children: freeDeliveryThreshold }) })) }));
124
374
  };
@@ -1,13 +1,20 @@
1
1
  import React from 'react';
2
+ import type { EnhancedItem } from '@wix/headless-restaurants-menus/services';
2
3
  /**
3
- * CoreMenuItem
4
+ * CoreClickableItem
4
5
  *
5
- * A core menu item component that displays the item image, name, description, and price
6
- * using the project's design system for colors and fonts.
6
+ * A core component that provides item data with context (menu and section IDs)
7
+ * without managing global state. Follows the direct props pattern.
7
8
  */
9
+ export interface SelectedItemData extends EnhancedItem {
10
+ sectionId: string;
11
+ menuId: string;
12
+ }
8
13
  export declare function CoreClickableItem({ children, }: {
9
14
  children: (props: {
10
- item: any;
11
- itemSelected: () => void;
15
+ item: EnhancedItem;
16
+ sectionId: string;
17
+ menuId: string;
18
+ itemData: SelectedItemData;
12
19
  }) => React.ReactNode;
13
20
  }): React.ReactNode;
@@ -1,21 +1,20 @@
1
1
  import { useItemContext, useMenuContext, useSectionContext, } from '@wix/headless-restaurants-menus/react';
2
- import { useService } from '@wix/services-manager-react';
3
- import { OLOSettingsServiceDefinition } from '@wix/headless-restaurants-olo/services';
4
- // import { OLOSettingsServiceDefinition } from "../../services/OLOSettingsService";
5
- /**
6
- * CoreMenuItem
7
- *
8
- * A core menu item component that displays the item image, name, description, and price
9
- * using the project's design system for colors and fonts.
10
- */
11
2
  export function CoreClickableItem({ children, }) {
12
3
  const { item } = useItemContext();
13
4
  const { section } = useSectionContext();
14
5
  const { menu } = useMenuContext();
15
- const service = useService(OLOSettingsServiceDefinition);
16
- const itemSelected = () => {
17
- const selectedItem = { ...item, sectionId: section._id, menuId: menu._id };
18
- service.selectedItem?.set(selectedItem);
6
+ // Ensure IDs are defined (fallback to empty string if undefined)
7
+ const sectionId = section._id ?? '';
8
+ const menuId = menu._id ?? '';
9
+ const itemData = {
10
+ ...item,
11
+ sectionId,
12
+ menuId,
19
13
  };
20
- return children({ item, itemSelected });
14
+ return children({
15
+ item,
16
+ sectionId,
17
+ menuId,
18
+ itemData,
19
+ });
21
20
  }
@@ -0,0 +1,78 @@
1
+ import React from 'react';
2
+ import { DispatchType } from '../../types/fulfillments-types.js';
3
+ export interface RootProps {
4
+ children: React.ReactNode;
5
+ }
6
+ /**
7
+ * Root component for FulfillmentDetails
8
+ * Provides context for all fulfillment detail sub-components
9
+ */
10
+ export declare const Root: React.FC<RootProps>;
11
+ export interface AddressNameProps {
12
+ children: (props: {
13
+ addressName: string | null;
14
+ fullAddress: string | null;
15
+ hasAddress: boolean;
16
+ dispatchType: DispatchType | null;
17
+ }) => React.ReactNode;
18
+ }
19
+ /**
20
+ * Component that provides the address name for the selected fulfillment
21
+ * For pickup: shows the restaurant/pickup location name
22
+ * For delivery: shows the delivery address
23
+ */
24
+ export declare const AddressName: React.FC<AddressNameProps>;
25
+ export interface FulfillmentDateProps {
26
+ children: (props: {
27
+ selectedDate: Date | null;
28
+ onDateChange: (date: Date) => void;
29
+ minDate: Date;
30
+ maxDate: Date;
31
+ }) => React.ReactNode;
32
+ }
33
+ /**
34
+ * Component that provides date picker functionality for fulfillment scheduling
35
+ * Allows users to select when they want to receive their order
36
+ */
37
+ export declare const FulfillmentDate: React.FC<FulfillmentDateProps>;
38
+ export interface TimeSlotOption {
39
+ id: string;
40
+ label: string;
41
+ startTime: Date;
42
+ endTime: Date;
43
+ dispatchType: DispatchType;
44
+ isAsap: boolean;
45
+ }
46
+ export interface AvailableTimeSlotsProps {
47
+ children: (props: {
48
+ timeSlots: TimeSlotOption[];
49
+ selectedTimeSlotId: string | null;
50
+ onTimeSlotChange: (timeSlotId: string) => void;
51
+ hasTimeSlots: boolean;
52
+ }) => React.ReactNode;
53
+ }
54
+ /**
55
+ * Component that provides available time slots for the selected date
56
+ * Shows time ranges when the order can be fulfilled
57
+ */
58
+ export declare const AvailableTimeSlots: React.FC<AvailableTimeSlotsProps>;
59
+ export declare enum FulfillmentTypeEnum {
60
+ ASAP = "ASAP",
61
+ PREORDER = "PREORDER",
62
+ ASAP_AND_FUTURE = "ASAP_AND_FUTURE"
63
+ }
64
+ export interface FulfillmentTypeProps {
65
+ children: (props: {
66
+ selectedType: FulfillmentTypeEnum;
67
+ onTypeChange: (type: FulfillmentTypeEnum) => void;
68
+ hasAsapOption: boolean;
69
+ hasPreorderOption: boolean;
70
+ hasAsapAndFutureOption: boolean;
71
+ }) => React.ReactNode;
72
+ }
73
+ /**
74
+ * Component that provides fulfillment type selection (ASAP or Pre-order)
75
+ * ASAP: Order will be fulfilled as soon as possible
76
+ * Pre-order: Order will be fulfilled at a scheduled time
77
+ */
78
+ export declare const FulfillmentType: React.FC<FulfillmentTypeProps>;
@@ -0,0 +1,177 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { useService } from '@wix/services-manager-react';
4
+ import { FulfillmentsServiceDefinition } from '../../services/fulfillments-service.js';
5
+ /**
6
+ * Root component for FulfillmentDetails
7
+ * Provides context for all fulfillment detail sub-components
8
+ */
9
+ export const Root = ({ children }) => {
10
+ return _jsx(_Fragment, { children: children });
11
+ };
12
+ /**
13
+ * Component that provides the address name for the selected fulfillment
14
+ * For pickup: shows the restaurant/pickup location name
15
+ * For delivery: shows the delivery address
16
+ */
17
+ export const AddressName = ({ children }) => {
18
+ const fulfillmentsService = useService(FulfillmentsServiceDefinition);
19
+ const selectedDispatchInfo = fulfillmentsService.selectedDispatchInfoSignal?.get();
20
+ const selectedAddress = selectedDispatchInfo?.address ?? null;
21
+ const selectedTimeSlot = fulfillmentsService.selectedTimeSlot?.get();
22
+ const dispatchType = selectedTimeSlot?.dispatchType ?? null;
23
+ const addressName = selectedAddress?.formatted ?? null;
24
+ const fullAddress = selectedAddress
25
+ ? [
26
+ // @ts-expect-error - selectedAddress is not typed
27
+ selectedAddress.addressLine,
28
+ selectedAddress.city,
29
+ // @ts-expect-error - selectedAddress is not typed
30
+ selectedAddress.subdivision,
31
+ // @ts-expect-error - selectedAddress is not typed
32
+ selectedAddress.postalCode,
33
+ selectedAddress.country,
34
+ ]
35
+ .filter(Boolean)
36
+ .join(', ')
37
+ : null;
38
+ const hasAddress = Boolean(selectedAddress);
39
+ return children({
40
+ addressName,
41
+ fullAddress,
42
+ hasAddress,
43
+ dispatchType,
44
+ });
45
+ };
46
+ /**
47
+ * Component that provides date picker functionality for fulfillment scheduling
48
+ * Allows users to select when they want to receive their order
49
+ */
50
+ export const FulfillmentDate = ({ children, }) => {
51
+ const fulfillmentsService = useService(FulfillmentsServiceDefinition);
52
+ const selectedTimeSlot = fulfillmentsService.selectedTimeSlot?.get();
53
+ const [selectedDate, setSelectedDate] = useState(selectedTimeSlot?.startTime ?? null);
54
+ const onDateChange = (date) => {
55
+ setSelectedDate(date);
56
+ // Find time slots for the selected date and update if needed
57
+ const timeSlots = fulfillmentsService.firstAvailableTimeSlots?.get() ?? [];
58
+ const slotsForDate = timeSlots.filter((slot) => {
59
+ const slotDate = new Date(slot.startTime);
60
+ return (slotDate.getFullYear() === date.getFullYear() &&
61
+ slotDate.getMonth() === date.getMonth() &&
62
+ slotDate.getDate() === date.getDate());
63
+ });
64
+ // If there are slots for this date, select the first one
65
+ if (slotsForDate.length > 0) {
66
+ fulfillmentsService.setSelectedTimeSlot(slotsForDate[0]);
67
+ }
68
+ };
69
+ // Set min date to today
70
+ const minDate = new Date();
71
+ minDate.setHours(0, 0, 0, 0);
72
+ // Set max date to 30 days from now (reasonable booking window)
73
+ const maxDate = new Date();
74
+ maxDate.setDate(maxDate.getDate() + 30);
75
+ maxDate.setHours(23, 59, 59, 999);
76
+ return children({
77
+ selectedDate,
78
+ onDateChange,
79
+ minDate,
80
+ maxDate,
81
+ });
82
+ };
83
+ /**
84
+ * Component that provides available time slots for the selected date
85
+ * Shows time ranges when the order can be fulfilled
86
+ */
87
+ export const AvailableTimeSlots = ({ children, }) => {
88
+ const fulfillmentsService = useService(FulfillmentsServiceDefinition);
89
+ const timeSlots = fulfillmentsService.firstAvailableTimeSlots?.get() ?? [];
90
+ console.log('timeSlots', timeSlots);
91
+ const selectedTimeSlot = fulfillmentsService.selectedTimeSlot?.get();
92
+ const formatTimeRange = (startTime, endTime) => {
93
+ const options = {
94
+ hour: 'numeric',
95
+ minute: 'numeric',
96
+ hour12: true,
97
+ };
98
+ const start = startTime.toLocaleString('en-US', options);
99
+ const end = endTime.toLocaleString('en-US', options);
100
+ return `${start} - ${end}`;
101
+ };
102
+ const timeSlotOptions = timeSlots.map((slot) => ({
103
+ id: slot.id,
104
+ label: slot.startsNow
105
+ ? 'ASAP'
106
+ : formatTimeRange(slot.startTime, slot.endTime),
107
+ startTime: slot.startTime,
108
+ endTime: slot.endTime,
109
+ dispatchType: slot.dispatchType,
110
+ isAsap: slot.startsNow ?? false,
111
+ }));
112
+ const onTimeSlotChange = (timeSlotId) => {
113
+ const timeSlot = timeSlots.find((slot) => slot.id === timeSlotId);
114
+ if (timeSlot) {
115
+ fulfillmentsService.setSelectedTimeSlot(timeSlot);
116
+ }
117
+ };
118
+ const hasTimeSlots = timeSlotOptions.length > 0;
119
+ const selectedTimeSlotId = selectedTimeSlot?.id ?? null;
120
+ return children({
121
+ timeSlots: timeSlotOptions,
122
+ selectedTimeSlotId,
123
+ onTimeSlotChange,
124
+ hasTimeSlots,
125
+ });
126
+ };
127
+ // ========================================
128
+ // FULFILLMENT TYPE COMPONENT
129
+ // ========================================
130
+ export var FulfillmentTypeEnum;
131
+ (function (FulfillmentTypeEnum) {
132
+ FulfillmentTypeEnum["ASAP"] = "ASAP";
133
+ FulfillmentTypeEnum["PREORDER"] = "PREORDER";
134
+ FulfillmentTypeEnum["ASAP_AND_FUTURE"] = "ASAP_AND_FUTURE";
135
+ })(FulfillmentTypeEnum || (FulfillmentTypeEnum = {}));
136
+ /**
137
+ * Component that provides fulfillment type selection (ASAP or Pre-order)
138
+ * ASAP: Order will be fulfilled as soon as possible
139
+ * Pre-order: Order will be fulfilled at a scheduled time
140
+ */
141
+ export const FulfillmentType = ({ children, }) => {
142
+ const fulfillmentsService = useService(FulfillmentsServiceDefinition);
143
+ const timeSlots = fulfillmentsService.firstAvailableTimeSlots?.get() ?? [];
144
+ const selectedTimeSlot = fulfillmentsService.selectedTimeSlot?.get();
145
+ const hasAsapOption = timeSlots.some((slot) => slot.startsNow);
146
+ const hasPreorderOption = timeSlots.some((slot) => !slot.startsNow);
147
+ const currentType = selectedTimeSlot?.startsNow
148
+ ? FulfillmentTypeEnum.ASAP
149
+ : FulfillmentTypeEnum.PREORDER;
150
+ const [selectedType, setSelectedType] = useState(currentType);
151
+ const onTypeChange = (type) => {
152
+ console.log('onTypeChange', type);
153
+ setSelectedType(type);
154
+ // Find appropriate time slot based on type
155
+ const targetSlot = timeSlots.find((slot) => {
156
+ if (type === FulfillmentTypeEnum.ASAP) {
157
+ return slot.startsNow;
158
+ }
159
+ else {
160
+ return !slot.startsNow;
161
+ }
162
+ });
163
+ if (targetSlot) {
164
+ fulfillmentsService.setSelectedTimeSlot(targetSlot);
165
+ }
166
+ };
167
+ console.log('selectedType', selectedType);
168
+ console.log('hasAsapOption', hasAsapOption);
169
+ console.log('hasPreorderOption', hasPreorderOption);
170
+ return children({
171
+ selectedType,
172
+ onTypeChange,
173
+ hasAsapOption,
174
+ hasPreorderOption,
175
+ hasAsapAndFutureOption: false,
176
+ });
177
+ };
@@ -10,14 +10,12 @@ import { getAvailabilityText } from '../../services/utils.js';
10
10
  export const Root = ({ children, itemDetailsServiceConfig, }) => {
11
11
  const service = useService(OLOSettingsServiceDefinition);
12
12
  const selectedItem = service.selectedItem?.get();
13
- let config = itemDetailsServiceConfig;
14
- if (!config) {
15
- config = loadItemServiceConfig({
13
+ const config = itemDetailsServiceConfig ??
14
+ loadItemServiceConfig({
16
15
  item: selectedItem,
17
16
  // @ts-expect-error - operation is not typed
18
17
  operationId: service.operation?.get()?.id ?? '',
19
18
  });
20
- }
21
19
  if (config.item) {
22
20
  service.selectedItem?.set(config.item);
23
21
  }