From 2389ca88e4a0a350f36147a1c2dbb134af54bb6e Mon Sep 17 00:00:00 2001 From: p2405951 Date: Fri, 30 Jan 2026 16:35:48 +0100 Subject: [PATCH 01/47] convert setting modals + associate function --- src/components/ui/button/button.jsx | 16 -- src/components/ui/button/button.tsx | 53 +++++ src/components/ui/input/input.jsx | 38 ---- src/components/ui/input/input.tsx | 107 ++++++++++ src/components/ui/modal/modal.jsx | 31 --- src/components/ui/modal/modal.tsx | 71 +++++++ .../ui/themeSwitcher/ThemeSwitcher.jsx | 32 --- .../ui/themeSwitcher/ThemeSwitcher.tsx | 73 +++++++ .../SettingsModal/SettingsModal.jsx | 86 -------- .../SettingsModal/SettingsModal.tsx | 187 ++++++++++++++++++ src/utils/fetchWrapper.js | 45 ----- src/utils/fetchWrapper.ts | 92 +++++++++ src/utils/getXSRF.js | 19 -- src/utils/getXSRF.ts | 40 ++++ src/utils/users/deleteUser.js | 13 -- src/utils/users/deleteUser.ts | 13 ++ src/utils/users/updateUser.js | 13 -- src/utils/users/updateUser.ts | 33 ++++ tsconfig.json | 2 +- 19 files changed, 670 insertions(+), 294 deletions(-) delete mode 100644 src/components/ui/button/button.jsx create mode 100644 src/components/ui/button/button.tsx delete mode 100644 src/components/ui/input/input.jsx create mode 100644 src/components/ui/input/input.tsx delete mode 100644 src/components/ui/modal/modal.jsx create mode 100644 src/components/ui/modal/modal.tsx delete mode 100644 src/components/ui/themeSwitcher/ThemeSwitcher.jsx create mode 100644 src/components/ui/themeSwitcher/ThemeSwitcher.tsx delete mode 100644 src/pages/Profile/components/SettingsModal/SettingsModal.jsx create mode 100644 src/pages/Profile/components/SettingsModal/SettingsModal.tsx delete mode 100644 src/utils/fetchWrapper.js create mode 100644 src/utils/fetchWrapper.ts delete mode 100644 src/utils/getXSRF.js create mode 100644 src/utils/getXSRF.ts delete mode 100644 src/utils/users/deleteUser.js create mode 100644 src/utils/users/deleteUser.ts delete mode 100644 src/utils/users/updateUser.js create mode 100644 src/utils/users/updateUser.ts diff --git a/src/components/ui/button/button.jsx b/src/components/ui/button/button.jsx deleted file mode 100644 index 0fc74ab..0000000 --- a/src/components/ui/button/button.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import styles from "./button.module.css"; - -const Button = ({ children, variant = "primary", onClick, className }) => { - let btnStyle; - - if (variant === "primary") btnStyle = styles.primary; - else if (variant === "danger") btnStyle = styles.danger; - else if(variant === "transparent") btnStyle = styles.transparent; - else btnStyle = styles.default; - - return
- {children} -
; -}; - -export default Button; diff --git a/src/components/ui/button/button.tsx b/src/components/ui/button/button.tsx new file mode 100644 index 0000000..5b0e5f0 --- /dev/null +++ b/src/components/ui/button/button.tsx @@ -0,0 +1,53 @@ +/*import styles from "./button.module.css"; + +const Button = ({ children, variant = "primary", onClick, className }) => { + let btnStyle; + + if (variant === "primary") btnStyle = styles.primary; + else if (variant === "danger") btnStyle = styles.danger; + else if(variant === "transparent") btnStyle = styles.transparent; + else btnStyle = styles.default; + + return
+ {children} +
; +}; + +export default Button; +*/ + +import styles from "./button.module.css"; +import { ReactNode, MouseEvent } from "react"; + +interface ButtonProps { + children: ReactNode; + variant?: "primary" | "danger" | "transparent" | "default"; + onClick?: (event: MouseEvent) => void; + className?: string; +} + +const Button = ({ + children, + variant = "primary", + onClick, + className = "", +}: ButtonProps) => { + const variantStyles = { + primary: styles.primary, + danger: styles.danger, + transparent: styles.transparent, + default: styles.default, + }; + const btnStyle = variantStyles[variant in variantStyles ? variant : "default"]; + + return ( +
+ {children} +
+ ); +}; + +export default Button; diff --git a/src/components/ui/input/input.jsx b/src/components/ui/input/input.jsx deleted file mode 100644 index 7713b27..0000000 --- a/src/components/ui/input/input.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import styles from "./input.module.css"; -import { useState } from "react"; - -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; - - const toggleShowPassword = () => { - setShowPassword(!showPassword); - }; - - if(password) return ( -
- - -
- ); - - return -}; - -export default TextInput; diff --git a/src/components/ui/input/input.tsx b/src/components/ui/input/input.tsx new file mode 100644 index 0000000..48f9ce6 --- /dev/null +++ b/src/components/ui/input/input.tsx @@ -0,0 +1,107 @@ +/*import styles from "./input.module.css"; +import { useState } from "react"; + +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; + + const toggleShowPassword = () => { + setShowPassword(!showPassword); + }; + + if(password) return ( +
+ + +
+ ); + + return +}; + +export default TextInput;*/ + + +import styles from "./input.module.css"; +import { useState, InputHTMLAttributes, ChangeEvent } from "react"; + +interface TextInputProps extends InputHTMLAttributes { + placeholder?: string; + onChange: (event: ChangeEvent) => void; + value: string; + borderStyle?: "square" | "rounded"; + password?: boolean; + className?: string; +} + +const TextInput = ({ + placeholder, + onChange, + value, + borderStyle = "square", + password = false, + className = "", + ...props +}: TextInputProps) => { + const [showPassword, setShowPassword] = useState(false); + const borderStyles = { + square: styles.square, + rounded: styles.rounded, + }; + + const inputBorderStyle = borderStyles[borderStyle] || styles.square; + + const toggleShowPassword = () => { + setShowPassword(!showPassword); + }; + + if (password) { + return ( +
+ + +
+ ); + } + + return ( + + ); +}; + +export default TextInput; diff --git a/src/components/ui/modal/modal.jsx b/src/components/ui/modal/modal.jsx deleted file mode 100644 index 822398c..0000000 --- a/src/components/ui/modal/modal.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import { createPortal } from "react-dom"; -import styles from "./modal.module.css"; -import Button from "../button/button.jsx"; - -const Modal = ({ open, onClose, children, title }) => { - - if (!open) return null; - - const handleOverlayClick = () => { - onClose(); - }; - - const handleModalClick = (e) => { - e.stopPropagation(); - }; - - return createPortal( -
-
- {title &&

{title}

} - {children} -
- -
-
-
, - document.body - ); -}; - -export default Modal; diff --git a/src/components/ui/modal/modal.tsx b/src/components/ui/modal/modal.tsx new file mode 100644 index 0000000..e8ba5db --- /dev/null +++ b/src/components/ui/modal/modal.tsx @@ -0,0 +1,71 @@ +/*import { createPortal } from "react-dom"; +import styles from "./modal.module.css"; +import Button from "../button/button.jsx"; + +const Modal = ({ open, onClose, children, title }) => { + + if (!open) return null; + + const handleOverlayClick = () => { + onClose(); + }; + + const handleModalClick = (e) => { + e.stopPropagation(); + }; + + return createPortal( +
+
+ {title &&

{title}

} + {children} +
+ +
+
+
, + document.body + ); +}; + +export default Modal;*/ + +import { createPortal } from "react-dom"; +import styles from "./modal.module.css"; +import Button from "../button/button"; +import { ReactNode, MouseEvent } from "react"; + +interface ModalProps { + open: boolean; + onClose: () => void; + children: ReactNode; + title?: string; +} + +const Modal = ({ open, onClose, children, title }: ModalProps) => { + if (!open) return null; + + const handleOverlayClick = () => { + onClose(); + }; + + const handleModalClick = (e: MouseEvent) => { + e.stopPropagation(); + }; + + return createPortal( +
+
+ {title &&

{title}

} + {children} +
+ +
+
+
, + document.body + ); +}; + +export default Modal; + diff --git a/src/components/ui/themeSwitcher/ThemeSwitcher.jsx b/src/components/ui/themeSwitcher/ThemeSwitcher.jsx deleted file mode 100644 index ef3ffc6..0000000 --- a/src/components/ui/themeSwitcher/ThemeSwitcher.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import Button from "../button/button.jsx"; -import { useState, useEffect } from "react"; -import styles from "./ThemeSwitcher.module.css"; - - -export default function ThemeSwitcher() { - - const [theme, setTheme] = useState(""); - - useEffect(() => { - const t = localStorage.getItem("theme"); - if(t) setTheme(t); - else setTheme("light"); - }, []); - - const switchTheme = () => { - - let newTheme = theme === "light" ? "dark" : "light"; - - if(theme === null || theme === "") newTheme = "dark"; - - localStorage.setItem("theme", newTheme); - setTheme(newTheme); - - window.dispatchEvent(new Event("theme-changed")); - }; - - return -} \ No newline at end of file diff --git a/src/components/ui/themeSwitcher/ThemeSwitcher.tsx b/src/components/ui/themeSwitcher/ThemeSwitcher.tsx new file mode 100644 index 0000000..4062bb8 --- /dev/null +++ b/src/components/ui/themeSwitcher/ThemeSwitcher.tsx @@ -0,0 +1,73 @@ +/*import Button from "../button/button.jsx"; +import { useState, useEffect } from "react"; +import styles from "./ThemeSwitcher.module.css"; + + +export default function ThemeSwitcher() { + + const [theme, setTheme] = useState(""); + + useEffect(() => { + const t = localStorage.getItem("theme"); + if(t) setTheme(t); + else setTheme("light"); + }, []); + + const switchTheme = () => { + + let newTheme = theme === "light" ? "dark" : "light"; + + if(theme === null || theme === "") newTheme = "dark"; + + localStorage.setItem("theme", newTheme); + setTheme(newTheme); + + window.dispatchEvent(new Event("theme-changed")); + }; + + return +}*/ + +import Button from "../button/button"; +import { useState, useEffect } from "react"; +import styles from "./ThemeSwitcher.module.css"; + +type Theme = "light" | "dark"; + +export default function ThemeSwitcher() { + const [theme, setTheme] = useState(""); + + useEffect(() => { + const savedTheme = localStorage.getItem("theme") as Theme | null; + if (savedTheme) { + setTheme(savedTheme); + } else { + setTheme("light"); + } + }, []); + + const switchTheme = () => { + const newTheme: Theme = theme === "light" ? "dark" : "light"; + localStorage.setItem("theme", newTheme); + setTheme(newTheme); + window.dispatchEvent(new Event("theme-changed")); + }; + + return ( + + ); +} diff --git a/src/pages/Profile/components/SettingsModal/SettingsModal.jsx b/src/pages/Profile/components/SettingsModal/SettingsModal.jsx deleted file mode 100644 index 4283559..0000000 --- a/src/pages/Profile/components/SettingsModal/SettingsModal.jsx +++ /dev/null @@ -1,86 +0,0 @@ -import Modal from "../../../../components/ui/modal/modal.jsx"; -import Button from "../../../../components/ui/button/button.jsx"; -import styles from "./SettingsModal.module.css" -import { useContext, useState } from "react"; -import ThemeSwitcher from "../../../../components/ui/themeSwitcher/ThemeSwitcher.jsx"; -import { AuthContext } from "../../../../contexts/auth/AuthContext.js"; -import TextInput from "../../../../components/ui/input/input.jsx"; -import updateUser from "../../../../utils/users/updateUser.js"; -import deleteUser from "../../../../utils/users/deleteUser.js"; - - -export default function SettingsModal() { - - const { user, logout, update } = useContext(AuthContext); - - const [open, setOpen] = useState(false); - const [isDelete, setIsDelete] = useState(false); - - const [name, setName] = useState(user?.name); - const [lastname, setLastName] = useState(user?.lastname); - const [phone, setPhone] = useState(user?.phone); - - const handleLogout = () => { - logout(); - } - - const handleSubmit = async () => { - await updateUser(name, lastname, phone); - update(); - } - - const handleDelete = async () => { - await deleteUser(); - update(); - } - - return ( - <> - - - setOpen(false)}> -
- -
-

Compte

- - - -
-

Changer de Prénom

- setName(e.target.value)} type={"text"} /> -
- -
-

Changer de nom

- setLastName(e.target.value)} type={"text"} /> -
- -
-

Changer de numéro de téléphone

- setPhone(e.target.value)} type={"text"} /> -
- - - -
- -
-

Apparence

- -
-
-
- - - setIsDelete(false)}> -
-

Êtes vous vraiment sĂ»r de vouloir supprimer votre compte ?

- -
-
- - ) -} \ No newline at end of file diff --git a/src/pages/Profile/components/SettingsModal/SettingsModal.tsx b/src/pages/Profile/components/SettingsModal/SettingsModal.tsx new file mode 100644 index 0000000..452d668 --- /dev/null +++ b/src/pages/Profile/components/SettingsModal/SettingsModal.tsx @@ -0,0 +1,187 @@ +/*import Modal from "../../../../components/ui/modal/modal.jsx"; +import Button from "../../../../components/ui/button/button.jsx"; +import styles from "./SettingsModal.module.css" +import { useContext, useState } from "react"; +import ThemeSwitcher from "../../../../components/ui/themeSwitcher/ThemeSwitcher.jsx"; +import { AuthContext } from "../../../../contexts/auth/AuthContext.js"; +import TextInput from "../../../../components/ui/input/input.jsx"; +import updateUser from "../../../../utils/users/updateUser.js"; +import deleteUser from "../../../../utils/users/deleteUser.js"; + + +export default function SettingsModal() { + + const { user, logout, update } = useContext(AuthContext); + + const [open, setOpen] = useState(false); + const [isDelete, setIsDelete] = useState(false); + + const [name, setName] = useState(user?.name); + const [lastname, setLastName] = useState(user?.lastname); + const [phone, setPhone] = useState(user?.phone); + + const handleLogout = () => { + logout(); + } + + const handleSubmit = async () => { + await updateUser(name, lastname, phone); + update(); + } + + const handleDelete = async () => { + await deleteUser(); + update(); + } + + return ( + <> + + + setOpen(false)}> +
+ +
+

Compte

+ + + +
+

Changer de Prénom

+ setName(e.target.value)} type={"text"} /> +
+ +
+

Changer de nom

+ setLastName(e.target.value)} type={"text"} /> +
+ +
+

Changer de numéro de téléphone

+ setPhone(e.target.value)} type={"text"} /> +
+ + + +
+ +
+

Apparence

+ +
+
+
+ + + setIsDelete(false)}> +
+

Êtes vous vraiment sĂ»r de vouloir supprimer votre compte ?

+ +
+
+ + ) +}*/ + +import Modal from "../../../../components/ui/modal/modal"; +import Button from "../../../../components/ui/button/button"; +import styles from "./SettingsModal.module.css"; +import { useContext, useState } from "react"; +import ThemeSwitcher from "../../../../components/ui/themeSwitcher/ThemeSwitcher"; +import { AuthContext } from "../../../../contexts/auth/AuthContext"; +import TextInput from "../../../../components/ui/input/input"; +import updateUser from "../../../../utils/users/updateUser"; +import deleteUser from "../../../../utils/users/deleteUser"; + +interface User { + name: string; + lastname: string; + phone: string | null; +} + +interface AuthContextType { + user: User | null; + logout: () => void; + update: () => void; +} + +export default function SettingsModal() { + const { user, logout, update } = useContext(AuthContext) as AuthContextType; + + const [open, setOpen] = useState(false); + const [isDelete, setIsDelete] = useState(false); + const [name, setName] = useState(user?.name || ""); + const [lastname, setLastName] = useState(user?.lastname || ""); + const [phone, setPhone] = useState(user?.phone || null); + + const handleLogout = () => { + logout(); + }; + + const handleSubmit = async () => { + await updateUser(name, lastname, phone); + update(); + }; + + const handleDelete = async () => { + await deleteUser(); + update(); + }; + + return ( + <> + + + setOpen(false)}> +
+
+

Compte

+ + + +
+

Changer de Prénom

+ setName(e.target.value)} type={"text"} /> +
+ +
+

Changer de nom

+ setLastName(e.target.value)} type={"text"} /> +
+ +
+

Changer de numéro de téléphone

+ setPhone(e.target.value)} type={"text"} /> +
+ + +
+ +
+

Apparence

+ +
+
+
+ + setIsDelete(false)}> +
+

Êtes-vous vraiment sĂ»r de vouloir supprimer votre compte ?

+ +
+
+ + ); +} diff --git a/src/utils/fetchWrapper.js b/src/utils/fetchWrapper.js deleted file mode 100644 index aa9f0a0..0000000 --- a/src/utils/fetchWrapper.js +++ /dev/null @@ -1,45 +0,0 @@ -export default async function fetchWrapper( - path, - data = null, - method = "GET", - headers = {} -) { - try { - const res = await fetch( - `${import.meta.env.VITE_API_URL}${path}`, - { - method, - credentials: "include", - headers: { - "Accept": "application/json", - "Content-Type": "application/json", - ...headers, - }, - body: data ? JSON.stringify(data) : null, - } - ); - - let responseData = null; - - try { - responseData = await res.json(); - } catch (_) { - responseData = null; - } - - return { - status: res.status, - data: responseData, - }; - - } catch (err) { - console.error("❌ Erreur rĂ©seau :", err); - - return { - status: 0, - data: { - message: "Network error", - }, - }; - } -} diff --git a/src/utils/fetchWrapper.ts b/src/utils/fetchWrapper.ts new file mode 100644 index 0000000..a130373 --- /dev/null +++ b/src/utils/fetchWrapper.ts @@ -0,0 +1,92 @@ +/*export default async function fetchWrapper(path,data = null,method = "GET",headers = {}) { + try { + const res = await fetch( + `${import.meta.env.VITE_API_URL}${path}`, + { + method, + credentials: "include", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + ...headers, + }, + body: data ? JSON.stringify(data) : null, + } + ); + + let responseData = null; + + try { + responseData = await res.json(); + } catch (_) { + responseData = null; + } + + return { + status: res.status, + data: responseData, + }; + + } catch (err) { + console.error("❌ Erreur rĂ©seau :", err); + + return { + status: 0, + data: { + message: "Network error", + }, + }; + } +}*/ + +interface FetchResponse { + status: number; + data: T | null; +} + +export default async function fetchWrapper( + path: string, + data: any = null, + method: string = "GET", + headers: Record = {} +): Promise { + try { + const res = await fetch( + `${import.meta.env.VITE_API_URL}${path}`, + { + method, + credentials: "include", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + ...headers, + }, + body: data ? JSON.stringify(data) : null, + } + ); + + let responseData = null; + + try { + responseData = await res.json(); + } catch (_) { + responseData = null; + } + + return { + status: res.status, + data: responseData, + }; + + } catch (err) { + console.error("❌ Erreur rĂ©seau :", err); + + return { + status: 0, + data: { + message: "Network error", + }, + }; + } +} + diff --git a/src/utils/getXSRF.js b/src/utils/getXSRF.js deleted file mode 100644 index f1dc5fc..0000000 --- a/src/utils/getXSRF.js +++ /dev/null @@ -1,19 +0,0 @@ -export default async function getXSRFToken() { - const response = await fetch(`${import.meta.env.VITE_API_URL}/sanctum/csrf-cookie`, { - method: 'GET', - credentials: 'include' - }); - - if (!response.ok && response.status !== 204) { - throw new Error(`Error : ${response.status}`); - } - - const name = 'XSRF-TOKEN'; - const value = `; ${document.cookie}`; - const parts = value.split(`; ${name}=`); - if (parts.length === 2) { - return decodeURIComponent(parts.pop().split(';').shift()); - } - - throw new Error('Error: Invalid XSRF-TOKEN'); -} diff --git a/src/utils/getXSRF.ts b/src/utils/getXSRF.ts new file mode 100644 index 0000000..f96606d --- /dev/null +++ b/src/utils/getXSRF.ts @@ -0,0 +1,40 @@ +/*export default async function getXSRFToken() { + const response = await fetch(`${import.meta.env.VITE_API_URL}/sanctum/csrf-cookie`, { + method: 'GET', + credentials: 'include' + }); + + if (!response.ok && response.status !== 204) { + throw new Error(`Error : ${response.status}`); + } + + const name = 'XSRF-TOKEN'; + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) { + return decodeURIComponent(parts.pop().split(';').shift()); + } + + throw new Error('Error: Invalid XSRF-TOKEN'); +}*/ + +export default async function getXSRFToken(): Promise { + const response = await fetch(`${import.meta.env.VITE_API_URL}/sanctum/csrf-cookie`, { + method: 'GET', + credentials: 'include' + }); + + if (!response.ok && response.status !== 204) { + throw new Error(`Error : ${response.status}`); + } + + const name = 'XSRF-TOKEN'; + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) { + return decodeURIComponent(parts.pop()!.split(';').shift()!); + } + + throw new Error('Error: Invalid XSRF-TOKEN'); +} + diff --git a/src/utils/users/deleteUser.js b/src/utils/users/deleteUser.js deleted file mode 100644 index 4cb9dcf..0000000 --- a/src/utils/users/deleteUser.js +++ /dev/null @@ -1,13 +0,0 @@ -import fetchWrapper from "../fetchWrapper.js"; -import getXSRFToken from "../getXSRF.js"; - -export default async function deleteUser() { - const csrfToken = await getXSRFToken(); - - return fetchWrapper( - "/api/users", - null, - "DELETE", - { "X-XSRF-TOKEN": csrfToken } - ); -} diff --git a/src/utils/users/deleteUser.ts b/src/utils/users/deleteUser.ts new file mode 100644 index 0000000..193e5b0 --- /dev/null +++ b/src/utils/users/deleteUser.ts @@ -0,0 +1,13 @@ +import fetchWrapper from "../fetchWrapper"; +import getXSRFToken from "../getXSRF"; + +export default async function deleteUser(): Promise<{status: number;data: any;}> { + const csrfToken: string = await getXSRFToken(); + + return fetchWrapper( + "/api/users", + null, + "DELETE", + { "X-XSRF-TOKEN": csrfToken } + ); +} diff --git a/src/utils/users/updateUser.js b/src/utils/users/updateUser.js deleted file mode 100644 index af18273..0000000 --- a/src/utils/users/updateUser.js +++ /dev/null @@ -1,13 +0,0 @@ -import fetchWrapper from "../fetchWrapper.js"; -import getXSRFToken from "../getXSRF.js"; - -export default async function updateUser(name, lastname, phone) { - const csrfToken = await getXSRFToken(); - - return fetchWrapper( - "/api/users/update", - { name, lastname, phone }, - "POST", - { "X-XSRF-TOKEN": csrfToken } - ); -} diff --git a/src/utils/users/updateUser.ts b/src/utils/users/updateUser.ts new file mode 100644 index 0000000..6714250 --- /dev/null +++ b/src/utils/users/updateUser.ts @@ -0,0 +1,33 @@ +/*import fetchWrapper from "../fetchWrapper.js"; +import getXSRFToken from "../getXSRF.js"; + +export default async function updateUser(name, lastname, phone) { + const csrfToken = await getXSRFToken(); + + return fetchWrapper( + "/api/users/update", + { name, lastname, phone }, + "POST", + { "X-XSRF-TOKEN": csrfToken } + ); +}*/ + +import fetchWrapper from "../fetchWrapper"; +import getXSRFToken from "../getXSRF"; + +interface UserUpdateData { + name: string; + lastname: string; + phone: string | null; +} + +interface RequestHeaders { + "X-XSRF-TOKEN": string; +} +export default async function updateUser(name: string,lastname: string,phone: string | null): Promise { + const csrfToken: string = await getXSRFToken(); + const userData: UserUpdateData = {name,lastname,phone,}; + const headers: RequestHeaders = {"X-XSRF-TOKEN": csrfToken,}; + return fetchWrapper("/api/users/update",userData,"POST",headers); +} + diff --git a/tsconfig.json b/tsconfig.json index 693c67f..f5783f0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -54,7 +54,7 @@ "isolatedModules": true, "noUncheckedIndexedAccess": true, - "exactOptionalPropertyTypes": true, + "exactOptionalPropertyTypes": false, "resolveJsonModule": true, "skipLibCheck": true From f4472483adb93cb415871efc382fa65e2cee3347 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Fri, 30 Jan 2026 16:55:48 +0100 Subject: [PATCH 02/47] fix calendar --- src/pages/Home/components/Calendar/calendar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Home/components/Calendar/calendar.jsx b/src/pages/Home/components/Calendar/calendar.jsx index 556c099..5b027e9 100644 --- a/src/pages/Home/components/Calendar/calendar.jsx +++ b/src/pages/Home/components/Calendar/calendar.jsx @@ -5,7 +5,7 @@ import {EventContext} from "../../../../contexts/events/EventContext.js"; function Calendar() { - const { events } = useContext(EventContext); + const { events=[] } = useContext(EventContext); const monthNames = [ "Janvier", "FĂ©vrier", "Mars", "Avril", "Mai", "Juin", From f6def2f817446507b78aab1a7d8b113f48a566d9 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Fri, 6 Feb 2026 15:07:14 +0100 Subject: [PATCH 03/47] Convert Input and Button UI component to TypeScript --- src/components/Filter/Filter.jsx | 2 +- src/components/Footer/Footer.jsx | 2 +- src/components/ToolBar/ToolBar.jsx | 2 +- .../createEventBtn/CreateEventBtn.jsx | 4 +- src/components/ui/button/button.jsx | 16 ----- src/components/ui/button/button.tsx | 44 +++++++++++++ src/components/ui/input/input.jsx | 38 ------------ src/components/ui/input/input.tsx | 62 +++++++++++++++++++ src/components/ui/modal/modal.jsx | 2 +- .../ui/themeSwitcher/ThemeSwitcher.jsx | 2 +- .../pendingMembers/PendingMembers.jsx | 2 +- src/pages/Events/components/Event/Event.jsx | 2 +- .../Login/components/loginform/loginForm.jsx | 4 +- .../SettingsModal/SettingsModal.jsx | 4 +- src/pages/Register/RegisterPage.jsx | 4 +- .../components/manageMember/ManageMember.jsx | 2 +- .../deactivateMember/deactivateMemberBtn.jsx | 2 +- .../manageMember/modifyrole/ModifyRole.jsx | 2 +- src/pages/Volunteers/VolunteerCard.jsx | 2 +- .../components/EventTask/EventTask.jsx | 2 +- .../createTaskBtn/createTaskBtn.jsx | 4 +- .../deleteEventBtn/deleteEventBtn.jsx | 2 +- .../deleteTaskBtn/deleteTaskBtn.jsx | 2 +- .../ValidationErrorPage.jsx | 2 +- src/stories/Button.stories.jsx | 2 +- src/stories/Input.stories.jsx | 2 +- src/stories/Modal.stories.jsx | 2 +- 27 files changed, 134 insertions(+), 82 deletions(-) delete mode 100644 src/components/ui/button/button.jsx create mode 100644 src/components/ui/button/button.tsx delete mode 100644 src/components/ui/input/input.jsx create mode 100644 src/components/ui/input/input.tsx diff --git a/src/components/Filter/Filter.jsx b/src/components/Filter/Filter.jsx index d323cc9..eb0bf87 100644 --- a/src/components/Filter/Filter.jsx +++ b/src/components/Filter/Filter.jsx @@ -1,5 +1,5 @@ import styles from "./Filter.module.css"; -import Button from "../ui/button/button.jsx" +import Button from "../ui/button/button.tsx" function Filter({isFilterVisible, filters, setFilters}){ const setAlphabeticalOrder = (order) => { diff --git a/src/components/Footer/Footer.jsx b/src/components/Footer/Footer.jsx index 068f976..754f071 100644 --- a/src/components/Footer/Footer.jsx +++ b/src/components/Footer/Footer.jsx @@ -2,7 +2,7 @@ import styles from "./Footer.module.css" import {Link} from "react-router"; import {useState} from "react"; import Modal from "../ui/modal/modal.jsx"; -import Button from "../ui/button/button.jsx"; +import Button from "../ui/button/button.tsx"; import scrollToTop from "../../utils/scrollToTop.js"; export default function Footer(){ diff --git a/src/components/ToolBar/ToolBar.jsx b/src/components/ToolBar/ToolBar.jsx index 2b90746..874fa70 100644 --- a/src/components/ToolBar/ToolBar.jsx +++ b/src/components/ToolBar/ToolBar.jsx @@ -3,7 +3,7 @@ import Filter from "../Filter/Filter.jsx"; import {useContext, useEffect, useState} from "react"; import SearchBar from "../SearchBar/SearchBar.jsx"; import CreateEventBtn from "../createEventBtn/CreateEventBtn.jsx"; -import Button from "../ui/button/button.jsx"; +import Button from "../ui/button/button.tsx"; import {AuthContext} from "../../contexts/auth/AuthContext.js"; function ToolBar({setFilters, filters, showCreate = true}) { diff --git a/src/components/createEventBtn/CreateEventBtn.jsx b/src/components/createEventBtn/CreateEventBtn.jsx index 18877cd..becbc76 100644 --- a/src/components/createEventBtn/CreateEventBtn.jsx +++ b/src/components/createEventBtn/CreateEventBtn.jsx @@ -1,8 +1,8 @@ import styles from "./createEventBtn.module.css" import { useState, useContext } from "react"; import Modal from "../ui/modal/modal.jsx"; -import Button from "../ui/button/button.jsx"; -import TextInput from "../ui/input/input.jsx"; +import Button from "../ui/button/button.tsx"; +import TextInput from "../ui/input/input.tsx"; import { EventContext } from "../../contexts/events/EventContext.js"; diff --git a/src/components/ui/button/button.jsx b/src/components/ui/button/button.jsx deleted file mode 100644 index 0fc74ab..0000000 --- a/src/components/ui/button/button.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import styles from "./button.module.css"; - -const Button = ({ children, variant = "primary", onClick, className }) => { - let btnStyle; - - if (variant === "primary") btnStyle = styles.primary; - else if (variant === "danger") btnStyle = styles.danger; - else if(variant === "transparent") btnStyle = styles.transparent; - else btnStyle = styles.default; - - return
- {children} -
; -}; - -export default Button; diff --git a/src/components/ui/button/button.tsx b/src/components/ui/button/button.tsx new file mode 100644 index 0000000..1866c39 --- /dev/null +++ b/src/components/ui/button/button.tsx @@ -0,0 +1,44 @@ +import { ReactNode, MouseEventHandler } from "react"; +import styles from "./button.module.css"; + +type ButtonVariant = "primary" | "danger" | "transparent" | "default"; + +interface ButtonProps { + children: ReactNode; + variant?: ButtonVariant; + onClick?: MouseEventHandler; + className?: string; +} + +const Button = ({ + children, + variant = "primary", + onClick, + className = "", + }: ButtonProps) => { + let btnStyle: string; + + if (variant === "primary") { // @ts-ignore + btnStyle = styles.primary; + } + else if (variant === "danger") { // @ts-ignore + btnStyle = styles.danger; + } + else if (variant === "transparent") { // @ts-ignore + btnStyle = styles.transparent; + } + else { // @ts-ignore + btnStyle = styles.default; + } + + return ( +
+ {children} +
+ ); +}; + +export default Button; diff --git a/src/components/ui/input/input.jsx b/src/components/ui/input/input.jsx deleted file mode 100644 index 7713b27..0000000 --- a/src/components/ui/input/input.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import styles from "./input.module.css"; -import { useState } from "react"; - -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; - - const toggleShowPassword = () => { - setShowPassword(!showPassword); - }; - - if(password) return ( -
- - -
- ); - - return -}; - -export default TextInput; diff --git a/src/components/ui/input/input.tsx b/src/components/ui/input/input.tsx new file mode 100644 index 0000000..0cc1e7f --- /dev/null +++ b/src/components/ui/input/input.tsx @@ -0,0 +1,62 @@ +import styles from "./input.module.css"; +import { useState, ChangeEvent, InputHTMLAttributes } from "react"; + +interface TextInputProps extends InputHTMLAttributes { + placeholder?: string; + value?: string; + onChange?: (e: ChangeEvent) => void; + borderStyle?: "square" | "rounded"; + password?: boolean; + className?: string; +} + +const TextInput = ({ + placeholder, + onChange, + value, + borderStyle = "square", + password, + className = "", + ...props + }: TextInputProps) => { + const [showPassword, setShowPassword] = useState(false); + + let inputBorderStyle = styles.square; + if (borderStyle === "rounded") inputBorderStyle = styles.rounded; + + const toggleShowPassword = () => { + setShowPassword((prev) => !prev); + }; + + if (password) + return ( +
+ + +
+ ); + + return ( + + ); +}; + +export default TextInput; diff --git a/src/components/ui/modal/modal.jsx b/src/components/ui/modal/modal.jsx index 822398c..82d60a2 100644 --- a/src/components/ui/modal/modal.jsx +++ b/src/components/ui/modal/modal.jsx @@ -1,6 +1,6 @@ import { createPortal } from "react-dom"; import styles from "./modal.module.css"; -import Button from "../button/button.jsx"; +import Button from "../button/button.tsx"; const Modal = ({ open, onClose, children, title }) => { diff --git a/src/components/ui/themeSwitcher/ThemeSwitcher.jsx b/src/components/ui/themeSwitcher/ThemeSwitcher.jsx index ef3ffc6..25e0369 100644 --- a/src/components/ui/themeSwitcher/ThemeSwitcher.jsx +++ b/src/components/ui/themeSwitcher/ThemeSwitcher.jsx @@ -1,4 +1,4 @@ -import Button from "../button/button.jsx"; +import Button from "../button/button.tsx"; import { useState, useEffect } from "react"; import styles from "./ThemeSwitcher.module.css"; diff --git a/src/pages/Admin/components/pendingMembers/PendingMembers.jsx b/src/pages/Admin/components/pendingMembers/PendingMembers.jsx index 69dc4e4..151856e 100644 --- a/src/pages/Admin/components/pendingMembers/PendingMembers.jsx +++ b/src/pages/Admin/components/pendingMembers/PendingMembers.jsx @@ -1,5 +1,5 @@ import React, {useState, useEffect} from "react"; -import Button from "../../../../components/ui/button/button.jsx"; +import Button from "../../../../components/ui/button/button.tsx"; import styles from "./PendingMembers.module.css"; import Modal from "../../../../components/ui/modal/modal.jsx"; import getUserToValidate from "../../../../utils/users/getUserToValidate.js"; diff --git a/src/pages/Events/components/Event/Event.jsx b/src/pages/Events/components/Event/Event.jsx index e011d56..41181d3 100644 --- a/src/pages/Events/components/Event/Event.jsx +++ b/src/pages/Events/components/Event/Event.jsx @@ -1,7 +1,7 @@ import styles from "./Event.module.css"; import { useState, useEffect } from "react"; import { useNavigate } from "react-router"; -import Button from "/src/components/ui/button/button.jsx" +import Button from "/src/components/ui/button/button" function Event({event}){ diff --git a/src/pages/Login/components/loginform/loginForm.jsx b/src/pages/Login/components/loginform/loginForm.jsx index ee2ee6b..80654f3 100644 --- a/src/pages/Login/components/loginform/loginForm.jsx +++ b/src/pages/Login/components/loginform/loginForm.jsx @@ -1,7 +1,7 @@ import { useState, useContext } from "react"; import styles from "./loginForm.module.css"; -import Button from "../../../../components/ui/button/button.jsx"; -import Input from "../../../../components/ui/input/input.jsx"; +import Button from "../../../../components/ui/button/button.tsx"; +import Input from "../../../../components/ui/input/input.tsx"; import Modal from "../../../../components/ui/modal/modal.jsx"; import { useNavigate } from "react-router"; import { AuthContext } from "../../../../contexts/auth/AuthContext.js"; diff --git a/src/pages/Profile/components/SettingsModal/SettingsModal.jsx b/src/pages/Profile/components/SettingsModal/SettingsModal.jsx index 4283559..a831fba 100644 --- a/src/pages/Profile/components/SettingsModal/SettingsModal.jsx +++ b/src/pages/Profile/components/SettingsModal/SettingsModal.jsx @@ -1,10 +1,10 @@ import Modal from "../../../../components/ui/modal/modal.jsx"; -import Button from "../../../../components/ui/button/button.jsx"; +import Button from "../../../../components/ui/button/button.tsx"; import styles from "./SettingsModal.module.css" import { useContext, useState } from "react"; import ThemeSwitcher from "../../../../components/ui/themeSwitcher/ThemeSwitcher.jsx"; import { AuthContext } from "../../../../contexts/auth/AuthContext.js"; -import TextInput from "../../../../components/ui/input/input.jsx"; +import TextInput from "../../../../components/ui/input/input.tsx"; import updateUser from "../../../../utils/users/updateUser.js"; import deleteUser from "../../../../utils/users/deleteUser.js"; diff --git a/src/pages/Register/RegisterPage.jsx b/src/pages/Register/RegisterPage.jsx index f0547c3..f3bebaa 100644 --- a/src/pages/Register/RegisterPage.jsx +++ b/src/pages/Register/RegisterPage.jsx @@ -1,7 +1,7 @@ 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 Input from "../../components/ui/input/input.tsx"; +import Button from "../../components/ui/button/button.tsx"; import {useContext, useEffect, useState} from "react"; import register from "../../utils/register.js"; import { useNavigate } from "react-router"; diff --git a/src/pages/VolunteerProfile/components/manageMember/ManageMember.jsx b/src/pages/VolunteerProfile/components/manageMember/ManageMember.jsx index 6266501..2ee7fc0 100644 --- a/src/pages/VolunteerProfile/components/manageMember/ManageMember.jsx +++ b/src/pages/VolunteerProfile/components/manageMember/ManageMember.jsx @@ -1,4 +1,4 @@ -import Button from "../../../../components/ui/button/button.jsx"; +import Button from "../../../../components/ui/button/button.tsx"; import Modal from "../../../../components/ui/modal/modal.jsx"; import {useContext, useState} from "react"; import styles from "./manageMember.module.css" diff --git a/src/pages/VolunteerProfile/components/manageMember/deactivateMember/deactivateMemberBtn.jsx b/src/pages/VolunteerProfile/components/manageMember/deactivateMember/deactivateMemberBtn.jsx index ccb8390..af6c7af 100644 --- a/src/pages/VolunteerProfile/components/manageMember/deactivateMember/deactivateMemberBtn.jsx +++ b/src/pages/VolunteerProfile/components/manageMember/deactivateMember/deactivateMemberBtn.jsx @@ -1,4 +1,4 @@ -import Button from "../../../../../components/ui/button/button.jsx"; +import Button from "../../../../../components/ui/button/button.tsx"; import Modal from "../../../../../components/ui/modal/modal.jsx"; import { useState } from "react"; import deactivateUser from "../../../../../utils/users/deactivateUser.js" diff --git a/src/pages/VolunteerProfile/components/manageMember/modifyrole/ModifyRole.jsx b/src/pages/VolunteerProfile/components/manageMember/modifyrole/ModifyRole.jsx index bc4d495..b5ac70b 100644 --- a/src/pages/VolunteerProfile/components/manageMember/modifyrole/ModifyRole.jsx +++ b/src/pages/VolunteerProfile/components/manageMember/modifyrole/ModifyRole.jsx @@ -1,5 +1,5 @@ import styles from "../manageMember.module.css"; -import Button from "../../../../../components/ui/button/button.jsx"; +import Button from "../../../../../components/ui/button/button.tsx"; import modifyRole from "../../../../../utils/users/modifyRole.js"; import { useState, useEffect } from "react"; import getRoles from "../../../../../utils/getRoles.js"; diff --git a/src/pages/Volunteers/VolunteerCard.jsx b/src/pages/Volunteers/VolunteerCard.jsx index 144d708..f69f6df 100644 --- a/src/pages/Volunteers/VolunteerCard.jsx +++ b/src/pages/Volunteers/VolunteerCard.jsx @@ -2,7 +2,7 @@ import React, {useState} from "react"; import styles from "./VolunteersPage.module.css"; import formatDate from "../../utils/date/formatDate.js"; import { useNavigate } from "react-router"; -import Button from "../../components/ui/button/button.jsx"; +import Button from "../../components/ui/button/button.tsx"; export default function VolunteerCard({ user }) { diff --git a/src/pages/eventDetail/components/EventTask/EventTask.jsx b/src/pages/eventDetail/components/EventTask/EventTask.jsx index bbde7b6..2c60a79 100644 --- a/src/pages/eventDetail/components/EventTask/EventTask.jsx +++ b/src/pages/eventDetail/components/EventTask/EventTask.jsx @@ -1,6 +1,6 @@ import styles from "./eventTask.module.css"; import { formatDateLetter } from "../../../../utils/date/formatDateLetter.js"; -import Button from "../../../../components/ui/button/button.jsx"; +import Button from "../../../../components/ui/button/button.tsx"; import assign from "../../../../utils/tasks/assign.js"; import unAssign from "../../../../utils/tasks/unAssign.js"; import { useContext, useEffect, useState } from "react"; diff --git a/src/pages/eventDetail/components/createTaskBtn/createTaskBtn.jsx b/src/pages/eventDetail/components/createTaskBtn/createTaskBtn.jsx index 8862ada..a6bdb89 100644 --- a/src/pages/eventDetail/components/createTaskBtn/createTaskBtn.jsx +++ b/src/pages/eventDetail/components/createTaskBtn/createTaskBtn.jsx @@ -1,7 +1,7 @@ import styles from "../../eventDetail.module.css"; -import Button from "../../../../components/ui/button/button.jsx"; +import Button from "../../../../components/ui/button/button.tsx"; import {useContext, useState} from "react"; -import TextInput from "../../../../components/ui/input/input.jsx"; +import TextInput from "../../../../components/ui/input/input.tsx"; import Modal from "../../../../components/ui/modal/modal.jsx"; import createTaskApi from "../../../../utils/tasks/createTask.js"; import {EventDetailContext} from "../../../../contexts/eventDetail/eventDetail.js"; diff --git a/src/pages/eventDetail/components/deleteEventBtn/deleteEventBtn.jsx b/src/pages/eventDetail/components/deleteEventBtn/deleteEventBtn.jsx index 813a995..92eb5c3 100644 --- a/src/pages/eventDetail/components/deleteEventBtn/deleteEventBtn.jsx +++ b/src/pages/eventDetail/components/deleteEventBtn/deleteEventBtn.jsx @@ -1,5 +1,5 @@ import styles from "../../eventDetail.module.css"; -import Button from "../../../../components/ui/button/button.jsx"; +import Button from "../../../../components/ui/button/button.tsx"; import Modal from "../../../../components/ui/modal/modal.jsx"; import { useContext, useState } from "react"; import { EventDetailContext } from "../../../../contexts/eventDetail/eventDetail.js"; diff --git a/src/pages/eventDetail/components/deleteTaskBtn/deleteTaskBtn.jsx b/src/pages/eventDetail/components/deleteTaskBtn/deleteTaskBtn.jsx index fdad16f..28f507a 100644 --- a/src/pages/eventDetail/components/deleteTaskBtn/deleteTaskBtn.jsx +++ b/src/pages/eventDetail/components/deleteTaskBtn/deleteTaskBtn.jsx @@ -1,7 +1,7 @@ import { useContext } from "react"; import { AuthContext } from "../../../../contexts/auth/AuthContext.js"; import { EventDetailContext } from "../../../../contexts/eventDetail/eventDetail.js"; -import Button from "../../../../components/ui/button/button.jsx"; +import Button from "../../../../components/ui/button/button.tsx"; import { useState } from "react"; import Modal from "../../../../components/ui/modal/modal.jsx"; import styles from "../../eventDetail.module.css"; diff --git a/src/pages/waitValidationPage/ValidationErrorPage.jsx b/src/pages/waitValidationPage/ValidationErrorPage.jsx index aeff346..3086d77 100644 --- a/src/pages/waitValidationPage/ValidationErrorPage.jsx +++ b/src/pages/waitValidationPage/ValidationErrorPage.jsx @@ -1,6 +1,6 @@ import styles from "./validationErrorPage.module.css" import Background from "../../components/background/background.jsx"; -import Button from "../../components/ui/button/button.jsx"; +import Button from "../../components/ui/button/button.tsx"; import { useContext } from "react"; import { AuthContext } from "../../contexts/auth/AuthContext.js"; import { Navigate, useNavigate, Link } from "react-router"; diff --git a/src/stories/Button.stories.jsx b/src/stories/Button.stories.jsx index 3f86188..3179067 100644 --- a/src/stories/Button.stories.jsx +++ b/src/stories/Button.stories.jsx @@ -1,6 +1,6 @@ import { fn } from 'storybook/test'; -import Button from '../components/ui/button/button.jsx'; +import Button from '../components/ui/button/button.tsx'; export default { diff --git a/src/stories/Input.stories.jsx b/src/stories/Input.stories.jsx index 1d3cc0c..86cd833 100644 --- a/src/stories/Input.stories.jsx +++ b/src/stories/Input.stories.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import TextInput from "../components/ui/input/input.jsx"; +import TextInput from "../components/ui/input/input.tsx"; export default { diff --git a/src/stories/Modal.stories.jsx b/src/stories/Modal.stories.jsx index 1ece2ae..eb0b6ec 100644 --- a/src/stories/Modal.stories.jsx +++ b/src/stories/Modal.stories.jsx @@ -1,5 +1,5 @@ import Modal from "../components/ui/modal/modal.jsx"; -import Button from "../components/ui/button/button.jsx"; +import Button from "../components/ui/button/button.tsx"; import { useState } from "react"; export default { From d79f9f5c8459c3ca5d58ec9ebf2672bdae572085 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Fri, 6 Feb 2026 15:07:32 +0100 Subject: [PATCH 04/47] Create registerMember admin component --- src/interfaces/response.interface.ts | 4 ++ src/pages/Admin/AdminPage.jsx | 13 ++---- .../registerMember/registerMember.module.css | 27 +++++++++++ .../registerMember/registerMember.tsx | 46 +++++++++++++++++++ .../registerMember/responseData.inteface.ts | 4 ++ src/utils/users/adminCreateUser.ts | 14 ++++++ 6 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 src/interfaces/response.interface.ts create mode 100644 src/pages/Admin/components/registerMember/registerMember.module.css create mode 100644 src/pages/Admin/components/registerMember/registerMember.tsx create mode 100644 src/pages/Admin/components/registerMember/responseData.inteface.ts create mode 100644 src/utils/users/adminCreateUser.ts diff --git a/src/interfaces/response.interface.ts b/src/interfaces/response.interface.ts new file mode 100644 index 0000000..5f7e27e --- /dev/null +++ b/src/interfaces/response.interface.ts @@ -0,0 +1,4 @@ +export default interface Response { + status: number; + data: T; +} \ No newline at end of file diff --git a/src/pages/Admin/AdminPage.jsx b/src/pages/Admin/AdminPage.jsx index b8cf562..c300ff8 100644 --- a/src/pages/Admin/AdminPage.jsx +++ b/src/pages/Admin/AdminPage.jsx @@ -1,10 +1,10 @@ -import { useState, useContext } from "react"; +import { useContext } from "react"; import styles from "./AdminPage.module.css"; -import ParticipationChart from "./components/chart/ParticipationChart.jsx"; import IncompleteEvents from "./components/IncompleteEvent/IncompleteEvent.jsx"; import PendingMembers from "./components/pendingMembers/PendingMembers.jsx"; import { AuthContext } from "../../contexts/auth/AuthContext.js"; import { useNavigate } from "react-router"; +import RegisterMember from "./components/registerMember/registerMember.tsx"; function AdminPage() { @@ -12,18 +12,11 @@ function AdminPage() { const { user } = useContext(AuthContext); const navigate = useNavigate(); - const [selected, setSelected] = useState("3"); - const [participationRate] = useState(89); - if(!user.isAdmin) navigate("/"); return (
- +
diff --git a/src/pages/Admin/components/registerMember/registerMember.module.css b/src/pages/Admin/components/registerMember/registerMember.module.css new file mode 100644 index 0000000..071f79b --- /dev/null +++ b/src/pages/Admin/components/registerMember/registerMember.module.css @@ -0,0 +1,27 @@ +.leftBlock { + width: 40%; + display: flex; + flex-direction: column; + align-items: center; + height: 100%; + min-height: calc(100vh - 260px); + box-sizing: border-box; + padding: 20px; + margin: 0; +} + +.registerMemberHeader { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + margin-bottom: 10px; +} + +.registerForm { + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; + justify-content: center; +} \ No newline at end of file diff --git a/src/pages/Admin/components/registerMember/registerMember.tsx b/src/pages/Admin/components/registerMember/registerMember.tsx new file mode 100644 index 0000000..eec292b --- /dev/null +++ b/src/pages/Admin/components/registerMember/registerMember.tsx @@ -0,0 +1,46 @@ +import React, { useState } from "react" +import styles from "./registerMember.module.css" +import TextInput from "../../../../components/ui/input/input" +import Button from "../../../../components/ui/button/button"; +import Response from "../../../../interfaces/response.interface"; +import ResponseData from "./responseData.inteface"; +import adminCreateUser from "../../../../utils/users/adminCreateUser"; + + +const RegisterMember: React.FC = () => { + + const [name, setName] = useState(""); + const [lastname, setLastName] = useState(""); + const [email, setEmail] = useState(""); + const [phone, setPhone] = useState(""); + const [password, setPassword] = useState(""); + + + const handleCreate = async (): Promise => { + + if(name !== "" || lastname !== "" || email !== "" || phone !== "" ) { + const response: Response = await adminCreateUser(email, name, lastname, phone); + if(response.status === 200) setPassword(response.data.password); + } + + } + + return
+
+

Enregister un bénévole

+
+ +
+ setName(e.target.value)} /> + setLastName(e.target.value)} /> + setEmail(e.target.value)} /> + setPhone(e.target.value)} /> + +

{password !== "" && `Mot de passe: ${password}`}

+ + +
+
+} + +export default RegisterMember \ No newline at end of file diff --git a/src/pages/Admin/components/registerMember/responseData.inteface.ts b/src/pages/Admin/components/registerMember/responseData.inteface.ts new file mode 100644 index 0000000..528d1d3 --- /dev/null +++ b/src/pages/Admin/components/registerMember/responseData.inteface.ts @@ -0,0 +1,4 @@ +export default interface ResponseData { + message: string; + password: string; +} \ No newline at end of file diff --git a/src/utils/users/adminCreateUser.ts b/src/utils/users/adminCreateUser.ts new file mode 100644 index 0000000..01860d3 --- /dev/null +++ b/src/utils/users/adminCreateUser.ts @@ -0,0 +1,14 @@ +import fetchWrapper from "../fetchWrapper.js"; +import getXSRFToken from "../getXSRF.js"; +import Response from "../../interfaces/response.interface"; + +export default async function adminCreateUser(email: string, name: string, lastname: string, phone: string): Promise { + const csrfToken = await getXSRFToken(); + + return fetchWrapper( + "/api/users/create", + { email, name, lastname, phone }, + "POST", + { "X-XSRF-TOKEN": csrfToken } + ); +} From 3c992d5df7da0ef2dd60c9065c18b362c7078645 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Fri, 6 Feb 2026 15:11:03 +0100 Subject: [PATCH 05/47] Convert Modal component to Typescript --- src/components/Footer/Footer.jsx | 2 +- src/components/createEventBtn/CreateEventBtn.jsx | 2 +- src/components/ui/modal/{modal.jsx => modal.tsx} | 13 ++++++++++--- .../components/pendingMembers/PendingMembers.jsx | 2 +- src/pages/Login/components/loginform/loginForm.jsx | 2 +- .../components/SettingsModal/SettingsModal.jsx | 2 +- src/pages/Register/RegisterPage.jsx | 2 +- .../components/manageMember/ManageMember.jsx | 2 +- .../deactivateMember/deactivateMemberBtn.jsx | 2 +- .../manageMember/modifyrole/ModifyRole.jsx | 2 +- .../eventDetail/components/EventTask/EventTask.jsx | 2 +- .../components/createTaskBtn/createTaskBtn.jsx | 2 +- .../components/deleteEventBtn/deleteEventBtn.jsx | 2 +- .../components/deleteTaskBtn/deleteTaskBtn.jsx | 2 +- src/stories/Modal.stories.jsx | 2 +- 15 files changed, 24 insertions(+), 17 deletions(-) rename src/components/ui/modal/{modal.jsx => modal.tsx} (67%) diff --git a/src/components/Footer/Footer.jsx b/src/components/Footer/Footer.jsx index 754f071..d5ded57 100644 --- a/src/components/Footer/Footer.jsx +++ b/src/components/Footer/Footer.jsx @@ -1,7 +1,7 @@ import styles from "./Footer.module.css" import {Link} from "react-router"; import {useState} from "react"; -import Modal from "../ui/modal/modal.jsx"; +import Modal from "../ui/modal/modal.tsx"; import Button from "../ui/button/button.tsx"; import scrollToTop from "../../utils/scrollToTop.js"; diff --git a/src/components/createEventBtn/CreateEventBtn.jsx b/src/components/createEventBtn/CreateEventBtn.jsx index becbc76..019b836 100644 --- a/src/components/createEventBtn/CreateEventBtn.jsx +++ b/src/components/createEventBtn/CreateEventBtn.jsx @@ -1,6 +1,6 @@ import styles from "./createEventBtn.module.css" import { useState, useContext } from "react"; -import Modal from "../ui/modal/modal.jsx"; +import Modal from "../ui/modal/modal.tsx"; import Button from "../ui/button/button.tsx"; import TextInput from "../ui/input/input.tsx"; import { EventContext } from "../../contexts/events/EventContext.js"; diff --git a/src/components/ui/modal/modal.jsx b/src/components/ui/modal/modal.tsx similarity index 67% rename from src/components/ui/modal/modal.jsx rename to src/components/ui/modal/modal.tsx index 82d60a2..46db76f 100644 --- a/src/components/ui/modal/modal.jsx +++ b/src/components/ui/modal/modal.tsx @@ -1,16 +1,23 @@ +import { ReactNode, MouseEvent } from "react"; import { createPortal } from "react-dom"; import styles from "./modal.module.css"; -import Button from "../button/button.tsx"; +import Button from "../button/button"; -const Modal = ({ open, onClose, children, title }) => { +interface ModalProps { + open: boolean; + onClose: () => void; + children: ReactNode; + title?: string; +} +const Modal = ({ open, onClose, children, title }: ModalProps) => { if (!open) return null; const handleOverlayClick = () => { onClose(); }; - const handleModalClick = (e) => { + const handleModalClick = (e: MouseEvent) => { e.stopPropagation(); }; diff --git a/src/pages/Admin/components/pendingMembers/PendingMembers.jsx b/src/pages/Admin/components/pendingMembers/PendingMembers.jsx index 151856e..19b1a1a 100644 --- a/src/pages/Admin/components/pendingMembers/PendingMembers.jsx +++ b/src/pages/Admin/components/pendingMembers/PendingMembers.jsx @@ -1,7 +1,7 @@ import React, {useState, useEffect} from "react"; import Button from "../../../../components/ui/button/button.tsx"; import styles from "./PendingMembers.module.css"; -import Modal from "../../../../components/ui/modal/modal.jsx"; +import Modal from "../../../../components/ui/modal/modal.tsx"; import getUserToValidate from "../../../../utils/users/getUserToValidate.js"; import getUserById from "../../../../utils/users/getUserById.js"; import validateUser from "../../../../utils/users/validateUser.js"; diff --git a/src/pages/Login/components/loginform/loginForm.jsx b/src/pages/Login/components/loginform/loginForm.jsx index 80654f3..b4b3338 100644 --- a/src/pages/Login/components/loginform/loginForm.jsx +++ b/src/pages/Login/components/loginform/loginForm.jsx @@ -2,7 +2,7 @@ import { useState, useContext } from "react"; import styles from "./loginForm.module.css"; import Button from "../../../../components/ui/button/button.tsx"; import Input from "../../../../components/ui/input/input.tsx"; -import Modal from "../../../../components/ui/modal/modal.jsx"; +import Modal from "../../../../components/ui/modal/modal.tsx"; import { useNavigate } from "react-router"; import { AuthContext } from "../../../../contexts/auth/AuthContext.js"; diff --git a/src/pages/Profile/components/SettingsModal/SettingsModal.jsx b/src/pages/Profile/components/SettingsModal/SettingsModal.jsx index a831fba..c5e0ff3 100644 --- a/src/pages/Profile/components/SettingsModal/SettingsModal.jsx +++ b/src/pages/Profile/components/SettingsModal/SettingsModal.jsx @@ -1,4 +1,4 @@ -import Modal from "../../../../components/ui/modal/modal.jsx"; +import Modal from "../../../../components/ui/modal/modal.tsx"; import Button from "../../../../components/ui/button/button.tsx"; import styles from "./SettingsModal.module.css" import { useContext, useState } from "react"; diff --git a/src/pages/Register/RegisterPage.jsx b/src/pages/Register/RegisterPage.jsx index f3bebaa..48c9751 100644 --- a/src/pages/Register/RegisterPage.jsx +++ b/src/pages/Register/RegisterPage.jsx @@ -6,7 +6,7 @@ import {useContext, useEffect, useState} from "react"; import register from "../../utils/register.js"; import { useNavigate } from "react-router"; import { AuthContext } from "../../contexts/auth/AuthContext.js"; -import Modal from "../../components/ui/modal/modal.jsx"; +import Modal from "../../components/ui/modal/modal.tsx"; function RegisterPage() { diff --git a/src/pages/VolunteerProfile/components/manageMember/ManageMember.jsx b/src/pages/VolunteerProfile/components/manageMember/ManageMember.jsx index 2ee7fc0..46f8ac2 100644 --- a/src/pages/VolunteerProfile/components/manageMember/ManageMember.jsx +++ b/src/pages/VolunteerProfile/components/manageMember/ManageMember.jsx @@ -1,5 +1,5 @@ import Button from "../../../../components/ui/button/button.tsx"; -import Modal from "../../../../components/ui/modal/modal.jsx"; +import Modal from "../../../../components/ui/modal/modal.tsx"; import {useContext, useState} from "react"; import styles from "./manageMember.module.css" import DeactivateMemberBtn from "./deactivateMember/deactivateMemberBtn.jsx"; diff --git a/src/pages/VolunteerProfile/components/manageMember/deactivateMember/deactivateMemberBtn.jsx b/src/pages/VolunteerProfile/components/manageMember/deactivateMember/deactivateMemberBtn.jsx index af6c7af..2f586f6 100644 --- a/src/pages/VolunteerProfile/components/manageMember/deactivateMember/deactivateMemberBtn.jsx +++ b/src/pages/VolunteerProfile/components/manageMember/deactivateMember/deactivateMemberBtn.jsx @@ -1,5 +1,5 @@ import Button from "../../../../../components/ui/button/button.tsx"; -import Modal from "../../../../../components/ui/modal/modal.jsx"; +import Modal from "../../../../../components/ui/modal/modal.tsx"; import { useState } from "react"; import deactivateUser from "../../../../../utils/users/deactivateUser.js" import { useNavigate } from "react-router"; diff --git a/src/pages/VolunteerProfile/components/manageMember/modifyrole/ModifyRole.jsx b/src/pages/VolunteerProfile/components/manageMember/modifyrole/ModifyRole.jsx index b5ac70b..9f847f4 100644 --- a/src/pages/VolunteerProfile/components/manageMember/modifyrole/ModifyRole.jsx +++ b/src/pages/VolunteerProfile/components/manageMember/modifyrole/ModifyRole.jsx @@ -3,7 +3,7 @@ import Button from "../../../../../components/ui/button/button.tsx"; import modifyRole from "../../../../../utils/users/modifyRole.js"; import { useState, useEffect } from "react"; import getRoles from "../../../../../utils/getRoles.js"; -import Modal from "../../../../../components/ui/modal/modal.jsx"; +import Modal from "../../../../../components/ui/modal/modal.tsx"; export default function ModifyRole({ user }) { diff --git a/src/pages/eventDetail/components/EventTask/EventTask.jsx b/src/pages/eventDetail/components/EventTask/EventTask.jsx index 2c60a79..ff10dbb 100644 --- a/src/pages/eventDetail/components/EventTask/EventTask.jsx +++ b/src/pages/eventDetail/components/EventTask/EventTask.jsx @@ -5,7 +5,7 @@ import assign from "../../../../utils/tasks/assign.js"; import unAssign from "../../../../utils/tasks/unAssign.js"; import { useContext, useEffect, useState } from "react"; import isAssigned from "../../../../utils/tasks/isAssigned.js"; -import Modal from "../../../../components/ui/modal/modal.jsx"; +import Modal from "../../../../components/ui/modal/modal.tsx"; import { AuthContext } from "../../../../contexts/auth/AuthContext.js"; import DeleteTaskBtn from "../deleteTaskBtn/deleteTaskBtn.jsx"; diff --git a/src/pages/eventDetail/components/createTaskBtn/createTaskBtn.jsx b/src/pages/eventDetail/components/createTaskBtn/createTaskBtn.jsx index a6bdb89..6ad9d4b 100644 --- a/src/pages/eventDetail/components/createTaskBtn/createTaskBtn.jsx +++ b/src/pages/eventDetail/components/createTaskBtn/createTaskBtn.jsx @@ -2,7 +2,7 @@ import styles from "../../eventDetail.module.css"; import Button from "../../../../components/ui/button/button.tsx"; import {useContext, useState} from "react"; import TextInput from "../../../../components/ui/input/input.tsx"; -import Modal from "../../../../components/ui/modal/modal.jsx"; +import Modal from "../../../../components/ui/modal/modal.tsx"; import createTaskApi from "../../../../utils/tasks/createTask.js"; import {EventDetailContext} from "../../../../contexts/eventDetail/eventDetail.js"; diff --git a/src/pages/eventDetail/components/deleteEventBtn/deleteEventBtn.jsx b/src/pages/eventDetail/components/deleteEventBtn/deleteEventBtn.jsx index 92eb5c3..c3b4383 100644 --- a/src/pages/eventDetail/components/deleteEventBtn/deleteEventBtn.jsx +++ b/src/pages/eventDetail/components/deleteEventBtn/deleteEventBtn.jsx @@ -1,6 +1,6 @@ import styles from "../../eventDetail.module.css"; import Button from "../../../../components/ui/button/button.tsx"; -import Modal from "../../../../components/ui/modal/modal.jsx"; +import Modal from "../../../../components/ui/modal/modal.tsx"; import { useContext, useState } from "react"; import { EventDetailContext } from "../../../../contexts/eventDetail/eventDetail.js"; import { EventContext } from "../../../../contexts/events/EventContext.js"; diff --git a/src/pages/eventDetail/components/deleteTaskBtn/deleteTaskBtn.jsx b/src/pages/eventDetail/components/deleteTaskBtn/deleteTaskBtn.jsx index 28f507a..90a749b 100644 --- a/src/pages/eventDetail/components/deleteTaskBtn/deleteTaskBtn.jsx +++ b/src/pages/eventDetail/components/deleteTaskBtn/deleteTaskBtn.jsx @@ -3,7 +3,7 @@ import { AuthContext } from "../../../../contexts/auth/AuthContext.js"; import { EventDetailContext } from "../../../../contexts/eventDetail/eventDetail.js"; import Button from "../../../../components/ui/button/button.tsx"; import { useState } from "react"; -import Modal from "../../../../components/ui/modal/modal.jsx"; +import Modal from "../../../../components/ui/modal/modal.tsx"; import styles from "../../eventDetail.module.css"; export default function DeleteTaskBtn({ taskId, eventId, taskName }) { diff --git a/src/stories/Modal.stories.jsx b/src/stories/Modal.stories.jsx index eb0b6ec..0983105 100644 --- a/src/stories/Modal.stories.jsx +++ b/src/stories/Modal.stories.jsx @@ -1,4 +1,4 @@ -import Modal from "../components/ui/modal/modal.jsx"; +import Modal from "../components/ui/modal/modal.tsx"; import Button from "../components/ui/button/button.tsx"; import { useState } from "react"; From 85d9e60025fa0f63d92770c9f716f9eb2472b2b0 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Fri, 6 Feb 2026 15:21:05 +0100 Subject: [PATCH 06/47] add TS + implement upload profilePicture --- src/config/appConfig.js | 4 -- .../Home/components/EventList/eventList.jsx | 31 +++++----- src/pages/Profile/ProfilePage.tsx | 60 ++++++++++++++----- tsconfig.json | 59 +++--------------- 4 files changed, 68 insertions(+), 86 deletions(-) delete mode 100644 src/config/appConfig.js diff --git a/src/config/appConfig.js b/src/config/appConfig.js deleted file mode 100644 index 12764ab..0000000 --- a/src/config/appConfig.js +++ /dev/null @@ -1,4 +0,0 @@ -export const APP_CONFIG = { - contactEmail: "contact@example.com", - contactPhone: "+33 01 02 03 04 05" -} \ No newline at end of file diff --git a/src/pages/Home/components/EventList/eventList.jsx b/src/pages/Home/components/EventList/eventList.jsx index 975adf9..9ab2321 100644 --- a/src/pages/Home/components/EventList/eventList.jsx +++ b/src/pages/Home/components/EventList/eventList.jsx @@ -4,33 +4,32 @@ import EventItem from "./eventItem.jsx"; import { EventContext } from "../../../../contexts/events/EventContext.js"; import { useContext } from "react"; - function EventList() { - - const { events } = useContext(EventContext); + // Utilise une valeur par défaut pour `events` si elle est `undefined` + const { events = [] } = useContext(EventContext); const sortedEvents = useMemo(() => { + // Vérifie explicitement que `events` est un tableau avant de trier + if (!Array.isArray(events)) return []; return [...events].sort( (a, b) => new Date(a.start) - new Date(b.start) ); }, [events]); - return (
-

Liste des événements à venir

- {sortedEvents.length > 0 ? ( -
- {sortedEvents.map((eventGroup, index) => ( - - ))} -
- ) : ( -

Aucun événement planifié pour l'instant.

- )} +

Liste des événements à venir

+ {sortedEvents.length > 0 ? ( +
+ {sortedEvents.map((eventGroup, index) => ( + + ))} +
+ ) : ( +

Aucun événement planifié pour l'instant.

+ )}
- ); } -export default EventList; \ No newline at end of file +export default EventList; diff --git a/src/pages/Profile/ProfilePage.tsx b/src/pages/Profile/ProfilePage.tsx index 84c2211..0733f40 100644 --- a/src/pages/Profile/ProfilePage.tsx +++ b/src/pages/Profile/ProfilePage.tsx @@ -1,12 +1,8 @@ -import { useContext, useEffect } from "react"; -import styles from "./ProfilePage.module.css"; - +import { useContext, useEffect, useState, useRef } from "react"; +import styles from "../Profile/ProfilePage.module.css"; import { AuthContext } from "../../contexts/auth/AuthContext"; import formatDate from "../../utils/date/formatDate"; -//import Task from "../../components/Task/Task"; -//import SettingsModal from "./components/SettingsModal/SettingsModal"; - interface TaskType { id: number; title?: string; @@ -22,6 +18,7 @@ interface User { phone: string | null; created_at: string; tasks: TaskType[]; + profilePicture?: string | null; } interface AuthContextType { @@ -29,10 +26,32 @@ interface AuthContextType { update: () => void; } - - -function ProfilePage(){ +function ProfilePage() { const { user, update } = useContext(AuthContext) as AuthContextType; + const [profilePicture, setProfilePicture] = useState(user?.profilePicture || null); + const fileInputRef = useRef(null); + + // Fonction pour déclencher l'input file + const handleEditPictureClick = () => { + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }; + + // Fonction pour gérer le changement de fichier + const handleFileChange = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + // Logique pour uploader la photo (à adapter selon ton backend) + const formData = new FormData(); + formData.append("profilePicture", file); + + // Appelle la fonction pour uploader la photo (exemple) + // const response = await uploadProfilePicture(formData); + // setProfilePicture(response.url); // Met à jour l'URL de la photo + // update(); // Met à jour le contexte utilisateur + }; useEffect(() => { update(); @@ -44,10 +63,23 @@ function ProfilePage(){
Photo de profil + +
@@ -79,19 +111,18 @@ function ProfilePage(){
- {/* */} + {/* */}

TĂąches :

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

Aucune tĂąche pour le moment.

- )}*/} + )} */}
@@ -101,7 +132,6 @@ function ProfilePage(){ export default ProfilePage; - /*import React, { useContext, useEffect } from "react"; import styles from "./ProfilePage.module.css"; import { AuthContext } from "../../contexts/auth/AuthContext.js"; diff --git a/tsconfig.json b/tsconfig.json index f5783f0..b3a52d5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,49 +1,4 @@ { - // Visit https://aka.ms/tsconfig to read more about this file - /*"compilerOptions": {*/ - // File Layout - // "rootDir": "./src", - // "outDir": "./dist", - - // Environment Settings - // See also https://aka.ms/tsconfig/module - /*"module": "nodenext", - "target": "esnext", - "types": [],*/ - // For nodejs: - // "lib": ["esnext"], - // "types": ["node"], - // and npm install -D @types/node - - // Other Outputs - /*"sourceMap": true, - "declaration": true, - "declarationMap": true,*/ - - // Stricter Typechecking Options - /*"noUncheckedIndexedAccess": true, - "exactOptionalPropertyTypes": true,*/ - - // Style Options - // "noImplicitReturns": true, - // "noImplicitOverride": true, - // "noUnusedLocals": true, - // "noUnusedParameters": true, - // "noFallthroughCasesInSwitch": true, - // "noPropertyAccessFromIndexSignature": true, - - // Recommended Options - /* "strict": true, - "jsx": "react-jsx", - "verbatimModuleSyntax": true, - "isolatedModules": true, - "noUncheckedSideEffectImports": true, - "moduleDetection": "force", - "skipLibCheck": true, - } -} - -{*/ "compilerOptions": { "target": "ESNext", "lib": ["DOM", "DOM.Iterable", "ESNext"], @@ -52,11 +7,13 @@ "jsx": "react-jsx", "strict": true, "isolatedModules": true, - - "noUncheckedIndexedAccess": true, - "exactOptionalPropertyTypes": false, - + "noUncheckedIndexedAccess": false, + "exactOptionalPropertyTypes": true, "resolveJsonModule": true, - "skipLibCheck": true - } + "skipLibCheck": true, + "allowJs": true, + "esModuleInterop": true + }, + "include": ["src"], + "exclude": ["**/*.js"] } From c5d4edb8a1b2d9c913730600169ed8acb8cd5036 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Fri, 6 Feb 2026 15:23:35 +0100 Subject: [PATCH 07/47] Control errors and feedback --- .../registerMember/registerMember.module.css | 4 +++ .../registerMember/registerMember.tsx | 32 +++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/pages/Admin/components/registerMember/registerMember.module.css b/src/pages/Admin/components/registerMember/registerMember.module.css index 071f79b..d35ed3b 100644 --- a/src/pages/Admin/components/registerMember/registerMember.module.css +++ b/src/pages/Admin/components/registerMember/registerMember.module.css @@ -24,4 +24,8 @@ gap: 10px; align-items: center; justify-content: center; +} + +.modalContent { + text-align: center; } \ No newline at end of file diff --git a/src/pages/Admin/components/registerMember/registerMember.tsx b/src/pages/Admin/components/registerMember/registerMember.tsx index eec292b..79b6ff9 100644 --- a/src/pages/Admin/components/registerMember/registerMember.tsx +++ b/src/pages/Admin/components/registerMember/registerMember.tsx @@ -5,6 +5,7 @@ import Button from "../../../../components/ui/button/button"; import Response from "../../../../interfaces/response.interface"; import ResponseData from "./responseData.inteface"; import adminCreateUser from "../../../../utils/users/adminCreateUser"; +import Modal from "../../../../components/ui/modal/modal"; const RegisterMember: React.FC = () => { @@ -15,14 +16,36 @@ const RegisterMember: React.FC = () => { const [phone, setPhone] = useState(""); const [password, setPassword] = useState(""); + const [open, setOpen] = useState(false); + const [title, setTitle] = useState(""); + const [message, setMessage] = useState(""); + const handleCreate = async (): Promise => { if(name !== "" || lastname !== "" || email !== "" || phone !== "" ) { const response: Response = await adminCreateUser(email, name, lastname, phone); - if(response.status === 200) setPassword(response.data.password); + if(response.status === 200) { + setPassword(response.data.password); + setTitle(`Utilisateur enregistré avec succÚs`); + setMessage(`L'utilisateur ${name} ${lastname} a été enregistré avec le mot de passe: ${response.data.password}`); + setOpen(true); + } else { + setTitle(`Erreur`); + setMessage(`Une erreur est survenue lors de l'enregistrement de ${name} ${lastname}`); + setOpen(true); + } + } else { + setTitle("Erreur"); + setMessage("Un des champs n'a pas été rempli"); + setOpen(true); } + } + const showPassword = (): void => { + setTitle(`Mot de passe de ${name} ${lastname}`); + setMessage(password); + setOpen(true); } return
@@ -36,10 +59,15 @@ const RegisterMember: React.FC = () => { setEmail(e.target.value)} /> setPhone(e.target.value)} /> -

{password !== "" && `Mot de passe: ${password}`}

+ {password !== "" && }
+ + setOpen(false)} title={title}> +

{message}

+
+ } From 461b39718b071f633ff31fbee69ea0bb8fb1c44e Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Fri, 6 Feb 2026 15:30:41 +0100 Subject: [PATCH 08/47] Fix empty check --- .../Admin/components/registerMember/registerMember.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/Admin/components/registerMember/registerMember.tsx b/src/pages/Admin/components/registerMember/registerMember.tsx index 79b6ff9..e233176 100644 --- a/src/pages/Admin/components/registerMember/registerMember.tsx +++ b/src/pages/Admin/components/registerMember/registerMember.tsx @@ -23,7 +23,7 @@ const RegisterMember: React.FC = () => { const handleCreate = async (): Promise => { - if(name !== "" || lastname !== "" || email !== "" || phone !== "" ) { + if(name !== "" && lastname !== "" && email !== "" && phone !== "" ) { const response: Response = await adminCreateUser(email, name, lastname, phone); if(response.status === 200) { setPassword(response.data.password); @@ -56,8 +56,8 @@ const RegisterMember: React.FC = () => {
setName(e.target.value)} /> setLastName(e.target.value)} /> - setEmail(e.target.value)} /> - setPhone(e.target.value)} /> + setEmail(e.target.value)} /> + setPhone(e.target.value)} /> {password !== "" && } From 155d510125f221b183ab60e1942c86f82b83dfde Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 10 Feb 2026 14:14:08 +0100 Subject: [PATCH 09/47] Fix style --- .../registerMember/registerMember.module.css | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/pages/Admin/components/registerMember/registerMember.module.css b/src/pages/Admin/components/registerMember/registerMember.module.css index d35ed3b..b15de9f 100644 --- a/src/pages/Admin/components/registerMember/registerMember.module.css +++ b/src/pages/Admin/components/registerMember/registerMember.module.css @@ -1,13 +1,13 @@ .leftBlock { - width: 40%; + width: 100%; + max-width: 720px; display: flex; flex-direction: column; align-items: center; - height: 100%; min-height: calc(100vh - 260px); box-sizing: border-box; padding: 20px; - margin: 0; + margin: 0 auto; } .registerMemberHeader { @@ -15,17 +15,38 @@ align-items: center; justify-content: center; width: 100%; - margin-bottom: 10px; + margin-bottom: 20px; } .registerForm { + width: 100%; display: flex; flex-direction: column; - gap: 10px; - align-items: center; - justify-content: center; + gap: 16px; + align-items: stretch; +} + +.registerForm input { + width: 100%; + box-sizing: border-box; +} + +.registerForm button { + width: 100%; } .modalContent { text-align: center; -} \ No newline at end of file +} + +@media (min-width: 768px) { + .leftBlock { + width: 70%; + } +} + +@media (min-width: 1024px) { + .leftBlock { + width: 40%; + } +} From 47b1074f928886bc748bd1898043474d3848e0bb Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 10 Feb 2026 14:54:30 +0100 Subject: [PATCH 10/47] Create interfaces --- .../Volunteers/interfaces/task.interface.ts | 16 ++++++++++++++++ .../Volunteers/interfaces/user.interface.ts | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/pages/Volunteers/interfaces/task.interface.ts create mode 100644 src/pages/Volunteers/interfaces/user.interface.ts diff --git a/src/pages/Volunteers/interfaces/task.interface.ts b/src/pages/Volunteers/interfaces/task.interface.ts new file mode 100644 index 0000000..b6206c9 --- /dev/null +++ b/src/pages/Volunteers/interfaces/task.interface.ts @@ -0,0 +1,16 @@ +export default interface Task { + id: number; + name: string; + description: string; + location: string; + start: string; + end: string; + max_participants: number; + events_id: number; + created_at: string; + updated_at: string; + pivot: { + user_id: number; + task_id: number; + } +} \ No newline at end of file diff --git a/src/pages/Volunteers/interfaces/user.interface.ts b/src/pages/Volunteers/interfaces/user.interface.ts new file mode 100644 index 0000000..e22a1cd --- /dev/null +++ b/src/pages/Volunteers/interfaces/user.interface.ts @@ -0,0 +1,16 @@ +import Task from "./task.interface"; + +export default interface User { + id: number; + name: string; + lastname: string; + email: string; + role: string; + isAdmin: boolean; + phone: string | null; + created_at: string; + updated_at: string; + verified_at: string | null; + validate: number; + tasks: Task[]; +} \ No newline at end of file From a164db6d763fce5d529f6d5dcc0030f0f9bc6b3f Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 10 Feb 2026 14:54:50 +0100 Subject: [PATCH 11/47] Convert to tsx + move file --- src/pages/Volunteers/VolunteerCard.jsx | 78 ------------------- .../VolunteersCard/VolunteerCard.tsx | 18 +++++ 2 files changed, 18 insertions(+), 78 deletions(-) delete mode 100644 src/pages/Volunteers/VolunteerCard.jsx create mode 100644 src/pages/Volunteers/components/VolunteersCard/VolunteerCard.tsx diff --git a/src/pages/Volunteers/VolunteerCard.jsx b/src/pages/Volunteers/VolunteerCard.jsx deleted file mode 100644 index f69f6df..0000000 --- a/src/pages/Volunteers/VolunteerCard.jsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, {useState} from "react"; -import styles from "./VolunteersPage.module.css"; -import formatDate from "../../utils/date/formatDate.js"; -import { useNavigate } from "react-router"; -import Button from "../../components/ui/button/button.tsx"; - -export default function VolunteerCard({ user }) { - - const [isExpanded, setIsExpanded] = useState(false); - - const toggleExpand = () => { - setIsExpanded(!isExpanded); - }; - - const navigate = useNavigate(); - - return ( -
setIsExpanded(!isExpanded))}> - -
-

- {user?.name}{" "} - {user?.lastname} -

- - -
- -
- {!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/components/VolunteersCard/VolunteerCard.tsx b/src/pages/Volunteers/components/VolunteersCard/VolunteerCard.tsx new file mode 100644 index 0000000..5fde757 --- /dev/null +++ b/src/pages/Volunteers/components/VolunteersCard/VolunteerCard.tsx @@ -0,0 +1,18 @@ +import styles from "./VolunteersCard.module.css"; +import { useNavigate } from "react-router"; +import Button from "../../../../components/ui/button/button"; +import User from "../../interfaces/user.interface" + + +interface VolunteersCardProps { + user: User; +} + +export default function VolunteerCard({ user }: VolunteersCardProps) { + const navigate = useNavigate(); + + return ( +
+
+ ); +} From 63bf3637619a319c7c18278bb1d93ba006971d15 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 10 Feb 2026 14:55:08 +0100 Subject: [PATCH 12/47] Set direction to row --- src/pages/Volunteers/VolunteersPage.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Volunteers/VolunteersPage.module.css b/src/pages/Volunteers/VolunteersPage.module.css index 6ef455b..c712e56 100644 --- a/src/pages/Volunteers/VolunteersPage.module.css +++ b/src/pages/Volunteers/VolunteersPage.module.css @@ -26,8 +26,8 @@ .usersList { display: flex; - flex-direction: column; align-items: center; + flex-wrap: wrap; gap: 10px; } .profileboard { From 26dcc0fcd3aaf394ad6d7f09ae28a957af453d10 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Tue, 10 Feb 2026 14:58:25 +0100 Subject: [PATCH 13/47] add responsive profile --- src/config/appConfig.js | 4 + src/pages/Profile/ProfilePage.module.css | 249 ++++++++++++----------- src/pages/Profile/ProfilePage.tsx | 4 +- 3 files changed, 139 insertions(+), 118 deletions(-) create mode 100644 src/config/appConfig.js diff --git a/src/config/appConfig.js b/src/config/appConfig.js new file mode 100644 index 0000000..12764ab --- /dev/null +++ b/src/config/appConfig.js @@ -0,0 +1,4 @@ +export const APP_CONFIG = { + contactEmail: "contact@example.com", + contactPhone: "+33 01 02 03 04 05" +} \ No newline at end of file diff --git a/src/pages/Profile/ProfilePage.module.css b/src/pages/Profile/ProfilePage.module.css index 3c5b26b..e354d42 100644 --- a/src/pages/Profile/ProfilePage.module.css +++ b/src/pages/Profile/ProfilePage.module.css @@ -1,158 +1,175 @@ +/* Conteneur principal */ .container { - width: 100%; - padding: 12px; - box-sizing: border-box; + width: 100%; + padding: 12px; + box-sizing: border-box; + margin: 0 auto; } + .profileboard { - width: 100%; - padding: 16px; - border-radius: 20px; - box-sizing: border-box; -} -.settingImage{ - width:100%; - display: flex; - justify-content: center; + width: 100%; + padding: 16px; + border-radius: 20px; + box-sizing: border-box; } .contentWrapper { - display: flex; - flex-direction: column; - align-items: center; - width: 100%; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + gap: 20px; } .profilePictureContainer { - width: 120px; - height: 120px; - margin-bottom: 16px; + width: 120px; + height: 120px; + margin-bottom: 0; + position: relative; + display: flex; + justify-content: center; } .profilePicture { - width: 100%; - height: 100%; - border-radius: 50%; - object-fit: cover; + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; +} + +.editPictureButton { + margin-top: 16px; + padding-top: 12px; + padding-bottom: 12px; + background: rgba(255, 255, 255, 0.95); + border: none; + border-radius: 24px; + cursor: pointer; + font-size: 0.95rem; + font-weight: 600; + color: #e74c3c; + box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15); + transition: all 0.3s ease; + backdrop-filter: blur(5px); + white-space: nowrap; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + width: auto; + min-width: 140px; +} + +.editPictureButton:hover { + background: rgba(255, 255, 255, 1); + transform: translateY(-2px); + box-shadow: 0 5px 12px rgba(0, 0, 0, 0.2); + color: #c0392b; } .description { - width: 100%; - text-align: left; + width: 100%; + text-align: left; } - .headerProfile { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 16px; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; } -.description h2 { - font-size: 1.6rem; - margin-bottom: 10px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.description button { - width: 44px; - height: 44px; - min-width: 44px; - min-height: 44px; - display: flex; - align-items: center; - justify-content: center; - padding: 0; - background: transparent; - border: none; - cursor: pointer; -} - -.modifyIcon { - width: 22px; - height: 22px; - display: block; - margin: 0; +.headerProfile h2 { + font-size: 1.6rem; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } +/* Conteneur des informations principales */ .topDescription { - display: flex; - flex-direction: column; - gap: 12px; - margin-bottom: 20px; + display: flex; + flex-direction: column; + gap: 16px; + width: 100%; + margin-bottom: 20px; } +/* Bloc d'informations */ .infoBlock { - width: 100%; - padding: 12px; + width: 100%; + padding: 12px; + box-sizing: border-box; } -.eventTasks { - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease, opacity 0.2s ease; - opacity: 0; - padding-left: 16px; -} - -.eventTasks.expanded { - max-height: 500px; - opacity: 1; - margin-top: 10px; +/* Conteneur pour les paramÚtres */ +.settingImage { + width: 100%; + display: flex; + justify-content: center; + margin-top: 16px; } +/* Adaptation pour tablette et PC */ @media (min-width: 768px) { - .contentWrapper { - flex-direction: row; - align-items: flex-start; - } + .contentWrapper { + flex-direction: row; + align-items: flex-start; + gap: 24px; + } - .profilePictureContainer { - width: 150px; - height: 150px; - margin-right: 24px; - margin-bottom: 0; - } + .profilePictureContainer { + width: 150px; + height: 150px; + margin-right: 0; + } - .description { - width: calc(100% - 180px); - } + .description { + width: calc(100% - 180px); + } - .topDescription { - flex-direction: row; - justify-content: space-between; - } + .topDescription { + flex-direction: row; + justify-content: space-between; + flex-wrap: wrap; + } - .infoBlock { - width: 48%; - } + .infoBlock { + flex: 1; + min-width: calc(50% - 8px); + max-width: calc(50% - 8px); + } - .description h2 { - font-size: 1.8rem; - } + .editPictureButton { + margin: 16px auto 0 auto; + display: block; + } + + .headerProfile h2 { + font-size: 1.8rem; + } } +/* Adaptation pour grand écran */ @media (min-width: 1024px) { - .container { - max-width: 1200px; - margin: 0 auto; - padding: 24px; - } + .container { + max-width: 1200px; + padding: 24px; + } - .profileboard { - padding: 28px; - } + .profileboard { + padding: 28px; + } - .profilePictureContainer { - width: 180px; - height: 180px; - } + .profilePictureContainer { + width: 180px; + height: 180px; + } - .description { - width: calc(100% - 220px); - } + .description { + width: calc(100% - 220px); + } - .description h2 { - font-size: 2rem; - } + .headerProfile h2 { + font-size: 2rem; + } } diff --git a/src/pages/Profile/ProfilePage.tsx b/src/pages/Profile/ProfilePage.tsx index 0733f40..bbcbb71 100644 --- a/src/pages/Profile/ProfilePage.tsx +++ b/src/pages/Profile/ProfilePage.tsx @@ -67,7 +67,8 @@ function ProfilePage() { alt="Photo de profil" className={styles.profilePicture} /> -
From 5cbfad4ade244659e3985fefd3cddaf221f2de6d Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 10 Feb 2026 15:19:58 +0100 Subject: [PATCH 14/47] Convert to typescript --- src/pages/Volunteers/VolunteersPage.jsx | 53 ------------------- src/pages/Volunteers/VolunteersPage.tsx | 68 +++++++++++++++++++++++++ src/router.js | 2 +- 3 files changed, 69 insertions(+), 54 deletions(-) delete mode 100644 src/pages/Volunteers/VolunteersPage.jsx create mode 100644 src/pages/Volunteers/VolunteersPage.tsx diff --git a/src/pages/Volunteers/VolunteersPage.jsx b/src/pages/Volunteers/VolunteersPage.jsx deleted file mode 100644 index 169275a..0000000 --- a/src/pages/Volunteers/VolunteersPage.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useState, useEffect } from "react"; -import styles from "./VolunteersPage.module.css"; -import VolunteerCard from "./VolunteerCard.jsx"; -import getAllUser from "../../utils/users/getAllUsers.js"; -import ToolBar from "../../components/ToolBar/ToolBar.jsx"; -import filter from "../../utils/filter.js"; -import getUserById from "../../utils/users/getUserById.js"; - -function VolunteerPage() { - const [filters, setFilters] = useState({ - alphabetical: "asc", - yearOrder: "desc", - }); - const [users, setUsers] = useState([]); - - useEffect(() => { - (async () => { - const result = await getAllUser(); - - if (result.status !== 200 || !Array.isArray(result.data)) { - console.error("Erreur lors du chargement des utilisateurs", result); - return; - } - - const usersData = await Promise.all( - result.data.map(async (id) => { - const res = await getUserById(id); - - if (res.status === 200) { - return res.data; - } - console.warn(`Utilisateur ${id} non récupéré`, res.status); - return null; - }) - ); - - setUsers(usersData.filter(Boolean)); - })(); - }, []); - - return ( -
- -
- {filter(users, filters, "user").map((user, index) => ( - - ))} -
-
- ); -} - -export default VolunteerPage; diff --git a/src/pages/Volunteers/VolunteersPage.tsx b/src/pages/Volunteers/VolunteersPage.tsx new file mode 100644 index 0000000..d234833 --- /dev/null +++ b/src/pages/Volunteers/VolunteersPage.tsx @@ -0,0 +1,68 @@ +import { useEffect, useState } from "react"; +import styles from "./VolunteersPage.module.css"; +import VolunteerCard from "./components/VolunteersCard/VolunteerCard"; +import getAllUser from "../../utils/users/getAllUsers"; +import ToolBar from "../../components/ToolBar/ToolBar"; +import filter from "../../utils/filter"; +import getUserById from "../../utils/users/getUserById"; +import User from "./interfaces/user.interface"; + +interface Filters { + alphabetical: "asc" | "desc"; + yearOrder: "asc" | "desc"; +} + +const VolunteerPage: React.FC = () => { + const [filters, setFilters] = useState({ + alphabetical: "asc", + yearOrder: "desc", + }); + + const [users, setUsers] = useState([]); + + useEffect(() => { + const fetchUsers = async (): Promise => { + const result = await getAllUser(); + + if (result.status !== 200 || !Array.isArray(result.data)) { + console.error("Erreur lors du chargement des utilisateurs", result); + return; + } + + const usersData = await Promise.all( + result.data.map(async (id: number): Promise => { + const res = await getUserById(id); + + if (res.status === 200) { + return res.data as User; + } + + console.warn(`Utilisateur ${id} non récupéré`, res.status); + return null; + }) + ); + + setUsers(usersData.filter((user: any): user is User => user !== null)); + }; + + fetchUsers(); + }, []); + + return ( +
+ + +
+ {filter(users, filters, "user").map((user: User) => ( + + ))} +
+
+ ); +}; + +export default VolunteerPage; diff --git a/src/router.js b/src/router.js index b9137b0..9d40c88 100644 --- a/src/router.js +++ b/src/router.js @@ -3,7 +3,7 @@ import Layout from "./layout.jsx"; import HomePage from "./pages/Home/HomePage"; import LoginPage from "./pages/Login/LoginPage"; import RegisterPage from "./pages/Register/RegisterPage.jsx"; -import VolunteersPage from "./pages/Volunteers/VolunteersPage"; +import VolunteersPage from "./pages/Volunteers/VolunteersPage.tsx"; import ProfilePage from "./pages/Profile/ProfilePage"; import EventsPage from "./pages/Events/EventsPage.jsx"; import AdminPage from "./pages/Admin/AdminPage.jsx"; From 8441b9f3b250733b260461535bffb7f8c595d4fa Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 10 Feb 2026 15:20:11 +0100 Subject: [PATCH 15/47] Redesign volunteerCard --- .../Volunteers/VolunteersPage.module.css | 12 +++-- .../VolunteersCard/VolunteerCard.tsx | 13 ++++- .../VolunteersCard/VolunteersCard.module.css | 47 +++++++++++++++++++ 3 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 src/pages/Volunteers/components/VolunteersCard/VolunteersCard.module.css diff --git a/src/pages/Volunteers/VolunteersPage.module.css b/src/pages/Volunteers/VolunteersPage.module.css index c712e56..adcb564 100644 --- a/src/pages/Volunteers/VolunteersPage.module.css +++ b/src/pages/Volunteers/VolunteersPage.module.css @@ -25,11 +25,12 @@ } .usersList { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 10px; + display: grid; + gap: 16px; + width: 100%; + grid-template-columns: 1fr; } + .profileboard { width: 100%; max-width: 380px; @@ -146,6 +147,9 @@ } .usersList { gap: 20px; + grid-template-columns: repeat(3, 1fr); + padding-right: 5em; + padding-left: 5em; } .profileboard { max-width: 700px; diff --git a/src/pages/Volunteers/components/VolunteersCard/VolunteerCard.tsx b/src/pages/Volunteers/components/VolunteersCard/VolunteerCard.tsx index 5fde757..a3d5f01 100644 --- a/src/pages/Volunteers/components/VolunteersCard/VolunteerCard.tsx +++ b/src/pages/Volunteers/components/VolunteersCard/VolunteerCard.tsx @@ -9,10 +9,21 @@ interface VolunteersCardProps { } export default function VolunteerCard({ user }: VolunteersCardProps) { + const navigate = useNavigate(); + const handleNavigate = (): void => { navigate(`/profile/${user.id}`) } + return ( -
+
+
+ profile +

{user.name} {user.lastname}

+
+
+

{user.email}

+ +
); } diff --git a/src/pages/Volunteers/components/VolunteersCard/VolunteersCard.module.css b/src/pages/Volunteers/components/VolunteersCard/VolunteersCard.module.css new file mode 100644 index 0000000..4ed1ccf --- /dev/null +++ b/src/pages/Volunteers/components/VolunteersCard/VolunteersCard.module.css @@ -0,0 +1,47 @@ +.volunteerCard { + display: flex; + flex-direction: column; + gap: 12px; +} + +.header { + display: flex; + align-items: center; + gap: 12px; + min-width: 0; +} + +.header p { + flex: 1; + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.content { + display: flex; + align-items: center; + gap: 12px; + min-width: 0; +} + +.content p { + flex: 1; + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.content button { + flex-shrink: 0; +} + +.profilePicture { + background-color: #1d1d1f; + border-radius: 20px; + width: 40px; + height: 40px; + flex-shrink: 0; +} From d2e969c9156e0b19333fb9cf8a563e846ded4692 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 10 Feb 2026 15:22:38 +0100 Subject: [PATCH 16/47] Add loading --- .../Volunteers/VolunteersPage.module.css | 8 +++ src/pages/Volunteers/VolunteersPage.tsx | 59 ++++++++++++------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/pages/Volunteers/VolunteersPage.module.css b/src/pages/Volunteers/VolunteersPage.module.css index adcb564..4f5c444 100644 --- a/src/pages/Volunteers/VolunteersPage.module.css +++ b/src/pages/Volunteers/VolunteersPage.module.css @@ -141,6 +141,14 @@ } } +.loading { + width: 100%; + text-align: center; + font-size: 1.1rem; + opacity: 1; + color: white; +} + @media screen and (min-width: 1024px) { .container { padding: 20px; diff --git a/src/pages/Volunteers/VolunteersPage.tsx b/src/pages/Volunteers/VolunteersPage.tsx index d234833..869d6a5 100644 --- a/src/pages/Volunteers/VolunteersPage.tsx +++ b/src/pages/Volunteers/VolunteersPage.tsx @@ -19,35 +19,45 @@ const VolunteerPage: React.FC = () => { }); const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); useEffect(() => { const fetchUsers = async (): Promise => { - const result = await getAllUser(); + setLoading(true); - if (result.status !== 200 || !Array.isArray(result.data)) { - console.error("Erreur lors du chargement des utilisateurs", result); - return; + try { + const result = await getAllUser(); + + if (result.status !== 200 || !Array.isArray(result.data)) { + console.error("Erreur lors du chargement des utilisateurs", result); + return; + } + + const usersData = await Promise.all( + result.data.map(async (id: number): Promise => { + const res = await getUserById(id); + + if (res.status === 200) { + return res.data as User; + } + + console.warn(`Utilisateur ${id} non récupéré`, res.status); + return null; + }) + ); + + setUsers(usersData.filter((user): user is User => user !== null)); + } catch (error) { + console.error("Erreur réseau", error); + } finally { + setLoading(false); } - - const usersData = await Promise.all( - result.data.map(async (id: number): Promise => { - const res = await getUserById(id); - - if (res.status === 200) { - return res.data as User; - } - - console.warn(`Utilisateur ${id} non récupéré`, res.status); - return null; - }) - ); - - setUsers(usersData.filter((user: any): user is User => user !== null)); }; fetchUsers(); }, []); + return (
{ />
- {filter(users, filters, "user").map((user: User) => ( - - ))} + {loading ? ( +
Chargement des bénévoles

+ ) : ( + filter(users, filters, "user").map((user: User) => ( + + )) + )}
+
); }; From cea12ae77800f66c9fb6f4bf9bd6b76492702b8b Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 10 Feb 2026 15:29:19 +0100 Subject: [PATCH 17/47] fix style --- .../Volunteers/VolunteersPage.module.css | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/pages/Volunteers/VolunteersPage.module.css b/src/pages/Volunteers/VolunteersPage.module.css index 4f5c444..b953cb5 100644 --- a/src/pages/Volunteers/VolunteersPage.module.css +++ b/src/pages/Volunteers/VolunteersPage.module.css @@ -3,26 +3,6 @@ position: relative; padding: 10px; } -.userName { - display: flex; - gap: 5px; - font-weight: bold; - font-size: 1rem; - overflow: hidden; -} - -.firstName { - white-space: nowrap; - flex-shrink: 0; -} - -.lastName { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex-shrink: 1; - max-width: 100px; -} .usersList { display: grid; @@ -31,6 +11,10 @@ grid-template-columns: 1fr; } +.usersList > * { + min-width: 0; +} + .profileboard { width: 100%; max-width: 380px; From 9b6c779a3683400e03bfcbbbf0795059e4779226 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Wed, 11 Feb 2026 15:23:36 +0100 Subject: [PATCH 18/47] correction vite-env.d.ts --- global.d.ts | 7 ------- src/pages/Profile/ProfilePage.tsx | 3 ++- src/vite-env.d.ts | 6 ++++++ tsconfig.json | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 global.d.ts create mode 100644 src/vite-env.d.ts diff --git a/global.d.ts b/global.d.ts deleted file mode 100644 index 02f8d6c..0000000 --- a/global.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/// - -declare module "*.module.css" { - const classes: { readonly [key: string]: string }; - export default classes; -} -// permet Ă  TS de reconnaĂźtre les fichiers modules.css diff --git a/src/pages/Profile/ProfilePage.tsx b/src/pages/Profile/ProfilePage.tsx index bbcbb71..4c2fc6b 100644 --- a/src/pages/Profile/ProfilePage.tsx +++ b/src/pages/Profile/ProfilePage.tsx @@ -1,5 +1,6 @@ import { useContext, useEffect, useState, useRef } from "react"; -import styles from "../Profile/ProfilePage.module.css"; +// @ts-ignore +import styles from "./ProfilePage.module.css"; import { AuthContext } from "../../contexts/auth/AuthContext"; import formatDate from "../../utils/date/formatDate"; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..b67473e --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,6 @@ +/// + +declare module "*.module.css" { + const classes: { [key: string]: string }; + export default classes; +} diff --git a/tsconfig.json b/tsconfig.json index b3a52d5..3417353 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,6 @@ "allowJs": true, "esModuleInterop": true }, - "include": ["src"], - "exclude": ["**/*.js"] + "include": ["src"] + // "exclude": ["**/*.js"] } From ed9067169336878fee61d2dbe4e112822d92f57b Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Wed, 11 Feb 2026 15:35:05 +0100 Subject: [PATCH 19/47] Create component for searchBar + btn --- src/components/SearchBar/SearchBar.jsx | 100 --------------- src/components/SearchBar/SearchBar.module.css | 4 +- src/components/SearchBar/SearchBar.tsx | 120 ++++++++++++++++++ .../components/searchBtn/SearchBtn.tsx | 38 ++++++ 4 files changed, 161 insertions(+), 101 deletions(-) delete mode 100644 src/components/SearchBar/SearchBar.jsx create mode 100644 src/components/SearchBar/SearchBar.tsx create mode 100644 src/components/ToolBar/components/searchBtn/SearchBtn.tsx diff --git a/src/components/SearchBar/SearchBar.jsx b/src/components/SearchBar/SearchBar.jsx deleted file mode 100644 index a1dded2..0000000 --- a/src/components/SearchBar/SearchBar.jsx +++ /dev/null @@ -1,100 +0,0 @@ -import styles from "./SearchBar.module.css" -import {Link} from "react-router"; -import {useEffect, useState} from "react"; -import searchEvents from "../../utils/events/searchEvents.js"; -import searchUsers from "../../utils/users/searchUsers.js"; -import { useLocation } from "react-router"; - - -function SearchBar() { - const [searchQuery, setSearchQuery] = useState(''); - const [searchResults, setSearchResults] = useState([]); - const location = useLocation(); - - useEffect(() => { - const performSearch = async (query) => { - if (query.trim() === '') { - setSearchResults([]); - return; - } - - let data; - - if (location.pathname.includes("events")) { - data = await searchEvents(query); - } else if (location.pathname.includes("volunteers")) { - data = await searchUsers(query); - } - - - if (data) { - setSearchResults(data); - } else { - setSearchResults([]); - } - }; - - - const handler = setTimeout(() => { - performSearch(searchQuery); - }, 300); - - return () => { - clearTimeout(handler); - }; - }, [searchQuery]); - - return ( -
-
element.preventDefault()}> - Rechercher - setSearchQuery(element.target.value)} - /> -
- -
- {searchResults.length > 0 ? ( -
- ) : null} -
- -
- {searchResults.map((result, i) => ( - (location.pathname.includes("events")) ? ( - -
-
- {result.image == null ? ( - Pas de photo de l'événement - ) : ( - Photo de l'événement - )} -
-

{result.name}

-
- - ) : location.pathname.includes("volunteers") ? ( - -
-
- {result.image == null ? ( - Pas de photo de l'événement - ) : ( - Photo de l'événement - )} -
-

{result.name} {result.lastname}

-
- - ) : null - ))} -
-
- ); -} - -export default SearchBar; \ No newline at end of file diff --git a/src/components/SearchBar/SearchBar.module.css b/src/components/SearchBar/SearchBar.module.css index d35c808..c6a093d 100644 --- a/src/components/SearchBar/SearchBar.module.css +++ b/src/components/SearchBar/SearchBar.module.css @@ -1,6 +1,8 @@ .searchBarContainer{ position: fixed; - top: 4rem; + top: 20vh; + left: 50%; + transform: translateX(-50%); z-index: 100; width: 60%; background: #fff; diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx new file mode 100644 index 0000000..d1ef4c6 --- /dev/null +++ b/src/components/SearchBar/SearchBar.tsx @@ -0,0 +1,120 @@ +import styles from "./SearchBar.module.css"; +import { Link } from "react-router"; +import { useEffect, useState } from "react"; +import searchEvents from "../../utils/events/searchEvents.js"; +import searchUsers from "../../utils/users/searchUsers.js"; +import { useLocation } from "react-router"; + +function SearchBar(): any { + const [searchQuery, setSearchQuery] = useState(""); + const [searchResults, setSearchResults] = useState([]); + const location: any = useLocation(); + + useEffect(() => { + const performSearch = async (query: string): Promise => { + if (query.trim() === "") { + setSearchResults([]); + return; + } + + let data: any; + + if (location.pathname.includes("events")) { + data = await searchEvents(query); + } else if (location.pathname.includes("volunteers")) { + data = await searchUsers(query); + } + + if (data) { + setSearchResults(data); + } else { + setSearchResults([]); + } + }; + + const handler: ReturnType = setTimeout(() => { + performSearch(searchQuery); + }, 300); + + return () => { + clearTimeout(handler); + }; + }, [searchQuery, location.pathname]); + + return ( +
+
) => + element.preventDefault() + } + > + Rechercher + ) => + setSearchQuery(element.target.value) + } + /> +
+ +
+ {searchResults.length > 0 ? ( +
+ ) : null} +
+ +
+ {searchResults.map((result: any, i: number) => + location.pathname.includes("events") ? ( + +
+
+ {result.image == null ? ( + Pas de photo de l'événement + ) : ( + Photo de l'événement + )} +
+

{result.name}

+
+ + ) : location.pathname.includes("volunteers") ? ( + +
+
+ {result.image == null ? ( + Pas de photo de l'événement + ) : ( + Photo de l'événement + )} +
+

+ {result.name} {result.lastname} +

+
+ + ) : null + )} +
+
+ ); +} + +export default SearchBar; diff --git a/src/components/ToolBar/components/searchBtn/SearchBtn.tsx b/src/components/ToolBar/components/searchBtn/SearchBtn.tsx new file mode 100644 index 0000000..c93e24a --- /dev/null +++ b/src/components/ToolBar/components/searchBtn/SearchBtn.tsx @@ -0,0 +1,38 @@ +import styles from "../../ToolBar.module.css" +import Button from "../../../ui/button/button"; +import { useEffect, useState } from "react"; +import SearchBar from "../../../SearchBar/SearchBar"; + +export default function SearchBtn() { + + const [isSearchVisible, setIsSearchVisible] = useState(false); + + useEffect(() => { + if (isSearchVisible) { + document.documentElement.style.overflow = 'hidden'; + } else { + document.documentElement.style.overflow = ''; + } + + return () => { + document.documentElement.style.overflow = ''; + }; + }, [isSearchVisible]); + + return <> + + {isSearchVisible ? ( +
+
setIsSearchVisible(false)}>
+ +
+ ) : null} + +
+ +
+ + +} \ No newline at end of file From 171a202859c96f5cfab10befe18a306b06fccf1a Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Wed, 11 Feb 2026 15:35:18 +0100 Subject: [PATCH 20/47] Use searchBarBtn component --- src/components/ToolBar/ToolBar.jsx | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/src/components/ToolBar/ToolBar.jsx b/src/components/ToolBar/ToolBar.jsx index 874fa70..a4590a3 100644 --- a/src/components/ToolBar/ToolBar.jsx +++ b/src/components/ToolBar/ToolBar.jsx @@ -1,50 +1,27 @@ import styles from "./ToolBar.module.css" import Filter from "../Filter/Filter.jsx"; import {useContext, useEffect, useState} from "react"; -import SearchBar from "../SearchBar/SearchBar.jsx"; +use import SearchBar from "../SearchBar/SearchBar.tsx"; import CreateEventBtn from "../createEventBtn/CreateEventBtn.jsx"; import Button from "../ui/button/button.tsx"; import {AuthContext} from "../../contexts/auth/AuthContext.js"; +import SearchBtn from "./components/searchBtn/SearchBtn.tsx"; function ToolBar({setFilters, filters, showCreate = true}) { const { user } = useContext(AuthContext); const [isFilterVisible, setIsFilterVisible] = useState(false); - const [isSearchVisible, setIsSearchVisible] = useState(false); const toggleFilters = () => { setIsFilterVisible(prev => !prev); }; - - useEffect(() => { - if (isSearchVisible) { - document.documentElement.style.overflow = 'hidden'; - } else { - document.documentElement.style.overflow = ''; - } - - return () => { - document.documentElement.style.overflow = ''; - }; - }, [isSearchVisible]); - - - return (
- {isSearchVisible ? ( -
-
setIsSearchVisible(false)}>
- -
- ) : null}
{showCreate && user.isAdmin ? ( ) : null} - +
-
- -
-
+
); diff --git a/src/components/ToolBar/tools/FilterBtn.tsx b/src/components/ToolBar/tools/FilterBtn.tsx new file mode 100644 index 0000000..1a5d89b --- /dev/null +++ b/src/components/ToolBar/tools/FilterBtn.tsx @@ -0,0 +1,29 @@ +import styles from "../ToolBar.module.css" +import Button from "../../ui/button/button"; +import { useEffect, useState } from "react"; +import SearchBar from "../../SearchBar/SearchBar"; +import Filter from "../../Filter/Filter.jsx"; + + +interface FilterBtnProps { + setFilters: (filter: string) => void; + filters: string; +} + +export default function FilterBtn({ setFilters, filters }: FilterBtnProps) { + + const [isFilterVisible, setIsFilterVisible] = useState(false); + + const toggleFilters = () => { + setIsFilterVisible(prev => !prev); + }; + + return
+ +
+ +
+
+} \ No newline at end of file From 87c0cc02e7488b7017ed837afa846462b2a81cdd Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Wed, 11 Feb 2026 16:47:15 +0100 Subject: [PATCH 24/47] Remove useless imports --- src/components/ToolBar/tools/FilterBtn.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/ToolBar/tools/FilterBtn.tsx b/src/components/ToolBar/tools/FilterBtn.tsx index 1a5d89b..672a3f3 100644 --- a/src/components/ToolBar/tools/FilterBtn.tsx +++ b/src/components/ToolBar/tools/FilterBtn.tsx @@ -1,7 +1,6 @@ import styles from "../ToolBar.module.css" import Button from "../../ui/button/button"; -import { useEffect, useState } from "react"; -import SearchBar from "../../SearchBar/SearchBar"; +import { useState } from "react"; import Filter from "../../Filter/Filter.jsx"; From 451da54fc33fec41aa44ae96e4bb6625efbd1673 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Wed, 11 Feb 2026 17:08:52 +0100 Subject: [PATCH 25/47] Fix errors --- src/components/Filter/Filter.jsx | 56 ---------------- src/components/Filter/Filter.tsx | 78 ++++++++++++++++++++++ src/components/ToolBar/tools/FilterBtn.tsx | 10 ++- 3 files changed, 85 insertions(+), 59 deletions(-) delete mode 100644 src/components/Filter/Filter.jsx create mode 100644 src/components/Filter/Filter.tsx diff --git a/src/components/Filter/Filter.jsx b/src/components/Filter/Filter.jsx deleted file mode 100644 index eb0bf87..0000000 --- a/src/components/Filter/Filter.jsx +++ /dev/null @@ -1,56 +0,0 @@ -import styles from "./Filter.module.css"; -import Button from "../ui/button/button.tsx" - -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 ( -
- - - - - - - - - - - - - - - -
- ) -} - -export default Filter; \ No newline at end of file diff --git a/src/components/Filter/Filter.tsx b/src/components/Filter/Filter.tsx new file mode 100644 index 0000000..d917928 --- /dev/null +++ b/src/components/Filter/Filter.tsx @@ -0,0 +1,78 @@ +import styles from "./Filter.module.css"; +import Button from "../ui/button/button"; +import React from "react"; + +type FiltersType = { + alphabetical: "asc" | "desc" | null; + yearOrder: "asc" | "desc" | null; + [key: string]: any; +}; + +type FilterProps = { + isFilterVisible: boolean; + filters: FiltersType; + setFilters: React.Dispatch>; +}; + +function Filter({ isFilterVisible, filters, setFilters }: FilterProps): any { + + const setAlphabeticalOrder = (order: "asc" | "desc"): void => { + setFilters((prev) => ({ + ...prev, + alphabetical: prev.alphabetical === order ? null : order + })); + }; + + const setYearOrder = (order: "asc" | "desc"): void => { + setFilters((prev) => ({ + ...prev, + yearOrder: prev.yearOrder === order ? null : order + })); + }; + + return ( +
+ + + + + + + + + + + + + + + +
+ ); +} + +export default Filter; diff --git a/src/components/ToolBar/tools/FilterBtn.tsx b/src/components/ToolBar/tools/FilterBtn.tsx index 672a3f3..0f87ae9 100644 --- a/src/components/ToolBar/tools/FilterBtn.tsx +++ b/src/components/ToolBar/tools/FilterBtn.tsx @@ -1,12 +1,16 @@ import styles from "../ToolBar.module.css" import Button from "../../ui/button/button"; import { useState } from "react"; -import Filter from "../../Filter/Filter.jsx"; +import Filter from "../../Filter/Filter"; +interface Filters { + alphabetical: "asc" | "desc"; + yearOrder: "asc" | "desc"; +} interface FilterBtnProps { - setFilters: (filter: string) => void; - filters: string; + setFilters: any + filters: Filters } export default function FilterBtn({ setFilters, filters }: FilterBtnProps) { From fa6336fffcaa6e863fc8d0823d1c37ef6b4ade9f Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Wed, 11 Feb 2026 17:17:50 +0100 Subject: [PATCH 26/47] Move createEventBtn --- src/components/ToolBar/ToolBar.jsx | 3 +-- .../{ => ToolBar/tools}/createEventBtn/CreateEventBtn.jsx | 8 ++++---- .../tools}/createEventBtn/createEventBtn.module.css | 0 3 files changed, 5 insertions(+), 6 deletions(-) rename src/components/{ => ToolBar/tools}/createEventBtn/CreateEventBtn.jsx (91%) rename src/components/{ => ToolBar/tools}/createEventBtn/createEventBtn.module.css (100%) diff --git a/src/components/ToolBar/ToolBar.jsx b/src/components/ToolBar/ToolBar.jsx index 351ceab..8bfb46b 100644 --- a/src/components/ToolBar/ToolBar.jsx +++ b/src/components/ToolBar/ToolBar.jsx @@ -1,6 +1,6 @@ import styles from "./ToolBar.module.css" import { useContext } from "react"; -import CreateEventBtn from "../createEventBtn/CreateEventBtn.jsx"; +import CreateEventBtn from "./tools/createEventBtn/CreateEventBtn.jsx"; import { AuthContext } from "../../contexts/auth/AuthContext.js"; import SearchBtn from "./tools/SearchBtn.tsx"; import FilterBtn from "./tools/FilterBtn.tsx"; @@ -14,7 +14,6 @@ function ToolBar({setFilters, filters, showCreate = true}) { {showCreate && user.isAdmin ? ( ) : null} -
diff --git a/src/components/createEventBtn/CreateEventBtn.jsx b/src/components/ToolBar/tools/createEventBtn/CreateEventBtn.jsx similarity index 91% rename from src/components/createEventBtn/CreateEventBtn.jsx rename to src/components/ToolBar/tools/createEventBtn/CreateEventBtn.jsx index 019b836..e7e886f 100644 --- a/src/components/createEventBtn/CreateEventBtn.jsx +++ b/src/components/ToolBar/tools/createEventBtn/CreateEventBtn.jsx @@ -1,9 +1,9 @@ import styles from "./createEventBtn.module.css" import { useState, useContext } from "react"; -import Modal from "../ui/modal/modal.tsx"; -import Button from "../ui/button/button.tsx"; -import TextInput from "../ui/input/input.tsx"; -import { EventContext } from "../../contexts/events/EventContext.js"; +import Modal from "../../../ui/modal/modal.tsx"; +import Button from "../../../ui/button/button.tsx"; +import TextInput from "../../../ui/input/input.tsx"; +import { EventContext } from "../../../../contexts/events/EventContext.js"; export default function CreateEventBtn() { diff --git a/src/components/createEventBtn/createEventBtn.module.css b/src/components/ToolBar/tools/createEventBtn/createEventBtn.module.css similarity index 100% rename from src/components/createEventBtn/createEventBtn.module.css rename to src/components/ToolBar/tools/createEventBtn/createEventBtn.module.css From eb9a0f58eff19d3eff296f67f51d33d2c81eeb6b Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Fri, 13 Feb 2026 14:20:07 +0100 Subject: [PATCH 27/47] Move isAdmin condition --- src/components/ToolBar/ToolBar.jsx | 7 +------ .../ToolBar/tools/createEventBtn/CreateEventBtn.jsx | 4 ++++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/ToolBar/ToolBar.jsx b/src/components/ToolBar/ToolBar.jsx index 8bfb46b..4461da7 100644 --- a/src/components/ToolBar/ToolBar.jsx +++ b/src/components/ToolBar/ToolBar.jsx @@ -1,19 +1,14 @@ import styles from "./ToolBar.module.css" -import { useContext } from "react"; import CreateEventBtn from "./tools/createEventBtn/CreateEventBtn.jsx"; -import { AuthContext } from "../../contexts/auth/AuthContext.js"; import SearchBtn from "./tools/SearchBtn.tsx"; import FilterBtn from "./tools/FilterBtn.tsx"; function ToolBar({setFilters, filters, showCreate = true}) { - const { user } = useContext(AuthContext); return (
- {showCreate && user.isAdmin ? ( - - ) : null} + {showCreate && }
diff --git a/src/components/ToolBar/tools/createEventBtn/CreateEventBtn.jsx b/src/components/ToolBar/tools/createEventBtn/CreateEventBtn.jsx index e7e886f..11b96d6 100644 --- a/src/components/ToolBar/tools/createEventBtn/CreateEventBtn.jsx +++ b/src/components/ToolBar/tools/createEventBtn/CreateEventBtn.jsx @@ -4,11 +4,13 @@ import Modal from "../../../ui/modal/modal.tsx"; import Button from "../../../ui/button/button.tsx"; import TextInput from "../../../ui/input/input.tsx"; import { EventContext } from "../../../../contexts/events/EventContext.js"; +import {AuthContext} from "../../../../contexts/auth/AuthContext.ts"; export default function CreateEventBtn() { const { addEvent } = useContext(EventContext); + const { user } = useContext(AuthContext); const [isOpen, setIsOpen] = useState(false); const [name, setName] = useState(""); @@ -24,6 +26,8 @@ export default function CreateEventBtn() { setIsOpen(false); } + if(!user.isAdmin) return null; + return <> From fe1948e632c24678093969920bb233b6123a0fd6 Mon Sep 17 00:00:00 2001 From: Giovanni-Josserand Date: Sun, 22 Feb 2026 16:20:25 +0100 Subject: [PATCH 28/47] create pendingMembers context --- .../pendingMembers/PendingMembersContext.js | 3 ++ .../pendingMembers/PendingMembersProvider.jsx | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/contexts/pendingMembers/PendingMembersContext.js create mode 100644 src/contexts/pendingMembers/PendingMembersProvider.jsx diff --git a/src/contexts/pendingMembers/PendingMembersContext.js b/src/contexts/pendingMembers/PendingMembersContext.js new file mode 100644 index 0000000..1941409 --- /dev/null +++ b/src/contexts/pendingMembers/PendingMembersContext.js @@ -0,0 +1,3 @@ +import { createContext } from "react"; + +export const PendingMembersContext = createContext(null); \ No newline at end of file diff --git a/src/contexts/pendingMembers/PendingMembersProvider.jsx b/src/contexts/pendingMembers/PendingMembersProvider.jsx new file mode 100644 index 0000000..48ff64d --- /dev/null +++ b/src/contexts/pendingMembers/PendingMembersProvider.jsx @@ -0,0 +1,37 @@ +import { useState } from "react"; +import { PendingMembersContext } from "./PendingMembersContext"; + +import getUserToValidate from "../../utils/users/getUserToValidate"; +import getUserById from "../../utils/users/getUserById"; + +export function PendingMembersProvider({ children }) { + + const [pendingMembers, setPendingMembers] = useState([]); + + const updatePendingMembers = async () => { + const result = await getUserToValidate(); + const userIds = result.data; + + if (!userIds || userIds.length === 0) { + setPendingMembers([]); + return; + } + + const users = await Promise.all( + userIds.map(id => getUserById(id)) + ); + + setPendingMembers(users.map(u => u.data)); + }; + + return ( + + {children} + + ); +} \ No newline at end of file From c95f8cfae7c49cc2ec1b59041b7b488bccb3cab8 Mon Sep 17 00:00:00 2001 From: Giovanni-Josserand Date: Sun, 22 Feb 2026 16:20:31 +0100 Subject: [PATCH 29/47] use pendingMembers context --- src/components/Header/Header.jsx | 6 ++-- .../pendingMembers/PendingMembers.jsx | 30 +++++-------------- .../echo/listeners/userCreatedListener.js | 6 +++- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx index 829c26e..314943b 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -10,6 +10,7 @@ import { userNotificationsListener } from "../../utils/echo/listeners/userNotifi import NotificationCard from "../NotificationCard/NotificationCard.jsx"; import { EventContext } from "../../contexts/events/EventContext.js"; import {AuthContext} from "../../contexts/auth/AuthContext.js"; +import { PendingMembersContext } from "../../contexts/pendingMembers/PendingMembersContext"; function Header() { @@ -20,6 +21,7 @@ function Header() { const [mobileMenu, setMobileMenu] = useState(false); const [notifications, setNotifications] = useState([]); const notificationRef = useRef(null); + const { updatePendingMembers } = useContext(PendingMembersContext); useEffect(() => { @@ -41,7 +43,7 @@ function Header() { echoInstance = echo; const activeChannels = [ - userCreatedListener(echo, user, setNotifications, setunreadNotification), + userCreatedListener(echo, user, setNotifications, setunreadNotification, updatePendingMembers), userNotificationsListener(echo, user, setNotifications, setunreadNotification), ]; channelsToLeave = activeChannels.filter(name => name !== null); @@ -86,7 +88,7 @@ function Header() { } } } catch (err) { - console.error('Erreur suppression notification :', err); + console.error('Erreur pour ouvrir/fermer le menu de notifications :', err); } } diff --git a/src/pages/Admin/components/pendingMembers/PendingMembers.jsx b/src/pages/Admin/components/pendingMembers/PendingMembers.jsx index 19b1a1a..f8280f6 100644 --- a/src/pages/Admin/components/pendingMembers/PendingMembers.jsx +++ b/src/pages/Admin/components/pendingMembers/PendingMembers.jsx @@ -1,11 +1,10 @@ -import React, {useState, useEffect} from "react"; +import React, {useState, useEffect, useContext} from "react"; import Button from "../../../../components/ui/button/button.tsx"; import styles from "./PendingMembers.module.css"; import Modal from "../../../../components/ui/modal/modal.tsx"; -import getUserToValidate from "../../../../utils/users/getUserToValidate.js"; -import getUserById from "../../../../utils/users/getUserById.js"; import validateUser from "../../../../utils/users/validateUser.js"; import deleteOtherUser from "../../../../utils/users/deleteOtherUser.js"; +import { PendingMembersContext } from "../../../../contexts/pendingMembers/PendingMembersContext"; function PendingMembers() { @@ -13,23 +12,10 @@ function PendingMembers() { const [open, setOpen] = useState(false); const [message, setMessage] = useState(""); const [title, setTitle] = useState(""); - - const [pendingMembers, setPendingMembers] = useState([]); - - const fetchUsers = async () => { - - const result = await getUserToValidate(); - const userIds = result.data; - - if (userIds && userIds.length > 0) { - const users = await Promise.all(userIds.map(id => getUserById(id))); - const usersData = users.map(u => u.data); - setPendingMembers(usersData); - } else setPendingMembers([]); - } + const { pendingMembers, updatePendingMembers } = useContext(PendingMembersContext); useEffect(() => { - fetchUsers(); + updatePendingMembers(); }, []); const handleValidate = async (id, name) => { @@ -38,14 +24,12 @@ function PendingMembers() { if(result.status === 200) { setTitle("Utilisateur accepté avec succÚs") setMessage(`L'utilisateur ${name} a été accepté avec succÚs`) - setOpen(true); } else { setTitle("Erreur") setMessage(`Une erreur est survenue durant l'acceptation de l'utilisateur ${name}`) - setOpen(true); } - - fetchUsers(); + setOpen(true); + updatePendingMembers(); } const handleRefuse = async (id, name) => { @@ -61,7 +45,7 @@ function PendingMembers() { setOpen(true); } - fetchUsers(); + updatePendingMembers(); } return ( diff --git a/src/utils/echo/listeners/userCreatedListener.js b/src/utils/echo/listeners/userCreatedListener.js index e2bb017..ef550b7 100644 --- a/src/utils/echo/listeners/userCreatedListener.js +++ b/src/utils/echo/listeners/userCreatedListener.js @@ -1,4 +1,4 @@ -export const userCreatedListener = (echo, userData, setNotifications, setunreadNotification) => { +export const userCreatedListener = (echo, userData, setNotifications, setunreadNotification, updatePendingMembers) => { if (userData.isAdmin) { const channelName = "users.registration"; echo.private(channelName) @@ -11,6 +11,10 @@ export const userCreatedListener = (echo, userData, setNotifications, setunreadN }; setNotifications(prev => [newNotification, ...prev]); setunreadNotification(true); + + if (typeof updatePendingMembers === "function") { + updatePendingMembers(); + } }); return channelName; } From 17ca3a9e954ce3317c2e1e34c743afd683870597 Mon Sep 17 00:00:00 2001 From: Giovanni-Josserand Date: Sun, 22 Feb 2026 16:23:16 +0100 Subject: [PATCH 30/47] use pendingMembers context --- src/main.jsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index bd63d05..b742534 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -6,16 +6,19 @@ import router from './router'; import {AuthProvider} from "./contexts/auth/authProvider.jsx"; import {EventProvider} from "./contexts/events/EventProvider.jsx"; import {EventDetailProvider} from "./contexts/eventDetail/eventDetailProvider.jsx"; +import {PendingMembersProvider} from "./contexts/pendingMembers/PendingMembersProvider.jsx"; createRoot(document.getElementById('root')).render( - - - - - + + + + + + + , ) From 258170d4d823f40abbc2312e8d4b9a99f81233e1 Mon Sep 17 00:00:00 2001 From: Giovanni-Josserand Date: Sun, 22 Feb 2026 16:31:24 +0100 Subject: [PATCH 31/47] use pendingMembers context --- src/utils/echo/listeners/userCreatedListener.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/utils/echo/listeners/userCreatedListener.js b/src/utils/echo/listeners/userCreatedListener.js index ef550b7..a4a36c4 100644 --- a/src/utils/echo/listeners/userCreatedListener.js +++ b/src/utils/echo/listeners/userCreatedListener.js @@ -11,10 +11,7 @@ export const userCreatedListener = (echo, userData, setNotifications, setunreadN }; setNotifications(prev => [newNotification, ...prev]); setunreadNotification(true); - - if (typeof updatePendingMembers === "function") { - updatePendingMembers(); - } + updatePendingMembers(); }); return channelName; } From 1f6fb932b6fa29dc37b4df28bc2edcd08234efad Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 24 Feb 2026 14:47:30 +0100 Subject: [PATCH 32/47] Create Loading component --- src/components/ui/loading/loading.module.css | 25 ++++++++++++++++++++ src/components/ui/loading/loading.tsx | 12 ++++++++++ src/stories/Loading.stories.jsx | 13 ++++++++++ 3 files changed, 50 insertions(+) create mode 100644 src/components/ui/loading/loading.module.css create mode 100644 src/components/ui/loading/loading.tsx create mode 100644 src/stories/Loading.stories.jsx diff --git a/src/components/ui/loading/loading.module.css b/src/components/ui/loading/loading.module.css new file mode 100644 index 0000000..b12ba9f --- /dev/null +++ b/src/components/ui/loading/loading.module.css @@ -0,0 +1,25 @@ +.wrapper { + display: flex; + justify-content: center; + align-items: center; + + width: 100%; + min-height: 200px; +} + +.spinner { + width: 48px; + height: 48px; + + border: 5px solid #e0e0e0; + border-top: 5px solid #3498db; + + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/src/components/ui/loading/loading.tsx b/src/components/ui/loading/loading.tsx new file mode 100644 index 0000000..536cc99 --- /dev/null +++ b/src/components/ui/loading/loading.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import styles from "./loading.module.css"; + +const Loading: React.FC = () => { + return ( +
+
+
+ ); +}; + +export default Loading; \ No newline at end of file diff --git a/src/stories/Loading.stories.jsx b/src/stories/Loading.stories.jsx new file mode 100644 index 0000000..6986aa8 --- /dev/null +++ b/src/stories/Loading.stories.jsx @@ -0,0 +1,13 @@ +import Loading from "../components/ui/loading/loading.tsx"; + + +export default { + title: 'UI/Loading', + component: Loading, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +}; + +export const Primary = {}; \ No newline at end of file From ffa6cd649443966a7dec2d909cdc1165fb5f22c9 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 24 Feb 2026 14:47:36 +0100 Subject: [PATCH 33/47] Use Loading component --- src/pages/Volunteers/VolunteersPage.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/pages/Volunteers/VolunteersPage.tsx b/src/pages/Volunteers/VolunteersPage.tsx index 869d6a5..ae43fbc 100644 --- a/src/pages/Volunteers/VolunteersPage.tsx +++ b/src/pages/Volunteers/VolunteersPage.tsx @@ -6,6 +6,7 @@ import ToolBar from "../../components/ToolBar/ToolBar"; import filter from "../../utils/filter"; import getUserById from "../../utils/users/getUserById"; import User from "./interfaces/user.interface"; +import Loading from "../../components/ui/loading/loading"; interface Filters { alphabetical: "asc" | "desc"; @@ -57,6 +58,7 @@ const VolunteerPage: React.FC = () => { fetchUsers(); }, []); + if(loading) return ; return (
@@ -67,13 +69,9 @@ const VolunteerPage: React.FC = () => { />
- {loading ? ( -
Chargement des bénévoles

- ) : ( - filter(users, filters, "user").map((user: User) => ( + {filter(users, filters, "user").map((user: User) => ( - )) - )} + ))}
From ad770c105549493d1980c99459a5a39bec47d946 Mon Sep 17 00:00:00 2001 From: Giovanni-Josserand Date: Tue, 24 Feb 2026 15:40:35 +0100 Subject: [PATCH 34/47] fix reverb from cross origin --- src/utils/echo/initEcho.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/utils/echo/initEcho.js b/src/utils/echo/initEcho.js index 9d27b37..4e59d4b 100644 --- a/src/utils/echo/initEcho.js +++ b/src/utils/echo/initEcho.js @@ -14,8 +14,31 @@ export default function initEcho() { encrypted: import.meta.env.VITE_ENCRYPTED === 'true', cluster: import.meta.env.VITE_CLUSTER, enabledTransports: ['ws', 'wss'], + authorizer: (channel) => { + return { + authorize: (socketId, callback) => { + fetch(`${import.meta.env.VITE_API_URL}/broadcasting/auth`, { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest', + }, + body: JSON.stringify({ + socket_id: socketId, + channel_name: channel.name, + }), + }) + .then(res => res.json()) + .then(data => callback(null, data)) + .catch(err => callback(err, null)); + }, + }; + }, }); + + window.Echo = echoInstance; return echoInstance; } From bd415209a524ea598d34104a8f3df57102dc7d39 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 24 Feb 2026 16:34:23 +0100 Subject: [PATCH 35/47] Merge VolunteerProfilePage into ProfilePage --- src/pages/Profile/ProfilePage.tsx | 251 +++++++++++++----------------- src/router.js | 10 +- 2 files changed, 114 insertions(+), 147 deletions(-) diff --git a/src/pages/Profile/ProfilePage.tsx b/src/pages/Profile/ProfilePage.tsx index 84c2211..fccbfc1 100644 --- a/src/pages/Profile/ProfilePage.tsx +++ b/src/pages/Profile/ProfilePage.tsx @@ -1,165 +1,132 @@ -import { useContext, useEffect } from "react"; +import {useContext, useEffect, useState} from "react"; import styles from "./ProfilePage.module.css"; - -import { AuthContext } from "../../contexts/auth/AuthContext"; +import {AuthContext} from "../../contexts/auth/AuthContext"; import formatDate from "../../utils/date/formatDate"; +import Task from "../../components/Task/Task"; +import SettingsModal from "./components/SettingsModal/SettingsModal"; +import { useParams } from "react-router"; +import { useNavigate } from "react-router"; +import getUserById from "../../utils/users/getUserById.js"; -//import Task from "../../components/Task/Task"; -//import SettingsModal from "./components/SettingsModal/SettingsModal"; interface TaskType { - id: number; - title?: string; - completed?: boolean; - [key: string]: unknown; + id: number; + title?: string; + completed?: boolean; + + [key: string]: unknown; } interface User { - name: string; - lastname: string; - role: string; - email: string; - phone: string | null; - created_at: string; - tasks: TaskType[]; + id: number; + name: string; + lastname: string; + role: string; + email: string; + phone: string | null; + created_at: string; + tasks: TaskType[]; } interface AuthContextType { - user: User | null; - update: () => void; + user: User; + update: () => void; } - -function ProfilePage(){ - const { user, update } = useContext(AuthContext) as AuthContextType; - - useEffect(() => { - update(); - }, []); - - return ( -
-
-
-
- Photo de profil -
- -
-
-

- {user?.name} {user?.lastname} -

-
- -
-
-

- Role : {user?.role} -

-

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

-
- -
-

- Mail : {user?.email} -

-

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

-
- -
- {/* */} -
-
- -

TĂąches :

- - {/* {user && user.tasks.length > 0 ? ( - user.tasks.map((task) => ( - - )) - ) : ( -

Aucune tĂąche pour le moment.

- )}*/} -
-
-
-
- ); -} - -export default ProfilePage; - - -/*import React, { useContext, useEffect } from "react"; -import styles from "./ProfilePage.module.css"; -import { AuthContext } from "../../contexts/auth/AuthContext.js"; -import formatDate from "../../utils/date/formatDate.js"; -import Task from "../../components/Task/Task"; -import SettingsModal from "./components/SettingsModal/SettingsModal.jsx"; - function ProfilePage() { - const { user, update } = useContext(AuthContext); + const navigate = useNavigate(); + const { id } = useParams(); + const { user, update } = useContext(AuthContext) as AuthContextType; + const [profileUser, setProfileUser] = useState(null); + + const isOwnProfile = !id || user.id.toString() === id; useEffect(() => { - update(); - }, []) + update(); + (async () => { - return ( -
-
-
-
- Photo de profil -
-
-
-

{user?.name} {user?.lastname}

+ if(!isOwnProfile) { + console.log(id); + const res = await getUserById(id); + + if (res.status === 404) { + navigate("/404"); + return; + } + + if (res.status !== 200) { + console.error("Erreur lors du chargement du profil", res); + return; + } + + setProfileUser(res.data); + } else setProfileUser(user) + }) () + }, []); + + return ( +
+
+
+
+ Photo de profil +
+ +
+
+

+ {profileUser?.name} {profileUser?.lastname} +

+
+ +
+
+

+ Role : {profileUser?.role} +

+

+ Membre depuis :{" "} + {profileUser && formatDate(profileUser.created_at)} +

+
+ +
+

+ Mail : {profileUser?.email} +

+

+ Téléphone :{" "} + {profileUser?.phone ?? "Pas de numéro enregistré"} +

+
+ + {isOwnProfile && ( +
+ +
+ )} +
+ +

TĂąches :

+ + {profileUser && profileUser.tasks.length > 0 ? ( + profileUser.tasks.map((task) => ( + + )) + ) : ( +

Aucune tĂąche pour le moment.

+ )} +
+
-
-
-

Role : {user?.role}

-

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

-
-
-

Mail : {user?.email}

-

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

-
-
- -
-
- -

TĂąches :

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

Aucune tĂąche pour le moment.

- )} - -
-
-
- ); + ); } -export default ProfilePage;*/ \ No newline at end of file +export default ProfilePage; \ No newline at end of file diff --git a/src/router.js b/src/router.js index 9d40c88..f7ee98e 100644 --- a/src/router.js +++ b/src/router.js @@ -47,10 +47,6 @@ const router = createBrowserRouter([ path: "/admin", Component:AdminPage, }, - { - path:"/profile", - Component:ProfilePage, - }, { path: "/events", Component: EventsPage, @@ -59,9 +55,13 @@ const router = createBrowserRouter([ path: "/volunteers", Component: VolunteersPage, }, + { + path:"/profile", + Component:ProfilePage, + }, { path: "/profile/:id", - Component: VolunteerProfilePage, + Component: ProfilePage, }, { path: "/events/:id", From 4ee690a6d5930c9ac98ef8a7a2690785bf630bb3 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 24 Feb 2026 16:34:34 +0100 Subject: [PATCH 36/47] Fix display --- src/pages/Profile/ProfilePage.module.css | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/pages/Profile/ProfilePage.module.css b/src/pages/Profile/ProfilePage.module.css index 3c5b26b..a03cb05 100644 --- a/src/pages/Profile/ProfilePage.module.css +++ b/src/pages/Profile/ProfilePage.module.css @@ -3,17 +3,20 @@ padding: 12px; box-sizing: border-box; } + .profileboard { width: 100%; padding: 16px; border-radius: 20px; box-sizing: border-box; } -.settingImage{ - width:100%; - display: flex; - justify-content: center; + +.settingImage { + display: flex; + justify-content: center; + align-items: center; } + .contentWrapper { display: flex; flex-direction: column; @@ -49,9 +52,9 @@ .description h2 { font-size: 1.6rem; margin-bottom: 10px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .description button { From 8871d169df24b1fd0d029b876c1c0a486453bd29 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 24 Feb 2026 16:46:38 +0100 Subject: [PATCH 37/47] Show ManageMember component --- src/pages/Profile/ProfilePage.tsx | 5 + .../components/manageMember/ManageMember.jsx | 0 .../deactivateMember/deactivateMemberBtn.jsx | 0 .../manageMember/manageMember.module.css | 0 .../manageMember/modifyrole/ModifyRole.jsx | 0 .../VolunteerProfile/VolunteerProfilePage.jsx | 84 --------- .../VolunteerProfilePage.module.css | 163 ------------------ 7 files changed, 5 insertions(+), 247 deletions(-) rename src/pages/{VolunteerProfile => Profile}/components/manageMember/ManageMember.jsx (100%) rename src/pages/{VolunteerProfile => Profile}/components/manageMember/deactivateMember/deactivateMemberBtn.jsx (100%) rename src/pages/{VolunteerProfile => Profile}/components/manageMember/manageMember.module.css (100%) rename src/pages/{VolunteerProfile => Profile}/components/manageMember/modifyrole/ModifyRole.jsx (100%) delete mode 100644 src/pages/VolunteerProfile/VolunteerProfilePage.jsx delete mode 100644 src/pages/VolunteerProfile/VolunteerProfilePage.module.css diff --git a/src/pages/Profile/ProfilePage.tsx b/src/pages/Profile/ProfilePage.tsx index fccbfc1..f0037f9 100644 --- a/src/pages/Profile/ProfilePage.tsx +++ b/src/pages/Profile/ProfilePage.tsx @@ -7,6 +7,7 @@ import SettingsModal from "./components/SettingsModal/SettingsModal"; import { useParams } from "react-router"; import { useNavigate } from "react-router"; import getUserById from "../../utils/users/getUserById.js"; +import ManageMember from "./components/manageMember/ManageMember.jsx"; interface TaskType { @@ -25,6 +26,7 @@ interface User { email: string; phone: string | null; created_at: string; + isAdmin: boolean; tasks: TaskType[]; } @@ -83,6 +85,9 @@ function ProfilePage() {

{profileUser?.name} {profileUser?.lastname}

+ {(user?.isAdmin && profileUser && !isOwnProfile) && ( + + )}
diff --git a/src/pages/VolunteerProfile/components/manageMember/ManageMember.jsx b/src/pages/Profile/components/manageMember/ManageMember.jsx similarity index 100% rename from src/pages/VolunteerProfile/components/manageMember/ManageMember.jsx rename to src/pages/Profile/components/manageMember/ManageMember.jsx diff --git a/src/pages/VolunteerProfile/components/manageMember/deactivateMember/deactivateMemberBtn.jsx b/src/pages/Profile/components/manageMember/deactivateMember/deactivateMemberBtn.jsx similarity index 100% rename from src/pages/VolunteerProfile/components/manageMember/deactivateMember/deactivateMemberBtn.jsx rename to src/pages/Profile/components/manageMember/deactivateMember/deactivateMemberBtn.jsx diff --git a/src/pages/VolunteerProfile/components/manageMember/manageMember.module.css b/src/pages/Profile/components/manageMember/manageMember.module.css similarity index 100% rename from src/pages/VolunteerProfile/components/manageMember/manageMember.module.css rename to src/pages/Profile/components/manageMember/manageMember.module.css diff --git a/src/pages/VolunteerProfile/components/manageMember/modifyrole/ModifyRole.jsx b/src/pages/Profile/components/manageMember/modifyrole/ModifyRole.jsx similarity index 100% rename from src/pages/VolunteerProfile/components/manageMember/modifyrole/ModifyRole.jsx rename to src/pages/Profile/components/manageMember/modifyrole/ModifyRole.jsx diff --git a/src/pages/VolunteerProfile/VolunteerProfilePage.jsx b/src/pages/VolunteerProfile/VolunteerProfilePage.jsx deleted file mode 100644 index 763b7f7..0000000 --- a/src/pages/VolunteerProfile/VolunteerProfilePage.jsx +++ /dev/null @@ -1,84 +0,0 @@ -import styles from "./VolunteerProfilePage.module.css"; -import { useParams, useNavigate } from "react-router"; -import { useEffect, useState, useContext } from "react"; -import getUserById from "../../utils/users/getUserById.js"; -import formatDate from "../../utils/date/formatDate.js"; -import Task from "../../components/Task/Task.jsx"; -import ManageMember from "./components/manageMember/ManageMember.jsx"; -import { AuthContext } from "../../contexts/auth/AuthContext.js"; - -export default function VolunteerProfilePage() { - const { id } = useParams(); - const navigate = useNavigate(); - const { user: currentUser } = useContext(AuthContext); - - const [user, setUser] = useState(null); - - useEffect(() => { - (async () => { - const res = await getUserById(id); - - if (res.status === 404) { - navigate("/404"); - return; - } - - if (res.status !== 200) { - console.error("Erreur lors du chargement du profil", res); - return; - } - - setUser(res.data); - })(); - }, [id, navigate]); - - if (!user) return null; - - return ( -
-
-
-
- Photo de profil -
- -
-
-

{user.name} {user.lastname}

- {currentUser?.isAdmin && ( - - )} -
- -
-
-

Role : {user.role}

-

Membre depuis : {formatDate(user.created_at)}

-
-
-

Mail : {user.email}

-

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

-
-
- -

TĂąches :

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

Aucune tĂąche pour le moment.

- )} -
-
-
-
- ); -} diff --git a/src/pages/VolunteerProfile/VolunteerProfilePage.module.css b/src/pages/VolunteerProfile/VolunteerProfilePage.module.css deleted file mode 100644 index 834af59..0000000 --- a/src/pages/VolunteerProfile/VolunteerProfilePage.module.css +++ /dev/null @@ -1,163 +0,0 @@ -.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; -} - -@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; - } -} From e30dd780ba3d6f6d7963b84f892e7aa7a920f1fe Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 24 Feb 2026 16:59:25 +0100 Subject: [PATCH 38/47] Remove VolunteerProfilePage import --- src/router.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/router.js b/src/router.js index f7ee98e..662f8f8 100644 --- a/src/router.js +++ b/src/router.js @@ -8,7 +8,6 @@ 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"; import eventDetail from "./pages/eventDetail/eventDetail.jsx"; import legalNotices from "./pages/LegalNotices/LegalNotices.jsx"; From c012a2a063d1133d667f1a147299ee499327ba2e Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 24 Feb 2026 16:59:34 +0100 Subject: [PATCH 39/47] Convert to TS --- .../SettingsModal/SettingsModal.jsx | 86 --------- .../SettingsModal/SettingsModal.tsx | 167 ++++++++++++++++++ 2 files changed, 167 insertions(+), 86 deletions(-) delete mode 100644 src/pages/Profile/components/SettingsModal/SettingsModal.jsx create mode 100644 src/pages/Profile/components/SettingsModal/SettingsModal.tsx diff --git a/src/pages/Profile/components/SettingsModal/SettingsModal.jsx b/src/pages/Profile/components/SettingsModal/SettingsModal.jsx deleted file mode 100644 index c5e0ff3..0000000 --- a/src/pages/Profile/components/SettingsModal/SettingsModal.jsx +++ /dev/null @@ -1,86 +0,0 @@ -import Modal from "../../../../components/ui/modal/modal.tsx"; -import Button from "../../../../components/ui/button/button.tsx"; -import styles from "./SettingsModal.module.css" -import { useContext, useState } from "react"; -import ThemeSwitcher from "../../../../components/ui/themeSwitcher/ThemeSwitcher.jsx"; -import { AuthContext } from "../../../../contexts/auth/AuthContext.js"; -import TextInput from "../../../../components/ui/input/input.tsx"; -import updateUser from "../../../../utils/users/updateUser.js"; -import deleteUser from "../../../../utils/users/deleteUser.js"; - - -export default function SettingsModal() { - - const { user, logout, update } = useContext(AuthContext); - - const [open, setOpen] = useState(false); - const [isDelete, setIsDelete] = useState(false); - - const [name, setName] = useState(user?.name); - const [lastname, setLastName] = useState(user?.lastname); - const [phone, setPhone] = useState(user?.phone); - - const handleLogout = () => { - logout(); - } - - const handleSubmit = async () => { - await updateUser(name, lastname, phone); - update(); - } - - const handleDelete = async () => { - await deleteUser(); - update(); - } - - return ( - <> - - - setOpen(false)}> -
- -
-

Compte

- - - -
-

Changer de Prénom

- setName(e.target.value)} type={"text"} /> -
- -
-

Changer de nom

- setLastName(e.target.value)} type={"text"} /> -
- -
-

Changer de numéro de téléphone

- setPhone(e.target.value)} type={"text"} /> -
- - - -
- -
-

Apparence

- -
-
-
- - - setIsDelete(false)}> -
-

Êtes vous vraiment sĂ»r de vouloir supprimer votre compte ?

- -
-
- - ) -} \ No newline at end of file diff --git a/src/pages/Profile/components/SettingsModal/SettingsModal.tsx b/src/pages/Profile/components/SettingsModal/SettingsModal.tsx new file mode 100644 index 0000000..fa6701f --- /dev/null +++ b/src/pages/Profile/components/SettingsModal/SettingsModal.tsx @@ -0,0 +1,167 @@ +import {useContext, useState, ChangeEvent} from "react"; +import Modal from "../../../../components/ui/modal/modal"; +import Button from "../../../../components/ui/button/button"; +import styles from "./SettingsModal.module.css"; +import ThemeSwitcher from "../../../../components/ui/themeSwitcher/ThemeSwitcher.jsx"; +import {AuthContext} from "../../../../contexts/auth/AuthContext"; +import TextInput from "../../../../components/ui/input/input"; +import updateUser from "../../../../utils/users/updateUser.js"; +import deleteUser from "../../../../utils/users/deleteUser.js"; + +interface TaskType { + id: number; + title?: string; + completed?: boolean; + + [key: string]: unknown; +} + +interface User { + id: number; + name: string; + lastname: string; + role: string; + email: string; + phone: string | null; + created_at: string; + isAdmin: boolean; + tasks: TaskType[]; +} + +interface AuthContextType { + user: User; + update: () => void; + logout: () => void; +} + +export default function SettingsModal() { + const {user, logout, update} = useContext(AuthContext) as AuthContextType; + + const [open, setOpen] = useState(false); + const [isDelete, setIsDelete] = useState(false); + + const [name, setName] = useState(user?.name ?? ""); + const [lastname, setLastName] = useState(user?.lastname ?? ""); + const [phone, setPhone] = useState(user?.phone ?? ""); + + const handleLogout = (): void => { + logout(); + }; + + const handleSubmit = async (): Promise => { + await updateUser(name, lastname, phone || null); + update(); + setOpen(false); + }; + + const handleDelete = async (): Promise => { + await deleteUser(); + update(); + }; + + const handleNameChange = (e: ChangeEvent): void => { + setName(e.target.value); + }; + + const handleLastnameChange = (e: ChangeEvent): void => { + setLastName(e.target.value); + }; + + const handlePhoneChange = (e: ChangeEvent): void => { + setPhone(e.target.value); + }; + + + return ( + <> + + + setOpen(false)} + > +
+
+

Compte

+ + + + + +
+

Changer de Prénom

+ +
+ +
+

Changer de nom

+ +
+ +
+

Changer de numéro de téléphone

+ +
+ + +
+ +
+

Apparence

+ +
+
+
+ + setIsDelete(false)} + > +
+

+ Êtes vous vraiment sĂ»r de vouloir supprimer votre compte ? +

+ +
+
+ + ); +} \ No newline at end of file From d8baa33330c018c5274a0436947c67ed708e0f9c Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 24 Feb 2026 17:00:45 +0100 Subject: [PATCH 40/47] Fix className error --- src/components/ui/button/button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/button/button.tsx b/src/components/ui/button/button.tsx index 1866c39..b57fc8c 100644 --- a/src/components/ui/button/button.tsx +++ b/src/components/ui/button/button.tsx @@ -7,7 +7,7 @@ interface ButtonProps { children: ReactNode; variant?: ButtonVariant; onClick?: MouseEventHandler; - className?: string; + className?: string | undefined; } const Button = ({ From f0534bd0f5505ac5dbdbbfa63a28b0d7219fd6f1 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 24 Feb 2026 17:17:47 +0100 Subject: [PATCH 41/47] Fix style --- .../SettingsModal/SettingsModal.module.css | 71 +++++++++++++------ 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/src/pages/Profile/components/SettingsModal/SettingsModal.module.css b/src/pages/Profile/components/SettingsModal/SettingsModal.module.css index d6b0b12..7808db3 100644 --- a/src/pages/Profile/components/SettingsModal/SettingsModal.module.css +++ b/src/pages/Profile/components/SettingsModal/SettingsModal.module.css @@ -1,73 +1,98 @@ .content { width: 100%; - padding: 16px; + padding: 20px; display: flex; flex-direction: column; - gap: 20px; + gap: 28px; } .modifyIcon { - height: 28px; - width: 28px; + height: 26px; + width: 26px; + transition: transform 0.2s ease, opacity 0.2s ease; +} + +.modifyIcon:hover { + transform: rotate(15deg); + opacity: 0.8; } .section { display: flex; flex-direction: column; - gap: 12px; - margin-top: 10px; + gap: 16px; + padding: 18px; + border-radius: 12px; + background: var(--glass-bg, rgba(255, 255, 255, 0.25)); + backdrop-filter: blur(6px); } .section h3 { - font-size: 1.1rem; - margin-bottom: 5px; + font-size: 1.15rem; + font-weight: 600; } .section h4 { - font-size: 0.95rem; + font-size: 0.9rem; font-weight: 500; -} - -.submitBtn { - width: 100%; - margin-top: 15px; + opacity: 0.85; } .section button { width: 100%; } -@media (min-width: 600px) { +.submitBtn { + width: 100%; + text-align: center; + margin-top: 10px; +} + +.alignText { + text-align: center; + line-height: 1.5; +} + +.section :global(input) { + width: 100%; + box-sizing: border-box; +} + +@media (min-width: 640px) { .content { - padding: 24px; + padding: 28px; + gap: 32px; } - .section h3 { - font-size: 1.2rem; + .section { + padding: 22px; + gap: 18px; } .submitBtn { width: auto; align-self: center; - min-width: 200px; + min-width: 220px; } .section button { width: fit-content; } } + @media (min-width: 900px) { .content { - padding: 30px; - gap: 30px; + padding: 36px; + gap: 36px; } .section { - gap: 14px; + padding: 26px; + border-radius: 16px; } .modifyIcon { height: 30px; width: 30px; } -} +} \ No newline at end of file From 8d7a55c5f85429f0722f8bc291ff2a5bd4ca8514 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 24 Feb 2026 17:29:24 +0100 Subject: [PATCH 42/47] Fix update --- src/pages/Profile/ProfilePage.tsx | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/pages/Profile/ProfilePage.tsx b/src/pages/Profile/ProfilePage.tsx index f0037f9..8e613d0 100644 --- a/src/pages/Profile/ProfilePage.tsx +++ b/src/pages/Profile/ProfilePage.tsx @@ -4,8 +4,8 @@ import {AuthContext} from "../../contexts/auth/AuthContext"; import formatDate from "../../utils/date/formatDate"; import Task from "../../components/Task/Task"; import SettingsModal from "./components/SettingsModal/SettingsModal"; -import { useParams } from "react-router"; -import { useNavigate } from "react-router"; +import {useParams} from "react-router"; +import {useNavigate} from "react-router"; import getUserById from "../../utils/users/getUserById.js"; import ManageMember from "./components/manageMember/ManageMember.jsx"; @@ -35,21 +35,20 @@ interface AuthContextType { update: () => void; } - function ProfilePage() { const navigate = useNavigate(); - const { id } = useParams(); - const { user, update } = useContext(AuthContext) as AuthContextType; + const {id} = useParams(); + const {user, update} = useContext(AuthContext) as AuthContextType; const [profileUser, setProfileUser] = useState(null); const isOwnProfile = !id || user.id.toString() === id; useEffect(() => { - update(); + (async () => { - if(!isOwnProfile) { + if (!isOwnProfile) { console.log(id); const res = await getUserById(id); @@ -64,9 +63,12 @@ function ProfilePage() { } setProfileUser(res.data); - } else setProfileUser(user) - }) () - }, []); + } else { + update(); + setProfileUser(user) + } + })() + }, [user]); return (
@@ -86,7 +88,7 @@ function ProfilePage() { {profileUser?.name} {profileUser?.lastname} {(user?.isAdmin && profileUser && !isOwnProfile) && ( - + )}
@@ -113,7 +115,7 @@ function ProfilePage() { {isOwnProfile && (
- +
)}
From 39b21f8ff8ccda999c486d8c183875df01d520d8 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 24 Feb 2026 17:41:15 +0100 Subject: [PATCH 43/47] Convert to TS --- src/components/Task/Task.jsx | 29 --------------- src/components/Task/Task.tsx | 71 ++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 29 deletions(-) delete mode 100644 src/components/Task/Task.jsx create mode 100644 src/components/Task/Task.tsx diff --git a/src/components/Task/Task.jsx b/src/components/Task/Task.jsx deleted file mode 100644 index d14cabf..0000000 --- a/src/components/Task/Task.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, {useEffect, useState} from "react"; -import getEventById from "../../utils/events/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.data); - }) () - }, []) - - 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.tsx b/src/components/Task/Task.tsx new file mode 100644 index 0000000..7c939b2 --- /dev/null +++ b/src/components/Task/Task.tsx @@ -0,0 +1,71 @@ +import { useEffect, useState } from "react"; +import getEventById from "../../utils/events/getEventById.js"; +import styles from "./Task.module.css"; +import formatDate from "../../utils/date/formatDate"; + +interface TaskType { + id: number; + name: string; + description: string; + location: string; + start: string; + end: string; + max_participants: number; + events_id: number; + created_at: string; + updated_at: string; + pivot: { + user_id: number; + task_id: number; + }; +} + +interface EventType { + id: number; + name: string; + description?: string; + location?: string; + start?: string; + end?: string; + created_at?: string; + updated_at?: string; +} + +interface TaskProps { + task: TaskType; +} + +const Task: React.FC = ({ task }) => { + const [event, setEvent] = useState(null); + + useEffect(() => { + const fetchEvent = async (): Promise => { + try { + const e = await getEventById(task.events_id); + setEvent(e.data as EventType); + } catch (error) { + console.error("Erreur lors du chargement de l'événement", error); + } + }; + + fetchEvent(); + }, [task.events_id]); + + return ( +
+
+
+

+ {event?.name} - {task.name} +

+

{task.description}

+

+ {formatDate(task.start)} +

+
+
+
+ ); +}; + +export default Task; \ No newline at end of file From d9b0e97df86cef2f212b3943411318a94e7d3073 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Tue, 3 Mar 2026 11:53:30 +0100 Subject: [PATCH 44/47] convert TS --- src/contexts/auth/AuthContext.ts | 1 + src/pages/Profile/ProfilePage.tsx | 185 ++++++++++++++++-------------- 2 files changed, 101 insertions(+), 85 deletions(-) diff --git a/src/contexts/auth/AuthContext.ts b/src/contexts/auth/AuthContext.ts index 03e6786..e4f5d00 100644 --- a/src/contexts/auth/AuthContext.ts +++ b/src/contexts/auth/AuthContext.ts @@ -15,6 +15,7 @@ export interface User { email: string; phone: string | null; created_at: string; + profile_photo_path?: string | null; tasks: Task[]; } diff --git a/src/pages/Profile/ProfilePage.tsx b/src/pages/Profile/ProfilePage.tsx index 4c2fc6b..223b63c 100644 --- a/src/pages/Profile/ProfilePage.tsx +++ b/src/pages/Profile/ProfilePage.tsx @@ -1,8 +1,10 @@ import { useContext, useEffect, useState, useRef } from "react"; -// @ts-ignore import styles from "./ProfilePage.module.css"; import { AuthContext } from "../../contexts/auth/AuthContext"; import formatDate from "../../utils/date/formatDate"; +import getXSRFToken from "../../utils/getXSRF.js"; +import fetchWrapper from "../../utils/fetchWrapper"; + interface TaskType { id: number; @@ -19,7 +21,7 @@ interface User { phone: string | null; created_at: string; tasks: TaskType[]; - profilePicture?: string | null; + profile_photo_path?: string | null; } interface AuthContextType { @@ -28,8 +30,13 @@ interface AuthContextType { } function ProfilePage() { + const csrfToken = getXSRFToken(); const { user, update } = useContext(AuthContext) as AuthContextType; - const [profilePicture, setProfilePicture] = useState(user?.profilePicture || null); + /*const [profilePicture, setProfilePicture] = useState( + user?.profile_photo_path ? `${import.meta.env.VITE_API_URL}/storage/${user.profile_photo_path}`: null);*/ + const [profilePicture, setProfilePicture] = useState(null); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); const fileInputRef = useRef(null); // Fonction pour déclencher l'input file @@ -39,24 +46,81 @@ function ProfilePage() { } }; - // Fonction pour gérer le changement de fichier const handleFileChange = async (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; + const file = e.target.files?.[0]; + if (!file) return; - // Logique pour uploader la photo (à adapter selon ton backend) - const formData = new FormData(); - formData.append("profilePicture", file); + const formData = new FormData(); + formData.append("photo", file); - // Appelle la fonction pour uploader la photo (exemple) - // const response = await uploadProfilePicture(formData); - // setProfilePicture(response.url); // Met à jour l'URL de la photo - // update(); // Met à jour le contexte utilisateur + try { + const response = await fetch(`${import.meta.env.VITE_API_URL}/api/profile-photo`, { + method: "POST", + headers: { + "X-XSRF-TOKEN": decodeURIComponent(await csrfToken), + }, + credentials: "include", + body: formData, +}); + + if (response.ok) { + const data = await response.json(); + setProfilePicture(`${import.meta.env.VITE_API_URL}/storage/${data?.profile_photo_path}`); + setSuccess("Photo de profil mise à jour avec succÚs !"); + setError(null); + await update(); + + /*if (response.ok) { + const data = await response.json(); + console.log("data:", data); + console.log("URL:", `/storage/${data?.profile_photo_path}`); + + setProfilePicture(`${import.meta.env.VITE_API_URL}/storage/${data?.profile_photo_path}`); + setSuccess("Photo de profil mise à jour avec succÚs !"); + setError(null); + update();*/ +} else { + const errorData = await response.json(); + setError(errorData?.message || "Erreur lors de l'upload de la photo."); + setSuccess(null); + } + } catch (err) { + setError("Erreur réseau. Vérifiez votre connexion."); + setSuccess(null); + } + + +}; + + // Fonction pour supprimer la photo de profil + const handleDeletePicture = async () => { + try { + const token = localStorage.getItem("token"); + const response = await fetchWrapper("/api/profile-photo",null,"DELETE"); + if (response.status >= 200 && response.status < 300) { + setProfilePicture(null); + setSuccess("Photo de profil supprimée avec succÚs !"); + setError(null); + await update(); + } else { + const errorData =response.data; + setError(errorData.message || "Erreur lors de la suppression de la photo."); + setSuccess(null); + } + } catch (err) { + setError("Erreur réseau. Vérifiez votre connexion."); + setSuccess(null); + } }; - useEffect(() => { - update(); - }, []); +useEffect(() => { + console.log("user reçu:", user); + if (user?.profile_photo_path) { + setProfilePicture(`${import.meta.env.VITE_API_URL}/storage/${user.profile_photo_path}`); + } else if (user && !user.profile_photo_path && !profilePicture) { + setProfilePicture(null); + } +}, [user]); return (
@@ -69,19 +133,31 @@ function ProfilePage() { className={styles.profilePicture} />
- - + {user?.profile_photo_path && ( + + )} +
+ + {error &&

{error}

} + {success &&

{success}

}
@@ -133,64 +209,3 @@ function ProfilePage() { export default ProfilePage; -/*import React, { useContext, useEffect } from "react"; -import styles from "./ProfilePage.module.css"; -import { AuthContext } from "../../contexts/auth/AuthContext.js"; -import formatDate from "../../utils/date/formatDate.js"; -import Task from "../../components/Task/Task"; -import SettingsModal from "./components/SettingsModal/SettingsModal.jsx"; - -function ProfilePage() { - - const { user, update } = useContext(AuthContext); - - useEffect(() => { - update(); - }, []) - - return ( -
-
-
-
- Photo de profil -
-
-
-

{user?.name} {user?.lastname}

-
-
-
-

Role : {user?.role}

-

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

-
-
-

Mail : {user?.email}

-

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

-
-
- -
-
- -

TĂąches :

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

Aucune tĂąche pour le moment.

- )} - -
-
-
-
- ); -} - -export default ProfilePage;*/ \ No newline at end of file From feebac62ade80a45138c62812e449391b3300c1a Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 3 Mar 2026 12:03:44 +0100 Subject: [PATCH 45/47] update user info when task inscription is modified --- src/pages/eventDetail/components/EventTask/EventTask.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/eventDetail/components/EventTask/EventTask.jsx b/src/pages/eventDetail/components/EventTask/EventTask.jsx index ff10dbb..6bea6e0 100644 --- a/src/pages/eventDetail/components/EventTask/EventTask.jsx +++ b/src/pages/eventDetail/components/EventTask/EventTask.jsx @@ -11,7 +11,7 @@ import DeleteTaskBtn from "../deleteTaskBtn/deleteTaskBtn.jsx"; export default function EventTask({ task, index, eventId }) { - const { user } = useContext(AuthContext) + const { update } = useContext(AuthContext) const [assigned, setAssigned] = useState(false); const [open, setOpen] = useState(false); @@ -33,6 +33,7 @@ export default function EventTask({ task, index, eventId }) { switch (res.status) { case 200: + update() setTitle("Inscription rĂ©ussie") setMessage(`Vous ĂȘtes maintenant inscrit Ă  la tĂąche ${task.name}`); setOpen(true); @@ -58,6 +59,7 @@ export default function EventTask({ task, index, eventId }) { switch (res.status) { case 200: + update() setTitle("DĂ©sinscription rĂ©ussie") setMessage(`Vous n'ĂȘtes plus inscrit Ă  la tĂąche ${task.name}`); setOpen(true); From 846900f9d19a99b0de1307f4d6e1361421c8f506 Mon Sep 17 00:00:00 2001 From: T'JAMPENS QUENTIN p2406187 Date: Tue, 3 Mar 2026 12:03:59 +0100 Subject: [PATCH 46/47] Remove update from profile useEffect --- src/pages/Profile/ProfilePage.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/Profile/ProfilePage.tsx b/src/pages/Profile/ProfilePage.tsx index 8e613d0..5369f96 100644 --- a/src/pages/Profile/ProfilePage.tsx +++ b/src/pages/Profile/ProfilePage.tsx @@ -64,11 +64,10 @@ function ProfilePage() { setProfileUser(res.data); } else { - update(); setProfileUser(user) } })() - }, [user]); + }, [user, id]); return (
From edd81b66de3754d9e7afc100ea730692ab89de17 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Tue, 3 Mar 2026 12:30:32 +0100 Subject: [PATCH 47/47] fix inline code with ProfilePicture --- src/pages/Profile/ProfilePage.module.css | 16 -- src/pages/Profile/ProfilePage.tsx | 36 ++-- .../SettingsModal/SettingsModal.tsx | 186 ------------------ src/utils/users/deleteProfilePhoto.ts | 10 + src/utils/users/replaceProfilePhoto.ts | 5 + src/utils/users/uploadProfilePhoto.ts | 28 +++ 6 files changed, 56 insertions(+), 225 deletions(-) create mode 100644 src/utils/users/deleteProfilePhoto.ts create mode 100644 src/utils/users/replaceProfilePhoto.ts create mode 100644 src/utils/users/uploadProfilePhoto.ts diff --git a/src/pages/Profile/ProfilePage.module.css b/src/pages/Profile/ProfilePage.module.css index 7c2a179..2bcecee 100644 --- a/src/pages/Profile/ProfilePage.module.css +++ b/src/pages/Profile/ProfilePage.module.css @@ -7,12 +7,6 @@ } .profileboard { -<<<<<<< HEAD - width: 100%; - padding: 16px; - border-radius: 20px; - box-sizing: border-box; -======= width: 100%; padding: 16px; border-radius: 20px; @@ -23,7 +17,6 @@ display: flex; justify-content: center; align-items: center; ->>>>>>> dev } .contentWrapper { @@ -91,14 +84,6 @@ margin-bottom: 16px; } -<<<<<<< HEAD -.headerProfile h2 { - font-size: 1.6rem; - margin: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -======= .description h2 { font-size: 1.6rem; margin-bottom: 10px; @@ -126,7 +111,6 @@ height: 22px; display: block; margin: 0; ->>>>>>> dev } /* Conteneur des informations principales */ diff --git a/src/pages/Profile/ProfilePage.tsx b/src/pages/Profile/ProfilePage.tsx index fe8b82a..eddae6a 100644 --- a/src/pages/Profile/ProfilePage.tsx +++ b/src/pages/Profile/ProfilePage.tsx @@ -1,5 +1,5 @@ import getXSRFToken from "../../utils/getXSRF.js"; -import fetchWrapper from "../../utils/fetchWrapper"; +// import fetchWrapper from "../../utils/fetchWrapper"; import {useContext, useEffect, useState} from "react"; import styles from "./ProfilePage.module.css"; import {AuthContext} from "../../contexts/auth/AuthContext"; @@ -12,6 +12,9 @@ import {useRef} from "react"; import getUserById from "../../utils/users/getUserById.js"; import ManageMember from "./components/manageMember/ManageMember.jsx"; +import uploadProfilePhoto from "../../utils/users/uploadProfilePhoto"; +import deleteProfilePhoto from "../../utils/users/deleteProfilePhoto.js"; + interface TaskType { id: number; @@ -61,7 +64,6 @@ function ProfilePage() { const isOwnProfile = !id || user?.id.toString() === id; - // Charger le profil de l'utilisateur (soi-mĂȘme ou un autre) useEffect(() => { (async () => { if (!isOwnProfile && id) { @@ -85,38 +87,26 @@ function ProfilePage() { })(); }, [user, id]); - // Fonctions pour la photo de profil (uniquement si c'est le profil de l'utilisateur connectĂ©) const handleEditPictureClick = () => { if (fileInputRef.current) { fileInputRef.current.click(); } }; + // RemplacĂ© : handleFileChange extrait vers utils/users/uploadProfilePhoto const handleFileChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; - const formData = new FormData(); - formData.append("photo", file); - try { - const csrfToken = await getXSRFToken(); - const response = await fetch(`${import.meta.env.VITE_API_URL}/api/profile-photo`, { - method: "POST", - headers: { "X-XSRF-TOKEN": decodeURIComponent(csrfToken) }, - credentials: "include", - body: formData, - }); - - if (response.ok) { - const data = await response.json(); - setProfilePicture(`${import.meta.env.VITE_API_URL}/storage/${data.profile_photo_path}`); + const res = await uploadProfilePhoto(file); + if (res.ok) { + setProfilePicture(`${import.meta.env.VITE_API_URL}/storage/${res.data.profile_photo_path}`); setSuccess("Photo mise Ă  jour !"); setError(null); await update(); } else { - const errorData = await response.json(); - setError(errorData.message || "Erreur lors de l'upload."); + setError(res.data?.message || res.error || "Erreur lors de l'upload."); setSuccess(null); } } catch (err) { @@ -125,17 +115,17 @@ function ProfilePage() { } }; + // RemplacĂ© : handleDeletePicture extrait vers utils/users/deleteProfilePhoto const handleDeletePicture = async () => { try { - const response = await fetchWrapper("/api/profile-photo", null, "DELETE"); - if (response.status >= 200 && response.status < 300) { + const res = await deleteProfilePhoto(); + if (res.status >= 200 && res.status < 300) { setProfilePicture(null); setSuccess("Photo supprimĂ©e !"); setError(null); await update(); } else { - const errorData = response.data; - setError(errorData.message || "Erreur lors de la suppression."); + setError(res.data?.message || "Erreur lors de la suppression."); setSuccess(null); } } catch (err) { diff --git a/src/pages/Profile/components/SettingsModal/SettingsModal.tsx b/src/pages/Profile/components/SettingsModal/SettingsModal.tsx index 4c2e7b1..e028735 100644 --- a/src/pages/Profile/components/SettingsModal/SettingsModal.tsx +++ b/src/pages/Profile/components/SettingsModal/SettingsModal.tsx @@ -1,58 +1,3 @@ -<<<<<<< HEAD -/*import Modal from "../../../../components/ui/modal/modal.jsx"; -import Button from "../../../../components/ui/button/button.jsx"; -import styles from "./SettingsModal.module.css" -import { useContext, useState } from "react"; -import ThemeSwitcher from "../../../../components/ui/themeSwitcher/ThemeSwitcher.jsx"; -import { AuthContext } from "../../../../contexts/auth/AuthContext.js"; -import TextInput from "../../../../components/ui/input/input.jsx"; -import updateUser from "../../../../utils/users/updateUser.js"; -import deleteUser from "../../../../utils/users/deleteUser.js"; - - -export default function SettingsModal() { - - const { user, logout, update } = useContext(AuthContext); - - const [open, setOpen] = useState(false); - const [isDelete, setIsDelete] = useState(false); - - const [name, setName] = useState(user?.name); - const [lastname, setLastName] = useState(user?.lastname); - const [phone, setPhone] = useState(user?.phone); - - const handleLogout = () => { - logout(); - } - - const handleSubmit = async () => { - await updateUser(name, lastname, phone); - update(); - } - - const handleDelete = async () => { - await deleteUser(); - update(); - } - - return ( - <> - - - setOpen(false)}> -
- -
-

Compte

- - - -
-

Changer de Prénom

- setName(e.target.value)} type={"text"} /> -======= import {useContext, useState, ChangeEvent} from "react"; import Modal from "../../../../components/ui/modal/modal"; import Button from "../../../../components/ui/button/button"; @@ -164,31 +109,19 @@ export default function SettingsModal() { onChange={handleNameChange} type="text" /> ->>>>>>> dev

Changer de nom

-<<<<<<< HEAD - setLastName(e.target.value)} type={"text"} /> -======= ->>>>>>> dev

Changer de numéro de téléphone

-<<<<<<< HEAD - setPhone(e.target.value)} type={"text"} /> -
- - - -======= Confirmer ->>>>>>> dev

Apparence

-<<<<<<< HEAD - -======= ->>>>>>> dev
-<<<<<<< HEAD - - setIsDelete(false)}> -
-

Êtes vous vraiment sĂ»r de vouloir supprimer votre compte ?

- -
-
- - ) -}*/ - -import Modal from "../../../../components/ui/modal/modal"; -import Button from "../../../../components/ui/button/button"; -import styles from "./SettingsModal.module.css"; -import { useContext, useState } from "react"; -import ThemeSwitcher from "../../../../components/ui/themeSwitcher/ThemeSwitcher"; -import { AuthContext } from "../../../../contexts/auth/AuthContext"; -import TextInput from "../../../../components/ui/input/input"; -import updateUser from "../../../../utils/users/updateUser"; -import deleteUser from "../../../../utils/users/deleteUser"; - -interface User { - name: string; - lastname: string; - phone: string | null; -} - -interface AuthContextType { - user: User | null; - logout: () => void; - update: () => void; -} - -export default function SettingsModal() { - const { user, logout, update } = useContext(AuthContext) as AuthContextType; - - const [open, setOpen] = useState(false); - const [isDelete, setIsDelete] = useState(false); - const [name, setName] = useState(user?.name || ""); - const [lastname, setLastName] = useState(user?.lastname || ""); - const [phone, setPhone] = useState(user?.phone || null); - - const handleLogout = () => { - logout(); - }; - - const handleSubmit = async () => { - await updateUser(name, lastname, phone); - update(); - }; - - const handleDelete = async () => { - await deleteUser(); - update(); - }; - - return ( - <> - - - setOpen(false)}> -
-
-

Compte

- - - -
-

Changer de Prénom

- setName(e.target.value)} type={"text"} /> -
- -
-

Changer de nom

- setLastName(e.target.value)} type={"text"} /> -
- -
-

Changer de numéro de téléphone

- setPhone(e.target.value)} type={"text"} /> -
- - -
- -
-

Apparence

- -
-
-
- - setIsDelete(false)}> -
-

Êtes-vous vraiment sĂ»r de vouloir supprimer votre compte ?

- -
-
- - ); -} -======= ); } ->>>>>>> dev diff --git a/src/utils/users/deleteProfilePhoto.ts b/src/utils/users/deleteProfilePhoto.ts new file mode 100644 index 0000000..d0ca642 --- /dev/null +++ b/src/utils/users/deleteProfilePhoto.ts @@ -0,0 +1,10 @@ +import fetchWrapper from "../fetchWrapper"; + +export default async function deleteProfilePhoto(): Promise<{ status: number; ok: boolean; data?: any; error?: string }> { + try { + const response = await fetchWrapper("/api/profile-photo", null, "DELETE"); + return { status: response.status || 0, ok: response.status >= 200 && response.status < 300, data: response.data }; + } catch (err: any) { + return { status: 0, ok: false, error: err?.message || "Network error" }; + } +} diff --git a/src/utils/users/replaceProfilePhoto.ts b/src/utils/users/replaceProfilePhoto.ts new file mode 100644 index 0000000..92569eb --- /dev/null +++ b/src/utils/users/replaceProfilePhoto.ts @@ -0,0 +1,5 @@ +import uploadProfilePhoto from "./uploadProfilePhoto"; + +export default async function replaceProfilePhoto(file: File) { + return uploadProfilePhoto(file); +} diff --git a/src/utils/users/uploadProfilePhoto.ts b/src/utils/users/uploadProfilePhoto.ts new file mode 100644 index 0000000..ca252a2 --- /dev/null +++ b/src/utils/users/uploadProfilePhoto.ts @@ -0,0 +1,28 @@ +import getXSRFToken from "../getXSRF.js"; + +export default async function uploadProfilePhoto(file: File): Promise<{ status: number; ok: boolean; data?: any; error?: string }> { + const formData = new FormData(); + formData.append("photo", file); + + try { + const csrfToken = await getXSRFToken(); + const response = await fetch(`${import.meta.env.VITE_API_URL}/api/profile-photo`, { + method: "POST", + headers: { "X-XSRF-TOKEN": decodeURIComponent(csrfToken) }, + credentials: "include", + body: formData, + }); + + const status = response.status; + let data = null; + try { + data = await response.json(); + } catch (e) { + // no json + } + + return { status, ok: response.ok, data }; + } catch (err: any) { + return { status: 0, ok: false, error: err?.message || "Network error" }; + } +}