@umituz/react-native-subscription 2.27.1 → 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.1",
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,25 +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
137
  const postAuthAction = () => {
90
- if (hasSubscriptionRef.current) {
91
- action();
92
- return;
93
- }
138
+ pendingActionRef.current = action;
139
+ isWaitingForAuthCreditsRef.current = true;
94
140
 
95
- const currentBalance = creditBalanceRef.current;
96
- if (currentBalance < requiredCredits) {
97
- pendingActionRef.current = action;
98
- isWaitingForPurchaseRef.current = true;
99
- onShowPaywallRef.current(requiredCredits);
100
- return;
141
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
142
+ console.log("[useFeatureGate] Auth completed, waiting for credits to load");
101
143
  }
102
-
103
- action();
104
144
  };
105
145
  onShowAuthModal(postAuthAction);
106
146
  return;
@@ -124,7 +164,7 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
124
164
 
125
165
  action();
126
166
  },
127
- [isAuthenticated, hasSubscription, requiredCredits, onShowAuthModal, onShowPaywall]
167
+ [isAuthenticated, hasSubscription, requiredCredits, onShowAuthModal, onShowPaywall, isCreditsLoaded]
128
168
  );
129
169
 
130
170
  const hasCredits = creditBalance >= requiredCredits;