'use client' import { useState, useEffect, useRef, useCallback } from 'react'; import { useAuth } from '@/components/AuthProvider'; import { supabase } from '@/lib/supabase'; import { PRICING_TIERS, getPlanByPriceId, PricingTier } from '@/lib/stripe'; export interface Subscription { id: string; status: string; price_id: string; current_period_start: string; current_period_end: string; cancel_at_period_end: boolean; stripe_customer_id?: string | null; } export function useSubscription() { const { user } = useAuth(); const [subscription, setSubscription] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const hasFetched = useRef(false); useEffect(() => { if (!user) { setSubscription(null); setLoading(false); hasFetched.current = false; return; } // Always fetch when user changes fetchSubscription(); }, [user?.id]); // Refetch subscription when page becomes visible (in case user upgraded in another tab) useEffect(() => { const handleVisibilityChange = () => { if (!document.hidden && user?.id) { // Reset the fetch flag to allow refetching hasFetched.current = false; fetchSubscription(); } }; document.addEventListener('visibilitychange', handleVisibilityChange); return () => { document.removeEventListener('visibilitychange', handleVisibilityChange); }; }, [user?.id]); const fetchSubscription = async () => { if (!user) return; try { setLoading(true); const { data, error } = await supabase .from('subscriptions') .select('id, status, price_id, current_period_start, current_period_end, cancel_at_period_end, stripe_customer_id') .eq('user_id', user.id) .in('status', ['active', 'trialing', 'past_due']) // Include all manageable statuses .order('created_at', { ascending: false }) .maybeSingle(); // Use maybeSingle() instead of single() to handle 0 or 1 rows gracefully if (error) { throw error; } setSubscription(data); setError(null); hasFetched.current = true; } catch (err) { console.error('Error fetching subscription:', err); setError(err instanceof Error ? err.message : 'Failed to fetch subscription'); } finally { setLoading(false); } }; // Subscription status helpers const isActive = subscription?.status === 'active' || subscription?.status === 'trialing'; const isPastDue = subscription?.status === 'past_due'; const isCanceled = subscription?.status === 'canceled'; const willCancelAtPeriodEnd = subscription?.cancel_at_period_end || false; // Get current plan (same logic as billing page) const getCurrentPlan = (): PricingTier => { if (!subscription?.price_id) return PRICING_TIERS.FREE; return getPlanByPriceId(subscription.price_id) || PRICING_TIERS.FREE; }; // Simple feature access checking based on plan features const hasFeatureAccess = (feature: string): boolean => { const currentPlan = getCurrentPlan(); if (!isActive && currentPlan.id !== 'free') return false; return currentPlan.features.includes(feature); }; // Check if user can manage billing (has a Stripe customer ID and manageable status) const canManageBilling = (): boolean => { return !!( subscription?.stripe_customer_id && subscription?.status && ['active', 'trialing', 'past_due'].includes(subscription.status) ); }; // Plan comparison helpers const canUpgrade = (targetPriceId: string): boolean => { const currentPlan = getCurrentPlan(); const targetPlan = getPlanByPriceId(targetPriceId); if (!targetPlan) return false; if (currentPlan.id === 'free') return true; return targetPlan.pricing.monthly.price > currentPlan.pricing.monthly.price; }; const canDowngrade = (targetPriceId: string): boolean => { const currentPlan = getCurrentPlan(); const targetPlan = getPlanByPriceId(targetPriceId); if (!targetPlan || currentPlan.id === 'free') return false; return targetPlan.pricing.monthly.price < currentPlan.pricing.monthly.price; }; // Force refresh by fetching fresh data const refetch = useCallback(async () => { hasFetched.current = false; await fetchSubscription(); }, []); // Clear cache and refetch (useful after checkout) const refreshAfterCheckout = useCallback(async () => { setSubscription(null); setError(null); hasFetched.current = false; await fetchSubscription(); }, []); return { subscription, loading, error, isActive, isPastDue, isCanceled, willCancelAtPeriodEnd, getCurrentPlan, hasFeatureAccess, canUpgrade, canDowngrade, canManageBilling, refetch, refreshAfterCheckout }; }