@decocms/apps-vtex 8.0.0 → 8.1.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/package.json +7 -52
  2. package/src/index.ts +12 -15
  3. package/src/utils/constants.ts +2 -56
  4. package/src/utils/cookieSanitizer.ts +59 -59
  5. package/src/utils/operationRouter.ts +76 -72
  6. package/src/utils/types.ts +709 -1557
  7. package/src/vtexClient.ts +444 -0
  8. package/src/README.md +0 -6
  9. package/src/__tests__/client-segment-cookie.test.ts +0 -255
  10. package/src/__tests__/client-set-cookie-forward.test.ts +0 -257
  11. package/src/__tests__/schemas.test.ts +0 -71
  12. package/src/actions/address.ts +0 -250
  13. package/src/actions/analytics/sendEvent.ts +0 -86
  14. package/src/actions/auth.ts +0 -333
  15. package/src/actions/checkout.ts +0 -659
  16. package/src/actions/index.ts +0 -11
  17. package/src/actions/masterData.ts +0 -168
  18. package/src/actions/misc.ts +0 -188
  19. package/src/actions/newsletter.ts +0 -105
  20. package/src/actions/orders.ts +0 -34
  21. package/src/actions/profile.ts +0 -201
  22. package/src/actions/session.ts +0 -85
  23. package/src/actions/trigger.ts +0 -43
  24. package/src/actions/wishlist.ts +0 -114
  25. package/src/client.ts +0 -667
  26. package/src/commerceLoaders.ts +0 -267
  27. package/src/hooks/__tests__/createCart.render.test.ts +0 -114
  28. package/src/hooks/__tests__/createCart.test.ts +0 -92
  29. package/src/hooks/__tests__/createUseCart.test.ts +0 -184
  30. package/src/hooks/__tests__/createUseUser.test.ts +0 -48
  31. package/src/hooks/__tests__/createUseWishlist.test.ts +0 -87
  32. package/src/hooks/cartQuery.ts +0 -204
  33. package/src/hooks/createCart.ts +0 -427
  34. package/src/hooks/createUseCart.ts +0 -360
  35. package/src/hooks/createUseUser.ts +0 -153
  36. package/src/hooks/createUseWishlist.ts +0 -242
  37. package/src/hooks/index.ts +0 -25
  38. package/src/hooks/useAutocomplete.ts +0 -83
  39. package/src/hooks/useCart.ts +0 -243
  40. package/src/hooks/useUser.ts +0 -78
  41. package/src/hooks/useWishlist.ts +0 -119
  42. package/src/invoke.ts +0 -235
  43. package/src/loaders/ProductDetailsPage.ts +0 -1
  44. package/src/loaders/ProductList.ts +0 -1
  45. package/src/loaders/ProductListingPage.ts +0 -1
  46. package/src/loaders/address.ts +0 -115
  47. package/src/loaders/autocomplete.ts +0 -58
  48. package/src/loaders/brands.ts +0 -44
  49. package/src/loaders/cart/attachments.ts +0 -53
  50. package/src/loaders/cart/full.ts +0 -67
  51. package/src/loaders/cart/gifts.ts +0 -43
  52. package/src/loaders/cart/orderFormId.ts +0 -31
  53. package/src/loaders/cart/shipping.test.ts +0 -125
  54. package/src/loaders/cart/shipping.ts +0 -136
  55. package/src/loaders/cart/summary.ts +0 -36
  56. package/src/loaders/cart.ts +0 -52
  57. package/src/loaders/catalog.ts +0 -163
  58. package/src/loaders/collections.ts +0 -55
  59. package/src/loaders/index.ts +0 -20
  60. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +0 -51
  61. package/src/loaders/intelligentSearch/productDetailsPage.ts +0 -113
  62. package/src/loaders/intelligentSearch/productList.ts +0 -154
  63. package/src/loaders/intelligentSearch/productListingPage.ts +0 -526
  64. package/src/loaders/intelligentSearch/suggestions.ts +0 -46
  65. package/src/loaders/legacy/productDetailsPage.ts +0 -1
  66. package/src/loaders/legacy/productList.ts +0 -1
  67. package/src/loaders/legacy/relatedProductsLoader.ts +0 -81
  68. package/src/loaders/legacy.ts +0 -635
  69. package/src/loaders/logistics.ts +0 -111
  70. package/src/loaders/minicart.ts +0 -78
  71. package/src/loaders/navbar.ts +0 -26
  72. package/src/loaders/orders.ts +0 -92
  73. package/src/loaders/pageType.ts +0 -68
  74. package/src/loaders/paths/PDPDefaultPath.ts +0 -51
  75. package/src/loaders/payment.ts +0 -97
  76. package/src/loaders/productListFull.ts +0 -159
  77. package/src/loaders/profile.ts +0 -138
  78. package/src/loaders/promotion.ts +0 -30
  79. package/src/loaders/search.ts +0 -124
  80. package/src/loaders/session.ts +0 -83
  81. package/src/loaders/user.ts +0 -87
  82. package/src/loaders/wishlist.ts +0 -89
  83. package/src/loaders/wishlistProducts.ts +0 -69
  84. package/src/loaders/workflow/products.ts +0 -64
  85. package/src/loaders/workflow.ts +0 -316
  86. package/src/logo.png +0 -0
  87. package/src/manifest.gen.ts +0 -89
  88. package/src/middleware.ts +0 -240
  89. package/src/mod.ts +0 -157
  90. package/src/registry.ts +0 -9
  91. package/src/schemas.gen.ts +0 -3191
  92. package/src/schemas.ts +0 -30
  93. package/src/types.ts +0 -248
  94. package/src/utils/__tests__/cartProjection.test.ts +0 -118
  95. package/src/utils/__tests__/cookieSanitizer.test.ts +0 -162
  96. package/src/utils/__tests__/fetch.test.ts +0 -80
  97. package/src/utils/__tests__/fetchCache.test.ts +0 -155
  98. package/src/utils/__tests__/instrumentedFetch.test.ts +0 -158
  99. package/src/utils/__tests__/intelligentSearch.test.ts +0 -88
  100. package/src/utils/__tests__/minicart.test.ts +0 -184
  101. package/src/utils/__tests__/operationRouter.test.ts +0 -227
  102. package/src/utils/__tests__/resilience.test.ts +0 -205
  103. package/src/utils/__tests__/resourceRange.test.ts +0 -32
  104. package/src/utils/__tests__/simulationCache.test.ts +0 -40
  105. package/src/utils/__tests__/sitemap.test.ts +0 -185
  106. package/src/utils/__tests__/slugify.test.ts +0 -31
  107. package/src/utils/__tests__/transform.test.ts +0 -769
  108. package/src/utils/accountLoaders.ts +0 -203
  109. package/src/utils/authHelpers.ts +0 -85
  110. package/src/utils/batch.ts +0 -18
  111. package/src/utils/cartProjection.ts +0 -96
  112. package/src/utils/cookies.ts +0 -167
  113. package/src/utils/enrichment.ts +0 -560
  114. package/src/utils/fetch.ts +0 -107
  115. package/src/utils/fetchCache.ts +0 -199
  116. package/src/utils/index.ts +0 -19
  117. package/src/utils/instrumentedFetch.ts +0 -97
  118. package/src/utils/intelligentSearch.ts +0 -96
  119. package/src/utils/legacy.ts +0 -139
  120. package/src/utils/minicart.ts +0 -139
  121. package/src/utils/pickAndOmit.ts +0 -25
  122. package/src/utils/proxy.ts +0 -435
  123. package/src/utils/resilience.ts +0 -352
  124. package/src/utils/resourceRange.ts +0 -10
  125. package/src/utils/segment.ts +0 -152
  126. package/src/utils/similars.ts +0 -37
  127. package/src/utils/simulationCache.ts +0 -73
  128. package/src/utils/sitemap.ts +0 -282
  129. package/src/utils/slugCache.ts +0 -28
  130. package/src/utils/slugify.ts +0 -13
  131. package/src/utils/transform.ts +0 -1522
  132. package/src/utils/vtexId.ts +0 -138
  133. package/tsconfig.json +0 -7
@@ -1,635 +0,0 @@
1
- /**
2
- * Legacy VTEX Catalog Search loaders.
3
- * Pure async functions — require configureVtex() to have been called.
4
- *
5
- * Ported from deco-cx/apps:
6
- * vtex/loaders/legacy/productDetailsPage.ts
7
- * vtex/loaders/legacy/productList.ts
8
- * vtex/loaders/legacy/productListingPage.ts
9
- * vtex/loaders/legacy/suggestions.ts
10
- *
11
- * @see https://developers.vtex.com/docs/api-reference/search-api
12
- */
13
- import type {
14
- Filter,
15
- Product,
16
- ProductDetailsPage,
17
- ProductListingPage,
18
- Suggestion,
19
- } from "@decocms/apps-commerce/types";
20
- import { getVtexConfig, vtexFetch, vtexFetchResponse } from "../client";
21
- import {
22
- getMapAndTerm,
23
- getValidTypesFromPageTypes,
24
- pageTypesFromPathname,
25
- pageTypesToBreadcrumbList,
26
- pageTypesToSeo,
27
- } from "../utils/legacy";
28
- import {
29
- legacyFacetToFilter,
30
- parsePageType,
31
- pickSku,
32
- sortProducts,
33
- toProduct,
34
- toProductPage,
35
- } from "../utils/transform";
36
- import type { LegacyFacet, LegacyItem, LegacyProduct, LegacySort, PageType } from "../utils/types";
37
-
38
- // ---------------------------------------------------------------------------
39
- // Shared helpers
40
- // ---------------------------------------------------------------------------
41
-
42
- const MAX_ALLOWED_PAGES = 500;
43
-
44
- function salesChannelParam(): string {
45
- return getVtexConfig().salesChannel ?? "1";
46
- }
47
-
48
- function buildSearchParams(
49
- extra: Record<string, string | string[] | number | undefined>,
50
- ): URLSearchParams {
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;
64
- }
65
-
66
- // ---------------------------------------------------------------------------
67
- // legacyProductDetailsPage
68
- // ---------------------------------------------------------------------------
69
-
70
- export interface LegacyPDPOptions {
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;
79
- }
80
-
81
- /**
82
- * Fetch a product details page using the legacy Catalog search API.
83
- *
84
- * @see https://developers.vtex.com/docs/api-reference/search-api#get-/api/catalog_system/pub/products/search/-slug-/p
85
- * Ported from: vtex/loaders/legacy/productDetailsPage.ts
86
- */
87
- export async function legacyProductDetailsPage(
88
- opts: LegacyPDPOptions,
89
- ): Promise<ProductDetailsPage | null> {
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
- };
144
- }
145
-
146
- // ---------------------------------------------------------------------------
147
- // legacyProductList
148
- // ---------------------------------------------------------------------------
149
-
150
- export type LegacyProductListQuery =
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[] };
156
-
157
- export interface LegacyProductListOptions {
158
- query: LegacyProductListQuery;
159
- baseUrl: string;
160
- priceCurrency?: string;
161
- }
162
-
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";
167
- }
168
- function isSkuIdsQuery(q: LegacyProductListQuery): q is { skuIds: string[] } {
169
- return "skuIds" in q && Array.isArray((q as any).skuIds);
170
- }
171
- function isProductIdsQuery(q: LegacyProductListQuery): q is { productIds: string[] } {
172
- return "productIds" in q && Array.isArray((q as any).productIds);
173
- }
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);
178
- }
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));
183
- }
184
-
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;
227
- }
228
-
229
- /**
230
- * Fetch a product list using the legacy Catalog search API.
231
- *
232
- * @see https://developers.vtex.com/docs/api-reference/search-api#get-/api/catalog_system/pub/products/search
233
- * Ported from: vtex/loaders/legacy/productList.ts
234
- */
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;
268
- }
269
-
270
- // ---------------------------------------------------------------------------
271
- // legacyProductListingPage
272
- // ---------------------------------------------------------------------------
273
-
274
- export const LEGACY_SORT_OPTIONS = [
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" },
283
- ] as const;
284
-
285
- const IS_TO_LEGACY: Record<string, LegacySort> = {
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",
294
- };
295
-
296
- const formatPriceFromPathToFacet = (term: string) =>
297
- term.replace(/de-\d+[,]?[\d]+-a-\d+[,]?[\d]+/, (match) => match.replaceAll(",", "."));
298
-
299
- const removeForwardSlash = (str: string) => str.slice(str.startsWith("/") ? 1 : 0);
300
-
301
- const getTerm = (path: string, map: string) => {
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;
306
- };
307
-
308
- export const getFirstItemAvailable = (item: LegacyItem) =>
309
- !!item?.sellers?.find((s) => s.commertialOffer?.AvailableQuantity > 0);
310
-
311
- const getTermFallback = (url: URL, isPage: boolean, hasFilters: boolean) => {
312
- const pathList = url.pathname.split("/").slice(1);
313
- if (!isPage && !hasFilters && pathList.length === 1) return pathList[0];
314
- return "";
315
- };
316
-
317
- export interface LegacyPLPOptions {
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[];
345
- }
346
-
347
- /**
348
- * Fetch a product listing page (PLP) using the legacy Catalog search API.
349
- * Handles categories, departments, brands, collections, and full-text search.
350
- *
351
- * @see https://developers.vtex.com/docs/api-reference/search-api#get-/api/catalog_system/pub/products/search
352
- * Ported from: vtex/loaders/legacy/productListingPage.ts
353
- */
354
- export async function legacyProductListingPage(
355
- opts: LegacyPLPOptions,
356
- ): Promise<ProductListingPage | null> {
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 ? `${url.pathname}?${nextPage.toString()}` : undefined,
535
- previousPage: hasPreviousPage ? `${url.pathname}?${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
- };
544
- }
545
-
546
- // ---------------------------------------------------------------------------
547
- // legacySuggestions
548
- // ---------------------------------------------------------------------------
549
-
550
- export interface LegacySuggestionsOptions {
551
- query?: string;
552
- /** Max results. Defaults to 4. */
553
- count?: number;
554
- }
555
-
556
- interface AutocompleteItem {
557
- productId: string;
558
- itemId: string;
559
- name: string;
560
- nameComplete: string;
561
- imageUrl: string;
562
- }
563
-
564
- interface AutocompleteResult {
565
- name: string;
566
- href: string;
567
- items: AutocompleteItem[];
568
- }
569
-
570
- interface AutocompleteResponse {
571
- itemsReturned: AutocompleteResult[];
572
- }
573
-
574
- /**
575
- * Fetch legacy autocomplete/search suggestions.
576
- *
577
- * @see https://developers.vtex.com/docs/api-reference/search-api#get-/buscaautocomplete
578
- * Ported from: vtex/loaders/legacy/suggestions.ts
579
- */
580
- export async function legacySuggestions(
581
- opts: LegacySuggestionsOptions = {},
582
- ): Promise<Suggestion | null> {
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 };
624
- }
625
-
626
- // ---------------------------------------------------------------------------
627
- // Short aliases — sites use invoke.vtex.loaders.legacy.productDetailsPage()
628
- // ---------------------------------------------------------------------------
629
-
630
- export {
631
- legacyProductDetailsPage as productDetailsPage,
632
- legacyProductList as productList,
633
- legacyProductListingPage as productListingPage,
634
- legacySuggestions as suggestions,
635
- };