@umituz/react-native-subscription 1.9.0 → 1.10.1

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.9.0",
3
+ "version": "1.10.1",
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",
@@ -26,12 +26,13 @@ interface FirestoreTimestamp {
26
26
  toDate: () => Date;
27
27
  }
28
28
 
29
- interface UserCreditsDocument {
29
+ // Document structure when READING from Firestore
30
+ interface UserCreditsDocumentRead {
30
31
  textCredits: number;
31
32
  imageCredits: number;
32
- purchasedAt: FirestoreTimestamp | FieldValue;
33
- lastUpdatedAt: FirestoreTimestamp | FieldValue;
34
- lastPurchaseAt?: FirestoreTimestamp | FieldValue;
33
+ purchasedAt?: FirestoreTimestamp;
34
+ lastUpdatedAt?: FirestoreTimestamp;
35
+ lastPurchaseAt?: FirestoreTimestamp;
35
36
  processedPurchases?: string[];
36
37
  }
37
38
 
@@ -60,7 +61,7 @@ export class CreditsRepository extends BaseRepository {
60
61
  return { success: true, data: undefined };
61
62
  }
62
63
 
63
- const data = snapshot.data() as UserCreditsDocument;
64
+ const data = snapshot.data() as UserCreditsDocumentRead;
64
65
  return {
65
66
  success: true,
66
67
  data: {
@@ -107,7 +108,7 @@ export class CreditsRepository extends BaseRepository {
107
108
  let processedPurchases: string[] = [];
108
109
 
109
110
  if (creditsDoc.exists()) {
110
- const existing = creditsDoc.data() as UserCreditsDocument;
111
+ const existing = creditsDoc.data() as UserCreditsDocumentRead;
111
112
  processedPurchases = existing.processedPurchases || [];
112
113
 
113
114
  if (purchaseId && processedPurchases.includes(purchaseId)) {
@@ -122,7 +123,10 @@ export class CreditsRepository extends BaseRepository {
122
123
  (existing.textCredits || 0) + this.config.textCreditLimit;
123
124
  newImageCredits =
124
125
  (existing.imageCredits || 0) + this.config.imageCreditLimit;
125
- purchasedAt = existing.purchasedAt || now;
126
+ // Keep existing purchasedAt if available, otherwise use server timestamp
127
+ if (existing.purchasedAt) {
128
+ purchasedAt = existing.purchasedAt as unknown as FieldValue;
129
+ }
126
130
  }
127
131
 
128
132
  if (purchaseId) {
@@ -1,24 +1,24 @@
1
1
  /**
2
2
  * useFeatureGate Hook
3
3
  *
4
- * Centralized gate for premium features - checks auth first, then premium.
5
- * Generic and reusable - accepts all dependencies via params.
4
+ * Feature gating with TanStack Query for server state.
5
+ * Checks auth and premium status before allowing actions.
6
6
  *
7
7
  * Flow:
8
- * 1. NOT authenticated → Show auth modal (login/register)
9
- * 2. Authenticated but not premium → Show paywall
8
+ * 1. NOT authenticated → onShowAuthModal(callback)
9
+ * 2. Authenticated but not premium → onShowPaywall()
10
10
  * 3. Authenticated and premium → Execute action
11
11
  *
12
12
  * @example
13
13
  * ```typescript
14
14
  * const { requireFeature } = useFeatureGate({
15
- * isAuthenticated: user !== null,
16
- * isPremium: subscriptionStore.isPremium,
17
- * onShowAuthModal: (callback) => authModalStore.show(callback),
15
+ * userId: user?.uid,
16
+ * isAuthenticated: !!user,
17
+ * onShowAuthModal: (cb) => authModal.show(cb),
18
18
  * onShowPaywall: () => setShowPaywall(true),
19
- * getIsPremium: () => subscriptionStore.getState().isPremium,
20
19
  * });
21
20
  *
21
+ * // Gate a premium feature
22
22
  * const handleGenerate = () => {
23
23
  * requireFeature(() => generateContent());
24
24
  * };
@@ -26,66 +26,70 @@
26
26
  */
27
27
 
28
28
  import { useCallback } from "react";
29
+ import { useCredits } from "./useCredits";
29
30
 
30
31
  export interface UseFeatureGateParams {
31
- /** Whether user is currently authenticated */
32
+ /** User ID for credits check */
33
+ userId: string | undefined;
34
+ /** Whether user is authenticated */
32
35
  isAuthenticated: boolean;
33
- /** Whether user has premium access (for immediate check) */
34
- isPremium: boolean;
35
36
  /** Callback to show auth modal with pending action */
36
37
  onShowAuthModal: (pendingCallback: () => void | Promise<void>) => void;
37
38
  /** Callback to show paywall */
38
39
  onShowPaywall: () => void;
39
- /** Function to get fresh isPremium status (avoids stale closure after login) */
40
- getIsPremium: () => boolean;
41
40
  }
42
41
 
43
42
  export interface UseFeatureGateResult {
44
- /** Gate a feature - checks auth first, then premium */
43
+ /** Gate a feature - checks auth first, then premium (credits) */
45
44
  requireFeature: (action: () => void | Promise<void>) => void;
46
45
  /** Whether user is authenticated */
47
46
  isAuthenticated: boolean;
48
- /** Whether user has premium access */
47
+ /** Whether user has premium access (has credits) */
49
48
  isPremium: boolean;
50
- /** Whether feature access is allowed (authenticated + premium) */
49
+ /** Whether feature access is allowed */
51
50
  canAccess: boolean;
51
+ /** Loading state */
52
+ isLoading: boolean;
52
53
  }
53
54
 
54
55
  export function useFeatureGate(
55
56
  params: UseFeatureGateParams
56
57
  ): UseFeatureGateResult {
57
- const {
58
- isAuthenticated,
59
- isPremium,
60
- onShowAuthModal,
61
- onShowPaywall,
62
- getIsPremium,
63
- } = params;
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;
64
68
 
65
69
  const requireFeature = useCallback(
66
70
  (action: () => void | Promise<void>) => {
67
- // Step 1: Check if user is NOT authenticated → show auth modal
71
+ // Step 1: Check authentication
68
72
  if (!isAuthenticated) {
69
- // After login, get FRESH premium status (not stale closure)
73
+ // After auth, re-check premium before executing
70
74
  onShowAuthModal(() => {
71
- const currentIsPremium = getIsPremium();
72
- if (currentIsPremium) {
73
- action();
74
- } else {
75
- onShowPaywall();
76
- }
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();
77
79
  });
78
80
  return;
79
81
  }
80
82
 
81
- // Step 2: Check premium (will show paywall if not premium)
82
- if (isPremium) {
83
- action();
84
- } else {
83
+ // Step 2: Check premium (has credits from TanStack Query)
84
+ if (!isPremium) {
85
85
  onShowPaywall();
86
+ return;
86
87
  }
88
+
89
+ // Step 3: User is authenticated and premium - execute action
90
+ action();
87
91
  },
88
- [isAuthenticated, isPremium, onShowAuthModal, onShowPaywall, getIsPremium]
92
+ [isAuthenticated, isPremium, onShowAuthModal, onShowPaywall]
89
93
  );
90
94
 
91
95
  return {
@@ -93,5 +97,6 @@ export function useFeatureGate(
93
97
  isAuthenticated,
94
98
  isPremium,
95
99
  canAccess: isAuthenticated && isPremium,
100
+ isLoading,
96
101
  };
97
102
  }