Merge branch 'dev' into 'features/searchBar'

# Conflicts:
#   src/components/Event/Event.jsx
#   src/index.css
#   src/pages/Events/EventsPage.jsx
#   src/utils/getUser.js
#   src/utils/getXSRF.js
#   src/utils/login.js
#   src/utils/register.js
This commit is contained in:
JOSSERAND GIOVANNI p2405212
2025-12-07 15:43:03 +00:00
49 changed files with 1146 additions and 581 deletions
+15 -12
View File
@@ -1,9 +1,12 @@
import styles from "./Event.module.css";
import {useState, useEffect} from "react";
import {Link} from "react-router";
import { useState, useEffect } from "react";
import { useNavigate } from "react-router";
import Button from "/src/components/ui/button/button.jsx"
function Event({event}){
const navigate = useNavigate();
const [eventMenu, seteventMenu] = useState(false);
const [windowSize, setWindowSize] = useState({
width: null,
@@ -31,7 +34,7 @@ function Event({event}){
<div className={styles.eventContent}>
<div className={`${styles.eventImageDiv}`}>
{event.image == null ? (
<img src="empty.png" className={styles.eventImage} alt="Pas de photo de l'événement" />
<img src="/empty.png" className={styles.eventImage} alt="Pas de photo de l'événement" />
) : (
<img src={event.image + `.png`} alt="Photo de l'événement" />
)}
@@ -57,21 +60,21 @@ function Event({event}){
</div>
{windowSize.width > 768 ? (
<div className={styles.eventButtons}>
<button
className={`${styles.moreButton} ${eventMenu ? styles.moreButtonClicked : ""}`}
onClick={()=>{seteventMenu(!eventMenu)}}>
<Button
variant={"transparent"}
onClick={()=>{seteventMenu(!eventMenu)}}
className={`${eventMenu ? styles.moreButtonClicked : ""}`}
>
{">"}
</button>
</Button>
</div>
) : null}
</div>
<div className={styles.eventContainerBottom}>
{(windowSize.width <= 768 || eventMenu) ? (
<Link
to={`/event/` + event.id}
className={styles.moreLink}>
<Button>En savoir plus </Button>
</Link>
<Button onClick={() => navigate(`/event/` + event.id)}>Voir plus</Button>
) : null}
</div>
</div>
+3 -13
View File
@@ -3,6 +3,7 @@
flex-direction: column;
margin: 1rem 0;
width: 80%;
padding: 1rem 0.5rem;
}
.eventContainerTop {
@@ -46,11 +47,6 @@
font-size: 2rem;
}
.eventButtons {
display: flex;
flex-direction: column;
@@ -64,9 +60,6 @@
white-space: nowrap;
}
.moreButton {
background: none;
border: none;
@@ -95,19 +88,16 @@
rotate: 90deg;
}
.eventMenuOpen {
display: flex;
flex-direction: column;
margin-left: 1.5em;
}
.eventMenuClose {
display: none;
}
@media screen and (max-width: 768px) {
.eventImage {
width: 100%;
+2 -2
View File
@@ -18,7 +18,7 @@ function Header() {
setNotifications(notifData || []);
}
loadNotifications();
//loadNotifications();
const handleClickOutside = (event) => {
if (
notificationRef.current &&
@@ -70,7 +70,7 @@ function Header() {
{unreadNotification && <span className={styles.notificationBadge}></span>}
</button>
<Link to="" className={styles.profileLink}>
<Link to="/profile" className={styles.profileLink}>
<svg xmlns="http://www.w3.org/2000/svg" height="30px" viewBox="0 -960 960 960" width="auto" fill="currentFill">
<path d="M480-492.31q-57.75 0-98.87-41.12Q340-574.56 340-632.31q0-57.75 41.13-98.87 41.12-41.13 98.87-41.13 57.75 0 98.87 41.13Q620-690.06 620-632.31q0 57.75-41.13 98.88-41.12 41.12-98.87 41.12ZM180-187.69v-88.93q0-29.38 15.96-54.42 15.96-25.04 42.66-38.5 59.3-29.07 119.65-43.61 60.35-14.54 121.73-14.54t121.73 14.54q60.35 14.54 119.65 43.61 26.7 13.46 42.66 38.5Q780-306 780-276.62v88.93H180Z" />
</svg>
+29
View File
@@ -0,0 +1,29 @@
import React, {useEffect, useState} from "react";
import getEventById from "../../utils/event/getEventById.js";
import styles from "./Task.module.css";
import formatDate from "../../utils/date/formatDate.js";
const Task = ({ task }) => {
const [event, setEvent] = useState(null);
useEffect(() => {
(async () => {
const e = await getEventById(task.id);
setEvent(e);
}) ()
}, [])
return (
<div className={`${styles.task} glassCard`}>
<div className={styles.content}>
<div>
<h2> {event && event.name} - {task.name}</h2>
<p className={styles.desc}> {task.description} </p>
<p className={styles.date}> {formatDate(task.start)} </p>
</div>
</div>
</div>
);
}
export default Task;
+22
View File
@@ -0,0 +1,22 @@
.task {
width: 100%;
padding: 12px;
margin-bottom: 10px;
border-radius: 8px;
}
.content {
display: flex;
justify-content: space-between;
flex-direction: column;
}
.desc {
font-size: 0.8rem;
color: #666;
margin: 0 0 0 0;
}
.date {
font-size: 0.8rem;
}
+21 -4
View File
@@ -1,8 +1,10 @@
import styles from "./background.module.css";
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
const Background = ({ children, className }) => {
const circlesRef = useRef([]);
const circlesRef = useRef([]);
const [theme, setTheme] = useState("light");
const getRandomTranslation = () => {
const randomX = Math.floor(Math.random() * 100) - 30;
@@ -20,8 +22,23 @@ const Background = ({ children, className }) => {
});
}, []);
return (
<div className={`${styles["background-container"]} ${className || ''}`}>
useEffect(() => {
const updateTheme = () => {
setTheme(localStorage.getItem("theme") || "light");
};
updateTheme(); // Initial load
window.addEventListener("theme-changed", updateTheme);
return () => {
window.removeEventListener("theme-changed", updateTheme);
};
}, []);
return (
<div className={`${styles.backgroundContainer} ${className || ''} ${theme === "light" ? styles.backgroundLight : styles.backgroundDark}`}>
<div
ref={(el) => (circlesRef.current[0] = el)}
className={`${styles["circle"]} ${styles["circle-1"]}`}
@@ -1,10 +1,9 @@
/* BACKGROUND ////////////////////////////////////////////////////////*/
.background-container {
.backgroundContainer {
z-index: 0;
margin: 0;
padding: 0;
min-height: 100vh;
background: #00F0FF;
overflow: hidden;
position: relative;
width: 100vw;
@@ -13,6 +12,14 @@
align-items: center;
}
.backgroundLight {
background: #00F0FF;
}
.backgroundDark {
background: #001C83;
}
.circle {
position: absolute;
border-radius: 50%;
@@ -82,7 +89,6 @@
.circle-1 {
width: 300px;
height: 300px;
background-color: #019AFF;
top: -5%;
left: -5%;
}
@@ -90,7 +96,6 @@
.circle-2 {
width: 175px;
height: 175px;
background-color: #91FCFF;
top: 58%;
left: 10%;
}
@@ -98,7 +103,6 @@
.circle-3 {
width: 140px;
height: 140px;
background-color: #91FCFF;
top: 29%;
right: 9%;
}
@@ -106,7 +110,6 @@
.circle-4 {
width: 250px;
height: 250px;
background-color: #019AFF;
bottom: 0;
right: 0;
}
@@ -114,7 +117,6 @@
.circle-5 {
width: 240px;
height: 240px;
background-color: #015AFF;
top: 2%;
right: 10%;
}
+3 -32
View File
@@ -9,7 +9,6 @@ import { AuthContext } from "../../contexts/auth/AuthContext.js";
function LoginForm({ className }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
let navigate = useNavigate();
const { login } = useContext(AuthContext);
@@ -19,12 +18,8 @@ function LoginForm({ className }) {
navigate("/");
}
const toggleShowPassword = () => {
setShowPassword(!showPassword);
};
return (
<div className={`${className} ${"glassCard"}`}>
<div className={`${className} glassCard ${styles.inputContainer}`}>
<div className={styles.formgroup}>
<Input
type="email"
@@ -35,35 +30,11 @@ function LoginForm({ className }) {
/>
</div>
<div className={styles.formgroup}>
<div style={{ position: 'relative', width: '100%' }}>
<Input
type={showPassword ? "text" : "password"}
value={password}
placeholder="Mot de passe"
onChange={e => setPassword(e.target.value)}
style={{ width: '100%' }}
/>
<button
type="button"
onClick={toggleShowPassword}
style={{
position: 'absolute',
width: '30px',
right: '10px',
top: '50%',
transform: 'translateY(-50%)',
background: 'none',
border: 'none',
cursor: 'pointer',
fontSize: '16px',
}}
>
{showPassword ? '👁️' : '🔒'}
</button>
</div>
<Input password={true} onChange={e => setPassword(e.target.value)} value={password} />
</div>
<div className={styles.logButton}>
<Button variant={"transparent"} onClick={() => navigate("/register")}>Vous n'avez pas de compte ?</Button>
<Button onClick={handleSubmit} variant={"default"}> Se connecter </Button>
</div>
</div>
@@ -18,14 +18,21 @@ input::placeholder{
font-size: 20px;
color: rgba(0,0,0,0.45);
}
.formgroup{
margin-top:23px;
}
.logButton{
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
margin-top: 12px;
}
.formgroup{
margin-top:23px;
.inputContainer {
padding: 15px;
}
/* style scoped (module) */
+2 -2
View File
@@ -1,6 +1,6 @@
import styles from "./button.module.css";
const Button = ({ children, variant = "primary", onClick }) => {
const Button = ({ children, variant = "primary", onClick, className }) => {
let btnStyle;
if (variant === "primary") btnStyle = styles.primary;
@@ -8,7 +8,7 @@ const Button = ({ children, variant = "primary", onClick }) => {
else if(variant === "transparent") btnStyle = styles.transparent;
else btnStyle = styles.default;
return <div className={`${styles.btn} ${btnStyle}`} onClick={onClick}>
return <div className={`${styles.btn} ${btnStyle} ${className}`} onClick={onClick}>
{children}
</div>;
};
+27 -2
View File
@@ -1,13 +1,38 @@
import styles from "./input.module.css";
import { useState } from "react";
const TextInput = ({ placeholder, onChange, value, borderStyle, ...props }) => {
const TextInput = ({ placeholder, onChange, value, borderStyle, password, className, ...props }) => {
const [showPassword, setShowPassword] = useState(false);
let inputBorderStyle = styles.square;
if (borderStyle === "square") inputBorderStyle = styles.square;
if(borderStyle === "rounded") inputBorderStyle = styles.rounded;
return <input className={`${styles.input} ${inputBorderStyle}`} onChange={onChange} placeholder={placeholder} value={value} {...props} />
const toggleShowPassword = () => {
setShowPassword(!showPassword);
};
if(password) return (
<div className={styles.passwordContainer}>
<TextInput
type={showPassword ? "text" : "password"}
value={value}
placeholder="Mot de passe"
onChange={onChange}
className={styles.passwordInput}
/>
<button
type="button"
onClick={toggleShowPassword}
className={styles.passwordBtn}
>
{showPassword ? '👁️' : '🔒'}
</button>
</div>
);
return <input className={`${styles.input} ${inputBorderStyle} ${className}`} onChange={onChange} placeholder={placeholder} value={value} {...props} />
};
export default TextInput;
+20
View File
@@ -30,3 +30,23 @@
border-radius: 12px;
}
.passwordContainer {
position: relative;
width: 100%;
}
.passwordInput {
width: 100%;
}
.passwordBtn {
position: absolute;
width: 30px;
right: 10px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
font-size: 16px;
}
+23
View File
@@ -0,0 +1,23 @@
import styles from "./modal.module.css"
import Button from "../button/button.jsx";
const Modal = ({ open, onClose, children, title }) => {
if(!open) return null;
return <div className={styles.overlay}>
<div className={styles.modal}>
{title && <h2>{title}</h2>}
{children}
<div className={styles.modalFooter}> <Button onClick={onClose}>Fermer</Button> </div>
</div>
</div>
}
export default Modal;
+35
View File
@@ -0,0 +1,35 @@
.overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.25);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal {
padding: 20px 20px 20px 20px;
border-radius: 8px;
min-width: 300px;
max-width: 70%;
position: relative;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
width: fit-content;
border: 1px solid rgba(200, 200, 200, 0.3);
background: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
color: #000;
outline: none;
}
.modalFooter {
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
@@ -0,0 +1,19 @@
import Button from "../button/button.jsx";
import React from "react";
export default function ThemeSwitcher() {
const switchTheme = () => {
const currentTheme = localStorage.getItem("theme");
let newTheme = currentTheme === "light" ? "dark" : "light";
if(currentTheme === null) newTheme = "dark";
localStorage.setItem("theme", newTheme);
window.dispatchEvent(new Event("theme-changed"));
};
return <Button variant={"default"} onClick={switchTheme}> Changer de thème </Button>
}