@umituz/react-native-subscription 2.26.13 → 2.26.14

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.26.13",
3
+ "version": "2.26.14",
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",
@@ -2,76 +2,62 @@
2
2
  * useCreditsGate Hook
3
3
  *
4
4
  * Single responsibility: Credits gating
5
- * Checks if user has enough credits before allowing actions.
6
- *
7
- * @example
8
- * ```typescript
9
- * const { requireCredits, hasCredits } = useCreditsGate({
10
- * hasCredits: canAfford(cost),
11
- * creditBalance: credits,
12
- * requiredCredits: cost,
13
- * onCreditsRequired: (required) => showPaywall(required),
14
- * });
15
- *
16
- * const handleGenerate = () => {
17
- * requireCredits(() => generate());
18
- * };
19
- * ```
5
+ * Uses ref pattern to avoid stale closure issues.
20
6
  */
21
7
 
22
- import { useCallback } from "react";
8
+ import { useCallback, useRef, useEffect } from "react";
9
+
10
+ declare const __DEV__: boolean;
23
11
 
24
12
  export interface UseCreditsGateParams {
25
- /** Whether user has enough credits for the action */
26
- hasCredits: boolean;
27
- /** Current credit balance */
28
- creditBalance: number;
29
- /** Credits required for this action (optional, for display) */
30
- requiredCredits?: number;
31
- /** Callback when credits are required - receives required amount */
32
- onCreditsRequired: (requiredCredits?: number) => void;
13
+ readonly creditBalance: number;
14
+ readonly requiredCredits?: number;
15
+ readonly onCreditsRequired: (requiredCredits?: number) => void;
33
16
  }
34
17
 
35
18
  export interface UseCreditsGateResult {
36
- /** Whether user has enough credits */
37
- hasCredits: boolean;
38
- /** Current credit balance */
39
- creditBalance: number;
40
- /** Gate action behind credits - executes if has credits, else shows paywall */
41
- requireCredits: (action: () => void | Promise<void>) => boolean;
19
+ readonly hasCredits: boolean;
20
+ readonly creditBalance: number;
21
+ readonly requireCredits: () => boolean;
42
22
  }
43
23
 
44
- declare const __DEV__: boolean;
45
-
46
24
  export function useCreditsGate(
47
25
  params: UseCreditsGateParams
48
26
  ): UseCreditsGateResult {
49
- const { hasCredits, creditBalance, requiredCredits, onCreditsRequired } =
50
- params;
27
+ const { creditBalance, requiredCredits = 1, onCreditsRequired } = params;
28
+
29
+ const creditBalanceRef = useRef(creditBalance);
30
+ const onCreditsRequiredRef = useRef(onCreditsRequired);
31
+
32
+ useEffect(() => {
33
+ creditBalanceRef.current = creditBalance;
34
+ }, [creditBalance]);
35
+
36
+ useEffect(() => {
37
+ onCreditsRequiredRef.current = onCreditsRequired;
38
+ }, [onCreditsRequired]);
39
+
40
+ const hasCredits = creditBalance >= requiredCredits;
41
+
42
+ const requireCredits = useCallback((): boolean => {
43
+ const currentBalance = creditBalanceRef.current;
44
+ const canAfford = currentBalance >= requiredCredits;
45
+
46
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
47
+ console.log("[useCreditsGate] requireCredits", {
48
+ currentBalance,
49
+ requiredCredits,
50
+ canAfford,
51
+ });
52
+ }
53
+
54
+ if (!canAfford) {
55
+ onCreditsRequiredRef.current(requiredCredits);
56
+ return false;
57
+ }
51
58
 
52
- const requireCredits = useCallback(
53
- (_action: () => void | Promise<void>): boolean => {
54
- if (typeof __DEV__ !== "undefined" && __DEV__) {
55
- console.log("[useCreditsGate] requireCredits called", {
56
- hasCredits,
57
- creditBalance,
58
- requiredCredits,
59
- });
60
- }
61
- if (!hasCredits) {
62
- if (typeof __DEV__ !== "undefined" && __DEV__) {
63
- console.log("[useCreditsGate] No credits, showing paywall");
64
- }
65
- onCreditsRequired(requiredCredits);
66
- return false;
67
- }
68
- if (typeof __DEV__ !== "undefined" && __DEV__) {
69
- console.log("[useCreditsGate] Has credits, allowing action");
70
- }
71
- return true;
72
- },
73
- [hasCredits, creditBalance, requiredCredits, onCreditsRequired]
74
- );
59
+ return true;
60
+ }, [requiredCredits]);
75
61
 
76
62
  return {
77
63
  hasCredits,
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * useFeatureGate Hook
3
3
  * Combines auth, subscription, and credits gates into a unified feature gate.
4
+ * Uses ref pattern to avoid stale closure issues.
4
5
  */
5
6
 
6
7
  import { useCallback, useRef, useEffect } from "react";
@@ -10,38 +11,22 @@ import { useCreditsGate } from "./useCreditsGate";
10
11
 
11
12
  declare const __DEV__: boolean;
12
13
 
13
-
14
-
15
14
  export interface UseFeatureGateParams {
16
- /** Whether user is authenticated (not guest/anonymous) */
17
- isAuthenticated: boolean;
18
- /** Callback to show auth modal with pending action */
19
- onShowAuthModal: (pendingCallback: () => void | Promise<void>) => void;
20
- /** Whether user has active subscription (optional, defaults to false) */
21
- hasSubscription?: boolean;
22
- /** Whether user has enough credits for the action */
23
- hasCredits: boolean;
24
- /** Current credit balance */
25
- creditBalance: number;
26
- /** Credits required for this action (optional, for paywall display) */
27
- requiredCredits?: number;
28
- /** Callback to show paywall - receives required credits */
29
- onShowPaywall: (requiredCredits?: number) => void;
15
+ readonly isAuthenticated: boolean;
16
+ readonly onShowAuthModal: (pendingCallback: () => void | Promise<void>) => void;
17
+ readonly hasSubscription?: boolean;
18
+ readonly creditBalance: number;
19
+ readonly requiredCredits?: number;
20
+ readonly onShowPaywall: (requiredCredits?: number) => void;
30
21
  }
31
22
 
32
23
  export interface UseFeatureGateResult {
33
- /** Gate a feature - checks auth, subscription, then credits */
34
- requireFeature: (action: () => void | Promise<void>) => void;
35
- /** Whether user is authenticated */
36
- isAuthenticated: boolean;
37
- /** Whether user has active subscription */
38
- hasSubscription: boolean;
39
- /** Whether user has enough credits */
40
- hasCredits: boolean;
41
- /** Current credit balance */
42
- creditBalance: number;
43
- /** Whether feature access is allowed */
44
- canAccess: boolean;
24
+ readonly requireFeature: (action: () => void | Promise<void>) => void;
25
+ readonly isAuthenticated: boolean;
26
+ readonly hasSubscription: boolean;
27
+ readonly hasCredits: boolean;
28
+ readonly creditBalance: number;
29
+ readonly canAccess: boolean;
45
30
  }
46
31
 
47
32
  export function useFeatureGate(
@@ -51,25 +36,22 @@ export function useFeatureGate(
51
36
  isAuthenticated,
52
37
  onShowAuthModal,
53
38
  hasSubscription = false,
54
- hasCredits,
55
39
  creditBalance,
56
- requiredCredits,
40
+ requiredCredits = 1,
57
41
  onShowPaywall,
58
42
  } = params;
59
43
 
60
- // Store pending action for execution after purchase
61
44
  const pendingActionRef = useRef<(() => void | Promise<void>) | null>(null);
62
45
  const prevCreditBalanceRef = useRef(creditBalance);
63
46
  const isWaitingForPurchaseRef = useRef(false);
64
47
 
65
- // Refs to always get current values in closures
66
- const hasCreditsRef = useRef(hasCredits);
48
+ const creditBalanceRef = useRef(creditBalance);
67
49
  const hasSubscriptionRef = useRef(hasSubscription);
68
50
  const onShowPaywallRef = useRef(onShowPaywall);
69
51
 
70
52
  useEffect(() => {
71
- hasCreditsRef.current = hasCredits;
72
- }, [hasCredits]);
53
+ creditBalanceRef.current = creditBalance;
54
+ }, [creditBalance]);
73
55
 
74
56
  useEffect(() => {
75
57
  hasSubscriptionRef.current = hasSubscription;
@@ -79,11 +61,9 @@ export function useFeatureGate(
79
61
  onShowPaywallRef.current = onShowPaywall;
80
62
  }, [onShowPaywall]);
81
63
 
82
- // Execute pending action when credits increase after purchase
83
64
  useEffect(() => {
84
- const prevBalance = prevCreditBalanceRef.current ?? 0; // ← FIX: Default to 0 if undefined
85
- const currentBalance = creditBalance;
86
- const creditsIncreased = currentBalance > prevBalance;
65
+ const prevBalance = prevCreditBalanceRef.current ?? 0;
66
+ const creditsIncreased = creditBalance > prevBalance;
87
67
 
88
68
  if (isWaitingForPurchaseRef.current && creditsIncreased && pendingActionRef.current) {
89
69
  const action = pendingActionRef.current;
@@ -91,11 +71,7 @@ export function useFeatureGate(
91
71
  isWaitingForPurchaseRef.current = false;
92
72
 
93
73
  if (typeof __DEV__ !== "undefined" && __DEV__) {
94
- console.log("[useFeatureGate] Credits increased, executing pending action", {
95
- prevBalance,
96
- currentBalance,
97
- creditsIncreased,
98
- });
74
+ console.log("[useFeatureGate] Credits increased, executing pending action");
99
75
  }
100
76
  action();
101
77
  }
@@ -103,7 +79,6 @@ export function useFeatureGate(
103
79
  prevCreditBalanceRef.current = creditBalance;
104
80
  }, [creditBalance]);
105
81
 
106
- // Compose individual gates
107
82
  const authGate = useAuthGate({
108
83
  isAuthenticated,
109
84
  onAuthRequired: onShowAuthModal,
@@ -115,7 +90,6 @@ export function useFeatureGate(
115
90
  });
116
91
 
117
92
  const creditsGate = useCreditsGate({
118
- hasCredits,
119
93
  creditBalance,
120
94
  requiredCredits,
121
95
  onCreditsRequired: onShowPaywall,
@@ -124,92 +98,64 @@ export function useFeatureGate(
124
98
  const requireFeature = useCallback(
125
99
  (action: () => void | Promise<void>) => {
126
100
  if (typeof __DEV__ !== "undefined" && __DEV__) {
127
- console.log("[useFeatureGate] requireFeature called", {
101
+ console.log("[useFeatureGate] requireFeature", {
128
102
  isAuthenticated,
129
103
  hasSubscription,
130
- hasCredits,
131
- creditBalance,
104
+ creditBalance: creditBalanceRef.current,
105
+ requiredCredits,
132
106
  });
133
107
  }
134
108
 
135
- // Step 1: Auth check
136
109
  if (!authGate.requireAuth(() => {})) {
137
- // Wrap action to re-check credits after auth succeeds
138
- // Using refs to get current values when callback executes
139
110
  const postAuthAction = () => {
140
- // Subscription check (bypasses credits if subscribed)
141
111
  if (hasSubscriptionRef.current) {
142
- if (typeof __DEV__ !== "undefined" && __DEV__) {
143
- console.log("[useFeatureGate] User has subscription, executing action");
144
- }
145
112
  action();
146
113
  return;
147
114
  }
148
115
 
149
- // Credits check
150
- if (!hasCreditsRef.current) {
116
+ const currentBalance = creditBalanceRef.current;
117
+ if (currentBalance < requiredCredits) {
151
118
  pendingActionRef.current = action;
152
119
  isWaitingForPurchaseRef.current = true;
153
120
  onShowPaywallRef.current(requiredCredits);
154
121
  return;
155
122
  }
156
123
 
157
- // All checks passed
158
- if (typeof __DEV__ !== "undefined" && __DEV__) {
159
- console.log("[useFeatureGate] User has credits, executing action");
160
- }
161
124
  action();
162
125
  };
163
126
  onShowAuthModal(postAuthAction);
164
127
  return;
165
128
  }
166
129
 
167
- // Step 2: Subscription check (bypasses credits if subscribed)
168
130
  if (hasSubscription) {
169
131
  action();
170
132
  return;
171
133
  }
172
134
 
173
- // Step 3: Credits check
174
- if (!creditsGate.requireCredits(() => {})) {
175
- // Store pending action for execution after purchase
135
+ if (!creditsGate.requireCredits()) {
176
136
  pendingActionRef.current = action;
177
137
  isWaitingForPurchaseRef.current = true;
178
138
  return;
179
139
  }
180
140
 
181
- // Step 4: All checks passed, execute action
182
141
  action();
183
142
  },
184
- [
185
- authGate,
186
- creditsGate,
187
- hasSubscription,
188
- requiredCredits,
189
- onShowAuthModal,
190
- ]
143
+ [authGate, creditsGate, hasSubscription, requiredCredits, isAuthenticated, onShowAuthModal]
191
144
  );
192
145
 
146
+ const hasCredits = creditBalance >= requiredCredits;
147
+
193
148
  return {
194
149
  requireFeature,
195
150
  isAuthenticated: authGate.isAuthenticated,
196
151
  hasSubscription: subscriptionGate.hasSubscription,
197
- hasCredits: creditsGate.hasCredits,
198
- creditBalance: creditsGate.creditBalance,
152
+ hasCredits,
153
+ creditBalance,
199
154
  canAccess: isAuthenticated && (hasSubscription || hasCredits),
200
155
  };
201
156
  }
202
157
 
203
158
  export { useAuthGate, useSubscriptionGate, useCreditsGate };
204
- export type {
205
- UseAuthGateParams,
206
- UseAuthGateResult,
207
- } from "./useAuthGate";
208
- export type {
209
- UseSubscriptionGateParams,
210
- UseSubscriptionGateResult,
211
- } from "./useSubscriptionGate";
212
- export type {
213
- UseCreditsGateParams,
214
- UseCreditsGateResult,
215
- } from "./useCreditsGate";
159
+ export type { UseAuthGateParams, UseAuthGateResult } from "./useAuthGate";
160
+ export type { UseSubscriptionGateParams, UseSubscriptionGateResult } from "./useSubscriptionGate";
161
+ export type { UseCreditsGateParams, UseCreditsGateResult } from "./useCreditsGate";