@decocms/apps 0.23.3 → 0.24.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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/commerce/components/Image.tsx +129 -143
  3. package/commerce/components/JsonLd.tsx +192 -201
  4. package/commerce/components/Picture.tsx +65 -75
  5. package/commerce/sdk/analytics.ts +15 -15
  6. package/commerce/sdk/formatPrice.ts +13 -16
  7. package/commerce/sdk/url.ts +7 -7
  8. package/commerce/sdk/useOffer.ts +46 -57
  9. package/commerce/sdk/useVariantPossibilities.ts +25 -25
  10. package/commerce/types/commerce.ts +868 -875
  11. package/commerce/utils/canonical.ts +5 -8
  12. package/commerce/utils/constants.ts +5 -6
  13. package/commerce/utils/filters.ts +4 -4
  14. package/commerce/utils/productToAnalyticsItem.ts +52 -56
  15. package/commerce/utils/stateByZip.ts +42 -42
  16. package/package.json +23 -4
  17. package/shopify/actions/cart/addItems.ts +24 -25
  18. package/shopify/actions/cart/updateCoupons.ts +19 -20
  19. package/shopify/actions/cart/updateItems.ts +19 -20
  20. package/shopify/actions/user/signIn.ts +25 -30
  21. package/shopify/actions/user/signUp.ts +19 -24
  22. package/shopify/client.ts +24 -24
  23. package/shopify/index.ts +20 -18
  24. package/shopify/init.ts +18 -21
  25. package/shopify/loaders/ProductDetailsPage.ts +16 -20
  26. package/shopify/loaders/ProductList.ts +66 -69
  27. package/shopify/loaders/ProductListingPage.ts +150 -158
  28. package/shopify/loaders/RelatedProducts.ts +24 -27
  29. package/shopify/loaders/cart.ts +53 -52
  30. package/shopify/loaders/shop.ts +22 -27
  31. package/shopify/loaders/user.ts +27 -32
  32. package/shopify/utils/admin/admin.ts +33 -34
  33. package/shopify/utils/admin/queries.ts +2 -2
  34. package/shopify/utils/cart.ts +18 -14
  35. package/shopify/utils/cookies.ts +62 -65
  36. package/shopify/utils/enums.ts +424 -424
  37. package/shopify/utils/graphql.ts +44 -55
  38. package/shopify/utils/storefront/queries.ts +24 -29
  39. package/shopify/utils/storefront/storefront.graphql.gen.ts +55 -55
  40. package/shopify/utils/transform.ts +370 -376
  41. package/shopify/utils/types.ts +118 -118
  42. package/shopify/utils/user.ts +11 -11
  43. package/shopify/utils/utils.ts +135 -140
  44. package/vtex/actions/address.ts +86 -86
  45. package/vtex/actions/auth.ts +14 -27
  46. package/vtex/actions/checkout.ts +36 -49
  47. package/vtex/actions/masterData.ts +10 -27
  48. package/vtex/actions/misc.ts +101 -111
  49. package/vtex/actions/newsletter.ts +48 -52
  50. package/vtex/actions/orders.ts +13 -16
  51. package/vtex/actions/profile.ts +55 -55
  52. package/vtex/actions/session.ts +36 -35
  53. package/vtex/actions/trigger.ts +25 -25
  54. package/vtex/actions/wishlist.ts +51 -53
  55. package/vtex/client.ts +14 -42
  56. package/vtex/hooks/index.ts +4 -4
  57. package/vtex/hooks/useAutocomplete.ts +42 -48
  58. package/vtex/hooks/useCart.ts +153 -165
  59. package/vtex/hooks/useUser.ts +40 -40
  60. package/vtex/hooks/useWishlist.ts +70 -70
  61. package/vtex/inline-loaders/productDetailsPage.ts +1 -3
  62. package/vtex/inline-loaders/productList.ts +121 -127
  63. package/vtex/inline-loaders/productListingPage.ts +10 -34
  64. package/vtex/inline-loaders/relatedProducts.ts +1 -3
  65. package/vtex/inline-loaders/suggestions.ts +36 -39
  66. package/vtex/inline-loaders/workflowProducts.ts +45 -49
  67. package/vtex/invoke.ts +159 -194
  68. package/vtex/loaders/address.ts +49 -54
  69. package/vtex/loaders/brands.ts +19 -26
  70. package/vtex/loaders/cart.ts +24 -21
  71. package/vtex/loaders/catalog.ts +51 -53
  72. package/vtex/loaders/collections.ts +25 -27
  73. package/vtex/loaders/legacy.ts +487 -534
  74. package/vtex/loaders/logistics.ts +33 -37
  75. package/vtex/loaders/navbar.ts +5 -8
  76. package/vtex/loaders/orders.ts +28 -39
  77. package/vtex/loaders/pageType.ts +41 -35
  78. package/vtex/loaders/payment.ts +27 -37
  79. package/vtex/loaders/profile.ts +38 -38
  80. package/vtex/loaders/promotion.ts +5 -8
  81. package/vtex/loaders/search.ts +56 -59
  82. package/vtex/loaders/session.ts +22 -30
  83. package/vtex/loaders/user.ts +39 -41
  84. package/vtex/loaders/wishlist.ts +35 -35
  85. package/vtex/loaders/wishlistProducts.ts +3 -15
  86. package/vtex/loaders/workflow.ts +220 -227
  87. package/vtex/middleware.ts +116 -119
  88. package/vtex/types.ts +201 -201
  89. package/vtex/utils/batch.ts +13 -16
  90. package/vtex/utils/cookies.ts +76 -80
  91. package/vtex/utils/enrichment.ts +62 -42
  92. package/vtex/utils/fetchCache.ts +1 -4
  93. package/vtex/utils/index.ts +6 -6
  94. package/vtex/utils/intelligentSearch.ts +48 -57
  95. package/vtex/utils/legacy.ts +108 -124
  96. package/vtex/utils/pickAndOmit.ts +15 -20
  97. package/vtex/utils/proxy.ts +136 -146
  98. package/vtex/utils/resourceRange.ts +3 -3
  99. package/vtex/utils/segment.ts +100 -111
  100. package/vtex/utils/similars.ts +1 -2
  101. package/vtex/utils/sitemap.ts +91 -91
  102. package/vtex/utils/slugCache.ts +2 -6
  103. package/vtex/utils/slugify.ts +9 -9
  104. package/vtex/utils/transform.ts +1012 -1105
  105. package/vtex/utils/types.ts +1381 -1381
  106. package/vtex/utils/vtexId.ts +44 -47
  107. package/.github/workflows/release.yml +0 -34
  108. package/.releaserc.json +0 -28
  109. package/knip.json +0 -19
  110. package/tsconfig.json +0 -11
@@ -11,36 +11,29 @@
11
11
  * @see https://developers.vtex.com/docs/api-reference/search-api
12
12
  */
13
13
  import type {
14
- Filter,
15
- Product,
16
- ProductDetailsPage,
17
- ProductListingPage,
18
- Suggestion,
14
+ Filter,
15
+ Product,
16
+ ProductDetailsPage,
17
+ ProductListingPage,
18
+ Suggestion,
19
19
  } from "../../commerce/types/commerce";
20
20
  import { getVtexConfig, vtexFetch, vtexFetchResponse } from "../client";
21
21
  import {
22
- getMapAndTerm,
23
- getValidTypesFromPageTypes,
24
- isFilterParam,
25
- pageTypesFromPathname,
26
- pageTypesToBreadcrumbList,
27
- pageTypesToSeo,
22
+ getMapAndTerm,
23
+ getValidTypesFromPageTypes,
24
+ pageTypesFromPathname,
25
+ pageTypesToBreadcrumbList,
26
+ pageTypesToSeo,
28
27
  } from "../utils/legacy";
29
28
  import {
30
- legacyFacetToFilter,
31
- parsePageType,
32
- pickSku,
33
- sortProducts,
34
- toProduct,
35
- toProductPage,
29
+ legacyFacetToFilter,
30
+ parsePageType,
31
+ pickSku,
32
+ sortProducts,
33
+ toProduct,
34
+ toProductPage,
36
35
  } from "../utils/transform";
37
- import type {
38
- LegacyFacet,
39
- LegacyItem,
40
- LegacyProduct,
41
- LegacySort,
42
- PageType,
43
- } from "../utils/types";
36
+ import type { LegacyFacet, LegacyItem, LegacyProduct, LegacySort, PageType } from "../utils/types";
44
37
 
45
38
  // ---------------------------------------------------------------------------
46
39
  // Shared helpers
@@ -49,25 +42,25 @@ import type {
49
42
  const MAX_ALLOWED_PAGES = 500;
50
43
 
51
44
  function salesChannelParam(): string {
52
- return getVtexConfig().salesChannel ?? "1";
45
+ return getVtexConfig().salesChannel ?? "1";
53
46
  }
54
47
 
55
48
  function buildSearchParams(
56
- extra: Record<string, string | string[] | number | undefined>,
49
+ extra: Record<string, string | string[] | number | undefined>,
57
50
  ): URLSearchParams {
58
- const params = new URLSearchParams();
59
- const sc = salesChannelParam();
60
- if (sc) params.set("sc", sc);
61
-
62
- for (const [key, val] of Object.entries(extra)) {
63
- if (val == null) continue;
64
- if (Array.isArray(val)) {
65
- for (const v of val) params.append(key, v);
66
- } else {
67
- params.set(key, String(val));
68
- }
69
- }
70
- return params;
51
+ const params = new URLSearchParams();
52
+ const sc = salesChannelParam();
53
+ if (sc) params.set("sc", sc);
54
+
55
+ for (const [key, val] of Object.entries(extra)) {
56
+ if (val == null) continue;
57
+ if (Array.isArray(val)) {
58
+ for (const v of val) params.append(key, v);
59
+ } else {
60
+ params.set(key, String(val));
61
+ }
62
+ }
63
+ return params;
71
64
  }
72
65
 
73
66
  // ---------------------------------------------------------------------------
@@ -75,14 +68,14 @@ function buildSearchParams(
75
68
  // ---------------------------------------------------------------------------
76
69
 
77
70
  export interface LegacyPDPOptions {
78
- slug: string;
79
- skuId?: string;
80
- baseUrl: string;
81
- priceCurrency?: string;
82
- includeOriginalAttributes?: string[];
83
- preferDescription?: boolean;
84
- /** When true, pages with ?skuId are still indexable */
85
- indexingSkus?: boolean;
71
+ slug: string;
72
+ skuId?: string;
73
+ baseUrl: string;
74
+ priceCurrency?: string;
75
+ includeOriginalAttributes?: string[];
76
+ preferDescription?: boolean;
77
+ /** When true, pages with ?skuId are still indexable */
78
+ indexingSkus?: boolean;
86
79
  }
87
80
 
88
81
  /**
@@ -92,66 +85,62 @@ export interface LegacyPDPOptions {
92
85
  * Ported from: vtex/loaders/legacy/productDetailsPage.ts
93
86
  */
94
87
  export async function legacyProductDetailsPage(
95
- opts: LegacyPDPOptions,
88
+ opts: LegacyPDPOptions,
96
89
  ): Promise<ProductDetailsPage | null> {
97
- const {
98
- slug,
99
- skuId,
100
- baseUrl,
101
- priceCurrency = "BRL",
102
- includeOriginalAttributes,
103
- preferDescription,
104
- indexingSkus,
105
- } = opts;
106
-
107
- const lowercaseSlug = slug.toLowerCase();
108
- const qs = buildSearchParams({});
109
-
110
- const response = await vtexFetch<LegacyProduct[]>(
111
- `/api/catalog_system/pub/products/search/${lowercaseSlug}/p?${qs}`,
112
- );
113
-
114
- if (response && !Array.isArray(response)) {
115
- throw new Error(
116
- `Error while fetching VTEX data ${JSON.stringify(response)}`,
117
- );
118
- }
119
-
120
- const [product] = response;
121
- if (!product) return null;
122
-
123
- const sku = pickSku(product, skuId);
124
-
125
- const kitItems: LegacyProduct[] =
126
- Array.isArray(sku.kitItems) && sku.kitItems.length > 0
127
- ? await vtexFetch<LegacyProduct[]>(
128
- `/api/catalog_system/pub/products/search/?${buildSearchParams({
129
- _from: 0,
130
- _to: 49,
131
- fq: sku.kitItems.map((item) => `skuId:${item.itemId}`),
132
- })}`,
133
- )
134
- : [];
135
-
136
- const page = toProductPage(product, sku, kitItems, {
137
- baseUrl,
138
- priceCurrency,
139
- includeOriginalAttributes,
140
- });
141
-
142
- const url = new URL(baseUrl);
143
-
144
- return {
145
- ...page,
146
- seo: {
147
- title: product.productTitle || product.productName,
148
- description: preferDescription
149
- ? product.description
150
- : product.metaTagDescription,
151
- canonical: new URL(`/${product.linkText}/p`, url.origin).href,
152
- noIndexing: indexingSkus ? false : !!skuId,
153
- },
154
- };
90
+ const {
91
+ slug,
92
+ skuId,
93
+ baseUrl,
94
+ priceCurrency = "BRL",
95
+ includeOriginalAttributes,
96
+ preferDescription,
97
+ indexingSkus,
98
+ } = opts;
99
+
100
+ const lowercaseSlug = slug.toLowerCase();
101
+ const qs = buildSearchParams({});
102
+
103
+ const response = await vtexFetch<LegacyProduct[]>(
104
+ `/api/catalog_system/pub/products/search/${lowercaseSlug}/p?${qs}`,
105
+ );
106
+
107
+ if (response && !Array.isArray(response)) {
108
+ throw new Error(`Error while fetching VTEX data ${JSON.stringify(response)}`);
109
+ }
110
+
111
+ const [product] = response;
112
+ if (!product) return null;
113
+
114
+ const sku = pickSku(product, skuId);
115
+
116
+ const kitItems: LegacyProduct[] =
117
+ Array.isArray(sku.kitItems) && sku.kitItems.length > 0
118
+ ? await vtexFetch<LegacyProduct[]>(
119
+ `/api/catalog_system/pub/products/search/?${buildSearchParams({
120
+ _from: 0,
121
+ _to: 49,
122
+ fq: sku.kitItems.map((item) => `skuId:${item.itemId}`),
123
+ })}`,
124
+ )
125
+ : [];
126
+
127
+ const page = toProductPage(product, sku, kitItems, {
128
+ baseUrl,
129
+ priceCurrency,
130
+ includeOriginalAttributes,
131
+ });
132
+
133
+ const url = new URL(baseUrl);
134
+
135
+ return {
136
+ ...page,
137
+ seo: {
138
+ title: product.productTitle || product.productName,
139
+ description: preferDescription ? product.description : product.metaTagDescription,
140
+ canonical: new URL(`/${product.linkText}/p`, url.origin).href,
141
+ noIndexing: indexingSkus ? false : !!skuId,
142
+ },
143
+ };
155
144
  }
156
145
 
157
146
  // ---------------------------------------------------------------------------
@@ -159,74 +148,82 @@ export async function legacyProductDetailsPage(
159
148
  // ---------------------------------------------------------------------------
160
149
 
161
150
  export type LegacyProductListQuery =
162
- | { collection: string; count: number; sort?: LegacySort }
163
- | { term?: string; count: number; sort?: LegacySort }
164
- | { fq: string[]; count: number; sort?: LegacySort }
165
- | { skuIds: string[] }
166
- | { productIds: string[] };
151
+ | { collection: string; count: number; sort?: LegacySort }
152
+ | { term?: string; count: number; sort?: LegacySort }
153
+ | { fq: string[]; count: number; sort?: LegacySort }
154
+ | { skuIds: string[] }
155
+ | { productIds: string[] };
167
156
 
168
157
  export interface LegacyProductListOptions {
169
- query: LegacyProductListQuery;
170
- baseUrl: string;
171
- priceCurrency?: string;
158
+ query: LegacyProductListQuery;
159
+ baseUrl: string;
160
+ priceCurrency?: string;
172
161
  }
173
162
 
174
- function isCollectionQuery(q: LegacyProductListQuery): q is { collection: string; count: number; sort?: LegacySort } {
175
- return "collection" in q && typeof (q as any).collection === "string";
163
+ function isCollectionQuery(
164
+ q: LegacyProductListQuery,
165
+ ): q is { collection: string; count: number; sort?: LegacySort } {
166
+ return "collection" in q && typeof (q as any).collection === "string";
176
167
  }
177
168
  function isSkuIdsQuery(q: LegacyProductListQuery): q is { skuIds: string[] } {
178
- return "skuIds" in q && Array.isArray((q as any).skuIds);
169
+ return "skuIds" in q && Array.isArray((q as any).skuIds);
179
170
  }
180
171
  function isProductIdsQuery(q: LegacyProductListQuery): q is { productIds: string[] } {
181
- return "productIds" in q && Array.isArray((q as any).productIds);
172
+ return "productIds" in q && Array.isArray((q as any).productIds);
182
173
  }
183
- function isFqQuery(q: LegacyProductListQuery): q is { fq: string[]; count: number; sort?: LegacySort } {
184
- return "fq" in q && Array.isArray((q as any).fq);
174
+ function isFqQuery(
175
+ q: LegacyProductListQuery,
176
+ ): q is { fq: string[]; count: number; sort?: LegacySort } {
177
+ return "fq" in q && Array.isArray((q as any).fq);
185
178
  }
186
- function isTermQuery(q: LegacyProductListQuery): q is { term?: string; count: number; sort?: LegacySort } {
187
- return "term" in q || ("count" in q && !isCollectionQuery(q as any) && !isFqQuery(q as any));
179
+ function isTermQuery(
180
+ q: LegacyProductListQuery,
181
+ ): q is { term?: string; count: number; sort?: LegacySort } {
182
+ return "term" in q || ("count" in q && !isCollectionQuery(q as any) && !isFqQuery(q as any));
188
183
  }
189
184
 
190
- function queryToSearchParams(query: LegacyProductListQuery): Record<string, string | string[] | number | undefined> {
191
- if (isSkuIdsQuery(query)) {
192
- return {
193
- fq: query.skuIds.map((id) => `skuId:${id}`),
194
- _from: 0,
195
- _to: Math.max(query.skuIds.length - 1, 0),
196
- };
197
- }
198
-
199
- if (isProductIdsQuery(query)) {
200
- return {
201
- fq: query.productIds.map((id) => `productId:${id}`),
202
- _from: 0,
203
- _to: Math.max(query.productIds.length - 1, 0),
204
- };
205
- }
206
-
207
- const count = "count" in query ? (query.count ?? 12) : 12;
208
- const sort = "sort" in query ? query.sort : undefined;
209
- const base: Record<string, string | string[] | number | undefined> = {
210
- _from: 0,
211
- _to: Math.max(count - 1, 0),
212
- O: sort,
213
- };
214
-
215
- if (isCollectionQuery(query)) {
216
- base.fq = [`productClusterIds:${query.collection}`];
217
- return base;
218
- }
219
-
220
- if (isFqQuery(query)) {
221
- base.fq = query.fq;
222
- return base;
223
- }
224
-
225
- if (isTermQuery(query) && query.term) {
226
- base.ft = encodeURIComponent(query.term);
227
- }
228
-
229
- return base;
185
+ function queryToSearchParams(
186
+ query: LegacyProductListQuery,
187
+ ): Record<string, string | string[] | number | undefined> {
188
+ if (isSkuIdsQuery(query)) {
189
+ return {
190
+ fq: query.skuIds.map((id) => `skuId:${id}`),
191
+ _from: 0,
192
+ _to: Math.max(query.skuIds.length - 1, 0),
193
+ };
194
+ }
195
+
196
+ if (isProductIdsQuery(query)) {
197
+ return {
198
+ fq: query.productIds.map((id) => `productId:${id}`),
199
+ _from: 0,
200
+ _to: Math.max(query.productIds.length - 1, 0),
201
+ };
202
+ }
203
+
204
+ const count = "count" in query ? (query.count ?? 12) : 12;
205
+ const sort = "sort" in query ? query.sort : undefined;
206
+ const base: Record<string, string | string[] | number | undefined> = {
207
+ _from: 0,
208
+ _to: Math.max(count - 1, 0),
209
+ O: sort,
210
+ };
211
+
212
+ if (isCollectionQuery(query)) {
213
+ base.fq = [`productClusterIds:${query.collection}`];
214
+ return base;
215
+ }
216
+
217
+ if (isFqQuery(query)) {
218
+ base.fq = query.fq;
219
+ return base;
220
+ }
221
+
222
+ if (isTermQuery(query) && query.term) {
223
+ base.ft = encodeURIComponent(query.term);
224
+ }
225
+
226
+ return base;
230
227
  }
231
228
 
232
229
  /**
@@ -235,43 +232,39 @@ function queryToSearchParams(query: LegacyProductListQuery): Record<string, stri
235
232
  * @see https://developers.vtex.com/docs/api-reference/search-api#get-/api/catalog_system/pub/products/search
236
233
  * Ported from: vtex/loaders/legacy/productList.ts
237
234
  */
238
- export async function legacyProductList(
239
- opts: LegacyProductListOptions,
240
- ): Promise<Product[] | null> {
241
- const { query, baseUrl, priceCurrency = "BRL" } = opts;
242
- const searchArgs = queryToSearchParams(query);
243
- const qs = buildSearchParams(searchArgs);
244
-
245
- const vtexProducts = await vtexFetch<LegacyProduct[]>(
246
- `/api/catalog_system/pub/products/search/?${qs}`,
247
- );
248
-
249
- if (vtexProducts && !Array.isArray(vtexProducts)) {
250
- throw new Error(
251
- `Error while fetching VTEX data ${JSON.stringify(vtexProducts)}`,
252
- );
253
- }
254
-
255
- const preferredSKU = (items: LegacyItem[]): LegacyItem => {
256
- if (isSkuIdsQuery(query)) {
257
- const fetchedSkus = new Set(query.skuIds);
258
- return items.find((item) => fetchedSkus.has(item.itemId)) || items[0];
259
- }
260
- return items[0];
261
- };
262
-
263
- let products = vtexProducts.map((p) =>
264
- toProduct(p, preferredSKU(p.items), 0, { baseUrl, priceCurrency }),
265
- );
266
-
267
- if (isSkuIdsQuery(query)) {
268
- products = sortProducts(products, query.skuIds, "sku");
269
- }
270
- if (isProductIdsQuery(query)) {
271
- products = sortProducts(products, query.productIds, "inProductGroupWithID");
272
- }
273
-
274
- return products;
235
+ export async function legacyProductList(opts: LegacyProductListOptions): Promise<Product[] | null> {
236
+ const { query, baseUrl, priceCurrency = "BRL" } = opts;
237
+ const searchArgs = queryToSearchParams(query);
238
+ const qs = buildSearchParams(searchArgs);
239
+
240
+ const vtexProducts = await vtexFetch<LegacyProduct[]>(
241
+ `/api/catalog_system/pub/products/search/?${qs}`,
242
+ );
243
+
244
+ if (vtexProducts && !Array.isArray(vtexProducts)) {
245
+ throw new Error(`Error while fetching VTEX data ${JSON.stringify(vtexProducts)}`);
246
+ }
247
+
248
+ const preferredSKU = (items: LegacyItem[]): LegacyItem => {
249
+ if (isSkuIdsQuery(query)) {
250
+ const fetchedSkus = new Set(query.skuIds);
251
+ return items.find((item) => fetchedSkus.has(item.itemId)) || items[0];
252
+ }
253
+ return items[0];
254
+ };
255
+
256
+ let products = vtexProducts.map((p) =>
257
+ toProduct(p, preferredSKU(p.items), 0, { baseUrl, priceCurrency }),
258
+ );
259
+
260
+ if (isSkuIdsQuery(query)) {
261
+ products = sortProducts(products, query.skuIds, "sku");
262
+ }
263
+ if (isProductIdsQuery(query)) {
264
+ products = sortProducts(products, query.productIds, "inProductGroupWithID");
265
+ }
266
+
267
+ return products;
275
268
  }
276
269
 
277
270
  // ---------------------------------------------------------------------------
@@ -279,81 +272,76 @@ export async function legacyProductList(
279
272
  // ---------------------------------------------------------------------------
280
273
 
281
274
  export const LEGACY_SORT_OPTIONS = [
282
- { label: "price:desc", value: "OrderByPriceDESC" },
283
- { label: "price:asc", value: "OrderByPriceASC" },
284
- { label: "orders:desc", value: "OrderByTopSaleDESC" },
285
- { label: "name:desc", value: "OrderByNameDESC" },
286
- { label: "name:asc", value: "OrderByNameASC" },
287
- { label: "release:desc", value: "OrderByReleaseDateDESC" },
288
- { label: "discount:desc", value: "OrderByBestDiscountDESC" },
289
- { label: "relevance:desc", value: "OrderByScoreDESC" },
275
+ { label: "price:desc", value: "OrderByPriceDESC" },
276
+ { label: "price:asc", value: "OrderByPriceASC" },
277
+ { label: "orders:desc", value: "OrderByTopSaleDESC" },
278
+ { label: "name:desc", value: "OrderByNameDESC" },
279
+ { label: "name:asc", value: "OrderByNameASC" },
280
+ { label: "release:desc", value: "OrderByReleaseDateDESC" },
281
+ { label: "discount:desc", value: "OrderByBestDiscountDESC" },
282
+ { label: "relevance:desc", value: "OrderByScoreDESC" },
290
283
  ] as const;
291
284
 
292
285
  const IS_TO_LEGACY: Record<string, LegacySort> = {
293
- "price:desc": "OrderByPriceDESC",
294
- "price:asc": "OrderByPriceASC",
295
- "orders:desc": "OrderByTopSaleDESC",
296
- "name:desc": "OrderByNameDESC",
297
- "name:asc": "OrderByNameASC",
298
- "release:desc": "OrderByReleaseDateDESC",
299
- "discount:desc": "OrderByBestDiscountDESC",
300
- "relevance:desc": "OrderByScoreDESC",
286
+ "price:desc": "OrderByPriceDESC",
287
+ "price:asc": "OrderByPriceASC",
288
+ "orders:desc": "OrderByTopSaleDESC",
289
+ "name:desc": "OrderByNameDESC",
290
+ "name:asc": "OrderByNameASC",
291
+ "release:desc": "OrderByReleaseDateDESC",
292
+ "discount:desc": "OrderByBestDiscountDESC",
293
+ "relevance:desc": "OrderByScoreDESC",
301
294
  };
302
295
 
303
296
  const formatPriceFromPathToFacet = (term: string) =>
304
- term.replace(/de-\d+[,]?[\d]+-a-\d+[,]?[\d]+/, (match) =>
305
- match.replaceAll(",", "."),
306
- );
297
+ term.replace(/de-\d+[,]?[\d]+-a-\d+[,]?[\d]+/, (match) => match.replaceAll(",", "."));
307
298
 
308
- const removeForwardSlash = (str: string) =>
309
- str.slice(str.startsWith("/") ? 1 : 0);
299
+ const removeForwardSlash = (str: string) => str.slice(str.startsWith("/") ? 1 : 0);
310
300
 
311
301
  const getTerm = (path: string, map: string) => {
312
- const mapSegments = map.split(",");
313
- const pathSegments = removeForwardSlash(path).split("/");
314
- const term = pathSegments.slice(0, mapSegments.length).join("/");
315
- return mapSegments.includes("priceFrom")
316
- ? formatPriceFromPathToFacet(term)
317
- : term;
302
+ const mapSegments = map.split(",");
303
+ const pathSegments = removeForwardSlash(path).split("/");
304
+ const term = pathSegments.slice(0, mapSegments.length).join("/");
305
+ return mapSegments.includes("priceFrom") ? formatPriceFromPathToFacet(term) : term;
318
306
  };
319
307
 
320
308
  const getFirstItemAvailable = (item: LegacyItem) =>
321
- !!item?.sellers?.find((s) => s.commertialOffer?.AvailableQuantity > 0);
309
+ !!item?.sellers?.find((s) => s.commertialOffer?.AvailableQuantity > 0);
322
310
 
323
311
  const getTermFallback = (url: URL, isPage: boolean, hasFilters: boolean) => {
324
- const pathList = url.pathname.split("/").slice(1);
325
- if (!isPage && !hasFilters && pathList.length === 1) return pathList[0];
326
- return "";
312
+ const pathList = url.pathname.split("/").slice(1);
313
+ if (!isPage && !hasFilters && pathList.length === 1) return pathList[0];
314
+ return "";
327
315
  };
328
316
 
329
317
  export interface LegacyPLPOptions {
330
- /** URL of the page being rendered (used for filter links, pagination, etc.) */
331
- url: URL;
332
- /** Override the search term (path). Defaults to url.pathname */
333
- term?: string;
334
- /** Items per page */
335
- count?: number;
336
- /** Current page number (0-indexed internally; see pageOffset) */
337
- page?: number;
338
- /** Starting page offset. Defaults to 1. */
339
- pageOffset?: number;
340
- sort?: LegacySort;
341
- /** FullText search term */
342
- ft?: string;
343
- /** Filter query */
344
- fq?: string;
345
- /** Map parameter */
346
- map?: string;
347
- /** Filter behavior: dynamic (default) or static */
348
- filters?: "dynamic" | "static";
349
- /** Base URL for building canonical/absolute links */
350
- baseUrl: string;
351
- priceCurrency?: string;
352
- /** Use collection name as page title */
353
- useCollectionName?: boolean;
354
- /** Ignore case when checking if a facet is selected */
355
- ignoreCaseSelected?: boolean;
356
- includeOriginalAttributes?: string[];
318
+ /** URL of the page being rendered (used for filter links, pagination, etc.) */
319
+ url: URL;
320
+ /** Override the search term (path). Defaults to url.pathname */
321
+ term?: string;
322
+ /** Items per page */
323
+ count?: number;
324
+ /** Current page number (0-indexed internally; see pageOffset) */
325
+ page?: number;
326
+ /** Starting page offset. Defaults to 1. */
327
+ pageOffset?: number;
328
+ sort?: LegacySort;
329
+ /** FullText search term */
330
+ ft?: string;
331
+ /** Filter query */
332
+ fq?: string;
333
+ /** Map parameter */
334
+ map?: string;
335
+ /** Filter behavior: dynamic (default) or static */
336
+ filters?: "dynamic" | "static";
337
+ /** Base URL for building canonical/absolute links */
338
+ baseUrl: string;
339
+ priceCurrency?: string;
340
+ /** Use collection name as page title */
341
+ useCollectionName?: boolean;
342
+ /** Ignore case when checking if a facet is selected */
343
+ ignoreCaseSelected?: boolean;
344
+ includeOriginalAttributes?: string[];
357
345
  }
358
346
 
359
347
  /**
@@ -364,223 +352,195 @@ export interface LegacyPLPOptions {
364
352
  * Ported from: vtex/loaders/legacy/productListingPage.ts
365
353
  */
366
354
  export async function legacyProductListingPage(
367
- opts: LegacyPLPOptions,
355
+ opts: LegacyPLPOptions,
368
356
  ): Promise<ProductListingPage | null> {
369
- const {
370
- url,
371
- baseUrl,
372
- priceCurrency = "BRL",
373
- filters: filtersBehavior = "dynamic",
374
- ignoreCaseSelected,
375
- useCollectionName,
376
- includeOriginalAttributes,
377
- } = opts;
378
-
379
- const currentPageOffset = opts.pageOffset ?? 1;
380
- const countFromSearchParams = url.searchParams.get("PS");
381
- const count = Number(countFromSearchParams ?? opts.count ?? 12);
382
-
383
- const maybeMap = opts.map || url.searchParams.get("map") || undefined;
384
- const maybeTerm = opts.term || url.pathname || "";
385
-
386
- const pageParam = url.searchParams.get("page")
387
- ? Number(url.searchParams.get("page")) - currentPageOffset
388
- : 0;
389
- const page = opts.page ?? pageParam;
390
- const O: LegacySort =
391
- (url.searchParams.get("O") as LegacySort) ??
392
- IS_TO_LEGACY[url.searchParams.get("sort") ?? ""] ??
393
- opts.sort ??
394
- (LEGACY_SORT_OPTIONS[0].value as LegacySort);
395
- const fq = [
396
- ...new Set([
397
- ...(opts.fq ? [opts.fq] : []),
398
- ...url.searchParams.getAll("fq"),
399
- ]),
400
- ];
401
- const _from = page * count;
402
- const _to = (page + 1) * count - 1;
403
-
404
- const allPageTypes = await pageTypesFromPathname(maybeTerm);
405
- const pageTypes = getValidTypesFromPageTypes(allPageTypes);
406
- const pageType: PageType = pageTypes.at(-1) || pageTypes[0];
407
-
408
- const missingParams = typeof maybeMap !== "string" || !maybeTerm;
409
- const [map, term] =
410
- missingParams && fq.length > 0
411
- ? ["", ""]
412
- : missingParams
413
- ? getMapAndTerm(pageTypes)
414
- : [maybeMap, maybeTerm];
415
-
416
- const isPage = pageTypes.length > 0;
417
- const hasFilters = fq.length > 0 || !map;
418
- const ftFallback = getTermFallback(url, isPage, hasFilters);
419
- const ft =
420
- opts.ft ||
421
- url.searchParams.get("ft") ||
422
- url.searchParams.get("q") ||
423
- ftFallback;
424
- const isInSearchFormat = ft;
425
-
426
- if (!isPage && !hasFilters && !isInSearchFormat) return null;
427
-
428
- const fmap = url.searchParams.get("fmap") ?? map;
429
- const sc = salesChannelParam();
430
- const searchBase: Record<string, string | string[] | number | undefined> = {
431
- _from,
432
- _to,
433
- O,
434
- ft: ft || undefined,
435
- fq: fq.length > 0 ? fq : undefined,
436
- map,
437
- sc,
438
- };
439
-
440
- const [vtexProductsResponse, vtexFacets] = await Promise.all([
441
- vtexFetchResponse(
442
- `/api/catalog_system/pub/products/search/${getTerm(term, map)}?${buildSearchParams(searchBase)}`,
443
- ),
444
- vtexFetch<{
445
- CategoriesTrees: LegacyFacet[];
446
- Departments: LegacyFacet[];
447
- Brands: LegacyFacet[];
448
- SpecificationFilters: Record<string, LegacyFacet[]>;
449
- PriceRanges: LegacyFacet[];
450
- }>(
451
- `/api/catalog_system/pub/facets/search/${getTerm(term, fmap)}?${buildSearchParams({
452
- ...searchBase,
453
- map: fmap,
454
- })}`,
455
- ),
456
- ]);
457
-
458
- const vtexProducts = (await vtexProductsResponse.json()) as LegacyProduct[];
459
- const resources = vtexProductsResponse.headers.get("resources") ?? "";
460
- const [, _total] = resources.split("/");
461
-
462
- if (vtexProducts && !Array.isArray(vtexProducts)) {
463
- throw new Error(
464
- `Error while fetching VTEX data ${JSON.stringify(vtexProducts)}`,
465
- );
466
- }
467
-
468
- const products = vtexProducts.map((p) =>
469
- toProduct(
470
- p,
471
- p.items.find(getFirstItemAvailable) ?? p.items[0],
472
- 0,
473
- { baseUrl, priceCurrency, includeOriginalAttributes },
474
- ),
475
- );
476
-
477
- const currentPageTypes = !useCollectionName
478
- ? pageTypes
479
- : pageTypes.map((pt) => {
480
- if (pt.id !== pageTypes.at(-1)?.id) return pt;
481
- const name =
482
- products?.[0]?.additionalProperty?.find(
483
- (property) =>
484
- property.name === "cluster" &&
485
- property.propertyID === pt.name,
486
- )?.value ?? pt.name;
487
- return { ...pt, name };
488
- });
489
-
490
- const getFlatCategories = (
491
- trees: LegacyFacet[],
492
- ): Record<string, LegacyFacet[]> => {
493
- const flat: Record<string, LegacyFacet[]> = {};
494
- trees.forEach((cat) => (flat[cat.Name] = cat.Children || []));
495
- return flat;
496
- };
497
-
498
- const getCategoryFacets = (
499
- trees: LegacyFacet[],
500
- isDeptOrCat: boolean,
501
- ): LegacyFacet[] => {
502
- if (!isDeptOrCat) return [];
503
- for (const category of trees) {
504
- if (category.Id === Number(pageType?.id)) return category.Children || [];
505
- if (category.Children?.length) {
506
- const child = getCategoryFacets(category.Children, isDeptOrCat);
507
- if (child.length) return child;
508
- }
509
- }
510
- return [];
511
- };
512
-
513
- const isDeptOrCat =
514
- pageType?.pageType === "Department" ||
515
- pageType?.pageType === "Category" ||
516
- pageType?.pageType === "SubCategory";
517
-
518
- const flatCategories = !isDeptOrCat
519
- ? getFlatCategories(vtexFacets.CategoriesTrees)
520
- : {};
521
-
522
- const filters = Object.entries({
523
- Departments: vtexFacets.Departments,
524
- Categories: getCategoryFacets(vtexFacets.CategoriesTrees, isDeptOrCat),
525
- Brands: vtexFacets.Brands,
526
- ...vtexFacets.SpecificationFilters,
527
- PriceRanges: vtexFacets.PriceRanges,
528
- ...flatCategories,
529
- })
530
- .map(([name, facets]) =>
531
- legacyFacetToFilter(
532
- name,
533
- facets,
534
- url,
535
- map,
536
- term,
537
- filtersBehavior,
538
- ignoreCaseSelected,
539
- name === "Categories",
540
- ),
541
- )
542
- .flat()
543
- .filter((x): x is Filter => Boolean(x));
544
-
545
- const itemListElement = pageTypesToBreadcrumbList(pageTypes, baseUrl);
546
- const totalRecords = parseInt(_total, 10);
547
- const hasMoreResources = _to < totalRecords - 1;
548
- const hasNextPage = page < MAX_ALLOWED_PAGES && hasMoreResources;
549
- const hasPreviousPage = page > 0;
550
-
551
- const nextPage = new URLSearchParams(url.searchParams);
552
- const previousPage = new URLSearchParams(url.searchParams);
553
- if (hasNextPage) nextPage.set("page", String(page + currentPageOffset + 1));
554
- if (hasPreviousPage) previousPage.set("page", String(page + currentPageOffset - 1));
555
-
556
- const currentPage = page + currentPageOffset;
557
-
558
- return {
559
- "@type": "ProductListingPage",
560
- breadcrumb: {
561
- "@type": "BreadcrumbList",
562
- itemListElement,
563
- numberOfItems: itemListElement.length,
564
- },
565
- filters,
566
- products,
567
- pageInfo: {
568
- nextPage: hasNextPage ? `?${nextPage.toString()}` : undefined,
569
- previousPage: hasPreviousPage
570
- ? `?${previousPage.toString()}`
571
- : undefined,
572
- currentPage,
573
- records: totalRecords,
574
- recordPerPage: count,
575
- pageTypes: allPageTypes.map(parsePageType),
576
- },
577
- sortOptions: LEGACY_SORT_OPTIONS.map((o) => ({ ...o })),
578
- seo: pageTypesToSeo(
579
- currentPageTypes,
580
- baseUrl,
581
- hasPreviousPage ? currentPage : undefined,
582
- ),
583
- };
357
+ const {
358
+ url,
359
+ baseUrl,
360
+ priceCurrency = "BRL",
361
+ filters: filtersBehavior = "dynamic",
362
+ ignoreCaseSelected,
363
+ useCollectionName,
364
+ includeOriginalAttributes,
365
+ } = opts;
366
+
367
+ const currentPageOffset = opts.pageOffset ?? 1;
368
+ const countFromSearchParams = url.searchParams.get("PS");
369
+ const count = Number(countFromSearchParams ?? opts.count ?? 12);
370
+
371
+ const maybeMap = opts.map || url.searchParams.get("map") || undefined;
372
+ const maybeTerm = opts.term || url.pathname || "";
373
+
374
+ const pageParam = url.searchParams.get("page")
375
+ ? Number(url.searchParams.get("page")) - currentPageOffset
376
+ : 0;
377
+ const page = opts.page ?? pageParam;
378
+ const O: LegacySort =
379
+ (url.searchParams.get("O") as LegacySort) ??
380
+ IS_TO_LEGACY[url.searchParams.get("sort") ?? ""] ??
381
+ opts.sort ??
382
+ (LEGACY_SORT_OPTIONS[0].value as LegacySort);
383
+ const fq = [...new Set([...(opts.fq ? [opts.fq] : []), ...url.searchParams.getAll("fq")])];
384
+ const _from = page * count;
385
+ const _to = (page + 1) * count - 1;
386
+
387
+ const allPageTypes = await pageTypesFromPathname(maybeTerm);
388
+ const pageTypes = getValidTypesFromPageTypes(allPageTypes);
389
+ const pageType: PageType = pageTypes.at(-1) || pageTypes[0];
390
+
391
+ const missingParams = typeof maybeMap !== "string" || !maybeTerm;
392
+ const [map, term] =
393
+ missingParams && fq.length > 0
394
+ ? ["", ""]
395
+ : missingParams
396
+ ? getMapAndTerm(pageTypes)
397
+ : [maybeMap, maybeTerm];
398
+
399
+ const isPage = pageTypes.length > 0;
400
+ const hasFilters = fq.length > 0 || !map;
401
+ const ftFallback = getTermFallback(url, isPage, hasFilters);
402
+ const ft = opts.ft || url.searchParams.get("ft") || url.searchParams.get("q") || ftFallback;
403
+ const isInSearchFormat = ft;
404
+
405
+ if (!isPage && !hasFilters && !isInSearchFormat) return null;
406
+
407
+ const fmap = url.searchParams.get("fmap") ?? map;
408
+ const sc = salesChannelParam();
409
+ const searchBase: Record<string, string | string[] | number | undefined> = {
410
+ _from,
411
+ _to,
412
+ O,
413
+ ft: ft || undefined,
414
+ fq: fq.length > 0 ? fq : undefined,
415
+ map,
416
+ sc,
417
+ };
418
+
419
+ const [vtexProductsResponse, vtexFacets] = await Promise.all([
420
+ vtexFetchResponse(
421
+ `/api/catalog_system/pub/products/search/${getTerm(term, map)}?${buildSearchParams(searchBase)}`,
422
+ ),
423
+ vtexFetch<{
424
+ CategoriesTrees: LegacyFacet[];
425
+ Departments: LegacyFacet[];
426
+ Brands: LegacyFacet[];
427
+ SpecificationFilters: Record<string, LegacyFacet[]>;
428
+ PriceRanges: LegacyFacet[];
429
+ }>(
430
+ `/api/catalog_system/pub/facets/search/${getTerm(term, fmap)}?${buildSearchParams({
431
+ ...searchBase,
432
+ map: fmap,
433
+ })}`,
434
+ ),
435
+ ]);
436
+
437
+ const vtexProducts = (await vtexProductsResponse.json()) as LegacyProduct[];
438
+ const resources = vtexProductsResponse.headers.get("resources") ?? "";
439
+ const [, _total] = resources.split("/");
440
+
441
+ if (vtexProducts && !Array.isArray(vtexProducts)) {
442
+ throw new Error(`Error while fetching VTEX data ${JSON.stringify(vtexProducts)}`);
443
+ }
444
+
445
+ const products = vtexProducts.map((p) =>
446
+ toProduct(p, p.items.find(getFirstItemAvailable) ?? p.items[0], 0, {
447
+ baseUrl,
448
+ priceCurrency,
449
+ includeOriginalAttributes,
450
+ }),
451
+ );
452
+
453
+ const currentPageTypes = !useCollectionName
454
+ ? pageTypes
455
+ : pageTypes.map((pt) => {
456
+ if (pt.id !== pageTypes.at(-1)?.id) return pt;
457
+ const name =
458
+ products?.[0]?.additionalProperty?.find(
459
+ (property) => property.name === "cluster" && property.propertyID === pt.name,
460
+ )?.value ?? pt.name;
461
+ return { ...pt, name };
462
+ });
463
+
464
+ const getFlatCategories = (trees: LegacyFacet[]): Record<string, LegacyFacet[]> => {
465
+ const flat: Record<string, LegacyFacet[]> = {};
466
+ trees.forEach((cat) => (flat[cat.Name] = cat.Children || []));
467
+ return flat;
468
+ };
469
+
470
+ const getCategoryFacets = (trees: LegacyFacet[], isDeptOrCat: boolean): LegacyFacet[] => {
471
+ if (!isDeptOrCat) return [];
472
+ for (const category of trees) {
473
+ if (category.Id === Number(pageType?.id)) return category.Children || [];
474
+ if (category.Children?.length) {
475
+ const child = getCategoryFacets(category.Children, isDeptOrCat);
476
+ if (child.length) return child;
477
+ }
478
+ }
479
+ return [];
480
+ };
481
+
482
+ const isDeptOrCat =
483
+ pageType?.pageType === "Department" ||
484
+ pageType?.pageType === "Category" ||
485
+ pageType?.pageType === "SubCategory";
486
+
487
+ const flatCategories = !isDeptOrCat ? getFlatCategories(vtexFacets.CategoriesTrees) : {};
488
+
489
+ const filters = Object.entries({
490
+ Departments: vtexFacets.Departments,
491
+ Categories: getCategoryFacets(vtexFacets.CategoriesTrees, isDeptOrCat),
492
+ Brands: vtexFacets.Brands,
493
+ ...vtexFacets.SpecificationFilters,
494
+ PriceRanges: vtexFacets.PriceRanges,
495
+ ...flatCategories,
496
+ })
497
+ .flatMap(([name, facets]) =>
498
+ legacyFacetToFilter(
499
+ name,
500
+ facets,
501
+ url,
502
+ map,
503
+ term,
504
+ filtersBehavior,
505
+ ignoreCaseSelected,
506
+ name === "Categories",
507
+ ),
508
+ )
509
+ .filter((x): x is Filter => Boolean(x));
510
+
511
+ const itemListElement = pageTypesToBreadcrumbList(pageTypes, baseUrl);
512
+ const totalRecords = parseInt(_total, 10);
513
+ const hasMoreResources = _to < totalRecords - 1;
514
+ const hasNextPage = page < MAX_ALLOWED_PAGES && hasMoreResources;
515
+ const hasPreviousPage = page > 0;
516
+
517
+ const nextPage = new URLSearchParams(url.searchParams);
518
+ const previousPage = new URLSearchParams(url.searchParams);
519
+ if (hasNextPage) nextPage.set("page", String(page + currentPageOffset + 1));
520
+ if (hasPreviousPage) previousPage.set("page", String(page + currentPageOffset - 1));
521
+
522
+ const currentPage = page + currentPageOffset;
523
+
524
+ return {
525
+ "@type": "ProductListingPage",
526
+ breadcrumb: {
527
+ "@type": "BreadcrumbList",
528
+ itemListElement,
529
+ numberOfItems: itemListElement.length,
530
+ },
531
+ filters,
532
+ products,
533
+ pageInfo: {
534
+ nextPage: hasNextPage ? `?${nextPage.toString()}` : undefined,
535
+ previousPage: hasPreviousPage ? `?${previousPage.toString()}` : undefined,
536
+ currentPage,
537
+ records: totalRecords,
538
+ recordPerPage: count,
539
+ pageTypes: allPageTypes.map(parsePageType),
540
+ },
541
+ sortOptions: LEGACY_SORT_OPTIONS.map((o) => ({ ...o })),
542
+ seo: pageTypesToSeo(currentPageTypes, baseUrl, hasPreviousPage ? currentPage : undefined),
543
+ };
584
544
  }
585
545
 
586
546
  // ---------------------------------------------------------------------------
@@ -588,27 +548,27 @@ export async function legacyProductListingPage(
588
548
  // ---------------------------------------------------------------------------
589
549
 
590
550
  export interface LegacySuggestionsOptions {
591
- query?: string;
592
- /** Max results. Defaults to 4. */
593
- count?: number;
551
+ query?: string;
552
+ /** Max results. Defaults to 4. */
553
+ count?: number;
594
554
  }
595
555
 
596
556
  interface AutocompleteItem {
597
- productId: string;
598
- itemId: string;
599
- name: string;
600
- nameComplete: string;
601
- imageUrl: string;
557
+ productId: string;
558
+ itemId: string;
559
+ name: string;
560
+ nameComplete: string;
561
+ imageUrl: string;
602
562
  }
603
563
 
604
564
  interface AutocompleteResult {
605
- name: string;
606
- href: string;
607
- items: AutocompleteItem[];
565
+ name: string;
566
+ href: string;
567
+ items: AutocompleteItem[];
608
568
  }
609
569
 
610
570
  interface AutocompleteResponse {
611
- itemsReturned: AutocompleteResult[];
571
+ itemsReturned: AutocompleteResult[];
612
572
  }
613
573
 
614
574
  /**
@@ -618,54 +578,47 @@ interface AutocompleteResponse {
618
578
  * Ported from: vtex/loaders/legacy/suggestions.ts
619
579
  */
620
580
  export async function legacySuggestions(
621
- opts: LegacySuggestionsOptions = {},
581
+ opts: LegacySuggestionsOptions = {},
622
582
  ): Promise<Suggestion | null> {
623
- const { count = 4, query } = opts;
624
-
625
- const params = new URLSearchParams({
626
- maxRows: String(count),
627
- productNameContains: encodeURIComponent(query ?? ""),
628
- suggestionsStack: "",
629
- });
630
-
631
- const { salesChannel } = getVtexConfig();
632
- if (salesChannel) params.set("sc", salesChannel);
633
-
634
- const suggestions = await vtexFetch<AutocompleteResponse>(
635
- `/buscaautocomplete?${params}`,
636
- );
637
-
638
- const searches: Suggestion["searches"] = suggestions.itemsReturned
639
- .filter(({ items }) => !items?.length)
640
- .map(({ name, href }) => ({ term: name, href }));
641
-
642
- const products: Suggestion["products"] = suggestions.itemsReturned
643
- .filter(({ items }) => !!items.length)
644
- .map(
645
- ({
646
- items: [{ productId, itemId, imageUrl, name, nameComplete }],
647
- href,
648
- }): Product => {
649
- const parsedUrl = new URL(href, "https://placeholder.com");
650
- return {
651
- "@type": "Product",
652
- productID: itemId,
653
- sku: itemId,
654
- inProductGroupWithID: productId,
655
- isVariantOf: {
656
- "@type": "ProductGroup",
657
- name: nameComplete,
658
- url: parsedUrl.pathname,
659
- hasVariant: [],
660
- additionalProperty: [],
661
- productGroupID: productId,
662
- },
663
- image: [{ "@type": "ImageObject", url: imageUrl }],
664
- name,
665
- url: parsedUrl.pathname + parsedUrl.search + parsedUrl.hash,
666
- };
667
- },
668
- );
669
-
670
- return { searches, products };
583
+ const { count = 4, query } = opts;
584
+
585
+ const params = new URLSearchParams({
586
+ maxRows: String(count),
587
+ productNameContains: encodeURIComponent(query ?? ""),
588
+ suggestionsStack: "",
589
+ });
590
+
591
+ const { salesChannel } = getVtexConfig();
592
+ if (salesChannel) params.set("sc", salesChannel);
593
+
594
+ const suggestions = await vtexFetch<AutocompleteResponse>(`/buscaautocomplete?${params}`);
595
+
596
+ const searches: Suggestion["searches"] = suggestions.itemsReturned
597
+ .filter(({ items }) => !items?.length)
598
+ .map(({ name, href }) => ({ term: name, href }));
599
+
600
+ const products: Suggestion["products"] = suggestions.itemsReturned
601
+ .filter(({ items }) => !!items.length)
602
+ .map(({ items: [{ productId, itemId, imageUrl, name, nameComplete }], href }): Product => {
603
+ const parsedUrl = new URL(href, "https://placeholder.com");
604
+ return {
605
+ "@type": "Product",
606
+ productID: itemId,
607
+ sku: itemId,
608
+ inProductGroupWithID: productId,
609
+ isVariantOf: {
610
+ "@type": "ProductGroup",
611
+ name: nameComplete,
612
+ url: parsedUrl.pathname,
613
+ hasVariant: [],
614
+ additionalProperty: [],
615
+ productGroupID: productId,
616
+ },
617
+ image: [{ "@type": "ImageObject", url: imageUrl }],
618
+ name,
619
+ url: parsedUrl.pathname + parsedUrl.search + parsedUrl.hash,
620
+ };
621
+ });
622
+
623
+ return { searches, products };
671
624
  }