@jay-framework/wix-stores 0.15.0
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 +184 -0
- package/dist/actions/get-categories.jay-action +8 -0
- package/dist/actions/get-product-by-slug.jay-action +10 -0
- package/dist/actions/search-products.jay-action +33 -0
- package/dist/contracts/category-list.jay-contract +50 -0
- package/dist/contracts/category-list.jay-contract.d.ts +40 -0
- package/dist/contracts/category-page.jay-contract +193 -0
- package/dist/contracts/category-page.jay-contract.d.ts +124 -0
- package/dist/contracts/media-gallery.jay-contract +22 -0
- package/dist/contracts/media-gallery.jay-contract.d.ts +53 -0
- package/dist/contracts/media.jay-contract +5 -0
- package/dist/contracts/media.jay-contract.d.ts +25 -0
- package/dist/contracts/product-card.jay-contract +182 -0
- package/dist/contracts/product-card.jay-contract.d.ts +120 -0
- package/dist/contracts/product-options.jay-contract +66 -0
- package/dist/contracts/product-options.jay-contract.d.ts +57 -0
- package/dist/contracts/product-page.jay-contract +151 -0
- package/dist/contracts/product-page.jay-contract.d.ts +218 -0
- package/dist/contracts/product-search.jay-contract +252 -0
- package/dist/contracts/product-search.jay-contract.d.ts +169 -0
- package/dist/index.client.js +649 -0
- package/dist/index.d.ts +943 -0
- package/dist/index.js +1140 -0
- package/package.json +66 -0
- package/plugin.yaml +34 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1140 @@
|
|
|
1
|
+
import { getCurrentCartClient } from "@jay-framework/wix-cart";
|
|
2
|
+
import { WIX_CART_CONTEXT, WIX_CART_SERVICE, cartIndicator, cartPage, provideWixCartContext, provideWixCartService } from "@jay-framework/wix-cart";
|
|
3
|
+
import { createJayService, makeJayQuery, ActionError, makeJayStackComponent, RenderPipeline, makeJayInit } from "@jay-framework/fullstack-component";
|
|
4
|
+
import { inventoryItemsV3, productsV3 } from "@wix/stores";
|
|
5
|
+
import { categories } from "@wix/categories";
|
|
6
|
+
import { registerService, getService } from "@jay-framework/stack-server-runtime";
|
|
7
|
+
import { createJayContext } from "@jay-framework/runtime";
|
|
8
|
+
import "@jay-framework/component";
|
|
9
|
+
import { WIX_CLIENT_SERVICE } from "@jay-framework/wix-server-client";
|
|
10
|
+
import * as fs from "fs";
|
|
11
|
+
import * as path from "path";
|
|
12
|
+
import * as yaml from "js-yaml";
|
|
13
|
+
var CurrentSort = /* @__PURE__ */ ((CurrentSort2) => {
|
|
14
|
+
CurrentSort2[CurrentSort2["relevance"] = 0] = "relevance";
|
|
15
|
+
CurrentSort2[CurrentSort2["priceAsc"] = 1] = "priceAsc";
|
|
16
|
+
CurrentSort2[CurrentSort2["priceDesc"] = 2] = "priceDesc";
|
|
17
|
+
CurrentSort2[CurrentSort2["newest"] = 3] = "newest";
|
|
18
|
+
CurrentSort2[CurrentSort2["nameAsc"] = 4] = "nameAsc";
|
|
19
|
+
CurrentSort2[CurrentSort2["nameDesc"] = 5] = "nameDesc";
|
|
20
|
+
return CurrentSort2;
|
|
21
|
+
})(CurrentSort || {});
|
|
22
|
+
const instances = {
|
|
23
|
+
productsV3ClientInstance: void 0,
|
|
24
|
+
categoriesClientInstance: void 0,
|
|
25
|
+
inventoryV3ClientInstance: void 0
|
|
26
|
+
};
|
|
27
|
+
function getProductsV3Client(wixClient) {
|
|
28
|
+
if (!instances.productsV3ClientInstance) {
|
|
29
|
+
instances.productsV3ClientInstance = wixClient.use(productsV3);
|
|
30
|
+
}
|
|
31
|
+
return instances.productsV3ClientInstance;
|
|
32
|
+
}
|
|
33
|
+
function getCategoriesClient(wixClient) {
|
|
34
|
+
if (!instances.categoriesClientInstance) {
|
|
35
|
+
instances.categoriesClientInstance = wixClient.use(categories);
|
|
36
|
+
}
|
|
37
|
+
return instances.categoriesClientInstance;
|
|
38
|
+
}
|
|
39
|
+
function getInventoryClient(wixClient) {
|
|
40
|
+
if (!instances.inventoryV3ClientInstance) {
|
|
41
|
+
instances.inventoryV3ClientInstance = wixClient.use(inventoryItemsV3);
|
|
42
|
+
}
|
|
43
|
+
return instances.inventoryV3ClientInstance;
|
|
44
|
+
}
|
|
45
|
+
const WIX_STORES_SERVICE_MARKER = createJayService("Wix Store Service");
|
|
46
|
+
function provideWixStoresService(wixClient, options) {
|
|
47
|
+
const service = {
|
|
48
|
+
products: getProductsV3Client(wixClient),
|
|
49
|
+
categories: getCategoriesClient(wixClient),
|
|
50
|
+
inventory: getInventoryClient(wixClient),
|
|
51
|
+
// Keep cart for backward compatibility, but prefer WIX_CART_SERVICE
|
|
52
|
+
cart: getCurrentCartClient(wixClient),
|
|
53
|
+
categoryPrefixes: options?.categoryPrefixes ?? []
|
|
54
|
+
};
|
|
55
|
+
registerService(WIX_STORES_SERVICE_MARKER, service);
|
|
56
|
+
return service;
|
|
57
|
+
}
|
|
58
|
+
var MediaType$1 = /* @__PURE__ */ ((MediaType2) => {
|
|
59
|
+
MediaType2[MediaType2["IMAGE"] = 0] = "IMAGE";
|
|
60
|
+
MediaType2[MediaType2["VIDEO"] = 1] = "VIDEO";
|
|
61
|
+
return MediaType2;
|
|
62
|
+
})(MediaType$1 || {});
|
|
63
|
+
var AvailabilityStatus = /* @__PURE__ */ ((AvailabilityStatus2) => {
|
|
64
|
+
AvailabilityStatus2[AvailabilityStatus2["IN_STOCK"] = 0] = "IN_STOCK";
|
|
65
|
+
AvailabilityStatus2[AvailabilityStatus2["OUT_OF_STOCK"] = 1] = "OUT_OF_STOCK";
|
|
66
|
+
AvailabilityStatus2[AvailabilityStatus2["PARTIALLY_OUT_OF_STOCK"] = 2] = "PARTIALLY_OUT_OF_STOCK";
|
|
67
|
+
return AvailabilityStatus2;
|
|
68
|
+
})(AvailabilityStatus || {});
|
|
69
|
+
var PreorderStatus = /* @__PURE__ */ ((PreorderStatus2) => {
|
|
70
|
+
PreorderStatus2[PreorderStatus2["ENABLED"] = 0] = "ENABLED";
|
|
71
|
+
PreorderStatus2[PreorderStatus2["DISABLED"] = 1] = "DISABLED";
|
|
72
|
+
PreorderStatus2[PreorderStatus2["PARTIALLY_ENABLED"] = 2] = "PARTIALLY_ENABLED";
|
|
73
|
+
return PreorderStatus2;
|
|
74
|
+
})(PreorderStatus || {});
|
|
75
|
+
var ProductType$1 = /* @__PURE__ */ ((ProductType2) => {
|
|
76
|
+
ProductType2[ProductType2["PHYSICAL"] = 0] = "PHYSICAL";
|
|
77
|
+
ProductType2[ProductType2["DIGITAL"] = 1] = "DIGITAL";
|
|
78
|
+
return ProductType2;
|
|
79
|
+
})(ProductType$1 || {});
|
|
80
|
+
var QuickAddType = /* @__PURE__ */ ((QuickAddType2) => {
|
|
81
|
+
QuickAddType2[QuickAddType2["SIMPLE"] = 0] = "SIMPLE";
|
|
82
|
+
QuickAddType2[QuickAddType2["SINGLE_OPTION"] = 1] = "SINGLE_OPTION";
|
|
83
|
+
QuickAddType2[QuickAddType2["NEEDS_CONFIGURATION"] = 2] = "NEEDS_CONFIGURATION";
|
|
84
|
+
return QuickAddType2;
|
|
85
|
+
})(QuickAddType || {});
|
|
86
|
+
var OptionRenderType$1 = /* @__PURE__ */ ((OptionRenderType2) => {
|
|
87
|
+
OptionRenderType2[OptionRenderType2["TEXT_CHOICES"] = 0] = "TEXT_CHOICES";
|
|
88
|
+
OptionRenderType2[OptionRenderType2["COLOR_SWATCH_CHOICES"] = 1] = "COLOR_SWATCH_CHOICES";
|
|
89
|
+
return OptionRenderType2;
|
|
90
|
+
})(OptionRenderType$1 || {});
|
|
91
|
+
var ChoiceType$1 = /* @__PURE__ */ ((ChoiceType2) => {
|
|
92
|
+
ChoiceType2[ChoiceType2["CHOICE_TEXT"] = 0] = "CHOICE_TEXT";
|
|
93
|
+
ChoiceType2[ChoiceType2["ONE_COLOR"] = 1] = "ONE_COLOR";
|
|
94
|
+
return ChoiceType2;
|
|
95
|
+
})(ChoiceType$1 || {});
|
|
96
|
+
function parseWixMediaUrl(url) {
|
|
97
|
+
if (!url) return null;
|
|
98
|
+
const match = url.match(
|
|
99
|
+
/^wix:(image|video|document|audio):\/\/v1\/([^/]+)\/([^#]+)(?:#(.*))?$/
|
|
100
|
+
);
|
|
101
|
+
if (!match) return null;
|
|
102
|
+
const [, type, mediaId, fileName, hashParams] = match;
|
|
103
|
+
const result = {
|
|
104
|
+
type,
|
|
105
|
+
mediaId,
|
|
106
|
+
fileName: decodeURIComponent(fileName)
|
|
107
|
+
};
|
|
108
|
+
if (hashParams) {
|
|
109
|
+
const params = new URLSearchParams(hashParams);
|
|
110
|
+
const originWidth = params.get("originWidth");
|
|
111
|
+
const originHeight = params.get("originHeight");
|
|
112
|
+
if (originWidth) result.originWidth = parseInt(originWidth, 10);
|
|
113
|
+
if (originHeight) result.originHeight = parseInt(originHeight, 10);
|
|
114
|
+
const posterUri = params.get("posterUri");
|
|
115
|
+
const posterWidth = params.get("posterWidth");
|
|
116
|
+
const posterHeight = params.get("posterHeight");
|
|
117
|
+
if (posterUri) result.posterUri = decodeURIComponent(posterUri);
|
|
118
|
+
if (posterWidth) result.posterWidth = parseInt(posterWidth, 10);
|
|
119
|
+
if (posterHeight) result.posterHeight = parseInt(posterHeight, 10);
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
function formatWixMediaUrl(_id, url, resize) {
|
|
124
|
+
const resizeFragment = resize ? `/v1/fit/w_${resize.w},h_${resize.h},q_90/file.jpg` : "";
|
|
125
|
+
if (url == null ? void 0 : url.startsWith("wix:")) {
|
|
126
|
+
const parsed = parseWixMediaUrl(url);
|
|
127
|
+
if (parsed) {
|
|
128
|
+
return `https://static.wixstatic.com/media/${parsed.mediaId}${resizeFragment}`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if ((url == null ? void 0 : url.startsWith("http://")) || (url == null ? void 0 : url.startsWith("https://"))) {
|
|
132
|
+
return url;
|
|
133
|
+
}
|
|
134
|
+
if (_id) {
|
|
135
|
+
return `https://static.wixstatic.com/media/${_id}${resizeFragment}`;
|
|
136
|
+
}
|
|
137
|
+
return "";
|
|
138
|
+
}
|
|
139
|
+
function resolveProductPrefix(product, prefixConfig) {
|
|
140
|
+
if (!prefixConfig?.length || !product.allCategoriesInfo?.categories) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const productCategoryIds = new Set(
|
|
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;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
function resolveProductPrefixConfig(product, prefixConfig) {
|
|
154
|
+
if (!prefixConfig?.length || !product.allCategoriesInfo?.categories) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const productCategoryIds = new Set(
|
|
158
|
+
product.allCategoriesInfo.categories.map((c) => c._id).filter(Boolean)
|
|
159
|
+
);
|
|
160
|
+
for (const config of prefixConfig) {
|
|
161
|
+
if (productCategoryIds.has(config.categoryId)) {
|
|
162
|
+
return config;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
function mapAvailabilityStatus(status) {
|
|
168
|
+
switch (status) {
|
|
169
|
+
case "OUT_OF_STOCK":
|
|
170
|
+
return AvailabilityStatus.OUT_OF_STOCK;
|
|
171
|
+
case "PARTIALLY_OUT_OF_STOCK":
|
|
172
|
+
return AvailabilityStatus.PARTIALLY_OUT_OF_STOCK;
|
|
173
|
+
default:
|
|
174
|
+
return AvailabilityStatus.IN_STOCK;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function mapPreorderStatus(status) {
|
|
178
|
+
switch (status) {
|
|
179
|
+
case "ENABLED":
|
|
180
|
+
return PreorderStatus.ENABLED;
|
|
181
|
+
case "PARTIALLY_ENABLED":
|
|
182
|
+
return PreorderStatus.PARTIALLY_ENABLED;
|
|
183
|
+
default:
|
|
184
|
+
return PreorderStatus.DISABLED;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function mapMediaType$1(mediaType) {
|
|
188
|
+
return mediaType === "VIDEO" ? MediaType$1.VIDEO : MediaType$1.IMAGE;
|
|
189
|
+
}
|
|
190
|
+
function mapProductType$1(productType) {
|
|
191
|
+
return productType === "DIGITAL" ? ProductType$1.DIGITAL : ProductType$1.PHYSICAL;
|
|
192
|
+
}
|
|
193
|
+
function isValidPrice(amount) {
|
|
194
|
+
if (!amount) return false;
|
|
195
|
+
const numAmount = parseFloat(amount);
|
|
196
|
+
return !isNaN(numAmount) && numAmount > 0;
|
|
197
|
+
}
|
|
198
|
+
function getQuickAddType(product) {
|
|
199
|
+
const optionCount = product.options?.length ?? 0;
|
|
200
|
+
const hasModifiers = (product.modifiers?.length ?? 0) > 0;
|
|
201
|
+
if (hasModifiers || optionCount > 1) {
|
|
202
|
+
return QuickAddType.NEEDS_CONFIGURATION;
|
|
203
|
+
}
|
|
204
|
+
if (optionCount === 1) {
|
|
205
|
+
return QuickAddType.SINGLE_OPTION;
|
|
206
|
+
}
|
|
207
|
+
return QuickAddType.SIMPLE;
|
|
208
|
+
}
|
|
209
|
+
function mapOptionRenderType(renderType) {
|
|
210
|
+
return renderType === "COLOR_SWATCH_CHOICES" ? OptionRenderType$1.COLOR_SWATCH_CHOICES : OptionRenderType$1.TEXT_CHOICES;
|
|
211
|
+
}
|
|
212
|
+
function mapChoiceType(choiceType) {
|
|
213
|
+
return choiceType === "ONE_COLOR" ? ChoiceType$1.ONE_COLOR : ChoiceType$1.CHOICE_TEXT;
|
|
214
|
+
}
|
|
215
|
+
function mapQuickOption(option, variantsInfo) {
|
|
216
|
+
if (!option) return null;
|
|
217
|
+
const optionId = option._id;
|
|
218
|
+
const choices = option.choicesSettings?.choices || [];
|
|
219
|
+
return {
|
|
220
|
+
_id: optionId,
|
|
221
|
+
name: option.name || "",
|
|
222
|
+
optionRenderType: mapOptionRenderType(option.optionRenderType),
|
|
223
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
224
|
+
choices: choices.map((choice) => {
|
|
225
|
+
return {
|
|
226
|
+
choiceId: choice.choiceId,
|
|
227
|
+
name: choice.name || "",
|
|
228
|
+
choiceType: mapChoiceType(choice.choiceType),
|
|
229
|
+
colorCode: choice.colorCode || "",
|
|
230
|
+
inStock: choice.inStock,
|
|
231
|
+
isSelected: false
|
|
232
|
+
};
|
|
233
|
+
})
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const DEFAULT_PRODUCT_PAGE_PATH = "/products";
|
|
237
|
+
function mapProductToCard(product, productPagePath = DEFAULT_PRODUCT_PAGE_PATH, prefixConfig) {
|
|
238
|
+
const mainMedia = product.media?.main;
|
|
239
|
+
const slug = product.slug || "";
|
|
240
|
+
const matchedPrefix = prefixConfig?.length ? resolveProductPrefixConfig(product, prefixConfig) : null;
|
|
241
|
+
const productUrl = slug ? matchedPrefix ? `${productPagePath}/${matchedPrefix.prefix}/${slug}` : `${productPagePath}/${slug}` : "";
|
|
242
|
+
const firstVariant = product.variantsInfo?.variants?.[0];
|
|
243
|
+
const variantPrice = firstVariant?.price;
|
|
244
|
+
const actualAmount = variantPrice?.actualPrice?.amount || product.actualPriceRange?.minValue?.amount || "0";
|
|
245
|
+
const actualFormattedAmount = variantPrice?.actualPrice?.formattedAmount || product.actualPriceRange?.minValue?.formattedAmount || "";
|
|
246
|
+
const compareAtAmount = variantPrice?.compareAtPrice?.amount || product.compareAtPriceRange?.minValue?.amount;
|
|
247
|
+
const compareAtFormattedAmount = variantPrice?.compareAtPrice?.formattedAmount || product.compareAtPriceRange?.minValue?.formattedAmount || "";
|
|
248
|
+
const hasDiscount = isValidPrice(compareAtAmount) && compareAtAmount !== actualAmount;
|
|
249
|
+
return {
|
|
250
|
+
_id: product._id || "",
|
|
251
|
+
name: product.name || "",
|
|
252
|
+
slug,
|
|
253
|
+
productUrl,
|
|
254
|
+
categoryPrefix: matchedPrefix?.name ?? "",
|
|
255
|
+
mainMedia: {
|
|
256
|
+
url: mainMedia ? formatWixMediaUrl(mainMedia._id, mainMedia.url) : "",
|
|
257
|
+
altText: mainMedia?.altText || product.name || "",
|
|
258
|
+
mediaType: mapMediaType$1(mainMedia?.mediaType)
|
|
259
|
+
},
|
|
260
|
+
thumbnail: {
|
|
261
|
+
url: mainMedia ? formatWixMediaUrl(mainMedia._id, mainMedia.url, { w: 300, h: 300 }) : "",
|
|
262
|
+
altText: mainMedia?.altText || product.name || "",
|
|
263
|
+
width: 300,
|
|
264
|
+
height: 300
|
|
265
|
+
},
|
|
266
|
+
// Simplified price fields
|
|
267
|
+
price: actualFormattedAmount,
|
|
268
|
+
strikethroughPrice: hasDiscount ? compareAtFormattedAmount : "",
|
|
269
|
+
hasDiscount,
|
|
270
|
+
inventory: {
|
|
271
|
+
availabilityStatus: mapAvailabilityStatus(product.inventory?.availabilityStatus),
|
|
272
|
+
preorderStatus: mapPreorderStatus(product.inventory?.preorderStatus)
|
|
273
|
+
},
|
|
274
|
+
ribbon: {
|
|
275
|
+
_id: product.ribbon?._id || "",
|
|
276
|
+
name: product.ribbon?.name || ""
|
|
277
|
+
},
|
|
278
|
+
hasRibbon: !!product.ribbon?.name,
|
|
279
|
+
brand: {
|
|
280
|
+
_id: product.brand?._id || "",
|
|
281
|
+
name: product.brand?.name || ""
|
|
282
|
+
},
|
|
283
|
+
productType: mapProductType$1(product.productType),
|
|
284
|
+
isAddingToCart: false,
|
|
285
|
+
// Quick add behavior
|
|
286
|
+
quickAddType: getQuickAddType(product),
|
|
287
|
+
quickOption: getQuickAddType(product) === QuickAddType.SINGLE_OPTION ? mapQuickOption(product.options?.[0], product.variantsInfo) : null
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const PRICE_BUCKET_BOUNDARIES = [
|
|
291
|
+
0,
|
|
292
|
+
20,
|
|
293
|
+
40,
|
|
294
|
+
100,
|
|
295
|
+
200,
|
|
296
|
+
400,
|
|
297
|
+
1e3,
|
|
298
|
+
2e3,
|
|
299
|
+
4e3,
|
|
300
|
+
1e4,
|
|
301
|
+
2e4,
|
|
302
|
+
4e4,
|
|
303
|
+
1e5
|
|
304
|
+
];
|
|
305
|
+
const PRICE_BUCKETS = PRICE_BUCKET_BOUNDARIES.slice(0, -1).map((from, i) => ({
|
|
306
|
+
from,
|
|
307
|
+
to: PRICE_BUCKET_BOUNDARIES[i + 1]
|
|
308
|
+
}));
|
|
309
|
+
PRICE_BUCKETS.push({ from: PRICE_BUCKET_BOUNDARIES[PRICE_BUCKET_BOUNDARIES.length - 1] });
|
|
310
|
+
const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX_STORES_SERVICE_MARKER).withHandler(
|
|
311
|
+
async (input, wixStores) => {
|
|
312
|
+
const { query, filters = {}, sortBy = "relevance", cursor, pageSize = 12 } = input;
|
|
313
|
+
try {
|
|
314
|
+
const filterConditions = [
|
|
315
|
+
// Only visible products
|
|
316
|
+
{ visible: { $eq: true } }
|
|
317
|
+
];
|
|
318
|
+
const hasMinPrice = filters.minPrice !== void 0 && filters.minPrice > 0;
|
|
319
|
+
const hasMaxPrice = filters.maxPrice !== void 0 && filters.maxPrice > 0;
|
|
320
|
+
if (hasMinPrice) {
|
|
321
|
+
filterConditions.push({
|
|
322
|
+
"actualPriceRange.minValue.amount": { $gte: String(filters.minPrice) }
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (hasMaxPrice) {
|
|
326
|
+
filterConditions.push({
|
|
327
|
+
"actualPriceRange.minValue.amount": { $lte: String(filters.maxPrice) }
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
if (filters.inStockOnly) {
|
|
331
|
+
filterConditions.push({
|
|
332
|
+
"inventory.availabilityStatus": { $eq: "IN_STOCK" }
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
if (filters.categoryIds && filters.categoryIds.length > 0) {
|
|
336
|
+
filterConditions.push({
|
|
337
|
+
"allCategoriesInfo.categories.id": {
|
|
338
|
+
$hasAll: filters.categoryIds
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
const filter = filterConditions.length === 1 ? filterConditions[0] : { $and: filterConditions };
|
|
343
|
+
const sort = [];
|
|
344
|
+
switch (sortBy) {
|
|
345
|
+
case "price_asc":
|
|
346
|
+
sort.push({ fieldName: "actualPriceRange.minValue.amount", order: "ASC" });
|
|
347
|
+
break;
|
|
348
|
+
case "price_desc":
|
|
349
|
+
sort.push({ fieldName: "actualPriceRange.minValue.amount", order: "DESC" });
|
|
350
|
+
break;
|
|
351
|
+
case "name_asc":
|
|
352
|
+
sort.push({ fieldName: "name", order: "ASC" });
|
|
353
|
+
break;
|
|
354
|
+
case "name_desc":
|
|
355
|
+
sort.push({ fieldName: "name", order: "DESC" });
|
|
356
|
+
break;
|
|
357
|
+
case "newest":
|
|
358
|
+
sort.push({ fieldName: "_createdDate", order: "DESC" });
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
const cursorPaging = cursor ? { cursor, limit: pageSize } : { limit: pageSize };
|
|
362
|
+
const hasSearchQuery = query && query.trim().length > 0;
|
|
363
|
+
const search = hasSearchQuery ? {
|
|
364
|
+
expression: query.trim(),
|
|
365
|
+
fields: ["name", "description"]
|
|
366
|
+
} : void 0;
|
|
367
|
+
const aggregations = [
|
|
368
|
+
// Total count via COUNT_DISTINCT on slug
|
|
369
|
+
{
|
|
370
|
+
fieldPath: "slug",
|
|
371
|
+
name: "total-count",
|
|
372
|
+
type: "SCALAR",
|
|
373
|
+
scalar: { type: "COUNT_DISTINCT" }
|
|
374
|
+
},
|
|
375
|
+
// Price buckets with product counts
|
|
376
|
+
{
|
|
377
|
+
fieldPath: "actualPriceRange.minValue.amount",
|
|
378
|
+
name: "price-buckets",
|
|
379
|
+
type: "RANGE",
|
|
380
|
+
range: { buckets: PRICE_BUCKETS }
|
|
381
|
+
},
|
|
382
|
+
// Min price for slider bound
|
|
383
|
+
{
|
|
384
|
+
fieldPath: "actualPriceRange.minValue.amount",
|
|
385
|
+
name: "min-price",
|
|
386
|
+
type: "SCALAR",
|
|
387
|
+
scalar: { type: "MIN" }
|
|
388
|
+
},
|
|
389
|
+
// Max price for slider bound
|
|
390
|
+
{
|
|
391
|
+
fieldPath: "actualPriceRange.minValue.amount",
|
|
392
|
+
name: "max-price",
|
|
393
|
+
type: "SCALAR",
|
|
394
|
+
scalar: { type: "MAX" }
|
|
395
|
+
}
|
|
396
|
+
];
|
|
397
|
+
const searchResult = await wixStores.products.searchProducts(
|
|
398
|
+
{
|
|
399
|
+
filter,
|
|
400
|
+
// @ts-expect-error - Wix SDK types don't match actual API
|
|
401
|
+
sort: sort.length > 0 ? sort : void 0,
|
|
402
|
+
cursorPaging,
|
|
403
|
+
search,
|
|
404
|
+
// @ts-expect-error - Wix SDK types don't include aggregations
|
|
405
|
+
aggregations
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
fields: [
|
|
409
|
+
"CURRENCY",
|
|
410
|
+
"VARIANT_OPTION_CHOICE_NAMES",
|
|
411
|
+
...wixStores.categoryPrefixes.length > 0 ? ["ALL_CATEGORIES_INFO"] : []
|
|
412
|
+
]
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
const products = searchResult.products || [];
|
|
416
|
+
const nextCursor = searchResult.pagingMetadata?.cursors?.next || null;
|
|
417
|
+
const aggResults = searchResult.aggregationData?.results || [];
|
|
418
|
+
const totalCountAgg = aggResults.find((a) => a.name === "total-count")?.scalar;
|
|
419
|
+
const minPriceAgg = aggResults.find((a) => a.name === "min-price")?.scalar;
|
|
420
|
+
const maxPriceAgg = aggResults.find((a) => a.name === "max-price")?.scalar;
|
|
421
|
+
const totalCount = totalCountAgg?.value ?? products.length;
|
|
422
|
+
const bucketsAgg = aggResults.find((a) => a.name === "price-buckets").ranges;
|
|
423
|
+
const minBound = minPriceAgg?.value;
|
|
424
|
+
const maxBound = maxPriceAgg?.value;
|
|
425
|
+
const buckets = bucketsAgg.results || [];
|
|
426
|
+
const currencySymbol = products[0]?.currency === "ILS" ? "₪" : products[0]?.currency === "USD" ? "$" : products[0]?.currency === "EUR" ? "€" : products[0]?.currency === "GBP" ? "£" : "$";
|
|
427
|
+
const priceRanges = [
|
|
428
|
+
{
|
|
429
|
+
rangeId: "all",
|
|
430
|
+
label: "Show all",
|
|
431
|
+
minValue: null,
|
|
432
|
+
maxValue: null,
|
|
433
|
+
productCount: totalCount,
|
|
434
|
+
isSelected: true
|
|
435
|
+
}
|
|
436
|
+
];
|
|
437
|
+
const bucketRanges = buckets.filter((bucket) => (bucket.count ?? 0) > 0).map((bucket) => {
|
|
438
|
+
const from = bucket.from ?? 0;
|
|
439
|
+
const to = bucket.to;
|
|
440
|
+
const label = to ? `${currencySymbol}${from} - ${currencySymbol}${to}` : `${currencySymbol}${from}+`;
|
|
441
|
+
return {
|
|
442
|
+
rangeId: `${from}-${to ?? "plus"}`,
|
|
443
|
+
label,
|
|
444
|
+
minValue: from,
|
|
445
|
+
maxValue: to ?? null,
|
|
446
|
+
productCount: bucket.count ?? 0,
|
|
447
|
+
isSelected: false
|
|
448
|
+
};
|
|
449
|
+
});
|
|
450
|
+
priceRanges.push(...bucketRanges);
|
|
451
|
+
const prefixConfig = wixStores.categoryPrefixes;
|
|
452
|
+
const mappedProducts = products.map(
|
|
453
|
+
(p) => mapProductToCard(p, "/products", prefixConfig)
|
|
454
|
+
);
|
|
455
|
+
return {
|
|
456
|
+
products: mappedProducts,
|
|
457
|
+
totalCount,
|
|
458
|
+
nextCursor,
|
|
459
|
+
hasMore: nextCursor !== null,
|
|
460
|
+
priceAggregation: {
|
|
461
|
+
minBound,
|
|
462
|
+
maxBound,
|
|
463
|
+
ranges: priceRanges
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.error("[wixStores.searchProducts] Search failed:", error);
|
|
468
|
+
throw new ActionError("SEARCH_FAILED", "Failed to search products");
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
);
|
|
472
|
+
const getProductBySlug = makeJayQuery("wixStores.getProductBySlug").withServices(WIX_STORES_SERVICE_MARKER).withCaching({ maxAge: 300, staleWhileRevalidate: 600 }).withHandler(
|
|
473
|
+
async (input, wixStores) => {
|
|
474
|
+
const { slug } = input;
|
|
475
|
+
if (!slug) {
|
|
476
|
+
throw new ActionError("INVALID_INPUT", "Product slug is required");
|
|
477
|
+
}
|
|
478
|
+
try {
|
|
479
|
+
const prefixConfig = wixStores.categoryPrefixes;
|
|
480
|
+
const fields = [
|
|
481
|
+
"MEDIA_ITEMS_INFO",
|
|
482
|
+
"VARIANT_OPTION_CHOICE_NAMES",
|
|
483
|
+
...prefixConfig.length > 0 ? ["ALL_CATEGORIES_INFO"] : []
|
|
484
|
+
];
|
|
485
|
+
const result = await wixStores.products.getProductBySlug(slug, {
|
|
486
|
+
fields: [...fields]
|
|
487
|
+
});
|
|
488
|
+
const product = result.product;
|
|
489
|
+
if (!product) {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
return mapProductToCard(product, "/products", prefixConfig);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
console.error("[wixStores.getProductBySlug] Failed to get product:", error);
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
);
|
|
499
|
+
const getCategories = makeJayQuery("wixStores.getCategories").withServices(WIX_STORES_SERVICE_MARKER).withCaching({ maxAge: 3600 }).withHandler(
|
|
500
|
+
async (_input, wixStores) => {
|
|
501
|
+
try {
|
|
502
|
+
const result = await wixStores.categories.queryCategories({
|
|
503
|
+
treeReference: {
|
|
504
|
+
appNamespace: "@wix/stores"
|
|
505
|
+
}
|
|
506
|
+
}).eq("visible", true).find();
|
|
507
|
+
return (result.items || []).map((cat) => ({
|
|
508
|
+
categoryId: cat._id || "",
|
|
509
|
+
categoryName: cat.name || ""
|
|
510
|
+
}));
|
|
511
|
+
} catch (error) {
|
|
512
|
+
console.error("[wixStores.getCategories] Failed to load categories:", error);
|
|
513
|
+
throw new ActionError("LOAD_FAILED", "Failed to load categories");
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
);
|
|
517
|
+
const PAGE_SIZE = 12;
|
|
518
|
+
async function renderSlowlyChanging$2(props, wixStores) {
|
|
519
|
+
const Pipeline = RenderPipeline.for();
|
|
520
|
+
const categoryPrefix = props.category;
|
|
521
|
+
const categoryConfig = categoryPrefix ? wixStores.categoryPrefixes.find((c) => c.prefix === categoryPrefix) : null;
|
|
522
|
+
const baseCategoryId = categoryConfig?.categoryId ?? null;
|
|
523
|
+
return Pipeline.try(async () => {
|
|
524
|
+
let query = wixStores.categories.queryCategories({
|
|
525
|
+
treeReference: { appNamespace: "@wix/stores" }
|
|
526
|
+
}).eq("visible", true);
|
|
527
|
+
if (baseCategoryId) {
|
|
528
|
+
query = query.eq("parentCategory.id", baseCategoryId);
|
|
529
|
+
}
|
|
530
|
+
const categoriesResult = await query.find();
|
|
531
|
+
return categoriesResult.items || [];
|
|
532
|
+
}).recover((error) => {
|
|
533
|
+
console.error("Failed to load categories:", error);
|
|
534
|
+
return Pipeline.ok([]);
|
|
535
|
+
}).toPhaseOutput((categories2) => {
|
|
536
|
+
const categoryInfos = categories2.map((cat) => ({
|
|
537
|
+
categoryId: cat._id || "",
|
|
538
|
+
categoryName: cat.name || "",
|
|
539
|
+
categorySlug: cat.slug || ""
|
|
540
|
+
}));
|
|
541
|
+
return {
|
|
542
|
+
viewState: {
|
|
543
|
+
searchFields: "name,description,sku",
|
|
544
|
+
fuzzySearch: true,
|
|
545
|
+
emptyStateMessage: "Enter a search term to find products",
|
|
546
|
+
filters: {
|
|
547
|
+
categoryFilter: {
|
|
548
|
+
categories: categoryInfos
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
carryForward: {
|
|
553
|
+
searchFields: "name,description,sku",
|
|
554
|
+
fuzzySearch: true,
|
|
555
|
+
categories: categoryInfos,
|
|
556
|
+
baseCategoryId
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
|
|
562
|
+
const Pipeline = RenderPipeline.for();
|
|
563
|
+
return Pipeline.try(async () => {
|
|
564
|
+
const baseCategoryIds = slowCarryForward.baseCategoryId ? [slowCarryForward.baseCategoryId] : [];
|
|
565
|
+
const result = await searchProducts({
|
|
566
|
+
query: "",
|
|
567
|
+
filters: baseCategoryIds.length > 0 ? { categoryIds: baseCategoryIds } : void 0,
|
|
568
|
+
pageSize: PAGE_SIZE
|
|
569
|
+
});
|
|
570
|
+
return result;
|
|
571
|
+
}).recover((error) => {
|
|
572
|
+
console.error("Failed to load products:", error);
|
|
573
|
+
return Pipeline.ok({
|
|
574
|
+
products: [],
|
|
575
|
+
totalCount: 0,
|
|
576
|
+
nextCursor: null,
|
|
577
|
+
hasMore: false,
|
|
578
|
+
priceAggregation: {
|
|
579
|
+
minBound: 0,
|
|
580
|
+
maxBound: 1e3,
|
|
581
|
+
ranges: [
|
|
582
|
+
{
|
|
583
|
+
rangeId: "all",
|
|
584
|
+
label: "Show all",
|
|
585
|
+
minValue: null,
|
|
586
|
+
maxValue: null,
|
|
587
|
+
productCount: 0,
|
|
588
|
+
isSelected: true
|
|
589
|
+
}
|
|
590
|
+
]
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
}).toPhaseOutput((result) => {
|
|
594
|
+
const priceAgg = result.priceAggregation || {
|
|
595
|
+
minBound: 0,
|
|
596
|
+
maxBound: 1e3,
|
|
597
|
+
ranges: [
|
|
598
|
+
{
|
|
599
|
+
rangeId: "all",
|
|
600
|
+
label: "Show all",
|
|
601
|
+
minValue: null,
|
|
602
|
+
maxValue: null,
|
|
603
|
+
productCount: result.totalCount,
|
|
604
|
+
isSelected: true
|
|
605
|
+
}
|
|
606
|
+
]
|
|
607
|
+
};
|
|
608
|
+
return {
|
|
609
|
+
viewState: {
|
|
610
|
+
searchExpression: "",
|
|
611
|
+
isSearching: false,
|
|
612
|
+
hasSearched: false,
|
|
613
|
+
searchResults: result.products,
|
|
614
|
+
resultCount: result.products.length,
|
|
615
|
+
hasResults: result.products.length > 0,
|
|
616
|
+
hasSuggestions: false,
|
|
617
|
+
suggestions: [],
|
|
618
|
+
filters: {
|
|
619
|
+
inStockOnly: false,
|
|
620
|
+
priceRange: {
|
|
621
|
+
// Initialize sliders to full range (bounds)
|
|
622
|
+
minPrice: priceAgg.minBound,
|
|
623
|
+
maxPrice: priceAgg.maxBound,
|
|
624
|
+
minBound: priceAgg.minBound,
|
|
625
|
+
maxBound: priceAgg.maxBound,
|
|
626
|
+
ranges: priceAgg.ranges
|
|
627
|
+
},
|
|
628
|
+
categoryFilter: {
|
|
629
|
+
categories: slowCarryForward.categories.map((cat) => ({
|
|
630
|
+
categoryId: cat.categoryId,
|
|
631
|
+
isSelected: false
|
|
632
|
+
}))
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
sortBy: {
|
|
636
|
+
currentSort: CurrentSort.relevance
|
|
637
|
+
},
|
|
638
|
+
hasMore: result.hasMore,
|
|
639
|
+
loadedCount: result.products.length,
|
|
640
|
+
totalCount: result.totalCount
|
|
641
|
+
},
|
|
642
|
+
carryForward: {
|
|
643
|
+
searchFields: slowCarryForward.searchFields,
|
|
644
|
+
fuzzySearch: slowCarryForward.fuzzySearch,
|
|
645
|
+
categories: slowCarryForward.categories,
|
|
646
|
+
baseCategoryId: slowCarryForward.baseCategoryId
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
const productSearch = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withSlowlyRender(renderSlowlyChanging$2).withFastRender(renderFastChanging$1);
|
|
652
|
+
var ProductType = /* @__PURE__ */ ((ProductType2) => {
|
|
653
|
+
ProductType2[ProductType2["PHYSICAL"] = 0] = "PHYSICAL";
|
|
654
|
+
ProductType2[ProductType2["DIGITAL"] = 1] = "DIGITAL";
|
|
655
|
+
return ProductType2;
|
|
656
|
+
})(ProductType || {});
|
|
657
|
+
var StockStatus = /* @__PURE__ */ ((StockStatus2) => {
|
|
658
|
+
StockStatus2[StockStatus2["OUT_OF_STOCK"] = 0] = "OUT_OF_STOCK";
|
|
659
|
+
StockStatus2[StockStatus2["IN_STOCK"] = 1] = "IN_STOCK";
|
|
660
|
+
return StockStatus2;
|
|
661
|
+
})(StockStatus || {});
|
|
662
|
+
var OptionRenderType = /* @__PURE__ */ ((OptionRenderType2) => {
|
|
663
|
+
OptionRenderType2[OptionRenderType2["TEXT_CHOICES"] = 0] = "TEXT_CHOICES";
|
|
664
|
+
OptionRenderType2[OptionRenderType2["COLOR_SWATCH_CHOICES"] = 1] = "COLOR_SWATCH_CHOICES";
|
|
665
|
+
return OptionRenderType2;
|
|
666
|
+
})(OptionRenderType || {});
|
|
667
|
+
var ChoiceType = /* @__PURE__ */ ((ChoiceType2) => {
|
|
668
|
+
ChoiceType2[ChoiceType2["CHOICE_TEXT"] = 0] = "CHOICE_TEXT";
|
|
669
|
+
ChoiceType2[ChoiceType2["ONE_COLOR"] = 1] = "ONE_COLOR";
|
|
670
|
+
return ChoiceType2;
|
|
671
|
+
})(ChoiceType || {});
|
|
672
|
+
var ModifierType = /* @__PURE__ */ ((ModifierType2) => {
|
|
673
|
+
ModifierType2[ModifierType2["TEXT_CHOICES"] = 0] = "TEXT_CHOICES";
|
|
674
|
+
ModifierType2[ModifierType2["COLOR_SWATCH_CHOICES"] = 1] = "COLOR_SWATCH_CHOICES";
|
|
675
|
+
ModifierType2[ModifierType2["FREE_TEXT"] = 2] = "FREE_TEXT";
|
|
676
|
+
return ModifierType2;
|
|
677
|
+
})(ModifierType || {});
|
|
678
|
+
var ChoiceType = /* @__PURE__ */ ((ChoiceType2) => {
|
|
679
|
+
ChoiceType2[ChoiceType2["CHOICE_TEXT"] = 0] = "CHOICE_TEXT";
|
|
680
|
+
ChoiceType2[ChoiceType2["ONE_COLOR"] = 1] = "ONE_COLOR";
|
|
681
|
+
return ChoiceType2;
|
|
682
|
+
})(ChoiceType || {});
|
|
683
|
+
var Selected = /* @__PURE__ */ ((Selected2) => {
|
|
684
|
+
Selected2[Selected2["selected"] = 0] = "selected";
|
|
685
|
+
Selected2[Selected2["notSelected"] = 1] = "notSelected";
|
|
686
|
+
return Selected2;
|
|
687
|
+
})(Selected || {});
|
|
688
|
+
var MediaType = /* @__PURE__ */ ((MediaType2) => {
|
|
689
|
+
MediaType2[MediaType2["IMAGE"] = 0] = "IMAGE";
|
|
690
|
+
MediaType2[MediaType2["VIDEO"] = 1] = "VIDEO";
|
|
691
|
+
return MediaType2;
|
|
692
|
+
})(MediaType || {});
|
|
693
|
+
async function* loadProductParams([wixStores]) {
|
|
694
|
+
const prefixConfig = wixStores.categoryPrefixes;
|
|
695
|
+
const hasPrefixes = prefixConfig.length > 0;
|
|
696
|
+
const fields = hasPrefixes ? ["ALL_CATEGORIES_INFO"] : [];
|
|
697
|
+
try {
|
|
698
|
+
let result = await wixStores.products.queryProducts({ fields: [...fields] }).find();
|
|
699
|
+
yield result.items.map((product) => mapProductToParams(product, prefixConfig));
|
|
700
|
+
while (result.hasNext()) {
|
|
701
|
+
result = await result.next();
|
|
702
|
+
yield result.items.map((product) => mapProductToParams(product, prefixConfig));
|
|
703
|
+
}
|
|
704
|
+
} catch (error) {
|
|
705
|
+
console.error("Failed to load product slugs:", error);
|
|
706
|
+
yield [];
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function mapProductToParams(product, prefixConfig) {
|
|
710
|
+
const prefix = resolveProductPrefix(product, prefixConfig);
|
|
711
|
+
return {
|
|
712
|
+
slug: product.slug ?? "",
|
|
713
|
+
...prefix ? { category: prefix } : {}
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
function mapProductType(productType) {
|
|
717
|
+
return productType === "DIGITAL" ? ProductType.DIGITAL : ProductType.PHYSICAL;
|
|
718
|
+
}
|
|
719
|
+
function mapInfoSections(infoSections) {
|
|
720
|
+
return infoSections.map((infoSection) => ({
|
|
721
|
+
_id: infoSection._id,
|
|
722
|
+
plainDescription: infoSection.plainDescription || "",
|
|
723
|
+
title: infoSection.title || "",
|
|
724
|
+
uniqueName: infoSection.uniqueName || ""
|
|
725
|
+
}));
|
|
726
|
+
}
|
|
727
|
+
function mapSeoData(seoData) {
|
|
728
|
+
return {
|
|
729
|
+
tags: seoData?.tags?.map((tag, index) => ({
|
|
730
|
+
position: index.toString().padStart(2, "0"),
|
|
731
|
+
type: tag.type,
|
|
732
|
+
props: Object.entries(tag.props || {}).map(([key, value]) => ({ key, value })),
|
|
733
|
+
meta: Object.entries(tag.meta || {}).map(([key, value]) => ({ key, value })),
|
|
734
|
+
children: tag.children
|
|
735
|
+
})),
|
|
736
|
+
settings: {
|
|
737
|
+
preventAutoRedirect: seoData?.settings?.preventAutoRedirect || false,
|
|
738
|
+
keywords: seoData?.settings?.keywords.map((keyword) => ({
|
|
739
|
+
isMain: keyword.isMain,
|
|
740
|
+
origin: keyword.origin,
|
|
741
|
+
term: keyword.term
|
|
742
|
+
}))
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
function mapMediaType(mediaType) {
|
|
747
|
+
if (mediaType === "VIDEO")
|
|
748
|
+
return MediaType.VIDEO;
|
|
749
|
+
else
|
|
750
|
+
return MediaType.IMAGE;
|
|
751
|
+
}
|
|
752
|
+
function mapMedia(media) {
|
|
753
|
+
const mainMediaType = mapMediaType(media.main.mediaType);
|
|
754
|
+
return {
|
|
755
|
+
selectedMedia: {
|
|
756
|
+
url: formatWixMediaUrl(media.main._id, media.main.url),
|
|
757
|
+
mediaType: mainMediaType,
|
|
758
|
+
thumbnail_50x50: formatWixMediaUrl(media.main._id, media.main.url, { w: 50, h: 50 })
|
|
759
|
+
},
|
|
760
|
+
availableMedia: media.itemsInfo?.items?.map((item) => ({
|
|
761
|
+
mediaId: item._id,
|
|
762
|
+
media: {
|
|
763
|
+
url: formatWixMediaUrl(item._id, item.url),
|
|
764
|
+
mediaType: item.mediaType === "IMAGE" ? MediaType.IMAGE : MediaType.VIDEO,
|
|
765
|
+
thumbnail_50x50: formatWixMediaUrl(item._id, item.url, { w: 50, h: 50 })
|
|
766
|
+
},
|
|
767
|
+
selected: item._id === media.main._id ? Selected.selected : Selected.notSelected
|
|
768
|
+
})) ?? []
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
function mapOptionsToSlowVS(options) {
|
|
772
|
+
return options?.map((option) => ({
|
|
773
|
+
name: option.name,
|
|
774
|
+
optionRenderType: option.optionRenderType === "TEXT_CHOICES" ? OptionRenderType.TEXT_CHOICES : OptionRenderType.COLOR_SWATCH_CHOICES,
|
|
775
|
+
_id: option._id,
|
|
776
|
+
choices: option.choicesSettings?.choices?.map((choice) => ({
|
|
777
|
+
name: choice.name,
|
|
778
|
+
choiceId: choice.choiceId,
|
|
779
|
+
choiceType: choice.choiceType === "CHOICE_TEXT" ? ChoiceType.CHOICE_TEXT : ChoiceType.ONE_COLOR,
|
|
780
|
+
inStock: choice.inStock,
|
|
781
|
+
colorCode: choice.colorCode
|
|
782
|
+
})) ?? []
|
|
783
|
+
})) ?? [];
|
|
784
|
+
}
|
|
785
|
+
function mapOptionsToFastVS(options) {
|
|
786
|
+
return options?.map((option) => ({
|
|
787
|
+
_id: option._id,
|
|
788
|
+
textChoiceSelection: void 0,
|
|
789
|
+
choices: option.choicesSettings?.choices?.map((choice) => ({
|
|
790
|
+
choiceId: choice.choiceId,
|
|
791
|
+
isSelected: false
|
|
792
|
+
})) ?? []
|
|
793
|
+
})) ?? [];
|
|
794
|
+
}
|
|
795
|
+
function mapModifierType(modifierRenderType) {
|
|
796
|
+
switch (modifierRenderType) {
|
|
797
|
+
case "FREE_TEXT":
|
|
798
|
+
return ModifierType.FREE_TEXT;
|
|
799
|
+
case "TEXT_CHOICES":
|
|
800
|
+
return ModifierType.TEXT_CHOICES;
|
|
801
|
+
case "SWATCH_CHOICES":
|
|
802
|
+
return ModifierType.COLOR_SWATCH_CHOICES;
|
|
803
|
+
default:
|
|
804
|
+
return ModifierType.FREE_TEXT;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
function mapModifierChoiceType(choiceType) {
|
|
808
|
+
if (choiceType === "ONE_COLOR")
|
|
809
|
+
return ChoiceType.ONE_COLOR;
|
|
810
|
+
else
|
|
811
|
+
return ChoiceType.CHOICE_TEXT;
|
|
812
|
+
}
|
|
813
|
+
function mapModifiersToSlowVS(modifiers) {
|
|
814
|
+
return modifiers?.map((modifier) => ({
|
|
815
|
+
name: modifier.name || modifier.freeTextSettings?.title || "",
|
|
816
|
+
_id: modifier._id,
|
|
817
|
+
modifierType: mapModifierType(modifier.modifierRenderType),
|
|
818
|
+
textInputLength: modifier.freeTextSettings?.maxCharCount,
|
|
819
|
+
textInputRequired: modifier.mandatory,
|
|
820
|
+
choices: modifier.choicesSettings?.choices?.map((choice) => ({
|
|
821
|
+
name: choice.name,
|
|
822
|
+
choiceId: choice.choiceId,
|
|
823
|
+
colorCode: choice.colorCode,
|
|
824
|
+
choiceType: mapModifierChoiceType(choice.choiceType)
|
|
825
|
+
})) ?? []
|
|
826
|
+
})) ?? [];
|
|
827
|
+
}
|
|
828
|
+
function mapModifiersToFastVS(modifiers) {
|
|
829
|
+
return modifiers?.map((modifier) => ({
|
|
830
|
+
_id: modifier._id,
|
|
831
|
+
textModifierSelection: void 0,
|
|
832
|
+
choices: modifier.choicesSettings?.choices?.map((choice) => ({
|
|
833
|
+
choiceId: choice.choiceId,
|
|
834
|
+
isSelected: false
|
|
835
|
+
})) ?? []
|
|
836
|
+
})) ?? [];
|
|
837
|
+
}
|
|
838
|
+
function mapVariants(variantsInfo) {
|
|
839
|
+
return variantsInfo?.variants.map((variant) => ({
|
|
840
|
+
_id: variant._id,
|
|
841
|
+
choices: variant.choices,
|
|
842
|
+
sku: variant.sku,
|
|
843
|
+
price: variant.price.actualPrice.formattedAmount,
|
|
844
|
+
inventoryStatus: variant.inventoryStatus.inStock ? StockStatus.IN_STOCK : StockStatus.OUT_OF_STOCK,
|
|
845
|
+
mediaId: variant.media?._id,
|
|
846
|
+
strikethroughPrice: variant.price.compareAtPrice?.formattedAmount || ""
|
|
847
|
+
})) || [];
|
|
848
|
+
}
|
|
849
|
+
async function renderSlowlyChanging$1(props, wixStores) {
|
|
850
|
+
const Pipeline = RenderPipeline.for();
|
|
851
|
+
const prefixConfig = wixStores.categoryPrefixes;
|
|
852
|
+
const hasPrefixes = prefixConfig.length > 0;
|
|
853
|
+
return Pipeline.try(async () => {
|
|
854
|
+
const fields = [
|
|
855
|
+
"INFO_SECTION",
|
|
856
|
+
"INFO_SECTION_PLAIN_DESCRIPTION",
|
|
857
|
+
"MEDIA_ITEMS_INFO",
|
|
858
|
+
"PLAIN_DESCRIPTION",
|
|
859
|
+
"CURRENCY",
|
|
860
|
+
...hasPrefixes ? ["ALL_CATEGORIES_INFO"] : []
|
|
861
|
+
];
|
|
862
|
+
const response = await wixStores.products.getProductBySlug(props.slug, {
|
|
863
|
+
fields: [...fields]
|
|
864
|
+
});
|
|
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
|
+
return response;
|
|
872
|
+
}).recover((error) => {
|
|
873
|
+
console.log("product page error", error);
|
|
874
|
+
return Pipeline.clientError(404, "not found");
|
|
875
|
+
}).toPhaseOutput((getProductResponse) => {
|
|
876
|
+
const product = getProductResponse.product;
|
|
877
|
+
const { _id, name, plainDescription, options, modifiers, actualPriceRange, compareAtPriceRange, media, productType, brand, ribbon, infoSections, seoData, physicalProperties, inventory, variantsInfo } = product;
|
|
878
|
+
return {
|
|
879
|
+
viewState: {
|
|
880
|
+
_id,
|
|
881
|
+
productName: name || "",
|
|
882
|
+
description: plainDescription,
|
|
883
|
+
brand: brand?.name || "",
|
|
884
|
+
ribbon: ribbon?.name || "",
|
|
885
|
+
productType: mapProductType(productType),
|
|
886
|
+
options: mapOptionsToSlowVS(options),
|
|
887
|
+
infoSections: mapInfoSections(infoSections),
|
|
888
|
+
modifiers: mapModifiersToSlowVS(modifiers),
|
|
889
|
+
seoData: mapSeoData(seoData)
|
|
890
|
+
},
|
|
891
|
+
carryForward: {
|
|
892
|
+
productId: _id,
|
|
893
|
+
mediaGallery: mapMedia(media),
|
|
894
|
+
options: mapOptionsToFastVS(options),
|
|
895
|
+
modifiers: mapModifiersToFastVS(modifiers),
|
|
896
|
+
sku: "N/A not in API",
|
|
897
|
+
price: actualPriceRange?.minValue?.formattedAmount || "",
|
|
898
|
+
strikethroughPrice: actualPriceRange?.minValue?.amount !== product.compareAtPriceRange?.minValue?.amount ? compareAtPriceRange?.minValue?.formattedAmount || "" : "",
|
|
899
|
+
pricePerUnit: physicalProperties?.pricePerUnitRange?.minValue?.description,
|
|
900
|
+
stockStatus: inventory?.availabilityStatus === "IN_STOCK" ? StockStatus.IN_STOCK : StockStatus.OUT_OF_STOCK,
|
|
901
|
+
variants: mapVariants(variantsInfo)
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
async function renderFastChanging(props, slowCarryForward, wixStores) {
|
|
907
|
+
const Pipeline = RenderPipeline.for();
|
|
908
|
+
const isInStock = slowCarryForward.stockStatus === StockStatus.IN_STOCK;
|
|
909
|
+
return Pipeline.ok({
|
|
910
|
+
actionsEnabled: isInStock,
|
|
911
|
+
options: slowCarryForward.options,
|
|
912
|
+
modifiers: slowCarryForward.modifiers,
|
|
913
|
+
mediaGallery: slowCarryForward.mediaGallery,
|
|
914
|
+
sku: slowCarryForward.variants[0].sku,
|
|
915
|
+
price: slowCarryForward.variants[0].price,
|
|
916
|
+
pricePerUnit: slowCarryForward.pricePerUnit || "",
|
|
917
|
+
stockStatus: slowCarryForward.stockStatus,
|
|
918
|
+
strikethroughPrice: slowCarryForward.variants[0].strikethroughPrice,
|
|
919
|
+
quantity: { quantity: 1 }
|
|
920
|
+
}).toPhaseOutput((viewState) => ({
|
|
921
|
+
viewState,
|
|
922
|
+
carryForward: {
|
|
923
|
+
productId: slowCarryForward.productId,
|
|
924
|
+
variants: slowCarryForward.variants
|
|
925
|
+
}
|
|
926
|
+
}));
|
|
927
|
+
}
|
|
928
|
+
const productPage = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withLoadParams(loadProductParams).withSlowlyRender(renderSlowlyChanging$1).withFastRender(renderFastChanging);
|
|
929
|
+
async function renderSlowlyChanging(props, wixStores) {
|
|
930
|
+
const Pipeline = RenderPipeline.for();
|
|
931
|
+
return Pipeline.try(async () => {
|
|
932
|
+
const result = await wixStores.categories.queryCategories({
|
|
933
|
+
treeReference: {
|
|
934
|
+
appNamespace: "@wix/stores"
|
|
935
|
+
}
|
|
936
|
+
}).eq("visible", true).find();
|
|
937
|
+
return result.items || [];
|
|
938
|
+
}).recover((error) => {
|
|
939
|
+
console.error("Failed to load categories:", error);
|
|
940
|
+
return Pipeline.ok([]);
|
|
941
|
+
}).toPhaseOutput((categories2) => {
|
|
942
|
+
const categoryItems = categories2.map((cat) => ({
|
|
943
|
+
_id: cat._id || "",
|
|
944
|
+
name: cat.name || "",
|
|
945
|
+
slug: cat.slug || "",
|
|
946
|
+
description: cat.description || "",
|
|
947
|
+
productCount: cat.itemCounter || 0,
|
|
948
|
+
imageUrl: cat.media?.mainMedia?.url || ""
|
|
949
|
+
}));
|
|
950
|
+
return {
|
|
951
|
+
viewState: {
|
|
952
|
+
categories: categoryItems,
|
|
953
|
+
hasCategories: categoryItems.length > 0
|
|
954
|
+
},
|
|
955
|
+
carryForward: {}
|
|
956
|
+
};
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
const categoryList = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withSlowlyRender(renderSlowlyChanging);
|
|
960
|
+
const WIX_STORES_CONTEXT = createJayContext();
|
|
961
|
+
function loadWixStoresConfig() {
|
|
962
|
+
const configPath = path.join(process.cwd(), "config", ".wix-stores.yaml");
|
|
963
|
+
if (!fs.existsSync(configPath)) {
|
|
964
|
+
return { categoryPrefixes: [] };
|
|
965
|
+
}
|
|
966
|
+
const fileContents = fs.readFileSync(configPath, "utf8");
|
|
967
|
+
const raw = yaml.load(fileContents);
|
|
968
|
+
if (!raw) {
|
|
969
|
+
return { categoryPrefixes: [] };
|
|
970
|
+
}
|
|
971
|
+
const prefixes = [];
|
|
972
|
+
if (Array.isArray(raw.categoryPrefixes)) {
|
|
973
|
+
for (const entry of raw.categoryPrefixes) {
|
|
974
|
+
if (typeof entry.categoryId === "string" && typeof entry.prefix === "string") {
|
|
975
|
+
prefixes.push({
|
|
976
|
+
categoryId: entry.categoryId.trim(),
|
|
977
|
+
prefix: entry.prefix.trim(),
|
|
978
|
+
name: typeof entry.name === "string" ? entry.name.trim() : entry.prefix.trim()
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return { categoryPrefixes: prefixes };
|
|
984
|
+
}
|
|
985
|
+
const init = makeJayInit().withServer(async () => {
|
|
986
|
+
console.log("[wix-stores] Initializing Wix Stores service...");
|
|
987
|
+
const wixClient = getService(WIX_CLIENT_SERVICE);
|
|
988
|
+
const storesConfig = loadWixStoresConfig();
|
|
989
|
+
provideWixStoresService(wixClient, {
|
|
990
|
+
categoryPrefixes: storesConfig.categoryPrefixes
|
|
991
|
+
});
|
|
992
|
+
if (storesConfig.categoryPrefixes.length > 0) {
|
|
993
|
+
console.log(`[wix-stores] Category prefixes configured: ${storesConfig.categoryPrefixes.map((p) => p.prefix).join(", ")}`);
|
|
994
|
+
}
|
|
995
|
+
console.log("[wix-stores] Server initialization complete");
|
|
996
|
+
return {
|
|
997
|
+
enableClientCart: true,
|
|
998
|
+
enableClientSearch: true
|
|
999
|
+
};
|
|
1000
|
+
});
|
|
1001
|
+
const CONFIG_FILE_NAME = ".wix-stores.yaml";
|
|
1002
|
+
const CONFIG_TEMPLATE = `# Wix Stores Configuration
|
|
1003
|
+
#
|
|
1004
|
+
# Category Prefixes (optional):
|
|
1005
|
+
# Maps root Wix categories to URL prefix slugs.
|
|
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.
|
|
1008
|
+
#
|
|
1009
|
+
# To find category IDs, use: jay-stack action wix-stores/getCategories
|
|
1010
|
+
#
|
|
1011
|
+
# categoryPrefixes:
|
|
1012
|
+
# - categoryId: "<root-category-id>"
|
|
1013
|
+
# prefix: "<url-prefix>"
|
|
1014
|
+
# name: "<display-name>"
|
|
1015
|
+
# - categoryId: "<another-root-category-id>"
|
|
1016
|
+
# prefix: "<another-url-prefix>"
|
|
1017
|
+
# name: "<another-display-name>"
|
|
1018
|
+
`;
|
|
1019
|
+
async function setupWixStores(ctx) {
|
|
1020
|
+
if (ctx.initError) {
|
|
1021
|
+
return {
|
|
1022
|
+
status: "error",
|
|
1023
|
+
message: `Service init failed (is wix-server-client configured?). ${ctx.initError.message}`
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
try {
|
|
1027
|
+
getService(WIX_STORES_SERVICE_MARKER);
|
|
1028
|
+
} catch {
|
|
1029
|
+
return {
|
|
1030
|
+
status: "error",
|
|
1031
|
+
message: "WixStoresService not available. Run setup for wix-server-client first."
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
const configPath = path.join(ctx.configDir, CONFIG_FILE_NAME);
|
|
1035
|
+
const configCreated = [];
|
|
1036
|
+
if (!fs.existsSync(configPath)) {
|
|
1037
|
+
if (!fs.existsSync(ctx.configDir)) {
|
|
1038
|
+
fs.mkdirSync(ctx.configDir, { recursive: true });
|
|
1039
|
+
}
|
|
1040
|
+
fs.writeFileSync(configPath, CONFIG_TEMPLATE, "utf-8");
|
|
1041
|
+
configCreated.push(`config/${CONFIG_FILE_NAME}`);
|
|
1042
|
+
}
|
|
1043
|
+
const service = getService(WIX_STORES_SERVICE_MARKER);
|
|
1044
|
+
const prefixCount = service.categoryPrefixes.length;
|
|
1045
|
+
const message = prefixCount > 0 ? `Wix Stores configured with ${prefixCount} category prefix(es): ${service.categoryPrefixes.map((p) => p.prefix).join(", ")}` : "Wix Stores service verified";
|
|
1046
|
+
return {
|
|
1047
|
+
status: "configured",
|
|
1048
|
+
message,
|
|
1049
|
+
...configCreated.length > 0 ? { configCreated } : {}
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
async function generateWixStoresReferences(ctx) {
|
|
1053
|
+
if (ctx.initError) {
|
|
1054
|
+
throw new Error(`init failed: ${ctx.initError.message}`);
|
|
1055
|
+
}
|
|
1056
|
+
let storesService;
|
|
1057
|
+
try {
|
|
1058
|
+
storesService = getService(WIX_STORES_SERVICE_MARKER);
|
|
1059
|
+
} catch {
|
|
1060
|
+
throw new Error("WixStoresService not available. Run jay-stack setup first.");
|
|
1061
|
+
}
|
|
1062
|
+
fs.mkdirSync(ctx.referencesDir, { recursive: true });
|
|
1063
|
+
const allCategories = [];
|
|
1064
|
+
let result = await storesService.categories.queryCategories({
|
|
1065
|
+
treeReference: { appNamespace: "@wix/stores" }
|
|
1066
|
+
}).eq("visible", true).limit(100).find();
|
|
1067
|
+
allCategories.push(...result.items || []);
|
|
1068
|
+
while (result.hasNext()) {
|
|
1069
|
+
result = await result.next();
|
|
1070
|
+
allCategories.push(...result.items || []);
|
|
1071
|
+
}
|
|
1072
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
1073
|
+
for (const cat of allCategories) {
|
|
1074
|
+
if (!cat._id) continue;
|
|
1075
|
+
nodeMap.set(cat._id, {
|
|
1076
|
+
_id: cat._id,
|
|
1077
|
+
name: cat.name || "",
|
|
1078
|
+
slug: cat.slug || "",
|
|
1079
|
+
productCount: cat.itemCounter || 0,
|
|
1080
|
+
children: []
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
const roots = [];
|
|
1084
|
+
for (const cat of allCategories) {
|
|
1085
|
+
if (!cat._id) continue;
|
|
1086
|
+
const node = nodeMap.get(cat._id);
|
|
1087
|
+
const parentId = cat.parentCategory?._id;
|
|
1088
|
+
if (parentId && nodeMap.has(parentId)) {
|
|
1089
|
+
nodeMap.get(parentId).children.push(node);
|
|
1090
|
+
} else {
|
|
1091
|
+
roots.push(node);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
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
|
+
const categoriesPath = path.join(ctx.referencesDir, "categories.yaml");
|
|
1102
|
+
fs.writeFileSync(
|
|
1103
|
+
categoriesPath,
|
|
1104
|
+
yaml.dump(
|
|
1105
|
+
{
|
|
1106
|
+
_generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1107
|
+
_description: "Wix Stores category tree for agent discovery. Shows category hierarchy, IDs, product counts, and configured URL prefixes.",
|
|
1108
|
+
totalCategories: allCategories.length,
|
|
1109
|
+
configuredPrefixes: configuredPrefixes.length > 0 ? configuredPrefixes : void 0,
|
|
1110
|
+
categoryTree: roots
|
|
1111
|
+
},
|
|
1112
|
+
{ indent: 2, lineWidth: 120, noRefs: true }
|
|
1113
|
+
),
|
|
1114
|
+
"utf-8"
|
|
1115
|
+
);
|
|
1116
|
+
return {
|
|
1117
|
+
referencesCreated: [`agent-kit/references/${ctx.pluginName}/categories.yaml`],
|
|
1118
|
+
message: `${allCategories.length} categories (${roots.length} root)`
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
export {
|
|
1122
|
+
WIX_CART_CONTEXT,
|
|
1123
|
+
WIX_CART_SERVICE,
|
|
1124
|
+
WIX_STORES_CONTEXT,
|
|
1125
|
+
WIX_STORES_SERVICE_MARKER,
|
|
1126
|
+
cartIndicator,
|
|
1127
|
+
cartPage,
|
|
1128
|
+
categoryList,
|
|
1129
|
+
generateWixStoresReferences,
|
|
1130
|
+
getCategories,
|
|
1131
|
+
getProductBySlug,
|
|
1132
|
+
init,
|
|
1133
|
+
productPage,
|
|
1134
|
+
productSearch,
|
|
1135
|
+
provideWixCartContext,
|
|
1136
|
+
provideWixCartService,
|
|
1137
|
+
provideWixStoresService,
|
|
1138
|
+
searchProducts,
|
|
1139
|
+
setupWixStores
|
|
1140
|
+
};
|