@jay-framework/wix-stores 0.15.0 → 0.15.4

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.
@@ -22,7 +22,8 @@ export interface CategoryOfCategoryFilterOfFilterOfProductSearchViewState {
22
22
  categoryId: string,
23
23
  categoryName: string,
24
24
  categorySlug: string,
25
- isSelected: boolean
25
+ isSelected: boolean,
26
+ categoryUrl: string
26
27
  }
27
28
 
28
29
  export interface CategoryFilterOfFilterOfProductSearchViewState {
@@ -53,6 +54,57 @@ export interface SuggestionOfProductSearchViewState {
53
54
  suggestionText: string
54
55
  }
55
56
 
57
+ export interface BreadcrumbOfCategoryHeaderOfProductSearchViewState {
58
+ categoryId: string,
59
+ name: string,
60
+ slug: string,
61
+ url: string
62
+ }
63
+
64
+ export interface PropOfTagOfSeoDatumOfCategoryHeaderOfProductSearchViewState {
65
+ key: string,
66
+ value: string
67
+ }
68
+
69
+ export interface MetaOfTagOfSeoDatumOfCategoryHeaderOfProductSearchViewState {
70
+ key: string,
71
+ value: string
72
+ }
73
+
74
+ export interface TagOfSeoDatumOfCategoryHeaderOfProductSearchViewState {
75
+ position: string,
76
+ type: string,
77
+ props: Array<PropOfTagOfSeoDatumOfCategoryHeaderOfProductSearchViewState>,
78
+ meta: Array<MetaOfTagOfSeoDatumOfCategoryHeaderOfProductSearchViewState>,
79
+ children: string
80
+ }
81
+
82
+ export interface KeywordOfSettingOfSeoDatumOfCategoryHeaderOfProductSearchViewState {
83
+ term: string,
84
+ isMain: boolean,
85
+ origin: string
86
+ }
87
+
88
+ export interface SettingOfSeoDatumOfCategoryHeaderOfProductSearchViewState {
89
+ preventAutoRedirect: boolean,
90
+ keywords: Array<KeywordOfSettingOfSeoDatumOfCategoryHeaderOfProductSearchViewState>
91
+ }
92
+
93
+ export interface SeoDatumOfCategoryHeaderOfProductSearchViewState {
94
+ tags: Array<TagOfSeoDatumOfCategoryHeaderOfProductSearchViewState>,
95
+ settings: SettingOfSeoDatumOfCategoryHeaderOfProductSearchViewState
96
+ }
97
+
98
+ export interface CategoryHeaderOfProductSearchViewState {
99
+ name: string,
100
+ description: string,
101
+ imageUrl: string,
102
+ hasImage: boolean,
103
+ productCount: number,
104
+ breadcrumbs: Array<BreadcrumbOfCategoryHeaderOfProductSearchViewState>,
105
+ seoData: SeoDatumOfCategoryHeaderOfProductSearchViewState
106
+ }
107
+
56
108
  export interface ProductSearchViewState {
57
109
  searchExpression: string,
58
110
  searchFields: string,
@@ -69,15 +121,17 @@ export interface ProductSearchViewState {
69
121
  loadedCount: number,
70
122
  totalCount: number,
71
123
  hasSuggestions: boolean,
72
- suggestions: Array<SuggestionOfProductSearchViewState>
124
+ suggestions: Array<SuggestionOfProductSearchViewState>,
125
+ categoryHeader: CategoryHeaderOfProductSearchViewState
73
126
  }
74
127
 
75
128
  export type ProductSearchSlowViewState = Pick<ProductSearchViewState, 'searchFields' | 'fuzzySearch' | 'emptyStateMessage'> & {
76
129
  filters: {
77
130
  categoryFilter: {
78
- categories: Array<Pick<ProductSearchViewState['filters']['categoryFilter']['categories'][number], 'categoryId' | 'categoryName' | 'categorySlug'>>;
131
+ categories: Array<Pick<ProductSearchViewState['filters']['categoryFilter']['categories'][number], 'categoryId' | 'categoryName' | 'categorySlug' | 'categoryUrl'>>;
79
132
  };
80
133
  };
134
+ categoryHeader: ProductSearchViewState['categoryHeader'];
81
135
  };
82
136
 
83
137
  export type ProductSearchFastViewState = Pick<ProductSearchViewState, 'searchExpression' | 'isSearching' | 'hasSearched' | 'resultCount' | 'hasResults' | 'hasMore' | 'loadedCount' | 'totalCount' | 'hasSuggestions'> & {
@@ -1,10 +1,10 @@
1
- import { WIX_CART_CONTEXT, cartIndicator, cartPage } from "@jay-framework/wix-cart/client";
1
+ import { WIX_CART_CONTEXT } from "@jay-framework/wix-cart/client";
2
+ import { WIX_CART_CONTEXT as WIX_CART_CONTEXT2, cartIndicator, cartPage } from "@jay-framework/wix-cart/client";
2
3
  import { makeJayStackComponent, makeJayInit } from "@jay-framework/fullstack-component";
3
4
  import { registerReactiveGlobalContext, createSignal, createMemo, createEffect } from "@jay-framework/component";
4
5
  import { patch, REPLACE } from "@jay-framework/json-patch";
5
6
  import { createJayContext, useGlobalContext } from "@jay-framework/runtime";
6
7
  import { WIX_CLIENT_CONTEXT } from "@jay-framework/wix-server-client/client";
7
- import { WIX_CART_CONTEXT as WIX_CART_CONTEXT2 } from "@jay-framework/wix-cart";
8
8
  import { productsV3 } from "@wix/stores";
9
9
  import "@wix/categories";
10
10
  import { createActionCaller } from "@jay-framework/stack-client-runtime";
@@ -31,7 +31,7 @@ const WIX_STORES_CONTEXT = createJayContext();
31
31
  function provideWixStoresContext() {
32
32
  const wixClientContext = useGlobalContext(WIX_CLIENT_CONTEXT);
33
33
  const wixClient = wixClientContext.client;
34
- const cartContext = useGlobalContext(WIX_CART_CONTEXT2);
34
+ const cartContext = useGlobalContext(WIX_CART_CONTEXT);
35
35
  const catalogClient = getProductsV3Client(wixClient);
36
36
  const storesContext = registerReactiveGlobalContext(WIX_STORES_CONTEXT, () => {
37
37
  async function addToCart(productId, quantity = 1, selections) {
@@ -324,10 +324,69 @@ var CurrentSort = /* @__PURE__ */ ((CurrentSort2) => {
324
324
  })(CurrentSort || {});
325
325
  const searchProducts = createActionCaller("wixStores.searchProducts", "GET");
326
326
  createActionCaller("wixStores.getProductBySlug", "GET");
327
+ const getVariantStock = createActionCaller("wixStores.getVariantStock", "GET");
327
328
  createActionCaller("wixStores.getCategories", "GET");
329
+ var QuickAddType = /* @__PURE__ */ ((QuickAddType2) => {
330
+ QuickAddType2[QuickAddType2["SIMPLE"] = 0] = "SIMPLE";
331
+ QuickAddType2[QuickAddType2["SINGLE_OPTION"] = 1] = "SINGLE_OPTION";
332
+ QuickAddType2[QuickAddType2["COLOR_AND_TEXT_OPTIONS"] = 2] = "COLOR_AND_TEXT_OPTIONS";
333
+ QuickAddType2[QuickAddType2["NEEDS_CONFIGURATION"] = 3] = "NEEDS_CONFIGURATION";
334
+ return QuickAddType2;
335
+ })(QuickAddType || {});
328
336
  const PAGE_SIZE = 12;
337
+ function mapSortToAction(sort) {
338
+ switch (sort) {
339
+ case CurrentSort.priceAsc:
340
+ return "price_asc";
341
+ case CurrentSort.priceDesc:
342
+ return "price_desc";
343
+ case CurrentSort.newest:
344
+ return "newest";
345
+ case CurrentSort.nameAsc:
346
+ return "name_asc";
347
+ case CurrentSort.nameDesc:
348
+ return "name_desc";
349
+ default:
350
+ return "relevance";
351
+ }
352
+ }
353
+ function updateUrlFilters(searchTerm, filters, sort, categories) {
354
+ if (typeof window === "undefined")
355
+ return;
356
+ const params = new URLSearchParams();
357
+ if (searchTerm)
358
+ params.set("q", searchTerm);
359
+ const selectedSlugs = filters.categoryFilter.categories.filter((c) => c.isSelected).map((c) => {
360
+ const info = categories.find((cat) => cat.categoryId === c.categoryId);
361
+ return info?.categorySlug;
362
+ }).filter(Boolean);
363
+ if (selectedSlugs.length)
364
+ params.set("cat", selectedSlugs.join(","));
365
+ if (filters.priceRange.minPrice > 0)
366
+ params.set("min", String(filters.priceRange.minPrice));
367
+ if (filters.priceRange.maxPrice > 0 && filters.priceRange.maxPrice < filters.priceRange.maxBound) {
368
+ params.set("max", String(filters.priceRange.maxPrice));
369
+ }
370
+ if (filters.inStockOnly)
371
+ params.set("inStock", "1");
372
+ if (sort !== CurrentSort.relevance) {
373
+ const sortNames = {
374
+ [CurrentSort.priceAsc]: "priceAsc",
375
+ [CurrentSort.priceDesc]: "priceDesc",
376
+ [CurrentSort.newest]: "newest",
377
+ [CurrentSort.nameAsc]: "nameAsc",
378
+ [CurrentSort.nameDesc]: "nameDesc"
379
+ };
380
+ const sortName = sortNames[sort];
381
+ if (sortName)
382
+ params.set("sort", sortName);
383
+ }
384
+ const query = params.toString();
385
+ window.history.replaceState(null, "", query ? `?${query}` : window.location.pathname);
386
+ }
329
387
  function ProductSearchInteractive(props, refs, viewStateSignals, fastCarryForward, storesContext) {
330
388
  const baseCategoryId = fastCarryForward.baseCategoryId;
389
+ const variantStockCache = {};
331
390
  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;
332
391
  const [submittedSearchTerm, setSubmittedSearchTerm] = createSignal(null);
333
392
  let currentCursor = null;
@@ -335,22 +394,6 @@ function ProductSearchInteractive(props, refs, viewStateSignals, fastCarryForwar
335
394
  let debounceTimeout = null;
336
395
  let searchVersion = 0;
337
396
  const DEBOUNCE_MS = 300;
338
- const mapSortToAction = (sort) => {
339
- switch (sort) {
340
- case CurrentSort.priceAsc:
341
- return "price_asc";
342
- case CurrentSort.priceDesc:
343
- return "price_desc";
344
- case CurrentSort.newest:
345
- return "newest";
346
- case CurrentSort.nameAsc:
347
- return "name_asc";
348
- case CurrentSort.nameDesc:
349
- return "name_desc";
350
- default:
351
- return "relevance";
352
- }
353
- };
354
397
  const performSearch = async (version, searchTerm, currentFilters, currentSort) => {
355
398
  setIsSearching(true);
356
399
  setHasSearched(true);
@@ -379,6 +422,7 @@ function ProductSearchInteractive(props, refs, viewStateSignals, fastCarryForwar
379
422
  setHasMore(result.hasMore);
380
423
  setHasResults(result.products.length > 0);
381
424
  currentCursor = result.nextCursor;
425
+ updateUrlFilters(searchTerm, currentFilters, currentSort, fastCarryForward.categories);
382
426
  } catch (error) {
383
427
  if (version === searchVersion) {
384
428
  console.error("Search failed:", error);
@@ -573,6 +617,49 @@ function ProductSearchInteractive(props, refs, viewStateSignals, fastCarryForwar
573
617
  ]));
574
618
  }
575
619
  });
620
+ const loadVariantStock = async (productId) => {
621
+ if (variantStockCache[productId])
622
+ return;
623
+ const stockMap = await getVariantStock({ productId });
624
+ variantStockCache[productId] = stockMap;
625
+ const currentResults = searchResults();
626
+ const productIndex = currentResults.findIndex((p) => p._id === productId);
627
+ if (productIndex === -1)
628
+ return;
629
+ const product = currentResults[productIndex];
630
+ if (product.quickAddType !== QuickAddType.COLOR_AND_TEXT_OPTIONS)
631
+ return;
632
+ const selectedColor = product.quickOption?.choices?.find((c) => c.isSelected);
633
+ const textChoices = product.secondQuickOption?.choices;
634
+ if (!selectedColor || !textChoices)
635
+ return;
636
+ const colorStock = stockMap[selectedColor.choiceId];
637
+ const updatedTextChoices = textChoices.map((c) => ({
638
+ ...c,
639
+ inStock: colorStock?.[c.choiceId] ?? false
640
+ }));
641
+ setSearchResults(patch(searchResults(), [
642
+ {
643
+ op: REPLACE,
644
+ path: [productIndex, "secondQuickOption", "choices"],
645
+ value: updatedTextChoices
646
+ }
647
+ ]));
648
+ };
649
+ refs.searchResults.productLink.onmouseenter(({ coordinate }) => {
650
+ const [productId] = coordinate;
651
+ const product = searchResults().find((p) => p._id === productId);
652
+ if (product?.quickAddType === QuickAddType.COLOR_AND_TEXT_OPTIONS) {
653
+ loadVariantStock(productId);
654
+ }
655
+ });
656
+ refs.searchResults.quickOption.choices.choiceButton.onmouseenter(({ coordinate }) => {
657
+ const [productId] = coordinate;
658
+ const product = searchResults().find((p) => p._id === productId);
659
+ if (product?.quickAddType === QuickAddType.COLOR_AND_TEXT_OPTIONS) {
660
+ loadVariantStock(productId);
661
+ }
662
+ });
576
663
  refs.searchResults.quickOption.choices.choiceButton.onclick(async ({ coordinate }) => {
577
664
  const [productId, choiceId] = coordinate;
578
665
  const currentResults = searchResults();
@@ -580,6 +667,44 @@ function ProductSearchInteractive(props, refs, viewStateSignals, fastCarryForwar
580
667
  if (productIndex === -1)
581
668
  return;
582
669
  const product = currentResults[productIndex];
670
+ if (product.quickAddType === QuickAddType.COLOR_AND_TEXT_OPTIONS) {
671
+ const choices = product.quickOption?.choices;
672
+ if (!choices)
673
+ return;
674
+ const updatedChoices = choices.map((c) => ({
675
+ ...c,
676
+ isSelected: c.choiceId === choiceId
677
+ }));
678
+ let updated = patch(currentResults, [
679
+ {
680
+ op: REPLACE,
681
+ path: [productIndex, "quickOption", "choices"],
682
+ value: updatedChoices
683
+ }
684
+ ]);
685
+ const stockMap = variantStockCache[productId];
686
+ if (stockMap) {
687
+ const colorStock = stockMap[choiceId];
688
+ const textChoices = product.secondQuickOption?.choices;
689
+ if (textChoices) {
690
+ const updatedTextChoices = textChoices.map((c) => ({
691
+ ...c,
692
+ inStock: colorStock?.[c.choiceId] ?? false
693
+ }));
694
+ updated = patch(updated, [
695
+ {
696
+ op: REPLACE,
697
+ path: [productIndex, "secondQuickOption", "choices"],
698
+ value: updatedTextChoices
699
+ }
700
+ ]);
701
+ }
702
+ } else {
703
+ loadVariantStock(productId);
704
+ }
705
+ setSearchResults(updated);
706
+ return;
707
+ }
583
708
  const choice = product.quickOption?.choices?.find((c) => c.choiceId === choiceId);
584
709
  if (!choice || !choice.inStock) {
585
710
  console.warn("Choice not available or out of stock");
@@ -603,6 +728,41 @@ function ProductSearchInteractive(props, refs, viewStateSignals, fastCarryForwar
603
728
  ]));
604
729
  }
605
730
  });
731
+ refs.searchResults.secondQuickOption.choices.choiceButton.onclick(async ({ coordinate }) => {
732
+ const [productId, choiceId] = coordinate;
733
+ const currentResults = searchResults();
734
+ const productIndex = currentResults.findIndex((p) => p._id === productId);
735
+ if (productIndex === -1)
736
+ return;
737
+ const product = currentResults[productIndex];
738
+ const textChoice = product.secondQuickOption?.choices?.find((c) => c.choiceId === choiceId);
739
+ const selectedColor = product.quickOption?.choices?.find((c) => c.isSelected);
740
+ if (!textChoice || !textChoice.inStock) {
741
+ console.warn("Text choice not available or out of stock");
742
+ return;
743
+ }
744
+ setSearchResults(patch(currentResults, [
745
+ { op: REPLACE, path: [productIndex, "isAddingToCart"], value: true }
746
+ ]));
747
+ try {
748
+ const colorOptionId = product.quickOption?._id || "";
749
+ const textOptionId = product.secondQuickOption?._id || "";
750
+ await storesContext.addToCart(productId, 1, {
751
+ options: {
752
+ [colorOptionId]: selectedColor?.choiceId || "",
753
+ [textOptionId]: textChoice.choiceId
754
+ },
755
+ modifiers: {},
756
+ customTextFields: {}
757
+ });
758
+ } catch (error) {
759
+ console.error("Failed to add to cart:", error);
760
+ } finally {
761
+ setSearchResults(patch(searchResults(), [
762
+ { op: REPLACE, path: [productIndex, "isAddingToCart"], value: false }
763
+ ]));
764
+ }
765
+ });
606
766
  refs.searchResults.viewOptionsButton.onclick(({ coordinate }) => {
607
767
  const [productId] = coordinate;
608
768
  const product = searchResults().find((p) => p._id === productId);
@@ -634,10 +794,9 @@ const init = makeJayInit().withClient(async (data) => {
634
794
  console.log("[wix-stores] Initializing client-side stores context...");
635
795
  provideWixStoresContext();
636
796
  console.log("[wix-stores] Client initialization complete");
637
- console.log(`[wix-stores] Search enabled: ${data.enableClientSearch}`);
638
797
  });
639
798
  export {
640
- WIX_CART_CONTEXT,
799
+ WIX_CART_CONTEXT2 as WIX_CART_CONTEXT,
641
800
  WIX_STORES_CONTEXT,
642
801
  cartIndicator,
643
802
  cartPage,