@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.
|
|
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 =
|
|
90
|
-
|
|
91
|
-
|
|
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]
|
|
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;
|