@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.
- package/cjs/dist/react/BuyNow.d.ts +1 -0
- package/cjs/dist/react/BuyNow.js +1 -0
- package/cjs/dist/react/Category.d.ts +17 -0
- package/cjs/dist/react/Category.js +37 -0
- package/cjs/dist/react/Collection.d.ts +141 -0
- package/cjs/dist/react/Collection.js +198 -0
- package/cjs/dist/react/FilteredCollection.d.ts +65 -0
- package/cjs/dist/react/FilteredCollection.js +117 -0
- package/cjs/dist/react/PayNow.d.ts +1 -0
- package/cjs/dist/react/PayNow.js +1 -0
- package/cjs/dist/react/Product.d.ts +70 -0
- package/cjs/dist/react/Product.js +56 -0
- package/cjs/dist/react/ProductMediaGallery.d.ts +128 -0
- package/cjs/dist/react/ProductMediaGallery.js +100 -0
- package/cjs/dist/react/ProductModifiers.d.ts +156 -0
- package/cjs/dist/react/ProductModifiers.js +159 -0
- package/cjs/dist/react/ProductVariantSelector.d.ts +169 -0
- package/cjs/dist/react/ProductVariantSelector.js +166 -0
- package/cjs/dist/react/RelatedProducts.d.ts +60 -0
- package/cjs/dist/react/RelatedProducts.js +68 -0
- package/cjs/dist/react/SocialSharing.d.ts +119 -0
- package/cjs/dist/react/SocialSharing.js +80 -0
- package/cjs/dist/react/Sort.d.ts +17 -0
- package/cjs/dist/react/Sort.js +41 -0
- package/cjs/dist/react/index.d.ts +10 -0
- package/cjs/dist/react/index.js +33 -0
- package/cjs/dist/services/catalog-options-service.d.ts +30 -0
- package/cjs/dist/services/catalog-options-service.js +162 -0
- package/cjs/dist/services/catalog-price-range-service.d.ts +23 -0
- package/cjs/dist/services/catalog-price-range-service.js +95 -0
- package/cjs/dist/services/category-service.d.ts +25 -0
- package/cjs/dist/services/category-service.js +67 -0
- package/cjs/dist/services/collection-service.d.ts +37 -0
- package/cjs/dist/services/collection-service.js +454 -0
- package/cjs/dist/services/filter-service.d.ts +56 -0
- package/cjs/dist/services/filter-service.js +155 -0
- package/cjs/dist/services/product-media-gallery-service.d.ts +25 -0
- package/cjs/dist/services/product-media-gallery-service.js +105 -0
- package/cjs/dist/services/product-modifiers-service.d.ts +36 -0
- package/cjs/dist/services/product-modifiers-service.js +104 -0
- package/cjs/dist/services/product-service.d.ts +27 -0
- package/cjs/dist/services/product-service.js +51 -0
- package/cjs/dist/services/related-products-service.d.ts +25 -0
- package/cjs/dist/services/related-products-service.js +54 -0
- package/cjs/dist/services/selected-variant-service.d.ts +51 -0
- package/cjs/dist/services/selected-variant-service.js +396 -0
- package/cjs/dist/services/social-sharing-service.d.ts +41 -0
- package/cjs/dist/services/social-sharing-service.js +157 -0
- package/cjs/dist/services/sort-service.d.ts +19 -0
- package/cjs/dist/services/sort-service.js +37 -0
- package/cjs/dist/utils/url-params.d.ts +5 -0
- package/cjs/dist/utils/url-params.js +50 -0
- package/dist/react/BuyNow.d.ts +1 -0
- package/dist/react/BuyNow.js +1 -0
- package/dist/react/Category.d.ts +17 -0
- package/dist/react/Category.js +31 -0
- package/dist/react/Collection.d.ts +141 -0
- package/dist/react/Collection.js +190 -0
- package/dist/react/FilteredCollection.d.ts +65 -0
- package/dist/react/FilteredCollection.js +107 -0
- package/dist/react/PayNow.d.ts +1 -0
- package/dist/react/PayNow.js +1 -0
- package/dist/react/Product.d.ts +70 -0
- package/dist/react/Product.js +50 -0
- package/dist/react/ProductMediaGallery.d.ts +128 -0
- package/dist/react/ProductMediaGallery.js +92 -0
- package/dist/react/ProductModifiers.d.ts +156 -0
- package/dist/react/ProductModifiers.js +151 -0
- package/dist/react/ProductVariantSelector.d.ts +169 -0
- package/dist/react/ProductVariantSelector.js +157 -0
- package/dist/react/RelatedProducts.d.ts +60 -0
- package/dist/react/RelatedProducts.js +60 -0
- package/dist/react/SocialSharing.d.ts +119 -0
- package/dist/react/SocialSharing.js +71 -0
- package/dist/react/Sort.d.ts +17 -0
- package/dist/react/Sort.js +36 -0
- package/dist/react/index.d.ts +10 -0
- package/dist/react/index.js +10 -0
- package/dist/services/catalog-options-service.d.ts +30 -0
- package/dist/services/catalog-options-service.js +158 -0
- package/dist/services/catalog-price-range-service.d.ts +23 -0
- package/dist/services/catalog-price-range-service.js +91 -0
- package/dist/services/category-service.d.ts +25 -0
- package/dist/services/category-service.js +63 -0
- package/dist/services/collection-service.d.ts +37 -0
- package/dist/services/collection-service.js +417 -0
- package/dist/services/filter-service.d.ts +56 -0
- package/dist/services/filter-service.js +152 -0
- package/dist/services/product-media-gallery-service.d.ts +25 -0
- package/dist/services/product-media-gallery-service.js +101 -0
- package/dist/services/product-modifiers-service.d.ts +36 -0
- package/dist/services/product-modifiers-service.js +100 -0
- package/dist/services/product-service.d.ts +27 -0
- package/dist/services/product-service.js +47 -0
- package/dist/services/related-products-service.d.ts +25 -0
- package/dist/services/related-products-service.js +50 -0
- package/dist/services/selected-variant-service.d.ts +51 -0
- package/dist/services/selected-variant-service.js +392 -0
- package/dist/services/social-sharing-service.d.ts +41 -0
- package/dist/services/social-sharing-service.js +153 -0
- package/dist/services/sort-service.d.ts +19 -0
- package/dist/services/sort-service.js +34 -0
- package/dist/utils/url-params.d.ts +5 -0
- package/dist/utils/url-params.js +46 -0
- 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>>;
|