@decocms/apps-vtex 8.1.0-next.0 → 8.1.0-next.2

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 (163) hide show
  1. package/dist/index.d.ts +6 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +6 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/utils/constants.d.ts +28 -0
  6. package/dist/utils/constants.d.ts.map +1 -0
  7. package/dist/utils/constants.js +17 -0
  8. package/dist/utils/constants.js.map +1 -0
  9. package/dist/utils/cookieSanitizer.d.ts +87 -0
  10. package/dist/utils/cookieSanitizer.d.ts.map +1 -0
  11. package/dist/utils/cookieSanitizer.js +136 -0
  12. package/dist/utils/cookieSanitizer.js.map +1 -0
  13. package/dist/utils/operationRouter.d.ts +25 -0
  14. package/dist/utils/operationRouter.d.ts.map +1 -0
  15. package/dist/utils/operationRouter.js +120 -0
  16. package/dist/utils/operationRouter.js.map +1 -0
  17. package/dist/utils/types.d.ts +944 -0
  18. package/dist/utils/types.d.ts.map +1 -0
  19. package/dist/utils/types.js +25 -0
  20. package/dist/utils/types.js.map +1 -0
  21. package/dist/vtexClient.d.ts +173 -0
  22. package/dist/vtexClient.d.ts.map +1 -0
  23. package/dist/vtexClient.js +242 -0
  24. package/dist/vtexClient.js.map +1 -0
  25. package/package.json +16 -57
  26. package/src/index.ts +11 -29
  27. package/src/utils/constants.ts +2 -56
  28. package/src/utils/cookieSanitizer.ts +59 -59
  29. package/src/utils/operationRouter.ts +75 -65
  30. package/src/utils/types.ts +709 -1557
  31. package/src/vtexClient.ts +358 -347
  32. package/src/README.md +0 -6
  33. package/src/__conformance__/upstream.test.ts +0 -158
  34. package/src/__tests__/client-segment-cookie.test.ts +0 -255
  35. package/src/__tests__/client-set-cookie-forward.test.ts +0 -257
  36. package/src/__tests__/schemas.test.ts +0 -71
  37. package/src/actions/address.ts +0 -250
  38. package/src/actions/analytics/sendEvent.ts +0 -86
  39. package/src/actions/auth.ts +0 -333
  40. package/src/actions/checkout.ts +0 -659
  41. package/src/actions/index.ts +0 -11
  42. package/src/actions/masterData.ts +0 -168
  43. package/src/actions/misc.ts +0 -188
  44. package/src/actions/newsletter.ts +0 -105
  45. package/src/actions/orders.ts +0 -34
  46. package/src/actions/profile.ts +0 -201
  47. package/src/actions/session.ts +0 -85
  48. package/src/actions/trigger.ts +0 -43
  49. package/src/actions/wishlist.ts +0 -114
  50. package/src/client.ts +0 -724
  51. package/src/commerceLoaders.ts +0 -267
  52. package/src/hooks/__tests__/createCart.render.test.ts +0 -114
  53. package/src/hooks/__tests__/createCart.test.ts +0 -92
  54. package/src/hooks/__tests__/createUseCart.test.ts +0 -184
  55. package/src/hooks/__tests__/createUseUser.test.ts +0 -48
  56. package/src/hooks/__tests__/createUseWishlist.test.ts +0 -87
  57. package/src/hooks/cartQuery.ts +0 -204
  58. package/src/hooks/createCart.ts +0 -427
  59. package/src/hooks/createUseCart.ts +0 -360
  60. package/src/hooks/createUseUser.ts +0 -153
  61. package/src/hooks/createUseWishlist.ts +0 -242
  62. package/src/hooks/index.ts +0 -25
  63. package/src/hooks/useAutocomplete.ts +0 -83
  64. package/src/hooks/useCart.ts +0 -243
  65. package/src/hooks/useUser.ts +0 -78
  66. package/src/hooks/useWishlist.ts +0 -119
  67. package/src/invoke.ts +0 -235
  68. package/src/loaders/ProductDetailsPage.ts +0 -1
  69. package/src/loaders/ProductList.ts +0 -1
  70. package/src/loaders/ProductListingPage.ts +0 -1
  71. package/src/loaders/__tests__/legacyProductList.test.ts +0 -157
  72. package/src/loaders/address.ts +0 -115
  73. package/src/loaders/autocomplete.ts +0 -56
  74. package/src/loaders/brands.ts +0 -44
  75. package/src/loaders/cart/attachments.ts +0 -53
  76. package/src/loaders/cart/full.ts +0 -67
  77. package/src/loaders/cart/gifts.ts +0 -43
  78. package/src/loaders/cart/orderFormId.ts +0 -31
  79. package/src/loaders/cart/shipping.test.ts +0 -125
  80. package/src/loaders/cart/shipping.ts +0 -136
  81. package/src/loaders/cart/summary.ts +0 -36
  82. package/src/loaders/cart.ts +0 -52
  83. package/src/loaders/catalog.ts +0 -163
  84. package/src/loaders/collections.ts +0 -55
  85. package/src/loaders/index.ts +0 -20
  86. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +0 -51
  87. package/src/loaders/intelligentSearch/productDetailsPage.ts +0 -111
  88. package/src/loaders/intelligentSearch/productList.ts +0 -170
  89. package/src/loaders/intelligentSearch/productListingPage.ts +0 -525
  90. package/src/loaders/intelligentSearch/suggestions.ts +0 -44
  91. package/src/loaders/legacy/productDetailsPage.ts +0 -1
  92. package/src/loaders/legacy/productList.ts +0 -1
  93. package/src/loaders/legacy/relatedProductsLoader.ts +0 -79
  94. package/src/loaders/legacy.ts +0 -710
  95. package/src/loaders/logistics.ts +0 -111
  96. package/src/loaders/minicart.ts +0 -78
  97. package/src/loaders/navbar.ts +0 -26
  98. package/src/loaders/orders.ts +0 -92
  99. package/src/loaders/pageType.ts +0 -68
  100. package/src/loaders/paths/PDPDefaultPath.ts +0 -51
  101. package/src/loaders/payment.ts +0 -97
  102. package/src/loaders/productListFull.ts +0 -157
  103. package/src/loaders/profile.ts +0 -138
  104. package/src/loaders/promotion.ts +0 -30
  105. package/src/loaders/search.ts +0 -124
  106. package/src/loaders/session.ts +0 -83
  107. package/src/loaders/user.ts +0 -87
  108. package/src/loaders/wishlist.ts +0 -89
  109. package/src/loaders/wishlistProducts.ts +0 -69
  110. package/src/loaders/workflow/products.ts +0 -62
  111. package/src/loaders/workflow.ts +0 -316
  112. package/src/logo.png +0 -0
  113. package/src/manifest.gen.ts +0 -89
  114. package/src/middleware.cacheHeaders.test.ts +0 -44
  115. package/src/middleware.ts +0 -240
  116. package/src/mod.ts +0 -173
  117. package/src/registry.ts +0 -9
  118. package/src/schemas.gen.ts +0 -3191
  119. package/src/schemas.ts +0 -30
  120. package/src/types.ts +0 -248
  121. package/src/utils/__tests__/cartProjection.test.ts +0 -118
  122. package/src/utils/__tests__/cookieSanitizer.test.ts +0 -162
  123. package/src/utils/__tests__/fetch.test.ts +0 -80
  124. package/src/utils/__tests__/fetchCache.test.ts +0 -155
  125. package/src/utils/__tests__/instrumentedFetch.test.ts +0 -158
  126. package/src/utils/__tests__/intelligentSearch.test.ts +0 -88
  127. package/src/utils/__tests__/minicart.test.ts +0 -184
  128. package/src/utils/__tests__/operationRouter.test.ts +0 -227
  129. package/src/utils/__tests__/resilience.test.ts +0 -205
  130. package/src/utils/__tests__/resourceRange.test.ts +0 -32
  131. package/src/utils/__tests__/simulationCache.test.ts +0 -40
  132. package/src/utils/__tests__/sitemap.test.ts +0 -185
  133. package/src/utils/__tests__/slugify.test.ts +0 -31
  134. package/src/utils/__tests__/storefrontBaseUrl.test.ts +0 -61
  135. package/src/utils/__tests__/transform.test.ts +0 -1111
  136. package/src/utils/accountLoaders.ts +0 -203
  137. package/src/utils/authHelpers.ts +0 -85
  138. package/src/utils/batch.ts +0 -18
  139. package/src/utils/buildOfferShelf.test.ts +0 -59
  140. package/src/utils/cartProjection.ts +0 -96
  141. package/src/utils/cookies.ts +0 -167
  142. package/src/utils/enrichment.ts +0 -556
  143. package/src/utils/fetch.ts +0 -111
  144. package/src/utils/fetchCache.ts +0 -55
  145. package/src/utils/index.ts +0 -19
  146. package/src/utils/instrumentedFetch.ts +0 -98
  147. package/src/utils/intelligentSearch.ts +0 -96
  148. package/src/utils/legacy.ts +0 -139
  149. package/src/utils/minicart.ts +0 -139
  150. package/src/utils/pickAndOmit.ts +0 -25
  151. package/src/utils/proxy.ts +0 -495
  152. package/src/utils/resilience.ts +0 -352
  153. package/src/utils/resourceRange.ts +0 -10
  154. package/src/utils/segment.ts +0 -152
  155. package/src/utils/similars.ts +0 -35
  156. package/src/utils/simulationCache.ts +0 -73
  157. package/src/utils/sitemap.ts +0 -283
  158. package/src/utils/slugCache.ts +0 -28
  159. package/src/utils/slugify.ts +0 -13
  160. package/src/utils/transform.ts +0 -1691
  161. package/src/utils/vtexId.ts +0 -151
  162. package/src/vtexClient.test.ts +0 -204
  163. package/tsconfig.json +0 -7
@@ -1,119 +0,0 @@
1
- /**
2
- * Client-side wishlist hook for VTEX.
3
- *
4
- * Reads the wishlist via the invoke proxy and provides
5
- * add/remove mutations with automatic cache invalidation.
6
- *
7
- * @example
8
- * ```tsx
9
- * import { useWishlist } from "@decocms/apps/vtex/hooks/useWishlist";
10
- *
11
- * function WishlistButton({ productId, sku }: Props) {
12
- * const { isInWishlist, toggle, isLoading } = useWishlist();
13
- * const wishlisted = isInWishlist(productId);
14
- * return (
15
- * <button onClick={() => toggle({ productId, sku })} disabled={isLoading}>
16
- * {wishlisted ? "♥" : "♡"}
17
- * </button>
18
- * );
19
- * }
20
- * ```
21
- */
22
-
23
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
24
-
25
- export interface WishlistItem {
26
- id: string;
27
- productId: string;
28
- sku: string;
29
- }
30
-
31
- const WISHLIST_QUERY_KEY = ["vtex", "wishlist"] as const;
32
-
33
- async function fetchWishlist(): Promise<WishlistItem[]> {
34
- const res = await fetch("/deco/invoke/vtex/loaders/wishlist", {
35
- method: "POST",
36
- headers: { "Content-Type": "application/json" },
37
- body: JSON.stringify({}),
38
- credentials: "include",
39
- });
40
- if (!res.ok) {
41
- if (res.status === 401) return [];
42
- throw new Error(`Wishlist fetch failed: ${res.status}`);
43
- }
44
- return res.json();
45
- }
46
-
47
- async function addToWishlist(item: { productId: string; sku: string }): Promise<void> {
48
- const res = await fetch("/deco/invoke/vtex/actions/wishlist/addItem", {
49
- method: "POST",
50
- headers: { "Content-Type": "application/json" },
51
- body: JSON.stringify(item),
52
- credentials: "include",
53
- });
54
- if (!res.ok) throw new Error(`Add to wishlist failed: ${res.status}`);
55
- }
56
-
57
- async function removeFromWishlist(id: string): Promise<void> {
58
- const res = await fetch("/deco/invoke/vtex/actions/wishlist/removeItem", {
59
- method: "POST",
60
- headers: { "Content-Type": "application/json" },
61
- body: JSON.stringify({ id }),
62
- credentials: "include",
63
- });
64
- if (!res.ok) throw new Error(`Remove from wishlist failed: ${res.status}`);
65
- }
66
-
67
- export interface UseWishlistOptions {
68
- enabled?: boolean;
69
- staleTime?: number;
70
- }
71
-
72
- export function useWishlist(options?: UseWishlistOptions) {
73
- const queryClient = useQueryClient();
74
-
75
- const query = useQuery({
76
- queryKey: WISHLIST_QUERY_KEY,
77
- queryFn: fetchWishlist,
78
- staleTime: options?.staleTime ?? 60_000,
79
- enabled: options?.enabled !== false,
80
- });
81
-
82
- const items = query.data ?? [];
83
- const productIdSet = new Set(items.map((i) => i.productId));
84
-
85
- const addMutation = useMutation({
86
- mutationFn: addToWishlist,
87
- onSuccess: () => queryClient.invalidateQueries({ queryKey: WISHLIST_QUERY_KEY }),
88
- });
89
-
90
- const removeMutation = useMutation({
91
- mutationFn: removeFromWishlist,
92
- onSuccess: () => queryClient.invalidateQueries({ queryKey: WISHLIST_QUERY_KEY }),
93
- });
94
-
95
- function isInWishlist(productId: string): boolean {
96
- return productIdSet.has(productId);
97
- }
98
-
99
- function toggle(item: { productId: string; sku: string }) {
100
- const existing = items.find((i) => i.productId === item.productId);
101
- if (existing) {
102
- removeMutation.mutate(existing.id);
103
- } else {
104
- addMutation.mutate(item);
105
- }
106
- }
107
-
108
- return {
109
- items,
110
- isLoading: query.isLoading || addMutation.isPending || removeMutation.isPending,
111
- isError: query.isError,
112
- isInWishlist,
113
- toggle,
114
- add: addMutation,
115
- remove: removeMutation,
116
- refetch: query.refetch,
117
- count: items.length,
118
- };
119
- }
package/src/invoke.ts DELETED
@@ -1,235 +0,0 @@
1
- /**
2
- * Typed `invoke.vtex.actions.*` object — generator contract.
3
- *
4
- * This file is the source of truth that `@decocms/start/scripts/generate-invoke.ts`
5
- * scans to emit the site-local `src/server/invoke.gen.ts`. The generator:
6
- *
7
- * 1. parses the imports in this file to learn which action lives where,
8
- * 2. walks `invoke.vtex.actions.*` and extracts each entry's:
9
- * - validated input type (the arrow function's first parameter),
10
- * - imported action function (matched against the import map by name),
11
- * - output type (the outermost `as` cast's `Promise<...>` payload),
12
- * 3. emits a top-level `createServerFn` per action so TanStack Start's
13
- * compiler can transform `.handler()` into client RPC stubs (the
14
- * compiler only walks top-level decls, not factory-returned ones).
15
- *
16
- * Every action gets a `forwardResponseCookies()` call in the generated
17
- * handler — that bridges `Set-Cookie` headers captured by
18
- * `vtexFetchWithCookies` into TanStack Start's HTTP response. Without it,
19
- * `checkout.vtex.com` and `CheckoutOrderFormOwnership` never reach the
20
- * browser, and the storefront's mini-cart drifts away from VTEX's
21
- * server-side orderForm.
22
- *
23
- * To add a new action:
24
- * 1. Add an entry below with the input/output types and the action call,
25
- * 2. From a site repo: `npm run generate:invoke`.
26
- */
27
-
28
- import type { CartProjection, CartSection } from "@decocms/apps-commerce/types";
29
- import { createInvokeFn } from "@decocms/tanstack/sdk/createInvoke";
30
- import {
31
- addCouponToCart,
32
- addCouponToCartV2,
33
- addItemsToCart,
34
- addItemsToCartV2,
35
- getOrCreateCart,
36
- getOrCreateCartV2,
37
- getSellersByRegion,
38
- type RegionResult,
39
- type SimulationItem,
40
- setShippingPostalCode,
41
- simulateCart,
42
- updateCartItems,
43
- updateCartItemsV2,
44
- updateOrderFormAttachment,
45
- } from "./actions/checkout";
46
- import { type NotifyMeProps, notifyMe } from "./actions/misc";
47
- import { type SubscribeProps, subscribe } from "./actions/newsletter";
48
- import { createSession, editSession, type SessionData } from "./actions/session";
49
- import type { OrderForm } from "./types";
50
- import type { VtexCartProjectionResult } from "./utils/cartProjection";
51
-
52
- // ---------------------------------------------------------------------------
53
- // invoke.vtex.actions — typed server functions callable from client
54
- //
55
- // Action bodies receive the validated input object directly and pass it
56
- // straight to the action function (which expects a single `props` object).
57
- // The arrow function body is what the generator parses for "which action
58
- // is this entry calling" — keep the call site shaped as `actionName(data)`
59
- // so the matcher in `generate-invoke.ts` picks the right importedFn.
60
- // ---------------------------------------------------------------------------
61
-
62
- export const invoke = {
63
- vtex: {
64
- actions: {
65
- // -- Cart (OrderForm CRUD) --------------------------------------------
66
-
67
- getOrCreateCart: createInvokeFn((data: { orderFormId?: string }) =>
68
- getOrCreateCart(data),
69
- ) as unknown as (ctx: { data: { orderFormId?: string } }) => Promise<OrderForm>,
70
-
71
- addItemsToCart: createInvokeFn(
72
- (data: {
73
- orderFormId: string;
74
- orderItems: Array<{
75
- id: string;
76
- seller: string;
77
- quantity: number;
78
- }>;
79
- }) => addItemsToCart(data),
80
- ) as unknown as (ctx: {
81
- data: {
82
- orderFormId: string;
83
- orderItems: Array<{
84
- id: string;
85
- seller: string;
86
- quantity: number;
87
- }>;
88
- };
89
- }) => Promise<OrderForm>,
90
-
91
- updateCartItems: createInvokeFn(
92
- (data: { orderFormId: string; orderItems: Array<{ index: number; quantity: number }> }) =>
93
- updateCartItems(data),
94
- ) as unknown as (ctx: {
95
- data: { orderFormId: string; orderItems: Array<{ index: number; quantity: number }> };
96
- }) => Promise<OrderForm>,
97
-
98
- addCouponToCart: createInvokeFn((data: { orderFormId: string; text: string }) =>
99
- addCouponToCart(data),
100
- ) as unknown as (ctx: { data: { orderFormId: string; text: string } }) => Promise<OrderForm>,
101
-
102
- // -- Cart v2 (fragmented sections + projected response) ----------------
103
-
104
- getOrCreateCartV2: createInvokeFn(
105
- (data: { orderFormId?: string; projection?: CartProjection; sections?: CartSection[] }) =>
106
- getOrCreateCartV2(data),
107
- ) as unknown as (ctx: {
108
- data: { orderFormId?: string; projection?: CartProjection; sections?: CartSection[] };
109
- }) => Promise<VtexCartProjectionResult>,
110
-
111
- addItemsToCartV2: createInvokeFn(
112
- (data: {
113
- orderFormId: string;
114
- orderItems: Array<{ id: string; seller: string; quantity: number }>;
115
- projection?: CartProjection;
116
- sections?: CartSection[];
117
- }) => addItemsToCartV2(data),
118
- ) as unknown as (ctx: {
119
- data: {
120
- orderFormId: string;
121
- orderItems: Array<{ id: string; seller: string; quantity: number }>;
122
- projection?: CartProjection;
123
- sections?: CartSection[];
124
- };
125
- }) => Promise<VtexCartProjectionResult>,
126
-
127
- updateCartItemsV2: createInvokeFn(
128
- (data: {
129
- orderFormId: string;
130
- orderItems: Array<{ index: number; quantity: number }>;
131
- projection?: CartProjection;
132
- sections?: CartSection[];
133
- }) => updateCartItemsV2(data),
134
- ) as unknown as (ctx: {
135
- data: {
136
- orderFormId: string;
137
- orderItems: Array<{ index: number; quantity: number }>;
138
- projection?: CartProjection;
139
- sections?: CartSection[];
140
- };
141
- }) => Promise<VtexCartProjectionResult>,
142
-
143
- addCouponToCartV2: createInvokeFn(
144
- (data: {
145
- orderFormId: string;
146
- text: string;
147
- projection?: CartProjection;
148
- sections?: CartSection[];
149
- }) => addCouponToCartV2(data),
150
- ) as unknown as (ctx: {
151
- data: {
152
- orderFormId: string;
153
- text: string;
154
- projection?: CartProjection;
155
- sections?: CartSection[];
156
- };
157
- }) => Promise<VtexCartProjectionResult>,
158
-
159
- simulateCart: createInvokeFn(
160
- (data: { items: SimulationItem[]; postalCode: string; country?: string }) =>
161
- simulateCart(data),
162
- ),
163
-
164
- // -- Shipping / Region ------------------------------------------------
165
-
166
- getSellersByRegion: createInvokeFn((data: { postalCode: string; salesChannel?: string }) =>
167
- getSellersByRegion(data),
168
- ) as unknown as (ctx: {
169
- data: { postalCode: string; salesChannel?: string };
170
- }) => Promise<RegionResult | null>,
171
-
172
- setShippingPostalCode: createInvokeFn(
173
- (data: { orderFormId: string; postalCode: string; country?: string }) =>
174
- setShippingPostalCode(data),
175
- ) as unknown as (ctx: {
176
- data: { orderFormId: string; postalCode: string; country?: string };
177
- }) => Promise<boolean>,
178
-
179
- updateOrderFormAttachment: createInvokeFn(
180
- (data: { orderFormId: string; attachment: string; body: Record<string, unknown> }) =>
181
- updateOrderFormAttachment(data),
182
- ) as unknown as (ctx: {
183
- data: { orderFormId: string; attachment: string; body: Record<string, unknown> };
184
- }) => Promise<OrderForm>,
185
-
186
- // -- Session ----------------------------------------------------------
187
-
188
- createSession: createInvokeFn((data: Record<string, any>) => createSession({ data })),
189
-
190
- editSession: createInvokeFn((data: { public: Record<string, { value: string }> }) =>
191
- editSession(data),
192
- ) as unknown as (ctx: {
193
- data: { public: Record<string, { value: string }> };
194
- }) => Promise<SessionData>,
195
-
196
- // -- MasterData -------------------------------------------------------
197
- //
198
- // Generic MasterData CRUD (createDocument / getDocument / patchDocument /
199
- // searchDocuments / uploadAttachment) is deliberately NOT exposed here.
200
- //
201
- // Every entry in this object is emitted by @decocms/blocks-cli's
202
- // generate-invoke.ts as a top-level createServerFn, which TanStack Start
203
- // compiles into a public, unauthenticated /_serverFn/<hash> endpoint. A
204
- // generic action that takes a client-supplied `entity` and proxies to the
205
- // VTEX MasterData API with the app's admin appKey/appToken lets any caller
206
- // read/enumerate/write arbitrary entities — e.g. searchDocuments({ entity:
207
- // "CL", ... }) dumps the Clients (customer PII) table. Authentication alone
208
- // can't make this safe: the generic (entity, filter) shape has no room for
209
- // per-entity/per-record authorization.
210
- //
211
- // The underlying functions remain exported from ./actions/masterData for
212
- // SERVER-SIDE use. A site that needs MasterData defines a narrow,
213
- // entity-pinned, validated action in its own src/server/invoke.ts: pin
214
- // `entity` server-side (never take it from the client), validate input, and
215
- // for user-scoped data derive identity from the session cookie
216
- // (parseVtexAuthJwt) rather than a client-supplied id. See `subscribe` /
217
- // `notifyMe` below for the purpose-specific pattern.
218
- //
219
- // generate-invoke.ts also carries a PRIVILEGED_ACTIONS guard that refuses
220
- // to emit these names even if re-added here, so this stays enforced.
221
-
222
- // -- Newsletter -------------------------------------------------------
223
-
224
- subscribe: createInvokeFn((data: SubscribeProps) => subscribe(data)) as unknown as (ctx: {
225
- data: SubscribeProps;
226
- }) => Promise<void>,
227
-
228
- // -- Misc -------------------------------------------------------------
229
-
230
- notifyMe: createInvokeFn((data: NotifyMeProps) => notifyMe(data)) as unknown as (ctx: {
231
- data: NotifyMeProps;
232
- }) => Promise<void>,
233
- },
234
- },
235
- } as const;
@@ -1 +0,0 @@
1
- export { default, type PDPProps } from "./intelligentSearch/productDetailsPage";
@@ -1 +0,0 @@
1
- export { default, type ProductListProps } from "./intelligentSearch/productList";
@@ -1 +0,0 @@
1
- export { default, type PLPProps } from "./intelligentSearch/productListingPage";
@@ -1,157 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- // `legacyProductList` is the SHELF loader. It accepted no payload-shaping
4
- // options, so every shelf paid the full product for every entry — measured on a
5
- // real home shelf (`count: 28`): 1095 KB, 39.1 KB per product, of which
6
- // `isVariantOf` 19.1 KB and the 48-rung `offers.priceSpecification` 12.8 KB.
7
- // `legacyProductListingPage` already took them; this file pins the pass-through,
8
- // because declaring the options WITHOUT forwarding them is exactly the bug the
9
- // PLP loader had.
10
-
11
- vi.mock("../../client", () => ({
12
- getVtexConfig: () => ({ account: "test", salesChannel: "1" }),
13
- vtexFetch: vi.fn(),
14
- vtexFetchResponse: vi.fn(),
15
- }));
16
-
17
- import { vtexFetch } from "../../client";
18
- import { legacyProductList } from "../legacy";
19
-
20
- const sellers = (price: number) => [
21
- {
22
- sellerId: "1",
23
- sellerName: "Seller One",
24
- sellerDefault: true,
25
- commertialOffer: {
26
- AvailableQuantity: 5,
27
- Price: price,
28
- ListPrice: price + 30,
29
- PriceWithoutDiscount: price + 30,
30
- spotPrice: price,
31
- PriceValidUntil: "2025-12-31",
32
- // A real ladder: every method the store accepts, every installment count.
33
- Installments: ["Visa", "Master", "Amex", "Boleto"].flatMap((method) =>
34
- Array.from({ length: 4 }, (_, i) => ({
35
- Value: price / (i + 1),
36
- NumberOfInstallments: i + 1,
37
- Name: `${method} ${i + 1}x`,
38
- InterestRate: 0,
39
- TotalValuePlusInterestRate: price,
40
- PaymentSystemName: method,
41
- })),
42
- ),
43
- GiftSkuIds: [],
44
- teasers: [],
45
- },
46
- },
47
- ];
48
-
49
- const sku = (itemId: string, price: number) => ({
50
- itemId,
51
- name: `SKU ${itemId}`,
52
- nameComplete: `SKU ${itemId}`,
53
- complementName: "",
54
- ean: "1234567890123",
55
- referenceId: [{ Key: "RefId", Value: `REF-${itemId}` }],
56
- images: Array.from({ length: 4 }, (_, i) => ({
57
- imageId: `${itemId}-${i}`,
58
- imageUrl: `https://img.com/${itemId}-${i}.jpg`,
59
- imageText: `img${i}`,
60
- imageLabel: `label${i}`,
61
- })),
62
- sellers: sellers(price),
63
- Videos: [],
64
- estimatedDateArrival: null,
65
- measurementUnit: "un",
66
- unitMultiplier: 1,
67
- variations: [],
68
- attachments: [],
69
- isKit: false,
70
- });
71
-
72
- const legacyProduct = () => ({
73
- productId: "PROD1",
74
- productName: "Test Product",
75
- brand: "TestBrand",
76
- brandId: 1,
77
- brandImageUrl: null,
78
- linkText: "test-product",
79
- productReference: "REF1",
80
- categoryId: "1",
81
- productTitle: "Test Product",
82
- metaTagDescription: "meta",
83
- clusterHighlights: {},
84
- productClusters: {},
85
- searchableClusters: {},
86
- categories: ["/Electronics/"],
87
- categoriesIds: ["/1/"],
88
- link: "https://test/test-product/p",
89
- description: "x".repeat(2000),
90
- items: [sku("SKU1", 90), sku("SKU2", 60), sku("SKU3", 120)],
91
- allSpecifications: [],
92
- allSpecificationsGroups: [],
93
- skuSpecifications: [],
94
- releaseDate: "2024-01-01",
95
- });
96
-
97
- const opts = { query: { count: 1 } as any, baseUrl: "https://example.com" };
98
- const ladderOf = (p: any) => p?.offers?.offers?.[0]?.priceSpecification ?? [];
99
-
100
- describe("legacyProductList — payload-shaping options reach toProduct", () => {
101
- beforeEach(() => {
102
- (vtexFetch as any).mockReset();
103
- (vtexFetch as any).mockResolvedValue([legacyProduct()]);
104
- });
105
-
106
- it("no options: full product, full ladder, every variant, every image", async () => {
107
- const [p] = (await legacyProductList(opts)) as any[];
108
- expect(ladderOf(p).length).toBeGreaterThan(10);
109
- expect(p.isVariantOf.hasVariant).toHaveLength(3);
110
- expect(ladderOf(p.isVariantOf.hasVariant[0]).length).toBeGreaterThan(10);
111
- expect(p.image).toHaveLength(4);
112
- expect(p.description).toBeTruthy();
113
- });
114
-
115
- it("leanVariants empties the ladder on every variant", async () => {
116
- const [p] = (await legacyProductList({ ...opts, leanVariants: true })) as any[];
117
- for (const v of p.isVariantOf.hasVariant) expect(ladderOf(v)).toEqual([]);
118
- });
119
-
120
- it("displayedVariantId keeps the ladder on the one variant the card renders", async () => {
121
- const [p] = (await legacyProductList({
122
- ...opts,
123
- leanVariants: true,
124
- displayedVariantId: (items: any[]) => items[1].itemId,
125
- })) as any[];
126
- const kept = p.isVariantOf.hasVariant.find((v: any) => v.sku === "SKU2");
127
- expect(ladderOf(kept).length).toBeGreaterThan(0);
128
- // ...and it is the LEAN shape, not a second full product.
129
- expect(kept.description).toBeUndefined();
130
- });
131
-
132
- it("priceSpecifications rewrites the root ladder with the caller's rule", async () => {
133
- const [p] = (await legacyProductList({
134
- ...opts,
135
- priceSpecifications: (specs) => specs.filter((s) => !s.priceComponentType),
136
- })) as any[];
137
- expect(ladderOf(p).every((s: any) => !s.priceComponentType)).toBe(true);
138
- expect(ladderOf(p).length).toBeLessThan(10);
139
- });
140
-
141
- it("maxImages caps image[] by position", async () => {
142
- const [p] = (await legacyProductList({ ...opts, maxImages: 2 })) as any[];
143
- expect(p.image).toHaveLength(2);
144
- });
145
-
146
- it("every option absent is byte-for-byte the previous output", async () => {
147
- const [before] = (await legacyProductList(opts)) as any[];
148
- const [after] = (await legacyProductList({
149
- ...opts,
150
- leanVariants: undefined,
151
- displayedVariantId: undefined,
152
- maxImages: undefined,
153
- priceSpecifications: undefined,
154
- })) as any[];
155
- expect(JSON.stringify(after)).toBe(JSON.stringify(before));
156
- });
157
- });
@@ -1,115 +0,0 @@
1
- /**
2
- * VTEX Address API loaders.
3
- * Pure async functions — require configureVtex() to have been called.
4
- *
5
- * Ported from deco-cx/apps:
6
- * vtex/loaders/address/getAddressByPostalCode.ts
7
- * vtex/loaders/address/getUserAddresses.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
- export interface PostalAddress {
18
- "@type": "PostalAddress";
19
- postalCode?: string;
20
- addressLocality?: string;
21
- addressRegion?: string;
22
- addressCountry?: string;
23
- streetAddress?: string;
24
- identifier?: string;
25
- areaServed?: string;
26
- description?: string;
27
- disambiguatingDescription?: string;
28
- latitude?: number;
29
- longitude?: number;
30
- }
31
-
32
- export interface VtexAddress {
33
- addressId: string;
34
- addressType: string;
35
- addressName: string;
36
- city: string;
37
- complement: string;
38
- country: string;
39
- neighborhood: string;
40
- number: string;
41
- postalCode: string;
42
- geoCoordinates: number[];
43
- receiverName: string;
44
- state: string;
45
- street: string;
46
- }
47
-
48
- // ---------------------------------------------------------------------------
49
- // getAddressByPostalCode
50
- // ---------------------------------------------------------------------------
51
-
52
- /**
53
- * Look up a postal address by country + postal code (public API).
54
- * @see https://developers.vtex.com/docs/api-reference/checkout-api#get-/api/checkout/pub/postal-code/-countryCode-/-postalCode-
55
- */
56
- export async function getAddressByPostalCode(
57
- countryCode: string,
58
- postalCode: string,
59
- ): Promise<PostalAddress> {
60
- const data = await vtexFetch<Record<string, any>>(
61
- `/api/checkout/pub/postal-code/${countryCode}/${postalCode}`,
62
- );
63
-
64
- return {
65
- "@type": "PostalAddress",
66
- postalCode: data.postalCode,
67
- addressLocality: data.city,
68
- addressRegion: data.state,
69
- addressCountry: data.country,
70
- streetAddress: data.street || undefined,
71
- identifier: data.number || undefined,
72
- areaServed: data.neighborhood || undefined,
73
- description: data.complement || undefined,
74
- disambiguatingDescription: data.reference || undefined,
75
- latitude: data.geoCoordinates?.[0] ?? undefined,
76
- longitude: data.geoCoordinates?.[1] ?? undefined,
77
- };
78
- }
79
-
80
- // ---------------------------------------------------------------------------
81
- // getUserAddresses (authenticated — VTEX IO GraphQL)
82
- // ---------------------------------------------------------------------------
83
-
84
- const USER_ADDRESSES_QUERY = `query Addresses @context(scope: "private") {
85
- profile {
86
- cacheId
87
- addresses {
88
- addressId: id
89
- addressType
90
- addressName
91
- city
92
- complement
93
- country
94
- neighborhood
95
- number
96
- postalCode
97
- geoCoordinates
98
- receiverName
99
- state
100
- street
101
- }
102
- }
103
- }`;
104
-
105
- /**
106
- * Fetch addresses for the currently authenticated user.
107
- * Requires a valid VTEX auth cookie.
108
- */
109
- export async function getUserAddresses(authCookie: string): Promise<VtexAddress[]> {
110
- const { profile } = await vtexIOGraphQL<{
111
- profile: { addresses: VtexAddress[] };
112
- }>({ query: USER_ADDRESSES_QUERY }, { cookie: authCookie });
113
-
114
- return profile?.addresses ?? [];
115
- }
@@ -1,56 +0,0 @@
1
- /**
2
- * VTEX Intelligent Search autocomplete — suggestions + product results.
3
- *
4
- * Combines /autocomplete_suggestions/ and /product_search/ in parallel,
5
- * transforms IS products to schema.org via pickSku + toProduct.
6
- */
7
- import { getVtexConfig, intelligentSearch as vtexIS, storefrontBaseUrl } from "../client";
8
- import { pickSku, toProduct as toSchemaProduct } from "../utils/transform";
9
-
10
- export interface AutocompleteProps {
11
- query: string;
12
- count?: number;
13
- showSponsored?: boolean;
14
- placement?: string;
15
- fuzzy?: string;
16
- }
17
-
18
- export interface AutocompleteResult {
19
- searches: Array<{ term: string; count: number; attributes?: any[] }>;
20
- products: any[];
21
- }
22
-
23
- export async function autocompleteSearch(props: AutocompleteProps): Promise<AutocompleteResult> {
24
- const query = props.query || "";
25
- const count = props.count ?? 4;
26
- if (!query.trim()) return { searches: [], products: [] };
27
-
28
- try {
29
- const [suggestionsData, productsData] = await Promise.all([
30
- vtexIS<{
31
- searches: Array<{ term: string; count: number; attributes?: any[] }>;
32
- }>("/autocomplete_suggestions/", { query }),
33
- vtexIS<{ products: any[] }>("/product_search/", {
34
- query,
35
- count: String(count),
36
- showSponsored: props.showSponsored !== false ? "true" : "false",
37
- placement: props.placement ?? "top-search",
38
- fuzzy: props.fuzzy ?? "0",
39
- }),
40
- ]);
41
-
42
- const config = getVtexConfig();
43
- const baseUrl = storefrontBaseUrl(config);
44
-
45
- return {
46
- searches: suggestionsData.searches ?? [],
47
- products: (productsData.products ?? []).slice(0, count).map((p: any) => {
48
- const sku = pickSku(p);
49
- return toSchemaProduct(p, sku, 0, { baseUrl, priceCurrency: "BRL" });
50
- }),
51
- };
52
- } catch (error) {
53
- console.error("[vtex] autocompleteSearch error:", error);
54
- return { searches: [], products: [] };
55
- }
56
- }