From f3490a4e9584b959450ca45c8e74e430396425b3 Mon Sep 17 00:00:00 2001 From: Luc SORIGNET Date: Sat, 1 Mar 2025 17:52:47 +0100 Subject: [PATCH] refactor: gestion des erreurs --- Back-End/Auth/views.py | 1 + Front-End/src/app/[locale]/admin/layout.js | 21 ++++++- Front-End/src/app/[locale]/parents/layout.js | 58 +++++++++++++++---- .../src/app/[locale]/users/login/page.js | 4 +- .../app/[locale]/users/password/reset/page.js | 2 +- Front-End/src/app/actions/authAction.js | 5 +- Front-End/src/app/actions/messagerieAction.js | 2 +- .../app/actions/registerFileGroupAction.js | 2 +- Front-End/src/app/actions/schoolAction.js | 2 +- .../src/app/actions/subscriptionAction.js | 2 +- Front-End/src/components/DropdownMenu.js | 43 +++++++++----- Front-End/src/components/ProtectedRoute.js | 10 +++- Front-End/src/pages/api/auth/[...nextauth].js | 4 +- Front-End/src/utils/rights.js | 18 ++++++ 14 files changed, 136 insertions(+), 38 deletions(-) create mode 100644 Front-End/src/utils/rights.js diff --git a/Back-End/Auth/views.py b/Back-End/Auth/views.py index 45c3598..20fe255 100644 --- a/Back-End/Auth/views.py +++ b/Back-End/Auth/views.py @@ -479,6 +479,7 @@ class ResetPasswordView(APIView): profil.set_password(newProfilConnection.get('password1')) profil.code = '' profil.datePeremption = '' + profil.is_active = True profil.save() clear_cache() retourErreur='' diff --git a/Front-End/src/app/[locale]/admin/layout.js b/Front-End/src/app/[locale]/admin/layout.js index 55bff65..a59ce20 100644 --- a/Front-End/src/app/[locale]/admin/layout.js +++ b/Front-End/src/app/[locale]/admin/layout.js @@ -34,6 +34,7 @@ import { fetchEstablishment } from '@/app/actions/schoolAction'; import ProtectedRoute from '@/components/ProtectedRoute'; import { getGravatarUrl } from '@/utils/gravatar'; import Footer from '@/components/Footer'; +import { getRightStr, RIGHTS } from '@/utils/rights'; export default function Layout({ children, @@ -75,6 +76,20 @@ export default function Layout({ const dropdownItems = [ { + type: 'info', + content: ( +
+
{user?.email || 'Utilisateur'}
+
{getRightStr(user?.role) || ''}
+
+ ) + }, + { + type: 'separator', + content:
+ }, + { + type: 'item', label: 'Déconnexion', onClick: handleDisconnect, icon: LogOut, @@ -111,8 +126,10 @@ export default function Layout({ fetchUser(); }, [session]); + + return ( - + {!isLoading && (
{/* Sidebar avec hauteur forcée */} @@ -163,7 +180,7 @@ export default function Layout({ } items={dropdownItems} buttonClassName="" - menuClassName="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded shadow-lg" + menuClassName="absolute right-0 mt-2 w-64 bg-white border border-gray-200 rounded shadow-lg" /> {/* Main Content */} diff --git a/Front-End/src/app/[locale]/parents/layout.js b/Front-End/src/app/[locale]/parents/layout.js index 3b7b1b8..c982afe 100644 --- a/Front-End/src/app/[locale]/parents/layout.js +++ b/Front-End/src/app/[locale]/parents/layout.js @@ -8,11 +8,14 @@ import Logo from '@/components/Logo'; // Ajout de l'importation du composant Log import { FE_PARENTS_HOME_URL,FE_PARENTS_MESSAGERIE_URL,FE_PARENTS_SETTINGS_URL } from '@/utils/Url'; // Ajout de l'importation de l'URL de la page d'accueil parent import { fetchMessages } from '@/app/actions/messagerieAction'; import ProtectedRoute from '@/components/ProtectedRoute'; -import { disconnect } from '@/app/actions/authAction'; +import { disconnect, getUser } from '@/app/actions/authAction'; import Popup from '@/components/Popup'; import logger from '@/utils/logger'; import { useSession } from 'next-auth/react'; import { FE_USERS_LOGIN_URL } from '@/utils/Url'; +import { getRightStr, RIGHTS } from '@/utils/rights'; +import { getGravatarUrl } from '@/utils/gravatar'; +import Image from 'next/image'; export default function Layout({ children, @@ -22,6 +25,7 @@ export default function Layout({ const [messages, setMessages] = useState([]); const { data: session, status } = useSession(); const [userId, setUserId] = useState(null); + const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isPopupVisible, setIsPopupVisible] = useState(false); @@ -33,6 +37,16 @@ export default function Layout({ setIsPopupVisible(false); disconnect(); }; + useEffect(() => { + const fetchUser = async () => { + if (session) { // Vérifier que la session existe + const userData = await getUser(); + setUser(userData); + } + }; + + fetchUser(); + }, [session]); // useEffect(() => { // if (status === 'loading') return; @@ -61,9 +75,30 @@ export default function Layout({ // if (isLoading) { // return
Loading...
; // } - +const dropdownItems = [ + { + type: 'info', + content: ( +
+
{user?.email || 'Utilisateur'}
+
{getRightStr(user?.role) || ''}
+
+ ) + }, + { + type: 'separator', + content:
+ }, + { label: 'Settings', icon: Settings , onClick: () => { router.push(FE_PARENTS_SETTINGS_URL); } }, + { + type: 'item', + label: 'Déconnexion', + onClick: handleDisconnect, + icon: LogOut, + }, + ]; return ( - +
{/* Entête */}
@@ -92,13 +127,16 @@ export default function Layout({ )}
} - items={[ - { label: 'Se déconnecter', icon: LogOut, onClick: handleDisconnect }, - { label: 'Settings', icon: Settings , onClick: () => { router.push(FE_PARENTS_SETTINGS_URL); } } - ]} - buttonClassName="p-1 md:p-2 rounded-full hover:bg-gray-200" - menuClassName="absolute right-0 mt-2 w-36 md:w-48 bg-white border border-gray-200 rounded-md shadow-lg" + buttonContent={Profile} + items={dropdownItems} + buttonClassName="" + menuClassName="absolute right-0 mt-2 w-64 bg-white border border-gray-200 rounded shadow-lg" />
diff --git a/Front-End/src/app/[locale]/users/login/page.js b/Front-End/src/app/[locale]/users/login/page.js index d100398..c114ed9 100644 --- a/Front-End/src/app/[locale]/users/login/page.js +++ b/Front-End/src/app/[locale]/users/login/page.js @@ -85,7 +85,7 @@ export default function Page() {
-

Authentification

+

Authentification

{ e.preventDefault(); handleFormLogin(new FormData(e.target)); }}> @@ -93,7 +93,7 @@ export default function Page() {
- +
diff --git a/Front-End/src/app/[locale]/users/password/reset/page.js b/Front-End/src/app/[locale]/users/password/reset/page.js index 07d8079..83f0f96 100644 --- a/Front-End/src/app/[locale]/users/password/reset/page.js +++ b/Front-End/src/app/[locale]/users/password/reset/page.js @@ -23,7 +23,7 @@ export default function Page() { const [errorMessage, setErrorMessage] = useState(""); const [password1FieldError,setPassword1FieldError] = useState("") const [password2FieldError,setPassword2FieldError] = useState("") - const [isLoading, setIsLoading] = useState(true); + const [isLoading, setIsLoading] = useState(false); const [popupVisible, setPopupVisible] = useState(false); const [popupMessage, setPopupMessage] = useState(""); diff --git a/Front-End/src/app/actions/authAction.js b/Front-End/src/app/actions/authAction.js index c406336..357151a 100644 --- a/Front-End/src/app/actions/authAction.js +++ b/Front-End/src/app/actions/authAction.js @@ -8,13 +8,16 @@ import { BE_AUTH_NEW_PASSWORD_URL, FE_USERS_LOGIN_URL, } from '@/utils/Url'; +import logger from '@/utils/logger'; + const requestResponseHandler = async (response) => { const body = await response.json(); if (response.ok) { return body; } - const error = new Error('Form submission error'); + + const error = new Error(body?.errorMessage || "Une erreur est survenue"); error.details = body; throw error; }; diff --git a/Front-End/src/app/actions/messagerieAction.js b/Front-End/src/app/actions/messagerieAction.js index d7623fc..c62bdad 100644 --- a/Front-End/src/app/actions/messagerieAction.js +++ b/Front-End/src/app/actions/messagerieAction.js @@ -9,7 +9,7 @@ const requestResponseHandler = async (response) => { return body; } // Throw an error with the JSON body containing the form errors - const error = new Error('Form submission error'); + const error = new Error(body?.errorMessage || "Une erreur est survenue"); error.details = body; throw error; } diff --git a/Front-End/src/app/actions/registerFileGroupAction.js b/Front-End/src/app/actions/registerFileGroupAction.js index 6de0050..2280bc5 100644 --- a/Front-End/src/app/actions/registerFileGroupAction.js +++ b/Front-End/src/app/actions/registerFileGroupAction.js @@ -11,7 +11,7 @@ const requestResponseHandler = async (response) => { return body; } // Throw an error with the JSON body containing the form errors - const error = new Error('Form submission error'); + const error = new Error(body?.errorMessage || "Une erreur est survenue"); error.details = body; throw error; } diff --git a/Front-End/src/app/actions/schoolAction.js b/Front-End/src/app/actions/schoolAction.js index 47c8270..f3def56 100644 --- a/Front-End/src/app/actions/schoolAction.js +++ b/Front-End/src/app/actions/schoolAction.js @@ -18,7 +18,7 @@ const requestResponseHandler = async (response) => { return body; } // Throw an error with the JSON body containing the form errors - const error = new Error('Form submission error'); + const error = new Error(body?.errorMessage || "Une erreur est survenue"); error.details = body; throw error; } diff --git a/Front-End/src/app/actions/subscriptionAction.js b/Front-End/src/app/actions/subscriptionAction.js index cbbc94d..6bd1bbd 100644 --- a/Front-End/src/app/actions/subscriptionAction.js +++ b/Front-End/src/app/actions/subscriptionAction.js @@ -17,7 +17,7 @@ const requestResponseHandler = async (response) => { return body; } // Throw an error with the JSON body containing the form errors - const error = new Error('Form submission error'); + const error = new Error(body?.errorMessage || "Une erreur est survenue"); error.details = body; throw error; } diff --git a/Front-End/src/components/DropdownMenu.js b/Front-End/src/components/DropdownMenu.js index 4c13fe2..c1b452e 100644 --- a/Front-End/src/components/DropdownMenu.js +++ b/Front-End/src/components/DropdownMenu.js @@ -10,7 +10,6 @@ const DropdownMenu = ({ buttonContent, items, buttonClassName, menuClassName, dr const actualDropdownOpen = isControlled ? propDropdownOpen : dropdownOpen; const actualSetDropdownOpen = isControlled ? propSetDropdownOpen : setDropdownOpen; - const handleClickOutside = (event) => { if (menuRef.current && !menuRef.current.contains(event.target)) { actualSetDropdownOpen(false); @@ -23,6 +22,34 @@ const DropdownMenu = ({ buttonContent, items, buttonClassName, menuClassName, dr document.removeEventListener('mousedown', handleClickOutside); }; }, []); + + const renderMenuItem = (item, index) => { + // Si l'élément est de type 'info', afficher simplement le contenu + if (item.type === 'info') { + return
{item.content}
; + } + + // Si l'élément est de type 'separator', afficher le séparateur + if (item.type === 'separator') { + return
{item.content}
; + } + + // Par défaut ou si l'élément est de type 'item', afficher un bouton cliquable + return ( + + ); + }; + return (
{actualDropdownOpen && (
- {items.map((item, index) => ( - - ))} + {items.map((item, index) => renderMenuItem(item, index))}
)}
diff --git a/Front-End/src/components/ProtectedRoute.js b/Front-End/src/components/ProtectedRoute.js index 61064c9..badfa7f 100644 --- a/Front-End/src/components/ProtectedRoute.js +++ b/Front-End/src/components/ProtectedRoute.js @@ -4,7 +4,7 @@ import { useSession } from 'next-auth/react'; import Loader from '@/components/Loader'; // Importez le composant Loader import { FE_USERS_LOGIN_URL } from '@/utils/Url'; -const ProtectedRoute = ({ children }) => { +const ProtectedRoute = ({ children, requiredRight }) => { const { data: session, status } = useSession({ required: true, onUnauthenticated() { @@ -18,7 +18,13 @@ const ProtectedRoute = ({ children }) => { return ; } - // Autoriser l'affichage si authentifié + // Vérifier le rôle de l'utilisateur + if (session && requiredRight && session.user.droit !== requiredRight) { + router.push(`${FE_USERS_LOGIN_URL}`); + return null; + } + + // Autoriser l'affichage si authentifié et rôle correct return session ? children : null; }; diff --git a/Front-End/src/pages/api/auth/[...nextauth].js b/Front-End/src/pages/api/auth/[...nextauth].js index 646a162..616f9bd 100644 --- a/Front-End/src/pages/api/auth/[...nextauth].js +++ b/Front-End/src/pages/api/auth/[...nextauth].js @@ -26,9 +26,9 @@ const options = { logger.debug("API response:", user); return user; } - - throw new Error('Invalid credentials'); + logger.error('Invalid credentials') } catch (error) { + logger.error('Invalid credentials') throw new Error(error.message || 'Invalid credentials'); } } diff --git a/Front-End/src/utils/rights.js b/Front-End/src/utils/rights.js new file mode 100644 index 0000000..6c8a195 --- /dev/null +++ b/Front-End/src/utils/rights.js @@ -0,0 +1,18 @@ +export const RIGHTS = { + TEACHER: 0, + ADMIN: 1, + PARENT: 2 +} + +export function getRightStr(right){ + if (right === RIGHTS.ADMIN){ + return "ADMINISTRATEUR"; + } + else if (right === RIGHTS.TEACHER){ + return "PROFESSEUR"; + }else if (right === RIGHTS.PARENT){ + return "PARENT"; + }else{ + return "NON DEFINI"; + } +} \ No newline at end of file