Merge branch 'dev' into 'features/stat'

# Conflicts:
#   package-lock.json
#   src/router.js
#   vite.config.js
This commit is contained in:
T'JAMPENS QUENTIN p2406187
2025-12-02 13:52:55 +00:00
45 changed files with 1732 additions and 215 deletions
-3
View File
@@ -1,3 +0,0 @@
<svg width="60px" height="60px" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34.325 52.5C33.8855 53.2577 33.2546 53.8866 32.4956 54.3238C31.7365 54.761 30.8759 54.9911 30 54.9911C29.1241 54.9911 28.2635 54.761 27.5044 54.3238C26.7454 53.8866 26.1145 53.2577 25.675 52.5M45 20C45 16.0218 43.4196 12.2064 40.6066 9.3934C37.7936 6.58035 33.9782 5 30 5C26.0218 5 22.2064 6.58035 19.3934 9.3934C16.5804 12.2064 15 16.0218 15 20C15 37.5 7.5 42.5 7.5 42.5H52.5C52.5 42.5 45 37.5 45 20Z" stroke="#1E1E1E" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 605 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000"><path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/></svg>

Before

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000"><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.93H180Zm60-60h480v-28.93q0-12.15-7.04-22.5-7.04-10.34-19.11-16.88-51.7-25.46-105.42-38.58Q534.7-367.69 480-367.69q-54.7 0-108.43 13.11-53.72 13.12-105.42 38.58-12.07 6.54-19.11 16.88-7.04 10.35-7.04 22.5v28.93Zm240-304.62q33 0 56.5-23.5t23.5-56.5q0-33-23.5-56.5t-56.5-23.5q-33 0-56.5 23.5t-23.5 56.5q0 33 23.5 56.5t56.5 23.5Zm0-80Zm0 384.62Z"/></svg>

Before

Width:  |  Height:  |  Size: 844 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

+79
View File
@@ -0,0 +1,79 @@
import styles from "./Event.module.css";
import {useState, useEffect} from "react";
import {Link} from "react-router";
import Button from "/src/components/ui/button/button.jsx"
function Event({event}){
const [eventMenu, seteventMenu] = useState(false);
const [windowSize, setWindowSize] = useState({
width: null,
height: null,
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener("resize", handleResize);
handleResize();
return () => window.removeEventListener("resize", handleResize);
}, []);
return (
<div className={`${styles.eventContainer} glassCard`}>
<div className={styles.eventContainerTop}>
<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={event.image + `.png`} alt="Photo de l'événement" />
)}
</div>
<div className={styles.eventInfos}>
<p className={styles.eventTitle}>{event.name}</p>
<p>{event.description}</p>
<div className={eventMenu ? styles.eventMenuOpen : styles.eventMenuClose}>
<ul>
{event.tasks
.slice(0,5)
.map((task, index) => (
<li key={index}> - {task.name}</li>
))}
{event.tasks.length > 5 ? (
<li>...</li>
) : null}
</ul>
</div>
</div>
</div>
{windowSize.width > 768 ? (
<div className={styles.eventButtons}>
<button
className={`${styles.moreButton} ${eventMenu ? styles.moreButtonClicked : ""}`}
onClick={()=>{seteventMenu(!eventMenu)}}>
{">"}
</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>
) : null}
</div>
</div>
)
}
export default Event;
+135
View File
@@ -0,0 +1,135 @@
.eventContainer {
display: flex;
flex-direction: column;
margin: 1rem 0;
width: 80%;
}
.eventContainerTop {
display: flex;
}
.eventContainerBottom{
display: flex;
align-items: center;
justify-content: end;
}
.moreLink {
width: fit-content;
}
.eventImageDiv {
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
width: auto;
height: 6rem;
flex-shrink: 0;
}
.eventImage {
max-width: 100%;
max-height: 100%;
object-fit: contain;
border-radius: 20px;
}
.eventContent {
display: flex;
}
.eventTitle {
font-size: 2rem;
}
.eventButtons {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
width: fit-content;
overflow: visible;
}
.eventButtons a {
white-space: nowrap;
}
.moreButton {
background: none;
border: none;
color: #019AFF;
font-size: 1.5rem;
transition: all 0.2s ease;
cursor: pointer;
border-radius: 50%;
width: 55px;
height: 55px;
}
.moreButton:hover {
font-size: 2rem;
background: rgba(191, 191, 191, 0.5);
}
.moreButton:focus {
outline: none;
}
.moreButton:focus-visible {
outline: 2px solid black;
}
.moreButtonClicked {
rotate: 90deg;
}
.eventMenuOpen {
display: flex;
}
.eventMenuClose {
display: none;
}
@media screen and (max-width: 768px) {
.eventImage {
width: 100%;
height: auto;
display: block;
object-fit: cover;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.eventContainer{
padding: 0;
}
.eventImageDiv {
margin: 0;
}
.eventContent {
flex-direction: column;
}
.eventInfos, .eventContainerBottom {
padding: 10px;
}
}
+61
View File
@@ -0,0 +1,61 @@
import styles from "./Filter.module.css";
function Filter({isFilterVisible, filters, setFilters}){
const setAlphabeticalOrder = (order) => {
setFilters(prev => ({
...prev,
alphabetical : prev.alphabetical === order ? null : order
}));
};
const setYearOrder = (order) => {
setFilters(prev => ({
...prev,
yearOrder: prev.yearOrder === order ? null : order
}));
};
return (
<div className={`${styles.filterContainer} ${isFilterVisible ? styles.filterOpen : styles.filterClose}`}>
<abbr title="Tri alphabétique croissant (A à Z)">
<button className={`${styles.filterTag} glassCard ${filters.alphabetical !== "asc" ? "" : styles.active}`}
onClick={() => setAlphabeticalOrder("asc")}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
<path d="m80-280 150-400h86l150 400h-82l-34-96H196l-32 96H80Zm140-164h104l-48-150h-6l-50 150Zm328 164v-76l202-252H556v-72h282v76L638-352h202v72H548ZM360-760l120-120 120 120H360ZM480-80 360-200h240L480-80Z"/>
</svg>
</button>
</abbr>
<abbr title="Tri alphabétique décroissant (Z à A)">
<button className={`${styles.filterTag} glassCard ${filters.alphabetical !== "desc" ? "" : styles.active}`}
onClick={() => setAlphabeticalOrder("desc")}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
<path d="M 480,-880 360,-760 H 600 Z M 360,-200 480,-80 600,-200 Z" />
<path d="M 71.679924,-674.40094 H 398.50264 v 60.67705 l -208.59362,252.3436 h 214.5832 v 75.7812 H 65.690344 v -60.67705 L 274.28397,-598.61973 H 71.679924 Z" />
<path d="M 769.04932,-356.43238 H 612.27858 l -24.73957,70.83329 H 486.75782 l 144.01033,-388.80185 h 119.53118 l 144.01033,388.80185 H 793.52847 Z m -131.77076,-72.13537 h 106.51035 l -53.12496,-154.68741 z" />
</svg>
</button>
</abbr>
<abbr title="Tri chronologique croissant (du plus ancien au plus récent)">
<button className={`${styles.filterTag} glassCard ${filters.yearOrder !== "asc" ? "" : styles.active}`}
onClick={() => setYearOrder("asc")}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
<path d="M340-160q-125 0-212.5-87.5T40-460q0-125 87.5-212.5T340-760q52 0 98 16.5t84 45.5l42-42 56 56-42 42q29 38 45.5 84.5T640-460q0 125-87.5 212.5T340-160Zm400 0v-488l-44 44-56-56 140-140 140 140-57 56-43-43v487h-80ZM240-800v-80h200v80H240Zm100 560q92 0 156-64t64-156q0-92-64-156t-156-64q-92 0-156 64t-64 156q0 92 64 156t156 64Zm-40-180h80v-200h-80v200Zm40-40Z"/>
</svg>
</button>
</abbr>
<abbr title="Tri chronologique décroissant (du plus récent au plus ancien)">
<button className={`${styles.filterTag} glassCard ${filters.yearOrder !== "desc" ? "" : styles.active}`}
onClick={() => setYearOrder("desc")}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
<path d="M340-160q-125 0-212.5-87.5T40-460q0-125 87.5-212.5T340-760q52 0 98 16.5t84 45.5l42-42 56 56-42 42q29 38 45.5 84.5T640-460q0 125-87.5 212.5T340-160Zm440 0L640-300l56-56 44 44v-488h80v487l43-43 57 56-140 140ZM240-800v-80h200v80H240Zm100 560q92 0 156-64t64-156q0-92-64-156t-156-64q-92 0-156 64t-64 156q0 92 64 156t156 64Zm-40-180h80v-200h-80v200Zm40-40Z"/>
</svg>
</button>
</abbr>
</div>
)
}
export default Filter;
+85
View File
@@ -0,0 +1,85 @@
.filterContainer {
display: flex;
flex-direction: column;
gap: 0.4rem;
overflow: hidden;
max-height: 0;
opacity: 0;
transition: max-height 0.5s ease, opacity 0.8s ease, transform 0.5s ease;
}
.filterOpen {
max-height: 200px;
opacity: 1;
transform: translateY(0);
}
.filterClose{
max-height: 200px;
opacity: 0;
}
.filterTag {
border: none;
background: none;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
width: 45px;
height: 45px;
}
.filterTag:focus {
outline: none;
}
.filterTag:focus-visible {
outline: 2px solid black;
}
.filterTag:hover {
cursor: pointer;
svg{
fill: #019AFF;
}
}
.filterTag svg {
transform: scale(1);
color: black;
}
.active {
border: 1px solid #019AFF;
}
.active svg {
fill: #019AFF;
transform: scale(1);
}
@media screen and (max-width: 768px){
.filterContainer {
flex-direction: row;
flex-wrap: wrap;
}
.filterOpen{
display: flex;
}
.filterClose{
display: none;
}
.filterMenu {
width: 100%;
}
abbr {
width: fit-content;
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ function Header() {
<nav className={`${mobileMenu ? styles.inactiveHeaderContent : styles.activeHeaderContent}`}>
<div className={styles.headerLeftContent}>
<NavLink className={styles.logoLink} to="/">
<img src="/src/assets/logo.png" alt="logo" className={styles.logo} />
<img src="logo.png" alt="logo" className={styles.logo} />
</NavLink>
<div className={styles.navLinks}>
<NavLink to="/" className={({ isActive }) => isActive ? styles.activeLink : styles.link}>
+1 -1
View File
@@ -179,7 +179,7 @@ svg {
opacity: 0;
transform: translateY(-10px);
transition: opacity 0.3s ease, transform 0.3s ease;
z-index: 1;
z-index: 0;
}
.activeNotificationDiv {
@@ -1,47 +1,22 @@
/*import { useState } from "react";
import login from "../../utils/login.js";
import getUser from "../../utils/getUser.js";
import styles from "./form.module.css";
function LoginForm({ className }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
async function handleSubmit(e) {
e.preventDefault();
await login(email, password);
getUser();
}
return (
<div className={`${className} ${"glassCard"}`}>
<div className={styles.formgroup}>
<input type="email" id="email" value={email} placeholder="Adresse mail" onChange={e => setEmail(e.target.value)} required/>
</div>
import { useState, useContext } from "react";
import styles from "./loginForm.module.css";
import Button from "../ui/button/button.jsx";
import Input from "../ui/input/input.jsx";
import { useNavigate } from "react-router";
import { AuthContext } from "../../contexts/auth/AuthContext.js";
<div className={styles.formgroup}>
<input type="password" id="password" value={password} placeholder="Mot de passe" onChange={e => setPassword(e.target.value)} required/>
<br/>
</div>
<div className={styles.logButton} >
<button className={styles.loginButton} onClick={e => handleSubmit(e)}>Se connecter</button>
</div>
</div>
);
}
export default LoginForm;*/
import { useState } from "react";
import login from "../../utils/login.js";
import getUser from "../../utils/getUser.js";
import styles from "./form.module.css";
function LoginForm({ className }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
let navigate = useNavigate();
const { login } = useContext(AuthContext);
async function handleSubmit(e) {
e.preventDefault();
await login(email, password);
getUser();
navigate("/");
}
const toggleShowPassword = () => {
@@ -51,27 +26,22 @@ function LoginForm({ className }) {
return (
<div className={`${className} ${"glassCard"}`}>
<div className={styles.formgroup}>
<input
<Input
type="email"
id="email"
className={styles.input}
value={email}
placeholder="Adresse mail"
onChange={e => setEmail(e.target.value)}
required
style={{ width: '100%' }}
/>
</div>
<div className={styles.formgroup}>
<div style={{ position: 'relative', width: '100%' }}>
<input
<Input
type={showPassword ? "text" : "password"}
id="password"
className={styles.input}
value={password}
placeholder="Mot de passe"
onChange={e => setPassword(e.target.value)}
required
style={{ width: '100%', paddingRight: '40px' }}
style={{ width: '100%' }}
/>
<button
type="button"
@@ -92,8 +62,9 @@ function LoginForm({ className }) {
</button>
</div>
</div>
<div className={styles.logButton}>
<button className={styles.loginButton} onClick={handleSubmit}>Se connecter</button>
<Button onClick={handleSubmit} variant={"default"}> Se connecter </Button>
</div>
</div>
);
+23
View File
@@ -0,0 +1,23 @@
import styles from "./modal.module.css"
import Button from "../button/button.jsx";
const Modal = ({ open, onClose, children, title }) => {
if(!open) return null;
return <div className={styles.overlay}>
<div className={styles.modal}>
{title && <h2>{title}</h2>}
{children}
<div className={styles.modalFooter}> <Button onClick={onClose}>Fermer</Button> </div>
</div>
</div>
}
export default Modal;
+35
View File
@@ -0,0 +1,35 @@
.overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.25);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal {
padding: 20px 20px 20px 20px;
border-radius: 8px;
min-width: 300px;
max-width: 70%;
position: relative;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
width: fit-content;
border: 1px solid rgba(200, 200, 200, 0.3);
background: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
color: #000;
outline: none;
}
.modalFooter {
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
+3
View File
@@ -0,0 +1,3 @@
import { createContext } from "react";
export const AuthContext = createContext(null);
+37
View File
@@ -0,0 +1,37 @@
import { useState, useEffect } from "react";
import loginFunction from "../../utils/login.js";
import getUser from "../../utils/getUser.js";
import { AuthContext } from "./AuthContext";
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
(async () => {
const u = await getUser();
setUser(u);
setLoading(false);
})();
}, []);
const login = async (email, password) => {
const res = await loginFunction(email, password);
if (!res || res.status !== 200) throw new Error("Login error");
const u = await getUser();
setUser(u);
return res;
};
const logout = async () => {
// TODO: Appeller fonction logout
setUser(null);
};
return (
<AuthContext.Provider value={{ user, loading, login, logout }}>
{children}
</AuthContext.Provider>
);
}
+6 -5
View File
@@ -17,16 +17,17 @@
-moz-osx-font-smoothing: grayscale;
}
@media screen and (max-width: 768px) {
.content{
padding: 0.7rem;
z-index:1;
}
}
.content {
padding: 1.5rem;
z-index: 1;
}
a {
font-weight: 500;
color: #646cff;
+8
View File
@@ -2,10 +2,18 @@ import { Outlet } from "react-router";
import Footer from "./components/Footer/Footer.jsx"
import Header from "./components/Header/Header.jsx";
import Background from "./components/background/background.jsx";
import { Navigate } from "react-router";
import { useContext} from "react";
import { AuthContext } from "./contexts/auth/AuthContext.js";
function Layout(){
const { user, loading } = useContext(AuthContext);
if (loading) return <p>Loading</p>;
if (!user) return <Navigate to="/login" />;
return (
<div>
<Background>
+4 -1
View File
@@ -3,10 +3,13 @@ import { createRoot } from 'react-dom/client'
import './index.css'
import { RouterProvider } from "react-router";
import router from './router';
import { AuthProvider } from "./contexts/auth/authProvider.jsx";
createRoot(document.getElementById('root')).render(
<StrictMode>
<RouterProvider router={router} />
<AuthProvider>
<RouterProvider router={router} />
</AuthProvider>
</StrictMode>,
)
+251
View File
@@ -0,0 +1,251 @@
import {useState} from "react";
import Event from "../../components/Event/Event.jsx";
import {Link} from "react-router";
import styles from "./EventsPage.module.css";
import Filter from "../../components/Filter/Filter.jsx";
function EventsPage(){
const [isFilterVisible, setIsFilterVisible] = useState(false);
const toggleFilters = () => {
setIsFilterVisible(prev => !prev);
};
const [filters, setFilters] = useState({
alphabetical: "asc",
yearOrder: "asc",
});
const [events] = useState([
{
id: 1,
name: "Marathon de la Ville",
description: "Participez au marathon annuel et soutenez les associations locales.",
image: null,
tasks: [
{
name: "Inscription des participants",
start: "2018-09-24 22:21:20",
end: "2018-09-24 22:21:20",
place: "Centre sportif",
description: "Accueillir et enregistrer tous les coureurs.",
volunteers_count: 5
},
{
name: "Distribution des dossards",
start: "2018-09-24 22:21:20",
end: "2018-09-24 22:21:20",
place: "Salle principale",
description: "Remettre les dossards et goodies aux participants.",
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 lentretien.",
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
},
]
},
]);
const compareDate = (dateA, dateB) => {
const dA = new Date(dateA);
const dB = new Date(dateB);
if (filters.yearOrder === "asc") {
return dB - dA;
} else {
return dA - dB;
}
};
const sortedEvents = [...events].sort((a, b) => {
const sortedATasks = a.tasks.sort((taskA, taskB) =>
compareDate(taskA.start, taskB.start)
);
const sortedBTasks = b.tasks.sort((taskA, taskB) =>
compareDate(taskA.start, taskB.start)
);
const timeComparison = compareDate(
sortedATasks[0].start,
sortedBTasks[0].start
);
if (timeComparison !== 0) {
return timeComparison;
}
if (filters.alphabetical !== null) {
return filters.alphabetical === "asc"
? a.name.localeCompare(b.name)
: b.name.localeCompare(a.name);
}
return 0;
});
return (
<div className={styles.eventsContainer}>
<div className={styles.eventsButtons}>
<Link
to="/event/create"
className={`${styles.createButton} glassCard`}>
+
</Link>
<div className={`${styles.filterContainer} ${isFilterVisible ? "glassCard" : ""}`}>
<button className={`${isFilterVisible ? "" : styles.mobileFilter} ${styles.sortButton} glassCard`} onClick={toggleFilters}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
<path d="M440-160q-17 0-28.5-11.5T400-200v-240L168-736q-15-20-4.5-42t36.5-22h560q26 0 36.5 22t-4.5 42L560-440v240q0 17-11.5 28.5T520-160h-80Zm40-308 198-252H282l198 252Zm0 0Z"/>
</svg>
</button>
<div className={styles.filterMenu}>
<Filter isFilterVisible={isFilterVisible} filters={filters} setFilters={setFilters}/>
</div>
</div>
</div>
<div className={styles.eventsList}>
{sortedEvents.map((event, index) => (
<Event key={index} event={event} />
))}
</div>
</div>
)
}
export default EventsPage;
+99
View File
@@ -0,0 +1,99 @@
.eventsContainer {
width: 100%;
}
.eventsList{
display: flex;
align-items: center;
flex-direction: column;
}
.eventsButtons {
overflow: visible;
position: fixed;
top: 10rem;
right: 2rem;
display: flex;
flex-direction: column;
align-items: flex-end;
width: fit-content;
height: fit-content;
gap: 1rem;
}
.filterContainer{
padding: 0;
border-radius: 30px;
overflow: visible;
}
.filterMenu{
padding: 5px 5px 5px 5px;
}
.createButton, .sortButton {
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 50%;
width: 55px;
height: 55px;
transition: all 0.5s ease;
}
.createButton {
font-size: 2rem;
color: #019AFF;
}
.sortButton svg {
transform: scale(0.8);
fill: #019AFF;
}
.sortButton:hover {
cursor: pointer;
}
.createButton:hover, .mobileFilter:hover {
transform: translateY(-5px);
}
.sortButton:focus {
outline: none;
}
.sortButton:focus-visible {
outline: 2px solid black;
}
@media screen and (max-width: 768px) {
.eventsButtons {
flex-direction: row;
position: relative;
top: 0;
right: 0;
align-items: flex-start;
width: 100%;
margin-top: 16px;
}
.filterContainer{
display: flex;
width: fit-content;
}
.filterMenu{
width: fit-content;
}
.createButton:hover, .mobileFilter:hover {
transform: none !important;
}
}
+7 -4
View File
@@ -1,12 +1,15 @@
import styles from "./LoginPage.module.css";
import LoginForm from "../../components/form/form.jsx";
import LoginForm from "../../components/loginform/loginForm.jsx";
import Background from "../../components/background/background.jsx";
function LoginPage() {
return (
return <Background>
<div className={styles.container}>
<LoginForm />
<LoginForm />
</div>
);
</Background>
}
export default LoginPage;
+1
View File
@@ -9,6 +9,7 @@
align-items: center;
padding: 20px;
border-radius:12px;
margin-left: 33.3%;
}
+110
View File
@@ -0,0 +1,110 @@
import React, { useState } 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>
);
}
function ProfilePage() {
return (
<div className={styles.container}>
<div className={`${styles.profileboard} glassCard`}>
<div className={styles.contentWrapper}>
<div className={styles.profilePictureContainer}>
<img
src="../src/assets/react.svg"
alt="Photo de profil"
className={styles.profilePicture}
/>
</div>
<div className={styles.description}>
<h2>LEBLOND Malo</h2>
<div className={styles.topDescription}>
<div className={styles.infoBlock}>
<p><strong>Role :</strong> Président</p>
<p><strong>Membre depuis :</strong> 2019</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>
</div>
<button href="/../UpdateProfile.jsx">M</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"]}
/>
</div>
</div>
</div>
</div>
);
}
export default ProfilePage;
+158
View File
@@ -0,0 +1,158 @@
.container {
width: 100%;
padding: 10px;
box-sizing: border-box;
}
.profileboard {
width: 100%;
padding: 15px;
border-radius: 10px;
box-sizing: border-box;
}
.contentWrapper {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.profilePictureContainer {
width: 120px;
height: 120px;
margin-bottom: 15px;
}
.profilePicture {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.description {
width: 100%;
text-align: left;
margin-left:4%;
}
.topDescription {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 15px;
}
.infoBlock {
width: 100%;
background: rgba(255, 255, 255, 0.1);
padding: 10px;
border-radius: 5px;
}
.event {
width: 100%;
padding: 12px;
margin-bottom: 10px;
border-radius: 8px;
}
.eventTasks {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out, opacity 0.2s ease;
opacity: 0;
padding-left: 20px;
}
.eventTasks.expanded {
max-height: 500px;
opacity: 1;
margin-top: 10px;
}
.description h2{
font-size:30px;
margin-bottom: 2%;
}
.profilePicture{
width:90%;
}
/* Tablette (768px et plus) */
@media screen and (min-width: 768px) {
.contentWrapper {
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
}
.profilePictureContainer {
width: 150px;
height: 150px;
margin-right: 20px;
margin-bottom: 0;
}
.description {
width: calc(100% - 170px);
margin-left:4%;
}
.topDescription {
flex-direction: row;
justify-content: space-between;
}
.infoBlock {
width: 48%;
}
.event {
padding: 15px;
}
.description h2{
font-size:30px;
margin-bottom: 2%;
}
.profilePicture{
width:90%;
}
}
.description button{
width:50px;
height:50px;
}
/* Desktop (1024px et plus) */
@media screen and (min-width: 1024px) {
.container {
max-width: 100%;
margin: 0 auto;
padding: 20px;
}
.profileboard {
padding: 25px;
}
.profilePictureContainer {
width: 30%;
height:auto;
}
.profilePicture{
width:90%;
}
.description {
width: calc(100% - 200px);
margin-left:4%;
}
.event {
padding: 18px;
}
.description h2{
font-size:30px;
margin-bottom: 2%;
}
}
+105
View File
@@ -0,0 +1,105 @@
import styles from "./RegisterPage.module.css";
import Background from "../../components/background/background.jsx";
import Input from "../../components/ui/input/input.jsx";
import Button from "../../components/ui/button/button.jsx";
import { useState } from "react";
import register from "../../utils/register.js";
import { useNavigate } from "react-router";
function RegisterPage() {
const navigate = useNavigate();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [name, setName] = useState("");
const [lastName, setLastName] = useState("");
const [showPassword, setShowPassword] = useState(false);
async function handleSubmit(e) {
e.preventDefault();
const res = await register(email, password, name, lastName);
if (!res || res.status !== 200) throw new Error("Register error");
navigate("/login");
}
return <Background>
<div className={styles.container}>
<div className={"glassCard"}>
<h1>Créer un compte</h1>
<div className={styles.formGroup}>
<Input
value={name}
placeholder="Prénom"
onChange={e => setName(e.target.value)}
style={{ width: '100%' }}
/>
</div>
<div className={styles.formGroup}>
<Input
value={lastName}
placeholder="Nom"
onChange={e => setLastName(e.target.value)}
style={{ width: '100%' }}
/>
</div>
<div className={styles.formGroup}>
<Input
type="email"
value={email}
placeholder="Adresse mail"
onChange={e => setEmail(e.target.value)}
style={{ width: '100%' }}
/>
</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>
</div>
<div className={styles.logButton}>
<Button variant={"transparent"} onClick={() => navigate("/login")}>Vous avez déjà un compte ?</Button>
<Button variant={"default"} onClick={handleSubmit}> Se connecter </Button>
</div>
</div>
</div>
</Background>
}
export default RegisterPage;
@@ -0,0 +1,29 @@
.container {
position: relative;
z-index: 2;
display: flex;
width: 30%;
justify-content: center;
align-items: center;
padding: 20px;
border-radius:12px;
}
:global(.glassCard) {
--glass-bg: #ffffff9f;
}
.logButton{
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
margin-top: 12px;
}
.formGroup{
margin-top:23px;
}
h1 {
overflow: visible;
text-align: center;
}
+108
View File
@@ -0,0 +1,108 @@
import React, { useState } from "react";
import styles from "./VolunteersPage.module.css";
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",
},
];
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}
/>
))}
</div>
);
}
export default VolunteerPage;
@@ -0,0 +1,125 @@
.container {
width: 100%;
padding: 10px;
box-sizing: border-box;
margin-top: 50px;
}
.profileboard {
width: 100%;
padding: 15px;
border-radius: 10px;
box-sizing: border-box;
margin-top: 3%;
position: relative;
}
.mainContent {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
width: 100%;
}
.basicInfo {
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;
}
.textInfo {
display: flex;
flex-direction: column;
flex: 1;
}
.contactInfo {
margin-top: 10px;
}
.contactInfo p {
margin: 5px 0;
}
.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;
}
.voirPlus {
position: absolute;
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) {
.profilePictureContainer {
width: 150px;
height: 150px;
}
.voirPlus{
text-align: right;
}
}
@media screen and (min-width: 1024px) {
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
margin-top:50px;
margin-bottom: 50px;
}
.profileboard {
padding: 25px;
}
.profilePictureContainer {
width: 200px;
height: auto;
}
}
+24
View File
@@ -3,8 +3,20 @@ 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";
import EventsPage from "./pages/Events/EventsPage.jsx";
const router = createBrowserRouter([
{
path: "/login",
Component: LoginPage,
},
{
path: "/register",
Component: RegisterPage,
},
{
path: "/",
Component: Layout,
@@ -20,6 +32,18 @@ const router = createBrowserRouter([
{
path: "/stat",
Component:StatPage,
},
{
path:"/profile",
Component:ProfilePage,
},
{
path: "/events",
Component: EventsPage,
},
{
path: "/volunteers",
Component: VolunteersPage,
}
]
}
+49
View File
@@ -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>,
};
+19 -10
View File
@@ -1,12 +1,21 @@
export default async function getUser() {
await fetch('http://localhost:8000/api/me', {
method: 'GET',
credentials: 'include',
headers: {
'Accept': 'application/json'
try {
const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/me`, {
method: 'GET',
credentials: 'include',
headers: {
'Accept': 'application/json'
}
});
if (!res.ok) {
throw new Error(`Erreur serveur : ${res.status}`);
}
})
.then(res => res.json())
.then(data => console.log('Utilisateur connecté :', data))
.catch(err => console.error('Erreur :', err));
}
const data = await res.json();
return data;
} catch (err) {
console.error('Erreur :', err);
return null;
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
export default async function getXSRFToken() {
const response = await fetch('http://localhost:8000/sanctum/csrf-cookie', {
const response = await fetch(`http://${import.meta.env.VITE_API_URL}/sanctum/csrf-cookie`, {
method: 'GET',
credentials: 'include'
});
+25 -27
View File
@@ -3,31 +3,29 @@ import getXSRFToken from "./getXSRF.js";
export default async function login(email, password) {
const csrfToken = await getXSRFToken();
await fetch('http://localhost:8000/api/users/login', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': csrfToken,
'Accept': 'application/json'
},
body: JSON.stringify({
email: email,
password: password
})
})
.then(response => {
console.log('Code HTTP :', response.status);
if (!response.ok) {
throw new Error(`Erreur HTTP ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('Connexion réussie ! Données utilisateur :', data);
})
.catch(error => {
console.error('Erreur lors de la connexion :', error.message);
try {
const response = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/login`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': csrfToken,
'Accept': 'application/json'
},
body: JSON.stringify({ email, password })
});
}
console.log('Code HTTP :', response.status);
if (!response.ok) {
throw new Error(`Erreur HTTP ${response.status}`);
}
const data = await response.json();
console.log('Connexion réussie ! Données utilisateur :', data);
return { status: response.status, data };
} catch (error) {
return error;
}
}
+30
View File
@@ -0,0 +1,30 @@
import getXSRFToken from "./getXSRF.js";
export default async function register(email, password, name, lastname) {
const csrfToken = await getXSRFToken();
try {
const response = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/register`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': csrfToken,
'Accept': 'application/json'
},
body: JSON.stringify({ email, password, name, lastname }),
});
console.log('Code HTTP :', response.status);
if (!response.ok) {
throw new Error(`Erreur HTTP ${response.status}`);
}
const data = await response.json();
return { status: response.status, data };
} catch (error) {
return error;
}
}