/**
* 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 */}
{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 */}
{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 */}
{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 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;