import React, { createContext, useContext, useState, useCallback, useEffect } from 'react'; import { useAuth } from './AuthContext'; import { Subscription, SubscriptionPlan, SubscriptionStatus, SubscriptionFeatureFlag, planHasFeature, isSubscriptionActive, SUBSCRIPTION_PLANS, } from '@/types/subscription'; interface SubscriptionContextType { subscription: Subscription | null; isLoading: boolean; refreshSubscription: () => Promise; // Convenience methods hasFeature: (feature: SubscriptionFeatureFlag) => boolean; hasActiveSubscription: boolean; canAccessFeature: (feature: SubscriptionFeatureFlag) => boolean; isWithinLimits: (limitType: 'organizations' | 'users' | 'storage' | 'apiCalls', current: number) => boolean; getRemainingLimit: (limitType: 'organizations' | 'users' | 'storage' | 'apiCalls', current: number) => number; } const SubscriptionContext = createContext(undefined); export const useSubscription = () => { const context = useContext(SubscriptionContext); if (context === undefined) { throw new Error('useSubscription must be used within a SubscriptionProvider'); } return context; }; interface SubscriptionProviderProps { children: React.ReactNode; } /** * Subscription context provider for managing subscription state */ export const SubscriptionProvider = ({ children }: SubscriptionProviderProps) => { const { user, isAuthenticated } = useAuth(); const [subscription, setSubscription] = useState(null); const [isLoading, setIsLoading] = useState(true); const fetchSubscription = useCallback(async (): Promise => { if (!isAuthenticated || !user) { return null; } try { const token = localStorage.getItem('auth_token'); const isProduction = import.meta.env.PROD; const baseUrl = import.meta.env.VITE_API_BASE_URL || (isProduction ? 'https://api.bugulma.city' : ''); const response = await fetch(`${baseUrl}/api/v1/subscription`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, signal: AbortSignal.timeout(5000), }); if (response.ok) { const data = await response.json(); return { ...data, currentPeriodStart: new Date(data.currentPeriodStart), currentPeriodEnd: new Date(data.currentPeriodEnd), trialEnd: data.trialEnd ? new Date(data.trialEnd) : undefined, }; } // If no subscription, return default free plan return { id: 'free', userId: user.id, plan: 'free' as SubscriptionPlan, status: 'none' as SubscriptionStatus, billingPeriod: 'monthly', currentPeriodStart: new Date(), currentPeriodEnd: new Date(), cancelAtPeriodEnd: false, features: SUBSCRIPTION_PLANS.free.features, limits: SUBSCRIPTION_PLANS.free.limits, }; } catch (error) { console.error('Failed to fetch subscription:', error); // Return free plan on error return { id: 'free', userId: user.id, plan: 'free' as SubscriptionPlan, status: 'none' as SubscriptionStatus, billingPeriod: 'monthly', currentPeriodStart: new Date(), currentPeriodEnd: new Date(), cancelAtPeriodEnd: false, features: SUBSCRIPTION_PLANS.free.features, limits: SUBSCRIPTION_PLANS.free.limits, }; } }, [isAuthenticated, user]); const refreshSubscription = useCallback(async () => { setIsLoading(true); try { const sub = await fetchSubscription(); setSubscription(sub); } catch (error) { console.error('Failed to refresh subscription:', error); } finally { setIsLoading(false); } }, [fetchSubscription]); useEffect(() => { if (isAuthenticated) { refreshSubscription(); } else { setSubscription(null); setIsLoading(false); } }, [isAuthenticated, refreshSubscription]); const hasFeature = useCallback( (feature: SubscriptionFeatureFlag): boolean => { if (!subscription) return false; return planHasFeature(subscription.plan, feature); }, [subscription] ); const hasActiveSubscription = subscription ? isSubscriptionActive(subscription.status) : false; const canAccessFeature = useCallback( (feature: SubscriptionFeatureFlag): boolean => { if (!subscription) return false; return hasActiveSubscription && hasFeature(feature); }, [subscription, hasActiveSubscription, hasFeature] ); const isWithinLimits = useCallback( (limitType: 'organizations' | 'users' | 'storage' | 'apiCalls', current: number): boolean => { if (!subscription) return true; // No subscription = free plan limits const limit = subscription.limits[limitType]; if (limit === undefined || limit === -1) return true; // Unlimited return current < limit; }, [subscription] ); const getRemainingLimit = useCallback( (limitType: 'organizations' | 'users' | 'storage' | 'apiCalls', current: number): number => { if (!subscription) { const freeLimit = SUBSCRIPTION_PLANS.free.limits[limitType] || 0; return Math.max(0, freeLimit - current); } const limit = subscription.limits[limitType]; if (limit === undefined || limit === -1) return Infinity; return Math.max(0, limit - current); }, [subscription] ); const value: SubscriptionContextType = { subscription, isLoading, refreshSubscription, hasFeature, hasActiveSubscription, canAccessFeature, isWithinLimits, getRemainingLimit, }; return {children}; };