@wix/headless-stores 0.0.36 → 0.0.38
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/{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 +66 -3
- package/cjs/dist/react/SelectedVariant.js +106 -7
- package/cjs/dist/react/index.d.ts +6 -9
- package/cjs/dist/react/index.js +6 -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 +185 -54
- 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/{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 +66 -3
- package/dist/react/SelectedVariant.js +106 -7
- package/dist/react/index.d.ts +6 -9
- package/dist/react/index.js +6 -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 +185 -54
- 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/ProductActions.d.ts +0 -70
- package/cjs/dist/react/ProductActions.js +0 -104
- 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/ProductActions.d.ts +0 -70
- package/dist/react/ProductActions.js +0 -104
- 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
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { defineService } from "@wix/services-definitions";
|
|
2
|
+
import { implementService } from "@wix/services-definitions";
|
|
3
|
+
import { SignalsServiceDefinition, } from "@wix/services-definitions/core-services/signals";
|
|
4
|
+
import { productsV3 } from "@wix/stores";
|
|
5
|
+
import { ProductsListServiceDefinition } from "./products-list-service.js";
|
|
6
|
+
import { customizationsV3 } from "@wix/stores";
|
|
7
|
+
/**
|
|
8
|
+
* Enumeration of inventory status types available for filtering.
|
|
9
|
+
* Maps to the Wix Stores API inventory availability statuses.
|
|
10
|
+
*
|
|
11
|
+
* @enum {string}
|
|
12
|
+
*/
|
|
13
|
+
export var InventoryStatusType;
|
|
14
|
+
(function (InventoryStatusType) {
|
|
15
|
+
/** Product is in stock and available for purchase */
|
|
16
|
+
InventoryStatusType["IN_STOCK"] = "IN_STOCK";
|
|
17
|
+
/** Product is out of stock */
|
|
18
|
+
InventoryStatusType["OUT_OF_STOCK"] = "OUT_OF_STOCK";
|
|
19
|
+
/** Product is partially out of stock (some variants available) */
|
|
20
|
+
InventoryStatusType["PARTIALLY_OUT_OF_STOCK"] = "PARTIALLY_OUT_OF_STOCK";
|
|
21
|
+
})(InventoryStatusType || (InventoryStatusType = {}));
|
|
22
|
+
/**
|
|
23
|
+
* Loads products list filters service configuration from the Wix Stores API for SSR initialization.
|
|
24
|
+
* This function fetches customization data that will be used to build product filter options.
|
|
25
|
+
*
|
|
26
|
+
* @returns {Promise<ProductsListFiltersServiceConfig>} Promise that resolves to the filters configuration
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```astro
|
|
30
|
+
* ---
|
|
31
|
+
* // Astro page example - pages/products.astro
|
|
32
|
+
* import { loadProductsListFiltersServiceConfig } from '@wix/stores/services';
|
|
33
|
+
* import { ProductsListFilters } from '@wix/stores/components';
|
|
34
|
+
*
|
|
35
|
+
* // Load filters configuration during SSR
|
|
36
|
+
* const filtersConfig = await loadProductsListFiltersServiceConfig();
|
|
37
|
+
* ---
|
|
38
|
+
*
|
|
39
|
+
* <ProductsListFilters.Root filtersConfig={filtersConfig}>
|
|
40
|
+
* <ProductsListFilters.MinPrice>
|
|
41
|
+
* {({ minPrice, setMinPrice }) => (
|
|
42
|
+
* <input
|
|
43
|
+
* type="number"
|
|
44
|
+
* value={minPrice}
|
|
45
|
+
* onChange={(e) => setMinPrice(parseFloat(e.target.value))}
|
|
46
|
+
* placeholder="Min Price"
|
|
47
|
+
* />
|
|
48
|
+
* )}
|
|
49
|
+
* </ProductsListFilters.MinPrice>
|
|
50
|
+
* </ProductsListFilters.Root>
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* // Next.js page example
|
|
56
|
+
* import { GetServerSideProps } from 'next';
|
|
57
|
+
* import { loadProductsListFiltersServiceConfig } from '@wix/stores/services';
|
|
58
|
+
*
|
|
59
|
+
* export const getServerSideProps: GetServerSideProps = async () => {
|
|
60
|
+
* const filtersConfig = await loadProductsListFiltersServiceConfig();
|
|
61
|
+
*
|
|
62
|
+
* return {
|
|
63
|
+
* props: {
|
|
64
|
+
* filtersConfig,
|
|
65
|
+
* },
|
|
66
|
+
* };
|
|
67
|
+
* };
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export async function loadProductsListFiltersServiceConfig() {
|
|
71
|
+
const { items: customizations = [] } = await customizationsV3
|
|
72
|
+
.queryCustomizations()
|
|
73
|
+
.find();
|
|
74
|
+
return {
|
|
75
|
+
customizations,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Service definition for the Products List Filters service.
|
|
80
|
+
* This defines the reactive API contract for managing product list filtering capabilities
|
|
81
|
+
* including price, inventory status, and product option filters.
|
|
82
|
+
*
|
|
83
|
+
* @constant
|
|
84
|
+
*/
|
|
85
|
+
export const ProductsListFiltersServiceDefinition = defineService("products-list-filters");
|
|
86
|
+
/**
|
|
87
|
+
* Implementation of the Products List Filters service that manages reactive filtering state.
|
|
88
|
+
* This service provides signals for all filter types (price, inventory, product options) and
|
|
89
|
+
* automatically updates the products list search options when filters change.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```tsx
|
|
93
|
+
* import { ProductsListFiltersService, ProductsListFiltersServiceDefinition } from '@wix/stores/services';
|
|
94
|
+
* import { useService } from '@wix/services-manager-react';
|
|
95
|
+
*
|
|
96
|
+
* function FiltersComponent({ filtersConfig }) {
|
|
97
|
+
* return (
|
|
98
|
+
* <ServiceProvider services={createServicesMap([
|
|
99
|
+
* [ProductsListFiltersServiceDefinition, ProductsListFiltersService.withConfig(filtersConfig)]
|
|
100
|
+
* ])}>
|
|
101
|
+
* <FilterControls />
|
|
102
|
+
* </ServiceProvider>
|
|
103
|
+
* );
|
|
104
|
+
* }
|
|
105
|
+
*
|
|
106
|
+
* function FilterControls() {
|
|
107
|
+
* const filtersService = useService(ProductsListFiltersServiceDefinition);
|
|
108
|
+
* const minPrice = filtersService.minPrice.get();
|
|
109
|
+
* const maxPrice = filtersService.maxPrice.get();
|
|
110
|
+
* const selectedInventoryStatuses = filtersService.selectedInventoryStatuses.get();
|
|
111
|
+
* const availableProductOptions = filtersService.availableProductOptions.get();
|
|
112
|
+
* const isFiltered = filtersService.isFiltered.get();
|
|
113
|
+
*
|
|
114
|
+
* return (
|
|
115
|
+
* <div>
|
|
116
|
+
* <div>
|
|
117
|
+
* <input
|
|
118
|
+
* type="number"
|
|
119
|
+
* value={minPrice}
|
|
120
|
+
* onChange={(e) => filtersService.setMinPrice(parseFloat(e.target.value))}
|
|
121
|
+
* placeholder="Min Price"
|
|
122
|
+
* />
|
|
123
|
+
* <input
|
|
124
|
+
* type="number"
|
|
125
|
+
* value={maxPrice}
|
|
126
|
+
* onChange={(e) => filtersService.setMaxPrice(parseFloat(e.target.value))}
|
|
127
|
+
* placeholder="Max Price"
|
|
128
|
+
* />
|
|
129
|
+
* </div>
|
|
130
|
+
*
|
|
131
|
+
* {availableProductOptions.map(option => (
|
|
132
|
+
* <div key={option.id}>
|
|
133
|
+
* <h4>{option.name}</h4>
|
|
134
|
+
* {option.choices.map(choice => (
|
|
135
|
+
* <label key={choice.id}>
|
|
136
|
+
* <input
|
|
137
|
+
* type="checkbox"
|
|
138
|
+
* onChange={() => filtersService.toggleProductOption(option.id, choice.id)}
|
|
139
|
+
* />
|
|
140
|
+
* {choice.name}
|
|
141
|
+
* </label>
|
|
142
|
+
* ))}
|
|
143
|
+
* </div>
|
|
144
|
+
* ))}
|
|
145
|
+
*
|
|
146
|
+
* {isFiltered && (
|
|
147
|
+
* <button onClick={() => filtersService.reset()}>
|
|
148
|
+
* Clear All Filters
|
|
149
|
+
* </button>
|
|
150
|
+
* )}
|
|
151
|
+
* </div>
|
|
152
|
+
* );
|
|
153
|
+
* }
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export const ProductsListFiltersService = implementService.withConfig()(ProductsListFiltersServiceDefinition, ({ getService, config }) => {
|
|
157
|
+
let firstRun = true;
|
|
158
|
+
const signalsService = getService(SignalsServiceDefinition);
|
|
159
|
+
const productsListService = getService(ProductsListServiceDefinition);
|
|
160
|
+
const { customizations } = config;
|
|
161
|
+
const aggregationData = productsListService.aggregations.get()?.results;
|
|
162
|
+
// TODO: use the aggregations to get the available inventory statuses
|
|
163
|
+
// and the available price ranges
|
|
164
|
+
// and the available product options
|
|
165
|
+
// and the available product choices
|
|
166
|
+
const minPriceSignal = signalsService.signal(getMinPrice(productsListService.searchOptions.get()));
|
|
167
|
+
const maxPriceSignal = signalsService.signal(getMaxPrice(productsListService.searchOptions.get()));
|
|
168
|
+
const availableInventoryStatusesSignal = signalsService.signal([
|
|
169
|
+
InventoryStatusType.IN_STOCK,
|
|
170
|
+
InventoryStatusType.OUT_OF_STOCK,
|
|
171
|
+
InventoryStatusType.PARTIALLY_OUT_OF_STOCK,
|
|
172
|
+
]);
|
|
173
|
+
const selectedInventoryStatusesSignal = signalsService.signal(getSelectedInventoryStatuses(productsListService.searchOptions.get()));
|
|
174
|
+
// TODO: Get product options from aggregations data
|
|
175
|
+
const availableProductOptionsSignal = signalsService.signal(getAvailableProductOptions(aggregationData, customizations));
|
|
176
|
+
const selectedProductOptionsSignal = signalsService.signal(getSelectedProductOptions(productsListService.searchOptions.get()));
|
|
177
|
+
const isFilteredSignal = signalsService.signal(false);
|
|
178
|
+
if (typeof window !== "undefined") {
|
|
179
|
+
signalsService.effect(() => {
|
|
180
|
+
// CRITICAL: Read the signals FIRST to establish dependencies, even on first run
|
|
181
|
+
const minPrice = minPriceSignal.get();
|
|
182
|
+
const maxPrice = maxPriceSignal.get();
|
|
183
|
+
const selectedInventoryStatuses = selectedInventoryStatusesSignal.get();
|
|
184
|
+
const selectedProductOptions = selectedProductOptionsSignal.get();
|
|
185
|
+
if (firstRun) {
|
|
186
|
+
firstRun = false;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
isFilteredSignal.set(true);
|
|
190
|
+
// Build new search options with updated price filters
|
|
191
|
+
const newSearchOptions = {
|
|
192
|
+
...productsListService.searchOptions.peek(),
|
|
193
|
+
};
|
|
194
|
+
delete newSearchOptions.cursorPaging?.cursor;
|
|
195
|
+
// Initialize filter if it doesn't exist
|
|
196
|
+
if (!newSearchOptions.filter) {
|
|
197
|
+
newSearchOptions.filter = {};
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
// Copy existing filter to avoid mutation
|
|
201
|
+
newSearchOptions.filter = { ...newSearchOptions.filter };
|
|
202
|
+
}
|
|
203
|
+
// Remove existing price filters
|
|
204
|
+
delete newSearchOptions.filter["actualPriceRange.minValue.amount"];
|
|
205
|
+
delete newSearchOptions.filter["actualPriceRange.maxValue.amount"];
|
|
206
|
+
// Remove existing inventory filter
|
|
207
|
+
delete newSearchOptions.filter["inventory.availabilityStatus"];
|
|
208
|
+
// Remove existing product option filters
|
|
209
|
+
// First, find and remove any existing option filters
|
|
210
|
+
Object.keys(newSearchOptions.filter).forEach((key) => {
|
|
211
|
+
if (key.startsWith("options.")) {
|
|
212
|
+
delete newSearchOptions.filter[key];
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
// Add new price filters if they have valid values
|
|
216
|
+
if (minPrice > 0) {
|
|
217
|
+
newSearchOptions.filter["actualPriceRange.minValue.amount"] = { $gte: minPrice };
|
|
218
|
+
}
|
|
219
|
+
if (maxPrice > 0) {
|
|
220
|
+
newSearchOptions.filter["actualPriceRange.maxValue.amount"] = { $lte: maxPrice };
|
|
221
|
+
}
|
|
222
|
+
// Add new inventory filter if there are selected statuses
|
|
223
|
+
if (selectedInventoryStatuses.length > 0) {
|
|
224
|
+
if (selectedInventoryStatuses.length === 1) {
|
|
225
|
+
newSearchOptions.filter["inventory.availabilityStatus"] =
|
|
226
|
+
selectedInventoryStatuses[0];
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
newSearchOptions.filter["inventory.availabilityStatus"] =
|
|
230
|
+
{
|
|
231
|
+
$in: selectedInventoryStatuses,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Add new product option filters if there are selected options
|
|
236
|
+
if (selectedProductOptions &&
|
|
237
|
+
Object.keys(selectedProductOptions).length > 0) {
|
|
238
|
+
for (const [optionId, choiceIds] of Object.entries(selectedProductOptions)) {
|
|
239
|
+
if (choiceIds && choiceIds.length > 0) {
|
|
240
|
+
// Handle inventory filter separately
|
|
241
|
+
if (optionId === "inventory-filter") {
|
|
242
|
+
newSearchOptions.filter["inventory.availabilityStatus"] = {
|
|
243
|
+
$in: choiceIds,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
// Regular product options filter
|
|
248
|
+
newSearchOptions.filter["options.choicesSettings.choices.choiceId"] = {
|
|
249
|
+
$hasSome: choiceIds,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Use callback to update search options
|
|
256
|
+
productsListService.setSearchOptions(newSearchOptions);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
minPrice: minPriceSignal,
|
|
261
|
+
maxPrice: maxPriceSignal,
|
|
262
|
+
availableInventoryStatuses: availableInventoryStatusesSignal,
|
|
263
|
+
selectedInventoryStatuses: selectedInventoryStatusesSignal,
|
|
264
|
+
availableProductOptions: availableProductOptionsSignal,
|
|
265
|
+
selectedProductOptions: selectedProductOptionsSignal,
|
|
266
|
+
setMinPrice: (minPrice) => {
|
|
267
|
+
minPriceSignal.set(minPrice);
|
|
268
|
+
},
|
|
269
|
+
setMaxPrice: (maxPrice) => {
|
|
270
|
+
maxPriceSignal.set(maxPrice);
|
|
271
|
+
},
|
|
272
|
+
toggleInventoryStatus: (status) => {
|
|
273
|
+
const current = selectedInventoryStatusesSignal.get();
|
|
274
|
+
const isSelected = current.includes(status);
|
|
275
|
+
if (isSelected) {
|
|
276
|
+
selectedInventoryStatusesSignal.set(current.filter((s) => s !== status));
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
selectedInventoryStatusesSignal.set([...current, status]);
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
toggleProductOption: (optionId, choiceId) => {
|
|
283
|
+
const current = selectedProductOptionsSignal.get();
|
|
284
|
+
const currentChoices = current[optionId] || [];
|
|
285
|
+
const isSelected = currentChoices.includes(choiceId);
|
|
286
|
+
if (isSelected) {
|
|
287
|
+
// Remove the choice
|
|
288
|
+
const newChoices = currentChoices.filter((id) => id !== choiceId);
|
|
289
|
+
if (newChoices.length === 0) {
|
|
290
|
+
const newOptions = { ...current };
|
|
291
|
+
delete newOptions[optionId];
|
|
292
|
+
selectedProductOptionsSignal.set(newOptions);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
selectedProductOptionsSignal.set({
|
|
296
|
+
...current,
|
|
297
|
+
[optionId]: newChoices,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
// Add the choice
|
|
303
|
+
selectedProductOptionsSignal.set({
|
|
304
|
+
...current,
|
|
305
|
+
[optionId]: [...currentChoices, choiceId],
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
isFiltered: isFilteredSignal,
|
|
310
|
+
reset: () => {
|
|
311
|
+
// TODO: reset the filters to the original values from the aggregation data
|
|
312
|
+
minPriceSignal.set(0);
|
|
313
|
+
maxPriceSignal.set(0);
|
|
314
|
+
selectedInventoryStatusesSignal.set([]);
|
|
315
|
+
selectedProductOptionsSignal.set({});
|
|
316
|
+
isFilteredSignal.set(false);
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
});
|
|
320
|
+
function getMinPrice(searchOptions) {
|
|
321
|
+
const filter = searchOptions.filter;
|
|
322
|
+
if (!filter)
|
|
323
|
+
return 0;
|
|
324
|
+
const minPriceFilter = filter["actualPriceRange.minValue.amount"];
|
|
325
|
+
if (typeof minPriceFilter === "object" &&
|
|
326
|
+
minPriceFilter !== null &&
|
|
327
|
+
"$gte" in minPriceFilter) {
|
|
328
|
+
return Number(minPriceFilter.$gte) || 0;
|
|
329
|
+
}
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
function getMaxPrice(searchOptions) {
|
|
333
|
+
const filter = searchOptions.filter;
|
|
334
|
+
if (!filter)
|
|
335
|
+
return 0;
|
|
336
|
+
const maxPriceFilter = filter["actualPriceRange.maxValue.amount"];
|
|
337
|
+
if (typeof maxPriceFilter === "object" &&
|
|
338
|
+
maxPriceFilter !== null &&
|
|
339
|
+
"$lte" in maxPriceFilter) {
|
|
340
|
+
return Number(maxPriceFilter.$lte) || 0;
|
|
341
|
+
}
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
function getSelectedInventoryStatuses(searchOptions) {
|
|
345
|
+
const filter = searchOptions.filter;
|
|
346
|
+
if (!filter)
|
|
347
|
+
return [];
|
|
348
|
+
const inventoryFilter = filter["inventory.availabilityStatus"];
|
|
349
|
+
if (typeof inventoryFilter === "string" && inventoryFilter.length > 0) {
|
|
350
|
+
return [inventoryFilter];
|
|
351
|
+
}
|
|
352
|
+
if (typeof inventoryFilter === "object" &&
|
|
353
|
+
inventoryFilter !== null &&
|
|
354
|
+
"$in" in inventoryFilter) {
|
|
355
|
+
return Array.isArray(inventoryFilter.$in) ? inventoryFilter.$in : [];
|
|
356
|
+
}
|
|
357
|
+
return [];
|
|
358
|
+
}
|
|
359
|
+
function getSelectedProductOptions(searchOptions) {
|
|
360
|
+
const filter = searchOptions.filter;
|
|
361
|
+
if (!filter)
|
|
362
|
+
return {};
|
|
363
|
+
const selectedOptions = {};
|
|
364
|
+
// Look for options.{optionId}.choice filters
|
|
365
|
+
Object.keys(filter).forEach((key) => {
|
|
366
|
+
if (key.startsWith("options.") && key.endsWith(".choice")) {
|
|
367
|
+
const optionId = key.slice(8, -7); // Remove "options." and ".choice"
|
|
368
|
+
const optionFilter = filter[key];
|
|
369
|
+
if (typeof optionFilter === "string" && optionFilter.length > 0) {
|
|
370
|
+
selectedOptions[optionId] = [optionFilter];
|
|
371
|
+
}
|
|
372
|
+
else if (typeof optionFilter === "object" &&
|
|
373
|
+
optionFilter !== null &&
|
|
374
|
+
"$in" in optionFilter &&
|
|
375
|
+
Array.isArray(optionFilter.$in)) {
|
|
376
|
+
selectedOptions[optionId] = optionFilter.$in;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
return selectedOptions;
|
|
381
|
+
}
|
|
382
|
+
function getAvailableProductOptions(aggregationData = [], customizations = []) {
|
|
383
|
+
// Helper function to match aggregation names case-insensitively
|
|
384
|
+
const matchesAggregationName = (name, aggregationNames) => {
|
|
385
|
+
return aggregationNames.some((aggName) => aggName.toLowerCase() === name.toLowerCase());
|
|
386
|
+
};
|
|
387
|
+
// Helper function to sort choices intelligently (numbers first, then alphabetically)
|
|
388
|
+
const sortChoicesIntelligently = (choices) => {
|
|
389
|
+
return [...choices].sort((a, b) => {
|
|
390
|
+
const aIsNumber = /^\d+$/.test(a.name);
|
|
391
|
+
const bIsNumber = /^\d+$/.test(b.name);
|
|
392
|
+
if (aIsNumber && bIsNumber) {
|
|
393
|
+
return parseInt(b.name) - parseInt(a.name);
|
|
394
|
+
}
|
|
395
|
+
if (aIsNumber && !bIsNumber)
|
|
396
|
+
return -1;
|
|
397
|
+
if (!aIsNumber && bIsNumber)
|
|
398
|
+
return 1;
|
|
399
|
+
return a.name.localeCompare(b.name);
|
|
400
|
+
});
|
|
401
|
+
};
|
|
402
|
+
// Extract option names from aggregation data
|
|
403
|
+
const optionNames = [];
|
|
404
|
+
const choiceNames = [];
|
|
405
|
+
// Process aggregation results to extract available option and choice names
|
|
406
|
+
aggregationData.forEach((result) => {
|
|
407
|
+
if (result.name === "optionNames" && result.values?.results) {
|
|
408
|
+
optionNames.push(...result.values.results
|
|
409
|
+
.map((item) => item.value)
|
|
410
|
+
.filter((value) => typeof value === "string"));
|
|
411
|
+
}
|
|
412
|
+
if (result.name === "choiceNames" && result.values?.results) {
|
|
413
|
+
choiceNames.push(...result.values.results
|
|
414
|
+
.map((item) => item.value)
|
|
415
|
+
.filter((value) => typeof value === "string"));
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
// Build options by matching customizations with aggregation data
|
|
419
|
+
const options = customizations
|
|
420
|
+
.filter((customization) => customization.name &&
|
|
421
|
+
customization._id &&
|
|
422
|
+
customization.customizationType ===
|
|
423
|
+
customizationsV3.CustomizationType.PRODUCT_OPTION &&
|
|
424
|
+
(optionNames.length === 0 ||
|
|
425
|
+
matchesAggregationName(customization.name, optionNames)))
|
|
426
|
+
.map((customization) => {
|
|
427
|
+
const choices = (customization.choicesSettings?.choices || [])
|
|
428
|
+
.filter((choice) => choice._id &&
|
|
429
|
+
choice.name &&
|
|
430
|
+
(choiceNames.length === 0 ||
|
|
431
|
+
matchesAggregationName(choice.name, choiceNames)))
|
|
432
|
+
.map((choice) => ({
|
|
433
|
+
id: choice._id,
|
|
434
|
+
name: choice.name,
|
|
435
|
+
colorCode: choice.colorCode,
|
|
436
|
+
}));
|
|
437
|
+
return {
|
|
438
|
+
id: customization._id,
|
|
439
|
+
name: customization.name,
|
|
440
|
+
choices: sortChoicesIntelligently(choices),
|
|
441
|
+
optionRenderType: customization.customizationRenderType,
|
|
442
|
+
};
|
|
443
|
+
})
|
|
444
|
+
.filter((option) => option.choices.length > 0);
|
|
445
|
+
return options;
|
|
446
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { type Signal } from "@wix/services-definitions/core-services/signals";
|
|
2
|
+
/**
|
|
3
|
+
* Service definition for the Products List Pagination service.
|
|
4
|
+
* This defines the reactive API contract for managing product list pagination state and navigation.
|
|
5
|
+
*
|
|
6
|
+
* @constant
|
|
7
|
+
*/
|
|
8
|
+
export declare const ProductsListPaginationServiceDefinition: string & {
|
|
9
|
+
__api: {
|
|
10
|
+
/** Reactive signal containing the current page size limit */
|
|
11
|
+
currentLimit: Signal<number>;
|
|
12
|
+
/** Reactive signal containing the current cursor for pagination */
|
|
13
|
+
currentCursor: Signal<string | null>;
|
|
14
|
+
/** Computed signal indicating if there's a next page available */
|
|
15
|
+
hasNextPage: {
|
|
16
|
+
get: () => boolean;
|
|
17
|
+
};
|
|
18
|
+
/** Computed signal indicating if there's a previous page available */
|
|
19
|
+
hasPrevPage: {
|
|
20
|
+
get: () => boolean;
|
|
21
|
+
};
|
|
22
|
+
/** Function to set the page size limit */
|
|
23
|
+
setLimit: (limit: number) => void;
|
|
24
|
+
/** Function to navigate to the next page */
|
|
25
|
+
nextPage: () => void;
|
|
26
|
+
/** Function to navigate to the previous page */
|
|
27
|
+
prevPage: () => void;
|
|
28
|
+
/** Function to navigate to the first page */
|
|
29
|
+
goToFirstPage: () => void;
|
|
30
|
+
/** Function to load more items (increase the limit) */
|
|
31
|
+
loadMore: (count: number) => void;
|
|
32
|
+
};
|
|
33
|
+
__config: {};
|
|
34
|
+
isServiceDefinition?: boolean;
|
|
35
|
+
} & {
|
|
36
|
+
/** Reactive signal containing the current page size limit */
|
|
37
|
+
currentLimit: Signal<number>;
|
|
38
|
+
/** Reactive signal containing the current cursor for pagination */
|
|
39
|
+
currentCursor: Signal<string | null>;
|
|
40
|
+
/** Computed signal indicating if there's a next page available */
|
|
41
|
+
hasNextPage: {
|
|
42
|
+
get: () => boolean;
|
|
43
|
+
};
|
|
44
|
+
/** Computed signal indicating if there's a previous page available */
|
|
45
|
+
hasPrevPage: {
|
|
46
|
+
get: () => boolean;
|
|
47
|
+
};
|
|
48
|
+
/** Function to set the page size limit */
|
|
49
|
+
setLimit: (limit: number) => void;
|
|
50
|
+
/** Function to navigate to the next page */
|
|
51
|
+
nextPage: () => void;
|
|
52
|
+
/** Function to navigate to the previous page */
|
|
53
|
+
prevPage: () => void;
|
|
54
|
+
/** Function to navigate to the first page */
|
|
55
|
+
goToFirstPage: () => void;
|
|
56
|
+
/** Function to load more items (increase the limit) */
|
|
57
|
+
loadMore: (count: number) => void;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Configuration interface for the Products List Pagination service.
|
|
61
|
+
* Currently empty as this service doesn't require initial configuration.
|
|
62
|
+
*
|
|
63
|
+
* @interface ProductsListPaginationServiceConfig
|
|
64
|
+
*/
|
|
65
|
+
export type ProductsListPaginationServiceConfig = {};
|
|
66
|
+
/**
|
|
67
|
+
* Implementation of the Products List Pagination service that manages reactive pagination state.
|
|
68
|
+
* This service provides signals for pagination state and automatically updates the products list
|
|
69
|
+
* search options when pagination settings change. It supports both cursor-based pagination
|
|
70
|
+
* and load-more functionality.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```tsx
|
|
74
|
+
* import { ProductsListPaginationService, ProductsListPaginationServiceDefinition } from '@wix/stores/services';
|
|
75
|
+
* import { useService } from '@wix/services-manager-react';
|
|
76
|
+
*
|
|
77
|
+
* function PaginationComponent() {
|
|
78
|
+
* return (
|
|
79
|
+
* <ServiceProvider services={createServicesMap([
|
|
80
|
+
* [ProductsListPaginationServiceDefinition, ProductsListPaginationService.withConfig({})]
|
|
81
|
+
* ])}>
|
|
82
|
+
* <PaginationControls />
|
|
83
|
+
* </ServiceProvider>
|
|
84
|
+
* );
|
|
85
|
+
* }
|
|
86
|
+
*
|
|
87
|
+
* function PaginationControls() {
|
|
88
|
+
* const paginationService = useService(ProductsListPaginationServiceDefinition);
|
|
89
|
+
* const currentLimit = paginationService.currentLimit.get();
|
|
90
|
+
* const hasNextPage = paginationService.hasNextPage.get();
|
|
91
|
+
* const hasPrevPage = paginationService.hasPrevPage.get();
|
|
92
|
+
*
|
|
93
|
+
* return (
|
|
94
|
+
* <div>
|
|
95
|
+
* <div>
|
|
96
|
+
* Items per page:
|
|
97
|
+
* <select
|
|
98
|
+
* value={currentLimit}
|
|
99
|
+
* onChange={(e) => paginationService.setLimit(parseInt(e.target.value))}
|
|
100
|
+
* >
|
|
101
|
+
* <option value={12}>12</option>
|
|
102
|
+
* <option value={24}>24</option>
|
|
103
|
+
* <option value={48}>48</option>
|
|
104
|
+
* </select>
|
|
105
|
+
* </div>
|
|
106
|
+
*
|
|
107
|
+
* <div>
|
|
108
|
+
* <button
|
|
109
|
+
* onClick={() => paginationService.goToFirstPage()}
|
|
110
|
+
* disabled={!hasPrevPage}
|
|
111
|
+
* >
|
|
112
|
+
* First
|
|
113
|
+
* </button>
|
|
114
|
+
* <button
|
|
115
|
+
* onClick={() => paginationService.prevPage()}
|
|
116
|
+
* disabled={!hasPrevPage}
|
|
117
|
+
* >
|
|
118
|
+
* Previous
|
|
119
|
+
* </button>
|
|
120
|
+
* <button
|
|
121
|
+
* onClick={() => paginationService.nextPage()}
|
|
122
|
+
* disabled={!hasNextPage}
|
|
123
|
+
* >
|
|
124
|
+
* Next
|
|
125
|
+
* </button>
|
|
126
|
+
* </div>
|
|
127
|
+
*
|
|
128
|
+
* <button onClick={() => paginationService.loadMore(12)}>
|
|
129
|
+
* Load More
|
|
130
|
+
* </button>
|
|
131
|
+
* </div>
|
|
132
|
+
* );
|
|
133
|
+
* }
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export declare const ProductsListPaginationService: import("@wix/services-definitions").ServiceFactory<string & {
|
|
137
|
+
__api: {
|
|
138
|
+
/** Reactive signal containing the current page size limit */
|
|
139
|
+
currentLimit: Signal<number>;
|
|
140
|
+
/** Reactive signal containing the current cursor for pagination */
|
|
141
|
+
currentCursor: Signal<string | null>;
|
|
142
|
+
/** Computed signal indicating if there's a next page available */
|
|
143
|
+
hasNextPage: {
|
|
144
|
+
get: () => boolean;
|
|
145
|
+
};
|
|
146
|
+
/** Computed signal indicating if there's a previous page available */
|
|
147
|
+
hasPrevPage: {
|
|
148
|
+
get: () => boolean;
|
|
149
|
+
};
|
|
150
|
+
/** Function to set the page size limit */
|
|
151
|
+
setLimit: (limit: number) => void;
|
|
152
|
+
/** Function to navigate to the next page */
|
|
153
|
+
nextPage: () => void;
|
|
154
|
+
/** Function to navigate to the previous page */
|
|
155
|
+
prevPage: () => void;
|
|
156
|
+
/** Function to navigate to the first page */
|
|
157
|
+
goToFirstPage: () => void;
|
|
158
|
+
/** Function to load more items (increase the limit) */
|
|
159
|
+
loadMore: (count: number) => void;
|
|
160
|
+
};
|
|
161
|
+
__config: {};
|
|
162
|
+
isServiceDefinition?: boolean;
|
|
163
|
+
} & {
|
|
164
|
+
/** Reactive signal containing the current page size limit */
|
|
165
|
+
currentLimit: Signal<number>;
|
|
166
|
+
/** Reactive signal containing the current cursor for pagination */
|
|
167
|
+
currentCursor: Signal<string | null>;
|
|
168
|
+
/** Computed signal indicating if there's a next page available */
|
|
169
|
+
hasNextPage: {
|
|
170
|
+
get: () => boolean;
|
|
171
|
+
};
|
|
172
|
+
/** Computed signal indicating if there's a previous page available */
|
|
173
|
+
hasPrevPage: {
|
|
174
|
+
get: () => boolean;
|
|
175
|
+
};
|
|
176
|
+
/** Function to set the page size limit */
|
|
177
|
+
setLimit: (limit: number) => void;
|
|
178
|
+
/** Function to navigate to the next page */
|
|
179
|
+
nextPage: () => void;
|
|
180
|
+
/** Function to navigate to the previous page */
|
|
181
|
+
prevPage: () => void;
|
|
182
|
+
/** Function to navigate to the first page */
|
|
183
|
+
goToFirstPage: () => void;
|
|
184
|
+
/** Function to load more items (increase the limit) */
|
|
185
|
+
loadMore: (count: number) => void;
|
|
186
|
+
}, ProductsListPaginationServiceConfig>;
|