@umituz/react-native-subscription 2.27.2 → 2.27.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.27.2",
3
+ "version": "2.27.3",
4
4
  "description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { useQuery } from "@umituz/react-native-design-system";
10
- import { useCallback, useMemo, useEffect } from "react";
10
+ import { useCallback, useMemo, useEffect, useState } from "react";
11
11
  import {
12
12
  useAuthStore,
13
13
  selectUserId,
@@ -32,6 +32,7 @@ const freeCreditsInitAttempted = new Set<string>();
32
32
  export interface UseCreditsResult {
33
33
  credits: UserCredits | null;
34
34
  isLoading: boolean;
35
+ isCreditsLoaded: boolean;
35
36
  error: Error | null;
36
37
  hasCredits: boolean;
37
38
  creditsPercent: number;
@@ -43,6 +44,7 @@ export const useCredits = (): UseCreditsResult => {
43
44
  const userId = useAuthStore(selectUserId);
44
45
  const isAnonymous = useAuthStore(selectIsAnonymous);
45
46
  const isRegisteredUser = !!userId && !isAnonymous;
47
+ const [isInitializingFreeCredits, setIsInitializingFreeCredits] = useState(false);
46
48
 
47
49
  const isConfigured = isCreditsRepositoryConfigured();
48
50
  const config = getCreditsConfig();
@@ -95,6 +97,7 @@ export const useCredits = (): UseCreditsResult => {
95
97
  !freeCreditsInitAttempted.has(userId)
96
98
  ) {
97
99
  freeCreditsInitAttempted.add(userId);
100
+ setIsInitializingFreeCredits(true);
98
101
 
99
102
  if (typeof __DEV__ !== "undefined" && __DEV__) {
100
103
  console.log("[useCredits] Initializing free credits for registered user:", userId.slice(0, 8));
@@ -102,6 +105,7 @@ export const useCredits = (): UseCreditsResult => {
102
105
 
103
106
  const repository = getCreditsRepository();
104
107
  repository.initializeFreeCredits(userId).then((result) => {
108
+ setIsInitializingFreeCredits(false);
105
109
  if (result.success) {
106
110
  if (typeof __DEV__ !== "undefined" && __DEV__) {
107
111
  console.log("[useCredits] Free credits initialized:", result.data?.credits);
@@ -137,9 +141,12 @@ export const useCredits = (): UseCreditsResult => {
137
141
  [credits]
138
142
  );
139
143
 
144
+ const isCreditsLoaded = isFetched && !isLoading && !isInitializingFreeCredits;
145
+
140
146
  return {
141
147
  credits,
142
148
  isLoading,
149
+ isCreditsLoaded,
143
150
  error: error as Error | null,
144
151
  hasCredits: derivedValues.hasCredits,
145
152
  creditsPercent: derivedValues.creditsPercent,
@@ -2,6 +2,7 @@
2
2
  * useFeatureGate Hook
3
3
  * Unified feature gate: Auth → Subscription → Credits
4
4
  * Uses ref pattern to avoid stale closure issues.
5
+ * Event-driven approach - no polling, no waiting.
5
6
  */
6
7
 
7
8
  import { useCallback, useRef, useEffect } from "react";
@@ -15,6 +16,7 @@ export interface UseFeatureGateParams {
15
16
  readonly creditBalance: number;
16
17
  readonly requiredCredits?: number;
17
18
  readonly onShowPaywall: (requiredCredits?: number) => void;
19
+ readonly isCreditsLoaded?: boolean;
18
20
  }
19
21
 
20
22
  export interface UseFeatureGateResult {
@@ -34,15 +36,18 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
34
36
  creditBalance,
35
37
  requiredCredits = 1,
36
38
  onShowPaywall,
39
+ isCreditsLoaded = true,
37
40
  } = params;
38
41
 
39
42
  const pendingActionRef = useRef<(() => void | Promise<void>) | null>(null);
40
43
  const prevCreditBalanceRef = useRef(creditBalance);
41
44
  const isWaitingForPurchaseRef = useRef(false);
45
+ const isWaitingForAuthCreditsRef = useRef(false);
42
46
 
43
47
  const creditBalanceRef = useRef(creditBalance);
44
48
  const hasSubscriptionRef = useRef(hasSubscription);
45
49
  const onShowPaywallRef = useRef(onShowPaywall);
50
+ const requiredCreditsRef = useRef(requiredCredits);
46
51
 
47
52
  useEffect(() => {
48
53
  creditBalanceRef.current = creditBalance;
@@ -56,6 +61,48 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
56
61
  onShowPaywallRef.current = onShowPaywall;
57
62
  }, [onShowPaywall]);
58
63
 
64
+ useEffect(() => {
65
+ requiredCreditsRef.current = requiredCredits;
66
+ }, [requiredCredits]);
67
+
68
+ useEffect(() => {
69
+ if (!isWaitingForAuthCreditsRef.current || !isCreditsLoaded || !pendingActionRef.current) {
70
+ return;
71
+ }
72
+
73
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
74
+ console.log("[useFeatureGate] Credits loaded after auth", {
75
+ credits: creditBalance,
76
+ hasSubscription,
77
+ isCreditsLoaded,
78
+ });
79
+ }
80
+
81
+ isWaitingForAuthCreditsRef.current = false;
82
+
83
+ if (hasSubscription || creditBalance >= requiredCredits) {
84
+ const action = pendingActionRef.current;
85
+ pendingActionRef.current = null;
86
+
87
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
88
+ console.log("[useFeatureGate] Proceeding with action after auth", {
89
+ credits: creditBalance,
90
+ hasSubscription,
91
+ });
92
+ }
93
+ action();
94
+ return;
95
+ }
96
+
97
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
98
+ console.log("[useFeatureGate] No credits after auth, showing paywall", {
99
+ credits: creditBalance,
100
+ });
101
+ }
102
+ isWaitingForPurchaseRef.current = true;
103
+ onShowPaywall(requiredCredits);
104
+ }, [isCreditsLoaded, creditBalance, hasSubscription, requiredCredits, onShowPaywall]);
105
+
59
106
  useEffect(() => {
60
107
  const prevBalance = prevCreditBalanceRef.current ?? 0;
61
108
  const creditsIncreased = creditBalance > prevBalance;
@@ -82,57 +129,18 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
82
129
  hasSubscription,
83
130
  creditBalance: creditBalanceRef.current,
84
131
  requiredCredits,
132
+ isCreditsLoaded,
85
133
  });
86
134
  }
87
135
 
88
136
  if (!isAuthenticated) {
89
- const postAuthAction = async () => {
90
- // Wait for free credits to initialize after registration (max 3 seconds)
91
- const maxWaitTime = 3000;
92
- const checkInterval = 100;
93
- let waited = 0;
94
-
95
- while (waited < maxWaitTime) {
96
- await new Promise((resolve) => setTimeout(resolve, checkInterval));
97
- waited += checkInterval;
98
-
99
- if (creditBalanceRef.current > 0 || hasSubscriptionRef.current) {
100
- if (typeof __DEV__ !== "undefined" && __DEV__) {
101
- console.log("[useFeatureGate] Credits/subscription detected after auth", {
102
- credits: creditBalanceRef.current,
103
- hasSubscription: hasSubscriptionRef.current,
104
- waitedMs: waited,
105
- });
106
- }
107
- break;
108
- }
109
- }
110
-
111
- if (hasSubscriptionRef.current) {
112
- action();
113
- return;
114
- }
115
-
116
- const currentBalance = creditBalanceRef.current;
117
- if (currentBalance < requiredCredits) {
118
- if (typeof __DEV__ !== "undefined" && __DEV__) {
119
- console.log("[useFeatureGate] No credits after waiting, showing paywall", {
120
- credits: currentBalance,
121
- waitedMs: waited,
122
- });
123
- }
124
- pendingActionRef.current = action;
125
- isWaitingForPurchaseRef.current = true;
126
- onShowPaywallRef.current(requiredCredits);
127
- return;
128
- }
137
+ const postAuthAction = () => {
138
+ pendingActionRef.current = action;
139
+ isWaitingForAuthCreditsRef.current = true;
129
140
 
130
141
  if (typeof __DEV__ !== "undefined" && __DEV__) {
131
- console.log("[useFeatureGate] Proceeding with action after auth", {
132
- credits: currentBalance,
133
- });
142
+ console.log("[useFeatureGate] Auth completed, waiting for credits to load");
134
143
  }
135
- action();
136
144
  };
137
145
  onShowAuthModal(postAuthAction);
138
146
  return;
@@ -156,7 +164,7 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
156
164
 
157
165
  action();
158
166
  },
159
- [isAuthenticated, hasSubscription, requiredCredits, onShowAuthModal, onShowPaywall]
167
+ [isAuthenticated, hasSubscription, requiredCredits, onShowAuthModal, onShowPaywall, isCreditsLoaded]
160
168
  );
161
169
 
162
170
  const hasCredits = creditBalance >= requiredCredits;