mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-02-06 19:09:14 +00:00
Compare commits
7 Commits
9a67c2e78f
...
feat/home_
| Author | SHA1 | Date | |
|---|---|---|---|
| 61e55e014f | |||
| 48993a34c4 | |||
| a0bf8552f1 | |||
| 562dc2b231 | |||
| 9a49ccf6b8 | |||
| b2026f1cab | |||
| a5b6bf3a25 |
14036
logs/erp.log
14036
logs/erp.log
File diff suppressed because one or more lines are too long
@ -149,6 +149,10 @@ public class SecurityConfig {
|
|||||||
"/pagos/redsys/**"
|
"/pagos/redsys/**"
|
||||||
)
|
)
|
||||||
.permitAll()
|
.permitAll()
|
||||||
|
.requestMatchers("/impersonate/exit")
|
||||||
|
.hasRole("PREVIOUS_ADMINISTRATOR")
|
||||||
|
.requestMatchers("/impersonate")
|
||||||
|
.hasAnyRole("SUPERADMIN", "ADMIN")
|
||||||
.requestMatchers("/users/**").hasAnyRole("SUPERADMIN", "ADMIN")
|
.requestMatchers("/users/**").hasAnyRole("SUPERADMIN", "ADMIN")
|
||||||
.anyRequest().authenticated())
|
.anyRequest().authenticated())
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import java.util.Optional;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -63,6 +65,8 @@ import jakarta.validation.Valid;
|
|||||||
@RequestMapping("/presupuesto")
|
@RequestMapping("/presupuesto")
|
||||||
public class PresupuestoController {
|
public class PresupuestoController {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(PresupuestoController.class);
|
||||||
|
|
||||||
private final PresupuestoRepository presupuestoRepository;
|
private final PresupuestoRepository presupuestoRepository;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -824,6 +828,7 @@ public class PresupuestoController {
|
|||||||
return ResponseEntity.ok(Map.of("id", saveResult.get("presupuesto_id"),
|
return ResponseEntity.ok(Map.of("id", saveResult.get("presupuesto_id"),
|
||||||
"message", messageSource.getMessage("presupuesto.exito.guardado", null, locale)));
|
"message", messageSource.getMessage("presupuesto.exito.guardado", null, locale)));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
log.error("Error al guardar el presupuesto", ex);
|
||||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
.body(Map.of("message",
|
.body(Map.of("message",
|
||||||
messageSource.getMessage("presupuesto.error.save-internal-error", null, locale),
|
messageSource.getMessage("presupuesto.error.save-internal-error", null, locale),
|
||||||
|
|||||||
@ -0,0 +1,115 @@
|
|||||||
|
package com.imprimelibros.erp.users;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
|
||||||
|
import com.imprimelibros.erp.config.Sanitizer;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class ImpersonationController {
|
||||||
|
|
||||||
|
private static final String PREVIOUS_ADMIN_ROLE = "ROLE_PREVIOUS_ADMINISTRATOR";
|
||||||
|
private static final String SESSION_ATTR = "IMPERSONATOR_AUTH";
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
private final Sanitizer sanitizer;
|
||||||
|
|
||||||
|
public ImpersonationController(UserService userService, Sanitizer sanitizer) {
|
||||||
|
this.userService = userService;
|
||||||
|
this.sanitizer = sanitizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/impersonate")
|
||||||
|
@PreAuthorize("hasRole('ADMIN') or hasRole('SUPERADMIN')")
|
||||||
|
public String impersonate(
|
||||||
|
@RequestParam("username") String username,
|
||||||
|
Authentication authentication,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
|
||||||
|
if (authentication == null) {
|
||||||
|
return "redirect:/login";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasRole(authentication, PREVIOUS_ADMIN_ROLE)) {
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = sanitizer.plain(username);
|
||||||
|
if (normalized == null || normalized.isBlank()) {
|
||||||
|
return "redirect:/users";
|
||||||
|
}
|
||||||
|
normalized = normalized.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (authentication.getName() != null
|
||||||
|
&& authentication.getName().equalsIgnoreCase(normalized)) {
|
||||||
|
return "redirect:/users";
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDetails target;
|
||||||
|
try {
|
||||||
|
target = userService.loadUserByUsername(normalized);
|
||||||
|
} catch (UsernameNotFoundException ex) {
|
||||||
|
throw new AccessDeniedException("No autorizado");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean currentIsSuperAdmin = hasRole(authentication, "ROLE_SUPERADMIN");
|
||||||
|
boolean targetIsSuperAdmin = target.getAuthorities().stream()
|
||||||
|
.anyMatch(a -> "ROLE_SUPERADMIN".equals(a.getAuthority()));
|
||||||
|
if (targetIsSuperAdmin && !currentIsSuperAdmin) {
|
||||||
|
throw new AccessDeniedException("No autorizado");
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpSession session = request.getSession(true);
|
||||||
|
if (session.getAttribute(SESSION_ATTR) == null) {
|
||||||
|
session.setAttribute(SESSION_ATTR, authentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GrantedAuthority> authorities = new ArrayList<>(target.getAuthorities());
|
||||||
|
authorities.add(new SimpleGrantedAuthority(PREVIOUS_ADMIN_ROLE));
|
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken newAuth = new UsernamePasswordAuthenticationToken(
|
||||||
|
target, target.getPassword(), authorities);
|
||||||
|
newAuth.setDetails(authentication.getDetails());
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(newAuth);
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/impersonate/exit")
|
||||||
|
@PreAuthorize("hasRole('PREVIOUS_ADMINISTRATOR')")
|
||||||
|
public String exit(HttpServletRequest request) {
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
if (session != null) {
|
||||||
|
Object previous = session.getAttribute(SESSION_ATTR);
|
||||||
|
if (previous instanceof Authentication previousAuth) {
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(previousAuth);
|
||||||
|
} else {
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
}
|
||||||
|
session.removeAttribute(SESSION_ATTR);
|
||||||
|
}
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasRole(Authentication auth, String role) {
|
||||||
|
return auth != null
|
||||||
|
&& auth.getAuthorities().stream()
|
||||||
|
.anyMatch(a -> role.equals(a.getAuthority()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -81,6 +81,9 @@ public class UserController {
|
|||||||
"usuarios.delete.button",
|
"usuarios.delete.button",
|
||||||
"app.yes",
|
"app.yes",
|
||||||
"app.cancelar",
|
"app.cancelar",
|
||||||
|
"usuarios.impersonate.title",
|
||||||
|
"usuarios.impersonate.text",
|
||||||
|
"usuarios.impersonate.button",
|
||||||
"usuarios.delete.ok.title",
|
"usuarios.delete.ok.title",
|
||||||
"usuarios.delete.ok.text");
|
"usuarios.delete.ok.text");
|
||||||
|
|
||||||
@ -132,26 +135,36 @@ public class UserController {
|
|||||||
.collect(Collectors.joining(" ")))
|
.collect(Collectors.joining(" ")))
|
||||||
.add("actions", (user) -> {
|
.add("actions", (user) -> {
|
||||||
|
|
||||||
boolean isSuperAdmin = authentication.getAuthorities().stream()
|
boolean isSuperAdmin = authentication != null && authentication.getAuthorities().stream()
|
||||||
.anyMatch(a -> a.getAuthority().equals("ROLE_SUPERADMIN"));
|
.anyMatch(a -> a.getAuthority().equals("ROLE_SUPERADMIN"));
|
||||||
|
|
||||||
if (!isSuperAdmin) {
|
boolean isSelf = authentication != null
|
||||||
return "<div class=\"hstack gap-3 flex-wrap\">\n" +
|
&& authentication.getName() != null
|
||||||
" <a href=\"javascript:void(0);\" data-id=\"" + user.getId()
|
&& authentication.getName().equalsIgnoreCase(user.getUserName());
|
||||||
+ "\" class=\"link-success btn-edit-user fs-15\"><i class=\"ri-edit-2-line\"></i></a>\n"
|
|
||||||
+
|
boolean targetIsSuperAdmin = user.getRoles().stream()
|
||||||
" </div>";
|
.anyMatch(r -> "SUPERADMIN".equalsIgnoreCase(r.getName()));
|
||||||
} else {
|
|
||||||
// Admin editando otro admin o usuario normal: puede editarse y eliminarse
|
StringBuilder actions = new StringBuilder();
|
||||||
return "<div class=\"hstack gap-3 flex-wrap\">\n" +
|
actions.append("<div class=\"hstack gap-3 flex-wrap\">");
|
||||||
" <a href=\"javascript:void(0);\" data-id=\"" + user.getId()
|
actions.append("<a href=\"javascript:void(0);\" data-id=\"")
|
||||||
+ "\" class=\"link-success btn-edit-user fs-15\"><i class=\"ri-edit-2-line\"></i></a>\n"
|
.append(user.getId())
|
||||||
+
|
.append("\" class=\"link-success btn-edit-user fs-15\"><i class=\"ri-edit-2-line\"></i></a>");
|
||||||
" <a href=\"javascript:void(0);\" data-id=\"" + user.getId()
|
|
||||||
+ "\" class=\"link-danger btn-delete-user fs-15\"><i class=\"user-delete ri-delete-bin-line\"></i></a>\n"
|
if (!isSelf && (isSuperAdmin || !targetIsSuperAdmin)) {
|
||||||
+
|
actions.append("<a href=\"javascript:void(0);\" data-username=\"")
|
||||||
" </div>";
|
.append(user.getUserName())
|
||||||
|
.append("\" class=\"link-info btn-impersonate-user fs-15\"><i class=\"ri-user-shared-line\"></i></a>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSuperAdmin) {
|
||||||
|
actions.append("<a href=\"javascript:void(0);\" data-id=\"")
|
||||||
|
.append(user.getId())
|
||||||
|
.append("\" class=\"link-danger btn-delete-user fs-15\"><i class=\"user-delete ri-delete-bin-line\"></i></a>");
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.append("</div>");
|
||||||
|
return actions.toString();
|
||||||
})
|
})
|
||||||
.where(base)
|
.where(base)
|
||||||
// Filtros custom:
|
// Filtros custom:
|
||||||
|
|||||||
@ -18,6 +18,8 @@ server.error.include-binding-errors=never
|
|||||||
# Opcional: desactivar Whitelabel y servir tu propia página de error
|
# Opcional: desactivar Whitelabel y servir tu propia página de error
|
||||||
server.error.whitelabel.enabled=false
|
server.error.whitelabel.enabled=false
|
||||||
|
|
||||||
|
# Servelet options
|
||||||
|
server.servlet.context-path=/intranet
|
||||||
|
|
||||||
# Archivo principal dentro del contenedor (monta /var/log/imprimelibros como volumen)
|
# Archivo principal dentro del contenedor (monta /var/log/imprimelibros como volumen)
|
||||||
logging.file.name=/var/log/imprimelibros/erp.log
|
logging.file.name=/var/log/imprimelibros/erp.log
|
||||||
@ -42,6 +44,6 @@ safekat.api.password=Safekat2024
|
|||||||
redsys.environment=test
|
redsys.environment=test
|
||||||
redsys.url=https://sis-t.redsys.es:25443/sis/realizarPago
|
redsys.url=https://sis-t.redsys.es:25443/sis/realizarPago
|
||||||
redsys.refund.url=https://sis-t.redsys.es:25443/sis/rest/trataPeticionREST
|
redsys.refund.url=https://sis-t.redsys.es:25443/sis/rest/trataPeticionREST
|
||||||
redsys.urls.ok=https://imprimelibros.jjimenez.eu/pagos/redsys/ok
|
redsys.urls.ok=https://app.imprimelibros.com/intranet/pagos/redsys/ok
|
||||||
redsys.urls.ko=https://imprimelibros.jjimenez.eu/pagos/redsys/ko
|
redsys.urls.ko=https://app.imprimelibros.com/intranet/pagos/redsys/ko
|
||||||
redsys.urls.notify=https://imprimelibros.jjimenez.eu/pagos/redsys/notify
|
redsys.urls.notify=https://app.imprimelibros.com/intranet/pagos/redsys/notify
|
||||||
@ -1,7 +1,7 @@
|
|||||||
spring.application.name=erp
|
spring.application.name=erp
|
||||||
# Active profile
|
# Active profile
|
||||||
#spring.profiles.active=dev
|
spring.profiles.active=dev
|
||||||
spring.profiles.active=test
|
#spring.profiles.active=test
|
||||||
#spring.profiles.active=prod
|
#spring.profiles.active=prod
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -6,4 +6,5 @@ app.cancelar=Cancel
|
|||||||
app.guardar=Save
|
app.guardar=Save
|
||||||
app.editar=Edit
|
app.editar=Edit
|
||||||
app.eliminar=Delete
|
app.eliminar=Delete
|
||||||
app.imprimir=Print
|
app.imprimir=Print
|
||||||
|
app.impersonate.exit=Return to my user
|
||||||
|
|||||||
@ -32,4 +32,5 @@ app.sidebar.gestion-pagos=Gestión de Pagos
|
|||||||
|
|
||||||
app.errors.403=No tienes permiso para acceder a esta página.
|
app.errors.403=No tienes permiso para acceder a esta página.
|
||||||
|
|
||||||
app.validation.required=Campo obligatorio
|
app.validation.required=Campo obligatorio
|
||||||
|
app.impersonate.exit=Volver a mi usuario
|
||||||
|
|||||||
@ -1 +1,3 @@
|
|||||||
|
usuarios.impersonate.title=Sign in as user
|
||||||
|
usuarios.impersonate.text=You are about to sign in as <b>{0}</b>. You can return to your user from the menu.
|
||||||
|
usuarios.impersonate.button=Continue
|
||||||
|
|||||||
@ -53,4 +53,7 @@ usuarios.delete.title=Eliminar usuario
|
|||||||
usuarios.delete.button=Si, ELIMINAR
|
usuarios.delete.button=Si, ELIMINAR
|
||||||
usuarios.delete.text=¿Está seguro de que desea eliminar al usuario?<br>Esta acción no se puede deshacer.
|
usuarios.delete.text=¿Está seguro de que desea eliminar al usuario?<br>Esta acción no se puede deshacer.
|
||||||
usuarios.delete.ok.title=Usuario eliminado
|
usuarios.delete.ok.title=Usuario eliminado
|
||||||
usuarios.delete.ok.text=El usuario ha sido eliminado con éxito.
|
usuarios.delete.ok.text=El usuario ha sido eliminado con éxito.
|
||||||
|
usuarios.impersonate.title=Entrar como usuario
|
||||||
|
usuarios.impersonate.text=Vas a iniciar sesión como <b>{0}</b>. Podrás volver a tu usuario desde el menú.
|
||||||
|
usuarios.impersonate.button=Entrar
|
||||||
|
|||||||
312
src/main/resources/static/assets/css/home.css
Normal file
312
src/main/resources/static/assets/css/home.css
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
:root {
|
||||||
|
/* ====== Colores (cámbialos a tu gusto) ====== */
|
||||||
|
--banner-bg-1: #a5a091;
|
||||||
|
--banner-bg-2: #8292a8;
|
||||||
|
|
||||||
|
--banner-panel-bg: #a1b1b2;
|
||||||
|
--banner-panel-border: rgba(255, 255, 255, .75);
|
||||||
|
|
||||||
|
--text-main: #ffffff;
|
||||||
|
--text-muted: rgba(255, 255, 255, .8);
|
||||||
|
|
||||||
|
--accent-1: #e5745b;
|
||||||
|
/* salmón */
|
||||||
|
--accent-2: #92b2a7;
|
||||||
|
/* tu verde corporativo */
|
||||||
|
--accent-3: #7cc7ff;
|
||||||
|
/* toque azul claro */
|
||||||
|
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--card-border: rgba(8, 42, 67, .18);
|
||||||
|
--card-title: #0a314b;
|
||||||
|
--card-chip-bg: var(--accent-1);
|
||||||
|
--card-chip-text: #ffffff;
|
||||||
|
|
||||||
|
--shadow: 0 10px 30px rgba(0, 0, 0, .18);
|
||||||
|
|
||||||
|
/* ====== Medidas ====== */
|
||||||
|
--radius-lg: 18px;
|
||||||
|
--radius-md: 14px;
|
||||||
|
--radius-sm: 10px;
|
||||||
|
|
||||||
|
--pad: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-loyalty-banner {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
color: var(--text-main);
|
||||||
|
|
||||||
|
/* padding fluido */
|
||||||
|
padding: clamp(14px, 2.2vw, 22px);
|
||||||
|
|
||||||
|
/* Importante: reserva espacio inferior para decoraciones (libro) en desktop */
|
||||||
|
padding-bottom: clamp(18px, 3.2vw, 46px);
|
||||||
|
|
||||||
|
/* Fondo con gradiente + textura sutil */
|
||||||
|
background:
|
||||||
|
radial-gradient(1200px 500px at 20% 0%, rgba(124, 199, 255, .20), transparent 60%),
|
||||||
|
radial-gradient(900px 450px at 90% 20%, rgba(243, 162, 133, .22), transparent 65%),
|
||||||
|
linear-gradient(135deg, var(--banner-bg-1), var(--banner-bg-2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Opcional pero ayuda a que haya “lienzo” para el libro */
|
||||||
|
@media (min-width: 1101px) {
|
||||||
|
.ib-loyalty-banner {
|
||||||
|
min-height: 240px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Decoraciones generales ===== */
|
||||||
|
.ib-loyalty-banner .decor {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: .95;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Círculos/“sellos” */
|
||||||
|
.ib-loyalty-banner .decor::before,
|
||||||
|
.ib-loyalty-banner .decor::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 140px;
|
||||||
|
height: 140px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 10px solid rgba(243, 162, 133, .65);
|
||||||
|
box-shadow: inset 0 0 0 10px rgba(255, 255, 255, .08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-loyalty-banner .decor::before {
|
||||||
|
top: -42px;
|
||||||
|
right: -50px;
|
||||||
|
transform: rotate(10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-loyalty-banner .decor::after {
|
||||||
|
bottom: -55px;
|
||||||
|
left: -55px;
|
||||||
|
border-color: rgba(243, 162, 133, .55);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Libros “dibujados” con SVG como background ===== */
|
||||||
|
.ib-loyalty-banner .book {
|
||||||
|
position: absolute;
|
||||||
|
width: 190px;
|
||||||
|
height: 150px;
|
||||||
|
opacity: .9;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
background-size: contain;
|
||||||
|
background-image: url("/assets/images/open-book.svg");
|
||||||
|
|
||||||
|
/* Sombra sin cargarte otros filtros */
|
||||||
|
filter: drop-shadow(0 10px 18px rgba(0, 0, 0, .25));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Libro pequeño: lo subimos un poco para que no se quede “bajo” cuando el layout crece */
|
||||||
|
.ib-loyalty-banner .book.small {
|
||||||
|
width: 150px;
|
||||||
|
height: 120px;
|
||||||
|
left: 22px;
|
||||||
|
bottom: 18px;
|
||||||
|
/* antes 10px */
|
||||||
|
opacity: .85;
|
||||||
|
z-index: 1;
|
||||||
|
/* por encima del fondo, por debajo del contenido (contenido z-index 2) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Contenido ===== */
|
||||||
|
.ib-loyalty-inner {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
gap: clamp(12px, 2vw, 18px);
|
||||||
|
|
||||||
|
/* CLAVE: NO estires ambas columnas a la misma altura */
|
||||||
|
align-items: start;
|
||||||
|
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
/* Dos columnas con mínimos reales */
|
||||||
|
grid-template-columns: minmax(320px, 1.05fr) minmax(320px, .95fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apila antes para que no se estrangule */
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.ib-loyalty-inner {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Panel principal (logo + textos) */
|
||||||
|
.ib-loyalty-hero {
|
||||||
|
border: 2px solid var(--banner-panel-border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
background: var(--banner-panel-bg);
|
||||||
|
padding: 18px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.ib-loyalty-hero::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: -2px;
|
||||||
|
height: 6px;
|
||||||
|
background: linear-gradient(90deg, transparent, var(--accent-1), transparent);
|
||||||
|
opacity: .9;
|
||||||
|
border-radius: 999px;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
.ib-loyalty-head {
|
||||||
|
display: flex;
|
||||||
|
gap: 14px;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
/* responsive */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-loyalty-logo {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-loyalty-logo img {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-loyalty-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: clamp(1.05rem, 1.2vw, 1.35rem);
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: .2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-loyalty-sub {
|
||||||
|
margin: 4px 0 0 0;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: clamp(.9rem, 1vw, 1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Rewards ===== */
|
||||||
|
.ib-rewards {
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: 14px 14px 10px;
|
||||||
|
background: rgba(255, 255, 255, .04);
|
||||||
|
border: 1px solid rgba(255, 255, 255, .12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-rewards h6 {
|
||||||
|
margin: 4px 6px 12px;
|
||||||
|
font-size: .95rem;
|
||||||
|
letter-spacing: .25px;
|
||||||
|
opacity: .95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-rewards-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
/* auto-fit: se adapta solo */
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-card {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--card-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 10px 10px 9px;
|
||||||
|
color: var(--card-title);
|
||||||
|
box-shadow: 0 8px 18px rgba(0, 0, 0, .10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-card .range {
|
||||||
|
font-size: .82rem;
|
||||||
|
opacity: .85;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-card .percent {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1;
|
||||||
|
margin: 2px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-card .chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--card-chip-bg);
|
||||||
|
color: var(--card-chip-text);
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: .78rem;
|
||||||
|
letter-spacing: .2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tarjeta “0%” con borde punteado (si la usas) */
|
||||||
|
.ib-card.is-empty {
|
||||||
|
background: rgba(255, 255, 255, .9);
|
||||||
|
border: 2px dashed rgba(10, 49, 75, .35);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Ajustes extra para móviles ===== */
|
||||||
|
@media (max-width: 420px) {
|
||||||
|
.ib-rewards {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-card {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Libro: reduce presencia o desaparece en móvil ===== */
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.ib-loyalty-banner .book.small {
|
||||||
|
opacity: .55;
|
||||||
|
left: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
width: 120px;
|
||||||
|
height: 95px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.ib-loyalty-banner .book.small {
|
||||||
|
display: none;
|
||||||
|
/* fuera en móviles pequeños */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== OPCIONAL: hero más compacto en móvil ===== */
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.ib-loyalty-head {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-loyalty-logo {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ib-loyalty-logo img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
84
src/main/resources/static/assets/images/open-book.svg
Normal file
84
src/main/resources/static/assets/images/open-book.svg
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg fill="#ffffff" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 511 511" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<path d="M487.5,128.106H479v-24.5c0-2.905-1.678-5.549-4.307-6.786C405.088,64.066,325.408,63.6,255.5,95.371
|
||||||
|
C185.592,63.6,105.912,64.067,36.307,96.82C33.678,98.057,32,100.701,32,103.606v24.5h-8.5c-12.958,0-23.5,10.542-23.5,23.5v264
|
||||||
|
c0,12.958,10.542,23.5,23.5,23.5h464c12.958,0,23.5-10.542,23.5-23.5v-264C511,138.648,500.458,128.106,487.5,128.106z
|
||||||
|
M263,239.583c0-0.009,0-0.019,0-0.028V108.416c64.137-28.707,136.861-28.707,201,0v27.161c0,0.01-0.001,0.02-0.001,0.029
|
||||||
|
s0.001,0.02,0.001,0.029v244.438c-32.237-13.461-66.371-20.193-100.5-20.193c-34.129,0-68.264,6.732-100.5,20.193V239.583z
|
||||||
|
M215,96.391c11.187,3.204,22.217,7.198,33,12.025v117.177l-12.34-8.227c-2.52-1.68-5.801-1.68-8.32,0L215,225.593V96.391z
|
||||||
|
M47,135.626c0-0.007,0.001-0.013,0.001-0.02S47,135.594,47,135.587v-27.171c48.563-21.736,102.046-26.999,153-15.82v32.856
|
||||||
|
c-26.767-5.505-54.078-6.777-81.328-3.75c-4.117,0.457-7.083,4.165-6.626,8.282c0.458,4.116,4.162,7.085,8.282,6.626
|
||||||
|
c26.708-2.967,53.479-1.562,79.671,4.165v48.686c-15.912-3.265-32.14-5.067-48.377-5.323c-4.145-0.078-7.552,3.239-7.618,7.38
|
||||||
|
c-0.065,4.142,3.239,7.552,7.38,7.618c16.331,0.258,32.654,2.164,48.614,5.647v16.66c-43.389-8.909-88.39-6.644-130.748,6.665
|
||||||
|
c-3.952,1.241-6.148,5.451-4.907,9.403c1.007,3.204,3.964,5.254,7.153,5.254c0.745,0,1.502-0.112,2.25-0.347
|
||||||
|
c40.908-12.852,84.428-14.773,126.252-5.638v2.825c0,2.766,1.522,5.308,3.961,6.612c2.438,1.306,5.398,1.162,7.699-0.372
|
||||||
|
l19.84-13.227l16.5,11v136.454c-32.237-13.461-66.371-20.193-100.5-20.193c-34.129,0-68.264,6.732-100.5,20.193V135.626z
|
||||||
|
M224,424.106H23.5c-4.687,0-8.5-3.813-8.5-8.5v-264c0-4.687,3.813-8.5,8.5-8.5H32v248.5v8c0,4.142,3.358,7.5,7.5,7.5H224V424.106z
|
||||||
|
M57.29,392.106c58.099-22.934,122.32-22.935,180.42,0H57.29z M272,424.106h-33v-17h33V424.106z M453.71,392.106H273.29
|
||||||
|
C331.389,369.172,395.61,369.172,453.71,392.106z M496,415.606c0,4.687-3.813,8.5-8.5,8.5H287v-17h184.5c4.142,0,7.5-3.358,7.5-7.5
|
||||||
|
v-8v-248.5h8.5c4.687,0,8.5,3.813,8.5,8.5V415.606z"/>
|
||||||
|
<path d="M309.96,317.749c-8.302,1.74-16.615,3.911-24.708,6.454c-3.952,1.242-6.148,5.452-4.907,9.403
|
||||||
|
c1.007,3.204,3.964,5.254,7.153,5.254c0.745,0,1.502-0.112,2.25-0.347c7.628-2.396,15.464-4.443,23.288-6.083
|
||||||
|
c4.054-0.85,6.652-4.825,5.802-8.879C317.989,319.497,314.011,316.9,309.96,317.749z"/>
|
||||||
|
<path d="M439.502,338.859c3.189,0,6.147-2.051,7.153-5.254c1.241-3.952-0.956-8.162-4.907-9.403
|
||||||
|
c-32.073-10.076-65.329-13.842-98.844-11.188c-4.129,0.326-7.211,3.938-6.885,8.068s3.935,7.213,8.068,6.885
|
||||||
|
c31.59-2.499,62.935,1.048,93.165,10.546C438,338.748,438.757,338.859,439.502,338.859z"/>
|
||||||
|
<path d="M287.498,306.767c0.745,0,1.502-0.112,2.25-0.347c48.249-15.159,99.256-15.159,147.504,0
|
||||||
|
c3.952,1.24,8.162-0.956,9.403-4.907c1.241-3.952-0.956-8.162-4.907-9.403c-51.191-16.083-105.306-16.083-156.496,0
|
||||||
|
c-3.952,1.241-6.149,5.451-4.907,9.403C281.352,304.716,284.309,306.767,287.498,306.767z"/>
|
||||||
|
<path d="M287.498,274.859c0.745,0,1.502-0.112,2.25-0.347c27.681-8.697,56.409-12.412,85.399-11.037
|
||||||
|
c4.147,0.192,7.651-2.999,7.847-7.137c0.196-4.138-2.999-7.65-7.137-7.847c-30.753-1.456-61.236,2.483-90.605,11.71
|
||||||
|
c-3.952,1.242-6.149,5.452-4.907,9.403C281.352,272.81,284.309,274.859,287.498,274.859z"/>
|
||||||
|
<path d="M441.748,260.202c-10.76-3.38-21.846-6.086-32.952-8.043c-4.08-0.719-7.968,2.006-8.688,6.085
|
||||||
|
c-0.719,4.079,2.005,7.969,6.085,8.688c10.467,1.844,20.917,4.395,31.058,7.581c0.749,0.235,1.505,0.347,2.25,0.347
|
||||||
|
c3.189,0,6.147-2.051,7.153-5.254C447.896,265.653,445.7,261.443,441.748,260.202z"/>
|
||||||
|
<path d="M287.498,242.767c0.745,0,1.502-0.112,2.25-0.347c48.249-15.159,99.256-15.159,147.504,0
|
||||||
|
c3.952,1.24,8.162-0.956,9.403-4.907c1.241-3.952-0.956-8.162-4.907-9.403c-51.191-16.083-105.306-16.083-156.496,0
|
||||||
|
c-3.952,1.241-6.149,5.451-4.907,9.403C281.352,240.716,284.309,242.767,287.498,242.767z"/>
|
||||||
|
<path d="M334.678,185.702c-16.732,1.858-33.362,5.36-49.426,10.407c-3.952,1.241-6.148,5.451-4.907,9.403
|
||||||
|
c1.007,3.204,3.964,5.254,7.153,5.254c0.745,0,1.502-0.112,2.25-0.347c15.141-4.757,30.815-8.057,46.585-9.809
|
||||||
|
c4.117-0.457,7.083-4.165,6.626-8.282S338.79,185.244,334.678,185.702z"/>
|
||||||
|
<path d="M367.386,199.137c23.725,0.375,47.231,4.17,69.866,11.283c0.748,0.234,1.505,0.347,2.25,0.347
|
||||||
|
c3.189,0,6.146-2.051,7.153-5.254c1.241-3.952-0.956-8.162-4.907-9.403c-24.015-7.545-48.955-11.572-74.125-11.97
|
||||||
|
c-4.125-0.078-7.552,3.239-7.618,7.38S363.244,199.072,367.386,199.137z"/>
|
||||||
|
<path d="M390.671,168.704c4.116,0.46,7.825-2.509,8.282-6.626c0.458-4.117-2.509-7.825-6.626-8.282
|
||||||
|
c-36.252-4.027-72.278-0.526-107.075,10.406c-3.952,1.242-6.148,5.452-4.907,9.403c1.007,3.204,3.964,5.254,7.153,5.254
|
||||||
|
c0.745,0,1.502-0.112,2.25-0.347C322.545,168.208,356.5,164.909,390.671,168.704z"/>
|
||||||
|
<path d="M441.748,164.202c-5.418-1.702-10.96-3.246-16.472-4.588c-4.03-0.98-8.082,1.488-9.062,5.512
|
||||||
|
c-0.98,4.024,1.488,8.082,5.512,9.062c5.196,1.265,10.419,2.72,15.526,4.324c0.748,0.235,1.505,0.347,2.25,0.347
|
||||||
|
c3.189,0,6.147-2.051,7.153-5.254C447.896,169.653,445.7,165.443,441.748,164.202z"/>
|
||||||
|
<path d="M287.498,146.767c0.745,0,1.502-0.112,2.25-0.347c5.103-1.604,10.325-3.058,15.521-4.324
|
||||||
|
c4.024-0.98,6.492-5.037,5.512-9.062s-5.038-6.492-9.062-5.512c-5.513,1.342-11.053,2.886-16.468,4.587
|
||||||
|
c-3.951,1.242-6.148,5.452-4.907,9.403C281.352,144.716,284.309,146.767,287.498,146.767z"/>
|
||||||
|
<path d="M336.329,136.611c34.172-3.796,68.126-0.496,100.923,9.809c0.748,0.234,1.505,0.347,2.25,0.347
|
||||||
|
c3.189,0,6.146-2.051,7.153-5.254c1.241-3.952-0.956-8.162-4.907-9.403c-34.797-10.933-70.824-14.435-107.076-10.406
|
||||||
|
c-4.117,0.457-7.083,4.165-6.626,8.282C328.504,134.102,332.21,137.07,336.329,136.611z"/>
|
||||||
|
<path d="M93.96,317.749c-8.302,1.74-16.615,3.911-24.708,6.454c-3.952,1.242-6.148,5.452-4.907,9.403
|
||||||
|
c1.007,3.204,3.964,5.254,7.153,5.254c0.745,0,1.502-0.112,2.25-0.347c7.628-2.396,15.464-4.443,23.288-6.083
|
||||||
|
c4.054-0.85,6.652-4.825,5.802-8.879S98.011,316.9,93.96,317.749z"/>
|
||||||
|
<path d="M223.502,338.859c3.189,0,6.147-2.051,7.153-5.254c1.241-3.952-0.956-8.162-4.907-9.403
|
||||||
|
c-32.073-10.076-65.331-13.842-98.844-11.188c-4.129,0.326-7.211,3.938-6.885,8.068s3.934,7.213,8.068,6.885
|
||||||
|
c31.591-2.499,62.935,1.048,93.165,10.546C222,338.748,222.757,338.859,223.502,338.859z"/>
|
||||||
|
<path d="M71.498,306.767c0.745,0,1.502-0.112,2.25-0.347c48.249-15.159,99.256-15.159,147.504,0
|
||||||
|
c3.952,1.24,8.162-0.956,9.403-4.907c1.241-3.952-0.956-8.162-4.907-9.403c-51.191-16.083-105.307-16.083-156.496,0
|
||||||
|
c-3.952,1.241-6.149,5.451-4.907,9.403C65.352,304.716,68.309,306.767,71.498,306.767z"/>
|
||||||
|
<path d="M71.498,274.859c0.745,0,1.502-0.112,2.25-0.347c27.681-8.697,56.411-12.412,85.399-11.037
|
||||||
|
c4.158,0.192,7.65-2.999,7.847-7.137c0.196-4.138-2.999-7.65-7.137-7.847c-30.756-1.456-61.236,2.483-90.605,11.71
|
||||||
|
c-3.952,1.242-6.149,5.452-4.907,9.403C65.352,272.81,68.309,274.859,71.498,274.859z"/>
|
||||||
|
<path d="M190.194,266.932c10.467,1.844,20.917,4.395,31.058,7.581c0.749,0.235,1.505,0.347,2.25,0.347
|
||||||
|
c3.189,0,6.147-2.051,7.153-5.254c1.241-3.952-0.956-8.162-4.907-9.403c-10.76-3.38-21.846-6.086-32.952-8.043
|
||||||
|
c-4.079-0.719-7.969,2.006-8.688,6.085C183.39,262.323,186.114,266.213,190.194,266.932z"/>
|
||||||
|
<path d="M118.678,185.702c-16.732,1.858-33.362,5.36-49.426,10.407c-3.952,1.241-6.148,5.451-4.907,9.403
|
||||||
|
c1.007,3.204,3.964,5.254,7.153,5.254c0.745,0,1.502-0.112,2.25-0.347c15.141-4.757,30.815-8.057,46.585-9.809
|
||||||
|
c4.117-0.457,7.083-4.165,6.626-8.282C126.503,188.212,122.788,185.244,118.678,185.702z"/>
|
||||||
|
<path d="M64.345,173.605c1.007,3.204,3.964,5.254,7.153,5.254c0.745,0,1.502-0.112,2.25-0.347
|
||||||
|
c32.797-10.305,66.752-13.604,100.923-9.809c4.116,0.46,7.825-2.509,8.282-6.626c0.458-4.117-2.509-7.825-6.626-8.282
|
||||||
|
c-36.253-4.027-72.278-0.526-107.075,10.406C65.3,165.444,63.104,169.654,64.345,173.605z"/>
|
||||||
|
<path d="M71.498,146.767c0.745,0,1.502-0.112,2.25-0.347c5.103-1.604,10.325-3.058,15.521-4.324
|
||||||
|
c4.024-0.98,6.492-5.037,5.512-9.062s-5.038-6.492-9.062-5.512c-5.513,1.342-11.053,2.886-16.468,4.587
|
||||||
|
c-3.951,1.242-6.148,5.452-4.907,9.403C65.352,144.716,68.309,146.767,71.498,146.767z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.0 KiB |
@ -147,6 +147,55 @@ $(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Botón "Entrar como"
|
||||||
|
$(document).on('click', '.btn-impersonate-user', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const username = $(this).data('username');
|
||||||
|
|
||||||
|
const title = window.languageBundle.get(['usuarios.impersonate.title']) || 'Entrar como usuario';
|
||||||
|
const textTpl = window.languageBundle.get(['usuarios.impersonate.text'])
|
||||||
|
|| 'Vas a iniciar sesión como <b>{0}</b>.';
|
||||||
|
const confirmText = window.languageBundle.get(['usuarios.impersonate.button']) || 'Entrar';
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title,
|
||||||
|
html: textTpl.replace('{0}', username),
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
buttonsStyling: false,
|
||||||
|
customClass: {
|
||||||
|
confirmButton: 'btn btn-info w-xs mt-2',
|
||||||
|
cancelButton: 'btn btn-light w-xs mt-2'
|
||||||
|
},
|
||||||
|
confirmButtonText: confirmText,
|
||||||
|
cancelButtonText: window.languageBundle.get(['app.cancelar']) || 'Cancelar',
|
||||||
|
}).then((result) => {
|
||||||
|
if (!result.isConfirmed) return;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/impersonate',
|
||||||
|
type: 'POST',
|
||||||
|
data: { username },
|
||||||
|
success: function () {
|
||||||
|
window.location.href = '/';
|
||||||
|
},
|
||||||
|
error: function (xhr) {
|
||||||
|
const msg = (xhr.responseJSON && xhr.responseJSON.message)
|
||||||
|
|| 'No se pudo iniciar sesión como ese usuario.';
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'No se pudo suplantar',
|
||||||
|
text: msg,
|
||||||
|
buttonsStyling: false,
|
||||||
|
customClass: {
|
||||||
|
confirmButton: 'btn btn-secondary me-2',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Submit del form en el modal
|
// Submit del form en el modal
|
||||||
$(document).on('submit', '#userForm', function (e) {
|
$(document).on('submit', '#userForm', function (e) {
|
||||||
|
|||||||
@ -0,0 +1,71 @@
|
|||||||
|
<div id="fidelity-banner" th:fragment="home-container-user">
|
||||||
|
|
||||||
|
<div class="ib-loyalty-banner">
|
||||||
|
<!-- Decoraciones -->
|
||||||
|
<div class="decor"></div>
|
||||||
|
<div class="book small"></div>
|
||||||
|
|
||||||
|
<div class="ib-loyalty-inner">
|
||||||
|
<!-- Panel principal -->
|
||||||
|
<div class="ib-loyalty-hero">
|
||||||
|
<div class="ib-loyalty-head">
|
||||||
|
<div class="ib-loyalty-logo">
|
||||||
|
<img src="/assets/images/logo-sm.png" alt="Logo" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 class="ib-loyalty-title">Programa de Fidelidad</h3>
|
||||||
|
<p class="ib-loyalty-sub">
|
||||||
|
Aumenta tus compras en los últimos 12 meses y obtén descuentos automáticos.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rewards -->
|
||||||
|
<div class="ib-rewards">
|
||||||
|
<h6>Recompensas</h6>
|
||||||
|
|
||||||
|
<div class="ib-rewards-grid">
|
||||||
|
<div class="ib-card ">
|
||||||
|
<div class="range">Menos de 1.200€</div>
|
||||||
|
<div class="percent">0%</div>
|
||||||
|
<span class="chip">Descuento</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ib-card">
|
||||||
|
<div class="range">1.200€ – 1.999€</div>
|
||||||
|
<div class="percent">1%</div>
|
||||||
|
<span class="chip">Descuento</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ib-card">
|
||||||
|
<div class="range">2.000€ – 2.999€</div>
|
||||||
|
<div class="percent">2%</div>
|
||||||
|
<span class="chip">Descuento</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ib-card">
|
||||||
|
<div class="range">3.000€ – 3.999€</div>
|
||||||
|
<div class="percent">3%</div>
|
||||||
|
<span class="chip">Descuento</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ib-card">
|
||||||
|
<div class="range">4.000€ – 4.999€</div>
|
||||||
|
<div class="percent">4%</div>
|
||||||
|
<span class="chip">Descuento</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ib-card">
|
||||||
|
<div class="range">más de 5.000€</div>
|
||||||
|
<div class="percent">5%</div>
|
||||||
|
<span class="chip">Descuento</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
@ -8,6 +8,8 @@
|
|||||||
<th:block layout:fragment="pagecss">
|
<th:block layout:fragment="pagecss">
|
||||||
<link th:href="@{/assets/css/presupuestador.css}" rel="stylesheet"
|
<link th:href="@{/assets/css/presupuestador.css}" rel="stylesheet"
|
||||||
th:unless="${#authorization.expression('isAuthenticated()')}" />
|
th:unless="${#authorization.expression('isAuthenticated()')}" />
|
||||||
|
<link th:href="@{/assets/css/home.css}" rel="stylesheet"
|
||||||
|
th:if="${#authorization.expression('isAuthenticated()')}" />
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
@ -22,7 +24,7 @@
|
|||||||
<th:block layout:fragment="content">
|
<th:block layout:fragment="content">
|
||||||
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
<div th:insert="~{imprimelibros/home/home-container-user :: home-container-user}"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div th:unless="${#authorization.expression('isAuthenticated()')}">
|
<div th:unless="${#authorization.expression('isAuthenticated()')}">
|
||||||
|
|||||||
@ -103,6 +103,14 @@
|
|||||||
<a class="dropdown-item" href="/apps-chat"><i
|
<a class="dropdown-item" href="/apps-chat"><i
|
||||||
class="mdi mdi-message-text-outline text-muted fs-16 align-middle me-1"></i>
|
class="mdi mdi-message-text-outline text-muted fs-16 align-middle me-1"></i>
|
||||||
<span class="align-middle" th:text="#{app.mensajes}">Mensajes</span></a>
|
<span class="align-middle" th:text="#{app.mensajes}">Mensajes</span></a>
|
||||||
|
<div sec:authorize="hasRole('PREVIOUS_ADMINISTRATOR')">
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item" href="#"
|
||||||
|
onclick="document.getElementById('exitImpersonationForm').submit(); return false;">
|
||||||
|
<i class="mdi mdi-account-switch text-muted fs-16 align-middle me-1"></i>
|
||||||
|
<span class="align-middle" th:text="#{app.impersonate.exit}">Volver a mi usuario</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<a class="dropdown-item" href="#"
|
<a class="dropdown-item" href="#"
|
||||||
onclick="document.getElementById('logoutForm').submit(); return false;">
|
onclick="document.getElementById('logoutForm').submit(); return false;">
|
||||||
@ -127,7 +135,10 @@
|
|||||||
<form id="logoutForm" th:action="@{/logout}" method="post" class="d-none">
|
<form id="logoutForm" th:action="@{/logout}" method="post" class="d-none">
|
||||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
</form>
|
</form>
|
||||||
|
<form id="exitImpersonationForm" th:action="@{/impersonate/exit}" method="post" class="d-none">
|
||||||
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||||
|
</form>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user