From 3d77b8aaf930e5e1cff9e4cc6eebd6938e200efb Mon Sep 17 00:00:00 2001 From: NKoelblen Date: Thu, 26 Mar 2026 17:09:23 +0100 Subject: [PATCH 01/22] Add authentication pages and update routing for login and account management --- src/App.tsx | 6 + src/layout/admin/adminHeader.tsx | 9 +- src/layout/rootPaper.tsx | 11 +- src/pages/admin/accountPage.tsx | 242 +++++++++++++++++++++++++ src/pages/public/loginPage.tsx | 111 ++++++++++++ src/pages/public/resetPasswordPage.tsx | 75 ++++++++ 6 files changed, 450 insertions(+), 4 deletions(-) create mode 100644 src/pages/admin/accountPage.tsx create mode 100644 src/pages/public/loginPage.tsx create mode 100644 src/pages/public/resetPasswordPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 489d512..8ac349d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,9 @@ import Home from "./pages/public/homePage"; import PublicLayout from "./layout/public/publicLayout"; import AdminLayout from "./layout/admin/adminLayout"; import Dashboard from "./pages/admin/dashboardPage"; +import Account from "./pages/admin/accountPage"; +import Login from "./pages/public/loginPage"; +import ResetPassword from "./pages/public/resetPasswordPage"; export default function App() { return ( @@ -20,6 +23,8 @@ export default function App() { } /> + } /> + } /> {/* Admin routes */} } /> + } /> } diff --git a/src/layout/admin/adminHeader.tsx b/src/layout/admin/adminHeader.tsx index 7dea570..8a02b18 100644 --- a/src/layout/admin/adminHeader.tsx +++ b/src/layout/admin/adminHeader.tsx @@ -1,18 +1,21 @@ import AppBar from "@mui/material/AppBar"; import Toolbar from "@mui/material/Toolbar"; import Button from "@mui/material/Button"; -import { Link as RouterLink } from "react-router-dom"; +import { Link } from "react-router-dom"; export default function AdminHeader() { return ( - - + ); diff --git a/src/layout/rootPaper.tsx b/src/layout/rootPaper.tsx index 9a11eb3..35823ff 100644 --- a/src/layout/rootPaper.tsx +++ b/src/layout/rootPaper.tsx @@ -1,6 +1,11 @@ +import type { PaperProps } from "@mui/material"; import { ResponsivePaper } from "../components/ResponsiveLayout"; +import type { ResponsiveLayoutProps } from "../types/responsiveComponents"; -export default function RootPaper({ children }: { children: React.ReactNode }) { +export default function RootPaper({ + children, + ...props +}: ResponsiveLayoutProps<{ children: React.ReactNode }> & PaperProps) { return ( {children} diff --git a/src/pages/admin/accountPage.tsx b/src/pages/admin/accountPage.tsx new file mode 100644 index 0000000..6d9e91f --- /dev/null +++ b/src/pages/admin/accountPage.tsx @@ -0,0 +1,242 @@ +import { + Button, + IconButton, + InputAdornment, + Link, + TextField, +} from "@mui/material"; +import ResponsiveTitle from "../../components/responsiveTitle"; +import { useState } from "react"; +import Icon from "@mdi/react"; +import { mdiEyeOff, mdiEye } from "@mdi/js"; +import { ResponsiveStack } from "../../components/ResponsiveLayout"; +import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; + +export default function Account() { + const initialLogin = "onoko"; + const initialEmail = "hello@onoko.dev"; + const [login, setLogin] = useState(initialLogin); + const [email, setEmail] = useState(initialEmail); + const [showPassword, setShowPassword] = useState(false); + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [passwordError, setPasswordError] = useState(""); + const [newPasswordError, setNewPasswordError] = useState(""); + + const handleTogglePassword = () => setShowPassword((v) => !v); + + const validatePassword = (pwd: string) => { + if (!pwd) return ""; + if (pwd.length < 20) { + return "Au moins 20 caractères."; + } + if (!/[A-Z]/.test(pwd)) { + return "Au moins une majuscule."; + } + if (!/[a-z]/.test(pwd)) { + return "Au moins une minuscule."; + } + if (!/[0-9]/.test(pwd)) { + return "Au moins un chiffre."; + } + if (!/[^A-Za-z0-9]/.test(pwd)) { + return "Au moins un caractère spécial."; + } + return ""; + }; + + const handleCurrentPasswordChange = ( + e: React.ChangeEvent, + ) => { + setCurrentPassword(e.target.value); + }; + + const handleNewPasswordChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setNewPassword(value); + const validationMsg = validatePassword(value); + setNewPasswordError(validationMsg); + if (value !== confirmPassword) { + setPasswordError("Les mots de passe doivent être identiques."); + } else { + setPasswordError(""); + } + }; + + const handleConfirmPasswordChange = ( + e: React.ChangeEvent, + ) => { + setConfirmPassword(e.target.value); + if (newPassword && e.target.value !== newPassword) { + setPasswordError("Les mots de passe doivent être identiques."); + } else { + setPasswordError(""); + } + }; + + return ( + + Mon compte + + + setLogin(e.target.value)} + /> + setEmail(e.target.value)} + /> + + + + + + + {showPassword ? ( + + ) : ( + + )} + + + ), + }, + }} + /> + + Mot de passe oublié ?{" "} + Réinitialiser mon mot de passe + + + + + + {showPassword ? ( + + ) : ( + + )} + + + ), + }, + }} + /> + + + {showPassword ? ( + + ) : ( + + )} + + + ), + }, + }} + /> + + + + + + ); +} diff --git a/src/pages/public/loginPage.tsx b/src/pages/public/loginPage.tsx new file mode 100644 index 0000000..f42237f --- /dev/null +++ b/src/pages/public/loginPage.tsx @@ -0,0 +1,111 @@ +import { + TextField, + IconButton, + InputAdornment, + Button, + Link, +} from "@mui/material"; +import { useState } from "react"; +import Icon from "@mdi/react"; +import { mdiEyeOff, mdiEye } from "@mdi/js"; +import { + ResponsivePaper, + ResponsiveStack, +} from "../../components/ResponsiveLayout"; +import RootPaper from "../../layout/rootPaper"; +import ResponsiveTitle from "../../components/responsiveTitle"; +import { Link as RouterLink } from "react-router-dom"; +import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; + +export default function Login() { + const [showPassword, setShowPassword] = useState(false); + const handleTogglePassword = () => setShowPassword((v) => !v); + + return ( + + + + Accéder à l'espace Administrateur + + + + + + {showPassword ? ( + + ) : ( + + )} + + + ), + }, + }} + /> + + + + + + + + Mot de passe oublié ?{" "} + Réinitialiser mon mot de passe + + + + + ); +} diff --git a/src/pages/public/resetPasswordPage.tsx b/src/pages/public/resetPasswordPage.tsx new file mode 100644 index 0000000..6334495 --- /dev/null +++ b/src/pages/public/resetPasswordPage.tsx @@ -0,0 +1,75 @@ +import { + TextField, + IconButton, + InputAdornment, + Button, + Link, +} from "@mui/material"; +import Icon from "@mdi/react"; +import { mdiEyeOff, mdiEye } from "@mdi/js"; +import { + ResponsivePaper, + ResponsiveStack, +} from "../../components/ResponsiveLayout"; +import RootPaper from "../../layout/rootPaper"; +import ResponsiveTitle from "../../components/responsiveTitle"; +import { Link as RouterLink } from "react-router-dom"; +import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; + +export default function ResetPassword() { + return ( + + + + Réinitialiser mon mot de passe + + + + + + + + + + + + + ); +} From 1921d96491a8847ea348236d162bc845bd42c65d Mon Sep 17 00:00:00 2001 From: NKoelblen Date: Thu, 26 Mar 2026 17:21:23 +0100 Subject: [PATCH 02/22] Add logout functionality and update account management UI --- src/App.tsx | 2 ++ src/layout/admin/adminHeader.tsx | 4 ++++ src/pages/admin/accountPage.tsx | 12 +++++++++--- src/pages/public/logoutPage.tsx | 14 ++++++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 src/pages/public/logoutPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 8ac349d..6b39d11 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import Dashboard from "./pages/admin/dashboardPage"; import Account from "./pages/admin/accountPage"; import Login from "./pages/public/loginPage"; import ResetPassword from "./pages/public/resetPasswordPage"; +import LogoutPage from "./pages/public/logoutPage"; export default function App() { return ( @@ -24,6 +25,7 @@ export default function App() { } /> } /> + } /> } /> {/* Admin routes */} Mon compte + ); diff --git a/src/pages/admin/accountPage.tsx b/src/pages/admin/accountPage.tsx index 6d9e91f..944eb74 100644 --- a/src/pages/admin/accountPage.tsx +++ b/src/pages/admin/accountPage.tsx @@ -75,9 +75,15 @@ export default function Account() { } }; + const handleSubmit = () => { + // @TODO: Implémenter la logique de soumission du formulaire (vérification du mot de passe actuel, mise à jour des informations, etc.) + }; + return ( - - Mon compte + + + Mon compte + - diff --git a/src/pages/public/logoutPage.tsx b/src/pages/public/logoutPage.tsx index e636c1c..8cfdfd7 100644 --- a/src/pages/public/logoutPage.tsx +++ b/src/pages/public/logoutPage.tsx @@ -4,7 +4,6 @@ import { useNavigate } from "react-router-dom"; export default function LogoutPage() { const navigate = useNavigate(); useEffect(() => { - // Clear authentication tokens (localStorage, cookies, etc.) localStorage.removeItem("token"); localStorage.removeItem("refreshToken"); // TODO: Ajouter ici la suppression de cookies si besoin diff --git a/src/services/authService.ts b/src/services/authService.ts new file mode 100644 index 0000000..5522592 --- /dev/null +++ b/src/services/authService.ts @@ -0,0 +1,29 @@ +export async function loginApi( + login: string, + password: string, +): Promise { + try { + const response = await fetch("http://localhost:4000/4ntjnra6", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ login, password }), + }); + if (!response.ok) { + throw new Error("Échec de l'authentification"); + } + const data = await response.json(); + if (!data.token) { + throw new Error("Token manquant dans la réponse"); + } + return data.token; + } catch (err: any) { + if (err instanceof TypeError && err.message === "Failed to fetch") { + throw new Error( + "Impossible de contacter l'API. Vérifiez que le serveur est bien démarré et que CORS est autorisé.", + ); + } + throw err; + } +} diff --git a/src/utils/authUtils.ts b/src/utils/authUtils.ts new file mode 100644 index 0000000..2962b2b --- /dev/null +++ b/src/utils/authUtils.ts @@ -0,0 +1,4 @@ +export function isAuthenticated() { + // Ex: vérifier la présence d'un token + return Boolean(localStorage.getItem("token")); +} From a190f70b94bf12d9bc00df0fd79469b48b1e5f31 Mon Sep 17 00:00:00 2001 From: NKoelblen Date: Thu, 26 Mar 2026 19:08:54 +0100 Subject: [PATCH 05/22] Remove sensitive environment variables and update .gitignore to include .env file --- .env | 3 --- .gitignore | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 19fc545..0000000 --- a/.env +++ /dev/null @@ -1,3 +0,0 @@ -VITE_API_URL = http://localhost:4000 -VITE_ENVIRONMENT = development -VITE_API_TOKEN = 4!yHZwaR2yBt@LVA \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2107e24..e2ea484 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -# Logs logs *.log npm-debug.log* @@ -23,3 +22,5 @@ dist-ssr *.njsproj *.sln *.sw? + +.env \ No newline at end of file From f2deb683a6ae5ec33817a503c99825cef7141ad6 Mon Sep 17 00:00:00 2001 From: NKoelblen Date: Thu, 26 Mar 2026 19:24:27 +0100 Subject: [PATCH 06/22] Refactor authentication routes and add URL utility function for cleaner API calls --- .env.example | 4 ++-- src/App.tsx | 3 ++- src/constants/apiConstants.ts | 2 ++ src/pages/public/resetPasswordPage.tsx | 14 +++----------- src/services/authService.ts | 5 ++++- src/utils/urlUtils.ts | 14 ++++++++++++++ 6 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 src/utils/urlUtils.ts diff --git a/.env.example b/.env.example index e89d7a7..a878b9e 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,3 @@ VITE_API_URL = http://localhost:4000 -VITE_ENVIRONMENT = development # accept "development", "production", "staging" -VITE_API_TOKEN = ${API_TOKEN_PRODUCTION} \ No newline at end of file +VITE_LOGIN_ROUTE = /login +VITE_ENVIRONMENT = development # accept "development", "staging", "production" diff --git a/src/App.tsx b/src/App.tsx index 5776221..f49edcf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import Login from "./pages/public/loginPage"; import ResetPassword from "./pages/public/resetPasswordPage"; import LogoutPage from "./pages/public/logoutPage"; import RequireAuth from "./pages/admin/requireAuth"; +import { LOGIN_ROUTE } from "./constants/apiConstants"; export default function App() { return ( @@ -24,7 +25,7 @@ export default function App() { } /> - } /> + } /> } /> } /> {/* Admin routes */} diff --git a/src/constants/apiConstants.ts b/src/constants/apiConstants.ts index e69de29..ffc385f 100644 --- a/src/constants/apiConstants.ts +++ b/src/constants/apiConstants.ts @@ -0,0 +1,2 @@ +export const API_URL = import.meta.env.VITE_API_URL || "http://localhost:4000"; +export const LOGIN_ROUTE = import.meta.env.VITE_LOGIN_ROUTE || "/login"; diff --git a/src/pages/public/resetPasswordPage.tsx b/src/pages/public/resetPasswordPage.tsx index 6334495..372ffef 100644 --- a/src/pages/public/resetPasswordPage.tsx +++ b/src/pages/public/resetPasswordPage.tsx @@ -1,12 +1,4 @@ -import { - TextField, - IconButton, - InputAdornment, - Button, - Link, -} from "@mui/material"; -import Icon from "@mdi/react"; -import { mdiEyeOff, mdiEye } from "@mdi/js"; +import { TextField, Button } from "@mui/material"; import { ResponsivePaper, ResponsiveStack, @@ -14,7 +6,7 @@ import { import RootPaper from "../../layout/rootPaper"; import ResponsiveTitle from "../../components/responsiveTitle"; import { Link as RouterLink } from "react-router-dom"; -import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; +import { LOGIN_ROUTE } from "../../constants/apiConstants"; export default function ResetPassword() { return ( @@ -60,7 +52,7 @@ export default function ResetPassword() { fullWidth sx={{ textWrap: "nowrap" }} component={RouterLink} - to="/4ntjnra6" + to={`/${LOGIN_ROUTE}`} > Revenir à la page de connexion diff --git a/src/services/authService.ts b/src/services/authService.ts index 5522592..587ee1f 100644 --- a/src/services/authService.ts +++ b/src/services/authService.ts @@ -1,9 +1,12 @@ +import { API_URL, LOGIN_ROUTE } from "../constants/apiConstants"; +import { joinUrl } from "../utils/urlUtils"; + export async function loginApi( login: string, password: string, ): Promise { try { - const response = await fetch("http://localhost:4000/4ntjnra6", { + const response = await fetch(joinUrl(API_URL, LOGIN_ROUTE), { method: "POST", headers: { "Content-Type": "application/json", diff --git a/src/utils/urlUtils.ts b/src/utils/urlUtils.ts new file mode 100644 index 0000000..a5e6a8a --- /dev/null +++ b/src/utils/urlUtils.ts @@ -0,0 +1,14 @@ +/** + * Concatène proprement une base d'URL et un chemin, sans double slash. + * @param baseUrl ex: http://localhost:4000/ + * @param path ex: /4ntjnra6 + * @returns string ex: http://localhost:4000/4ntjnra6 + */ +export function joinUrl(baseUrl: string, path: string): string { + try { + return new URL(path, baseUrl).href; + } catch (err) { + console.error("URL invalide:", err); + return baseUrl + path; + } +} From 6245f3ef3814f324b647d409f58ff36f4deab2d8 Mon Sep 17 00:00:00 2001 From: NKoelblen Date: Fri, 27 Mar 2026 06:59:58 +0100 Subject: [PATCH 07/22] Implement centralized authentication context and refactor authentication flow --- src/App.tsx | 59 ++++++++-------- src/context/AuthContext.tsx | 117 ++++++++++++++++++++++++++++++++ src/pages/admin/requireAuth.tsx | 12 +++- src/pages/public/loginPage.tsx | 27 +++++--- src/utils/authUtils.ts | 26 ++++++- 5 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 src/context/AuthContext.tsx diff --git a/src/App.tsx b/src/App.tsx index f49edcf..83d2587 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,40 +9,43 @@ import ResetPassword from "./pages/public/resetPasswordPage"; import LogoutPage from "./pages/public/logoutPage"; import RequireAuth from "./pages/admin/requireAuth"; import { LOGIN_ROUTE } from "./constants/apiConstants"; +import { AuthProvider } from "./context/AuthContext"; export default function App() { return ( - - {/* FrontOffice routes */} - - - } /> - - - } - /> - } /> - } /> - } /> - {/* Admin routes */} - - + + + {/* FrontOffice routes */} + - } /> - } /> + } /> - - - } - /> - + + } + /> + } /> + } /> + } /> + {/* Admin routes */} + + + + } /> + } /> + + + + } + /> + + ); } diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx new file mode 100644 index 0000000..007d9f0 --- /dev/null +++ b/src/context/AuthContext.tsx @@ -0,0 +1,117 @@ +import { + createContext, + useContext, + useEffect, + useState, + type ReactNode, +} from "react"; +import { useNavigate } from "react-router-dom"; +import { loginApi } from "../services/authService"; + +interface AuthContextType { + isAuthenticated: boolean; + loading: boolean; + user: any; + login: (login: string, password: string) => Promise; + logout: () => void; + checkAuth: () => Promise; +} + +const AuthContext = createContext(undefined); + +export function AuthProvider({ children }: { children: ReactNode }) { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [loading, setLoading] = useState(true); + const [user, setUser] = useState(null); + const navigate = useNavigate(); + + // Vérifie le token auprès de l'API + const checkAuth = async (): Promise => { + const token = localStorage.getItem("token"); + if (!token) { + setIsAuthenticated(false); + setUser(null); + return false; + } + try { + const apiUrl = import.meta.env.VITE_API_URL || "http://localhost:4000"; + const url = new URL("/verify-token", apiUrl).href; + const res = await fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + }, + }); + if (res.ok) { + setIsAuthenticated(true); + try { + const data = await res.json(); + setUser(data.user || null); + } catch { + setUser(null); + } + return true; + } else { + setIsAuthenticated(false); + setUser(null); + localStorage.removeItem("token"); + return false; + } + } catch (err) { + setIsAuthenticated(false); + setUser(null); + localStorage.removeItem("token"); + return false; + } + }; + + // Login centralisé + const login = async ( + loginValue: string, + password: string, + ): Promise => { + setLoading(true); + try { + const token = await loginApi(loginValue, password); + localStorage.setItem("token", token); + const ok = await checkAuth(); + setLoading(false); + return ok; + } catch (err) { + setLoading(false); + throw err; + } + }; + + // Logout centralisé + const logout = () => { + localStorage.removeItem("token"); + setIsAuthenticated(false); + setUser(null); + navigate("/"); + }; + + // Vérification initiale au chargement + useEffect(() => { + (async () => { + setLoading(true); + await checkAuth(); + setLoading(false); + })(); + // eslint-disable-next-line + }, []); + + return ( + + {children} + + ); +} + +export function useAuth() { + const ctx = useContext(AuthContext); + if (!ctx) throw new Error("useAuth must be used within an AuthProvider"); + return ctx; +} diff --git a/src/pages/admin/requireAuth.tsx b/src/pages/admin/requireAuth.tsx index 3d25e80..2ed7611 100644 --- a/src/pages/admin/requireAuth.tsx +++ b/src/pages/admin/requireAuth.tsx @@ -1,5 +1,6 @@ import { Navigate, useLocation } from "react-router-dom"; -import { isAuthenticated } from "../../utils/authUtils"; +import { useAuth } from "../../context/AuthContext"; +import { useEffect } from "react"; export default function RequireAuth({ children, @@ -7,7 +8,14 @@ export default function RequireAuth({ children: React.ReactNode; }) { const location = useLocation(); - if (!isAuthenticated()) { + const { isAuthenticated, loading, checkAuth } = useAuth(); + useEffect(() => { + // Vérifie le token à chaque navigation + checkAuth(); + // eslint-disable-next-line + }, [location.pathname]); + if (loading) return null; + if (!isAuthenticated) { return ; } return <>{children}; diff --git a/src/pages/public/loginPage.tsx b/src/pages/public/loginPage.tsx index 0c56c3a..044ba32 100644 --- a/src/pages/public/loginPage.tsx +++ b/src/pages/public/loginPage.tsx @@ -16,33 +16,44 @@ import RootPaper from "../../layout/rootPaper"; import ResponsiveTitle from "../../components/responsiveTitle"; import { Link as RouterLink, useNavigate } from "react-router-dom"; import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; -import { loginApi } from "../../services/authService"; +import { useAuth } from "../../context/AuthContext"; +import { useEffect } from "react"; export default function Login() { const [showPassword, setShowPassword] = useState(false); const [login, setLogin] = useState(""); const [password, setPassword] = useState(""); - const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const navigate = useNavigate(); + const { login: loginContext, loading, isAuthenticated } = useAuth(); + const [redirecting, setRedirecting] = useState(false); + + useEffect(() => { + if (isAuthenticated) { + setRedirecting(true); + navigate("/admin", { replace: true }); + } + }, [isAuthenticated, navigate]); const handleTogglePassword = () => setShowPassword((v) => !v); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(""); - setLoading(true); try { - const token = await loginApi(login, password); - localStorage.setItem("token", token); - navigate("/admin"); + const ok = await loginContext(login, password); + if (ok) { + navigate("/admin"); + } else { + setError("Échec de l'authentification"); + } } catch (err: any) { setError(err.message || "Erreur de connexion"); - } finally { - setLoading(false); } }; + if (loading || redirecting) return null; + return ( now; } From 3f319907ff18dd25aec5073f02d6878638b07c7b Mon Sep 17 00:00:00 2001 From: NKoelblen Date: Fri, 27 Mar 2026 07:34:15 +0100 Subject: [PATCH 08/22] Refactor account page: implement user info fetching and add PasswordField component for better password management --- src/components/passwordField.tsx | 52 +++++ src/pages/admin/accountPage.tsx | 329 ++++++++++++++++--------------- src/types/baseComponent.ts | 8 + 3 files changed, 229 insertions(+), 160 deletions(-) create mode 100644 src/components/passwordField.tsx create mode 100644 src/types/baseComponent.ts diff --git a/src/components/passwordField.tsx b/src/components/passwordField.tsx new file mode 100644 index 0000000..3a14991 --- /dev/null +++ b/src/components/passwordField.tsx @@ -0,0 +1,52 @@ +import { IconButton, InputAdornment, TextField } from "@mui/material"; +import Icon from "@mdi/react"; +import { mdiEyeOff, mdiEye } from "@mdi/js"; +import { useState } from "react"; +import type { PasswordFieldProps } from "../types/baseComponent"; + +export default function PasswordField({ + label, + value, + onChange, + error, + helperText, + errorText, +}: PasswordFieldProps) { + const [showPassword, setShowPassword] = useState(false); + + return ( + + setShowPassword((v) => !v)} + edge="end" + aria-label={ + showPassword + ? "Masquer le mot de passe" + : "Afficher le mot de passe" + } + > + {showPassword ? ( + + ) : ( + + )} + + + ), + }, + }} + /> + ); +} diff --git a/src/pages/admin/accountPage.tsx b/src/pages/admin/accountPage.tsx index 944eb74..c43e53b 100644 --- a/src/pages/admin/accountPage.tsx +++ b/src/pages/admin/accountPage.tsx @@ -1,30 +1,51 @@ -import { - Button, - IconButton, - InputAdornment, - Link, - TextField, -} from "@mui/material"; +import { Button, Link, TextField } from "@mui/material"; import ResponsiveTitle from "../../components/responsiveTitle"; -import { useState } from "react"; -import Icon from "@mdi/react"; -import { mdiEyeOff, mdiEye } from "@mdi/js"; +import { useState, useEffect } from "react"; import { ResponsiveStack } from "../../components/ResponsiveLayout"; import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; +import PasswordField from "../../components/passwordField"; export default function Account() { - const initialLogin = "onoko"; - const initialEmail = "hello@onoko.dev"; - const [login, setLogin] = useState(initialLogin); - const [email, setEmail] = useState(initialEmail); - const [showPassword, setShowPassword] = useState(false); + const [login, setLogin] = useState(""); + const [email, setEmail] = useState(""); + const [initialUser, setInitialUser] = useState<{login: string; email: string} | null>(null); + const [loadingUser, setLoadingUser] = useState(true); + const [userError, setUserError] = useState(""); const [currentPassword, setCurrentPassword] = useState(""); + const [submitError, setSubmitError] = useState(""); + const [submitSuccess, setSubmitSuccess] = useState(null); + const [submitting, setSubmitting] = useState(false); const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [passwordError, setPasswordError] = useState(""); const [newPasswordError, setNewPasswordError] = useState(""); - const handleTogglePassword = () => setShowPassword((v) => !v); + useEffect(() => { + const fetchUserInfo = async () => { + setLoadingUser(true); + setUserError(""); + try { + const token = localStorage.getItem("token"); + if (!token) throw new Error("Token manquant"); + const res = await fetch("http://localhost:4000/account", { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + if (!res.ok) + throw new Error("Erreur lors de la récupération du compte"); + const data = await res.json(); + setLogin(data.login || ""); + setEmail(data.email || ""); + setInitialUser({ login: data.login || "", email: data.email || "" }); + } catch (e: any) { + setUserError(e.message || "Erreur inconnue"); + } finally { + setLoadingUser(false); + } + }; + fetchUserInfo(); + }, []); const validatePassword = (pwd: string) => { if (!pwd) return ""; @@ -75,8 +96,42 @@ export default function Account() { } }; - const handleSubmit = () => { - // @TODO: Implémenter la logique de soumission du formulaire (vérification du mot de passe actuel, mise à jour des informations, etc.) + const handleSubmit = async () => { + setSubmitError(""); + setSubmitSuccess(null); + setSubmitting(true); + try { + if (!initialUser) throw new Error("Utilisateur non chargé"); + const token = localStorage.getItem("token"); + if (!token) throw new Error("Token manquant"); + const payload: any = { oldPassword: currentPassword }; + if (login !== initialUser.login) payload.login = login; + if (email !== initialUser.email) payload.email = email; + if (newPassword) payload.newPassword = newPassword; + const res = await fetch("http://localhost:4000/account", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(payload), + }); + const data = await res.json(); + if (!res.ok || !data.success) { + throw new Error(data.message || "Échec de la mise à jour"); + } + setSubmitSuccess(true); + // Mettre à jour l'état initial pour refléter les nouvelles valeurs + setInitialUser({ login, email }); + setNewPassword(""); + setConfirmPassword(""); + setCurrentPassword(""); + } catch (e: any) { + setSubmitError(e.message || "Erreur inconnue"); + setSubmitSuccess(false); + } finally { + setSubmitting(false); + } }; return ( @@ -84,164 +139,118 @@ export default function Account() { Mon compte - - - setLogin(e.target.value)} - /> - setEmail(e.target.value)} - /> - - - - + {loadingUser ? ( +
+ Chargement... +
+ ) : userError ? ( +
+ {userError} +
+ ) : ( + + - - {showPassword ? ( - - ) : ( - - )} - - - ), - }, - }} + value={login} + onChange={(e) => setLogin(e.target.value)} /> - - Mot de passe oublié ?{" "} - Réinitialiser mon mot de passe - - - - - {showPassword ? ( - - ) : ( - - )} - - - ), - }, - }} - /> - - - {showPassword ? ( - - ) : ( - - )} - - - ), - }, - }} + type="email" + value={email} + onChange={(e) => setEmail(e.target.value)} /> + + + + + + Mot de passe oublié ?{" "} + + Réinitialiser mon mot de passe + + + + + + + + -
+ )} + {submitError && ( +
{submitError}
+ )} + {submitSuccess && ( +
Mise à jour réussie !
+ )}
); diff --git a/src/types/baseComponent.ts b/src/types/baseComponent.ts new file mode 100644 index 0000000..a92fe9f --- /dev/null +++ b/src/types/baseComponent.ts @@ -0,0 +1,8 @@ +export interface PasswordFieldProps { + label: string; + value: string; + onChange: (e: React.ChangeEvent) => void; + error?: boolean; + helperText?: string; + errorText?: string; +} From 33b16d77f3bbf2c58509742db26b05d9feed8b64 Mon Sep 17 00:00:00 2001 From: NKoelblen Date: Fri, 27 Mar 2026 07:48:24 +0100 Subject: [PATCH 09/22] Refactor password field component: add required prop support and update usage in login and account pages; introduce ResetPasswordLink component for better password recovery navigation --- src/components/passwordField.tsx | 3 +- src/components/resetPasswordLink.tsx | 11 +++++ src/pages/admin/accountPage.tsx | 67 ++++++++++++++------------ src/pages/public/loginPage.tsx | 38 +++------------ src/pages/public/resetPasswordPage.tsx | 10 ++-- src/types/baseComponent.ts | 1 + 6 files changed, 64 insertions(+), 66 deletions(-) create mode 100644 src/components/resetPasswordLink.tsx diff --git a/src/components/passwordField.tsx b/src/components/passwordField.tsx index 3a14991..3fba22b 100644 --- a/src/components/passwordField.tsx +++ b/src/components/passwordField.tsx @@ -11,6 +11,7 @@ export default function PasswordField({ error, helperText, errorText, + required, }: PasswordFieldProps) { const [showPassword, setShowPassword] = useState(false); @@ -19,7 +20,7 @@ export default function PasswordField({ label={label} type={showPassword ? "text" : "password"} fullWidth - required + required={required} value={value} onChange={onChange} error={error} diff --git a/src/components/resetPasswordLink.tsx b/src/components/resetPasswordLink.tsx new file mode 100644 index 0000000..9c17d78 --- /dev/null +++ b/src/components/resetPasswordLink.tsx @@ -0,0 +1,11 @@ +import { Link } from "@mui/material"; +import ResponsiveBodyTypography from "./responsiveBodyTypography"; + +export default function ResetPasswordLink() { + return ( + + Mot de passe oublié ?{" "} + Réinitialiser mon mot de passe + + ); +} diff --git a/src/pages/admin/accountPage.tsx b/src/pages/admin/accountPage.tsx index c43e53b..189ec56 100644 --- a/src/pages/admin/accountPage.tsx +++ b/src/pages/admin/accountPage.tsx @@ -4,16 +4,20 @@ import { useState, useEffect } from "react"; import { ResponsiveStack } from "../../components/ResponsiveLayout"; import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; import PasswordField from "../../components/passwordField"; +import ResetPasswordLink from "../../components/resetPasswordLink"; export default function Account() { const [login, setLogin] = useState(""); const [email, setEmail] = useState(""); - const [initialUser, setInitialUser] = useState<{login: string; email: string} | null>(null); + const [initialUser, setInitialUser] = useState<{ + login: string; + email: string; + } | null>(null); const [loadingUser, setLoadingUser] = useState(true); const [userError, setUserError] = useState(""); const [currentPassword, setCurrentPassword] = useState(""); const [submitError, setSubmitError] = useState(""); - const [submitSuccess, setSubmitSuccess] = useState(null); + const [submitSuccess, setSubmitSuccess] = useState(null); const [submitting, setSubmitting] = useState(false); const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); @@ -189,20 +193,19 @@ export default function Account() { value={currentPassword} onChange={handleCurrentPasswordChange} error={ - !!(initialUser && (login !== initialUser.login || - email !== initialUser.email || - newPassword !== "" || - confirmPassword !== "") && - !currentPassword) + !!( + initialUser && + (login !== initialUser.login || + email !== initialUser.email || + newPassword !== "" || + confirmPassword !== "") && + !currentPassword + ) } + required errorText="Le mot de passe est obligatoire pour valider les changements." /> - - Mot de passe oublié ?{" "} - - Réinitialiser mon mot de passe - - +
{submitError} )} {submitSuccess && ( -
Mise à jour réussie !
+
+ Mise à jour réussie ! +
)} diff --git a/src/pages/public/loginPage.tsx b/src/pages/public/loginPage.tsx index 044ba32..7b963d7 100644 --- a/src/pages/public/loginPage.tsx +++ b/src/pages/public/loginPage.tsx @@ -18,6 +18,8 @@ import { Link as RouterLink, useNavigate } from "react-router-dom"; import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; import { useAuth } from "../../context/AuthContext"; import { useEffect } from "react"; +import PasswordField from "../../components/passwordField"; +import ResetPasswordLink from "../../components/resetPasswordLink"; export default function Login() { const [showPassword, setShowPassword] = useState(false); @@ -93,38 +95,13 @@ export default function Login() { disabled={loading} required /> - setPassword(e.target.value)} - autoComplete="current-password" - disabled={loading} + error={!!error} + errorText={error} required - slotProps={{ - input: { - endAdornment: ( - - - {showPassword ? ( - - ) : ( - - )} - - - ), - }, - }} />
{error && ( @@ -163,10 +140,7 @@ export default function Login() { {loading ? "Connexion..." : "Me connecter"}
- - Mot de passe oublié ?{" "} - Réinitialiser mon mot de passe - +
diff --git a/src/pages/public/resetPasswordPage.tsx b/src/pages/public/resetPasswordPage.tsx index 372ffef..c295940 100644 --- a/src/pages/public/resetPasswordPage.tsx +++ b/src/pages/public/resetPasswordPage.tsx @@ -7,8 +7,11 @@ import RootPaper from "../../layout/rootPaper"; import ResponsiveTitle from "../../components/responsiveTitle"; import { Link as RouterLink } from "react-router-dom"; import { LOGIN_ROUTE } from "../../constants/apiConstants"; +import { useAuth } from "../../context/AuthContext"; export default function ResetPassword() { + const { isAuthenticated } = useAuth ? useAuth() : { isAuthenticated: false }; + return ( - Revenir à la page de connexion + {isAuthenticated + ? "Revenir à l'espace administrateur" + : "Revenir à la page de connexion"} + + + + + + ); +} diff --git a/src/pages/public/resetPasswordPage.tsx b/src/pages/public/resetPasswordPage.tsx index c295940..67ed8d1 100644 --- a/src/pages/public/resetPasswordPage.tsx +++ b/src/pages/public/resetPasswordPage.tsx @@ -1,16 +1,56 @@ -import { TextField, Button } from "@mui/material"; +import { Button } from "@mui/material"; import { ResponsivePaper, ResponsiveStack, } from "../../components/ResponsiveLayout"; -import RootPaper from "../../layout/rootPaper"; import ResponsiveTitle from "../../components/responsiveTitle"; -import { Link as RouterLink } from "react-router-dom"; -import { LOGIN_ROUTE } from "../../constants/apiConstants"; +import RootPaper from "../../layout/rootPaper"; +import { useState } from "react"; +import { useSearchParams } from "react-router-dom"; +import NewPasswordFields from "../../components/newPasswordFields"; import { useAuth } from "../../context/AuthContext"; +import { Link as RouterLink } from "react-router-dom"; +import { API_URL, LOGIN_ROUTE } from "../../constants/apiConstants"; export default function ResetPassword() { const { isAuthenticated } = useAuth ? useAuth() : { isAuthenticated: false }; + const [searchParams] = useSearchParams(); + const token = searchParams.get("token") || ""; + + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [newPasswordError, setNewPasswordError] = useState(""); + const [confirmPasswordError, setConfirmPasswordError] = useState(""); + + const [submitError, setSubmitError] = useState(""); + const [submitSuccess, setSubmitSuccess] = useState(false); + const [submitting, setSubmitting] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setSubmitError(""); + setSubmitSuccess(false); + setSubmitting(true); + try { + const res = await fetch(`${API_URL}/auth/reset/confirm`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ token, newPassword }), + }); + const data = await res.json(); + if (!res.ok || !data.success) { + throw new Error( + data.message || "Échec de la réinitialisation du mot de passe", + ); + } + setSubmitSuccess(true); + } catch (e: any) { + setSubmitError(e.message || "Erreur inconnue"); + setSubmitSuccess(false); + } finally { + setSubmitting(false); + } + }; return ( - + + {submitError && ( +
{submitError}
+ )} + {submitSuccess && ( +
+ Votre mot de passe a été réinitialisé avec succès. +
+ )} - {isAuthenticated - ? "Revenir à l'espace administrateur" - : "Revenir à la page de connexion"} + Revenir à + {isAuthenticated ? ( + <> l'espace administrateur + ) : ( + <> la page de connexion + )} - From 10373befd81a88e81e7fc3fce6f481a98e9d39b3 Mon Sep 17 00:00:00 2001 From: NKoelblen Date: Fri, 27 Mar 2026 15:20:47 +0100 Subject: [PATCH 11/22] Refactor and enhance components: update routing comments for clarity, improve prop types in responsive components, and add detailed documentation for new password fields and authentication context. Remove deprecated responsive component types and introduce new types for better type safety. --- src/App.tsx | 7 +++-- src/components/ResponsiveLayout.tsx | 8 +++--- src/components/newPasswordFields.tsx | 27 ++++++++++++------- src/components/passwordField.tsx | 10 +++++++ src/components/resetPasswordLink.tsx | 4 +++ src/components/responsiveBodyTypography.tsx | 9 ++++++- src/components/responsiveTitle.tsx | 9 ++++++- src/constants/apiConstants.ts | 5 ++++ src/context/AuthContext.tsx | 25 ++++++----------- src/hooks/useAuth.tsx | 14 ++++++++++ src/layout/admin/adminHeader.tsx | 3 +++ src/layout/admin/adminLayout.tsx | 4 +++ src/layout/public/publicFooter.tsx | 4 +++ src/layout/public/publicHeader.tsx | 4 +++ src/layout/public/publicLayout.tsx | 4 +++ src/layout/rootPaper.tsx | 9 ++++++- src/pages/admin/accountPage.tsx | 25 +++++++++++------ src/pages/admin/dashboardPage.tsx | 3 +++ src/pages/admin/requireAuth.tsx | 12 ++++++--- src/pages/public/homePage.tsx | 5 +++- src/pages/public/loginPage.tsx | 19 +++++++++---- src/pages/public/logoutPage.tsx | 4 +++ src/pages/public/requestResetPasswordPage.tsx | 13 ++++++--- src/pages/public/resetPasswordPage.tsx | 9 +++++-- src/services/appolloClient.ts | 4 +++ src/services/authService.ts | 5 ++++ src/theme.ts | 14 ++++++++-- src/types/authTypes.ts | 8 ++++++ src/types/baseComponent.ts | 11 ++++++++ ...onsiveComponents.ts => responsiveTypes.ts} | 14 ++++++++++ src/utils/authUtils.ts | 9 +++++-- src/utils/responsiveUtils.ts | 15 ++++++++++- src/utils/urlUtils.ts | 6 ++--- 33 files changed, 257 insertions(+), 65 deletions(-) create mode 100644 src/hooks/useAuth.tsx create mode 100644 src/types/authTypes.ts rename src/types/{responsiveComponents.ts => responsiveTypes.ts} (60%) diff --git a/src/App.tsx b/src/App.tsx index b173b28..579d15e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,7 +17,7 @@ export default function App() { - {/* FrontOffice routes */} + {/* Routes publiques */} } /> + + {/* Routes d'authentification */} } /> } /> } /> } /> - {/* Admin routes */} + + {/* Routes admin */} ( Component: React.ElementType, diff --git a/src/components/newPasswordFields.tsx b/src/components/newPasswordFields.tsx index 7fa764a..4466374 100644 --- a/src/components/newPasswordFields.tsx +++ b/src/components/newPasswordFields.tsx @@ -1,5 +1,18 @@ +import type { NewPasswordFieldsProps } from "../types/baseComponent"; import PasswordField from "./passwordField"; +/** + * Composant pour les champs de saisie du nouveau mot de passe et de sa confirmation. + * Gère la validation du mot de passe et l'affichage des erreurs associées. + * @param props.newPassword Le mot de passe saisi par l'utilisateur. + * @param props.setNewPassword Fonction pour mettre à jour le mot de passe saisi. + * @param props.confirmPassword Le mot de passe de confirmation saisi par l'utilisateur. + * @param props.setConfirmPassword Fonction pour mettre à jour le mot de passe de confirmation. + * @param props.newPasswordError Message d'erreur lié au mot de passe saisi. + * @param props.setNewPasswordError Fonction pour mettre à jour le message d'erreur du mot de passe. + * @param props.confirmPasswordError Message d'erreur lié au mot de passe de confirmation. + * @param props.setConfirmPasswordError Fonction pour mettre à jour le message d'erreur du mot de passe de confirmation. + */ export default function NewPasswordFields({ newPassword, setNewPassword, @@ -9,16 +22,8 @@ export default function NewPasswordFields({ setNewPasswordError, confirmPasswordError, setConfirmPasswordError, -}: { - newPassword: string; - setNewPassword: (pwd: string) => void; - confirmPassword: string; - setConfirmPassword: (pwd: string) => void; - newPasswordError: string; - setNewPasswordError: (msg: string) => void; - confirmPasswordError: string; - setConfirmPasswordError: (msg: string) => void; -}) { +}: NewPasswordFieldsProps) { + // Fonction de validation du mot de passe selon les critères définis. const validatePassword = (pwd: string) => { if (!pwd) return ""; if (pwd.length < 20) { @@ -39,6 +44,7 @@ export default function NewPasswordFields({ return ""; }; + // Gestion du changement de valeur du champ de nouveau mot de passe, avec validation en temps réel. const handleNewPasswordChange = (e: React.ChangeEvent) => { const value = e.target.value; setNewPassword(value); @@ -51,6 +57,7 @@ export default function NewPasswordFields({ } }; + // Gestion du changement de valeur du champ de confirmation du mot de passe, avec validation en temps réel. const handleConfirmPasswordChange = ( e: React.ChangeEvent, ) => { diff --git a/src/components/passwordField.tsx b/src/components/passwordField.tsx index 3fba22b..f31c3f7 100644 --- a/src/components/passwordField.tsx +++ b/src/components/passwordField.tsx @@ -4,6 +4,16 @@ import { mdiEyeOff, mdiEye } from "@mdi/js"; import { useState } from "react"; import type { PasswordFieldProps } from "../types/baseComponent"; +/** + * Composant de champ de mot de passe avec option d'affichage du mot de passe. + * @param {string} props.label Le label du champ. + * @param {string} props.value La valeur actuelle du champ. + * @param {function} props.onChange Fonction de rappel pour gérer les changements de valeur. + * @param {boolean} props.error Indique si le champ est en erreur. + * @param {string} props.helperText Texte d'aide à afficher sous le champ. + * @param {string} props.errorText Texte d'erreur à afficher lorsque le champ est en erreur. + * @param {boolean} props.required Indique si le champ est requis. + */ export default function PasswordField({ label, value, diff --git a/src/components/resetPasswordLink.tsx b/src/components/resetPasswordLink.tsx index 72ac868..5f505e4 100644 --- a/src/components/resetPasswordLink.tsx +++ b/src/components/resetPasswordLink.tsx @@ -1,6 +1,10 @@ import { Link } from "@mui/material"; import ResponsiveBodyTypography from "./responsiveBodyTypography"; +/** + * Composant de lien pour la réinitialisation du mot de passe. + * Affiche un message invitant l'utilisateur à réinitialiser son mot de passe en cas d'oubli. + */ export default function ResetPasswordLink() { return ( diff --git a/src/components/responsiveBodyTypography.tsx b/src/components/responsiveBodyTypography.tsx index e371d7e..aed5cff 100644 --- a/src/components/responsiveBodyTypography.tsx +++ b/src/components/responsiveBodyTypography.tsx @@ -1,8 +1,15 @@ import Typography from "@mui/material/Typography"; import { verticalMediaQuery } from "../theme"; import { useTheme } from "@mui/material"; -import type { ResponsiveBodyTypographyProps } from "../types/responsiveComponents"; +import type { ResponsiveBodyTypographyProps } from "../types/responsiveTypes"; +/** + * Composant de typographie pour le corps de texte avec une taille de police responsive. + * La taille de la police s'adapte en fonction de la taille de l'écran, avec des limites pour éviter que le texte ne devienne trop petit ou trop grand. + * @param {string} props.variant Le variant de typographie à utiliser (ex: "body1", "body2", etc.). + * @param {React.ReactNode} props.children Le contenu à afficher à l'intérieur du composant. + * @param {object} props.props Propriétés supplémentaires à passer au composant Typography. + */ export default function ResponsiveBodyTypography({ variant, children, diff --git a/src/components/responsiveTitle.tsx b/src/components/responsiveTitle.tsx index 8fbf152..d4b849f 100644 --- a/src/components/responsiveTitle.tsx +++ b/src/components/responsiveTitle.tsx @@ -1,8 +1,15 @@ import Typography from "@mui/material/Typography"; import { verticalMediaQuery } from "../theme"; import { useTheme } from "@mui/material"; -import type { ResponsiveTitleProps } from "../types/responsiveComponents"; +import type { ResponsiveTitleProps } from "../types/responsiveTypes"; +/** + * Composant de typographie pour les titres avec une taille de police responsive. + * La taille de la police s'adapte en fonction de la taille de l'écran, avec des limites pour éviter que le texte ne devienne trop petit ou trop grand. + * @param {string} props.variant Le variant de typographie à utiliser (ex: "h1", "h2", etc.). + * @param {React.ReactNode} props.children Le contenu à afficher à l'intérieur du composant. + * @param {object} props.props Propriétés supplémentaires à passer au composant Typography. + */ export default function ResponsiveTitle({ variant, children, diff --git a/src/constants/apiConstants.ts b/src/constants/apiConstants.ts index ffc385f..80b317b 100644 --- a/src/constants/apiConstants.ts +++ b/src/constants/apiConstants.ts @@ -1,2 +1,7 @@ +/** + * Fichier de constantes pour les URLs de l'API et les routes d'authentification. + * Les valeurs sont récupérées à partir des variables d'environnement, avec des valeurs par défaut pour le développement local. + */ + export const API_URL = import.meta.env.VITE_API_URL || "http://localhost:4000"; export const LOGIN_ROUTE = import.meta.env.VITE_LOGIN_ROUTE || "/login"; diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index 007d9f0..ad46583 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -7,18 +7,16 @@ import { } from "react"; import { useNavigate } from "react-router-dom"; import { loginApi } from "../services/authService"; +import type { AuthContextType } from "../types/authTypes"; -interface AuthContextType { - isAuthenticated: boolean; - loading: boolean; - user: any; - login: (login: string, password: string) => Promise; - logout: () => void; - checkAuth: () => Promise; -} - -const AuthContext = createContext(undefined); +export const AuthContext = createContext( + undefined, +); +/** + * Fournit le contexte d'authentification pour l'application, gérant l'état de l'utilisateur, les fonctions de login/logout, et la vérification du token. + * @param {React.ReactNode} children Les composants enfants qui auront accès au contexte d'authentification. + */ export function AuthProvider({ children }: { children: ReactNode }) { const [isAuthenticated, setIsAuthenticated] = useState(false); const [loading, setLoading] = useState(true); @@ -98,7 +96,6 @@ export function AuthProvider({ children }: { children: ReactNode }) { await checkAuth(); setLoading(false); })(); - // eslint-disable-next-line }, []); return ( @@ -109,9 +106,3 @@ export function AuthProvider({ children }: { children: ReactNode }) { ); } - -export function useAuth() { - const ctx = useContext(AuthContext); - if (!ctx) throw new Error("useAuth must be used within an AuthProvider"); - return ctx; -} diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx new file mode 100644 index 0000000..e703ed9 --- /dev/null +++ b/src/hooks/useAuth.tsx @@ -0,0 +1,14 @@ +import { useContext } from "react"; +import type { AuthContextType } from "../types/authTypes"; +import { AuthContext } from "../context/AuthContext"; + +/** + * Hook personnalisé pour accéder au contexte d'authentification. + * Doit être utilisé à l'intérieur d'un composant enveloppé par AuthProvider. + * @returns {AuthContextType} Le contexte d'authentification avec les fonctions et états disponibles. + */ +export function useAuth(): AuthContextType { + const ctx = useContext(AuthContext); + if (!ctx) throw new Error("useAuth must be used within an AuthProvider"); + return ctx; +} diff --git a/src/layout/admin/adminHeader.tsx b/src/layout/admin/adminHeader.tsx index bb8ff90..ac71a8a 100644 --- a/src/layout/admin/adminHeader.tsx +++ b/src/layout/admin/adminHeader.tsx @@ -3,6 +3,9 @@ import Toolbar from "@mui/material/Toolbar"; import Button from "@mui/material/Button"; import { Link } from "react-router-dom"; +/** + * Entête de l'espace admin, avec des liens vers les différentes sections et la déconnexion. + */ export default function AdminHeader() { return ( diff --git a/src/layout/admin/adminLayout.tsx b/src/layout/admin/adminLayout.tsx index 3fc3fd9..0331186 100644 --- a/src/layout/admin/adminLayout.tsx +++ b/src/layout/admin/adminLayout.tsx @@ -2,6 +2,10 @@ import AdminHeader from "./adminHeader"; import RootPaper from "../rootPaper"; import { ResponsivePaper } from "../../components/ResponsiveLayout"; +/** + * Layout principal de l'espace admin, avec une entête et une zone de contenu. + * Utilisé pour les pages de l'espace admin. + */ export default function AdminLayout({ children, }: { diff --git a/src/layout/public/publicFooter.tsx b/src/layout/public/publicFooter.tsx index 16b855e..7c74c6b 100644 --- a/src/layout/public/publicFooter.tsx +++ b/src/layout/public/publicFooter.tsx @@ -1,6 +1,10 @@ import { Toolbar } from "@mui/material"; import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; +/** + * Pied de page pour les pages publiques, affichant un message de copyright. + * Utilisé sur les pages d'accueil, de connexion, etc. + */ export default function PublicFooter() { return ( diff --git a/src/layout/public/publicHeader.tsx b/src/layout/public/publicHeader.tsx index ecb9ba0..ea8eb4b 100644 --- a/src/layout/public/publicHeader.tsx +++ b/src/layout/public/publicHeader.tsx @@ -3,6 +3,10 @@ import Toolbar from "@mui/material/Toolbar"; import Button from "@mui/material/Button"; import { Link as RouterLink } from "react-router-dom"; +/** + * Entête pour les pages publiques, avec des liens vers l'accueil et l'espace admin. + * Utilisé sur les pages d'accueil, de connexion, etc. + */ export default function PublicHeader() { return ( diff --git a/src/layout/public/publicLayout.tsx b/src/layout/public/publicLayout.tsx index 25d5503..936f493 100644 --- a/src/layout/public/publicLayout.tsx +++ b/src/layout/public/publicLayout.tsx @@ -3,6 +3,10 @@ import PublicFooter from "./publicFooter"; import RootPaper from "../rootPaper"; import { ResponsivePaper } from "../../components/ResponsiveLayout"; +/** + * Layout principal pour les pages publiques (accueil, connexion, etc.). + * Affiche une entête, un pied de page et une zone de contenu centrale. + */ export default function PublicLayout({ children, }: { diff --git a/src/layout/rootPaper.tsx b/src/layout/rootPaper.tsx index 35823ff..cc13621 100644 --- a/src/layout/rootPaper.tsx +++ b/src/layout/rootPaper.tsx @@ -1,7 +1,14 @@ import type { PaperProps } from "@mui/material"; import { ResponsivePaper } from "../components/ResponsiveLayout"; -import type { ResponsiveLayoutProps } from "../types/responsiveComponents"; +import type { ResponsiveLayoutProps } from "../types/responsiveTypes"; +/** + * Composant de layout principal pour les pages de l'application. + * Utilise un ResponsivePaper pour créer une zone de contenu centrale avec une hauteur minimale de 100vh. + * Permet d'aligner le contenu au centre de la page et d'ajouter des styles personnalisés via les props. + * @param {React.ReactNode} props.children Le contenu à afficher à l'intérieur du layout. + * @param {object} props.props Propriétés supplémentaires à passer au composant ResponsivePaper. + */ export default function RootPaper({ children, ...props diff --git a/src/pages/admin/accountPage.tsx b/src/pages/admin/accountPage.tsx index cb201e1..49240d1 100644 --- a/src/pages/admin/accountPage.tsx +++ b/src/pages/admin/accountPage.tsx @@ -6,24 +6,31 @@ import PasswordField from "../../components/passwordField"; import ResetPasswordLink from "../../components/resetPasswordLink"; import NewPasswordFields from "../../components/newPasswordFields"; +/** + * Page de gestion du compte utilisateur dans l'espace admin. + * Permet de voir et modifier le login, l'email et le mot de passe du compte. + */ export default function Account() { - const [login, setLogin] = useState(""); - const [email, setEmail] = useState(""); + const [loadingUser, setLoadingUser] = useState(true); const [initialUser, setInitialUser] = useState<{ login: string; email: string; } | null>(null); - const [loadingUser, setLoadingUser] = useState(true); + const [login, setLogin] = useState(""); + const [email, setEmail] = useState(""); const [userError, setUserError] = useState(""); + const [currentPassword, setCurrentPassword] = useState(""); - const [submitError, setSubmitError] = useState(""); - const [submitSuccess, setSubmitSuccess] = useState(null); - const [submitting, setSubmitting] = useState(false); - const [newPassword, setNewPassword] = useState(""); - const [confirmPassword, setConfirmPassword] = useState(""); const [passwordError, setPasswordError] = useState(""); + const [newPassword, setNewPassword] = useState(""); const [newPasswordError, setNewPasswordError] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + + const [submitting, setSubmitting] = useState(false); + const [submitSuccess, setSubmitSuccess] = useState(null); + const [submitError, setSubmitError] = useState(""); + // Récupération des informations du compte à l'affichage de la page, avec gestion du chargement et des erreurs. useEffect(() => { const fetchUserInfo = async () => { setLoadingUser(true); @@ -51,12 +58,14 @@ export default function Account() { fetchUserInfo(); }, []); + // Gestion des changements du mot de passe actuel pour valider les changements de compte. const handleCurrentPasswordChange = ( e: React.ChangeEvent, ) => { setCurrentPassword(e.target.value); }; + // Gestion de la soumission du formulaire de mise à jour du compte, avec validation et affichage des erreurs/succès. const handleSubmit = async () => { setSubmitError(""); setSubmitSuccess(null); diff --git a/src/pages/admin/dashboardPage.tsx b/src/pages/admin/dashboardPage.tsx index 38cd7b6..93b8fd5 100644 --- a/src/pages/admin/dashboardPage.tsx +++ b/src/pages/admin/dashboardPage.tsx @@ -1,5 +1,8 @@ import ResponsiveTitle from "../../components/responsiveTitle"; +/** + * Page d'accueil de l'espace admin. + */ export default function Dashboard() { return Espace Admin; } diff --git a/src/pages/admin/requireAuth.tsx b/src/pages/admin/requireAuth.tsx index 2ed7611..2b778d4 100644 --- a/src/pages/admin/requireAuth.tsx +++ b/src/pages/admin/requireAuth.tsx @@ -1,7 +1,11 @@ import { Navigate, useLocation } from "react-router-dom"; -import { useAuth } from "../../context/AuthContext"; import { useEffect } from "react"; +import { useAuth } from "../../hooks/useAuth"; +/** + * Composant de protection des routes de l'espace admin. Redirige vers la page de login si l'utilisateur n'est pas authentifié. + * Vérifie le token à chaque navigation pour assurer la sécurité. + */ export default function RequireAuth({ children, }: { @@ -9,14 +13,16 @@ export default function RequireAuth({ }) { const location = useLocation(); const { isAuthenticated, loading, checkAuth } = useAuth(); + + // Vérifie l'authentification à chaque changement de route pour s'assurer que l'utilisateur est toujours authentifié. useEffect(() => { - // Vérifie le token à chaque navigation checkAuth(); - // eslint-disable-next-line }, [location.pathname]); + if (loading) return null; if (!isAuthenticated) { return ; } + return <>{children}; } diff --git a/src/pages/public/homePage.tsx b/src/pages/public/homePage.tsx index 3a828e4..406632a 100644 --- a/src/pages/public/homePage.tsx +++ b/src/pages/public/homePage.tsx @@ -3,9 +3,12 @@ import ResponsiveTitle from "../../components/responsiveTitle"; import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; import { ResponsivePaper } from "../../components/ResponsiveLayout"; +/** + * Page d'accueil publique du site. + */ export default function Home() { const theme = useTheme(); - console.log(theme); + return ( <> Hello World ! diff --git a/src/pages/public/loginPage.tsx b/src/pages/public/loginPage.tsx index b5ffcaf..600db02 100644 --- a/src/pages/public/loginPage.tsx +++ b/src/pages/public/loginPage.tsx @@ -8,19 +8,22 @@ import RootPaper from "../../layout/rootPaper"; import ResponsiveTitle from "../../components/responsiveTitle"; import { Link as RouterLink, useNavigate } from "react-router-dom"; import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; -import { useAuth } from "../../context/AuthContext"; import { useEffect } from "react"; import PasswordField from "../../components/passwordField"; import ResetPasswordLink from "../../components/resetPasswordLink"; +import { useAuth } from "../../hooks/useAuth"; +/** + * Page de connexion à l'espace admin. + * Permet aux utilisateurs autorisés de se connecter pour accéder à l'administration du site. + */ export default function Login() { - const [login, setLogin] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(""); - const navigate = useNavigate(); const { login: loginContext, loading, isAuthenticated } = useAuth(); + + const navigate = useNavigate(); const [redirecting, setRedirecting] = useState(false); + // Redirige automatiquement vers l'espace admin si l'utilisateur est déjà authentifié, avec gestion du chargement et de la redirection. useEffect(() => { if (isAuthenticated) { setRedirecting(true); @@ -28,6 +31,12 @@ export default function Login() { } }, [isAuthenticated, navigate]); + const [login, setLogin] = useState(""); + const [password, setPassword] = useState(""); + + const [error, setError] = useState(""); + + // Gère la soumission du formulaire de connexion, en appelant la fonction de login du contexte d'authentification et en gérant les erreurs éventuelles. const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(""); diff --git a/src/pages/public/logoutPage.tsx b/src/pages/public/logoutPage.tsx index 8cfdfd7..bf067ef 100644 --- a/src/pages/public/logoutPage.tsx +++ b/src/pages/public/logoutPage.tsx @@ -1,6 +1,10 @@ import { useEffect } from "react"; import { useNavigate } from "react-router-dom"; +/** + * Page de déconnexion. Supprime les tokens d'authentification et redirige vers la page d'accueil. + * Utilisée pour permettre aux utilisateurs de se déconnecter proprement de l'application. + */ export default function LogoutPage() { const navigate = useNavigate(); useEffect(() => { diff --git a/src/pages/public/requestResetPasswordPage.tsx b/src/pages/public/requestResetPasswordPage.tsx index 75cbb68..bf1fa25 100644 --- a/src/pages/public/requestResetPasswordPage.tsx +++ b/src/pages/public/requestResetPasswordPage.tsx @@ -7,16 +7,23 @@ import RootPaper from "../../layout/rootPaper"; import ResponsiveTitle from "../../components/responsiveTitle"; import { Link as RouterLink } from "react-router-dom"; import { API_URL, LOGIN_ROUTE } from "../../constants/apiConstants"; -import { useAuth } from "../../context/AuthContext"; import { useState } from "react"; +import { useAuth } from "../../hooks/useAuth"; +/** + * Page de demande de réinitialisation du mot de passe. + * Permet aux utilisateurs de demander un lien de réinitialisation en fournissant leur adresse e-mail. + */ export default function RequestResetPassword() { - const { isAuthenticated } = useAuth ? useAuth() : { isAuthenticated: false }; + const { isAuthenticated } = useAuth(); + const [email, setEmail] = useState(""); + const [submitting, setSubmitting] = useState(false); - const [submitError, setSubmitError] = useState(""); const [submitSuccess, setSubmitSuccess] = useState(null); + const [submitError, setSubmitError] = useState(""); + // Gère la soumission du formulaire de réinitialisation, en envoyant une requête au backend avec le token et le nouveau mot de passe, et en gérant les réponses pour afficher les messages appropriés. const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setSubmitError(""); diff --git a/src/pages/public/resetPasswordPage.tsx b/src/pages/public/resetPasswordPage.tsx index 67ed8d1..727c89b 100644 --- a/src/pages/public/resetPasswordPage.tsx +++ b/src/pages/public/resetPasswordPage.tsx @@ -8,12 +8,16 @@ import RootPaper from "../../layout/rootPaper"; import { useState } from "react"; import { useSearchParams } from "react-router-dom"; import NewPasswordFields from "../../components/newPasswordFields"; -import { useAuth } from "../../context/AuthContext"; import { Link as RouterLink } from "react-router-dom"; import { API_URL, LOGIN_ROUTE } from "../../constants/apiConstants"; +import { useAuth } from "../../hooks/useAuth"; +/** + * Page de réinitialisation du mot de passe. Permet aux utilisateurs de réinitialiser leur mot de passe en fournissant un nouveau mot de passe et une confirmation, après avoir cliqué sur le lien de réinitialisation reçu par e-mail. + * Gère la validation des champs, l'envoi de la requête de réinitialisation au backend et l'affichage des messages de succès ou d'erreur. + */ export default function ResetPassword() { - const { isAuthenticated } = useAuth ? useAuth() : { isAuthenticated: false }; + const { isAuthenticated } = useAuth(); const [searchParams] = useSearchParams(); const token = searchParams.get("token") || ""; @@ -26,6 +30,7 @@ export default function ResetPassword() { const [submitSuccess, setSubmitSuccess] = useState(false); const [submitting, setSubmitting] = useState(false); + // Gère la soumission du formulaire de réinitialisation, en envoyant une requête au backend avec le token et le nouveau mot de passe, et en gérant les réponses pour afficher les messages appropriés. const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setSubmitError(""); diff --git a/src/services/appolloClient.ts b/src/services/appolloClient.ts index 7732dc2..de6f563 100644 --- a/src/services/appolloClient.ts +++ b/src/services/appolloClient.ts @@ -1,5 +1,9 @@ import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client"; +/** + * Configure et exporte une instance d'Apollo Client pour interagir avec le backend GraphQL. + * Utilise un cache en mémoire pour stocker les données récupérées et un lien HTTP pour communiquer avec le serveur GraphQL à l'URL spécifiée. + */ const apolloClient = new ApolloClient({ cache: new InMemoryCache(), link: new HttpLink({ diff --git a/src/services/authService.ts b/src/services/authService.ts index 587ee1f..32e9079 100644 --- a/src/services/authService.ts +++ b/src/services/authService.ts @@ -1,6 +1,11 @@ import { API_URL, LOGIN_ROUTE } from "../constants/apiConstants"; import { joinUrl } from "../utils/urlUtils"; +/** + * Service d'authentification pour gérer les interactions avec l'API d'authentification. + * Fournit une fonction de login qui envoie les identifiants de l'utilisateur au backend et récupère un token d'authentification en cas de succès. + * Gère également les erreurs de connexion, notamment les problèmes de réseau ou les réponses invalides du serveur. + */ export async function loginApi( login: string, password: string, diff --git a/src/theme.ts b/src/theme.ts index ffc5f22..73173ee 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -1,15 +1,24 @@ import { createTheme, type Theme } from "@mui/material/styles"; +// Breakpoints verticaux basés sur la hauteur de l'écran, en complément des breakpoints horizontaux classiques. const verticalBreakpoints = { tight: 0, compact: 552, loose: 792, }; +/** + * Fonction utilitaire pour générer des media queries basées sur la hauteur de l'écran. + * Permet de créer des breakpoints verticaux pour adapter le design en fonction de la hauteur de l'écran, en complément des breakpoints horizontaux classiques. + * Utilise les valeurs définies dans verticalBreakpoints pour générer des media queries "up" (min-height) ou "down" (max-height). + * @param {keyof typeof verticalBreakpoints} key Le nom du breakpoint vertical à utiliser (tight, compact, loose). + * @param {"up" | "down"} direction La direction de la media query, soit "up" pour min-height, soit "down" pour max-height. Par défaut, "up". + * @returns {string} La media query CSS correspondante à appliquer dans les styles. + */ export const verticalMediaQuery = ( key: keyof typeof verticalBreakpoints, direction: "up" | "down" = "up", -) => { +): string => { const px = verticalBreakpoints[key]; if (direction === "up") { return `@media (min-height:${px}px)`; @@ -18,6 +27,7 @@ export const verticalMediaQuery = ( } }; +// Thème personnalisé pour l'application, basé sur le thème sombre de Material-UI avec des couleurs et des typographies adaptées. const baseTheme = createTheme({ palette: { mode: "dark", @@ -78,6 +88,7 @@ const baseTheme = createTheme({ }, }); +// Ajout de formes personnalisées au thème pour une plus grande flexibilité dans les styles des composants. const customShapes = { borderRadiusXs: "2px", borderRadiusSm: "4px", @@ -87,7 +98,6 @@ const customShapes = { borderRadiusXxl: "32px", borderRadiusFull: "9999px", }; - Object.assign(baseTheme.shape, customShapes); const theme = baseTheme as Theme; diff --git a/src/types/authTypes.ts b/src/types/authTypes.ts new file mode 100644 index 0000000..246a6aa --- /dev/null +++ b/src/types/authTypes.ts @@ -0,0 +1,8 @@ +export interface AuthContextType { + isAuthenticated: boolean; + loading: boolean; + user: any; + login: (login: string, password: string) => Promise; + logout: () => void; + checkAuth: () => Promise; +} diff --git a/src/types/baseComponent.ts b/src/types/baseComponent.ts index 8709e56..06f34c9 100644 --- a/src/types/baseComponent.ts +++ b/src/types/baseComponent.ts @@ -7,3 +7,14 @@ export interface PasswordFieldProps { errorText?: string; required?: boolean; } + +export interface NewPasswordFieldsProps { + newPassword: string; + setNewPassword: (pwd: string) => void; + confirmPassword: string; + setConfirmPassword: (pwd: string) => void; + newPasswordError: string; + setNewPasswordError: (msg: string) => void; + confirmPasswordError: string; + setConfirmPasswordError: (msg: string) => void; +} diff --git a/src/types/responsiveComponents.ts b/src/types/responsiveTypes.ts similarity index 60% rename from src/types/responsiveComponents.ts rename to src/types/responsiveTypes.ts index 7249072..73dca98 100644 --- a/src/types/responsiveComponents.ts +++ b/src/types/responsiveTypes.ts @@ -16,3 +16,17 @@ export type ResponsiveLayoutProps

= P & { rowGap?: string | number; children: React.ReactNode; }; + +export type GetResponsiveSxProps = { + marginY?: string; + paddingY?: string; + rowGap?: string; +}; + +export type ResponsiveSxProps = { + maxWidth: number; + marginY: { xs: string; sm: string; md: string }; + paddingY: { xs: string; sm: string; md: string }; + rowGap: { xs: string; sm: string; md: string }; + [key: string]: any; +}; diff --git a/src/utils/authUtils.ts b/src/utils/authUtils.ts index 745dd01..f07c294 100644 --- a/src/utils/authUtils.ts +++ b/src/utils/authUtils.ts @@ -1,5 +1,8 @@ /** - * Décode un JWT sans vérification de signature (usage frontend seulement) + * Utilitaires liés à l'authentification, notamment la gestion des tokens JWT et la vérification de l'état d'authentification de l'utilisateur. + * Fournit des fonctions pour décoder les tokens JWT et vérifier leur validité en fonction de leur date d'expiration. + * @param {string} token Le token JWT à décoder. + * @returns {any | null} Le payload décodé du token, ou null en cas d'erreur de décodage. */ function decodeJwt(token: string): any | null { try { @@ -11,7 +14,9 @@ function decodeJwt(token: string): any | null { } /** - * Vérifie la présence d'un token et sa validité (expiration) + * Vérifie si l'utilisateur est actuellement authentifié en vérifiant la présence d'un token JWT valide dans le stockage local. + * Décodé le token pour vérifier sa date d'expiration et s'assurer qu'il est encore valide. + * @returns {boolean} true si l'utilisateur est authentifié, false sinon. */ export function isAuthenticated(): boolean { const token = localStorage.getItem("token"); diff --git a/src/utils/responsiveUtils.ts b/src/utils/responsiveUtils.ts index b6057e4..faeb391 100644 --- a/src/utils/responsiveUtils.ts +++ b/src/utils/responsiveUtils.ts @@ -1,11 +1,24 @@ import { useTheme } from "@mui/material"; import { verticalMediaQuery } from "../theme"; +import type { + GetResponsiveSxProps, + ResponsiveSxProps, +} from "../types/responsiveTypes"; +/** + * Utilitaire pour générer des styles responsives basés sur les propriétés de marge, de padding et d'espacement entre les éléments. + * Utilise les breakpoints et les fonctions de thème de Material-UI pour créer des styles qui s'adaptent à différentes tailles d'écran et orientations. + * Permet de limiter les valeurs de marge, de padding et d'espacement à des maximums définis dans le thème pour éviter des espacements excessifs sur les grands écrans. + * @param {string} props.marginY La marge verticale à appliquer, qui sera limitée par les breakpoints du thème. + * @param {string} props.paddingY Le padding vertical à appliquer, qui sera limité par les breakpoints du thème. + * @param {string} props.rowGap L'espacement entre les éléments (row gap) à appliquer, qui sera limité par les breakpoints du thème. + * @returns {{ maxWidth: number; marginY: { xs: string; sm: string; md: string }; paddingY: { xs: string; sm: string; md: string }; rowGap: { xs: string; sm: string; md: string }; [key: string]: any;}} Un objet de styles responsives à appliquer aux composants. + */ export function getResponsiveSx({ marginY = "0px", paddingY = "0px", rowGap = "0px", -}) { +}: GetResponsiveSxProps): ResponsiveSxProps { const theme = useTheme(); const maxSpacing = theme.spacing(24); diff --git a/src/utils/urlUtils.ts b/src/utils/urlUtils.ts index a5e6a8a..8d5dc81 100644 --- a/src/utils/urlUtils.ts +++ b/src/utils/urlUtils.ts @@ -1,8 +1,8 @@ /** * Concatène proprement une base d'URL et un chemin, sans double slash. - * @param baseUrl ex: http://localhost:4000/ - * @param path ex: /4ntjnra6 - * @returns string ex: http://localhost:4000/4ntjnra6 + * @param {string} baseUrl ex: http://localhost:4000/ + * @param {string} path ex: /4ntjnra6 + * @returns {string} ex: http://localhost:4000/4ntjnra6 */ export function joinUrl(baseUrl: string, path: string): string { try { From 64d520d071b2f319dbd936e5214c1852fd94c3ac Mon Sep 17 00:00:00 2001 From: NKoelblen Date: Fri, 27 Mar 2026 15:41:26 +0100 Subject: [PATCH 12/22] Add Snackbar and Alert components for error and success messages in account, login, request reset, and reset password pages --- src/pages/admin/accountPage.tsx | 239 ++++++++++-------- src/pages/public/loginPage.tsx | 22 +- src/pages/public/requestResetPasswordPage.tsx | 32 ++- src/pages/public/resetPasswordPage.tsx | 24 +- 4 files changed, 185 insertions(+), 132 deletions(-) diff --git a/src/pages/admin/accountPage.tsx b/src/pages/admin/accountPage.tsx index 49240d1..4af6dfe 100644 --- a/src/pages/admin/accountPage.tsx +++ b/src/pages/admin/accountPage.tsx @@ -1,4 +1,10 @@ -import { Button, TextField } from "@mui/material"; +import { + Button, + CircularProgress, + TextField, + Snackbar, + Alert, +} from "@mui/material"; import ResponsiveTitle from "../../components/responsiveTitle"; import { useState, useEffect } from "react"; import { ResponsiveStack } from "../../components/ResponsiveLayout"; @@ -105,122 +111,139 @@ export default function Account() { }; return ( - + Mon compte - {loadingUser ? ( -

- Chargement... -
- ) : userError ? ( -
: null} + {userError && ( + - {userError} -
- ) : ( - - - setLogin(e.target.value)} - /> - setEmail(e.target.value)} - /> - - - - - - - - - - - - + + {userError} + + )} {submitError && ( -
{submitError}
+ + + {submitError} + + )} {submitSuccess && ( -
- Mise à jour réussie ! -
+ + + Les informations du compte ont été mises à jour avec succès. + + + )} + {!loadingUser && ( + <> + + + setLogin(e.target.value)} + /> + setEmail(e.target.value)} + /> + + + + + + + + + + + + + + )} - ); } diff --git a/src/pages/public/loginPage.tsx b/src/pages/public/loginPage.tsx index 600db02..417d919 100644 --- a/src/pages/public/loginPage.tsx +++ b/src/pages/public/loginPage.tsx @@ -1,4 +1,4 @@ -import { TextField, Button } from "@mui/material"; +import { TextField, Button, Snackbar, Alert } from "@mui/material"; import { useState } from "react"; import { ResponsivePaper, @@ -75,6 +75,17 @@ export default function Login() { }} elevation={1} > + {error && ( + + + {error} + + + )} - {error && ( - - {error} - - )} + {submitError && ( + + + {submitError} + + + )} + {submitSuccess && ( + + + Si l'adresse existe, un e-mail de réinitialisation a été envoyé. + + + )} - {submitError && ( -
{submitError}
- )} - {submitSuccess && ( -
- Si l'adresse existe, un e-mail de réinitialisation a été envoyé. -
- )} {submitError && ( -
{submitError}
+ + + {submitError} + + )} {submitSuccess && ( -
- Votre mot de passe a été réinitialisé avec succès. -
+ + + Votre mot de passe a été réinitialisé avec succès. + + )} Date: Fri, 27 Mar 2026 15:46:14 +0100 Subject: [PATCH 13/22] Refactor Alert components: change variant from 'filled' to 'outlined' for error and success messages in account, login, request reset, and reset password pages --- src/pages/admin/accountPage.tsx | 12 ++++++------ src/pages/public/loginPage.tsx | 4 ++-- src/pages/public/requestResetPasswordPage.tsx | 8 ++++---- src/pages/public/resetPasswordPage.tsx | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/pages/admin/accountPage.tsx b/src/pages/admin/accountPage.tsx index 4af6dfe..6f4331b 100644 --- a/src/pages/admin/accountPage.tsx +++ b/src/pages/admin/accountPage.tsx @@ -124,9 +124,9 @@ export default function Account() { - + {userError} @@ -135,9 +135,9 @@ export default function Account() { - + {submitError} @@ -146,9 +146,9 @@ export default function Account() { - + Les informations du compte ont été mises à jour avec succès. diff --git a/src/pages/public/loginPage.tsx b/src/pages/public/loginPage.tsx index 417d919..2f73c6d 100644 --- a/src/pages/public/loginPage.tsx +++ b/src/pages/public/loginPage.tsx @@ -79,9 +79,9 @@ export default function Login() { - + {error} diff --git a/src/pages/public/requestResetPasswordPage.tsx b/src/pages/public/requestResetPasswordPage.tsx index aa94545..7876c64 100644 --- a/src/pages/public/requestResetPasswordPage.tsx +++ b/src/pages/public/requestResetPasswordPage.tsx @@ -84,9 +84,9 @@ export default function RequestResetPassword() { - + {submitError} @@ -95,9 +95,9 @@ export default function RequestResetPassword() { - + Si l'adresse existe, un e-mail de réinitialisation a été envoyé. diff --git a/src/pages/public/resetPasswordPage.tsx b/src/pages/public/resetPasswordPage.tsx index 6ba95da..bd36b53 100644 --- a/src/pages/public/resetPasswordPage.tsx +++ b/src/pages/public/resetPasswordPage.tsx @@ -102,9 +102,9 @@ export default function ResetPassword() { - + {submitError} @@ -113,9 +113,9 @@ export default function ResetPassword() { - + Votre mot de passe a été réinitialisé avec succès. From 96e0e855d37f812998757b772bfe046b8b980b77 Mon Sep 17 00:00:00 2001 From: NKoelblen Date: Sat, 28 Mar 2026 08:59:23 +0100 Subject: [PATCH 14/22] Implement custom Snackbar components and enhance password field functionality: add autoComplete prop, integrate ClosableSnackbar and CustomSnackbar for better user feedback in various pages. --- src/components/closableSnackbar.tsx | 43 ++++++ src/components/customSnackBar.tsx | 28 ++++ src/components/passwordField.tsx | 2 + src/layout/admin/adminHeader.tsx | 72 +++++++--- src/layout/admin/adminLayout.tsx | 2 + src/layout/public/publicHeader.tsx | 9 +- src/pages/admin/accountPage.tsx | 93 ++++++------- src/pages/admin/dashboardPage.tsx | 6 +- src/pages/public/loginPage.tsx | 15 +-- src/pages/public/requestResetPasswordPage.tsx | 46 +++---- src/pages/public/resetPasswordPage.tsx | 34 ++--- src/theme.ts | 123 ++++++++++++++++++ src/types/baseComponent.ts | 18 +++ 13 files changed, 359 insertions(+), 132 deletions(-) create mode 100644 src/components/closableSnackbar.tsx create mode 100644 src/components/customSnackBar.tsx diff --git a/src/components/closableSnackbar.tsx b/src/components/closableSnackbar.tsx new file mode 100644 index 0000000..c172349 --- /dev/null +++ b/src/components/closableSnackbar.tsx @@ -0,0 +1,43 @@ +import { type SnackbarCloseReason } from "@mui/material"; +import CustomSnackbar from "./customSnackBar"; +import type { ClosableSnackbarProps } from "../types/baseComponent"; + +/** + * Composant de snackbar personnalisée avec possibilité de fermeture manuelle ou automatique. + * Permet d'afficher des messages de succès, d'erreur, d'avertissement ou d'information à l'utilisateur, avec une gestion intégrée de l'ouverture et de la fermeture du snackbar. + * Utilise le composant CustomSnackbar pour afficher le message avec le style approprié en fonction de la gravité du message. + * @param {boolean} props.open Indique si le snackbar est ouvert ou fermé. + * @param {function} props.setOpen Fonction pour mettre à jour l'état d'ouverture du snackbar. + * @param {string} props.message Le message à afficher dans le snackbar. + * @param {function} props.onClose Fonction optionnelle à appeler lors de la fermeture du snackbar. + * @param {"success" | "error" | "warning" | "info"} props.severity La gravité du message, qui détermine le style du snackbar. + * @param {number} props.autohideDuration Durée en millisecondes avant que le snackbar ne se ferme automatiquement. Par défaut, 6000 ms (6 secondes). + */ +export default function ClosableSnackbar({ + open, + setOpen, + message, + onClose, + severity, + autohideDuration = 6000, +}: ClosableSnackbarProps) { + const handleClose = ( + _: React.SyntheticEvent | Event, + reason?: SnackbarCloseReason, + ) => { + if (reason === "clickaway") { + return; + } + setOpen(false); + }; + + return ( + + ); +} diff --git a/src/components/customSnackBar.tsx b/src/components/customSnackBar.tsx new file mode 100644 index 0000000..45dd293 --- /dev/null +++ b/src/components/customSnackBar.tsx @@ -0,0 +1,28 @@ +import { Alert, Snackbar } from "@mui/material"; +import type { CustomSnackbarProps } from "../types/baseComponent"; + +/** + * Composant de snackbar personnalisée pour afficher des messages de succès, d'erreur, d'avertissement ou d'information à l'utilisateur. + * Utilise les composants Snackbar et Alert de Material-UI pour afficher le message avec le style approprié en fonction de la gravité du message. + * Permet de personnaliser le message, la gravité, la durée d'affichage et la fonction de fermeture du snackbar. + * @param {boolean} props.open Indique si le snackbar est ouvert ou fermé. + * @param {string} props.message Le message à afficher dans le snackbar. + * @param {"success" | "error" | "warning" | "info"} props.severity La gravité du message, qui détermine le style du snackbar. + * @param {function} props.onClose Fonction optionnelle à appeler lors de la fermeture du snackbar. + * @param {number} props.autohideDuration Durée en millisecondes avant que le snackbar ne se ferme automatiquement. Par défaut, 6000 ms (6 secondes). + */ +export default function CustomSnackbar({ + open, + message, + severity, + onClose, + autohideDuration, +}: CustomSnackbarProps) { + return ( + + + {message} + + + ); +} diff --git a/src/components/passwordField.tsx b/src/components/passwordField.tsx index f31c3f7..5af5c15 100644 --- a/src/components/passwordField.tsx +++ b/src/components/passwordField.tsx @@ -22,6 +22,7 @@ export default function PasswordField({ helperText, errorText, required, + autoComplete, }: PasswordFieldProps) { const [showPassword, setShowPassword] = useState(false); @@ -35,6 +36,7 @@ export default function PasswordField({ onChange={onChange} error={error} helperText={error ? errorText || "Ce champ est requis" : helperText || ""} + autoComplete={autoComplete} slotProps={{ input: { endAdornment: ( diff --git a/src/layout/admin/adminHeader.tsx b/src/layout/admin/adminHeader.tsx index ac71a8a..fd31b83 100644 --- a/src/layout/admin/adminHeader.tsx +++ b/src/layout/admin/adminHeader.tsx @@ -2,28 +2,66 @@ import AppBar from "@mui/material/AppBar"; import Toolbar from "@mui/material/Toolbar"; import Button from "@mui/material/Button"; import { Link } from "react-router-dom"; +import { useState } from "react"; +import { + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, +} from "@mui/material"; /** * Entête de l'espace admin, avec des liens vers les différentes sections et la déconnexion. */ export default function AdminHeader() { + const [logoutConfirm, setLogoutConfirm] = useState(false); + return ( - - - - - - - - + <> + + + + + + + + + setLogoutConfirm(false)}> + Voulez-vous vous déconnecter ? + + + Vous devrez vous reconnecter pour accéder à nouveau à l'espace + administrateur. + + + + + + + + ); } diff --git a/src/layout/admin/adminLayout.tsx b/src/layout/admin/adminLayout.tsx index 0331186..a8fbadc 100644 --- a/src/layout/admin/adminLayout.tsx +++ b/src/layout/admin/adminLayout.tsx @@ -17,11 +17,13 @@ export default function AdminLayout({ - - diff --git a/src/pages/admin/accountPage.tsx b/src/pages/admin/accountPage.tsx index 6f4331b..3c4ac20 100644 --- a/src/pages/admin/accountPage.tsx +++ b/src/pages/admin/accountPage.tsx @@ -1,16 +1,12 @@ -import { - Button, - CircularProgress, - TextField, - Snackbar, - Alert, -} from "@mui/material"; +import { Button, CircularProgress, Container, TextField } from "@mui/material"; import ResponsiveTitle from "../../components/responsiveTitle"; import { useState, useEffect } from "react"; import { ResponsiveStack } from "../../components/ResponsiveLayout"; import PasswordField from "../../components/passwordField"; import ResetPasswordLink from "../../components/resetPasswordLink"; import NewPasswordFields from "../../components/newPasswordFields"; +import ClosableSnackbar from "../../components/closableSnackbar"; +import CustomSnackbar from "../../components/customSnackBar"; /** * Page de gestion du compte utilisateur dans l'espace admin. @@ -36,6 +32,9 @@ export default function Account() { const [submitSuccess, setSubmitSuccess] = useState(null); const [submitError, setSubmitError] = useState(""); + const [successSnackbarOpen, setSuccessSnackbarOpen] = useState(false); + const [errorSnackbarOpen, setErrorSnackbarOpen] = useState(false); + // Récupération des informations du compte à l'affichage de la page, avec gestion du chargement et des erreurs. useEffect(() => { const fetchUserInfo = async () => { @@ -110,51 +109,40 @@ export default function Account() { } }; + // Affichage des snackbars de succès ou d'erreur en fonction des résultats des actions de mise à jour du compte ou de récupération des informations, avec gestion de l'ouverture et de la fermeture. + useEffect(() => { + if (submitSuccess) setSuccessSnackbarOpen(true); + if (userError || submitError) setErrorSnackbarOpen(true); + }, [submitSuccess, userError, submitError]); + return ( - + <> Mon compte - {loadingUser ? : null} - {userError && ( - - - {userError} - - - )} - {submitError && ( - + + {loadingUser ? ( + + ) : ( + - - {submitError} - - - )} - {submitSuccess && ( - - - Les informations du compte ont été mises à jour avec succès. - - - )} - {!loadingUser && ( - <> setLogin(e.target.value)} + autoComplete="username" /> setEmail(e.target.value)} + autoComplete="email" /> @@ -183,7 +173,7 @@ export default function Account() { columnGap={2} width="100%" > - + @@ -220,7 +211,6 @@ export default function Account() { - + )} - + ); } diff --git a/src/pages/admin/dashboardPage.tsx b/src/pages/admin/dashboardPage.tsx index 93b8fd5..73ca81f 100644 --- a/src/pages/admin/dashboardPage.tsx +++ b/src/pages/admin/dashboardPage.tsx @@ -4,5 +4,9 @@ import ResponsiveTitle from "../../components/responsiveTitle"; * Page d'accueil de l'espace admin. */ export default function Dashboard() { - return Espace Admin; + return ( + + Espace Admin + + ); } diff --git a/src/pages/public/loginPage.tsx b/src/pages/public/loginPage.tsx index 2f73c6d..a6a7a12 100644 --- a/src/pages/public/loginPage.tsx +++ b/src/pages/public/loginPage.tsx @@ -1,4 +1,4 @@ -import { TextField, Button, Snackbar, Alert } from "@mui/material"; +import { TextField, Button } from "@mui/material"; import { useState } from "react"; import { ResponsivePaper, @@ -7,11 +7,11 @@ import { import RootPaper from "../../layout/rootPaper"; import ResponsiveTitle from "../../components/responsiveTitle"; import { Link as RouterLink, useNavigate } from "react-router-dom"; -import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; import { useEffect } from "react"; import PasswordField from "../../components/passwordField"; import ResetPasswordLink from "../../components/resetPasswordLink"; import { useAuth } from "../../hooks/useAuth"; +import CustomSnackbar from "../../components/customSnackBar"; /** * Page de connexion à l'espace admin. @@ -76,15 +76,7 @@ export default function Login() { elevation={1} > {error && ( - - - {error} - - + )}
- setLogoutConfirm(false)}> - Voulez-vous vous déconnecter ? - - - Vous devrez vous reconnecter pour accéder à nouveau à l'espace - administrateur. - - - - - - - + setLogoutConfirm(false)} + title="Voulez-vous vous déconnecter ?" + content="Vous devrez vous reconnecter pour accéder à nouveau à l'espace administrateur." + actions={ + <> + + + + } + /> ); } diff --git a/src/theme.ts b/src/theme.ts index cf50193..bc41092 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -208,6 +208,57 @@ const baseTheme = createTheme({ }, }, }, + MuiDialog: { + styleOverrides: { + paper: { + borderRadius: "8px", + padding: "24px 16px", + margin: "48px 32px", + gap: "12px", + }, + }, + }, + MuiDialogTitle: { + styleOverrides: { + root: { + fontSize: "1.5rem", + lineHeight: 1, + letterSpacing: "normal", + padding: "0px", + }, + }, + }, + MuiDialogContent: { + styleOverrides: { + root: { + fontSize: "1rem", + lineHeight: 1.5, + letterSpacing: "normal", + padding: "0px", + }, + }, + }, + MuiDialogContentText: { + styleOverrides: { + root: { + fontSize: "1rem", + lineHeight: 1.5, + letterSpacing: "normal", + }, + }, + }, + MuiDialogActions: { + styleOverrides: { + root: { + padding: "0px", + gap: "8px", + "& .MuiButton-root": { + marginLeft: "0px", + marginRight: "0px", + }, + }, + }, + }, }, }); From b7417044f6d9695b073ed55ef0ba365f611bad1b Mon Sep 17 00:00:00 2001 From: NKoelblen Date: Sat, 28 Mar 2026 14:27:09 +0100 Subject: [PATCH 17/22] Refactor authentication structure: move LogoutPage to 'auth' directory, implement AccountMenu component for user actions, and update headers to utilize new components. --- src/App.tsx | 2 +- src/components/accountMenu.tsx | 57 ++++++++++++++++++++++++ src/layout/admin/adminHeader.tsx | 56 +++++------------------ src/layout/public/publicHeader.tsx | 25 +++++++---- src/pages/{admin => auth}/logoutPage.tsx | 10 +++-- 5 files changed, 93 insertions(+), 57 deletions(-) create mode 100644 src/components/accountMenu.tsx rename src/pages/{admin => auth}/logoutPage.tsx (72%) diff --git a/src/App.tsx b/src/App.tsx index 86e3f51..aa73d72 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,7 @@ import Account from "./pages/admin/accountPage"; import Login from "./pages/auth/loginPage"; import RequestResetPassword from "./pages/auth/requestResetPasswordPage"; import ResetPassword from "./pages/auth/resetPasswordPage"; -import LogoutPage from "./pages/admin/logoutPage"; +import LogoutPage from "./pages/auth/logoutPage"; import RequireAuth from "./pages/admin/requireAuth"; import { LOGIN_ROUTE } from "./constants/apiConstants"; import { AuthProvider } from "./context/AuthContext"; diff --git a/src/components/accountMenu.tsx b/src/components/accountMenu.tsx new file mode 100644 index 0000000..cf87c6b --- /dev/null +++ b/src/components/accountMenu.tsx @@ -0,0 +1,57 @@ +import { mdiAccount } from "@mdi/js"; +import Icon from "@mdi/react"; +import { Button, IconButton, Menu, MenuItem } from "@mui/material"; +import CustomDialog from "./customDialog"; +import { Link, useLocation } from "react-router"; +import { useState } from "react"; + +export default function AccountMenu() { + const [logoutConfirm, setLogoutConfirm] = useState(false); + const [anchorEl, setAnchorEl] = useState(null); + const handleCloseDialog = () => { + setLogoutConfirm(false); + setAnchorEl(null); + }; + const location = useLocation(); + return ( + <> + setAnchorEl(e.currentTarget)} color="inherit"> + + + setAnchorEl(null)} + > + setAnchorEl(null)} + selected={location.pathname === "/admin/account"} + > + Mon compte + + setLogoutConfirm(true)}> + Me déconnecter + + + + + + + } + /> + + ); +} diff --git a/src/layout/admin/adminHeader.tsx b/src/layout/admin/adminHeader.tsx index e4b220a..da18cde 100644 --- a/src/layout/admin/adminHeader.tsx +++ b/src/layout/admin/adminHeader.tsx @@ -4,6 +4,7 @@ import Button from "@mui/material/Button"; import { Link } from "react-router-dom"; import { useState } from "react"; import CustomDialog from "../../components/customDialog"; +import AccountMenu from "../../components/accountMenu"; /** * Entête de l'espace admin, avec des liens vers les différentes sections et la déconnexion. @@ -12,49 +13,16 @@ export default function AdminHeader() { const [logoutConfirm, setLogoutConfirm] = useState(false); return ( - <> - - - - - - - - - setLogoutConfirm(false)} - title="Voulez-vous vous déconnecter ?" - content="Vous devrez vous reconnecter pour accéder à nouveau à l'espace administrateur." - actions={ - <> - - - - } - /> - + + + + + + + ); } diff --git a/src/layout/public/publicHeader.tsx b/src/layout/public/publicHeader.tsx index 44f3325..6ec8cd4 100644 --- a/src/layout/public/publicHeader.tsx +++ b/src/layout/public/publicHeader.tsx @@ -2,26 +2,35 @@ import AppBar from "@mui/material/AppBar"; import Toolbar from "@mui/material/Toolbar"; import Button from "@mui/material/Button"; import { Link as RouterLink } from "react-router-dom"; +import AccountMenu from "../../components/accountMenu"; +import { useAuth } from "../../hooks/useAuth"; /** * Entête pour les pages publiques, avec des liens vers l'accueil et l'espace admin. * Utilisé sur les pages d'accueil, de connexion, etc. */ export default function PublicHeader() { + const { isAuthenticated } = useAuth(); + return ( - + {isAuthenticated && ( + <> + + + + )} ); diff --git a/src/pages/admin/logoutPage.tsx b/src/pages/auth/logoutPage.tsx similarity index 72% rename from src/pages/admin/logoutPage.tsx rename to src/pages/auth/logoutPage.tsx index bf067ef..51a5776 100644 --- a/src/pages/admin/logoutPage.tsx +++ b/src/pages/auth/logoutPage.tsx @@ -1,17 +1,19 @@ import { useEffect } from "react"; import { useNavigate } from "react-router-dom"; +import { useAuth } from "../../hooks/useAuth"; /** * Page de déconnexion. Supprime les tokens d'authentification et redirige vers la page d'accueil. * Utilisée pour permettre aux utilisateurs de se déconnecter proprement de l'application. */ export default function LogoutPage() { + const { logout } = useAuth(); const navigate = useNavigate(); + useEffect(() => { - localStorage.removeItem("token"); - localStorage.removeItem("refreshToken"); - // TODO: Ajouter ici la suppression de cookies si besoin + logout(); navigate("/", { replace: true }); - }, [navigate]); + }, [logout]); + return null; } From 9f4bc36164679d18f5ec93d949576c296b872b7d Mon Sep 17 00:00:00 2001 From: NKoelblen Date: Sat, 28 Mar 2026 14:41:17 +0100 Subject: [PATCH 18/22] Refactor admin header and account menu: integrate IconButton for navigation, enhance layout with Menu for user actions, and apply theme-based maxWidth adjustments in authentication pages. --- src/components/accountMenu.tsx | 1 + src/layout/admin/adminHeader.tsx | 43 +++++++++++++++++---- src/pages/admin/accountPage.tsx | 2 +- src/pages/auth/loginPage.tsx | 6 ++- src/pages/auth/requestResetPasswordPage.tsx | 10 +++-- src/pages/auth/resetPasswordPage.tsx | 12 +++--- 6 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/components/accountMenu.tsx b/src/components/accountMenu.tsx index cf87c6b..e872c42 100644 --- a/src/components/accountMenu.tsx +++ b/src/components/accountMenu.tsx @@ -13,6 +13,7 @@ export default function AccountMenu() { setAnchorEl(null); }; const location = useLocation(); + return ( <> setAnchorEl(e.currentTarget)} color="inherit"> diff --git a/src/layout/admin/adminHeader.tsx b/src/layout/admin/adminHeader.tsx index da18cde..0fedd95 100644 --- a/src/layout/admin/adminHeader.tsx +++ b/src/layout/admin/adminHeader.tsx @@ -1,26 +1,53 @@ import AppBar from "@mui/material/AppBar"; import Toolbar from "@mui/material/Toolbar"; import Button from "@mui/material/Button"; -import { Link } from "react-router-dom"; +import { Link, useLocation } from "react-router-dom"; import { useState } from "react"; import CustomDialog from "../../components/customDialog"; import AccountMenu from "../../components/accountMenu"; +import { IconButton, Menu, MenuItem } from "@mui/material"; +import Icon from "@mdi/react"; +import { mdiHome } from "@mdi/js"; /** * Entête de l'espace admin, avec des liens vers les différentes sections et la déconnexion. */ export default function AdminHeader() { - const [logoutConfirm, setLogoutConfirm] = useState(false); + const [anchorEl, setAnchorEl] = useState(null); + const location = useLocation(); return ( - - + setAnchorEl(e.currentTarget)} + color="inherit" + > + + + setAnchorEl(null)} + > + setAnchorEl(null)} + selected={location.pathname === "/"} + > + Site + + setAnchorEl(null)} + selected={location.pathname === "/admin"} + > + Tableau de bord + + diff --git a/src/pages/admin/accountPage.tsx b/src/pages/admin/accountPage.tsx index 3c4ac20..42ce57e 100644 --- a/src/pages/admin/accountPage.tsx +++ b/src/pages/admin/accountPage.tsx @@ -139,9 +139,9 @@ export default function Account() { display: "flex", flexDirection: "column", gap: 3, - maxWidth: "calc((100% - 15 * 16px) / 12 * 4 + 5 * 16px)", }} component="form" + maxWidth="lg" > diff --git a/src/pages/auth/requestResetPasswordPage.tsx b/src/pages/auth/requestResetPasswordPage.tsx index 3d5adb5..bdbb8d2 100644 --- a/src/pages/auth/requestResetPasswordPage.tsx +++ b/src/pages/auth/requestResetPasswordPage.tsx @@ -1,4 +1,4 @@ -import { TextField, Button } from "@mui/material"; +import { TextField, Button, useTheme } from "@mui/material"; import { ResponsivePaper, ResponsiveStack, @@ -17,6 +17,8 @@ import ClosableSnackbar from "../../components/closableSnackbar"; * Permet aux utilisateurs de demander un lien de réinitialisation en fournissant leur adresse e-mail. */ export default function RequestResetPassword() { + const theme = useTheme(); + const { isAuthenticated } = useAuth(); const [email, setEmail] = useState(""); @@ -77,7 +79,7 @@ export default function RequestResetPassword() { flexDirection: "column", justifyContent: "center", paddingX: 4, - maxWidth: "calc((100% - 15 * 16px) / 12 * 4 + 5 * 16px)", // 4 columns + 3 gaps + maxWidth: theme.breakpoints.values.md, }} elevation={1} > @@ -118,9 +120,9 @@ export default function RequestResetPassword() { > Revenir à {isAuthenticated ? ( - <> l'espace administrateur + <> l'espace administrateur ) : ( - <> la page de connexion + <> la page de connexion )} - {isAuthenticated && ( - <> - - - - )} ); diff --git a/src/layout/public/publicLayout.tsx b/src/layout/public/publicLayout.tsx index 936f493..68ca51a 100644 --- a/src/layout/public/publicLayout.tsx +++ b/src/layout/public/publicLayout.tsx @@ -2,6 +2,8 @@ import PublicHeader from "./publicHeader"; import PublicFooter from "./publicFooter"; import RootPaper from "../rootPaper"; import { ResponsivePaper } from "../../components/ResponsiveLayout"; +import { useAuth } from "../../hooks/useAuth"; +import AdminHeader from "../admin/adminHeader"; /** * Layout principal pour les pages publiques (accueil, connexion, etc.). @@ -12,8 +14,11 @@ export default function PublicLayout({ }: { children: React.ReactNode; }) { + const { isAuthenticated } = useAuth(); + return ( + {isAuthenticated ? : null} - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad - minim veniam, quis nostrud exercitation ullamco laboris nisi ut - aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla - pariatur. Excepteur sint occaecat cupidatat non proident, sunt in - culpa qui officia deserunt mollit anim id est laborum. +

+ Lorem ipsum dolor sit amet. Aut earum galisum qui iusto deserunt id + repellat modi. Aut esse consequatur est omnis odit sed enim fuga. + Aut aliquam libero et aspernatur numquam ut amet quae aut + dignissimos rerum aut perspiciatis voluptate.{" "} +

+

+ Ut beatae adipisci a dolor fugiat eos maiores illo cum minima sunt. + Sit autem numquam quo beatae quia eos optio harum sed cumque dolorum + sit dolore animi. Qui corrupti vitae vel voluptatum velit id quasi + voluptatem qui doloremque quos qui vitae corrupti sit quibusdam + veniam.{" "} +

+

+ Sed dolores ducimus qui dolores atque est minus suscipit. In eveniet + voluptatum aut placeat nobis eum corrupti delectus.{" "} +

+

+ Et unde molestias ad provident consequatur qui saepe dolor non earum + iusto quo aliquid sapiente. Sed consequatur dolore qui nihil + nesciunt et molestiae ducimus? Hic possimus dolore quo labore + perferendis ad facilis dolorem quo sapiente quam eos maxime + doloremque?{" "} +

+

+ Aut velit dicta cum numquam quibusdam est corrupti natus in error + voluptatibus non ipsa quasi sed accusantium delectus. Et voluptate + mollitia vel voluptatum accusantium et ullam expedita in odio + galisum ex odio obcaecati ut doloribus adipisci. Ut possimus + consectetur sed omnis dolore sed voluptatum aperiam ut quas rerum id + dolores molestiae ut nulla consequatur est eveniet esse. In + voluptatem quia eum quos voluptatem et sapiente corporis hic galisum + aliquam et quod cumque et quidem ipsum At libero quod?{" "} +

+

+ In voluptates internos non nisi natus aut reprehenderit aperiam in + magnam debitis. Sit voluptatem assumenda eos unde fugit qui enim + corporis a mollitia dolor sed ullam iste non eius nemo est + voluptatem maiores. Vel amet maxime et dolores excepturi rem + asperiores magnam ex necessitatibus galisum sed voluptatum + nihil.{" "} +

diff --git a/src/theme.ts b/src/theme.ts index cd21a66..e6580c4 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -163,6 +163,18 @@ const baseTheme = createTheme({ "& .MuiSvgIcon-root": { fontSize: "inherit", }, + variants: [ + { + props: { size: "adminMenu" }, + style: { + minWidth: "0px !important", + fontSize: "1rem", + lineHeight: 1, + letterSpacing: "normal", + padding: "4px", + }, + }, + ], }, }, }, From 0383d35ca2816278dfd604969b98a000db53b38b Mon Sep 17 00:00:00 2001 From: Onoko Date: Sun, 29 Mar 2026 15:01:38 +0200 Subject: [PATCH 22/22] Refactor components and restructure file organization - Moved CustomMenu, CustomSnackbar, PasswordField, ResponsiveBodyTypography, ResponsiveTitle, PasswordField, ResponsiveBodyTypography, ResponsiveTitle, ClosableSnackbar and CustomDialog and other components to a new 'custom' directory for better organization. - Updated imports across various pages and layouts to reflect the new structure. - Refactored AuthLayout to streamline authentication-related layouts. --- src/components/accountMenu.tsx | 4 +- .../{ => custom}/closableSnackbar.tsx | 2 +- src/components/{ => custom}/customDialog.tsx | 0 src/components/{ => custom}/customMenu.tsx | 0 .../{ => custom}/customSnackBar.tsx | 2 +- src/components/{ => custom}/passwordField.tsx | 2 +- .../{ => custom}/responsiveBodyTypography.tsx | 4 +- .../responsiveLayout.tsx} | 4 +- .../{ => custom}/responsiveTitle.tsx | 4 +- src/components/newPasswordFields.tsx | 2 +- src/components/resetPasswordLink.tsx | 2 +- src/layout/admin/adminHeader.tsx | 2 +- src/layout/admin/adminLayout.tsx | 2 +- src/layout/auth/authLayout.tsx | 42 +++++ src/layout/public/publicFooter.tsx | 2 +- src/layout/public/publicLayout.tsx | 2 +- src/layout/rootPaper.tsx | 2 +- src/pages/admin/accountPage.tsx | 10 +- src/pages/admin/dashboardPage.tsx | 2 +- src/pages/auth/loginPage.tsx | 148 +++++++----------- src/pages/auth/requestResetPasswordPage.tsx | 134 ++++++---------- src/pages/auth/resetPasswordPage.tsx | 138 +++++++--------- src/pages/public/homePage.tsx | 6 +- 23 files changed, 235 insertions(+), 281 deletions(-) rename src/components/{ => custom}/closableSnackbar.tsx (96%) rename src/components/{ => custom}/customDialog.tsx (100%) rename src/components/{ => custom}/customMenu.tsx (100%) rename src/components/{ => custom}/customSnackBar.tsx (94%) rename src/components/{ => custom}/passwordField.tsx (96%) rename src/components/{ => custom}/responsiveBodyTypography.tsx (92%) rename src/components/{ResponsiveLayout.tsx => custom/responsiveLayout.tsx} (92%) rename src/components/{ => custom}/responsiveTitle.tsx (93%) create mode 100644 src/layout/auth/authLayout.tsx diff --git a/src/components/accountMenu.tsx b/src/components/accountMenu.tsx index 40aafb0..d3ee26c 100644 --- a/src/components/accountMenu.tsx +++ b/src/components/accountMenu.tsx @@ -7,10 +7,10 @@ import { ListItemText, MenuItem, } from "@mui/material"; -import CustomDialog from "./customDialog"; +import CustomDialog from "./custom/customDialog"; import { Link, useLocation } from "react-router"; import { useState } from "react"; -import CustomMenu from "./customMenu"; +import CustomMenu from "./custom/customMenu"; export default function AccountMenu() { const [logoutConfirm, setLogoutConfirm] = useState(false); diff --git a/src/components/closableSnackbar.tsx b/src/components/custom/closableSnackbar.tsx similarity index 96% rename from src/components/closableSnackbar.tsx rename to src/components/custom/closableSnackbar.tsx index 75f4ab2..d4b2452 100644 --- a/src/components/closableSnackbar.tsx +++ b/src/components/custom/closableSnackbar.tsx @@ -1,6 +1,6 @@ import { type SnackbarCloseReason } from "@mui/material"; import CustomSnackbar from "./customSnackBar"; -import type { ClosableSnackbarProps } from "../types/baseComponent"; +import type { ClosableSnackbarProps } from "../../types/baseComponent"; /** * Composant de snackbar personnalisée avec possibilité de fermeture manuelle ou automatique. diff --git a/src/components/customDialog.tsx b/src/components/custom/customDialog.tsx similarity index 100% rename from src/components/customDialog.tsx rename to src/components/custom/customDialog.tsx diff --git a/src/components/customMenu.tsx b/src/components/custom/customMenu.tsx similarity index 100% rename from src/components/customMenu.tsx rename to src/components/custom/customMenu.tsx diff --git a/src/components/customSnackBar.tsx b/src/components/custom/customSnackBar.tsx similarity index 94% rename from src/components/customSnackBar.tsx rename to src/components/custom/customSnackBar.tsx index 45dd293..a7f888b 100644 --- a/src/components/customSnackBar.tsx +++ b/src/components/custom/customSnackBar.tsx @@ -1,5 +1,5 @@ import { Alert, Snackbar } from "@mui/material"; -import type { CustomSnackbarProps } from "../types/baseComponent"; +import type { CustomSnackbarProps } from "../../types/baseComponent"; /** * Composant de snackbar personnalisée pour afficher des messages de succès, d'erreur, d'avertissement ou d'information à l'utilisateur. diff --git a/src/components/passwordField.tsx b/src/components/custom/passwordField.tsx similarity index 96% rename from src/components/passwordField.tsx rename to src/components/custom/passwordField.tsx index 5af5c15..9940a60 100644 --- a/src/components/passwordField.tsx +++ b/src/components/custom/passwordField.tsx @@ -2,7 +2,7 @@ import { IconButton, InputAdornment, TextField } from "@mui/material"; import Icon from "@mdi/react"; import { mdiEyeOff, mdiEye } from "@mdi/js"; import { useState } from "react"; -import type { PasswordFieldProps } from "../types/baseComponent"; +import type { PasswordFieldProps } from "../../types/baseComponent"; /** * Composant de champ de mot de passe avec option d'affichage du mot de passe. diff --git a/src/components/responsiveBodyTypography.tsx b/src/components/custom/responsiveBodyTypography.tsx similarity index 92% rename from src/components/responsiveBodyTypography.tsx rename to src/components/custom/responsiveBodyTypography.tsx index aed5cff..be0c448 100644 --- a/src/components/responsiveBodyTypography.tsx +++ b/src/components/custom/responsiveBodyTypography.tsx @@ -1,7 +1,7 @@ import Typography from "@mui/material/Typography"; -import { verticalMediaQuery } from "../theme"; +import { verticalMediaQuery } from "../../theme"; import { useTheme } from "@mui/material"; -import type { ResponsiveBodyTypographyProps } from "../types/responsiveTypes"; +import type { ResponsiveBodyTypographyProps } from "../../types/responsiveTypes"; /** * Composant de typographie pour le corps de texte avec une taille de police responsive. diff --git a/src/components/ResponsiveLayout.tsx b/src/components/custom/responsiveLayout.tsx similarity index 92% rename from src/components/ResponsiveLayout.tsx rename to src/components/custom/responsiveLayout.tsx index 8cbe942..96697e8 100644 --- a/src/components/ResponsiveLayout.tsx +++ b/src/components/custom/responsiveLayout.tsx @@ -3,8 +3,8 @@ import Box, { type BoxProps } from "@mui/material/Box"; import Stack, { type StackProps } from "@mui/material/Stack"; import Paper, { type PaperProps } from "@mui/material/Paper"; import ImageList, { type ImageListProps } from "@mui/material/ImageList"; -import type { ResponsiveLayoutProps } from "../types/responsiveTypes"; -import { getResponsiveSx } from "../utils/responsiveUtils"; +import type { ResponsiveLayoutProps } from "../../types/responsiveTypes"; +import { getResponsiveSx } from "../../utils/responsiveUtils"; /** * Composant générique pour appliquer un layout responsive à n'importe quel composant MUI diff --git a/src/components/responsiveTitle.tsx b/src/components/custom/responsiveTitle.tsx similarity index 93% rename from src/components/responsiveTitle.tsx rename to src/components/custom/responsiveTitle.tsx index d4b849f..72ba782 100644 --- a/src/components/responsiveTitle.tsx +++ b/src/components/custom/responsiveTitle.tsx @@ -1,7 +1,7 @@ import Typography from "@mui/material/Typography"; -import { verticalMediaQuery } from "../theme"; +import { verticalMediaQuery } from "../../theme"; import { useTheme } from "@mui/material"; -import type { ResponsiveTitleProps } from "../types/responsiveTypes"; +import type { ResponsiveTitleProps } from "../../types/responsiveTypes"; /** * Composant de typographie pour les titres avec une taille de police responsive. diff --git a/src/components/newPasswordFields.tsx b/src/components/newPasswordFields.tsx index 4466374..fa29d1f 100644 --- a/src/components/newPasswordFields.tsx +++ b/src/components/newPasswordFields.tsx @@ -1,5 +1,5 @@ import type { NewPasswordFieldsProps } from "../types/baseComponent"; -import PasswordField from "./passwordField"; +import PasswordField from "./custom/passwordField"; /** * Composant pour les champs de saisie du nouveau mot de passe et de sa confirmation. diff --git a/src/components/resetPasswordLink.tsx b/src/components/resetPasswordLink.tsx index 2626ecb..82b8470 100644 --- a/src/components/resetPasswordLink.tsx +++ b/src/components/resetPasswordLink.tsx @@ -1,5 +1,5 @@ import { Link } from "@mui/material"; -import ResponsiveBodyTypography from "./responsiveBodyTypography"; +import ResponsiveBodyTypography from "./custom/responsiveBodyTypography"; /** * Composant de lien pour la réinitialisation du mot de passe. diff --git a/src/layout/admin/adminHeader.tsx b/src/layout/admin/adminHeader.tsx index 8c5230b..c874748 100644 --- a/src/layout/admin/adminHeader.tsx +++ b/src/layout/admin/adminHeader.tsx @@ -6,7 +6,7 @@ import AccountMenu from "../../components/accountMenu"; import { IconButton, MenuItem } from "@mui/material"; import Icon from "@mdi/react"; import { mdiHome } from "@mdi/js"; -import CustomMenu from "../../components/customMenu"; +import CustomMenu from "../../components/custom/customMenu"; /** * Entête de l'espace admin, avec des liens vers les différentes sections et la déconnexion. diff --git a/src/layout/admin/adminLayout.tsx b/src/layout/admin/adminLayout.tsx index a8fbadc..27cbb47 100644 --- a/src/layout/admin/adminLayout.tsx +++ b/src/layout/admin/adminLayout.tsx @@ -1,6 +1,6 @@ import AdminHeader from "./adminHeader"; import RootPaper from "../rootPaper"; -import { ResponsivePaper } from "../../components/ResponsiveLayout"; +import { ResponsivePaper } from "../../components/custom/responsiveLayout"; /** * Layout principal de l'espace admin, avec une entête et une zone de contenu. diff --git a/src/layout/auth/authLayout.tsx b/src/layout/auth/authLayout.tsx new file mode 100644 index 0000000..e63313c --- /dev/null +++ b/src/layout/auth/authLayout.tsx @@ -0,0 +1,42 @@ +import { useTheme } from "@mui/material"; +import { ResponsivePaper } from "../../components/custom/responsiveLayout"; +import RootPaper from "../rootPaper"; + +export default function AuthLayout({ + children, + component, + onSubmit, +}: { + children: React.ReactNode; + component?: React.ElementType; + onSubmit?: (e: React.SubmitEvent) => void; +}) { + const theme = useTheme(); + + return ( + + + {children} + + + ); +} diff --git a/src/layout/public/publicFooter.tsx b/src/layout/public/publicFooter.tsx index 7c74c6b..98f163d 100644 --- a/src/layout/public/publicFooter.tsx +++ b/src/layout/public/publicFooter.tsx @@ -1,5 +1,5 @@ import { Toolbar } from "@mui/material"; -import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; +import ResponsiveBodyTypography from "../../components/custom/responsiveBodyTypography"; /** * Pied de page pour les pages publiques, affichant un message de copyright. diff --git a/src/layout/public/publicLayout.tsx b/src/layout/public/publicLayout.tsx index 68ca51a..988d92b 100644 --- a/src/layout/public/publicLayout.tsx +++ b/src/layout/public/publicLayout.tsx @@ -1,7 +1,7 @@ import PublicHeader from "./publicHeader"; import PublicFooter from "./publicFooter"; import RootPaper from "../rootPaper"; -import { ResponsivePaper } from "../../components/ResponsiveLayout"; +import { ResponsivePaper } from "../../components/custom/responsiveLayout"; import { useAuth } from "../../hooks/useAuth"; import AdminHeader from "../admin/adminHeader"; diff --git a/src/layout/rootPaper.tsx b/src/layout/rootPaper.tsx index ba6a531..a2ffab6 100644 --- a/src/layout/rootPaper.tsx +++ b/src/layout/rootPaper.tsx @@ -1,5 +1,5 @@ import type { PaperProps } from "@mui/material"; -import { ResponsivePaper } from "../components/ResponsiveLayout"; +import { ResponsivePaper } from "../components/custom/responsiveLayout"; import type { ResponsiveLayoutProps } from "../types/responsiveTypes"; /** diff --git a/src/pages/admin/accountPage.tsx b/src/pages/admin/accountPage.tsx index 42ce57e..dcbacd7 100644 --- a/src/pages/admin/accountPage.tsx +++ b/src/pages/admin/accountPage.tsx @@ -1,12 +1,12 @@ import { Button, CircularProgress, Container, TextField } from "@mui/material"; -import ResponsiveTitle from "../../components/responsiveTitle"; +import ResponsiveTitle from "../../components/custom/responsiveTitle"; import { useState, useEffect } from "react"; -import { ResponsiveStack } from "../../components/ResponsiveLayout"; -import PasswordField from "../../components/passwordField"; +import { ResponsiveStack } from "../../components/custom/responsiveLayout"; +import PasswordField from "../../components/custom/passwordField"; import ResetPasswordLink from "../../components/resetPasswordLink"; import NewPasswordFields from "../../components/newPasswordFields"; -import ClosableSnackbar from "../../components/closableSnackbar"; -import CustomSnackbar from "../../components/customSnackBar"; +import ClosableSnackbar from "../../components/custom/closableSnackbar"; +import CustomSnackbar from "../../components/custom/customSnackBar"; /** * Page de gestion du compte utilisateur dans l'espace admin. diff --git a/src/pages/admin/dashboardPage.tsx b/src/pages/admin/dashboardPage.tsx index 73ca81f..d9cbb9d 100644 --- a/src/pages/admin/dashboardPage.tsx +++ b/src/pages/admin/dashboardPage.tsx @@ -1,4 +1,4 @@ -import ResponsiveTitle from "../../components/responsiveTitle"; +import ResponsiveTitle from "../../components/custom/responsiveTitle"; /** * Page d'accueil de l'espace admin. diff --git a/src/pages/auth/loginPage.tsx b/src/pages/auth/loginPage.tsx index 55671e3..08353b2 100644 --- a/src/pages/auth/loginPage.tsx +++ b/src/pages/auth/loginPage.tsx @@ -1,25 +1,20 @@ -import { TextField, Button, useTheme } from "@mui/material"; +import { TextField, Button } from "@mui/material"; import { useState } from "react"; -import { - ResponsivePaper, - ResponsiveStack, -} from "../../components/ResponsiveLayout"; -import RootPaper from "../../layout/rootPaper"; -import ResponsiveTitle from "../../components/responsiveTitle"; +import { ResponsiveStack } from "../../components/custom/responsiveLayout"; +import ResponsiveTitle from "../../components/custom/responsiveTitle"; import { Link as RouterLink, useNavigate } from "react-router-dom"; import { useEffect } from "react"; -import PasswordField from "../../components/passwordField"; +import PasswordField from "../../components/custom/passwordField"; import ResetPasswordLink from "../../components/resetPasswordLink"; import { useAuth } from "../../hooks/useAuth"; -import CustomSnackbar from "../../components/customSnackBar"; +import CustomSnackbar from "../../components/custom/customSnackBar"; +import AuthLayout from "../../layout/auth/authLayout"; /** * Page de connexion à l'espace admin. * Permet aux utilisateurs autorisés de se connecter pour accéder à l'administration du site. */ export default function Login() { - const theme = useTheme(); - const { login: loginContext, loading, isAuthenticated } = useAuth(); const navigate = useNavigate(); @@ -57,87 +52,64 @@ export default function Login() { if (loading || redirecting) return null; return ( - - + {error && } + - {error && ( - - )} - - Accéder à l'espace Administrateur - - - + + ) => + setLogin(e.target.value) + } + autoComplete="username" + disabled={loading} + required + /> + ) => + setPassword(e.target.value) + } + error={!!error} + errorText={error} + required + /> + + + + - - - + Revenir au site + + - - + +
+ ); } diff --git a/src/pages/auth/requestResetPasswordPage.tsx b/src/pages/auth/requestResetPasswordPage.tsx index bdbb8d2..0cdf9e3 100644 --- a/src/pages/auth/requestResetPasswordPage.tsx +++ b/src/pages/auth/requestResetPasswordPage.tsx @@ -1,24 +1,19 @@ -import { TextField, Button, useTheme } from "@mui/material"; -import { - ResponsivePaper, - ResponsiveStack, -} from "../../components/ResponsiveLayout"; -import RootPaper from "../../layout/rootPaper"; -import ResponsiveTitle from "../../components/responsiveTitle"; +import { TextField, Button } from "@mui/material"; +import { ResponsiveStack } from "../../components/custom/responsiveLayout"; +import ResponsiveTitle from "../../components/custom/responsiveTitle"; import { Link as RouterLink } from "react-router-dom"; import { API_URL, LOGIN_ROUTE } from "../../constants/apiConstants"; import { useState } from "react"; import { useAuth } from "../../hooks/useAuth"; -import CustomSnackbar from "../../components/customSnackBar"; -import ClosableSnackbar from "../../components/closableSnackbar"; +import CustomSnackbar from "../../components/custom/customSnackBar"; +import ClosableSnackbar from "../../components/custom/closableSnackbar"; +import AuthLayout from "../../layout/auth/authLayout"; /** * Page de demande de réinitialisation du mot de passe. * Permet aux utilisateurs de demander un lien de réinitialisation en fournissant leur adresse e-mail. */ export default function RequestResetPassword() { - const theme = useTheme(); - const { isAuthenticated } = useAuth(); const [email, setEmail] = useState(""); @@ -54,12 +49,7 @@ export default function RequestResetPassword() { }; return ( - + {submitError && ( )} @@ -69,74 +59,54 @@ export default function RequestResetPassword() { message="Si l'adresse existe, un e-mail de réinitialisation a été envoyé." severity="success" /> - - - Demander la réinitialisation - de mon mot de passe - - - + + setEmail(e.target.value)} + required + disabled={submitting} + autoComplete="email" + /> + + + + + - - + {submitting ? "Envoi..." : "Envoyer"} + - - +
+ ); } diff --git a/src/pages/auth/resetPasswordPage.tsx b/src/pages/auth/resetPasswordPage.tsx index e0d5f30..449bbb1 100644 --- a/src/pages/auth/resetPasswordPage.tsx +++ b/src/pages/auth/resetPasswordPage.tsx @@ -1,26 +1,21 @@ -import { Alert, Button, Snackbar, useTheme } from "@mui/material"; -import { - ResponsivePaper, - ResponsiveStack, -} from "../../components/ResponsiveLayout"; -import ResponsiveTitle from "../../components/responsiveTitle"; -import RootPaper from "../../layout/rootPaper"; +import { Button } from "@mui/material"; +import { ResponsiveStack } from "../../components/custom/responsiveLayout"; +import ResponsiveTitle from "../../components/custom/responsiveTitle"; import { useState } from "react"; import { useSearchParams } from "react-router-dom"; import NewPasswordFields from "../../components/newPasswordFields"; import { Link as RouterLink } from "react-router-dom"; import { API_URL, LOGIN_ROUTE } from "../../constants/apiConstants"; import { useAuth } from "../../hooks/useAuth"; -import ClosableSnackbar from "../../components/closableSnackbar"; -import CustomSnackbar from "../../components/customSnackBar"; +import ClosableSnackbar from "../../components/custom/closableSnackbar"; +import CustomSnackbar from "../../components/custom/customSnackBar"; +import AuthLayout from "../../layout/auth/authLayout"; /** * Page de réinitialisation du mot de passe. Permet aux utilisateurs de réinitialiser leur mot de passe en fournissant un nouveau mot de passe et une confirmation, après avoir cliqué sur le lien de réinitialisation reçu par e-mail. * Gère la validation des champs, l'envoi de la requête de réinitialisation au backend et l'affichage des messages de succès ou d'erreur. */ export default function ResetPassword() { - const theme = useTheme(); - const { isAuthenticated } = useAuth(); const [searchParams] = useSearchParams(); const token = searchParams.get("token") || ""; @@ -62,12 +57,7 @@ export default function ResetPassword() { }; return ( - + {submitError && ( )} @@ -77,75 +67,55 @@ export default function ResetPassword() { message="Votre mot de passe a été réinitialisé avec succès." severity="success" /> - - - Réinitialiser mon mot de passe - - - - - - + + + + + + + - - + {submitting ? "Réinitialisation..." : "Réinitialiser"} + - - + + ); } diff --git a/src/pages/public/homePage.tsx b/src/pages/public/homePage.tsx index edaccb6..247edb7 100644 --- a/src/pages/public/homePage.tsx +++ b/src/pages/public/homePage.tsx @@ -1,7 +1,7 @@ import { useTheme } from "@mui/material"; -import ResponsiveTitle from "../../components/responsiveTitle"; -import ResponsiveBodyTypography from "../../components/responsiveBodyTypography"; -import { ResponsivePaper } from "../../components/ResponsiveLayout"; +import ResponsiveTitle from "../../components/custom/responsiveTitle"; +import ResponsiveBodyTypography from "../../components/custom/responsiveBodyTypography"; +import { ResponsivePaper } from "../../components/custom/responsiveLayout"; /** * Page d'accueil publique du site.