From 2389ca88e4a0a350f36147a1c2dbb134af54bb6e Mon Sep 17 00:00:00 2001 From: p2405951 Date: Fri, 30 Jan 2026 16:35:48 +0100 Subject: [PATCH 1/7] 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 2/7] 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 85d9e60025fa0f63d92770c9f716f9eb2472b2b0 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Fri, 6 Feb 2026 15:21:05 +0100 Subject: [PATCH 3/7] 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 26dcc0fcd3aaf394ad6d7f09ae28a957af453d10 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Tue, 10 Feb 2026 14:58:25 +0100 Subject: [PATCH 4/7] 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} /> - - + {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 edd81b66de3754d9e7afc100ea730692ab89de17 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Tue, 3 Mar 2026 12:30:32 +0100 Subject: [PATCH 7/7] 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" }; + } +}