@decocms/apps-vtex 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/package.json +67 -0
  2. package/src/README.md +6 -0
  3. package/src/__tests__/client-segment-cookie.test.ts +255 -0
  4. package/src/__tests__/client-set-cookie-forward.test.ts +257 -0
  5. package/src/actions/address.ts +250 -0
  6. package/src/actions/analytics/sendEvent.ts +86 -0
  7. package/src/actions/auth.ts +333 -0
  8. package/src/actions/checkout.ts +522 -0
  9. package/src/actions/index.ts +11 -0
  10. package/src/actions/masterData.ts +168 -0
  11. package/src/actions/misc.ts +188 -0
  12. package/src/actions/newsletter.ts +105 -0
  13. package/src/actions/orders.ts +34 -0
  14. package/src/actions/profile.ts +201 -0
  15. package/src/actions/session.ts +85 -0
  16. package/src/actions/trigger.ts +43 -0
  17. package/src/actions/wishlist.ts +114 -0
  18. package/src/client.ts +667 -0
  19. package/src/commerceLoaders.ts +261 -0
  20. package/src/hooks/__tests__/createUseCart.test.ts +184 -0
  21. package/src/hooks/__tests__/createUseUser.test.ts +48 -0
  22. package/src/hooks/__tests__/createUseWishlist.test.ts +87 -0
  23. package/src/hooks/createUseCart.ts +360 -0
  24. package/src/hooks/createUseUser.ts +153 -0
  25. package/src/hooks/createUseWishlist.ts +242 -0
  26. package/src/hooks/index.ts +19 -0
  27. package/src/hooks/useAutocomplete.ts +83 -0
  28. package/src/hooks/useCart.ts +243 -0
  29. package/src/hooks/useUser.ts +78 -0
  30. package/src/hooks/useWishlist.ts +119 -0
  31. package/src/index.ts +17 -0
  32. package/src/invoke.ts +181 -0
  33. package/src/loaders/ProductDetailsPage.ts +1 -0
  34. package/src/loaders/ProductList.ts +1 -0
  35. package/src/loaders/ProductListingPage.ts +1 -0
  36. package/src/loaders/address.ts +115 -0
  37. package/src/loaders/autocomplete.ts +58 -0
  38. package/src/loaders/brands.ts +44 -0
  39. package/src/loaders/cart.ts +52 -0
  40. package/src/loaders/catalog.ts +163 -0
  41. package/src/loaders/collections.ts +55 -0
  42. package/src/loaders/index.ts +19 -0
  43. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +20 -0
  44. package/src/loaders/intelligentSearch/productDetailsPage.ts +95 -0
  45. package/src/loaders/intelligentSearch/productList.ts +154 -0
  46. package/src/loaders/intelligentSearch/productListingPage.ts +506 -0
  47. package/src/loaders/intelligentSearch/suggestions.ts +46 -0
  48. package/src/loaders/legacy/productDetailsPage.ts +1 -0
  49. package/src/loaders/legacy/productList.ts +1 -0
  50. package/src/loaders/legacy/relatedProductsLoader.ts +81 -0
  51. package/src/loaders/legacy.ts +635 -0
  52. package/src/loaders/logistics.ts +111 -0
  53. package/src/loaders/minicart.ts +78 -0
  54. package/src/loaders/navbar.ts +26 -0
  55. package/src/loaders/orders.ts +92 -0
  56. package/src/loaders/pageType.ts +68 -0
  57. package/src/loaders/payment.ts +97 -0
  58. package/src/loaders/productListFull.ts +159 -0
  59. package/src/loaders/profile.ts +138 -0
  60. package/src/loaders/promotion.ts +30 -0
  61. package/src/loaders/search.ts +124 -0
  62. package/src/loaders/session.ts +83 -0
  63. package/src/loaders/user.ts +87 -0
  64. package/src/loaders/wishlist.ts +89 -0
  65. package/src/loaders/wishlistProducts.ts +69 -0
  66. package/src/loaders/workflow/products.ts +64 -0
  67. package/src/loaders/workflow.ts +316 -0
  68. package/src/logo.png +0 -0
  69. package/src/middleware.ts +240 -0
  70. package/src/mod.ts +152 -0
  71. package/src/registry.ts +9 -0
  72. package/src/types.ts +248 -0
  73. package/src/utils/__tests__/cookieSanitizer.test.ts +162 -0
  74. package/src/utils/__tests__/fetch.test.ts +80 -0
  75. package/src/utils/__tests__/fetchCache.test.ts +112 -0
  76. package/src/utils/__tests__/instrumentedFetch.test.ts +158 -0
  77. package/src/utils/__tests__/intelligentSearch.test.ts +88 -0
  78. package/src/utils/__tests__/minicart.test.ts +184 -0
  79. package/src/utils/__tests__/operationRouter.test.ts +227 -0
  80. package/src/utils/__tests__/resourceRange.test.ts +32 -0
  81. package/src/utils/__tests__/sitemap.test.ts +185 -0
  82. package/src/utils/__tests__/slugify.test.ts +31 -0
  83. package/src/utils/__tests__/transform.test.ts +698 -0
  84. package/src/utils/accountLoaders.ts +203 -0
  85. package/src/utils/authHelpers.ts +85 -0
  86. package/src/utils/batch.ts +18 -0
  87. package/src/utils/cookieSanitizer.ts +173 -0
  88. package/src/utils/cookies.ts +167 -0
  89. package/src/utils/enrichment.ts +560 -0
  90. package/src/utils/fetch.ts +107 -0
  91. package/src/utils/fetchCache.ts +222 -0
  92. package/src/utils/index.ts +19 -0
  93. package/src/utils/instrumentedFetch.ts +78 -0
  94. package/src/utils/intelligentSearch.ts +96 -0
  95. package/src/utils/legacy.ts +139 -0
  96. package/src/utils/minicart.ts +139 -0
  97. package/src/utils/operationRouter.ts +139 -0
  98. package/src/utils/pickAndOmit.ts +25 -0
  99. package/src/utils/proxy.ts +435 -0
  100. package/src/utils/resourceRange.ts +10 -0
  101. package/src/utils/segment.ts +152 -0
  102. package/src/utils/similars.ts +37 -0
  103. package/src/utils/sitemap.ts +282 -0
  104. package/src/utils/slugCache.ts +28 -0
  105. package/src/utils/slugify.ts +13 -0
  106. package/src/utils/transform.ts +1509 -0
  107. package/src/utils/types.ts +1884 -0
  108. package/src/utils/vtexId.ts +138 -0
  109. package/tsconfig.json +7 -0
@@ -0,0 +1,163 @@
1
+ /**
2
+ * VTEX Catalog System API loaders.
3
+ * Pure async functions using vtexFetch. Require configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps vtex/loaders/legacy/*.ts and vtex/utils/client.ts
6
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api
7
+ */
8
+ import { getVtexConfig, vtexFetch } from "../client";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Product search (public)
12
+ // ---------------------------------------------------------------------------
13
+
14
+ export type LegacySort =
15
+ | ""
16
+ | "OrderByTopSaleDESC"
17
+ | "OrderByReleaseDateDESC"
18
+ | "OrderByBestDiscountDESC"
19
+ | "OrderByPriceDESC"
20
+ | "OrderByPriceASC"
21
+ | "OrderByNameASC"
22
+ | "OrderByNameDESC"
23
+ | "OrderByScoreDESC";
24
+
25
+ export interface SearchProductsOpts {
26
+ fq?: string | string[];
27
+ ft?: string;
28
+ sort?: LegacySort;
29
+ from?: number;
30
+ to?: number;
31
+ }
32
+
33
+ /**
34
+ * Search products using the VTEX Catalog API.
35
+ * @see https://developers.vtex.com/docs/api-reference/search-api#get-/api/catalog_system/pub/products/search
36
+ */
37
+ export async function searchProducts<T = any>(opts: SearchProductsOpts = {}): Promise<T[]> {
38
+ const params = new URLSearchParams();
39
+ const fqs = Array.isArray(opts.fq) ? opts.fq : opts.fq ? [opts.fq] : [];
40
+ for (const f of fqs) params.append("fq", f);
41
+ if (opts.ft) params.set("ft", opts.ft);
42
+ if (opts.sort) params.set("O", opts.sort);
43
+ if (opts.from != null) params.set("_from", String(opts.from));
44
+ if (opts.to != null) params.set("_to", String(opts.to));
45
+
46
+ const { salesChannel } = getVtexConfig();
47
+ if (salesChannel) params.set("sc", salesChannel);
48
+
49
+ return vtexFetch<T[]>(`/api/catalog_system/pub/products/search/?${params}`);
50
+ }
51
+
52
+ /**
53
+ * Get a product by productId or skuId.
54
+ * @see https://developers.vtex.com/docs/api-reference/search-api#get-/api/catalog_system/pub/products/search
55
+ */
56
+ export async function getProductByIdOrSku<T = any>(opts: {
57
+ productId?: string;
58
+ skuId?: string;
59
+ }): Promise<T | null> {
60
+ let fq: string;
61
+ if (opts.productId) fq = `productId:${opts.productId}`;
62
+ else if (opts.skuId) fq = `skuId:${opts.skuId}`;
63
+ else throw new Error("Neither productId nor skuId provided.");
64
+
65
+ const results = await searchProducts<T>({ fq });
66
+ return results[0] ?? null;
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Product variations (public)
71
+ // ---------------------------------------------------------------------------
72
+
73
+ /**
74
+ * Get product SKU variations.
75
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/products/variations/-productId-
76
+ */
77
+ export async function getProductVariations<T = any>(productId: string): Promise<T> {
78
+ return vtexFetch<T>(`/api/catalog_system/pub/products/variations/${productId}`);
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Product specification (private — requires appKey/appToken)
83
+ // ---------------------------------------------------------------------------
84
+
85
+ /**
86
+ * Get product specifications (private API).
87
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pvt/products/-productId-/specification
88
+ */
89
+ export async function getProductSpecification<T = any>(productId: string): Promise<T> {
90
+ return vtexFetch<T>(`/api/catalog_system/pvt/products/${productId}/Specification`);
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Cross-selling
95
+ // ---------------------------------------------------------------------------
96
+
97
+ export type CrossSellingType =
98
+ | "similars"
99
+ | "suggestions"
100
+ | "accessories"
101
+ | "whosawalsosaw"
102
+ | "whosawalsobought"
103
+ | "whoboughtalsobought"
104
+ | "showtogether";
105
+
106
+ /**
107
+ * Get cross-selling products.
108
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/products/crossselling/-type-/-productId-
109
+ */
110
+ export async function getCrossSelling<T = any>(
111
+ type: CrossSellingType,
112
+ productId: string,
113
+ ): Promise<T[]> {
114
+ return vtexFetch<T[]>(`/api/catalog_system/pub/products/crossselling/${type}/${productId}`);
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Categories
119
+ // ---------------------------------------------------------------------------
120
+
121
+ /**
122
+ * Get the category tree up to a given depth level.
123
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/category/tree/-categoryLevels-
124
+ */
125
+ export async function getCategoryTree<T = any>(levels = 3): Promise<T[]> {
126
+ return vtexFetch<T[]>(`/api/catalog_system/pub/category/tree/${levels}`);
127
+ }
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // Brands
131
+ // ---------------------------------------------------------------------------
132
+
133
+ /**
134
+ * Get all brands.
135
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/brand/list
136
+ */
137
+ export async function getBrands<T = any>(): Promise<T[]> {
138
+ return vtexFetch<T[]>("/api/catalog_system/pub/brand/list");
139
+ }
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // Page type
143
+ // ---------------------------------------------------------------------------
144
+
145
+ /**
146
+ * Get the page type for a given term/path.
147
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pub/portal/pagetype/-term-
148
+ */
149
+ export async function getPageType<T = any>(term: string): Promise<T> {
150
+ return vtexFetch<T>(`/api/catalog_system/pub/portal/pagetype/${term}`);
151
+ }
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // Facets (legacy)
155
+ // ---------------------------------------------------------------------------
156
+
157
+ /**
158
+ * Get facets/filters for a search term.
159
+ * @see https://developers.vtex.com/docs/api-reference/search-api#get-/api/catalog_system/pub/facets/search/-term-
160
+ */
161
+ export async function getFacets<T = any>(term: string): Promise<T> {
162
+ return vtexFetch<T>(`/api/catalog_system/pub/facets/search/${term}`);
163
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * VTEX Collections loader.
3
+ * Pure async function — requires configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/collections/list.ts
7
+ *
8
+ * @see https://developers.vtex.com/docs/api-reference/catalog-api#get-/api/catalog_system/pvt/collection/search
9
+ */
10
+ import { vtexFetch } from "../client";
11
+ import type { CollectionList } from "../utils/types";
12
+
13
+ export interface CollectionOption {
14
+ value: string;
15
+ label: string;
16
+ }
17
+
18
+ /**
19
+ * Fetch collections, optionally filtering by search term.
20
+ *
21
+ * Without a term returns up to 3 000 collections; with a term
22
+ * returns the first 15 matches. Results are mapped to a simple
23
+ * `{ value, label }` list suitable for dropdowns / selectors.
24
+ *
25
+ * Note: uses the **pvt** (private) endpoint — requires appKey/appToken.
26
+ */
27
+ export async function getCollections(term?: string): Promise<CollectionOption[]> {
28
+ const params = new URLSearchParams();
29
+
30
+ if (term) {
31
+ params.set("page", "1");
32
+ params.set("pageSize", "15");
33
+ const list = await vtexFetch<CollectionList>(
34
+ `/api/catalog_system/pvt/collection/search/${encodeURIComponent(term)}?${params}`,
35
+ );
36
+ return mapToOptions(list);
37
+ }
38
+
39
+ params.set("page", "1");
40
+ params.set("pageSize", "3000");
41
+ params.set("orderByAsc", "false");
42
+ const list = await vtexFetch<CollectionList>(
43
+ `/api/catalog_system/pvt/collection/search?${params}`,
44
+ );
45
+ return mapToOptions(list);
46
+ }
47
+
48
+ function mapToOptions(list: CollectionList): CollectionOption[] {
49
+ return (
50
+ list.items?.map((c) => ({
51
+ value: `${c.id}`,
52
+ label: `${c.id} - ${c.name}`,
53
+ })) ?? []
54
+ );
55
+ }
@@ -0,0 +1,19 @@
1
+ export * from "./address";
2
+ export * from "./brands";
3
+ export * from "./cart";
4
+ export * from "./catalog";
5
+ export * from "./collections";
6
+ export * from "./legacy";
7
+ export * from "./logistics";
8
+ export * from "./navbar";
9
+ export * from "./orders";
10
+ export * from "./pageType";
11
+ export * from "./payment";
12
+ export * from "./profile";
13
+ export * from "./promotion";
14
+ export * from "./search";
15
+ export * from "./session";
16
+ export * from "./user";
17
+ export * from "./wishlist";
18
+ export * from "./wishlistProducts";
19
+ export * from "./workflow";
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { mapLabelledFuzzyToFuzzy } from "../productListingPage";
3
+
4
+ describe("mapLabelledFuzzyToFuzzy", () => {
5
+ it("translates 'automatic' to 'auto'", () => {
6
+ expect(mapLabelledFuzzyToFuzzy("automatic")).toBe("auto");
7
+ });
8
+
9
+ it("translates 'enabled' to '1'", () => {
10
+ expect(mapLabelledFuzzyToFuzzy("enabled")).toBe("1");
11
+ });
12
+
13
+ it("translates 'disabled' to '0'", () => {
14
+ expect(mapLabelledFuzzyToFuzzy("disabled")).toBe("0");
15
+ });
16
+
17
+ it("returns undefined for missing label", () => {
18
+ expect(mapLabelledFuzzyToFuzzy(undefined)).toBeUndefined();
19
+ });
20
+ });
@@ -0,0 +1,95 @@
1
+ /**
2
+ * PDP loader using Legacy Catalog API + shared transform pipeline.
3
+ * Maps VTEX catalog response to schema.org ProductDetailsPage
4
+ * following the same pattern as deco-cx/apps.
5
+ */
6
+
7
+ import type { ProductDetailsPage } from "@decocms/apps-commerce/types";
8
+ import { getVtexConfig, vtexCachedFetch } from "../../client";
9
+ import { searchBySlug } from "../../utils/slugCache";
10
+ import { pickSku, toProductPage } from "../../utils/transform";
11
+ import type { LegacyProduct } from "../../utils/types";
12
+
13
+ export interface PDPProps {
14
+ slug?: string;
15
+ skuId?: string;
16
+ /** When true, PDP pages with ?skuId remain indexable */
17
+ indexingSkus?: boolean;
18
+ /** Use product.description instead of metaTagDescription for SEO */
19
+ preferDescription?: boolean;
20
+ /**
21
+ * Use lean variant transform for hasVariant[]. Defaults to false on PDPs:
22
+ * variant selectors need image[] and real inventoryLevel to render thumbnails
23
+ * and per-SKU stock state, so a full toProduct(level=1) is the safe default.
24
+ * Opt-in only if payload size becomes a problem.
25
+ */
26
+ leanVariants?: boolean;
27
+ /** When leanVariants is true, still include image[0] on each variant. Default true. */
28
+ variantIncludeImage?: boolean;
29
+ /** When leanVariants is true, still include inventoryLevel on each variant. Default true. */
30
+ variantIncludeInventory?: boolean;
31
+ }
32
+
33
+ export default async function vtexProductDetailsPage(
34
+ props: PDPProps,
35
+ ): Promise<ProductDetailsPage | null> {
36
+ const {
37
+ slug,
38
+ skuId,
39
+ indexingSkus,
40
+ preferDescription,
41
+ leanVariants = false,
42
+ variantIncludeImage = true,
43
+ variantIncludeInventory = true,
44
+ } = props;
45
+ if (!slug) return null;
46
+
47
+ try {
48
+ const linkText = slug.replace(/\/p$/, "").replace(/^\//, "").toLowerCase();
49
+ const config = getVtexConfig();
50
+ const sc = config.salesChannel;
51
+
52
+ const products = await searchBySlug(linkText);
53
+
54
+ if (!products || products.length === 0) {
55
+ return null;
56
+ }
57
+
58
+ const product = products[0];
59
+ const baseUrl = config.publicUrl
60
+ ? `https://${config.publicUrl}`
61
+ : `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
62
+
63
+ const sku = pickSku(product, skuId);
64
+
65
+ const kitItems: LegacyProduct[] =
66
+ Array.isArray(sku.kitItems) && sku.kitItems.length > 0
67
+ ? ((await vtexCachedFetch<LegacyProduct[]>(
68
+ `/api/catalog_system/pub/products/search/?fq=${sku.kitItems.map((item: any) => `skuId:${item.itemId}`).join("&fq=")}&_from=0&_to=49${sc ? `&sc=${sc}` : ""}`,
69
+ )) ?? [])
70
+ : [];
71
+
72
+ const page = toProductPage(product, sku, kitItems, {
73
+ baseUrl,
74
+ priceCurrency: "BRL",
75
+ leanVariants,
76
+ variantIncludeImage,
77
+ variantIncludeInventory,
78
+ });
79
+
80
+ return {
81
+ ...page,
82
+ seo: {
83
+ title: product.productTitle || product.productName,
84
+ description: preferDescription
85
+ ? product.description
86
+ : product.metaTagDescription || product.description?.substring(0, 160) || "",
87
+ canonical: `/${product.linkText}/p`,
88
+ noIndexing: indexingSkus ? false : !!skuId,
89
+ },
90
+ };
91
+ } catch (error) {
92
+ console.error("[VTEX] PDP error:", error);
93
+ return null;
94
+ }
95
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Lean product list loader for shelf/card display.
3
+ * Same API call as productList.ts but uses toProductShelf() for ~90% smaller payloads.
4
+ *
5
+ * Use this loader for ProductShelf sections where only card-level data is needed
6
+ * (name, URL, images, price, installments, PIX, availability, brand).
7
+ */
8
+
9
+ import type { Product } from "@decocms/apps-commerce/types";
10
+ import { getVtexConfig, intelligentSearch, toFacetPath } from "../../client";
11
+ import { pickSku, sortProducts, toProductShelf } from "../../utils/transform";
12
+ import type { Product as ProductVTEX } from "../../utils/types";
13
+
14
+ export interface ProductListProps {
15
+ props?: CollectionProps | QueryProps | ProductIDProps | FacetsProps;
16
+ }
17
+
18
+ interface CollectionProps {
19
+ collection: string;
20
+ count?: number;
21
+ sort?: string;
22
+ hideUnavailableItems?: boolean;
23
+ }
24
+
25
+ interface QueryProps {
26
+ query: string;
27
+ count?: number;
28
+ sort?: string;
29
+ fuzzy?: string;
30
+ hideUnavailableItems?: boolean;
31
+ }
32
+
33
+ interface ProductIDProps {
34
+ ids: string[];
35
+ hideUnavailableItems?: boolean;
36
+ }
37
+
38
+ interface FacetsProps {
39
+ query?: string;
40
+ facets: string;
41
+ count?: number;
42
+ sort?: string;
43
+ hideUnavailableItems?: boolean;
44
+ }
45
+
46
+ function isCollectionProps(p: any): p is CollectionProps {
47
+ return typeof p?.collection === "string";
48
+ }
49
+ function isProductIDProps(p: any): p is ProductIDProps {
50
+ return Array.isArray(p?.ids) && p.ids.length > 0;
51
+ }
52
+ function isFacetsProps(p: any): p is FacetsProps {
53
+ return typeof p?.facets === "string";
54
+ }
55
+
56
+ function resolveParams(props: ProductListProps): {
57
+ query: string;
58
+ count: number;
59
+ sort: string;
60
+ facetPath: string;
61
+ fuzzy?: string;
62
+ hideUnavailableItems: boolean;
63
+ ids?: string[];
64
+ } {
65
+ const inner = props.props ?? props;
66
+
67
+ if (isProductIDProps(inner)) {
68
+ return {
69
+ query: `sku:${inner.ids.join(";")}`,
70
+ count: inner.ids.length,
71
+ sort: "",
72
+ facetPath: "",
73
+ hideUnavailableItems: inner.hideUnavailableItems ?? false,
74
+ ids: inner.ids,
75
+ };
76
+ }
77
+
78
+ if (isFacetsProps(inner)) {
79
+ return {
80
+ query: inner.query ?? "",
81
+ count: inner.count ?? 12,
82
+ sort: inner.sort ?? "",
83
+ facetPath: inner.facets,
84
+ hideUnavailableItems: inner.hideUnavailableItems ?? false,
85
+ };
86
+ }
87
+
88
+ if (isCollectionProps(inner)) {
89
+ return {
90
+ query: "",
91
+ count: inner.count ?? 12,
92
+ sort: inner.sort ?? "",
93
+ facetPath: toFacetPath([{ key: "productClusterIds", value: inner.collection }]),
94
+ hideUnavailableItems: inner.hideUnavailableItems ?? false,
95
+ };
96
+ }
97
+
98
+ return {
99
+ query: (inner as any).query ?? "",
100
+ count: (inner as any).count ?? 12,
101
+ sort: (inner as any).sort ?? "",
102
+ facetPath: "",
103
+ fuzzy: (inner as any).fuzzy,
104
+ hideUnavailableItems: (inner as any).hideUnavailableItems ?? false,
105
+ };
106
+ }
107
+
108
+ export default async function vtexProductListShelf(
109
+ props: ProductListProps,
110
+ ): Promise<Product[] | null> {
111
+ try {
112
+ const { query, count, sort, facetPath, fuzzy, hideUnavailableItems, ids } =
113
+ resolveParams(props);
114
+
115
+ const config = getVtexConfig();
116
+ const locale = config.locale ?? "pt-BR";
117
+
118
+ const params: Record<string, string> = {
119
+ page: "1",
120
+ count: String(count),
121
+ locale,
122
+ hideUnavailableItems: String(hideUnavailableItems),
123
+ };
124
+ if (query) params.query = query;
125
+ if (sort) params.sort = sort;
126
+ if (fuzzy) params.fuzzy = fuzzy;
127
+
128
+ const endpoint = facetPath ? `/product_search/${facetPath}` : "/product_search/";
129
+
130
+ const data = await intelligentSearch<{ products: ProductVTEX[] }>(endpoint, params);
131
+
132
+ const vtexProducts = data.products ?? [];
133
+ const baseUrl = config.publicUrl
134
+ ? `https://${config.publicUrl}`
135
+ : `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
136
+
137
+ let products = vtexProducts.map((p) => {
138
+ const fetchedSkus = ids ? new Set(ids) : null;
139
+ const preferredSku = fetchedSkus
140
+ ? (p.items.find((item) => fetchedSkus.has(item.itemId)) ?? pickSku(p))
141
+ : pickSku(p);
142
+ return toProductShelf(p, preferredSku, 0, { baseUrl, priceCurrency: "BRL" });
143
+ });
144
+
145
+ if (ids) {
146
+ products = sortProducts(products, ids, "sku");
147
+ }
148
+
149
+ return products;
150
+ } catch (error) {
151
+ console.error("[VTEX] ProductListShelf error:", error);
152
+ return null;
153
+ }
154
+ }