@wix/headless-stores 0.0.9 → 0.0.11

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 (135) hide show
  1. package/cjs/dist/enums/index.d.ts +2 -0
  2. package/cjs/dist/enums/index.js +18 -0
  3. package/cjs/dist/enums/social-platform-enums.d.ts +25 -0
  4. package/cjs/dist/enums/social-platform-enums.js +30 -0
  5. package/cjs/dist/enums/sort-enums.d.ts +17 -0
  6. package/cjs/dist/enums/sort-enums.js +21 -0
  7. package/cjs/dist/react/BuyNow.d.ts +2 -4
  8. package/cjs/dist/react/Category.d.ts +15 -0
  9. package/cjs/dist/react/Category.js +22 -0
  10. package/cjs/dist/react/Collection.d.ts +151 -0
  11. package/cjs/dist/react/Collection.js +209 -0
  12. package/cjs/dist/react/FilteredCollection.d.ts +95 -0
  13. package/cjs/dist/react/FilteredCollection.js +150 -0
  14. package/cjs/dist/react/PayNow.d.ts +2 -4
  15. package/cjs/dist/react/Product.d.ts +43 -0
  16. package/cjs/dist/react/Product.js +35 -0
  17. package/cjs/dist/react/ProductActions.d.ts +42 -0
  18. package/cjs/dist/react/ProductActions.js +83 -0
  19. package/cjs/dist/react/ProductModifiers.d.ts +164 -0
  20. package/cjs/dist/react/ProductModifiers.js +168 -0
  21. package/cjs/dist/react/ProductVariantSelector.d.ts +153 -0
  22. package/cjs/dist/react/ProductVariantSelector.js +138 -0
  23. package/cjs/dist/react/RelatedProducts.d.ts +64 -0
  24. package/cjs/dist/react/RelatedProducts.js +73 -0
  25. package/cjs/dist/react/SelectedVariant.d.ts +66 -0
  26. package/cjs/dist/react/SelectedVariant.js +52 -0
  27. package/cjs/dist/react/SocialSharing.d.ts +112 -0
  28. package/cjs/dist/react/SocialSharing.js +86 -0
  29. package/cjs/dist/react/Sort.d.ts +14 -0
  30. package/cjs/dist/react/Sort.js +17 -0
  31. package/cjs/dist/react/index.d.ts +11 -0
  32. package/cjs/dist/react/index.js +34 -0
  33. package/cjs/dist/services/buy-now-service.js +2 -2
  34. package/cjs/dist/services/catalog-options-service.d.ts +30 -0
  35. package/cjs/dist/services/catalog-options-service.js +156 -0
  36. package/cjs/dist/services/catalog-price-range-service.d.ts +23 -0
  37. package/cjs/dist/services/catalog-price-range-service.js +97 -0
  38. package/cjs/dist/services/category-service.d.ts +26 -0
  39. package/cjs/dist/services/category-service.js +71 -0
  40. package/cjs/dist/services/collection-service.d.ts +37 -0
  41. package/cjs/dist/services/collection-service.js +569 -0
  42. package/cjs/dist/services/filter-service.d.ts +56 -0
  43. package/cjs/dist/services/filter-service.js +147 -0
  44. package/cjs/dist/services/product-media-gallery-service.d.ts +25 -0
  45. package/cjs/dist/services/product-media-gallery-service.js +105 -0
  46. package/cjs/dist/services/product-modifiers-service.d.ts +34 -0
  47. package/cjs/dist/services/product-modifiers-service.js +111 -0
  48. package/cjs/dist/services/product-service.d.ts +28 -0
  49. package/cjs/dist/services/product-service.js +68 -0
  50. package/cjs/dist/services/related-products-service.d.ts +25 -0
  51. package/cjs/dist/services/related-products-service.js +54 -0
  52. package/cjs/dist/services/selected-variant-service.d.ts +59 -0
  53. package/cjs/dist/services/selected-variant-service.js +541 -0
  54. package/cjs/dist/services/social-sharing-service.d.ts +41 -0
  55. package/cjs/dist/services/social-sharing-service.js +141 -0
  56. package/cjs/dist/services/sort-service.d.ts +20 -0
  57. package/cjs/dist/services/sort-service.js +32 -0
  58. package/cjs/dist/utils/url-params.d.ts +5 -0
  59. package/cjs/dist/utils/url-params.js +50 -0
  60. package/dist/enums/index.d.ts +2 -0
  61. package/dist/enums/index.js +2 -0
  62. package/dist/enums/social-platform-enums.d.ts +25 -0
  63. package/dist/enums/social-platform-enums.js +27 -0
  64. package/dist/enums/sort-enums.d.ts +17 -0
  65. package/dist/enums/sort-enums.js +18 -0
  66. package/dist/react/BuyNow.d.ts +2 -4
  67. package/dist/react/Category.d.ts +15 -0
  68. package/dist/react/Category.js +18 -0
  69. package/dist/react/Collection.d.ts +151 -0
  70. package/dist/react/Collection.js +201 -0
  71. package/dist/react/FilteredCollection.d.ts +95 -0
  72. package/dist/react/FilteredCollection.js +140 -0
  73. package/dist/react/PayNow.d.ts +2 -4
  74. package/dist/react/Product.d.ts +43 -0
  75. package/dist/react/Product.js +30 -0
  76. package/dist/react/ProductActions.d.ts +42 -0
  77. package/dist/react/ProductActions.js +79 -0
  78. package/dist/react/ProductModifiers.d.ts +164 -0
  79. package/dist/react/ProductModifiers.js +160 -0
  80. package/dist/react/ProductVariantSelector.d.ts +153 -0
  81. package/dist/react/ProductVariantSelector.js +130 -0
  82. package/dist/react/RelatedProducts.d.ts +64 -0
  83. package/dist/react/RelatedProducts.js +65 -0
  84. package/dist/react/SelectedVariant.d.ts +66 -0
  85. package/dist/react/SelectedVariant.js +46 -0
  86. package/dist/react/SocialSharing.d.ts +112 -0
  87. package/dist/react/SocialSharing.js +77 -0
  88. package/dist/react/Sort.d.ts +14 -0
  89. package/dist/react/Sort.js +14 -0
  90. package/dist/react/index.d.ts +11 -0
  91. package/dist/react/index.js +11 -0
  92. package/dist/services/buy-now-service.js +2 -2
  93. package/dist/services/catalog-options-service.d.ts +30 -0
  94. package/dist/services/catalog-options-service.js +152 -0
  95. package/dist/services/catalog-price-range-service.d.ts +23 -0
  96. package/dist/services/catalog-price-range-service.js +93 -0
  97. package/dist/services/category-service.d.ts +26 -0
  98. package/dist/services/category-service.js +67 -0
  99. package/dist/services/collection-service.d.ts +37 -0
  100. package/dist/services/collection-service.js +532 -0
  101. package/dist/services/filter-service.d.ts +56 -0
  102. package/dist/services/filter-service.js +144 -0
  103. package/dist/services/product-media-gallery-service.d.ts +25 -0
  104. package/dist/services/product-media-gallery-service.js +101 -0
  105. package/dist/services/product-modifiers-service.d.ts +34 -0
  106. package/dist/services/product-modifiers-service.js +107 -0
  107. package/dist/services/product-service.d.ts +28 -0
  108. package/dist/services/product-service.js +64 -0
  109. package/dist/services/related-products-service.d.ts +25 -0
  110. package/dist/services/related-products-service.js +50 -0
  111. package/dist/services/selected-variant-service.d.ts +59 -0
  112. package/dist/services/selected-variant-service.js +538 -0
  113. package/dist/services/social-sharing-service.d.ts +41 -0
  114. package/dist/services/social-sharing-service.js +137 -0
  115. package/dist/services/sort-service.d.ts +20 -0
  116. package/dist/services/sort-service.js +29 -0
  117. package/dist/utils/url-params.d.ts +5 -0
  118. package/dist/utils/url-params.js +46 -0
  119. package/package.json +8 -1
  120. package/dist/astro/BuyNowServiceContext.d.ts +0 -2
  121. package/dist/astro/BuyNowServiceContext.js +0 -6
  122. package/dist/astro/ManagerProviderContext.d.ts +0 -2
  123. package/dist/astro/ManagerProviderContext.js +0 -7
  124. package/dist/astro/withBuyButtonService.d.ts +0 -2
  125. package/dist/astro/withBuyButtonService.js +0 -16
  126. package/dist/react/CurrentCartServiceProvider.d.ts +0 -5
  127. package/dist/react/CurrentCartServiceProvider.js +0 -12
  128. package/dist/react/VariantSelectorServiceProvider.d.ts +0 -7
  129. package/dist/react/VariantSelectorServiceProvider.js +0 -22
  130. package/dist/react/hookim/index.d.ts +0 -5
  131. package/dist/react/hookim/index.js +0 -22
  132. package/dist/services/CurrentCartService.d.ts +0 -18
  133. package/dist/services/CurrentCartService.js +0 -9
  134. package/dist/services/VariantSelectorServices.d.ts +0 -8
  135. package/dist/services/VariantSelectorServices.js +0 -20
@@ -0,0 +1,532 @@
1
+ import { defineService, implementService, } from '@wix/services-definitions';
2
+ import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
3
+ import { searchProducts as searchProductsSDK, SortDirection, } from '@wix/auto_sdk_stores_products-v-3';
4
+ import { queryVariants } from '@wix/auto_sdk_stores_read-only-variants-v-3';
5
+ import { FilterServiceDefinition } from './filter-service';
6
+ import { CategoryServiceDefinition } from './category-service';
7
+ import { SortServiceDefinition } from './sort-service';
8
+ import { URLParamsUtils } from '../utils/url-params';
9
+ import { SortType } from '../enums/sort-enums';
10
+ const searchProducts = async (searchOptions) => {
11
+ const searchParams = {
12
+ filter: searchOptions.search?.filter,
13
+ sort: searchOptions.search?.sort,
14
+ ...(searchOptions.paging && { cursorPaging: searchOptions.paging }),
15
+ };
16
+ const options = {
17
+ fields: searchOptions.fields || [],
18
+ };
19
+ const result = await searchProductsSDK(searchParams, options);
20
+ // Fetch missing variants for all products in one batch request
21
+ if (result.products) {
22
+ result.products = await fetchMissingVariants(result.products);
23
+ }
24
+ return result;
25
+ };
26
+ // Helper to build search options with supported filters
27
+ const buildSearchOptions = (filters, selectedCategory, sortBy) => {
28
+ const searchOptions = {
29
+ search: {},
30
+ fields: [
31
+ 'DESCRIPTION',
32
+ 'DIRECT_CATEGORIES_INFO',
33
+ 'BREADCRUMBS_INFO',
34
+ 'INFO_SECTION',
35
+ 'MEDIA_ITEMS_INFO',
36
+ 'PLAIN_DESCRIPTION',
37
+ 'THUMBNAIL',
38
+ 'URL',
39
+ 'VARIANT_OPTION_CHOICE_NAMES',
40
+ 'WEIGHT_MEASUREMENT_UNIT_INFO',
41
+ ],
42
+ };
43
+ // Build filter conditions array
44
+ const filterConditions = [];
45
+ // Add category filter if selected
46
+ if (selectedCategory) {
47
+ filterConditions.push({
48
+ 'allCategoriesInfo.categories': {
49
+ $matchItems: [
50
+ {
51
+ id: {
52
+ $in: [selectedCategory],
53
+ },
54
+ },
55
+ ],
56
+ },
57
+ });
58
+ }
59
+ // Add price range filter if provided
60
+ if (filters?.priceRange) {
61
+ const { min, max } = filters.priceRange;
62
+ if (min > 0) {
63
+ filterConditions.push({
64
+ 'actualPriceRange.minValue.amount': { $gte: min.toString() },
65
+ });
66
+ }
67
+ if (max > 0 && max < 999999) {
68
+ filterConditions.push({
69
+ 'actualPriceRange.maxValue.amount': { $lte: max.toString() },
70
+ });
71
+ }
72
+ }
73
+ // Add product options filter if provided
74
+ if (filters?.selectedOptions &&
75
+ Object.keys(filters.selectedOptions).length > 0) {
76
+ for (const [optionId, choiceIds] of Object.entries(filters.selectedOptions)) {
77
+ if (choiceIds.length > 0) {
78
+ // Handle inventory filter separately
79
+ if (optionId === 'inventory-filter') {
80
+ filterConditions.push({
81
+ 'inventory.availabilityStatus': {
82
+ $in: choiceIds,
83
+ },
84
+ });
85
+ }
86
+ else {
87
+ // Regular product options filter
88
+ filterConditions.push({
89
+ 'options.choicesSettings.choices.choiceId': {
90
+ $hasSome: choiceIds,
91
+ },
92
+ });
93
+ }
94
+ }
95
+ }
96
+ }
97
+ // Apply filters
98
+ if (filterConditions.length > 0) {
99
+ if (filterConditions.length === 1) {
100
+ // Single condition - no need for $and wrapper
101
+ searchOptions.search.filter = filterConditions[0];
102
+ }
103
+ else {
104
+ // Multiple conditions - use $and
105
+ searchOptions.search.filter = {
106
+ $and: filterConditions,
107
+ };
108
+ }
109
+ }
110
+ // Add sort if provided
111
+ if (sortBy) {
112
+ switch (sortBy) {
113
+ case SortType.NAME_ASC:
114
+ searchOptions.search.sort = [
115
+ { fieldName: 'name', order: SortDirection.ASC },
116
+ ];
117
+ break;
118
+ case SortType.NAME_DESC:
119
+ searchOptions.search.sort = [
120
+ { fieldName: 'name', order: SortDirection.DESC },
121
+ ];
122
+ break;
123
+ case SortType.PRICE_ASC:
124
+ searchOptions.search.sort = [
125
+ {
126
+ fieldName: 'actualPriceRange.minValue.amount',
127
+ order: SortDirection.ASC,
128
+ },
129
+ ];
130
+ break;
131
+ case SortType.PRICE_DESC:
132
+ searchOptions.search.sort = [
133
+ {
134
+ fieldName: 'actualPriceRange.minValue.amount',
135
+ order: SortDirection.DESC,
136
+ },
137
+ ];
138
+ break;
139
+ case SortType.RECOMMENDED:
140
+ searchOptions.search.sort = [
141
+ {
142
+ fieldName: 'allCategoriesInfo.categories.index',
143
+ selectItemsBy: [
144
+ {
145
+ 'allCategoriesInfo.categories.id': selectedCategory,
146
+ },
147
+ ],
148
+ },
149
+ ];
150
+ break;
151
+ }
152
+ }
153
+ return searchOptions;
154
+ };
155
+ export const CollectionServiceDefinition = defineService('collection');
156
+ export const CollectionService = implementService.withConfig()(CollectionServiceDefinition, ({ getService, config }) => {
157
+ const signalsService = getService(SignalsServiceDefinition);
158
+ const collectionFilters = getService(FilterServiceDefinition);
159
+ const categoryService = getService(CategoryServiceDefinition);
160
+ const sortService = getService(SortServiceDefinition);
161
+ const hasMoreProducts = signalsService.signal((config.initialHasMore ?? true));
162
+ let nextCursor = config.initialCursor;
163
+ const initialProducts = config.initialProducts || [];
164
+ // Signal declarations
165
+ const productsList = signalsService.signal(initialProducts);
166
+ const isLoading = signalsService.signal(false);
167
+ const error = signalsService.signal(null);
168
+ const totalProducts = signalsService.signal(initialProducts.length);
169
+ const hasProducts = signalsService.signal((initialProducts.length > 0));
170
+ const pageSize = config.pageSize || 12;
171
+ let allProducts = initialProducts;
172
+ // Debouncing mechanism to prevent multiple simultaneous refreshes
173
+ let refreshTimeout = null;
174
+ let isRefreshing = false;
175
+ let isInitializingCatalogData = true;
176
+ const loadMore = async () => {
177
+ // Don't load more if there are no more products available
178
+ if (!hasMoreProducts.get()) {
179
+ return;
180
+ }
181
+ try {
182
+ isLoading.set(true);
183
+ error.set(null);
184
+ // For loadMore, use no filters or sorting to work with cursor pagination
185
+ const searchOptions = buildSearchOptions(undefined, undefined, undefined);
186
+ // Add pagination
187
+ searchOptions.paging = { limit: pageSize };
188
+ if (nextCursor) {
189
+ searchOptions.paging.cursor = nextCursor;
190
+ }
191
+ const currentProducts = productsList.get();
192
+ const productResults = await searchProducts(searchOptions);
193
+ // Update cursor for next pagination
194
+ nextCursor = productResults.pagingMetadata?.cursors?.next || undefined;
195
+ // Check if there are more products to load
196
+ const hasMore = Boolean(nextCursor &&
197
+ productResults.products &&
198
+ productResults.products.length === pageSize);
199
+ hasMoreProducts.set(hasMore);
200
+ // Update allProducts with the new data
201
+ allProducts = [...allProducts, ...(productResults.products || [])];
202
+ // Add new products to the list
203
+ const additionalProducts = productResults.products || [];
204
+ productsList.set([...currentProducts, ...additionalProducts]);
205
+ totalProducts.set(currentProducts.length + additionalProducts.length);
206
+ hasProducts.set(currentProducts.length + additionalProducts.length > 0);
207
+ }
208
+ catch (err) {
209
+ error.set(err instanceof Error ? err.message : 'Failed to load more products');
210
+ }
211
+ finally {
212
+ isLoading.set(false);
213
+ }
214
+ };
215
+ const refresh = async (setTotalProducts = true) => {
216
+ if (isRefreshing)
217
+ return;
218
+ try {
219
+ isRefreshing = true;
220
+ isLoading.set(true);
221
+ error.set(null);
222
+ const filters = collectionFilters.currentFilters.get();
223
+ const selectedCategory = categoryService.selectedCategory.get();
224
+ const sortBy = sortService.currentSort.get();
225
+ // Use regular search for all sorting options including recommended
226
+ const searchOptions = buildSearchOptions(filters, selectedCategory, sortBy);
227
+ // Add pagination
228
+ searchOptions.paging = { limit: pageSize };
229
+ const productResults = await searchProducts(searchOptions);
230
+ const isPriceSort = sortBy === SortType.PRICE_ASC || sortBy === SortType.PRICE_DESC;
231
+ if (isPriceSort) {
232
+ productResults.products = productResults.products?.sort((a, b) => {
233
+ const aPrice = Number(a.actualPriceRange?.minValue?.amount) || 0;
234
+ const bPrice = Number(b.actualPriceRange?.minValue?.amount) || 0;
235
+ return sortBy === SortType.PRICE_ASC
236
+ ? aPrice - bPrice
237
+ : bPrice - aPrice;
238
+ });
239
+ }
240
+ // Reset pagination state
241
+ nextCursor = productResults.pagingMetadata?.cursors?.next || undefined;
242
+ const hasMore = Boolean(productResults.pagingMetadata?.cursors?.next &&
243
+ productResults.products &&
244
+ productResults.products.length === pageSize);
245
+ hasMoreProducts.set(hasMore);
246
+ // Update allProducts with the new data
247
+ allProducts = productResults.products || [];
248
+ // All filtering is handled server-side
249
+ productsList.set(allProducts);
250
+ if (setTotalProducts) {
251
+ totalProducts.set(allProducts.length);
252
+ }
253
+ hasProducts.set(allProducts.length > 0);
254
+ }
255
+ catch (err) {
256
+ error.set(err instanceof Error ? err.message : 'Failed to refresh products');
257
+ }
258
+ finally {
259
+ isLoading.set(false);
260
+ isRefreshing = false;
261
+ }
262
+ };
263
+ // Debounced refresh function
264
+ const debouncedRefresh = async (setTotalProducts = true) => {
265
+ if (refreshTimeout) {
266
+ clearTimeout(refreshTimeout);
267
+ }
268
+ return new Promise(resolve => {
269
+ refreshTimeout = setTimeout(async () => {
270
+ await refresh(setTotalProducts);
271
+ resolve();
272
+ }, 50); // 50ms debounce delay
273
+ });
274
+ };
275
+ // Refresh with server-side filtering when any filters change
276
+ collectionFilters.currentFilters.subscribe(() => {
277
+ // Skip refresh during catalog data initialization to prevent double API calls
278
+ if (isInitializingCatalogData) {
279
+ return;
280
+ }
281
+ // All filtering (categories, price, options) is now handled server-side
282
+ debouncedRefresh(false);
283
+ });
284
+ // Initialize catalog data when the service starts
285
+ const initializeCatalogData = async () => {
286
+ const selectedCategory = categoryService.selectedCategory.get();
287
+ await collectionFilters.loadCatalogPriceRange(selectedCategory || undefined);
288
+ await collectionFilters.loadCatalogOptions(selectedCategory || undefined);
289
+ // Reset flag to allow filter changes to trigger refreshes
290
+ isInitializingCatalogData = false;
291
+ };
292
+ // Load catalog data on initialization
293
+ void initializeCatalogData();
294
+ sortService.currentSort.subscribe(() => {
295
+ debouncedRefresh(false);
296
+ });
297
+ categoryService.selectedCategory.subscribe(() => {
298
+ debouncedRefresh(true).then(() => {
299
+ initializeCatalogData();
300
+ });
301
+ });
302
+ return {
303
+ products: productsList,
304
+ isLoading,
305
+ error,
306
+ totalProducts,
307
+ hasProducts,
308
+ hasMoreProducts,
309
+ loadMore,
310
+ refresh: debouncedRefresh,
311
+ };
312
+ });
313
+ // Helper function to parse URL parameters
314
+ function parseURLParams(searchParams, products = []) {
315
+ const defaultFilters = {
316
+ priceRange: { min: 0, max: 0 },
317
+ selectedOptions: {},
318
+ };
319
+ if (!searchParams) {
320
+ return {
321
+ initialSort: SortType.NEWEST,
322
+ initialFilters: defaultFilters,
323
+ };
324
+ }
325
+ const urlParams = URLParamsUtils.parseSearchParams(searchParams);
326
+ // Parse sort parameter
327
+ const sortMap = {
328
+ name_asc: SortType.NAME_ASC,
329
+ name_desc: SortType.NAME_DESC,
330
+ price_asc: SortType.PRICE_ASC,
331
+ price_desc: SortType.PRICE_DESC,
332
+ recommended: SortType.RECOMMENDED,
333
+ };
334
+ const initialSort = sortMap[urlParams['sort']] || SortType.NEWEST;
335
+ // Check if there are any filter parameters (excluding sort)
336
+ const filterParams = Object.keys(urlParams).filter(key => key !== 'sort');
337
+ if (filterParams.length === 0 || products.length === 0) {
338
+ return { initialSort, initialFilters: defaultFilters };
339
+ }
340
+ // Calculate available price range from products
341
+ const priceRange = calculatePriceRange(products);
342
+ const initialFilters = {
343
+ priceRange,
344
+ selectedOptions: {},
345
+ };
346
+ // Apply price filters from URL
347
+ if (urlParams['minPrice']) {
348
+ const min = parseFloat(urlParams['minPrice']);
349
+ if (!isNaN(min))
350
+ initialFilters.priceRange.min = min;
351
+ }
352
+ if (urlParams['maxPrice']) {
353
+ const max = parseFloat(urlParams['maxPrice']);
354
+ if (!isNaN(max))
355
+ initialFilters.priceRange.max = max;
356
+ }
357
+ // Parse option filters
358
+ const optionsMap = buildOptionsMap(products);
359
+ parseOptionFilters(urlParams, optionsMap, initialFilters);
360
+ // Parse inventory filter from 'availability' URL parameter
361
+ if (urlParams['availability']) {
362
+ const availabilityValues = Array.isArray(urlParams['availability'])
363
+ ? urlParams['availability']
364
+ : [urlParams['availability']];
365
+ const inventoryStatusValues = availabilityValues.map(value => value.replace(/\s+/g, '_').toUpperCase());
366
+ initialFilters.selectedOptions['inventory-filter'] = inventoryStatusValues;
367
+ }
368
+ return { initialSort, initialFilters };
369
+ }
370
+ // Helper function to calculate price range from products
371
+ function calculatePriceRange(products) {
372
+ if (products.length === 0) {
373
+ return { min: 0, max: 1000 };
374
+ }
375
+ let minPrice = Infinity;
376
+ let maxPrice = 0;
377
+ products.forEach(product => {
378
+ const min = parseFloat(product.actualPriceRange?.minValue?.amount || '0');
379
+ const max = parseFloat(product.actualPriceRange?.maxValue?.amount || '0');
380
+ if (min > 0)
381
+ minPrice = Math.min(minPrice, min);
382
+ if (max > 0)
383
+ maxPrice = Math.max(maxPrice, max);
384
+ });
385
+ // If no valid prices found, return default range
386
+ if (minPrice === Infinity) {
387
+ minPrice = 0;
388
+ maxPrice = 1000;
389
+ }
390
+ return { min: minPrice, max: maxPrice };
391
+ }
392
+ // Helper function to build options map from products
393
+ function buildOptionsMap(products) {
394
+ const optionsMap = new Map();
395
+ products.forEach(product => {
396
+ product.options?.forEach(option => {
397
+ if (!option._id || !option.name)
398
+ return;
399
+ if (!optionsMap.has(option.name)) {
400
+ optionsMap.set(option.name, { id: option._id, choices: [] });
401
+ }
402
+ const optionData = optionsMap.get(option.name);
403
+ option.choicesSettings?.choices?.forEach(choice => {
404
+ if (choice.choiceId &&
405
+ choice.name &&
406
+ !optionData.choices.find(c => c.id === choice.choiceId)) {
407
+ optionData.choices.push({ id: choice.choiceId, name: choice.name });
408
+ }
409
+ });
410
+ });
411
+ });
412
+ return optionsMap;
413
+ }
414
+ // Helper function to parse option filters from URL parameters
415
+ function parseOptionFilters(urlParams, optionsMap, filters) {
416
+ Object.entries(urlParams).forEach(([key, value]) => {
417
+ if (['sort', 'minPrice', 'maxPrice'].includes(key))
418
+ return;
419
+ const option = optionsMap.get(key);
420
+ if (option) {
421
+ const values = Array.isArray(value) ? value : [value];
422
+ const matchingChoices = option.choices.filter(choice => values.includes(choice.name));
423
+ if (matchingChoices.length > 0) {
424
+ filters.selectedOptions[option.id] = matchingChoices.map(c => c.id);
425
+ }
426
+ }
427
+ });
428
+ }
429
+ export async function loadCollectionServiceConfig(categoryId, searchParams, preloadedCategories) {
430
+ try {
431
+ // Use pre-loaded categories if provided, otherwise load them
432
+ let categories;
433
+ if (preloadedCategories) {
434
+ categories = preloadedCategories;
435
+ }
436
+ else {
437
+ const { loadCategoriesConfig } = await import('./category-service');
438
+ const categoriesConfig = await loadCategoriesConfig();
439
+ categories = categoriesConfig.categories;
440
+ }
441
+ // Build search options with category filter
442
+ const searchOptions = buildSearchOptions(undefined, categoryId, undefined);
443
+ const pageSize = 12;
444
+ searchOptions.paging = { limit: pageSize };
445
+ const productResults = await searchProducts(searchOptions);
446
+ // Parse URL parameters for initial state
447
+ const { initialSort, initialFilters } = parseURLParams(searchParams, productResults.products || []);
448
+ return {
449
+ initialProducts: productResults.products || [],
450
+ pageSize,
451
+ initialCursor: productResults.pagingMetadata?.cursors?.next || undefined,
452
+ initialHasMore: Boolean(productResults.pagingMetadata?.cursors?.next &&
453
+ productResults.products &&
454
+ productResults.products.length === pageSize),
455
+ initialSort,
456
+ initialFilters,
457
+ categories,
458
+ };
459
+ }
460
+ catch (error) {
461
+ console.warn('Failed to load initial products:', error);
462
+ const { initialSort, initialFilters } = parseURLParams(searchParams);
463
+ return {
464
+ initialProducts: [],
465
+ pageSize: 12,
466
+ initialHasMore: false,
467
+ initialSort,
468
+ initialFilters,
469
+ categories: [],
470
+ };
471
+ }
472
+ }
473
+ // Add function to fetch missing variants for all products in one request
474
+ const fetchMissingVariants = async (products) => {
475
+ // Find products that need variants (both single and multi-variant products)
476
+ const productsNeedingVariants = products.filter(product => !product.variantsInfo?.variants &&
477
+ product.variantSummary?.variantCount &&
478
+ product.variantSummary.variantCount > 0);
479
+ if (productsNeedingVariants.length === 0) {
480
+ return products;
481
+ }
482
+ try {
483
+ const productIds = productsNeedingVariants
484
+ .map(p => p._id)
485
+ .filter(Boolean);
486
+ if (productIds.length === 0) {
487
+ return products;
488
+ }
489
+ const items = [];
490
+ const res = await queryVariants({})
491
+ .in('productData.productId', productIds)
492
+ .limit(100)
493
+ .find();
494
+ items.push(...res.items);
495
+ let nextRes = res;
496
+ while (nextRes.hasNext()) {
497
+ nextRes = await nextRes.next();
498
+ items.push(...nextRes.items);
499
+ }
500
+ const variantsByProductId = new Map();
501
+ items.forEach(item => {
502
+ const productId = item.productData?.productId;
503
+ if (productId) {
504
+ if (!variantsByProductId.has(productId)) {
505
+ variantsByProductId.set(productId, []);
506
+ }
507
+ variantsByProductId.get(productId).push({
508
+ ...item,
509
+ choices: item.optionChoices,
510
+ });
511
+ }
512
+ });
513
+ // Update products with their variants
514
+ return products.map(product => {
515
+ const variants = variantsByProductId.get(product._id || '');
516
+ if (variants && variants.length > 0) {
517
+ return {
518
+ ...product,
519
+ variantsInfo: {
520
+ ...product.variantsInfo,
521
+ variants,
522
+ },
523
+ };
524
+ }
525
+ return product;
526
+ });
527
+ }
528
+ catch (error) {
529
+ console.error('Failed to fetch missing variants:', error);
530
+ return products;
531
+ }
532
+ };
@@ -0,0 +1,56 @@
1
+ import type { Signal } from '../../Signal';
2
+ export interface ProductOption {
3
+ id: string;
4
+ name: string;
5
+ choices: {
6
+ id: string;
7
+ name: string;
8
+ colorCode?: string;
9
+ }[];
10
+ optionRenderType?: string;
11
+ }
12
+ export interface PriceRange {
13
+ min: number;
14
+ max: number;
15
+ }
16
+ export interface AvailableOptions {
17
+ productOptions: ProductOption[];
18
+ priceRange: PriceRange;
19
+ }
20
+ export interface Filter {
21
+ priceRange: {
22
+ min: number;
23
+ max: number;
24
+ };
25
+ selectedOptions: {
26
+ [optionId: string]: string[];
27
+ };
28
+ }
29
+ export interface FilterServiceAPI {
30
+ currentFilters: Signal<Filter>;
31
+ applyFilters: (filters: Filter) => Promise<void>;
32
+ clearFilters: () => Promise<void>;
33
+ availableOptions: Signal<{
34
+ productOptions: ProductOption[];
35
+ priceRange: {
36
+ min: number;
37
+ max: number;
38
+ };
39
+ }>;
40
+ loadCatalogPriceRange: (categoryId?: string) => Promise<void>;
41
+ loadCatalogOptions: (categoryId?: string) => Promise<void>;
42
+ isFullyLoaded: Signal<boolean>;
43
+ }
44
+ export declare const FilterServiceDefinition: string & {
45
+ __api: FilterServiceAPI;
46
+ __config: {};
47
+ isServiceDefinition?: boolean;
48
+ } & FilterServiceAPI;
49
+ export declare const defaultFilter: Filter;
50
+ export declare const FilterService: import("@wix/services-definitions").ServiceFactory<string & {
51
+ __api: FilterServiceAPI;
52
+ __config: {};
53
+ isServiceDefinition?: boolean;
54
+ } & FilterServiceAPI, {
55
+ initialFilters?: Filter;
56
+ }, import("@wix/services-definitions").ThreadMode.MAIN>;