diff --git a/.env.exemple b/.env.exemple new file mode 100644 index 0000000..3452b5c --- /dev/null +++ b/.env.exemple @@ -0,0 +1 @@ +VITE_API_URL= \ No newline at end of file diff --git a/.gitignore b/.gitignore index f52343a..bd7187d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ dist-ssr *storybook.log storybook-static + +.env \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index dc53079..4b8ff7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,9 @@ "name": "sae-front", "version": "0.0.0", "dependencies": { + "chart.js": "^4.5.1", "react": "^19.1.1", + "react-chartjs-2": "^5.3.1", "react-dom": "^19.1.1", "react-router": "^7.9.4" }, diff --git a/package.json b/package.json index d2aa97d..c86a179 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,13 @@ "lint": "eslint .", "preview": "vite preview", "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build" + "build-storybook": "storybook build", + "test": "vitest" }, "dependencies": { + "chart.js": "^4.5.1", "react": "^19.1.1", + "react-chartjs-2": "^5.3.1", "react-dom": "^19.1.1", "react-router": "^7.9.4" }, @@ -26,17 +29,17 @@ "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.4", + "@vitest/browser-playwright": "^4.0.8", + "@vitest/coverage-v8": "^4.0.8", "eslint": "^9.36.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.22", "eslint-plugin-storybook": "^10.0.6", "globals": "^16.4.0", + "playwright": "^1.56.1", "prop-types": "^15.8.1", "storybook": "^10.0.6", "vite": "^7.1.7", - "vitest": "^4.0.8", - "playwright": "^1.56.1", - "@vitest/browser-playwright": "^4.0.8", - "@vitest/coverage-v8": "^4.0.8" + "vitest": "^4.0.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f9b236..c6cc5fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,15 @@ importers: .: dependencies: + chart.js: + specifier: ^4.5.1 + version: 4.5.1 react: specifier: ^19.1.1 version: 19.2.0 + react-chartjs-2: + specifier: ^5.3.1 + version: 5.3.1(chart.js@4.5.1)(react@19.2.0) react-dom: specifier: ^19.1.1 version: 19.2.0(react@19.2.0) @@ -436,6 +442,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@kurkle/color@0.3.4': + resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} + '@mdx-js/react@3.1.1': resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} peerDependencies: @@ -949,6 +958,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chart.js@4.5.1: + resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==} + engines: {pnpm: '>=8'} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -1510,6 +1523,12 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + react-chartjs-2@5.3.1: + resolution: {integrity: sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==} + peerDependencies: + chart.js: ^4.1.1 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-docgen-typescript@2.4.0: resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==} peerDependencies: @@ -2186,6 +2205,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@kurkle/color@0.3.4': {} + '@mdx-js/react@3.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: '@types/mdx': 2.0.13 @@ -2738,6 +2759,10 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chart.js@4.5.1: + dependencies: + '@kurkle/color': 0.3.4 + check-error@2.1.1: {} chromatic@12.2.0: {} @@ -3255,6 +3280,11 @@ snapshots: queue-microtask@1.2.3: {} + react-chartjs-2@5.3.1(chart.js@4.5.1)(react@19.2.0): + dependencies: + chart.js: 4.5.1 + react: 19.2.0 + react-docgen-typescript@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 diff --git a/public/icons/pen.svg b/public/icons/pen.svg new file mode 100644 index 0000000..b13361f --- /dev/null +++ b/public/icons/pen.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/components/Event/Event.jsx b/src/components/Event/Event.jsx index f3941b5..6a75fd1 100644 --- a/src/components/Event/Event.jsx +++ b/src/components/Event/Event.jsx @@ -1,9 +1,12 @@ import styles from "./Event.module.css"; -import {useState, useEffect} from "react"; -import {Link} from "react-router"; +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router"; import Button from "/src/components/ui/button/button.jsx" function Event({event}){ + + const navigate = useNavigate(); + const [eventMenu, seteventMenu] = useState(false); const [windowSize, setWindowSize] = useState({ width: null, @@ -31,7 +34,7 @@ function Event({event}){
{event.image == null ? ( - Pas de photo de l'événement + Pas de photo de l'événement ) : ( Photo de l'événement )} @@ -57,21 +60,21 @@ function Event({event}){
{windowSize.width > 768 ? (
- +
) : null}
{(windowSize.width <= 768 || eventMenu) ? ( - - - + + + ) : null}
diff --git a/src/components/Event/Event.module.css b/src/components/Event/Event.module.css index 04a40f6..22c5fe8 100644 --- a/src/components/Event/Event.module.css +++ b/src/components/Event/Event.module.css @@ -3,6 +3,7 @@ flex-direction: column; margin: 1rem 0; width: 80%; + padding: 1rem 0.5rem; } .eventContainerTop { @@ -46,11 +47,6 @@ font-size: 2rem; } - - - - - .eventButtons { display: flex; flex-direction: column; @@ -64,9 +60,6 @@ white-space: nowrap; } - - - .moreButton { background: none; border: none; @@ -95,19 +88,16 @@ rotate: 90deg; } - .eventMenuOpen { display: flex; + flex-direction: column; + margin-left: 1.5em; } .eventMenuClose { display: none; } - - - - @media screen and (max-width: 768px) { .eventImage { width: 100%; diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx index 2a8ea25..14043a7 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -18,7 +18,7 @@ function Header() { setNotifications(notifData || []); } - loadNotifications(); + //loadNotifications(); const handleClickOutside = (event) => { if ( notificationRef.current && @@ -70,7 +70,7 @@ function Header() { {unreadNotification && } - + diff --git a/src/components/Task/Task.jsx b/src/components/Task/Task.jsx new file mode 100644 index 0000000..740aeda --- /dev/null +++ b/src/components/Task/Task.jsx @@ -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 ( +
+
+
+

{event && event.name} - {task.name}

+

{task.description}

+

{formatDate(task.start)}

+
+
+
+ ); +} + +export default Task; \ No newline at end of file diff --git a/src/components/Task/Task.module.css b/src/components/Task/Task.module.css new file mode 100644 index 0000000..052d0a3 --- /dev/null +++ b/src/components/Task/Task.module.css @@ -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; +} \ No newline at end of file diff --git a/src/components/background/background.jsx b/src/components/background/background.jsx index b77ce82..96df4b6 100644 --- a/src/components/background/background.jsx +++ b/src/components/background/background.jsx @@ -1,8 +1,10 @@ import styles from "./background.module.css"; -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; const Background = ({ children, className }) => { - const circlesRef = useRef([]); + + const circlesRef = useRef([]); + const [theme, setTheme] = useState("light"); const getRandomTranslation = () => { const randomX = Math.floor(Math.random() * 100) - 30; @@ -20,8 +22,23 @@ const Background = ({ children, className }) => { }); }, []); - return ( -
+ useEffect(() => { + const updateTheme = () => { + setTheme(localStorage.getItem("theme") || "light"); + }; + + updateTheme(); // Initial load + + window.addEventListener("theme-changed", updateTheme); + + return () => { + window.removeEventListener("theme-changed", updateTheme); + }; + }, []); + + + return ( +
(circlesRef.current[0] = el)} className={`${styles["circle"]} ${styles["circle-1"]}`} diff --git a/src/components/background/background.module.css b/src/components/background/background.module.css index e5cca18..c0e7211 100644 --- a/src/components/background/background.module.css +++ b/src/components/background/background.module.css @@ -1,10 +1,9 @@ /* BACKGROUND ////////////////////////////////////////////////////////*/ -.background-container { +.backgroundContainer { z-index: 0; margin: 0; padding: 0; min-height: 100vh; - background: #00F0FF; overflow: hidden; position: relative; width: 100vw; @@ -13,6 +12,14 @@ align-items: center; } +.backgroundLight { + background: #00F0FF; +} + +.backgroundDark { + background: #001C83; +} + .circle { position: absolute; border-radius: 50%; @@ -82,7 +89,6 @@ .circle-1 { width: 300px; height: 300px; - background-color: #019AFF; top: -5%; left: -5%; } @@ -90,7 +96,6 @@ .circle-2 { width: 175px; height: 175px; - background-color: #91FCFF; top: 58%; left: 10%; } @@ -98,7 +103,6 @@ .circle-3 { width: 140px; height: 140px; - background-color: #91FCFF; top: 29%; right: 9%; } @@ -106,7 +110,6 @@ .circle-4 { width: 250px; height: 250px; - background-color: #019AFF; bottom: 0; right: 0; } @@ -114,7 +117,6 @@ .circle-5 { width: 240px; height: 240px; - background-color: #015AFF; top: 2%; right: 10%; } diff --git a/src/components/loginform/loginForm.jsx b/src/components/loginform/loginForm.jsx index edc6c55..cd22971 100644 --- a/src/components/loginform/loginForm.jsx +++ b/src/components/loginform/loginForm.jsx @@ -9,7 +9,6 @@ import { AuthContext } from "../../contexts/auth/AuthContext.js"; function LoginForm({ className }) { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const [showPassword, setShowPassword] = useState(false); let navigate = useNavigate(); const { login } = useContext(AuthContext); @@ -19,12 +18,8 @@ function LoginForm({ className }) { navigate("/"); } - const toggleShowPassword = () => { - setShowPassword(!showPassword); - }; - return ( -
+
-
- setPassword(e.target.value)} - style={{ width: '100%' }} - /> - -
+ setPassword(e.target.value)} value={password} />
+
diff --git a/src/components/loginform/loginForm.module.css b/src/components/loginform/loginForm.module.css index 6b5626e..45caff4 100644 --- a/src/components/loginform/loginForm.module.css +++ b/src/components/loginform/loginForm.module.css @@ -18,14 +18,21 @@ input::placeholder{ font-size: 20px; color: rgba(0,0,0,0.45); } + +.formgroup{ + margin-top:23px; +} + .logButton{ display: flex; justify-content: center; + flex-direction: column; align-items: center; margin-top: 12px; } -.formgroup{ - margin-top:23px; + +.inputContainer { + padding: 15px; } /* style scoped (module) */ diff --git a/src/components/ui/button/button.jsx b/src/components/ui/button/button.jsx index 5ff769c..0fc74ab 100644 --- a/src/components/ui/button/button.jsx +++ b/src/components/ui/button/button.jsx @@ -1,6 +1,6 @@ import styles from "./button.module.css"; -const Button = ({ children, variant = "primary", onClick }) => { +const Button = ({ children, variant = "primary", onClick, className }) => { let btnStyle; if (variant === "primary") btnStyle = styles.primary; @@ -8,7 +8,7 @@ const Button = ({ children, variant = "primary", onClick }) => { else if(variant === "transparent") btnStyle = styles.transparent; else btnStyle = styles.default; - return
+ return
{children}
; }; diff --git a/src/components/ui/input/input.jsx b/src/components/ui/input/input.jsx index c756a3e..0218b3e 100644 --- a/src/components/ui/input/input.jsx +++ b/src/components/ui/input/input.jsx @@ -1,13 +1,38 @@ import styles from "./input.module.css"; +import { useState } from "react"; -const TextInput = ({ placeholder, onChange, value, borderStyle, ...props }) => { +const TextInput = ({ placeholder, onChange, value, borderStyle, password, className, ...props }) => { + const [showPassword, setShowPassword] = useState(false); let inputBorderStyle = styles.square; if (borderStyle === "square") inputBorderStyle = styles.square; if(borderStyle === "rounded") inputBorderStyle = styles.rounded; - return + const toggleShowPassword = () => { + setShowPassword(!showPassword); + }; + + if(password) return ( +
+ + +
+ ); + + return }; export default TextInput; diff --git a/src/components/ui/input/input.module.css b/src/components/ui/input/input.module.css index eaafa25..e56ef67 100644 --- a/src/components/ui/input/input.module.css +++ b/src/components/ui/input/input.module.css @@ -30,3 +30,23 @@ border-radius: 12px; } +.passwordContainer { + position: relative; + width: 100%; +} + +.passwordInput { + width: 100%; +} + +.passwordBtn { + position: absolute; + width: 30px; + right: 10px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + cursor: pointer; + font-size: 16px; +} diff --git a/src/components/ui/modal/modal.jsx b/src/components/ui/modal/modal.jsx new file mode 100644 index 0000000..66ae46b --- /dev/null +++ b/src/components/ui/modal/modal.jsx @@ -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
+ +
+ + {title &&

{title}

} + + {children} + +
+ +
+ +
+} + +export default Modal; \ No newline at end of file diff --git a/src/components/ui/modal/modal.module.css b/src/components/ui/modal/modal.module.css new file mode 100644 index 0000000..76b108a --- /dev/null +++ b/src/components/ui/modal/modal.module.css @@ -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; +} diff --git a/src/components/ui/themeSwitcher/ThemeSwitcher.jsx b/src/components/ui/themeSwitcher/ThemeSwitcher.jsx new file mode 100644 index 0000000..1b9a84f --- /dev/null +++ b/src/components/ui/themeSwitcher/ThemeSwitcher.jsx @@ -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 +} \ No newline at end of file diff --git a/src/contexts/auth/authProvider.jsx b/src/contexts/auth/authProvider.jsx index 7151a96..009ec58 100644 --- a/src/contexts/auth/authProvider.jsx +++ b/src/contexts/auth/authProvider.jsx @@ -2,6 +2,7 @@ import { useState, useEffect } from "react"; import loginFunction from "../../utils/login.js"; import getUser from "../../utils/getUser.js"; import { AuthContext } from "./AuthContext"; +import logoutFunction from "../../utils/auth/logout.js" export function AuthProvider({ children }) { @@ -25,7 +26,7 @@ export function AuthProvider({ children }) { }; const logout = async () => { - // TODO: Appeller fonction logout + await logoutFunction(); setUser(null); }; diff --git a/src/index.css b/src/index.css index 87c36d8..6246007 100644 --- a/src/index.css +++ b/src/index.css @@ -50,7 +50,6 @@ body { .glassCard { width: 100%; - padding: 10px; height: fit-content; background: rgba(255, 255, 255, 0.65); backdrop-filter: blur(3px); diff --git a/src/pages/Events/EventsPage.jsx b/src/pages/Events/EventsPage.jsx index 70f2e9a..02c70bf 100644 --- a/src/pages/Events/EventsPage.jsx +++ b/src/pages/Events/EventsPage.jsx @@ -1,4 +1,4 @@ -import {useState} from "react"; +import { useEffect, useState } from "react"; import Event from "../../components/Event/Event.jsx"; import styles from "./EventsPage.module.css"; import ToolBar from "../../components/ToolBar/ToolBar.jsx"; @@ -36,144 +36,15 @@ function EventsPage(){ volunteers_count: 3 }, ] - }, - { - id: 2, - name: "Festival de Musique d’Été", - description: "Un week-end de concerts en plein air avec des artistes locaux et internationaux.", - image: null, - tasks: [ - { - name: "Montage de la scène", - start: "2018-09-24 22:21:20", - end: "2018-09-24 22:21:20", - place: "Parc central", - description: "Installer la scène, les éclairages et le son.", - volunteers_count: 6 - }, - { - name: "Accueil du public", - start: "2018-09-24 22:21:20", - end: "2018-09-24 22:21:20", - place: "Entrée principale", - description: "Vérifier les billets et orienter les visiteurs.", - volunteers_count: 4 - }, - ] - }, - { - id: 3, - name: "Conférence Tech Innovante", - description: "Journée de conférences et workshops autour des nouvelles technologies.", - image: null, - tasks: [ - { - name: "Préparation des salles", - start: "2018-09-24 22:21:20", - end: "2018-09-24 22:21:20", - place: "Campus Tech", - description: "Installer les chaises, projecteurs et matériel pour les conférences.", - volunteers_count: 4 - }, - { - name: "Accueil des intervenants", - start: "2018-09-24 22:21:20", - end: "2018-09-24 22:21:20", - place: "Hall principal", - description: "Assister les speakers et orienter vers les salles.", - volunteers_count: 2 - }, - { - name: "Gestion du buffet", - start: "2018-09-24 22:21:20", - end: "2018-09-24 22:21:20", - place: "Salle de restauration", - description: "Distribuer boissons et repas aux participants.", - volunteers_count: 3 - }, - ] - }, - { - id: 4, - name: "Atelier de Jardinage Urbain", - description: "Apprenez à cultiver vos plantes en ville et participez à la création de jardins partagés.", - image: null, - tasks: [ - { - name: "Installation du matériel", - start: "2018-09-24 22:21:20", - end: "2018-09-24 22:21:20", - place: "Jardin partagé", - description: "Préparer outils et terreaux pour les participants.", - volunteers_count: 2 - }, - { - name: "Encadrement des participants", - start: "2018-09-24 22:21:20", - end: "2018-09-24 22:21:20", - place: "Jardin partagé", - description: "Aider et conseiller sur la plantation et l’entretien.", - volunteers_count: 3 - }, - ] - }, - { - id: 5, - name: "Collecte de Sang", - description: "Aidez à sauver des vies en participant à notre collecte de sang annuelle.", - image: null, - tasks: [ - { - name: "Accueil et enregistrement", - start: "2020-09-24 22:21:20", - end: "2020-09-24 22:21:20", - place: "Salle municipale", - description: "Enregistrer les donneurs et expliquer le processus.", - volunteers_count: 4 - }, - { - name: "Préparation des salles", - start: "2018-09-24 22:21:20", - end: "2018-09-24 22:21:20", - place: "Salle municipale", - description: "Installer les lits, tables et matériel médical.", - volunteers_count: 3 - }, - { - name: "Assistance post-don", - start: "2018-09-24 22:21:20", - end: "2018-09-24 22:21:20", - place: "Salle municipale", - description: "Accompagner les donneurs après le don et proposer des rafraîchissements.", - volunteers_count: 2 - }, - { - name: "Accueil et enregistrement", - start: "2020-09-24 22:21:20", - end: "2020-09-24 22:21:20", - place: "Salle municipale", - description: "Enregistrer les donneurs et expliquer le processus.", - volunteers_count: 4 - }, - { - name: "Préparation des salles", - start: "2018-09-24 22:21:20", - end: "2018-09-24 22:21:20", - place: "Salle municipale", - description: "Installer les lits, tables et matériel médical.", - volunteers_count: 3 - }, - { - name: "Assistance post-don", - start: "2018-09-24 22:21:20", - end: "2018-09-24 22:21:20", - place: "Salle municipale", - description: "Accompagner les donneurs après le don et proposer des rafraîchissements.", - volunteers_count: 2 - }, - ] - }, - ]); + } + */ + + useEffect(() => { + (async () => { + const data = await getAllEvents(); + setEvents(data.data); + }) () + }, []); return ( diff --git a/src/pages/Login/LoginPage.jsx b/src/pages/Login/LoginPage.jsx index fcea9bb..93154f4 100644 --- a/src/pages/Login/LoginPage.jsx +++ b/src/pages/Login/LoginPage.jsx @@ -3,15 +3,14 @@ import LoginForm from "../../components/loginform/loginForm.jsx"; import Background from "../../components/background/background.jsx"; function LoginPage() { - return - -
- -
- -
+ return ( + +
+

Connexion

+ +
+
+ ); } export default LoginPage; - - diff --git a/src/pages/Login/LoginPage.module.css b/src/pages/Login/LoginPage.module.css index fd5d61d..69e4f8c 100644 --- a/src/pages/Login/LoginPage.module.css +++ b/src/pages/Login/LoginPage.module.css @@ -1,16 +1,23 @@ .container { - position: relative; + position: absolute; z-index: 2; display: flex; + flex-direction: column; width: 600px; - overflow: hidden; max-height: 80%; justify-content: center; align-items: center; padding: 20px; - border-radius:12px; - margin-left: 33.3%; + border-radius: 12px; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); } - - +.title { + font-size: 36px; + color: white; + text-align: center; + margin-bottom: 20px; + text-shadow: 0 2px 8px rgba(0,0,0,0.3); +} diff --git a/src/pages/Profile/ProfilePage.jsx b/src/pages/Profile/ProfilePage.jsx index c38328f..0271d42 100644 --- a/src/pages/Profile/ProfilePage.jsx +++ b/src/pages/Profile/ProfilePage.jsx @@ -1,105 +1,63 @@ -import React, { useState } from "react"; +import React, { useContext } from "react"; import styles from "./ProfilePage.module.css"; - -function Event({ title,date, tasks }) { - const [isExpanded, setIsExpanded] = useState(false); - - const toggleExpand = () => { - setIsExpanded(!isExpanded); - }; - - return ( -
-
-
-

{title}

-

{date}

-
- -
-
    - {tasks.map((task, index) => ( -
  • -- {task}
  • - ))} -
-
- ); -} - +import { AuthContext } from "../../contexts/auth/AuthContext.js"; +import formatDate from "../../utils/date/formatDate.js"; +import Task from "../../components/Task/Task"; +import Button from "../../components/ui/button/button.jsx"; +import { useNavigate } from "react-router"; +import ThemeSwitcher from "../../components/ui/themeSwitcher/ThemeSwitcher.jsx"; function ProfilePage() { + + const { user, logout } = useContext(AuthContext); + const navigate = useNavigate(); + + const handleLogout = () => { + logout(); + } + return (
Photo de profil
-

LEBLOND Malo

+
+

{user.name} {user.lastname}

+
+ + +
+
-

Role : Président

-

Membre depuis : 2019

+

Role : {user.role}

+

Membre depuis : {formatDate(user.created_at)}

-

Mail : mailpro@gmail.com

-

Téléphone : 08 67 87 32 21

+

Mail : {user.email}

+

Téléphone : {user.phone}

- +
+

Vos Événements :

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

Aucune tâche pour le moment.

+ )} +
diff --git a/src/pages/Profile/ProfilePage.module.css b/src/pages/Profile/ProfilePage.module.css index 54569c6..e4e467b 100644 --- a/src/pages/Profile/ProfilePage.module.css +++ b/src/pages/Profile/ProfilePage.module.css @@ -7,7 +7,7 @@ .profileboard { width: 100%; padding: 15px; - border-radius: 10px; + border-radius: 20px; box-sizing: border-box; } @@ -51,13 +51,6 @@ border-radius: 5px; } -.event { - width: 100%; - padding: 12px; - margin-bottom: 10px; - border-radius: 8px; -} - .eventTasks { max-height: 0; overflow: hidden; @@ -73,12 +66,18 @@ } .description h2{ font-size:30px; - margin-bottom: 2%; + } .profilePicture{ width:90%; - } -/* Tablette (768px et plus) */ +} + +.settingsBtns { + display: flex; + gap: 5px; + justify-content: right; +} + @media screen and (min-width: 768px) { .contentWrapper { flex-direction: row; @@ -112,7 +111,6 @@ } .description h2{ font-size:30px; - margin-bottom: 2%; } .profilePicture{ width:90%; @@ -124,6 +122,19 @@ } +.modifyIcon { + height:50px; + width:50px; +} + +.headerProfile { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + /* Desktop (1024px et plus) */ @media screen and (min-width: 1024px) { .container { @@ -133,7 +144,7 @@ } .profileboard { - padding: 25px; + padding: 25px; } .profilePictureContainer { @@ -153,6 +164,5 @@ } .description h2{ font-size:30px; - margin-bottom: 2%; } } diff --git a/src/pages/Register/RegisterPage.jsx b/src/pages/Register/RegisterPage.jsx index 489858d..cac9e4d 100644 --- a/src/pages/Register/RegisterPage.jsx +++ b/src/pages/Register/RegisterPage.jsx @@ -30,9 +30,9 @@ function RegisterPage() {
-
+

Créer un compte

-

Créer un compte

+
-
- setPassword(e.target.value)} - style={{ width: '100%' }} - /> - -
+ setPassword(e.target.value)} value={password} />
diff --git a/src/pages/Register/RegisterPage.module.css b/src/pages/Register/RegisterPage.module.css index 7a379ee..7c221bb 100644 --- a/src/pages/Register/RegisterPage.module.css +++ b/src/pages/Register/RegisterPage.module.css @@ -2,6 +2,7 @@ position: relative; z-index: 2; display: flex; + flex-direction: column; width: 30%; justify-content: center; align-items: center; @@ -9,6 +10,15 @@ border-radius:12px; } +.title { + font-size: 36px; + color: white; + text-align: center; + margin-bottom: 20px; + text-shadow: 0 2px 8px rgba(0,0,0,0.3); +} + + :global(.glassCard) { --glass-bg: #ffffff9f; } @@ -26,4 +36,8 @@ h1 { overflow: visible; text-align: center; +} + +.inputContainer { + padding: 15px; } \ No newline at end of file diff --git a/src/pages/Stat/StatPage.jsx b/src/pages/Stat/StatPage.jsx new file mode 100644 index 0000000..cd75918 --- /dev/null +++ b/src/pages/Stat/StatPage.jsx @@ -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 ( +
+
+
+
+ + + + +
+ +
+
+
+ +
+ {participationRate}% +
+
+
+
+
+
+

Événements incomplets

+
+ {incompleteEvents.map((event, index) => ( +
+

{event.title}

+

{event.tasks}

+
+ ))} +
+
+
+

Membres en attente de validation

+
+ {pendingMembers.map((member, index) => ( +
+

{member.name}

+
+ + +
+
+ ))} +
+
+
+
+ ); +} + +export default StatPage; diff --git a/src/pages/Stat/StatPage.module.css b/src/pages/Stat/StatPage.module.css new file mode 100644 index 0000000..666a610 --- /dev/null +++ b/src/pages/Stat/StatPage.module.css @@ -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; + } +} + + diff --git a/src/pages/Volunteers/VolunteerCard.jsx b/src/pages/Volunteers/VolunteerCard.jsx new file mode 100644 index 0000000..a2a3f84 --- /dev/null +++ b/src/pages/Volunteers/VolunteerCard.jsx @@ -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 ( +
+ +
+

{user?.name}

+ +
+ +
+ {!isExpanded ? ( +
+

Rôle : {user?.role}

+

Membre depuis : {user?.created_at && formatDate(user.created_at)}

+
+ ) : ( +
+
+ profile +
+ +
+
+

Rôle : {user?.role}

+

Membre depuis : {user?.created_at && formatDate(user.created_at)}

+
+ +
+

Email : {user?.email}

+ {user?.phone &&

Téléphone : {user.phone}

} +
+
+
+ )} +
+ + {isExpanded && ( +
+ +
+ )} + +
+ ); + + + +} \ No newline at end of file diff --git a/src/pages/Volunteers/VolunteersPage.jsx b/src/pages/Volunteers/VolunteersPage.jsx index 782f2a3..0fa3d2c 100644 --- a/src/pages/Volunteers/VolunteersPage.jsx +++ b/src/pages/Volunteers/VolunteersPage.jsx @@ -1,106 +1,23 @@ - -import React, { useState } from "react"; +import { useState, useEffect } from "react"; import styles from "./VolunteersPage.module.css"; +import VolunteerCard from "./VolunteerCard.jsx"; +import getAllUser from "../../utils/users/getAllUsers.js"; -function Volunteer({ name, role, since, email, phone, photo }) { - const [isExpanded, setIsExpanded] = useState(false); - - const toggleExpand = () => { - setIsExpanded(!isExpanded); - }; - - return ( -
-
- {!isExpanded ? ( -
-

{name}

-

Rôle : {role}

-

Membre depuis : {since}

-
- ) : ( -
-
- {`${name}'s -
-
-
-

{name}

-

Rôle : {role}

-

Membre depuis : {since}

-
-
-

Email : {email}

-

Téléphone : {phone}

-
-
-
- )} - - {isExpanded && ( -
- VOIR PLUS -
- )} -
-
- ); -} function VolunteerPage() { - const volunteers = [ - { - name: "LEBLOND Malo", - role: "Président", - since: "2019", - email: "malo.leblond@example.com", - phone: "08 67 87 32 21", - photo: "../src/assets/react.svg", - }, - { - name: "DUPONT Jean", - role: "Bénévole", - since: "2020", - email: "jean.dupont@example.com", - phone: "06 12 34 56 78", - photo: "../src/assets/react.svg", - }, - { - name: "MARTIN Sophie", - role: "Bénévole", - since: "2021", - email: "sophie.martin@example.com", - phone: "06 87 65 43 21", - photo: "../src/assets/react.svg", - }, - { - name: "MARTIN Sophie", - role: "Bénévole", - since: "2021", - email: "sophie.martin@example.com", - phone: "06 87 65 43 21", - photo: "../src/assets/react.svg", - }, - ]; + + const [users, setUsers] = useState([]); + + useEffect(() => { + (async () => { + const data = await getAllUser(); + setUsers(data); + }) () + }, []) return (
- {volunteers.map((volunteer, index) => ( - - ))} + {users.map((user, index) => )}
); } diff --git a/src/pages/Volunteers/VolunteersPage.module.css b/src/pages/Volunteers/VolunteersPage.module.css index 01bb808..c18433e 100644 --- a/src/pages/Volunteers/VolunteersPage.module.css +++ b/src/pages/Volunteers/VolunteersPage.module.css @@ -1,125 +1,117 @@ .container { - width: 100%; - padding: 10px; - box-sizing: border-box; - margin-top: 50px; + width: 100%; + padding: 10px; + margin-top: 50px; + box-sizing: border-box; } .profileboard { - width: 100%; - padding: 15px; - border-radius: 10px; - box-sizing: border-box; - margin-top: 3%; - position: relative; + width: 100%; + padding: 15px; + margin-top: 3%; + display: flex; + flex-direction: column; + gap: 10px; + box-sizing: border-box; } +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.header h2 { + margin: 0; +} + + .mainContent { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: flex-start; - width: 100%; + display: flex; + flex-direction: row; + width: 100%; + flex: 1; } .basicInfo { - flex: 1; + flex: 1; } .expandedContent { - display: flex; - flex-direction: row; - align-items: flex-start; - width: 100%; -} - -.profilePictureContainer { - width: 120px; - height: 120px; - margin-right: 20px; -} - -.profilePicture { - width: 100%; - height: 100%; - border-radius: 50%; - object-fit: cover; + display: flex; + flex-direction: row; + width: 100%; } .textInfo { - display: flex; - flex-direction: column; - flex: 1; + display: flex; + flex-direction: column; + flex: 1; } .contactInfo { - margin-top: 10px; + margin-top: 10px; } .contactInfo p { - margin: 5px 0; + margin: 5px 0; +} + +.bottomBar { + display: flex; + justify-content: flex-end; +} + +.profilePictureContainer { + width: 120px; + height: 120px; + margin-right: 20px; +} + +.profilePicture { + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; } .expandButton { - width: 30px; - height: 30px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - background: transparent; - border: none; - font-size: 16px; - margin-left: 10px; - position: absolute; - top: 10px; - right: 10px; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + background: transparent; + border: none; + font-size: 16px; } -.voirPlus { - position: absolute; - bottom: 10px; - right: 10px; - color: #007bff; - text-decoration: none; - font-weight: bold; - cursor: pointer; -} - -.voirPlus:hover { - text-decoration: underline; +.moreButtonClicked { + rotate: 90deg; } @media screen and (min-width: 768px) { - .profilePictureContainer { - width: 150px; - height: 150px; - } - .voirPlus{ - text-align: right; - } + .profilePictureContainer { + width: 150px; + height: 150px; + } } @media screen and (min-width: 1024px) { - .container { - max-width: 1200px; - margin: 0 auto; - padding: 20px; - margin-top:50px; - margin-bottom: 50px; - } + .container { + max-width: 1200px; + margin: 50px auto; + padding: 20px; + } - .profileboard { - padding: 25px; - } + .profileboard { + padding: 25px; + } - .profilePictureContainer { - width: 200px; - height: auto; - } + .profilePictureContainer { + width: 200px; + height: auto; + } } - - - - - diff --git a/src/router.js b/src/router.js index 49775e1..bd0c986 100644 --- a/src/router.js +++ b/src/router.js @@ -2,6 +2,7 @@ import { createBrowserRouter } from "react-router"; import Layout from "./layout.jsx"; import HomePage from "./pages/Home/HomePage"; import LoginPage from "./pages/Login/LoginPage"; +import StatPage from "./pages/Stat/StatPage"; import RegisterPage from "./pages/Register/RegisterPage.jsx"; import VolunteersPage from "./pages/Volunteers/VolunteersPage"; import ProfilePage from "./pages/Profile/ProfilePage"; @@ -25,8 +26,8 @@ const router = createBrowserRouter([ Component: HomePage, }, { - path: "/login", - Component: LoginPage, + path: "/stat", + Component:StatPage, }, { path:"/profile", diff --git a/src/stories/Modal.stories.jsx b/src/stories/Modal.stories.jsx new file mode 100644 index 0000000..1ece2ae --- /dev/null +++ b/src/stories/Modal.stories.jsx @@ -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 ( +
+ + setIsOpen(false)}> + {args.children} + +
+ ); +}; + +export const Default = Template.bind({}); +Default.args = { + title: "Hello", + children:

Lorem Ipsum...

, +}; diff --git a/src/utils/auth/logout.js b/src/utils/auth/logout.js new file mode 100644 index 0000000..fa34b2f --- /dev/null +++ b/src/utils/auth/logout.js @@ -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; + } +} diff --git a/src/utils/date/formatDate.js b/src/utils/date/formatDate.js new file mode 100644 index 0000000..3fd1371 --- /dev/null +++ b/src/utils/date/formatDate.js @@ -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}`; +} \ No newline at end of file diff --git a/src/utils/date/formatDate.test.js b/src/utils/date/formatDate.test.js new file mode 100644 index 0000000..97179dc --- /dev/null +++ b/src/utils/date/formatDate.test.js @@ -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); +}) \ No newline at end of file diff --git a/src/utils/event/getAllEvents.js b/src/utils/event/getAllEvents.js new file mode 100644 index 0000000..f5e37c5 --- /dev/null +++ b/src/utils/event/getAllEvents.js @@ -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; + } + +} \ No newline at end of file diff --git a/src/utils/event/getEventById.js b/src/utils/event/getEventById.js new file mode 100644 index 0000000..8d09b78 --- /dev/null +++ b/src/utils/event/getEventById.js @@ -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; + } + +} \ No newline at end of file diff --git a/src/utils/getUser.js b/src/utils/getUser.js index b5c817d..24297ca 100644 --- a/src/utils/getUser.js +++ b/src/utils/getUser.js @@ -1,6 +1,6 @@ export default async function getUser() { try { - const res = await fetch('http://localhost:80/api/users/me', { + const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/me`, { method: 'GET', credentials: 'include', headers: { diff --git a/src/utils/getXSRF.js b/src/utils/getXSRF.js index 46a9415..0c78e40 100644 --- a/src/utils/getXSRF.js +++ b/src/utils/getXSRF.js @@ -1,5 +1,5 @@ export default async function getXSRFToken() { - const response = await fetch('http://localhost:80/sanctum/csrf-cookie', { + const response = await fetch(`http://${import.meta.env.VITE_API_URL}/sanctum/csrf-cookie`, { method: 'GET', credentials: 'include' }); diff --git a/src/utils/login.js b/src/utils/login.js index 6107185..161268c 100644 --- a/src/utils/login.js +++ b/src/utils/login.js @@ -4,7 +4,7 @@ export default async function login(email, password) { const csrfToken = await getXSRFToken(); try { - const response = await fetch('http://localhost:80/api/users/login', { + const response = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/login`, { method: 'POST', credentials: 'include', headers: { diff --git a/src/utils/register.js b/src/utils/register.js index 2cdacf6..313df77 100644 --- a/src/utils/register.js +++ b/src/utils/register.js @@ -4,7 +4,7 @@ export default async function register(email, password, name, lastname) { const csrfToken = await getXSRFToken(); try { - const response = await fetch('http://localhost:80/api/users/register', { + const response = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/register`, { method: 'POST', credentials: 'include', headers: { diff --git a/src/utils/users/getAllUsers.js b/src/utils/users/getAllUsers.js new file mode 100644 index 0000000..c9c383e --- /dev/null +++ b/src/utils/users/getAllUsers.js @@ -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; + } +} diff --git a/src/utils/users/getUserById.js b/src/utils/users/getUserById.js new file mode 100644 index 0000000..59af94a --- /dev/null +++ b/src/utils/users/getUserById.js @@ -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; + } +} \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index 59b0166..3277a15 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,39 +1,48 @@ +// vite.config.js /// -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { storybookTest } from '@storybook/addon-vitest/vitest-plugin' +import { playwright } from '@vitest/browser-playwright' -// https://vite.dev/config/ -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; -import { playwright } from '@vitest/browser-playwright'; const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)); -// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon export default defineConfig({ plugins: [react()], base: '/', test: { - projects: [{ - extends: true, - plugins: [ - // The plugin will run tests for the stories defined in your Storybook config - // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest - storybookTest({ - configDir: path.join(dirname, '.storybook') - })], - test: { - name: 'storybook', - browser: { - enabled: true, - headless: true, - provider: playwright({}), - instances: [{ - browser: 'chromium' - }] + projects: [ + { + extends: true, + plugins: [ + storybookTest({ + configDir: path.join(dirname, '.storybook'), + }), + ], + test: { + name: 'storybook', + browser: { + enabled: true, + headless: true, + provider: playwright({}), + instances: [{ browser: 'chromium' }], + }, + setupFiles: ['.storybook/vitest.setup.js'], }, - setupFiles: ['.storybook/vitest.setup.js'] - } - }] - } -}); \ No newline at end of file + }, + { + test: { + name: 'unit', + include: [ + 'src/**/*.test.js', + 'src/**/*.test.ts', + 'utils/**/*.test.js', + ], + environment: 'node', + }, + }, + ], + }, +})