@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.
Files changed (43) hide show
  1. package/dist/react/config/environment.d.ts +1 -22
  2. package/dist/react/config/environment.js +1 -132
  3. package/dist/react/utils/deviceInfo.d.ts +1 -39
  4. package/dist/react/utils/deviceInfo.js +1 -163
  5. package/dist/react/utils/jwtDecoder.d.ts +1 -14
  6. package/dist/react/utils/jwtDecoder.js +1 -86
  7. package/dist/react/utils/tokenStorage.d.ts +1 -16
  8. package/dist/react/utils/tokenStorage.js +1 -53
  9. package/dist/v2/core/client.d.ts +96 -0
  10. package/dist/v2/core/client.js +430 -0
  11. package/dist/v2/core/config/environment.d.ts +36 -0
  12. package/dist/v2/core/config/environment.js +155 -0
  13. package/dist/v2/core/pathRemapping.js +61 -3
  14. package/dist/v2/core/resources/apiClient.d.ts +13 -0
  15. package/dist/v2/core/resources/apiClient.js +77 -9
  16. package/dist/v2/core/resources/funnel.d.ts +21 -0
  17. package/dist/v2/core/resources/payments.d.ts +23 -0
  18. package/dist/v2/core/types.d.ts +271 -0
  19. package/dist/v2/core/types.js +4 -0
  20. package/dist/v2/core/utils/deviceInfo.d.ts +39 -0
  21. package/dist/v2/core/utils/deviceInfo.js +162 -0
  22. package/dist/v2/core/utils/eventDispatcher.d.ts +10 -0
  23. package/dist/v2/core/utils/eventDispatcher.js +24 -0
  24. package/dist/v2/core/utils/jwtDecoder.d.ts +14 -0
  25. package/dist/v2/core/utils/jwtDecoder.js +85 -0
  26. package/dist/v2/core/utils/pluginConfig.d.ts +1 -0
  27. package/dist/v2/core/utils/pluginConfig.js +64 -8
  28. package/dist/v2/core/utils/tokenStorage.d.ts +19 -0
  29. package/dist/v2/core/utils/tokenStorage.js +52 -0
  30. package/dist/v2/react/components/ApplePayButton.js +1 -1
  31. package/dist/v2/react/components/DebugDrawer.js +90 -1
  32. package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +12 -0
  33. package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +54 -0
  34. package/dist/v2/react/hooks/useFunnel.d.ts +2 -1
  35. package/dist/v2/react/hooks/useFunnel.js +245 -69
  36. package/dist/v2/react/hooks/useGoogleAutocomplete.js +26 -18
  37. package/dist/v2/react/hooks/useISOData.js +4 -2
  38. package/dist/v2/react/hooks/useOffersQuery.d.ts +42 -29
  39. package/dist/v2/react/hooks/useOffersQuery.js +266 -204
  40. package/dist/v2/react/hooks/usePaymentQuery.js +99 -6
  41. package/dist/v2/react/providers/TagadaProvider.d.ts +13 -21
  42. package/dist/v2/react/providers/TagadaProvider.js +79 -673
  43. 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 { useMemo, useCallback, useState } from 'react';
6
- import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
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, refetch } = useQuery({
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
- // Mutations for offer actions
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 initCheckoutSessionMutation = useMutation({
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 payWithCheckoutSessionMutation.mutateAsync({ checkoutSessionId, orderId });
172
- }, [payWithCheckoutSessionMutation]);
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 initCheckoutSessionMutation.mutateAsync({
141
+ return await initCheckoutSessionAsync({
180
142
  offerId,
181
143
  orderId,
182
144
  customerId: effectiveCustomerId
183
145
  });
184
- }, [initCheckoutSessionMutation, session?.customerId]);
185
- return {
186
- // Query data
187
- offers,
188
- isLoading,
189
- error,
190
- // Actions
191
- refetch: async () => {
192
- await refetch();
193
- },
194
- // Offer management
195
- createCheckoutSession,
196
- payOffer,
197
- transformToCheckout,
198
- // Helper functions
199
- getOffer,
200
- getTotalValue,
201
- getTotalSavings,
202
- // Payment functions
203
- payWithCheckoutSession,
204
- initCheckoutSession,
205
- // Checkout session management (similar to postPurchases)
206
- getCheckoutSessionState: (offerId) => {
207
- return checkoutSessions[offerId] || null;
208
- },
209
- initializeOfferCheckout: async (offerId) => {
210
- // Check if customer ID is available
211
- if (!session?.customerId) {
212
- throw new Error('Customer ID is required. Make sure the session is properly initialized.');
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
- // Initialize checkout session using transformToCheckout
215
- const initResult = await offersResource.transformToCheckout(offerId, returnUrl);
216
- if (!initResult.checkoutUrl) {
217
- throw new Error('Failed to initialize checkout session');
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
- // Extract session ID from checkout URL or use a placeholder
220
- const sessionId = 'session_' + offerId; // This would need to be extracted from the actual response
221
- // Initialize session state
222
- setCheckoutSessions(prev => ({
223
- ...prev,
224
- [offerId]: {
225
- checkoutSessionId: sessionId,
226
- orderSummary: null,
227
- selectedVariants: {},
228
- loadingVariants: {},
229
- isUpdatingSummary: false,
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
- // Fetch order summary with variant options
233
- await fetchOrderSummary(offerId, sessionId);
234
- },
235
- getAvailableVariants: (offerId, productId) => {
236
- const sessionState = checkoutSessions[offerId];
237
- if (!sessionState?.orderSummary?.options?.[productId])
238
- return [];
239
- return sessionState.orderSummary.options[productId].map((variant) => ({
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
- selectVariant: async (offerId, productId, variantId) => {
250
- const sessionState = checkoutSessions[offerId];
251
- if (!sessionState?.checkoutSessionId || !sessionState.orderSummary) {
252
- throw new Error('Checkout session not initialized for this offer');
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
- const sessionId = sessionState.checkoutSessionId;
255
- // Set loading state for this specific variant
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]: true,
375
+ [productId]: false,
263
376
  }
264
377
  }
265
378
  }));
266
- try {
267
- const sessionState = checkoutSessions[offerId];
268
- if (!sessionState?.orderSummary?.options?.[productId]) {
269
- throw new Error('No variants available for this product');
270
- }
271
- const availableVariants = sessionState.orderSummary.options[productId].map((variant) => ({
272
- variantId: variant.id,
273
- variantName: variant.name,
274
- variantSku: variant.sku,
275
- variantDefault: variant.default,
276
- variantExternalId: variant.externalVariantId,
277
- priceId: variant.prices[0]?.id,
278
- currencyOptions: variant.prices[0]?.currencyOptions,
279
- }));
280
- const selectedVariant = availableVariants.find(v => v.variantId === variantId);
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
  }