@jay-framework/wix-stores 0.16.5 → 0.17.1
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/dist/contracts/product-card.jay-contract +5 -0
- package/dist/contracts/product-card.jay-contract.d.ts +2 -0
- package/dist/contracts/product-page.jay-contract +1 -0
- package/dist/contracts/product-page.jay-contract.d.ts +2 -1
- package/dist/contracts/related-products.jay-contract +31 -0
- package/dist/contracts/related-products.jay-contract.d.ts +36 -0
- package/dist/index.client.js +195 -178
- package/dist/index.d.ts +37 -2
- package/dist/index.js +154 -38
- package/package.json +16 -15
- package/plugin.yaml +4 -0
|
@@ -183,6 +183,11 @@ tags:
|
|
|
183
183
|
description: Secondary option for quick selection (text choices for COLOR_AND_TEXT_OPTIONS). Rendered as buttons or dropdown. Selection adds to cart.
|
|
184
184
|
link: ./product-options
|
|
185
185
|
|
|
186
|
+
- tag: cardContainer
|
|
187
|
+
type: interactive
|
|
188
|
+
elementType: HTMLElement
|
|
189
|
+
description: The product card root element. Must be placed on the outermost card wrapper so that mouseenter preloads variant stock for quick-add options.
|
|
190
|
+
|
|
186
191
|
- tag: viewOptionsButton
|
|
187
192
|
type: interactive
|
|
188
193
|
elementType: HTMLButtonElement
|
|
@@ -116,6 +116,7 @@ export type ProductCardInteractiveViewState = Pick<ProductCardViewState, 'price'
|
|
|
116
116
|
export interface ProductCardRefs {
|
|
117
117
|
productLink: HTMLElementProxy<ProductCardViewState, HTMLAnchorElement>,
|
|
118
118
|
addToCartButton: HTMLElementProxy<ProductCardViewState, HTMLButtonElement>,
|
|
119
|
+
cardContainer: HTMLElementProxy<ProductCardViewState, HTMLElement>,
|
|
119
120
|
viewOptionsButton: HTMLElementProxy<ProductCardViewState, HTMLButtonElement>,
|
|
120
121
|
quickOption: ProductOptionsRefs,
|
|
121
122
|
secondQuickOption: ProductOptionsRefs
|
|
@@ -125,6 +126,7 @@ export interface ProductCardRefs {
|
|
|
125
126
|
export interface ProductCardRepeatedRefs {
|
|
126
127
|
productLink: HTMLElementCollectionProxy<ProductCardViewState, HTMLAnchorElement>,
|
|
127
128
|
addToCartButton: HTMLElementCollectionProxy<ProductCardViewState, HTMLButtonElement>,
|
|
129
|
+
cardContainer: HTMLElementCollectionProxy<ProductCardViewState, HTMLElement>,
|
|
128
130
|
viewOptionsButton: HTMLElementCollectionProxy<ProductCardViewState, HTMLButtonElement>,
|
|
129
131
|
quickOption: ProductOptionsRepeatedRefs,
|
|
130
132
|
secondQuickOption: ProductOptionsRepeatedRefs
|
|
@@ -16,6 +16,7 @@ tags:
|
|
|
16
16
|
- {tag: description, type: data, dataType: string, description: Product description}
|
|
17
17
|
- {tag: brand, type: data, dataType: string, description: Brand name}
|
|
18
18
|
- {tag: ribbon, type: data, dataType: string, description: "Ribbon text (e.g., \"New\", \"Sale\")"}
|
|
19
|
+
- {tag: categorySlug, type: data, dataType: string, description: Slug of the product's main category}
|
|
19
20
|
- {tag: productType, type: variant, dataType: "enum (PHYSICAL | DIGITAL)", description: Product type }
|
|
20
21
|
|
|
21
22
|
- {tag: sku, type: data, dataType: string, phase: fast+interactive, description: Product SKU, or chosen variant SKU}
|
|
@@ -84,6 +84,7 @@ export interface ProductPageViewState {
|
|
|
84
84
|
description: string,
|
|
85
85
|
brand: string,
|
|
86
86
|
ribbon: string,
|
|
87
|
+
categorySlug: string,
|
|
87
88
|
productType: ProductType,
|
|
88
89
|
sku: string,
|
|
89
90
|
price: string,
|
|
@@ -98,7 +99,7 @@ export interface ProductPageViewState {
|
|
|
98
99
|
modifiers: Array<ModifierOfProductPageViewState>
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
export type ProductPageSlowViewState = Pick<ProductPageViewState, '_id' | 'productName' | 'description' | 'brand' | 'ribbon' | 'productType'> & {
|
|
102
|
+
export type ProductPageSlowViewState = Pick<ProductPageViewState, '_id' | 'productName' | 'description' | 'brand' | 'ribbon' | 'categorySlug' | 'productType'> & {
|
|
102
103
|
options: Array<Pick<ProductPageViewState['options'][number], '_id' | 'name' | 'optionRenderType'> & {
|
|
103
104
|
choices: Array<Pick<ProductPageViewState['options'][number]['choices'][number], 'choiceId' | 'choiceType' | 'name' | 'colorCode' | 'inStock'>>;
|
|
104
105
|
}>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: related-products
|
|
2
|
+
description: Related products grid showing products from the same category. Use on product pages alongside the product-page component.
|
|
3
|
+
props:
|
|
4
|
+
- name: productId
|
|
5
|
+
type: string
|
|
6
|
+
description: Product ID to exclude from results
|
|
7
|
+
- name: categorySlug
|
|
8
|
+
type: string
|
|
9
|
+
description: Category slug to filter related products by
|
|
10
|
+
- name: limit
|
|
11
|
+
type: number
|
|
12
|
+
description: Maximum number of related products to show (default 4)
|
|
13
|
+
tags:
|
|
14
|
+
- tag: products
|
|
15
|
+
type: sub-contract
|
|
16
|
+
repeated: true
|
|
17
|
+
trackBy: _id
|
|
18
|
+
phase: fast+interactive
|
|
19
|
+
description: Related product cards
|
|
20
|
+
link: ./product-card
|
|
21
|
+
|
|
22
|
+
- tag: hasProducts
|
|
23
|
+
type: variant
|
|
24
|
+
dataType: boolean
|
|
25
|
+
phase: fast+interactive
|
|
26
|
+
description: Whether there are related products to show
|
|
27
|
+
|
|
28
|
+
- tag: categoryName
|
|
29
|
+
type: data
|
|
30
|
+
dataType: string
|
|
31
|
+
description: Name of the category these products belong to
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {JayContract} from "@jay-framework/runtime";
|
|
2
|
+
import {ProductCardViewState, ProductCardRefs, ProductCardRepeatedRefs} from "./product-card.jay-contract";
|
|
3
|
+
|
|
4
|
+
export interface RelatedProductsViewState {
|
|
5
|
+
products: Array<ProductCardViewState>,
|
|
6
|
+
hasProducts: boolean,
|
|
7
|
+
categoryName: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type RelatedProductsSlowViewState = Pick<RelatedProductsViewState, 'categoryName'>;
|
|
11
|
+
|
|
12
|
+
export type RelatedProductsFastViewState = Pick<RelatedProductsViewState, 'hasProducts'> & {
|
|
13
|
+
products: Array<RelatedProductsViewState['products'][number]>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type RelatedProductsInteractiveViewState = Pick<RelatedProductsViewState, 'hasProducts'> & {
|
|
17
|
+
products: Array<RelatedProductsViewState['products'][number]>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export interface RelatedProductsRefs {
|
|
22
|
+
products: ProductCardRepeatedRefs
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export interface RelatedProductsRepeatedRefs {
|
|
27
|
+
products: ProductCardRepeatedRefs
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RelatedProductsProps {
|
|
31
|
+
productId?: string;
|
|
32
|
+
categorySlug?: string;
|
|
33
|
+
limit?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type RelatedProductsContract = JayContract<RelatedProductsViewState, RelatedProductsRefs, RelatedProductsSlowViewState, RelatedProductsFastViewState, RelatedProductsInteractiveViewState, RelatedProductsProps>
|
package/dist/index.client.js
CHANGED
|
@@ -339,6 +339,186 @@ var QuickAddType = /* @__PURE__ */ ((QuickAddType2) => {
|
|
|
339
339
|
QuickAddType2[QuickAddType2["NEEDS_CONFIGURATION"] = 3] = "NEEDS_CONFIGURATION";
|
|
340
340
|
return QuickAddType2;
|
|
341
341
|
})(QuickAddType || {});
|
|
342
|
+
function setupCardInteractions(refs, cards, storesContext) {
|
|
343
|
+
const variantStockCache = {};
|
|
344
|
+
let variantStockApplied = /* @__PURE__ */ new Set();
|
|
345
|
+
const variantStockLoading = /* @__PURE__ */ new Set();
|
|
346
|
+
const loadVariantStock = async (productId) => {
|
|
347
|
+
if (variantStockApplied.has(productId) || variantStockLoading.has(productId)) return;
|
|
348
|
+
variantStockLoading.add(productId);
|
|
349
|
+
try {
|
|
350
|
+
const currentResults = cards.get();
|
|
351
|
+
const productIndex = currentResults.findIndex((p) => p._id === productId);
|
|
352
|
+
if (productIndex === -1) return;
|
|
353
|
+
const product = currentResults[productIndex];
|
|
354
|
+
if (product?.quickAddType !== QuickAddType.COLOR_AND_TEXT_OPTIONS) return;
|
|
355
|
+
const stockMap = variantStockCache[productId] ?? await getVariantStock({ productId });
|
|
356
|
+
variantStockCache[productId] = stockMap;
|
|
357
|
+
variantStockApplied.add(productId);
|
|
358
|
+
const selectedColor = product.quickOption?.choices?.find((c) => c.isSelected);
|
|
359
|
+
const textChoices = product.secondQuickOption?.choices;
|
|
360
|
+
if (!selectedColor || !textChoices) return;
|
|
361
|
+
const colorStock = stockMap[selectedColor.choiceId];
|
|
362
|
+
const updatedTextChoices = textChoices.map((c) => ({
|
|
363
|
+
...c,
|
|
364
|
+
inStock: colorStock?.[c.choiceId] ?? false
|
|
365
|
+
}));
|
|
366
|
+
cards.set(
|
|
367
|
+
patch(cards.get(), [
|
|
368
|
+
{
|
|
369
|
+
op: REPLACE,
|
|
370
|
+
path: [productIndex, "secondQuickOption", "choices"],
|
|
371
|
+
value: updatedTextChoices
|
|
372
|
+
}
|
|
373
|
+
])
|
|
374
|
+
);
|
|
375
|
+
} finally {
|
|
376
|
+
variantStockLoading.delete(productId);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
refs.addToCartButton.onclick(async ({ coordinate }) => {
|
|
380
|
+
const [productId] = coordinate;
|
|
381
|
+
const currentResults = cards.get();
|
|
382
|
+
const productIndex = currentResults.findIndex((p) => p._id === productId);
|
|
383
|
+
if (productIndex === -1) return;
|
|
384
|
+
cards.set(
|
|
385
|
+
patch(currentResults, [
|
|
386
|
+
{ op: REPLACE, path: [productIndex, "isAddingToCart"], value: true }
|
|
387
|
+
])
|
|
388
|
+
);
|
|
389
|
+
try {
|
|
390
|
+
await storesContext.addToCart(productId, 1);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
console.error("Failed to add to cart:", error);
|
|
393
|
+
} finally {
|
|
394
|
+
cards.set(
|
|
395
|
+
patch(cards.get(), [
|
|
396
|
+
{ op: REPLACE, path: [productIndex, "isAddingToCart"], value: false }
|
|
397
|
+
])
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
refs.cardContainer.onmouseenter(({ coordinate }) => {
|
|
402
|
+
const [productId] = coordinate;
|
|
403
|
+
loadVariantStock(productId);
|
|
404
|
+
});
|
|
405
|
+
refs.quickOption.choices.choiceButton.onclick(async ({ coordinate }) => {
|
|
406
|
+
const [productId, choiceId] = coordinate;
|
|
407
|
+
const currentResults = cards.get();
|
|
408
|
+
const productIndex = currentResults.findIndex((p) => p._id === productId);
|
|
409
|
+
if (productIndex === -1) return;
|
|
410
|
+
const product = currentResults[productIndex];
|
|
411
|
+
if (product.quickAddType === QuickAddType.COLOR_AND_TEXT_OPTIONS) {
|
|
412
|
+
const choices = product.quickOption?.choices;
|
|
413
|
+
if (!choices) return;
|
|
414
|
+
const updatedChoices = choices.map((c) => ({
|
|
415
|
+
...c,
|
|
416
|
+
isSelected: c.choiceId === choiceId
|
|
417
|
+
}));
|
|
418
|
+
let updated = patch(currentResults, [
|
|
419
|
+
{
|
|
420
|
+
op: REPLACE,
|
|
421
|
+
path: [productIndex, "quickOption", "choices"],
|
|
422
|
+
value: updatedChoices
|
|
423
|
+
}
|
|
424
|
+
]);
|
|
425
|
+
const stockMap = variantStockCache[productId];
|
|
426
|
+
if (stockMap) {
|
|
427
|
+
const colorStock = stockMap[choiceId];
|
|
428
|
+
const textChoices = product.secondQuickOption?.choices;
|
|
429
|
+
if (textChoices) {
|
|
430
|
+
const updatedTextChoices = textChoices.map((c) => ({
|
|
431
|
+
...c,
|
|
432
|
+
inStock: colorStock?.[c.choiceId] ?? false
|
|
433
|
+
}));
|
|
434
|
+
updated = patch(updated, [
|
|
435
|
+
{
|
|
436
|
+
op: REPLACE,
|
|
437
|
+
path: [productIndex, "secondQuickOption", "choices"],
|
|
438
|
+
value: updatedTextChoices
|
|
439
|
+
}
|
|
440
|
+
]);
|
|
441
|
+
}
|
|
442
|
+
} else {
|
|
443
|
+
loadVariantStock(productId);
|
|
444
|
+
}
|
|
445
|
+
cards.set(updated);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const choice = product.quickOption?.choices?.find((c) => c.choiceId === choiceId);
|
|
449
|
+
if (!choice || !choice.inStock) return;
|
|
450
|
+
cards.set(
|
|
451
|
+
patch(currentResults, [
|
|
452
|
+
{ op: REPLACE, path: [productIndex, "isAddingToCart"], value: true }
|
|
453
|
+
])
|
|
454
|
+
);
|
|
455
|
+
try {
|
|
456
|
+
const optionId = product.quickOption._id;
|
|
457
|
+
await storesContext.addToCart(productId, 1, {
|
|
458
|
+
options: { [optionId]: choice.choiceId },
|
|
459
|
+
modifiers: {},
|
|
460
|
+
customTextFields: {}
|
|
461
|
+
});
|
|
462
|
+
} catch (error) {
|
|
463
|
+
console.error("Failed to add to cart:", error);
|
|
464
|
+
} finally {
|
|
465
|
+
cards.set(
|
|
466
|
+
patch(cards.get(), [
|
|
467
|
+
{ op: REPLACE, path: [productIndex, "isAddingToCart"], value: false }
|
|
468
|
+
])
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
refs.secondQuickOption.choices.choiceButton.onclick(async ({ coordinate }) => {
|
|
473
|
+
const [productId, choiceId] = coordinate;
|
|
474
|
+
const currentResults = cards.get();
|
|
475
|
+
const productIndex = currentResults.findIndex((p) => p._id === productId);
|
|
476
|
+
if (productIndex === -1) return;
|
|
477
|
+
const product = currentResults[productIndex];
|
|
478
|
+
const textChoice = product.secondQuickOption?.choices?.find(
|
|
479
|
+
(c) => c.choiceId === choiceId
|
|
480
|
+
);
|
|
481
|
+
const selectedColor = product.quickOption?.choices?.find((c) => c.isSelected);
|
|
482
|
+
if (!textChoice || !textChoice.inStock) return;
|
|
483
|
+
cards.set(
|
|
484
|
+
patch(currentResults, [
|
|
485
|
+
{ op: REPLACE, path: [productIndex, "isAddingToCart"], value: true }
|
|
486
|
+
])
|
|
487
|
+
);
|
|
488
|
+
try {
|
|
489
|
+
const colorOptionId = product.quickOption?._id || "";
|
|
490
|
+
const textOptionId = product.secondQuickOption?._id || "";
|
|
491
|
+
await storesContext.addToCart(productId, 1, {
|
|
492
|
+
options: {
|
|
493
|
+
[colorOptionId]: selectedColor?.choiceId || "",
|
|
494
|
+
[textOptionId]: textChoice.choiceId
|
|
495
|
+
},
|
|
496
|
+
modifiers: {},
|
|
497
|
+
customTextFields: {}
|
|
498
|
+
});
|
|
499
|
+
} catch (error) {
|
|
500
|
+
console.error("Failed to add to cart:", error);
|
|
501
|
+
} finally {
|
|
502
|
+
cards.set(
|
|
503
|
+
patch(cards.get(), [
|
|
504
|
+
{ op: REPLACE, path: [productIndex, "isAddingToCart"], value: false }
|
|
505
|
+
])
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
refs.viewOptionsButton.onclick(({ coordinate }) => {
|
|
510
|
+
const [productId] = coordinate;
|
|
511
|
+
const product = cards.get().find((p) => p._id === productId);
|
|
512
|
+
if (product?.productUrl) {
|
|
513
|
+
window.location.href = product.productUrl;
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
return {
|
|
517
|
+
resetCache() {
|
|
518
|
+
variantStockApplied = /* @__PURE__ */ new Set();
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
}
|
|
342
522
|
const PAGE_SIZE = 12;
|
|
343
523
|
function mapSortToAction(sort) {
|
|
344
524
|
switch (sort) {
|
|
@@ -425,7 +605,6 @@ function buildOptionFiltersViewState(baseOptionFilters, filteredResult, optionSe
|
|
|
425
605
|
}
|
|
426
606
|
function ProductSearchInteractive(props, refs, viewStateSignals, fastCarryForward, storesContext) {
|
|
427
607
|
const baseCategoryId = fastCarryForward.baseCategoryId;
|
|
428
|
-
const variantStockCache = {};
|
|
429
608
|
const { searchExpression: [searchExpression, setSearchExpression], isSearching: [isSearching, setIsSearching], hasSearched: [hasSearched, setHasSearched], searchResults: [searchResults, setSearchResults], resultCount: [resultCount, setResultCount], hasResults: [hasResults, setHasResults], hasSuggestions: [hasSuggestions, setHasSuggestions], suggestions: [suggestions, setSuggestions], filters: [filters, setFilters], sortBy: [sortBy, setSortBy], hasMore: [hasMore, setHasMore], loadedCount: [loadedCount, setLoadedCount], totalCount: [totalCount, setTotalCount] } = viewStateSignals;
|
|
430
609
|
const [submittedSearchTerm, setSubmittedSearchTerm] = createSignal(null);
|
|
431
610
|
let currentCursor = null;
|
|
@@ -476,6 +655,7 @@ function ProductSearchInteractive(props, refs, viewStateSignals, fastCarryForwar
|
|
|
476
655
|
if (version !== searchVersion) {
|
|
477
656
|
return;
|
|
478
657
|
}
|
|
658
|
+
resetCardCache();
|
|
479
659
|
setSearchResults(result.products);
|
|
480
660
|
setResultCount(result.products.length);
|
|
481
661
|
setTotalCount(result.totalCount);
|
|
@@ -688,182 +868,7 @@ function ProductSearchInteractive(props, refs, viewStateSignals, fastCarryForwar
|
|
|
688
868
|
setSubmittedSearchTerm(suggestion.suggestionText);
|
|
689
869
|
}
|
|
690
870
|
});
|
|
691
|
-
refs.searchResults
|
|
692
|
-
const [productId] = coordinate;
|
|
693
|
-
const currentResults = searchResults();
|
|
694
|
-
const productIndex = currentResults.findIndex((p) => p._id === productId);
|
|
695
|
-
if (productIndex === -1)
|
|
696
|
-
return;
|
|
697
|
-
setSearchResults(patch(currentResults, [
|
|
698
|
-
{ op: REPLACE, path: [productIndex, "isAddingToCart"], value: true }
|
|
699
|
-
]));
|
|
700
|
-
try {
|
|
701
|
-
await storesContext.addToCart(productId, 1);
|
|
702
|
-
} catch (error) {
|
|
703
|
-
console.error("Failed to add to cart:", error);
|
|
704
|
-
} finally {
|
|
705
|
-
setSearchResults(patch(searchResults(), [
|
|
706
|
-
{ op: REPLACE, path: [productIndex, "isAddingToCart"], value: false }
|
|
707
|
-
]));
|
|
708
|
-
}
|
|
709
|
-
});
|
|
710
|
-
const variantStockLoading = /* @__PURE__ */ new Set();
|
|
711
|
-
const loadVariantStock = async (productId) => {
|
|
712
|
-
if (variantStockCache[productId] || variantStockLoading.has(productId))
|
|
713
|
-
return;
|
|
714
|
-
variantStockLoading.add(productId);
|
|
715
|
-
try {
|
|
716
|
-
const currentResults = searchResults();
|
|
717
|
-
const productIndex = currentResults.findIndex((p) => p._id === productId);
|
|
718
|
-
if (productIndex === -1)
|
|
719
|
-
return;
|
|
720
|
-
const product = currentResults[productIndex];
|
|
721
|
-
if (product?.quickAddType !== QuickAddType.COLOR_AND_TEXT_OPTIONS)
|
|
722
|
-
return;
|
|
723
|
-
const stockMap = await getVariantStock({ productId });
|
|
724
|
-
variantStockCache[productId] = stockMap;
|
|
725
|
-
const selectedColor = product.quickOption?.choices?.find((c) => c.isSelected);
|
|
726
|
-
const textChoices = product.secondQuickOption?.choices;
|
|
727
|
-
if (!selectedColor || !textChoices)
|
|
728
|
-
return;
|
|
729
|
-
const colorStock = stockMap[selectedColor.choiceId];
|
|
730
|
-
const updatedTextChoices = textChoices.map((c) => ({
|
|
731
|
-
...c,
|
|
732
|
-
inStock: colorStock?.[c.choiceId] ?? false
|
|
733
|
-
}));
|
|
734
|
-
setSearchResults(patch(searchResults(), [
|
|
735
|
-
{
|
|
736
|
-
op: REPLACE,
|
|
737
|
-
path: [productIndex, "secondQuickOption", "choices"],
|
|
738
|
-
value: updatedTextChoices
|
|
739
|
-
}
|
|
740
|
-
]));
|
|
741
|
-
} finally {
|
|
742
|
-
variantStockLoading.delete(productId);
|
|
743
|
-
}
|
|
744
|
-
};
|
|
745
|
-
refs.searchResults.productLink.onmouseenter(({ coordinate }) => {
|
|
746
|
-
const [productId] = coordinate;
|
|
747
|
-
loadVariantStock(productId);
|
|
748
|
-
});
|
|
749
|
-
refs.searchResults.quickOption.choices.choiceButton.onmouseenter(({ coordinate }) => {
|
|
750
|
-
const [productId] = coordinate;
|
|
751
|
-
loadVariantStock(productId);
|
|
752
|
-
});
|
|
753
|
-
refs.searchResults.quickOption.choices.choiceButton.onclick(async ({ coordinate }) => {
|
|
754
|
-
const [productId, choiceId] = coordinate;
|
|
755
|
-
const currentResults = searchResults();
|
|
756
|
-
const productIndex = currentResults.findIndex((p) => p._id === productId);
|
|
757
|
-
if (productIndex === -1)
|
|
758
|
-
return;
|
|
759
|
-
const product = currentResults[productIndex];
|
|
760
|
-
if (product.quickAddType === QuickAddType.COLOR_AND_TEXT_OPTIONS) {
|
|
761
|
-
const choices = product.quickOption?.choices;
|
|
762
|
-
if (!choices)
|
|
763
|
-
return;
|
|
764
|
-
const updatedChoices = choices.map((c) => ({
|
|
765
|
-
...c,
|
|
766
|
-
isSelected: c.choiceId === choiceId
|
|
767
|
-
}));
|
|
768
|
-
let updated = patch(currentResults, [
|
|
769
|
-
{
|
|
770
|
-
op: REPLACE,
|
|
771
|
-
path: [productIndex, "quickOption", "choices"],
|
|
772
|
-
value: updatedChoices
|
|
773
|
-
}
|
|
774
|
-
]);
|
|
775
|
-
const stockMap = variantStockCache[productId];
|
|
776
|
-
if (stockMap) {
|
|
777
|
-
const colorStock = stockMap[choiceId];
|
|
778
|
-
const textChoices = product.secondQuickOption?.choices;
|
|
779
|
-
if (textChoices) {
|
|
780
|
-
const updatedTextChoices = textChoices.map((c) => ({
|
|
781
|
-
...c,
|
|
782
|
-
inStock: colorStock?.[c.choiceId] ?? false
|
|
783
|
-
}));
|
|
784
|
-
updated = patch(updated, [
|
|
785
|
-
{
|
|
786
|
-
op: REPLACE,
|
|
787
|
-
path: [productIndex, "secondQuickOption", "choices"],
|
|
788
|
-
value: updatedTextChoices
|
|
789
|
-
}
|
|
790
|
-
]);
|
|
791
|
-
}
|
|
792
|
-
} else {
|
|
793
|
-
loadVariantStock(productId);
|
|
794
|
-
}
|
|
795
|
-
setSearchResults(updated);
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
const choice = product.quickOption?.choices?.find((c) => c.choiceId === choiceId);
|
|
799
|
-
if (!choice || !choice.inStock) {
|
|
800
|
-
console.warn("Choice not available or out of stock");
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
setSearchResults(patch(currentResults, [
|
|
804
|
-
{ op: REPLACE, path: [productIndex, "isAddingToCart"], value: true }
|
|
805
|
-
]));
|
|
806
|
-
try {
|
|
807
|
-
const optionId = product.quickOption._id;
|
|
808
|
-
await storesContext.addToCart(productId, 1, {
|
|
809
|
-
options: { [optionId]: choice.choiceId },
|
|
810
|
-
modifiers: {},
|
|
811
|
-
customTextFields: {}
|
|
812
|
-
});
|
|
813
|
-
} catch (error) {
|
|
814
|
-
console.error("Failed to add to cart:", error);
|
|
815
|
-
} finally {
|
|
816
|
-
setSearchResults(patch(searchResults(), [
|
|
817
|
-
{ op: REPLACE, path: [productIndex, "isAddingToCart"], value: false }
|
|
818
|
-
]));
|
|
819
|
-
}
|
|
820
|
-
});
|
|
821
|
-
refs.searchResults.secondQuickOption.choices.choiceButton.onclick(async ({ coordinate }) => {
|
|
822
|
-
const [productId, choiceId] = coordinate;
|
|
823
|
-
const currentResults = searchResults();
|
|
824
|
-
const productIndex = currentResults.findIndex((p) => p._id === productId);
|
|
825
|
-
if (productIndex === -1)
|
|
826
|
-
return;
|
|
827
|
-
const product = currentResults[productIndex];
|
|
828
|
-
const textChoice = product.secondQuickOption?.choices?.find((c) => c.choiceId === choiceId);
|
|
829
|
-
const selectedColor = product.quickOption?.choices?.find((c) => c.isSelected);
|
|
830
|
-
if (!textChoice || !textChoice.inStock) {
|
|
831
|
-
console.warn("Text choice not available or out of stock");
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
setSearchResults(patch(currentResults, [
|
|
835
|
-
{ op: REPLACE, path: [productIndex, "isAddingToCart"], value: true }
|
|
836
|
-
]));
|
|
837
|
-
try {
|
|
838
|
-
const colorOptionId = product.quickOption?._id || "";
|
|
839
|
-
const textOptionId = product.secondQuickOption?._id || "";
|
|
840
|
-
await storesContext.addToCart(productId, 1, {
|
|
841
|
-
options: {
|
|
842
|
-
[colorOptionId]: selectedColor?.choiceId || "",
|
|
843
|
-
[textOptionId]: textChoice.choiceId
|
|
844
|
-
},
|
|
845
|
-
modifiers: {},
|
|
846
|
-
customTextFields: {}
|
|
847
|
-
});
|
|
848
|
-
} catch (error) {
|
|
849
|
-
console.error("Failed to add to cart:", error);
|
|
850
|
-
} finally {
|
|
851
|
-
setSearchResults(patch(searchResults(), [
|
|
852
|
-
{ op: REPLACE, path: [productIndex, "isAddingToCart"], value: false }
|
|
853
|
-
]));
|
|
854
|
-
}
|
|
855
|
-
});
|
|
856
|
-
refs.searchResults.secondQuickOption.choices.choiceButton.onmouseenter(({ coordinate }) => {
|
|
857
|
-
const [productId] = coordinate;
|
|
858
|
-
loadVariantStock(productId);
|
|
859
|
-
});
|
|
860
|
-
refs.searchResults.viewOptionsButton.onclick(({ coordinate }) => {
|
|
861
|
-
const [productId] = coordinate;
|
|
862
|
-
const product = searchResults().find((p) => p._id === productId);
|
|
863
|
-
if (product?.productUrl) {
|
|
864
|
-
window.location.href = product.productUrl;
|
|
865
|
-
}
|
|
866
|
-
});
|
|
871
|
+
const { resetCache: resetCardCache } = setupCardInteractions(refs.searchResults, { get: searchResults, set: setSearchResults }, storesContext);
|
|
867
872
|
return {
|
|
868
873
|
render: () => ({
|
|
869
874
|
searchExpression: searchExpression(),
|
|
@@ -883,6 +888,17 @@ function ProductSearchInteractive(props, refs, viewStateSignals, fastCarryForwar
|
|
|
883
888
|
};
|
|
884
889
|
}
|
|
885
890
|
const productSearch = makeJayStackComponent().withProps().withContexts(WIX_STORES_CONTEXT).withInteractive(ProductSearchInteractive);
|
|
891
|
+
function RelatedProductsInteractive(_props, refs, viewStateSignals, _fastCarryForward, storesContext) {
|
|
892
|
+
const { products: [products, setProducts], hasProducts: [hasProducts] } = viewStateSignals;
|
|
893
|
+
setupCardInteractions(refs.products, { get: products, set: setProducts }, storesContext);
|
|
894
|
+
return {
|
|
895
|
+
render: () => ({
|
|
896
|
+
products: products(),
|
|
897
|
+
hasProducts: hasProducts()
|
|
898
|
+
})
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
const relatedProducts = makeJayStackComponent().withProps().withContexts(WIX_STORES_CONTEXT).withInteractive(RelatedProductsInteractive);
|
|
886
902
|
const categoryList = makeJayStackComponent().withProps();
|
|
887
903
|
const init = makeJayInit().withClient(async (data) => {
|
|
888
904
|
console.log("[wix-stores] Initializing client-side stores context...");
|
|
@@ -898,5 +914,6 @@ export {
|
|
|
898
914
|
init,
|
|
899
915
|
productPage,
|
|
900
916
|
productSearch,
|
|
901
|
-
provideWixStoresContext
|
|
917
|
+
provideWixStoresContext,
|
|
918
|
+
relatedProducts
|
|
902
919
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -130,6 +130,7 @@ interface ProductCardViewState {
|
|
|
130
130
|
interface ProductCardRepeatedRefs {
|
|
131
131
|
productLink: HTMLElementCollectionProxy<ProductCardViewState, HTMLAnchorElement>,
|
|
132
132
|
addToCartButton: HTMLElementCollectionProxy<ProductCardViewState, HTMLButtonElement>,
|
|
133
|
+
cardContainer: HTMLElementCollectionProxy<ProductCardViewState, HTMLElement>,
|
|
133
134
|
viewOptionsButton: HTMLElementCollectionProxy<ProductCardViewState, HTMLButtonElement>,
|
|
134
135
|
quickOption: ProductOptionsRepeatedRefs,
|
|
135
136
|
secondQuickOption: ProductOptionsRepeatedRefs
|
|
@@ -744,6 +745,7 @@ interface ProductPageViewState {
|
|
|
744
745
|
description: string,
|
|
745
746
|
brand: string,
|
|
746
747
|
ribbon: string,
|
|
748
|
+
categorySlug: string,
|
|
747
749
|
productType: ProductType,
|
|
748
750
|
sku: string,
|
|
749
751
|
price: string,
|
|
@@ -758,7 +760,7 @@ interface ProductPageViewState {
|
|
|
758
760
|
modifiers: Array<ModifierOfProductPageViewState>
|
|
759
761
|
}
|
|
760
762
|
|
|
761
|
-
type ProductPageSlowViewState = Pick<ProductPageViewState, '_id' | 'productName' | 'description' | 'brand' | 'ribbon' | 'productType'> & {
|
|
763
|
+
type ProductPageSlowViewState = Pick<ProductPageViewState, '_id' | 'productName' | 'description' | 'brand' | 'ribbon' | 'categorySlug' | 'productType'> & {
|
|
762
764
|
options: Array<Pick<ProductPageViewState['options'][number], '_id' | 'name' | 'optionRenderType'> & {
|
|
763
765
|
choices: Array<Pick<ProductPageViewState['options'][number]['choices'][number], 'choiceId' | 'choiceType' | 'name' | 'colorCode' | 'inStock'>>;
|
|
764
766
|
}>;
|
|
@@ -864,6 +866,39 @@ interface ProductFastCarryForward {
|
|
|
864
866
|
*/
|
|
865
867
|
declare const productPage: _jay_framework_fullstack_component.JayStackComponentDefinition<ProductPageRefs, ProductPageSlowViewState, ProductPageFastViewState, ProductPageInteractiveViewState, [ProductSlowCarryForward, WixStoresService], [Signals<ProductPageFastViewState>, ProductFastCarryForward, WixStoresContext], PageProps & ProductPageParams, ProductPageParams, _jay_framework_component.JayComponentCore<PageProps & ProductPageParams, ProductPageInteractiveViewState>>;
|
|
866
868
|
|
|
869
|
+
interface RelatedProductsViewState {
|
|
870
|
+
products: Array<ProductCardViewState>,
|
|
871
|
+
hasProducts: boolean,
|
|
872
|
+
categoryName: string
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
type RelatedProductsSlowViewState = Pick<RelatedProductsViewState, 'categoryName'>;
|
|
876
|
+
|
|
877
|
+
type RelatedProductsFastViewState = Pick<RelatedProductsViewState, 'hasProducts'> & {
|
|
878
|
+
products: Array<RelatedProductsViewState['products'][number]>;
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
type RelatedProductsInteractiveViewState = Pick<RelatedProductsViewState, 'hasProducts'> & {
|
|
882
|
+
products: Array<RelatedProductsViewState['products'][number]>;
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
interface RelatedProductsRefs {
|
|
887
|
+
products: ProductCardRepeatedRefs
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
interface RelatedProductsProps {
|
|
891
|
+
productId: string;
|
|
892
|
+
categorySlug?: string;
|
|
893
|
+
limit?: number;
|
|
894
|
+
}
|
|
895
|
+
interface RelatedSlowCarryForward {
|
|
896
|
+
categoryId: string;
|
|
897
|
+
productId: string;
|
|
898
|
+
limit: number;
|
|
899
|
+
}
|
|
900
|
+
declare const relatedProducts: _jay_framework_fullstack_component.JayStackComponentDefinition<RelatedProductsRefs, RelatedProductsSlowViewState, RelatedProductsFastViewState, RelatedProductsInteractiveViewState, [RelatedSlowCarryForward, WixStoresService], [Signals<RelatedProductsFastViewState>, Record<string, never>, WixStoresContext], PageProps & RelatedProductsProps, {}, _jay_framework_component.JayComponentCore<PageProps & RelatedProductsProps, RelatedProductsInteractiveViewState>>;
|
|
901
|
+
|
|
867
902
|
interface CategoryOfCategoryListViewState {
|
|
868
903
|
_id: string,
|
|
869
904
|
name: string,
|
|
@@ -1033,4 +1068,4 @@ declare function generateWixStoresReferences(ctx: PluginReferencesContext): Prom
|
|
|
1033
1068
|
*/
|
|
1034
1069
|
declare const generator: _jay_framework_fullstack_component.DynamicContractGenerator<[WixStoresService]>;
|
|
1035
1070
|
|
|
1036
|
-
export { type CategoryListParams, type CategoryTree, type GetProductBySlugInput, type ProductPageParams, type ProductSearchParams, WIX_STORES_CONTEXT, WIX_STORES_SERVICE_MARKER, type WixStoresContext, type WixStoresInitData, type WixStoresService, type WixStoresServiceOptions, buildCategoryUrl, buildProductUrl, categoryList, findCategoryImage, findRootCategoryId, findRootCategorySlug, generateWixStoresReferences, getCategories, getProductBySlug, getVariantStock, init, productPage, generator as productPageContractGenerator, productSearch, provideWixStoresService, searchProducts, setupWixStores };
|
|
1071
|
+
export { type CategoryListParams, type CategoryTree, type GetProductBySlugInput, type ProductPageParams, type ProductSearchParams, type RelatedProductsProps, WIX_STORES_CONTEXT, WIX_STORES_SERVICE_MARKER, type WixStoresContext, type WixStoresInitData, type WixStoresService, type WixStoresServiceOptions, buildCategoryUrl, buildProductUrl, categoryList, findCategoryImage, findRootCategoryId, findRootCategorySlug, generateWixStoresReferences, getCategories, getProductBySlug, getVariantStock, init, productPage, generator as productPageContractGenerator, productSearch, provideWixStoresService, relatedProducts, searchProducts, setupWixStores };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WIX_CART_CONTEXT, WIX_CART_SERVICE, cartIndicator, cartPage, provideWixCartContext, provideWixCartService } from "@jay-framework/wix-cart";
|
|
2
|
-
import { createJayService, makeJayQuery, ActionError,
|
|
2
|
+
import { createJayService, makeJayQuery, ActionError, RenderPipeline, makeJayStackComponent, makeJayInit, makeContractGenerator } from "@jay-framework/fullstack-component";
|
|
3
3
|
import { customizationsV3, inventoryItemsV3, productsV3 } from "@wix/stores";
|
|
4
4
|
import { categories } from "@wix/categories";
|
|
5
5
|
import { registerService, getService } from "@jay-framework/stack-server-runtime";
|
|
@@ -763,6 +763,19 @@ const getCategories = makeJayQuery("wixStores.getCategories").withServices(WIX_S
|
|
|
763
763
|
}
|
|
764
764
|
}
|
|
765
765
|
);
|
|
766
|
+
function handleError(error) {
|
|
767
|
+
const errorCode = error["details"]?.applicationError?.code;
|
|
768
|
+
if (!!errorCode) {
|
|
769
|
+
error["details"]?.requestId;
|
|
770
|
+
if (errorCode === "NOT_FOUND") {
|
|
771
|
+
return RenderPipeline.for().clientError(404, "not found");
|
|
772
|
+
}
|
|
773
|
+
return RenderPipeline.for().serverError(
|
|
774
|
+
500,
|
|
775
|
+
"Wix SDK Error: " + error.message
|
|
776
|
+
);
|
|
777
|
+
} else return RenderPipeline.for().serverError(500, "unknown server error");
|
|
778
|
+
}
|
|
766
779
|
const PAGE_SIZE = 12;
|
|
767
780
|
function mapSortToAction(sort) {
|
|
768
781
|
switch (sort) {
|
|
@@ -941,7 +954,7 @@ async function buildCategoryHeader(wixStoreService, category, categoryUrlTemplat
|
|
|
941
954
|
}
|
|
942
955
|
return header;
|
|
943
956
|
}
|
|
944
|
-
async function renderSlowlyChanging$
|
|
957
|
+
async function renderSlowlyChanging$3(props, wixStores) {
|
|
945
958
|
const Pipeline = RenderPipeline.for();
|
|
946
959
|
const categorySlug = props.category ?? null;
|
|
947
960
|
const prefixSlug = props.prefix ?? null;
|
|
@@ -982,11 +995,7 @@ async function renderSlowlyChanging$2(props, wixStores) {
|
|
|
982
995
|
productsResult
|
|
983
996
|
};
|
|
984
997
|
}).recover((error) => {
|
|
985
|
-
|
|
986
|
-
return Pipeline.ok({
|
|
987
|
-
categories: [],
|
|
988
|
-
productsResult: null
|
|
989
|
-
});
|
|
998
|
+
return handleError(error);
|
|
990
999
|
}).toPhaseOutput(({ categories: categories2, productsResult }) => {
|
|
991
1000
|
const categoryInfos = categories2.map((cat) => ({
|
|
992
1001
|
categoryId: cat._id || "",
|
|
@@ -1018,7 +1027,7 @@ async function renderSlowlyChanging$2(props, wixStores) {
|
|
|
1018
1027
|
};
|
|
1019
1028
|
});
|
|
1020
1029
|
}
|
|
1021
|
-
async function renderFastChanging$
|
|
1030
|
+
async function renderFastChanging$2(props, slowCarryForward, _wixStores) {
|
|
1022
1031
|
const Pipeline = RenderPipeline.for();
|
|
1023
1032
|
const urlFilters = parseUrlFilters(props.url);
|
|
1024
1033
|
const initialSort = parseSortParam(urlFilters.sort);
|
|
@@ -1134,15 +1143,22 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
|
|
|
1134
1143
|
}
|
|
1135
1144
|
async function* loadSearchParams([wixStores]) {
|
|
1136
1145
|
try {
|
|
1137
|
-
let
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1146
|
+
let sumItems = function(node) {
|
|
1147
|
+
for (const child of node.children) {
|
|
1148
|
+
node.itemCount += sumItems(child);
|
|
1149
|
+
}
|
|
1150
|
+
return node.itemCount;
|
|
1151
|
+
}, collectParams = function(node, rootSlug) {
|
|
1152
|
+
if (!node.slug || node.itemCount === 0)
|
|
1153
|
+
return;
|
|
1154
|
+
if (rootSlug) {
|
|
1155
|
+
params.push({ prefix: rootSlug, category: node.slug });
|
|
1156
|
+
} else {
|
|
1157
|
+
params.push({ prefix: node.slug });
|
|
1158
|
+
}
|
|
1159
|
+
for (const child of node.children) {
|
|
1160
|
+
collectParams(child, rootSlug ?? node.slug);
|
|
1144
1161
|
}
|
|
1145
|
-
return current._id !== cat._id ? current : null;
|
|
1146
1162
|
};
|
|
1147
1163
|
const allCategories = [];
|
|
1148
1164
|
let result = await wixStores.categories.queryCategories({
|
|
@@ -1153,29 +1169,52 @@ async function* loadSearchParams([wixStores]) {
|
|
|
1153
1169
|
result = await result.next();
|
|
1154
1170
|
allCategories.push(...result.items || []);
|
|
1155
1171
|
}
|
|
1156
|
-
const
|
|
1172
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
1173
|
+
const roots = [];
|
|
1174
|
+
const childPendingParent = /* @__PURE__ */ new Map();
|
|
1157
1175
|
for (const cat of allCategories) {
|
|
1158
|
-
if (cat._id)
|
|
1159
|
-
categoryById.set(cat._id, cat);
|
|
1160
|
-
}
|
|
1161
|
-
const params = [];
|
|
1162
|
-
for (const cat of allCategories) {
|
|
1163
|
-
if (!cat.slug || (cat.itemCounter ?? 0) === 0)
|
|
1176
|
+
if (!cat._id)
|
|
1164
1177
|
continue;
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1178
|
+
const node = {
|
|
1179
|
+
slug: cat.slug || "",
|
|
1180
|
+
itemCount: cat.itemCounter ?? 0,
|
|
1181
|
+
children: []
|
|
1182
|
+
};
|
|
1183
|
+
nodeById.set(cat._id, node);
|
|
1184
|
+
const pending = childPendingParent.get(cat._id);
|
|
1185
|
+
if (pending) {
|
|
1186
|
+
node.children.push(...pending);
|
|
1187
|
+
childPendingParent.delete(cat._id);
|
|
1188
|
+
}
|
|
1189
|
+
if (cat.parentCategory?._id) {
|
|
1190
|
+
const parent = nodeById.get(cat.parentCategory._id);
|
|
1191
|
+
if (parent) {
|
|
1192
|
+
parent.children.push(node);
|
|
1193
|
+
} else {
|
|
1194
|
+
const siblings = childPendingParent.get(cat.parentCategory._id);
|
|
1195
|
+
if (siblings)
|
|
1196
|
+
siblings.push(node);
|
|
1197
|
+
else
|
|
1198
|
+
childPendingParent.set(cat.parentCategory._id, [node]);
|
|
1199
|
+
}
|
|
1168
1200
|
} else {
|
|
1169
|
-
|
|
1201
|
+
roots.push(node);
|
|
1170
1202
|
}
|
|
1171
1203
|
}
|
|
1204
|
+
for (const root of roots) {
|
|
1205
|
+
sumItems(root);
|
|
1206
|
+
}
|
|
1207
|
+
const params = [];
|
|
1208
|
+
for (const root of roots) {
|
|
1209
|
+
collectParams(root, null);
|
|
1210
|
+
}
|
|
1172
1211
|
yield params;
|
|
1173
1212
|
} catch (error) {
|
|
1174
1213
|
console.error("Failed to load category params:", error);
|
|
1175
1214
|
yield [];
|
|
1176
1215
|
}
|
|
1177
1216
|
}
|
|
1178
|
-
const productSearch = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withLoadParams(loadSearchParams).withSlowlyRender(renderSlowlyChanging$
|
|
1217
|
+
const productSearch = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withLoadParams(loadSearchParams).withSlowlyRender(renderSlowlyChanging$3).withFastRender(renderFastChanging$2);
|
|
1179
1218
|
var ProductType = /* @__PURE__ */ ((ProductType2) => {
|
|
1180
1219
|
ProductType2[ProductType2["PHYSICAL"] = 0] = "PHYSICAL";
|
|
1181
1220
|
ProductType2[ProductType2["DIGITAL"] = 1] = "DIGITAL";
|
|
@@ -1404,7 +1443,7 @@ function mapVariants(variantsInfo) {
|
|
|
1404
1443
|
strikethroughPrice: variant.price.compareAtPrice?.formattedAmount || ""
|
|
1405
1444
|
})) || [];
|
|
1406
1445
|
}
|
|
1407
|
-
async function renderSlowlyChanging$
|
|
1446
|
+
async function renderSlowlyChanging$2(props, wixStores) {
|
|
1408
1447
|
const Pipeline = RenderPipeline.for();
|
|
1409
1448
|
const template = wixStores.urls.product;
|
|
1410
1449
|
const needsCategories = template.includes("{category}") || template.includes("{prefix}");
|
|
@@ -1417,21 +1456,25 @@ async function renderSlowlyChanging$1(props, wixStores) {
|
|
|
1417
1456
|
"CURRENCY",
|
|
1418
1457
|
...needsCategories ? ["ALL_CATEGORIES_INFO"] : []
|
|
1419
1458
|
];
|
|
1420
|
-
const response = await
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1459
|
+
const [response, tree] = await Promise.all([
|
|
1460
|
+
wixStores.products.getProductBySlug(props.slug, {
|
|
1461
|
+
fields: [...fields]
|
|
1462
|
+
}),
|
|
1463
|
+
wixStores.getCategoryTree()
|
|
1464
|
+
]);
|
|
1465
|
+
return { response, tree };
|
|
1424
1466
|
}).recover((error) => {
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
}).toPhaseOutput((getProductResponse) => {
|
|
1467
|
+
return handleError(error);
|
|
1468
|
+
}).toPhaseOutput(({ response: getProductResponse, tree }) => {
|
|
1428
1469
|
const product = getProductResponse.product;
|
|
1429
1470
|
const { _id, name, plainDescription, options, modifiers, actualPriceRange, compareAtPriceRange, media, productType, brand, ribbon, infoSections, seoData, physicalProperties, inventory, variantsInfo } = product;
|
|
1471
|
+
const categorySlug = tree.slugMap.get(product.mainCategoryId) || "";
|
|
1430
1472
|
return {
|
|
1431
1473
|
viewState: {
|
|
1432
1474
|
_id,
|
|
1433
1475
|
productName: name || "",
|
|
1434
1476
|
description: plainDescription,
|
|
1477
|
+
categorySlug,
|
|
1435
1478
|
brand: brand?.name || "",
|
|
1436
1479
|
ribbon: ribbon?.name || "",
|
|
1437
1480
|
productType: mapProductType(productType),
|
|
@@ -1456,7 +1499,7 @@ async function renderSlowlyChanging$1(props, wixStores) {
|
|
|
1456
1499
|
};
|
|
1457
1500
|
});
|
|
1458
1501
|
}
|
|
1459
|
-
async function renderFastChanging(props, slowCarryForward, wixStores) {
|
|
1502
|
+
async function renderFastChanging$1(props, slowCarryForward, wixStores) {
|
|
1460
1503
|
const Pipeline = RenderPipeline.for();
|
|
1461
1504
|
const { variants } = slowCarryForward;
|
|
1462
1505
|
const defaultVariant = variants.find((v) => v.inventoryStatus === StockStatus.IN_STOCK) || variants[0];
|
|
@@ -1506,7 +1549,78 @@ async function renderFastChanging(props, slowCarryForward, wixStores) {
|
|
|
1506
1549
|
}
|
|
1507
1550
|
}));
|
|
1508
1551
|
}
|
|
1509
|
-
const productPage = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withLoadParams(loadProductParams).withSlowlyRender(renderSlowlyChanging$
|
|
1552
|
+
const productPage = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withLoadParams(loadProductParams).withSlowlyRender(renderSlowlyChanging$2).withFastRender(renderFastChanging$1);
|
|
1553
|
+
const DEFAULT_LIMIT = 4;
|
|
1554
|
+
async function renderSlowlyChanging$1(props, wixStores) {
|
|
1555
|
+
const Pipeline = RenderPipeline.for();
|
|
1556
|
+
const limit = props.limit ?? DEFAULT_LIMIT;
|
|
1557
|
+
return Pipeline.try(async () => {
|
|
1558
|
+
const categorySlug = props.categorySlug;
|
|
1559
|
+
if (!categorySlug) {
|
|
1560
|
+
return { categoryId: "", categoryName: "" };
|
|
1561
|
+
}
|
|
1562
|
+
const tree = await wixStores.getCategoryTree();
|
|
1563
|
+
let categoryId = "";
|
|
1564
|
+
let categoryName = "";
|
|
1565
|
+
for (const [id, slug] of tree.slugMap) {
|
|
1566
|
+
if (slug === categorySlug) {
|
|
1567
|
+
categoryId = id;
|
|
1568
|
+
categoryName = slug;
|
|
1569
|
+
break;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
if (categoryId) {
|
|
1573
|
+
try {
|
|
1574
|
+
const result = await wixStores.categories.queryCategories({ treeReference: { appNamespace: "@wix/stores" } }).eq("_id", categoryId).limit(1).find();
|
|
1575
|
+
if (result.items?.[0]?.name) {
|
|
1576
|
+
categoryName = result.items[0].name;
|
|
1577
|
+
}
|
|
1578
|
+
} catch {
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
return { categoryId, categoryName };
|
|
1582
|
+
}).recover((error) => {
|
|
1583
|
+
return handleError(error);
|
|
1584
|
+
}).toPhaseOutput(({ categoryId, categoryName }) => ({
|
|
1585
|
+
viewState: {
|
|
1586
|
+
categoryName
|
|
1587
|
+
},
|
|
1588
|
+
carryForward: {
|
|
1589
|
+
categoryId,
|
|
1590
|
+
productId: props.productId,
|
|
1591
|
+
limit
|
|
1592
|
+
}
|
|
1593
|
+
}));
|
|
1594
|
+
}
|
|
1595
|
+
async function renderFastChanging(_props, slowCarryForward, _wixStores) {
|
|
1596
|
+
const Pipeline = RenderPipeline.for();
|
|
1597
|
+
const { categoryId, productId, limit } = slowCarryForward;
|
|
1598
|
+
if (!categoryId) {
|
|
1599
|
+
return Pipeline.ok({
|
|
1600
|
+
products: [],
|
|
1601
|
+
hasProducts: false
|
|
1602
|
+
}).toPhaseOutput((viewState) => ({ viewState, carryForward: {} }));
|
|
1603
|
+
}
|
|
1604
|
+
return Pipeline.try(async () => {
|
|
1605
|
+
const result = await searchProducts({
|
|
1606
|
+
query: "",
|
|
1607
|
+
filters: { categoryIds: [categoryId] },
|
|
1608
|
+
pageSize: limit + 1
|
|
1609
|
+
});
|
|
1610
|
+
const products = result.products.filter((p) => p._id !== productId).slice(0, limit);
|
|
1611
|
+
return {
|
|
1612
|
+
products,
|
|
1613
|
+
hasProducts: products.length > 0
|
|
1614
|
+
};
|
|
1615
|
+
}).recover((error) => {
|
|
1616
|
+
console.error("Failed to load related products:", error);
|
|
1617
|
+
return Pipeline.ok({
|
|
1618
|
+
products: [],
|
|
1619
|
+
hasProducts: false
|
|
1620
|
+
});
|
|
1621
|
+
}).toPhaseOutput((viewState) => ({ viewState, carryForward: {} }));
|
|
1622
|
+
}
|
|
1623
|
+
const relatedProducts = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withSlowlyRender(renderSlowlyChanging$1).withFastRender(renderFastChanging);
|
|
1510
1624
|
async function findCategoryBySlug(categoriesClient, slug) {
|
|
1511
1625
|
const result = await categoriesClient.queryCategories({ treeReference: { appNamespace: "@wix/stores" } }).eq("slug", slug).eq("visible", true).limit(1).find();
|
|
1512
1626
|
return result.items?.[0] ?? null;
|
|
@@ -1859,6 +1973,7 @@ tags:
|
|
|
1859
1973
|
- {tag: description, type: data, dataType: string, description: Product description}
|
|
1860
1974
|
- {tag: brand, type: data, dataType: string, description: Brand name}
|
|
1861
1975
|
- {tag: ribbon, type: data, dataType: string, description: "Ribbon text (e.g., \\"New\\", \\"Sale\\")"}
|
|
1976
|
+
- {tag: categorySlug, type: data, dataType: string, description: Slug of the product's main category}
|
|
1862
1977
|
- {tag: productType, type: variant, dataType: "enum (PHYSICAL | DIGITAL)", description: Product type }
|
|
1863
1978
|
|
|
1864
1979
|
- {tag: sku, type: data, dataType: string, phase: fast+interactive, description: Product SKU, or chosen variant SKU}
|
|
@@ -1991,6 +2106,7 @@ export {
|
|
|
1991
2106
|
provideWixCartContext,
|
|
1992
2107
|
provideWixCartService,
|
|
1993
2108
|
provideWixStoresService,
|
|
2109
|
+
relatedProducts,
|
|
1994
2110
|
searchProducts,
|
|
1995
2111
|
setupWixStores
|
|
1996
2112
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/wix-stores",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Wix Stores API client for Jay Framework",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"./media.jay-contract": "./dist/contracts/media.jay-contract",
|
|
18
18
|
"./media-gallery.jay-contract": "./dist/contracts/media-gallery.jay-contract",
|
|
19
19
|
"./product-search.jay-contract": "./dist/contracts/product-search.jay-contract",
|
|
20
|
+
"./related-products.jay-contract": "./dist/contracts/related-products.jay-contract",
|
|
20
21
|
"./category-list.jay-contract": "./dist/contracts/category-list.jay-contract",
|
|
21
22
|
"./search-products.jay-action": "./dist/actions/search-products.jay-action",
|
|
22
23
|
"./get-product-by-slug.jay-action": "./dist/actions/get-product-by-slug.jay-action",
|
|
@@ -37,16 +38,16 @@
|
|
|
37
38
|
"test": ":"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
|
-
"@jay-framework/component": "^0.
|
|
41
|
-
"@jay-framework/fullstack-component": "^0.
|
|
42
|
-
"@jay-framework/reactive": "^0.
|
|
43
|
-
"@jay-framework/runtime": "^0.
|
|
44
|
-
"@jay-framework/secure": "^0.
|
|
45
|
-
"@jay-framework/stack-client-runtime": "^0.
|
|
46
|
-
"@jay-framework/stack-server-runtime": "^0.
|
|
47
|
-
"@jay-framework/wix-cart": "^0.
|
|
48
|
-
"@jay-framework/wix-server-client": "^0.
|
|
49
|
-
"@jay-framework/wix-utils": "^0.
|
|
41
|
+
"@jay-framework/component": "^0.17.1",
|
|
42
|
+
"@jay-framework/fullstack-component": "^0.17.1",
|
|
43
|
+
"@jay-framework/reactive": "^0.17.1",
|
|
44
|
+
"@jay-framework/runtime": "^0.17.1",
|
|
45
|
+
"@jay-framework/secure": "^0.17.1",
|
|
46
|
+
"@jay-framework/stack-client-runtime": "^0.17.1",
|
|
47
|
+
"@jay-framework/stack-server-runtime": "^0.17.1",
|
|
48
|
+
"@jay-framework/wix-cart": "^0.17.1",
|
|
49
|
+
"@jay-framework/wix-server-client": "^0.17.1",
|
|
50
|
+
"@jay-framework/wix-utils": "^0.17.1",
|
|
50
51
|
"@wix/categories": "^1.0.185",
|
|
51
52
|
"@wix/data-extension-schema": "^1.0.221",
|
|
52
53
|
"@wix/sdk": "^1.21.5",
|
|
@@ -58,10 +59,10 @@
|
|
|
58
59
|
"@babel/core": "^7.23.7",
|
|
59
60
|
"@babel/preset-env": "^7.23.8",
|
|
60
61
|
"@babel/preset-typescript": "^7.23.3",
|
|
61
|
-
"@jay-framework/compiler-jay-stack": "^0.
|
|
62
|
-
"@jay-framework/jay-cli": "^0.
|
|
63
|
-
"@jay-framework/jay-stack-cli": "^0.
|
|
64
|
-
"@jay-framework/vite-plugin": "^0.
|
|
62
|
+
"@jay-framework/compiler-jay-stack": "^0.17.1",
|
|
63
|
+
"@jay-framework/jay-cli": "^0.17.1",
|
|
64
|
+
"@jay-framework/jay-stack-cli": "^0.17.1",
|
|
65
|
+
"@jay-framework/vite-plugin": "^0.17.1",
|
|
65
66
|
"nodemon": "^3.0.3",
|
|
66
67
|
"rimraf": "^5.0.5",
|
|
67
68
|
"tslib": "^2.6.2",
|
package/plugin.yaml
CHANGED
|
@@ -7,6 +7,10 @@ contracts:
|
|
|
7
7
|
component: productSearch
|
|
8
8
|
description: Headless product search page
|
|
9
9
|
# category-page removed — use product-search with prefix/category params instead
|
|
10
|
+
- name: related-products
|
|
11
|
+
contract: related-products.jay-contract
|
|
12
|
+
component: relatedProducts
|
|
13
|
+
description: Related products from the same category
|
|
10
14
|
- name: category-list
|
|
11
15
|
contract: category-list.jay-contract
|
|
12
16
|
component: categoryList
|