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:
commit
c233d4cda3
49 files changed
+1146
-581
No files matched your search
@@ -0,0 +1 @@
|
||||
VITE_API_URL=
|
||||
@@ -25,3 +25,5 @@ dist-ssr
|
||||
|
||||
*storybook.log
|
||||
storybook-static
|
||||
|
||||
.env
|
||||
Generated
+2
@@ -8,7 +8,9 @@
|
||||
"name": "sae-front",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"chart.js": "^4.5.1",
|
||||
"react": "^19.1.1",
|
||||
"react-chartjs-2": "^5.3.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-router": "^7.9.4"
|
||||
},
|
||||
|
||||
+8
-5
@@ -9,10 +9,13 @@
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
"build-storybook": "storybook build",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"chart.js": "^4.5.1",
|
||||
"react": "^19.1.1",
|
||||
"react-chartjs-2": "^5.3.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-router": "^7.9.4"
|
||||
},
|
||||
@@ -26,17 +29,17 @@
|
||||
"@types/react": "^19.1.16",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"@vitest/browser-playwright": "^4.0.8",
|
||||
"@vitest/coverage-v8": "^4.0.8",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.22",
|
||||
"eslint-plugin-storybook": "^10.0.6",
|
||||
"globals": "^16.4.0",
|
||||
"playwright": "^1.56.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"storybook": "^10.0.6",
|
||||
"vite": "^7.1.7",
|
||||
"vitest": "^4.0.8",
|
||||
"playwright": "^1.56.1",
|
||||
"@vitest/browser-playwright": "^4.0.8",
|
||||
"@vitest/coverage-v8": "^4.0.8"
|
||||
"vitest": "^4.0.8"
|
||||
}
|
||||
}
|
||||
Generated
+30
@@ -8,9 +8,15 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
chart.js:
|
||||
specifier: ^4.5.1
|
||||
version: 4.5.1
|
||||
react:
|
||||
specifier: ^19.1.1
|
||||
version: 19.2.0
|
||||
react-chartjs-2:
|
||||
specifier: ^5.3.1
|
||||
version: 5.3.1(chart.js@4.5.1)(react@19.2.0)
|
||||
react-dom:
|
||||
specifier: ^19.1.1
|
||||
version: 19.2.0(react@19.2.0)
|
||||
@@ -436,6 +442,9 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@kurkle/color@0.3.4':
|
||||
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
|
||||
|
||||
'@mdx-js/react@3.1.1':
|
||||
resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==}
|
||||
peerDependencies:
|
||||
@@ -949,6 +958,10 @@ packages:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
chart.js@4.5.1:
|
||||
resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==}
|
||||
engines: {pnpm: '>=8'}
|
||||
|
||||
check-error@2.1.1:
|
||||
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
|
||||
engines: {node: '>= 16'}
|
||||
@@ -1510,6 +1523,12 @@ packages:
|
||||
queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
react-chartjs-2@5.3.1:
|
||||
resolution: {integrity: sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==}
|
||||
peerDependencies:
|
||||
chart.js: ^4.1.1
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
react-docgen-typescript@2.4.0:
|
||||
resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==}
|
||||
peerDependencies:
|
||||
@@ -2186,6 +2205,8 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@kurkle/color@0.3.4': {}
|
||||
|
||||
'@mdx-js/react@3.1.1(@types/react@19.2.2)(react@19.2.0)':
|
||||
dependencies:
|
||||
'@types/mdx': 2.0.13
|
||||
@@ -2738,6 +2759,10 @@ snapshots:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
chart.js@4.5.1:
|
||||
dependencies:
|
||||
'@kurkle/color': 0.3.4
|
||||
|
||||
check-error@2.1.1: {}
|
||||
|
||||
chromatic@12.2.0: {}
|
||||
@@ -3255,6 +3280,11 @@ snapshots:
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
react-chartjs-2@5.3.1(chart.js@4.5.1)(react@19.2.0):
|
||||
dependencies:
|
||||
chart.js: 4.5.1
|
||||
react: 19.2.0
|
||||
|
||||
react-docgen-typescript@2.4.0(typescript@5.9.3):
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 4H7.2C6.0799 4 5.51984 4 5.09202 4.21799C4.71569 4.40974 4.40973 4.7157 4.21799 5.09202C4 5.51985 4 6.0799 4 7.2V16.8C4 17.9201 4 18.4802 4.21799 18.908C4.40973 19.2843 4.71569 19.5903 5.09202 19.782C5.51984 20 6.0799 20 7.2 20H16.8C17.9201 20 18.4802 20 18.908 19.782C19.2843 19.5903 19.5903 19.2843 19.782 18.908C20 18.4802 20 17.9201 20 16.8V12.5M15.5 5.5L18.3284 8.32843M10.7627 10.2373L17.411 3.58902C18.192 2.80797 19.4584 2.80797 20.2394 3.58902C21.0205 4.37007 21.0205 5.6364 20.2394 6.41745L13.3774 13.2794C12.6158 14.0411 12.235 14.4219 11.8012 14.7247C11.4162 14.9936 11.0009 15.2162 10.564 15.3882C10.0717 15.582 9.54378 15.6885 8.48793 15.9016L8 16L8.04745 15.6678C8.21536 14.4925 8.29932 13.9048 8.49029 13.3561C8.65975 12.8692 8.89125 12.4063 9.17906 11.9786C9.50341 11.4966 9.92319 11.0768 10.7627 10.2373Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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,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%;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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%;
|
||||
}
|
||||
|
||||
@@ -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) */
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect } from "react";
|
||||
import loginFunction from "../../utils/login.js";
|
||||
import getUser from "../../utils/getUser.js";
|
||||
import { AuthContext } from "./AuthContext";
|
||||
import logoutFunction from "../../utils/auth/logout.js"
|
||||
|
||||
|
||||
export function AuthProvider({ children }) {
|
||||
@@ -25,7 +26,7 @@ export function AuthProvider({ children }) {
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
// TODO: Appeller fonction logout
|
||||
await logoutFunction();
|
||||
setUser(null);
|
||||
};
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ body {
|
||||
|
||||
.glassCard {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
height: fit-content;
|
||||
background: rgba(255, 255, 255, 0.65);
|
||||
backdrop-filter: blur(3px);
|
||||
|
||||
+10
-139
@@ -1,4 +1,4 @@
|
||||
import {useState} from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Event from "../../components/Event/Event.jsx";
|
||||
import styles from "./EventsPage.module.css";
|
||||
import ToolBar from "../../components/ToolBar/ToolBar.jsx";
|
||||
@@ -36,144 +36,15 @@ function EventsPage(){
|
||||
volunteers_count: 3
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Festival de Musique d’Été",
|
||||
description: "Un week-end de concerts en plein air avec des artistes locaux et internationaux.",
|
||||
image: null,
|
||||
tasks: [
|
||||
{
|
||||
name: "Montage de la scène",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Parc central",
|
||||
description: "Installer la scène, les éclairages et le son.",
|
||||
volunteers_count: 6
|
||||
},
|
||||
{
|
||||
name: "Accueil du public",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Entrée principale",
|
||||
description: "Vérifier les billets et orienter les visiteurs.",
|
||||
volunteers_count: 4
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Conférence Tech Innovante",
|
||||
description: "Journée de conférences et workshops autour des nouvelles technologies.",
|
||||
image: null,
|
||||
tasks: [
|
||||
{
|
||||
name: "Préparation des salles",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Campus Tech",
|
||||
description: "Installer les chaises, projecteurs et matériel pour les conférences.",
|
||||
volunteers_count: 4
|
||||
},
|
||||
{
|
||||
name: "Accueil des intervenants",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Hall principal",
|
||||
description: "Assister les speakers et orienter vers les salles.",
|
||||
volunteers_count: 2
|
||||
},
|
||||
{
|
||||
name: "Gestion du buffet",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Salle de restauration",
|
||||
description: "Distribuer boissons et repas aux participants.",
|
||||
volunteers_count: 3
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Atelier de Jardinage Urbain",
|
||||
description: "Apprenez à cultiver vos plantes en ville et participez à la création de jardins partagés.",
|
||||
image: null,
|
||||
tasks: [
|
||||
{
|
||||
name: "Installation du matériel",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Jardin partagé",
|
||||
description: "Préparer outils et terreaux pour les participants.",
|
||||
volunteers_count: 2
|
||||
},
|
||||
{
|
||||
name: "Encadrement des participants",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Jardin partagé",
|
||||
description: "Aider et conseiller sur la plantation et l’entretien.",
|
||||
volunteers_count: 3
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Collecte de Sang",
|
||||
description: "Aidez à sauver des vies en participant à notre collecte de sang annuelle.",
|
||||
image: null,
|
||||
tasks: [
|
||||
{
|
||||
name: "Accueil et enregistrement",
|
||||
start: "2020-09-24 22:21:20",
|
||||
end: "2020-09-24 22:21:20",
|
||||
place: "Salle municipale",
|
||||
description: "Enregistrer les donneurs et expliquer le processus.",
|
||||
volunteers_count: 4
|
||||
},
|
||||
{
|
||||
name: "Préparation des salles",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Salle municipale",
|
||||
description: "Installer les lits, tables et matériel médical.",
|
||||
volunteers_count: 3
|
||||
},
|
||||
{
|
||||
name: "Assistance post-don",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Salle municipale",
|
||||
description: "Accompagner les donneurs après le don et proposer des rafraîchissements.",
|
||||
volunteers_count: 2
|
||||
},
|
||||
{
|
||||
name: "Accueil et enregistrement",
|
||||
start: "2020-09-24 22:21:20",
|
||||
end: "2020-09-24 22:21:20",
|
||||
place: "Salle municipale",
|
||||
description: "Enregistrer les donneurs et expliquer le processus.",
|
||||
volunteers_count: 4
|
||||
},
|
||||
{
|
||||
name: "Préparation des salles",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Salle municipale",
|
||||
description: "Installer les lits, tables et matériel médical.",
|
||||
volunteers_count: 3
|
||||
},
|
||||
{
|
||||
name: "Assistance post-don",
|
||||
start: "2018-09-24 22:21:20",
|
||||
end: "2018-09-24 22:21:20",
|
||||
place: "Salle municipale",
|
||||
description: "Accompagner les donneurs après le don et proposer des rafraîchissements.",
|
||||
volunteers_count: 2
|
||||
},
|
||||
]
|
||||
},
|
||||
]);
|
||||
}
|
||||
*/
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await getAllEvents();
|
||||
setEvents(data.data);
|
||||
}) ()
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,15 +3,14 @@ import LoginForm from "../../components/loginform/loginForm.jsx";
|
||||
import Background from "../../components/background/background.jsx";
|
||||
|
||||
function LoginPage() {
|
||||
return <Background>
|
||||
|
||||
<div className={styles.container}>
|
||||
<LoginForm />
|
||||
</div>
|
||||
|
||||
</Background>
|
||||
return (
|
||||
<Background>
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>Connexion</h1>
|
||||
<LoginForm />
|
||||
</div>
|
||||
</Background>
|
||||
);
|
||||
}
|
||||
|
||||
export default LoginPage;
|
||||
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
.container {
|
||||
position: relative;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 600px;
|
||||
overflow: hidden;
|
||||
max-height: 80%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border-radius:12px;
|
||||
margin-left: 33.3%;
|
||||
border-radius: 12px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.title {
|
||||
font-size: 36px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
text-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||
}
|
||||
@@ -1,105 +1,63 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useContext } from "react";
|
||||
import styles from "./ProfilePage.module.css";
|
||||
|
||||
function Event({ title,date, tasks }) {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
const toggleExpand = () => {
|
||||
setIsExpanded(!isExpanded);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${styles.event} glassCard`}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div style={{display:'flex', flexDirection:'column'}}>
|
||||
<h3>{title}</h3>
|
||||
<p style={{ fontSize: '0.8rem', color: '#666', margin: '0 0 0 0' }}>{date}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={toggleExpand}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
fontSize: '1.2rem',
|
||||
}}
|
||||
>
|
||||
{isExpanded ? '▼' : '▶'}
|
||||
</button>
|
||||
</div>
|
||||
<ul
|
||||
className={`${styles.eventTasks} ${isExpanded ? styles.expanded : ''}`}
|
||||
>
|
||||
{tasks.map((task, index) => (
|
||||
<li key={index}>-- {task}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
import { AuthContext } from "../../contexts/auth/AuthContext.js";
|
||||
import formatDate from "../../utils/date/formatDate.js";
|
||||
import Task from "../../components/Task/Task";
|
||||
import Button from "../../components/ui/button/button.jsx";
|
||||
import { useNavigate } from "react-router";
|
||||
import ThemeSwitcher from "../../components/ui/themeSwitcher/ThemeSwitcher.jsx";
|
||||
|
||||
function ProfilePage() {
|
||||
|
||||
const { user, logout } = useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={`${styles.profileboard} glassCard`}>
|
||||
<div className={styles.contentWrapper}>
|
||||
<div className={styles.profilePictureContainer}>
|
||||
<img
|
||||
src="../src/assets/react.svg"
|
||||
src="/react.svg"
|
||||
alt="Photo de profil"
|
||||
className={styles.profilePicture}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
<h2>LEBLOND Malo</h2>
|
||||
<div className={styles.headerProfile}>
|
||||
<h2> {user.name} {user.lastname} </h2>
|
||||
<div className={styles.settingsBtns}>
|
||||
<ThemeSwitcher />
|
||||
<Button variant={"danger"} onClick={handleLogout}> Déconnexion </Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.topDescription}>
|
||||
<div className={styles.infoBlock}>
|
||||
<p><strong>Role :</strong> Président</p>
|
||||
<p><strong>Membre depuis :</strong> 2019</p>
|
||||
<p><strong>Role :</strong> {user.role}</p>
|
||||
<p><strong>Membre depuis :</strong> {formatDate(user.created_at)}</p>
|
||||
</div>
|
||||
<div className={styles.infoBlock}>
|
||||
<p><strong>Mail :</strong> mailpro@gmail.com</p>
|
||||
<p><strong>Téléphone :</strong> 08 67 87 32 21</p>
|
||||
<p><strong>Mail :</strong> {user.email} </p>
|
||||
<p><strong>Téléphone :</strong> {user.phone}</p>
|
||||
</div>
|
||||
<button href="/../UpdateProfile.jsx">M</button>
|
||||
<Button variant={"transparent"} onClick={() => navigate("/profile/update")}>
|
||||
<img src={"/icons/pen.svg"} alt={"Modify btn"} className={styles.modifyIcon}/>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<h2>Vos Événements :</h2>
|
||||
<Event
|
||||
title="Titre event 1"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
<Event
|
||||
title="Titre event 2"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
<Event
|
||||
title="Titre event 3"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
<Event
|
||||
title="Titre event 4"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
<Event
|
||||
title="Titre event 5"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
<Event
|
||||
title="Titre event 6"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
<Event
|
||||
title="Titre event 7"
|
||||
date="04/11/2025"
|
||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
||||
/>
|
||||
{user.tasks && user.tasks.length > 0 ? (
|
||||
user.tasks.map((task, index) => (
|
||||
<Task key={index} task={task} />
|
||||
))
|
||||
) : (
|
||||
<p>Aucune tâche pour le moment.</p>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
.profileboard {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
border-radius: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -51,13 +51,6 @@
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.event {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.eventTasks {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
@@ -73,12 +66,18 @@
|
||||
}
|
||||
.description h2{
|
||||
font-size:30px;
|
||||
margin-bottom: 2%;
|
||||
|
||||
}
|
||||
.profilePicture{
|
||||
width:90%;
|
||||
}
|
||||
/* Tablette (768px et plus) */
|
||||
}
|
||||
|
||||
.settingsBtns {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.contentWrapper {
|
||||
flex-direction: row;
|
||||
@@ -112,7 +111,6 @@
|
||||
}
|
||||
.description h2{
|
||||
font-size:30px;
|
||||
margin-bottom: 2%;
|
||||
}
|
||||
.profilePicture{
|
||||
width:90%;
|
||||
@@ -124,6 +122,19 @@
|
||||
|
||||
}
|
||||
|
||||
.modifyIcon {
|
||||
height:50px;
|
||||
width:50px;
|
||||
}
|
||||
|
||||
.headerProfile {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Desktop (1024px et plus) */
|
||||
@media screen and (min-width: 1024px) {
|
||||
.container {
|
||||
@@ -133,7 +144,7 @@
|
||||
}
|
||||
|
||||
.profileboard {
|
||||
padding: 25px;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.profilePictureContainer {
|
||||
@@ -153,6 +164,5 @@
|
||||
}
|
||||
.description h2{
|
||||
font-size:30px;
|
||||
margin-bottom: 2%;
|
||||
}
|
||||
}
|
||||
@@ -30,9 +30,9 @@ function RegisterPage() {
|
||||
|
||||
<div className={styles.container}>
|
||||
|
||||
<div className={"glassCard"}>
|
||||
<h1 className={styles.title}>Créer un compte</h1>
|
||||
|
||||
<h1>Créer un compte</h1>
|
||||
<div className={`glassCard ${styles.inputContainer}`}>
|
||||
|
||||
<div className={styles.formGroup}>
|
||||
<Input
|
||||
@@ -62,32 +62,7 @@ function RegisterPage() {
|
||||
/>
|
||||
</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={() => setShowPassword(!showPassword)}
|
||||
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}>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 30%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -9,6 +10,15 @@
|
||||
border-radius:12px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
text-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
|
||||
:global(.glassCard) {
|
||||
--glass-bg: #ffffff9f;
|
||||
}
|
||||
@@ -26,4 +36,8 @@
|
||||
h1 {
|
||||
overflow: visible;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inputContainer {
|
||||
padding: 15px;
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import React, { useState } from "react";
|
||||
import { Doughnut } from "react-chartjs-2";
|
||||
import { Chart, ArcElement } from "chart.js";
|
||||
import styles from "./StatPage.module.css";
|
||||
import Button from "./../../components/ui/button/button";
|
||||
|
||||
// Enregistrer les éléments nécessaires
|
||||
Chart.register(ArcElement);
|
||||
|
||||
function StatPage() {
|
||||
const [months, setMonths] = useState(3);
|
||||
|
||||
const [participationRate, setParticipationRate] = useState(89);
|
||||
|
||||
const incompleteEvents = [
|
||||
{ title: "Événement A", tasks: "Préparation des flyers" },
|
||||
{ title: "Événement B", tasks: "Réservation de la salle" },
|
||||
{ title: "Événement C", tasks: "Communication sur les réseaux" },
|
||||
];
|
||||
|
||||
const pendingMembers = [
|
||||
{ name: "Antoine Ordonneau" },
|
||||
{ name: "Giovanni Josserand" },
|
||||
{ name: "Quentin T'Jampens" },
|
||||
];
|
||||
|
||||
const handleValidate = (name) => {
|
||||
console.log(`Valider ${name}`);
|
||||
};
|
||||
|
||||
const handleReject = (name) => {
|
||||
console.log(`Refuser ${name}`);
|
||||
};
|
||||
|
||||
const chartData = {
|
||||
labels: ["Progress", "Background"],
|
||||
datasets: [
|
||||
{
|
||||
data: [participationRate, 100 - participationRate],
|
||||
backgroundColor: ["#1e60f9ff", "#ffffffff"],
|
||||
borderWidth: 0,
|
||||
weight:10,
|
||||
cutout: "90%",
|
||||
circumference: 360,
|
||||
rotation: -90,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
animateScale: true,
|
||||
animateRotate: true,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={`${styles.leftBlock} glassCard`}>
|
||||
<div className={styles.participationHeader}>
|
||||
<div className={styles.partiText}>
|
||||
<label htmlFor="months">Taux de participation </label>
|
||||
<label htmlFor="months2">depuis : </label>
|
||||
|
||||
<select
|
||||
id="months"
|
||||
value={months}
|
||||
onChange={(e) => setMonths(e.target.value)}
|
||||
className={styles.monthsDropdown}
|
||||
>
|
||||
<option value="1">1 mois</option>
|
||||
<option value="3">3 mois</option>
|
||||
<option value="6">6 mois</option>
|
||||
<option value="12">12 mois</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className={styles.chartContainer}>
|
||||
<div style={{ position: "relative", width: "100%", height: "100%" }}>
|
||||
<Doughnut data={chartData} options={chartOptions} />
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
textAlign:"center",
|
||||
transform: "translate(-50%, -50%)",
|
||||
fontSize: "60px",
|
||||
fontWeight: "bold",
|
||||
color: "#333",
|
||||
}}
|
||||
>
|
||||
{participationRate}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.rightSection}>
|
||||
<div className={`${styles.rightBlock} glassCard`}>
|
||||
<h2>Événements incomplets</h2>
|
||||
<div className={styles.eventsContainer}>
|
||||
{incompleteEvents.map((event, index) => (
|
||||
<div key={index} className={`glassCard ${styles.eventCard}`}>
|
||||
<h3>{event.title}</h3>
|
||||
<p>{event.tasks}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${styles.rightBlock} glassCard`}>
|
||||
<h2>Membres en attente de validation</h2>
|
||||
<div className={styles.membersContainer}>
|
||||
{pendingMembers.map((member, index) => (
|
||||
<div key={index} className={`glassCard ${styles.memberCard}`}>
|
||||
<p>{member.name}</p>
|
||||
<div className={styles.buttonGroup}>
|
||||
<Button
|
||||
onClick={() => handleValidate(member.name)}
|
||||
variant="primary"
|
||||
>
|
||||
Valider
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleReject(member.name)}
|
||||
variant="danger"
|
||||
>
|
||||
Refuser
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default StatPage;
|
||||
@@ -0,0 +1,194 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
gap: 20px;
|
||||
}
|
||||
/* style a montrer a giovanni prcq il est + bo */
|
||||
/*.glassCard {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}*/
|
||||
|
||||
.block {
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.leftBlock {
|
||||
width: 40%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height:fit-content;
|
||||
}
|
||||
|
||||
.participationHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
.partiText{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
.participationHeader label{
|
||||
font-size:20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.monthsDropdown {
|
||||
width:25%;
|
||||
border-radius: 5px;
|
||||
opacity: 1;
|
||||
background-color: transparent;
|
||||
border:none;
|
||||
}
|
||||
|
||||
|
||||
.chartContainer {
|
||||
|
||||
max-width: 80%;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.circularProgress {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.progressBackground {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.progressBar {
|
||||
transition: stroke-dasharray 0.5s ease;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.progressText {
|
||||
font-weight: bold;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
.rightSection {
|
||||
width: 60%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.rightBlock {
|
||||
width: 100%;
|
||||
}
|
||||
.rightBlock>h2{
|
||||
font-size:29px;
|
||||
}
|
||||
|
||||
.eventsContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
|
||||
.eventCard {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
padding-bottom:200px;
|
||||
}
|
||||
.eventCard>h3{
|
||||
font-size: 24px;
|
||||
}
|
||||
.membersContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.memberCard {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
.memberCard p{
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
|
||||
.buttonGroup {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 760px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.leftBlock {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
height:fit-content;
|
||||
}
|
||||
|
||||
.rightSection {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.eventsContainer {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.eventCard {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.donutChart {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.memberCard {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
}
|
||||
|
||||
.buttonGroup {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.validateButton,
|
||||
.rejectButton {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import styles from "./VolunteersPage.module.css";
|
||||
import getUserById from "../../utils/users/getUserById.js";
|
||||
import formatDate from "../../utils/date/formatDate.js";
|
||||
import { useNavigate } from "react-router";
|
||||
import Button from "../../components/ui/button/button.jsx";
|
||||
|
||||
export default function VolunteerCard({ id }) {
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [user, setUser] = useState({});
|
||||
|
||||
const toggleExpand = () => {
|
||||
setIsExpanded(!isExpanded);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await getUserById(id);
|
||||
setUser(data);
|
||||
}) ()
|
||||
}, []);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className={`${styles.profileboard} glassCard`}>
|
||||
|
||||
<div className={styles.header}>
|
||||
<h2>{user?.name}</h2>
|
||||
<Button
|
||||
variant={"transparent"}
|
||||
onClick={toggleExpand}
|
||||
className={`${isExpanded ? styles.moreButtonClicked : ""}`}
|
||||
>
|
||||
{">"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={styles.mainContent}>
|
||||
{!isExpanded ? (
|
||||
<div className={styles.basicInfo}>
|
||||
<p><strong>Rôle :</strong> {user?.role}</p>
|
||||
<p><strong>Membre depuis :</strong> {user?.created_at && formatDate(user.created_at)}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.expandedContent}>
|
||||
<div className={styles.profilePictureContainer}>
|
||||
<img src={"/react.svg"} alt="profile" className={styles.profilePicture} />
|
||||
</div>
|
||||
|
||||
<div className={styles.textInfo}>
|
||||
<div className={styles.basicInfo}>
|
||||
<p><strong>Rôle :</strong> {user?.role}</p>
|
||||
<p><strong>Membre depuis :</strong> {user?.created_at && formatDate(user.created_at)}</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.contactInfo}>
|
||||
<p><strong>Email :</strong> {user?.email}</p>
|
||||
{user?.phone && <p><strong>Téléphone :</strong> {user.phone}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div className={styles.bottomBar}>
|
||||
<Button
|
||||
onClick={() => navigate(`/profile/${id}`)}
|
||||
variant="transparent"
|
||||
>
|
||||
Voir plus
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,106 +1,23 @@
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import styles from "./VolunteersPage.module.css";
|
||||
import VolunteerCard from "./VolunteerCard.jsx";
|
||||
import getAllUser from "../../utils/users/getAllUsers.js";
|
||||
|
||||
function Volunteer({ name, role, since, email, phone, photo }) {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
const toggleExpand = () => {
|
||||
setIsExpanded(!isExpanded);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${styles.profileboard} glassCard`}>
|
||||
<div className={styles.mainContent}>
|
||||
{!isExpanded ? (
|
||||
<div className={styles.basicInfo}>
|
||||
<h2>{name}</h2>
|
||||
<p><strong>Rôle :</strong> {role}</p>
|
||||
<p><strong>Membre depuis :</strong> {since}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.expandedContent}>
|
||||
<div className={styles.profilePictureContainer}>
|
||||
<img src={photo} alt={`${name}'s profile`} className={styles.profilePicture} />
|
||||
</div>
|
||||
<div className={styles.textInfo}>
|
||||
<div className={styles.basicInfo}>
|
||||
<h2>{name}</h2>
|
||||
<p><strong>Rôle :</strong> {role}</p>
|
||||
<p><strong>Membre depuis :</strong> {since}</p>
|
||||
</div>
|
||||
<div className={styles.contactInfo}>
|
||||
<p><strong>Email :</strong> {email}</p>
|
||||
<p><strong>Téléphone :</strong> {phone}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
className={`${styles.expandButton} glassCard`}
|
||||
onClick={toggleExpand}
|
||||
>
|
||||
{isExpanded ? "▲" : "▼"}
|
||||
</button>
|
||||
{isExpanded && (
|
||||
<div className="voirPlus">
|
||||
<a href="#" className={styles.voirPlus}>VOIR PLUS</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VolunteerPage() {
|
||||
const volunteers = [
|
||||
{
|
||||
name: "LEBLOND Malo",
|
||||
role: "Président",
|
||||
since: "2019",
|
||||
email: "malo.leblond@example.com",
|
||||
phone: "08 67 87 32 21",
|
||||
photo: "../src/assets/react.svg",
|
||||
},
|
||||
{
|
||||
name: "DUPONT Jean",
|
||||
role: "Bénévole",
|
||||
since: "2020",
|
||||
email: "jean.dupont@example.com",
|
||||
phone: "06 12 34 56 78",
|
||||
photo: "../src/assets/react.svg",
|
||||
},
|
||||
{
|
||||
name: "MARTIN Sophie",
|
||||
role: "Bénévole",
|
||||
since: "2021",
|
||||
email: "sophie.martin@example.com",
|
||||
phone: "06 87 65 43 21",
|
||||
photo: "../src/assets/react.svg",
|
||||
},
|
||||
{
|
||||
name: "MARTIN Sophie",
|
||||
role: "Bénévole",
|
||||
since: "2021",
|
||||
email: "sophie.martin@example.com",
|
||||
phone: "06 87 65 43 21",
|
||||
photo: "../src/assets/react.svg",
|
||||
},
|
||||
];
|
||||
|
||||
const [users, setUsers] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await getAllUser();
|
||||
setUsers(data);
|
||||
}) ()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{volunteers.map((volunteer, index) => (
|
||||
<Volunteer
|
||||
key={index}
|
||||
name={volunteer.name}
|
||||
role={volunteer.role}
|
||||
since={volunteer.since}
|
||||
email={volunteer.email}
|
||||
phone={volunteer.phone}
|
||||
photo={volunteer.photo}
|
||||
/>
|
||||
))}
|
||||
{users.map((user, index) => <VolunteerCard key={index} id={user} /> )}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,125 +1,117 @@
|
||||
.container {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
margin-top: 50px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-top: 50px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.profileboard {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
margin-top: 3%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
margin-top: 3%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
.mainContent {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.basicInfo {
|
||||
flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.expandedContent {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profilePictureContainer {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.profilePicture {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.textInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.contactInfo {
|
||||
margin-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.contactInfo p {
|
||||
margin: 5px 0;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.bottomBar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.profilePictureContainer {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.profilePicture {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.expandButton {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
margin-left: 10px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.voirPlus {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.voirPlus:hover {
|
||||
text-decoration: underline;
|
||||
.moreButtonClicked {
|
||||
rotate: 90deg;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.profilePictureContainer {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.voirPlus{
|
||||
text-align: right;
|
||||
}
|
||||
.profilePictureContainer {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
margin-top:50px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.profileboard {
|
||||
padding: 25px;
|
||||
}
|
||||
.profileboard {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.profilePictureContainer {
|
||||
width: 200px;
|
||||
height: auto;
|
||||
}
|
||||
.profilePictureContainer {
|
||||
width: 200px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-2
@@ -2,6 +2,7 @@ import { createBrowserRouter } from "react-router";
|
||||
import Layout from "./layout.jsx";
|
||||
import HomePage from "./pages/Home/HomePage";
|
||||
import LoginPage from "./pages/Login/LoginPage";
|
||||
import StatPage from "./pages/Stat/StatPage";
|
||||
import RegisterPage from "./pages/Register/RegisterPage.jsx";
|
||||
import VolunteersPage from "./pages/Volunteers/VolunteersPage";
|
||||
import ProfilePage from "./pages/Profile/ProfilePage";
|
||||
@@ -25,8 +26,8 @@ const router = createBrowserRouter([
|
||||
Component: HomePage,
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
Component: LoginPage,
|
||||
path: "/stat",
|
||||
Component:StatPage,
|
||||
},
|
||||
{
|
||||
path:"/profile",
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import Modal from "../components/ui/modal/modal.jsx";
|
||||
import Button from "../components/ui/button/button.jsx";
|
||||
import { useState } from "react";
|
||||
|
||||
export default {
|
||||
title: 'UI/Modal',
|
||||
component: Modal,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
open: {
|
||||
control: 'boolean',
|
||||
description: 'Variable qui gère si la modal est ouverte ou fermée.',
|
||||
},
|
||||
children: {
|
||||
control: Object,
|
||||
description: 'Contenu de la modal.',
|
||||
},
|
||||
title: {
|
||||
control: 'text',
|
||||
description: 'Titre de la modal',
|
||||
},
|
||||
onClose: {
|
||||
action: 'changed',
|
||||
description: 'Fonction appelée pour fermer la modal.',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => setIsOpen(true)}>Ouvrir</Button>
|
||||
<Modal {...args} open={isOpen} onClose={() => setIsOpen(false)}>
|
||||
{args.children}
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
title: "Hello",
|
||||
children: <p>Lorem Ipsum...</p>,
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import getXSRFToken from "../getXSRF.js";
|
||||
|
||||
export default async function logout() {
|
||||
|
||||
const csrfToken = await getXSRFToken();
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/logout`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'X-XSRF-TOKEN': csrfToken
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Code HTTP :', response.status);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erreur HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return { status: response.status };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export default function formatDate(d) {
|
||||
const date = new Date(d);
|
||||
|
||||
const jj = String(date.getUTCDate()).padStart(2, '0');
|
||||
const mm = String(date.getUTCMonth() + 1).padStart(2, '0');
|
||||
const aaaa = date.getUTCFullYear();
|
||||
|
||||
return `${jj}/${mm}/${aaaa}`;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { expect, test } from 'vitest';
|
||||
import formatDate from './formatDate';
|
||||
|
||||
const date = "2025-11-28T11:26:45.000000Z"
|
||||
const res = "28/11/2025"
|
||||
|
||||
test("Format Date", () => {
|
||||
expect(formatDate(date)).toBe(res);
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
export default async function getAllEvents() {
|
||||
|
||||
try {
|
||||
const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/events/`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Erreur serveur : ${res.status}`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error('Erreur :', err);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export default async function GetEventById(id) {
|
||||
|
||||
try {
|
||||
const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/events/${id}`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Erreur serveur : ${res.status}`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error('Erreur :', err);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
export default async function getUser() {
|
||||
try {
|
||||
const res = await fetch('http://localhost:80/api/users/me', {
|
||||
const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/me`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default async function getXSRFToken() {
|
||||
const response = await fetch('http://localhost:80/sanctum/csrf-cookie', {
|
||||
const response = await fetch(`http://${import.meta.env.VITE_API_URL}/sanctum/csrf-cookie`, {
|
||||
method: 'GET',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ export default async function login(email, password) {
|
||||
const csrfToken = await getXSRFToken();
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:80/api/users/login', {
|
||||
const response = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/login`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
|
||||
@@ -4,7 +4,7 @@ export default async function register(email, password, name, lastname) {
|
||||
const csrfToken = await getXSRFToken();
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:80/api/users/register', {
|
||||
const response = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/register`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export default async function getAllUser() {
|
||||
try {
|
||||
const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Erreur serveur : ${res.status}`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error('Erreur :', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export default async function getUserById (id) {
|
||||
try {
|
||||
const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/${id}`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Erreur serveur : ${res.status}`);
|
||||
}
|
||||
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
console.error('Erreur :', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+39
-30
@@ -1,39 +1,48 @@
|
||||
// vite.config.js
|
||||
/// <reference types="vitest/config" />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'
|
||||
import { playwright } from '@vitest/browser-playwright'
|
||||
|
||||
// https://vite.dev/config/
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
|
||||
import { playwright } from '@vitest/browser-playwright';
|
||||
const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
base: '/',
|
||||
test: {
|
||||
projects: [{
|
||||
extends: true,
|
||||
plugins: [
|
||||
// The plugin will run tests for the stories defined in your Storybook config
|
||||
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
|
||||
storybookTest({
|
||||
configDir: path.join(dirname, '.storybook')
|
||||
})],
|
||||
test: {
|
||||
name: 'storybook',
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: playwright({}),
|
||||
instances: [{
|
||||
browser: 'chromium'
|
||||
}]
|
||||
projects: [
|
||||
{
|
||||
extends: true,
|
||||
plugins: [
|
||||
storybookTest({
|
||||
configDir: path.join(dirname, '.storybook'),
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
name: 'storybook',
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: playwright({}),
|
||||
instances: [{ browser: 'chromium' }],
|
||||
},
|
||||
setupFiles: ['.storybook/vitest.setup.js'],
|
||||
},
|
||||
setupFiles: ['.storybook/vitest.setup.js']
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
test: {
|
||||
name: 'unit',
|
||||
include: [
|
||||
'src/**/*.test.js',
|
||||
'src/**/*.test.ts',
|
||||
'utils/**/*.test.js',
|
||||
],
|
||||
environment: 'node',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user