@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "1.8.2",
3
+ "version": "1.10.0",
4
4
  "description": "Subscription management, 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
@@ -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
+ }