@umituz/react-native-subscription 2.26.12 → 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.12",
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,61 +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
24
  export function useCreditsGate(
45
25
  params: UseCreditsGateParams
46
26
  ): UseCreditsGateResult {
47
- const { hasCredits, creditBalance, requiredCredits, onCreditsRequired } =
48
- 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
+ }
49
58
 
50
- const requireCredits = useCallback(
51
- (_action: () => void | Promise<void>): boolean => {
52
- if (!hasCredits) {
53
- onCreditsRequired(requiredCredits);
54
- return false;
55
- }
56
- return true;
57
- },
58
- [hasCredits, requiredCredits, onCreditsRequired]
59
- );
59
+ return true;
60
+ }, [requiredCredits]);
60
61
 
61
62
  return {
62
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,
@@ -123,85 +97,65 @@ export function useFeatureGate(
123
97
 
124
98
  const requireFeature = useCallback(
125
99
  (action: () => void | Promise<void>) => {
100
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
101
+ console.log("[useFeatureGate] requireFeature", {
102
+ isAuthenticated,
103
+ hasSubscription,
104
+ creditBalance: creditBalanceRef.current,
105
+ requiredCredits,
106
+ });
107
+ }
126
108
 
127
- // Step 1: Auth check
128
109
  if (!authGate.requireAuth(() => {})) {
129
- // Wrap action to re-check credits after auth succeeds
130
- // Using refs to get current values when callback executes
131
110
  const postAuthAction = () => {
132
- // Subscription check (bypasses credits if subscribed)
133
111
  if (hasSubscriptionRef.current) {
134
- if (typeof __DEV__ !== "undefined" && __DEV__) {
135
- console.log("[useFeatureGate] User has subscription, executing action");
136
- }
137
112
  action();
138
113
  return;
139
114
  }
140
115
 
141
- // Credits check
142
- if (!hasCreditsRef.current) {
116
+ const currentBalance = creditBalanceRef.current;
117
+ if (currentBalance < requiredCredits) {
143
118
  pendingActionRef.current = action;
144
119
  isWaitingForPurchaseRef.current = true;
145
120
  onShowPaywallRef.current(requiredCredits);
146
121
  return;
147
122
  }
148
123
 
149
- // All checks passed
150
- if (typeof __DEV__ !== "undefined" && __DEV__) {
151
- console.log("[useFeatureGate] User has credits, executing action");
152
- }
153
124
  action();
154
125
  };
155
126
  onShowAuthModal(postAuthAction);
156
127
  return;
157
128
  }
158
129
 
159
- // Step 2: Subscription check (bypasses credits if subscribed)
160
130
  if (hasSubscription) {
161
131
  action();
162
132
  return;
163
133
  }
164
134
 
165
- // Step 3: Credits check
166
- if (!creditsGate.requireCredits(() => {})) {
167
- // Store pending action for execution after purchase
135
+ if (!creditsGate.requireCredits()) {
168
136
  pendingActionRef.current = action;
169
137
  isWaitingForPurchaseRef.current = true;
170
138
  return;
171
139
  }
172
140
 
173
- // Step 4: All checks passed, execute action
174
141
  action();
175
142
  },
176
- [
177
- authGate,
178
- creditsGate,
179
- hasSubscription,
180
- requiredCredits,
181
- onShowAuthModal,
182
- ]
143
+ [authGate, creditsGate, hasSubscription, requiredCredits, isAuthenticated, onShowAuthModal]
183
144
  );
184
145
 
146
+ const hasCredits = creditBalance >= requiredCredits;
147
+
185
148
  return {
186
149
  requireFeature,
187
150
  isAuthenticated: authGate.isAuthenticated,
188
151
  hasSubscription: subscriptionGate.hasSubscription,
189
- hasCredits: creditsGate.hasCredits,
190
- creditBalance: creditsGate.creditBalance,
152
+ hasCredits,
153
+ creditBalance,
191
154
  canAccess: isAuthenticated && (hasSubscription || hasCredits),
192
155
  };
193
156
  }
194
157
 
195
158
  export { useAuthGate, useSubscriptionGate, useCreditsGate };
196
- export type {
197
- UseAuthGateParams,
198
- UseAuthGateResult,
199
- } from "./useAuthGate";
200
- export type {
201
- UseSubscriptionGateParams,
202
- UseSubscriptionGateResult,
203
- } from "./useSubscriptionGate";
204
- export type {
205
- UseCreditsGateParams,
206
- UseCreditsGateResult,
207
- } from "./useCreditsGate";
159
+ export type { UseAuthGateParams, UseAuthGateResult } from "./useAuthGate";
160
+ export type { UseSubscriptionGateParams, UseSubscriptionGateResult } from "./useSubscriptionGate";
161
+ export type { UseCreditsGateParams, UseCreditsGateResult } from "./useCreditsGate";