@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/utils/enrichment.ts
CHANGED
|
@@ -32,6 +32,28 @@ import { pickSku, toInventories, toProduct, toReview } from "./transform";
|
|
|
32
32
|
import type { LegacyProduct } from "./types";
|
|
33
33
|
import { buildAuthCookieHeader, VTEX_AUTH_COOKIE } from "./vtexId";
|
|
34
34
|
|
|
35
|
+
// -------------------------------------------------------------------------
|
|
36
|
+
// Constants
|
|
37
|
+
// -------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/** VTEX prices come in cents — divide by this to get the currency value. */
|
|
40
|
+
const CENTS_DIVISOR = 100;
|
|
41
|
+
|
|
42
|
+
/** Default number of products per simulation API call. */
|
|
43
|
+
const DEFAULT_SIMULATION_BATCH_SIZE = 50;
|
|
44
|
+
|
|
45
|
+
/** Maximum wishlist items to fetch in a single query. */
|
|
46
|
+
const WISHLIST_MAX_ITEMS = 500;
|
|
47
|
+
|
|
48
|
+
/** Batch size for kit-item product lookups. */
|
|
49
|
+
const KIT_ITEMS_BATCH_SIZE = 10;
|
|
50
|
+
|
|
51
|
+
/** Batch size for variant product lookups. */
|
|
52
|
+
const VARIANTS_BATCH_SIZE = 15;
|
|
53
|
+
|
|
54
|
+
/** Number of reviews to fetch per product. */
|
|
55
|
+
const REVIEWS_PAGE_SIZE = 10;
|
|
56
|
+
|
|
35
57
|
// -------------------------------------------------------------------------
|
|
36
58
|
// Types
|
|
37
59
|
// -------------------------------------------------------------------------
|
|
@@ -47,10 +69,7 @@ export interface EnrichmentContext {
|
|
|
47
69
|
* A product enricher takes a list of products and returns an enriched list.
|
|
48
70
|
* Enrichers are composed via `createProductPipeline`.
|
|
49
71
|
*/
|
|
50
|
-
export type ProductEnricher = (
|
|
51
|
-
products: Product[],
|
|
52
|
-
ctx: EnrichmentContext,
|
|
53
|
-
) => Promise<Product[]>;
|
|
72
|
+
export type ProductEnricher = (products: Product[], ctx: EnrichmentContext) => Promise<Product[]>;
|
|
54
73
|
|
|
55
74
|
// -------------------------------------------------------------------------
|
|
56
75
|
// Pipeline
|
|
@@ -63,9 +82,7 @@ export type ProductEnricher = (
|
|
|
63
82
|
* This is intentional: some enrichers depend on previous enrichments
|
|
64
83
|
* (e.g., wishlist may need SKU IDs added by simulation).
|
|
65
84
|
*/
|
|
66
|
-
export function createProductPipeline(
|
|
67
|
-
...enrichers: ProductEnricher[]
|
|
68
|
-
): ProductEnricher {
|
|
85
|
+
export function createProductPipeline(...enrichers: ProductEnricher[]): ProductEnricher {
|
|
69
86
|
return async (products, ctx) => {
|
|
70
87
|
if (!products.length) return products;
|
|
71
88
|
|
|
@@ -115,20 +132,15 @@ interface SimulationResult {
|
|
|
115
132
|
*
|
|
116
133
|
* @param options.batchSize - Max products per simulation call. @default 50
|
|
117
134
|
*/
|
|
118
|
-
export function withSimulation(options?: {
|
|
119
|
-
batchSize
|
|
120
|
-
}): ProductEnricher {
|
|
121
|
-
const batchSize = options?.batchSize ?? 50;
|
|
135
|
+
export function withSimulation(options?: { batchSize?: number }): ProductEnricher {
|
|
136
|
+
const batchSize = options?.batchSize ?? DEFAULT_SIMULATION_BATCH_SIZE;
|
|
122
137
|
|
|
123
138
|
return async (products, ctx) => {
|
|
124
139
|
const config = getVtexConfig();
|
|
125
140
|
const sc = ctx.salesChannel ?? config.salesChannel ?? "1";
|
|
126
141
|
|
|
127
142
|
const skuItems: SimulationItem[] = [];
|
|
128
|
-
const skuToProductIndex = new Map<
|
|
129
|
-
string,
|
|
130
|
-
{ productIdx: number; offerIdx: number }
|
|
131
|
-
>();
|
|
143
|
+
const skuToProductIndex = new Map<string, { productIdx: number; offerIdx: number }>();
|
|
132
144
|
|
|
133
145
|
for (let pi = 0; pi < products.length; pi++) {
|
|
134
146
|
const product = products[pi];
|
|
@@ -191,7 +203,7 @@ export function withSimulation(options?: {
|
|
|
191
203
|
const offers = [...aggOffer.offers];
|
|
192
204
|
const offer = { ...offers[mapping.offerIdx] };
|
|
193
205
|
|
|
194
|
-
offer.price = simItem.sellingPrice /
|
|
206
|
+
offer.price = simItem.sellingPrice / CENTS_DIVISOR;
|
|
195
207
|
if (simItem.listPrice) {
|
|
196
208
|
(offer as any).priceSpecification = [
|
|
197
209
|
...(Array.isArray((offer as any).priceSpecification)
|
|
@@ -199,10 +211,10 @@ export function withSimulation(options?: {
|
|
|
199
211
|
: []),
|
|
200
212
|
].map((spec: any) => {
|
|
201
213
|
if (spec?.priceType === "https://schema.org/ListPrice") {
|
|
202
|
-
return { ...spec, price: simItem.listPrice /
|
|
214
|
+
return { ...spec, price: simItem.listPrice / CENTS_DIVISOR };
|
|
203
215
|
}
|
|
204
216
|
if (spec?.priceType === "https://schema.org/SalePrice") {
|
|
205
|
-
return { ...spec, price: simItem.sellingPrice /
|
|
217
|
+
return { ...spec, price: simItem.sellingPrice / CENTS_DIVISOR };
|
|
206
218
|
}
|
|
207
219
|
return spec;
|
|
208
220
|
});
|
|
@@ -217,10 +229,7 @@ export function withSimulation(options?: {
|
|
|
217
229
|
result[mapping.productIdx] = product;
|
|
218
230
|
}
|
|
219
231
|
} catch (error) {
|
|
220
|
-
console.error(
|
|
221
|
-
"[Simulation] Batch failed:",
|
|
222
|
-
error instanceof Error ? error.message : error,
|
|
223
|
-
);
|
|
232
|
+
console.error("[Simulation] Batch failed:", error instanceof Error ? error.message : error);
|
|
224
233
|
}
|
|
225
234
|
}
|
|
226
235
|
|
|
@@ -275,9 +284,7 @@ export function withWishlist(): ProductEnricher {
|
|
|
275
284
|
try {
|
|
276
285
|
const parts = authCookie.split(".");
|
|
277
286
|
if (parts.length === 3) {
|
|
278
|
-
const payload = JSON.parse(
|
|
279
|
-
atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")),
|
|
280
|
-
);
|
|
287
|
+
const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
|
|
281
288
|
email = payload.sub ?? payload.userId;
|
|
282
289
|
}
|
|
283
290
|
} catch {
|
|
@@ -290,7 +297,7 @@ export function withWishlist(): ProductEnricher {
|
|
|
290
297
|
const data = await vtexIOGraphQL<WishlistData>(
|
|
291
298
|
{
|
|
292
299
|
query: WISHLIST_QUERY,
|
|
293
|
-
variables: { shopperId: email, name: "Wishlist", from: 0, to:
|
|
300
|
+
variables: { shopperId: email, name: "Wishlist", from: 0, to: WISHLIST_MAX_ITEMS },
|
|
294
301
|
},
|
|
295
302
|
{ Cookie: buildAuthCookieHeader(authCookie, getVtexConfig().account) },
|
|
296
303
|
);
|
|
@@ -369,7 +376,7 @@ export function withKitItems(): ProductEnricher {
|
|
|
369
376
|
? `https://${config.publicUrl}`
|
|
370
377
|
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
371
378
|
|
|
372
|
-
const batches = batch([...productIDs],
|
|
379
|
+
const batches = batch([...productIDs], KIT_ITEMS_BATCH_SIZE);
|
|
373
380
|
const productsById = new Map<string, ProductLeaf>();
|
|
374
381
|
|
|
375
382
|
for (const ids of batches) {
|
|
@@ -389,10 +396,7 @@ export function withKitItems(): ProductEnricher {
|
|
|
389
396
|
}
|
|
390
397
|
}
|
|
391
398
|
} catch (e) {
|
|
392
|
-
console.error(
|
|
393
|
-
"[KitItems] Batch failed:",
|
|
394
|
-
e instanceof Error ? e.message : e,
|
|
395
|
-
);
|
|
399
|
+
console.error("[KitItems] Batch failed:", e instanceof Error ? e.message : e);
|
|
396
400
|
}
|
|
397
401
|
}
|
|
398
402
|
|
|
@@ -428,7 +432,7 @@ export function withVariants(): ProductEnricher {
|
|
|
428
432
|
? `https://${config.publicUrl}`
|
|
429
433
|
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
430
434
|
|
|
431
|
-
const batches = batch([...productIDs],
|
|
435
|
+
const batches = batch([...productIDs], VARIANTS_BATCH_SIZE);
|
|
432
436
|
const productsById = new Map<string, Product>();
|
|
433
437
|
|
|
434
438
|
for (const ids of batches) {
|
|
@@ -446,10 +450,7 @@ export function withVariants(): ProductEnricher {
|
|
|
446
450
|
productsById.set(product.productID, product);
|
|
447
451
|
}
|
|
448
452
|
} catch (e) {
|
|
449
|
-
console.error(
|
|
450
|
-
"[Variants] Batch failed:",
|
|
451
|
-
e instanceof Error ? e.message : e,
|
|
452
|
-
);
|
|
453
|
+
console.error("[Variants] Batch failed:", e instanceof Error ? e.message : e);
|
|
453
454
|
}
|
|
454
455
|
}
|
|
455
456
|
|
|
@@ -476,14 +477,28 @@ export function withReviews(): ProductEnricher {
|
|
|
476
477
|
|
|
477
478
|
const reviewPromises = products.map((product) =>
|
|
478
479
|
vtexFetch<any>(
|
|
479
|
-
`https://${myHost}/reviews-and-ratings/api/reviews?product_id=${product.inProductGroupWithID ?? ""}&from=0&to
|
|
480
|
-
).catch(() =>
|
|
480
|
+
`https://${myHost}/reviews-and-ratings/api/reviews?product_id=${product.inProductGroupWithID ?? ""}&from=0&to=${REVIEWS_PAGE_SIZE}&status=true`,
|
|
481
|
+
).catch((error) => {
|
|
482
|
+
console.error(
|
|
483
|
+
"[Reviews] Failed for product",
|
|
484
|
+
product.inProductGroupWithID,
|
|
485
|
+
error instanceof Error ? error.message : error,
|
|
486
|
+
);
|
|
487
|
+
return {};
|
|
488
|
+
}),
|
|
481
489
|
);
|
|
482
490
|
|
|
483
491
|
const ratingPromises = products.map((product) =>
|
|
484
492
|
vtexFetch<any>(
|
|
485
493
|
`https://${myHost}/reviews-and-ratings/api/rating/${product.inProductGroupWithID ?? ""}`,
|
|
486
|
-
).catch(() =>
|
|
494
|
+
).catch((error) => {
|
|
495
|
+
console.error(
|
|
496
|
+
"[Ratings] Failed for product",
|
|
497
|
+
product.inProductGroupWithID,
|
|
498
|
+
error instanceof Error ? error.message : error,
|
|
499
|
+
);
|
|
500
|
+
return {};
|
|
501
|
+
}),
|
|
487
502
|
);
|
|
488
503
|
|
|
489
504
|
const [reviews, ratings] = await Promise.all([
|
|
@@ -508,9 +523,14 @@ export function withInventory(): ProductEnricher {
|
|
|
508
523
|
const inventories = await Promise.all(
|
|
509
524
|
products.map((product) => {
|
|
510
525
|
if (!product.sku) return Promise.resolve({});
|
|
511
|
-
return vtexFetch<any>(
|
|
512
|
-
|
|
513
|
-
|
|
526
|
+
return vtexFetch<any>(`/api/logistics/pvt/inventory/skus/${product.sku}`).catch((error) => {
|
|
527
|
+
console.error(
|
|
528
|
+
"[Inventory] Failed for SKU",
|
|
529
|
+
product.sku,
|
|
530
|
+
error instanceof Error ? error.message : error,
|
|
531
|
+
);
|
|
532
|
+
return {};
|
|
533
|
+
});
|
|
514
534
|
}),
|
|
515
535
|
);
|
|
516
536
|
|
package/vtex/utils/fetchCache.ts
CHANGED
|
@@ -36,9 +36,7 @@ const inflight = new Map<string, Promise<CacheEntry>>();
|
|
|
36
36
|
|
|
37
37
|
function evictIfNeeded() {
|
|
38
38
|
if (store.size <= DEFAULT_MAX_ENTRIES) return;
|
|
39
|
-
const sorted = [...store.entries()].sort(
|
|
40
|
-
(a, b) => a[1].createdAt - b[1].createdAt,
|
|
41
|
-
);
|
|
39
|
+
const sorted = [...store.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt);
|
|
42
40
|
const toRemove = sorted.slice(0, store.size - DEFAULT_MAX_ENTRIES);
|
|
43
41
|
for (const [key] of toRemove) store.delete(key);
|
|
44
42
|
}
|
|
@@ -92,7 +90,6 @@ async function executeFetch(
|
|
|
92
90
|
`[vtex-fetch] attempt ${attempt + 1}/${attempts} failed — ${url}: ${lastError.message}`,
|
|
93
91
|
);
|
|
94
92
|
await sleep(RETRY_DELAYS[attempt] ?? 400);
|
|
95
|
-
continue;
|
|
96
93
|
}
|
|
97
94
|
}
|
|
98
95
|
}
|
package/vtex/utils/index.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
export * from "./batch";
|
|
2
2
|
export * from "./cookies";
|
|
3
|
+
export * from "./enrichment";
|
|
4
|
+
export * from "./fetchCache";
|
|
3
5
|
export * from "./intelligentSearch";
|
|
4
6
|
export * from "./legacy";
|
|
5
7
|
export * from "./pickAndOmit";
|
|
8
|
+
export * from "./proxy";
|
|
9
|
+
export * from "./resourceRange";
|
|
6
10
|
export * from "./segment";
|
|
7
11
|
export * from "./similars";
|
|
12
|
+
export * from "./sitemap";
|
|
13
|
+
export * from "./slugCache";
|
|
8
14
|
export * from "./slugify";
|
|
9
15
|
export * from "./transform";
|
|
10
16
|
export * from "./types";
|
|
11
|
-
export * from "./proxy";
|
|
12
17
|
export * from "./vtexId";
|
|
13
|
-
export * from "./sitemap";
|
|
14
|
-
export * from "./enrichment";
|
|
15
|
-
export * from "./resourceRange";
|
|
16
|
-
export * from "./fetchCache";
|
|
17
|
-
export * from "./slugCache";
|
|
@@ -1,84 +1,75 @@
|
|
|
1
1
|
import { vtexFetch } from "../client";
|
|
2
|
-
import type {
|
|
3
|
-
PageType,
|
|
4
|
-
SelectedFacet,
|
|
5
|
-
SimulationBehavior,
|
|
6
|
-
Sort,
|
|
7
|
-
} from "./types";
|
|
2
|
+
import type { PageType, SelectedFacet, SimulationBehavior, Sort } from "./types";
|
|
8
3
|
|
|
9
4
|
export const SESSION_COOKIE = "vtex_is_session";
|
|
10
5
|
export const ANONYMOUS_COOKIE = "vtex_is_anonymous";
|
|
11
6
|
|
|
12
|
-
export const withDefaultFacets = (
|
|
13
|
-
|
|
14
|
-
) => {
|
|
15
|
-
return [...allFacets];
|
|
7
|
+
export const withDefaultFacets = (allFacets: readonly SelectedFacet[]) => {
|
|
8
|
+
return [...allFacets];
|
|
16
9
|
};
|
|
17
10
|
|
|
18
11
|
export const toPath = (facets: SelectedFacet[]) =>
|
|
19
|
-
|
|
12
|
+
facets.map(({ key, value }) => (key ? `${key}/${value}` : value)).join("/");
|
|
20
13
|
|
|
21
14
|
interface Params {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
15
|
+
query: string;
|
|
16
|
+
page: number;
|
|
17
|
+
count: number;
|
|
18
|
+
sort: Sort;
|
|
19
|
+
fuzzy: string;
|
|
20
|
+
locale: string;
|
|
21
|
+
hideUnavailableItems: boolean;
|
|
22
|
+
simulationBehavior: SimulationBehavior;
|
|
30
23
|
}
|
|
31
24
|
|
|
32
25
|
export const withDefaultParams = ({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
26
|
+
query = "",
|
|
27
|
+
page = 0,
|
|
28
|
+
count = 12,
|
|
29
|
+
sort = "",
|
|
30
|
+
fuzzy = "auto",
|
|
31
|
+
locale = "pt-BR",
|
|
32
|
+
hideUnavailableItems,
|
|
33
|
+
simulationBehavior = "default",
|
|
41
34
|
}: Partial<Params>) => ({
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
35
|
+
page: page + 1,
|
|
36
|
+
count,
|
|
37
|
+
query,
|
|
38
|
+
sort,
|
|
39
|
+
...(fuzzy ? { fuzzy } : {}),
|
|
40
|
+
locale,
|
|
41
|
+
hideUnavailableItems: hideUnavailableItems ?? false,
|
|
42
|
+
simulationBehavior,
|
|
50
43
|
});
|
|
51
44
|
|
|
52
|
-
export const isFilterParam = (keyFilter: string): boolean =>
|
|
53
|
-
keyFilter.startsWith("filter.");
|
|
45
|
+
export const isFilterParam = (keyFilter: string): boolean => keyFilter.startsWith("filter.");
|
|
54
46
|
|
|
55
47
|
const segmentsFromTerm = (term: string) => term.split("/").filter(Boolean);
|
|
56
48
|
|
|
57
49
|
const segmentsFromSearchParams = (url: string) => {
|
|
58
|
-
|
|
50
|
+
const searchParams = new URLSearchParams(url).entries();
|
|
59
51
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
const categories = Array.from(searchParams)
|
|
53
|
+
.sort()
|
|
54
|
+
.reduce((acc, [key, value]) => {
|
|
55
|
+
if (key.includes("filter.category")) {
|
|
56
|
+
acc.push(value);
|
|
57
|
+
}
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
return acc;
|
|
60
|
+
}, [] as string[]);
|
|
68
61
|
|
|
69
|
-
|
|
62
|
+
return categories.length ? categories : segmentsFromTerm(url);
|
|
70
63
|
};
|
|
71
64
|
|
|
72
|
-
export const pageTypesFromUrl = async (
|
|
73
|
-
|
|
74
|
-
): Promise<PageType[]> => {
|
|
75
|
-
const segments = segmentsFromSearchParams(url);
|
|
65
|
+
export const pageTypesFromUrl = async (url: string): Promise<PageType[]> => {
|
|
66
|
+
const segments = segmentsFromSearchParams(url);
|
|
76
67
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
68
|
+
return await Promise.all(
|
|
69
|
+
segments.map((_, index) =>
|
|
70
|
+
vtexFetch<PageType>(
|
|
71
|
+
`/api/catalog_system/pub/portal/pagetype/${segments.slice(0, index + 1).join("/")}`,
|
|
72
|
+
),
|
|
73
|
+
),
|
|
74
|
+
);
|
|
84
75
|
};
|
package/vtex/utils/legacy.ts
CHANGED
|
@@ -1,155 +1,139 @@
|
|
|
1
1
|
import type { Seo } from "../../commerce/types/commerce";
|
|
2
2
|
import { vtexFetch } from "../client";
|
|
3
|
+
import type { WrappedSegment } from "./segment";
|
|
3
4
|
import { slugify } from "./slugify";
|
|
4
5
|
import type { PageType } from "./types";
|
|
5
|
-
import { WrappedSegment } from "./segment";
|
|
6
6
|
|
|
7
7
|
const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
8
8
|
|
|
9
|
-
export const toSegmentParams = (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
));
|
|
9
|
+
export const toSegmentParams = ({ payload: segment }: WrappedSegment) =>
|
|
10
|
+
Object.fromEntries(
|
|
11
|
+
Object.entries({
|
|
12
|
+
utmi_campaign: segment.utmi_campaign ?? undefined,
|
|
13
|
+
utm_campaign: segment.utm_campaign ?? undefined,
|
|
14
|
+
utm_source: segment.utm_source ?? undefined,
|
|
15
|
+
sc: segment.channel ?? undefined,
|
|
16
|
+
}).filter(([_, v]) => v !== undefined),
|
|
17
|
+
);
|
|
19
18
|
|
|
20
19
|
const PAGE_TYPE_TO_MAP_PARAM = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
Brand: "b",
|
|
21
|
+
Category: "c",
|
|
22
|
+
Department: "c",
|
|
23
|
+
SubCategory: "c",
|
|
24
|
+
Collection: "productClusterIds",
|
|
25
|
+
Cluster: "productClusterIds",
|
|
26
|
+
Search: "ft",
|
|
27
|
+
FullText: "ft",
|
|
28
|
+
Product: "p",
|
|
29
|
+
NotFound: null,
|
|
31
30
|
};
|
|
32
31
|
|
|
33
32
|
const segmentsFromTerm = (term: string) => term.split("/").filter(Boolean);
|
|
34
33
|
|
|
35
34
|
export const getValidTypesFromPageTypes = (pagetypes: PageType[]) => {
|
|
36
|
-
|
|
37
|
-
.filter((type) => PAGE_TYPE_TO_MAP_PARAM[type.pageType]);
|
|
35
|
+
return pagetypes.filter((type) => PAGE_TYPE_TO_MAP_PARAM[type.pageType]);
|
|
38
36
|
};
|
|
39
37
|
|
|
40
|
-
export const pageTypesFromPathname = async (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
),
|
|
51
|
-
);
|
|
38
|
+
export const pageTypesFromPathname = async (term: string): Promise<PageType[]> => {
|
|
39
|
+
const segments = segmentsFromTerm(term);
|
|
40
|
+
|
|
41
|
+
return await Promise.all(
|
|
42
|
+
segments.map((_, index) =>
|
|
43
|
+
vtexFetch<PageType>(
|
|
44
|
+
`/api/catalog_system/pub/portal/pagetype/${segments.slice(0, index + 1).join("/")}`,
|
|
45
|
+
),
|
|
46
|
+
),
|
|
47
|
+
);
|
|
52
48
|
};
|
|
53
49
|
|
|
54
|
-
export const getMapAndTerm = (
|
|
55
|
-
|
|
56
|
-
) =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (map === "ft" && term === "s") {
|
|
74
|
-
return ["", ""];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return [map, term];
|
|
50
|
+
export const getMapAndTerm = (pageTypes: PageType[]) => {
|
|
51
|
+
const term = pageTypes
|
|
52
|
+
.map((type, index) =>
|
|
53
|
+
type.url ? segmentsFromTerm(new URL(`http://${type.url}`).pathname)[index] : null,
|
|
54
|
+
)
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.join("/");
|
|
57
|
+
|
|
58
|
+
const map = pageTypes
|
|
59
|
+
.map((type) => PAGE_TYPE_TO_MAP_PARAM[type.pageType])
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join(",");
|
|
62
|
+
|
|
63
|
+
if (map === "ft" && term === "s") {
|
|
64
|
+
return ["", ""];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return [map, term];
|
|
78
68
|
};
|
|
79
69
|
|
|
80
|
-
export const pageTypesToBreadcrumbList = (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
item: new URL(`/${slug.join("/")}`, baseUrl).href,
|
|
98
|
-
position,
|
|
99
|
-
});
|
|
100
|
-
});
|
|
70
|
+
export const pageTypesToBreadcrumbList = (pages: PageType[], baseUrl: string) => {
|
|
71
|
+
const filteredPages = pages.filter(
|
|
72
|
+
({ pageType }) =>
|
|
73
|
+
pageType === "Category" || pageType === "Department" || pageType === "SubCategory",
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return filteredPages.map((page, index) => {
|
|
77
|
+
const position = index + 1;
|
|
78
|
+
const slug = filteredPages.slice(0, position).map((x) => slugify(x.name!));
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
"@type": "ListItem" as const,
|
|
82
|
+
name: page.name!,
|
|
83
|
+
item: new URL(`/${slug.join("/")}`, baseUrl).href,
|
|
84
|
+
position,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
101
87
|
};
|
|
102
88
|
|
|
103
89
|
export const pageTypesToSeo = (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
90
|
+
pages: PageType[],
|
|
91
|
+
baseUrl: string,
|
|
92
|
+
currentPage?: number,
|
|
107
93
|
): Seo | null => {
|
|
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
|
-
};
|
|
94
|
+
const current = pages.at(-1);
|
|
95
|
+
const url = new URL(baseUrl);
|
|
96
|
+
const fullTextSearch = url.searchParams.get("q");
|
|
97
|
+
const hasMapTermOrSkuId = !!(url.searchParams.get("map") || url.searchParams.get("skuId"));
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
(!current || current.pageType === "Search" || current.pageType === "FullText") &&
|
|
101
|
+
fullTextSearch
|
|
102
|
+
) {
|
|
103
|
+
return {
|
|
104
|
+
title: capitalize(fullTextSearch),
|
|
105
|
+
description: capitalize(fullTextSearch),
|
|
106
|
+
canonical: url.href,
|
|
107
|
+
noIndexing: hasMapTermOrSkuId,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!current) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
title: current.title || current.name || "",
|
|
117
|
+
description: current.metaTagDescription!,
|
|
118
|
+
noIndexing: hasMapTermOrSkuId,
|
|
119
|
+
canonical: toCanonical(
|
|
120
|
+
new URL(
|
|
121
|
+
current.url && current.pageType !== "Collection"
|
|
122
|
+
? current.url.replace(/^[^/]*\//, "/").toLowerCase()
|
|
123
|
+
: url,
|
|
124
|
+
url,
|
|
125
|
+
),
|
|
126
|
+
currentPage,
|
|
127
|
+
),
|
|
128
|
+
};
|
|
145
129
|
};
|
|
146
130
|
|
|
147
131
|
function toCanonical(url: URL, page?: number) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
132
|
+
if (typeof page === "number") {
|
|
133
|
+
url.searchParams.set("page", `${page}`);
|
|
134
|
+
}
|
|
151
135
|
|
|
152
|
-
|
|
136
|
+
return url.href;
|
|
153
137
|
}
|
|
154
138
|
|
|
155
139
|
export { isFilterParam } from "./intelligentSearch";
|