@wix/headless-restaurants-olo 0.0.33 → 0.0.35

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 (59) hide show
  1. package/cjs/dist/react/FulfillmentDetails.d.ts +139 -15
  2. package/cjs/dist/react/FulfillmentDetails.js +128 -24
  3. package/cjs/dist/react/ItemDetails.d.ts +4 -4
  4. package/cjs/dist/react/ItemDetails.js +5 -6
  5. package/cjs/dist/react/Settings.d.ts +8 -0
  6. package/cjs/dist/react/Settings.js +18 -25
  7. package/cjs/dist/react/core/FulfillmentDetails.d.ts +73 -7
  8. package/cjs/dist/react/core/FulfillmentDetails.js +133 -86
  9. package/cjs/dist/react/core/ItemDetails.d.ts +1 -1
  10. package/cjs/dist/react/core/ItemDetails.js +5 -16
  11. package/cjs/dist/react/core/Settings.d.ts +19 -4
  12. package/cjs/dist/react/core/Settings.js +29 -20
  13. package/cjs/dist/services/fulfillment-details-service.d.ts +127 -0
  14. package/cjs/dist/services/fulfillment-details-service.js +351 -0
  15. package/cjs/dist/services/fulfillments-service.js +67 -43
  16. package/cjs/dist/services/index.d.ts +2 -0
  17. package/cjs/dist/services/index.js +1 -0
  18. package/cjs/dist/services/item-details-service.d.ts +1 -1
  19. package/cjs/dist/services/item-details-service.js +10 -16
  20. package/cjs/dist/services/olo-settings-service.d.ts +0 -1
  21. package/cjs/dist/services/olo-settings-service.js +1 -3
  22. package/cjs/dist/services/utils.d.ts +4 -0
  23. package/cjs/dist/services/utils.js +12 -0
  24. package/cjs/dist/types/fulfillments-types.d.ts +18 -1
  25. package/cjs/dist/types/fulfillments-types.js +9 -0
  26. package/cjs/dist/utils/date-utils.d.ts +164 -0
  27. package/cjs/dist/utils/date-utils.js +297 -0
  28. package/cjs/dist/utils/fulfillments-utils.d.ts +3 -2
  29. package/cjs/dist/utils/fulfillments-utils.js +13 -57
  30. package/dist/react/FulfillmentDetails.d.ts +139 -15
  31. package/dist/react/FulfillmentDetails.js +128 -24
  32. package/dist/react/ItemDetails.d.ts +4 -4
  33. package/dist/react/ItemDetails.js +5 -6
  34. package/dist/react/Settings.d.ts +8 -0
  35. package/dist/react/Settings.js +18 -25
  36. package/dist/react/core/FulfillmentDetails.d.ts +73 -7
  37. package/dist/react/core/FulfillmentDetails.js +133 -86
  38. package/dist/react/core/ItemDetails.d.ts +1 -1
  39. package/dist/react/core/ItemDetails.js +5 -16
  40. package/dist/react/core/Settings.d.ts +19 -4
  41. package/dist/react/core/Settings.js +29 -20
  42. package/dist/services/fulfillment-details-service.d.ts +127 -0
  43. package/dist/services/fulfillment-details-service.js +351 -0
  44. package/dist/services/fulfillments-service.js +67 -43
  45. package/dist/services/index.d.ts +2 -0
  46. package/dist/services/index.js +1 -0
  47. package/dist/services/item-details-service.d.ts +1 -1
  48. package/dist/services/item-details-service.js +10 -16
  49. package/dist/services/olo-settings-service.d.ts +0 -1
  50. package/dist/services/olo-settings-service.js +1 -3
  51. package/dist/services/utils.d.ts +4 -0
  52. package/dist/services/utils.js +12 -0
  53. package/dist/types/fulfillments-types.d.ts +18 -1
  54. package/dist/types/fulfillments-types.js +9 -0
  55. package/dist/utils/date-utils.d.ts +164 -0
  56. package/dist/utils/date-utils.js +297 -0
  57. package/dist/utils/fulfillments-utils.d.ts +3 -2
  58. package/dist/utils/fulfillments-utils.js +13 -57
  59. package/package.json +3 -2
@@ -0,0 +1,127 @@
1
+ import { type Signal, type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
2
+ import { DispatchType, TimeSlot, FulfillmentTypeEnum } from '../types/fulfillments-types.js';
3
+ import { Operation } from '../types/operation.js';
4
+ import { Address } from '@wix/auto_sdk_restaurants_operations';
5
+ /**
6
+ * Date availability information
7
+ */
8
+ export interface AvailableDate {
9
+ /** The date that is available */
10
+ date: Date;
11
+ /** Whether this date has available slots */
12
+ hasAvailableSlots: boolean;
13
+ }
14
+ /**
15
+ * API interface for the Fulfillment Details service
16
+ * Provides reactive state management for fulfillment scheduling
17
+ */
18
+ export interface FulfillmentDetailsServiceAPI {
19
+ /** Currently selected time slot */
20
+ timeslot: Signal<TimeSlot | null>;
21
+ /** Delivery/pickup address */
22
+ address: Signal<Address | null>;
23
+ /** Selected date for fulfillment */
24
+ date: Signal<Date | null>;
25
+ /** Dispatch type (PICKUP or DELIVERY) */
26
+ dispatchType: Signal<DispatchType | null>;
27
+ /** ASAP time slot (when user wants the order as soon as possible) */
28
+ asapTimeSlot: Signal<TimeSlot | null>;
29
+ /** Loading state for async operations */
30
+ isLoading: Signal<boolean>;
31
+ /** Error state */
32
+ error: Signal<string | null>;
33
+ /** Available dates within the configured range */
34
+ availableDates: Signal<AvailableDate[]>;
35
+ /** Available time slots for the selected date */
36
+ availableTimeSlotsForDate: Signal<TimeSlot[]>;
37
+ /** Available time slots filtered by current dispatch type */
38
+ availableTimeSlots: ReadOnlySignal<TimeSlot[]>;
39
+ /** Selected scheduling type (ASAP or PREORDER) */
40
+ schedulingType: Signal<FulfillmentTypeEnum | undefined>;
41
+ /**
42
+ * Get the currently selected time slot
43
+ * @returns The selected time slot or null
44
+ */
45
+ getTimeslot: () => TimeSlot | null;
46
+ /**
47
+ * Set the selected time slot
48
+ * @param timeslot - The time slot to select
49
+ */
50
+ setTimeslot: (timeslot: TimeSlot | null) => void;
51
+ /**
52
+ * Set the delivery/pickup address
53
+ * @param address - The address to set
54
+ */
55
+ setAddress: (address: Address | null) => void;
56
+ /**
57
+ * Set the selected date for fulfillment
58
+ * Also triggers fetching available time slots for the date
59
+ * @param date - The date to select
60
+ */
61
+ setDate: (date: Date | null) => Promise<void>;
62
+ /**
63
+ * Set the dispatch type (PICKUP or DELIVERY)
64
+ * @param type - The dispatch type to set
65
+ */
66
+ setDispatchType: (type: DispatchType | null) => void;
67
+ /**
68
+ * Set the ASAP time slot
69
+ * @param timeslot - The ASAP time slot to set
70
+ */
71
+ setAsapTimeSlot: (timeslot: TimeSlot | null) => void;
72
+ /**
73
+ * Set the scheduling type (ASAP or PREORDER)
74
+ * Also selects an appropriate time slot based on the type
75
+ * @param type - The scheduling type to set
76
+ */
77
+ setSchedulingType: (type: FulfillmentTypeEnum | undefined) => void;
78
+ /**
79
+ * Calculate available dates within a date range
80
+ * @param startDate - Start of the date range
81
+ * @param endDate - End of the date range
82
+ * @returns Promise that resolves to available dates
83
+ */
84
+ calculateAvailableDatesInRange: (startDate: Date, endDate: Date) => Promise<AvailableDate[]>;
85
+ /**
86
+ * Calculate available time slots for a specific date
87
+ * @param date - The date to get time slots for
88
+ * @returns Promise that resolves to available time slots
89
+ */
90
+ calculateAvailableTimeSlotsForDate: (date: Date) => Promise<TimeSlot[]>;
91
+ }
92
+ /**
93
+ * Configuration for the Fulfillment Details service
94
+ */
95
+ export interface FulfillmentDetailsServiceConfig {
96
+ /** The operation to get fulfillment details for */
97
+ operation?: Operation;
98
+ /** Initial dispatch type */
99
+ initialDispatchType?: DispatchType;
100
+ /** Initial address */
101
+ initialAddress?: Address;
102
+ /** Number of days ahead to calculate available dates (default: 30) */
103
+ daysAhead?: number;
104
+ }
105
+ export declare const FulfillmentDetailsServiceDefinition: string & {
106
+ __api: FulfillmentDetailsServiceAPI;
107
+ __config: {};
108
+ isServiceDefinition?: boolean;
109
+ } & FulfillmentDetailsServiceAPI;
110
+ export declare const FulfillmentDetailsService: import("@wix/services-definitions").ServiceFactory<string & {
111
+ __api: FulfillmentDetailsServiceAPI;
112
+ __config: {};
113
+ isServiceDefinition?: boolean;
114
+ } & FulfillmentDetailsServiceAPI, FulfillmentDetailsServiceConfig>;
115
+ /**
116
+ * Load configuration for the FulfillmentDetailsService
117
+ * Used for SSR initialization
118
+ *
119
+ * @param operation - The operation to configure the service with
120
+ * @param options - Additional configuration options
121
+ * @returns The service configuration
122
+ */
123
+ export declare function loadFulfillmentDetailsServiceConfig(operation: Operation, options?: {
124
+ initialDispatchType?: DispatchType;
125
+ initialAddress?: Address;
126
+ daysAhead?: number;
127
+ }): FulfillmentDetailsServiceConfig;
@@ -0,0 +1,351 @@
1
+ import { defineService, implementService } from '@wix/services-definitions';
2
+ import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
3
+ import * as operationsSDK from '@wix/auto_sdk_restaurants_operations';
4
+ import { DispatchType, FulfillmentTypeEnum, } from '../types/fulfillments-types.js';
5
+ import { convertDateToTimezone, createTimeSlotId, getLocale, getTimezone, resolveFulfillmentInfo, } from '../utils/fulfillments-utils.js';
6
+ import { createDateFromYMD } from '../utils/date-utils.js';
7
+ import { FulfillmentsServiceDefinition } from './fulfillments-service.js';
8
+ // ========================================
9
+ // Service Definition
10
+ // ========================================
11
+ export const FulfillmentDetailsServiceDefinition = defineService('fulfillment-details');
12
+ // ========================================
13
+ // Service Implementation
14
+ // ========================================
15
+ export const FulfillmentDetailsService = implementService.withConfig()(FulfillmentDetailsServiceDefinition, ({ getService, config }) => {
16
+ if (!config.operation) {
17
+ throw new Error('Operation is required for FulfillmentDetailsService');
18
+ }
19
+ const fulfillmentsService = getService(FulfillmentsServiceDefinition);
20
+ const signalsService = getService(SignalsServiceDefinition);
21
+ const operation = config.operation;
22
+ const daysAhead = config.daysAhead ?? 30;
23
+ // ========================================
24
+ // Initialize Signals
25
+ // ========================================
26
+ const timeslot = signalsService.signal(null);
27
+ const address = signalsService.signal(config?.initialAddress ??
28
+ fulfillmentsService.selectedDispatchInfoSignal?.get()?.address ??
29
+ null);
30
+ const date = signalsService.signal(null);
31
+ const dispatchType = signalsService.signal(config.initialDispatchType ??
32
+ fulfillmentsService.selectedFulfillment.get()
33
+ ?.type ??
34
+ null);
35
+ const asapTimeSlot = signalsService.signal(null);
36
+ const isLoading = signalsService.signal(false);
37
+ const error = signalsService.signal(null);
38
+ const availableDates = signalsService.signal([]);
39
+ const availableTimeSlotsForDate = signalsService.signal([]);
40
+ const schedulingType = signalsService.signal(fulfillmentsService.schedulingType?.get());
41
+ // ========================================
42
+ // Helper Functions
43
+ // ========================================
44
+ /**
45
+ * Process time slots from SDK response to our TimeSlot format
46
+ */
47
+ const processTimeSlots = (timeSlotsResponse, targetDispatchType) => {
48
+ if (!timeSlotsResponse)
49
+ return [];
50
+ const slots = [];
51
+ for (const fulfillmentSlot of timeSlotsResponse) {
52
+ const { timeSlot, fulfilmentType, fulfillmentInfo } = fulfillmentSlot;
53
+ // Filter by dispatch type if specified
54
+ const slotDispatchType = fulfilmentType === 'DELIVERY'
55
+ ? DispatchType.DELIVERY
56
+ : DispatchType.PICKUP;
57
+ if (targetDispatchType && slotDispatchType !== targetDispatchType) {
58
+ continue;
59
+ }
60
+ const { startTime, endTime, orderSchedulingType } = timeSlot ?? {};
61
+ if (!startTime || !endTime)
62
+ continue;
63
+ const selectedFulfillmentInfo = resolveFulfillmentInfo(fulfillmentInfo ?? []);
64
+ const fulfillmentDetails = {
65
+ maxTimeOptions: selectedFulfillmentInfo?.maxTime,
66
+ durationRangeOptions: selectedFulfillmentInfo?.durationRange,
67
+ ...selectedFulfillmentInfo,
68
+ };
69
+ const timezone = getTimezone();
70
+ const locale = getLocale();
71
+ slots.push({
72
+ id: createTimeSlotId(startTime, endTime),
73
+ startTime: convertDateToTimezone(startTime, timezone, locale),
74
+ endTime: convertDateToTimezone(endTime, timezone, locale),
75
+ dispatchType: slotDispatchType,
76
+ startsNow: orderSchedulingType === operationsSDK.OrderSchedulingType.ASAP,
77
+ fulfillmentDetails,
78
+ });
79
+ }
80
+ return slots;
81
+ };
82
+ /**
83
+ * Check if two dates are on the same day
84
+ */
85
+ const isSameDay = (date1, date2) => {
86
+ return (date1.getFullYear() === date2.getFullYear() &&
87
+ date1.getMonth() === date2.getMonth() &&
88
+ date1.getDate() === date2.getDate());
89
+ };
90
+ // ========================================
91
+ // Action Implementations
92
+ // ========================================
93
+ const getTimeslot = () => {
94
+ return timeslot.get();
95
+ };
96
+ const setTimeslot = (slot) => {
97
+ timeslot.set(slot);
98
+ // Update date if timeslot has a start time
99
+ if (slot?.startTime) {
100
+ date.set(slot.startTime);
101
+ }
102
+ // Update dispatch type based on timeslot
103
+ if (slot?.dispatchType) {
104
+ dispatchType.set(slot.dispatchType);
105
+ }
106
+ // Update asap timeslot if this is an ASAP slot
107
+ if (slot?.startsNow) {
108
+ asapTimeSlot.set(slot);
109
+ }
110
+ };
111
+ const setAddress = (addr) => {
112
+ address.set(addr);
113
+ };
114
+ const setDate = async (selectedDate) => {
115
+ date.set(selectedDate);
116
+ // Fetch time slots for the selected date
117
+ if (selectedDate) {
118
+ await calculateAvailableTimeSlotsForDateFn(selectedDate);
119
+ }
120
+ else {
121
+ availableTimeSlotsForDate.set([]);
122
+ }
123
+ };
124
+ const setDispatchType = (type) => {
125
+ console.log('setDispatchType', type);
126
+ dispatchType.set(type);
127
+ // Re-filter available time slots based on new dispatch type
128
+ const currentDate = date.get();
129
+ if (currentDate) {
130
+ calculateAvailableTimeSlotsForDateFn(currentDate);
131
+ }
132
+ };
133
+ const setAsapTimeSlot = (slot) => {
134
+ asapTimeSlot.set(slot);
135
+ if (slot) {
136
+ timeslot.set(slot);
137
+ if (slot.startTime) {
138
+ date.set(slot.startTime);
139
+ }
140
+ }
141
+ };
142
+ const setSchedulingType = (type) => {
143
+ schedulingType.set(type);
144
+ // Find appropriate time slot based on type
145
+ const slots = availableTimeSlotsForDate.get();
146
+ const targetSlot = slots.find((slot) => {
147
+ if (type === FulfillmentTypeEnum.ASAP) {
148
+ return slot.startsNow;
149
+ }
150
+ else {
151
+ return !slot.startsNow;
152
+ }
153
+ });
154
+ if (targetSlot) {
155
+ setTimeslot(targetSlot);
156
+ }
157
+ };
158
+ // ========================================
159
+ // Fetch Method Implementations
160
+ // ========================================
161
+ const calculateAvailableDatesInRangeFn = async (startDate, endDate) => {
162
+ isLoading.set(true);
163
+ error.set(null);
164
+ try {
165
+ // Call the SDK to get available dates in the range
166
+ const response = await operationsSDK.calculateAvailableDatesInRange(operation.id, {
167
+ from: {
168
+ year: startDate.getFullYear(),
169
+ month: startDate.getMonth() + 1, // SDK uses 1-based months
170
+ day: startDate.getDate(),
171
+ },
172
+ until: {
173
+ year: endDate.getFullYear(),
174
+ month: endDate.getMonth() + 1,
175
+ day: endDate.getDate(),
176
+ },
177
+ deliveryAddress: address.get() ?? undefined,
178
+ });
179
+ const { availableDatesPerFulfillmentType } = response;
180
+ // Process the response to extract available dates
181
+ const datesMap = new Map();
182
+ const currentDispatchType = dispatchType.get();
183
+ for (const fulfillmentDates of availableDatesPerFulfillmentType ??
184
+ []) {
185
+ const { fulfilmentType, dates } = fulfillmentDates;
186
+ // Filter by dispatch type if set
187
+ if (currentDispatchType) {
188
+ const slotType = fulfilmentType === 'DELIVERY'
189
+ ? DispatchType.DELIVERY
190
+ : DispatchType.PICKUP;
191
+ if (slotType !== currentDispatchType)
192
+ continue;
193
+ }
194
+ if (dates) {
195
+ for (const sdkDate of dates) {
196
+ if (!sdkDate.year || !sdkDate.month || !sdkDate.day)
197
+ continue;
198
+ const dateObj = createDateFromYMD(sdkDate.year, sdkDate.month, sdkDate.day);
199
+ const dateKey = dateObj.toISOString().split('T')[0];
200
+ if (!datesMap.has(dateKey)) {
201
+ datesMap.set(dateKey, {
202
+ date: dateObj,
203
+ hasAvailableSlots: true,
204
+ });
205
+ }
206
+ }
207
+ }
208
+ }
209
+ const dates = Array.from(datesMap.values()).sort((a, b) => a.date.getTime() - b.date.getTime());
210
+ availableDates.set(dates);
211
+ return dates;
212
+ }
213
+ catch (err) {
214
+ const errorMessage = err instanceof Error
215
+ ? err.message
216
+ : 'Failed to calculate available dates';
217
+ error.set(errorMessage);
218
+ console.error('Error calculating available dates:', err);
219
+ return [];
220
+ }
221
+ finally {
222
+ isLoading.set(false);
223
+ }
224
+ };
225
+ const calculateAvailableTimeSlotsForDateFn = async (targetDate) => {
226
+ isLoading.set(true);
227
+ error.set(null);
228
+ try {
229
+ // Call the SDK to get time slots for the specific date
230
+ const response = await operationsSDK.calculateAvailableTimeSlotsForDate(operation.id, {
231
+ date: {
232
+ year: targetDate.getFullYear(),
233
+ month: targetDate.getMonth() + 1,
234
+ day: targetDate.getDate(),
235
+ },
236
+ deliveryAddress: address.get() ?? undefined,
237
+ });
238
+ const { timeslotsPerFulfillmentType } = response;
239
+ const currentDispatchType = dispatchType.get();
240
+ const slots = processTimeSlots(timeslotsPerFulfillmentType, currentDispatchType).reverse();
241
+ availableTimeSlotsForDate.set(slots);
242
+ // Auto-select first available slot if none selected
243
+ if (slots.length > 0 && !timeslot.get()) {
244
+ const firstAsapSlot = slots.find((s) => s.startsNow);
245
+ setTimeslot(firstAsapSlot ?? slots[0]);
246
+ }
247
+ return slots;
248
+ }
249
+ catch (err) {
250
+ const errorMessage = err instanceof Error
251
+ ? err.message
252
+ : 'Failed to calculate time slots for date';
253
+ error.set(errorMessage);
254
+ console.error('Error calculating time slots for date:', err);
255
+ return [];
256
+ }
257
+ finally {
258
+ isLoading.set(false);
259
+ }
260
+ };
261
+ // ========================================
262
+ // Initialize with default date range
263
+ // ========================================
264
+ // Auto-load available dates on service initialization
265
+ const initializeDates = async () => {
266
+ const startDate = new Date();
267
+ startDate.setHours(0, 0, 0, 0);
268
+ const endDate = new Date();
269
+ endDate.setDate(endDate.getDate() + daysAhead);
270
+ endDate.setHours(23, 59, 59, 999);
271
+ await calculateAvailableDatesInRangeFn(startDate, endDate);
272
+ // If we have available dates, auto-select today or first available
273
+ const dates = availableDates.get();
274
+ if (dates.length > 0) {
275
+ const today = new Date();
276
+ const todayAvailable = dates.find((d) => isSameDay(d.date, today) && d.hasAvailableSlots);
277
+ const firstAvailable = todayAvailable ?? dates.find((d) => d.hasAvailableSlots);
278
+ if (firstAvailable) {
279
+ await setDate(firstAvailable.date);
280
+ }
281
+ }
282
+ };
283
+ // Initialize asynchronously (don't block service creation)
284
+ initializeDates().catch((err) => {
285
+ console.error('Error initializing fulfillment details:', err);
286
+ error.set('Failed to initialize fulfillment details');
287
+ });
288
+ // ========================================
289
+ // Computed Signals
290
+ // ========================================
291
+ /**
292
+ * Available time slots filtered by current dispatch type
293
+ */
294
+ const availableTimeSlots = signalsService.computed(() => {
295
+ const currentDispatchType = dispatchType.get();
296
+ console.log('currentDispatchType', currentDispatchType);
297
+ const allTimeSlots = availableTimeSlotsForDate.get();
298
+ if (!currentDispatchType) {
299
+ return allTimeSlots;
300
+ }
301
+ return allTimeSlots.filter((slot) => slot.dispatchType === currentDispatchType);
302
+ });
303
+ // ========================================
304
+ // Return Service API
305
+ // ========================================
306
+ return {
307
+ // Signals
308
+ timeslot,
309
+ address,
310
+ date,
311
+ dispatchType,
312
+ asapTimeSlot,
313
+ isLoading,
314
+ error,
315
+ availableDates,
316
+ availableTimeSlotsForDate,
317
+ availableTimeSlots,
318
+ schedulingType,
319
+ // Actions
320
+ getTimeslot,
321
+ setTimeslot,
322
+ setAddress,
323
+ setDate,
324
+ setDispatchType,
325
+ setAsapTimeSlot,
326
+ setSchedulingType,
327
+ // Fetch methods
328
+ calculateAvailableDatesInRange: calculateAvailableDatesInRangeFn,
329
+ calculateAvailableTimeSlotsForDate: calculateAvailableTimeSlotsForDateFn,
330
+ };
331
+ });
332
+ // ========================================
333
+ // Config Loader
334
+ // ========================================
335
+ /**
336
+ * Load configuration for the FulfillmentDetailsService
337
+ * Used for SSR initialization
338
+ *
339
+ * @param operation - The operation to configure the service with
340
+ * @param options - Additional configuration options
341
+ * @returns The service configuration
342
+ */
343
+ export function loadFulfillmentDetailsServiceConfig(operation, options) {
344
+ console.log('options', options);
345
+ return {
346
+ operation,
347
+ initialDispatchType: options?.initialDispatchType,
348
+ initialAddress: options?.initialAddress,
349
+ daysAhead: options?.daysAhead ?? 30,
350
+ };
351
+ }
@@ -2,8 +2,8 @@ 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 * as fulfillemtMethodsSDK from '@wix/auto_sdk_restaurants_fulfillment-methods';
5
- import { processFulfillments, processFulfillmentTimeSlotByOperationList, } from '../utils/fulfillments-utils.js';
6
- import { DispatchType, } from '../types/fulfillments-types.js';
5
+ import { convertDateToTimezone, getLocale, getTimezone, processFulfillments, processFulfillmentTimeSlotByOperationList, } from '../utils/fulfillments-utils.js';
6
+ import { DispatchType, FulfillmentTypeEnum, } from '../types/fulfillments-types.js';
7
7
  export const FulfillmentsServiceDefinition = defineService('fulfillments');
8
8
  export const FulfillmentsService = implementService.withConfig()(FulfillmentsServiceDefinition, ({ getService, config }) => {
9
9
  if (!config.operation) {
@@ -12,42 +12,80 @@ export const FulfillmentsService = implementService.withConfig()(FulfillmentsSer
12
12
  const signalsService = getService(SignalsServiceDefinition);
13
13
  const firstAvailableTimeSlotsByOperationList = new Map(config.firstAvailableTimeSlots?.map(processFulfillmentTimeSlotByOperationList) ?? []);
14
14
  const fulfillments = signalsService.signal(config.fulfillments ?? []);
15
- const firstAvailableTimeSlots = signalsService.signal(firstAvailableTimeSlotsByOperationList.get(
16
- // @ts-expect-error - operation is not typed
17
- config.operation?.id ?? '') ?? []);
15
+ const firstAvailableTimeSlots = signalsService.signal(firstAvailableTimeSlotsByOperationList.get(config.operation?.id ?? '') ?? []);
18
16
  const isLoading = signalsService.signal(false);
19
17
  const error = signalsService.signal(null);
20
18
  const dispatchesInfoSignal = signalsService.signal({});
21
19
  const selectedDispatchInfoSignal = signalsService.signal(null);
22
20
  const asapTimeSignal = signalsService.signal(undefined);
23
21
  const asapTimeRangeSignal = signalsService.signal(undefined);
24
- processFulfillments(fulfillments.get() ?? [], firstAvailableTimeSlots.get() ?? [], config.operation ?? undefined).then((result) => {
25
- const { dispatchesInfo } = result ?? {};
26
- dispatchesInfoSignal.set(dispatchesInfo ?? {});
27
- const selectedDispatchInfo = dispatchesInfoSignal.get()[selectedFulfillment.get()?.type];
28
- selectedDispatchInfoSignal.set(selectedDispatchInfo ?? null);
29
- const { maxTimeOptions, durationRangeOptions } = selectedDispatchInfo ?? {};
30
- // const isDelivery = !!address;
31
- const isAsap = !!selectedTimeSlot.get()?.startsNow;
32
- // if (isDelivery && isAsap) {
33
- const { fulfillmentDetails } = selectedTimeSlot.get() ?? {};
34
- if (isAsap) {
35
- asapTimeSignal.set(fulfillmentDetails?.maxTimeOptions ?? maxTimeOptions);
36
- asapTimeRangeSignal.set(fulfillmentDetails?.durationRangeOptions ?? durationRangeOptions);
37
- }
38
- });
22
+ const selectedFulfillment = signalsService.signal(null);
39
23
  const pickupTimeSlot = firstAvailableTimeSlots
40
24
  .get()
41
25
  ?.find((t) => t.dispatchType === DispatchType.PICKUP) ?? null;
42
26
  const initialSelected = pickupTimeSlot ?? firstAvailableTimeSlots.get()?.[0] ?? null;
43
27
  const selectedTimeSlot = signalsService.signal(initialSelected);
44
- const selectedFulfillment = signalsService.signal(null);
28
+ const { dispatchesInfo } = processFulfillments(fulfillments.get() ?? [], firstAvailableTimeSlots.get() ?? [], config.operation ?? undefined) ?? {};
29
+ // Initialize selected fulfillment for initial time slot
30
+ if (initialSelected && fulfillments.get()?.length > 0) {
31
+ const timezone = getTimezone();
32
+ const locale = getLocale();
33
+ initialSelected.startTime = convertDateToTimezone(initialSelected.startTime, timezone, locale);
34
+ initialSelected.endTime = convertDateToTimezone(initialSelected.endTime, timezone, locale);
35
+ const fulfillmentList = fulfillments.get();
36
+ const matchingFulfillment = fulfillmentList.find((f) => f.type === initialSelected.dispatchType) ?? null;
37
+ selectedFulfillment.set(matchingFulfillment);
38
+ }
39
+ dispatchesInfoSignal.set(dispatchesInfo ?? {});
40
+ const selectedDispatchInfo = dispatchesInfoSignal.get()[selectedFulfillment.get()?.type];
41
+ selectedDispatchInfoSignal.set(selectedDispatchInfo ?? null);
42
+ const { maxTimeOptions, durationRangeOptions } = selectedDispatchInfo ?? {};
43
+ // const isDelivery = !!address;
44
+ const isAsap = !!selectedTimeSlot.get()?.startsNow;
45
+ // if (isDelivery && isAsap) {
46
+ const { fulfillmentDetails } = selectedTimeSlot.get() ?? {};
47
+ if (isAsap) {
48
+ asapTimeSignal.set(fulfillmentDetails?.maxTimeOptions ?? maxTimeOptions);
49
+ asapTimeRangeSignal.set(fulfillmentDetails?.durationRangeOptions ?? durationRangeOptions);
50
+ }
51
+ // });
45
52
  const availableTypes = signalsService.signal([]);
53
+ const getSchedulingType = (timeSlot) => {
54
+ if (!timeSlot)
55
+ return undefined;
56
+ if (config.operation?.operationType === operationsSDK.SchedulingType.ASAP) {
57
+ return timeSlot.startsNow
58
+ ? FulfillmentTypeEnum.ASAP
59
+ : FulfillmentTypeEnum.ASAP_AND_FUTURE;
60
+ }
61
+ return FulfillmentTypeEnum.PREORDER;
62
+ };
63
+ const schedulingType = signalsService.signal(getSchedulingType(initialSelected));
64
+ // Compute initial fulfillment type options based on time slots
65
+ const computeFulfillmentTypeOptions = (timeSlots) => {
66
+ const hasAsap = timeSlots
67
+ .filter((slot) => slot.dispatchType === selectedFulfillment.get()?.type)
68
+ .some((slot) => slot.startsNow);
69
+ const hasFuture = !!config.operation?.allowAsapFutureHandling;
70
+ return {
71
+ hasAsapOption: hasAsap,
72
+ hasPreorderOption: !hasAsap && hasFuture,
73
+ hasAsapAndFutureOption: hasAsap && hasFuture,
74
+ };
75
+ };
76
+ const initialOptions = computeFulfillmentTypeOptions(firstAvailableTimeSlots.get() ?? []);
77
+ const hasAsapOption = signalsService.signal(initialOptions.hasAsapOption);
78
+ const hasPreorderOption = signalsService.signal(initialOptions.hasPreorderOption);
79
+ const hasAsapAndFutureOption = signalsService.signal(initialOptions.hasAsapAndFutureOption);
46
80
  selectedTimeSlot.set(initialSelected);
47
81
  const selectFulfillmentType = (type) => {
48
82
  const timeSlot = firstAvailableTimeSlots.get()?.find((t) => t.dispatchType === type) ??
49
83
  null;
50
84
  if (timeSlot) {
85
+ const timezone = getTimezone();
86
+ const locale = getLocale();
87
+ timeSlot.startTime = convertDateToTimezone(timeSlot.startTime, timezone, locale);
88
+ timeSlot.endTime = convertDateToTimezone(timeSlot.endTime, timezone, locale);
51
89
  setSelectedTimeSlot(timeSlot);
52
90
  }
53
91
  };
@@ -56,39 +94,21 @@ export const FulfillmentsService = implementService.withConfig()(FulfillmentsSer
56
94
  * This action updates:
57
95
  * - selectedTimeSlot signal
58
96
  * - selectedFulfillment signal (based on dispatch type)
97
+ * - schedulingType signal (based on order scheduling type)
59
98
  * - minOrderPrice signal (based on fulfillment method)
60
99
  */
61
100
  const setSelectedTimeSlot = (timeSlot) => {
101
+ console.log('setSelectedTimeSlot', timeSlot);
62
102
  // Update the selected time slot
63
103
  selectedTimeSlot.set(timeSlot);
104
+ // Update scheduling type based on order scheduling configuration
105
+ schedulingType.set(getSchedulingType(timeSlot));
64
106
  // Update selected fulfillment based on dispatch type
65
107
  const matchingFulfillment = fulfillments.get().find((f) => f.type === timeSlot.dispatchType) ??
66
108
  null;
67
109
  selectedFulfillment.set(matchingFulfillment);
68
110
  selectedDispatchInfoSignal.set(dispatchesInfoSignal.get()[matchingFulfillment?.type] ?? null);
69
111
  };
70
- // Initialize selected fulfillment for initial time slot
71
- if (initialSelected && fulfillments.get()?.length > 0) {
72
- const fulfillmentList = fulfillments.get();
73
- const matchingFulfillment = fulfillmentList.find((f) => f.type === initialSelected.dispatchType) ?? null;
74
- selectedFulfillment.set(matchingFulfillment);
75
- }
76
- // Load time slots and fulfillments if not provided
77
- if (!config.firstAvailableTimeSlots && config.operation) {
78
- loadFulfillmentsServiceConfig(config.operation).then((loadedConfig) => {
79
- const timeSlotsMap = new Map(loadedConfig.firstAvailableTimeSlots?.map(processFulfillmentTimeSlotByOperationList) ?? []);
80
- const newTimeSlots =
81
- // @ts-expect-error - operation is not typed
82
- timeSlotsMap.get(loadedConfig.operation?.id ?? '') ?? [];
83
- firstAvailableTimeSlots.set(newTimeSlots);
84
- fulfillments.set(loadedConfig.fulfillments ?? []);
85
- // Set initial time slot using the action to ensure all related signals are updated
86
- const newInitialSelected = newTimeSlots[0];
87
- if (newInitialSelected) {
88
- setSelectedTimeSlot(newInitialSelected);
89
- }
90
- });
91
- }
92
112
  availableTypes.set(Array.from(new Set(firstAvailableTimeSlots.get()?.map((t) => t.dispatchType) ?? [])).sort((a, _) => (a === DispatchType.PICKUP ? -1 : 1)));
93
113
  return {
94
114
  firstAvailableTimeSlots,
@@ -100,6 +120,10 @@ export const FulfillmentsService = implementService.withConfig()(FulfillmentsSer
100
120
  asapTimeRangeSignal,
101
121
  selectedTimeSlot,
102
122
  availableTypes,
123
+ schedulingType,
124
+ hasAsapOption,
125
+ hasPreorderOption,
126
+ hasAsapAndFutureOption,
103
127
  isLoading,
104
128
  error,
105
129
  setSelectedTimeSlot,
@@ -2,3 +2,5 @@ export { ItemService, ItemServiceDefinition, loadItemServiceConfig, ItemServiceC
2
2
  export { OLOSettingsService, OLOSettingsServiceDefinition, loadOLOSettingsServiceConfig, type OLOSettingsServiceConfig, type OLOSettingsServiceAPI, } from './olo-settings-service.js';
3
3
  export { AvailabilityStatus, AvailabilityStatusMap, RuleType, RuleTypeMap, AddToCartButtonState, AddToCartButtonLabelMap, } from './common-types.js';
4
4
  export { FulfillmentsService, FulfillmentsServiceDefinition, loadFulfillmentsServiceConfig, } from './fulfillments-service.js';
5
+ export { type FulfillmentsServiceConfig } from '../types/fulfillments-types.js';
6
+ export { FulfillmentDetailsService, FulfillmentDetailsServiceDefinition, loadFulfillmentDetailsServiceConfig, type FulfillmentDetailsServiceAPI, type FulfillmentDetailsServiceConfig, type AvailableDate, } from './fulfillment-details-service.js';
@@ -2,3 +2,4 @@ export { ItemService, ItemServiceDefinition, loadItemServiceConfig, } from './it
2
2
  export { OLOSettingsService, OLOSettingsServiceDefinition, loadOLOSettingsServiceConfig, } from './olo-settings-service.js';
3
3
  export { AvailabilityStatus, RuleType, AddToCartButtonState, } from './common-types.js';
4
4
  export { FulfillmentsService, FulfillmentsServiceDefinition, loadFulfillmentsServiceConfig, } from './fulfillments-service.js';
5
+ export { FulfillmentDetailsService, FulfillmentDetailsServiceDefinition, loadFulfillmentDetailsServiceConfig, } from './fulfillment-details-service.js';
@@ -16,6 +16,7 @@ export interface ItemServiceAPI {
16
16
  quantity: Signal<number>;
17
17
  specialRequest: Signal<string>;
18
18
  lineItem: ReadOnlySignal<LineItem>;
19
+ onHandleAddToCart: (onClick: (lineItem: LineItem) => void) => void;
19
20
  buttonState: ReadOnlySignal<AddToCartButtonState | undefined>;
20
21
  addToCartButtonDisabled: Signal<boolean>;
21
22
  price: ReadOnlySignal<number>;
@@ -43,7 +44,6 @@ export interface ItemServiceAPI {
43
44
  endDate?: Date;
44
45
  weeklyAvailabilitySummary?: WeeklyAvailability;
45
46
  };
46
- doesModifierGroupHaveError: ReadOnlySignal<boolean>;
47
47
  }
48
48
  /**
49
49
  * Service definition for the Item service.