Add user delete process

This commit is contained in:
Urtzi Alfaro
2025-10-31 11:54:19 +01:00
parent 63f5c6d512
commit 269d3b5032
74 changed files with 16783 additions and 213 deletions

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Crown, Users, MapPin, Package, TrendingUp, RefreshCw, AlertCircle, CheckCircle, ArrowRight, Star, ExternalLink, Download, CreditCard, X, Activity, Database, Zap, HardDrive, ShoppingCart, ChefHat } from 'lucide-react';
import { Crown, Users, MapPin, Package, TrendingUp, RefreshCw, AlertCircle, CheckCircle, ArrowRight, Star, ExternalLink, Download, CreditCard, X, Activity, Database, Zap, HardDrive, ShoppingCart, ChefHat, Settings } from 'lucide-react';
import { Button, Card, Badge, Modal } from '../../../../components/ui';
import { DialogModal } from '../../../../components/ui/DialogModal/DialogModal';
import { PageHeader } from '../../../../components/layout';
@@ -25,10 +25,12 @@ const SubscriptionPage: React.FC = () => {
const [cancelling, setCancelling] = useState(false);
const [invoices, setInvoices] = useState<any[]>([]);
const [invoicesLoading, setInvoicesLoading] = useState(false);
const [invoicesLoaded, setInvoicesLoaded] = useState(false);
// Load subscription data on component mount
React.useEffect(() => {
loadSubscriptionData();
loadInvoices();
}, []);
const loadSubscriptionData = async () => {
@@ -220,33 +222,33 @@ const SubscriptionPage: React.FC = () => {
const tenantId = currentTenant?.id || user?.tenant_id;
if (!tenantId) {
showToast.error('No se encontró información del tenant');
return;
}
try {
setInvoicesLoading(true);
// In a real implementation, this would call an API endpoint to get invoices
// const invoices = await subscriptionService.getInvoices(tenantId);
// For now, we'll simulate some invoices
setInvoices([
{ id: 'inv_001', date: '2023-10-01', amount: 49.00, status: 'paid', description: 'Plan Starter Mensual' },
{ id: 'inv_002', date: '2023-09-01', amount: 49.00, status: 'paid', description: 'Plan Starter Mensual' },
{ id: 'inv_003', date: '2023-08-01', amount: 49.00, status: 'paid', description: 'Plan Starter Mensual' },
]);
const fetchedInvoices = await subscriptionService.getInvoices(tenantId);
setInvoices(fetchedInvoices);
setInvoicesLoaded(true);
} catch (error) {
console.error('Error loading invoices:', error);
showToast.error('Error al cargar las facturas');
// Don't show error toast on initial load, just log it
if (invoicesLoaded) {
showToast.error('Error al cargar las facturas');
}
} finally {
setInvoicesLoading(false);
}
};
const handleDownloadInvoice = (invoiceId: string) => {
// In a real implementation, this would download the actual invoice
console.log(`Downloading invoice: ${invoiceId}`);
showToast.info(`Descargando factura ${invoiceId}`);
const handleDownloadInvoice = (invoice: any) => {
if (invoice.invoice_pdf) {
window.open(invoice.invoice_pdf, '_blank');
} else if (invoice.hosted_invoice_url) {
window.open(invoice.hosted_invoice_url, '_blank');
} else {
showToast.warning('No hay PDF disponible para esta factura');
}
};
const ProgressBar: React.FC<{ value: number; className?: string }> = ({ value, className = '' }) => {
@@ -303,7 +305,7 @@ const SubscriptionPage: React.FC = () => {
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] flex items-center">
<Crown className="w-5 h-5 mr-2 text-yellow-500" />
Plan Actual: {usageSummary.plan}
Plan Actual
</h3>
<Badge
variant={usageSummary.status === 'active' ? 'success' : 'default'}
@@ -313,52 +315,35 @@ const SubscriptionPage: React.FC = () => {
</Badge>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg">
<div className="flex items-center justify-between">
<span className="text-sm text-[var(--text-secondary)]">Precio Mensual</span>
<span className="font-semibold text-[var(--text-primary)]">{subscriptionService.formatPrice(usageSummary.monthly_price)}</span>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg border border-[var(--border-secondary)]">
<div className="flex flex-col">
<span className="text-sm text-[var(--text-secondary)] mb-1">Plan</span>
<span className="font-semibold text-[var(--text-primary)] text-lg capitalize">{usageSummary.plan}</span>
</div>
</div>
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg">
<div className="flex items-center justify-between">
<span className="text-sm text-[var(--text-secondary)]">Próxima Facturación</span>
<span className="font-medium text-[var(--text-primary)]">
{new Date(usageSummary.next_billing_date).toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit' })}
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg border border-[var(--border-secondary)]">
<div className="flex flex-col">
<span className="text-sm text-[var(--text-secondary)] mb-1">Precio Mensual</span>
<span className="font-semibold text-[var(--text-primary)] text-lg">{subscriptionService.formatPrice(usageSummary.monthly_price)}</span>
</div>
</div>
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg border border-[var(--border-secondary)]">
<div className="flex flex-col">
<span className="text-sm text-[var(--text-secondary)] mb-1">Próxima Facturación</span>
<span className="font-medium text-[var(--text-primary)] text-lg">
{new Date(usageSummary.next_billing_date).toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit', year: 'numeric' })}
</span>
</div>
</div>
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg">
<div className="flex items-center justify-between">
<span className="text-sm text-[var(--text-secondary)]">Usuarios</span>
<span className="font-medium text-[var(--text-primary)]">
{usageSummary.usage.users.current}/{usageSummary.usage.users.unlimited ? '∞' : usageSummary.usage.users.limit ?? 0}
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg border border-[var(--border-secondary)]">
<div className="flex flex-col">
<span className="text-sm text-[var(--text-secondary)] mb-1">Ciclo de Facturación</span>
<span className="font-medium text-[var(--text-primary)] text-lg capitalize">
{usageSummary.billing_cycle === 'monthly' ? 'Mensual' : 'Anual'}
</span>
</div>
</div>
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg">
<div className="flex items-center justify-between">
<span className="text-sm text-[var(--text-secondary)]">Ubicaciones</span>
<span className="font-medium text-[var(--text-primary)]">
{usageSummary.usage.locations.current}/{usageSummary.usage.locations.unlimited ? '∞' : usageSummary.usage.locations.limit ?? 0}
</span>
</div>
</div>
</div>
<div className="flex flex-wrap gap-2">
<Button variant="outline" onClick={() => window.open('https://billing.bakery.com', '_blank')} className="flex items-center gap-2">
<ExternalLink className="w-4 h-4" />
Portal de Facturación
</Button>
<Button variant="outline" onClick={() => console.log('Download invoice')} className="flex items-center gap-2">
<Download className="w-4 h-4" />
Descargar Facturas
</Button>
<Button variant="outline" onClick={loadSubscriptionData} className="flex items-center gap-2">
<RefreshCw className="w-4 h-4" />
Actualizar
</Button>
</div>
</Card>
@@ -584,7 +569,7 @@ const SubscriptionPage: React.FC = () => {
</Card>
{/* Available Plans */}
<div>
<Card className="p-6">
<h3 className="text-lg font-semibold mb-6 text-[var(--text-primary)] flex items-center">
<Crown className="w-5 h-5 mr-2 text-yellow-500" />
Planes Disponibles
@@ -595,7 +580,7 @@ const SubscriptionPage: React.FC = () => {
onPlanSelect={handleUpgradeClick}
showPilotBanner={false}
/>
</div>
</Card>
{/* Invoices Section */}
<Card className="p-6">
@@ -604,18 +589,9 @@ const SubscriptionPage: React.FC = () => {
<Download className="w-5 h-5 mr-2 text-blue-500" />
Historial de Facturas
</h3>
<Button
variant="outline"
onClick={loadInvoices}
disabled={invoicesLoading}
className="flex items-center gap-2"
>
<RefreshCw className={`w-4 h-4 ${invoicesLoading ? 'animate-spin' : ''}`} />
{invoicesLoading ? 'Cargando...' : 'Actualizar'}
</Button>
</div>
{invoicesLoading ? (
{invoicesLoading && !invoicesLoaded ? (
<div className="flex items-center justify-center py-8">
<div className="flex flex-col items-center gap-4">
<RefreshCw className="w-8 h-8 animate-spin text-[var(--color-primary)]" />
@@ -624,42 +600,57 @@ const SubscriptionPage: React.FC = () => {
</div>
) : invoices.length === 0 ? (
<div className="text-center py-8">
<p className="text-[var(--text-secondary)]">No hay facturas disponibles</p>
<div className="flex flex-col items-center gap-3">
<div className="p-4 bg-[var(--bg-secondary)] rounded-full">
<Download className="w-8 h-8 text-[var(--text-tertiary)]" />
</div>
<p className="text-[var(--text-secondary)]">No hay facturas disponibles</p>
<p className="text-sm text-[var(--text-tertiary)]">Las facturas aparecerán aquí una vez realizados los pagos</p>
</div>
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-[var(--border-color)]">
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium">ID</th>
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium">Fecha</th>
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium">Descripción</th>
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium">Monto</th>
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium">Estado</th>
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium">Acciones</th>
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium text-sm">Fecha</th>
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium text-sm">Descripción</th>
<th className="text-right py-3 px-4 text-[var(--text-secondary)] font-medium text-sm">Monto</th>
<th className="text-center py-3 px-4 text-[var(--text-secondary)] font-medium text-sm">Estado</th>
<th className="text-center py-3 px-4 text-[var(--text-secondary)] font-medium text-sm">Acciones</th>
</tr>
</thead>
<tbody>
{invoices.map((invoice) => (
<tr key={invoice.id} className="border-b border-[var(--border-color)] hover:bg-[var(--bg-secondary)]">
<td className="py-3 px-4 text-[var(--text-primary)]">{invoice.id}</td>
<td className="py-3 px-4 text-[var(--text-primary)]">{invoice.date}</td>
<td className="py-3 px-4 text-[var(--text-primary)]">{invoice.description}</td>
<td className="py-3 px-4 text-[var(--text-primary)]">{subscriptionService.formatPrice(invoice.amount)}</td>
<td className="py-3 px-4">
<Badge variant={invoice.status === 'paid' ? 'success' : 'default'}>
{invoice.status === 'paid' ? 'Pagada' : 'Pendiente'}
<tr key={invoice.id} className="border-b border-[var(--border-color)] hover:bg-[var(--bg-secondary)] transition-colors">
<td className="py-3 px-4 text-[var(--text-primary)] font-medium">
{new Date(invoice.date).toLocaleDateString('es-ES', {
day: '2-digit',
month: 'short',
year: 'numeric'
})}
</td>
<td className="py-3 px-4 text-[var(--text-primary)]">
{invoice.description || 'Suscripción'}
</td>
<td className="py-3 px-4 text-[var(--text-primary)] font-semibold text-right">
{subscriptionService.formatPrice(invoice.amount)}
</td>
<td className="py-3 px-4 text-center">
<Badge variant={invoice.status === 'paid' ? 'success' : invoice.status === 'open' ? 'warning' : 'default'}>
{invoice.status === 'paid' ? 'Pagada' : invoice.status === 'open' ? 'Pendiente' : invoice.status}
</Badge>
</td>
<td className="py-3 px-4">
<td className="py-3 px-4 text-center">
<Button
variant="outline"
size="sm"
onClick={() => handleDownloadInvoice(invoice.id)}
className="flex items-center gap-2"
onClick={() => handleDownloadInvoice(invoice)}
disabled={!invoice.invoice_pdf && !invoice.hosted_invoice_url}
className="flex items-center gap-2 mx-auto"
>
<Download className="w-4 h-4" />
Descargar
{invoice.invoice_pdf ? 'PDF' : 'Ver'}
</Button>
</td>
</tr>
@@ -673,38 +664,73 @@ const SubscriptionPage: React.FC = () => {
{/* Subscription Management */}
<Card className="p-6">
<h3 className="text-lg font-semibold mb-6 text-[var(--text-primary)] flex items-center">
<CreditCard className="w-5 h-5 mr-2 text-red-500" />
<Settings className="w-5 h-5 mr-2 text-purple-500" />
Gestión de Suscripción
</h3>
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<h4 className="font-medium text-[var(--text-primary)] mb-2">Cancelar Suscripción</h4>
<p className="text-sm text-[var(--text-secondary)] mb-4">
Si cancelas tu suscripción, perderás acceso a las funcionalidades premium al final del período de facturación actual.
</p>
<Button
variant="danger"
onClick={handleCancellationClick}
className="flex items-center gap-2"
>
<X className="w-4 h-4" />
Cancelar Suscripción
</Button>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Payment Method Card */}
<div className="p-6 bg-[var(--bg-secondary)] rounded-lg border border-[var(--border-secondary)] hover:border-[var(--color-primary)]/30 transition-colors">
<div className="flex items-start gap-4">
<div className="p-3 bg-blue-500/10 rounded-lg border border-blue-500/20 flex-shrink-0">
<CreditCard className="w-6 h-6 text-blue-500" />
</div>
<div className="flex-1">
<h4 className="font-semibold text-[var(--text-primary)] mb-2">Método de Pago</h4>
<p className="text-sm text-[var(--text-secondary)] mb-4 leading-relaxed">
Actualiza tu información de pago para asegurar la continuidad de tu servicio sin interrupciones.
</p>
<Button
variant="outline"
className="flex items-center gap-2 w-full sm:w-auto"
onClick={() => showToast.info('Función disponible próximamente')}
>
<CreditCard className="w-4 h-4" />
Actualizar Método de Pago
</Button>
</div>
</div>
</div>
<div className="flex-1">
<h4 className="font-medium text-[var(--text-primary)] mb-2">Método de Pago</h4>
<p className="text-sm text-[var(--text-secondary)] mb-4">
Actualiza tu información de pago para asegurar la continuidad de tu servicio.
</p>
<Button
variant="outline"
className="flex items-center gap-2"
>
<CreditCard className="w-4 h-4" />
Actualizar Método de Pago
</Button>
{/* Cancel Subscription Card */}
<div className="p-6 bg-[var(--bg-secondary)] rounded-lg border border-[var(--border-secondary)] hover:border-red-500/30 transition-colors">
<div className="flex items-start gap-4">
<div className="p-3 bg-red-500/10 rounded-lg border border-red-500/20 flex-shrink-0">
<AlertCircle className="w-6 h-6 text-red-500" />
</div>
<div className="flex-1">
<h4 className="font-semibold text-[var(--text-primary)] mb-2">Cancelar Suscripción</h4>
<p className="text-sm text-[var(--text-secondary)] mb-4 leading-relaxed">
Si cancelas, mantendrás acceso de solo lectura hasta el final de tu período de facturación actual.
</p>
<Button
variant="danger"
onClick={handleCancellationClick}
className="flex items-center gap-2 w-full sm:w-auto"
>
<X className="w-4 h-4" />
Cancelar Suscripción
</Button>
</div>
</div>
</div>
</div>
{/* Additional Info */}
<div className="mt-6 p-4 bg-blue-500/5 border border-blue-500/20 rounded-lg">
<div className="flex items-start gap-3">
<Activity className="w-5 h-5 text-blue-500 flex-shrink-0 mt-0.5" />
<div>
<p className="text-sm text-[var(--text-primary)] font-medium mb-1">
¿Necesitas ayuda?
</p>
<p className="text-sm text-[var(--text-secondary)]">
Si tienes preguntas sobre tu suscripción o necesitas asistencia, contacta a nuestro equipo de soporte en{' '}
<a href="mailto:support@bakery-ia.com" className="text-blue-500 hover:underline">
support@bakery-ia.com
</a>
</p>
</div>
</div>
</div>
</Card>