diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx index 03a1161..d467539 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -1,7 +1,8 @@ import styles from "./Header.module.css" import { Link, NavLink } from "react-router"; -import { useEffect, useRef, useState } from "react"; +import {useContext, useEffect, useRef, useState} from "react"; import getUserNotifications from "../../utils/getUserNotifications.js"; +import {AuthContext} from "../../contexts/auth/AuthContext.js"; function Header() { const [notificationMenu, setnotificationMenu] = useState(false); @@ -10,6 +11,7 @@ function Header() { const [notifications, setNotifications] = useState([{"content" : "ok"}, {"content" : "pourquoi pas antoine"}]); const notificationRef = useRef(null); + const { user } = useContext(AuthContext); useEffect(() => { @@ -41,7 +43,7 @@ function Header() { @@ -84,6 +89,9 @@ function Header() { isActive ? styles.activeLink : styles.link}> Bénévoles + {user.isAdmin && isActive ? styles.activeLink : styles.link}> + Administration + } @@ -114,7 +122,7 @@ function Header() { className={styles.closeBtn} onClick={() => setNotifications(prev => prev.filter((_, i) => i !== index))} > - supprimer la notification + supprimer la notification )) diff --git a/src/components/ToolBar/ToolBar.jsx b/src/components/ToolBar/ToolBar.jsx index d1f069f..67ff494 100644 --- a/src/components/ToolBar/ToolBar.jsx +++ b/src/components/ToolBar/ToolBar.jsx @@ -1,8 +1,8 @@ import styles from "./ToolBar.module.css" -import {Link} from "react-router"; import Filter from "../Filter/Filter.jsx"; import {useEffect, useState} from "react"; import SearchBar from "../SearchBar/SearchBar.jsx"; +import CreateEventBtn from "../createEventBtn/CreateEventBtn.jsx"; function ToolBar({setFilters, filters, showCreate = true}) { @@ -37,11 +37,7 @@ function ToolBar({setFilters, filters, showCreate = true}) { ) : null}
{showCreate ? ( - - + - + ) : null} + + setIsOpen(false)}> + +
+
+

Titre

+ setName(e.target.value)} /> +
+ +
+

Description

+ setDescription(e.target.value)} /> +
+ + + +
+
+ +} \ No newline at end of file diff --git a/src/components/createEventBtn/createEventBtn.module.css b/src/components/createEventBtn/createEventBtn.module.css new file mode 100644 index 0000000..5676052 --- /dev/null +++ b/src/components/createEventBtn/createEventBtn.module.css @@ -0,0 +1,41 @@ +.createButton { + font-size: 1.5rem; + color: black; +} + +.createButton { + display: flex; + align-items: center; + justify-content: center; + border: none; + border-radius: 50% !important; + width: 55px !important; + height: 55px !important; + transition: all 0.5s ease; +} + +.createButton:hover { + transform: translateY(-5px); + cursor: pointer; +} + +.inputContainer { + + h2 { + margin-bottom: 0.5rem; + } + + margin: 1em; +} + +.create { + align-self: center; +} + +.section { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 15px; + margin-bottom: 15px; +} \ No newline at end of file diff --git a/src/layout.jsx b/src/layout.jsx index 339734c..4fa0991 100644 --- a/src/layout.jsx +++ b/src/layout.jsx @@ -12,7 +12,8 @@ function Layout(){ const { user, loading } = useContext(AuthContext); if (loading) return

Loading

; - // if (!user) return ; + if (!user) return ; + if(user && user.validate === 0) return ; return (
diff --git a/src/pages/PageNotFound/PageNotFound.jsx b/src/pages/PageNotFound/PageNotFound.jsx new file mode 100644 index 0000000..27b8630 --- /dev/null +++ b/src/pages/PageNotFound/PageNotFound.jsx @@ -0,0 +1,24 @@ +import Background from "../../components/background/background.jsx"; +import styles from "./PageNotFound.module.css" +import { useNavigate } from "react-router"; + +export default function PageNotFound() { + + const navigate = useNavigate(); + + return +
+

Page non trouvée

+
+

+ Désolé, la page que vous recherchez n’existe pas ou n’est plus disponible.
+ Vous pouvez :
+ - Vérifier l’URL pour une éventuelle erreur de saisie.
+ - Revenir à l’accueil navigate("/")} className={styles.redirectSpan}>ici.
+ +

+

Contact : contact@example.com

+
+
+
+} \ No newline at end of file diff --git a/src/pages/PageNotFound/PageNotFound.module.css b/src/pages/PageNotFound/PageNotFound.module.css new file mode 100644 index 0000000..70e36f9 --- /dev/null +++ b/src/pages/PageNotFound/PageNotFound.module.css @@ -0,0 +1,33 @@ +.title { + color: white; + font-size: xx-large; + margin-bottom: 20px; +} + +.content { + display: flex; + flex-direction: column; + z-index: 2; + padding: 10px; +} + +.container { + padding: 20px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 15px; + + p { + font-size: large; + } +} +.redirectSpan { + color: blue; + text-decoration: underline; +} + +.redirectSpan:hover { + cursor: pointer; +} \ No newline at end of file diff --git a/src/pages/Profile/ProfilePage.jsx b/src/pages/Profile/ProfilePage.jsx index aaa1338..b2602d0 100644 --- a/src/pages/Profile/ProfilePage.jsx +++ b/src/pages/Profile/ProfilePage.jsx @@ -31,12 +31,12 @@ function ProfilePage() {

Mail : {user.email}

-

Téléphone : {user.phone}

+

Téléphone : {user.phone === null ? "Pas de numéro enregistré" : user.phone}

-

Vos Événements :

+

Tâches :

{user.tasks && user.tasks.length > 0 ? ( user.tasks.map((task, index) => ( diff --git a/src/pages/VolunteerProfile/VolunteerProfilePage.jsx b/src/pages/VolunteerProfile/VolunteerProfilePage.jsx new file mode 100644 index 0000000..1f18805 --- /dev/null +++ b/src/pages/VolunteerProfile/VolunteerProfilePage.jsx @@ -0,0 +1,69 @@ +import styles from "./VolunteerProfilePage.module.css" +import { useParams } from "react-router"; +import { useEffect, useState } from "react"; +import getUserById from "../../utils/users/getUserById.js"; +import formatDate from "../../utils/date/formatDate.js"; +import Task from "../../components/Task/Task.jsx"; +import { useNavigate } from "react-router"; + +export default function VolunteerProfilePage() { + + const params = useParams(); + const id = params.id; + + const navigate = useNavigate(); + + const [user, setUser] = useState({}); + + useEffect(() => { + (async () => { + const result = await getUserById(id); + if (result?.error === 'not_found') { + navigate("/404"); + } else { + setUser(result); + } + })(); + }, [id]); + + + + return
+
+
+
+ Photo de profil +
+
+
+

{user.name} {user.lastname}

+
+
+
+

Role : {user.role}

+

Membre depuis : {formatDate(user.created_at)}

+
+
+

Mail : {user.email}

+

Téléphone : {user.phone === null ? "Pas de numéro enregistré" : user.phone}

+
+
+ +

Tâches :

+ {user.tasks && user.tasks.length > 0 ? ( + user.tasks.map((task, index) => ( + + )) + ) : ( +

Aucune tâche pour le moment.

+ )} + +
+
+
+
+} \ No newline at end of file diff --git a/src/pages/VolunteerProfile/VolunteerProfilePage.module.css b/src/pages/VolunteerProfile/VolunteerProfilePage.module.css new file mode 100644 index 0000000..e9861dc --- /dev/null +++ b/src/pages/VolunteerProfile/VolunteerProfilePage.module.css @@ -0,0 +1,164 @@ +.container { + width: 100%; + padding: 10px; + box-sizing: border-box; +} + +.profileboard { + width: 100%; + padding: 15px; + border-radius: 20px; + 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; + align-items: center; + gap: 10px; + margin-bottom: 15px; +} + +.infoBlock { + width: 100%; + background: rgba(255, 255, 255, 0.1); + padding: 10px; + border-radius: 5px; +} + +.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; + +} +.profilePicture{ + width:90%; +} + +.settingsBtns { + display: flex; + gap: 5px; + justify-content: right; +} + +@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; + } + .profilePicture{ + width:90%; + } +} +.description button{ + width:50px; + height:50px; + +} + +.headerProfile { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +/* Desktop (1024px et plus) */ +@media screen and (min-width: 1024px) { + .container { + 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; + } +} diff --git a/src/pages/waitValidationPage/ValidationErrorPage.jsx b/src/pages/waitValidationPage/ValidationErrorPage.jsx new file mode 100644 index 0000000..15995e9 --- /dev/null +++ b/src/pages/waitValidationPage/ValidationErrorPage.jsx @@ -0,0 +1,30 @@ +import styles from "./validationErrorPage.module.css" +import Background from "../../components/background/background.jsx"; +import Button from "../../components/ui/button/button.jsx"; +import { useContext } from "react"; +import { AuthContext } from "../../contexts/auth/AuthContext.js"; +import { Navigate, useNavigate } from "react-router"; + +export default function ValidationErrorPage() { + + const { logout, user } = useContext(AuthContext); + const navigate = useNavigate(); + + const handleLogout = async () => { + await logout(); + navigate("/login"); + } + + if(user && user.validate === 1) return ; + + return +
+

Votre compte n'a pas été validé

+
+

Votre compte n'a pas encore été validé, veuillez attendre la vérification de votre compte par un membre autorisé.

+

Contact : contact@example.com

+ +
+
+
+} \ No newline at end of file diff --git a/src/pages/waitValidationPage/validationErrorPage.module.css b/src/pages/waitValidationPage/validationErrorPage.module.css new file mode 100644 index 0000000..e4a398d --- /dev/null +++ b/src/pages/waitValidationPage/validationErrorPage.module.css @@ -0,0 +1,25 @@ +.title { + color: white; + font-size: xx-large; + margin-bottom: 20px; +} + +.content { + display: flex; + flex-direction: column; + z-index: 2; + padding: 10px; +} + +.container { + padding: 20px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 15px; + + p { + font-size: large; + } +} \ No newline at end of file diff --git a/src/router.js b/src/router.js index f54d582..73e049e 100644 --- a/src/router.js +++ b/src/router.js @@ -7,6 +7,9 @@ import VolunteersPage from "./pages/Volunteers/VolunteersPage"; import ProfilePage from "./pages/Profile/ProfilePage"; import EventsPage from "./pages/Events/EventsPage.jsx"; import AdminPage from "./pages/Admin/AdminPage.jsx"; +import ValidationErrorPage from "./pages/waitValidationPage/ValidationErrorPage.jsx"; +import VolunteerProfilePage from "./pages/VolunteerProfile/VolunteerProfilePage.jsx"; +import PageNotFound from "./pages/PageNotFound/PageNotFound.jsx"; const router = createBrowserRouter([ { @@ -17,6 +20,18 @@ const router = createBrowserRouter([ path: "/register", Component: RegisterPage, }, + { + path: "/error/validation", + Component: ValidationErrorPage + }, + { + path: "/*", + Component: PageNotFound, + }, + { + path: "/404", + Component: PageNotFound, + }, { path: "/", Component: Layout, @@ -41,6 +56,10 @@ const router = createBrowserRouter([ path: "/volunteers", Component: VolunteersPage, }, + { + path: "/profile/:id", + Component: VolunteerProfilePage, + } ] } ]) diff --git a/src/stories/ThemeSwitcher.stories.jsx b/src/stories/ThemeSwitcher.stories.jsx new file mode 100644 index 0000000..f285b15 --- /dev/null +++ b/src/stories/ThemeSwitcher.stories.jsx @@ -0,0 +1,20 @@ +import ThemeSwitcher from "../components/ui/themeSwitcher/ThemeSwitcher.jsx"; +import Background from "../components/background/background.jsx"; + +export default { + title: 'UI/ThemeSwitcher', + component: ThemeSwitcher, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +}; + +const ThemeSwitcherRender = () => { + return + + +}; + +export const Default = () => ; + diff --git a/src/utils/event/createEvent.js b/src/utils/event/createEvent.js new file mode 100644 index 0000000..4098dd7 --- /dev/null +++ b/src/utils/event/createEvent.js @@ -0,0 +1,36 @@ +import getXSRFToken from "../getXSRF.js"; + +export default async function createEvent(name, description) { + const csrfToken = await getXSRFToken(); + + try { + const response = await fetch(`http://${import.meta.env.VITE_API_URL}/api/events/create`, { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'X-XSRF-TOKEN': csrfToken, + 'Accept': 'application/json' + }, + body: JSON.stringify({ name, description }), + }); + + const data = await response.json(); + + if (!response.ok) { + if (response.status === 422) { + return { + status: 422, + errors: data.errors + }; + } + + throw new Error(`Erreur HTTP ${response.status}`); + } + + return { status: response.status, data }; + + } catch (error) { + return { status: "error", error }; + } +} diff --git a/src/utils/users/getUserById.js b/src/utils/users/getUserById.js index 59af94a..1fad659 100644 --- a/src/utils/users/getUserById.js +++ b/src/utils/users/getUserById.js @@ -1,4 +1,4 @@ -export default async function getUserById (id) { +export default async function getUserById(id) { try { const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/${id}`, { method: 'GET', @@ -9,12 +9,15 @@ export default async function getUserById (id) { }); if (!res.ok) { + if (res.status === 404) { + return { error: 'not_found' }; + } throw new Error(`Erreur serveur : ${res.status}`); } return await res.json(); } catch (err) { console.error('Erreur :', err); - return null; + return { error: 'server_error' }; } -} \ No newline at end of file +}