@decocms/apps-vtex 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/package.json +67 -0
  2. package/src/README.md +6 -0
  3. package/src/__tests__/client-segment-cookie.test.ts +255 -0
  4. package/src/__tests__/client-set-cookie-forward.test.ts +257 -0
  5. package/src/actions/address.ts +250 -0
  6. package/src/actions/analytics/sendEvent.ts +86 -0
  7. package/src/actions/auth.ts +333 -0
  8. package/src/actions/checkout.ts +522 -0
  9. package/src/actions/index.ts +11 -0
  10. package/src/actions/masterData.ts +168 -0
  11. package/src/actions/misc.ts +188 -0
  12. package/src/actions/newsletter.ts +105 -0
  13. package/src/actions/orders.ts +34 -0
  14. package/src/actions/profile.ts +201 -0
  15. package/src/actions/session.ts +85 -0
  16. package/src/actions/trigger.ts +43 -0
  17. package/src/actions/wishlist.ts +114 -0
  18. package/src/client.ts +667 -0
  19. package/src/commerceLoaders.ts +261 -0
  20. package/src/hooks/__tests__/createUseCart.test.ts +184 -0
  21. package/src/hooks/__tests__/createUseUser.test.ts +48 -0
  22. package/src/hooks/__tests__/createUseWishlist.test.ts +87 -0
  23. package/src/hooks/createUseCart.ts +360 -0
  24. package/src/hooks/createUseUser.ts +153 -0
  25. package/src/hooks/createUseWishlist.ts +242 -0
  26. package/src/hooks/index.ts +19 -0
  27. package/src/hooks/useAutocomplete.ts +83 -0
  28. package/src/hooks/useCart.ts +243 -0
  29. package/src/hooks/useUser.ts +78 -0
  30. package/src/hooks/useWishlist.ts +119 -0
  31. package/src/index.ts +17 -0
  32. package/src/invoke.ts +181 -0
  33. package/src/loaders/ProductDetailsPage.ts +1 -0
  34. package/src/loaders/ProductList.ts +1 -0
  35. package/src/loaders/ProductListingPage.ts +1 -0
  36. package/src/loaders/address.ts +115 -0
  37. package/src/loaders/autocomplete.ts +58 -0
  38. package/src/loaders/brands.ts +44 -0
  39. package/src/loaders/cart.ts +52 -0
  40. package/src/loaders/catalog.ts +163 -0
  41. package/src/loaders/collections.ts +55 -0
  42. package/src/loaders/index.ts +19 -0
  43. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +20 -0
  44. package/src/loaders/intelligentSearch/productDetailsPage.ts +95 -0
  45. package/src/loaders/intelligentSearch/productList.ts +154 -0
  46. package/src/loaders/intelligentSearch/productListingPage.ts +506 -0
  47. package/src/loaders/intelligentSearch/suggestions.ts +46 -0
  48. package/src/loaders/legacy/productDetailsPage.ts +1 -0
  49. package/src/loaders/legacy/productList.ts +1 -0
  50. package/src/loaders/legacy/relatedProductsLoader.ts +81 -0
  51. package/src/loaders/legacy.ts +635 -0
  52. package/src/loaders/logistics.ts +111 -0
  53. package/src/loaders/minicart.ts +78 -0
  54. package/src/loaders/navbar.ts +26 -0
  55. package/src/loaders/orders.ts +92 -0
  56. package/src/loaders/pageType.ts +68 -0
  57. package/src/loaders/payment.ts +97 -0
  58. package/src/loaders/productListFull.ts +159 -0
  59. package/src/loaders/profile.ts +138 -0
  60. package/src/loaders/promotion.ts +30 -0
  61. package/src/loaders/search.ts +124 -0
  62. package/src/loaders/session.ts +83 -0
  63. package/src/loaders/user.ts +87 -0
  64. package/src/loaders/wishlist.ts +89 -0
  65. package/src/loaders/wishlistProducts.ts +69 -0
  66. package/src/loaders/workflow/products.ts +64 -0
  67. package/src/loaders/workflow.ts +316 -0
  68. package/src/logo.png +0 -0
  69. package/src/middleware.ts +240 -0
  70. package/src/mod.ts +152 -0
  71. package/src/registry.ts +9 -0
  72. package/src/types.ts +248 -0
  73. package/src/utils/__tests__/cookieSanitizer.test.ts +162 -0
  74. package/src/utils/__tests__/fetch.test.ts +80 -0
  75. package/src/utils/__tests__/fetchCache.test.ts +112 -0
  76. package/src/utils/__tests__/instrumentedFetch.test.ts +158 -0
  77. package/src/utils/__tests__/intelligentSearch.test.ts +88 -0
  78. package/src/utils/__tests__/minicart.test.ts +184 -0
  79. package/src/utils/__tests__/operationRouter.test.ts +227 -0
  80. package/src/utils/__tests__/resourceRange.test.ts +32 -0
  81. package/src/utils/__tests__/sitemap.test.ts +185 -0
  82. package/src/utils/__tests__/slugify.test.ts +31 -0
  83. package/src/utils/__tests__/transform.test.ts +698 -0
  84. package/src/utils/accountLoaders.ts +203 -0
  85. package/src/utils/authHelpers.ts +85 -0
  86. package/src/utils/batch.ts +18 -0
  87. package/src/utils/cookieSanitizer.ts +173 -0
  88. package/src/utils/cookies.ts +167 -0
  89. package/src/utils/enrichment.ts +560 -0
  90. package/src/utils/fetch.ts +107 -0
  91. package/src/utils/fetchCache.ts +222 -0
  92. package/src/utils/index.ts +19 -0
  93. package/src/utils/instrumentedFetch.ts +78 -0
  94. package/src/utils/intelligentSearch.ts +96 -0
  95. package/src/utils/legacy.ts +139 -0
  96. package/src/utils/minicart.ts +139 -0
  97. package/src/utils/operationRouter.ts +139 -0
  98. package/src/utils/pickAndOmit.ts +25 -0
  99. package/src/utils/proxy.ts +435 -0
  100. package/src/utils/resourceRange.ts +10 -0
  101. package/src/utils/segment.ts +152 -0
  102. package/src/utils/similars.ts +37 -0
  103. package/src/utils/sitemap.ts +282 -0
  104. package/src/utils/slugCache.ts +28 -0
  105. package/src/utils/slugify.ts +13 -0
  106. package/src/utils/transform.ts +1509 -0
  107. package/src/utils/types.ts +1884 -0
  108. package/src/utils/vtexId.ts +138 -0
  109. package/tsconfig.json +7 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * VTEX Profile API loaders.
3
+ * Pure async functions — require configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/profile/getCurrentProfile.ts
7
+ * vtex/loaders/profile/getProfileByEmail.ts
8
+ *
9
+ * @see https://developers.vtex.com/docs/api-reference/checkout-api
10
+ */
11
+ import { vtexFetch, vtexIOGraphQL } from "../client";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Types
15
+ // ---------------------------------------------------------------------------
16
+
17
+ const ADDRESS_FIELDS = `
18
+ addressType
19
+ receiverName
20
+ addressId
21
+ postalCode
22
+ city
23
+ state
24
+ country
25
+ street
26
+ number
27
+ neighborhood
28
+ complement
29
+ reference
30
+ geoCoordinates
31
+ `;
32
+
33
+ export interface Profile {
34
+ id: string;
35
+ cacheId: string;
36
+ email: string;
37
+ firstName: string;
38
+ lastName: string;
39
+ document: string;
40
+ userId: string;
41
+ birthDate: string;
42
+ gender: string;
43
+ homePhone: string;
44
+ businessPhone: string;
45
+ addresses: Record<string, unknown>[];
46
+ isCorporate: boolean;
47
+ tradeName: string;
48
+ corporateName: string;
49
+ corporateDocument: string;
50
+ stateRegistration: string;
51
+ payments: Record<string, unknown>[];
52
+ customFields: Array<{ key: string; value: string }>;
53
+ passwordLastUpdate: string;
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // getCurrentProfile (authenticated — VTEX IO GraphQL)
58
+ // ---------------------------------------------------------------------------
59
+
60
+ const PROFILE_QUERY = `query getUserProfile($customFields: String) {
61
+ profile(customFields: $customFields) {
62
+ id
63
+ cacheId
64
+ email
65
+ firstName
66
+ lastName
67
+ document
68
+ userId
69
+ birthDate
70
+ gender
71
+ homePhone
72
+ businessPhone
73
+ addresses { ${ADDRESS_FIELDS} }
74
+ isCorporate
75
+ tradeName
76
+ corporateName
77
+ corporateDocument
78
+ stateRegistration
79
+ payments {
80
+ cacheId
81
+ id
82
+ paymentSystem
83
+ paymentSystemName
84
+ cardNumber
85
+ address { ${ADDRESS_FIELDS} }
86
+ isExpired
87
+ expirationDate
88
+ accountStatus
89
+ }
90
+ customFields {
91
+ key
92
+ value
93
+ }
94
+ passwordLastUpdate
95
+ }
96
+ }`;
97
+
98
+ /**
99
+ * Fetch the full profile for the currently authenticated user.
100
+ * Requires a valid VTEX auth cookie.
101
+ */
102
+ export async function getCurrentProfile(
103
+ authCookie: string,
104
+ customFields?: string[],
105
+ ): Promise<Profile> {
106
+ const { profile } = await vtexIOGraphQL<{ profile: Profile }>(
107
+ {
108
+ query: PROFILE_QUERY,
109
+ variables: { customFields: customFields?.join(",") },
110
+ },
111
+ { cookie: authCookie },
112
+ );
113
+
114
+ return profile;
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // getProfileByEmail (authenticated — REST)
119
+ // ---------------------------------------------------------------------------
120
+
121
+ /**
122
+ * Fetch a checkout profile by e-mail.
123
+ * Requires a valid VTEX auth cookie.
124
+ *
125
+ * @see https://developers.vtex.com/docs/api-reference/checkout-api#get-/api/checkout/pub/profiles
126
+ */
127
+ export async function getProfileByEmail<T = any>(
128
+ email: string,
129
+ authCookie: string,
130
+ ensureComplete?: boolean,
131
+ ): Promise<T> {
132
+ const params = new URLSearchParams({ email });
133
+ if (ensureComplete) params.set("ensureComplete", "true");
134
+
135
+ return vtexFetch<T>(`/api/checkout/pub/profiles?${params}`, {
136
+ headers: { cookie: authCookie },
137
+ });
138
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * VTEX Promotion loader.
3
+ * Pure async function — requires configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/getPromotionById.ts
7
+ *
8
+ * @see https://developers.vtex.com/docs/api-reference/promotions-and-taxes-api
9
+ */
10
+ import { vtexFetch } from "../client";
11
+ import type { Document } from "../utils/types";
12
+
13
+ /**
14
+ * Fetch a promotion / calculator-configuration by its ID.
15
+ *
16
+ * Note: uses the **pvt** (private) endpoint — requires appKey/appToken
17
+ * or a valid authentication cookie.
18
+ *
19
+ * @param promotionId - The `idCalculatorConfiguration` of the promotion
20
+ * @param authCookie - Optional cookie string for authenticated requests
21
+ */
22
+ export async function getPromotionById(
23
+ promotionId: string,
24
+ authCookie?: string,
25
+ ): Promise<Document[]> {
26
+ const headers: Record<string, string> = {};
27
+ if (authCookie) headers.cookie = authCookie;
28
+
29
+ return vtexFetch<Document[]>(`/api/rnb/pvt/calculatorconfiguration/${promotionId}`, { headers });
30
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * VTEX search-related loaders (Intelligent Search + Catalog).
3
+ * Pure async functions — require configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/intelligentSearch/topsearches.ts
7
+ * vtex/loaders/intelligentSearch/productSearchValidator.ts
8
+ * vtex/loaders/options/productIdByTerm.ts
9
+ *
10
+ * @see https://developers.vtex.com/docs/api-reference/intelligent-search-api
11
+ */
12
+ import { getVtexConfig, intelligentSearch } from "../client";
13
+ import type { Suggestion } from "../utils/types";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // getTopSearches
17
+ // ---------------------------------------------------------------------------
18
+
19
+ /**
20
+ * Fetch the top searches from Intelligent Search.
21
+ *
22
+ * @param locale - BCP-47 locale (defaults to the configured locale or "pt-BR")
23
+ */
24
+ export async function getTopSearches(locale?: string): Promise<Suggestion> {
25
+ const cfg = getVtexConfig();
26
+ const effectiveLocale = locale ?? cfg.locale ?? "pt-BR";
27
+
28
+ return intelligentSearch<Suggestion>("/top_searches", {
29
+ locale: effectiveLocale,
30
+ });
31
+ }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // validateProductSearch
35
+ // ---------------------------------------------------------------------------
36
+
37
+ export interface FacetsSearchProps {
38
+ query?: string;
39
+ facets?: string;
40
+ sort?: string;
41
+ count?: number;
42
+ page?: number;
43
+ locale?: string;
44
+ }
45
+
46
+ /**
47
+ * Validate whether a product search returns results.
48
+ *
49
+ * Runs the given search parameters against Intelligent Search.
50
+ * If no results are found and the props include facets, retries
51
+ * the search without facets.
52
+ *
53
+ * Returns the raw IS response or `null` when nothing is found.
54
+ */
55
+ export async function validateProductSearch<T = unknown>(
56
+ props: FacetsSearchProps,
57
+ fetcher: (props: FacetsSearchProps) => Promise<T[] | null>,
58
+ ): Promise<T[] | null> {
59
+ const results = await fetcher(props);
60
+ if (results !== null && results.length > 0) return results;
61
+
62
+ if (props.facets) {
63
+ return fetcher({ ...props, facets: "" });
64
+ }
65
+
66
+ return null;
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // getProductIdByTerm
71
+ // ---------------------------------------------------------------------------
72
+
73
+ interface ProductIdOption {
74
+ value: string;
75
+ label: string;
76
+ image?: string;
77
+ }
78
+
79
+ interface ISSuggestionProduct {
80
+ productId: string;
81
+ productName: string;
82
+ brand: string;
83
+ linkText: string;
84
+ items: Array<{
85
+ itemId: string;
86
+ name: string;
87
+ images: Array<{ imageUrl: string; imageText: string }>;
88
+ sellers: Array<{
89
+ commertialOffer: { Price: number; ListPrice: number };
90
+ }>;
91
+ }>;
92
+ }
93
+
94
+ interface ISSuggestionResponse {
95
+ searches: Array<{ term: string; count: number }>;
96
+ products: ISSuggestionProduct[];
97
+ }
98
+
99
+ /**
100
+ * Search for products by free-text term and return a list of
101
+ * `{ value (SKU ID), label, image }` options.
102
+ *
103
+ * Hits the IS autocomplete_suggestions endpoint.
104
+ */
105
+ export async function getProductIdByTerm(term?: string): Promise<ProductIdOption[]> {
106
+ const query = (term ?? "").trim();
107
+ if (!query) return [];
108
+
109
+ const data = await intelligentSearch<ISSuggestionResponse>("/autocomplete_suggestions/", {
110
+ query,
111
+ });
112
+
113
+ if (!data.products?.length) {
114
+ return [{ value: "No products found", label: "No products found" }];
115
+ }
116
+
117
+ return data.products.flatMap((product) =>
118
+ (product.items ?? []).map((item) => ({
119
+ value: item.itemId,
120
+ label: `${item.itemId} - ${product.productName} ${item.name} - ${product.productId}`,
121
+ image: item.images?.[0]?.imageUrl,
122
+ })),
123
+ );
124
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * VTEX Session API loaders.
3
+ * Pure async functions — require configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/session/getSession.ts
7
+ * vtex/loaders/session/getUserSessions.ts
8
+ *
9
+ * @see https://developers.vtex.com/docs/api-reference/session-manager-api
10
+ */
11
+ import { vtexFetch, vtexIOGraphQL } from "../client";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // getSession (REST)
15
+ // ---------------------------------------------------------------------------
16
+
17
+ /**
18
+ * Fetch the current session data.
19
+ *
20
+ * @param items - Keys to retrieve, e.g. `["public.variable1", "profile.email"]`.
21
+ * Pass `["*"]` (or omit) to retrieve all keys.
22
+ * @param authCookie - The raw `cookie` header value forwarded from the user request.
23
+ *
24
+ * @see https://developers.vtex.com/docs/api-reference/session-manager-api#get-/api/sessions
25
+ */
26
+ export async function getSession<T = any>(items: string[] = ["*"], authCookie: string): Promise<T> {
27
+ const qs = new URLSearchParams({ items: items.join(",") });
28
+ return vtexFetch<T>(`/api/sessions?${qs}`, {
29
+ headers: { cookie: authCookie },
30
+ });
31
+ }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // getUserSessions (authenticated — VTEX IO GraphQL)
35
+ // ---------------------------------------------------------------------------
36
+
37
+ export interface LoginSession {
38
+ id: string;
39
+ cacheId: string;
40
+ deviceType: string;
41
+ city: string;
42
+ lastAccess: string;
43
+ browser: string;
44
+ os: string;
45
+ ip: string;
46
+ fullAddress: string;
47
+ firstAccess: string;
48
+ }
49
+
50
+ export interface LoginSessionsInfo {
51
+ currentLoginSessionId: string;
52
+ loginSessions: LoginSession[];
53
+ }
54
+
55
+ const USER_SESSIONS_QUERY = `query getUserSessions {
56
+ loginSessionsInfo {
57
+ currentLoginSessionId
58
+ loginSessions {
59
+ id
60
+ cacheId
61
+ deviceType
62
+ city
63
+ lastAccess
64
+ browser
65
+ os
66
+ ip
67
+ fullAddress
68
+ firstAccess
69
+ }
70
+ }
71
+ }`;
72
+
73
+ /**
74
+ * Fetch all active login sessions for the currently authenticated user.
75
+ * Requires a valid VTEX auth cookie.
76
+ */
77
+ export async function getUserSessions(authCookie: string): Promise<LoginSessionsInfo> {
78
+ const { loginSessionsInfo } = await vtexIOGraphQL<{
79
+ loginSessionsInfo: LoginSessionsInfo;
80
+ }>({ query: USER_SESSIONS_QUERY }, { cookie: authCookie });
81
+
82
+ return loginSessionsInfo;
83
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * VTEX User API loader.
3
+ * Pure async function — requires configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/user.ts
7
+ *
8
+ * @see https://developers.vtex.com/docs/api-reference/checkout-api
9
+ */
10
+ import { vtexIOGraphQL } from "../client";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Types
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export interface Person {
17
+ "@id": string;
18
+ email: string;
19
+ givenName?: string;
20
+ familyName?: string;
21
+ taxID?: string;
22
+ gender?: string;
23
+ telephone?: string;
24
+ }
25
+
26
+ interface VtexUser {
27
+ id: string;
28
+ userId: string;
29
+ email: string;
30
+ firstName?: string;
31
+ lastName?: string;
32
+ profilePicture?: string;
33
+ gender?: string;
34
+ document?: string;
35
+ homePhone?: string;
36
+ businessPhone?: string;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // getUser (authenticated — VTEX IO GraphQL)
41
+ // ---------------------------------------------------------------------------
42
+
43
+ const USER_QUERY = `query getUserProfile {
44
+ profile {
45
+ id
46
+ userId
47
+ email
48
+ firstName
49
+ lastName
50
+ profilePicture
51
+ gender
52
+ document
53
+ homePhone
54
+ businessPhone
55
+ }
56
+ }`;
57
+
58
+ /**
59
+ * Fetch the authenticated user as a Schema.org-style `Person`.
60
+ * Returns `null` when no valid session exists or the query fails.
61
+ *
62
+ * @param authCookie - Raw `cookie` header value from the user request.
63
+ */
64
+ export async function getUser(authCookie: string): Promise<Person | null> {
65
+ try {
66
+ const { profile: user } = await vtexIOGraphQL<{ profile: VtexUser }>(
67
+ { query: USER_QUERY },
68
+ { cookie: authCookie },
69
+ );
70
+
71
+ return {
72
+ "@id": user.userId ?? user.id,
73
+ email: user.email,
74
+ givenName: user.firstName,
75
+ familyName: user.lastName,
76
+ taxID: user.document?.replace(/[^\d]/g, ""),
77
+ gender: user.gender
78
+ ? user.gender === "f"
79
+ ? "https://schema.org/Female"
80
+ : "https://schema.org/Male"
81
+ : undefined,
82
+ telephone: user.homePhone ?? user.businessPhone,
83
+ };
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * VTEX Wishlist API loader.
3
+ * Pure async function — requires configureVtex() to have been called.
4
+ *
5
+ * Ported from deco-cx/apps:
6
+ * vtex/loaders/wishlist.ts
7
+ *
8
+ * @see https://developers.vtex.com/docs/guides/vtex-wish-list
9
+ */
10
+ import { vtexIOGraphQL } from "../client";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Types
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export interface WishlistItem {
17
+ id: string;
18
+ productId: string;
19
+ sku: string;
20
+ title: string;
21
+ }
22
+
23
+ export interface GetWishlistOpts {
24
+ shopperId: string;
25
+ count?: number;
26
+ page?: number;
27
+ allRecords?: boolean;
28
+ }
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // getWishlist (authenticated — VTEX IO GraphQL)
32
+ // ---------------------------------------------------------------------------
33
+
34
+ const WISHLIST_QUERY = `query GetWishlist($shopperId: String!, $name: String!, $from: Int, $to: Int) {
35
+ viewList(shopperId: $shopperId, name: $name, from: $from, to: $to)
36
+ @context(provider: "vtex.wish-list@1.x") {
37
+ name
38
+ data {
39
+ id
40
+ productId
41
+ sku
42
+ title
43
+ }
44
+ }
45
+ }`;
46
+
47
+ /**
48
+ * Fetch the wishlist for a given shopper.
49
+ * Requires a valid VTEX auth cookie.
50
+ *
51
+ * @param authCookie - Raw `cookie` header value from the user request.
52
+ * @param opts.shopperId - The shopper identifier (usually the e-mail from the JWT `sub` claim).
53
+ * @param opts.count - Items per page (default: all).
54
+ * @param opts.page - Zero-based page index (default: 0).
55
+ * @param opts.allRecords - When true, ignores pagination and returns every item.
56
+ */
57
+ export async function getWishlist(
58
+ authCookie: string,
59
+ opts: GetWishlistOpts,
60
+ ): Promise<WishlistItem[]> {
61
+ try {
62
+ const { viewList } = await vtexIOGraphQL<{
63
+ viewList: { name?: string; data: WishlistItem[] };
64
+ }>(
65
+ {
66
+ operationName: "GetWishlist",
67
+ query: WISHLIST_QUERY,
68
+ variables: {
69
+ name: "Wishlist",
70
+ shopperId: opts.shopperId,
71
+ },
72
+ },
73
+ { cookie: authCookie },
74
+ );
75
+
76
+ const data = viewList.data ?? [];
77
+
78
+ if (opts.allRecords) return data;
79
+
80
+ const count = opts.count ?? Infinity;
81
+ const page = opts.page ?? 0;
82
+ return data.slice(count * page, count * (page + 1));
83
+ } catch (error) {
84
+ if (error instanceof DOMException && error.name === "AbortError") {
85
+ throw error;
86
+ }
87
+ return [];
88
+ }
89
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Wishlist Products loader.
3
+ * Returns a ProductListingPage built from the user's wishlist items.
4
+ *
5
+ * Ported from deco-cx/apps vtex/loaders/product/wishlist.ts
6
+ */
7
+ import type { Product, ProductListingPage } from "@decocms/apps-commerce/types";
8
+ import { getWishlist } from "./wishlist";
9
+
10
+ export interface WishlistProductsProps {
11
+ /** Items per page @default 12 */
12
+ count?: number;
13
+ /** 1 to start from index 1 @default 0 */
14
+ offset?: 0 | 1;
15
+ /** The user's auth cookie string */
16
+ authCookie: string;
17
+ /** The user's shopper ID (email) */
18
+ shopperId: string;
19
+ /** Current page URL (for pagination links) */
20
+ url: string;
21
+ }
22
+
23
+ function withPage(baseUrl: string, page: number): string {
24
+ const url = new URL(baseUrl);
25
+ url.searchParams.set("page", `${page}`);
26
+ return `?${url.searchParams}`;
27
+ }
28
+
29
+ export async function wishlistProducts(
30
+ props: WishlistProductsProps,
31
+ ): Promise<ProductListingPage | null> {
32
+ const { count: recordPerPage = 12, offset = 0, authCookie, shopperId, url: rawUrl } = props;
33
+
34
+ const url = new URL(rawUrl);
35
+ const page = Math.max(0, Number(url.searchParams.get("page") ?? offset) - offset);
36
+ const items = await getWishlist(authCookie, { shopperId, allRecords: true });
37
+ const records = items.length;
38
+ const start = page * recordPerPage;
39
+ const end = (page + 1) * recordPerPage;
40
+
41
+ const products: Product[] = items
42
+ .map(({ sku, productId }) => ({
43
+ "@type": "Product" as const,
44
+ inProductGroupWithID: productId,
45
+ productID: sku,
46
+ sku,
47
+ }))
48
+ .slice(start, end);
49
+
50
+ return {
51
+ "@type": "ProductListingPage",
52
+ breadcrumb: {
53
+ "@type": "BreadcrumbList",
54
+ itemListElement: [],
55
+ numberOfItems: 0,
56
+ },
57
+ filters: [],
58
+ products,
59
+ pageInfo: {
60
+ currentPage: page + offset,
61
+ nextPage: records > end ? withPage(rawUrl, page + 1 + offset) : undefined,
62
+ previousPage: page > 0 ? withPage(rawUrl, page - 1 + offset) : undefined,
63
+ recordPerPage,
64
+ records,
65
+ },
66
+ sortOptions: [],
67
+ seo: null,
68
+ };
69
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Workflow/collection products loader using Intelligent Search + shared transform.
3
+ * Maps IS response to schema.org Product[] following deco-cx/apps pattern.
4
+ */
5
+
6
+ import type { Product } from "@decocms/apps-commerce/types";
7
+ import { getVtexConfig, intelligentSearch, toFacetPath } from "../../client";
8
+ import { pickSku, toProduct } from "../../utils/transform";
9
+ import type { Product as ProductVTEX } from "../../utils/types";
10
+
11
+ export interface WorkflowProductsProps {
12
+ props?: {
13
+ query?: string;
14
+ count?: number;
15
+ sort?: string;
16
+ collection?: string;
17
+ };
18
+ page?: number;
19
+ pagesize?: number;
20
+ }
21
+
22
+ export default async function vtexWorkflowProducts(
23
+ props: WorkflowProductsProps,
24
+ ): Promise<Product[] | null> {
25
+ const inner = props.props ?? props;
26
+ const collection = (inner as any).collection;
27
+ const query = (inner as any).query ?? "";
28
+ const count = (inner as any).count ?? props.pagesize ?? 12;
29
+ const sort = (inner as any).sort ?? "";
30
+
31
+ try {
32
+ const config = getVtexConfig();
33
+ const locale = config.locale ?? "pt-BR";
34
+
35
+ const params: Record<string, string> = {
36
+ count: String(count),
37
+ locale,
38
+ page: String((props.page ?? 0) + 1),
39
+ };
40
+ if (query) params.query = query;
41
+ if (sort) params.sort = sort;
42
+
43
+ const facetPath = collection
44
+ ? toFacetPath([{ key: "productClusterIds", value: collection }])
45
+ : "";
46
+
47
+ const endpoint = facetPath ? `/product_search/${facetPath}` : "/product_search/";
48
+
49
+ const data = await intelligentSearch<{ products: ProductVTEX[] }>(endpoint, params);
50
+
51
+ const products = data.products ?? [];
52
+ const baseUrl = config.publicUrl
53
+ ? `https://${config.publicUrl}`
54
+ : `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
55
+
56
+ return products.map((p) => {
57
+ const sku = pickSku(p);
58
+ return toProduct(p, sku, 0, { baseUrl, priceCurrency: "BRL" });
59
+ });
60
+ } catch (error) {
61
+ console.error("[VTEX] Workflow products error:", error);
62
+ return null;
63
+ }
64
+ }