@umituz/react-native-subscription 1.8.2 → 1.9.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,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useFeatureGate Hook
|
|
3
|
+
*
|
|
4
|
+
* Centralized gate for premium features - checks auth first, then premium.
|
|
5
|
+
* Generic and reusable - accepts all dependencies via params.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. NOT authenticated → Show auth modal (login/register)
|
|
9
|
+
* 2. Authenticated but not premium → Show paywall
|
|
10
|
+
* 3. Authenticated and premium → Execute action
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const { requireFeature } = useFeatureGate({
|
|
15
|
+
* isAuthenticated: user !== null,
|
|
16
|
+
* isPremium: subscriptionStore.isPremium,
|
|
17
|
+
* onShowAuthModal: (callback) => authModalStore.show(callback),
|
|
18
|
+
* onShowPaywall: () => setShowPaywall(true),
|
|
19
|
+
* getIsPremium: () => subscriptionStore.getState().isPremium,
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* const handleGenerate = () => {
|
|
23
|
+
* requireFeature(() => generateContent());
|
|
24
|
+
* };
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { useCallback } from "react";
|
|
29
|
+
|
|
30
|
+
export interface UseFeatureGateParams {
|
|
31
|
+
/** Whether user is currently authenticated */
|
|
32
|
+
isAuthenticated: boolean;
|
|
33
|
+
/** Whether user has premium access (for immediate check) */
|
|
34
|
+
isPremium: boolean;
|
|
35
|
+
/** Callback to show auth modal with pending action */
|
|
36
|
+
onShowAuthModal: (pendingCallback: () => void | Promise<void>) => void;
|
|
37
|
+
/** Callback to show paywall */
|
|
38
|
+
onShowPaywall: () => void;
|
|
39
|
+
/** Function to get fresh isPremium status (avoids stale closure after login) */
|
|
40
|
+
getIsPremium: () => boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface UseFeatureGateResult {
|
|
44
|
+
/** Gate a feature - checks auth first, then premium */
|
|
45
|
+
requireFeature: (action: () => void | Promise<void>) => void;
|
|
46
|
+
/** Whether user is authenticated */
|
|
47
|
+
isAuthenticated: boolean;
|
|
48
|
+
/** Whether user has premium access */
|
|
49
|
+
isPremium: boolean;
|
|
50
|
+
/** Whether feature access is allowed (authenticated + premium) */
|
|
51
|
+
canAccess: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function useFeatureGate(
|
|
55
|
+
params: UseFeatureGateParams
|
|
56
|
+
): UseFeatureGateResult {
|
|
57
|
+
const {
|
|
58
|
+
isAuthenticated,
|
|
59
|
+
isPremium,
|
|
60
|
+
onShowAuthModal,
|
|
61
|
+
onShowPaywall,
|
|
62
|
+
getIsPremium,
|
|
63
|
+
} = params;
|
|
64
|
+
|
|
65
|
+
const requireFeature = useCallback(
|
|
66
|
+
(action: () => void | Promise<void>) => {
|
|
67
|
+
// Step 1: Check if user is NOT authenticated → show auth modal
|
|
68
|
+
if (!isAuthenticated) {
|
|
69
|
+
// After login, get FRESH premium status (not stale closure)
|
|
70
|
+
onShowAuthModal(() => {
|
|
71
|
+
const currentIsPremium = getIsPremium();
|
|
72
|
+
if (currentIsPremium) {
|
|
73
|
+
action();
|
|
74
|
+
} else {
|
|
75
|
+
onShowPaywall();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Step 2: Check premium (will show paywall if not premium)
|
|
82
|
+
if (isPremium) {
|
|
83
|
+
action();
|
|
84
|
+
} else {
|
|
85
|
+
onShowPaywall();
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
[isAuthenticated, isPremium, onShowAuthModal, onShowPaywall, getIsPremium]
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
requireFeature,
|
|
93
|
+
isAuthenticated,
|
|
94
|
+
isPremium,
|
|
95
|
+
canAccess: isAuthenticated && isPremium,
|
|
96
|
+
};
|
|
97
|
+
}
|