feat: add subscription UI, plan-based nav gating, and client subscription page
- Add plan field to UserInfo shared type - Subscription API client and React Query hooks - Client subscription page with status + payment history - Sidebar navigation filtered by tenant plan features - Subscription link added to navigation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
127
apps/web/app/(dashboard)/configuracion/suscripcion/page.tsx
Normal file
127
apps/web/app/(dashboard)/configuracion/suscripcion/page.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
'use client';
|
||||
|
||||
import { Header } from '@/components/layouts/header';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import { useSubscription, usePaymentHistory } from '@/lib/hooks/use-subscription';
|
||||
import { CreditCard, Calendar, CheckCircle, AlertCircle, Clock, XCircle } from 'lucide-react';
|
||||
|
||||
const statusConfig: Record<string, { label: string; color: string; icon: typeof CheckCircle }> = {
|
||||
authorized: { label: 'Activa', color: 'text-green-600 bg-green-50', icon: CheckCircle },
|
||||
pending: { label: 'Pendiente', color: 'text-yellow-600 bg-yellow-50', icon: Clock },
|
||||
paused: { label: 'Pausada', color: 'text-orange-600 bg-orange-50', icon: AlertCircle },
|
||||
cancelled: { label: 'Cancelada', color: 'text-red-600 bg-red-50', icon: XCircle },
|
||||
};
|
||||
|
||||
export default function SuscripcionPage() {
|
||||
const { user } = useAuthStore();
|
||||
const { data: subscription, isLoading } = useSubscription(user?.tenantId);
|
||||
const { data: payments } = usePaymentHistory(user?.tenantId);
|
||||
|
||||
const status = statusConfig[subscription?.status || ''] || statusConfig.pending;
|
||||
const StatusIcon = status.icon;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header title="Suscripción" />
|
||||
<main className="p-6 space-y-6">
|
||||
{/* Subscription Status */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<CreditCard className="h-5 w-5" />
|
||||
Estado de Suscripción
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoading ? (
|
||||
<div className="animate-pulse space-y-4">
|
||||
<div className="h-4 bg-muted rounded w-1/3" />
|
||||
<div className="h-4 bg-muted rounded w-1/2" />
|
||||
</div>
|
||||
) : subscription ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Plan</p>
|
||||
<p className="text-lg font-semibold capitalize">{subscription.plan}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Estado</p>
|
||||
<span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm font-medium ${status.color}`}>
|
||||
<StatusIcon className="h-4 w-4" />
|
||||
{status.label}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Monto Mensual</p>
|
||||
<p className="text-lg font-semibold">
|
||||
${Number(subscription.amount).toLocaleString('es-MX')} MXN
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted-foreground">No se encontró información de suscripción.</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Payment History */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Calendar className="h-5 w-5" />
|
||||
Historial de Pagos
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{payments && payments.length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b">
|
||||
<th className="text-left py-2 px-3 font-medium text-muted-foreground">Fecha</th>
|
||||
<th className="text-left py-2 px-3 font-medium text-muted-foreground">Monto</th>
|
||||
<th className="text-left py-2 px-3 font-medium text-muted-foreground">Estado</th>
|
||||
<th className="text-left py-2 px-3 font-medium text-muted-foreground">Método</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{payments.map((payment) => (
|
||||
<tr key={payment.id} className="border-b last:border-0">
|
||||
<td className="py-2 px-3">
|
||||
{new Date(payment.createdAt).toLocaleDateString('es-MX')}
|
||||
</td>
|
||||
<td className="py-2 px-3">
|
||||
${Number(payment.amount).toLocaleString('es-MX')}
|
||||
</td>
|
||||
<td className="py-2 px-3">
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${
|
||||
payment.status === 'approved'
|
||||
? 'bg-green-50 text-green-700'
|
||||
: payment.status === 'rejected'
|
||||
? 'bg-red-50 text-red-700'
|
||||
: 'bg-yellow-50 text-yellow-700'
|
||||
}`}>
|
||||
{payment.status === 'approved' ? 'Aprobado' :
|
||||
payment.status === 'rejected' ? 'Rechazado' : 'Pendiente'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-2 px-3 text-muted-foreground">
|
||||
{payment.paymentMethod || '-'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted-foreground text-center py-4">
|
||||
No hay pagos registrados aún.
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user