@umituz/react-native-subscription 2.35.16 → 2.35.18

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 (37) hide show
  1. package/package.json +1 -1
  2. package/src/domains/config/utils/planSelectors.ts +1 -1
  3. package/src/domains/credits/presentation/useCredits.ts +6 -20
  4. package/src/domains/paywall/hooks/usePaywallActions.ts +2 -82
  5. package/src/domains/revenuecat/core/customerInfoHelpers.ts +21 -0
  6. package/src/domains/subscription/application/SubscriptionAuthListener.ts +0 -19
  7. package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +2 -8
  8. package/src/domains/subscription/application/statusChangeHandlers.ts +0 -30
  9. package/src/domains/subscription/constants/thresholds.ts +10 -0
  10. package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +3 -3
  11. package/src/domains/subscription/infrastructure/handlers/package-operations/PackageFetcher.ts +0 -19
  12. package/src/domains/subscription/infrastructure/hooks/customer-info/useCustomerInfo.ts +1 -1
  13. package/src/domains/subscription/infrastructure/hooks/useInitializeSubscription.ts +2 -4
  14. package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +0 -44
  15. package/src/domains/subscription/infrastructure/services/CustomerInfoListenerManager.ts +1 -31
  16. package/src/domains/subscription/infrastructure/services/OfferingsFetcher.ts +0 -21
  17. package/src/domains/subscription/infrastructure/services/listeners/CustomerInfoHandler.ts +6 -36
  18. package/src/domains/subscription/infrastructure/services/purchase/PurchaseExecutor.ts +0 -6
  19. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +3 -44
  20. package/src/domains/subscription/presentation/featureGateActions.ts +0 -37
  21. package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.tsx +1 -1
  22. package/src/domains/subscription/presentation/screens/components/SubscriptionHeaderContent.tsx +1 -1
  23. package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +0 -43
  24. package/src/domains/subscription/presentation/useFeatureGate.ts +0 -39
  25. package/src/domains/subscription/presentation/useSubscriptionStatus.ts +6 -20
  26. package/src/domains/subscription/utils/authGuards.ts +26 -2
  27. package/src/domains/subscription/utils/expirationHelpers.ts +2 -2
  28. package/src/domains/wallet/presentation/hooks/useProductMetadata.ts +3 -6
  29. package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +3 -6
  30. package/src/shared/infrastructure/react-query/hooks/usePreviousUserCleanup.ts +39 -0
  31. package/src/shared/infrastructure/react-query/queryConfig.ts +22 -0
  32. package/src/shared/infrastructure/react-query/queryInvalidation.ts +46 -0
  33. package/src/shared/presentation/hooks/useServiceCall.ts +2 -1
  34. package/src/shared/utils/errorUtils.ts +32 -0
  35. package/src/utils/appUtils.ts +6 -0
  36. package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.constants.ts +0 -1
  37. package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.constants.ts +0 -1
@@ -4,8 +4,6 @@ export interface OfferingsFetcherDeps {
4
4
  isInitialized: () => boolean;
5
5
  }
6
6
 
7
- declare const __DEV__: boolean;
8
-
9
7
  export async function fetchOfferings(deps: OfferingsFetcherDeps): Promise<PurchasesOffering | null> {
10
8
  if (!deps.isInitialized()) return null;
11
9
  try {
@@ -21,35 +19,16 @@ export async function fetchOfferings(deps: OfferingsFetcherDeps): Promise<Purcha
21
19
  }
22
20
 
23
21
  if (offerings.current) {
24
- if (__DEV__) {
25
- console.log('[OfferingsFetcher] Using current offering:', {
26
- id: offerings.current.identifier,
27
- packagesCount: offerings.current.availablePackages.length,
28
- });
29
- }
30
22
  return offerings.current;
31
23
  }
32
24
 
33
25
  const allOfferings = Object.values(offerings.all);
34
26
  if (allOfferings.length > 0) {
35
- if (__DEV__) {
36
- console.log('[OfferingsFetcher] No current offering, using first from all:', {
37
- id: allOfferings[0].identifier,
38
- packagesCount: allOfferings[0].availablePackages.length,
39
- });
40
- }
41
27
  return allOfferings[0];
42
28
  }
43
29
 
44
- if (__DEV__) {
45
- console.warn('[OfferingsFetcher] No offerings available!');
46
- }
47
-
48
30
  return null;
49
31
  } catch (error) {
50
- if (__DEV__) {
51
- console.error('[OfferingsFetcher] Error:', error);
52
- }
53
32
  throw new Error(`Failed to fetch offerings: ${error instanceof Error ? error.message : "Unknown error"}`);
54
33
  }
55
34
  }
@@ -3,8 +3,6 @@ import type { RevenueCatConfig } from "../../../../revenuecat/core/types";
3
3
  import { syncPremiumStatus } from "../../utils/PremiumStatusSyncer";
4
4
  import { detectRenewal, updateRenewalState, type RenewalState } from "../../utils/renewal";
5
5
 
6
- declare const __DEV__: boolean;
7
-
8
6
  async function handleRenewal(
9
7
  userId: string,
10
8
  productId: string,
@@ -16,12 +14,8 @@ async function handleRenewal(
16
14
 
17
15
  try {
18
16
  await onRenewalDetected(userId, productId, expirationDate, customerInfo);
19
- } catch (error) {
20
- console.error('[CustomerInfoHandler] Renewal detection callback failed', {
21
- userId,
22
- productId,
23
- error
24
- });
17
+ } catch (_error) {
18
+ // Callback errors should not break customer info processing
25
19
  }
26
20
  }
27
21
 
@@ -37,14 +31,8 @@ async function handlePlanChange(
37
31
 
38
32
  try {
39
33
  await onPlanChanged(userId, newProductId, previousProductId, isUpgrade, customerInfo);
40
- } catch (error) {
41
- console.error('[CustomerInfoHandler] Plan change callback failed', {
42
- userId,
43
- newProductId,
44
- previousProductId,
45
- isUpgrade,
46
- error
47
- });
34
+ } catch (_error) {
35
+ // Callback errors should not break customer info processing
48
36
  }
49
37
  }
50
38
 
@@ -55,11 +43,8 @@ async function handlePremiumStatusSync(
55
43
  ): Promise<void> {
56
44
  try {
57
45
  await syncPremiumStatus(config, userId, customerInfo);
58
- } catch (error) {
59
- console.error('[CustomerInfoHandler] Premium status sync failed', {
60
- userId,
61
- error
62
- });
46
+ } catch (_error) {
47
+ // Sync errors are logged by PremiumStatusSyncer, don't break processing
63
48
  }
64
49
  }
65
50
 
@@ -84,19 +69,7 @@ export async function processCustomerInfo(
84
69
  config.entitlementIdentifier
85
70
  );
86
71
 
87
- if (typeof __DEV__ !== "undefined" && __DEV__) {
88
- console.log("[CustomerInfoHandler] Renewal detection result:", {
89
- isRenewal: renewalResult.isRenewal,
90
- isPlanChange: renewalResult.isPlanChange,
91
- productId: renewalResult.productId,
92
- previousProductId: renewalResult.previousProductId,
93
- });
94
- }
95
-
96
72
  if (renewalResult.isRenewal) {
97
- if (typeof __DEV__ !== "undefined" && __DEV__) {
98
- console.log("[CustomerInfoHandler] Handling renewal");
99
- }
100
73
  await handleRenewal(
101
74
  userId,
102
75
  renewalResult.productId!,
@@ -107,9 +80,6 @@ export async function processCustomerInfo(
107
80
  }
108
81
 
109
82
  if (renewalResult.isPlanChange) {
110
- if (typeof __DEV__ !== "undefined" && __DEV__) {
111
- console.log("[CustomerInfoHandler] Handling plan change");
112
- }
113
83
  await handlePlanChange(
114
84
  userId,
115
85
  renewalResult.productId!,
@@ -28,8 +28,6 @@ async function executeConsumablePurchase(
28
28
  };
29
29
  }
30
30
 
31
- declare const __DEV__: boolean;
32
-
33
31
  async function executeSubscriptionPurchase(
34
32
  config: RevenueCatConfig,
35
33
  userId: string,
@@ -59,10 +57,6 @@ async function executeSubscriptionPurchase(
59
57
 
60
58
  await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
61
59
 
62
- if (typeof __DEV__ !== "undefined" && __DEV__) {
63
- console.log("[PurchaseExecutor] Purchase flow completed successfully");
64
- }
65
-
66
60
  return {
67
61
  success: true,
68
62
  isPremium,
@@ -8,8 +8,6 @@ import type { RevenueCatConfig, PackageType } from "../../../revenuecat/core/typ
8
8
  import type { PurchaseSource } from "../../../subscription/core/SubscriptionConstants";
9
9
  import { getPremiumEntitlement } from "../../../revenuecat/core/types";
10
10
 
11
- declare const __DEV__: boolean;
12
-
13
11
  export async function syncPremiumStatus(
14
12
  config: RevenueCatConfig,
15
13
  userId: string,
@@ -25,9 +23,6 @@ export async function syncPremiumStatus(
25
23
  }
26
24
 
27
25
  if (!config.onPremiumStatusChanged) {
28
- if (typeof __DEV__ !== "undefined" && __DEV__) {
29
- console.log("[PremiumStatusSyncer] No onPremiumStatusChanged callback - skipping");
30
- }
31
26
  return { success: true };
32
27
  }
33
28
 
@@ -36,21 +31,8 @@ export async function syncPremiumStatus(
36
31
  config.entitlementIdentifier
37
32
  );
38
33
 
39
- if (typeof __DEV__ !== "undefined" && __DEV__) {
40
- console.log("[PremiumStatusSyncer] Premium entitlement:", {
41
- found: !!premiumEntitlement,
42
- productId: premiumEntitlement?.productIdentifier,
43
- expirationDate: premiumEntitlement?.expirationDate,
44
- willRenew: premiumEntitlement?.willRenew,
45
- periodType: premiumEntitlement?.periodType,
46
- });
47
- }
48
-
49
34
  try {
50
35
  if (premiumEntitlement) {
51
- if (typeof __DEV__ !== "undefined" && __DEV__) {
52
- console.log("[PremiumStatusSyncer] Calling onPremiumStatusChanged with premium=true");
53
- }
54
36
  await config.onPremiumStatusChanged(
55
37
  userId,
56
38
  true,
@@ -60,22 +42,11 @@ export async function syncPremiumStatus(
60
42
  premiumEntitlement.periodType as "NORMAL" | "INTRO" | "TRIAL" | undefined
61
43
  );
62
44
  } else {
63
- if (typeof __DEV__ !== "undefined" && __DEV__) {
64
- console.log("[PremiumStatusSyncer] Calling onPremiumStatusChanged with premium=false");
65
- }
66
45
  await config.onPremiumStatusChanged(userId, false, undefined, undefined, undefined, undefined);
67
46
  }
68
- if (typeof __DEV__ !== "undefined" && __DEV__) {
69
- console.log("[PremiumStatusSyncer] onPremiumStatusChanged completed successfully");
70
- }
71
47
  return { success: true };
72
48
  } catch (error) {
73
- console.error('[PremiumStatusSyncer] Premium status change callback failed', {
74
- userId,
75
- isPremium: !!premiumEntitlement,
76
- productId: premiumEntitlement?.productIdentifier,
77
- error
78
- });
49
+
79
50
  return {
80
51
  success: false,
81
52
  error: error instanceof Error ? error : new Error(String(error))
@@ -97,14 +68,7 @@ export async function notifyPurchaseCompleted(
97
68
 
98
69
  try {
99
70
  await config.onPurchaseCompleted(userId, productId, customerInfo, source, packageType);
100
- } catch (error) {
101
- console.error('[PremiumStatusSyncer] Purchase completion callback failed', {
102
- userId,
103
- productId,
104
- source,
105
- packageType,
106
- error
107
- });
71
+ } catch (_error) {
108
72
  // Silently fail callback notifications to prevent crashing the main flow
109
73
  }
110
74
  }
@@ -121,12 +85,7 @@ export async function notifyRestoreCompleted(
121
85
 
122
86
  try {
123
87
  await config.onRestoreCompleted(userId, isPremium, customerInfo);
124
- } catch (error) {
125
- console.error('[PremiumStatusSyncer] Restore completion callback failed', {
126
- userId,
127
- isPremium,
128
- error
129
- });
88
+ } catch (_error) {
130
89
  // Silently fail callback notifications to prevent crashing the main flow
131
90
  }
132
91
  }
@@ -1,7 +1,5 @@
1
1
  import type { MutableRefObject } from "react";
2
2
 
3
- declare const __DEV__: boolean;
4
-
5
3
  export const executeFeatureAction = (
6
4
  action: () => void | Promise<void>,
7
5
  isAuthenticated: boolean,
@@ -15,45 +13,20 @@ export const executeFeatureAction = (
15
13
  isWaitingForPurchaseRef: MutableRefObject<boolean>,
16
14
  isCreditsLoadedRef: MutableRefObject<boolean>
17
15
  ): boolean => {
18
- if (typeof __DEV__ !== "undefined" && __DEV__) {
19
- console.log("[FeatureGate] executeFeatureAction called:", {
20
- isAuthenticated,
21
- hasSubscription: hasSubscriptionRef.current,
22
- creditBalance: creditBalanceRef.current,
23
- requiredCredits: requiredCreditsRef.current,
24
- isCreditsLoaded: isCreditsLoadedRef.current,
25
- });
26
- }
27
16
 
28
17
  if (!isAuthenticated) {
29
- if (typeof __DEV__ !== "undefined" && __DEV__) {
30
- console.log("[FeatureGate] User not authenticated, showing auth modal");
31
- }
32
18
  const postAuthAction = () => {
33
- if (typeof __DEV__ !== "undefined" && __DEV__) {
34
- console.log("[FeatureGate] Post-auth action called");
35
- }
36
19
  if (hasSubscriptionRef.current || creditBalanceRef.current >= requiredCreditsRef.current) {
37
- if (typeof __DEV__ !== "undefined" && __DEV__) {
38
- console.log("[FeatureGate] Post-auth: User has access, executing action");
39
- }
40
20
  action();
41
21
  return;
42
22
  }
43
23
 
44
24
  if (isCreditsLoadedRef.current) {
45
- if (typeof __DEV__ !== "undefined" && __DEV__) {
46
- console.log("[FeatureGate] Post-auth: Credits loaded, showing paywall");
47
- }
48
25
  pendingActionRef.current = action;
49
26
  isWaitingForPurchaseRef.current = true;
50
27
  onShowPaywallRef.current(requiredCreditsRef.current);
51
28
  return;
52
29
  }
53
-
54
- if (typeof __DEV__ !== "undefined" && __DEV__) {
55
- console.log("[FeatureGate] Post-auth: Waiting for credits to load");
56
- }
57
30
  pendingActionRef.current = action;
58
31
  isWaitingForAuthCreditsRef.current = true;
59
32
  };
@@ -62,26 +35,16 @@ export const executeFeatureAction = (
62
35
  }
63
36
 
64
37
  if (hasSubscriptionRef.current) {
65
- if (typeof __DEV__ !== "undefined" && __DEV__) {
66
- console.log("[FeatureGate] User has subscription, executing action");
67
- }
68
38
  action();
69
39
  return true;
70
40
  }
71
41
 
72
42
  if (creditBalanceRef.current < requiredCreditsRef.current) {
73
- if (typeof __DEV__ !== "undefined" && __DEV__) {
74
- console.log("[FeatureGate] Insufficient credits, showing paywall");
75
- }
76
43
  pendingActionRef.current = action;
77
44
  isWaitingForPurchaseRef.current = true;
78
45
  onShowPaywallRef.current(requiredCreditsRef.current);
79
46
  return false;
80
47
  }
81
-
82
- if (typeof __DEV__ !== "undefined" && __DEV__) {
83
- console.log("[FeatureGate] User has enough credits, executing action");
84
- }
85
48
  action();
86
49
  return true;
87
50
  };
@@ -4,7 +4,7 @@ import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-syst
4
4
  import { PremiumStatusBadge } from "../../components/details/PremiumStatusBadge";
5
5
  import type { SubscriptionHeaderProps } from "./SubscriptionHeader.types";
6
6
  import { createSubscriptionHeaderStyles } from "./SubscriptionHeader.styles";
7
- import { EXPIRING_SOON_THRESHOLD_DAYS } from "./SubscriptionHeader.constants";
7
+ import { EXPIRATION_WARNING_THRESHOLD_DAYS as EXPIRING_SOON_THRESHOLD_DAYS } from "../../../constants/thresholds";
8
8
  import { SubscriptionHeaderContent } from "./SubscriptionHeaderContent";
9
9
 
10
10
  export type { SubscriptionHeaderProps } from "./SubscriptionHeader.types";
@@ -31,7 +31,7 @@ export const SubscriptionHeaderContent: React.FC<SubscriptionHeaderContentProps>
31
31
  translations,
32
32
  styles,
33
33
  willRenew,
34
- periodType,
34
+ periodType: _periodType,
35
35
  packageType,
36
36
  store,
37
37
  originalPurchaseDate,
@@ -9,8 +9,6 @@ import { usePremium } from "./usePremium";
9
9
  import type { PurchaseSource } from "../core/SubscriptionConstants";
10
10
  import { authPurchaseStateManager } from "../infrastructure/utils/authPurchaseState";
11
11
 
12
- declare const __DEV__: boolean;
13
-
14
12
  export type { PurchaseAuthProvider } from "../infrastructure/utils/authPurchaseState";
15
13
 
16
14
  export const configureAuthProvider = (provider: import("../infrastructure/utils/authPurchaseState").PurchaseAuthProvider): void => {
@@ -46,84 +44,43 @@ export const useAuthAwarePurchase = (
46
44
 
47
45
  const handlePurchase = useCallback(
48
46
  async (pkg: PurchasesPackage, source?: PurchaseSource): Promise<boolean> => {
49
- if (typeof __DEV__ !== "undefined" && __DEV__) {
50
- console.log("[useAuthAwarePurchase] handlePurchase called", {
51
- productId: pkg.product.identifier,
52
- source: source || params?.source,
53
- });
54
- }
55
47
 
56
48
  const authProvider = authPurchaseStateManager.getProvider();
57
49
 
58
50
  if (!authProvider) {
59
- if (typeof __DEV__ !== "undefined" && __DEV__) {
60
- console.error("[useAuthAwarePurchase] ❌ No auth provider configured");
61
- }
62
51
  return false;
63
52
  }
64
53
 
65
54
  const isAuth = authProvider.isAuthenticated();
66
55
 
67
- if (typeof __DEV__ !== "undefined" && __DEV__) {
68
- console.log("[useAuthAwarePurchase] Auth status:", { isAuth });
69
- }
70
-
71
56
  if (!isAuth) {
72
- if (typeof __DEV__ !== "undefined" && __DEV__) {
73
- console.log("[useAuthAwarePurchase] 🔐 User not authenticated, saving purchase and showing auth modal");
74
- }
75
57
  authPurchaseStateManager.savePurchase(pkg, source || params?.source || "settings");
76
58
  authProvider.showAuthModal();
77
59
  return false;
78
60
  }
79
61
 
80
- if (typeof __DEV__ !== "undefined" && __DEV__) {
81
- console.log("[useAuthAwarePurchase] ✅ User authenticated, proceeding with purchase");
82
- }
83
-
84
62
  const result = await purchasePackage(pkg);
85
63
 
86
- if (typeof __DEV__ !== "undefined" && __DEV__) {
87
- console.log("[useAuthAwarePurchase] Purchase result:", result);
88
- }
89
-
90
64
  return result;
91
65
  },
92
66
  [purchasePackage, params?.source]
93
67
  );
94
68
 
95
69
  const handleRestore = useCallback(async (): Promise<boolean> => {
96
- if (typeof __DEV__ !== "undefined" && __DEV__) {
97
- console.log("[useAuthAwarePurchase] handleRestore called");
98
- }
99
70
 
100
71
  const authProvider = authPurchaseStateManager.getProvider();
101
72
 
102
73
  if (!authProvider) {
103
- if (typeof __DEV__ !== "undefined" && __DEV__) {
104
- console.error("[useAuthAwarePurchase] ❌ No auth provider configured");
105
- }
106
74
  return false;
107
75
  }
108
76
 
109
77
  if (!authProvider.isAuthenticated()) {
110
- if (typeof __DEV__ !== "undefined" && __DEV__) {
111
- console.log("[useAuthAwarePurchase] 🔐 User not authenticated, showing auth modal");
112
- }
113
78
  authProvider.showAuthModal();
114
79
  return false;
115
80
  }
116
81
 
117
- if (typeof __DEV__ !== "undefined" && __DEV__) {
118
- console.log("[useAuthAwarePurchase] ✅ User authenticated, proceeding with restore");
119
- }
120
-
121
82
  const result = await restorePurchase();
122
83
 
123
- if (typeof __DEV__ !== "undefined" && __DEV__) {
124
- console.log("[useAuthAwarePurchase] Restore result:", result);
125
- }
126
-
127
84
  return result;
128
85
  }, [restorePurchase]);
129
86
 
@@ -6,8 +6,6 @@ import { executeFeatureAction } from "./featureGateActions";
6
6
 
7
7
  export type { UseFeatureGateParams, UseFeatureGateResult } from "./useFeatureGate.types";
8
8
 
9
- declare const __DEV__: boolean;
10
-
11
9
  export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResult {
12
10
  const {
13
11
  isAuthenticated,
@@ -27,16 +25,6 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
27
25
  const { creditBalanceRef, hasSubscriptionRef, onShowPaywallRef, requiredCreditsRef, isCreditsLoadedRef } = useSyncedRefs(creditBalance, hasSubscription, onShowPaywall, requiredCredits, isCreditsLoaded);
28
26
 
29
27
  useEffect(() => {
30
- if (typeof __DEV__ !== "undefined" && __DEV__) {
31
- console.log("[FeatureGate] Auth completion useEffect triggered:", {
32
- isWaitingForAuthCredits: isWaitingForAuthCreditsRef.current,
33
- isCreditsLoaded,
34
- hasPendingAction: !!pendingActionRef.current,
35
- hasSubscription,
36
- creditBalance,
37
- requiredCredits,
38
- });
39
- }
40
28
 
41
29
  const shouldExecute = canExecuteAuthAction(
42
30
  isWaitingForAuthCreditsRef.current,
@@ -47,14 +35,7 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
47
35
  requiredCredits
48
36
  );
49
37
 
50
- if (typeof __DEV__ !== "undefined" && __DEV__) {
51
- console.log("[FeatureGate] canExecuteAuthAction:", shouldExecute);
52
- }
53
-
54
38
  if (shouldExecute) {
55
- if (typeof __DEV__ !== "undefined" && __DEV__) {
56
- console.log("[FeatureGate] ✅ EXECUTING PENDING ACTION after auth!");
57
- }
58
39
  isWaitingForAuthCreditsRef.current = false;
59
40
  const action = pendingActionRef.current!;
60
41
  pendingActionRef.current = null;
@@ -63,9 +44,6 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
63
44
  }
64
45
 
65
46
  if (isWaitingForAuthCreditsRef.current && isCreditsLoaded && pendingActionRef.current) {
66
- if (typeof __DEV__ !== "undefined" && __DEV__) {
67
- console.log("[FeatureGate] Auth credits loaded but insufficient, showing paywall");
68
- }
69
47
  isWaitingForAuthCreditsRef.current = false;
70
48
  isWaitingForPurchaseRef.current = true;
71
49
  onShowPaywall(requiredCredits);
@@ -73,16 +51,6 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
73
51
  }, [isCreditsLoaded, creditBalance, hasSubscription, requiredCredits, onShowPaywall]);
74
52
 
75
53
  useEffect(() => {
76
- if (typeof __DEV__ !== "undefined" && __DEV__) {
77
- console.log("[FeatureGate] Purchase completion useEffect triggered:", {
78
- creditBalance,
79
- prevCreditBalance: prevCreditBalanceRef.current,
80
- hasSubscription,
81
- prevHasSubscription: hasSubscriptionRef.current,
82
- isWaitingForPurchase: isWaitingForPurchaseRef.current,
83
- hasPendingAction: !!pendingActionRef.current,
84
- });
85
- }
86
54
 
87
55
  const shouldExecute = canExecutePurchaseAction(
88
56
  isWaitingForPurchaseRef.current,
@@ -93,14 +61,7 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
93
61
  !!pendingActionRef.current
94
62
  );
95
63
 
96
- if (typeof __DEV__ !== "undefined" && __DEV__) {
97
- console.log("[FeatureGate] canExecutePurchaseAction:", shouldExecute);
98
- }
99
-
100
64
  if (shouldExecute) {
101
- if (typeof __DEV__ !== "undefined" && __DEV__) {
102
- console.log("[FeatureGate] ✅ EXECUTING PENDING ACTION after purchase!");
103
- }
104
65
  const action = pendingActionRef.current!;
105
66
  pendingActionRef.current = null;
106
67
  isWaitingForPurchaseRef.current = false;
@@ -1,10 +1,12 @@
1
1
  import { useQuery, useQueryClient } from "@umituz/react-native-design-system";
2
- import { useEffect, useRef } from "react";
2
+ import { useEffect } from "react";
3
3
  import { useAuthStore, selectUserId } from "@umituz/react-native-auth";
4
4
  import { SubscriptionManager } from "../infrastructure/managers/SubscriptionManager";
5
5
  import { subscriptionEventBus, SUBSCRIPTION_EVENTS } from "../../../shared/infrastructure/SubscriptionEventBus";
6
6
  import { SubscriptionStatusResult } from "./useSubscriptionStatus.types";
7
7
  import { isAuthenticated } from "../utils/authGuards";
8
+ import { NO_CACHE_QUERY_CONFIG } from "../../../shared/infrastructure/react-query/queryConfig";
9
+ import { usePreviousUserCleanup } from "../../../shared/infrastructure/react-query/hooks/usePreviousUserCleanup";
8
10
 
9
11
  export const subscriptionStatusQueryKeys = {
10
12
  all: ["subscriptionStatus"] as const,
@@ -38,27 +40,11 @@ export const useSubscriptionStatus = (): SubscriptionStatusResult => {
38
40
  }
39
41
  },
40
42
  enabled: queryEnabled,
41
- gcTime: 0,
42
- staleTime: 0,
43
- refetchOnMount: "always",
44
- refetchOnWindowFocus: "always",
45
- refetchOnReconnect: "always",
43
+ ...NO_CACHE_QUERY_CONFIG,
46
44
  });
47
45
 
48
- // Track previous userId to clear stale cache on logout/user switch
49
- const prevUserIdRef = useRef(userId);
50
-
51
- useEffect(() => {
52
- const prevUserId = prevUserIdRef.current;
53
- prevUserIdRef.current = userId;
54
-
55
- // Clear previous user's cache when userId changes (logout or user switch)
56
- if (prevUserId !== userId && isAuthenticated(prevUserId)) {
57
- queryClient.removeQueries({
58
- queryKey: subscriptionStatusQueryKeys.user(prevUserId),
59
- });
60
- }
61
- }, [userId, queryClient]);
46
+ // Clean up previous user's cache on logout/user switch
47
+ usePreviousUserCleanup(userId, queryClient, subscriptionStatusQueryKeys.user);
62
48
 
63
49
  useEffect(() => {
64
50
  if (!isAuthenticated(userId)) return undefined;
@@ -1,5 +1,29 @@
1
1
  import { isDefined } from "../../../shared/utils/validators";
2
2
 
3
- export const isAuthenticated = (userId: string | null | undefined): userId is string => {
3
+ export function isAuthenticated(userId: string | null | undefined): userId is string {
4
4
  return isDefined(userId) && userId.length > 0;
5
- };
5
+ }
6
+
7
+ /**
8
+ * Requires user to be authenticated, throws if not
9
+ * Type guard that asserts userId is string
10
+ *
11
+ * @param userId - User ID to check
12
+ * @param errorMessage - Custom error message (optional)
13
+ * @throws Error if user is not authenticated
14
+ *
15
+ * @example
16
+ * function purchaseProduct(userId: string | null) {
17
+ * requireAuthentication(userId); // throws if null/undefined
18
+ * // TypeScript now knows userId is string
19
+ * await purchase(userId);
20
+ * }
21
+ */
22
+ export function requireAuthentication(
23
+ userId: string | null | undefined,
24
+ errorMessage = "User not authenticated"
25
+ ): asserts userId is string {
26
+ if (!isAuthenticated(userId)) {
27
+ throw new Error(errorMessage);
28
+ }
29
+ }
@@ -1,5 +1,5 @@
1
- const DAYS_REMAINING_WARNING_THRESHOLD = 7;
1
+ import { EXPIRATION_WARNING_THRESHOLD_DAYS } from '../constants/thresholds';
2
2
 
3
3
  export function shouldHighlightExpiration(daysRemaining: number | null | undefined): boolean {
4
- return daysRemaining !== null && daysRemaining !== undefined && daysRemaining > 0 && daysRemaining <= DAYS_REMAINING_WARNING_THRESHOLD;
4
+ return daysRemaining !== null && daysRemaining !== undefined && daysRemaining > 0 && daysRemaining <= EXPIRATION_WARNING_THRESHOLD_DAYS;
5
5
  }
@@ -1,5 +1,6 @@
1
1
  import { useQuery } from "@umituz/react-native-design-system";
2
2
  import { useMemo } from "react";
3
+ import { NO_CACHE_QUERY_CONFIG } from "../../../../shared/infrastructure/react-query/queryConfig";
3
4
  import type {
4
5
  ProductMetadata,
5
6
  ProductMetadataConfig,
@@ -34,7 +35,7 @@ export function useProductMetadata({
34
35
  }: UseProductMetadataParams): UseProductMetadataResult {
35
36
  const service = useMemo(
36
37
  () => new ProductMetadataService(config),
37
- [config.collectionName, config.cacheTTL]
38
+ [config]
38
39
  );
39
40
 
40
41
  const queryKey = type
@@ -50,11 +51,7 @@ export function useProductMetadata({
50
51
  return service.getAll();
51
52
  },
52
53
  enabled,
53
- gcTime: 0,
54
- staleTime: 0,
55
- refetchOnMount: "always",
56
- refetchOnWindowFocus: "always",
57
- refetchOnReconnect: "always",
54
+ ...NO_CACHE_QUERY_CONFIG,
58
55
  });
59
56
 
60
57
  const products = data ?? [];
@@ -1,6 +1,7 @@
1
1
  import { useQuery } from "@umituz/react-native-design-system";
2
2
  import { useMemo } from "react";
3
3
  import { useAuthStore, selectUserId } from "@umituz/react-native-auth";
4
+ import { NO_CACHE_QUERY_CONFIG } from "../../../../shared/infrastructure/react-query/queryConfig";
4
5
  import type {
5
6
  CreditLog,
6
7
  TransactionRepositoryConfig,
@@ -33,7 +34,7 @@ export function useTransactionHistory({
33
34
 
34
35
  const repository = useMemo(
35
36
  () => new TransactionRepository(config),
36
- [config.collectionName, config.useUserSubcollection]
37
+ [config]
37
38
  );
38
39
 
39
40
  const { data, isLoading, error, refetch } = useQuery({
@@ -53,11 +54,7 @@ export function useTransactionHistory({
53
54
  return result.data ?? [];
54
55
  },
55
56
  enabled: !!userId,
56
- gcTime: 0,
57
- staleTime: 0,
58
- refetchOnMount: "always",
59
- refetchOnWindowFocus: "always",
60
- refetchOnReconnect: "always",
57
+ ...NO_CACHE_QUERY_CONFIG,
61
58
  });
62
59
 
63
60
  const transactions = data ?? [];