correction bug admin + lecture de toute les session dans l'edt pour l'admin

This commit is contained in:
trochas
2026-01-11 16:56:10 +01:00
parent ecbddd3a58
commit ddb2b93489
16 changed files with 176 additions and 51 deletions

View File

@@ -1,12 +1,10 @@
package hackathon.FrisbYEE.jpa.dto; package hackathon.FrisbYEE.jpa.dto;
import hackathon.FrisbYEE.jpa.metier.Role;
import lombok.Data; import lombok.Data;
@Data @Data
public class AdminDTO { public class AdminDTO {
private String id_keycloak;
private Integer id; private Integer id;
private String id_keycloak;
private String name; private String name;
private String prenom; private String prenom;
private Role role;
} }

View File

@@ -3,17 +3,12 @@ package hackathon.FrisbYEE.rest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import hackathon.FrisbYEE.jpa.dto.AdminDTO; import hackathon.FrisbYEE.jpa.dto.AdminDTO;
import hackathon.FrisbYEE.jpa.metier.Admin; import hackathon.FrisbYEE.jpa.metier.Admin;
import hackathon.FrisbYEE.jpa.service.AdminDAO; import hackathon.FrisbYEE.jpa.service.AdminDAO;
import io.swagger.v3.oas.annotations.parameters.RequestBody; import hackathon.FrisbYEE.jpa.service.UserDAO;
@RestController @RestController
@RequestMapping("/admin") @RequestMapping("/admin")
@@ -22,12 +17,24 @@ public class AdminResource {
@Autowired @Autowired
private AdminDAO adminDAO; private AdminDAO adminDAO;
@Autowired
private UserDAO userDAO;
@PostMapping("/create") @PostMapping("/create")
@PreAuthorize("hasRole('Admin')") // Only admin can create @PreAuthorize("hasRole('admin')") // Only admin can create
public ResponseEntity<AdminDTO> create(@RequestBody AdminDTO dto) { public ResponseEntity<AdminDTO> create(@RequestBody AdminDTO dto) {
userDAO.findByKeycloakId(dto.getId_keycloak())
.ifPresent(existing -> {
if (!(existing instanceof Admin)) {
userDAO.delete(existing);
userDAO.flush();
}
});
Admin admin = mapToEntity(dto); Admin admin = mapToEntity(dto);
if(adminDAO.findByKeycloakId(admin.getKeycloakId()).isPresent()) { if(adminDAO.findByKeycloakId(admin.getKeycloakId()).isPresent()) {
return ResponseEntity.status(200).body(mapToDTO(adminDAO.findByKeycloakId(admin.getKeycloakId()).get())); return ResponseEntity.status(200).body(mapToDTO(adminDAO.findByKeycloakId(admin.getKeycloakId()).get()));
} }
@@ -55,7 +62,6 @@ public class AdminResource {
dto.setId_keycloak(admin.getKeycloakId()); dto.setId_keycloak(admin.getKeycloakId());
dto.setName(admin.getName()); dto.setName(admin.getName());
dto.setPrenom(admin.getPrenom()); dto.setPrenom(admin.getPrenom());
dto.setRole(admin.getRole());
return dto; return dto;
} }
@@ -65,7 +71,7 @@ public class AdminResource {
admin.setKeycloakId(dto.getId_keycloak()); admin.setKeycloakId(dto.getId_keycloak());
admin.setName(dto.getName()); admin.setName(dto.getName());
admin.setPrenom(dto.getPrenom()); admin.setPrenom(dto.getPrenom());
admin.setRole(dto.getRole()); admin.setRole(hackathon.FrisbYEE.jpa.metier.Role.admin);
return admin; return admin;
} }

View File

@@ -1,8 +1,11 @@
package hackathon.FrisbYEE.rest; package hackathon.FrisbYEE.rest;
import hackathon.FrisbYEE.jpa.dto.CoachDTO; import hackathon.FrisbYEE.jpa.dto.CoachDTO;
import hackathon.FrisbYEE.jpa.metier.Admin;
import hackathon.FrisbYEE.jpa.metier.Coach; import hackathon.FrisbYEE.jpa.metier.Coach;
import hackathon.FrisbYEE.jpa.service.CoachDAO; import hackathon.FrisbYEE.jpa.service.CoachDAO;
import hackathon.FrisbYEE.jpa.service.UserDAO;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -17,13 +20,26 @@ import java.util.List;
@RestController @RestController
@RequestMapping("/coach") @RequestMapping("/coach")
public class CoachResource { public class CoachResource {
@Autowired @Autowired
private CoachDAO coachDAO; private CoachDAO coachDAO;
@Autowired
private UserDAO userDAO;
@PostMapping("/create") @PostMapping("/create")
@PreAuthorize("hasRole('Admin')") // Only admin can create @PreAuthorize("hasRole('admin') or hasRole('coach')") // Only admin can create
public ResponseEntity<CoachDTO> create(@RequestBody CoachDTO dto) { public ResponseEntity<CoachDTO> create(@RequestBody CoachDTO dto) {
userDAO.findByKeycloakId(dto.getId_keycloak())
.ifPresent(existing -> {
if (!(existing instanceof Coach)) {
userDAO.delete(existing);
userDAO.flush();
}
});
Coach coach = mapToEntity(dto); Coach coach = mapToEntity(dto);
if(coachDAO.existsByKeycloakId(coach.getKeycloakId())) { if(coachDAO.existsByKeycloakId(coach.getKeycloakId())) {
return ResponseEntity.status(200).body(mapToDTO(coachDAO.findByKeycloakId(coach.getKeycloakId()).get())); return ResponseEntity.status(200).body(mapToDTO(coachDAO.findByKeycloakId(coach.getKeycloakId()).get()));
} }

View File

@@ -19,6 +19,7 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -69,6 +70,30 @@ public class SessionResource {
return ResponseEntity.ok(dtos); return ResponseEntity.ok(dtos);
} }
@GetMapping("/all-between-dates")
@PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')")
public ResponseEntity<List<SessionDTO>> getAllBetweenDates(
@RequestParam LocalDate startDate,
@RequestParam LocalDate endDate
) {
List<Session> sessions = sessionDAO.findAll();
List<SessionDTO> dtos = new ArrayList<>();
System.out.println("date : " + startDate + " " + endDate);
for (Session session : sessions) {
LocalDate sessionDate = session.getCreneau().toLocalDate();
boolean isBetween =
(!sessionDate.isBefore(startDate) || session.getIsRecurrent()) &&
!sessionDate.isAfter(endDate);
if (isBetween) {
dtos.add(maptoDTO(session));
}
}
return ResponseEntity.ok(dtos);
}
@GetMapping("/{id}") @GetMapping("/{id}")
@PreAuthorize("hasRole('coach') or hasRole('athlete')") @PreAuthorize("hasRole('coach') or hasRole('athlete')")
public ResponseEntity<?> getById(@PathVariable Integer id) { public ResponseEntity<?> getById(@PathVariable Integer id) {

View File

@@ -62,11 +62,16 @@ export const activiteService = {
getByTheme: (theme: string) => api.get(`/activite/theme/${encodeURIComponent(theme)}`), getByTheme: (theme: string) => api.get(`/activite/theme/${encodeURIComponent(theme)}`),
getDataActivite: (id: number | string) => api.get(`/activite/${id}`), getDataActivite: (id: number | string) => api.get(`/activite/${id}`),
}; };
type DateBetween = {
startDate: string;
endDate: string;
}
export const sessionService = { export const sessionService = {
// controller uses singular /session/* endpoints // controller uses singular /session/* endpoints
create: (data: any) => api.post(`/session/create`, data), create: (data: SessionDTO) => api.post(`/session/create`, data),
getAll: () => api.get<SessionDTO[]>(`/session/all`), getAll: () => api.get<SessionDTO[]>(`/session/all`),
getAllBetweenDate: (data: any) => api.get<SessionDTO[]>(`/session/all-between-dates`,{params: data,}),
getById: (id: number | null) => api.get(`/session/${id}`), getById: (id: number | null) => api.get(`/session/${id}`),
delete: (id: number | null) => api.delete(`/session/delete/${id}`), delete: (id: number | null) => api.delete(`/session/delete/${id}`),
update: (id: number | null, data: any) => api.put(`/session/update/${id}`, data), update: (id: number | null, data: any) => api.put(`/session/update/${id}`, data),
@@ -79,7 +84,7 @@ export const sessionService = {
export const coachService = { export const coachService = {
// controller doesn't declare a class-level path consistently; support both common patterns // controller doesn't declare a class-level path consistently; support both common patterns
create: (data: any) => api.post<CoachDTO>(`/coach/create`, data), create: (data: CoachDTO) => api.post<CoachDTO>(`/coach/create`, data),
getAll: () => api.get<CoachDTO[]>(`/coach/all`), getAll: () => api.get<CoachDTO[]>(`/coach/all`),
getById: (id: number) => api.get(`/coach/${id}`), getById: (id: number) => api.get(`/coach/${id}`),
getByKeycloakId: (keycloakId: string) => api.get(`/coach/keycloak/${keycloakId}`), getByKeycloakId: (keycloakId: string) => api.get(`/coach/keycloak/${keycloakId}`),
@@ -97,7 +102,8 @@ export const userService = {
export const adminService = { export const adminService = {
getByKeycloakId: (keycloak_id: string) => api.get(`/admin/keycloak/${keycloak_id}`), getByKeycloakId: (keycloak_id: string) => api.get(`/admin/keycloak/${keycloak_id}`),
getById: (id: number | string) => api.get(`/admin/${id}`), getById: (id: number | string) => api.get<AdminDTO>(`/admin/${id}`),
create: (data: AdminDTO) => api.post<AdminDTO>("/admin/create", data),
}; };
export default api; export default api;

View File

@@ -55,7 +55,7 @@ export class Athlete extends User{
constructor(dto?:AthleteDTO){ constructor(dto?:AthleteDTO){
super(); super();
this.id = dto?.id ?? 0; this.id = dto?.id ?? null;
this.keycloakId = dto?.id_keycloak ?? ""; this.keycloakId = dto?.id_keycloak ?? "";
this.nom = dto?.name ?? ""; this.nom = dto?.name ?? "";
this.prenom = dto?.prenom ?? "" ; this.prenom = dto?.prenom ?? "" ;
@@ -90,7 +90,7 @@ export class Coach extends User{
constructor(dto?:CoachDTO){ constructor(dto?:CoachDTO){
super(); super();
this.id = dto?.id ?? 0; this.id = dto?.id ?? null;
this.keycloakId = dto?.id_keycloak ?? ""; this.keycloakId = dto?.id_keycloak ?? "";
this.nom = dto?.name ?? ""; this.nom = dto?.name ?? "";
this.prenom = dto?.prenom ?? ""; this.prenom = dto?.prenom ?? "";

View File

@@ -6,7 +6,7 @@ import { createSessionAPI, postSession } from "../requetes";
import './style/createSession.css'; import './style/createSession.css';
export const CreateSession = () => { export const CreateSession = () => {
const {user} = useLocalData(); const {userLocal: user} = useLocalData();
const [session,setSession] = useState<Session>(new Session()); const [session,setSession] = useState<Session>(new Session());
const [activities, setActivities] = useState<Activite[]>([]); const [activities, setActivities] = useState<Activite[]>([]);
const [name,setName] = useState(""); const [name,setName] = useState("");

View File

@@ -1,8 +1,8 @@
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { Athlete, Coach, Session} from "../classes" import { Admin, Athlete, Coach, Session} from "../classes"
import { useLocalData } from "../context/useLocalData" import { useLocalData } from "../context/useLocalData"
import './style/edt.css'; import './style/edt.css';
import {getSessionsOfUserAPI } from "../requetes"; import {getAllSessionsAPI, getAllSessionsBetweenAPI, getSessionsOfUserAPI } from "../requetes";
import EdtSession from "./edt_session"; import EdtSession from "./edt_session";
import {delay} from "../requetes"; import {delay} from "../requetes";
import Loading from "./loading"; import Loading from "./loading";
@@ -26,7 +26,8 @@ export function hoursToString(date:Date){
export const EDT =() =>{ export const EDT =() =>{
const {user,setUser} = useLocalData() const {userLocal} = useLocalData()
const {sessionsLocal,setSessionsLocal} = useLocalData()
const [sessions, setSessions] = useState<Session[]>([]) const [sessions, setSessions] = useState<Session[]>([])
const [week,setWeek] = useState<Date>(getFirstDay(new Date())); const [week,setWeek] = useState<Date>(getFirstDay(new Date()));
const [loadedWeek,setLoadedWeek] = useState<Date|null>(null); const [loadedWeek,setLoadedWeek] = useState<Date|null>(null);
@@ -35,12 +36,21 @@ export const EDT =() =>{
const week_days_nums:number[] = [1,2,3,4,5,6,0]; const week_days_nums:number[] = [1,2,3,4,5,6,0];
function loadSessions(date:Date){ function loadSessions(date:Date){
var maxDate = getNextDay(date,6) var maxDate = toDateOnly(getNextDay(date,6));
var newWeek: Session[] = [] var newWeek: Session[] = []
if(user instanceof Athlete || user instanceof Coach){ if(userLocal instanceof Athlete || userLocal instanceof Coach){
user.sessions.forEach(session => { userLocal.sessions.forEach(session => {
if((session.creneau >= date && session.creneau <= maxDate && !session.isRecurrent) || (session.isRecurrent && session.creneau<maxDate)){ const creneau = toDateOnly(session.creneau);
if((creneau >= date || session.isRecurrent) && (creneau <= maxDate)){
newWeek.push(session);
}
});
}
else if(userLocal instanceof Admin){
sessionsLocal.forEach(session => {
const creneau = toDateOnly(session.creneau);
if((creneau >= date || session.isRecurrent) && (creneau <= maxDate)){
newWeek.push(session); newWeek.push(session);
} }
}); });
@@ -50,7 +60,7 @@ export const EDT =() =>{
} }
function changeWeek(date:Date){ function changeWeek(date:Date){
setWeek(date); setWeek(toDateOnly(date));
} }
function isSameDay(date1:Date,date2:Date){ function isSameDay(date1:Date,date2:Date){
@@ -65,7 +75,7 @@ export const EDT =() =>{
updateWeek(week); updateWeek(week);
loadSessions(week) loadSessions(week)
setLoading(true); setLoading(true);
},[week,user]) },[week,userLocal])
useEffect(() => { useEffect(() => {
if(loadedWeek!==null){ if(loadedWeek!==null){
@@ -83,9 +93,22 @@ export const EDT =() =>{
//TODO updateSession //TODO updateSession
//await delay(2000); //await delay(2000);
//await updateSessionsOfUser(user,null,null); //await updateSessionsOfUser(user,null,null);
if(user instanceof Athlete || user instanceof Coach){ if(userLocal instanceof Athlete || userLocal instanceof Coach){
const newSessions:Session[] = await getSessionsOfUserAPI(user); const newSessions:Session[] = await getSessionsOfUserAPI(userLocal);
user.sessions = newSessions; userLocal.sessions = newSessions;
}
else if(userLocal instanceof Admin){
const newSessions:Session[] = await getAllSessionsBetweenAPI(week,getNextDay(week,6));
const date = toDateOnly(week);
var maxDate = toDateOnly(getNextDay(date,6));
sessionsLocal.forEach(sessionLocal => { //update seulement la semaine
const creneau = toDateOnly(sessionLocal.creneau);
if(!((creneau >= date || sessionLocal.isRecurrent) && (creneau <= maxDate))){
newSessions.push(sessionLocal);
}
});
setSessionsLocal(newSessions);
} }
setLoadedWeek(week); setLoadedWeek(week);
} }
@@ -108,7 +131,15 @@ export const EDT =() =>{
else{ else{
firstDate = getNextDay(date,-numWeek+1); firstDate = getNextDay(date,-numWeek+1);
} }
return firstDate; return toDateOnly(firstDate);
}
function toDateOnly(date: Date): Date {
return new Date(
date.getFullYear(),
date.getMonth(),
date.getDate()
);
} }
function getNextDay(date:Date,nb:number):Date{ function getNextDay(date:Date,nb:number):Date{

View File

@@ -9,7 +9,7 @@ import { Modal } from './Modal';
import './style/topBar.css'; import './style/topBar.css';
export const Login =() =>{ export const Login =() =>{
const {user,setUser} = useLocalData() const {userLocal: user,setUserLocal: setUser} = useLocalData()
const { keycloak } = useKeycloak(); const { keycloak } = useKeycloak();
const [open,setOpen] = useState<boolean>(false); const [open,setOpen] = useState<boolean>(false);

View File

@@ -15,7 +15,7 @@ type Props = {
function DetailSession({session,open,setOpen}:Props){ function DetailSession({session,open,setOpen}:Props){
const {user,setUser} = useLocalData() const {userLocal: user,setUserLocal: setUser} = useLocalData()
const [activites,setActivites] = useState<Activite[]>([]); const [activites,setActivites] = useState<Activite[]>([]);
const [open2, setOpen2] = useState<boolean>(false); const [open2, setOpen2] = useState<boolean>(false);

View File

@@ -104,6 +104,9 @@ function ObjectUser({user}:Props){
<div className="object_modal"> <div className="object_modal">
<div>{user.prenom}</div> <div>{user.prenom}</div>
<div>{user.nom}</div> <div>{user.nom}</div>
{user instanceof Athlete && <div>Role : Athlete</div>}
{user instanceof Coach && <div>Role : Coach</div>}
{user instanceof Admin && <div>Role : Admin</div>}
{(user instanceof Athlete || user instanceof Coach) && {(user instanceof Athlete || user instanceof Coach) &&
<div className='padding'> <div className='padding'>
<div className='list_object_modal'> <div className='list_object_modal'>

View File

@@ -15,7 +15,7 @@ import ObjectLigne from "./object/lignes";
export default function RessourcePanel() { export default function RessourcePanel() {
const { keycloak } = useKeycloak(); const { keycloak } = useKeycloak();
const { user } = useLocalData(); const { userLocal: user } = useLocalData();
//const user = getUserTest(); //TODO //const user = getUserTest(); //TODO
const [value,setValue] = useState<keyWord>("sessions"); const [value,setValue] = useState<keyWord>("sessions");

View File

@@ -2,12 +2,10 @@ import { createContext } from 'react'
import { Session, User } from '../classes'; import { Session, User } from '../classes';
interface LocalDataContextType { interface LocalDataContextType {
user:User; userLocal:User;
setUser: React.Dispatch<React.SetStateAction<User>> setUserLocal: React.Dispatch<React.SetStateAction<User>>
sessions: Session[]; sessionsLocal: Session[];
setSessions: React.Dispatch<React.SetStateAction<Session[]>> setSessionsLocal: React.Dispatch<React.SetStateAction<Session[]>>
users: User[];
setUsers: React.Dispatch<React.SetStateAction<User[]>>
} }

View File

@@ -3,15 +3,14 @@ import { Session, User } from '../classes'
import { LocalDataContext } from '../context/LocalDataContext' import { LocalDataContext } from '../context/LocalDataContext'
export const LocalDataProvider = ({ children }: { children: React.ReactNode }) => { export const LocalDataProvider = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<User>(new User()) const [userLocal, setUserLocal] = useState<User>(new User())
const [sessions, setSessions] = useState<Session[]>([]) const [sessionsLocal, setSessionsLocal] = useState<Session[]>([])
const [users, setUsers] = useState<User[]>([])
return ( return (
<LocalDataContext.Provider <LocalDataContext.Provider
value={{ user, setUser, sessions, setSessions, users, setUsers }}> value={{ userLocal, setUserLocal, sessionsLocal,setSessionsLocal}}>
{children} {children}
</LocalDataContext.Provider> </LocalDataContext.Provider>
) )

View File

@@ -1,4 +1,4 @@
import api, { activiteService, athleteService, coachService, sessionService } from "./api"; import api, { activiteService, adminService, athleteService, coachService, sessionService } from "./api";
import { Activite, Admin, Athlete, Coach, Session, User } from "./classes"; import { Activite, Admin, Athlete, Coach, Session, User } from "./classes";
import Keycloak from 'keycloak-js' import Keycloak from 'keycloak-js'
import { AdminDTO, AthleteDTO, CoachDTO, SessionDTO } from "./classesDTO"; import { AdminDTO, AthleteDTO, CoachDTO, SessionDTO } from "./classesDTO";
@@ -27,7 +27,9 @@ export async function loginOrRegister(keycloak:Keycloak): Promise<User|null>{
newAdmin.email = keycloak.tokenParsed.email || ""; newAdmin.email = keycloak.tokenParsed.email || "";
newAdmin.nom = keycloak.tokenParsed.family_name || ""; newAdmin.nom = keycloak.tokenParsed.family_name || "";
newAdmin.prenom = keycloak.tokenParsed.given_name || ""; newAdmin.prenom = keycloak.tokenParsed.given_name || "";
const response = await athleteService.create(newAdmin.toDTO()); console.log(newAdmin.keycloakId);
console.log(newAdmin.toDTO().id_keycloak);
const response = await adminService.create(newAdmin.toDTO());
const admin = new Admin(response.data); const admin = new Admin(response.data);
return admin; return admin;
} }
@@ -217,7 +219,7 @@ export async function postAthlete(athlete: Athlete):Promise<Athlete>{
export async function postSession(session: Session){ export async function postSession(session: Session){
try { try {
const data = { /* const data = {
name: session.name, name: session.name,
creneau: session.creneau, // string ISO OK creneau: session.creneau, // string ISO OK
duree: session.duree, duree: session.duree,
@@ -225,9 +227,9 @@ export async function postSession(session: Session){
coachId: session.coach?.id, coachId: session.coach?.id,
groupe: session.groupe ? session.groupe : undefined, groupe: session.groupe ? session.groupe : undefined,
} }*/
const response = await sessionService.create(data); const response = await sessionService.create(session.toDTO());
session.id = response.data.id; //TODO ? session.id = response.data.id; //TODO ?
session.activites.forEach(activite => { session.activites.forEach(activite => {
@@ -331,6 +333,44 @@ export async function getAllSessionsAPI():Promise<Session[]>{
} }
} }
function formatDateLocal(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
export async function getAllSessionsBetweenAPI(d1:Date,d2:Date):Promise<Session[]>{
try {
const data = {
startDate: formatDateLocal(d1),
endDate: formatDateLocal(d2)
}
console.log(d1 + " " + d2);
console.log(data);
const response = await sessionService.getAllBetweenDate(data);
const sessions = await Promise.all(
response.data.map(async sessionDTO => {
const session = new Session(sessionDTO);
const coach = await getCoachByIdAPI(sessionDTO.coachId);
if (coach != null) {
session.coach = coach;
}
return session;
})
);
return sessions;
} catch (error) {
console.error("Error fetching sessions:", error);
throw error;
}
}
//COACH //COACH
export async function getAllCoach(): Promise<Coach[]> { export async function getAllCoach(): Promise<Coach[]> {
try { try {

View File

@@ -30,6 +30,8 @@
background-color: rgba(255, 255, 255, 0.98); background-color: rgba(255, 255, 255, 0.98);
overflow: hidden; overflow: hidden;
transition: transform 0.3s ease; transition: transform 0.3s ease;
width: fit-content;
margin: 0 auto;
} }
.card-pf:hover { .card-pf:hover {
@@ -38,6 +40,7 @@
0 8px 20px rgba(16, 185, 129, 0.15); 0 8px 20px rgba(16, 185, 129, 0.15);
} }
/* Header de la card */ /* Header de la card */
#kc-form-login .card-pf h1, #kc-form-login .card-pf h1,
.login-pf-page h1 { .login-pf-page h1 {