mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-24 09:40:21 +00:00
trabajando en el signup. problema con el mismo username aunque este delete at
This commit is contained in:
@ -27,28 +27,49 @@ public class EmailService {
|
|||||||
this.messageSource = messageSource;
|
this.messageSource = messageSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendPasswordResetMail(String to, String username, String resetLink, Locale locale) throws MessagingException {
|
public void sendPasswordResetMail(String to, String fullName, String resetUrl, Locale locale) {
|
||||||
MimeMessage message = mailSender.createMimeMessage();
|
String subject = messageSource.getMessage("email.resetPassword.title", null, locale);
|
||||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
|
Map<String, Object> variables = Map.of(
|
||||||
|
"fullName", fullName,
|
||||||
|
"resetUrl", resetUrl);
|
||||||
|
sendEmail(to, subject, "imprimelibros/email/reset-password", variables);
|
||||||
|
}
|
||||||
|
|
||||||
helper.setFrom("no-reply@imprimelibros.com");
|
public void sendVerificationEmail(String to, String fullName, String verifyUrl, Locale locale) {
|
||||||
helper.setTo(to);
|
String subject = messageSource.getMessage("email.verify.title", null, locale);
|
||||||
helper.setSubject(messageSource.getMessage("email.resetPassword.title", null, locale));
|
Map<String, Object> variables = Map.of(
|
||||||
|
"fullName", fullName,
|
||||||
|
"verifyUrl", verifyUrl);
|
||||||
|
sendEmail(to, subject, "imprimelibros/email/verify", variables);
|
||||||
|
}
|
||||||
|
|
||||||
// Variables para la plantilla
|
|
||||||
Context context = new Context();
|
|
||||||
context.setVariables(Map.of(
|
|
||||||
"username", username,
|
|
||||||
"resetLink", resetLink,
|
|
||||||
"year", String.valueOf(java.time.Year.now().getValue())
|
|
||||||
));
|
|
||||||
|
|
||||||
// Procesar plantilla HTML
|
// ->>>>>>>> PRIVATE METHODS <<<<<<<<<<<-
|
||||||
String html = templateEngine.process("email/password-reset", context);
|
|
||||||
helper.setText(html, true);
|
|
||||||
|
|
||||||
helper.addInline("companyLogo", new ClassPathResource("static/images/logo-light.png"));
|
/******************
|
||||||
|
* Envía un email usando una plantilla Thymeleaf.
|
||||||
|
* @param to
|
||||||
|
* @param subject
|
||||||
|
* @param template
|
||||||
|
* @param variables
|
||||||
|
**********************************************/
|
||||||
|
|
||||||
mailSender.send(message);
|
private void sendEmail(String to, String subject, String template, Map<String, Object> variables) {
|
||||||
|
try {
|
||||||
|
Context context = new Context();
|
||||||
|
context.setVariables(variables);
|
||||||
|
String html = templateEngine.process(template, context);
|
||||||
|
|
||||||
|
MimeMessage message = mailSender.createMimeMessage();
|
||||||
|
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
|
||||||
|
helper.setFrom("no-reply@imprimelibros.com");
|
||||||
|
helper.setTo(to);
|
||||||
|
helper.setSubject(subject);
|
||||||
|
helper.setText(html, true);
|
||||||
|
|
||||||
|
mailSender.send(message);
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -125,6 +125,8 @@ public class SecurityConfig {
|
|||||||
"/",
|
"/",
|
||||||
"/login",
|
"/login",
|
||||||
"/signup",
|
"/signup",
|
||||||
|
"/verify",
|
||||||
|
"/reset-password",
|
||||||
"/assets/**",
|
"/assets/**",
|
||||||
"/css/**",
|
"/css/**",
|
||||||
"/js/**",
|
"/js/**",
|
||||||
|
|||||||
@ -4,11 +4,26 @@ import java.util.Locale;
|
|||||||
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.login.dto.SignupForm;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class LoginController {
|
public class LoginController {
|
||||||
|
|
||||||
|
private final SignupService signupService;
|
||||||
|
|
||||||
|
public LoginController(SignupService signupService) {
|
||||||
|
this.signupService = signupService;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/login")
|
@GetMapping("/login")
|
||||||
public String index(Model model, Locale locale) {
|
public String index(Model model, Locale locale) {
|
||||||
model.addAttribute("form", "_login");
|
model.addAttribute("form", "_login");
|
||||||
@ -17,9 +32,44 @@ public class LoginController {
|
|||||||
|
|
||||||
@GetMapping("/signup")
|
@GetMapping("/signup")
|
||||||
public String signup(Model model, Locale locale) {
|
public String signup(Model model, Locale locale) {
|
||||||
|
if (!model.containsAttribute("signupForm")) {
|
||||||
|
model.addAttribute("signupForm", new SignupForm());
|
||||||
|
}
|
||||||
model.addAttribute("form", "_signup");
|
model.addAttribute("form", "_signup");
|
||||||
return "imprimelibros/login/login";
|
return "imprimelibros/login/login";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/signup")
|
||||||
|
public String doSignup(@Valid @ModelAttribute("signupForm") SignupForm form,
|
||||||
|
BindingResult br,
|
||||||
|
RedirectAttributes ra,
|
||||||
|
Locale locale) {
|
||||||
|
if (br.hasErrors()) {
|
||||||
|
ra.addFlashAttribute("org.springframework.validation.BindingResult.signupForm", br);
|
||||||
|
ra.addFlashAttribute("signupForm", form);
|
||||||
|
ra.addFlashAttribute("signup_error", "Revisa el formulario");
|
||||||
|
return "redirect:/signup";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
signupService.register(form, locale);
|
||||||
|
ra.addFlashAttribute("info", "Te hemos enviado un email para confirmar tu correo.");
|
||||||
|
return "redirect:/login";
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
ra.addFlashAttribute("signup_error", ex.getMessage());
|
||||||
|
ra.addFlashAttribute("signupForm", form);
|
||||||
|
return "redirect:/signup";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/verify")
|
||||||
|
public String verify(@RequestParam("token") String token, RedirectAttributes ra) {
|
||||||
|
boolean ok = signupService.verify(token);
|
||||||
|
if (ok) {
|
||||||
|
ra.addFlashAttribute("info", "¡Cuenta verificada! Ya puedes iniciar sesión.");
|
||||||
|
} else {
|
||||||
|
ra.addFlashAttribute("danger", "Enlace inválido o caducado. Solicita uno nuevo.");
|
||||||
|
}
|
||||||
|
return "redirect:/login";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package com.imprimelibros.erp.login;
|
package com.imprimelibros.erp.login;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -9,6 +11,8 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
|||||||
|
|
||||||
import com.imprimelibros.erp.common.email.EmailService;
|
import com.imprimelibros.erp.common.email.EmailService;
|
||||||
import com.imprimelibros.erp.login.dto.SignupForm;
|
import com.imprimelibros.erp.login.dto.SignupForm;
|
||||||
|
import com.imprimelibros.erp.users.Role;
|
||||||
|
import com.imprimelibros.erp.users.RoleDao;
|
||||||
import com.imprimelibros.erp.users.User;
|
import com.imprimelibros.erp.users.User;
|
||||||
import com.imprimelibros.erp.users.UserDao;
|
import com.imprimelibros.erp.users.UserDao;
|
||||||
|
|
||||||
@ -18,6 +22,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
|||||||
public class SignupService {
|
public class SignupService {
|
||||||
|
|
||||||
private final UserDao userRepository;
|
private final UserDao userRepository;
|
||||||
|
private final RoleDao roleRepository;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final VerificationTokenRepository tokenRepository;
|
private final VerificationTokenRepository tokenRepository;
|
||||||
private final EmailService emailService;
|
private final EmailService emailService;
|
||||||
@ -26,31 +31,36 @@ public class SignupService {
|
|||||||
private static final long TOKEN_MINUTES = 60;
|
private static final long TOKEN_MINUTES = 60;
|
||||||
|
|
||||||
public SignupService(UserDao userRepository,
|
public SignupService(UserDao userRepository,
|
||||||
PasswordEncoder passwordEncoder,
|
RoleDao roleRepository,
|
||||||
VerificationTokenRepository tokenRepository,
|
PasswordEncoder passwordEncoder,
|
||||||
EmailService emailService) {
|
VerificationTokenRepository tokenRepository,
|
||||||
|
EmailService emailService) {
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
|
this.roleRepository = roleRepository;
|
||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
this.tokenRepository = tokenRepository;
|
this.tokenRepository = tokenRepository;
|
||||||
this.emailService = emailService;
|
this.emailService = emailService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void register(SignupForm form) {
|
public void register(SignupForm form, Locale locale) {
|
||||||
if (!form.getPassword().equals(form.getPasswordConfirm())) {
|
if (!form.getPassword().equals(form.getPasswordConfirm())) {
|
||||||
throw new IllegalArgumentException("Las contraseñas no coinciden");
|
throw new IllegalArgumentException("Las contraseñas no coinciden");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userRepository.existsByUsername(form.getUsername())) {
|
if (userRepository.existsByUserNameIgnoreCase(form.getUsername())) {
|
||||||
throw new IllegalArgumentException("El correo ya está registrado");
|
throw new IllegalArgumentException("El correo ya está registrado");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crear usuario deshabilitado
|
// Crear usuario deshabilitado
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setUsername(form.getUsername().trim().toLowerCase());
|
user.setUserName(form.getUsername().trim().toLowerCase());
|
||||||
|
user.setFullName(form.getName().trim());
|
||||||
user.setPassword(passwordEncoder.encode(form.getPassword()));
|
user.setPassword(passwordEncoder.encode(form.getPassword()));
|
||||||
user.setEnabled(false);
|
user.setEnabled(false);
|
||||||
// TODO: asignar rol por defecto si aplica (e.g., ROLE_USER)
|
var roles = new HashSet<Role>();
|
||||||
|
roles.add(roleRepository.findRoleByName("USER").orElseThrow());
|
||||||
|
user.setRoles(roles);
|
||||||
user = userRepository.save(user);
|
user = userRepository.save(user);
|
||||||
|
|
||||||
// Generar token
|
// Generar token
|
||||||
@ -68,20 +78,23 @@ public class SignupService {
|
|||||||
Map<String, Object> model = new HashMap<>();
|
Map<String, Object> model = new HashMap<>();
|
||||||
model.put("verifyUrl", verifyUrl);
|
model.put("verifyUrl", verifyUrl);
|
||||||
model.put("minutes", TOKEN_MINUTES);
|
model.put("minutes", TOKEN_MINUTES);
|
||||||
emailService.sendTemplate(
|
emailService.sendVerificationEmail(
|
||||||
user.getUsername(),
|
user.getUserName(),
|
||||||
"Confirma tu correo | ImprimeLibros ERP",
|
user.getFullName(),
|
||||||
"mail/verify-email",
|
verifyUrl,
|
||||||
model);
|
locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public boolean verify(String tokenValue) {
|
public boolean verify(String tokenValue) {
|
||||||
var tokenOpt = tokenRepository.findByToken(tokenValue);
|
var tokenOpt = tokenRepository.findByToken(tokenValue);
|
||||||
if (tokenOpt.isEmpty()) return false;
|
if (tokenOpt.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
var token = tokenOpt.get();
|
var token = tokenOpt.get();
|
||||||
if (token.isUsed() || token.isExpired()) return false;
|
if (token.isUsed() || token.isExpired())
|
||||||
|
return false;
|
||||||
|
|
||||||
var user = userRepository.findById(token.getUserId())
|
var user = userRepository.findById(token.getUserId())
|
||||||
.orElseThrow(() -> new IllegalStateException("Usuario no encontrado para el token"));
|
.orElseThrow(() -> new IllegalStateException("Usuario no encontrado para el token"));
|
||||||
|
|||||||
@ -5,18 +5,26 @@ import jakarta.validation.constraints.NotBlank;
|
|||||||
import jakarta.validation.constraints.Size;
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
public class SignupForm {
|
public class SignupForm {
|
||||||
@NotBlank @Email
|
|
||||||
|
@NotBlank(message = "{usuarios.error.correo}")
|
||||||
|
@Email(message = "{usuarios.error.correo.invalido}")
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
@NotBlank @Size(min = 8, message = "La contraseña debe tener al menos 8 caracteres")
|
@NotBlank(message = "{usuarios.error.nombre}")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@NotBlank(message = "{usuarios.error.contraseña}")
|
||||||
|
@Size(min = 6, message = "{usuarios.error.contraseña.tamaño}")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@NotBlank
|
@NotBlank(message = "{usuarios.error.confirmPassword.requerida}")
|
||||||
private String passwordConfirm;
|
private String passwordConfirm;
|
||||||
|
|
||||||
// getters/setters
|
// getters/setters
|
||||||
public String getUsername() { return username; }
|
public String getUsername() { return username; }
|
||||||
public void setUsername(String u) { this.username = u; }
|
public void setUsername(String u) { this.username = u; }
|
||||||
|
public String getName() { return name; }
|
||||||
|
public void setName(String n) { this.name = n; }
|
||||||
public String getPassword() { return password; }
|
public String getPassword() { return password; }
|
||||||
public void setPassword(String p) { this.password = p; }
|
public void setPassword(String p) { this.password = p; }
|
||||||
public String getPasswordConfirm() { return passwordConfirm; }
|
public String getPasswordConfirm() { return passwordConfirm; }
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
email.resetPassword.title=Restablecer contraseña
|
|
||||||
email.greeting=Hola
|
email.greeting=Hola
|
||||||
email.resetPassword.body=Hemos recibido una solicitud para restablecer tu contraseña. Haz clic en el siguiente botón:
|
email.verify.title=Verifica tu correo
|
||||||
email.resetPassword.button=Restablecer contraseña
|
email.verify.body=Haz clic en el siguiente botón para verificar tu correo electrónico:
|
||||||
email.resetPassword.ignoreMessage=Si no solicitaste este cambio, puedes ignorar este mensaje.
|
email.verify.button=Verificar cuenta
|
||||||
|
email.verify.link-instruction=Si no funciona, copia y pega esta URL en tu navegador:
|
||||||
|
email.verify.expiration=Este enlace caduca en {0} minutos.
|
||||||
|
email.verify.ignoreMessage=Si no solicitaste este cambio, puedes ignorar este mensaje.
|
||||||
email.footer=Imprimelibros - Todos los derechos reservados.
|
email.footer=Imprimelibros - Todos los derechos reservados.
|
||||||
|
|||||||
@ -15,5 +15,7 @@ login.password-placeholder=Introduce tu contraseña
|
|||||||
login.new-account=¿No tienes una cuenta?
|
login.new-account=¿No tienes una cuenta?
|
||||||
login.sign-up=Regístrate
|
login.sign-up=Regístrate
|
||||||
login.sign-up-button=Crear cuenta
|
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
|
||||||
@ -1,35 +1,59 @@
|
|||||||
|
/* Email base */
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
background: #f8f9fa;
|
margin: 0;
|
||||||
padding: 20px;
|
padding: 0;
|
||||||
|
background-color: #f5f7fb;
|
||||||
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.email-wrapper {
|
||||||
background: #fff;
|
width: 100%;
|
||||||
border-radius: 8px;
|
background: #f5f7fb;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
max-width: 600px;
|
|
||||||
margin: auto;
|
|
||||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.email-body {
|
||||||
text-align: center;
|
width: 100%;
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.email-container {
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-header {
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-content {
|
||||||
|
padding: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-footer {
|
||||||
|
background: #f9f9f9;
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botones */
|
||||||
.btn {
|
.btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 10px 20px;
|
padding: 12px 20px;
|
||||||
background: #0d6efd;
|
border-radius: 6px;
|
||||||
color: #fff;
|
font-size: 14px;
|
||||||
text-decoration: none;
|
font-weight: bold;
|
||||||
border-radius: 5px;
|
text-decoration: none;
|
||||||
}
|
text-align: center;
|
||||||
|
|
||||||
.footer {
|
|
||||||
margin-top: 20px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #6c757d;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
52
src/main/resources/templates/imprimelibros/email/layout.html
Normal file
52
src/main/resources/templates/imprimelibros/email/layout.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
<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}">
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<!-- Content (fragmento) -->
|
||||||
|
<tr>
|
||||||
|
<td class="email-content">
|
||||||
|
<div th:replace="~{::bodyContent}"></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
|
© <span th:text="${year} ?: ${#dates.year(#dates.createNow())}">2025</span>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
25
src/main/resources/templates/imprimelibros/email/verify.html
Normal file
25
src/main/resources/templates/imprimelibros/email/verify.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!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>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a th:href="${verifyUrl}" class="btn btn-secondary" target="_blank">
|
||||||
|
<span th:text="#{email.verify.button}">Verificar cuenta</span>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><span th:text="#{email.verify.link-instruction}">Si no funciona, copia y pega esta URL en tu navegador:</span>
|
||||||
|
</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>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@ -1,53 +1,56 @@
|
|||||||
<div th:fragment="_signup">
|
<div th:fragment="_signup">
|
||||||
|
|
||||||
<div class="p-lg-5 p-4">
|
<div class="p-lg-5 p-4">
|
||||||
<div>
|
<div>
|
||||||
<h5 class="text-primary" th:text="#{login.welcome}">¡Bienvenido!</h5>
|
<h5 class="text-primary" th:text="#{login.welcome}">¡Bienvenido!</h5>
|
||||||
<p class="text-muted" th:text="#{login.signup-subtitle}">Regístrate para continuar:</p>
|
<p class="text-muted" th:text="#{login.sign-up.title}">Crear cuenta</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div th:if="${info}" class="alert alert-info" th:text="${info}"></div>
|
||||||
<form th:action="@{/signup}" method="post">
|
<div th:if="${danger}" class="alert alert-danger" th:text="${danger}"></div>
|
||||||
<!-- CSRF obligatorio -->
|
<div th:if="${signup_error}" class="alert alert-danger" th:text="${signup_error}"></div>
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
|
||||||
|
|
||||||
<div th:if="${param.error}" class="alert alert-danger"
|
<!-- En el caso del formulario de signup, asegúrate de bindear el DTO -->
|
||||||
th:text="#{login.error}">
|
<form th:if="${form == '_signup'}" th:action="@{/signup}" method="post" th:object="${signupForm}">
|
||||||
Credenciales inválidas
|
|
||||||
|
|
||||||
|
|
||||||
|
<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 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>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mt-4">
|
||||||
<label for="username" class="form-label" th:text="#{login.email}">Correo electrónico</label>
|
<button class="btn btn-secondary w-100" type="submit" th:text="#{login.sign-up-button}">Crear
|
||||||
<input type="email" class="form-control" id="username" th:placeholder="#{login.email-placeholder}"
|
cuenta</button>
|
||||||
name="username">
|
</div>
|
||||||
</div>
|
</form>
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<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">
|
|
||||||
<label class="form-label" for="password-confirm-input" th:text="#{login.confirm-password}">Confirmar 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-confirm-input" name="password-confirm">
|
|
||||||
<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="mt-4">
|
|
||||||
<button class="btn btn-secondary w-100" type="submit" th:text="#{login.sign-up-button}">Crear cuenta</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
Reference in New Issue
Block a user