@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/hooks/useCart.ts
CHANGED
|
@@ -16,204 +16,192 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
20
20
|
|
|
21
21
|
export interface CartItem {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
id: string;
|
|
23
|
+
quantity: number;
|
|
24
|
+
seller: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface OrderForm {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
82
|
-
|
|
81
|
+
orderFormId: string,
|
|
82
|
+
items: Array<{ id: string; quantity: number; seller: string }>,
|
|
83
83
|
): Promise<OrderForm> {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
115
|
+
orderFormId: string,
|
|
116
|
+
index: number,
|
|
117
|
+
quantity: number,
|
|
127
118
|
): Promise<OrderForm> {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
}
|
package/vtex/hooks/useUser.ts
CHANGED
|
@@ -20,59 +20,59 @@
|
|
|
20
20
|
import { useQuery } from "@tanstack/react-query";
|
|
21
21
|
|
|
22
22
|
export interface VtexUser {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
|
|
40
|
+
const data = await res.json();
|
|
41
|
+
const profile = data?.namespaces?.profile;
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const email = profile?.email?.value;
|
|
44
|
+
if (!email) return { isLoggedIn: false };
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
60
|
-
|
|
59
|
+
enabled?: boolean;
|
|
60
|
+
staleTime?: number;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export function useUser(options?: UseUserOptions) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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 {
|
|
23
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
24
24
|
|
|
25
25
|
export interface WishlistItem {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
69
|
-
|
|
68
|
+
enabled?: boolean;
|
|
69
|
+
staleTime?: number;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
export function useWishlist(options?: UseWishlistOptions) {
|
|
73
|
-
|
|
73
|
+
const queryClient = useQueryClient();
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
83
|
-
|
|
82
|
+
const items = query.data ?? [];
|
|
83
|
+
const productIdSet = new Set(items.map((i) => i.productId));
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
const addMutation = useMutation({
|
|
86
|
+
mutationFn: addToWishlist,
|
|
87
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: WISHLIST_QUERY_KEY }),
|
|
88
|
+
});
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
const removeMutation = useMutation({
|
|
91
|
+
mutationFn: removeFromWishlist,
|
|
92
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: WISHLIST_QUERY_KEY }),
|
|
93
|
+
});
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
function isInWishlist(productId: string): boolean {
|
|
96
|
+
return productIdSet.has(productId);
|
|
97
|
+
}
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
},
|