- login.html: corregir link roto /password/reset → /request-password-reset
- request-password-reset.html: reescribir — pide email, AJAX, diseño de la app
- RequestPasswordResetPost: buscar por email (GetUserByEmail), no revela si
el correo existe o no (misma respuesta siempre — seguridad)
- password-reset.html: reescribir — eliminar campo nombre_usuario redundante,
validación de contraseñas en cliente, diseño consistente con el resto del app
- PasswordResetPost: usar c.Locals("email") del token (ya validado por
middleware) en vez de nombre_usuario del form
- password_reset.go middleware: ampliar expiración de token de 5 min a 1 hora
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
101 lines
6.2 KiB
HTML
Executable File
101 lines
6.2 KiB
HTML
Executable File
<div class="w-full max-w-sm" x-data="{
|
|
email: '',
|
|
loading: false,
|
|
sent: false,
|
|
error: '',
|
|
async submit() {
|
|
this.error = '';
|
|
if (!this.email.trim()) { this.error = 'Ingresa tu correo electrónico.'; return; }
|
|
this.loading = true;
|
|
try {
|
|
const res = await fetch('/do/reset-password', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: 'email=' + encodeURIComponent(this.email),
|
|
});
|
|
const d = await res.json();
|
|
if (d.success) { this.sent = true; }
|
|
else { this.error = d.message || 'No se pudo procesar la solicitud.'; }
|
|
} catch (e) {
|
|
this.error = 'Error de conexión. Intenta de nuevo.';
|
|
}
|
|
this.loading = false;
|
|
}
|
|
}">
|
|
<!-- Logo -->
|
|
<div class="mb-8 text-center">
|
|
<div class="inline-flex items-center justify-center w-14 h-14 rounded-2xl mb-4" style="background-color:#1e293b">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12a4.5 4.5 0 11-9 0 4.5 4.5 0 019 0zm0 0c0 1.657 1.007 3 2.25 3S21 13.657 21 12a9 9 0 10-2.636 6.364M16.5 12V8.25"/>
|
|
</svg>
|
|
</div>
|
|
<h1 class="text-2xl font-bold text-slate-800">Recuperar contraseña</h1>
|
|
<p class="text-sm text-slate-500 mt-1">Te enviaremos un enlace a tu correo</p>
|
|
</div>
|
|
|
|
<!-- Enviado -->
|
|
<div x-show="sent" class="text-center space-y-4">
|
|
<div class="flex items-center justify-center w-16 h-16 rounded-full bg-green-50 border-2 border-green-200 mx-auto">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/>
|
|
</svg>
|
|
</div>
|
|
<p class="text-slate-700 font-medium">¡Revisa tu correo!</p>
|
|
<p class="text-sm text-slate-500">Si el correo está registrado, recibirás instrucciones para restablecer tu contraseña en los próximos minutos.</p>
|
|
<a href="/login" class="inline-block mt-4 text-sm font-medium hover:underline" style="color:#8eb02f">Volver al inicio de sesión</a>
|
|
</div>
|
|
|
|
<!-- Formulario -->
|
|
<div x-show="!sent">
|
|
<!-- Error -->
|
|
<div x-show="error" class="mb-5 flex items-start gap-3 rounded-xl px-4 py-3" style="background:rgba(239,68,68,0.08); border:1px solid rgba(239,68,68,0.3)">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 flex-shrink-0 mt-0.5" style="color:#ef4444" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/>
|
|
</svg>
|
|
<span class="text-sm" style="color:#ef4444" x-text="error"></span>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 mb-1.5" for="email">Correo electrónico</label>
|
|
<div class="relative">
|
|
<div class="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"/>
|
|
</svg>
|
|
</div>
|
|
<input type="email" id="email" x-model="email" @keydown.enter="submit()"
|
|
placeholder="tu@correo.com" autocomplete="email"
|
|
class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl text-sm outline-none bg-white text-slate-800 focus:border-[#8eb02f] transition-all"
|
|
style="box-shadow:none"
|
|
onfocus="this.style.boxShadow='0 0 0 3px rgba(142,176,47,0.15)'; this.style.borderColor='#8eb02f'"
|
|
onblur="this.style.boxShadow='none'; this.style.borderColor='#e2e8f0'">
|
|
</div>
|
|
</div>
|
|
|
|
<button @click="submit()" :disabled="loading"
|
|
class="w-full py-2.5 rounded-xl text-sm font-semibold text-white transition-all mt-2"
|
|
:class="loading ? 'opacity-70 cursor-not-allowed' : 'hover:opacity-90'"
|
|
style="background-color:#8eb02f">
|
|
<span x-show="!loading" class="flex items-center justify-center gap-2">
|
|
Enviar enlace de recuperación
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"/>
|
|
</svg>
|
|
</span>
|
|
<span x-show="loading" class="flex items-center justify-center gap-2">
|
|
<svg class="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
|
|
</svg>
|
|
Enviando...
|
|
</span>
|
|
</button>
|
|
|
|
<div class="text-center pt-1">
|
|
<a href="/login" class="text-xs text-slate-500 hover:underline">← Volver al inicio de sesión</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|