@tagadapay/plugin-sdk 2.8.8 → 2.8.10
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/dist/react/config/environment.d.ts +1 -22
- package/dist/react/config/environment.js +1 -132
- package/dist/react/utils/deviceInfo.d.ts +1 -39
- package/dist/react/utils/deviceInfo.js +1 -163
- package/dist/react/utils/jwtDecoder.d.ts +1 -14
- package/dist/react/utils/jwtDecoder.js +1 -86
- package/dist/react/utils/tokenStorage.d.ts +1 -16
- package/dist/react/utils/tokenStorage.js +1 -53
- package/dist/v2/core/client.d.ts +96 -0
- package/dist/v2/core/client.js +430 -0
- package/dist/v2/core/config/environment.d.ts +36 -0
- package/dist/v2/core/config/environment.js +155 -0
- package/dist/v2/core/pathRemapping.js +61 -3
- package/dist/v2/core/resources/apiClient.d.ts +13 -0
- package/dist/v2/core/resources/apiClient.js +77 -9
- package/dist/v2/core/resources/funnel.d.ts +21 -0
- package/dist/v2/core/resources/payments.d.ts +23 -0
- package/dist/v2/core/types.d.ts +271 -0
- package/dist/v2/core/types.js +4 -0
- package/dist/v2/core/utils/deviceInfo.d.ts +39 -0
- package/dist/v2/core/utils/deviceInfo.js +162 -0
- package/dist/v2/core/utils/eventDispatcher.d.ts +10 -0
- package/dist/v2/core/utils/eventDispatcher.js +24 -0
- package/dist/v2/core/utils/jwtDecoder.d.ts +14 -0
- package/dist/v2/core/utils/jwtDecoder.js +85 -0
- package/dist/v2/core/utils/pluginConfig.d.ts +1 -0
- package/dist/v2/core/utils/pluginConfig.js +64 -8
- package/dist/v2/core/utils/tokenStorage.d.ts +19 -0
- package/dist/v2/core/utils/tokenStorage.js +52 -0
- package/dist/v2/react/components/ApplePayButton.js +1 -1
- package/dist/v2/react/components/DebugDrawer.js +90 -1
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +12 -0
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +54 -0
- package/dist/v2/react/hooks/useFunnel.d.ts +2 -1
- package/dist/v2/react/hooks/useFunnel.js +245 -69
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +26 -18
- package/dist/v2/react/hooks/useISOData.js +4 -2
- package/dist/v2/react/hooks/useOffersQuery.d.ts +42 -29
- package/dist/v2/react/hooks/useOffersQuery.js +266 -204
- package/dist/v2/react/hooks/usePaymentQuery.js +99 -6
- package/dist/v2/react/providers/TagadaProvider.d.ts +13 -21
- package/dist/v2/react/providers/TagadaProvider.js +79 -673
- package/package.json +1 -1
|
@@ -2,19 +2,31 @@
|
|
|
2
2
|
* Offers Hook using TanStack Query
|
|
3
3
|
* Handles offers with automatic caching
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
6
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
import { OffersResource } from '../../core/resources/offers';
|
|
8
8
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
9
|
-
import { usePluginConfig } from './usePluginConfig';
|
|
10
9
|
import { getGlobalApiClient } from './useApiQuery';
|
|
10
|
+
import { usePluginConfig } from './usePluginConfig';
|
|
11
11
|
export function useOffersQuery(options = {}) {
|
|
12
|
-
const { offerIds = [], enabled = true, returnUrl } = options;
|
|
12
|
+
const { offerIds = [], enabled = true, returnUrl, orderId: defaultOrderId, activeOfferId, skipPreview = false } = options;
|
|
13
13
|
const { storeId } = usePluginConfig();
|
|
14
14
|
const { isSessionInitialized, session } = useTagadaContext();
|
|
15
15
|
const _queryClient = useQueryClient();
|
|
16
|
+
// Version identifier for debugging
|
|
17
|
+
console.log('[useOffersQuery] Hook initialized - VERSION: 2.2-production-ready', {
|
|
18
|
+
activeOfferId,
|
|
19
|
+
skipPreview,
|
|
20
|
+
isSessionInitialized,
|
|
21
|
+
});
|
|
16
22
|
// State for checkout sessions per offer (similar to postPurchases)
|
|
17
23
|
const [checkoutSessions, setCheckoutSessions] = useState({});
|
|
24
|
+
const [isActiveSummaryLoading, setIsActiveSummaryLoading] = useState(false);
|
|
25
|
+
const lastPreviewedOfferRef = useRef(null);
|
|
26
|
+
// Use ref to break dependency cycles in callbacks
|
|
27
|
+
const checkoutSessionsRef = useRef(checkoutSessions);
|
|
28
|
+
// Update ref on every render
|
|
29
|
+
checkoutSessionsRef.current = checkoutSessions;
|
|
18
30
|
// Create offers resource client
|
|
19
31
|
const offersResource = useMemo(() => {
|
|
20
32
|
try {
|
|
@@ -26,6 +38,8 @@ export function useOffersQuery(options = {}) {
|
|
|
26
38
|
}, []);
|
|
27
39
|
// Helper function for fetching order summary (similar to postPurchases)
|
|
28
40
|
const fetchOrderSummary = useCallback(async (offerId, sessionId) => {
|
|
41
|
+
if (!isSessionInitialized)
|
|
42
|
+
return null;
|
|
29
43
|
try {
|
|
30
44
|
// Set updating state
|
|
31
45
|
setCheckoutSessions(prev => ({
|
|
@@ -59,7 +73,9 @@ export function useOffersQuery(options = {}) {
|
|
|
59
73
|
isUpdatingSummary: false,
|
|
60
74
|
}
|
|
61
75
|
}));
|
|
76
|
+
return orderSummary;
|
|
62
77
|
}
|
|
78
|
+
return null;
|
|
63
79
|
}
|
|
64
80
|
catch (error) {
|
|
65
81
|
setCheckoutSessions(prev => ({
|
|
@@ -71,11 +87,11 @@ export function useOffersQuery(options = {}) {
|
|
|
71
87
|
}));
|
|
72
88
|
throw error;
|
|
73
89
|
}
|
|
74
|
-
}, [offersResource]);
|
|
90
|
+
}, [offersResource, isSessionInitialized]);
|
|
75
91
|
// Create query key based on options
|
|
76
92
|
const queryKey = useMemo(() => ['offers', { storeId, offerIds }], [storeId, offerIds]);
|
|
77
93
|
// Use TanStack Query for fetching offers
|
|
78
|
-
const { data: offers = [], isLoading, error
|
|
94
|
+
const { data: offers = [], isLoading, error } = useQuery({
|
|
79
95
|
queryKey,
|
|
80
96
|
enabled: enabled && !!storeId && isSessionInitialized,
|
|
81
97
|
queryFn: async () => {
|
|
@@ -94,32 +110,7 @@ export function useOffersQuery(options = {}) {
|
|
|
94
110
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
95
111
|
refetchOnWindowFocus: false,
|
|
96
112
|
});
|
|
97
|
-
|
|
98
|
-
const createCheckoutSessionMutation = useMutation({
|
|
99
|
-
mutationFn: async ({ offerId, returnUrl }) => {
|
|
100
|
-
return await offersResource.createCheckoutSession(offerId, returnUrl);
|
|
101
|
-
},
|
|
102
|
-
onError: (_error) => {
|
|
103
|
-
// Error handling removed
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
const payOfferMutation = useMutation({
|
|
107
|
-
mutationFn: async ({ offerId, orderId }) => {
|
|
108
|
-
return await offersResource.payOffer(offerId, orderId);
|
|
109
|
-
},
|
|
110
|
-
onError: (_error) => {
|
|
111
|
-
// Error handling removed
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
const transformToCheckoutMutation = useMutation({
|
|
115
|
-
mutationFn: async ({ offerId, returnUrl }) => {
|
|
116
|
-
return await offersResource.transformToCheckout(offerId, returnUrl);
|
|
117
|
-
},
|
|
118
|
-
onError: (_error) => {
|
|
119
|
-
// Error handling removed
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
const payWithCheckoutSessionMutation = useMutation({
|
|
113
|
+
const { mutateAsync: payWithCheckoutSessionAsync } = useMutation({
|
|
123
114
|
mutationFn: async ({ checkoutSessionId, orderId }) => {
|
|
124
115
|
return await offersResource.payWithCheckoutSession(checkoutSessionId, orderId);
|
|
125
116
|
},
|
|
@@ -127,7 +118,7 @@ export function useOffersQuery(options = {}) {
|
|
|
127
118
|
// Error handling removed
|
|
128
119
|
},
|
|
129
120
|
});
|
|
130
|
-
const
|
|
121
|
+
const { mutateAsync: initCheckoutSessionAsync } = useMutation({
|
|
131
122
|
mutationFn: async ({ offerId, orderId, customerId }) => {
|
|
132
123
|
return await offersResource.initCheckoutSession(offerId, orderId, customerId);
|
|
133
124
|
},
|
|
@@ -135,108 +126,206 @@ export function useOffersQuery(options = {}) {
|
|
|
135
126
|
// Error handling removed
|
|
136
127
|
},
|
|
137
128
|
});
|
|
138
|
-
// Helper functions (matching V1 useOffers exactly)
|
|
139
|
-
const getOffer = useCallback((offerId) => {
|
|
140
|
-
return offers.find((offer) => offer.id === offerId);
|
|
141
|
-
}, [offers]);
|
|
142
|
-
const getTotalValue = useCallback(() => {
|
|
143
|
-
return offers.reduce((total, offer) => {
|
|
144
|
-
const firstSummary = offer.summaries[0];
|
|
145
|
-
return total + (firstSummary?.totalAdjustedAmount || 0);
|
|
146
|
-
}, 0);
|
|
147
|
-
}, [offers]);
|
|
148
|
-
const getTotalSavings = useCallback(() => {
|
|
149
|
-
return offers.reduce((total, offer) => {
|
|
150
|
-
const firstSummary = offer.summaries[0];
|
|
151
|
-
return total + (firstSummary?.totalPromotionAmount || 0);
|
|
152
|
-
}, 0);
|
|
153
|
-
}, [offers]);
|
|
154
|
-
// Action functions
|
|
155
|
-
const createCheckoutSession = useCallback(async (offerId, options) => {
|
|
156
|
-
return await createCheckoutSessionMutation.mutateAsync({
|
|
157
|
-
offerId,
|
|
158
|
-
returnUrl: options?.returnUrl || returnUrl,
|
|
159
|
-
});
|
|
160
|
-
}, [createCheckoutSessionMutation, returnUrl]);
|
|
161
|
-
const payOffer = useCallback(async (offerId, orderId) => {
|
|
162
|
-
return await payOfferMutation.mutateAsync({ offerId, orderId });
|
|
163
|
-
}, [payOfferMutation]);
|
|
164
|
-
const transformToCheckout = useCallback(async (offerId, options) => {
|
|
165
|
-
return await transformToCheckoutMutation.mutateAsync({
|
|
166
|
-
offerId,
|
|
167
|
-
returnUrl: options?.returnUrl || returnUrl,
|
|
168
|
-
});
|
|
169
|
-
}, [transformToCheckoutMutation, returnUrl]);
|
|
170
129
|
const payWithCheckoutSession = useCallback(async (checkoutSessionId, orderId) => {
|
|
171
|
-
return await
|
|
172
|
-
}, [
|
|
130
|
+
return await payWithCheckoutSessionAsync({ checkoutSessionId, orderId });
|
|
131
|
+
}, [payWithCheckoutSessionAsync]);
|
|
173
132
|
const initCheckoutSession = useCallback(async (offerId, orderId, customerId) => {
|
|
133
|
+
if (!isSessionInitialized) {
|
|
134
|
+
throw new Error('Cannot initialize checkout session: CMS session is not initialized');
|
|
135
|
+
}
|
|
174
136
|
// Use customerId from session context if not provided
|
|
175
137
|
const effectiveCustomerId = customerId || session?.customerId;
|
|
176
138
|
if (!effectiveCustomerId) {
|
|
177
139
|
throw new Error('Customer ID is required. Make sure the session is properly initialized.');
|
|
178
140
|
}
|
|
179
|
-
return await
|
|
141
|
+
return await initCheckoutSessionAsync({
|
|
180
142
|
offerId,
|
|
181
143
|
orderId,
|
|
182
144
|
customerId: effectiveCustomerId
|
|
183
145
|
});
|
|
184
|
-
}, [
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
146
|
+
}, [initCheckoutSessionAsync, session?.customerId, isSessionInitialized]);
|
|
147
|
+
const payOffer = useCallback(async (offerId, orderId) => {
|
|
148
|
+
if (!isSessionInitialized) {
|
|
149
|
+
throw new Error('Cannot pay offer: CMS session is not initialized');
|
|
150
|
+
}
|
|
151
|
+
const effectiveOrderId = orderId || defaultOrderId;
|
|
152
|
+
const effectiveCustomerId = session?.customerId;
|
|
153
|
+
if (!effectiveOrderId) {
|
|
154
|
+
throw new Error('Order ID is required for payment. Please provide it in the hook options or the function call.');
|
|
155
|
+
}
|
|
156
|
+
if (!effectiveCustomerId) {
|
|
157
|
+
throw new Error('Customer ID is required. Make sure the session is properly initialized.');
|
|
158
|
+
}
|
|
159
|
+
// 1. Init session
|
|
160
|
+
const { checkoutSessionId } = await initCheckoutSession(offerId, effectiveOrderId, effectiveCustomerId);
|
|
161
|
+
// 2. Pay
|
|
162
|
+
await payWithCheckoutSession(checkoutSessionId, effectiveOrderId);
|
|
163
|
+
}, [initCheckoutSession, payWithCheckoutSession, defaultOrderId, session?.customerId, isSessionInitialized]);
|
|
164
|
+
const preview = useCallback(async (offerId) => {
|
|
165
|
+
console.log('[useOffersQuery] preview() called for offer:', offerId);
|
|
166
|
+
if (!isSessionInitialized) {
|
|
167
|
+
console.log('[useOffersQuery] preview() - session not initialized, returning null');
|
|
168
|
+
// Return null silently to avoid errors during auto-initialization phases
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const effectiveOrderId = defaultOrderId;
|
|
172
|
+
const effectiveCustomerId = session?.customerId;
|
|
173
|
+
// Use ref to check current state without creating dependency
|
|
174
|
+
const currentSessions = checkoutSessionsRef.current;
|
|
175
|
+
// If we already have a summary in state, return it
|
|
176
|
+
if (currentSessions[offerId]?.orderSummary) {
|
|
177
|
+
console.log('[useOffersQuery] preview() - using cached summary for offer:', offerId);
|
|
178
|
+
return currentSessions[offerId].orderSummary;
|
|
179
|
+
}
|
|
180
|
+
// Prevent duplicate initialization if already has a session and is updating
|
|
181
|
+
if (currentSessions[offerId]?.checkoutSessionId && currentSessions[offerId]?.isUpdatingSummary) {
|
|
182
|
+
console.log('[useOffersQuery] preview() - already updating, skipping for offer:', offerId);
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
// If we don't have orderId, fallback to static summary from offer object
|
|
186
|
+
// as we can't initialize a checkout session properly without orderId (for upsells)
|
|
187
|
+
if (!effectiveOrderId || !effectiveCustomerId) {
|
|
188
|
+
const offer = offers.find(o => o.id === offerId);
|
|
189
|
+
return offer?.summaries?.[0] || null;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
// If we already have a session ID, reuse it instead of creating a new one
|
|
193
|
+
let sessionId = currentSessions[offerId]?.checkoutSessionId;
|
|
194
|
+
if (!sessionId) {
|
|
195
|
+
const { checkoutSessionId } = await initCheckoutSession(offerId, effectiveOrderId, effectiveCustomerId);
|
|
196
|
+
sessionId = checkoutSessionId;
|
|
197
|
+
// Update state with session ID
|
|
198
|
+
setCheckoutSessions(prev => ({
|
|
199
|
+
...prev,
|
|
200
|
+
[offerId]: {
|
|
201
|
+
checkoutSessionId: sessionId,
|
|
202
|
+
orderSummary: null,
|
|
203
|
+
selectedVariants: {},
|
|
204
|
+
loadingVariants: {},
|
|
205
|
+
isUpdatingSummary: false,
|
|
206
|
+
}
|
|
207
|
+
}));
|
|
213
208
|
}
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
209
|
+
// Fetch and return summary
|
|
210
|
+
return await fetchOrderSummary(offerId, sessionId);
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
console.error("Failed to preview offer", e);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}, [offers, defaultOrderId, session?.customerId, initCheckoutSession, fetchOrderSummary, isSessionInitialized]); // Removed checkoutSessions dependency
|
|
217
|
+
// Auto-preview effect for activeOfferId
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
console.log('[useOffersQuery v2.2] Auto-preview effect triggered:', {
|
|
220
|
+
activeOfferId,
|
|
221
|
+
skipPreview,
|
|
222
|
+
isSessionInitialized,
|
|
223
|
+
lastPreviewed: lastPreviewedOfferRef.current,
|
|
224
|
+
});
|
|
225
|
+
if (!activeOfferId || skipPreview || !isSessionInitialized) {
|
|
226
|
+
console.log('[useOffersQuery] Skipping auto-preview - conditions not met');
|
|
227
|
+
setIsActiveSummaryLoading(false); // Reset loading state if conditions not met
|
|
228
|
+
// Reset the ref when conditions are not met
|
|
229
|
+
if (!activeOfferId) {
|
|
230
|
+
lastPreviewedOfferRef.current = null;
|
|
218
231
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Skip if we've already previewed this exact offer
|
|
235
|
+
if (lastPreviewedOfferRef.current === activeOfferId) {
|
|
236
|
+
console.log('[useOffersQuery] Skipping auto-preview - already previewed:', activeOfferId);
|
|
237
|
+
setIsActiveSummaryLoading(false); // Ensure loading is false
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
console.log('[useOffersQuery] Starting auto-preview for offer:', activeOfferId);
|
|
241
|
+
let isMounted = true;
|
|
242
|
+
setIsActiveSummaryLoading(true); // Set loading immediately
|
|
243
|
+
// Debounce the preview call
|
|
244
|
+
const timer = setTimeout(() => {
|
|
245
|
+
if (!isMounted) {
|
|
246
|
+
console.log('[useOffersQuery] Component unmounted before preview call');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
console.log('[useOffersQuery] Calling preview for offer:', activeOfferId);
|
|
250
|
+
preview(activeOfferId)
|
|
251
|
+
.then(() => {
|
|
252
|
+
if (isMounted) {
|
|
253
|
+
console.log('[useOffersQuery] Preview successful for offer:', activeOfferId);
|
|
254
|
+
lastPreviewedOfferRef.current = activeOfferId; // Mark as previewed on success
|
|
230
255
|
}
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
256
|
+
})
|
|
257
|
+
.catch(err => {
|
|
258
|
+
console.error('[useOffersQuery] Failed to auto-preview offer:', activeOfferId, err);
|
|
259
|
+
// Don't mark as previewed on error, to avoid infinite retry loop
|
|
260
|
+
})
|
|
261
|
+
.finally(() => {
|
|
262
|
+
if (isMounted) {
|
|
263
|
+
console.log('[useOffersQuery] Preview finished for offer:', activeOfferId);
|
|
264
|
+
setIsActiveSummaryLoading(false);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}, 50);
|
|
268
|
+
return () => {
|
|
269
|
+
console.log('[useOffersQuery] Cleaning up auto-preview effect for offer:', activeOfferId);
|
|
270
|
+
isMounted = false;
|
|
271
|
+
clearTimeout(timer);
|
|
272
|
+
setIsActiveSummaryLoading(false); // Reset loading on unmount/cleanup
|
|
273
|
+
};
|
|
274
|
+
}, [activeOfferId, skipPreview, isSessionInitialized]); // FIXED: Removed 'preview' from dependencies to prevent infinite loop
|
|
275
|
+
const activeSummary = useMemo(() => {
|
|
276
|
+
if (!activeOfferId)
|
|
277
|
+
return null;
|
|
278
|
+
// Return dynamic summary if available, otherwise fallback to static summary
|
|
279
|
+
return checkoutSessions[activeOfferId]?.orderSummary || offers.find(o => o.id === activeOfferId)?.summaries?.[0] || null;
|
|
280
|
+
}, [activeOfferId, checkoutSessions, offers]);
|
|
281
|
+
const getAvailableVariants = useCallback((offerId, productId) => {
|
|
282
|
+
const sessionState = checkoutSessions[offerId]; // This hook needs to react to state changes
|
|
283
|
+
if (!sessionState?.orderSummary?.options?.[productId])
|
|
284
|
+
return [];
|
|
285
|
+
return sessionState.orderSummary.options[productId].map((variant) => ({
|
|
286
|
+
variantId: variant.id,
|
|
287
|
+
variantName: variant.name,
|
|
288
|
+
variantSku: variant.sku,
|
|
289
|
+
variantDefault: variant.default,
|
|
290
|
+
variantExternalId: variant.externalVariantId,
|
|
291
|
+
priceId: variant.prices[0]?.id,
|
|
292
|
+
currencyOptions: variant.prices[0]?.currencyOptions,
|
|
293
|
+
}));
|
|
294
|
+
}, [checkoutSessions]);
|
|
295
|
+
const isLoadingVariants = useCallback((offerId, productId) => {
|
|
296
|
+
return checkoutSessions[offerId]?.loadingVariants?.[productId] ?? false;
|
|
297
|
+
}, [checkoutSessions]);
|
|
298
|
+
const selectVariant = useCallback(async (offerId, productId, variantId) => {
|
|
299
|
+
if (!isSessionInitialized) {
|
|
300
|
+
throw new Error('Cannot select variant: CMS session is not initialized');
|
|
301
|
+
}
|
|
302
|
+
// Use ref for initial check to avoid dependency but we might need latest state for logic
|
|
303
|
+
// Actually for actions it's better to use ref or just dependency if action is not called in useEffect
|
|
304
|
+
const currentSessions = checkoutSessionsRef.current;
|
|
305
|
+
const sessionState = currentSessions[offerId];
|
|
306
|
+
if (!sessionState?.checkoutSessionId || !sessionState.orderSummary) {
|
|
307
|
+
throw new Error('Checkout session not initialized for this offer');
|
|
308
|
+
}
|
|
309
|
+
const sessionId = sessionState.checkoutSessionId;
|
|
310
|
+
// Set loading state for this specific variant
|
|
311
|
+
setCheckoutSessions(prev => ({
|
|
312
|
+
...prev,
|
|
313
|
+
[offerId]: {
|
|
314
|
+
...prev[offerId],
|
|
315
|
+
loadingVariants: {
|
|
316
|
+
...prev[offerId].loadingVariants,
|
|
317
|
+
[productId]: true,
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}));
|
|
321
|
+
try {
|
|
322
|
+
// We need to use the state from the ref to ensure we have the latest data without causing infinite loops
|
|
323
|
+
// if selectVariant was in a dependency array that triggered effects
|
|
324
|
+
const sessionState = checkoutSessionsRef.current[offerId];
|
|
325
|
+
if (!sessionState?.orderSummary?.options?.[productId]) {
|
|
326
|
+
throw new Error('No variants available for this product');
|
|
327
|
+
}
|
|
328
|
+
const availableVariants = sessionState.orderSummary.options[productId].map((variant) => ({
|
|
240
329
|
variantId: variant.id,
|
|
241
330
|
variantName: variant.name,
|
|
242
331
|
variantSku: variant.sku,
|
|
@@ -245,98 +334,71 @@ export function useOffersQuery(options = {}) {
|
|
|
245
334
|
priceId: variant.prices[0]?.id,
|
|
246
335
|
currencyOptions: variant.prices[0]?.currencyOptions,
|
|
247
336
|
}));
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
337
|
+
const selectedVariant = availableVariants.find(v => v.variantId === variantId);
|
|
338
|
+
if (!selectedVariant) {
|
|
339
|
+
throw new Error('Selected variant not found');
|
|
340
|
+
}
|
|
341
|
+
// Find the current item to get its quantity
|
|
342
|
+
const currentItem = sessionState.orderSummary.items.find(item => item.productId === productId);
|
|
343
|
+
if (!currentItem) {
|
|
344
|
+
throw new Error('Current item not found');
|
|
253
345
|
}
|
|
254
|
-
|
|
255
|
-
|
|
346
|
+
// Update selected variants state
|
|
347
|
+
setCheckoutSessions(prev => ({
|
|
348
|
+
...prev,
|
|
349
|
+
[offerId]: {
|
|
350
|
+
...prev[offerId],
|
|
351
|
+
selectedVariants: {
|
|
352
|
+
...prev[offerId].selectedVariants,
|
|
353
|
+
[productId]: variantId,
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}));
|
|
357
|
+
// Update line items on the server
|
|
358
|
+
await offersResource.updateLineItems(sessionId, [
|
|
359
|
+
{
|
|
360
|
+
variantId: selectedVariant.variantId,
|
|
361
|
+
quantity: currentItem.quantity,
|
|
362
|
+
},
|
|
363
|
+
]);
|
|
364
|
+
// Refetch order summary after successful line item update
|
|
365
|
+
return await fetchOrderSummary(offerId, sessionId);
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
// Clear loading state for this specific variant
|
|
256
369
|
setCheckoutSessions(prev => ({
|
|
257
370
|
...prev,
|
|
258
371
|
[offerId]: {
|
|
259
372
|
...prev[offerId],
|
|
260
373
|
loadingVariants: {
|
|
261
374
|
...prev[offerId].loadingVariants,
|
|
262
|
-
[productId]:
|
|
375
|
+
[productId]: false,
|
|
263
376
|
}
|
|
264
377
|
}
|
|
265
378
|
}));
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (!selectedVariant) {
|
|
282
|
-
throw new Error('Selected variant not found');
|
|
283
|
-
}
|
|
284
|
-
// Find the current item to get its quantity
|
|
285
|
-
const currentItem = sessionState.orderSummary.items.find(item => item.productId === productId);
|
|
286
|
-
if (!currentItem) {
|
|
287
|
-
throw new Error('Current item not found');
|
|
288
|
-
}
|
|
289
|
-
// Update selected variants state
|
|
290
|
-
setCheckoutSessions(prev => ({
|
|
291
|
-
...prev,
|
|
292
|
-
[offerId]: {
|
|
293
|
-
...prev[offerId],
|
|
294
|
-
selectedVariants: {
|
|
295
|
-
...prev[offerId].selectedVariants,
|
|
296
|
-
[productId]: variantId,
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}));
|
|
300
|
-
// Update line items on the server
|
|
301
|
-
await offersResource.updateLineItems(sessionId, [
|
|
302
|
-
{
|
|
303
|
-
variantId: selectedVariant.variantId,
|
|
304
|
-
quantity: currentItem.quantity,
|
|
305
|
-
},
|
|
306
|
-
]);
|
|
307
|
-
// Refetch order summary after successful line item update
|
|
308
|
-
await fetchOrderSummary(offerId, sessionId);
|
|
309
|
-
}
|
|
310
|
-
finally {
|
|
311
|
-
// Clear loading state for this specific variant
|
|
312
|
-
setCheckoutSessions(prev => ({
|
|
313
|
-
...prev,
|
|
314
|
-
[offerId]: {
|
|
315
|
-
...prev[offerId],
|
|
316
|
-
loadingVariants: {
|
|
317
|
-
...prev[offerId].loadingVariants,
|
|
318
|
-
[productId]: false,
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}));
|
|
322
|
-
}
|
|
323
|
-
},
|
|
324
|
-
getOrderSummary: (offerId) => {
|
|
325
|
-
return checkoutSessions[offerId]?.orderSummary ?? null;
|
|
326
|
-
},
|
|
327
|
-
isLoadingVariants: (offerId, productId) => {
|
|
328
|
-
return checkoutSessions[offerId]?.loadingVariants?.[productId] ?? false;
|
|
329
|
-
},
|
|
330
|
-
isUpdatingOrderSummary: (offerId) => {
|
|
331
|
-
return checkoutSessions[offerId]?.isUpdatingSummary || false;
|
|
332
|
-
},
|
|
333
|
-
confirmPurchase: async (offerId, _options) => {
|
|
334
|
-
const sessionState = checkoutSessions[offerId];
|
|
335
|
-
if (!sessionState?.checkoutSessionId) {
|
|
336
|
-
throw new Error('Checkout session not initialized for this offer');
|
|
337
|
-
}
|
|
338
|
-
// Use the enhanced payWithCheckoutSession with proper metadata
|
|
339
|
-
await offersResource.payWithCheckoutSession(sessionState.checkoutSessionId);
|
|
340
|
-
},
|
|
379
|
+
}
|
|
380
|
+
}, [offersResource, fetchOrderSummary, isSessionInitialized]); // Removed checkoutSessions dependency
|
|
381
|
+
const result = {
|
|
382
|
+
// Query data
|
|
383
|
+
offers,
|
|
384
|
+
isLoading,
|
|
385
|
+
error,
|
|
386
|
+
activeSummary,
|
|
387
|
+
isActiveSummaryLoading,
|
|
388
|
+
// Actions
|
|
389
|
+
payOffer,
|
|
390
|
+
preview,
|
|
391
|
+
getAvailableVariants,
|
|
392
|
+
selectVariant,
|
|
393
|
+
isLoadingVariants,
|
|
341
394
|
};
|
|
395
|
+
console.log('[useOffersQuery] Returning result:', {
|
|
396
|
+
offersCount: offers.length,
|
|
397
|
+
isLoading,
|
|
398
|
+
hasError: !!error,
|
|
399
|
+
activeOfferId,
|
|
400
|
+
hasActiveSummary: !!activeSummary,
|
|
401
|
+
isActiveSummaryLoading,
|
|
402
|
+
});
|
|
403
|
+
return result;
|
|
342
404
|
}
|