@decocms/apps 0.23.3 → 0.25.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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/commerce/components/Image.tsx +129 -143
  3. package/commerce/components/JsonLd.tsx +192 -201
  4. package/commerce/components/Picture.tsx +65 -75
  5. package/commerce/sdk/analytics.ts +15 -15
  6. package/commerce/sdk/formatPrice.ts +13 -16
  7. package/commerce/sdk/url.ts +7 -7
  8. package/commerce/sdk/useOffer.ts +46 -57
  9. package/commerce/sdk/useVariantPossibilities.ts +25 -25
  10. package/commerce/types/commerce.ts +868 -875
  11. package/commerce/utils/canonical.ts +5 -8
  12. package/commerce/utils/constants.ts +5 -6
  13. package/commerce/utils/filters.ts +4 -4
  14. package/commerce/utils/productToAnalyticsItem.ts +52 -56
  15. package/commerce/utils/stateByZip.ts +42 -42
  16. package/package.json +24 -4
  17. package/shopify/actions/cart/addItems.ts +24 -25
  18. package/shopify/actions/cart/updateCoupons.ts +19 -20
  19. package/shopify/actions/cart/updateItems.ts +19 -20
  20. package/shopify/actions/user/signIn.ts +25 -30
  21. package/shopify/actions/user/signUp.ts +19 -24
  22. package/shopify/client.ts +24 -24
  23. package/shopify/index.ts +20 -18
  24. package/shopify/init.ts +18 -21
  25. package/shopify/loaders/ProductDetailsPage.ts +16 -20
  26. package/shopify/loaders/ProductList.ts +66 -69
  27. package/shopify/loaders/ProductListingPage.ts +150 -158
  28. package/shopify/loaders/RelatedProducts.ts +24 -27
  29. package/shopify/loaders/cart.ts +53 -52
  30. package/shopify/loaders/shop.ts +22 -27
  31. package/shopify/loaders/user.ts +27 -32
  32. package/shopify/utils/admin/admin.ts +33 -34
  33. package/shopify/utils/admin/queries.ts +2 -2
  34. package/shopify/utils/cart.ts +18 -14
  35. package/shopify/utils/cookies.ts +62 -65
  36. package/shopify/utils/enums.ts +424 -424
  37. package/shopify/utils/graphql.ts +44 -55
  38. package/shopify/utils/storefront/queries.ts +24 -29
  39. package/shopify/utils/storefront/storefront.graphql.gen.ts +55 -55
  40. package/shopify/utils/transform.ts +370 -376
  41. package/shopify/utils/types.ts +118 -118
  42. package/shopify/utils/user.ts +11 -11
  43. package/shopify/utils/utils.ts +135 -140
  44. package/vtex/actions/address.ts +86 -86
  45. package/vtex/actions/auth.ts +14 -27
  46. package/vtex/actions/checkout.ts +36 -49
  47. package/vtex/actions/masterData.ts +10 -27
  48. package/vtex/actions/misc.ts +101 -111
  49. package/vtex/actions/newsletter.ts +48 -52
  50. package/vtex/actions/orders.ts +13 -16
  51. package/vtex/actions/profile.ts +55 -55
  52. package/vtex/actions/session.ts +36 -35
  53. package/vtex/actions/trigger.ts +25 -25
  54. package/vtex/actions/wishlist.ts +51 -53
  55. package/vtex/client.ts +14 -42
  56. package/vtex/hooks/index.ts +4 -4
  57. package/vtex/hooks/useAutocomplete.ts +42 -48
  58. package/vtex/hooks/useCart.ts +153 -165
  59. package/vtex/hooks/useUser.ts +40 -40
  60. package/vtex/hooks/useWishlist.ts +70 -70
  61. package/vtex/inline-loaders/productDetailsPage.ts +1 -3
  62. package/vtex/inline-loaders/productList.ts +121 -127
  63. package/vtex/inline-loaders/productListShelf.ts +159 -0
  64. package/vtex/inline-loaders/productListingPage.ts +10 -34
  65. package/vtex/inline-loaders/relatedProducts.ts +1 -3
  66. package/vtex/inline-loaders/suggestions.ts +36 -39
  67. package/vtex/inline-loaders/workflowProducts.ts +45 -49
  68. package/vtex/invoke.ts +159 -194
  69. package/vtex/loaders/address.ts +49 -54
  70. package/vtex/loaders/brands.ts +19 -26
  71. package/vtex/loaders/cart.ts +24 -21
  72. package/vtex/loaders/catalog.ts +51 -53
  73. package/vtex/loaders/collections.ts +25 -27
  74. package/vtex/loaders/legacy.ts +487 -534
  75. package/vtex/loaders/logistics.ts +33 -37
  76. package/vtex/loaders/navbar.ts +5 -8
  77. package/vtex/loaders/orders.ts +28 -39
  78. package/vtex/loaders/pageType.ts +41 -35
  79. package/vtex/loaders/payment.ts +27 -37
  80. package/vtex/loaders/profile.ts +38 -38
  81. package/vtex/loaders/promotion.ts +5 -8
  82. package/vtex/loaders/search.ts +56 -59
  83. package/vtex/loaders/session.ts +22 -30
  84. package/vtex/loaders/user.ts +39 -41
  85. package/vtex/loaders/wishlist.ts +35 -35
  86. package/vtex/loaders/wishlistProducts.ts +3 -15
  87. package/vtex/loaders/workflow.ts +220 -227
  88. package/vtex/middleware.ts +116 -119
  89. package/vtex/types.ts +201 -201
  90. package/vtex/utils/batch.ts +13 -16
  91. package/vtex/utils/cookies.ts +76 -80
  92. package/vtex/utils/enrichment.ts +62 -42
  93. package/vtex/utils/fetchCache.ts +1 -4
  94. package/vtex/utils/index.ts +6 -6
  95. package/vtex/utils/intelligentSearch.ts +48 -57
  96. package/vtex/utils/legacy.ts +108 -124
  97. package/vtex/utils/pickAndOmit.ts +15 -20
  98. package/vtex/utils/proxy.ts +136 -146
  99. package/vtex/utils/resourceRange.ts +3 -3
  100. package/vtex/utils/segment.ts +100 -111
  101. package/vtex/utils/similars.ts +1 -2
  102. package/vtex/utils/sitemap.ts +91 -91
  103. package/vtex/utils/slugCache.ts +2 -6
  104. package/vtex/utils/slugify.ts +9 -9
  105. package/vtex/utils/transform.ts +1178 -1105
  106. package/vtex/utils/types.ts +1381 -1381
  107. package/vtex/utils/vtexId.ts +44 -47
  108. package/.github/workflows/release.yml +0 -34
  109. package/.releaserc.json +0 -28
  110. package/knip.json +0 -19
  111. package/tsconfig.json +0 -11
@@ -16,204 +16,192 @@
16
16
  * ```
17
17
  */
18
18
 
19
- import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
19
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
20
20
 
21
21
  export interface CartItem {
22
- id: string;
23
- quantity: number;
24
- seller: string;
22
+ id: string;
23
+ quantity: number;
24
+ seller: string;
25
25
  }
26
26
 
27
27
  export interface OrderForm {
28
- orderFormId: string;
29
- items: CartItem[];
30
- totalizers: Array<{ id: string; name: string; value: number }>;
31
- value: number;
32
- [key: string]: unknown;
28
+ orderFormId: string;
29
+ items: CartItem[];
30
+ totalizers: Array<{ id: string; name: string; value: number }>;
31
+ value: number;
32
+ [key: string]: unknown;
33
33
  }
34
34
 
35
35
  const CART_QUERY_KEY = ["vtex", "cart"] as const;
36
36
 
37
37
  const DEFAULT_EXPECTED_SECTIONS = [
38
- "items",
39
- "totalizers",
40
- "clientProfileData",
41
- "shippingData",
42
- "paymentData",
43
- "sellers",
44
- "messages",
45
- "marketingData",
46
- "clientPreferencesData",
47
- "storePreferencesData",
48
- "giftRegistryData",
49
- "ratesAndBenefitsData",
50
- "openTextField",
51
- "commercialConditionData",
52
- "customData",
38
+ "items",
39
+ "totalizers",
40
+ "clientProfileData",
41
+ "shippingData",
42
+ "paymentData",
43
+ "sellers",
44
+ "messages",
45
+ "marketingData",
46
+ "clientPreferencesData",
47
+ "storePreferencesData",
48
+ "giftRegistryData",
49
+ "ratesAndBenefitsData",
50
+ "openTextField",
51
+ "commercialConditionData",
52
+ "customData",
53
53
  ];
54
54
 
55
55
  function getScParam(): string {
56
- if (typeof window !== "undefined") {
57
- const match = document.cookie.match(/(?:^|;\s*)VTEXSC=([^;]+)/);
58
- return match?.[1] ?? "";
59
- }
60
- return "";
56
+ if (typeof window !== "undefined") {
57
+ const match = document.cookie.match(/(?:^|;\s*)VTEXSC=([^;]+)/);
58
+ return match?.[1] ?? "";
59
+ }
60
+ return "";
61
61
  }
62
62
 
63
63
  function appendSc(url: string): string {
64
- const sc = getScParam();
65
- if (!sc) return url;
66
- return url.includes("?") ? `${url}&sc=${sc}` : `${url}?sc=${sc}`;
64
+ const sc = getScParam();
65
+ if (!sc) return url;
66
+ return url.includes("?") ? `${url}&sc=${sc}` : `${url}?sc=${sc}`;
67
67
  }
68
68
 
69
69
  async function fetchCart(): Promise<OrderForm> {
70
- const res = await fetch(appendSc("/api/checkout/pub/orderForm"), {
71
- method: "POST",
72
- headers: { "Content-Type": "application/json" },
73
- body: JSON.stringify({ expectedOrderFormSections: DEFAULT_EXPECTED_SECTIONS }),
74
- credentials: "include",
75
- });
76
- if (!res.ok) throw new Error(`Cart fetch failed: ${res.status}`);
77
- return res.json();
70
+ const res = await fetch(appendSc("/api/checkout/pub/orderForm"), {
71
+ method: "POST",
72
+ headers: { "Content-Type": "application/json" },
73
+ body: JSON.stringify({ expectedOrderFormSections: DEFAULT_EXPECTED_SECTIONS }),
74
+ credentials: "include",
75
+ });
76
+ if (!res.ok) throw new Error(`Cart fetch failed: ${res.status}`);
77
+ return res.json();
78
78
  }
79
79
 
80
80
  async function addItemsToCart(
81
- orderFormId: string,
82
- items: Array<{ id: string; quantity: number; seller: string }>,
81
+ orderFormId: string,
82
+ items: Array<{ id: string; quantity: number; seller: string }>,
83
83
  ): Promise<OrderForm> {
84
- const params = new URLSearchParams();
85
- params.append("allowedOutdatedData", "paymentData");
86
- const sc = getScParam();
87
- if (sc) params.set("sc", sc);
88
-
89
- const res = await fetch(
90
- `/api/checkout/pub/orderForm/${orderFormId}/items?${params}`,
91
- {
92
- method: "POST",
93
- headers: { "Content-Type": "application/json" },
94
- body: JSON.stringify({ orderItems: items }),
95
- credentials: "include",
96
- },
97
- );
98
- if (!res.ok) throw new Error(`Add to cart failed: ${res.status}`);
99
- return res.json();
84
+ const params = new URLSearchParams();
85
+ params.append("allowedOutdatedData", "paymentData");
86
+ const sc = getScParam();
87
+ if (sc) params.set("sc", sc);
88
+
89
+ const res = await fetch(`/api/checkout/pub/orderForm/${orderFormId}/items?${params}`, {
90
+ method: "POST",
91
+ headers: { "Content-Type": "application/json" },
92
+ body: JSON.stringify({ orderItems: items }),
93
+ credentials: "include",
94
+ });
95
+ if (!res.ok) throw new Error(`Add to cart failed: ${res.status}`);
96
+ return res.json();
100
97
  }
101
98
 
102
- async function addCouponsToCart(
103
- orderFormId: string,
104
- text: string,
105
- ): Promise<OrderForm> {
106
- const params = new URLSearchParams();
107
- const sc = getScParam();
108
- if (sc) params.set("sc", sc);
109
-
110
- const res = await fetch(
111
- `/api/checkout/pub/orderForm/${orderFormId}/coupons?${params}`,
112
- {
113
- method: "POST",
114
- headers: { "Content-Type": "application/json" },
115
- body: JSON.stringify({ text }),
116
- credentials: "include",
117
- },
118
- );
119
- if (!res.ok) throw new Error(`Add coupon failed: ${res.status}`);
120
- return res.json();
99
+ async function addCouponsToCart(orderFormId: string, text: string): Promise<OrderForm> {
100
+ const params = new URLSearchParams();
101
+ const sc = getScParam();
102
+ if (sc) params.set("sc", sc);
103
+
104
+ const res = await fetch(`/api/checkout/pub/orderForm/${orderFormId}/coupons?${params}`, {
105
+ method: "POST",
106
+ headers: { "Content-Type": "application/json" },
107
+ body: JSON.stringify({ text }),
108
+ credentials: "include",
109
+ });
110
+ if (!res.ok) throw new Error(`Add coupon failed: ${res.status}`);
111
+ return res.json();
121
112
  }
122
113
 
123
114
  async function updateItemQuantity(
124
- orderFormId: string,
125
- index: number,
126
- quantity: number,
115
+ orderFormId: string,
116
+ index: number,
117
+ quantity: number,
127
118
  ): Promise<OrderForm> {
128
- const params = new URLSearchParams();
129
- params.append("allowedOutdatedData", "paymentData");
130
- const sc = getScParam();
131
- if (sc) params.set("sc", sc);
132
-
133
- const res = await fetch(
134
- `/api/checkout/pub/orderForm/${orderFormId}/items/update?${params}`,
135
- {
136
- method: "POST",
137
- headers: { "Content-Type": "application/json" },
138
- body: JSON.stringify({ orderItems: [{ index, quantity }] }),
139
- credentials: "include",
140
- },
141
- );
142
- if (!res.ok) throw new Error(`Update quantity failed: ${res.status}`);
143
- return res.json();
119
+ const params = new URLSearchParams();
120
+ params.append("allowedOutdatedData", "paymentData");
121
+ const sc = getScParam();
122
+ if (sc) params.set("sc", sc);
123
+
124
+ const res = await fetch(`/api/checkout/pub/orderForm/${orderFormId}/items/update?${params}`, {
125
+ method: "POST",
126
+ headers: { "Content-Type": "application/json" },
127
+ body: JSON.stringify({ orderItems: [{ index, quantity }] }),
128
+ credentials: "include",
129
+ });
130
+ if (!res.ok) throw new Error(`Update quantity failed: ${res.status}`);
131
+ return res.json();
144
132
  }
145
133
 
146
134
  export interface UseCartOptions {
147
- /** Enable automatic refetching. @default true */
148
- enabled?: boolean;
149
- /** Stale time in ms. @default 0 (always refetch) */
150
- staleTime?: number;
135
+ /** Enable automatic refetching. @default true */
136
+ enabled?: boolean;
137
+ /** Stale time in ms. @default 0 (always refetch) */
138
+ staleTime?: number;
151
139
  }
152
140
 
153
141
  export function useCart(options?: UseCartOptions) {
154
- const queryClient = useQueryClient();
155
-
156
- const query = useQuery({
157
- queryKey: CART_QUERY_KEY,
158
- queryFn: fetchCart,
159
- staleTime: options?.staleTime ?? 0,
160
- enabled: options?.enabled !== false,
161
- });
162
-
163
- const addItems = useMutation({
164
- mutationFn: (items: Array<{ id: string; quantity: number; seller: string }>) => {
165
- const orderFormId = query.data?.orderFormId;
166
- if (!orderFormId) throw new Error("Cart not loaded");
167
- return addItemsToCart(orderFormId, items);
168
- },
169
- onSuccess: (data) => {
170
- queryClient.setQueryData(CART_QUERY_KEY, data);
171
- },
172
- });
173
-
174
- const updateQuantity = useMutation({
175
- mutationFn: ({ index, quantity }: { index: number; quantity: number }) => {
176
- const orderFormId = query.data?.orderFormId;
177
- if (!orderFormId) throw new Error("Cart not loaded");
178
- return updateItemQuantity(orderFormId, index, quantity);
179
- },
180
- onSuccess: (data) => {
181
- queryClient.setQueryData(CART_QUERY_KEY, data);
182
- },
183
- });
184
-
185
- const removeItem = useMutation({
186
- mutationFn: (index: number) => {
187
- const orderFormId = query.data?.orderFormId;
188
- if (!orderFormId) throw new Error("Cart not loaded");
189
- return updateItemQuantity(orderFormId, index, 0);
190
- },
191
- onSuccess: (data) => {
192
- queryClient.setQueryData(CART_QUERY_KEY, data);
193
- },
194
- });
195
-
196
- const addCoupons = useMutation({
197
- mutationFn: (text: string) => {
198
- const orderFormId = query.data?.orderFormId;
199
- if (!orderFormId) throw new Error("Cart not loaded");
200
- return addCouponsToCart(orderFormId, text);
201
- },
202
- onSuccess: (data) => {
203
- queryClient.setQueryData(CART_QUERY_KEY, data);
204
- },
205
- });
206
-
207
- return {
208
- cart: query.data ?? null,
209
- isLoading: query.isLoading,
210
- isError: query.isError,
211
- error: query.error,
212
- refetch: query.refetch,
213
- addItems,
214
- addCoupons,
215
- updateQuantity,
216
- removeItem,
217
- itemCount: query.data?.items?.length ?? 0,
218
- };
142
+ const queryClient = useQueryClient();
143
+
144
+ const query = useQuery({
145
+ queryKey: CART_QUERY_KEY,
146
+ queryFn: fetchCart,
147
+ staleTime: options?.staleTime ?? 0,
148
+ enabled: options?.enabled !== false,
149
+ });
150
+
151
+ const addItems = useMutation({
152
+ mutationFn: (items: Array<{ id: string; quantity: number; seller: string }>) => {
153
+ const orderFormId = query.data?.orderFormId;
154
+ if (!orderFormId) throw new Error("Cart not loaded");
155
+ return addItemsToCart(orderFormId, items);
156
+ },
157
+ onSuccess: (data) => {
158
+ queryClient.setQueryData(CART_QUERY_KEY, data);
159
+ },
160
+ });
161
+
162
+ const updateQuantity = useMutation({
163
+ mutationFn: ({ index, quantity }: { index: number; quantity: number }) => {
164
+ const orderFormId = query.data?.orderFormId;
165
+ if (!orderFormId) throw new Error("Cart not loaded");
166
+ return updateItemQuantity(orderFormId, index, quantity);
167
+ },
168
+ onSuccess: (data) => {
169
+ queryClient.setQueryData(CART_QUERY_KEY, data);
170
+ },
171
+ });
172
+
173
+ const removeItem = useMutation({
174
+ mutationFn: (index: number) => {
175
+ const orderFormId = query.data?.orderFormId;
176
+ if (!orderFormId) throw new Error("Cart not loaded");
177
+ return updateItemQuantity(orderFormId, index, 0);
178
+ },
179
+ onSuccess: (data) => {
180
+ queryClient.setQueryData(CART_QUERY_KEY, data);
181
+ },
182
+ });
183
+
184
+ const addCoupons = useMutation({
185
+ mutationFn: (text: string) => {
186
+ const orderFormId = query.data?.orderFormId;
187
+ if (!orderFormId) throw new Error("Cart not loaded");
188
+ return addCouponsToCart(orderFormId, text);
189
+ },
190
+ onSuccess: (data) => {
191
+ queryClient.setQueryData(CART_QUERY_KEY, data);
192
+ },
193
+ });
194
+
195
+ return {
196
+ cart: query.data ?? null,
197
+ isLoading: query.isLoading,
198
+ isError: query.isError,
199
+ error: query.error,
200
+ refetch: query.refetch,
201
+ addItems,
202
+ addCoupons,
203
+ updateQuantity,
204
+ removeItem,
205
+ itemCount: query.data?.items?.length ?? 0,
206
+ };
219
207
  }
@@ -20,59 +20,59 @@
20
20
  import { useQuery } from "@tanstack/react-query";
21
21
 
22
22
  export interface VtexUser {
23
- email?: string;
24
- firstName?: string;
25
- lastName?: string;
26
- userId?: string;
27
- isLoggedIn: boolean;
23
+ email?: string;
24
+ firstName?: string;
25
+ lastName?: string;
26
+ userId?: string;
27
+ isLoggedIn: boolean;
28
28
  }
29
29
 
30
30
  const USER_QUERY_KEY = ["vtex", "user"] as const;
31
31
 
32
32
  async function fetchUser(): Promise<VtexUser> {
33
- try {
34
- const res = await fetch(
35
- "/api/sessions?items=profile.email,profile.firstName,profile.lastName,profile.id",
36
- { credentials: "include" },
37
- );
38
- if (!res.ok) return { isLoggedIn: false };
33
+ try {
34
+ const res = await fetch(
35
+ "/api/sessions?items=profile.email,profile.firstName,profile.lastName,profile.id",
36
+ { credentials: "include" },
37
+ );
38
+ if (!res.ok) return { isLoggedIn: false };
39
39
 
40
- const data = await res.json();
41
- const profile = data?.namespaces?.profile;
40
+ const data = await res.json();
41
+ const profile = data?.namespaces?.profile;
42
42
 
43
- const email = profile?.email?.value;
44
- if (!email) return { isLoggedIn: false };
43
+ const email = profile?.email?.value;
44
+ if (!email) return { isLoggedIn: false };
45
45
 
46
- return {
47
- email,
48
- firstName: profile?.firstName?.value,
49
- lastName: profile?.lastName?.value,
50
- userId: profile?.id?.value,
51
- isLoggedIn: true,
52
- };
53
- } catch {
54
- return { isLoggedIn: false };
55
- }
46
+ return {
47
+ email,
48
+ firstName: profile?.firstName?.value,
49
+ lastName: profile?.lastName?.value,
50
+ userId: profile?.id?.value,
51
+ isLoggedIn: true,
52
+ };
53
+ } catch {
54
+ return { isLoggedIn: false };
55
+ }
56
56
  }
57
57
 
58
58
  export interface UseUserOptions {
59
- enabled?: boolean;
60
- staleTime?: number;
59
+ enabled?: boolean;
60
+ staleTime?: number;
61
61
  }
62
62
 
63
63
  export function useUser(options?: UseUserOptions) {
64
- const query = useQuery({
65
- queryKey: USER_QUERY_KEY,
66
- queryFn: fetchUser,
67
- staleTime: options?.staleTime ?? 30_000,
68
- enabled: options?.enabled !== false,
69
- });
64
+ const query = useQuery({
65
+ queryKey: USER_QUERY_KEY,
66
+ queryFn: fetchUser,
67
+ staleTime: options?.staleTime ?? 30_000,
68
+ enabled: options?.enabled !== false,
69
+ });
70
70
 
71
- return {
72
- user: query.data ?? null,
73
- isLoggedIn: query.data?.isLoggedIn ?? false,
74
- isLoading: query.isLoading,
75
- isError: query.isError,
76
- refetch: query.refetch,
77
- };
71
+ return {
72
+ user: query.data ?? null,
73
+ isLoggedIn: query.data?.isLoggedIn ?? false,
74
+ isLoading: query.isLoading,
75
+ isError: query.isError,
76
+ refetch: query.refetch,
77
+ };
78
78
  }
@@ -20,100 +20,100 @@
20
20
  * ```
21
21
  */
22
22
 
23
- import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
23
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
24
24
 
25
25
  export interface WishlistItem {
26
- id: string;
27
- productId: string;
28
- sku: string;
26
+ id: string;
27
+ productId: string;
28
+ sku: string;
29
29
  }
30
30
 
31
31
  const WISHLIST_QUERY_KEY = ["vtex", "wishlist"] as const;
32
32
 
33
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();
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
45
  }
46
46
 
47
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}`);
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
55
  }
56
56
 
57
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}`);
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
65
  }
66
66
 
67
67
  export interface UseWishlistOptions {
68
- enabled?: boolean;
69
- staleTime?: number;
68
+ enabled?: boolean;
69
+ staleTime?: number;
70
70
  }
71
71
 
72
72
  export function useWishlist(options?: UseWishlistOptions) {
73
- const queryClient = useQueryClient();
73
+ const queryClient = useQueryClient();
74
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
- });
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
81
 
82
- const items = query.data ?? [];
83
- const productIdSet = new Set(items.map((i) => i.productId));
82
+ const items = query.data ?? [];
83
+ const productIdSet = new Set(items.map((i) => i.productId));
84
84
 
85
- const addMutation = useMutation({
86
- mutationFn: addToWishlist,
87
- onSuccess: () => queryClient.invalidateQueries({ queryKey: WISHLIST_QUERY_KEY }),
88
- });
85
+ const addMutation = useMutation({
86
+ mutationFn: addToWishlist,
87
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: WISHLIST_QUERY_KEY }),
88
+ });
89
89
 
90
- const removeMutation = useMutation({
91
- mutationFn: removeFromWishlist,
92
- onSuccess: () => queryClient.invalidateQueries({ queryKey: WISHLIST_QUERY_KEY }),
93
- });
90
+ const removeMutation = useMutation({
91
+ mutationFn: removeFromWishlist,
92
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: WISHLIST_QUERY_KEY }),
93
+ });
94
94
 
95
- function isInWishlist(productId: string): boolean {
96
- return productIdSet.has(productId);
97
- }
95
+ function isInWishlist(productId: string): boolean {
96
+ return productIdSet.has(productId);
97
+ }
98
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
- }
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
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
- };
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
119
  }
@@ -61,9 +61,7 @@ export default async function vtexProductDetailsPage(
61
61
  title: product.productTitle || product.productName,
62
62
  description: preferDescription
63
63
  ? product.description
64
- : product.metaTagDescription ||
65
- product.description?.substring(0, 160) ||
66
- "",
64
+ : product.metaTagDescription || product.description?.substring(0, 160) || "",
67
65
  canonical: `/${product.linkText}/p`,
68
66
  noIndexing: indexingSkus ? false : !!skuId,
69
67
  },