'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<Subscription | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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
};
}