añadido gestion de usuarios

This commit is contained in:
Jaime Jiménez
2025-06-17 14:48:57 +02:00
parent 7709138428
commit f5b2c0b509
23 changed files with 673 additions and 31 deletions

View File

@ -67,6 +67,12 @@
<artifactId>spring-boot-starter-data-jpa</artifactId> <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> </dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId> <artifactId>mysql-connector-j</artifactId>

View File

@ -0,0 +1,15 @@
package com.printhub.printhub.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class PasswordEncoderConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,50 @@
package com.printhub.printhub.config;
import com.printhub.printhub.service.configuration.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.*;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private UserService userService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/register", "/assets/**", "/api/lang", "/api/lang/**").permitAll()
.anyRequest().authenticated())
.userDetailsService(userService)
.formLogin(login -> login
.loginPage("/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/", true)
.failureUrl("/login?error=true")
.permitAll())
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessHandler((request, response, authentication) -> {
String lang = request.getParameter("lang");
if (lang == null || lang.isBlank()) lang = "es";
response.sendRedirect("/login?logout&lang=" + lang);
}));
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig)
throws Exception {
return authConfig.getAuthenticationManager();
}
}

View File

@ -0,0 +1,25 @@
package com.printhub.printhub.controller.config;
import com.printhub.printhub.entity.configuration.User;
import com.printhub.printhub.repository.configuration.UserRepository;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.beans.factory.annotation.Autowired;
@ControllerAdvice
public class CurrentUserAdvice {
@Autowired
private UserRepository userRepository;
@ModelAttribute("currentUser")
public User getCurrentUser() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails userDetails) {
return userRepository.findByUsername(userDetails.getUsername()).orElse(null);
}
return null;
}
}

View File

@ -0,0 +1,48 @@
package com.printhub.printhub.controller.configuration;
import com.printhub.printhub.service.configuration.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Controller;
import com.printhub.printhub.entity.configuration.User;
import com.printhub.printhub.repository.configuration.UserRepository;
@Controller
public class AuthController {
private UserService userService;
private UserRepository userRepository;
@Autowired
public AuthController(UserRepository userRepository, UserService userService) {
this.userRepository = userRepository;
this.userService = userService;
}
@PostMapping("/register")
public String register(@RequestParam String username,
@RequestParam String password,
@RequestParam(defaultValue = "USER") String role) {
userService.register(username, password, role);
return "Usuario registrado correctamente.";
}
@GetMapping("/login")
public String login() {
return "printhub/login";
}
@ModelAttribute("currentUser")
public User currentUser() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof org.springframework.security.core.userdetails.User springUser) {
return userRepository.findByUsername(springUser.getUsername())
.orElse(null);
}
return null;
}
}

View File

@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import com.printhub.printhub.model.Breadcrum; import com.printhub.printhub.helper.Breadcrum;
import com.printhub.printhub.service.configuration.PrinterService; import com.printhub.printhub.service.configuration.PrinterService;
import com.printhub.printhub.utils.datatables.DataTableRequest; import com.printhub.printhub.utils.datatables.DataTableRequest;

View File

@ -0,0 +1,68 @@
package com.printhub.printhub.entity.configuration;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String alias;
private String cif;
private String address;
private String city;
private String zipCode;
private String country;
private String phone;
private String email;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
// Getters y Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getAlias() { return alias; }
public void setAlias(String alias) { this.alias = alias; }
public String getCif() { return cif; }
public void setCif(String cif) { this.cif = cif; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getZipCode() { return zipCode; }
public void setZipCode(String zipCode) { this.zipCode = zipCode; }
public String getCountry() { return country; }
public void setCountry(String country) { this.country = country; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
public LocalDateTime getDeletedAt() { return deletedAt; }
public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
}

View File

@ -1,4 +1,4 @@
package com.printhub.printhub.model.configuration; package com.printhub.printhub.entity.configuration;
import jakarta.persistence.*; import jakarta.persistence.*;

View File

@ -0,0 +1,96 @@
package com.printhub.printhub.entity.configuration;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String role; // Ej: ROLE_USER, ROLE_ADMIN
@Column(nullable = false)
private boolean enabled = true;
// Campos adicionales
private String firstName;
private String lastName;
@Column(columnDefinition = "TEXT")
private String comments;
private LocalDateTime lastActive;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;
// Getters y Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getComments() { return comments; }
public void setComments(String comments) { this.comments = comments; }
public LocalDateTime getLastActive() { return lastActive; }
public void setLastActive(LocalDateTime lastActive) { this.lastActive = lastActive; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
public LocalDateTime getDeletedAt() { return deletedAt; }
public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
public Customer getCustomer() { return customer; }
public void setCustomer(Customer customer) { this.customer = customer; }
}

View File

@ -1,4 +1,4 @@
package com.printhub.printhub.model; package com.printhub.printhub.helper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -1,9 +1,10 @@
package com.printhub.printhub.repository.configuration; package com.printhub.printhub.repository.configuration;
import com.printhub.printhub.model.configuration.Printer;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import com.printhub.printhub.entity.configuration.Printer;
import java.util.List; import java.util.List;
public interface PrinterRepository extends JpaRepository<Printer, Long>, JpaSpecificationExecutor<Printer> { public interface PrinterRepository extends JpaRepository<Printer, Long>, JpaSpecificationExecutor<Printer> {

View File

@ -0,0 +1,11 @@
package com.printhub.printhub.repository.configuration;
import com.printhub.printhub.entity.configuration.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}

View File

@ -0,0 +1,30 @@
package com.printhub.printhub.security;
import com.printhub.printhub.entity.configuration.User;
import com.printhub.printhub.repository.configuration.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class AuthenticationSuccessListener implements ApplicationListener<AuthenticationSuccessEvent> {
@Autowired
private UserRepository userRepository;
@Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
Object principal = event.getAuthentication().getPrincipal();
if (principal instanceof UserDetails userDetails) {
userRepository.findByUsername(userDetails.getUsername()).ifPresent(user -> {
user.setLastActive(LocalDateTime.now());
userRepository.save(user);
});
}
}
}

View File

@ -1,6 +1,6 @@
package com.printhub.printhub.service.configuration; package com.printhub.printhub.service.configuration;
import com.printhub.printhub.model.configuration.Printer; import com.printhub.printhub.entity.configuration.Printer;
import com.printhub.printhub.repository.configuration.PrinterRepository; import com.printhub.printhub.repository.configuration.PrinterRepository;
import com.printhub.printhub.specification.configuration.PrinterSpecification; import com.printhub.printhub.specification.configuration.PrinterSpecification;
import com.printhub.printhub.utils.datatables.DataTableBuilder; import com.printhub.printhub.utils.datatables.DataTableBuilder;

View File

@ -0,0 +1,50 @@
package com.printhub.printhub.service.configuration;
import com.printhub.printhub.entity.configuration.User;
import com.printhub.printhub.repository.configuration.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.*;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.Optional;
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public void register(String username, String password, String role) {
User user = new User();
user.setUsername(username);
user.setPassword(passwordEncoder.encode(password));
user.setRole("ROLE_" + role.toUpperCase());
user.setEnabled(true);
userRepository.save(user);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> userOpt = userRepository.findByUsername(username);
if (userOpt.isEmpty() || userOpt.get().getDeletedAt() != null) {
throw new UsernameNotFoundException("Usuario no encontrado o eliminado");
}
User user = userOpt.get();
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.isEnabled(),
true,
true,
user.getDeletedAt() == null,
Collections.singletonList(new SimpleGrantedAuthority(user.getRole()))
);
}
}

View File

@ -1,6 +1,6 @@
package com.printhub.printhub.specification.configuration; package com.printhub.printhub.specification.configuration;
import com.printhub.printhub.model.configuration.Printer; import com.printhub.printhub.entity.configuration.Printer;
import com.printhub.printhub.utils.datatables.DataTableRequest; import com.printhub.printhub.utils.datatables.DataTableRequest;
import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specification;

View File

@ -1 +1,10 @@
t-paginas=Pages # Breadcrumbs
t-paginas=Pages
# Roles
t-role.ROLE_ADMIN=Administrator
t-role.ROLE_USER=User
# Topbar
t-topbar-logout=Logout
t-topbar-profile=Profile

View File

@ -0,0 +1,16 @@
t-login-title=Welcome to Print Hub!
t-login-subtitle=Sign in to continue
t-login-username=Username
t-login-password=Password
t-login-button=Sign In
t-login-password-recovery-message=Forgot your password?
t-login-password-recovery=Recover Password
t-login-signup-message=Don't have an account ?
t-login-signup-signup=Sign Up
t-login-logout=You have logged out successfully
t-login-error=Incorrect username or password

View File

@ -1 +1,10 @@
t-paginas=Páginas # Breadcrumbs
t-paginas=Páginas
# Roles
t-role.ROLE_ADMIN=Administrador
t-role.ROLE_USER=Usuario
# Topbar
t-topbar-logout=Cerrar sesión
t-topbar-profile=Perfil

View File

@ -0,0 +1,14 @@
t-login-title=¡Bienvenido a Print Hub!
t-login-subtitle=Inicia sesión para continuar
t-login-username=Nombre de usuario
t-login-password=Contraseña
t-login-button=Iniciar sesión
t-login-password-recovery-message=¿Olvidaste tu contraseña?
t-login-password-recovery=Recuperar contraseña
t-login-signup-message=¿No tienes una cuenta?
t-login-signup-signup=Registrarse
t-login-logout=Has cerrado sesión correctamente
t-login-error=Nombre de usuario o contraseña incorrectos

View File

@ -49,6 +49,7 @@
<th:block layout:fragment="pagejs" /> <th:block layout:fragment="pagejs" />
<!-- App js --> <!-- App js -->
<script th:src="@{/assets/js/app.js}"></script> <script th:src="@{/assets/js/app.js}"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,168 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" lang="en"
data-layout="vertical" data-topbar="light" data-sidebar="dark" data-sidebar-size="lg" data-sidebar-image="none"
data-preloader="disable">
<head>
<!--page title-->
<div th:replace="~{printhub/partials/title-meta :: title-meta('Sign In')}"></div>
<!-- Page CSS -->
<div th:replace="~{printhub/partials/head-css :: head-css}"></div>
<style>
.auth-page-content {
opacity: 0;
transition: opacity 0.4s ease-in-out;
}
.auth-page-content.lang-loaded {
opacity: 1;
}
</style>
</head>
<body>
<div class="auth-page-wrapper auth-bg-cover py-5 d-flex justify-content-center align-items-center min-vh-100">
<div class="bg-overlay"></div>
<!-- auth-page content -->
<div class="auth-page-content overflow-hidden pt-lg-5 lang-loading">
<div class="container">
<div class="row">
<div class="col-lg-6 mx-auto">
<div class="card overflow-hidden">
<div class="row g-0">
<div class="p-lg-5 p-4">
<div class="text-end mb-3">
<button class="btn btn-sm me-2" onclick="setLang('es')">
<img src="/assets/images/flags/es.svg" alt="ES" height="16">
</button>
<button class="btn btn-sm" onclick="setLang('en')">
<img src="/assets/images/flags/gb.svg" alt="EN" height="16">
</button>
</div>
<div>
<h5 class="text-primary" data-key="t-login-title">Welcome Back !</h5>
<p class="text-muted" data-key="t-login-subtitle">Sign in to continue to
Velzon.
</p>
</div>
<div class="mt-4">
<div th:if="${param.error}" class="alert alert-danger text-center"
data-key="t-login-error">
Usuario o contraseña incorrectos.
</div>
<div th:if="${param.logout}" class="alert alert-success text-center"
data-key="t-login-logout">
Has cerrado sesión correctamente.
</div>
<form method="post" th:action="@{/login}">
<div class="mb-3">
<label for="username" class="form-label"
data-key="t-login-username">Username</label>
<input type="text" name="username" class="form-control" id="username"
placeholder="Enter username" required>
</div>
<div class="mb-3">
<label class="form-label" for="password-input"
data-key="t-login-password">Password</label>
<input type="password" name="password" class="form-control"
id="password-input" placeholder="Enter password" required>
</div>
<div class="mt-4">
<button class="btn btn-secondary w-100" type="submit"
data-key="t-login-button">Sign
In</button>
</div>
</form>
</div>
<div class="mt-5 text-center">
<p class="mb-0">
<span data-key="t-login-signup-message">Don't have an account ?</span>
<a href="/signup" class="link">
<i class="fa fa-user-plus me-1"></i>
<span data-key="t-login-signup-signup" class="label">Signup</span>
</a>
</p>
</div>
</div>
</div> <!-- end row -->
</div> <!-- end card -->
</div> <!-- end col -->
</div> <!-- end row -->
</div> <!-- end container -->
</div> <!-- end auth page content -->
<!-- footer -->
<footer class="footer">
<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="text-center">
<p class="mb-0">&copy;
<script>document.write(new Date().getFullYear())</script> PrintHub <i
class="mdi mdi-heart text-danger"></i> by JJO & IMN
</p>
</div>
</div>
</div>
</div>
</footer>
<!-- end Footer -->
</div>
<div th:replace="~{theme/partials/vendor-scripts :: scripts}"></div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<!-- password-addon init -->
<script src="/assets/js/pages/password-addon.init.js"></script>
<script>
function setLang(lang) {
localStorage.setItem("lang", lang);
applyTranslations(lang);
}
function applyTranslations(lang) {
const container = document.querySelector('.auth-page-content');
// Asegura que está invisible al inicio
container?.classList.remove('lang-loaded');
fetch('/api/lang?lang=' + lang,{
cache: "no-store"
})
.then(res => res.json())
.then(data => {
document.querySelectorAll('[data-key]').forEach(el => {
const key = el.getAttribute('data-key');
if (data[key]) {
if (el.children.length === 0) {
el.textContent = data[key];
}
}
});
})
.finally(() => {
// Aquí aplica el fade cuando ya están las traducciones
requestAnimationFrame(() => {
container?.classList.add('lang-loaded');
});
});
}
document.addEventListener('DOMContentLoaded', () => {
const lang = localStorage.getItem("lang") || "es";
applyTranslations(lang);
});
</script>
</body>
</html>

View File

@ -27,7 +27,9 @@
</a> </a>
</div> </div>
<button type="button" class="btn btn-sm px-3 fs-16 header-item vertical-menu-btn topnav-hamburger" id="topnav-hamburger-icon"> <button type="button"
class="btn btn-sm px-3 fs-16 header-item vertical-menu-btn topnav-hamburger"
id="topnav-hamburger-icon">
<span class="hamburger-icon"> <span class="hamburger-icon">
<span></span> <span></span>
<span></span> <span></span>
@ -39,20 +41,25 @@
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="dropdown ms-1 topbar-head-dropdown header-item"> <div class="dropdown ms-1 topbar-head-dropdown header-item">
<button type="button" class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <button type="button" class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img id="header-lang-img" src="" alt="Header Language" height="20" class="rounded"> <img id="header-lang-img" src="" alt="Header Language" height="20" class="rounded">
</button> </button>
<div class="dropdown-menu dropdown-menu-end"> <div class="dropdown-menu dropdown-menu-end">
<!-- item--> <!-- item-->
<a href="javascript:void(0);" class="dropdown-item notify-item language" data-lang="es" title="Spanish"> <a href="javascript:void(0);" class="dropdown-item notify-item language" data-lang="es"
<img src="/assets/images/flags/spain.svg" alt="user-image" class="me-2 rounded" height="18"> title="Spanish">
<img src="/assets/images/flags/spain.svg" alt="user-image" class="me-2 rounded"
height="18">
<span class="align-middle">Español</span> <span class="align-middle">Español</span>
</a> </a>
<!-- item--> <!-- item-->
<a href="javascript:void(0);" class="dropdown-item notify-item language py-2" data-lang="en" title="English"> <a href="javascript:void(0);" class="dropdown-item notify-item language py-2"
<img src="/assets/images/flags/gb.svg" alt="user-image" class="me-2 rounded" height="18"> data-lang="en" title="English">
<img src="/assets/images/flags/gb.svg" alt="user-image" class="me-2 rounded"
height="18">
<span class="align-middle">English</span> <span class="align-middle">English</span>
</a> </a>
@ -60,40 +67,58 @@
</div> </div>
<div class="ms-1 header-item d-none d-sm-flex"> <div class="ms-1 header-item d-none d-sm-flex">
<button type="button" class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle" data-toggle="fullscreen"> <button type="button" class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle"
data-toggle="fullscreen">
<i class='bx bx-fullscreen fs-22'></i> <i class='bx bx-fullscreen fs-22'></i>
</button> </button>
</div> </div>
<div class="ms-1 header-item d-none d-sm-flex"> <div class="ms-1 header-item d-none d-sm-flex">
<button type="button" class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle light-dark-mode"> <button type="button"
class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle light-dark-mode">
<i class='bx bx-moon fs-22'></i> <i class='bx bx-moon fs-22'></i>
</button> </button>
</div> </div>
<div class="dropdown ms-sm-3 header-item topbar-user"> <div class="dropdown ms-sm-3 header-item topbar-user">
<button type="button" class="btn" id="page-header-user-dropdown" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <button type="button" class="btn" id="page-header-user-dropdown" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<span class="d-flex align-items-center"> <span class="d-flex align-items-center">
<img class="rounded-circle header-profile-user" src="/assets/images/users/avatar-1.jpg" alt="Header Avatar">
<span class="text-start ms-xl-2"> <span class="text-start ms-xl-2">
<span class="d-none d-xl-inline-block ms-1 fw-medium user-name-text">Anna Adame</span> <span class="d-none d-xl-inline-block ms-1 fw-medium user-name-text"
<span class="d-none d-xl-block ms-1 fs-12 text-muted user-name-sub-text">Founder</span> th:text="${currentUser.firstName + ' ' + currentUser.lastName}">
</span>
<span class="d-none d-xl-block ms-1 fs-12 text-muted user-name-sub-text"
th:attr="data-key=${'t-role.' + currentUser.role} ?: ${currentUser.role}"
th:text="#{${'t-role.' + currentUser.role} ?: ${currentUser.role}}">
</span>
</span> </span>
</span> </span>
</button> </button>
<div class="dropdown-menu dropdown-menu-end"> <div class="dropdown-menu dropdown-menu-end">
<!-- item--> <!-- item-->
<h6 class="dropdown-header">Welcome Anna!</h6> <h6 class="dropdown-header" th:if="${currentUser != null}">
<a class="dropdown-item" href="/pages-profile"><i class="mdi mdi-account-circle text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Profile</span></a> Bienvenido <span
<a class="dropdown-item" href="/apps-chat"><i class="mdi mdi-message-text-outline text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Messages</span></a> th:text="${currentUser.firstName + ' ' + currentUser.lastName}">Usuario</span>!
<a class="dropdown-item" href="/apps-tasks-kanban"><i class="mdi mdi-calendar-check-outline text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Taskboard</span></a> </h6>
<a class="dropdown-item" href="/pages-faqs"><i class="mdi mdi-lifebuoy text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Help</span></a> <h6 class="dropdown-header" th:unless="${currentUser != null}">
<div class="dropdown-divider"></div> Bienvenido invitado
<a class="dropdown-item" href="/pages-profile"><i class="mdi mdi-wallet text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Balance : <b>$5971.67</b></span></a> </h6>
<a class="dropdown-item" href="/pages-profile-settings"><span class="badge bg-soft-success text-success mt-1 float-end">New</span><i class="mdi mdi-cog-outline text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Settings</span></a> <a class="dropdown-item" href="/pages-profile">
<a class="dropdown-item" href="auth-lockscreen-basic"><i class="mdi mdi-lock text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Lock screen</span></a> <i class="mdi mdi-account-circle text-muted fs-16 align-middle me-1"></i>
<a class="dropdown-item" href="auth-logout-basic"><i class="mdi mdi-logout text-muted fs-16 align-middle me-1"></i> <span class="align-middle" data-key="t-logout">Logout</span></a> <span class="align-middle" data-key="t-topbar-profile">
Profile
</span>
</a>
<a class="dropdown-item" href="#"
onclick="document.getElementById('logout-form').submit(); return false;">
<i class="mdi mdi-logout text-muted fs-16 align-middle me-1"></i>
<span class="align-middle" data-key="t-topbar-logout">Logout</span>
</a>
<form id="logout-form" th:action="@{/logout}" method="post" style="display:none;">
</form>
</div> </div>
</div> </div>
</div> </div>
@ -101,7 +126,7 @@
</div> </div>
</header> </header>
</div> </div>
</body> </body>