@wix/headless-restaurants-olo 0.0.39 → 0.0.41
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/FulfillmentDetails.d.ts +87 -0
- package/cjs/dist/react/FulfillmentDetails.js +206 -1
- package/cjs/dist/react/core/FulfillmentDetails.d.ts +96 -1
- package/cjs/dist/react/core/FulfillmentDetails.js +240 -1
- package/cjs/dist/services/fulfillment-details-service.js +14 -6
- package/cjs/dist/services/fulfillments-service.js +12 -4
- package/cjs/dist/services/item-details-service.d.ts +1 -1
- package/cjs/dist/services/olo-settings-service.d.ts +1 -1
- package/dist/react/FulfillmentDetails.d.ts +87 -0
- package/dist/react/FulfillmentDetails.js +206 -1
- package/dist/react/core/FulfillmentDetails.d.ts +96 -1
- package/dist/react/core/FulfillmentDetails.js +240 -1
- package/dist/services/fulfillment-details-service.js +14 -6
- package/dist/services/fulfillments-service.js +12 -4
- package/dist/services/item-details-service.d.ts +1 -1
- package/dist/services/olo-settings-service.d.ts +1 -1
- package/package.json +3 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { WixServices, useService } from '@wix/services-manager-react';
|
|
4
4
|
import { FulfillmentsServiceDefinition } from '../../services/fulfillments-service.js';
|
|
5
5
|
import { FulfillmentDetailsService, FulfillmentDetailsServiceDefinition, loadFulfillmentDetailsServiceConfig, } from '../../services/fulfillment-details-service.js';
|
|
@@ -7,6 +7,7 @@ import { OLOSettingsServiceDefinition } from '../../services/olo-settings-servic
|
|
|
7
7
|
import { DispatchType, FulfillmentTypeEnum, } from '../../types/fulfillments-types.js';
|
|
8
8
|
import { createServicesMap } from '@wix/services-manager';
|
|
9
9
|
import { formatTimeRange, getToday, getFutureDate, } from '../../utils/date-utils.js';
|
|
10
|
+
import { autocomplete, places } from '@wix/atlas';
|
|
10
11
|
/**
|
|
11
12
|
* Root component for FulfillmentDetails
|
|
12
13
|
* Provides context for all fulfillment detail sub-components
|
|
@@ -222,3 +223,241 @@ export const DeliveryAddress = ({ children, }) => {
|
|
|
222
223
|
onAddressClear,
|
|
223
224
|
});
|
|
224
225
|
};
|
|
226
|
+
/**
|
|
227
|
+
* Component that provides address autocomplete functionality
|
|
228
|
+
* Uses Atlas Autocomplete to predict addresses and Atlas Places to get place details
|
|
229
|
+
* Only provides functionality when dispatch type is DELIVERY
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```tsx
|
|
233
|
+
* <CoreFulfillmentDetails.AddressPicker>
|
|
234
|
+
* {({ inputValue, onInputChange, predictions, onPredictionSelect, isDelivery }) => (
|
|
235
|
+
* isDelivery && (
|
|
236
|
+
* <div>
|
|
237
|
+
* <input
|
|
238
|
+
* value={inputValue}
|
|
239
|
+
* onChange={(e) => onInputChange(e.target.value)}
|
|
240
|
+
* placeholder="Enter address"
|
|
241
|
+
* />
|
|
242
|
+
* {predictions.length > 0 && (
|
|
243
|
+
* <ul>
|
|
244
|
+
* {predictions.map((prediction, index) => (
|
|
245
|
+
* <li
|
|
246
|
+
* key={prediction.searchId || index}
|
|
247
|
+
* onClick={() => onPredictionSelect(prediction)}
|
|
248
|
+
* >
|
|
249
|
+
* {prediction.description}
|
|
250
|
+
* </li>
|
|
251
|
+
* ))}
|
|
252
|
+
* </ul>
|
|
253
|
+
* )}
|
|
254
|
+
* </div>
|
|
255
|
+
* )
|
|
256
|
+
* )}
|
|
257
|
+
* </CoreFulfillmentDetails.AddressPicker>
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
export const AddressPicker = ({ children }) => {
|
|
261
|
+
const fulfillmentDetailsService = useService(FulfillmentDetailsServiceDefinition);
|
|
262
|
+
const dispatchType = fulfillmentDetailsService.dispatchType?.get();
|
|
263
|
+
const isDelivery = dispatchType === DispatchType.DELIVERY;
|
|
264
|
+
const currentAddress = fulfillmentDetailsService?.address?.get();
|
|
265
|
+
const [inputValue, setInputValue] = useState(currentAddress?.formattedAddress ||
|
|
266
|
+
currentAddress?.addressLine ||
|
|
267
|
+
'');
|
|
268
|
+
const [predictions, setPredictions] = useState([]);
|
|
269
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
270
|
+
const [sessionToken, setSessionToken] = useState(null);
|
|
271
|
+
const isUserTypingRef = useRef(false);
|
|
272
|
+
const typingTimeoutRef = useRef(null);
|
|
273
|
+
// Generate a new session token when input changes
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
if (inputValue.length > 0) {
|
|
276
|
+
const newToken = crypto.randomUUID();
|
|
277
|
+
setSessionToken(newToken);
|
|
278
|
+
}
|
|
279
|
+
}, [inputValue]);
|
|
280
|
+
// Update input value when address changes externally
|
|
281
|
+
// Only update if user is not actively typing to prevent focus loss
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
// Skip update if user is currently typing
|
|
284
|
+
if (isUserTypingRef.current) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const address = fulfillmentDetailsService?.address?.get();
|
|
288
|
+
if (address) {
|
|
289
|
+
const formatted = address.formattedAddress || address.addressLine || '';
|
|
290
|
+
if (formatted !== inputValue) {
|
|
291
|
+
setInputValue(formatted);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
else if (!address && inputValue) {
|
|
295
|
+
setInputValue('');
|
|
296
|
+
}
|
|
297
|
+
}, [currentAddress, inputValue]);
|
|
298
|
+
const onInputChange = async (value) => {
|
|
299
|
+
// Clear any existing timeout
|
|
300
|
+
if (typingTimeoutRef.current) {
|
|
301
|
+
clearTimeout(typingTimeoutRef.current);
|
|
302
|
+
}
|
|
303
|
+
// Mark that user is typing to prevent external updates from overwriting
|
|
304
|
+
isUserTypingRef.current = true;
|
|
305
|
+
setInputValue(value);
|
|
306
|
+
// Reset typing flag after user stops typing for 1 second
|
|
307
|
+
typingTimeoutRef.current = setTimeout(() => {
|
|
308
|
+
isUserTypingRef.current = false;
|
|
309
|
+
typingTimeoutRef.current = null;
|
|
310
|
+
}, 1000);
|
|
311
|
+
if (value.length < 2) {
|
|
312
|
+
setPredictions([]);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
setIsLoading(true);
|
|
316
|
+
try {
|
|
317
|
+
// Check if autocomplete module is available
|
|
318
|
+
if (!autocomplete || typeof autocomplete.predict !== 'function') {
|
|
319
|
+
throw new Error('Autocomplete predict function is not available');
|
|
320
|
+
}
|
|
321
|
+
const response = await autocomplete.predict(value, {
|
|
322
|
+
sessionToken: sessionToken || undefined,
|
|
323
|
+
});
|
|
324
|
+
const addressPredictions = response.predictions?.map((prediction) => ({
|
|
325
|
+
description: prediction.description,
|
|
326
|
+
searchId: prediction.searchId,
|
|
327
|
+
mainText: prediction.textStructure?.mainText,
|
|
328
|
+
secondaryText: prediction.textStructure?.secondaryText,
|
|
329
|
+
})) || [];
|
|
330
|
+
setPredictions(addressPredictions);
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
console.error('Error fetching address predictions:', error);
|
|
334
|
+
// Log more details about the error
|
|
335
|
+
if (error?.response?.status === 404) {
|
|
336
|
+
console.error('API endpoint not found. Make sure you are in a Wix environment with proper permissions.');
|
|
337
|
+
}
|
|
338
|
+
setPredictions([]);
|
|
339
|
+
}
|
|
340
|
+
finally {
|
|
341
|
+
setIsLoading(false);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
const onPredictionSelect = async (prediction) => {
|
|
345
|
+
if (!prediction.searchId) {
|
|
346
|
+
console.error('Prediction missing searchId');
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
setIsLoading(true);
|
|
350
|
+
try {
|
|
351
|
+
// Check if places module is available
|
|
352
|
+
if (!places || typeof places.getPlace !== 'function') {
|
|
353
|
+
throw new Error('Places getPlace function is not available');
|
|
354
|
+
}
|
|
355
|
+
const response = await places.getPlace(prediction.searchId, {
|
|
356
|
+
sessionToken: sessionToken || undefined,
|
|
357
|
+
});
|
|
358
|
+
if (response.place?.address) {
|
|
359
|
+
const address = response.place.address;
|
|
360
|
+
const addressLine = address.addressLine ||
|
|
361
|
+
address.addressLine1 ||
|
|
362
|
+
(address.streetAddress
|
|
363
|
+
? `${address.streetAddress.number || ''} ${address.streetAddress.name || ''}`.trim()
|
|
364
|
+
: '') ||
|
|
365
|
+
'';
|
|
366
|
+
// Construct formatted address from components
|
|
367
|
+
const addressParts = [
|
|
368
|
+
addressLine,
|
|
369
|
+
address.city,
|
|
370
|
+
address.subdivision,
|
|
371
|
+
address.postalCode,
|
|
372
|
+
address.country,
|
|
373
|
+
].filter(Boolean);
|
|
374
|
+
const formattedAddress = addressParts.join(', ') || prediction.description || '';
|
|
375
|
+
const addressFields = {
|
|
376
|
+
addressLine,
|
|
377
|
+
addressLine2: address.addressLine2 || '',
|
|
378
|
+
city: address.city || '',
|
|
379
|
+
subdivision: address.subdivision || '',
|
|
380
|
+
postalCode: address.postalCode || '',
|
|
381
|
+
country: address.country || '',
|
|
382
|
+
formattedAddress,
|
|
383
|
+
};
|
|
384
|
+
// TODO - check first available time slot & available dates
|
|
385
|
+
fulfillmentDetailsService.setAddress(addressFields);
|
|
386
|
+
// Clear any pending timeout and mark that this is a programmatic update, not user typing
|
|
387
|
+
if (typingTimeoutRef.current) {
|
|
388
|
+
clearTimeout(typingTimeoutRef.current);
|
|
389
|
+
typingTimeoutRef.current = null;
|
|
390
|
+
}
|
|
391
|
+
isUserTypingRef.current = false;
|
|
392
|
+
setInputValue(formattedAddress);
|
|
393
|
+
setPredictions([]);
|
|
394
|
+
// Invalidate session token after place selection
|
|
395
|
+
setSessionToken(null);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
console.error('Error fetching place details:', error);
|
|
400
|
+
}
|
|
401
|
+
finally {
|
|
402
|
+
setIsLoading(false);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
return children({
|
|
406
|
+
inputValue,
|
|
407
|
+
onInputChange,
|
|
408
|
+
predictions,
|
|
409
|
+
isLoading,
|
|
410
|
+
onPredictionSelect,
|
|
411
|
+
isDelivery,
|
|
412
|
+
dispatchType,
|
|
413
|
+
});
|
|
414
|
+
};
|
|
415
|
+
/**
|
|
416
|
+
* Component that provides save functionality for fulfillment details
|
|
417
|
+
* Saves the selected time slot from FulfillmentDetailsService to FulfillmentsService
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* ```tsx
|
|
421
|
+
* <CoreFulfillmentDetails.SaveButton>
|
|
422
|
+
* {({ onSave, isSaving, canSave }) => (
|
|
423
|
+
* <button onClick={onSave} disabled={!canSave || isSaving}>
|
|
424
|
+
* {isSaving ? 'Saving...' : 'Save'}
|
|
425
|
+
* </button>
|
|
426
|
+
* )}
|
|
427
|
+
* </CoreFulfillmentDetails.SaveButton>
|
|
428
|
+
* ```
|
|
429
|
+
*/
|
|
430
|
+
export const SaveButton = ({ children }) => {
|
|
431
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
432
|
+
const fulfillmentsService = useService(FulfillmentsServiceDefinition);
|
|
433
|
+
const fulfillmentDetailsService = useService(FulfillmentDetailsServiceDefinition);
|
|
434
|
+
const selectedTimeSlot = fulfillmentDetailsService?.timeslot?.get() ?? null;
|
|
435
|
+
const dispatchType = fulfillmentDetailsService?.dispatchType?.get() ?? null;
|
|
436
|
+
// Compare with the current fulfillments service state to determine if there are changes
|
|
437
|
+
const currentFulfillmentsTimeSlot = fulfillmentsService?.selectedTimeSlot?.get() ?? null;
|
|
438
|
+
const hasChanges = selectedTimeSlot?.id !== currentFulfillmentsTimeSlot?.id ||
|
|
439
|
+
selectedTimeSlot?.startTime?.getTime() !==
|
|
440
|
+
currentFulfillmentsTimeSlot?.startTime?.getTime();
|
|
441
|
+
const canSave = selectedTimeSlot !== null;
|
|
442
|
+
const onSave = () => {
|
|
443
|
+
if (!selectedTimeSlot || isSaving || !hasChanges)
|
|
444
|
+
return;
|
|
445
|
+
setIsSaving(true);
|
|
446
|
+
try {
|
|
447
|
+
console.log('onSave', selectedTimeSlot);
|
|
448
|
+
// Save the selected time slot to the fulfillments service
|
|
449
|
+
fulfillmentsService.setSelectedTimeSlot(selectedTimeSlot);
|
|
450
|
+
}
|
|
451
|
+
finally {
|
|
452
|
+
setIsSaving(false);
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
return children({
|
|
456
|
+
onSave,
|
|
457
|
+
isSaving,
|
|
458
|
+
canSave,
|
|
459
|
+
hasChanges,
|
|
460
|
+
selectedTimeSlot,
|
|
461
|
+
dispatchType,
|
|
462
|
+
});
|
|
463
|
+
};
|
|
@@ -2,7 +2,12 @@ import { defineService, implementService } from '@wix/services-definitions';
|
|
|
2
2
|
import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
|
|
3
3
|
import * as operationsSDK from '@wix/auto_sdk_restaurants_operations';
|
|
4
4
|
import { DispatchType, FulfillmentTypeEnum, } from '../types/fulfillments-types.js';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
// convertDateToTimezone,
|
|
7
|
+
createTimeSlotId,
|
|
8
|
+
// getLocale,
|
|
9
|
+
// getTimezone,
|
|
10
|
+
resolveFulfillmentInfo, } from '../utils/fulfillments-utils.js';
|
|
6
11
|
import { createDateFromYMD } from '../utils/date-utils.js';
|
|
7
12
|
import { FulfillmentsServiceDefinition } from './fulfillments-service.js';
|
|
8
13
|
// ========================================
|
|
@@ -66,12 +71,14 @@ export const FulfillmentDetailsService = implementService.withConfig()(Fulfillme
|
|
|
66
71
|
durationRangeOptions: selectedFulfillmentInfo?.durationRange,
|
|
67
72
|
...selectedFulfillmentInfo,
|
|
68
73
|
};
|
|
69
|
-
const timezone = getTimezone();
|
|
70
|
-
const locale = getLocale();
|
|
74
|
+
// const timezone = getTimezone();
|
|
75
|
+
// const locale = getLocale();
|
|
71
76
|
slots.push({
|
|
72
77
|
id: createTimeSlotId(startTime, endTime),
|
|
73
|
-
startTime: convertDateToTimezone(startTime, timezone, locale),
|
|
74
|
-
endTime: convertDateToTimezone(endTime, timezone, locale),
|
|
78
|
+
// startTime: convertDateToTimezone(startTime, timezone, locale),
|
|
79
|
+
// endTime: convertDateToTimezone(endTime, timezone, locale),
|
|
80
|
+
startTime,
|
|
81
|
+
endTime,
|
|
75
82
|
dispatchType: slotDispatchType,
|
|
76
83
|
startsNow: orderSchedulingType === operationsSDK.OrderSchedulingType.ASAP,
|
|
77
84
|
fulfillmentDetails,
|
|
@@ -240,7 +247,8 @@ export const FulfillmentDetailsService = implementService.withConfig()(Fulfillme
|
|
|
240
247
|
const slots = processTimeSlots(timeslotsPerFulfillmentType, currentDispatchType).reverse();
|
|
241
248
|
availableTimeSlotsForDate.set(slots);
|
|
242
249
|
// Auto-select first available slot if none selected
|
|
243
|
-
if (slots.length > 0 && !timeslot.get()) {
|
|
250
|
+
// if (slots.length > 0 && !timeslot.get()) {
|
|
251
|
+
if (slots.length > 0) {
|
|
244
252
|
const firstAsapSlot = slots.find((s) => s.startsNow);
|
|
245
253
|
setTimeslot(firstAsapSlot ?? slots[0]);
|
|
246
254
|
}
|
|
@@ -82,10 +82,18 @@ export const FulfillmentsService = implementService.withConfig()(FulfillmentsSer
|
|
|
82
82
|
const timeSlot = firstAvailableTimeSlots.get()?.find((t) => t.dispatchType === type) ??
|
|
83
83
|
null;
|
|
84
84
|
if (timeSlot) {
|
|
85
|
-
const timezone = getTimezone();
|
|
86
|
-
const locale = getLocale();
|
|
87
|
-
timeSlot.startTime = convertDateToTimezone(
|
|
88
|
-
timeSlot.
|
|
85
|
+
// const timezone = getTimezone();
|
|
86
|
+
// const locale = getLocale();
|
|
87
|
+
// timeSlot.startTime = convertDateToTimezone(
|
|
88
|
+
// timeSlot.startTime,
|
|
89
|
+
// timezone,
|
|
90
|
+
// locale,
|
|
91
|
+
// );
|
|
92
|
+
// timeSlot.endTime = convertDateToTimezone(
|
|
93
|
+
// timeSlot.endTime,
|
|
94
|
+
// timezone,
|
|
95
|
+
// locale,
|
|
96
|
+
// );
|
|
89
97
|
setSelectedTimeSlot(timeSlot);
|
|
90
98
|
}
|
|
91
99
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Signal, type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
|
|
2
|
-
import { type LineItem } from '@wix/ecom/services';
|
|
2
|
+
import { type LineItem } from '@wix/headless-ecom/services';
|
|
3
3
|
import { itemVariants } from '@wix/restaurants';
|
|
4
4
|
import type { EnhancedItem } from '@wix/headless-restaurants-menus/services';
|
|
5
5
|
import { AddToCartButtonState, AvailabilityStatus, FutureAvailability, WeeklyAvailability } from './common-types.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { operations as operationsSDK, operationGroups as operationGroupsSDK } from '@wix/restaurants';
|
|
2
2
|
import { type Signal } from '@wix/services-definitions/core-services/signals';
|
|
3
|
-
import { MenusServiceConfig } from '@wix/restaurants/services';
|
|
3
|
+
import { MenusServiceConfig } from '@wix/headless-restaurants-menus/services';
|
|
4
4
|
export interface OLOSettingsServiceAPI {
|
|
5
5
|
operationGroup: Signal<operationGroupsSDK.OperationGroup | undefined>;
|
|
6
6
|
operation: Signal<operationsSDK.Operation | undefined>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/headless-restaurants-olo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.41",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"@radix-ui/react-checkbox": "^1.3.3",
|
|
55
55
|
"@radix-ui/react-radio-group": "^1.1.3",
|
|
56
56
|
"@radix-ui/react-slot": "^1.1.0",
|
|
57
|
+
"@wix/atlas": "^1.0.33",
|
|
57
58
|
"@wix/auto_sdk_restaurants_items": "^1.0.48",
|
|
58
59
|
"@wix/ecom": "^1.0.1461",
|
|
59
60
|
"@wix/essentials": "^1.0.0",
|
|
@@ -77,5 +78,5 @@
|
|
|
77
78
|
"groupId": "com.wixpress.headless-components"
|
|
78
79
|
}
|
|
79
80
|
},
|
|
80
|
-
"falconPackageHash": "
|
|
81
|
+
"falconPackageHash": "6f66175564e00a76ca5069b76e7d754e5c75d03c6dc4be5408c6ca4e"
|
|
81
82
|
}
|