@jay-framework/wix-stores 0.15.4 → 0.15.6

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/index.js CHANGED
@@ -1,7 +1,6 @@
1
- import { getCurrentCartClient } from "@jay-framework/wix-cart";
2
1
  import { WIX_CART_CONTEXT, WIX_CART_SERVICE, cartIndicator, cartPage, provideWixCartContext, provideWixCartService } from "@jay-framework/wix-cart";
3
2
  import { createJayService, makeJayQuery, ActionError, makeJayStackComponent, RenderPipeline, makeJayInit } from "@jay-framework/fullstack-component";
4
- import { inventoryItemsV3, productsV3 } from "@wix/stores";
3
+ import { customizationsV3, inventoryItemsV3, productsV3 } from "@wix/stores";
5
4
  import { categories } from "@wix/categories";
6
5
  import { registerService, getService } from "@jay-framework/stack-server-runtime";
7
6
  import { createJayContext } from "@jay-framework/runtime";
@@ -10,6 +9,11 @@ import { WIX_CLIENT_SERVICE } from "@jay-framework/wix-server-client";
10
9
  import * as fs from "fs";
11
10
  import * as path from "path";
12
11
  import * as yaml from "js-yaml";
12
+ var OptionRenderType$2 = /* @__PURE__ */ ((OptionRenderType2) => {
13
+ OptionRenderType2[OptionRenderType2["TEXT_CHOICES"] = 0] = "TEXT_CHOICES";
14
+ OptionRenderType2[OptionRenderType2["SWATCH_CHOICES"] = 1] = "SWATCH_CHOICES";
15
+ return OptionRenderType2;
16
+ })(OptionRenderType$2 || {});
13
17
  var CurrentSort = /* @__PURE__ */ ((CurrentSort2) => {
14
18
  CurrentSort2[CurrentSort2["relevance"] = 0] = "relevance";
15
19
  CurrentSort2[CurrentSort2["priceAsc"] = 1] = "priceAsc";
@@ -22,7 +26,8 @@ var CurrentSort = /* @__PURE__ */ ((CurrentSort2) => {
22
26
  const instances = {
23
27
  productsV3ClientInstance: void 0,
24
28
  categoriesClientInstance: void 0,
25
- inventoryV3ClientInstance: void 0
29
+ inventoryV3ClientInstance: void 0,
30
+ customizationsV3ClientInstance: void 0
26
31
  };
27
32
  function getProductsV3Client(wixClient) {
28
33
  if (!instances.productsV3ClientInstance) {
@@ -42,15 +47,23 @@ function getInventoryClient(wixClient) {
42
47
  }
43
48
  return instances.inventoryV3ClientInstance;
44
49
  }
50
+ function getCustomizationsV3Client(wixClient) {
51
+ if (!instances.customizationsV3ClientInstance) {
52
+ instances.customizationsV3ClientInstance = wixClient.use(customizationsV3);
53
+ }
54
+ return instances.customizationsV3ClientInstance;
55
+ }
45
56
  const WIX_STORES_SERVICE_MARKER = createJayService("Wix Store Service");
46
57
  function provideWixStoresService(wixClient, options) {
47
58
  let cachedTree = null;
59
+ let cachedCustomizations = null;
48
60
  const categoriesClient = getCategoriesClient(wixClient);
61
+ const customizationsClient = getCustomizationsV3Client(wixClient);
49
62
  const service = {
50
63
  products: getProductsV3Client(wixClient),
51
64
  categories: categoriesClient,
52
65
  inventory: getInventoryClient(wixClient),
53
- cart: getCurrentCartClient(wixClient),
66
+ customizations: customizationsClient,
54
67
  urls: options?.urls ?? { product: "/products/{slug}", category: null },
55
68
  defaultCategory: options?.defaultCategory ?? null,
56
69
  async getCategoryTree() {
@@ -86,6 +99,17 @@ function provideWixStoresService(wixClient, options) {
86
99
  }
87
100
  cachedTree = { slugMap, parentMap, rootIds, imageMap };
88
101
  return cachedTree;
102
+ },
103
+ async getCustomizations() {
104
+ if (cachedCustomizations) return cachedCustomizations;
105
+ try {
106
+ const result = await customizationsClient.queryCustomizations().eq("customizationType", "PRODUCT_OPTION").limit(100).find();
107
+ cachedCustomizations = result.items || [];
108
+ } catch (error) {
109
+ console.error("[wix-stores] Failed to load customizations:", error);
110
+ cachedCustomizations = [];
111
+ }
112
+ return cachedCustomizations;
89
113
  }
90
114
  };
91
115
  registerService(WIX_STORES_SERVICE_MARKER, service);
@@ -214,12 +238,16 @@ function buildProductUrl(urls, tree, slug, mainCategoryId) {
214
238
  function buildCategoryUrl(urls, tree, categorySlug, categoryId) {
215
239
  if (!urls.category) return null;
216
240
  let url = urls.category;
217
- url = url.replace("{category}", categorySlug);
218
241
  if (url.includes("{prefix}")) {
219
242
  const prefixSlug = findRootCategorySlug(categoryId, tree);
220
243
  if (!prefixSlug) return null;
221
244
  url = url.replace("{prefix}", prefixSlug);
245
+ const isRoot = tree.rootIds.has(categoryId);
246
+ url = url.replace("{category}", isRoot ? "" : categorySlug);
247
+ } else {
248
+ url = url.replace("{category}", categorySlug);
222
249
  }
250
+ url = url.replace(/\/\/+/g, "/").replace(/\/+$/, "/");
223
251
  return url.includes("{") ? null : url;
224
252
  }
225
253
  function mapAvailabilityStatus(status) {
@@ -329,8 +357,8 @@ function buildVariantStockMap(product) {
329
357
  const options = product.options;
330
358
  const variants = product.variantsInfo?.variants;
331
359
  if (!options || options.length !== 2 || !variants) return stockMap;
332
- const colorOption = options.find((o) => o.optionRenderType === "COLOR_SWATCH_CHOICES");
333
- const textOption = options.find((o) => o.optionRenderType !== "COLOR_SWATCH_CHOICES");
360
+ const colorOption = options.find((o) => o.optionRenderType === "SWATCH_CHOICES");
361
+ const textOption = options.find((o) => o.optionRenderType === "TEXT_CHOICES");
334
362
  if (!colorOption || !textOption) return stockMap;
335
363
  const colorOptionId = colorOption._id || "";
336
364
  const textOptionId = textOption._id || "";
@@ -428,6 +456,31 @@ const PRICE_BUCKETS = PRICE_BUCKET_BOUNDARIES.slice(0, -1).map((from, i) => ({
428
456
  to: PRICE_BUCKET_BOUNDARIES[i + 1]
429
457
  }));
430
458
  PRICE_BUCKETS.push({ from: PRICE_BUCKET_BOUNDARIES[PRICE_BUCKET_BOUNDARIES.length - 1] });
459
+ function getAvailableProductOptions(aggResults, customizations) {
460
+ const optionNamesAgg = aggResults.find((a) => a.name === "optionNames")?.values;
461
+ const choiceNamesAgg = aggResults.find((a) => a.name === "choiceNames")?.values;
462
+ if (!optionNamesAgg?.results?.length || !choiceNamesAgg?.results?.length) {
463
+ return [];
464
+ }
465
+ const optionNames = new Set(optionNamesAgg.results.map((e) => e.value));
466
+ const choiceCounts = new Map(
467
+ choiceNamesAgg.results.map((e) => [e.value.toLowerCase(), e.count ?? 0])
468
+ );
469
+ return customizations.filter(
470
+ (c) => c.customizationType === "PRODUCT_OPTION" && c.name && optionNames.has(c.name) && (c.customizationRenderType === "TEXT_CHOICES" || c.customizationRenderType === "SWATCH_CHOICES")
471
+ ).map((c) => ({
472
+ optionId: c._id || "",
473
+ optionName: c.name || "",
474
+ optionRenderType: c.customizationRenderType,
475
+ // Sort by product count descending (most used choices first)
476
+ choices: (c.choicesSettings?.choices || []).filter((ch) => ch.name && choiceCounts.has(ch.name.toLowerCase())).map((ch) => ({
477
+ choiceId: ch._id || "",
478
+ choiceName: ch.name || "",
479
+ colorCode: ch.colorCode || "",
480
+ productCount: choiceCounts.get(ch.name.toLowerCase()) ?? 0
481
+ })).sort((a, b) => b.productCount - a.productCount)
482
+ })).filter((o) => o.choices.length > 0);
483
+ }
431
484
  const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX_STORES_SERVICE_MARKER).withHandler(
432
485
  async (input, wixStores) => {
433
486
  const { query, filters = {}, sortBy = "relevance", cursor, pageSize = 12 } = input;
@@ -460,6 +513,22 @@ const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX
460
513
  }
461
514
  });
462
515
  }
516
+ if (filters.optionFilters && filters.optionFilters.length > 0) {
517
+ for (const optFilter of filters.optionFilters) {
518
+ if (optFilter.choiceNames.length > 0) {
519
+ filterConditions.push({
520
+ $and: [
521
+ { "options.name": { $hasSome: [optFilter.optionName] } },
522
+ {
523
+ "options.choicesSettings.choices.name": {
524
+ $hasSome: optFilter.choiceNames
525
+ }
526
+ }
527
+ ]
528
+ });
529
+ }
530
+ }
531
+ }
463
532
  const filter = filterConditions.length === 1 ? filterConditions[0] : { $and: filterConditions };
464
533
  const sort = [];
465
534
  switch (sortBy) {
@@ -513,6 +582,28 @@ const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX
513
582
  name: "max-price",
514
583
  type: "SCALAR",
515
584
  scalar: { type: "MAX" }
585
+ },
586
+ // Option names for option-based filtering
587
+ {
588
+ fieldPath: "options.name",
589
+ name: "optionNames",
590
+ type: "VALUE",
591
+ value: {
592
+ limit: 20,
593
+ sortType: "VALUE",
594
+ sortDirection: "DESC"
595
+ }
596
+ },
597
+ // Choice names for option-based filtering
598
+ {
599
+ fieldPath: "options.choicesSettings.choices.name",
600
+ name: "choiceNames",
601
+ type: "VALUE",
602
+ value: {
603
+ limit: 50,
604
+ sortType: "COUNT",
605
+ sortDirection: "DESC"
606
+ }
516
607
  }
517
608
  ];
518
609
  const searchResult = await wixStores.products.searchProducts(
@@ -567,6 +658,8 @@ const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX
567
658
  };
568
659
  });
569
660
  priceRanges.push(...bucketRanges);
661
+ const customizations = await wixStores.getCustomizations();
662
+ const optionFilters = getAvailableProductOptions(aggResults, customizations);
570
663
  const tree = await wixStores.getCategoryTree();
571
664
  const mappedProducts = products.map(
572
665
  (p) => mapProductToCard(p, wixStores.urls, tree)
@@ -580,7 +673,8 @@ const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX
580
673
  minBound,
581
674
  maxBound,
582
675
  ranges: priceRanges
583
- }
676
+ },
677
+ optionFilters
584
678
  };
585
679
  } catch (error) {
586
680
  console.error("[wixStores.searchProducts] Search failed:", error);
@@ -666,13 +760,28 @@ function mapSortToAction(sort) {
666
760
  function parseUrlFilters(url) {
667
761
  try {
668
762
  const params = new URL(url, "http://x").searchParams;
763
+ const optionSelections = /* @__PURE__ */ new Map();
764
+ const optParam = params.get("opt");
765
+ if (optParam) {
766
+ for (const segment of optParam.split(";")) {
767
+ const colonIdx = segment.indexOf(":");
768
+ if (colonIdx === -1)
769
+ continue;
770
+ const name = decodeURIComponent(segment.slice(0, colonIdx));
771
+ const choices = segment.slice(colonIdx + 1).split(",").map((c) => decodeURIComponent(c)).filter(Boolean);
772
+ if (choices.length > 0) {
773
+ optionSelections.set(name, new Set(choices));
774
+ }
775
+ }
776
+ }
669
777
  return {
670
778
  searchTerm: params.get("q") || "",
671
779
  selectedCategorySlugs: params.get("cat")?.split(",").filter(Boolean) || [],
672
780
  minPrice: params.has("min") ? Number(params.get("min")) : null,
673
781
  maxPrice: params.has("max") ? Number(params.get("max")) : null,
674
782
  inStockOnly: params.get("inStock") === "1",
675
- sort: params.get("sort") || "relevance"
783
+ sort: params.get("sort") || "relevance",
784
+ optionSelections
676
785
  };
677
786
  } catch {
678
787
  return {
@@ -681,7 +790,8 @@ function parseUrlFilters(url) {
681
790
  minPrice: null,
682
791
  maxPrice: null,
683
792
  inStockOnly: false,
684
- sort: "relevance"
793
+ sort: "relevance",
794
+ optionSelections: /* @__PURE__ */ new Map()
685
795
  };
686
796
  }
687
797
  }
@@ -696,6 +806,30 @@ function parseSortParam(sort) {
696
806
  };
697
807
  return sortMap[sort] ?? CurrentSort.relevance;
698
808
  }
809
+ function buildOptionFiltersViewState(baseOptionFilters, filteredResult, optionSelections) {
810
+ const filteredChoiceCounts = /* @__PURE__ */ new Map();
811
+ for (const opt of filteredResult.optionFilters || []) {
812
+ for (const ch of opt.choices) {
813
+ filteredChoiceCounts.set(ch.choiceName.toLowerCase(), ch.productCount);
814
+ }
815
+ }
816
+ return baseOptionFilters.map((opt) => ({
817
+ optionId: opt.optionId,
818
+ optionName: opt.optionName,
819
+ optionRenderType: opt.optionRenderType === "SWATCH_CHOICES" ? OptionRenderType$2.SWATCH_CHOICES : OptionRenderType$2.TEXT_CHOICES,
820
+ choices: opt.choices.map((ch) => {
821
+ const count = filteredChoiceCounts.get(ch.choiceName.toLowerCase()) ?? 0;
822
+ return {
823
+ choiceId: ch.choiceId,
824
+ choiceName: ch.choiceName,
825
+ colorCode: ch.colorCode,
826
+ productCount: count,
827
+ isSelected: optionSelections.get(opt.optionName)?.has(ch.choiceName) ?? false,
828
+ isDisabled: count === 0
829
+ };
830
+ })
831
+ }));
832
+ }
699
833
  const EMPTY_CATEGORY_HEADER = {
700
834
  name: "",
701
835
  description: "",
@@ -705,7 +839,7 @@ const EMPTY_CATEGORY_HEADER = {
705
839
  breadcrumbs: [],
706
840
  seoData: { tags: [], settings: { preventAutoRedirect: false, keywords: [] } }
707
841
  };
708
- async function findCategoryBySlug(categoriesClient, slug) {
842
+ async function findCategoryBySlug$1(categoriesClient, slug) {
709
843
  const result = await categoriesClient.queryCategories({ treeReference: { appNamespace: "@wix/stores" } }).eq("slug", slug).eq("visible", true).limit(1).find();
710
844
  return result.items?.[0] ?? null;
711
845
  }
@@ -719,15 +853,23 @@ async function loadCategoryDetails(categoriesClient, categoryId) {
719
853
  async function buildCategoryHeader(wixStoreService, category, categoryUrlTemplate) {
720
854
  const details = await loadCategoryDetails(wixStoreService.categories, category._id);
721
855
  const cat = details || category;
722
- const imageUrl = cat.image || "";
856
+ const imageUrl = cat.image ? formatWixMediaUrl("", cat.image) : "";
723
857
  const description = cat.description || "";
724
858
  const categoryTree = await wixStoreService.getCategoryTree();
725
- const breadcrumbs = (cat.breadcrumbsInfo?.breadcrumbs || []).map((b) => ({
726
- categoryId: b.categoryId,
727
- name: b.categoryName,
728
- slug: b.categorySlug,
729
- url: categoryUrlTemplate ? buildCategoryUrl(wixStoreService.urls, categoryTree, b.categorySlug, b.categoryId) : ""
730
- }));
859
+ const breadcrumbs = [
860
+ ...(cat.breadcrumbsInfo?.breadcrumbs || []).map((b) => ({
861
+ categoryId: b.categoryId,
862
+ name: b.categoryName,
863
+ slug: b.categorySlug,
864
+ url: categoryUrlTemplate ? buildCategoryUrl(wixStoreService.urls, categoryTree, b.categorySlug, b.categoryId) : ""
865
+ })),
866
+ {
867
+ categoryId: cat._id || "",
868
+ name: cat.name || "",
869
+ slug: cat.slug || "",
870
+ url: categoryUrlTemplate ? buildCategoryUrl(wixStoreService.urls, categoryTree, cat.slug || "", cat._id || "") : ""
871
+ }
872
+ ];
731
873
  const seoData = cat.seoData ? {
732
874
  tags: (cat.seoData.tags || []).map((tag, index) => ({
733
875
  position: index.toString().padStart(2, "0"),
@@ -767,7 +909,7 @@ async function buildCategoryHeader(wixStoreService, category, categoryUrlTemplat
767
909
  header = { ...header, description: parent.description };
768
910
  }
769
911
  if (!header.imageUrl) {
770
- const parentImage = parent.image || "";
912
+ const parentImage = parent.image ? formatWixMediaUrl("", parent.image) : "";
771
913
  if (parentImage) {
772
914
  header = { ...header, imageUrl: parentImage, hasImage: true };
773
915
  }
@@ -784,13 +926,13 @@ async function renderSlowlyChanging$2(props, wixStores) {
784
926
  let activeCategory = null;
785
927
  let baseCategoryId = null;
786
928
  if (subcategorySlug) {
787
- activeCategory = await findCategoryBySlug(wixStores.categories, subcategorySlug);
929
+ activeCategory = await findCategoryBySlug$1(wixStores.categories, subcategorySlug);
788
930
  baseCategoryId = activeCategory?._id ?? null;
789
931
  } else if (categorySlug) {
790
- activeCategory = await findCategoryBySlug(wixStores.categories, categorySlug);
932
+ activeCategory = await findCategoryBySlug$1(wixStores.categories, categorySlug);
791
933
  baseCategoryId = activeCategory?._id ?? null;
792
934
  } else if (defaultCategorySlug) {
793
- activeCategory = await findCategoryBySlug(wixStores.categories, defaultCategorySlug);
935
+ activeCategory = await findCategoryBySlug$1(wixStores.categories, defaultCategorySlug);
794
936
  }
795
937
  const tree = await wixStores.getCategoryTree();
796
938
  const categoryHeader = activeCategory ? await buildCategoryHeader(wixStores, activeCategory, wixStores.urls.category) : EMPTY_CATEGORY_HEADER;
@@ -801,18 +943,35 @@ async function renderSlowlyChanging$2(props, wixStores) {
801
943
  if (baseCategoryId) {
802
944
  query = query.eq("parentCategory.id", baseCategoryId);
803
945
  }
804
- const categoriesResult = await query.find();
805
- return categoriesResult.items || [];
946
+ const baseCategoryIds = baseCategoryId ? [baseCategoryId] : [];
947
+ const [categoriesResult, productsResult] = await Promise.all([
948
+ query.find(),
949
+ searchProducts({
950
+ query: "",
951
+ filters: {
952
+ categoryIds: baseCategoryIds.length > 0 ? baseCategoryIds : void 0
953
+ },
954
+ pageSize: PAGE_SIZE
955
+ })
956
+ ]);
957
+ return {
958
+ categories: categoriesResult.items || [],
959
+ productsResult
960
+ };
806
961
  }).recover((error) => {
807
- console.error("Failed to load categories:", error);
808
- return Pipeline.ok([]);
809
- }).toPhaseOutput((categories2) => {
962
+ console.error("Failed to load categories/products:", error);
963
+ return Pipeline.ok({
964
+ categories: [],
965
+ productsResult: null
966
+ });
967
+ }).toPhaseOutput(({ categories: categories2, productsResult }) => {
810
968
  const categoryInfos = categories2.map((cat) => ({
811
969
  categoryId: cat._id || "",
812
970
  categoryName: cat.name || "",
813
971
  categorySlug: cat.slug || "",
814
972
  categoryUrl: buildCategoryUrl(wixStores.urls, tree, cat.slug || "", cat._id || "") ?? ""
815
973
  }));
974
+ const baseOptionFilters = productsResult?.optionFilters || [];
816
975
  return {
817
976
  viewState: {
818
977
  searchFields: "name,description,sku",
@@ -829,7 +988,9 @@ async function renderSlowlyChanging$2(props, wixStores) {
829
988
  searchFields: "name,description,sku",
830
989
  fuzzySearch: true,
831
990
  categories: categoryInfos,
832
- baseCategoryId
991
+ baseCategoryId,
992
+ preloadedResult: productsResult,
993
+ baseOptionFilters
833
994
  }
834
995
  };
835
996
  });
@@ -839,7 +1000,15 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
839
1000
  const urlFilters = parseUrlFilters(props.url);
840
1001
  const initialSort = parseSortParam(urlFilters.sort);
841
1002
  const initialCategoryIds = urlFilters.selectedCategorySlugs.map((slug) => slowCarryForward.categories.find((c) => c.categorySlug === slug)?.categoryId).filter(Boolean);
1003
+ const initialOptionFilters = [];
1004
+ for (const [optionName, choiceNames] of urlFilters.optionSelections) {
1005
+ initialOptionFilters.push({ optionName, choiceNames: [...choiceNames] });
1006
+ }
1007
+ const hasActiveFilters = !!urlFilters.searchTerm || initialCategoryIds.length > 0 || urlFilters.minPrice !== null || urlFilters.maxPrice !== null || urlFilters.inStockOnly || initialSort !== CurrentSort.relevance || initialOptionFilters.length > 0;
842
1008
  return Pipeline.try(async () => {
1009
+ if (!hasActiveFilters && slowCarryForward.preloadedResult) {
1010
+ return slowCarryForward.preloadedResult;
1011
+ }
843
1012
  const baseCategoryIds = slowCarryForward.baseCategoryId ? [slowCarryForward.baseCategoryId, ...initialCategoryIds] : initialCategoryIds;
844
1013
  const result = await searchProducts({
845
1014
  query: urlFilters.searchTerm || "",
@@ -847,7 +1016,8 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
847
1016
  categoryIds: baseCategoryIds.length > 0 ? baseCategoryIds : void 0,
848
1017
  minPrice: urlFilters.minPrice ?? void 0,
849
1018
  maxPrice: urlFilters.maxPrice ?? void 0,
850
- inStockOnly: urlFilters.inStockOnly || void 0
1019
+ inStockOnly: urlFilters.inStockOnly || void 0,
1020
+ optionFilters: initialOptionFilters.length > 0 ? initialOptionFilters : void 0
851
1021
  },
852
1022
  sortBy: initialSort !== CurrentSort.relevance ? mapSortToAction(initialSort) : void 0,
853
1023
  pageSize: PAGE_SIZE
@@ -867,13 +1037,14 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
867
1037
  {
868
1038
  rangeId: "all",
869
1039
  label: "Show all",
870
- minValue: null,
871
- maxValue: null,
1040
+ minValue: 0,
1041
+ maxValue: 1e3,
872
1042
  productCount: 0,
873
1043
  isSelected: true
874
1044
  }
875
1045
  ]
876
- }
1046
+ },
1047
+ optionFilters: []
877
1048
  });
878
1049
  }).toPhaseOutput((result) => {
879
1050
  const priceAgg = result.priceAggregation || {
@@ -883,8 +1054,8 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
883
1054
  {
884
1055
  rangeId: "all",
885
1056
  label: "Show all",
886
- minValue: null,
887
- maxValue: null,
1057
+ minValue: 0,
1058
+ maxValue: 1e3,
888
1059
  productCount: result.totalCount,
889
1060
  isSelected: true
890
1061
  }
@@ -907,14 +1078,19 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
907
1078
  maxPrice: urlFilters.maxPrice ?? priceAgg.maxBound,
908
1079
  minBound: priceAgg.minBound,
909
1080
  maxBound: priceAgg.maxBound,
910
- ranges: priceAgg.ranges
1081
+ ranges: priceAgg.ranges.map((r) => ({
1082
+ ...r,
1083
+ minValue: r.minValue ?? 0,
1084
+ maxValue: r.maxValue ?? 0
1085
+ }))
911
1086
  },
912
1087
  categoryFilter: {
913
1088
  categories: slowCarryForward.categories.map((cat) => ({
914
1089
  categoryId: cat.categoryId,
915
1090
  isSelected: initialCategoryIds.includes(cat.categoryId)
916
1091
  }))
917
- }
1092
+ },
1093
+ optionFilters: buildOptionFiltersViewState(slowCarryForward.baseOptionFilters, result, urlFilters.optionSelections)
918
1094
  },
919
1095
  sortBy: {
920
1096
  currentSort: initialSort
@@ -927,7 +1103,8 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
927
1103
  searchFields: slowCarryForward.searchFields,
928
1104
  fuzzySearch: slowCarryForward.fuzzySearch,
929
1105
  categories: slowCarryForward.categories,
930
- baseCategoryId: slowCarryForward.baseCategoryId
1106
+ baseCategoryId: slowCarryForward.baseCategoryId,
1107
+ baseOptionFilters: slowCarryForward.baseOptionFilters
931
1108
  }
932
1109
  };
933
1110
  });
@@ -1023,24 +1200,25 @@ function mapInfoSections(infoSections) {
1023
1200
  uniqueName: infoSection.uniqueName || ""
1024
1201
  }));
1025
1202
  }
1026
- function mapSeoData(seoData) {
1027
- return {
1028
- tags: seoData?.tags?.map((tag, index) => ({
1029
- position: index.toString().padStart(2, "0"),
1030
- type: tag.type,
1031
- props: Object.entries(tag.props || {}).map(([key, value]) => ({ key, value })),
1032
- meta: Object.entries(tag.meta || {}).map(([key, value]) => ({ key, value })),
1033
- children: tag.children
1034
- })),
1035
- settings: {
1036
- preventAutoRedirect: seoData?.settings?.preventAutoRedirect || false,
1037
- keywords: seoData?.settings?.keywords.map((keyword) => ({
1038
- isMain: keyword.isMain,
1039
- origin: keyword.origin,
1040
- term: keyword.term
1041
- }))
1203
+ function mapSeoHeadTags(seoData) {
1204
+ if (!seoData)
1205
+ return [];
1206
+ const headTags = (seoData.tags || []).map((tag) => ({
1207
+ tag: tag.type || "meta",
1208
+ attrs: Object.fromEntries(Object.entries(tag.props || {}).map(([key, value]) => [key, value])),
1209
+ children: tag.children || void 0
1210
+ }));
1211
+ const keywords = seoData.settings?.keywords;
1212
+ if (keywords?.length) {
1213
+ const terms = keywords.map((k) => k.term).filter(Boolean);
1214
+ if (terms.length) {
1215
+ headTags.push({
1216
+ tag: "meta",
1217
+ attrs: { name: "keywords", content: terms.join(", ") }
1218
+ });
1042
1219
  }
1043
- };
1220
+ }
1221
+ return headTags;
1044
1222
  }
1045
1223
  function mapMediaType(mediaType) {
1046
1224
  if (mediaType === "VIDEO")
@@ -1178,9 +1356,9 @@ async function renderSlowlyChanging$1(props, wixStores) {
1178
1356
  productType: mapProductType(productType),
1179
1357
  options: mapOptionsToSlowVS(options),
1180
1358
  infoSections: mapInfoSections(infoSections),
1181
- modifiers: mapModifiersToSlowVS(modifiers),
1182
- seoData: mapSeoData(seoData)
1359
+ modifiers: mapModifiersToSlowVS(modifiers)
1183
1360
  },
1361
+ headTags: mapSeoHeadTags(seoData),
1184
1362
  carryForward: {
1185
1363
  productId: _id,
1186
1364
  mediaGallery: mapMedia(media),
@@ -1190,7 +1368,7 @@ async function renderSlowlyChanging$1(props, wixStores) {
1190
1368
  price: actualPriceRange?.minValue?.formattedAmount || "",
1191
1369
  strikethroughPrice: actualPriceRange?.minValue?.amount !== product.compareAtPriceRange?.minValue?.amount ? compareAtPriceRange?.minValue?.formattedAmount || "" : "",
1192
1370
  pricePerUnit: physicalProperties?.pricePerUnitRange?.minValue?.description,
1193
- stockStatus: inventory?.availabilityStatus === "IN_STOCK" ? StockStatus.IN_STOCK : StockStatus.OUT_OF_STOCK,
1371
+ stockStatus: inventory?.availabilityStatus === "IN_STOCK" || inventory?.availabilityStatus === "PARTIALLY_OUT_OF_STOCK" ? StockStatus.IN_STOCK : StockStatus.OUT_OF_STOCK,
1194
1372
  variants: mapVariants(variantsInfo)
1195
1373
  }
1196
1374
  };
@@ -1198,17 +1376,45 @@ async function renderSlowlyChanging$1(props, wixStores) {
1198
1376
  }
1199
1377
  async function renderFastChanging(props, slowCarryForward, wixStores) {
1200
1378
  const Pipeline = RenderPipeline.for();
1379
+ const { variants } = slowCarryForward;
1380
+ const defaultVariant = variants.find((v) => v.inventoryStatus === StockStatus.IN_STOCK) || variants[0];
1381
+ const options = slowCarryForward.options.map((option) => {
1382
+ const variantChoice = defaultVariant.choices.find((c) => c.optionChoiceIds.optionId === option._id);
1383
+ if (!variantChoice)
1384
+ return option;
1385
+ return {
1386
+ ...option,
1387
+ choices: option.choices.map((choice) => ({
1388
+ ...choice,
1389
+ isSelected: choice.choiceId === variantChoice.optionChoiceIds.choiceId
1390
+ }))
1391
+ };
1392
+ });
1393
+ let mediaGallery = slowCarryForward.mediaGallery;
1394
+ if (defaultVariant.mediaId) {
1395
+ const mediaIndex = mediaGallery.availableMedia.findIndex((m) => m.mediaId === defaultVariant.mediaId);
1396
+ if (mediaIndex >= 0) {
1397
+ mediaGallery = {
1398
+ selectedMedia: mediaGallery.availableMedia[mediaIndex].media,
1399
+ availableMedia: mediaGallery.availableMedia.map((m, i) => ({
1400
+ ...m,
1401
+ selected: i === mediaIndex ? Selected.selected : Selected.notSelected
1402
+ }))
1403
+ };
1404
+ }
1405
+ }
1201
1406
  const isInStock = slowCarryForward.stockStatus === StockStatus.IN_STOCK;
1202
1407
  return Pipeline.ok({
1203
- actionsEnabled: isInStock,
1204
- options: slowCarryForward.options,
1408
+ actionsEnabled: isInStock && defaultVariant.inventoryStatus === StockStatus.IN_STOCK,
1409
+ options,
1205
1410
  modifiers: slowCarryForward.modifiers,
1206
- mediaGallery: slowCarryForward.mediaGallery,
1207
- sku: slowCarryForward.variants[0].sku,
1208
- price: slowCarryForward.variants[0].price,
1411
+ mediaGallery,
1412
+ sku: defaultVariant.sku,
1413
+ price: defaultVariant.price,
1209
1414
  pricePerUnit: slowCarryForward.pricePerUnit || "",
1210
- stockStatus: slowCarryForward.stockStatus,
1211
- strikethroughPrice: slowCarryForward.variants[0].strikethroughPrice,
1415
+ stockStatus: defaultVariant.inventoryStatus,
1416
+ strikethroughPrice: defaultVariant.strikethroughPrice,
1417
+ isAddingToCart: false,
1212
1418
  quantity: { quantity: 1 }
1213
1419
  }).toPhaseOutput((viewState) => ({
1214
1420
  viewState,
@@ -1219,14 +1425,28 @@ async function renderFastChanging(props, slowCarryForward, wixStores) {
1219
1425
  }));
1220
1426
  }
1221
1427
  const productPage = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withLoadParams(loadProductParams).withSlowlyRender(renderSlowlyChanging$1).withFastRender(renderFastChanging);
1428
+ async function findCategoryBySlug(categoriesClient, slug) {
1429
+ const result = await categoriesClient.queryCategories({ treeReference: { appNamespace: "@wix/stores" } }).eq("slug", slug).eq("visible", true).limit(1).find();
1430
+ return result.items?.[0] ?? null;
1431
+ }
1222
1432
  async function renderSlowlyChanging(props, wixStores) {
1223
1433
  const Pipeline = RenderPipeline.for();
1434
+ const parentCategorySlug = props.parentCategory ?? wixStores.defaultCategory;
1435
+ let parentCategoryId = null;
1436
+ if (parentCategorySlug) {
1437
+ const parentCat = await findCategoryBySlug(wixStores.categories, parentCategorySlug);
1438
+ parentCategoryId = parentCat?._id ?? null;
1439
+ }
1224
1440
  return Pipeline.try(async () => {
1225
- const result = await wixStores.categories.queryCategories({
1441
+ let query = wixStores.categories.queryCategories({
1226
1442
  treeReference: {
1227
1443
  appNamespace: "@wix/stores"
1228
1444
  }
1229
- }).eq("visible", true).find();
1445
+ }).eq("visible", true);
1446
+ if (parentCategoryId) {
1447
+ query = query.eq("parentCategory.id", parentCategoryId);
1448
+ }
1449
+ const result = await query.find();
1230
1450
  return result.items || [];
1231
1451
  }).recover((error) => {
1232
1452
  console.error("Failed to load categories:", error);