terminado sign up

This commit is contained in:
2025-10-04 13:09:35 +02:00
parent d9c4f16cf0
commit b66ceee85c
15 changed files with 491 additions and 200 deletions

View File

@ -18,4 +18,12 @@ login.sign-up-button=Crear cuenta
login.sign-up.title=Crear una cuenta
login.sign-up.name=Nombre completo
login.error=Credenciales inválidas
login.error=Credenciales inválidas
login.signup.error.email.exists=El correo electrónico ya está en uso.
login.signup.error.email.exists-archived=El correo pertenece a una cuenta archivada. Por favor, contacta con soporte.
login.signup.error.password.mismatch=Las contraseñas no coinciden.
login.signup.error.review=Por favor, revisa el formulario.
login.signup.error.token.invalid=Enlace inválido o caducado. Solicita uno nuevo.
login.signup.success=Cuenta creada con éxito. Por favor, revisa tu correo para activar tu cuenta.
login.signup.success.verified=¡Cuenta verificada! Ya puedes iniciar sesión.

View File

@ -1,52 +1,79 @@
/* Email base */
/* ===========================
Email base
=========================== */
/* Evita modo oscuro forzado */
:root {
color-scheme: only light;
}
body {
font-family: Arial, Helvetica, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f7fb;
font-family: Arial, Helvetica, sans-serif;
background-color: #f5f7fb !important;
color: #333;
}
/* Contenedor principal */
.email-wrapper {
width: 100%;
background: #f5f7fb;
padding: 20px;
background-color: #f5f7fb !important;
padding: 20px 10px;
}
/* Cuerpo centrado */
.email-body {
width: 100%;
text-align: center;
}
/* Tarjeta del correo */
.email-container {
max-width: 600px;
width: 100%;
background: #ffffff;
margin: 0 auto;
background-color: #ffffff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
/* Encabezado */
.email-header {
background: #f0f0f0;
background-color: #f0f0f0;
padding: 20px;
text-align: center;
}
.email-header img {
display: block;
margin: 0 auto;
}
/* Contenido */
.email-content {
padding: 24px;
font-size: 14px;
line-height: 1.6;
color: #333;
text-align: left;
}
/* Footer */
.email-footer {
background: #f9f9f9;
background-color: #f9f9f9;
padding: 12px;
text-align: center;
font-size: 12px;
color: #777;
}
.email-footer a {
color: #2563eb;
text-decoration: none;
}
/* Botones */
.btn {
display: inline-block;
@ -56,4 +83,10 @@ body {
font-weight: bold;
text-decoration: none;
text-align: center;
background-color: #2563eb;
color: #fff !important;
}
.btn:hover {
opacity: 0.9;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,52 +1,84 @@
<!DOCTYPE html>
<html lang="es" xmlns:th="http://www.thymeleaf.org">
<html lang="es" xmlns:th="http://www.thymeleaf.org" th:fragment="mail(content)">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title th:text="${subject} ?: 'Notificación'">Notificación</title>
<link rel="stylesheet" th:href="@{/css/email.css}">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title th:text="${subject} ?: 'Notificación'">Notificación</title>
<!-- Evitar dark mode agresivo en clientes que lo respetan -->
<meta name="color-scheme" content="light only">
<meta name="supported-color-schemes" content="light">
<!-- Tu CSS incrustado -->
<style th:utext="${emailCss}"></style>
<!-- Hack específico para Gmail iOS (fuerza blanco en la tarjeta/contenido) -->
<style>
/* Gmail iOS: el selector u + .body apunta al cuerpo real que Gmail envuelve */
u + .body .email-container { background: #ffffff !important; }
u + .body .email-content { background: #ffffff !important; color: #333 !important; }
u + .body .email-header { background: #f0f0f0 !important; }
u + .body .email-footer { background: #f9f9f9 !important; }
</style>
</head>
<body>
<table role="presentation" class="email-wrapper">
<tr>
<td align="center" class="email-body">
<table role="presentation" class="email-container">
<!-- Header -->
<tr>
<td class="email-header">
<h2>
<img src="/assets/images/logo-light.png" alt="" height="45">
</h2>
</td>
</tr>
<!-- Añade class="body" para el hack de Gmail iOS -->
<body class="body" style="margin:0; padding:0; background-color:#f5f7fb;" bgcolor="#f5f7fb">
<!-- Content (fragmento) -->
<tr>
<td class="email-content">
<div th:replace="~{::bodyContent}"></div>
</td>
</tr>
<!-- Wrapper 100% -->
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"
class="email-wrapper"
style="width:100%; background-color:#f5f7fb;"
bgcolor="#f5f7fb">
<tr>
<td align="center" class="email-body" style="padding:20px 10px;">
<!-- Footer -->
<tr>
<td class="email-footer">
<p>
<strong th:text="${companyName} ?: 'ImprimeLibros'">ImprimeLibros</strong><br>
Calle José Picón, Nº 28 Local A, 28028, Madrid<br>
91 005 25 74 -
<a href="mailto:contacto@imprimelibros.com" style="color:#2563eb;">
contacto@imprimelibros.com
</a><br>
&copy; <span th:text="${year} ?: ${#dates.year(#dates.createNow())}">2025</span>
</p>
</td>
</tr>
</table>
<!-- Tarjeta blanca (con pixel blanco para evitar inversión en móvil) -->
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0"
class="email-container"
style="max-width:600px; width:100%; background-color:#ffffff; border-radius:8px; overflow:hidden; box-shadow:0 2px 6px rgba(0,0,0,0.08);"
bgcolor="#ffffff"
background="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMB/et0HFAAAAABJRU5ErkJggg==">
<!-- Header -->
<tr>
<td class="email-header" style="background:#f0f0f0; padding:20px; text-align:center;" bgcolor="#f0f0f0">
<h2 style="margin:0;">
<img src="cid:logo" alt="imprimelibros" height="45" style="display:block; margin:0 auto;">
</h2>
</td>
</tr>
</table>
</body>
</tr>
</html>
<!-- Content -->
<tr>
<td class="email-content"
style="padding:24px; font-size:14px; line-height:1.6; color:#333; text-align:left; background:#ffffff;"
bgcolor="#ffffff">
<th:block th:replace="~{${template} :: content}"></th:block>
</td>
</tr>
<!-- Footer -->
<tr>
<td class="email-footer" style="background:#f9f9f9; padding:12px; text-align:center; font-size:12px; color:#777;" bgcolor="#f9f9f9">
<p style="margin:0;">
<strong th:text="${companyName} ?: 'ImprimeLibros'">ImprimeLibros</strong><br>
Calle José Picón, Nº 28 Local A, 28028, Madrid<br>
91 005 25 74 -
<a href="mailto:contacto@imprimelibros.com" style="color:#2563eb; text-decoration:none;">
contacto@imprimelibros.com
</a><br>
&copy; <span th:text="${year} ?: ${#dates.year(#dates.createNow())}">2025</span>
</p>
</td>
</tr>
</table>
<!-- /Tarjeta -->
</td>
</tr>
</table>
<!-- /Wrapper -->
</body>
</html>

View File

@ -1,25 +1,45 @@
<!DOCTYPE html>
<html lang="es" xmlns:th="http://www.thymeleaf.org" th:replace="emails/layout :: bodyContent">
<body>
<p><span th:text="#{email.greeting}">Hola</span> <span th:text="${fullName}">usuario</span>,</p>
<p><span th:text="#{email.verify.body}">Haz clic en el siguiente botón para verificar tu correo electrónico:</span>
<html lang="es" xmlns:th="http://www.thymeleaf.org">
<th:block th:fragment="content">
<p style="margin:0 0 12px; color:#333333 !important;">
<span th:text="#{email.greeting}">Hola</span>
<span th:text="${fullName} ?: 'Usuario'">Usuario</span>,
</p>
<p>
<a th:href="${verifyUrl}" class="btn btn-secondary" target="_blank">
<span th:text="#{email.verify.button}">Verificar cuenta</span>
</a>
<p style="margin:0 0 12px; color:#333333 !important;">
<span th:text="#{email.verify.body}">
Haz clic en el siguiente botón para verificar tu correo electrónico:
</span>
</p>
<p><span th:text="#{email.verify.link-instruction}">Si no funciona, copia y pega esta URL en tu navegador:</span>
<p style="margin:0 0 16px;">
<a th:href="${verifyUrl}"
style="display:inline-block; padding:12px 20px; border-radius:6px; font-weight:bold;
background:#2563eb; color:#ffffff !important; text-decoration:none;">
<span th:text="#{email.verify.button}">Verificar cuenta</span>
</a>
</p>
<p><span th:text="${verifyUrl}">https://...</span></p>
<p><span th:text="#{email.verify.expiration(${minutes} ?: 60)}">Este enlace caduca en 60 minutos.</span></p>
<p><span th:text="#{email.verify.ignoreMessage}">Si no solicitaste este cambio, puedes ignorar este mensaje.</span>
<p style="margin:0 0 8px; color:#333333 !important;">
<span th:text="#{email.verify.link-instruction}">
Si no funciona, copia y pega esta URL en tu navegador:
</span>
</p>
</body>
</html>
<p style="margin:0 0 12px; color:#333333 !important;">
<span th:text="${verifyUrl}">https://...</span>
</p>
<p style="margin:0 0 12px; color:#333333 !important;">
<span th:text="#{email.verify.expiration(${minutes})}">
Este enlace caduca en 60 minutos.
</span>
</p>
<p style="margin:0; color:#333333 !important;">
<span th:text="#{email.verify.ignoreMessage}">
Si no solicitaste este cambio, puedes ignorar este mensaje.
</span>
</p>
</th:block>
</html>

View File

@ -1,63 +1,60 @@
<div th:fragment="_login">
<div class="p-lg-5 p-4">
<div>
<h5 class="text-primary" th:text="#{login.welcome}">¡Bienvenido!</h5>
<p class="text-muted" th:text="#{login.subtitle}">Inicie sesión para continuar:</p>
</div>
<div class="mt-4">
<form th:action="@{/login}" method="post">
<!-- CSRF obligatorio -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div>
<h5 class="text-primary" th:text="#{login.welcome}">¡Bienvenido!</h5>
<p class="text-muted" th:text="#{login.subtitle}">Inicie sesión para continuar:</p>
</div>
<div th:if="${param.error}" class="alert alert-danger"
th:text="#{login.error}">
Credenciales inválidas
<div class="mt-4">
<form th:action="@{/login}" method="post">
<!-- CSRF obligatorio -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div th:if="${param.error}" class="alert alert-danger" th:text="#{login.error}">
Credenciales inválidas
</div>
<div class="mb-3">
<label for="username" class="form-label" th:text="#{login.email}">Correo electrónico</label>
<input type="email" class="form-control" id="username" th:placeholder="#{login.email-placeholder}"
name="username">
</div>
<div class="mb-3">
<div class="float-end">
<a href="/auth-pass-reset-cover" class="text-muted" th:text="#{login.forgotPassword}">¿Olvidó su
contraseña?</a>
</div>
<div class="mb-3">
<label for="username" class="form-label" th:text="#{login.email}">Correo electrónico</label>
<input type="email" class="form-control" id="username" th:placeholder="#{login.email-placeholder}"
name="username">
<label class="form-label" for="password-input" th:text="#{login.password}">Contraseña</label>
<div class="position-relative auth-pass-inputgroup mb-3">
<input type="password" class="form-control pe-5 password-input"
th:placeholder="#{login.password-placeholder}" id="password-input" name="password">
<button
class="btn btn-link position-absolute end-0 top-0 text-decoration-none text-muted password-addon"
type="button" id="password-addon"><i class="ri-eye-fill align-middle"></i></button>
</div>
</div>
<div class="mb-3">
<div class="float-end">
<a href="/auth-pass-reset-cover" class="text-muted" th:text="#{login.forgotPassword}">¿Olvidó su
contraseña?</a>
</div>
<label class="form-label" for="password-input" th:text="#{login.password}">Contraseña</label>
<div class="position-relative auth-pass-inputgroup mb-3">
<input type="password" class="form-control pe-5 password-input"
th:placeholder="#{login.password-placeholder}" id="password-input" name="password">
<button
class="btn btn-link position-absolute end-0 top-0 text-decoration-none text-muted password-addon"
type="button" id="password-addon"><i class="ri-eye-fill align-middle"></i></button>
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="remember-me" name="remember-me">
<label class="form-check-label" for="remember-me" th:text="#{login.rememberMe}">Recuerdame</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="remember-me" name="remember-me">
<label class="form-check-label" for="remember-me" th:text="#{login.rememberMe}">Recuerdame</label>
</div>
<div class="mt-4">
<button class="btn btn-secondary w-100" type="submit" th:text="#{login.login}">Iniciar
Sesión</button>
</div>
<div class="mt-4">
<button class="btn btn-secondary w-100" type="submit" th:text="#{login.login}">Iniciar
Sesión</button>
</div>
</form>
</div>
</form>
</div>
<div class="mt-5 text-center">
<p class="mb-0">
<span th:text="#{login.new-account}">¿No tienes una cuenta?</span>
<a href="/signup" class="fw-semibold text-primary text-decoration-underline"
th:text="#{login.sign-up}">
Regístrate
</a>
</p>
</div>
<div class="mt-5 text-center">
<p class="mb-0">
<span th:text="#{login.new-account}">¿No tienes una cuenta?</span>
<a href="/signup" class="fw-semibold text-primary text-decoration-underline" th:text="#{login.sign-up}">
Regístrate
</a>
</p>
</div>
</div>

View File

@ -1,56 +1,51 @@
<div th:fragment="_signup">
<div class="p-lg-5 p-4">
<div>
<h5 class="text-primary" th:text="#{login.welcome}">¡Bienvenido!</h5>
<p class="text-muted" th:text="#{login.sign-up.title}">Crear cuenta</p>
<div>
<h5 class="text-primary" th:text="#{login.welcome}">¡Bienvenido!</h5>
<p class="text-muted" th:text="#{login.sign-up.title}">Crear cuenta</p>
</div>
<!-- En el caso del formulario de signup, asegúrate de bindear el DTO -->
<form th:if="${form == '_signup'}" th:action="@{/signup}" method="post" th:object="${signupForm}">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div class="mb-3">
<label for="username" class="form-label" th:text="#{login.email}">Correo electrónico</label>
<input type="email" class="form-control" id="username" th:field="*{username}"
th:placeholder="#{login.email-placeholder}">
<div class="text-danger" th:if="${#fields.hasErrors('username')}" th:errors="*{username}"></div>
</div>
<div th:if="${info}" class="alert alert-info" th:text="${info}"></div>
<div th:if="${danger}" class="alert alert-danger" th:text="${danger}"></div>
<div th:if="${signup_error}" class="alert alert-danger" th:text="${signup_error}"></div>
<div class="mb-3">
<label class="form-label" for="name" th:text="#{login.sign-up.name}">Nombre completo</label>
<input type="text" class="form-control" id="name" th:field="*{name}" th:placeholder="#{login.sign-up.name}">
<div class="text-danger" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
</div>
<!-- En el caso del formulario de signup, asegúrate de bindear el DTO -->
<form th:if="${form == '_signup'}" th:action="@{/signup}" method="post" th:object="${signupForm}">
<div class="mb-3">
<label class="form-label" for="password-input" th:text="#{login.password}">Contraseña</label>
<input type="password" class="form-control" id="password-input" th:field="*{password}"
th:placeholder="#{login.password-placeholder}">
<div class="text-danger" th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></div>
</div>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div class="mb-3">
<label for="username" class="form-label" th:text="#{login.email}">Correo electrónico</label>
<input type="email" class="form-control" id="username" th:field="*{username}"
th:placeholder="#{login.email-placeholder}">
<div class="text-danger" th:if="${#fields.hasErrors('username')}" th:errors="*{username}"></div>
<div class="mb-3">
<label class="form-label" for="password-confirm-input" th:text="#{login.confirm-password}">Confirmar
contraseña</label>
<input type="password" class="form-control" id="password-confirm-input" th:field="*{passwordConfirm}"
th:placeholder="#{login.password-placeholder}">
<div class="text-danger" th:if="${#fields.hasErrors('passwordConfirm')}" th:errors="*{passwordConfirm}">
</div>
</div>
<div class="mb-3">
<label class="form-label" for="name" th:text="#{login.sign-up.name}">Nombre completo</label>
<input type="text" class="form-control" id="name" th:field="*{name}"
th:placeholder="#{login.sign-up.name}">
<div class="text-danger" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
</div>
<div class="mb-3">
<label class="form-label" for="password-input" th:text="#{login.password}">Contraseña</label>
<input type="password" class="form-control" id="password-input" th:field="*{password}"
th:placeholder="#{login.password-placeholder}">
<div class="text-danger" th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></div>
</div>
<div class="mb-3">
<label class="form-label" for="password-confirm-input" th:text="#{login.confirm-password}">Confirmar
contraseña</label>
<input type="password" class="form-control" id="password-confirm-input" th:field="*{passwordConfirm}"
th:placeholder="#{login.password-placeholder}">
<div class="text-danger" th:if="${#fields.hasErrors('passwordConfirm')}" th:errors="*{passwordConfirm}">
</div>
</div>
<div class="mt-4">
<button class="btn btn-secondary w-100" type="submit" th:text="#{login.sign-up-button}">Crear
cuenta</button>
</div>
</form>
</div>
<div class="mt-4">
<button class="btn btn-secondary w-100" type="submit" th:text="#{login.sign-up-button}">Crear
cuenta</button>
</div>
</form>
</div>

View File

@ -37,7 +37,15 @@
<!-- end col -->
<div class="col-lg-6">
<div th:insert="~{${'imprimelibros/login/_items/' + form} :: ${form}}"></div>
<div class="p-lg-5 p-4">
<div th:if="${info}" class="alert alert-info" th:text="${info}"></div>
<div th:if="${danger}" class="alert alert-danger" th:text="${danger}"></div>
<div th:if="${signup_error}" class="alert alert-danger"
th:text="${signup_error}"></div>
<div th:insert="~{${'imprimelibros/login/_items/' + form} :: ${form}}"></div>
</div>
</div>
<!-- end col -->