/** * Subscription Management Page * Allows users to view current subscription, billing details, and upgrade plans */ import React, { useState, useEffect } from 'react'; import { Card, Button, Badge, Modal } from '../../../../components/ui'; import { PageHeader } from '../../../../components/layout'; import { CreditCard, Users, MapPin, Package, TrendingUp, Calendar, CheckCircle, AlertCircle, ArrowRight, Crown, Star, Zap, X, RefreshCw, Settings, Download, ExternalLink } from 'lucide-react'; import { useAuth } from '../../../../hooks/api/useAuth'; import { useBakeryStore } from '../../../../stores/bakery.store'; import { useToast } from '../../../../hooks/ui/useToast'; import { subscriptionService, type UsageSummary, type AvailablePlans } from '../../../../services/api'; import { isMockMode, getMockSubscription } from '../../../../config/mock.config'; interface PlanComparisonProps { plans: AvailablePlans['plans']; currentPlan: string; onUpgrade: (planKey: string) => void; } const ProgressBar: React.FC<{ value: number; className?: string }> = ({ value, className = '' }) => { const getProgressColor = () => { if (value >= 90) return 'bg-red-500'; if (value >= 80) return 'bg-yellow-500'; return 'bg-green-500'; }; return (
); }; // Tabs implementation interface TabsProps { defaultValue: string; className?: string; children: React.ReactNode; } interface TabsListProps { className?: string; children: React.ReactNode; } interface TabsTriggerProps { value: string; children: React.ReactNode; className?: string; } interface TabsContentProps { value: string; children: React.ReactNode; className?: string; } const TabsContext = React.createContext<{ activeTab: string; setActiveTab: (value: string) => void } | null>(null); const Tabs: React.FC & { List: React.FC; Trigger: React.FC; Content: React.FC; } = ({ defaultValue, className = '', children }) => { const [activeTab, setActiveTab] = useState(defaultValue); return (
{children}
); }; const TabsList: React.FC = ({ className = '', children }) => { return (
{children}
); }; const TabsTrigger: React.FC = ({ value, children, className = '' }) => { const context = React.useContext(TabsContext); if (!context) throw new Error('TabsTrigger must be used within Tabs'); const { activeTab, setActiveTab } = context; const isActive = activeTab === value; return ( ); }; const TabsContent: React.FC = ({ value, children, className = '' }) => { const context = React.useContext(TabsContext); if (!context) throw new Error('TabsContent must be used within Tabs'); const { activeTab } = context; if (activeTab !== value) return null; return (
{children}
); }; Tabs.List = TabsList; Tabs.Trigger = TabsTrigger; Tabs.Content = TabsContent; const PlanComparison: React.FC = ({ plans, currentPlan, onUpgrade }) => { const planOrder = ['starter', 'professional', 'enterprise']; const sortedPlans = Object.entries(plans).sort(([a], [b]) => planOrder.indexOf(a) - planOrder.indexOf(b) ); const getPlanColor = (planKey: string) => { switch (planKey) { case 'starter': return 'border-blue-500/30 bg-blue-500/5'; case 'professional': return 'border-purple-500/30 bg-purple-500/5'; case 'enterprise': return 'border-amber-500/30 bg-amber-500/5'; default: return 'border-[var(--border-primary)] bg-[var(--bg-secondary)]'; } }; return (
{sortedPlans.map(([planKey, plan]) => ( {plan.popular && (
Más Popular
)}

{plan.name}

{subscriptionService.formatPrice(plan.monthly_price)} /mes

{plan.description}

{plan.max_users === -1 ? 'Usuarios ilimitados' : `${plan.max_users} usuarios`}
{plan.max_locations === -1 ? 'Ubicaciones ilimitadas' : `${plan.max_locations} ubicación${plan.max_locations > 1 ? 'es' : ''}`}
{plan.max_products === -1 ? 'Productos ilimitados' : `${plan.max_products} productos`}
{currentPlan === planKey ? ( Plan Actual ) : ( )}
))}
); }; const SubscriptionPage: React.FC = () => { const { user, tenant_id } = useAuth(); const { currentTenant } = useBakeryStore(); const toast = useToast(); const [usageSummary, setUsageSummary] = useState(null); const [availablePlans, setAvailablePlans] = useState(null); const [loading, setLoading] = useState(true); const [upgradeDialogOpen, setUpgradeDialogOpen] = useState(false); const [selectedPlan, setSelectedPlan] = useState(''); const [upgrading, setUpgrading] = useState(false); useEffect(() => { if (currentTenant?.id || tenant_id || isMockMode()) { loadSubscriptionData(); } }, [currentTenant, tenant_id]); const loadSubscriptionData = async () => { let tenantId = currentTenant?.id || tenant_id; // In mock mode, use the mock tenant ID if no real tenant is available if (isMockMode() && !tenantId) { tenantId = getMockSubscription().tenant_id; console.log('🧪 Mock mode: Using mock tenant ID:', tenantId); } console.log('📊 Loading subscription data for tenant:', tenantId, '| Mock mode:', isMockMode()); if (!tenantId) return; try { setLoading(true); const [usage, plans] = await Promise.all([ subscriptionService.getUsageSummary(tenantId), subscriptionService.getAvailablePlans() ]); setUsageSummary(usage); setAvailablePlans(plans); } catch (error) { console.error('Error loading subscription data:', error); toast.error("No se pudo cargar la información de suscripción"); } finally { setLoading(false); } }; const handleUpgradeClick = (planKey: string) => { setSelectedPlan(planKey); setUpgradeDialogOpen(true); }; const handleUpgradeConfirm = async () => { let tenantId = currentTenant?.id || tenant_id; // In mock mode, use the mock tenant ID if no real tenant is available if (isMockMode() && !tenantId) { tenantId = getMockSubscription().tenant_id; } if (!tenantId || !selectedPlan) return; try { setUpgrading(true); const validation = await subscriptionService.validatePlanUpgrade( tenantId, selectedPlan ); if (!validation.can_upgrade) { toast.error(validation.reason); return; } const result = await subscriptionService.upgradePlan(tenantId, selectedPlan); if (result.success) { toast.success(result.message); await loadSubscriptionData(); setUpgradeDialogOpen(false); setSelectedPlan(''); } else { toast.error('Error al cambiar el plan'); } } catch (error) { console.error('Error upgrading plan:', error); toast.error('Error al procesar el cambio de plan'); } finally { setUpgrading(false); } }; if (loading) { return (

Cargando información de suscripción...

); } if (!usageSummary || !availablePlans) { return (

No se pudo cargar la información

Hubo un problema al cargar los datos de suscripción

); } const nextBillingDate = usageSummary.next_billing_date ? new Date(usageSummary.next_billing_date).toLocaleDateString('es-ES', { year: 'numeric', month: 'long', day: 'numeric' }) : 'No disponible'; const planInfo = subscriptionService.getPlanDisplayInfo(usageSummary.plan); return (
window.open('https://billing.bakery.com', '_blank'), variant: 'outline' }, { id: 'download-invoice', label: 'Descargar Factura', icon: Download, onClick: () => console.log('Download latest invoice'), variant: 'outline' } ]} metadata={[ { id: 'next-billing', label: 'Próxima facturación', value: nextBillingDate, icon: Calendar }, { id: 'monthly-cost', label: 'Coste mensual', value: subscriptionService.formatPrice(usageSummary.monthly_price), icon: CreditCard } ]} onRefresh={loadSubscriptionData} showRefreshButton /> {/* Quick Stats Overview */}

Usuarios

{usageSummary.usage.users.current}/{usageSummary.usage.users.unlimited ? '∞' : usageSummary.usage.users.limit}

Ubicaciones

{usageSummary.usage.locations.current}/{usageSummary.usage.locations.unlimited ? '∞' : usageSummary.usage.locations.limit}

Productos

{usageSummary.usage.products.current}/{usageSummary.usage.products.unlimited ? '∞' : usageSummary.usage.products.limit}

Estado

{usageSummary.status === 'active' ? 'Activo' : usageSummary.status}
Resumen Uso Planes Facturación
{/* Current Plan Summary */}

Tu Plan Actual

Plan
{planInfo.name} {usageSummary.plan === 'professional' && ( Popular )}
Precio {subscriptionService.formatPrice(usageSummary.monthly_price)}/mes
Estado {usageSummary.status === 'active' ? 'Activo' : 'Inactivo'}
Próxima facturación {nextBillingDate}
{/* Quick Actions */}

Acciones Rápidas

{/* Usage at a Glance */}

Uso de Recursos

{/* Users */}
Usuarios
{usageSummary.usage.users.current}/{usageSummary.usage.users.unlimited ? '∞' : usageSummary.usage.users.limit}

{usageSummary.usage.users.usage_percentage}% utilizado {usageSummary.usage.users.unlimited ? 'Ilimitado' : `${usageSummary.usage.users.limit - usageSummary.usage.users.current} restantes`}

{/* Locations */}
Ubicaciones
{usageSummary.usage.locations.current}/{usageSummary.usage.locations.unlimited ? '∞' : usageSummary.usage.locations.limit}

{usageSummary.usage.locations.usage_percentage}% utilizado {usageSummary.usage.locations.unlimited ? 'Ilimitado' : `${usageSummary.usage.locations.limit - usageSummary.usage.locations.current} restantes`}

{/* Products */}
Productos
{usageSummary.usage.products.current}/{usageSummary.usage.products.unlimited ? '∞' : usageSummary.usage.products.limit}

{usageSummary.usage.products.usage_percentage}% utilizado {usageSummary.usage.products.unlimited ? 'Ilimitado' : 'Ilimitado'}

Detalles de Uso

{/* Users Usage */}

Gestión de Usuarios

Usuarios activos {usageSummary.usage.users.current}
Límite del plan {usageSummary.usage.users.unlimited ? 'Ilimitado' : usageSummary.usage.users.limit}

{usageSummary.usage.users.usage_percentage}% de capacidad utilizada

{/* Locations Usage */}

Ubicaciones

Ubicaciones activas {usageSummary.usage.locations.current}
Límite del plan {usageSummary.usage.locations.unlimited ? 'Ilimitado' : usageSummary.usage.locations.limit}

{usageSummary.usage.locations.usage_percentage}% de capacidad utilizada

{/* Products Usage */}

Productos

Productos registrados {usageSummary.usage.products.current}
Límite del plan {usageSummary.usage.products.unlimited ? 'Ilimitado' : usageSummary.usage.products.limit}

{usageSummary.usage.products.usage_percentage}% de capacidad utilizada

Planes de Suscripción

Elige el plan que mejor se adapte a las necesidades de tu panadería

Información de Facturación

Plan actual: {planInfo.name}
Precio mensual: {subscriptionService.formatPrice(usageSummary.monthly_price)}
Próxima facturación: {nextBillingDate}

Métodos de Pago

•••• •••• •••• 4242
Visa terminada en 4242
Principal
{/* Upgrade Modal */} {upgradeDialogOpen && selectedPlan && availablePlans && ( setUpgradeDialogOpen(false)} title="Confirmar Cambio de Plan" >

¿Estás seguro de que quieres cambiar tu plan de suscripción?

{availablePlans.plans[selectedPlan] && (
Plan actual: {planInfo.name}
Nuevo plan: {availablePlans.plans[selectedPlan].name}
Nuevo precio: {subscriptionService.formatPrice(availablePlans.plans[selectedPlan].monthly_price)}/mes
)}
)}
); }; export default SubscriptionPage;