@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.
- package/cjs/dist/react/ClickableItem.d.ts +18 -3
- package/cjs/dist/react/ClickableItem.js +16 -9
- package/cjs/dist/react/FulfillmentDetails.d.ts +233 -0
- package/cjs/dist/react/FulfillmentDetails.js +255 -0
- package/cjs/dist/react/ItemDetails.d.ts +233 -36
- package/cjs/dist/react/ItemDetails.js +208 -0
- package/cjs/dist/react/OLO.d.ts +5 -160
- package/cjs/dist/react/OLO.js +6 -122
- package/cjs/dist/react/OLOMenus.d.ts +8 -2
- package/cjs/dist/react/OLOMenus.js +3 -4
- package/cjs/dist/react/Settings.d.ts +176 -48
- package/cjs/dist/react/Settings.js +276 -26
- package/cjs/dist/react/core/ClickableItem.d.ts +12 -5
- package/cjs/dist/react/core/ClickableItem.js +13 -14
- package/cjs/dist/react/core/FulfillmentDetails.d.ts +78 -0
- package/cjs/dist/react/core/FulfillmentDetails.js +177 -0
- package/cjs/dist/react/core/ItemDetails.js +2 -4
- package/cjs/dist/react/core/OLO.d.ts +6 -74
- package/cjs/dist/react/core/OLO.js +5 -44
- package/cjs/dist/react/core/OLOMenus.d.ts +1 -1
- package/cjs/dist/react/core/OLOMenus.js +2 -3
- package/cjs/dist/react/core/Settings.d.ts +138 -22
- package/cjs/dist/react/core/Settings.js +157 -34
- package/cjs/dist/react/core/index.d.ts +1 -0
- package/cjs/dist/react/core/index.js +1 -0
- package/cjs/dist/react/index.d.ts +2 -0
- package/cjs/dist/react/index.js +1 -0
- package/cjs/dist/services/fulfillments-service.js +80 -22
- package/cjs/dist/types/fulfillments-types.d.ts +49 -2
- package/cjs/dist/types/operation.d.ts +1 -1
- package/cjs/dist/utils/fulfillments-utils.d.ts +34 -1
- package/cjs/dist/utils/fulfillments-utils.js +209 -1
- package/dist/react/ClickableItem.d.ts +18 -3
- package/dist/react/ClickableItem.js +16 -9
- package/dist/react/FulfillmentDetails.d.ts +233 -0
- package/dist/react/FulfillmentDetails.js +255 -0
- package/dist/react/ItemDetails.d.ts +233 -36
- package/dist/react/ItemDetails.js +208 -0
- package/dist/react/OLO.d.ts +5 -160
- package/dist/react/OLO.js +6 -122
- package/dist/react/OLOMenus.d.ts +8 -2
- package/dist/react/OLOMenus.js +3 -4
- package/dist/react/Settings.d.ts +176 -48
- package/dist/react/Settings.js +276 -26
- package/dist/react/core/ClickableItem.d.ts +12 -5
- package/dist/react/core/ClickableItem.js +13 -14
- package/dist/react/core/FulfillmentDetails.d.ts +78 -0
- package/dist/react/core/FulfillmentDetails.js +177 -0
- package/dist/react/core/ItemDetails.js +2 -4
- package/dist/react/core/OLO.d.ts +6 -74
- package/dist/react/core/OLO.js +5 -44
- package/dist/react/core/OLOMenus.d.ts +1 -1
- package/dist/react/core/OLOMenus.js +2 -3
- package/dist/react/core/Settings.d.ts +138 -22
- package/dist/react/core/Settings.js +157 -34
- package/dist/react/core/index.d.ts +1 -0
- package/dist/react/core/index.js +1 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +1 -0
- package/dist/services/fulfillments-service.js +80 -22
- package/dist/types/fulfillments-types.d.ts +49 -2
- package/dist/types/operation.d.ts +1 -1
- package/dist/utils/fulfillments-utils.d.ts +34 -1
- package/dist/utils/fulfillments-utils.js +209 -1
- 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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
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.
|
|
103
|
-
* {({
|
|
104
|
-
*
|
|
105
|
-
* <div>
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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>
|
|
346
|
+
* <div>Free Delivery</div>
|
|
117
347
|
* )
|
|
118
348
|
* )}
|
|
119
|
-
* </Settings.
|
|
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
|
|
123
|
-
return (_jsx(CoreSettings.
|
|
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
|
-
*
|
|
4
|
+
* CoreClickableItem
|
|
4
5
|
*
|
|
5
|
-
* A core
|
|
6
|
-
*
|
|
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:
|
|
11
|
-
|
|
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
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
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({
|
|
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
|
-
|
|
14
|
-
|
|
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
|
}
|