merge conflict réglés

This commit is contained in:
Mathis 2025-04-12 14:19:23 +02:00
parent 16fd7fe9ff
commit 5fda9e9dac
5 changed files with 286 additions and 212 deletions

View File

@ -5,6 +5,7 @@ import io.vertx.ext.web.RoutingContext;
import at.favre.lib.crypto.bcrypt.BCrypt; import at.favre.lib.crypto.bcrypt.BCrypt;
import io.vertx.ext.auth.jwt.JWTAuth; import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.sqlclient.Tuple; import io.vertx.sqlclient.Tuple;
import io.vertx.sqlclient.Row;
public class AuthHandler { public class AuthHandler {
private final DatabaseService databaseService; private final DatabaseService databaseService;
@ -43,7 +44,7 @@ public class AuthHandler {
databaseService.pool databaseService.pool
.preparedQuery("INSERT INTO users (name, surname, email, gender, password, pseudo) VALUES (?, ?, ?, ?, ?, ?)") .preparedQuery("INSERT INTO users (name, surname, email, gender, password, pseudo) VALUES (?, ?, ?, ?, ?, ?)")
.execute(Tuple.of(name, surname, email, gender, hashedPassword,pseudo)) .execute(Tuple.of(name, surname, email, gender, hashedPassword, pseudo))
.onSuccess(result -> { .onSuccess(result -> {
context.response() context.response()
.setStatusCode(201) .setStatusCode(201)
@ -78,55 +79,91 @@ public class AuthHandler {
} }
databaseService.pool databaseService.pool
.preparedQuery("SELECT id,name, surname, password, points FROM users WHERE email = ?") // Ajout de name et surname .preparedQuery("SELECT id, name, surname, password, points FROM users WHERE email = ?")
.execute(Tuple.of(email)) .execute(Tuple.of(email))
.onSuccess(result -> { .onSuccess(result -> {
if (result.rowCount() == 0) { if (result.rowCount() == 0) {
context.response() context.response()
.setStatusCode(401) .setStatusCode(401)
.end(new JsonObject().put("error", "Email ou mot de passe incorrect").encode()); .end(new JsonObject().put("error", "Email ou mot de passe incorrect").encode());
return; return;
} }
var row = result.iterator().next(); // Récupération de la ligne de manière sécurisée
Integer id = row.getInteger("id"); Row row = null;
String storedHashedPassword = row.getString("password"); try {
Integer nbPointsUser = row.getInteger("points"); row = result.iterator().next();
String name = row.getString("name"); } catch (Exception e) {
String surname = row.getString("surname"); context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur lors de la récupération des données utilisateur").encode());
return;
}
BCrypt.Result verification = BCrypt.verifyer().verify(password.toCharArray(), storedHashedPassword); // Vérification que row n'est pas null avant d'y accéder
if (row == null) {
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Données utilisateur introuvables").encode());
return;
}
if (verification.verified) { // Récupération des champs de manière sécurisée
JsonObject claims = new JsonObject() Integer id = row.getInteger("id");
.put("sub", email) String storedHashedPassword = row.getString("password");
.put("name", name) Integer nbPointsUser = row.getInteger("points");
.put("surname", surname) String name = row.getString("name");
.put("id", id); String surname = row.getString("surname");
if (nbPointsUser <= 60) { // Vérification des champs obligatoires
claims.put("role", "user"); if (id == null || storedHashedPassword == null) {
} else if (nbPointsUser <= 100) { context.response()
claims.put("role", "complexe"); .setStatusCode(500)
} else if (nbPointsUser >= 200) { .end(new JsonObject().put("error", "Données utilisateur incomplètes").encode());
claims.put("role", "admin"); return;
} }
String token = jwtAuth.generateToken(claims); // Valeur par défaut pour les points si null
if (nbPointsUser == null) {
nbPointsUser = 0;
}
context.response() BCrypt.Result verification = BCrypt.verifyer().verify(password.toCharArray(), storedHashedPassword);
.setStatusCode(200)
.end(new JsonObject().put("token", token).encode()); if (verification.verified) {
} else { JsonObject claims = new JsonObject()
context.response() .put("sub", email)
.setStatusCode(401) .put("id", id);
.end(new JsonObject().put("error", "Email ou mot de passe incorrect").encode());
} // Ajout de name et surname seulement s'ils ne sont pas null
}) if (name != null) claims.put("name", name);
.onFailure(err -> { if (surname != null) claims.put("surname", surname);
System.err.println("Erreur de connexion : " + err.getMessage());
context.response() // Attribution du rôle en fonction des points
.setStatusCode(500) String role = "user"; // Rôle par défaut
.end(new JsonObject().put("error", "Erreur serveur").encode()); if (nbPointsUser >= 200) {
}); role = "admin";
}} } else if (nbPointsUser >= 100) {
role = "complexe";
}
claims.put("role", role);
String token = jwtAuth.generateToken(claims);
context.response()
.setStatusCode(200)
.end(new JsonObject().put("token", token).encode());
} else {
context.response()
.setStatusCode(401)
.end(new JsonObject().put("error", "Email ou mot de passe incorrect").encode());
}
})
.onFailure(err -> {
System.err.println("Erreur de connexion : " + err.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur serveur").encode());
});
}
}

View File

@ -6,6 +6,12 @@ import { useAuth } from "../AuthContext";
function Header() { function Header() {
const { token, user, logout } = useAuth(); const { token, user, logout } = useAuth();
const [isMenuOpen, setIsMenuOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false);
const [showAdminDropdown, setShowAdminDropdown] = useState(false);
// La fonction toggleAdminDropdown permet d'ouvrir/fermer le menu déroulant
const toggleAdminDropdown = () => {
setShowAdminDropdown((prev) => !prev);
};
return ( return (
<header className="bg-white shadow-md "> <header className="bg-white shadow-md ">
@ -90,17 +96,48 @@ function Header() {
</Link> </Link>
</li> </li>
)} )}
{(user?.role === "admin")&&( {user?.role === "admin" && (
<li> <li className="relative">
<Link <button
to="/dashboard" onClick={toggleAdminDropdown}
onClick={() => setIsMenuOpen(false)} className="flex items-center text-gray-600 hover:text-indigo-600 focus:outline-none"
className="text-gray-600 hover:text-indigo-600" >
> Admin
Administration <svg
</Link> className="ml-1 h-4 w-4 fill-current"
</li> viewBox="0 0 20 20"
>
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
</button>
{showAdminDropdown && (
<div className="absolute top-full left-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-50">
<Link
to="/dashboard"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setShowAdminDropdown(false)}
>
Dashboard
</Link>
<Link
to="/user"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setShowAdminDropdown(false)}
>
Gestion des Utilisateurs
</Link>
<Link
to="/adminobjet"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setShowAdminDropdown(false)}
>
Gestion des Objets Connectés
</Link>
</div>
)}
</li>
)} )}
<li className="sm:hidden"> <li className="sm:hidden">
<Link <Link
to="/profil" to="/profil"

View File

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState,useEffect } from "react";
import Sidebar from "./sidebar.jsx"; import Sidebar from "./sidebar.jsx";
import { RadioTower, ArrowRight, BadgePlus, Settings } from "lucide-react"; import { RadioTower, ArrowRight, BadgePlus, Settings } from "lucide-react";
import { API_BASE_URL } from "../../config.js"; import { API_BASE_URL } from "../../config.js";
@ -13,34 +13,7 @@ const initialWidgets = [
function Dashboard() { function Dashboard() {
// États simulés (données fictives pour l'exemple) // États simulés (données fictives pour l'exemple)
const [users, setUsers] = useState([ const [users, setUsers] = useState([])
{
id: 1,
username: "Alice",
email: "alice@example.com",
accessLevel: "Admin",
},
{ id: 2, username: "Bob", email: "bob@example.com", accessLevel: "User" },
{
id: 3,
username: "Charlie",
email: "charlie@example.com",
accessLevel: "Guest",
},
{
id: 4,
username: "David",
email: "david@example.com",
accessLevel: "User",
},
{ id: 5, username: "Eva", email: "eva@example.com", accessLevel: "User" },
{
id: 6,
username: "Frank",
email: "frank@example.com",
accessLevel: "Admin",
},
]);
const [logs, setLogs] = useState([ const [logs, setLogs] = useState([
{ {
@ -56,7 +29,11 @@ function Dashboard() {
timestamp: new Date().toLocaleString(), timestamp: new Date().toLocaleString(),
}, },
]); ]);
useEffect(() => {
axios.get(`${API_BASE_URL}/users`).then((response) => {
setUsers(response.data);
});
}, []);
// État pour simuler les objets présents dans la section AdminObjet.jsx // État pour simuler les objets présents dans la section AdminObjet.jsx
const [adminObjects, setAdminObjects] = useState([ const [adminObjects, setAdminObjects] = useState([
{ {
@ -111,13 +88,7 @@ function Dashboard() {
setWidgets([...widgets, newWidget]); setWidgets([...widgets, newWidget]);
setShowAddWidgetModal(false); setShowAddWidgetModal(false);
}; };
/*
useEffect(() => {
axios.get(`${API_BASE_URL}/users`).then((response) => {
setUsers(response.data);
});
}, []);
*/
return ( return (
<div className="flex min-h-screen"> <div className="flex min-h-screen">
<Sidebar /> <Sidebar />
@ -200,13 +171,13 @@ function Dashboard() {
{users.slice(0, 5).map((user) => ( {users.slice(0, 5).map((user) => (
<tr key={user.id}> <tr key={user.id}>
<td className="px-2 py-1 border border-gray-200"> <td className="px-2 py-1 border border-gray-200">
{user.username} {user.pseudo}
</td> </td>
<td className="px-2 py-1 border border-gray-200"> <td className="px-2 py-1 border border-gray-200">
{user.email} {user.email}
</td> </td>
<td className="px-2 py-1 border border-gray-200"> <td className="px-2 py-1 border border-gray-200">
{user.accessLevel} {user.role}
</td> </td>
</tr> </tr>
))} ))}

View File

@ -3,45 +3,35 @@ import Sidebar from "./sidebar.jsx";
import { API_BASE_URL } from "../../config.js"; import { API_BASE_URL } from "../../config.js";
import axios from "axios"; import axios from "axios";
// Définition de styles utilisés (si nécessaire vous pouvez convertir ces styles en classes Tailwind)
const thTd = "p-2 border border-gray-300 text-left"; const thTd = "p-2 border border-gray-300 text-left";
const th = `${thTd} bg-gray-100`; const th = `${thTd} bg-gray-100`;
function User() { function User() {
const [users, setUsers] = useState([]); const [users, setUsers] = useState([]);
const [logs, setLogs] = useState([]); const [logs, setLogs] = useState([]);
const [name, setName] = useState(""); const [name, setname] = useState("");
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [pointsInput, setPointsInput] = useState({}); const [pointsInput, setPointsInput] = useState({});
// Ajout d'un utilisateur
const handleAddUser = (e) => { const handleAddUser = (e) => {
e.preventDefault(); e.preventDefault();
const newUser = { const newUser = {
id: Date.now(), // ID généré temporairement ; en production, il sera géré par la BDD. id: Date.now(),
name, name,
email, email,
accessLevel: "User", accessLevel: "User",
points: 0, points: 0,
}; };
setUsers([...users, newUser]); setUsers([...users, newUser]);
logAction(name, "User added"); logAction(name, "Utilisateur ajouté");
setName(""); setname("");
setEmail(""); setEmail("");
// TODO : Envoyer newUser à l'API si nécessaire.
}; };
// Chargement des utilisateurs depuis l'API au montage du composant
useEffect(() => { useEffect(() => {
axios axios.get(`${API_BASE_URL}/users`).then((response) => {
.get(`${API_BASE_URL}/users`) setUsers(response.data);
.then((response) => setUsers(response.data)) });
.catch((error) =>
console.error("Erreur lors de la récupération des utilisateurs:", error)
);
}, []); }, []);
// Suppression d'un utilisateur
const handleDeleteUser = (userId) => { const handleDeleteUser = (userId) => {
const user = users.find((u) => u.id === userId); const user = users.find((u) => u.id === userId);
@ -75,7 +65,6 @@ function User() {
} }
}; };
// Changement du niveau d'accès
const handleChangeAccessLevel = (userId, newLevel) => { const handleChangeAccessLevel = (userId, newLevel) => {
setUsers( setUsers(
users.map((user) => { users.map((user) => {
@ -83,9 +72,11 @@ function User() {
const oldLevel = user.role; const oldLevel = user.role;
user.role = newLevel; user.role = newLevel;
if (user.role === "user") { if (user.role === "user") {
user.points = 60; user.points = 0;
} else if (newLevel === "admin") { } else if (user.role === "complexe") {
user.points = 100; user.points = 100;
} else if (user.role === "admin") {
user.points = 200;
} }
axios axios
.post(`${API_BASE_URL}/setUserPoints`, { .post(`${API_BASE_URL}/setUserPoints`, {
@ -94,42 +85,42 @@ function User() {
}) })
.then((response) => { .then((response) => {
alert("Le changement de niveau a bien été enregistré !"); alert("Le changement de niveau a bien été enregistré !");
console.log("Niveau changé:", response.data); console.log("Changement de niveau réussit :", response.data);
}) })
.catch((error) => { .catch((error) => {
alert("Erreur lors du changement de niveau !"); alert("Il y a eu une erreur dans le changement de niveau !");
console.error(error); console.error("Erreur lors du changement de niveau :", error);
}); });
logAction(user.name, `Access level changed from ${oldLevel} to ${newLevel}`); logAction(
user.name,
`Niveau d'accés changé de ${oldLevel} à ${newLevel}`
);
} }
return user; return user;
}) })
); );
}; };
// Ajustement des points : additionne les points saisis aux points existants
const handleAdjustPoints = (userId) => { const handleAdjustPoints = (userId) => {
const pointsToAdd = parseInt(pointsInput[userId]) || 0; const pointsToAdd = parseInt(pointsInput[userId]) || 0;
setUsers( setUsers(
users.map((user) => { users.map((user) => {
if (user.id === userId) { if (user.id === userId) {
user.points = pointsToAdd; user.points = pointsToAdd;
// On additionne au lieu de remplacer
user.points = (user.points || 0) + pointsToAdd;
axios axios
.post(`${API_BASE_URL}/setUserPoints`, { .post(`${API_BASE_URL}/setUserPoints`, {
id: user.id, id: user.id,
points: user.points, points: user.points,
}) })
.then((response) => { .then((response) => {
alert("Les points ont bien été enregistrés !"); alert("Les points ont bien été enregistré !");
console.log("Points mis à jour :", response.data); console.log("Ajout des points réussit :", response.data);
}) })
.catch((error) => { .catch((error) => {
alert("Erreur lors de l'ajustement des points !"); alert("Il y a eu une erreur dans l'ajout des points!");
console.error(error); console.error("Erreur lors de l'ajout des points :", error);
}); });
logAction(user.name, `Points adjusted by ${pointsToAdd}, new total: ${user.points}`); logAction(user.name, `Points ajustés par ${pointsToAdd}`);
} }
return user; return user;
}) })
@ -137,121 +128,107 @@ function User() {
setPointsInput({ ...pointsInput, [userId]: "" }); setPointsInput({ ...pointsInput, [userId]: "" });
}; };
// Fonction de journalisation des actions
const logAction = (name, action) => { const logAction = (name, action) => {
/*TODO*/
/*Ajouter le suivi dans un journal de log*/
const timestamp = new Date().toLocaleString(); const timestamp = new Date().toLocaleString();
setLogs([...logs, { id: Date.now(), name, action, timestamp }]); setLogs([...logs, { id: Date.now(), name, action, timestamp }]);
}; };
// Fonction pour générer et télécharger les logs dans un fichier texte
const downloadLogs = () => {
const logText = logs
.map((log) => `${log.timestamp} - ${log.name} - ${log.action}`)
.join("\n");
const blob = new Blob([logText], { type: "text/plain;charset=utf-8" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = "logs.txt";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
return ( return (
<div className="flex h-screen"> <div className="flex min-h-screen">
<Sidebar /> <Sidebar />
<main className="flex-grow p-5"> <main className="flex-grow p-5">
<section className="mt-5"> <section className="mt-5">
<h1 className="text-2xl font-bold mb-4">Gestion des utilisateurs</h1> <h1 className="text-2xl font-bold text-gray-900 mb-5">
<p className="mb-5">Gérez les utilisateurs à partir de ce panneau.</p> User Management
</h1>
<p>Manage users from this panel.</p>
{/* Formulaire d'ajout d'utilisateur */} {/* Formulaire d'ajout d'utilisateur */}
<form <form
className="grid grid-cols-[1fr_1fr_auto] gap-2 mb-5" className="gap-3 mb-5 grid grid-cols-[1fr_1fr_auto]"
onSubmit={handleAddUser} onSubmit={handleAddUser}
> >
<input <input
className="p-3 border rounded-md"
type="text" type="text"
id="name" id="name"
placeholder="Name" placeholder="name"
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setname(e.target.value)}
required required
className="p-2 border border-gray-300 rounded-md"
/> />
<input <input
className="p-3 border rounded-md"
type="email" type="email"
id="email" id="email"
placeholder="Email" placeholder="Email"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
required required
className="p-2 border border-gray-300 rounded-md"
/> />
<button <button
className="p-3 bg-green-600 text-white border-none rounded-md "
type="submit" type="submit"
className="p-2 bg-green-600 text-white rounded-md cursor-pointer"
> >
Add User Ajouter utilisateur
</button> </button>
</form> </form>
{/* Tableau des utilisateurs */} {/* Tableau des utilisateurs */}
<table className="w-full border-collapse mb-6"> <table className="w-full">
<thead> <thead>
<tr> <tr>
<th className={th}>Nom</th> <th className={`${th}`}>Nom</th>
<th className={th}>Email</th> <th className={`${th}`}>Email</th>
<th className={th}>Niveau d'accès</th> <th className={`${th}`}>Niveau d'accès</th>
<th className={th}>Points</th> <th className={`${th}`}>Points</th>
<th className={th}>Actions</th> <th className={`${th}`}>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{users.map((user) => ( {users.map((user) => (
<tr key={user.id}> <tr key={user.id}>
<td className={thTd}>{user.name}</td> <td className={`${thTd}`}>{user.name}</td>
<td className={thTd}>{user.email}</td> <td className={`${thTd}`}>{user.email}</td>
<td className={thTd}> <td className={`${thTd}`}>
<select <select
value={user.accessLevel} value={user.role}
onChange={(e) => onChange={(e) =>
handleChangeAccessLevel(user.id, e.target.value) handleChangeAccessLevel(user.id, e.target.value)
} }
className="p-1 rounded-md border border-gray-300" className="p-2 rounded-md"
> >
<option value="admin">Admin</option> <option value="admin">Admin</option>
<option value="user">User</option> <option value="user">User</option>
<option value="complexe">Complexe</option> <option value="complexe">Complexe</option>
</select> </select>
</td> </td>
<td className={thTd}> <td className={`${thTd}`}>
<input <input
className="border ml-4 w-16" className="border ml-4 w-16"
type="number" type="number"
min="0" min="0"
value={pointsInput[user.id] ?? user.points} value={pointsInput[user.id] || user.points}
onChange={(e) => onChange={(e) =>
setPointsInput({ setPointsInput({
...pointsInput, ...pointsInput,
[user.id]: e.target.value, [user.id]: e.target.value,
}) })
} }
className="w-16 ml-2 p-1 border border-gray-300 rounded-md"
/> />
<button <button
className="p-2 bg-green-600 text-white border-none rounded-md"
onClick={() => handleAdjustPoints(user.id)} onClick={() => handleAdjustPoints(user.id)}
className="p-2 bg-green-600 text-white rounded-md ml-2"
> >
Changer Changer
Changer
</button> </button>
</td> </td>
<td className={thTd}> <td className={`${thTd}`}>
<button <button
className="p-2 bg-red-600 text-white border-none rounded-md"
onClick={() => handleDeleteUser(user.id)} onClick={() => handleDeleteUser(user.id)}
className="p-2 bg-red-600 text-white rounded-md"
> >
Supprimer Supprimer
Supprimer
</button> </button>
</td> </td>
</tr> </tr>
@ -260,34 +237,28 @@ function User() {
</table> </table>
</section> </section>
{/* Tableau des logs */} {/* Tableau des logs */}
<section className="mt-10"> <section className="user-logs mt-10">
<h2 className="text-2xl font-bold mb-4"> <h2 className="text-2xl font-bold text-gray-900 mb-5">
Historique des connexions et journal des logs Historique des connexions et journal des logs{" "}
</h2> </h2>
<table className="w-full border-collapse mb-4"> <table className="w-full">
<thead> <thead>
<tr> <tr>
<th className={th}>Nom</th> <th className={`${th}`}>Nom</th>
<th className={th}>Action</th> <th className={`${th}`}>Action</th>
<th className={th}>Timestamp</th> <th className={`${th}`}>Timestamp</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{logs.map((log) => ( {logs.map((log) => (
<tr key={log.id}> <tr key={log.id}>
<td className={thTd}>{log.name}</td> <td className={`${thTd}`}>{log.name}</td>
<td className={thTd}>{log.action}</td> <td className={`${thTd}`}>{log.action}</td>
<td className={thTd}>{log.timestamp}</td> <td className={`${thTd}`}>{log.timestamp}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
<button
onClick={downloadLogs}
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md"
>
Télécharger les logs
</button>
</section> </section>
</main> </main>
</div> </div>

View File

@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Mail, Lock, AlertCircle } from "lucide-react"; import { Mail, Lock, AlertCircle, CheckCircle, Info, X } from "lucide-react";
import { useNavigate, Link } from "react-router-dom"; import { useNavigate, Link } from "react-router-dom";
import axios from "axios"; import axios from "axios";
import { useAuth } from "../AuthContext"; import { useAuth } from "../AuthContext";
@ -10,7 +10,11 @@ function Login() {
email: "", email: "",
password: "", password: "",
}); });
const [error, setError] = useState(""); const [alert, setAlert] = useState({
show: false,
type: "", // 'success', 'error', 'info', 'warning'
message: "",
});
const { login } = useAuth(); const { login } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
@ -20,14 +24,32 @@ function Login() {
...prev, ...prev,
[name]: value, [name]: value,
})); }));
if (error) setError(""); if (alert.show) setAlert({ ...alert, show: false });
};
const showAlert = (type, message) => {
setAlert({
show: true,
type,
message,
});
// Auto-hide success and info alerts after 5 seconds
if (type === 'success' || type === 'info') {
setTimeout(() => {
setAlert(prev => ({ ...prev, show: false }));
}, 5000);
}
}; };
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
setError(""); setAlert({ show: false, type: "", message: "" });
try { try {
// Afficher un message de chargement
showAlert("info", "Connexion en cours...");
const response = await axios.post(`${API_BASE_URL}/login`, formData, { const response = await axios.post(`${API_BASE_URL}/login`, formData, {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -36,37 +58,65 @@ function Login() {
const data = response.data; const data = response.data;
if (data.token) { if (data.token) {
showAlert("success", "Connexion réussie! Redirection...");
login(data.token); login(data.token);
navigate("/");
// Court délai pour montrer le message de succès avant la redirection
setTimeout(() => {
navigate("/");
}, 1000);
} else { } else {
setError("Authentification échouée : token manquant dans la réponse"); showAlert("error", "Authentification échouée : token manquant dans la réponse");
} }
} catch (error) { } catch (error) {
console.error("Erreur lors de la connexion", error); console.error("Erreur lors de la connexion", error);
if (error.response) { if (error.response) {
if (error.response.status === 401) { if (error.response.status === 401) {
setError("Email ou mot de passe incorrect"); showAlert("error", "Email ou mot de passe incorrect");
} else if (error.response.status === 422) { } else if (error.response.status === 422) {
setError("Données de formulaire invalides"); showAlert("error", "Données de formulaire invalides");
} else if (error.response.status >= 500) { } else if (error.response.status >= 500) {
setError("Erreur serveur. Veuillez réessayer plus tard."); showAlert("error", "Erreur serveur. Veuillez réessayer plus tard.");
} else { } else {
setError( showAlert("error", error.response.data.message || "Une erreur s'est produite lors de la connexion");
error.response.data.message ||
"Une erreur s'est produite lors de la connexion"
);
} }
} else if (error.request) { } else if (error.request) {
setError( showAlert("error", "Impossible de joindre le serveur. Vérifiez votre connexion internet.");
"Impossible de joindre le serveur. Vérifiez votre connexion internet."
);
} else { } else {
setError("Une erreur s'est produite. Veuillez réessayer."); showAlert("error", "Une erreur s'est produite. Veuillez réessayer.");
} }
} }
}; };
// Configuration des alertes selon le type
const alertConfig = {
success: {
bgColor: "bg-green-50",
borderColor: "border-green-200",
textColor: "text-green-700",
icon: <CheckCircle className="h-5 w-5 mr-2 mt-0.5 flex-shrink-0 text-green-500" />
},
error: {
bgColor: "bg-red-50",
borderColor: "border-red-200",
textColor: "text-red-700",
icon: <AlertCircle className="h-5 w-5 mr-2 mt-0.5 flex-shrink-0 text-red-500" />
},
info: {
bgColor: "bg-blue-50",
borderColor: "border-blue-200",
textColor: "text-blue-700",
icon: <Info className="h-5 w-5 mr-2 mt-0.5 flex-shrink-0 text-blue-500" />
},
warning: {
bgColor: "bg-yellow-50",
borderColor: "border-yellow-200",
textColor: "text-yellow-700",
icon: <AlertCircle className="h-5 w-5 mr-2 mt-0.5 flex-shrink-0 text-yellow-500" />
}
};
return ( return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50 py-12 px-4 sm:px-6 lg:px-8"> <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="md:w-96 w-full bg-white rounded-lg shadow-md p-6 mx-auto"> <div className="md:w-96 w-full bg-white rounded-lg shadow-md p-6 mx-auto">
@ -74,11 +124,19 @@ function Login() {
Connexion Connexion
</h2> </h2>
{/* Message d'erreur */} {/* Système d'alertes */}
{error && ( {alert.show && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 text-red-700 rounded-md flex items-start"> <div className={`mb-4 p-3 ${alertConfig[alert.type].bgColor} border ${alertConfig[alert.type].borderColor} ${alertConfig[alert.type].textColor} rounded-md flex items-start justify-between`}>
<AlertCircle className="h-5 w-5 mr-2 mt-0.5 flex-shrink-0" /> <div className="flex items-start">
<span className="text-sm">{error}</span> {alertConfig[alert.type].icon}
<span className="text-sm">{alert.message}</span>
</div>
<button
onClick={() => setAlert({ ...alert, show: false })}
className="ml-2 p-1 hover:bg-gray-200 rounded-full"
>
<X size={14} />
</button>
</div> </div>
)} )}