@umituz/react-native-subscription 2.26.13 → 2.26.15
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 +1 -1
- package/src/domains/paywall/components/PaywallModal.tsx +36 -13
- package/src/domains/paywall/components/PlanCard.tsx +16 -3
- package/src/domains/paywall/entities/types.ts +4 -0
- package/src/domains/paywall/hooks/usePaywallTranslations.ts +8 -0
- package/src/presentation/hooks/index.ts +0 -15
- package/src/presentation/hooks/useFeatureGate.ts +41 -116
- package/src/revenuecat/domain/types/RevenueCatTypes.ts +32 -0
- package/src/revenuecat/index.ts +1 -0
- package/src/revenuecat/presentation/hooks/useRevenueCatTrialEligibility.ts +179 -0
- package/src/presentation/hooks/useAuthAwarePurchase.md +0 -92
- package/src/presentation/hooks/useAuthAwarePurchase.ts +0 -138
- package/src/presentation/hooks/useAuthGate.md +0 -89
- package/src/presentation/hooks/useAuthGate.ts +0 -65
- package/src/presentation/hooks/useCreditChecker.md +0 -102
- package/src/presentation/hooks/useCreditChecker.ts +0 -41
- package/src/presentation/hooks/useCreditsGate.md +0 -94
- package/src/presentation/hooks/useCreditsGate.ts +0 -81
- package/src/presentation/hooks/useDevTestCallbacks.md +0 -91
- package/src/presentation/hooks/useDevTestCallbacks.ts +0 -142
- package/src/presentation/hooks/useInitializeCredits.md +0 -92
- package/src/presentation/hooks/useInitializeCredits.ts +0 -57
- package/src/presentation/hooks/usePremiumGate.md +0 -88
- package/src/presentation/hooks/usePremiumGate.ts +0 -116
- package/src/presentation/hooks/usePremiumWithCredits.md +0 -92
- package/src/presentation/hooks/usePremiumWithCredits.ts +0 -48
- package/src/presentation/hooks/useSubscription.md +0 -94
- package/src/presentation/hooks/useSubscription.ts +0 -119
- package/src/presentation/hooks/useSubscriptionDetails.md +0 -93
- package/src/presentation/hooks/useSubscriptionDetails.ts +0 -85
- package/src/presentation/hooks/useSubscriptionGate.md +0 -84
- package/src/presentation/hooks/useSubscriptionGate.ts +0 -67
- package/src/presentation/hooks/useSubscriptionStatus.md +0 -94
- package/src/presentation/hooks/useSubscriptionStatus.ts +0 -64
- package/src/presentation/hooks/useTrialEligibility.ts +0 -66
- package/src/presentation/hooks/useUserTier.md +0 -91
- package/src/presentation/hooks/useUserTier.ts +0 -78
- package/src/presentation/hooks/useUserTierWithRepository.md +0 -92
- package/src/presentation/hooks/useUserTierWithRepository.ts +0 -151
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useSubscriptionStatus Hook
|
|
3
|
-
*
|
|
4
|
-
* TanStack Query hook for checking real subscription status from RevenueCat.
|
|
5
|
-
* This provides the actual premium status based on entitlements, not credits.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useQuery } from "@umituz/react-native-design-system";
|
|
9
|
-
import { SubscriptionManager } from "../../revenuecat/infrastructure/managers/SubscriptionManager";
|
|
10
|
-
|
|
11
|
-
export const subscriptionStatusQueryKeys = {
|
|
12
|
-
all: ["subscriptionStatus"] as const,
|
|
13
|
-
user: (userId: string) => ["subscriptionStatus", userId] as const,
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export interface SubscriptionStatusResult {
|
|
17
|
-
isPremium: boolean;
|
|
18
|
-
expirationDate: Date | null;
|
|
19
|
-
isLoading: boolean;
|
|
20
|
-
error: Error | null;
|
|
21
|
-
refetch: () => void;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface UseSubscriptionStatusParams {
|
|
25
|
-
userId: string | undefined;
|
|
26
|
-
enabled?: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Check real subscription status from RevenueCat
|
|
31
|
-
*
|
|
32
|
-
* @param userId - User ID
|
|
33
|
-
* @param enabled - Whether to enable the query
|
|
34
|
-
* @returns Subscription status with isPremium flag
|
|
35
|
-
*/
|
|
36
|
-
export const useSubscriptionStatus = ({
|
|
37
|
-
userId,
|
|
38
|
-
enabled = true,
|
|
39
|
-
}: UseSubscriptionStatusParams): SubscriptionStatusResult => {
|
|
40
|
-
const { data, isLoading, error, refetch } = useQuery({
|
|
41
|
-
queryKey: subscriptionStatusQueryKeys.user(userId ?? ""),
|
|
42
|
-
queryFn: async () => {
|
|
43
|
-
if (!userId) {
|
|
44
|
-
return { isPremium: false, expirationDate: null };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return SubscriptionManager.checkPremiumStatus();
|
|
48
|
-
},
|
|
49
|
-
enabled: enabled && !!userId && SubscriptionManager.isInitializedForUser(userId),
|
|
50
|
-
staleTime: 0, // No cache - always fetch fresh premium status
|
|
51
|
-
gcTime: 0, // Don't cache - garbage collect immediately
|
|
52
|
-
refetchOnMount: true,
|
|
53
|
-
refetchOnWindowFocus: true,
|
|
54
|
-
refetchOnReconnect: true,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
isPremium: data?.isPremium ?? false,
|
|
59
|
-
expirationDate: data?.expirationDate ?? null,
|
|
60
|
-
isLoading,
|
|
61
|
-
error: error as Error | null,
|
|
62
|
-
refetch,
|
|
63
|
-
};
|
|
64
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useTrialEligibility Hook
|
|
3
|
-
* Checks if device is eligible for free trial
|
|
4
|
-
* Uses persistent device ID to prevent trial abuse
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useState, useEffect, useCallback } from "react";
|
|
8
|
-
import {
|
|
9
|
-
checkTrialEligibility,
|
|
10
|
-
getDeviceId,
|
|
11
|
-
} from "../../infrastructure/services/TrialService";
|
|
12
|
-
|
|
13
|
-
export interface UseTrialEligibilityResult {
|
|
14
|
-
/** Whether device is eligible for trial */
|
|
15
|
-
isEligible: boolean;
|
|
16
|
-
/** Whether eligibility check is in progress */
|
|
17
|
-
isLoading: boolean;
|
|
18
|
-
/** Reason why not eligible (if applicable) */
|
|
19
|
-
reason?: "already_used" | "device_not_found" | "error";
|
|
20
|
-
/** Device ID used for checking */
|
|
21
|
-
deviceId: string | null;
|
|
22
|
-
/** Refresh eligibility status */
|
|
23
|
-
refresh: () => Promise<void>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Hook to check trial eligibility based on device ID
|
|
28
|
-
* Device ID persists across app reinstalls via Keychain
|
|
29
|
-
*/
|
|
30
|
-
export function useTrialEligibility(): UseTrialEligibilityResult {
|
|
31
|
-
const [isEligible, setIsEligible] = useState(true);
|
|
32
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
33
|
-
const [reason, setReason] = useState<"already_used" | "device_not_found" | "error">();
|
|
34
|
-
const [deviceId, setDeviceId] = useState<string | null>(null);
|
|
35
|
-
|
|
36
|
-
const checkEligibility = useCallback(async () => {
|
|
37
|
-
setIsLoading(true);
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const id = await getDeviceId();
|
|
41
|
-
setDeviceId(id);
|
|
42
|
-
|
|
43
|
-
const result = await checkTrialEligibility(id);
|
|
44
|
-
setIsEligible(result.eligible);
|
|
45
|
-
setReason(result.reason);
|
|
46
|
-
} catch {
|
|
47
|
-
// On error, allow trial (better UX)
|
|
48
|
-
setIsEligible(true);
|
|
49
|
-
setReason("error");
|
|
50
|
-
} finally {
|
|
51
|
-
setIsLoading(false);
|
|
52
|
-
}
|
|
53
|
-
}, []);
|
|
54
|
-
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
checkEligibility();
|
|
57
|
-
}, [checkEligibility]);
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
isEligible,
|
|
61
|
-
isLoading,
|
|
62
|
-
reason,
|
|
63
|
-
deviceId,
|
|
64
|
-
refresh: checkEligibility,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# useUserTier Hook
|
|
2
|
-
|
|
3
|
-
Hook for determining and tracking user tier (guest, free, premium).
|
|
4
|
-
|
|
5
|
-
## Location
|
|
6
|
-
|
|
7
|
-
**Import Path**: `@umituz/react-native-subscription`
|
|
8
|
-
|
|
9
|
-
**File**: `src/presentation/hooks/useUserTier.ts`
|
|
10
|
-
|
|
11
|
-
**Type**: Hook
|
|
12
|
-
|
|
13
|
-
## Strategy
|
|
14
|
-
|
|
15
|
-
### Tier Determination Flow
|
|
16
|
-
|
|
17
|
-
1. **Auth State Check**: Determine if user is authenticated
|
|
18
|
-
2. **Subscription Check**: Verify active subscription status
|
|
19
|
-
3. **Tier Assignment**: Assign tier based on auth + subscription
|
|
20
|
-
4. **Real-time Updates**: Update tier when auth or subscription changes
|
|
21
|
-
5. **Caching**: Cache tier status for performance
|
|
22
|
-
6. **Transition Tracking**: Monitor and track tier changes
|
|
23
|
-
|
|
24
|
-
### Integration Points
|
|
25
|
-
|
|
26
|
-
- **Auth Context**: User authentication state
|
|
27
|
-
- **Subscription Repository**: `src/infrastructure/repositories/SubscriptionRepository.ts`
|
|
28
|
-
- **Domain Layer**: `src/domain/entities/README.md`
|
|
29
|
-
- **TanStack Query**: For caching and real-time updates
|
|
30
|
-
|
|
31
|
-
## Restrictions
|
|
32
|
-
|
|
33
|
-
### REQUIRED
|
|
34
|
-
|
|
35
|
-
- **Tier Handling**: MUST handle all three tiers (guest, free, premium)
|
|
36
|
-
- **Loading State**: MUST handle loading state
|
|
37
|
-
- **Auth Integration**: MUST sync with authentication state
|
|
38
|
-
- **Guest Support**: MUST support unauthenticated users
|
|
39
|
-
|
|
40
|
-
### PROHIBITED
|
|
41
|
-
|
|
42
|
-
- **NEVER** assume user is authenticated (check isGuest)
|
|
43
|
-
- **NEVER** use for security decisions without server validation
|
|
44
|
-
- **DO NOT** hardcode tier values (use hook return values)
|
|
45
|
-
|
|
46
|
-
### CRITICAL SAFETY
|
|
47
|
-
|
|
48
|
-
- **ALWAYS** check loading state before using tier values
|
|
49
|
-
- **NEVER** trust client-side tier for security enforcement
|
|
50
|
-
- **MUST** handle tier transitions gracefully
|
|
51
|
-
- **ALWAYS** test with all three tier types
|
|
52
|
-
|
|
53
|
-
## AI Agent Guidelines
|
|
54
|
-
|
|
55
|
-
### When Implementing Tier-Based Features
|
|
56
|
-
|
|
57
|
-
1. **Always** handle loading state before checking tier
|
|
58
|
-
2. **Always** handle all three tiers (guest, free, premium)
|
|
59
|
-
3. **Always** provide upgrade path for free users
|
|
60
|
-
4. **Never** show features to guests that require auth
|
|
61
|
-
5. **Always** track tier changes in analytics
|
|
62
|
-
|
|
63
|
-
### Integration Checklist
|
|
64
|
-
|
|
65
|
-
- [ ] Import from correct path: `@umituz/react-native-subscription`
|
|
66
|
-
- [ ] Handle loading state
|
|
67
|
-
- [ ] Implement logic for guest users
|
|
68
|
-
- [ ] Implement logic for free users
|
|
69
|
-
- [ ] Implement logic for premium users
|
|
70
|
-
- [ ] Provide upgrade prompts for free users
|
|
71
|
-
- [ ] Provide auth prompts for guest users
|
|
72
|
-
- [ ] Test tier transitions (guest → free → premium)
|
|
73
|
-
- [ ] Test tier downgrade (premium → free)
|
|
74
|
-
- [ ] Track tier changes in analytics
|
|
75
|
-
|
|
76
|
-
### Common Patterns
|
|
77
|
-
|
|
78
|
-
1. **Conditional Rendering**: Show/hide features based on tier
|
|
79
|
-
2. **Navigation Guards**: Redirect based on tier requirements
|
|
80
|
-
3. **Feature Flags**: Enable features per tier
|
|
81
|
-
4. **Progress Indicators**: Show tier progression
|
|
82
|
-
5. **Upgrade Prompts**: Guide users to higher tiers
|
|
83
|
-
|
|
84
|
-
## Related Documentation
|
|
85
|
-
|
|
86
|
-
- **useAuth**: Authentication state
|
|
87
|
-
- **usePremium**: Premium subscription check
|
|
88
|
-
- **useSubscription**: Detailed subscription information
|
|
89
|
-
- **useAuthGate**: Authentication and subscription gating
|
|
90
|
-
- **Domain Entities**: `src/domain/entities/README.md`
|
|
91
|
-
- **User Tier Utils**: `src/utils/README.md`
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useUserTier Hook
|
|
3
|
-
*
|
|
4
|
-
* Centralized hook for determining user tier (Guest, Freemium, Premium)
|
|
5
|
-
* Single source of truth for all premium/freemium/guest checks
|
|
6
|
-
*
|
|
7
|
-
* This hook only handles LOGICAL tier determination.
|
|
8
|
-
* Database operations should be handled by the app via PremiumStatusFetcher.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```typescript
|
|
12
|
-
* const { tier, isPremium, isGuest } = useUserTier({
|
|
13
|
-
* isGuest: false,
|
|
14
|
-
* userId: 'user123',
|
|
15
|
-
* isPremium: true, // App should fetch this from database
|
|
16
|
-
* });
|
|
17
|
-
*
|
|
18
|
-
* // Simple, clean checks
|
|
19
|
-
* if (tier === "guest") {
|
|
20
|
-
* // Show guest upgrade card
|
|
21
|
-
* } else if (tier === "freemium") {
|
|
22
|
-
* // Show freemium limits
|
|
23
|
-
* } else {
|
|
24
|
-
* // Premium features
|
|
25
|
-
* }
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import { useMemo } from 'react';
|
|
30
|
-
import { getUserTierInfo } from '../../utils/tierUtils';
|
|
31
|
-
import type { UserTierInfo } from '../../utils/types';
|
|
32
|
-
|
|
33
|
-
export interface UseUserTierParams {
|
|
34
|
-
/** Whether user is a guest */
|
|
35
|
-
isGuest: boolean;
|
|
36
|
-
/** User ID (null for guests) */
|
|
37
|
-
userId: string | null;
|
|
38
|
-
/** Whether user has active premium subscription (app should fetch from database) */
|
|
39
|
-
isPremium: boolean;
|
|
40
|
-
/** Optional: Loading state from app */
|
|
41
|
-
isLoading?: boolean;
|
|
42
|
-
/** Optional: Error state from app */
|
|
43
|
-
error?: string | null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface UseUserTierResult extends UserTierInfo {
|
|
47
|
-
/** Whether premium status is currently loading */
|
|
48
|
-
isLoading: boolean;
|
|
49
|
-
/** Premium status error (if any) */
|
|
50
|
-
error: string | null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Hook to get user tier information
|
|
55
|
-
* Combines auth state and premium status into single source of truth
|
|
56
|
-
*
|
|
57
|
-
* All premium/freemium/guest checks are centralized here:
|
|
58
|
-
* - Guest: isGuest || !userId → always freemium, never premium
|
|
59
|
-
* - Freemium: authenticated but !isPremium
|
|
60
|
-
* - Premium: authenticated && isPremium
|
|
61
|
-
*
|
|
62
|
-
* Note: This hook only handles LOGICAL tier determination.
|
|
63
|
-
* Database operations (fetching premium status) should be handled by the app.
|
|
64
|
-
*/
|
|
65
|
-
export function useUserTier(params: UseUserTierParams): UseUserTierResult {
|
|
66
|
-
const { isGuest, userId, isPremium, isLoading = false, error = null } = params;
|
|
67
|
-
|
|
68
|
-
// Calculate tier info using centralized logic
|
|
69
|
-
const tierInfo = useMemo(() => {
|
|
70
|
-
return getUserTierInfo(isGuest, userId, isPremium);
|
|
71
|
-
}, [isGuest, userId, isPremium]);
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
...tierInfo,
|
|
75
|
-
isLoading,
|
|
76
|
-
error,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# useUserTierWithRepository Hook
|
|
2
|
-
|
|
3
|
-
Automatically fetches premium status and provides tier information with repository integration.
|
|
4
|
-
|
|
5
|
-
## Location
|
|
6
|
-
|
|
7
|
-
**Import Path**: `@umituz/react-native-subscription`
|
|
8
|
-
|
|
9
|
-
**File**: `src/presentation/hooks/useUserTierWithRepository.ts`
|
|
10
|
-
|
|
11
|
-
**Type**: Hook
|
|
12
|
-
|
|
13
|
-
## Strategy
|
|
14
|
-
|
|
15
|
-
### Tier Determination with Repository
|
|
16
|
-
|
|
17
|
-
1. **Auth State Check**: Use provided auth provider to check authentication
|
|
18
|
-
2. **Premium Fetch**: Automatically fetch premium status from repository for authenticated users
|
|
19
|
-
3. **Tier Assignment**: Assign tier based on auth + subscription (guest/freemium/premium)
|
|
20
|
-
4. **Abort Control**: Use AbortController to prevent race conditions on rapid user changes
|
|
21
|
-
5. **Guest Optimization**: Skip repository fetch for guest users (no fetch needed)
|
|
22
|
-
6. **Real-time Updates**: React to auth state changes
|
|
23
|
-
|
|
24
|
-
### Integration Points
|
|
25
|
-
|
|
26
|
-
- **Auth Provider**: Custom auth provider with user state
|
|
27
|
-
- **Subscription Repository**: `src/infrastructure/repositories/SubscriptionRepository.ts`
|
|
28
|
-
- **Domain Layer**: `src/domain/entities/README.md`
|
|
29
|
-
- **TanStack Query**: For caching and real-time updates
|
|
30
|
-
|
|
31
|
-
## Restrictions
|
|
32
|
-
|
|
33
|
-
### REQUIRED
|
|
34
|
-
|
|
35
|
-
- **Auth Provider**: MUST provide valid auth provider object
|
|
36
|
-
- **Repository**: MUST provide subscription repository
|
|
37
|
-
- **Loading State**: MUST handle loading state
|
|
38
|
-
- **Error Handling**: MUST handle error state
|
|
39
|
-
|
|
40
|
-
### PROHIBITED
|
|
41
|
-
|
|
42
|
-
- **NEVER** call without valid auth provider
|
|
43
|
-
- **NEVER** call without valid repository
|
|
44
|
-
- **DO NOT** hardcode tier values (use hook return values)
|
|
45
|
-
- **DO NOT** assume instant data availability
|
|
46
|
-
|
|
47
|
-
### CRITICAL SAFETY
|
|
48
|
-
|
|
49
|
-
- **ALWAYS** check loading state before using tier values
|
|
50
|
-
- **NEVER** trust client-side tier for security enforcement
|
|
51
|
-
- **MUST** provide valid auth provider structure
|
|
52
|
-
- **ALWAYS** handle tier transitions gracefully
|
|
53
|
-
|
|
54
|
-
## AI Agent Guidelines
|
|
55
|
-
|
|
56
|
-
### When Implementing Tier Determination
|
|
57
|
-
|
|
58
|
-
1. **Always** provide valid auth provider with user, isGuest, isAuthenticated
|
|
59
|
-
2. **Always** provide valid subscription repository
|
|
60
|
-
3. **Always** handle loading state
|
|
61
|
-
4. **Always** handle error state
|
|
62
|
-
5. **Never** use for security decisions without server validation
|
|
63
|
-
|
|
64
|
-
### Integration Checklist
|
|
65
|
-
|
|
66
|
-
- [ ] Import from correct path: `@umituz/react-native-subscription`
|
|
67
|
-
- [ ] Provide valid auth provider object
|
|
68
|
-
- [ ] Provide valid subscription repository
|
|
69
|
-
- [ ] Handle loading state
|
|
70
|
-
- [ ] Handle error state
|
|
71
|
-
- [ ] Use refresh() when needed
|
|
72
|
-
- [ ] Test with guest user
|
|
73
|
-
- [ ] Test with freemium user
|
|
74
|
-
- [ ] Test with premium user
|
|
75
|
-
- [ ] Test user switching scenarios
|
|
76
|
-
|
|
77
|
-
### Common Patterns
|
|
78
|
-
|
|
79
|
-
1. **Auth Provider Setup**: Use with Firebase, custom auth, or auth library
|
|
80
|
-
2. **Tier-Based UI**: Show different features based on tier
|
|
81
|
-
3. **Navigation Guards**: Redirect based on tier requirements
|
|
82
|
-
4. **Tier Monitoring**: Track tier changes for analytics
|
|
83
|
-
5. **Manual Refresh**: Call refresh() after subscription changes
|
|
84
|
-
|
|
85
|
-
## Related Documentation
|
|
86
|
-
|
|
87
|
-
- **useUserTier**: Tier logic without repository
|
|
88
|
-
- **usePremium**: Premium status checking
|
|
89
|
-
- **useAuthGate**: Authentication gating
|
|
90
|
-
- **useSubscription**: Subscription details
|
|
91
|
-
- **Repository Pattern**: `src/infrastructure/repositories/README.md`
|
|
92
|
-
- **User Tier Utils**: `src/utils/README.md`
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useUserTierWithRepository Hook
|
|
3
|
-
* Automatically fetches premium status and provides user tier information.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useEffect, useState, useCallback } from 'react';
|
|
7
|
-
import { useUserTier, type UseUserTierParams } from './useUserTier';
|
|
8
|
-
import type { ISubscriptionRepository } from '../../application/ports/ISubscriptionRepository';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Auth provider interface
|
|
12
|
-
* Apps should provide an object that matches this interface
|
|
13
|
-
*/
|
|
14
|
-
export interface AuthProvider {
|
|
15
|
-
/** Current user object (null for guests) */
|
|
16
|
-
user: { uid: string } | null;
|
|
17
|
-
/** Whether user is a guest */
|
|
18
|
-
isGuest: boolean;
|
|
19
|
-
/** Whether user is authenticated */
|
|
20
|
-
isAuthenticated: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface UseUserTierWithRepositoryParams {
|
|
24
|
-
/** Auth provider (e.g., result of useAuth hook) */
|
|
25
|
-
auth: AuthProvider;
|
|
26
|
-
/** Subscription repository for fetching premium status */
|
|
27
|
-
repository: ISubscriptionRepository;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface UseUserTierWithRepositoryResult {
|
|
31
|
-
/** User tier: 'guest' | 'freemium' | 'premium' */
|
|
32
|
-
tier: 'guest' | 'freemium' | 'premium';
|
|
33
|
-
/** Whether user has premium access */
|
|
34
|
-
isPremium: boolean;
|
|
35
|
-
/** Whether user is a guest */
|
|
36
|
-
isGuest: boolean;
|
|
37
|
-
/** Whether user is authenticated */
|
|
38
|
-
isAuthenticated: boolean;
|
|
39
|
-
/** User ID (null for guests) */
|
|
40
|
-
userId: string | null;
|
|
41
|
-
/** Whether premium status is currently loading */
|
|
42
|
-
isLoading: boolean;
|
|
43
|
-
/** Premium status error (if any) */
|
|
44
|
-
error: string | null;
|
|
45
|
-
/** Refresh premium status from repository */
|
|
46
|
-
refresh: () => Promise<void>;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Hook that automatically fetches premium status and provides user tier information
|
|
51
|
-
*
|
|
52
|
-
* This hook eliminates the need for app-specific useUserTier wrappers by:
|
|
53
|
-
* 1. Automatically fetching premium status from repository
|
|
54
|
-
* 2. Handling loading and error states
|
|
55
|
-
* 3. Providing refresh functionality
|
|
56
|
-
* 4. Using centralized tier logic from useUserTier
|
|
57
|
-
*/
|
|
58
|
-
export function useUserTierWithRepository(
|
|
59
|
-
params: UseUserTierWithRepositoryParams,
|
|
60
|
-
): UseUserTierWithRepositoryResult {
|
|
61
|
-
const { auth, repository } = params;
|
|
62
|
-
const { user, isGuest, isAuthenticated } = auth;
|
|
63
|
-
|
|
64
|
-
const [isPremium, setIsPremium] = useState<boolean>(false);
|
|
65
|
-
const [isLoading, setIsLoading] = useState<boolean>(true);
|
|
66
|
-
const [error, setError] = useState<string | null>(null);
|
|
67
|
-
|
|
68
|
-
// Fetch premium status from repository
|
|
69
|
-
const fetchPremiumStatus = useCallback(async (signal?: AbortSignal) => {
|
|
70
|
-
// Guest users are never premium - no need to fetch
|
|
71
|
-
if (!isAuthenticated || !user) {
|
|
72
|
-
setIsPremium(false);
|
|
73
|
-
setIsLoading(false);
|
|
74
|
-
setError(null);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
setIsLoading(true);
|
|
80
|
-
setError(null);
|
|
81
|
-
|
|
82
|
-
const status = await repository.getSubscriptionStatus(user.uid);
|
|
83
|
-
|
|
84
|
-
// Check if operation was aborted
|
|
85
|
-
if (signal?.aborted) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const isPremiumValue =
|
|
90
|
-
status !== null && repository.isSubscriptionValid(status);
|
|
91
|
-
|
|
92
|
-
// Check again before setting state
|
|
93
|
-
if (!signal?.aborted) {
|
|
94
|
-
setIsPremium(isPremiumValue);
|
|
95
|
-
setIsLoading(false);
|
|
96
|
-
}
|
|
97
|
-
} catch (err) {
|
|
98
|
-
// Don't set state if operation was aborted
|
|
99
|
-
if (!signal?.aborted) {
|
|
100
|
-
const errorMessage =
|
|
101
|
-
err instanceof Error ? err.message : 'Failed to fetch premium status';
|
|
102
|
-
setError(errorMessage);
|
|
103
|
-
setIsPremium(false);
|
|
104
|
-
setIsLoading(false);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}, [isAuthenticated, user, repository]);
|
|
108
|
-
|
|
109
|
-
// Fetch premium status when auth state changes
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
const abortController = new AbortController();
|
|
112
|
-
|
|
113
|
-
fetchPremiumStatus(abortController.signal).catch(() => {
|
|
114
|
-
// Error is handled in fetchPremiumStatus
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
return () => {
|
|
118
|
-
abortController.abort();
|
|
119
|
-
};
|
|
120
|
-
}, [fetchPremiumStatus]);
|
|
121
|
-
|
|
122
|
-
// Refresh function
|
|
123
|
-
const refresh = useCallback(async () => {
|
|
124
|
-
if (!isAuthenticated || !user) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
const abortController = new AbortController();
|
|
128
|
-
try {
|
|
129
|
-
await fetchPremiumStatus(abortController.signal);
|
|
130
|
-
} finally {
|
|
131
|
-
abortController.abort();
|
|
132
|
-
}
|
|
133
|
-
}, [isAuthenticated, user, fetchPremiumStatus]);
|
|
134
|
-
|
|
135
|
-
// Use base useUserTier hook for tier logic
|
|
136
|
-
const useUserTierParams: UseUserTierParams = {
|
|
137
|
-
isGuest: isGuest || !isAuthenticated,
|
|
138
|
-
userId: user?.uid || null,
|
|
139
|
-
isPremium,
|
|
140
|
-
isLoading,
|
|
141
|
-
error,
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const tierInfo = useUserTier(useUserTierParams);
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
...tierInfo,
|
|
148
|
-
refresh,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|