mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
refactor: gestion des erreurs
This commit is contained in:
@ -479,6 +479,7 @@ class ResetPasswordView(APIView):
|
|||||||
profil.set_password(newProfilConnection.get('password1'))
|
profil.set_password(newProfilConnection.get('password1'))
|
||||||
profil.code = ''
|
profil.code = ''
|
||||||
profil.datePeremption = ''
|
profil.datePeremption = ''
|
||||||
|
profil.is_active = True
|
||||||
profil.save()
|
profil.save()
|
||||||
clear_cache()
|
clear_cache()
|
||||||
retourErreur=''
|
retourErreur=''
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import { fetchEstablishment } from '@/app/actions/schoolAction';
|
|||||||
import ProtectedRoute from '@/components/ProtectedRoute';
|
import ProtectedRoute from '@/components/ProtectedRoute';
|
||||||
import { getGravatarUrl } from '@/utils/gravatar';
|
import { getGravatarUrl } from '@/utils/gravatar';
|
||||||
import Footer from '@/components/Footer';
|
import Footer from '@/components/Footer';
|
||||||
|
import { getRightStr, RIGHTS } from '@/utils/rights';
|
||||||
|
|
||||||
export default function Layout({
|
export default function Layout({
|
||||||
children,
|
children,
|
||||||
@ -75,6 +76,20 @@ export default function Layout({
|
|||||||
|
|
||||||
const dropdownItems = [
|
const dropdownItems = [
|
||||||
{
|
{
|
||||||
|
type: 'info',
|
||||||
|
content: (
|
||||||
|
<div className="px-4 py-2">
|
||||||
|
<div className="font-medium">{user?.email || 'Utilisateur'}</div>
|
||||||
|
<div className="text-xs text-gray-400">{getRightStr(user?.role) || ''}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator',
|
||||||
|
content: <hr className="my-2 border-gray-200" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
label: 'Déconnexion',
|
label: 'Déconnexion',
|
||||||
onClick: handleDisconnect,
|
onClick: handleDisconnect,
|
||||||
icon: LogOut,
|
icon: LogOut,
|
||||||
@ -111,8 +126,10 @@ export default function Layout({
|
|||||||
fetchUser();
|
fetchUser();
|
||||||
}, [session]);
|
}, [session]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProtectedRoute>
|
<ProtectedRoute requiredRight={RIGHTS.ADMIN}>
|
||||||
{!isLoading && (
|
{!isLoading && (
|
||||||
<div className="flex min-h-screen bg-gray-50 relative">
|
<div className="flex min-h-screen bg-gray-50 relative">
|
||||||
{/* Sidebar avec hauteur forcée */}
|
{/* Sidebar avec hauteur forcée */}
|
||||||
@ -163,7 +180,7 @@ export default function Layout({
|
|||||||
}
|
}
|
||||||
items={dropdownItems}
|
items={dropdownItems}
|
||||||
buttonClassName=""
|
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"
|
||||||
/>
|
/>
|
||||||
</header>
|
</header>
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
|
|||||||
@ -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 { 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 { fetchMessages } from '@/app/actions/messagerieAction';
|
||||||
import ProtectedRoute from '@/components/ProtectedRoute';
|
import ProtectedRoute from '@/components/ProtectedRoute';
|
||||||
import { disconnect } from '@/app/actions/authAction';
|
import { disconnect, getUser } from '@/app/actions/authAction';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
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({
|
export default function Layout({
|
||||||
children,
|
children,
|
||||||
@ -22,6 +25,7 @@ export default function Layout({
|
|||||||
const [messages, setMessages] = useState([]);
|
const [messages, setMessages] = useState([]);
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
const [userId, setUserId] = useState(null);
|
const [userId, setUserId] = useState(null);
|
||||||
|
const [user, setUser] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
||||||
|
|
||||||
@ -33,6 +37,16 @@ export default function Layout({
|
|||||||
setIsPopupVisible(false);
|
setIsPopupVisible(false);
|
||||||
disconnect();
|
disconnect();
|
||||||
};
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchUser = async () => {
|
||||||
|
if (session) { // Vérifier que la session existe
|
||||||
|
const userData = await getUser();
|
||||||
|
setUser(userData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchUser();
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// if (status === 'loading') return;
|
// if (status === 'loading') return;
|
||||||
@ -61,9 +75,30 @@ export default function Layout({
|
|||||||
// if (isLoading) {
|
// if (isLoading) {
|
||||||
// return <div>Loading...</div>;
|
// return <div>Loading...</div>;
|
||||||
// }
|
// }
|
||||||
|
const dropdownItems = [
|
||||||
|
{
|
||||||
|
type: 'info',
|
||||||
|
content: (
|
||||||
|
<div className="px-4 py-2">
|
||||||
|
<div className="font-medium">{user?.email || 'Utilisateur'}</div>
|
||||||
|
<div className="text-xs text-gray-400">{getRightStr(user?.role) || ''}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator',
|
||||||
|
content: <hr className="my-2 border-gray-200" />
|
||||||
|
},
|
||||||
|
{ label: 'Settings', icon: Settings , onClick: () => { router.push(FE_PARENTS_SETTINGS_URL); } },
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
label: 'Déconnexion',
|
||||||
|
onClick: handleDisconnect,
|
||||||
|
icon: LogOut,
|
||||||
|
},
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<ProtectedRoute>
|
<ProtectedRoute requiredRight={RIGHTS.PARENT}>
|
||||||
<div className="flex flex-col min-h-screen bg-gray-50">
|
<div className="flex flex-col min-h-screen bg-gray-50">
|
||||||
{/* Entête */}
|
{/* Entête */}
|
||||||
<header className="bg-white border-b border-gray-200 px-4 py-2 md:px-8 md:py-4 flex items-center justify-between fixed top-0 left-0 right-0 z-10">
|
<header className="bg-white border-b border-gray-200 px-4 py-2 md:px-8 md:py-4 flex items-center justify-between fixed top-0 left-0 right-0 z-10">
|
||||||
@ -92,13 +127,16 @@ export default function Layout({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
buttonContent={<User className="h-5 w-5 md:h-6 md:w-6" />}
|
buttonContent={<Image
|
||||||
items={[
|
src={getGravatarUrl(user?.email)}
|
||||||
{ label: 'Se déconnecter', icon: LogOut, onClick: handleDisconnect },
|
alt="Profile"
|
||||||
{ label: 'Settings', icon: Settings , onClick: () => { router.push(FE_PARENTS_SETTINGS_URL); } }
|
className="w-8 h-8 rounded-full cursor-pointer"
|
||||||
]}
|
width={32}
|
||||||
buttonClassName="p-1 md:p-2 rounded-full hover:bg-gray-200"
|
height={32}
|
||||||
menuClassName="absolute right-0 mt-2 w-36 md:w-48 bg-white border border-gray-200 rounded-md shadow-lg"
|
/>}
|
||||||
|
items={dropdownItems}
|
||||||
|
buttonClassName=""
|
||||||
|
menuClassName="absolute right-0 mt-2 w-64 bg-white border border-gray-200 rounded shadow-lg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@ -85,7 +85,7 @@ export default function Page() {
|
|||||||
<div className="flex justify-center mb-4">
|
<div className="flex justify-center mb-4">
|
||||||
<Logo className="h-150 w-150" />
|
<Logo className="h-150 w-150" />
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl text-emerald-900 font-bold text-center mb-4">Authentification</h1>
|
<h1 className="text-2xl font-bold text-center mb-4">Authentification</h1>
|
||||||
<form className="max-w-md mx-auto" onSubmit={(e) => { e.preventDefault(); handleFormLogin(new FormData(e.target)); }}>
|
<form className="max-w-md mx-auto" onSubmit={(e) => { e.preventDefault(); handleFormLogin(new FormData(e.target)); }}>
|
||||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||||
<InputTextIcon name="login" type="text" IconItem={User} label="Identifiant" placeholder="Identifiant" errorMsg={userFieldError} className="w-full" />
|
<InputTextIcon name="login" type="text" IconItem={User} label="Identifiant" placeholder="Identifiant" errorMsg={userFieldError} className="w-full" />
|
||||||
@ -93,7 +93,7 @@ export default function Page() {
|
|||||||
<div className="input-group mb-4">
|
<div className="input-group mb-4">
|
||||||
</div>
|
</div>
|
||||||
<label className="text-red-500">{errorMessage}</label>
|
<label className="text-red-500">{errorMessage}</label>
|
||||||
<label><a className="float-right text-emerald-900" href={`${FE_USERS_NEW_PASSWORD_URL}`}>Mot de passe oublié ?</a></label>
|
<label><a className="float-right mb-4" href={`${FE_USERS_NEW_PASSWORD_URL}`}>Mot de passe oublié ?</a></label>
|
||||||
<div className="form-group-submit mt-4">
|
<div className="form-group-submit mt-4">
|
||||||
<Button text="Se Connecter" className="w-full" primary type="submit" name="connect" />
|
<Button text="Se Connecter" className="w-full" primary type="submit" name="connect" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export default function Page() {
|
|||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
const [password1FieldError,setPassword1FieldError] = useState("")
|
const [password1FieldError,setPassword1FieldError] = useState("")
|
||||||
const [password2FieldError,setPassword2FieldError] = useState("")
|
const [password2FieldError,setPassword2FieldError] = useState("")
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [popupVisible, setPopupVisible] = useState(false);
|
const [popupVisible, setPopupVisible] = useState(false);
|
||||||
const [popupMessage, setPopupMessage] = useState("");
|
const [popupMessage, setPopupMessage] = useState("");
|
||||||
|
|
||||||
|
|||||||
@ -8,13 +8,16 @@ import {
|
|||||||
BE_AUTH_NEW_PASSWORD_URL,
|
BE_AUTH_NEW_PASSWORD_URL,
|
||||||
FE_USERS_LOGIN_URL,
|
FE_USERS_LOGIN_URL,
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
|
|
||||||
const requestResponseHandler = async (response) => {
|
const requestResponseHandler = async (response) => {
|
||||||
const body = await response.json();
|
const body = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
const error = new Error('Form submission error');
|
|
||||||
|
const error = new Error(body?.errorMessage || "Une erreur est survenue");
|
||||||
error.details = body;
|
error.details = body;
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const requestResponseHandler = async (response) => {
|
|||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
// Throw an error with the JSON body containing the form errors
|
// 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;
|
error.details = body;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ const requestResponseHandler = async (response) => {
|
|||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
// Throw an error with the JSON body containing the form errors
|
// 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;
|
error.details = body;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const requestResponseHandler = async (response) => {
|
|||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
// Throw an error with the JSON body containing the form errors
|
// 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;
|
error.details = body;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const requestResponseHandler = async (response) => {
|
|||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
// Throw an error with the JSON body containing the form errors
|
// 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;
|
error.details = body;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ const DropdownMenu = ({ buttonContent, items, buttonClassName, menuClassName, dr
|
|||||||
const actualDropdownOpen = isControlled ? propDropdownOpen : dropdownOpen;
|
const actualDropdownOpen = isControlled ? propDropdownOpen : dropdownOpen;
|
||||||
const actualSetDropdownOpen = isControlled ? propSetDropdownOpen : setDropdownOpen;
|
const actualSetDropdownOpen = isControlled ? propSetDropdownOpen : setDropdownOpen;
|
||||||
|
|
||||||
|
|
||||||
const handleClickOutside = (event) => {
|
const handleClickOutside = (event) => {
|
||||||
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
||||||
actualSetDropdownOpen(false);
|
actualSetDropdownOpen(false);
|
||||||
@ -23,14 +22,20 @@ const DropdownMenu = ({ buttonContent, items, buttonClassName, menuClassName, dr
|
|||||||
document.removeEventListener('mousedown', handleClickOutside);
|
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 <div key={index}>{item.content}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si l'élément est de type 'separator', afficher le séparateur
|
||||||
|
if (item.type === 'separator') {
|
||||||
|
return <div key={index}>{item.content}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Par défaut ou si l'élément est de type 'item', afficher un bouton cliquable
|
||||||
return (
|
return (
|
||||||
<div className="relative" ref={menuRef}>
|
|
||||||
<button className={buttonClassName} onClick={() => actualSetDropdownOpen(!actualDropdownOpen)}>
|
|
||||||
{buttonContent}
|
|
||||||
</button>
|
|
||||||
{actualDropdownOpen && (
|
|
||||||
<div className={menuClassName}>
|
|
||||||
{items.map((item, index) => (
|
|
||||||
<button
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
className="block w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2"
|
className="block w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center gap-2"
|
||||||
@ -42,7 +47,17 @@ const DropdownMenu = ({ buttonContent, items, buttonClassName, menuClassName, dr
|
|||||||
{item.icon && <item.icon className="w-4 h-4" />}
|
{item.icon && <item.icon className="w-4 h-4" />}
|
||||||
<span className="flex items-center justify-center">{item.label}</span>
|
<span className="flex items-center justify-center">{item.label}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative" ref={menuRef}>
|
||||||
|
<button className={buttonClassName} onClick={() => actualSetDropdownOpen(!actualDropdownOpen)}>
|
||||||
|
{buttonContent}
|
||||||
|
</button>
|
||||||
|
{actualDropdownOpen && (
|
||||||
|
<div className={menuClassName}>
|
||||||
|
{items.map((item, index) => renderMenuItem(item, index))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { useSession } from 'next-auth/react';
|
|||||||
import Loader from '@/components/Loader'; // Importez le composant Loader
|
import Loader from '@/components/Loader'; // Importez le composant Loader
|
||||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||||
|
|
||||||
const ProtectedRoute = ({ children }) => {
|
const ProtectedRoute = ({ children, requiredRight }) => {
|
||||||
const { data: session, status } = useSession({
|
const { data: session, status } = useSession({
|
||||||
required: true,
|
required: true,
|
||||||
onUnauthenticated() {
|
onUnauthenticated() {
|
||||||
@ -18,7 +18,13 @@ const ProtectedRoute = ({ children }) => {
|
|||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
return session ? children : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -26,9 +26,9 @@ const options = {
|
|||||||
logger.debug("API response:", user);
|
logger.debug("API response:", user);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
logger.error('Invalid credentials')
|
||||||
throw new Error('Invalid credentials');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error('Invalid credentials')
|
||||||
throw new Error(error.message || 'Invalid credentials');
|
throw new Error(error.message || 'Invalid credentials');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
Front-End/src/utils/rights.js
Normal file
18
Front-End/src/utils/rights.js
Normal file
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user