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:
@@ -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>
|
||||
|
||||
@@ -52,6 +52,11 @@ export async function registerFromInvitation(
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function resendInvitation(id: string): Promise<{ message: string }> {
|
||||
const res = await apiClient.post(`/invitations/client/${id}/resend`);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function getClientInvitations(): Promise<ClientInvitation[]> {
|
||||
const res = await apiClient.get('/invitations/client');
|
||||
return res.data;
|
||||
|
||||
@@ -25,6 +25,14 @@ export function useRegisterFromInvitation() {
|
||||
});
|
||||
}
|
||||
|
||||
export function useResendInvitation() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: api.resendInvitation,
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['client-invitations'] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useClientInvitations() {
|
||||
return useQuery({
|
||||
queryKey: ['client-invitations'],
|
||||
|
||||
Reference in New Issue
Block a user