Files
mercadodevida/project/frontend/src/app/auth/confirm/page.tsx
chattie 18a518e58b F-201 F-202 F-203: POS cash close report + email + PIN admin
F-201: GET /pos/reports/cash-close/:id with financial summary, sales
  by state, payments breakdown, items sold. Extended /pos/sessions/:id.

F-202: Cash close email sent on session close to smtpReportEmail
  (best-effort). smtpReportEmail field added to admin SMTP settings.

F-203: Admin POS terminal config: selfpayMode, closeSessionRequiresPin,
  closeSessionPin (4-6 digits) with dedicated settings section.
2026-08-23 09:24:37 +02:00

75 lines
2.9 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import Link from 'next/link';
export default function ConfirmPage() {
const router = useRouter();
const params = useSearchParams();
const token = params.get('token');
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
const [message, setMessage] = useState('Confirmando tu email…');
useEffect(() => {
if (!token) {
setStatus('error');
setMessage('Falta el token de confirmación.');
return;
}
fetch(`/api/auth/confirm?token=${encodeURIComponent(token)}`)
.then(async (r) => {
const data = await r.json();
if (r.ok) {
setStatus('success');
setMessage(data.message ?? 'Email confirmado.');
setTimeout(() => router.push('/auth/login'), 3000);
} else {
setStatus('error');
setMessage(data.error?.message ?? 'Token inválido o ya confirmado.');
}
})
.catch(() => {
setStatus('error');
setMessage('Error de conexión.');
});
}, [token, router]);
return (
<div className="max-w-md mx-auto px-4 py-16">
<div className="bg-white border border-gray-200 rounded-2xl p-8 shadow-sm text-center">
{status === 'loading' && (
<div className="mb-4">
<svg className="animate-spin mx-auto" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#70ad47" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
</div>
)}
{status === 'success' && (
<div className="mb-4 flex justify-center">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7"/>
</svg>
</div>
)}
{status === 'error' && (
<div className="mb-4 flex justify-center">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#ef4444" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12"/>
</svg>
</div>
)}
<h1 className="text-2xl font-bold text-gray-900 mb-4" style={{ fontFamily: 'var(--font-heading)' }}>
{status === 'success' ? '¡Email confirmado!' : status === 'error' ? 'Error' : 'Confirmando…'}
</h1>
<p className="text-gray-600 mb-6">{message}</p>
{status === 'success' && (
<p className="text-sm text-gray-400 mb-4">Redirigiendo al login</p>
)}
<Link href="/auth/login" className="text-[#70ad47] hover:underline text-sm">
Ir a iniciar sesión
</Link>
</div>
</div>
);
}