@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.
- package/LICENSE +21 -0
- package/commerce/components/Image.tsx +129 -143
- package/commerce/components/JsonLd.tsx +192 -201
- package/commerce/components/Picture.tsx +65 -75
- package/commerce/sdk/analytics.ts +15 -15
- package/commerce/sdk/formatPrice.ts +13 -16
- package/commerce/sdk/url.ts +7 -7
- package/commerce/sdk/useOffer.ts +46 -57
- package/commerce/sdk/useVariantPossibilities.ts +25 -25
- package/commerce/types/commerce.ts +868 -875
- package/commerce/utils/canonical.ts +5 -8
- package/commerce/utils/constants.ts +5 -6
- package/commerce/utils/filters.ts +4 -4
- package/commerce/utils/productToAnalyticsItem.ts +52 -56
- package/commerce/utils/stateByZip.ts +42 -42
- package/package.json +23 -4
- package/shopify/actions/cart/addItems.ts +24 -25
- package/shopify/actions/cart/updateCoupons.ts +19 -20
- package/shopify/actions/cart/updateItems.ts +19 -20
- package/shopify/actions/user/signIn.ts +25 -30
- package/shopify/actions/user/signUp.ts +19 -24
- package/shopify/client.ts +24 -24
- package/shopify/index.ts +20 -18
- package/shopify/init.ts +18 -21
- package/shopify/loaders/ProductDetailsPage.ts +16 -20
- package/shopify/loaders/ProductList.ts +66 -69
- package/shopify/loaders/ProductListingPage.ts +150 -158
- package/shopify/loaders/RelatedProducts.ts +24 -27
- package/shopify/loaders/cart.ts +53 -52
- package/shopify/loaders/shop.ts +22 -27
- package/shopify/loaders/user.ts +27 -32
- package/shopify/utils/admin/admin.ts +33 -34
- package/shopify/utils/admin/queries.ts +2 -2
- package/shopify/utils/cart.ts +18 -14
- package/shopify/utils/cookies.ts +62 -65
- package/shopify/utils/enums.ts +424 -424
- package/shopify/utils/graphql.ts +44 -55
- package/shopify/utils/storefront/queries.ts +24 -29
- package/shopify/utils/storefront/storefront.graphql.gen.ts +55 -55
- package/shopify/utils/transform.ts +370 -376
- package/shopify/utils/types.ts +118 -118
- package/shopify/utils/user.ts +11 -11
- package/shopify/utils/utils.ts +135 -140
- package/vtex/actions/address.ts +86 -86
- package/vtex/actions/auth.ts +14 -27
- package/vtex/actions/checkout.ts +36 -49
- package/vtex/actions/masterData.ts +10 -27
- package/vtex/actions/misc.ts +101 -111
- package/vtex/actions/newsletter.ts +48 -52
- package/vtex/actions/orders.ts +13 -16
- package/vtex/actions/profile.ts +55 -55
- package/vtex/actions/session.ts +36 -35
- package/vtex/actions/trigger.ts +25 -25
- package/vtex/actions/wishlist.ts +51 -53
- package/vtex/client.ts +14 -42
- package/vtex/hooks/index.ts +4 -4
- package/vtex/hooks/useAutocomplete.ts +42 -48
- package/vtex/hooks/useCart.ts +153 -165
- package/vtex/hooks/useUser.ts +40 -40
- package/vtex/hooks/useWishlist.ts +70 -70
- package/vtex/inline-loaders/productDetailsPage.ts +1 -3
- package/vtex/inline-loaders/productList.ts +121 -127
- package/vtex/inline-loaders/productListingPage.ts +10 -34
- package/vtex/inline-loaders/relatedProducts.ts +1 -3
- package/vtex/inline-loaders/suggestions.ts +36 -39
- package/vtex/inline-loaders/workflowProducts.ts +45 -49
- package/vtex/invoke.ts +159 -194
- package/vtex/loaders/address.ts +49 -54
- package/vtex/loaders/brands.ts +19 -26
- package/vtex/loaders/cart.ts +24 -21
- package/vtex/loaders/catalog.ts +51 -53
- package/vtex/loaders/collections.ts +25 -27
- package/vtex/loaders/legacy.ts +487 -534
- package/vtex/loaders/logistics.ts +33 -37
- package/vtex/loaders/navbar.ts +5 -8
- package/vtex/loaders/orders.ts +28 -39
- package/vtex/loaders/pageType.ts +41 -35
- package/vtex/loaders/payment.ts +27 -37
- package/vtex/loaders/profile.ts +38 -38
- package/vtex/loaders/promotion.ts +5 -8
- package/vtex/loaders/search.ts +56 -59
- package/vtex/loaders/session.ts +22 -30
- package/vtex/loaders/user.ts +39 -41
- package/vtex/loaders/wishlist.ts +35 -35
- package/vtex/loaders/wishlistProducts.ts +3 -15
- package/vtex/loaders/workflow.ts +220 -227
- package/vtex/middleware.ts +116 -119
- package/vtex/types.ts +201 -201
- package/vtex/utils/batch.ts +13 -16
- package/vtex/utils/cookies.ts +76 -80
- package/vtex/utils/enrichment.ts +62 -42
- package/vtex/utils/fetchCache.ts +1 -4
- package/vtex/utils/index.ts +6 -6
- package/vtex/utils/intelligentSearch.ts +48 -57
- package/vtex/utils/legacy.ts +108 -124
- package/vtex/utils/pickAndOmit.ts +15 -20
- package/vtex/utils/proxy.ts +136 -146
- package/vtex/utils/resourceRange.ts +3 -3
- package/vtex/utils/segment.ts +100 -111
- package/vtex/utils/similars.ts +1 -2
- package/vtex/utils/sitemap.ts +91 -91
- package/vtex/utils/slugCache.ts +2 -6
- package/vtex/utils/slugify.ts +9 -9
- package/vtex/utils/transform.ts +1012 -1105
- package/vtex/utils/types.ts +1381 -1381
- package/vtex/utils/vtexId.ts +44 -47
- package/.github/workflows/release.yml +0 -34
- package/.releaserc.json +0 -28
- package/knip.json +0 -19
- package/tsconfig.json +0 -11
package/vtex/loaders/legacy.ts
CHANGED
|
@@ -11,36 +11,29 @@
|
|
|
11
11
|
* @see https://developers.vtex.com/docs/api-reference/search-api
|
|
12
12
|
*/
|
|
13
13
|
import type {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
pageTypesToSeo,
|
|
22
|
+
getMapAndTerm,
|
|
23
|
+
getValidTypesFromPageTypes,
|
|
24
|
+
pageTypesFromPathname,
|
|
25
|
+
pageTypesToBreadcrumbList,
|
|
26
|
+
pageTypesToSeo,
|
|
28
27
|
} from "../utils/legacy";
|
|
29
28
|
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
45
|
+
return getVtexConfig().salesChannel ?? "1";
|
|
53
46
|
}
|
|
54
47
|
|
|
55
48
|
function buildSearchParams(
|
|
56
|
-
|
|
49
|
+
extra: Record<string, string | string[] | number | undefined>,
|
|
57
50
|
): URLSearchParams {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
88
|
+
opts: LegacyPDPOptions,
|
|
96
89
|
): Promise<ProductDetailsPage | null> {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
158
|
+
query: LegacyProductListQuery;
|
|
159
|
+
baseUrl: string;
|
|
160
|
+
priceCurrency?: string;
|
|
172
161
|
}
|
|
173
162
|
|
|
174
|
-
function isCollectionQuery(
|
|
175
|
-
|
|
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
|
-
|
|
169
|
+
return "skuIds" in q && Array.isArray((q as any).skuIds);
|
|
179
170
|
}
|
|
180
171
|
function isProductIdsQuery(q: LegacyProductListQuery): q is { productIds: string[] } {
|
|
181
|
-
|
|
172
|
+
return "productIds" in q && Array.isArray((q as any).productIds);
|
|
182
173
|
}
|
|
183
|
-
function isFqQuery(
|
|
184
|
-
|
|
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(
|
|
187
|
-
|
|
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(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
309
|
+
!!item?.sellers?.find((s) => s.commertialOffer?.AvailableQuantity > 0);
|
|
322
310
|
|
|
323
311
|
const getTermFallback = (url: URL, isPage: boolean, hasFilters: boolean) => {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
355
|
+
opts: LegacyPLPOptions,
|
|
368
356
|
): Promise<ProductListingPage | null> {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
551
|
+
query?: string;
|
|
552
|
+
/** Max results. Defaults to 4. */
|
|
553
|
+
count?: number;
|
|
594
554
|
}
|
|
595
555
|
|
|
596
556
|
interface AutocompleteItem {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
557
|
+
productId: string;
|
|
558
|
+
itemId: string;
|
|
559
|
+
name: string;
|
|
560
|
+
nameComplete: string;
|
|
561
|
+
imageUrl: string;
|
|
602
562
|
}
|
|
603
563
|
|
|
604
564
|
interface AutocompleteResult {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
565
|
+
name: string;
|
|
566
|
+
href: string;
|
|
567
|
+
items: AutocompleteItem[];
|
|
608
568
|
}
|
|
609
569
|
|
|
610
570
|
interface AutocompleteResponse {
|
|
611
|
-
|
|
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
|
-
|
|
581
|
+
opts: LegacySuggestionsOptions = {},
|
|
622
582
|
): Promise<Suggestion | null> {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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
|
}
|