/** * Public Plans Hook * * Fetches public plans from the billing API for the marketing pricing page. * This endpoint doesn't require authentication. */ import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; import { API_BASE_URL } from '../api/config'; // ============================================================================= // Types // ============================================================================= export interface Feature { id: number; code: string; name: string; description: string; feature_type: 'boolean' | 'integer'; } export interface PlanFeature { id: number; feature: Feature; bool_value: boolean | null; int_value: number | null; value: boolean | number | null; } export interface Plan { id: number; code: string; name: string; description: string; display_order: number; is_active: boolean; } export interface PublicPlanVersion { id: number; plan: Plan; version: number; name: string; is_public: boolean; is_legacy: boolean; price_monthly_cents: number; price_yearly_cents: number; transaction_fee_percent: string; transaction_fee_fixed_cents: number; trial_days: number; is_most_popular: boolean; show_price: boolean; marketing_features: string[]; is_available: boolean; features: PlanFeature[]; created_at: string; } // ============================================================================= // API Client (no auth required) // ============================================================================= const publicApiClient = axios.create({ baseURL: API_BASE_URL, headers: { 'Content-Type': 'application/json', }, }); // ============================================================================= // API Functions // ============================================================================= /** * Fetch public plans from the billing catalog. * No authentication required. */ export const fetchPublicPlans = async (): Promise => { const response = await publicApiClient.get('/billing/plans/'); return response.data; }; // ============================================================================= // Hook // ============================================================================= /** * Hook to fetch public plans for the pricing page. */ export const usePublicPlans = () => { return useQuery({ queryKey: ['publicPlans'], queryFn: fetchPublicPlans, staleTime: 5 * 60 * 1000, // 5 minutes gcTime: 30 * 60 * 1000, // 30 minutes (formerly cacheTime) }); }; // ============================================================================= // Helper Functions // ============================================================================= /** * Format price from cents to dollars with currency symbol. */ export const formatPrice = (cents: number): string => { if (cents === 0) return '$0'; return `$${(cents / 100).toFixed(0)}`; }; /** * Get a feature value from a plan version by feature code. */ export const getPlanFeatureValue = ( planVersion: PublicPlanVersion, featureCode: string ): boolean | number | null => { const planFeature = planVersion.features.find( (pf) => pf.feature.code === featureCode ); return planFeature?.value ?? null; }; /** * Check if a plan has a boolean feature enabled. */ export const hasPlanFeature = ( planVersion: PublicPlanVersion, featureCode: string ): boolean => { const value = getPlanFeatureValue(planVersion, featureCode); return value === true; }; /** * Get an integer limit from a plan version. * Returns 0 if not set (unlimited) or the actual limit. */ export const getPlanLimit = ( planVersion: PublicPlanVersion, featureCode: string ): number => { const value = getPlanFeatureValue(planVersion, featureCode); return typeof value === 'number' ? value : 0; }; /** * Format a limit value for display. * 0 means unlimited. */ export const formatLimit = (value: number): string => { if (value === 0) return 'Unlimited'; return value.toLocaleString(); };