/** * @ai-summary React hook for tier-based feature access checking * @ai-context Used to gate premium features based on user subscription tier */ import { useQuery } from '@tanstack/react-query'; import { useAuth0 } from '@auth0/auth0-react'; import { apiClient } from '../api/client'; import type { SubscriptionTier } from '../../features/settings/types/profile.types'; // Feature tier configuration (mirrors backend) export interface FeatureConfig { minTier: SubscriptionTier; name: string; upgradePrompt: string; } interface FeatureTiersResponse { tiers: Record; features: Record; } interface AccessCheckResult { allowed: boolean; requiredTier: SubscriptionTier | null; config: FeatureConfig | null; } // Tier hierarchy for comparison const TIER_LEVELS: Record = { free: 0, pro: 1, enterprise: 2, }; /** * Hook to check if user can access tier-gated features * Fetches user profile for tier and feature config from backend */ export const useTierAccess = () => { const { isAuthenticated, isLoading: authLoading } = useAuth0(); // Fetch user profile for current tier const profileQuery = useQuery({ queryKey: ['user-profile'], queryFn: async () => { const response = await apiClient.get('/user/profile'); return response.data; }, enabled: isAuthenticated && !authLoading, staleTime: 5 * 60 * 1000, // 5 minutes gcTime: 10 * 60 * 1000, }); // Fetch feature tier config from backend (single source of truth) const featureConfigQuery = useQuery({ queryKey: ['feature-tiers'], queryFn: async () => { const response = await apiClient.get('/config/feature-tiers'); return response.data; }, enabled: isAuthenticated && !authLoading, staleTime: 30 * 60 * 1000, // 30 minutes - config rarely changes gcTime: 60 * 60 * 1000, // 1 hour cache refetchOnWindowFocus: false, refetchOnMount: false, }); const tier: SubscriptionTier = profileQuery.data?.subscriptionTier || 'free'; const features = featureConfigQuery.data?.features || {}; /** * Check if user can access a feature by key */ const hasAccess = (featureKey: string): boolean => { const config = features[featureKey]; if (!config) { // Unknown features are allowed (fail open for safety) return true; } return TIER_LEVELS[tier] >= TIER_LEVELS[config.minTier]; }; /** * Get detailed access information for a feature */ const checkAccess = (featureKey: string): AccessCheckResult => { const config = features[featureKey] || null; if (!config) { return { allowed: true, requiredTier: null, config: null, }; } return { allowed: TIER_LEVELS[tier] >= TIER_LEVELS[config.minTier], requiredTier: config.minTier, config, }; }; return { tier, loading: profileQuery.isLoading || featureConfigQuery.isLoading, hasAccess, checkAccess, }; }; export default useTierAccess;