@umituz/react-native-subscription 1.8.2 → 1.10.0
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
package/src/index.ts
CHANGED
|
@@ -64,6 +64,12 @@ export {
|
|
|
64
64
|
type UsePremiumGateResult,
|
|
65
65
|
} from "./presentation/hooks/usePremiumGate";
|
|
66
66
|
|
|
67
|
+
export {
|
|
68
|
+
useFeatureGate,
|
|
69
|
+
type UseFeatureGateParams,
|
|
70
|
+
type UseFeatureGateResult,
|
|
71
|
+
} from "./presentation/hooks/useFeatureGate";
|
|
72
|
+
|
|
67
73
|
export {
|
|
68
74
|
useUserTier,
|
|
69
75
|
type UseUserTierParams,
|
|
@@ -12,11 +12,11 @@ import {
|
|
|
12
12
|
getDoc,
|
|
13
13
|
runTransaction,
|
|
14
14
|
serverTimestamp,
|
|
15
|
+
type FieldValue,
|
|
15
16
|
} from "firebase/firestore";
|
|
16
17
|
import { BaseRepository, getFirestore } from "@umituz/react-native-firestore";
|
|
17
18
|
import type {
|
|
18
19
|
CreditType,
|
|
19
|
-
UserCredits,
|
|
20
20
|
CreditsConfig,
|
|
21
21
|
CreditsResult,
|
|
22
22
|
DeductCreditsResult,
|
|
@@ -24,15 +24,14 @@ import type {
|
|
|
24
24
|
|
|
25
25
|
interface FirestoreTimestamp {
|
|
26
26
|
toDate: () => Date;
|
|
27
|
-
isEqual: (other: FirestoreTimestamp) => boolean;
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
interface UserCreditsDocument {
|
|
31
30
|
textCredits: number;
|
|
32
31
|
imageCredits: number;
|
|
33
|
-
purchasedAt: FirestoreTimestamp;
|
|
34
|
-
lastUpdatedAt: FirestoreTimestamp;
|
|
35
|
-
lastPurchaseAt?: FirestoreTimestamp;
|
|
32
|
+
purchasedAt: FirestoreTimestamp | FieldValue;
|
|
33
|
+
lastUpdatedAt: FirestoreTimestamp | FieldValue;
|
|
34
|
+
lastPurchaseAt?: FirestoreTimestamp | FieldValue;
|
|
36
35
|
processedPurchases?: string[];
|
|
37
36
|
}
|
|
38
37
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useFeatureGate Hook
|
|
3
|
+
*
|
|
4
|
+
* Feature gating with TanStack Query for server state.
|
|
5
|
+
* Checks auth and premium status before allowing actions.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. NOT authenticated → onShowAuthModal(callback)
|
|
9
|
+
* 2. Authenticated but not premium → onShowPaywall()
|
|
10
|
+
* 3. Authenticated and premium → Execute action
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const { requireFeature } = useFeatureGate({
|
|
15
|
+
* userId: user?.uid,
|
|
16
|
+
* isAuthenticated: !!user,
|
|
17
|
+
* onShowAuthModal: (cb) => authModal.show(cb),
|
|
18
|
+
* onShowPaywall: () => setShowPaywall(true),
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Gate a premium feature
|
|
22
|
+
* const handleGenerate = () => {
|
|
23
|
+
* requireFeature(() => generateContent());
|
|
24
|
+
* };
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { useCallback } from "react";
|
|
29
|
+
import { useCredits } from "./useCredits";
|
|
30
|
+
|
|
31
|
+
export interface UseFeatureGateParams {
|
|
32
|
+
/** User ID for credits check */
|
|
33
|
+
userId: string | undefined;
|
|
34
|
+
/** Whether user is authenticated */
|
|
35
|
+
isAuthenticated: boolean;
|
|
36
|
+
/** Callback to show auth modal with pending action */
|
|
37
|
+
onShowAuthModal: (pendingCallback: () => void | Promise<void>) => void;
|
|
38
|
+
/** Callback to show paywall */
|
|
39
|
+
onShowPaywall: () => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface UseFeatureGateResult {
|
|
43
|
+
/** Gate a feature - checks auth first, then premium (credits) */
|
|
44
|
+
requireFeature: (action: () => void | Promise<void>) => void;
|
|
45
|
+
/** Whether user is authenticated */
|
|
46
|
+
isAuthenticated: boolean;
|
|
47
|
+
/** Whether user has premium access (has credits) */
|
|
48
|
+
isPremium: boolean;
|
|
49
|
+
/** Whether feature access is allowed */
|
|
50
|
+
canAccess: boolean;
|
|
51
|
+
/** Loading state */
|
|
52
|
+
isLoading: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function useFeatureGate(
|
|
56
|
+
params: UseFeatureGateParams
|
|
57
|
+
): UseFeatureGateResult {
|
|
58
|
+
const { userId, isAuthenticated, onShowAuthModal, onShowPaywall } = params;
|
|
59
|
+
|
|
60
|
+
// Use TanStack Query to get credits (server state)
|
|
61
|
+
const { credits, isLoading } = useCredits({
|
|
62
|
+
userId,
|
|
63
|
+
enabled: isAuthenticated && !!userId,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// User is premium if they have credits
|
|
67
|
+
const isPremium = credits !== null;
|
|
68
|
+
|
|
69
|
+
const requireFeature = useCallback(
|
|
70
|
+
(action: () => void | Promise<void>) => {
|
|
71
|
+
// Step 1: Check authentication
|
|
72
|
+
if (!isAuthenticated) {
|
|
73
|
+
// After auth, re-check premium before executing
|
|
74
|
+
onShowAuthModal(() => {
|
|
75
|
+
// This callback runs after successful auth
|
|
76
|
+
// The component will re-render with new auth state
|
|
77
|
+
// and user can try the action again
|
|
78
|
+
action();
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Step 2: Check premium (has credits from TanStack Query)
|
|
84
|
+
if (!isPremium) {
|
|
85
|
+
onShowPaywall();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Step 3: User is authenticated and premium - execute action
|
|
90
|
+
action();
|
|
91
|
+
},
|
|
92
|
+
[isAuthenticated, isPremium, onShowAuthModal, onShowPaywall]
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
requireFeature,
|
|
97
|
+
isAuthenticated,
|
|
98
|
+
isPremium,
|
|
99
|
+
canAccess: isAuthenticated && isPremium,
|
|
100
|
+
isLoading,
|
|
101
|
+
};
|
|
102
|
+
}
|