@umituz/react-native-subscription 2.14.8 → 2.14.9
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.14.
|
|
3
|
+
"version": "2.14.9",
|
|
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",
|
package/src/index.ts
CHANGED
|
@@ -53,8 +53,17 @@ export {
|
|
|
53
53
|
|
|
54
54
|
export {
|
|
55
55
|
useFeatureGate,
|
|
56
|
+
useAuthGate,
|
|
57
|
+
useSubscriptionGate,
|
|
58
|
+
useCreditsGate,
|
|
56
59
|
type UseFeatureGateParams,
|
|
57
60
|
type UseFeatureGateResult,
|
|
61
|
+
type UseAuthGateParams,
|
|
62
|
+
type UseAuthGateResult,
|
|
63
|
+
type UseSubscriptionGateParams,
|
|
64
|
+
type UseSubscriptionGateResult,
|
|
65
|
+
type UseCreditsGateParams,
|
|
66
|
+
type UseCreditsGateResult,
|
|
58
67
|
} from "./presentation/hooks/useFeatureGate";
|
|
59
68
|
|
|
60
69
|
export {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAuthGate Hook
|
|
3
|
+
*
|
|
4
|
+
* Single responsibility: Authentication gating
|
|
5
|
+
* Checks if user is authenticated before allowing actions.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const { requireAuth, isAuthenticated } = useAuthGate({
|
|
10
|
+
* isAuthenticated: !!user && !user.isAnonymous,
|
|
11
|
+
* onAuthRequired: (callback) => showAuthModal(callback),
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* const handleAction = () => {
|
|
15
|
+
* requireAuth(() => doSomething());
|
|
16
|
+
* };
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { useCallback } from "react";
|
|
21
|
+
|
|
22
|
+
declare const __DEV__: boolean;
|
|
23
|
+
|
|
24
|
+
export interface UseAuthGateParams {
|
|
25
|
+
/** Whether user is authenticated (not guest/anonymous) */
|
|
26
|
+
isAuthenticated: boolean;
|
|
27
|
+
/** Callback when auth is required - receives pending action callback */
|
|
28
|
+
onAuthRequired: (pendingCallback: () => void | Promise<void>) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface UseAuthGateResult {
|
|
32
|
+
/** Whether user is authenticated */
|
|
33
|
+
isAuthenticated: boolean;
|
|
34
|
+
/** Gate action behind auth - executes if authenticated, else shows auth modal */
|
|
35
|
+
requireAuth: (action: () => void | Promise<void>) => boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useAuthGate(params: UseAuthGateParams): UseAuthGateResult {
|
|
39
|
+
const { isAuthenticated, onAuthRequired } = params;
|
|
40
|
+
|
|
41
|
+
const requireAuth = useCallback(
|
|
42
|
+
(action: () => void | Promise<void>): boolean => {
|
|
43
|
+
if (!isAuthenticated) {
|
|
44
|
+
if (__DEV__) {
|
|
45
|
+
|
|
46
|
+
console.log("[useAuthGate] Not authenticated, showing auth modal");
|
|
47
|
+
}
|
|
48
|
+
onAuthRequired(action);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (__DEV__) {
|
|
53
|
+
|
|
54
|
+
console.log("[useAuthGate] Authenticated, proceeding");
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
},
|
|
58
|
+
[isAuthenticated, onAuthRequired]
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
isAuthenticated,
|
|
63
|
+
requireAuth,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useCreditsGate Hook
|
|
3
|
+
*
|
|
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
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { useCallback } from "react";
|
|
23
|
+
|
|
24
|
+
declare const __DEV__: boolean;
|
|
25
|
+
|
|
26
|
+
export interface UseCreditsGateParams {
|
|
27
|
+
/** Whether user has enough credits for the action */
|
|
28
|
+
hasCredits: boolean;
|
|
29
|
+
/** Current credit balance */
|
|
30
|
+
creditBalance: number;
|
|
31
|
+
/** Credits required for this action (optional, for display) */
|
|
32
|
+
requiredCredits?: number;
|
|
33
|
+
/** Callback when credits are required - receives required amount */
|
|
34
|
+
onCreditsRequired: (requiredCredits?: number) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface UseCreditsGateResult {
|
|
38
|
+
/** Whether user has enough credits */
|
|
39
|
+
hasCredits: boolean;
|
|
40
|
+
/** Current credit balance */
|
|
41
|
+
creditBalance: number;
|
|
42
|
+
/** Gate action behind credits - executes if has credits, else shows paywall */
|
|
43
|
+
requireCredits: (action: () => void | Promise<void>) => boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useCreditsGate(
|
|
47
|
+
params: UseCreditsGateParams
|
|
48
|
+
): UseCreditsGateResult {
|
|
49
|
+
const { hasCredits, creditBalance, requiredCredits, onCreditsRequired } =
|
|
50
|
+
params;
|
|
51
|
+
|
|
52
|
+
const requireCredits = useCallback(
|
|
53
|
+
(_action: () => void | Promise<void>): boolean => {
|
|
54
|
+
if (!hasCredits) {
|
|
55
|
+
if (__DEV__) {
|
|
56
|
+
|
|
57
|
+
console.log("[useCreditsGate] Insufficient credits", {
|
|
58
|
+
creditBalance,
|
|
59
|
+
requiredCredits,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
onCreditsRequired(requiredCredits);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (__DEV__) {
|
|
67
|
+
|
|
68
|
+
console.log("[useCreditsGate] Has credits, proceeding", {
|
|
69
|
+
creditBalance,
|
|
70
|
+
requiredCredits,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
},
|
|
75
|
+
[hasCredits, creditBalance, requiredCredits, onCreditsRequired]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
hasCredits,
|
|
80
|
+
creditBalance,
|
|
81
|
+
requireCredits,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -1,158 +1,180 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useFeatureGate Hook
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Combines auth, subscription, and credits gates into a unified feature gate.
|
|
5
|
+
* Uses composition of smaller, single-responsibility hooks.
|
|
6
6
|
*
|
|
7
7
|
* Flow:
|
|
8
|
-
* 1.
|
|
9
|
-
* 2.
|
|
10
|
-
* 3.
|
|
8
|
+
* 1. Auth check → Show auth modal if not authenticated
|
|
9
|
+
* 2. Subscription check → If subscribed, bypass credits and execute
|
|
10
|
+
* 3. Credits check → Show paywall if no credits
|
|
11
|
+
* 4. Execute action
|
|
11
12
|
*
|
|
12
13
|
* @example
|
|
13
14
|
* ```typescript
|
|
14
15
|
* const { requireFeature } = useFeatureGate({
|
|
15
|
-
*
|
|
16
|
-
* isAuthenticated: !!user,
|
|
17
|
-
* onShowAuthModal: (cb) =>
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* // Auth config
|
|
17
|
+
* isAuthenticated: !!user && !user.isAnonymous,
|
|
18
|
+
* onShowAuthModal: (cb) => showAuthModal(cb),
|
|
19
|
+
*
|
|
20
|
+
* // Subscription config (optional)
|
|
21
|
+
* hasSubscription: isPremium,
|
|
22
|
+
*
|
|
23
|
+
* // Credits config
|
|
24
|
+
* hasCredits: canAfford(cost),
|
|
25
|
+
* creditBalance: credits,
|
|
26
|
+
* requiredCredits: cost,
|
|
27
|
+
* onShowPaywall: (cost) => showPaywall(cost),
|
|
20
28
|
* });
|
|
21
29
|
*
|
|
22
|
-
* // Gate a premium feature
|
|
23
30
|
* const handleGenerate = () => {
|
|
24
|
-
* requireFeature(() =>
|
|
31
|
+
* requireFeature(() => generate());
|
|
25
32
|
* };
|
|
26
33
|
* ```
|
|
27
34
|
*/
|
|
28
35
|
|
|
29
36
|
import { useCallback } from "react";
|
|
30
|
-
import {
|
|
31
|
-
import
|
|
37
|
+
import { useAuthGate } from "./useAuthGate";
|
|
38
|
+
import { useSubscriptionGate } from "./useSubscriptionGate";
|
|
39
|
+
import { useCreditsGate } from "./useCreditsGate";
|
|
32
40
|
|
|
33
41
|
declare const __DEV__: boolean;
|
|
34
42
|
|
|
35
43
|
export interface UseFeatureGateParams {
|
|
36
|
-
/**
|
|
37
|
-
userId: string | undefined;
|
|
38
|
-
/** Whether user is authenticated */
|
|
44
|
+
/** Whether user is authenticated (not guest/anonymous) */
|
|
39
45
|
isAuthenticated: boolean;
|
|
40
46
|
/** Callback to show auth modal with pending action */
|
|
41
47
|
onShowAuthModal: (pendingCallback: () => void | Promise<void>) => void;
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
|
|
48
|
+
/** Whether user has active subscription (optional, defaults to false) */
|
|
49
|
+
hasSubscription?: boolean;
|
|
50
|
+
/** Whether user has enough credits for the action */
|
|
51
|
+
hasCredits: boolean;
|
|
52
|
+
/** Current credit balance */
|
|
53
|
+
creditBalance: number;
|
|
54
|
+
/** Credits required for this action (optional, for paywall display) */
|
|
55
|
+
requiredCredits?: number;
|
|
56
|
+
/** Callback to show paywall - receives required credits */
|
|
57
|
+
onShowPaywall: (requiredCredits?: number) => void;
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
export interface UseFeatureGateResult {
|
|
49
|
-
/** Gate a feature - checks auth
|
|
61
|
+
/** Gate a feature - checks auth, subscription, then credits */
|
|
50
62
|
requireFeature: (action: () => void | Promise<void>) => void;
|
|
51
63
|
/** Whether user is authenticated */
|
|
52
64
|
isAuthenticated: boolean;
|
|
53
|
-
/** Whether user has
|
|
65
|
+
/** Whether user has active subscription */
|
|
66
|
+
hasSubscription: boolean;
|
|
67
|
+
/** Whether user has enough credits */
|
|
54
68
|
hasCredits: boolean;
|
|
69
|
+
/** Current credit balance */
|
|
70
|
+
creditBalance: number;
|
|
55
71
|
/** Whether feature access is allowed */
|
|
56
72
|
canAccess: boolean;
|
|
57
|
-
/** Loading state */
|
|
58
|
-
isLoading: boolean;
|
|
59
|
-
/** Current credit balance for the specified type */
|
|
60
|
-
creditBalance: number;
|
|
61
73
|
}
|
|
62
74
|
|
|
63
75
|
export function useFeatureGate(
|
|
64
76
|
params: UseFeatureGateParams
|
|
65
77
|
): UseFeatureGateResult {
|
|
66
78
|
const {
|
|
67
|
-
userId,
|
|
68
79
|
isAuthenticated,
|
|
69
80
|
onShowAuthModal,
|
|
81
|
+
hasSubscription = false,
|
|
82
|
+
hasCredits,
|
|
83
|
+
creditBalance,
|
|
84
|
+
requiredCredits,
|
|
70
85
|
onShowPaywall,
|
|
71
|
-
creditType = "image",
|
|
72
86
|
} = params;
|
|
73
87
|
|
|
74
|
-
//
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
88
|
+
// Compose individual gates
|
|
89
|
+
const authGate = useAuthGate({
|
|
90
|
+
isAuthenticated,
|
|
91
|
+
onAuthRequired: onShowAuthModal,
|
|
78
92
|
});
|
|
79
93
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
? credits?.imageCredits ?? 0
|
|
85
|
-
: credits?.textCredits ?? 0;
|
|
94
|
+
const subscriptionGate = useSubscriptionGate({
|
|
95
|
+
hasSubscription,
|
|
96
|
+
onSubscriptionRequired: () => onShowPaywall(requiredCredits),
|
|
97
|
+
});
|
|
86
98
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
creditBalance,
|
|
94
|
-
isLoading,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
99
|
+
const creditsGate = useCreditsGate({
|
|
100
|
+
hasCredits,
|
|
101
|
+
creditBalance,
|
|
102
|
+
requiredCredits,
|
|
103
|
+
onCreditsRequired: onShowPaywall,
|
|
104
|
+
});
|
|
97
105
|
|
|
98
106
|
const requireFeature = useCallback(
|
|
99
107
|
(action: () => void | Promise<void>) => {
|
|
100
108
|
if (__DEV__) {
|
|
101
|
-
|
|
109
|
+
|
|
110
|
+
console.log("[useFeatureGate] Checking gates", {
|
|
102
111
|
isAuthenticated,
|
|
112
|
+
hasSubscription,
|
|
103
113
|
hasCredits,
|
|
104
114
|
creditBalance,
|
|
105
|
-
creditType,
|
|
106
115
|
});
|
|
107
116
|
}
|
|
108
117
|
|
|
109
|
-
// Step 1:
|
|
110
|
-
if (!
|
|
111
|
-
|
|
112
|
-
console.log("[useFeatureGate] Not authenticated, showing auth modal");
|
|
113
|
-
}
|
|
114
|
-
onShowAuthModal(() => {
|
|
115
|
-
// We NO LONGER call action() blindly here.
|
|
116
|
-
// The component will re-render with the new auth state,
|
|
117
|
-
// and the user should be allowed to try the action again.
|
|
118
|
-
});
|
|
118
|
+
// Step 1: Auth check
|
|
119
|
+
if (!authGate.requireAuth(() => {})) {
|
|
120
|
+
onShowAuthModal(action);
|
|
119
121
|
return;
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
// Step 2:
|
|
123
|
-
if (
|
|
124
|
+
// Step 2: Subscription check (bypasses credits if subscribed)
|
|
125
|
+
if (hasSubscription) {
|
|
124
126
|
if (__DEV__) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
creditType,
|
|
128
|
-
});
|
|
127
|
+
|
|
128
|
+
console.log("[useFeatureGate] Has subscription, executing action");
|
|
129
129
|
}
|
|
130
|
-
|
|
130
|
+
action();
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
// Step 3:
|
|
134
|
+
// Step 3: Credits check
|
|
135
|
+
if (!creditsGate.requireCredits(() => {})) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Step 4: All checks passed, execute action
|
|
135
140
|
if (__DEV__) {
|
|
136
|
-
|
|
141
|
+
|
|
142
|
+
console.log("[useFeatureGate] All gates passed, executing action");
|
|
137
143
|
}
|
|
138
144
|
action();
|
|
139
145
|
},
|
|
140
146
|
[
|
|
147
|
+
authGate,
|
|
148
|
+
creditsGate,
|
|
149
|
+
hasSubscription,
|
|
141
150
|
isAuthenticated,
|
|
142
151
|
hasCredits,
|
|
143
152
|
creditBalance,
|
|
144
|
-
creditType,
|
|
145
153
|
onShowAuthModal,
|
|
146
|
-
onShowPaywall,
|
|
147
154
|
]
|
|
148
155
|
);
|
|
149
156
|
|
|
150
157
|
return {
|
|
151
158
|
requireFeature,
|
|
152
|
-
isAuthenticated,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
159
|
+
isAuthenticated: authGate.isAuthenticated,
|
|
160
|
+
hasSubscription: subscriptionGate.hasSubscription,
|
|
161
|
+
hasCredits: creditsGate.hasCredits,
|
|
162
|
+
creditBalance: creditsGate.creditBalance,
|
|
163
|
+
canAccess: isAuthenticated && (hasSubscription || hasCredits),
|
|
157
164
|
};
|
|
158
165
|
}
|
|
166
|
+
|
|
167
|
+
// Re-export individual gates for standalone use
|
|
168
|
+
export { useAuthGate, useSubscriptionGate, useCreditsGate };
|
|
169
|
+
export type {
|
|
170
|
+
UseAuthGateParams,
|
|
171
|
+
UseAuthGateResult,
|
|
172
|
+
} from "./useAuthGate";
|
|
173
|
+
export type {
|
|
174
|
+
UseSubscriptionGateParams,
|
|
175
|
+
UseSubscriptionGateResult,
|
|
176
|
+
} from "./useSubscriptionGate";
|
|
177
|
+
export type {
|
|
178
|
+
UseCreditsGateParams,
|
|
179
|
+
UseCreditsGateResult,
|
|
180
|
+
} from "./useCreditsGate";
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSubscriptionGate Hook
|
|
3
|
+
*
|
|
4
|
+
* Single responsibility: Subscription/Premium gating
|
|
5
|
+
* Checks if user has active subscription before allowing actions.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const { requireSubscription, hasSubscription } = useSubscriptionGate({
|
|
10
|
+
* hasSubscription: isPremium,
|
|
11
|
+
* onSubscriptionRequired: () => showPaywall(),
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* const handlePremiumAction = () => {
|
|
15
|
+
* requireSubscription(() => doPremiumThing());
|
|
16
|
+
* };
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { useCallback } from "react";
|
|
21
|
+
|
|
22
|
+
declare const __DEV__: boolean;
|
|
23
|
+
|
|
24
|
+
export interface UseSubscriptionGateParams {
|
|
25
|
+
/** Whether user has active subscription */
|
|
26
|
+
hasSubscription: boolean;
|
|
27
|
+
/** Callback when subscription is required */
|
|
28
|
+
onSubscriptionRequired: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface UseSubscriptionGateResult {
|
|
32
|
+
/** Whether user has active subscription */
|
|
33
|
+
hasSubscription: boolean;
|
|
34
|
+
/** Gate action behind subscription - executes if subscribed, else shows paywall */
|
|
35
|
+
requireSubscription: (action: () => void | Promise<void>) => boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useSubscriptionGate(
|
|
39
|
+
params: UseSubscriptionGateParams
|
|
40
|
+
): UseSubscriptionGateResult {
|
|
41
|
+
const { hasSubscription, onSubscriptionRequired } = params;
|
|
42
|
+
|
|
43
|
+
const requireSubscription = useCallback(
|
|
44
|
+
(_action: () => void | Promise<void>): boolean => {
|
|
45
|
+
if (!hasSubscription) {
|
|
46
|
+
if (__DEV__) {
|
|
47
|
+
|
|
48
|
+
console.log("[useSubscriptionGate] No subscription, showing paywall");
|
|
49
|
+
}
|
|
50
|
+
onSubscriptionRequired();
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (__DEV__) {
|
|
55
|
+
|
|
56
|
+
console.log("[useSubscriptionGate] Has subscription, proceeding");
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
},
|
|
60
|
+
[hasSubscription, onSubscriptionRequired]
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
hasSubscription,
|
|
65
|
+
requireSubscription,
|
|
66
|
+
};
|
|
67
|
+
}
|