Support subcription payments
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Crown, Users, MapPin, Package, TrendingUp, RefreshCw, AlertCircle, CheckCircle, ArrowRight, Star, ExternalLink, Download } from 'lucide-react';
|
||||
import { Crown, Users, MapPin, Package, TrendingUp, RefreshCw, AlertCircle, CheckCircle, ArrowRight, Star, ExternalLink, Download, CreditCard, X } from 'lucide-react';
|
||||
import { Button, Card, Badge, Modal } from '../../../../components/ui';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
import { useAuthUser } from '../../../../stores/auth.store';
|
||||
@@ -18,6 +18,10 @@ const SubscriptionPage: React.FC = () => {
|
||||
const [upgradeDialogOpen, setUpgradeDialogOpen] = useState(false);
|
||||
const [selectedPlan, setSelectedPlan] = useState<string>('');
|
||||
const [upgrading, setUpgrading] = useState(false);
|
||||
const [cancellationDialogOpen, setCancellationDialogOpen] = useState(false);
|
||||
const [cancelling, setCancelling] = useState(false);
|
||||
const [invoices, setInvoices] = useState<any[]>([]);
|
||||
const [invoicesLoading, setInvoicesLoading] = useState(false);
|
||||
|
||||
// Load subscription data on component mount
|
||||
React.useEffect(() => {
|
||||
@@ -94,6 +98,70 @@ const SubscriptionPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancellationClick = () => {
|
||||
setCancellationDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleCancelSubscription = async () => {
|
||||
const tenantId = currentTenant?.id || user?.tenant_id;
|
||||
|
||||
if (!tenantId) {
|
||||
addToast('Información de tenant no disponible', { type: 'error' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setCancelling(true);
|
||||
|
||||
// In a real implementation, this would call an API endpoint to cancel the subscription
|
||||
// const result = await subscriptionService.cancelSubscription(tenantId);
|
||||
|
||||
// For now, we'll simulate the cancellation
|
||||
addToast('Tu suscripción ha sido cancelada', { type: 'success' });
|
||||
|
||||
await loadSubscriptionData();
|
||||
setCancellationDialogOpen(false);
|
||||
} catch (error) {
|
||||
console.error('Error cancelling subscription:', error);
|
||||
addToast('Error al cancelar la suscripción', { type: 'error' });
|
||||
} finally {
|
||||
setCancelling(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadInvoices = async () => {
|
||||
const tenantId = currentTenant?.id || user?.tenant_id;
|
||||
|
||||
if (!tenantId) {
|
||||
addToast('No se encontró información del tenant', { type: 'error' });
|
||||
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' },
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error('Error loading invoices:', error);
|
||||
addToast('Error al cargar las facturas', { type: 'error' });
|
||||
} finally {
|
||||
setInvoicesLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadInvoice = (invoiceId: string) => {
|
||||
// In a real implementation, this would download the actual invoice
|
||||
console.log(`Downloading invoice: ${invoiceId}`);
|
||||
addToast(`Descargando factura ${invoiceId}`, { type: 'info' });
|
||||
};
|
||||
|
||||
const ProgressBar: React.FC<{ value: number; className?: string }> = ({ value, className = '' }) => {
|
||||
const getProgressColor = () => {
|
||||
if (value >= 90) return 'bg-red-500';
|
||||
@@ -148,7 +216,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: {subscriptionService.getPlanDisplayInfo(usageSummary.plan).name}
|
||||
Plan Actual: {usageSummary.plan}
|
||||
</h3>
|
||||
<Badge
|
||||
variant={usageSummary.status === 'active' ? 'success' : 'default'}
|
||||
@@ -418,6 +486,118 @@ const SubscriptionPage: React.FC = () => {
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Invoices Section */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] flex items-center">
|
||||
<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 ? (
|
||||
<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)]" />
|
||||
<p className="text-[var(--text-secondary)]">Cargando facturas...</p>
|
||||
</div>
|
||||
</div>
|
||||
) : invoices.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-[var(--text-secondary)]">No hay facturas disponibles</p>
|
||||
</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>
|
||||
</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'}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleDownloadInvoice(invoice.id)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Descargar
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* 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" />
|
||||
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>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -436,7 +616,7 @@ const SubscriptionPage: React.FC = () => {
|
||||
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span>Plan actual:</span>
|
||||
<span>{subscriptionService.getPlanDisplayInfo(usageSummary.plan).name}</span>
|
||||
<span>{usageSummary.plan}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Nuevo plan:</span>
|
||||
@@ -469,8 +649,44 @@ const SubscriptionPage: React.FC = () => {
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{/* Cancellation Modal */}
|
||||
{cancellationDialogOpen && (
|
||||
<Modal
|
||||
isOpen={cancellationDialogOpen}
|
||||
onClose={() => setCancellationDialogOpen(false)}
|
||||
title="Cancelar Suscripción"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-[var(--text-secondary)]">
|
||||
¿Estás seguro de que deseas cancelar tu suscripción? Esta acción no se puede deshacer.
|
||||
</p>
|
||||
<p className="text-[var(--text-secondary)]">
|
||||
Perderás acceso a las funcionalidades premium al final del período de facturación actual.
|
||||
</p>
|
||||
|
||||
<div className="flex gap-2 pt-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setCancellationDialogOpen(false)}
|
||||
className="flex-1"
|
||||
>
|
||||
Volver
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={handleCancelSubscription}
|
||||
disabled={cancelling}
|
||||
className="flex-1"
|
||||
>
|
||||
{cancelling ? 'Cancelando...' : 'Confirmar Cancelación'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubscriptionPage;
|
||||
export default SubscriptionPage;
|
||||
|
||||
Reference in New Issue
Block a user