@jay-framework/wix-stores 0.15.1 → 0.15.5
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/README.md +157 -96
- package/dist/actions/get-variant-stock.jay-action +7 -0
- package/dist/actions/search-products.jay-action +12 -0
- package/dist/contracts/category-page.jay-contract.d.ts +10 -1
- package/dist/contracts/product-card.jay-contract +8 -2
- package/dist/contracts/product-card.jay-contract.d.ts +17 -4
- package/dist/contracts/product-options.jay-contract +0 -5
- package/dist/contracts/product-options.jay-contract.d.ts +1 -2
- package/dist/contracts/product-search.jay-contract +134 -1
- package/dist/contracts/product-search.jay-contract.d.ts +92 -4
- package/dist/index.client.js +276 -24
- package/dist/index.d.ts +230 -149
- package/dist/index.js +613 -156
- package/package.json +18 -16
- package/plugin.yaml +2 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { getCurrentCartClient } from "@jay-framework/wix-cart";
|
|
2
1
|
import { WIX_CART_CONTEXT, WIX_CART_SERVICE, cartIndicator, cartPage, provideWixCartContext, provideWixCartService } from "@jay-framework/wix-cart";
|
|
3
2
|
import { createJayService, makeJayQuery, ActionError, makeJayStackComponent, RenderPipeline, makeJayInit } from "@jay-framework/fullstack-component";
|
|
4
|
-
import { inventoryItemsV3, productsV3 } from "@wix/stores";
|
|
3
|
+
import { customizationsV3, inventoryItemsV3, productsV3 } from "@wix/stores";
|
|
5
4
|
import { categories } from "@wix/categories";
|
|
6
5
|
import { registerService, getService } from "@jay-framework/stack-server-runtime";
|
|
7
6
|
import { createJayContext } from "@jay-framework/runtime";
|
|
@@ -10,6 +9,11 @@ import { WIX_CLIENT_SERVICE } from "@jay-framework/wix-server-client";
|
|
|
10
9
|
import * as fs from "fs";
|
|
11
10
|
import * as path from "path";
|
|
12
11
|
import * as yaml from "js-yaml";
|
|
12
|
+
var OptionRenderType$2 = /* @__PURE__ */ ((OptionRenderType2) => {
|
|
13
|
+
OptionRenderType2[OptionRenderType2["TEXT_CHOICES"] = 0] = "TEXT_CHOICES";
|
|
14
|
+
OptionRenderType2[OptionRenderType2["SWATCH_CHOICES"] = 1] = "SWATCH_CHOICES";
|
|
15
|
+
return OptionRenderType2;
|
|
16
|
+
})(OptionRenderType$2 || {});
|
|
13
17
|
var CurrentSort = /* @__PURE__ */ ((CurrentSort2) => {
|
|
14
18
|
CurrentSort2[CurrentSort2["relevance"] = 0] = "relevance";
|
|
15
19
|
CurrentSort2[CurrentSort2["priceAsc"] = 1] = "priceAsc";
|
|
@@ -22,7 +26,8 @@ var CurrentSort = /* @__PURE__ */ ((CurrentSort2) => {
|
|
|
22
26
|
const instances = {
|
|
23
27
|
productsV3ClientInstance: void 0,
|
|
24
28
|
categoriesClientInstance: void 0,
|
|
25
|
-
inventoryV3ClientInstance: void 0
|
|
29
|
+
inventoryV3ClientInstance: void 0,
|
|
30
|
+
customizationsV3ClientInstance: void 0
|
|
26
31
|
};
|
|
27
32
|
function getProductsV3Client(wixClient) {
|
|
28
33
|
if (!instances.productsV3ClientInstance) {
|
|
@@ -42,15 +47,70 @@ function getInventoryClient(wixClient) {
|
|
|
42
47
|
}
|
|
43
48
|
return instances.inventoryV3ClientInstance;
|
|
44
49
|
}
|
|
50
|
+
function getCustomizationsV3Client(wixClient) {
|
|
51
|
+
if (!instances.customizationsV3ClientInstance) {
|
|
52
|
+
instances.customizationsV3ClientInstance = wixClient.use(customizationsV3);
|
|
53
|
+
}
|
|
54
|
+
return instances.customizationsV3ClientInstance;
|
|
55
|
+
}
|
|
45
56
|
const WIX_STORES_SERVICE_MARKER = createJayService("Wix Store Service");
|
|
46
57
|
function provideWixStoresService(wixClient, options) {
|
|
58
|
+
let cachedTree = null;
|
|
59
|
+
let cachedCustomizations = null;
|
|
60
|
+
const categoriesClient = getCategoriesClient(wixClient);
|
|
61
|
+
const customizationsClient = getCustomizationsV3Client(wixClient);
|
|
47
62
|
const service = {
|
|
48
63
|
products: getProductsV3Client(wixClient),
|
|
49
|
-
categories:
|
|
64
|
+
categories: categoriesClient,
|
|
50
65
|
inventory: getInventoryClient(wixClient),
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
66
|
+
customizations: customizationsClient,
|
|
67
|
+
urls: options?.urls ?? { product: "/products/{slug}", category: null },
|
|
68
|
+
defaultCategory: options?.defaultCategory ?? null,
|
|
69
|
+
async getCategoryTree() {
|
|
70
|
+
if (cachedTree) return cachedTree;
|
|
71
|
+
const slugMap = /* @__PURE__ */ new Map();
|
|
72
|
+
const parentMap = /* @__PURE__ */ new Map();
|
|
73
|
+
const rootIds = /* @__PURE__ */ new Set();
|
|
74
|
+
const imageMap = /* @__PURE__ */ new Map();
|
|
75
|
+
try {
|
|
76
|
+
const processItems = (items) => {
|
|
77
|
+
for (const cat of items) {
|
|
78
|
+
if (!cat._id || !cat.slug) continue;
|
|
79
|
+
slugMap.set(cat._id, cat.slug);
|
|
80
|
+
if (cat.parentCategory?._id) {
|
|
81
|
+
parentMap.set(cat._id, cat.parentCategory._id);
|
|
82
|
+
} else {
|
|
83
|
+
rootIds.add(cat._id);
|
|
84
|
+
}
|
|
85
|
+
const imageUrl = cat.media?.mainMedia?.image?.url || cat.media?.mainMedia?.url;
|
|
86
|
+
if (imageUrl) {
|
|
87
|
+
imageMap.set(cat._id, imageUrl);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
let result = await categoriesClient.queryCategories({ treeReference: { appNamespace: "@wix/stores" } }).eq("visible", true).limit(100).find();
|
|
92
|
+
processItems(result.items || []);
|
|
93
|
+
while (result.hasNext()) {
|
|
94
|
+
result = await result.next();
|
|
95
|
+
processItems(result.items || []);
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error("[wix-stores] Failed to build category tree:", error);
|
|
99
|
+
}
|
|
100
|
+
cachedTree = { slugMap, parentMap, rootIds, imageMap };
|
|
101
|
+
return cachedTree;
|
|
102
|
+
},
|
|
103
|
+
async getCustomizations() {
|
|
104
|
+
if (cachedCustomizations) return cachedCustomizations;
|
|
105
|
+
try {
|
|
106
|
+
const result = await customizationsClient.queryCustomizations().eq("customizationType", "PRODUCT_OPTION").limit(100).find();
|
|
107
|
+
cachedCustomizations = result.items || [];
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error("[wix-stores] Failed to load customizations:", error);
|
|
110
|
+
cachedCustomizations = [];
|
|
111
|
+
}
|
|
112
|
+
return cachedCustomizations;
|
|
113
|
+
}
|
|
54
114
|
};
|
|
55
115
|
registerService(WIX_STORES_SERVICE_MARKER, service);
|
|
56
116
|
return service;
|
|
@@ -80,7 +140,8 @@ var ProductType$1 = /* @__PURE__ */ ((ProductType2) => {
|
|
|
80
140
|
var QuickAddType = /* @__PURE__ */ ((QuickAddType2) => {
|
|
81
141
|
QuickAddType2[QuickAddType2["SIMPLE"] = 0] = "SIMPLE";
|
|
82
142
|
QuickAddType2[QuickAddType2["SINGLE_OPTION"] = 1] = "SINGLE_OPTION";
|
|
83
|
-
QuickAddType2[QuickAddType2["
|
|
143
|
+
QuickAddType2[QuickAddType2["COLOR_AND_TEXT_OPTIONS"] = 2] = "COLOR_AND_TEXT_OPTIONS";
|
|
144
|
+
QuickAddType2[QuickAddType2["NEEDS_CONFIGURATION"] = 3] = "NEEDS_CONFIGURATION";
|
|
84
145
|
return QuickAddType2;
|
|
85
146
|
})(QuickAddType || {});
|
|
86
147
|
var OptionRenderType$1 = /* @__PURE__ */ ((OptionRenderType2) => {
|
|
@@ -136,33 +197,54 @@ function formatWixMediaUrl(_id, url, resize) {
|
|
|
136
197
|
}
|
|
137
198
|
return "";
|
|
138
199
|
}
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
product.allCategoriesInfo.categories.map((c) => c._id).filter(Boolean)
|
|
145
|
-
);
|
|
146
|
-
for (const { categoryId, prefix } of prefixConfig) {
|
|
147
|
-
if (productCategoryIds.has(categoryId)) {
|
|
148
|
-
return prefix;
|
|
200
|
+
function findRootCategoryId(categoryId, tree) {
|
|
201
|
+
let current = categoryId;
|
|
202
|
+
for (let depth = 0; depth < 20; depth++) {
|
|
203
|
+
if (tree.rootIds.has(current)) {
|
|
204
|
+
return current;
|
|
149
205
|
}
|
|
206
|
+
const parentId = tree.parentMap.get(current);
|
|
207
|
+
if (!parentId) return current;
|
|
208
|
+
current = parentId;
|
|
150
209
|
}
|
|
151
|
-
return
|
|
210
|
+
return current;
|
|
152
211
|
}
|
|
153
|
-
function
|
|
154
|
-
|
|
155
|
-
|
|
212
|
+
function findRootCategorySlug(categoryId, tree) {
|
|
213
|
+
const rootId = findRootCategoryId(categoryId, tree);
|
|
214
|
+
return tree.slugMap.get(rootId) ?? "";
|
|
215
|
+
}
|
|
216
|
+
function findCategoryImage(categoryId, tree) {
|
|
217
|
+
let current = categoryId;
|
|
218
|
+
for (let depth = 0; depth < 20 && current; depth++) {
|
|
219
|
+
const image = tree.imageMap.get(current);
|
|
220
|
+
if (image) return image;
|
|
221
|
+
current = tree.parentMap.get(current);
|
|
156
222
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
223
|
+
return "";
|
|
224
|
+
}
|
|
225
|
+
function buildProductUrl(urls, tree, slug, mainCategoryId) {
|
|
226
|
+
let url = urls.product;
|
|
227
|
+
url = url.replace("{slug}", slug);
|
|
228
|
+
if (url.includes("{category}")) {
|
|
229
|
+
const categorySlug = tree.slugMap.get(mainCategoryId);
|
|
230
|
+
url = url.replace("{category}", categorySlug);
|
|
164
231
|
}
|
|
165
|
-
|
|
232
|
+
if (url.includes("{prefix}")) {
|
|
233
|
+
const prefixSlug = findRootCategorySlug(mainCategoryId, tree);
|
|
234
|
+
url = url.replace("{prefix}", prefixSlug);
|
|
235
|
+
}
|
|
236
|
+
return url;
|
|
237
|
+
}
|
|
238
|
+
function buildCategoryUrl(urls, tree, categorySlug, categoryId) {
|
|
239
|
+
if (!urls.category) return null;
|
|
240
|
+
let url = urls.category;
|
|
241
|
+
url = url.replace("{category}", categorySlug);
|
|
242
|
+
if (url.includes("{prefix}")) {
|
|
243
|
+
const prefixSlug = findRootCategorySlug(categoryId, tree);
|
|
244
|
+
if (!prefixSlug) return null;
|
|
245
|
+
url = url.replace("{prefix}", prefixSlug);
|
|
246
|
+
}
|
|
247
|
+
return url.includes("{") ? null : url;
|
|
166
248
|
}
|
|
167
249
|
function mapAvailabilityStatus(status) {
|
|
168
250
|
switch (status) {
|
|
@@ -198,7 +280,15 @@ function isValidPrice(amount) {
|
|
|
198
280
|
function getQuickAddType(product) {
|
|
199
281
|
const optionCount = product.options?.length ?? 0;
|
|
200
282
|
const hasModifiers = (product.modifiers?.length ?? 0) > 0;
|
|
201
|
-
if (hasModifiers || optionCount >
|
|
283
|
+
if (hasModifiers || optionCount > 2) {
|
|
284
|
+
return QuickAddType.NEEDS_CONFIGURATION;
|
|
285
|
+
}
|
|
286
|
+
if (optionCount === 2) {
|
|
287
|
+
const hasColor = product.options.some((o) => o.optionRenderType === "SWATCH_CHOICES");
|
|
288
|
+
const hasText = product.options.some((o) => o.optionRenderType === "TEXT_CHOICES");
|
|
289
|
+
if (hasColor && hasText) {
|
|
290
|
+
return QuickAddType.COLOR_AND_TEXT_OPTIONS;
|
|
291
|
+
}
|
|
202
292
|
return QuickAddType.NEEDS_CONFIGURATION;
|
|
203
293
|
}
|
|
204
294
|
if (optionCount === 1) {
|
|
@@ -214,31 +304,85 @@ function mapChoiceType(choiceType) {
|
|
|
214
304
|
}
|
|
215
305
|
function mapQuickOption(option, variantsInfo) {
|
|
216
306
|
if (!option) return null;
|
|
217
|
-
const optionId = option._id;
|
|
218
307
|
const choices = option.choicesSettings?.choices || [];
|
|
219
308
|
return {
|
|
220
|
-
_id:
|
|
309
|
+
_id: option._id || "",
|
|
221
310
|
name: option.name || "",
|
|
222
311
|
optionRenderType: mapOptionRenderType(option.optionRenderType),
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
isSelected: false
|
|
232
|
-
};
|
|
233
|
-
})
|
|
312
|
+
choices: choices.map((choice) => ({
|
|
313
|
+
choiceId: choice.choiceId || "",
|
|
314
|
+
name: choice.name || "",
|
|
315
|
+
choiceType: mapChoiceType(choice.choiceType),
|
|
316
|
+
colorCode: choice.colorCode || "",
|
|
317
|
+
inStock: choice.inStock ?? true,
|
|
318
|
+
isSelected: false
|
|
319
|
+
}))
|
|
234
320
|
};
|
|
235
321
|
}
|
|
236
|
-
|
|
237
|
-
|
|
322
|
+
function mapQuickAddOptions(product) {
|
|
323
|
+
const quickAddType = getQuickAddType(product);
|
|
324
|
+
if (quickAddType === QuickAddType.COLOR_AND_TEXT_OPTIONS) {
|
|
325
|
+
const colorOption = product.options.find((o) => o.optionRenderType === "SWATCH_CHOICES");
|
|
326
|
+
const textOption = product.options.find((o) => o.optionRenderType === "TEXT_CHOICES");
|
|
327
|
+
const quickOption = mapQuickOption(colorOption, product.variantsInfo);
|
|
328
|
+
const secondQuickOption = mapQuickOption(textOption, product.variantsInfo);
|
|
329
|
+
if (quickOption?.choices) {
|
|
330
|
+
const firstInStock = quickOption.choices.find((c) => c.inStock);
|
|
331
|
+
if (firstInStock) {
|
|
332
|
+
firstInStock.isSelected = true;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (secondQuickOption?.choices) {
|
|
336
|
+
for (const choice of secondQuickOption.choices) {
|
|
337
|
+
choice.inStock = false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return { quickAddType, quickOption, secondQuickOption };
|
|
341
|
+
}
|
|
342
|
+
if (quickAddType === QuickAddType.SINGLE_OPTION) {
|
|
343
|
+
return {
|
|
344
|
+
quickAddType,
|
|
345
|
+
quickOption: mapQuickOption(product.options?.[0], product.variantsInfo),
|
|
346
|
+
secondQuickOption: null
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
return { quickAddType, quickOption: null, secondQuickOption: null };
|
|
350
|
+
}
|
|
351
|
+
function buildVariantStockMap(product) {
|
|
352
|
+
const stockMap = {};
|
|
353
|
+
const options = product.options;
|
|
354
|
+
const variants = product.variantsInfo?.variants;
|
|
355
|
+
if (!options || options.length !== 2 || !variants) return stockMap;
|
|
356
|
+
const colorOption = options.find((o) => o.optionRenderType === "SWATCH_CHOICES");
|
|
357
|
+
const textOption = options.find((o) => o.optionRenderType === "TEXT_CHOICES");
|
|
358
|
+
if (!colorOption || !textOption) return stockMap;
|
|
359
|
+
const colorOptionId = colorOption._id || "";
|
|
360
|
+
const textOptionId = textOption._id || "";
|
|
361
|
+
const colorChoices = colorOption.choicesSettings?.choices || [];
|
|
362
|
+
const textChoices = textOption.choicesSettings?.choices || [];
|
|
363
|
+
for (const colorChoice of colorChoices) {
|
|
364
|
+
const cId = colorChoice.choiceId || "";
|
|
365
|
+
stockMap[cId] = {};
|
|
366
|
+
for (const textChoice of textChoices) {
|
|
367
|
+
const tId = textChoice.choiceId || "";
|
|
368
|
+
const variant = variants.find(
|
|
369
|
+
(v) => v.choices?.some(
|
|
370
|
+
(c) => c.optionChoiceIds?.optionId === colorOptionId && c.optionChoiceIds?.choiceId === cId
|
|
371
|
+
) && v.choices?.some(
|
|
372
|
+
(c) => c.optionChoiceIds?.optionId === textOptionId && c.optionChoiceIds?.choiceId === tId
|
|
373
|
+
)
|
|
374
|
+
);
|
|
375
|
+
stockMap[cId][tId] = variant?.inventoryStatus?.inStock ?? false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return stockMap;
|
|
379
|
+
}
|
|
380
|
+
function mapProductToCard(product, urls, tree) {
|
|
238
381
|
const mainMedia = product.media?.main;
|
|
239
382
|
const slug = product.slug || "";
|
|
240
|
-
const
|
|
241
|
-
const productUrl =
|
|
383
|
+
const mainCategoryId = product.mainCategoryId || "";
|
|
384
|
+
const productUrl = buildProductUrl(urls, tree, slug, mainCategoryId);
|
|
385
|
+
const categoryName = tree.slugMap.get(mainCategoryId);
|
|
242
386
|
const firstVariant = product.variantsInfo?.variants?.[0];
|
|
243
387
|
const variantPrice = firstVariant?.price;
|
|
244
388
|
const actualAmount = variantPrice?.actualPrice?.amount || product.actualPriceRange?.minValue?.amount || "0";
|
|
@@ -251,7 +395,7 @@ function mapProductToCard(product, productPagePath = DEFAULT_PRODUCT_PAGE_PATH,
|
|
|
251
395
|
name: product.name || "",
|
|
252
396
|
slug,
|
|
253
397
|
productUrl,
|
|
254
|
-
categoryPrefix:
|
|
398
|
+
categoryPrefix: categoryName,
|
|
255
399
|
mainMedia: {
|
|
256
400
|
url: mainMedia ? formatWixMediaUrl(mainMedia._id, mainMedia.url) : "",
|
|
257
401
|
altText: mainMedia?.altText || product.name || "",
|
|
@@ -263,7 +407,6 @@ function mapProductToCard(product, productPagePath = DEFAULT_PRODUCT_PAGE_PATH,
|
|
|
263
407
|
width: 300,
|
|
264
408
|
height: 300
|
|
265
409
|
},
|
|
266
|
-
// Simplified price fields
|
|
267
410
|
price: actualFormattedAmount,
|
|
268
411
|
strikethroughPrice: hasDiscount ? compareAtFormattedAmount : "",
|
|
269
412
|
hasDiscount,
|
|
@@ -282,11 +425,13 @@ function mapProductToCard(product, productPagePath = DEFAULT_PRODUCT_PAGE_PATH,
|
|
|
282
425
|
},
|
|
283
426
|
productType: mapProductType$1(product.productType),
|
|
284
427
|
isAddingToCart: false,
|
|
285
|
-
|
|
286
|
-
quickAddType: getQuickAddType(product),
|
|
287
|
-
quickOption: getQuickAddType(product) === QuickAddType.SINGLE_OPTION ? mapQuickOption(product.options?.[0], product.variantsInfo) : null
|
|
428
|
+
...mapQuickAddOptions(product)
|
|
288
429
|
};
|
|
289
430
|
}
|
|
431
|
+
function needsCategoryInfo(wixStores) {
|
|
432
|
+
const template = wixStores.urls.product;
|
|
433
|
+
return template.includes("{category}") || template.includes("{prefix}");
|
|
434
|
+
}
|
|
290
435
|
const PRICE_BUCKET_BOUNDARIES = [
|
|
291
436
|
0,
|
|
292
437
|
20,
|
|
@@ -307,6 +452,31 @@ const PRICE_BUCKETS = PRICE_BUCKET_BOUNDARIES.slice(0, -1).map((from, i) => ({
|
|
|
307
452
|
to: PRICE_BUCKET_BOUNDARIES[i + 1]
|
|
308
453
|
}));
|
|
309
454
|
PRICE_BUCKETS.push({ from: PRICE_BUCKET_BOUNDARIES[PRICE_BUCKET_BOUNDARIES.length - 1] });
|
|
455
|
+
function getAvailableProductOptions(aggResults, customizations) {
|
|
456
|
+
const optionNamesAgg = aggResults.find((a) => a.name === "optionNames")?.values;
|
|
457
|
+
const choiceNamesAgg = aggResults.find((a) => a.name === "choiceNames")?.values;
|
|
458
|
+
if (!optionNamesAgg?.results?.length || !choiceNamesAgg?.results?.length) {
|
|
459
|
+
return [];
|
|
460
|
+
}
|
|
461
|
+
const optionNames = new Set(optionNamesAgg.results.map((e) => e.value));
|
|
462
|
+
const choiceCounts = new Map(
|
|
463
|
+
choiceNamesAgg.results.map((e) => [e.value.toLowerCase(), e.count ?? 0])
|
|
464
|
+
);
|
|
465
|
+
return customizations.filter(
|
|
466
|
+
(c) => c.customizationType === "PRODUCT_OPTION" && c.name && optionNames.has(c.name) && (c.customizationRenderType === "TEXT_CHOICES" || c.customizationRenderType === "SWATCH_CHOICES")
|
|
467
|
+
).map((c) => ({
|
|
468
|
+
optionId: c._id || "",
|
|
469
|
+
optionName: c.name || "",
|
|
470
|
+
optionRenderType: c.customizationRenderType,
|
|
471
|
+
// Sort by product count descending (most used choices first)
|
|
472
|
+
choices: (c.choicesSettings?.choices || []).filter((ch) => ch.name && choiceCounts.has(ch.name.toLowerCase())).map((ch) => ({
|
|
473
|
+
choiceId: ch._id || "",
|
|
474
|
+
choiceName: ch.name || "",
|
|
475
|
+
colorCode: ch.colorCode || "",
|
|
476
|
+
productCount: choiceCounts.get(ch.name.toLowerCase()) ?? 0
|
|
477
|
+
})).sort((a, b) => b.productCount - a.productCount)
|
|
478
|
+
})).filter((o) => o.choices.length > 0);
|
|
479
|
+
}
|
|
310
480
|
const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX_STORES_SERVICE_MARKER).withHandler(
|
|
311
481
|
async (input, wixStores) => {
|
|
312
482
|
const { query, filters = {}, sortBy = "relevance", cursor, pageSize = 12 } = input;
|
|
@@ -339,6 +509,22 @@ const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX
|
|
|
339
509
|
}
|
|
340
510
|
});
|
|
341
511
|
}
|
|
512
|
+
if (filters.optionFilters && filters.optionFilters.length > 0) {
|
|
513
|
+
for (const optFilter of filters.optionFilters) {
|
|
514
|
+
if (optFilter.choiceNames.length > 0) {
|
|
515
|
+
filterConditions.push({
|
|
516
|
+
$and: [
|
|
517
|
+
{ "options.name": { $hasSome: [optFilter.optionName] } },
|
|
518
|
+
{
|
|
519
|
+
"options.choicesSettings.choices.name": {
|
|
520
|
+
$hasSome: optFilter.choiceNames
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
]
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
342
528
|
const filter = filterConditions.length === 1 ? filterConditions[0] : { $and: filterConditions };
|
|
343
529
|
const sort = [];
|
|
344
530
|
switch (sortBy) {
|
|
@@ -392,23 +578,43 @@ const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX
|
|
|
392
578
|
name: "max-price",
|
|
393
579
|
type: "SCALAR",
|
|
394
580
|
scalar: { type: "MAX" }
|
|
581
|
+
},
|
|
582
|
+
// Option names for option-based filtering
|
|
583
|
+
{
|
|
584
|
+
fieldPath: "options.name",
|
|
585
|
+
name: "optionNames",
|
|
586
|
+
type: "VALUE",
|
|
587
|
+
value: {
|
|
588
|
+
limit: 20,
|
|
589
|
+
sortType: "VALUE",
|
|
590
|
+
sortDirection: "DESC"
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
// Choice names for option-based filtering
|
|
594
|
+
{
|
|
595
|
+
fieldPath: "options.choicesSettings.choices.name",
|
|
596
|
+
name: "choiceNames",
|
|
597
|
+
type: "VALUE",
|
|
598
|
+
value: {
|
|
599
|
+
limit: 50,
|
|
600
|
+
sortType: "COUNT",
|
|
601
|
+
sortDirection: "DESC"
|
|
602
|
+
}
|
|
395
603
|
}
|
|
396
604
|
];
|
|
397
605
|
const searchResult = await wixStores.products.searchProducts(
|
|
398
606
|
{
|
|
399
607
|
filter,
|
|
400
|
-
// @ts-expect-error - Wix SDK types don't match actual API
|
|
401
608
|
sort: sort.length > 0 ? sort : void 0,
|
|
402
609
|
cursorPaging,
|
|
403
610
|
search,
|
|
404
|
-
// @ts-expect-error - Wix SDK types don't include aggregations
|
|
405
611
|
aggregations
|
|
406
612
|
},
|
|
407
613
|
{
|
|
408
614
|
fields: [
|
|
409
615
|
"CURRENCY",
|
|
410
616
|
"VARIANT_OPTION_CHOICE_NAMES",
|
|
411
|
-
...wixStores
|
|
617
|
+
...needsCategoryInfo(wixStores) ? ["ALL_CATEGORIES_INFO"] : []
|
|
412
618
|
]
|
|
413
619
|
}
|
|
414
620
|
);
|
|
@@ -448,9 +654,11 @@ const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX
|
|
|
448
654
|
};
|
|
449
655
|
});
|
|
450
656
|
priceRanges.push(...bucketRanges);
|
|
451
|
-
const
|
|
657
|
+
const customizations = await wixStores.getCustomizations();
|
|
658
|
+
const optionFilters = getAvailableProductOptions(aggResults, customizations);
|
|
659
|
+
const tree = await wixStores.getCategoryTree();
|
|
452
660
|
const mappedProducts = products.map(
|
|
453
|
-
(p) => mapProductToCard(p,
|
|
661
|
+
(p) => mapProductToCard(p, wixStores.urls, tree)
|
|
454
662
|
);
|
|
455
663
|
return {
|
|
456
664
|
products: mappedProducts,
|
|
@@ -461,7 +669,8 @@ const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX
|
|
|
461
669
|
minBound,
|
|
462
670
|
maxBound,
|
|
463
671
|
ranges: priceRanges
|
|
464
|
-
}
|
|
672
|
+
},
|
|
673
|
+
optionFilters
|
|
465
674
|
};
|
|
466
675
|
} catch (error) {
|
|
467
676
|
console.error("[wixStores.searchProducts] Search failed:", error);
|
|
@@ -476,11 +685,10 @@ const getProductBySlug = makeJayQuery("wixStores.getProductBySlug").withServices
|
|
|
476
685
|
throw new ActionError("INVALID_INPUT", "Product slug is required");
|
|
477
686
|
}
|
|
478
687
|
try {
|
|
479
|
-
const prefixConfig = wixStores.categoryPrefixes;
|
|
480
688
|
const fields = [
|
|
481
689
|
"MEDIA_ITEMS_INFO",
|
|
482
690
|
"VARIANT_OPTION_CHOICE_NAMES",
|
|
483
|
-
...
|
|
691
|
+
...needsCategoryInfo(wixStores) ? ["ALL_CATEGORIES_INFO"] : []
|
|
484
692
|
];
|
|
485
693
|
const result = await wixStores.products.getProductBySlug(slug, {
|
|
486
694
|
fields: [...fields]
|
|
@@ -489,13 +697,27 @@ const getProductBySlug = makeJayQuery("wixStores.getProductBySlug").withServices
|
|
|
489
697
|
if (!product) {
|
|
490
698
|
return null;
|
|
491
699
|
}
|
|
492
|
-
|
|
700
|
+
const tree = await wixStores.getCategoryTree();
|
|
701
|
+
return mapProductToCard(product, wixStores.urls, tree);
|
|
493
702
|
} catch (error) {
|
|
494
703
|
console.error("[wixStores.getProductBySlug] Failed to get product:", error);
|
|
495
704
|
return null;
|
|
496
705
|
}
|
|
497
706
|
}
|
|
498
707
|
);
|
|
708
|
+
const getVariantStock = makeJayQuery("wixStores.getVariantStock").withServices(WIX_STORES_SERVICE_MARKER).withHandler(
|
|
709
|
+
async (input, wixStores) => {
|
|
710
|
+
try {
|
|
711
|
+
const product = await wixStores.products.getProduct(input.productId, {
|
|
712
|
+
fields: ["VARIANT_OPTION_CHOICE_NAMES"]
|
|
713
|
+
});
|
|
714
|
+
return buildVariantStockMap(product);
|
|
715
|
+
} catch (error) {
|
|
716
|
+
console.error("[wixStores.getVariantStock] Failed:", error);
|
|
717
|
+
return {};
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
);
|
|
499
721
|
const getCategories = makeJayQuery("wixStores.getCategories").withServices(WIX_STORES_SERVICE_MARKER).withCaching({ maxAge: 3600 }).withHandler(
|
|
500
722
|
async (_input, wixStores) => {
|
|
501
723
|
try {
|
|
@@ -515,11 +737,193 @@ const getCategories = makeJayQuery("wixStores.getCategories").withServices(WIX_S
|
|
|
515
737
|
}
|
|
516
738
|
);
|
|
517
739
|
const PAGE_SIZE = 12;
|
|
740
|
+
function mapSortToAction(sort) {
|
|
741
|
+
switch (sort) {
|
|
742
|
+
case CurrentSort.priceAsc:
|
|
743
|
+
return "price_asc";
|
|
744
|
+
case CurrentSort.priceDesc:
|
|
745
|
+
return "price_desc";
|
|
746
|
+
case CurrentSort.newest:
|
|
747
|
+
return "newest";
|
|
748
|
+
case CurrentSort.nameAsc:
|
|
749
|
+
return "name_asc";
|
|
750
|
+
case CurrentSort.nameDesc:
|
|
751
|
+
return "name_desc";
|
|
752
|
+
default:
|
|
753
|
+
return "relevance";
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function parseUrlFilters(url) {
|
|
757
|
+
try {
|
|
758
|
+
const params = new URL(url, "http://x").searchParams;
|
|
759
|
+
const optionSelections = /* @__PURE__ */ new Map();
|
|
760
|
+
const optParam = params.get("opt");
|
|
761
|
+
if (optParam) {
|
|
762
|
+
for (const segment of optParam.split(";")) {
|
|
763
|
+
const colonIdx = segment.indexOf(":");
|
|
764
|
+
if (colonIdx === -1)
|
|
765
|
+
continue;
|
|
766
|
+
const name = decodeURIComponent(segment.slice(0, colonIdx));
|
|
767
|
+
const choices = segment.slice(colonIdx + 1).split(",").map((c) => decodeURIComponent(c)).filter(Boolean);
|
|
768
|
+
if (choices.length > 0) {
|
|
769
|
+
optionSelections.set(name, new Set(choices));
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return {
|
|
774
|
+
searchTerm: params.get("q") || "",
|
|
775
|
+
selectedCategorySlugs: params.get("cat")?.split(",").filter(Boolean) || [],
|
|
776
|
+
minPrice: params.has("min") ? Number(params.get("min")) : null,
|
|
777
|
+
maxPrice: params.has("max") ? Number(params.get("max")) : null,
|
|
778
|
+
inStockOnly: params.get("inStock") === "1",
|
|
779
|
+
sort: params.get("sort") || "relevance",
|
|
780
|
+
optionSelections
|
|
781
|
+
};
|
|
782
|
+
} catch {
|
|
783
|
+
return {
|
|
784
|
+
searchTerm: "",
|
|
785
|
+
selectedCategorySlugs: [],
|
|
786
|
+
minPrice: null,
|
|
787
|
+
maxPrice: null,
|
|
788
|
+
inStockOnly: false,
|
|
789
|
+
sort: "relevance",
|
|
790
|
+
optionSelections: /* @__PURE__ */ new Map()
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
function parseSortParam(sort) {
|
|
795
|
+
const sortMap = {
|
|
796
|
+
relevance: CurrentSort.relevance,
|
|
797
|
+
priceAsc: CurrentSort.priceAsc,
|
|
798
|
+
priceDesc: CurrentSort.priceDesc,
|
|
799
|
+
newest: CurrentSort.newest,
|
|
800
|
+
nameAsc: CurrentSort.nameAsc,
|
|
801
|
+
nameDesc: CurrentSort.nameDesc
|
|
802
|
+
};
|
|
803
|
+
return sortMap[sort] ?? CurrentSort.relevance;
|
|
804
|
+
}
|
|
805
|
+
function buildOptionFiltersViewState(baseOptionFilters, filteredResult, optionSelections) {
|
|
806
|
+
const filteredChoiceCounts = /* @__PURE__ */ new Map();
|
|
807
|
+
for (const opt of filteredResult.optionFilters || []) {
|
|
808
|
+
for (const ch of opt.choices) {
|
|
809
|
+
filteredChoiceCounts.set(ch.choiceName.toLowerCase(), ch.productCount);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return baseOptionFilters.map((opt) => ({
|
|
813
|
+
optionId: opt.optionId,
|
|
814
|
+
optionName: opt.optionName,
|
|
815
|
+
optionRenderType: opt.optionRenderType === "SWATCH_CHOICES" ? OptionRenderType$2.SWATCH_CHOICES : OptionRenderType$2.TEXT_CHOICES,
|
|
816
|
+
choices: opt.choices.map((ch) => {
|
|
817
|
+
const count = filteredChoiceCounts.get(ch.choiceName.toLowerCase()) ?? 0;
|
|
818
|
+
return {
|
|
819
|
+
choiceId: ch.choiceId,
|
|
820
|
+
choiceName: ch.choiceName,
|
|
821
|
+
colorCode: ch.colorCode,
|
|
822
|
+
productCount: count,
|
|
823
|
+
isSelected: optionSelections.get(opt.optionName)?.has(ch.choiceName) ?? false,
|
|
824
|
+
isDisabled: count === 0
|
|
825
|
+
};
|
|
826
|
+
})
|
|
827
|
+
}));
|
|
828
|
+
}
|
|
829
|
+
const EMPTY_CATEGORY_HEADER = {
|
|
830
|
+
name: "",
|
|
831
|
+
description: "",
|
|
832
|
+
imageUrl: "",
|
|
833
|
+
hasImage: false,
|
|
834
|
+
productCount: 0,
|
|
835
|
+
breadcrumbs: [],
|
|
836
|
+
seoData: { tags: [], settings: { preventAutoRedirect: false, keywords: [] } }
|
|
837
|
+
};
|
|
838
|
+
async function findCategoryBySlug(categoriesClient, slug) {
|
|
839
|
+
const result = await categoriesClient.queryCategories({ treeReference: { appNamespace: "@wix/stores" } }).eq("slug", slug).eq("visible", true).limit(1).find();
|
|
840
|
+
return result.items?.[0] ?? null;
|
|
841
|
+
}
|
|
842
|
+
async function loadCategoryDetails(categoriesClient, categoryId) {
|
|
843
|
+
try {
|
|
844
|
+
return await categoriesClient.getCategory(categoryId, { appNamespace: "@wix/stores" }, { fields: ["DESCRIPTION", "BREADCRUMBS_INFO"] });
|
|
845
|
+
} catch {
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
async function buildCategoryHeader(wixStoreService, category, categoryUrlTemplate) {
|
|
850
|
+
const details = await loadCategoryDetails(wixStoreService.categories, category._id);
|
|
851
|
+
const cat = details || category;
|
|
852
|
+
const imageUrl = cat.image || "";
|
|
853
|
+
const description = cat.description || "";
|
|
854
|
+
const categoryTree = await wixStoreService.getCategoryTree();
|
|
855
|
+
const breadcrumbs = (cat.breadcrumbsInfo?.breadcrumbs || []).map((b) => ({
|
|
856
|
+
categoryId: b.categoryId,
|
|
857
|
+
name: b.categoryName,
|
|
858
|
+
slug: b.categorySlug,
|
|
859
|
+
url: categoryUrlTemplate ? buildCategoryUrl(wixStoreService.urls, categoryTree, b.categorySlug, b.categoryId) : ""
|
|
860
|
+
}));
|
|
861
|
+
const seoData = cat.seoData ? {
|
|
862
|
+
tags: (cat.seoData.tags || []).map((tag, index) => ({
|
|
863
|
+
position: index.toString().padStart(2, "0"),
|
|
864
|
+
type: tag.type || "",
|
|
865
|
+
props: Object.entries(tag.props || {}).map(([key, value]) => ({
|
|
866
|
+
key,
|
|
867
|
+
value
|
|
868
|
+
})),
|
|
869
|
+
meta: Object.entries(tag.meta || {}).map(([key, value]) => ({
|
|
870
|
+
key,
|
|
871
|
+
value
|
|
872
|
+
})),
|
|
873
|
+
children: tag.children || ""
|
|
874
|
+
})),
|
|
875
|
+
settings: {
|
|
876
|
+
preventAutoRedirect: cat.seoData.settings?.preventAutoRedirect || false,
|
|
877
|
+
keywords: (cat.seoData.settings?.keywords || []).map((k) => ({
|
|
878
|
+
term: k.term || "",
|
|
879
|
+
isMain: k.isMain || false,
|
|
880
|
+
origin: k.origin || ""
|
|
881
|
+
}))
|
|
882
|
+
}
|
|
883
|
+
} : EMPTY_CATEGORY_HEADER.seoData;
|
|
884
|
+
let header = {
|
|
885
|
+
name: cat.name || "",
|
|
886
|
+
description,
|
|
887
|
+
imageUrl,
|
|
888
|
+
hasImage: !!imageUrl,
|
|
889
|
+
productCount: cat.itemCounter || 0,
|
|
890
|
+
breadcrumbs,
|
|
891
|
+
seoData
|
|
892
|
+
};
|
|
893
|
+
if ((!description || !imageUrl) && cat.parentCategory?._id) {
|
|
894
|
+
const parent = await loadCategoryDetails(wixStoreService.categories, cat.parentCategory._id);
|
|
895
|
+
if (parent) {
|
|
896
|
+
if (!header.description && parent.description) {
|
|
897
|
+
header = { ...header, description: parent.description };
|
|
898
|
+
}
|
|
899
|
+
if (!header.imageUrl) {
|
|
900
|
+
const parentImage = parent.image || "";
|
|
901
|
+
if (parentImage) {
|
|
902
|
+
header = { ...header, imageUrl: parentImage, hasImage: true };
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
return header;
|
|
908
|
+
}
|
|
518
909
|
async function renderSlowlyChanging$2(props, wixStores) {
|
|
519
910
|
const Pipeline = RenderPipeline.for();
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
const
|
|
911
|
+
const subcategorySlug = props.subcategory ?? null;
|
|
912
|
+
const categorySlug = props.category ?? null;
|
|
913
|
+
const defaultCategorySlug = wixStores.defaultCategory;
|
|
914
|
+
let activeCategory = null;
|
|
915
|
+
let baseCategoryId = null;
|
|
916
|
+
if (subcategorySlug) {
|
|
917
|
+
activeCategory = await findCategoryBySlug(wixStores.categories, subcategorySlug);
|
|
918
|
+
baseCategoryId = activeCategory?._id ?? null;
|
|
919
|
+
} else if (categorySlug) {
|
|
920
|
+
activeCategory = await findCategoryBySlug(wixStores.categories, categorySlug);
|
|
921
|
+
baseCategoryId = activeCategory?._id ?? null;
|
|
922
|
+
} else if (defaultCategorySlug) {
|
|
923
|
+
activeCategory = await findCategoryBySlug(wixStores.categories, defaultCategorySlug);
|
|
924
|
+
}
|
|
925
|
+
const tree = await wixStores.getCategoryTree();
|
|
926
|
+
const categoryHeader = activeCategory ? await buildCategoryHeader(wixStores, activeCategory, wixStores.urls.category) : EMPTY_CATEGORY_HEADER;
|
|
523
927
|
return Pipeline.try(async () => {
|
|
524
928
|
let query = wixStores.categories.queryCategories({
|
|
525
929
|
treeReference: { appNamespace: "@wix/stores" }
|
|
@@ -527,17 +931,35 @@ async function renderSlowlyChanging$2(props, wixStores) {
|
|
|
527
931
|
if (baseCategoryId) {
|
|
528
932
|
query = query.eq("parentCategory.id", baseCategoryId);
|
|
529
933
|
}
|
|
530
|
-
const
|
|
531
|
-
|
|
934
|
+
const baseCategoryIds = baseCategoryId ? [baseCategoryId] : [];
|
|
935
|
+
const [categoriesResult, productsResult] = await Promise.all([
|
|
936
|
+
query.find(),
|
|
937
|
+
searchProducts({
|
|
938
|
+
query: "",
|
|
939
|
+
filters: {
|
|
940
|
+
categoryIds: baseCategoryIds.length > 0 ? baseCategoryIds : void 0
|
|
941
|
+
},
|
|
942
|
+
pageSize: PAGE_SIZE
|
|
943
|
+
})
|
|
944
|
+
]);
|
|
945
|
+
return {
|
|
946
|
+
categories: categoriesResult.items || [],
|
|
947
|
+
productsResult
|
|
948
|
+
};
|
|
532
949
|
}).recover((error) => {
|
|
533
|
-
console.error("Failed to load categories:", error);
|
|
534
|
-
return Pipeline.ok(
|
|
535
|
-
|
|
950
|
+
console.error("Failed to load categories/products:", error);
|
|
951
|
+
return Pipeline.ok({
|
|
952
|
+
categories: [],
|
|
953
|
+
productsResult: null
|
|
954
|
+
});
|
|
955
|
+
}).toPhaseOutput(({ categories: categories2, productsResult }) => {
|
|
536
956
|
const categoryInfos = categories2.map((cat) => ({
|
|
537
957
|
categoryId: cat._id || "",
|
|
538
958
|
categoryName: cat.name || "",
|
|
539
|
-
categorySlug: cat.slug || ""
|
|
959
|
+
categorySlug: cat.slug || "",
|
|
960
|
+
categoryUrl: buildCategoryUrl(wixStores.urls, tree, cat.slug || "", cat._id || "") ?? ""
|
|
540
961
|
}));
|
|
962
|
+
const baseOptionFilters = productsResult?.optionFilters || [];
|
|
541
963
|
return {
|
|
542
964
|
viewState: {
|
|
543
965
|
searchFields: "name,description,sku",
|
|
@@ -547,24 +969,45 @@ async function renderSlowlyChanging$2(props, wixStores) {
|
|
|
547
969
|
categoryFilter: {
|
|
548
970
|
categories: categoryInfos
|
|
549
971
|
}
|
|
550
|
-
}
|
|
972
|
+
},
|
|
973
|
+
categoryHeader
|
|
551
974
|
},
|
|
552
975
|
carryForward: {
|
|
553
976
|
searchFields: "name,description,sku",
|
|
554
977
|
fuzzySearch: true,
|
|
555
978
|
categories: categoryInfos,
|
|
556
|
-
baseCategoryId
|
|
979
|
+
baseCategoryId,
|
|
980
|
+
preloadedResult: productsResult,
|
|
981
|
+
baseOptionFilters
|
|
557
982
|
}
|
|
558
983
|
};
|
|
559
984
|
});
|
|
560
985
|
}
|
|
561
986
|
async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
|
|
562
987
|
const Pipeline = RenderPipeline.for();
|
|
988
|
+
const urlFilters = parseUrlFilters(props.url);
|
|
989
|
+
const initialSort = parseSortParam(urlFilters.sort);
|
|
990
|
+
const initialCategoryIds = urlFilters.selectedCategorySlugs.map((slug) => slowCarryForward.categories.find((c) => c.categorySlug === slug)?.categoryId).filter(Boolean);
|
|
991
|
+
const initialOptionFilters = [];
|
|
992
|
+
for (const [optionName, choiceNames] of urlFilters.optionSelections) {
|
|
993
|
+
initialOptionFilters.push({ optionName, choiceNames: [...choiceNames] });
|
|
994
|
+
}
|
|
995
|
+
const hasActiveFilters = !!urlFilters.searchTerm || initialCategoryIds.length > 0 || urlFilters.minPrice !== null || urlFilters.maxPrice !== null || urlFilters.inStockOnly || initialSort !== CurrentSort.relevance || initialOptionFilters.length > 0;
|
|
563
996
|
return Pipeline.try(async () => {
|
|
564
|
-
|
|
997
|
+
if (!hasActiveFilters && slowCarryForward.preloadedResult) {
|
|
998
|
+
return slowCarryForward.preloadedResult;
|
|
999
|
+
}
|
|
1000
|
+
const baseCategoryIds = slowCarryForward.baseCategoryId ? [slowCarryForward.baseCategoryId, ...initialCategoryIds] : initialCategoryIds;
|
|
565
1001
|
const result = await searchProducts({
|
|
566
|
-
query: "",
|
|
567
|
-
filters:
|
|
1002
|
+
query: urlFilters.searchTerm || "",
|
|
1003
|
+
filters: {
|
|
1004
|
+
categoryIds: baseCategoryIds.length > 0 ? baseCategoryIds : void 0,
|
|
1005
|
+
minPrice: urlFilters.minPrice ?? void 0,
|
|
1006
|
+
maxPrice: urlFilters.maxPrice ?? void 0,
|
|
1007
|
+
inStockOnly: urlFilters.inStockOnly || void 0,
|
|
1008
|
+
optionFilters: initialOptionFilters.length > 0 ? initialOptionFilters : void 0
|
|
1009
|
+
},
|
|
1010
|
+
sortBy: initialSort !== CurrentSort.relevance ? mapSortToAction(initialSort) : void 0,
|
|
568
1011
|
pageSize: PAGE_SIZE
|
|
569
1012
|
});
|
|
570
1013
|
return result;
|
|
@@ -582,13 +1025,14 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
|
|
|
582
1025
|
{
|
|
583
1026
|
rangeId: "all",
|
|
584
1027
|
label: "Show all",
|
|
585
|
-
minValue:
|
|
586
|
-
maxValue:
|
|
1028
|
+
minValue: 0,
|
|
1029
|
+
maxValue: 1e3,
|
|
587
1030
|
productCount: 0,
|
|
588
1031
|
isSelected: true
|
|
589
1032
|
}
|
|
590
1033
|
]
|
|
591
|
-
}
|
|
1034
|
+
},
|
|
1035
|
+
optionFilters: []
|
|
592
1036
|
});
|
|
593
1037
|
}).toPhaseOutput((result) => {
|
|
594
1038
|
const priceAgg = result.priceAggregation || {
|
|
@@ -598,8 +1042,8 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
|
|
|
598
1042
|
{
|
|
599
1043
|
rangeId: "all",
|
|
600
1044
|
label: "Show all",
|
|
601
|
-
minValue:
|
|
602
|
-
maxValue:
|
|
1045
|
+
minValue: 0,
|
|
1046
|
+
maxValue: 1e3,
|
|
603
1047
|
productCount: result.totalCount,
|
|
604
1048
|
isSelected: true
|
|
605
1049
|
}
|
|
@@ -607,33 +1051,37 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
|
|
|
607
1051
|
};
|
|
608
1052
|
return {
|
|
609
1053
|
viewState: {
|
|
610
|
-
searchExpression:
|
|
1054
|
+
searchExpression: urlFilters.searchTerm,
|
|
611
1055
|
isSearching: false,
|
|
612
|
-
hasSearched:
|
|
1056
|
+
hasSearched: !!urlFilters.searchTerm,
|
|
613
1057
|
searchResults: result.products,
|
|
614
1058
|
resultCount: result.products.length,
|
|
615
1059
|
hasResults: result.products.length > 0,
|
|
616
1060
|
hasSuggestions: false,
|
|
617
1061
|
suggestions: [],
|
|
618
1062
|
filters: {
|
|
619
|
-
inStockOnly:
|
|
1063
|
+
inStockOnly: urlFilters.inStockOnly,
|
|
620
1064
|
priceRange: {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
maxPrice: priceAgg.maxBound,
|
|
1065
|
+
minPrice: urlFilters.minPrice ?? priceAgg.minBound,
|
|
1066
|
+
maxPrice: urlFilters.maxPrice ?? priceAgg.maxBound,
|
|
624
1067
|
minBound: priceAgg.minBound,
|
|
625
1068
|
maxBound: priceAgg.maxBound,
|
|
626
|
-
ranges: priceAgg.ranges
|
|
1069
|
+
ranges: priceAgg.ranges.map((r) => ({
|
|
1070
|
+
...r,
|
|
1071
|
+
minValue: r.minValue ?? 0,
|
|
1072
|
+
maxValue: r.maxValue ?? 0
|
|
1073
|
+
}))
|
|
627
1074
|
},
|
|
628
1075
|
categoryFilter: {
|
|
629
1076
|
categories: slowCarryForward.categories.map((cat) => ({
|
|
630
1077
|
categoryId: cat.categoryId,
|
|
631
|
-
isSelected:
|
|
1078
|
+
isSelected: initialCategoryIds.includes(cat.categoryId)
|
|
632
1079
|
}))
|
|
633
|
-
}
|
|
1080
|
+
},
|
|
1081
|
+
optionFilters: buildOptionFiltersViewState(slowCarryForward.baseOptionFilters, result, urlFilters.optionSelections)
|
|
634
1082
|
},
|
|
635
1083
|
sortBy: {
|
|
636
|
-
currentSort:
|
|
1084
|
+
currentSort: initialSort
|
|
637
1085
|
},
|
|
638
1086
|
hasMore: result.hasMore,
|
|
639
1087
|
loadedCount: result.products.length,
|
|
@@ -643,12 +1091,35 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
|
|
|
643
1091
|
searchFields: slowCarryForward.searchFields,
|
|
644
1092
|
fuzzySearch: slowCarryForward.fuzzySearch,
|
|
645
1093
|
categories: slowCarryForward.categories,
|
|
646
|
-
baseCategoryId: slowCarryForward.baseCategoryId
|
|
1094
|
+
baseCategoryId: slowCarryForward.baseCategoryId,
|
|
1095
|
+
baseOptionFilters: slowCarryForward.baseOptionFilters
|
|
647
1096
|
}
|
|
648
1097
|
};
|
|
649
1098
|
});
|
|
650
1099
|
}
|
|
651
|
-
|
|
1100
|
+
async function* loadSearchParams([wixStores]) {
|
|
1101
|
+
try {
|
|
1102
|
+
const result = await wixStores.categories.queryCategories({
|
|
1103
|
+
treeReference: { appNamespace: "@wix/stores" }
|
|
1104
|
+
}).eq("visible", true).limit(100).find();
|
|
1105
|
+
const categories2 = result.items || [];
|
|
1106
|
+
yield categories2.filter((cat) => cat.slug && (cat.itemCounter ?? 0) > 0).map((cat) => ({
|
|
1107
|
+
category: cat.slug
|
|
1108
|
+
}));
|
|
1109
|
+
for (const cat of categories2) {
|
|
1110
|
+
if (!cat.slug || !cat.parentCategory?._id || (cat.itemCounter ?? 0) === 0)
|
|
1111
|
+
continue;
|
|
1112
|
+
const parent = categories2.find((c) => c._id === cat.parentCategory?._id);
|
|
1113
|
+
if (parent?.slug) {
|
|
1114
|
+
yield [{ category: parent.slug, subcategory: cat.slug }];
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
console.error("Failed to load category params:", error);
|
|
1119
|
+
yield [];
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
const productSearch = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withLoadParams(loadSearchParams).withSlowlyRender(renderSlowlyChanging$2).withFastRender(renderFastChanging$1);
|
|
652
1123
|
var ProductType = /* @__PURE__ */ ((ProductType2) => {
|
|
653
1124
|
ProductType2[ProductType2["PHYSICAL"] = 0] = "PHYSICAL";
|
|
654
1125
|
ProductType2[ProductType2["DIGITAL"] = 1] = "DIGITAL";
|
|
@@ -691,28 +1162,21 @@ var MediaType = /* @__PURE__ */ ((MediaType2) => {
|
|
|
691
1162
|
return MediaType2;
|
|
692
1163
|
})(MediaType || {});
|
|
693
1164
|
async function* loadProductParams([wixStores]) {
|
|
694
|
-
const
|
|
695
|
-
const
|
|
696
|
-
const fields =
|
|
1165
|
+
const template = wixStores.urls.product;
|
|
1166
|
+
const needsCategories = template.includes("{category}") || template.includes("{prefix}");
|
|
1167
|
+
const fields = needsCategories ? ["ALL_CATEGORIES_INFO"] : [];
|
|
697
1168
|
try {
|
|
698
1169
|
let result = await wixStores.products.queryProducts({ fields: [...fields] }).find();
|
|
699
|
-
yield result.items.map((product) =>
|
|
1170
|
+
yield result.items.map((product) => ({ slug: product.slug ?? "" })).filter((p) => p.slug);
|
|
700
1171
|
while (result.hasNext()) {
|
|
701
1172
|
result = await result.next();
|
|
702
|
-
yield result.items.map((product) =>
|
|
1173
|
+
yield result.items.map((product) => ({ slug: product.slug ?? "" })).filter((p) => p.slug);
|
|
703
1174
|
}
|
|
704
1175
|
} catch (error) {
|
|
705
1176
|
console.error("Failed to load product slugs:", error);
|
|
706
1177
|
yield [];
|
|
707
1178
|
}
|
|
708
1179
|
}
|
|
709
|
-
function mapProductToParams(product, prefixConfig) {
|
|
710
|
-
const prefix = resolveProductPrefix(product, prefixConfig);
|
|
711
|
-
return {
|
|
712
|
-
slug: product.slug ?? "",
|
|
713
|
-
...prefix ? { category: prefix } : {}
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
1180
|
function mapProductType(productType) {
|
|
717
1181
|
return productType === "DIGITAL" ? ProductType.DIGITAL : ProductType.PHYSICAL;
|
|
718
1182
|
}
|
|
@@ -848,8 +1312,8 @@ function mapVariants(variantsInfo) {
|
|
|
848
1312
|
}
|
|
849
1313
|
async function renderSlowlyChanging$1(props, wixStores) {
|
|
850
1314
|
const Pipeline = RenderPipeline.for();
|
|
851
|
-
const
|
|
852
|
-
const
|
|
1315
|
+
const template = wixStores.urls.product;
|
|
1316
|
+
const needsCategories = template.includes("{category}") || template.includes("{prefix}");
|
|
853
1317
|
return Pipeline.try(async () => {
|
|
854
1318
|
const fields = [
|
|
855
1319
|
"INFO_SECTION",
|
|
@@ -857,17 +1321,11 @@ async function renderSlowlyChanging$1(props, wixStores) {
|
|
|
857
1321
|
"MEDIA_ITEMS_INFO",
|
|
858
1322
|
"PLAIN_DESCRIPTION",
|
|
859
1323
|
"CURRENCY",
|
|
860
|
-
...
|
|
1324
|
+
...needsCategories ? ["ALL_CATEGORIES_INFO"] : []
|
|
861
1325
|
];
|
|
862
1326
|
const response = await wixStores.products.getProductBySlug(props.slug, {
|
|
863
1327
|
fields: [...fields]
|
|
864
1328
|
});
|
|
865
|
-
if (props.category && hasPrefixes) {
|
|
866
|
-
const actualPrefix = resolveProductPrefix(response.product, prefixConfig);
|
|
867
|
-
if (actualPrefix !== props.category) {
|
|
868
|
-
throw new Error("Category prefix mismatch");
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
1329
|
return response;
|
|
872
1330
|
}).recover((error) => {
|
|
873
1331
|
console.log("product page error", error);
|
|
@@ -960,37 +1418,38 @@ const categoryList = makeJayStackComponent().withProps().withServices(WIX_STORES
|
|
|
960
1418
|
const WIX_STORES_CONTEXT = createJayContext();
|
|
961
1419
|
function loadWixStoresConfig() {
|
|
962
1420
|
const configPath = path.join(process.cwd(), "config", ".wix-stores.yaml");
|
|
1421
|
+
const defaults = {
|
|
1422
|
+
urls: { product: "/products/{slug}", category: null },
|
|
1423
|
+
defaultCategory: null
|
|
1424
|
+
};
|
|
963
1425
|
if (!fs.existsSync(configPath)) {
|
|
964
|
-
return
|
|
1426
|
+
return defaults;
|
|
965
1427
|
}
|
|
966
1428
|
const fileContents = fs.readFileSync(configPath, "utf8");
|
|
967
1429
|
const raw = yaml.load(fileContents);
|
|
968
1430
|
if (!raw) {
|
|
969
|
-
return
|
|
1431
|
+
return defaults;
|
|
970
1432
|
}
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
});
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
return { categoryPrefixes: prefixes };
|
|
1433
|
+
const urls = raw.urls;
|
|
1434
|
+
return {
|
|
1435
|
+
urls: {
|
|
1436
|
+
product: typeof urls?.product === "string" ? urls.product : defaults.urls.product,
|
|
1437
|
+
category: typeof urls?.category === "string" ? urls.category : null
|
|
1438
|
+
},
|
|
1439
|
+
defaultCategory: typeof raw.defaultCategory === "string" ? raw.defaultCategory : null
|
|
1440
|
+
};
|
|
984
1441
|
}
|
|
985
1442
|
const init = makeJayInit().withServer(async () => {
|
|
986
1443
|
console.log("[wix-stores] Initializing Wix Stores service...");
|
|
987
1444
|
const wixClient = getService(WIX_CLIENT_SERVICE);
|
|
988
1445
|
const storesConfig = loadWixStoresConfig();
|
|
989
1446
|
provideWixStoresService(wixClient, {
|
|
990
|
-
|
|
1447
|
+
urls: storesConfig.urls,
|
|
1448
|
+
defaultCategory: storesConfig.defaultCategory
|
|
991
1449
|
});
|
|
992
|
-
|
|
993
|
-
|
|
1450
|
+
console.log(`[wix-stores] URL templates: product="${storesConfig.urls.product}", category="${storesConfig.urls.category ?? "none"}"`);
|
|
1451
|
+
if (storesConfig.defaultCategory) {
|
|
1452
|
+
console.log(`[wix-stores] Default category: ${storesConfig.defaultCategory}`);
|
|
994
1453
|
}
|
|
995
1454
|
console.log("[wix-stores] Server initialization complete");
|
|
996
1455
|
return {
|
|
@@ -1001,20 +1460,22 @@ const init = makeJayInit().withServer(async () => {
|
|
|
1001
1460
|
const CONFIG_FILE_NAME = ".wix-stores.yaml";
|
|
1002
1461
|
const CONFIG_TEMPLATE = `# Wix Stores Configuration
|
|
1003
1462
|
#
|
|
1004
|
-
#
|
|
1005
|
-
#
|
|
1006
|
-
# Products under a root category get URLs like /products/{prefix}/{product-slug}
|
|
1007
|
-
# Each prefix gets its own search/listing page and product page templates.
|
|
1463
|
+
# URL templates for link generation.
|
|
1464
|
+
# Placeholders: {slug} (product), {category} (sub-category), {prefix} (root category)
|
|
1008
1465
|
#
|
|
1009
|
-
#
|
|
1466
|
+
# urls:
|
|
1467
|
+
# product: "/products/{slug}" # simple (default)
|
|
1468
|
+
# product: "/products/{category}/{slug}" # with categories
|
|
1469
|
+
# product: "/products/{prefix}/{category}/{slug}" # with prefixes + categories
|
|
1470
|
+
# category: "/products/{prefix}/{category}" # category deep-link pages
|
|
1010
1471
|
#
|
|
1011
|
-
#
|
|
1012
|
-
#
|
|
1013
|
-
#
|
|
1014
|
-
#
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1472
|
+
# Fallback category for pages without category context:
|
|
1473
|
+
# defaultCategory: "all-products"
|
|
1474
|
+
#
|
|
1475
|
+
# To see available categories: jay-stack setup wix-stores (generates category tree reference)
|
|
1476
|
+
|
|
1477
|
+
urls:
|
|
1478
|
+
product: "/products/{slug}"
|
|
1018
1479
|
`;
|
|
1019
1480
|
async function setupWixStores(ctx) {
|
|
1020
1481
|
if (ctx.initError) {
|
|
@@ -1041,8 +1502,7 @@ async function setupWixStores(ctx) {
|
|
|
1041
1502
|
configCreated.push(`config/${CONFIG_FILE_NAME}`);
|
|
1042
1503
|
}
|
|
1043
1504
|
const service = getService(WIX_STORES_SERVICE_MARKER);
|
|
1044
|
-
const
|
|
1045
|
-
const message = prefixCount > 0 ? `Wix Stores configured with ${prefixCount} category prefix(es): ${service.categoryPrefixes.map((p) => p.prefix).join(", ")}` : "Wix Stores service verified";
|
|
1505
|
+
const message = `Wix Stores configured (product URL: ${service.urls.product})`;
|
|
1046
1506
|
return {
|
|
1047
1507
|
status: "configured",
|
|
1048
1508
|
message,
|
|
@@ -1091,22 +1551,13 @@ async function generateWixStoresReferences(ctx) {
|
|
|
1091
1551
|
roots.push(node);
|
|
1092
1552
|
}
|
|
1093
1553
|
}
|
|
1094
|
-
const prefixConfig = storesService.categoryPrefixes;
|
|
1095
|
-
const configuredPrefixes = prefixConfig.map((p) => ({
|
|
1096
|
-
categoryId: p.categoryId,
|
|
1097
|
-
prefix: p.prefix,
|
|
1098
|
-
name: p.name,
|
|
1099
|
-
categoryName: nodeMap.get(p.categoryId)?.name ?? "unknown"
|
|
1100
|
-
}));
|
|
1101
1554
|
const categoriesPath = path.join(ctx.referencesDir, "categories.yaml");
|
|
1102
1555
|
fs.writeFileSync(
|
|
1103
1556
|
categoriesPath,
|
|
1104
1557
|
yaml.dump(
|
|
1105
1558
|
{
|
|
1106
|
-
|
|
1107
|
-
_description: "Wix Stores category tree for agent discovery. Shows category hierarchy, IDs, product counts, and configured URL prefixes.",
|
|
1559
|
+
_description: "Wix Stores category tree for agent discovery. Shows category hierarchy, IDs, slugs, product counts, and parent-child relationships.",
|
|
1108
1560
|
totalCategories: allCategories.length,
|
|
1109
|
-
configuredPrefixes: configuredPrefixes.length > 0 ? configuredPrefixes : void 0,
|
|
1110
1561
|
categoryTree: roots
|
|
1111
1562
|
},
|
|
1112
1563
|
{ indent: 2, lineWidth: 120, noRefs: true }
|
|
@@ -1123,12 +1574,18 @@ export {
|
|
|
1123
1574
|
WIX_CART_SERVICE,
|
|
1124
1575
|
WIX_STORES_CONTEXT,
|
|
1125
1576
|
WIX_STORES_SERVICE_MARKER,
|
|
1577
|
+
buildCategoryUrl,
|
|
1578
|
+
buildProductUrl,
|
|
1126
1579
|
cartIndicator,
|
|
1127
1580
|
cartPage,
|
|
1128
1581
|
categoryList,
|
|
1582
|
+
findCategoryImage,
|
|
1583
|
+
findRootCategoryId,
|
|
1584
|
+
findRootCategorySlug,
|
|
1129
1585
|
generateWixStoresReferences,
|
|
1130
1586
|
getCategories,
|
|
1131
1587
|
getProductBySlug,
|
|
1588
|
+
getVariantStock,
|
|
1132
1589
|
init,
|
|
1133
1590
|
productPage,
|
|
1134
1591
|
productSearch,
|