feat(invitations): reenviar invitaciones pendientes desde admin

Backend:
- client-invitations.service.ts: funcion resendInvitation() que
  genera nuevo token, actualiza expiresAt y reenvia el email.
- Controller + routes: POST /invitations/client/:id/resend

Frontend:
- API client + hook useResendInvitation con invalidacion de cache.
- Pagina /admin/invitar-cliente: boton 'Reenviar' por cada
  invitacion pendiente en la tabla.

Refs: docs/CAMBIOS-2026-05-09.md
This commit is contained in:
Horux Dev
2026-05-13 23:19:07 +00:00
parent b3b2838b6d
commit 69bf7417a8
6 changed files with 88 additions and 2 deletions

View File

@@ -2,7 +2,7 @@
import { useState } from 'react';
import { Header } from '@/components/layouts/header';
import { useCreateInvitation, useClientInvitations } from '@/lib/hooks/use-client-invitations';
import { useCreateInvitation, useClientInvitations, useResendInvitation } from '@/lib/hooks/use-client-invitations';
import { Button, Input, Card, CardContent, CardHeader, CardTitle } from '@horux/shared-ui';
import { format } from 'date-fns';
import { es } from 'date-fns/locale';
@@ -14,6 +14,7 @@ export default function InvitarClientePage() {
const [message, setMessage] = useState<{ kind: 'ok' | 'error'; text: string } | null>(null);
const createMut = useCreateInvitation();
const resendMut = useResendInvitation();
const { data: invitations, isLoading } = useClientInvitations();
const handleSubmit = async (e: React.FormEvent) => {
@@ -114,6 +115,7 @@ export default function InvitarClientePage() {
<th className="pb-2 font-medium">Estado</th>
<th className="pb-2 font-medium">Enviada</th>
<th className="pb-2 font-medium">Expira</th>
<th className="pb-2 font-medium"></th>
</tr>
</thead>
<tbody>
@@ -128,6 +130,18 @@ export default function InvitarClientePage() {
<td className="py-2 text-muted-foreground">
{format(new Date(inv.expiresAt), 'dd MMM yyyy', { locale: es })}
</td>
<td className="py-2">
{inv.status === 'pending' && (
<Button
variant="ghost"
size="sm"
onClick={() => resendMut.mutate(inv.id)}
disabled={resendMut.isPending}
>
{resendMut.isPending && resendMut.variables === inv.id ? 'Reenviando...' : 'Reenviar'}
</Button>
)}
</td>
</tr>
))}
</tbody>