@wix/headless-stores 0.0.8 → 0.0.10

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 (105) hide show
  1. package/cjs/dist/react/BuyNow.d.ts +1 -0
  2. package/cjs/dist/react/BuyNow.js +1 -0
  3. package/cjs/dist/react/Category.d.ts +17 -0
  4. package/cjs/dist/react/Category.js +37 -0
  5. package/cjs/dist/react/Collection.d.ts +141 -0
  6. package/cjs/dist/react/Collection.js +198 -0
  7. package/cjs/dist/react/FilteredCollection.d.ts +65 -0
  8. package/cjs/dist/react/FilteredCollection.js +117 -0
  9. package/cjs/dist/react/PayNow.d.ts +1 -0
  10. package/cjs/dist/react/PayNow.js +1 -0
  11. package/cjs/dist/react/Product.d.ts +70 -0
  12. package/cjs/dist/react/Product.js +56 -0
  13. package/cjs/dist/react/ProductMediaGallery.d.ts +128 -0
  14. package/cjs/dist/react/ProductMediaGallery.js +100 -0
  15. package/cjs/dist/react/ProductModifiers.d.ts +156 -0
  16. package/cjs/dist/react/ProductModifiers.js +159 -0
  17. package/cjs/dist/react/ProductVariantSelector.d.ts +169 -0
  18. package/cjs/dist/react/ProductVariantSelector.js +166 -0
  19. package/cjs/dist/react/RelatedProducts.d.ts +60 -0
  20. package/cjs/dist/react/RelatedProducts.js +68 -0
  21. package/cjs/dist/react/SocialSharing.d.ts +119 -0
  22. package/cjs/dist/react/SocialSharing.js +80 -0
  23. package/cjs/dist/react/Sort.d.ts +17 -0
  24. package/cjs/dist/react/Sort.js +41 -0
  25. package/cjs/dist/react/index.d.ts +10 -0
  26. package/cjs/dist/react/index.js +33 -0
  27. package/cjs/dist/services/catalog-options-service.d.ts +30 -0
  28. package/cjs/dist/services/catalog-options-service.js +162 -0
  29. package/cjs/dist/services/catalog-price-range-service.d.ts +23 -0
  30. package/cjs/dist/services/catalog-price-range-service.js +95 -0
  31. package/cjs/dist/services/category-service.d.ts +25 -0
  32. package/cjs/dist/services/category-service.js +67 -0
  33. package/cjs/dist/services/collection-service.d.ts +37 -0
  34. package/cjs/dist/services/collection-service.js +454 -0
  35. package/cjs/dist/services/filter-service.d.ts +56 -0
  36. package/cjs/dist/services/filter-service.js +155 -0
  37. package/cjs/dist/services/product-media-gallery-service.d.ts +25 -0
  38. package/cjs/dist/services/product-media-gallery-service.js +105 -0
  39. package/cjs/dist/services/product-modifiers-service.d.ts +36 -0
  40. package/cjs/dist/services/product-modifiers-service.js +104 -0
  41. package/cjs/dist/services/product-service.d.ts +27 -0
  42. package/cjs/dist/services/product-service.js +51 -0
  43. package/cjs/dist/services/related-products-service.d.ts +25 -0
  44. package/cjs/dist/services/related-products-service.js +54 -0
  45. package/cjs/dist/services/selected-variant-service.d.ts +51 -0
  46. package/cjs/dist/services/selected-variant-service.js +396 -0
  47. package/cjs/dist/services/social-sharing-service.d.ts +41 -0
  48. package/cjs/dist/services/social-sharing-service.js +157 -0
  49. package/cjs/dist/services/sort-service.d.ts +19 -0
  50. package/cjs/dist/services/sort-service.js +37 -0
  51. package/cjs/dist/utils/url-params.d.ts +5 -0
  52. package/cjs/dist/utils/url-params.js +50 -0
  53. package/dist/react/BuyNow.d.ts +1 -0
  54. package/dist/react/BuyNow.js +1 -0
  55. package/dist/react/Category.d.ts +17 -0
  56. package/dist/react/Category.js +31 -0
  57. package/dist/react/Collection.d.ts +141 -0
  58. package/dist/react/Collection.js +190 -0
  59. package/dist/react/FilteredCollection.d.ts +65 -0
  60. package/dist/react/FilteredCollection.js +107 -0
  61. package/dist/react/PayNow.d.ts +1 -0
  62. package/dist/react/PayNow.js +1 -0
  63. package/dist/react/Product.d.ts +70 -0
  64. package/dist/react/Product.js +50 -0
  65. package/dist/react/ProductMediaGallery.d.ts +128 -0
  66. package/dist/react/ProductMediaGallery.js +92 -0
  67. package/dist/react/ProductModifiers.d.ts +156 -0
  68. package/dist/react/ProductModifiers.js +151 -0
  69. package/dist/react/ProductVariantSelector.d.ts +169 -0
  70. package/dist/react/ProductVariantSelector.js +157 -0
  71. package/dist/react/RelatedProducts.d.ts +60 -0
  72. package/dist/react/RelatedProducts.js +60 -0
  73. package/dist/react/SocialSharing.d.ts +119 -0
  74. package/dist/react/SocialSharing.js +71 -0
  75. package/dist/react/Sort.d.ts +17 -0
  76. package/dist/react/Sort.js +36 -0
  77. package/dist/react/index.d.ts +10 -0
  78. package/dist/react/index.js +10 -0
  79. package/dist/services/catalog-options-service.d.ts +30 -0
  80. package/dist/services/catalog-options-service.js +158 -0
  81. package/dist/services/catalog-price-range-service.d.ts +23 -0
  82. package/dist/services/catalog-price-range-service.js +91 -0
  83. package/dist/services/category-service.d.ts +25 -0
  84. package/dist/services/category-service.js +63 -0
  85. package/dist/services/collection-service.d.ts +37 -0
  86. package/dist/services/collection-service.js +417 -0
  87. package/dist/services/filter-service.d.ts +56 -0
  88. package/dist/services/filter-service.js +152 -0
  89. package/dist/services/product-media-gallery-service.d.ts +25 -0
  90. package/dist/services/product-media-gallery-service.js +101 -0
  91. package/dist/services/product-modifiers-service.d.ts +36 -0
  92. package/dist/services/product-modifiers-service.js +100 -0
  93. package/dist/services/product-service.d.ts +27 -0
  94. package/dist/services/product-service.js +47 -0
  95. package/dist/services/related-products-service.d.ts +25 -0
  96. package/dist/services/related-products-service.js +50 -0
  97. package/dist/services/selected-variant-service.d.ts +51 -0
  98. package/dist/services/selected-variant-service.js +392 -0
  99. package/dist/services/social-sharing-service.d.ts +41 -0
  100. package/dist/services/social-sharing-service.js +153 -0
  101. package/dist/services/sort-service.d.ts +19 -0
  102. package/dist/services/sort-service.js +34 -0
  103. package/dist/utils/url-params.d.ts +5 -0
  104. package/dist/utils/url-params.js +46 -0
  105. package/package.json +2 -1
@@ -0,0 +1,454 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CollectionService = exports.CollectionServiceDefinition = void 0;
37
+ exports.loadCollectionServiceConfig = loadCollectionServiceConfig;
38
+ const services_definitions_1 = require("@wix/services-definitions");
39
+ const signals_1 = require("@wix/services-definitions/core-services/signals");
40
+ const stores_1 = require("@wix/stores");
41
+ const filter_service_1 = require("./filter-service");
42
+ const category_service_1 = require("./category-service");
43
+ const sort_service_1 = require("./sort-service");
44
+ const url_params_1 = require("../utils/url-params");
45
+ const searchProducts = async (searchOptions) => {
46
+ const searchParams = {
47
+ filter: searchOptions.search?.filter,
48
+ sort: searchOptions.search?.sort,
49
+ ...(searchOptions.paging && { cursorPaging: searchOptions.paging }),
50
+ };
51
+ const options = {
52
+ fields: searchOptions.fields || [],
53
+ };
54
+ // @ts-ignore
55
+ return await stores_1.productsV3.searchProducts(searchParams, options);
56
+ };
57
+ // Helper to build search options with supported filters
58
+ const buildSearchOptions = (filters, selectedCategory, sortBy,
59
+ // @ts-ignore
60
+ categories) => {
61
+ const searchOptions = {
62
+ search: {},
63
+ fields: [
64
+ "PLAIN_DESCRIPTION",
65
+ "MEDIA_ITEMS_INFO",
66
+ "CURRENCY",
67
+ "THUMBNAIL",
68
+ "URL",
69
+ "ALL_CATEGORIES_INFO",
70
+ ],
71
+ };
72
+ // Build filter conditions array
73
+ const filterConditions = [];
74
+ // Add category filter if selected
75
+ if (selectedCategory) {
76
+ filterConditions.push({
77
+ "allCategoriesInfo.categories": {
78
+ $matchItems: [
79
+ {
80
+ id: {
81
+ $in: [selectedCategory],
82
+ },
83
+ },
84
+ ],
85
+ },
86
+ });
87
+ }
88
+ // Add price range filter if provided
89
+ if (filters?.priceRange) {
90
+ const { min, max } = filters.priceRange;
91
+ if (min > 0) {
92
+ filterConditions.push({
93
+ "actualPriceRange.minValue.amount": { $gte: min.toString() },
94
+ });
95
+ }
96
+ if (max > 0 && max < 999999) {
97
+ filterConditions.push({
98
+ "actualPriceRange.maxValue.amount": { $lte: max.toString() },
99
+ });
100
+ }
101
+ }
102
+ // Add product options filter if provided
103
+ if (filters?.selectedOptions &&
104
+ Object.keys(filters.selectedOptions).length > 0) {
105
+ for (const [optionId, choiceIds] of Object.entries(filters.selectedOptions)) {
106
+ if (choiceIds.length > 0) {
107
+ // Handle inventory filter separately
108
+ if (optionId === "inventory-filter") {
109
+ filterConditions.push({
110
+ "inventory.availabilityStatus": {
111
+ $in: choiceIds,
112
+ },
113
+ });
114
+ }
115
+ else {
116
+ // Regular product options filter
117
+ filterConditions.push({
118
+ "options.choicesSettings.choices.choiceId": {
119
+ $hasSome: choiceIds,
120
+ },
121
+ });
122
+ }
123
+ }
124
+ }
125
+ }
126
+ // Apply filters
127
+ if (filterConditions.length > 0) {
128
+ if (filterConditions.length === 1) {
129
+ // Single condition - no need for $and wrapper
130
+ searchOptions.search.filter = filterConditions[0];
131
+ }
132
+ else {
133
+ // Multiple conditions - use $and
134
+ searchOptions.search.filter = {
135
+ $and: filterConditions,
136
+ };
137
+ }
138
+ }
139
+ // Add sort if provided
140
+ if (sortBy) {
141
+ switch (sortBy) {
142
+ case "name-asc":
143
+ searchOptions.search.sort = [{ fieldName: "name", order: "ASC" }];
144
+ break;
145
+ case "name-desc":
146
+ searchOptions.search.sort = [{ fieldName: "name", order: "DESC" }];
147
+ break;
148
+ case "price-asc":
149
+ searchOptions.search.sort = [
150
+ { fieldName: "actualPriceRange.minValue.amount", order: "ASC" },
151
+ ];
152
+ break;
153
+ case "price-desc":
154
+ searchOptions.search.sort = [
155
+ { fieldName: "actualPriceRange.minValue.amount", order: "DESC" },
156
+ ];
157
+ break;
158
+ }
159
+ }
160
+ return searchOptions;
161
+ };
162
+ exports.CollectionServiceDefinition = (0, services_definitions_1.defineService)("collection");
163
+ exports.CollectionService = services_definitions_1.implementService.withConfig()(exports.CollectionServiceDefinition, ({ getService, config }) => {
164
+ const signalsService = getService(signals_1.SignalsServiceDefinition);
165
+ const collectionFilters = getService(filter_service_1.FilterServiceDefinition);
166
+ const categoryService = getService(category_service_1.CategoryServiceDefinition);
167
+ const sortService = getService(sort_service_1.SortServiceDefinition);
168
+ const hasMoreProducts = signalsService.signal((config.initialHasMore ?? true));
169
+ let nextCursor = config.initialCursor;
170
+ const initialProducts = config.initialProducts || [];
171
+ // Signal declarations
172
+ const productsList = signalsService.signal(initialProducts);
173
+ const isLoading = signalsService.signal(false);
174
+ const error = signalsService.signal(null);
175
+ const totalProducts = signalsService.signal(initialProducts.length);
176
+ const hasProducts = signalsService.signal((initialProducts.length > 0));
177
+ const pageSize = config.pageSize || 12;
178
+ let allProducts = initialProducts;
179
+ // Debouncing mechanism to prevent multiple simultaneous refreshes
180
+ let refreshTimeout = null;
181
+ let isRefreshing = false;
182
+ let isInitializingCatalogData = true;
183
+ const loadMore = async () => {
184
+ // Don't load more if there are no more products available
185
+ if (!hasMoreProducts.get()) {
186
+ return;
187
+ }
188
+ try {
189
+ isLoading.set(true);
190
+ error.set(null);
191
+ // For loadMore, use no filters or sorting to work with cursor pagination
192
+ const searchOptions = buildSearchOptions(undefined, undefined, undefined, undefined);
193
+ // Add pagination
194
+ searchOptions.paging = { limit: pageSize };
195
+ if (nextCursor) {
196
+ searchOptions.paging.cursor = nextCursor;
197
+ }
198
+ const currentProducts = productsList.get();
199
+ const productResults = await searchProducts(searchOptions);
200
+ // Update cursor for next pagination
201
+ nextCursor = productResults.pagingMetadata?.cursors?.next || undefined;
202
+ // Check if there are more products to load
203
+ const hasMore = Boolean(nextCursor &&
204
+ productResults.products &&
205
+ productResults.products.length === pageSize);
206
+ hasMoreProducts.set(hasMore);
207
+ // Update allProducts with the new data
208
+ allProducts = [...allProducts, ...(productResults.products || [])];
209
+ // Add new products to the list
210
+ const additionalProducts = productResults.products || [];
211
+ productsList.set([...currentProducts, ...additionalProducts]);
212
+ totalProducts.set(currentProducts.length + additionalProducts.length);
213
+ hasProducts.set(currentProducts.length + additionalProducts.length > 0);
214
+ }
215
+ catch (err) {
216
+ error.set(err instanceof Error ? err.message : "Failed to load more products");
217
+ }
218
+ finally {
219
+ isLoading.set(false);
220
+ }
221
+ };
222
+ const refresh = async (setTotalProducts = true) => {
223
+ if (isRefreshing)
224
+ return;
225
+ try {
226
+ isRefreshing = true;
227
+ isLoading.set(true);
228
+ error.set(null);
229
+ const filters = collectionFilters.currentFilters.get();
230
+ const selectedCategory = categoryService.selectedCategory.get();
231
+ const sortBy = sortService.currentSort.get();
232
+ const categories = config.categories || categoryService.categories.get();
233
+ const searchOptions = buildSearchOptions(filters, selectedCategory, sortBy, categories);
234
+ // Add pagination
235
+ searchOptions.paging = { limit: pageSize };
236
+ const productResults = await searchProducts(searchOptions);
237
+ // Reset pagination state
238
+ nextCursor = productResults.pagingMetadata?.cursors?.next || undefined;
239
+ const hasMore = Boolean(productResults.pagingMetadata?.cursors?.next &&
240
+ productResults.products &&
241
+ productResults.products.length === pageSize);
242
+ hasMoreProducts.set(hasMore);
243
+ // Update allProducts with the new data
244
+ allProducts = productResults.products || [];
245
+ // All filtering is handled server-side
246
+ productsList.set(allProducts);
247
+ if (setTotalProducts) {
248
+ totalProducts.set(allProducts.length);
249
+ }
250
+ hasProducts.set(allProducts.length > 0);
251
+ }
252
+ catch (err) {
253
+ error.set(err instanceof Error ? err.message : "Failed to refresh products");
254
+ }
255
+ finally {
256
+ isLoading.set(false);
257
+ isRefreshing = false;
258
+ }
259
+ };
260
+ // Debounced refresh function
261
+ const debouncedRefresh = async (setTotalProducts = true) => {
262
+ if (refreshTimeout) {
263
+ clearTimeout(refreshTimeout);
264
+ }
265
+ return new Promise((resolve) => {
266
+ refreshTimeout = setTimeout(async () => {
267
+ await refresh(setTotalProducts);
268
+ resolve();
269
+ }, 50); // 50ms debounce delay
270
+ });
271
+ };
272
+ // Refresh with server-side filtering when any filters change
273
+ collectionFilters.currentFilters.subscribe(() => {
274
+ // Skip refresh during catalog data initialization to prevent double API calls
275
+ if (isInitializingCatalogData) {
276
+ return;
277
+ }
278
+ // All filtering (categories, price, options) is now handled server-side
279
+ debouncedRefresh(false);
280
+ });
281
+ // Initialize catalog data when the service starts
282
+ const initializeCatalogData = async () => {
283
+ const selectedCategory = categoryService.selectedCategory.get();
284
+ await collectionFilters.loadCatalogPriceRange(selectedCategory || undefined);
285
+ await collectionFilters.loadCatalogOptions(selectedCategory || undefined);
286
+ // Reset flag to allow filter changes to trigger refreshes
287
+ isInitializingCatalogData = false;
288
+ };
289
+ // Load catalog data on initialization
290
+ void initializeCatalogData();
291
+ sortService.currentSort.subscribe(() => {
292
+ debouncedRefresh(false);
293
+ });
294
+ return {
295
+ products: productsList,
296
+ isLoading,
297
+ error,
298
+ totalProducts,
299
+ hasProducts,
300
+ hasMoreProducts,
301
+ loadMore,
302
+ refresh: debouncedRefresh,
303
+ };
304
+ });
305
+ // Helper function to parse URL parameters
306
+ function parseURLParams(searchParams, products = []) {
307
+ const defaultFilters = {
308
+ priceRange: { min: 0, max: 0 },
309
+ selectedOptions: {},
310
+ };
311
+ if (!searchParams) {
312
+ return { initialSort: "", initialFilters: defaultFilters };
313
+ }
314
+ const urlParams = url_params_1.URLParamsUtils.parseSearchParams(searchParams);
315
+ // Parse sort parameter
316
+ const sortMap = {
317
+ name_asc: "name-asc",
318
+ name_desc: "name-desc",
319
+ price_asc: "price-asc",
320
+ price_desc: "price-desc",
321
+ };
322
+ const initialSort = sortMap[urlParams["sort"]] || "";
323
+ // Check if there are any filter parameters (excluding sort)
324
+ const filterParams = Object.keys(urlParams).filter((key) => key !== "sort");
325
+ if (filterParams.length === 0 || products.length === 0) {
326
+ return { initialSort, initialFilters: defaultFilters };
327
+ }
328
+ // Calculate available price range from products
329
+ const priceRange = calculatePriceRange(products);
330
+ const initialFilters = {
331
+ priceRange,
332
+ selectedOptions: {},
333
+ };
334
+ // Apply price filters from URL
335
+ if (urlParams["minPrice"]) {
336
+ const min = parseFloat(urlParams["minPrice"]);
337
+ if (!isNaN(min))
338
+ initialFilters.priceRange.min = min;
339
+ }
340
+ if (urlParams["maxPrice"]) {
341
+ const max = parseFloat(urlParams["maxPrice"]);
342
+ if (!isNaN(max))
343
+ initialFilters.priceRange.max = max;
344
+ }
345
+ // Parse option filters
346
+ const optionsMap = buildOptionsMap(products);
347
+ parseOptionFilters(urlParams, optionsMap, initialFilters);
348
+ // Parse inventory filter from 'availability' URL parameter
349
+ if (urlParams["availability"]) {
350
+ const availabilityValues = Array.isArray(urlParams["availability"])
351
+ ? urlParams["availability"]
352
+ : [urlParams["availability"]];
353
+ const inventoryStatusValues = availabilityValues.map((value) => value.replace(/\s+/g, "_").toUpperCase());
354
+ initialFilters.selectedOptions["inventory-filter"] = inventoryStatusValues;
355
+ }
356
+ return { initialSort, initialFilters };
357
+ }
358
+ // Helper function to calculate price range from products
359
+ function calculatePriceRange(products) {
360
+ if (products.length === 0) {
361
+ return { min: 0, max: 1000 };
362
+ }
363
+ let minPrice = Infinity;
364
+ let maxPrice = 0;
365
+ products.forEach((product) => {
366
+ const min = parseFloat(product.actualPriceRange?.minValue?.amount || "0");
367
+ const max = parseFloat(product.actualPriceRange?.maxValue?.amount || "0");
368
+ if (min > 0)
369
+ minPrice = Math.min(minPrice, min);
370
+ if (max > 0)
371
+ maxPrice = Math.max(maxPrice, max);
372
+ });
373
+ // If no valid prices found, return default range
374
+ if (minPrice === Infinity) {
375
+ minPrice = 0;
376
+ maxPrice = 1000;
377
+ }
378
+ return { min: minPrice, max: maxPrice };
379
+ }
380
+ // Helper function to build options map from products
381
+ function buildOptionsMap(products) {
382
+ const optionsMap = new Map();
383
+ products.forEach((product) => {
384
+ product.options?.forEach((option) => {
385
+ if (!option._id || !option.name)
386
+ return;
387
+ if (!optionsMap.has(option.name)) {
388
+ optionsMap.set(option.name, { id: option._id, choices: [] });
389
+ }
390
+ const optionData = optionsMap.get(option.name);
391
+ option.choicesSettings?.choices?.forEach((choice) => {
392
+ if (choice.choiceId &&
393
+ choice.name &&
394
+ !optionData.choices.find((c) => c.id === choice.choiceId)) {
395
+ optionData.choices.push({ id: choice.choiceId, name: choice.name });
396
+ }
397
+ });
398
+ });
399
+ });
400
+ return optionsMap;
401
+ }
402
+ // Helper function to parse option filters from URL parameters
403
+ function parseOptionFilters(urlParams, optionsMap, filters) {
404
+ Object.entries(urlParams).forEach(([key, value]) => {
405
+ if (["sort", "minPrice", "maxPrice"].includes(key))
406
+ return;
407
+ const option = optionsMap.get(key);
408
+ if (option) {
409
+ const values = Array.isArray(value) ? value : [value];
410
+ const matchingChoices = option.choices.filter((choice) => values.includes(choice.name));
411
+ if (matchingChoices.length > 0) {
412
+ filters.selectedOptions[option.id] = matchingChoices.map((c) => c.id);
413
+ }
414
+ }
415
+ });
416
+ }
417
+ async function loadCollectionServiceConfig(categoryId, searchParams) {
418
+ try {
419
+ // Load categories for service configuration
420
+ const { loadCategoriesConfig } = await Promise.resolve().then(() => __importStar(require("./category-service")));
421
+ const categoriesConfig = await loadCategoriesConfig();
422
+ const categories = categoriesConfig.categories;
423
+ // Build search options with category filter
424
+ const searchOptions = buildSearchOptions(undefined, categoryId, undefined, categories);
425
+ const pageSize = 12;
426
+ searchOptions.paging = { limit: pageSize };
427
+ const productResults = await searchProducts(searchOptions);
428
+ // Parse URL parameters for initial state
429
+ const { initialSort, initialFilters } = parseURLParams(searchParams, productResults.products || []);
430
+ return {
431
+ initialProducts: productResults.products || [],
432
+ pageSize,
433
+ initialCursor: productResults.pagingMetadata?.cursors?.next || undefined,
434
+ initialHasMore: Boolean(productResults.pagingMetadata?.cursors?.next &&
435
+ productResults.products &&
436
+ productResults.products.length === pageSize),
437
+ initialSort,
438
+ initialFilters,
439
+ categories,
440
+ };
441
+ }
442
+ catch (error) {
443
+ console.warn("Failed to load initial products:", error);
444
+ const { initialSort, initialFilters } = parseURLParams(searchParams);
445
+ return {
446
+ initialProducts: [],
447
+ pageSize: 12,
448
+ initialHasMore: false,
449
+ initialSort,
450
+ initialFilters,
451
+ categories: [],
452
+ };
453
+ }
454
+ }
@@ -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>;
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FilterService = exports.defaultFilter = exports.FilterServiceDefinition = void 0;
4
+ const services_definitions_1 = require("@wix/services-definitions");
5
+ const signals_1 = require("@wix/services-definitions/core-services/signals");
6
+ const url_params_1 = require("../utils/url-params");
7
+ const catalog_price_range_service_1 = require("./catalog-price-range-service");
8
+ const catalog_options_service_1 = require("./catalog-options-service");
9
+ exports.FilterServiceDefinition = (0, services_definitions_1.defineService)("filtered-collection");
10
+ exports.defaultFilter = {
11
+ priceRange: { min: 0, max: 0 },
12
+ selectedOptions: {},
13
+ };
14
+ exports.FilterService = services_definitions_1.implementService.withConfig()(exports.FilterServiceDefinition, ({ getService, config }) => {
15
+ const signalsService = getService(signals_1.SignalsServiceDefinition);
16
+ const catalogPriceRangeService = getService(catalog_price_range_service_1.CatalogPriceRangeServiceDefinition);
17
+ const catalogOptionsService = getService(catalog_options_service_1.CatalogOptionsServiceDefinition);
18
+ const currentFilters = signalsService.signal((config.initialFilters || exports.defaultFilter));
19
+ const availableOptions = signalsService.signal({
20
+ productOptions: [],
21
+ priceRange: { min: 0, max: 0 },
22
+ });
23
+ const isFullyLoaded = signalsService.signal(false);
24
+ // Helper function to check if both catalog data are loaded
25
+ const checkIfFullyLoaded = () => {
26
+ const catalogPriceRange = catalogPriceRangeService.catalogPriceRange.get();
27
+ const catalogOptions = catalogOptionsService.catalogOptions.get();
28
+ // Price range data is considered loaded whether it's null (no prices) or has valid data
29
+ const hasPriceRangeData = catalogPriceRange !== undefined; // includes null case
30
+ const hasOptionsData = !!(catalogOptions && catalogOptions.length >= 0); // Even 0 options is valid
31
+ isFullyLoaded.set(hasPriceRangeData && hasOptionsData);
32
+ };
33
+ // Subscribe to catalog price range changes and automatically update our signals
34
+ catalogPriceRangeService.catalogPriceRange.subscribe((catalogPriceRange) => {
35
+ if (catalogPriceRange &&
36
+ catalogPriceRange.minPrice < catalogPriceRange.maxPrice) {
37
+ const priceRange = {
38
+ min: catalogPriceRange.minPrice,
39
+ max: catalogPriceRange.maxPrice,
40
+ };
41
+ // Update available options with catalog price range
42
+ const currentAvailableOptions = availableOptions.get();
43
+ availableOptions.set({
44
+ ...currentAvailableOptions,
45
+ priceRange,
46
+ });
47
+ // Update current filters to use catalog price range
48
+ const currentFiltersValue = currentFilters.get();
49
+ // Only update if current filter range is at defaults (either 0-0 or 0-1000)
50
+ const isDefaultRange = (currentFiltersValue.priceRange.min === 0 &&
51
+ currentFiltersValue.priceRange.max === 0) ||
52
+ (currentFiltersValue.priceRange.min === 0 &&
53
+ currentFiltersValue.priceRange.max === 1000);
54
+ if (isDefaultRange) {
55
+ currentFilters.set({
56
+ ...currentFiltersValue,
57
+ priceRange,
58
+ });
59
+ }
60
+ }
61
+ // Check if fully loaded after price range update
62
+ checkIfFullyLoaded();
63
+ });
64
+ // Subscribe to catalog options changes and automatically update our signals
65
+ catalogOptionsService.catalogOptions.subscribe((catalogOptions) => {
66
+ if (catalogOptions && catalogOptions.length > 0) {
67
+ // Update available options with catalog options
68
+ const currentAvailableOptions = availableOptions.get();
69
+ availableOptions.set({
70
+ ...currentAvailableOptions,
71
+ productOptions: catalogOptions,
72
+ });
73
+ }
74
+ // Check if fully loaded after options update
75
+ checkIfFullyLoaded();
76
+ });
77
+ // Apply filters by delegating to the collection service
78
+ const applyFilters = async (filters) => {
79
+ currentFilters.set(filters);
80
+ // Update URL with filter parameters
81
+ const urlParams = {};
82
+ const availableOpts = availableOptions.get();
83
+ // Add price filters if different from defaults
84
+ if (availableOpts?.priceRange) {
85
+ if (filters.priceRange.min > availableOpts.priceRange.min) {
86
+ urlParams["minPrice"] = filters.priceRange.min.toString();
87
+ }
88
+ if (filters.priceRange.max < availableOpts.priceRange.max) {
89
+ urlParams["maxPrice"] = filters.priceRange.max.toString();
90
+ }
91
+ }
92
+ // Add option filters using option names as keys
93
+ if (availableOpts?.productOptions) {
94
+ Object.entries(filters.selectedOptions).forEach(([optionId, choiceIds]) => {
95
+ const option = availableOpts.productOptions.find((opt) => opt.id === optionId);
96
+ if (option && choiceIds.length > 0) {
97
+ const selectedChoices = option.choices.filter((choice) => choiceIds.includes(choice.id));
98
+ if (selectedChoices.length > 0) {
99
+ // Use 'availability' as URL param for inventory filter
100
+ const paramName = optionId === "inventory-filter" ? "availability" : option.name;
101
+ urlParams[paramName] = selectedChoices.map((choice) => choice.name);
102
+ }
103
+ }
104
+ });
105
+ }
106
+ // Preserve existing sort parameter
107
+ const currentParams = url_params_1.URLParamsUtils.getURLParams();
108
+ if (currentParams["sort"]) {
109
+ urlParams["sort"] = currentParams["sort"];
110
+ }
111
+ url_params_1.URLParamsUtils.updateURL(urlParams);
112
+ };
113
+ // Clear all filters by applying default filter state
114
+ const clearFilters = async () => {
115
+ const availablePriceRange = availableOptions.get()?.priceRange;
116
+ currentFilters.set({
117
+ ...exports.defaultFilter,
118
+ priceRange: availablePriceRange || { min: 0, max: 0 },
119
+ });
120
+ // Clear filter parameters from URL, keeping only sort parameter
121
+ const currentParams = url_params_1.URLParamsUtils.getURLParams();
122
+ const urlParams = {};
123
+ if (currentParams["sort"]) {
124
+ urlParams["sort"] = currentParams["sort"];
125
+ }
126
+ url_params_1.URLParamsUtils.updateURL(urlParams);
127
+ };
128
+ // const calculateAvailableOptions = async (
129
+ // products: productsV3.V3Product[]
130
+ // ) => {
131
+ // // No longer calculating options from current page products
132
+ // // Options are now loaded from the catalog-wide service
133
+ // // This function is kept for backward compatibility but does nothing
134
+ // console.log(
135
+ // "🔄 calculateAvailableOptions called but using catalog-wide options instead"
136
+ // );
137
+ // };
138
+ const loadCatalogPriceRange = async (categoryId) => {
139
+ // Just call the catalog service - subscriptions will handle signal updates
140
+ await catalogPriceRangeService.loadCatalogPriceRange(categoryId);
141
+ };
142
+ const loadCatalogOptions = async (categoryId) => {
143
+ // Just call the catalog service - subscriptions will handle signal updates
144
+ await catalogOptionsService.loadCatalogOptions(categoryId);
145
+ };
146
+ return {
147
+ currentFilters,
148
+ applyFilters,
149
+ clearFilters,
150
+ availableOptions,
151
+ loadCatalogPriceRange,
152
+ loadCatalogOptions,
153
+ isFullyLoaded,
154
+ };
155
+ });
@@ -0,0 +1,25 @@
1
+ import { type ServiceFactoryConfig } from "@wix/services-definitions";
2
+ import type { Signal, ReadOnlySignal } from "./Signal";
3
+ import { productsV3 } from "@wix/stores";
4
+ export interface ProductMediaGalleryServiceAPI {
5
+ selectedImageIndex: Signal<number>;
6
+ relevantImages: ReadOnlySignal<string[]>;
7
+ product: ReadOnlySignal<productsV3.V3Product | null>;
8
+ isLoading: ReadOnlySignal<boolean>;
9
+ totalImages: ReadOnlySignal<number>;
10
+ productName: ReadOnlySignal<string>;
11
+ setSelectedImageIndex: (index: number) => void;
12
+ nextImage: () => void;
13
+ previousImage: () => void;
14
+ }
15
+ export declare const ProductMediaGalleryServiceDefinition: string & {
16
+ __api: ProductMediaGalleryServiceAPI;
17
+ __config: {};
18
+ isServiceDefinition?: boolean;
19
+ } & ProductMediaGalleryServiceAPI;
20
+ export declare const ProductMediaGalleryService: import("@wix/services-definitions").ServiceFactory<string & {
21
+ __api: ProductMediaGalleryServiceAPI;
22
+ __config: {};
23
+ isServiceDefinition?: boolean;
24
+ } & ProductMediaGalleryServiceAPI, {}, import("@wix/services-definitions").ThreadMode.MAIN>;
25
+ export declare function loadProductMediaGalleryServiceConfig(productSlug: string): Promise<ServiceFactoryConfig<typeof ProductMediaGalleryService>>;