@wix/headless-stores 0.0.42 → 0.0.43
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 +9 -0
- package/cjs/dist/react/ProductListFilters.js +7 -0
- package/cjs/dist/services/products-list-search-service.d.ts +11 -10
- package/cjs/dist/services/products-list-search-service.js +34 -15
- package/dist/react/ProductListFilters.d.ts +9 -0
- package/dist/react/ProductListFilters.js +7 -0
- package/dist/services/products-list-search-service.d.ts +11 -10
- package/dist/services/products-list-search-service.js +34 -15
- 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,14 @@ 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
|
+
}
|
|
175
|
+
export interface CategoryFilterProps {
|
|
176
|
+
/** Content to display (can be a render function receiving category data or ReactNode) */
|
|
177
|
+
children: ((props: CategoryFilterRenderProps) => ReactNode) | ReactNode;
|
|
178
|
+
}
|
|
179
|
+
export declare function CategoryFilter(props: CategoryFilterProps): ReactNode;
|
|
171
180
|
/**
|
|
172
181
|
* Props for ProductOptions headless component
|
|
173
182
|
*/
|
|
@@ -148,6 +148,13 @@ 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
|
+
return typeof props.children === "function"
|
|
155
|
+
? props.children({ selectedCategory })
|
|
156
|
+
: props.children;
|
|
157
|
+
}
|
|
151
158
|
/**
|
|
152
159
|
* Headless component that renders content for each product option in the list.
|
|
153
160
|
* 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,12 +84,12 @@ 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
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
92
93
|
isFiltered: Signal<boolean>;
|
|
93
94
|
reset: () => void;
|
|
94
95
|
};
|
|
@@ -119,12 +120,12 @@ export declare const ProductsListSearchServiceDefinition: string & {
|
|
|
119
120
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
120
121
|
availableProductOptions: Signal<ProductOption[]>;
|
|
121
122
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
122
|
-
selectedCategory: Signal<
|
|
123
|
+
selectedCategory: Signal<Category | null>;
|
|
123
124
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
124
125
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
125
126
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
126
127
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
127
|
-
setSelectedCategory: (category:
|
|
128
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
128
129
|
isFiltered: Signal<boolean>;
|
|
129
130
|
reset: () => void;
|
|
130
131
|
};
|
|
@@ -135,7 +136,7 @@ export declare function convertUrlSortToSortType(urlSort: string): SortType | nu
|
|
|
135
136
|
/**
|
|
136
137
|
* Parse URL and build complete search options with all filters, sort, and pagination
|
|
137
138
|
*/
|
|
138
|
-
export declare function parseUrlForProductsListSearch(url: string, defaultSearchOptions?: productsV3.V3ProductSearch): Promise<{
|
|
139
|
+
export declare function parseUrlForProductsListSearch(url: string, categoriesList: Category[], defaultSearchOptions?: productsV3.V3ProductSearch): Promise<{
|
|
139
140
|
searchOptions: productsV3.V3ProductSearch;
|
|
140
141
|
initialSearchState: InitialSearchState;
|
|
141
142
|
}>;
|
|
@@ -172,12 +173,12 @@ export declare const ProductsListSearchService: import("@wix/services-definition
|
|
|
172
173
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
173
174
|
availableProductOptions: Signal<ProductOption[]>;
|
|
174
175
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
175
|
-
selectedCategory: Signal<
|
|
176
|
+
selectedCategory: Signal<Category | null>;
|
|
176
177
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
177
178
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
178
179
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
179
180
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
180
|
-
setSelectedCategory: (category:
|
|
181
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
181
182
|
isFiltered: Signal<boolean>;
|
|
182
183
|
reset: () => void;
|
|
183
184
|
};
|
|
@@ -208,12 +209,12 @@ export declare const ProductsListSearchService: import("@wix/services-definition
|
|
|
208
209
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
209
210
|
availableProductOptions: Signal<ProductOption[]>;
|
|
210
211
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
211
|
-
selectedCategory: Signal<
|
|
212
|
+
selectedCategory: Signal<Category | null>;
|
|
212
213
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
213
214
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
214
215
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
215
216
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
216
|
-
setSelectedCategory: (category:
|
|
217
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
217
218
|
isFiltered: Signal<boolean>;
|
|
218
219
|
reset: () => void;
|
|
219
220
|
}, 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();
|
|
@@ -550,7 +569,7 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
550
569
|
}
|
|
551
570
|
if (selectedCategory) {
|
|
552
571
|
newSearchOptions.filter["allCategoriesInfo.categories"] = {
|
|
553
|
-
$matchItems: [{ _id: { $in: [selectedCategory] } }],
|
|
572
|
+
$matchItems: [{ _id: { $in: [selectedCategory._id] } }],
|
|
554
573
|
};
|
|
555
574
|
}
|
|
556
575
|
if (selectedVisible !== null) {
|
|
@@ -571,7 +590,6 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
571
590
|
priceRange: { min: minPrice, max: maxPrice },
|
|
572
591
|
inventoryStatuses: selectedInventoryStatuses,
|
|
573
592
|
productOptions: selectedProductOptions,
|
|
574
|
-
...(selectedCategory && { category: selectedCategory }),
|
|
575
593
|
...(selectedVisible !== null && { visible: selectedVisible }),
|
|
576
594
|
...(selectedProductType && { productType: selectedProductType }),
|
|
577
595
|
};
|
|
@@ -580,6 +598,7 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
580
598
|
filters: currentFilters,
|
|
581
599
|
customizations,
|
|
582
600
|
catalogBounds,
|
|
601
|
+
categorySlug: selectedCategory?.slug || undefined,
|
|
583
602
|
});
|
|
584
603
|
});
|
|
585
604
|
}
|
|
@@ -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,14 @@ 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
|
+
}
|
|
175
|
+
export interface CategoryFilterProps {
|
|
176
|
+
/** Content to display (can be a render function receiving category data or ReactNode) */
|
|
177
|
+
children: ((props: CategoryFilterRenderProps) => ReactNode) | ReactNode;
|
|
178
|
+
}
|
|
179
|
+
export declare function CategoryFilter(props: CategoryFilterProps): ReactNode;
|
|
171
180
|
/**
|
|
172
181
|
* Props for ProductOptions headless component
|
|
173
182
|
*/
|
|
@@ -148,6 +148,13 @@ 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
|
+
return typeof props.children === "function"
|
|
155
|
+
? props.children({ selectedCategory })
|
|
156
|
+
: props.children;
|
|
157
|
+
}
|
|
151
158
|
/**
|
|
152
159
|
* Headless component that renders content for each product option in the list.
|
|
153
160
|
* 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,12 +84,12 @@ 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
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
92
93
|
isFiltered: Signal<boolean>;
|
|
93
94
|
reset: () => void;
|
|
94
95
|
};
|
|
@@ -119,12 +120,12 @@ export declare const ProductsListSearchServiceDefinition: string & {
|
|
|
119
120
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
120
121
|
availableProductOptions: Signal<ProductOption[]>;
|
|
121
122
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
122
|
-
selectedCategory: Signal<
|
|
123
|
+
selectedCategory: Signal<Category | null>;
|
|
123
124
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
124
125
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
125
126
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
126
127
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
127
|
-
setSelectedCategory: (category:
|
|
128
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
128
129
|
isFiltered: Signal<boolean>;
|
|
129
130
|
reset: () => void;
|
|
130
131
|
};
|
|
@@ -135,7 +136,7 @@ export declare function convertUrlSortToSortType(urlSort: string): SortType | nu
|
|
|
135
136
|
/**
|
|
136
137
|
* Parse URL and build complete search options with all filters, sort, and pagination
|
|
137
138
|
*/
|
|
138
|
-
export declare function parseUrlForProductsListSearch(url: string, defaultSearchOptions?: productsV3.V3ProductSearch): Promise<{
|
|
139
|
+
export declare function parseUrlForProductsListSearch(url: string, categoriesList: Category[], defaultSearchOptions?: productsV3.V3ProductSearch): Promise<{
|
|
139
140
|
searchOptions: productsV3.V3ProductSearch;
|
|
140
141
|
initialSearchState: InitialSearchState;
|
|
141
142
|
}>;
|
|
@@ -172,12 +173,12 @@ export declare const ProductsListSearchService: import("@wix/services-definition
|
|
|
172
173
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
173
174
|
availableProductOptions: Signal<ProductOption[]>;
|
|
174
175
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
175
|
-
selectedCategory: Signal<
|
|
176
|
+
selectedCategory: Signal<Category | null>;
|
|
176
177
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
177
178
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
178
179
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
179
180
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
180
|
-
setSelectedCategory: (category:
|
|
181
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
181
182
|
isFiltered: Signal<boolean>;
|
|
182
183
|
reset: () => void;
|
|
183
184
|
};
|
|
@@ -208,12 +209,12 @@ export declare const ProductsListSearchService: import("@wix/services-definition
|
|
|
208
209
|
selectedInventoryStatuses: Signal<InventoryStatusType[]>;
|
|
209
210
|
availableProductOptions: Signal<ProductOption[]>;
|
|
210
211
|
selectedProductOptions: Signal<Record<string, string[]>>;
|
|
211
|
-
selectedCategory: Signal<
|
|
212
|
+
selectedCategory: Signal<Category | null>;
|
|
212
213
|
setSelectedMinPrice: (minPrice: number) => void;
|
|
213
214
|
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
214
215
|
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
215
216
|
toggleProductOption: (optionId: string, choiceId: string) => void;
|
|
216
|
-
setSelectedCategory: (category:
|
|
217
|
+
setSelectedCategory: (category: Category | null) => void;
|
|
217
218
|
isFiltered: Signal<boolean>;
|
|
218
219
|
reset: () => void;
|
|
219
220
|
}, 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();
|
|
@@ -550,7 +569,7 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
550
569
|
}
|
|
551
570
|
if (selectedCategory) {
|
|
552
571
|
newSearchOptions.filter["allCategoriesInfo.categories"] = {
|
|
553
|
-
$matchItems: [{ _id: { $in: [selectedCategory] } }],
|
|
572
|
+
$matchItems: [{ _id: { $in: [selectedCategory._id] } }],
|
|
554
573
|
};
|
|
555
574
|
}
|
|
556
575
|
if (selectedVisible !== null) {
|
|
@@ -571,7 +590,6 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
571
590
|
priceRange: { min: minPrice, max: maxPrice },
|
|
572
591
|
inventoryStatuses: selectedInventoryStatuses,
|
|
573
592
|
productOptions: selectedProductOptions,
|
|
574
|
-
...(selectedCategory && { category: selectedCategory }),
|
|
575
593
|
...(selectedVisible !== null && { visible: selectedVisible }),
|
|
576
594
|
...(selectedProductType && { productType: selectedProductType }),
|
|
577
595
|
};
|
|
@@ -580,6 +598,7 @@ export const ProductsListSearchService = implementService.withConfig()(ProductsL
|
|
|
580
598
|
filters: currentFilters,
|
|
581
599
|
customizations,
|
|
582
600
|
catalogBounds,
|
|
601
|
+
categorySlug: selectedCategory?.slug || undefined,
|
|
583
602
|
});
|
|
584
603
|
});
|
|
585
604
|
}
|