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