@wix/headless-stores 0.0.42 → 0.0.44
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/ProductListFilters.d.ts +10 -0
- package/cjs/dist/react/ProductListFilters.js +8 -0
- package/cjs/dist/services/products-list-search-service.d.ts +23 -14
- package/cjs/dist/services/products-list-search-service.js +59 -17
- package/dist/react/ProductListFilters.d.ts +10 -0
- package/dist/react/ProductListFilters.js +8 -0
- package/dist/services/products-list-search-service.d.ts +23 -14
- package/dist/services/products-list-search-service.js +59 -17
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { type ProductOption, InventoryStatusType } from "../services/products-list-search-service.js";
|
|
3
|
+
import { Category } from "@wix/auto_sdk_categories_categories";
|
|
3
4
|
/**
|
|
4
5
|
* Props for InventoryStatus headless component
|
|
5
6
|
*/
|
|
@@ -168,6 +169,15 @@ export interface PriceRangeRenderProps {
|
|
|
168
169
|
* ```
|
|
169
170
|
*/
|
|
170
171
|
export declare function PriceRange(props: PriceRangeProps): ReactNode;
|
|
172
|
+
export interface CategoryFilterRenderProps {
|
|
173
|
+
selectedCategory: Category | null;
|
|
174
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
175
|
+
}
|
|
176
|
+
export interface CategoryFilterProps {
|
|
177
|
+
/** Content to display (can be a render function receiving category data or ReactNode) */
|
|
178
|
+
children: ((props: CategoryFilterRenderProps) => ReactNode) | ReactNode;
|
|
179
|
+
}
|
|
180
|
+
export declare function CategoryFilter(props: CategoryFilterProps): ReactNode;
|
|
171
181
|
/**
|
|
172
182
|
* Props for ProductOptions headless component
|
|
173
183
|
*/
|
|
@@ -148,6 +148,14 @@ export function PriceRange(props) {
|
|
|
148
148
|
})
|
|
149
149
|
: props.children;
|
|
150
150
|
}
|
|
151
|
+
export function CategoryFilter(props) {
|
|
152
|
+
const service = useService(ProductsListSearchServiceDefinition);
|
|
153
|
+
const selectedCategory = service.selectedCategory.get();
|
|
154
|
+
const setSelectedCategory = service.setSelectedCategory;
|
|
155
|
+
return typeof props.children === "function"
|
|
156
|
+
? props.children({ selectedCategory, setSelectedCategory })
|
|
157
|
+
: props.children;
|
|
158
|
+
}
|
|
151
159
|
/**
|
|
152
160
|
* Headless component that renders content for each product option in the list.
|
|
153
161
|
* Maps over all available product options and provides each option through a render prop.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Signal } from "@wix/services-definitions/core-services/signals";
|
|
2
2
|
import { productsV3, customizationsV3 } from "@wix/stores";
|
|
3
|
+
import { type Category } from "./category-service.js";
|
|
3
4
|
import { SortType } from "./../enums/sort-enums.js";
|
|
4
5
|
export { SortType } from "./../enums/sort-enums.js";
|
|
5
6
|
/**
|
|
@@ -42,7 +43,7 @@ type InitialSearchState = {
|
|
|
42
43
|
};
|
|
43
44
|
inventoryStatuses?: InventoryStatusType[];
|
|
44
45
|
productOptions?: Record<string, string[]>;
|
|
45
|
-
category?:
|
|
46
|
+
category?: Category;
|
|
46
47
|
visible?: boolean;
|
|
47
48
|
productType?: string;
|
|
48
49
|
};
|
|
@@ -83,13 +84,15 @@ export declare const ProductsListSearchServiceDefinition: string & {
|
|
|
83
84
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
84
85
|
availableProductOptions: Signal<ProductOption[]>;
|
|
85
86
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
86
|
-
selectedCategory: Signal<
|
|
87
|
+
selectedCategory: Signal<Category | null>;
|
|
87
88
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
88
89
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
89
90
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
90
91
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
91
|
-
setSelectedCategory: (category:
|
|
92
|
-
isFiltered:
|
|
92
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
93
|
+
isFiltered: {
|
|
94
|
+
get: () => boolean;
|
|
95
|
+
};
|
|
93
96
|
reset: () => void;
|
|
94
97
|
};
|
|
95
98
|
__config: {};
|
|
@@ -119,13 +122,15 @@ export declare const ProductsListSearchServiceDefinition: string & {
|
|
|
119
122
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
120
123
|
availableProductOptions: Signal<ProductOption[]>;
|
|
121
124
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
122
|
-
selectedCategory: Signal<
|
|
125
|
+
selectedCategory: Signal<Category | null>;
|
|
123
126
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
124
127
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
125
128
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
126
129
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
127
|
-
setSelectedCategory: (category:
|
|
128
|
-
isFiltered:
|
|
130
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
131
|
+
isFiltered: {
|
|
132
|
+
get: () => boolean;
|
|
133
|
+
};
|
|
129
134
|
reset: () => void;
|
|
130
135
|
};
|
|
131
136
|
/**
|
|
@@ -135,7 +140,7 @@ export declare function convertUrlSortToSortType(urlSort: string): SortType | nu
|
|
|
135
140
|
/**
|
|
136
141
|
* Parse URL and build complete search options with all filters, sort, and pagination
|
|
137
142
|
*/
|
|
138
|
-
export declare function parseUrlForProductsListSearch(url: string, defaultSearchOptions?: productsV3.V3ProductSearch): Promise<{
|
|
143
|
+
export declare function parseUrlForProductsListSearch(url: string, categoriesList: Category[], defaultSearchOptions?: productsV3.V3ProductSearch): Promise<{
|
|
139
144
|
searchOptions: productsV3.V3ProductSearch;
|
|
140
145
|
initialSearchState: InitialSearchState;
|
|
141
146
|
}>;
|
|
@@ -172,13 +177,15 @@ export declare const ProductsListSearchService: import("@wix/services-definition
|
|
|
172
177
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
173
178
|
availableProductOptions: Signal<ProductOption[]>;
|
|
174
179
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
175
|
-
selectedCategory: Signal<
|
|
180
|
+
selectedCategory: Signal<Category | null>;
|
|
176
181
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
177
182
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
178
183
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
179
184
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
180
|
-
setSelectedCategory: (category:
|
|
181
|
-
isFiltered:
|
|
185
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
186
|
+
isFiltered: {
|
|
187
|
+
get: () => boolean;
|
|
188
|
+
};
|
|
182
189
|
reset: () => void;
|
|
183
190
|
};
|
|
184
191
|
__config: {};
|
|
@@ -208,12 +215,14 @@ export declare const ProductsListSearchService: import("@wix/services-definition
|
|
|
208
215
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
209
216
|
availableProductOptions: Signal<ProductOption[]>;
|
|
210
217
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
211
|
-
selectedCategory: Signal<
|
|
218
|
+
selectedCategory: Signal<Category | null>;
|
|
212
219
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
213
220
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
214
221
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
215
222
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
216
|
-
setSelectedCategory: (category:
|
|
217
|
-
isFiltered:
|
|
223
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
224
|
+
isFiltered: {
|
|
225
|
+
get: () => boolean;
|
|
226
|
+
};
|
|
218
227
|
reset: () => void;
|
|
219
228
|
}, ProductsListSearchServiceConfig>;
|
|
@@ -2,6 +2,7 @@ import { defineService, implementService } from "@wix/services-definitions";
|
|
|
2
2
|
import { SignalsServiceDefinition } from "@wix/services-definitions/core-services/signals";
|
|
3
3
|
import { DEFAULT_QUERY_LIMIT, ProductsListServiceDefinition, } from "./products-list-service.js";
|
|
4
4
|
import { productsV3, customizationsV3 } from "@wix/stores";
|
|
5
|
+
import { loadCategoriesListServiceConfig } from "./categories-list-service.js";
|
|
5
6
|
const PRICE_FILTER_DEBOUNCE_TIME = 300;
|
|
6
7
|
import { SortType } from "./../enums/sort-enums.js";
|
|
7
8
|
export { SortType } from "./../enums/sort-enums.js";
|
|
@@ -63,7 +64,7 @@ export function convertUrlSortToSortType(urlSort) {
|
|
|
63
64
|
function updateUrlWithSearchState(searchState) {
|
|
64
65
|
if (typeof window === "undefined")
|
|
65
66
|
return;
|
|
66
|
-
const { sort, filters, customizations, catalogBounds } = searchState;
|
|
67
|
+
const { sort, filters, customizations, catalogBounds, categorySlug } = searchState;
|
|
67
68
|
// Convert filter IDs back to human-readable names for URL
|
|
68
69
|
const humanReadableOptions = {};
|
|
69
70
|
for (const [optionId, choiceIds] of Object.entries(filters?.productOptions ?? {})) {
|
|
@@ -91,10 +92,10 @@ function updateUrlWithSearchState(searchState) {
|
|
|
91
92
|
"minPrice",
|
|
92
93
|
"maxPrice",
|
|
93
94
|
"inventoryStatus",
|
|
94
|
-
"category",
|
|
95
95
|
"visible",
|
|
96
96
|
"productType",
|
|
97
97
|
// Product option names will be dynamically added below
|
|
98
|
+
// Note: category is NOT included here as it's handled in the URL path
|
|
98
99
|
];
|
|
99
100
|
// Remove existing search parameters first
|
|
100
101
|
searchParams.forEach((param) => params.delete(param));
|
|
@@ -124,10 +125,6 @@ function updateUrlWithSearchState(searchState) {
|
|
|
124
125
|
if (filters.inventoryStatuses && filters.inventoryStatuses.length > 0) {
|
|
125
126
|
params.set("inventoryStatus", filters.inventoryStatuses.join(","));
|
|
126
127
|
}
|
|
127
|
-
// Add category filter
|
|
128
|
-
if (filters.category) {
|
|
129
|
-
params.set("category", filters.category);
|
|
130
|
-
}
|
|
131
128
|
// Add visibility filter (only if explicitly false, since true is default)
|
|
132
129
|
if (filters.visible === false) {
|
|
133
130
|
params.set("visible", "false");
|
|
@@ -142,8 +139,15 @@ function updateUrlWithSearchState(searchState) {
|
|
|
142
139
|
params.set(optionName, values.join(","));
|
|
143
140
|
}
|
|
144
141
|
}
|
|
142
|
+
// Handle URL path construction with category
|
|
143
|
+
let baseUrl = window.location.pathname;
|
|
144
|
+
// If categorySlug is provided, update the path
|
|
145
|
+
if (categorySlug) {
|
|
146
|
+
if (categorySlug) {
|
|
147
|
+
baseUrl = `/category/${categorySlug}`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
145
150
|
// Build the new URL
|
|
146
|
-
const baseUrl = window.location.pathname;
|
|
147
151
|
const newUrl = params.toString()
|
|
148
152
|
? `${baseUrl}?${params.toString()}`
|
|
149
153
|
: baseUrl;
|
|
@@ -155,7 +159,7 @@ function updateUrlWithSearchState(searchState) {
|
|
|
155
159
|
/**
|
|
156
160
|
* Parse URL and build complete search options with all filters, sort, and pagination
|
|
157
161
|
*/
|
|
158
|
-
export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
|
|
162
|
+
export async function parseUrlForProductsListSearch(url, categoriesList, defaultSearchOptions) {
|
|
159
163
|
const urlObj = new URL(url);
|
|
160
164
|
const searchParams = urlObj.searchParams;
|
|
161
165
|
// Get customizations for product option parsing
|
|
@@ -171,6 +175,21 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
|
|
|
171
175
|
};
|
|
172
176
|
// Initialize search state for service
|
|
173
177
|
const initialSearchState = {};
|
|
178
|
+
// Extract category slug from URL path (e.g., /category/category-slug)
|
|
179
|
+
const pathSegments = urlObj.pathname.split("/");
|
|
180
|
+
const categoryIndex = pathSegments.findIndex((segment) => segment === "category");
|
|
181
|
+
let categorySlug = null;
|
|
182
|
+
let category = undefined;
|
|
183
|
+
if (categoryIndex !== -1 && categoryIndex + 1 < pathSegments.length) {
|
|
184
|
+
categorySlug = pathSegments[categoryIndex + 1] || null;
|
|
185
|
+
// Find the category by slug from the provided categories list
|
|
186
|
+
if (categorySlug) {
|
|
187
|
+
category = categoriesList.find((cat) => cat.slug === categorySlug);
|
|
188
|
+
if (category) {
|
|
189
|
+
initialSearchState.category = category;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
174
193
|
// Handle text search (q parameter)
|
|
175
194
|
const query = searchParams.get("q");
|
|
176
195
|
if (query) {
|
|
@@ -252,12 +271,11 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
|
|
|
252
271
|
filter["productType"] = productType;
|
|
253
272
|
initialSearchState.productType = productType;
|
|
254
273
|
}
|
|
255
|
-
|
|
274
|
+
// Add category filter if found
|
|
256
275
|
if (category) {
|
|
257
276
|
filter["allCategoriesInfo.categories"] = {
|
|
258
|
-
$matchItems: [{ _id: { $in: [category] } }],
|
|
277
|
+
$matchItems: [{ _id: { $in: [category._id] } }],
|
|
259
278
|
};
|
|
260
|
-
initialSearchState.category = category;
|
|
261
279
|
}
|
|
262
280
|
// Price range filtering
|
|
263
281
|
const minPrice = searchParams.get("minPrice");
|
|
@@ -285,7 +303,6 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
|
|
|
285
303
|
"maxPrice",
|
|
286
304
|
"inventory_status",
|
|
287
305
|
"inventoryStatus",
|
|
288
|
-
"category",
|
|
289
306
|
"visible",
|
|
290
307
|
"productType",
|
|
291
308
|
"q",
|
|
@@ -378,7 +395,9 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
|
|
|
378
395
|
* Load search service configuration from URL
|
|
379
396
|
*/
|
|
380
397
|
export async function loadProductsListSearchServiceConfig(url) {
|
|
381
|
-
|
|
398
|
+
// Load categories using the categories service
|
|
399
|
+
const categoriesListConfig = await loadCategoriesListServiceConfig();
|
|
400
|
+
const { initialSearchState } = await parseUrlForProductsListSearch(url, categoriesListConfig.categories);
|
|
382
401
|
const { items: customizations = [] } = await customizationsV3
|
|
383
402
|
.queryCustomizations()
|
|
384
403
|
.find();
|
|
@@ -419,7 +438,31 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
419
438
|
const selectedCategorySignal = signalsService.signal(initialSearchState?.category || null);
|
|
420
439
|
const selectedVisibleSignal = signalsService.signal(initialSearchState?.visible ?? null);
|
|
421
440
|
const selectedProductTypeSignal = signalsService.signal(initialSearchState?.productType || null);
|
|
422
|
-
|
|
441
|
+
// Computed signal to check if any filters are applied
|
|
442
|
+
const isFilteredSignal = signalsService.computed(() => {
|
|
443
|
+
const catalogPriceRange = getCatalogPriceRange(productsListService.aggregations.get()?.results || []);
|
|
444
|
+
const minPrice = userFilterMinPriceSignal.get();
|
|
445
|
+
const maxPrice = userFilterMaxPriceSignal.get();
|
|
446
|
+
const selectedInventoryStatuses = selectedInventoryStatusesSignal.get();
|
|
447
|
+
const selectedProductOptions = selectedProductOptionsSignal.get();
|
|
448
|
+
const selectedCategory = selectedCategorySignal.get();
|
|
449
|
+
const selectedVisible = selectedVisibleSignal.get();
|
|
450
|
+
const selectedProductType = selectedProductTypeSignal.get();
|
|
451
|
+
// Check if any filters are different from default values
|
|
452
|
+
const hasPriceFilter = minPrice > catalogPriceRange.minPrice ||
|
|
453
|
+
maxPrice < catalogPriceRange.maxPrice;
|
|
454
|
+
const hasInventoryFilter = selectedInventoryStatuses.length > 0;
|
|
455
|
+
const hasProductOptionsFilter = Object.keys(selectedProductOptions).length > 0;
|
|
456
|
+
const hasCategoryFilter = selectedCategory !== null;
|
|
457
|
+
const hasVisibilityFilter = selectedVisible !== null;
|
|
458
|
+
const hasProductTypeFilter = selectedProductType !== null;
|
|
459
|
+
return (hasPriceFilter ||
|
|
460
|
+
hasInventoryFilter ||
|
|
461
|
+
hasProductOptionsFilter ||
|
|
462
|
+
hasCategoryFilter ||
|
|
463
|
+
hasVisibilityFilter ||
|
|
464
|
+
hasProductTypeFilter);
|
|
465
|
+
});
|
|
423
466
|
// Computed signals for pagination
|
|
424
467
|
const hasNextPageSignal = signalsService.computed(() => {
|
|
425
468
|
const pagingMetadata = productsListService.pagingMetadata.get();
|
|
@@ -550,7 +593,7 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
550
593
|
}
|
|
551
594
|
if (selectedCategory) {
|
|
552
595
|
newSearchOptions.filter["allCategoriesInfo.categories"] = {
|
|
553
|
-
$matchItems: [{ _id: { $in: [selectedCategory] } }],
|
|
596
|
+
$matchItems: [{ _id: { $in: [selectedCategory._id] } }],
|
|
554
597
|
};
|
|
555
598
|
}
|
|
556
599
|
if (selectedVisible !== null) {
|
|
@@ -571,7 +614,6 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
571
614
|
priceRange: { min: minPrice, max: maxPrice },
|
|
572
615
|
inventoryStatuses: selectedInventoryStatuses,
|
|
573
616
|
productOptions: selectedProductOptions,
|
|
574
|
-
...(selectedCategory && { category: selectedCategory }),
|
|
575
617
|
...(selectedVisible !== null && { visible: selectedVisible }),
|
|
576
618
|
...(selectedProductType && { productType: selectedProductType }),
|
|
577
619
|
};
|
|
@@ -580,6 +622,7 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
580
622
|
filters: currentFilters,
|
|
581
623
|
customizations,
|
|
582
624
|
catalogBounds,
|
|
625
|
+
categorySlug: selectedCategory?.slug || undefined,
|
|
583
626
|
});
|
|
584
627
|
});
|
|
585
628
|
}
|
|
@@ -702,7 +745,6 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
702
745
|
selectedCategorySignal.set(null);
|
|
703
746
|
selectedVisibleSignal.set(null);
|
|
704
747
|
selectedProductTypeSignal.set(null);
|
|
705
|
-
isFilteredSignal.set(false);
|
|
706
748
|
},
|
|
707
749
|
};
|
|
708
750
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { type ProductOption, InventoryStatusType } from "../services/products-list-search-service.js";
|
|
3
|
+
import { Category } from "@wix/auto_sdk_categories_categories";
|
|
3
4
|
/**
|
|
4
5
|
* Props for InventoryStatus headless component
|
|
5
6
|
*/
|
|
@@ -168,6 +169,15 @@ export interface PriceRangeRenderProps {
|
|
|
168
169
|
* ```
|
|
169
170
|
*/
|
|
170
171
|
export declare function PriceRange(props: PriceRangeProps): ReactNode;
|
|
172
|
+
export interface CategoryFilterRenderProps {
|
|
173
|
+
selectedCategory: Category | null;
|
|
174
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
175
|
+
}
|
|
176
|
+
export interface CategoryFilterProps {
|
|
177
|
+
/** Content to display (can be a render function receiving category data or ReactNode) */
|
|
178
|
+
children: ((props: CategoryFilterRenderProps) => ReactNode) | ReactNode;
|
|
179
|
+
}
|
|
180
|
+
export declare function CategoryFilter(props: CategoryFilterProps): ReactNode;
|
|
171
181
|
/**
|
|
172
182
|
* Props for ProductOptions headless component
|
|
173
183
|
*/
|
|
@@ -148,6 +148,14 @@ export function PriceRange(props) {
|
|
|
148
148
|
})
|
|
149
149
|
: props.children;
|
|
150
150
|
}
|
|
151
|
+
export function CategoryFilter(props) {
|
|
152
|
+
const service = useService(ProductsListSearchServiceDefinition);
|
|
153
|
+
const selectedCategory = service.selectedCategory.get();
|
|
154
|
+
const setSelectedCategory = service.setSelectedCategory;
|
|
155
|
+
return typeof props.children === "function"
|
|
156
|
+
? props.children({ selectedCategory, setSelectedCategory })
|
|
157
|
+
: props.children;
|
|
158
|
+
}
|
|
151
159
|
/**
|
|
152
160
|
* Headless component that renders content for each product option in the list.
|
|
153
161
|
* Maps over all available product options and provides each option through a render prop.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Signal } from "@wix/services-definitions/core-services/signals";
|
|
2
2
|
import { productsV3, customizationsV3 } from "@wix/stores";
|
|
3
|
+
import { type Category } from "./category-service.js";
|
|
3
4
|
import { SortType } from "./../enums/sort-enums.js";
|
|
4
5
|
export { SortType } from "./../enums/sort-enums.js";
|
|
5
6
|
/**
|
|
@@ -42,7 +43,7 @@ type InitialSearchState = {
|
|
|
42
43
|
};
|
|
43
44
|
inventoryStatuses?: InventoryStatusType[];
|
|
44
45
|
productOptions?: Record<string, string[]>;
|
|
45
|
-
category?:
|
|
46
|
+
category?: Category;
|
|
46
47
|
visible?: boolean;
|
|
47
48
|
productType?: string;
|
|
48
49
|
};
|
|
@@ -83,13 +84,15 @@ export declare const ProductsListSearchServiceDefinition: string & {
|
|
|
83
84
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
84
85
|
availableProductOptions: Signal<ProductOption[]>;
|
|
85
86
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
86
|
-
selectedCategory: Signal<
|
|
87
|
+
selectedCategory: Signal<Category | null>;
|
|
87
88
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
88
89
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
89
90
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
90
91
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
91
|
-
setSelectedCategory: (category:
|
|
92
|
-
isFiltered:
|
|
92
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
93
|
+
isFiltered: {
|
|
94
|
+
get: () => boolean;
|
|
95
|
+
};
|
|
93
96
|
reset: () => void;
|
|
94
97
|
};
|
|
95
98
|
__config: {};
|
|
@@ -119,13 +122,15 @@ export declare const ProductsListSearchServiceDefinition: string & {
|
|
|
119
122
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
120
123
|
availableProductOptions: Signal<ProductOption[]>;
|
|
121
124
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
122
|
-
selectedCategory: Signal<
|
|
125
|
+
selectedCategory: Signal<Category | null>;
|
|
123
126
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
124
127
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
125
128
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
126
129
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
127
|
-
setSelectedCategory: (category:
|
|
128
|
-
isFiltered:
|
|
130
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
131
|
+
isFiltered: {
|
|
132
|
+
get: () => boolean;
|
|
133
|
+
};
|
|
129
134
|
reset: () => void;
|
|
130
135
|
};
|
|
131
136
|
/**
|
|
@@ -135,7 +140,7 @@ export declare function convertUrlSortToSortType(urlSort: string): SortType | nu
|
|
|
135
140
|
/**
|
|
136
141
|
* Parse URL and build complete search options with all filters, sort, and pagination
|
|
137
142
|
*/
|
|
138
|
-
export declare function parseUrlForProductsListSearch(url: string, defaultSearchOptions?: productsV3.V3ProductSearch): Promise<{
|
|
143
|
+
export declare function parseUrlForProductsListSearch(url: string, categoriesList: Category[], defaultSearchOptions?: productsV3.V3ProductSearch): Promise<{
|
|
139
144
|
searchOptions: productsV3.V3ProductSearch;
|
|
140
145
|
initialSearchState: InitialSearchState;
|
|
141
146
|
}>;
|
|
@@ -172,13 +177,15 @@ export declare const ProductsListSearchService: import("@wix/services-definition
|
|
|
172
177
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
173
178
|
availableProductOptions: Signal<ProductOption[]>;
|
|
174
179
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
175
|
-
selectedCategory: Signal<
|
|
180
|
+
selectedCategory: Signal<Category | null>;
|
|
176
181
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
177
182
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
178
183
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
179
184
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
180
|
-
setSelectedCategory: (category:
|
|
181
|
-
isFiltered:
|
|
185
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
186
|
+
isFiltered: {
|
|
187
|
+
get: () => boolean;
|
|
188
|
+
};
|
|
182
189
|
reset: () => void;
|
|
183
190
|
};
|
|
184
191
|
__config: {};
|
|
@@ -208,12 +215,14 @@ export declare const ProductsListSearchService: import("@wix/services-definition
|
|
|
208
215
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
209
216
|
availableProductOptions: Signal<ProductOption[]>;
|
|
210
217
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
211
|
-
selectedCategory: Signal<
|
|
218
|
+
selectedCategory: Signal<Category | null>;
|
|
212
219
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
213
220
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
214
221
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
215
222
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
216
|
-
setSelectedCategory: (category:
|
|
217
|
-
isFiltered:
|
|
223
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
224
|
+
isFiltered: {
|
|
225
|
+
get: () => boolean;
|
|
226
|
+
};
|
|
218
227
|
reset: () => void;
|
|
219
228
|
}, ProductsListSearchServiceConfig>;
|
|
@@ -2,6 +2,7 @@ import { defineService, implementService } from "@wix/services-definitions";
|
|
|
2
2
|
import { SignalsServiceDefinition } from "@wix/services-definitions/core-services/signals";
|
|
3
3
|
import { DEFAULT_QUERY_LIMIT, ProductsListServiceDefinition, } from "./products-list-service.js";
|
|
4
4
|
import { productsV3, customizationsV3 } from "@wix/stores";
|
|
5
|
+
import { loadCategoriesListServiceConfig } from "./categories-list-service.js";
|
|
5
6
|
const PRICE_FILTER_DEBOUNCE_TIME = 300;
|
|
6
7
|
import { SortType } from "./../enums/sort-enums.js";
|
|
7
8
|
export { SortType } from "./../enums/sort-enums.js";
|
|
@@ -63,7 +64,7 @@ export function convertUrlSortToSortType(urlSort) {
|
|
|
63
64
|
function updateUrlWithSearchState(searchState) {
|
|
64
65
|
if (typeof window === "undefined")
|
|
65
66
|
return;
|
|
66
|
-
const { sort, filters, customizations, catalogBounds } = searchState;
|
|
67
|
+
const { sort, filters, customizations, catalogBounds, categorySlug } = searchState;
|
|
67
68
|
// Convert filter IDs back to human-readable names for URL
|
|
68
69
|
const humanReadableOptions = {};
|
|
69
70
|
for (const [optionId, choiceIds] of Object.entries(filters?.productOptions ?? {})) {
|
|
@@ -91,10 +92,10 @@ function updateUrlWithSearchState(searchState) {
|
|
|
91
92
|
"minPrice",
|
|
92
93
|
"maxPrice",
|
|
93
94
|
"inventoryStatus",
|
|
94
|
-
"category",
|
|
95
95
|
"visible",
|
|
96
96
|
"productType",
|
|
97
97
|
// Product option names will be dynamically added below
|
|
98
|
+
// Note: category is NOT included here as it's handled in the URL path
|
|
98
99
|
];
|
|
99
100
|
// Remove existing search parameters first
|
|
100
101
|
searchParams.forEach((param) => params.delete(param));
|
|
@@ -124,10 +125,6 @@ function updateUrlWithSearchState(searchState) {
|
|
|
124
125
|
if (filters.inventoryStatuses && filters.inventoryStatuses.length > 0) {
|
|
125
126
|
params.set("inventoryStatus", filters.inventoryStatuses.join(","));
|
|
126
127
|
}
|
|
127
|
-
// Add category filter
|
|
128
|
-
if (filters.category) {
|
|
129
|
-
params.set("category", filters.category);
|
|
130
|
-
}
|
|
131
128
|
// Add visibility filter (only if explicitly false, since true is default)
|
|
132
129
|
if (filters.visible === false) {
|
|
133
130
|
params.set("visible", "false");
|
|
@@ -142,8 +139,15 @@ function updateUrlWithSearchState(searchState) {
|
|
|
142
139
|
params.set(optionName, values.join(","));
|
|
143
140
|
}
|
|
144
141
|
}
|
|
142
|
+
// Handle URL path construction with category
|
|
143
|
+
let baseUrl = window.location.pathname;
|
|
144
|
+
// If categorySlug is provided, update the path
|
|
145
|
+
if (categorySlug) {
|
|
146
|
+
if (categorySlug) {
|
|
147
|
+
baseUrl = `/category/${categorySlug}`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
145
150
|
// Build the new URL
|
|
146
|
-
const baseUrl = window.location.pathname;
|
|
147
151
|
const newUrl = params.toString()
|
|
148
152
|
? `${baseUrl}?${params.toString()}`
|
|
149
153
|
: baseUrl;
|
|
@@ -155,7 +159,7 @@ function updateUrlWithSearchState(searchState) {
|
|
|
155
159
|
/**
|
|
156
160
|
* Parse URL and build complete search options with all filters, sort, and pagination
|
|
157
161
|
*/
|
|
158
|
-
export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
|
|
162
|
+
export async function parseUrlForProductsListSearch(url, categoriesList, defaultSearchOptions) {
|
|
159
163
|
const urlObj = new URL(url);
|
|
160
164
|
const searchParams = urlObj.searchParams;
|
|
161
165
|
// Get customizations for product option parsing
|
|
@@ -171,6 +175,21 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
|
|
|
171
175
|
};
|
|
172
176
|
// Initialize search state for service
|
|
173
177
|
const initialSearchState = {};
|
|
178
|
+
// Extract category slug from URL path (e.g., /category/category-slug)
|
|
179
|
+
const pathSegments = urlObj.pathname.split("/");
|
|
180
|
+
const categoryIndex = pathSegments.findIndex((segment) => segment === "category");
|
|
181
|
+
let categorySlug = null;
|
|
182
|
+
let category = undefined;
|
|
183
|
+
if (categoryIndex !== -1 && categoryIndex + 1 < pathSegments.length) {
|
|
184
|
+
categorySlug = pathSegments[categoryIndex + 1] || null;
|
|
185
|
+
// Find the category by slug from the provided categories list
|
|
186
|
+
if (categorySlug) {
|
|
187
|
+
category = categoriesList.find((cat) => cat.slug === categorySlug);
|
|
188
|
+
if (category) {
|
|
189
|
+
initialSearchState.category = category;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
174
193
|
// Handle text search (q parameter)
|
|
175
194
|
const query = searchParams.get("q");
|
|
176
195
|
if (query) {
|
|
@@ -252,12 +271,11 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
|
|
|
252
271
|
filter["productType"] = productType;
|
|
253
272
|
initialSearchState.productType = productType;
|
|
254
273
|
}
|
|
255
|
-
|
|
274
|
+
// Add category filter if found
|
|
256
275
|
if (category) {
|
|
257
276
|
filter["allCategoriesInfo.categories"] = {
|
|
258
|
-
$matchItems: [{ _id: { $in: [category] } }],
|
|
277
|
+
$matchItems: [{ _id: { $in: [category._id] } }],
|
|
259
278
|
};
|
|
260
|
-
initialSearchState.category = category;
|
|
261
279
|
}
|
|
262
280
|
// Price range filtering
|
|
263
281
|
const minPrice = searchParams.get("minPrice");
|
|
@@ -285,7 +303,6 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
|
|
|
285
303
|
"maxPrice",
|
|
286
304
|
"inventory_status",
|
|
287
305
|
"inventoryStatus",
|
|
288
|
-
"category",
|
|
289
306
|
"visible",
|
|
290
307
|
"productType",
|
|
291
308
|
"q",
|
|
@@ -378,7 +395,9 @@ export async function parseUrlForProductsListSearch(url, defaultSearchOptions) {
|
|
|
378
395
|
* Load search service configuration from URL
|
|
379
396
|
*/
|
|
380
397
|
export async function loadProductsListSearchServiceConfig(url) {
|
|
381
|
-
|
|
398
|
+
// Load categories using the categories service
|
|
399
|
+
const categoriesListConfig = await loadCategoriesListServiceConfig();
|
|
400
|
+
const { initialSearchState } = await parseUrlForProductsListSearch(url, categoriesListConfig.categories);
|
|
382
401
|
const { items: customizations = [] } = await customizationsV3
|
|
383
402
|
.queryCustomizations()
|
|
384
403
|
.find();
|
|
@@ -419,7 +438,31 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
419
438
|
const selectedCategorySignal = signalsService.signal(initialSearchState?.category || null);
|
|
420
439
|
const selectedVisibleSignal = signalsService.signal(initialSearchState?.visible ?? null);
|
|
421
440
|
const selectedProductTypeSignal = signalsService.signal(initialSearchState?.productType || null);
|
|
422
|
-
|
|
441
|
+
// Computed signal to check if any filters are applied
|
|
442
|
+
const isFilteredSignal = signalsService.computed(() => {
|
|
443
|
+
const catalogPriceRange = getCatalogPriceRange(productsListService.aggregations.get()?.results || []);
|
|
444
|
+
const minPrice = userFilterMinPriceSignal.get();
|
|
445
|
+
const maxPrice = userFilterMaxPriceSignal.get();
|
|
446
|
+
const selectedInventoryStatuses = selectedInventoryStatusesSignal.get();
|
|
447
|
+
const selectedProductOptions = selectedProductOptionsSignal.get();
|
|
448
|
+
const selectedCategory = selectedCategorySignal.get();
|
|
449
|
+
const selectedVisible = selectedVisibleSignal.get();
|
|
450
|
+
const selectedProductType = selectedProductTypeSignal.get();
|
|
451
|
+
// Check if any filters are different from default values
|
|
452
|
+
const hasPriceFilter = minPrice > catalogPriceRange.minPrice ||
|
|
453
|
+
maxPrice < catalogPriceRange.maxPrice;
|
|
454
|
+
const hasInventoryFilter = selectedInventoryStatuses.length > 0;
|
|
455
|
+
const hasProductOptionsFilter = Object.keys(selectedProductOptions).length > 0;
|
|
456
|
+
const hasCategoryFilter = selectedCategory !== null;
|
|
457
|
+
const hasVisibilityFilter = selectedVisible !== null;
|
|
458
|
+
const hasProductTypeFilter = selectedProductType !== null;
|
|
459
|
+
return (hasPriceFilter ||
|
|
460
|
+
hasInventoryFilter ||
|
|
461
|
+
hasProductOptionsFilter ||
|
|
462
|
+
hasCategoryFilter ||
|
|
463
|
+
hasVisibilityFilter ||
|
|
464
|
+
hasProductTypeFilter);
|
|
465
|
+
});
|
|
423
466
|
// Computed signals for pagination
|
|
424
467
|
const hasNextPageSignal = signalsService.computed(() => {
|
|
425
468
|
const pagingMetadata = productsListService.pagingMetadata.get();
|
|
@@ -550,7 +593,7 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
550
593
|
}
|
|
551
594
|
if (selectedCategory) {
|
|
552
595
|
newSearchOptions.filter["allCategoriesInfo.categories"] = {
|
|
553
|
-
$matchItems: [{ _id: { $in: [selectedCategory] } }],
|
|
596
|
+
$matchItems: [{ _id: { $in: [selectedCategory._id] } }],
|
|
554
597
|
};
|
|
555
598
|
}
|
|
556
599
|
if (selectedVisible !== null) {
|
|
@@ -571,7 +614,6 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
571
614
|
priceRange: { min: minPrice, max: maxPrice },
|
|
572
615
|
inventoryStatuses: selectedInventoryStatuses,
|
|
573
616
|
productOptions: selectedProductOptions,
|
|
574
|
-
...(selectedCategory && { category: selectedCategory }),
|
|
575
617
|
...(selectedVisible !== null && { visible: selectedVisible }),
|
|
576
618
|
...(selectedProductType && { productType: selectedProductType }),
|
|
577
619
|
};
|
|
@@ -580,6 +622,7 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
580
622
|
filters: currentFilters,
|
|
581
623
|
customizations,
|
|
582
624
|
catalogBounds,
|
|
625
|
+
categorySlug: selectedCategory?.slug || undefined,
|
|
583
626
|
});
|
|
584
627
|
});
|
|
585
628
|
}
|
|
@@ -702,7 +745,6 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
702
745
|
selectedCategorySignal.set(null);
|
|
703
746
|
selectedVisibleSignal.set(null);
|
|
704
747
|
selectedProductTypeSignal.set(null);
|
|
705
|
-
isFilteredSignal.set(false);
|
|
706
748
|
},
|
|
707
749
|
};
|
|
708
750
|
});
|