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.log
|
||||||
storybook-static
|
storybook-static
|
||||||
|
|
||||||
|
.env
|
||||||
Generated
+2
@@ -8,7 +8,9 @@
|
|||||||
"name": "sae-front",
|
"name": "sae-front",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chart.js": "^4.5.1",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
"react-chartjs-2": "^5.3.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-router": "^7.9.4"
|
"react-router": "^7.9.4"
|
||||||
},
|
},
|
||||||
|
|||||||
+8
-5
@@ -9,10 +9,13 @@
|
|||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"storybook": "storybook dev -p 6006",
|
"storybook": "storybook dev -p 6006",
|
||||||
"build-storybook": "storybook build"
|
"build-storybook": "storybook build",
|
||||||
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chart.js": "^4.5.1",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
"react-chartjs-2": "^5.3.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-router": "^7.9.4"
|
"react-router": "^7.9.4"
|
||||||
},
|
},
|
||||||
@@ -26,17 +29,17 @@
|
|||||||
"@types/react": "^19.1.16",
|
"@types/react": "^19.1.16",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.1.9",
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
"@vitejs/plugin-react": "^5.0.4",
|
||||||
|
"@vitest/browser-playwright": "^4.0.8",
|
||||||
|
"@vitest/coverage-v8": "^4.0.8",
|
||||||
"eslint": "^9.36.0",
|
"eslint": "^9.36.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.22",
|
"eslint-plugin-react-refresh": "^0.4.22",
|
||||||
"eslint-plugin-storybook": "^10.0.6",
|
"eslint-plugin-storybook": "^10.0.6",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.4.0",
|
||||||
|
"playwright": "^1.56.1",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"storybook": "^10.0.6",
|
"storybook": "^10.0.6",
|
||||||
"vite": "^7.1.7",
|
"vite": "^7.1.7",
|
||||||
"vitest": "^4.0.8",
|
"vitest": "^4.0.8"
|
||||||
"playwright": "^1.56.1",
|
|
||||||
"@vitest/browser-playwright": "^4.0.8",
|
|
||||||
"@vitest/coverage-v8": "^4.0.8"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Generated
+30
@@ -8,9 +8,15 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
chart.js:
|
||||||
|
specifier: ^4.5.1
|
||||||
|
version: 4.5.1
|
||||||
react:
|
react:
|
||||||
specifier: ^19.1.1
|
specifier: ^19.1.1
|
||||||
version: 19.2.0
|
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:
|
react-dom:
|
||||||
specifier: ^19.1.1
|
specifier: ^19.1.1
|
||||||
version: 19.2.0(react@19.2.0)
|
version: 19.2.0(react@19.2.0)
|
||||||
@@ -436,6 +442,9 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.31':
|
'@jridgewell/trace-mapping@0.3.31':
|
||||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||||
|
|
||||||
|
'@kurkle/color@0.3.4':
|
||||||
|
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
|
||||||
|
|
||||||
'@mdx-js/react@3.1.1':
|
'@mdx-js/react@3.1.1':
|
||||||
resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==}
|
resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -949,6 +958,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
chart.js@4.5.1:
|
||||||
|
resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==}
|
||||||
|
engines: {pnpm: '>=8'}
|
||||||
|
|
||||||
check-error@2.1.1:
|
check-error@2.1.1:
|
||||||
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
|
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
@@ -1510,6 +1523,12 @@ packages:
|
|||||||
queue-microtask@1.2.3:
|
queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
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:
|
react-docgen-typescript@2.4.0:
|
||||||
resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==}
|
resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2186,6 +2205,8 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@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)':
|
'@mdx-js/react@3.1.1(@types/react@19.2.2)(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mdx': 2.0.13
|
'@types/mdx': 2.0.13
|
||||||
@@ -2738,6 +2759,10 @@ snapshots:
|
|||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
supports-color: 7.2.0
|
supports-color: 7.2.0
|
||||||
|
|
||||||
|
chart.js@4.5.1:
|
||||||
|
dependencies:
|
||||||
|
'@kurkle/color': 0.3.4
|
||||||
|
|
||||||
check-error@2.1.1: {}
|
check-error@2.1.1: {}
|
||||||
|
|
||||||
chromatic@12.2.0: {}
|
chromatic@12.2.0: {}
|
||||||
@@ -3255,6 +3280,11 @@ snapshots:
|
|||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
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):
|
react-docgen-typescript@2.4.0(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
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 styles from "./Event.module.css";
|
||||||
import {useState, useEffect} from "react";
|
import { useState, useEffect } from "react";
|
||||||
import {Link} from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import Button from "/src/components/ui/button/button.jsx"
|
import Button from "/src/components/ui/button/button.jsx"
|
||||||
|
|
||||||
function Event({event}){
|
function Event({event}){
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [eventMenu, seteventMenu] = useState(false);
|
const [eventMenu, seteventMenu] = useState(false);
|
||||||
const [windowSize, setWindowSize] = useState({
|
const [windowSize, setWindowSize] = useState({
|
||||||
width: null,
|
width: null,
|
||||||
@@ -31,7 +34,7 @@ function Event({event}){
|
|||||||
<div className={styles.eventContent}>
|
<div className={styles.eventContent}>
|
||||||
<div className={`${styles.eventImageDiv}`}>
|
<div className={`${styles.eventImageDiv}`}>
|
||||||
{event.image == null ? (
|
{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" />
|
<img src={event.image + `.png`} alt="Photo de l'événement" />
|
||||||
)}
|
)}
|
||||||
@@ -57,21 +60,21 @@ function Event({event}){
|
|||||||
</div>
|
</div>
|
||||||
{windowSize.width > 768 ? (
|
{windowSize.width > 768 ? (
|
||||||
<div className={styles.eventButtons}>
|
<div className={styles.eventButtons}>
|
||||||
<button
|
<Button
|
||||||
className={`${styles.moreButton} ${eventMenu ? styles.moreButtonClicked : ""}`}
|
variant={"transparent"}
|
||||||
onClick={()=>{seteventMenu(!eventMenu)}}>
|
onClick={()=>{seteventMenu(!eventMenu)}}
|
||||||
|
className={`${eventMenu ? styles.moreButtonClicked : ""}`}
|
||||||
|
>
|
||||||
{">"}
|
{">"}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.eventContainerBottom}>
|
<div className={styles.eventContainerBottom}>
|
||||||
{(windowSize.width <= 768 || eventMenu) ? (
|
{(windowSize.width <= 768 || eventMenu) ? (
|
||||||
<Link
|
|
||||||
to={`/event/` + event.id}
|
<Button onClick={() => navigate(`/event/` + event.id)}>Voir plus</Button>
|
||||||
className={styles.moreLink}>
|
|
||||||
<Button>En savoir plus →</Button>
|
|
||||||
</Link>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
|
padding: 1rem 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eventContainerTop {
|
.eventContainerTop {
|
||||||
@@ -46,11 +47,6 @@
|
|||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.eventButtons {
|
.eventButtons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -64,9 +60,6 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.moreButton {
|
.moreButton {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -95,19 +88,16 @@
|
|||||||
rotate: 90deg;
|
rotate: 90deg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.eventMenuOpen {
|
.eventMenuOpen {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eventMenuClose {
|
.eventMenuClose {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.eventImage {
|
.eventImage {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function Header() {
|
|||||||
setNotifications(notifData || []);
|
setNotifications(notifData || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNotifications();
|
//loadNotifications();
|
||||||
const handleClickOutside = (event) => {
|
const handleClickOutside = (event) => {
|
||||||
if (
|
if (
|
||||||
notificationRef.current &&
|
notificationRef.current &&
|
||||||
@@ -70,7 +70,7 @@ function Header() {
|
|||||||
{unreadNotification && <span className={styles.notificationBadge}></span>}
|
{unreadNotification && <span className={styles.notificationBadge}></span>}
|
||||||
</button>
|
</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">
|
<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" />
|
<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>
|
</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 styles from "./background.module.css";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
const Background = ({ children, className }) => {
|
const Background = ({ children, className }) => {
|
||||||
const circlesRef = useRef([]);
|
|
||||||
|
const circlesRef = useRef([]);
|
||||||
|
const [theme, setTheme] = useState("light");
|
||||||
|
|
||||||
const getRandomTranslation = () => {
|
const getRandomTranslation = () => {
|
||||||
const randomX = Math.floor(Math.random() * 100) - 30;
|
const randomX = Math.floor(Math.random() * 100) - 30;
|
||||||
@@ -20,8 +22,23 @@ const Background = ({ children, className }) => {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<div className={`${styles["background-container"]} ${className || ''}`}>
|
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
|
<div
|
||||||
ref={(el) => (circlesRef.current[0] = el)}
|
ref={(el) => (circlesRef.current[0] = el)}
|
||||||
className={`${styles["circle"]} ${styles["circle-1"]}`}
|
className={`${styles["circle"]} ${styles["circle-1"]}`}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
/* BACKGROUND ////////////////////////////////////////////////////////*/
|
/* BACKGROUND ////////////////////////////////////////////////////////*/
|
||||||
.background-container {
|
.backgroundContainer {
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #00F0FF;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
@@ -13,6 +12,14 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.backgroundLight {
|
||||||
|
background: #00F0FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backgroundDark {
|
||||||
|
background: #001C83;
|
||||||
|
}
|
||||||
|
|
||||||
.circle {
|
.circle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@@ -82,7 +89,6 @@
|
|||||||
.circle-1 {
|
.circle-1 {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
background-color: #019AFF;
|
|
||||||
top: -5%;
|
top: -5%;
|
||||||
left: -5%;
|
left: -5%;
|
||||||
}
|
}
|
||||||
@@ -90,7 +96,6 @@
|
|||||||
.circle-2 {
|
.circle-2 {
|
||||||
width: 175px;
|
width: 175px;
|
||||||
height: 175px;
|
height: 175px;
|
||||||
background-color: #91FCFF;
|
|
||||||
top: 58%;
|
top: 58%;
|
||||||
left: 10%;
|
left: 10%;
|
||||||
}
|
}
|
||||||
@@ -98,7 +103,6 @@
|
|||||||
.circle-3 {
|
.circle-3 {
|
||||||
width: 140px;
|
width: 140px;
|
||||||
height: 140px;
|
height: 140px;
|
||||||
background-color: #91FCFF;
|
|
||||||
top: 29%;
|
top: 29%;
|
||||||
right: 9%;
|
right: 9%;
|
||||||
}
|
}
|
||||||
@@ -106,7 +110,6 @@
|
|||||||
.circle-4 {
|
.circle-4 {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
height: 250px;
|
height: 250px;
|
||||||
background-color: #019AFF;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
@@ -114,7 +117,6 @@
|
|||||||
.circle-5 {
|
.circle-5 {
|
||||||
width: 240px;
|
width: 240px;
|
||||||
height: 240px;
|
height: 240px;
|
||||||
background-color: #015AFF;
|
|
||||||
top: 2%;
|
top: 2%;
|
||||||
right: 10%;
|
right: 10%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { AuthContext } from "../../contexts/auth/AuthContext.js";
|
|||||||
function LoginForm({ className }) {
|
function LoginForm({ className }) {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
const { login } = useContext(AuthContext);
|
const { login } = useContext(AuthContext);
|
||||||
|
|
||||||
@@ -19,12 +18,8 @@ function LoginForm({ className }) {
|
|||||||
navigate("/");
|
navigate("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleShowPassword = () => {
|
|
||||||
setShowPassword(!showPassword);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${className} ${"glassCard"}`}>
|
<div className={`${className} glassCard ${styles.inputContainer}`}>
|
||||||
<div className={styles.formgroup}>
|
<div className={styles.formgroup}>
|
||||||
<Input
|
<Input
|
||||||
type="email"
|
type="email"
|
||||||
@@ -35,35 +30,11 @@ function LoginForm({ className }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.formgroup}>
|
<div className={styles.formgroup}>
|
||||||
<div style={{ position: 'relative', width: '100%' }}>
|
<Input password={true} onChange={e => setPassword(e.target.value)} value={password} />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.logButton}>
|
<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>
|
<Button onClick={handleSubmit} variant={"default"}> Se connecter </Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,14 +18,21 @@ input::placeholder{
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: rgba(0,0,0,0.45);
|
color: rgba(0,0,0,0.45);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.formgroup{
|
||||||
|
margin-top:23px;
|
||||||
|
}
|
||||||
|
|
||||||
.logButton{
|
.logButton{
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
.formgroup{
|
|
||||||
margin-top:23px;
|
.inputContainer {
|
||||||
|
padding: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* style scoped (module) */
|
/* style scoped (module) */
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import styles from "./button.module.css";
|
import styles from "./button.module.css";
|
||||||
|
|
||||||
const Button = ({ children, variant = "primary", onClick }) => {
|
const Button = ({ children, variant = "primary", onClick, className }) => {
|
||||||
let btnStyle;
|
let btnStyle;
|
||||||
|
|
||||||
if (variant === "primary") btnStyle = styles.primary;
|
if (variant === "primary") btnStyle = styles.primary;
|
||||||
@@ -8,7 +8,7 @@ const Button = ({ children, variant = "primary", onClick }) => {
|
|||||||
else if(variant === "transparent") btnStyle = styles.transparent;
|
else if(variant === "transparent") btnStyle = styles.transparent;
|
||||||
else btnStyle = styles.default;
|
else btnStyle = styles.default;
|
||||||
|
|
||||||
return <div className={`${styles.btn} ${btnStyle}`} onClick={onClick}>
|
return <div className={`${styles.btn} ${btnStyle} ${className}`} onClick={onClick}>
|
||||||
{children}
|
{children}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,38 @@
|
|||||||
import styles from "./input.module.css";
|
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;
|
let inputBorderStyle = styles.square;
|
||||||
if (borderStyle === "square") inputBorderStyle = styles.square;
|
if (borderStyle === "square") inputBorderStyle = styles.square;
|
||||||
if(borderStyle === "rounded") inputBorderStyle = styles.rounded;
|
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;
|
export default TextInput;
|
||||||
@@ -30,3 +30,23 @@
|
|||||||
border-radius: 12px;
|
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 loginFunction from "../../utils/login.js";
|
||||||
import getUser from "../../utils/getUser.js";
|
import getUser from "../../utils/getUser.js";
|
||||||
import { AuthContext } from "./AuthContext";
|
import { AuthContext } from "./AuthContext";
|
||||||
|
import logoutFunction from "../../utils/auth/logout.js"
|
||||||
|
|
||||||
|
|
||||||
export function AuthProvider({ children }) {
|
export function AuthProvider({ children }) {
|
||||||
@@ -25,7 +26,7 @@ export function AuthProvider({ children }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
// TODO: Appeller fonction logout
|
await logoutFunction();
|
||||||
setUser(null);
|
setUser(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ body {
|
|||||||
|
|
||||||
.glassCard {
|
.glassCard {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
background: rgba(255, 255, 255, 0.65);
|
background: rgba(255, 255, 255, 0.65);
|
||||||
backdrop-filter: blur(3px);
|
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 Event from "../../components/Event/Event.jsx";
|
||||||
import styles from "./EventsPage.module.css";
|
import styles from "./EventsPage.module.css";
|
||||||
import ToolBar from "../../components/ToolBar/ToolBar.jsx";
|
import ToolBar from "../../components/ToolBar/ToolBar.jsx";
|
||||||
@@ -36,144 +36,15 @@ function EventsPage(){
|
|||||||
volunteers_count: 3
|
volunteers_count: 3
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
{
|
*/
|
||||||
id: 2,
|
|
||||||
name: "Festival de Musique d’Été",
|
useEffect(() => {
|
||||||
description: "Un week-end de concerts en plein air avec des artistes locaux et internationaux.",
|
(async () => {
|
||||||
image: null,
|
const data = await getAllEvents();
|
||||||
tasks: [
|
setEvents(data.data);
|
||||||
{
|
}) ()
|
||||||
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
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,15 +3,14 @@ import LoginForm from "../../components/loginform/loginForm.jsx";
|
|||||||
import Background from "../../components/background/background.jsx";
|
import Background from "../../components/background/background.jsx";
|
||||||
|
|
||||||
function LoginPage() {
|
function LoginPage() {
|
||||||
return <Background>
|
return (
|
||||||
|
<Background>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<LoginForm />
|
<h1 className={styles.title}>Connexion</h1>
|
||||||
</div>
|
<LoginForm />
|
||||||
|
</div>
|
||||||
</Background>
|
</Background>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LoginPage;
|
export default LoginPage;
|
||||||
|
|
||||||
|
|
||||||
@@ -1,16 +1,23 @@
|
|||||||
.container {
|
.container {
|
||||||
position: relative;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
width: 600px;
|
width: 600px;
|
||||||
overflow: hidden;
|
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius:12px;
|
border-radius: 12px;
|
||||||
margin-left: 33.3%;
|
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";
|
import styles from "./ProfilePage.module.css";
|
||||||
|
import { AuthContext } from "../../contexts/auth/AuthContext.js";
|
||||||
function Event({ title,date, tasks }) {
|
import formatDate from "../../utils/date/formatDate.js";
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
import Task from "../../components/Task/Task";
|
||||||
|
import Button from "../../components/ui/button/button.jsx";
|
||||||
const toggleExpand = () => {
|
import { useNavigate } from "react-router";
|
||||||
setIsExpanded(!isExpanded);
|
import ThemeSwitcher from "../../components/ui/themeSwitcher/ThemeSwitcher.jsx";
|
||||||
};
|
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function ProfilePage() {
|
function ProfilePage() {
|
||||||
|
|
||||||
|
const { user, logout } = useContext(AuthContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={`${styles.profileboard} glassCard`}>
|
<div className={`${styles.profileboard} glassCard`}>
|
||||||
<div className={styles.contentWrapper}>
|
<div className={styles.contentWrapper}>
|
||||||
<div className={styles.profilePictureContainer}>
|
<div className={styles.profilePictureContainer}>
|
||||||
<img
|
<img
|
||||||
src="../src/assets/react.svg"
|
src="/react.svg"
|
||||||
alt="Photo de profil"
|
alt="Photo de profil"
|
||||||
className={styles.profilePicture}
|
className={styles.profilePicture}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.description}>
|
<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.topDescription}>
|
||||||
<div className={styles.infoBlock}>
|
<div className={styles.infoBlock}>
|
||||||
<p><strong>Role :</strong> Président</p>
|
<p><strong>Role :</strong> {user.role}</p>
|
||||||
<p><strong>Membre depuis :</strong> 2019</p>
|
<p><strong>Membre depuis :</strong> {formatDate(user.created_at)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.infoBlock}>
|
<div className={styles.infoBlock}>
|
||||||
<p><strong>Mail :</strong> mailpro@gmail.com</p>
|
<p><strong>Mail :</strong> {user.email} </p>
|
||||||
<p><strong>Téléphone :</strong> 08 67 87 32 21</p>
|
<p><strong>Téléphone :</strong> {user.phone}</p>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<h2>Vos Événements :</h2>
|
<h2>Vos Événements :</h2>
|
||||||
<Event
|
{user.tasks && user.tasks.length > 0 ? (
|
||||||
title="Titre event 1"
|
user.tasks.map((task, index) => (
|
||||||
date="04/11/2025"
|
<Task key={index} task={task} />
|
||||||
tasks={["Votre tâche 1", "Votre tâche 2", "Votre tâche 3"]}
|
))
|
||||||
/>
|
) : (
|
||||||
<Event
|
<p>Aucune tâche pour le moment.</p>
|
||||||
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"]}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
.profileboard {
|
.profileboard {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border-radius: 10px;
|
border-radius: 20px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,13 +51,6 @@
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.event {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eventTasks {
|
.eventTasks {
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -73,12 +66,18 @@
|
|||||||
}
|
}
|
||||||
.description h2{
|
.description h2{
|
||||||
font-size:30px;
|
font-size:30px;
|
||||||
margin-bottom: 2%;
|
|
||||||
}
|
}
|
||||||
.profilePicture{
|
.profilePicture{
|
||||||
width:90%;
|
width:90%;
|
||||||
}
|
}
|
||||||
/* Tablette (768px et plus) */
|
|
||||||
|
.settingsBtns {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
justify-content: right;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.contentWrapper {
|
.contentWrapper {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -112,7 +111,6 @@
|
|||||||
}
|
}
|
||||||
.description h2{
|
.description h2{
|
||||||
font-size:30px;
|
font-size:30px;
|
||||||
margin-bottom: 2%;
|
|
||||||
}
|
}
|
||||||
.profilePicture{
|
.profilePicture{
|
||||||
width:90%;
|
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) */
|
/* Desktop (1024px et plus) */
|
||||||
@media screen and (min-width: 1024px) {
|
@media screen and (min-width: 1024px) {
|
||||||
.container {
|
.container {
|
||||||
@@ -133,7 +144,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.profileboard {
|
.profileboard {
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profilePictureContainer {
|
.profilePictureContainer {
|
||||||
@@ -153,6 +164,5 @@
|
|||||||
}
|
}
|
||||||
.description h2{
|
.description h2{
|
||||||
font-size:30px;
|
font-size:30px;
|
||||||
margin-bottom: 2%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,9 +30,9 @@ function RegisterPage() {
|
|||||||
|
|
||||||
<div className={styles.container}>
|
<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}>
|
<div className={styles.formGroup}>
|
||||||
<Input
|
<Input
|
||||||
@@ -62,32 +62,7 @@ function RegisterPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.formGroup}>
|
<div className={styles.formGroup}>
|
||||||
<div style={{ position: 'relative', width: '100%' }}>
|
<Input password={true} onChange={e => setPassword(e.target.value)} value={password} />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.logButton}>
|
<div className={styles.logButton}>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -9,6 +10,15 @@
|
|||||||
border-radius:12px;
|
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) {
|
:global(.glassCard) {
|
||||||
--glass-bg: #ffffff9f;
|
--glass-bg: #ffffff9f;
|
||||||
}
|
}
|
||||||
@@ -27,3 +37,7 @@ h1 {
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
text-align: center;
|
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 { useState, useEffect } from "react";
|
||||||
import React, { useState } from "react";
|
|
||||||
import styles from "./VolunteersPage.module.css";
|
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() {
|
function VolunteerPage() {
|
||||||
const volunteers = [
|
|
||||||
{
|
const [users, setUsers] = useState([]);
|
||||||
name: "LEBLOND Malo",
|
|
||||||
role: "Président",
|
useEffect(() => {
|
||||||
since: "2019",
|
(async () => {
|
||||||
email: "malo.leblond@example.com",
|
const data = await getAllUser();
|
||||||
phone: "08 67 87 32 21",
|
setUsers(data);
|
||||||
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",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
{volunteers.map((volunteer, index) => (
|
{users.map((user, index) => <VolunteerCard key={index} id={user} /> )}
|
||||||
<Volunteer
|
|
||||||
key={index}
|
|
||||||
name={volunteer.name}
|
|
||||||
role={volunteer.role}
|
|
||||||
since={volunteer.since}
|
|
||||||
email={volunteer.email}
|
|
||||||
phone={volunteer.phone}
|
|
||||||
photo={volunteer.photo}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,125 +1,117 @@
|
|||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
box-sizing: border-box;
|
margin-top: 50px;
|
||||||
margin-top: 50px;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profileboard {
|
.profileboard {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border-radius: 10px;
|
margin-top: 3%;
|
||||||
box-sizing: border-box;
|
display: flex;
|
||||||
margin-top: 3%;
|
flex-direction: column;
|
||||||
position: relative;
|
gap: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.mainContent {
|
.mainContent {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
width: 100%;
|
||||||
align-items: flex-start;
|
flex: 1;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.basicInfo {
|
.basicInfo {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expandedContent {
|
.expandedContent {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: flex-start;
|
width: 100%;
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profilePictureContainer {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profilePicture {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.textInfo {
|
.textInfo {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contactInfo {
|
.contactInfo {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contactInfo p {
|
.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 {
|
.expandButton {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin-left: 10px;
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.voirPlus {
|
.moreButtonClicked {
|
||||||
position: absolute;
|
rotate: 90deg;
|
||||||
bottom: 10px;
|
|
||||||
right: 10px;
|
|
||||||
color: #007bff;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voirPlus:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.profilePictureContainer {
|
.profilePictureContainer {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
height: 150px;
|
height: 150px;
|
||||||
}
|
}
|
||||||
.voirPlus{
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1024px) {
|
@media screen and (min-width: 1024px) {
|
||||||
.container {
|
.container {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 50px auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin-top:50px;
|
}
|
||||||
margin-bottom: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profileboard {
|
.profileboard {
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profilePictureContainer {
|
.profilePictureContainer {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+3
-2
@@ -2,6 +2,7 @@ import { createBrowserRouter } from "react-router";
|
|||||||
import Layout from "./layout.jsx";
|
import Layout from "./layout.jsx";
|
||||||
import HomePage from "./pages/Home/HomePage";
|
import HomePage from "./pages/Home/HomePage";
|
||||||
import LoginPage from "./pages/Login/LoginPage";
|
import LoginPage from "./pages/Login/LoginPage";
|
||||||
|
import StatPage from "./pages/Stat/StatPage";
|
||||||
import RegisterPage from "./pages/Register/RegisterPage.jsx";
|
import RegisterPage from "./pages/Register/RegisterPage.jsx";
|
||||||
import VolunteersPage from "./pages/Volunteers/VolunteersPage";
|
import VolunteersPage from "./pages/Volunteers/VolunteersPage";
|
||||||
import ProfilePage from "./pages/Profile/ProfilePage";
|
import ProfilePage from "./pages/Profile/ProfilePage";
|
||||||
@@ -25,8 +26,8 @@ const router = createBrowserRouter([
|
|||||||
Component: HomePage,
|
Component: HomePage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/login",
|
path: "/stat",
|
||||||
Component: LoginPage,
|
Component:StatPage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path:"/profile",
|
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() {
|
export default async function getUser() {
|
||||||
try {
|
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',
|
method: 'GET',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export default async function getXSRFToken() {
|
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',
|
method: 'GET',
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ export default async function login(email, password) {
|
|||||||
const csrfToken = await getXSRFToken();
|
const csrfToken = await getXSRFToken();
|
||||||
|
|
||||||
try {
|
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',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default async function register(email, password, name, lastname) {
|
|||||||
const csrfToken = await getXSRFToken();
|
const csrfToken = await getXSRFToken();
|
||||||
|
|
||||||
try {
|
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',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: {
|
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" />
|
/// <reference types="vitest/config" />
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react';
|
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));
|
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({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
base: '/',
|
base: '/',
|
||||||
test: {
|
test: {
|
||||||
projects: [{
|
projects: [
|
||||||
extends: true,
|
{
|
||||||
plugins: [
|
extends: true,
|
||||||
// The plugin will run tests for the stories defined in your Storybook config
|
plugins: [
|
||||||
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
|
storybookTest({
|
||||||
storybookTest({
|
configDir: path.join(dirname, '.storybook'),
|
||||||
configDir: path.join(dirname, '.storybook')
|
}),
|
||||||
})],
|
],
|
||||||
test: {
|
test: {
|
||||||
name: 'storybook',
|
name: 'storybook',
|
||||||
browser: {
|
browser: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
headless: true,
|
headless: true,
|
||||||
provider: playwright({}),
|
provider: playwright({}),
|
||||||
instances: [{
|
instances: [{ browser: 'chromium' }],
|
||||||
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