@umituz/react-native-subscription 2.13.11 → 2.13.13
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.13.
|
|
3
|
+
"version": "2.13.13",
|
|
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,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import { useQuery } from "@tanstack/react-query";
|
|
9
9
|
import type { UserCredits, CreditType } from "../../domain/entities/Credits";
|
|
10
|
+
|
|
11
|
+
declare const __DEV__: boolean;
|
|
10
12
|
import {
|
|
11
13
|
getCreditsRepository,
|
|
12
14
|
getCreditsConfig,
|
|
@@ -64,6 +66,18 @@ export const useCredits = ({
|
|
|
64
66
|
const hasTextCredits = (credits?.textCredits ?? 0) > 0;
|
|
65
67
|
const hasImageCredits = (credits?.imageCredits ?? 0) > 0;
|
|
66
68
|
|
|
69
|
+
if (__DEV__) {
|
|
70
|
+
console.log("[useCredits] State", {
|
|
71
|
+
userId,
|
|
72
|
+
enabled,
|
|
73
|
+
isLoading,
|
|
74
|
+
imageCredits: credits?.imageCredits ?? 0,
|
|
75
|
+
textCredits: credits?.textCredits ?? 0,
|
|
76
|
+
hasImageCredits,
|
|
77
|
+
hasTextCredits,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
67
81
|
const textCreditsPercent = credits
|
|
68
82
|
? Math.round((credits.textCredits / config.textCreditLimit) * 100)
|
|
69
83
|
: 0;
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* useFeatureGate Hook
|
|
3
3
|
*
|
|
4
4
|
* Feature gating with TanStack Query for server state.
|
|
5
|
-
* Checks auth
|
|
5
|
+
* Checks auth, premium status, AND credit balance before allowing actions.
|
|
6
6
|
*
|
|
7
7
|
* Flow:
|
|
8
8
|
* 1. NOT authenticated → onShowAuthModal(callback)
|
|
9
|
-
* 2. Authenticated but
|
|
10
|
-
* 3. Authenticated
|
|
9
|
+
* 2. Authenticated but no credits → onShowPaywall()
|
|
10
|
+
* 3. Authenticated with credits → Execute action
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
13
13
|
* ```typescript
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* isAuthenticated: !!user,
|
|
17
17
|
* onShowAuthModal: (cb) => authModal.show(cb),
|
|
18
18
|
* onShowPaywall: () => setShowPaywall(true),
|
|
19
|
+
* creditType: 'image', // or 'text'
|
|
19
20
|
* });
|
|
20
21
|
*
|
|
21
22
|
* // Gate a premium feature
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
|
|
28
29
|
import { useCallback } from "react";
|
|
29
30
|
import { useCredits } from "./useCredits";
|
|
31
|
+
import type { CreditType } from "../../domain/entities/Credits";
|
|
30
32
|
|
|
31
33
|
declare const __DEV__: boolean;
|
|
32
34
|
|
|
@@ -39,67 +41,118 @@ export interface UseFeatureGateParams {
|
|
|
39
41
|
onShowAuthModal: (pendingCallback: () => void | Promise<void>) => void;
|
|
40
42
|
/** Callback to show paywall */
|
|
41
43
|
onShowPaywall: () => void;
|
|
44
|
+
/** Credit type to check (default: 'image') */
|
|
45
|
+
creditType?: CreditType;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
export interface UseFeatureGateResult {
|
|
45
|
-
/** Gate a feature - checks auth first, then
|
|
49
|
+
/** Gate a feature - checks auth first, then credits balance */
|
|
46
50
|
requireFeature: (action: () => void | Promise<void>) => void;
|
|
47
51
|
/** Whether user is authenticated */
|
|
48
52
|
isAuthenticated: boolean;
|
|
49
|
-
/** Whether user has
|
|
50
|
-
|
|
53
|
+
/** Whether user has credits remaining */
|
|
54
|
+
hasCredits: boolean;
|
|
51
55
|
/** Whether feature access is allowed */
|
|
52
56
|
canAccess: boolean;
|
|
53
57
|
/** Loading state */
|
|
54
58
|
isLoading: boolean;
|
|
59
|
+
/** Current credit balance for the specified type */
|
|
60
|
+
creditBalance: number;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
export function useFeatureGate(
|
|
58
64
|
params: UseFeatureGateParams
|
|
59
65
|
): UseFeatureGateResult {
|
|
60
|
-
const {
|
|
66
|
+
const {
|
|
67
|
+
userId,
|
|
68
|
+
isAuthenticated,
|
|
69
|
+
onShowAuthModal,
|
|
70
|
+
onShowPaywall,
|
|
71
|
+
creditType = "image",
|
|
72
|
+
} = params;
|
|
61
73
|
|
|
62
74
|
// Use TanStack Query to get credits (server state)
|
|
63
|
-
const { credits, isLoading } = useCredits({
|
|
75
|
+
const { credits, isLoading, hasImageCredits, hasTextCredits } = useCredits({
|
|
64
76
|
userId,
|
|
65
77
|
enabled: isAuthenticated && !!userId,
|
|
66
78
|
});
|
|
67
79
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
// Check actual credit balance, not just existence
|
|
81
|
+
const hasCredits = creditType === "image" ? hasImageCredits : hasTextCredits;
|
|
82
|
+
const creditBalance =
|
|
83
|
+
creditType === "image"
|
|
84
|
+
? credits?.imageCredits ?? 0
|
|
85
|
+
: credits?.textCredits ?? 0;
|
|
86
|
+
|
|
87
|
+
if (__DEV__) {
|
|
88
|
+
console.log("[useFeatureGate] Hook state", {
|
|
89
|
+
userId,
|
|
90
|
+
isAuthenticated,
|
|
91
|
+
creditType,
|
|
92
|
+
hasCredits,
|
|
93
|
+
creditBalance,
|
|
94
|
+
isLoading,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
72
97
|
|
|
73
98
|
const requireFeature = useCallback(
|
|
74
99
|
(action: () => void | Promise<void>) => {
|
|
100
|
+
if (__DEV__) {
|
|
101
|
+
console.log("[useFeatureGate] requireFeature called", {
|
|
102
|
+
isAuthenticated,
|
|
103
|
+
hasCredits,
|
|
104
|
+
creditBalance,
|
|
105
|
+
creditType,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
75
109
|
// Step 1: Check authentication
|
|
76
110
|
if (!isAuthenticated) {
|
|
111
|
+
if (__DEV__) {
|
|
112
|
+
console.log("[useFeatureGate] Not authenticated, showing auth modal");
|
|
113
|
+
}
|
|
77
114
|
onShowAuthModal(() => {
|
|
78
115
|
// We NO LONGER call action() blindly here.
|
|
79
116
|
// The component will re-render with the new auth state,
|
|
80
117
|
// and the user should be allowed to try the action again.
|
|
81
|
-
// This avoids executing actions before credits are loaded or verified.
|
|
82
118
|
});
|
|
83
119
|
return;
|
|
84
120
|
}
|
|
85
121
|
|
|
86
|
-
// Step 2: Check
|
|
87
|
-
if (!
|
|
122
|
+
// Step 2: Check credit balance (not just existence)
|
|
123
|
+
if (!hasCredits) {
|
|
124
|
+
if (__DEV__) {
|
|
125
|
+
console.log("[useFeatureGate] No credits, showing paywall", {
|
|
126
|
+
creditBalance,
|
|
127
|
+
creditType,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
88
130
|
onShowPaywall();
|
|
89
131
|
return;
|
|
90
132
|
}
|
|
91
133
|
|
|
92
|
-
// Step 3: User is authenticated
|
|
134
|
+
// Step 3: User is authenticated with credits - execute action
|
|
135
|
+
if (__DEV__) {
|
|
136
|
+
console.log("[useFeatureGate] Access granted, executing action");
|
|
137
|
+
}
|
|
93
138
|
action();
|
|
94
139
|
},
|
|
95
|
-
[
|
|
140
|
+
[
|
|
141
|
+
isAuthenticated,
|
|
142
|
+
hasCredits,
|
|
143
|
+
creditBalance,
|
|
144
|
+
creditType,
|
|
145
|
+
onShowAuthModal,
|
|
146
|
+
onShowPaywall,
|
|
147
|
+
]
|
|
96
148
|
);
|
|
97
149
|
|
|
98
150
|
return {
|
|
99
151
|
requireFeature,
|
|
100
152
|
isAuthenticated,
|
|
101
|
-
|
|
102
|
-
canAccess: isAuthenticated &&
|
|
153
|
+
hasCredits,
|
|
154
|
+
canAccess: isAuthenticated && hasCredits,
|
|
103
155
|
isLoading,
|
|
156
|
+
creditBalance,
|
|
104
157
|
};
|
|
105
158
|
}
|