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;
@ -78,7 +79,7 @@ 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) {
@ -88,29 +89,64 @@ public class AuthHandler {
return; return;
} }
var row = result.iterator().next(); // Récupération de la ligne de manière sécurisée
Row row = null;
try {
row = result.iterator().next();
} catch (Exception e) {
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur lors de la récupération des données utilisateur").encode());
return;
}
// 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;
}
// Récupération des champs de manière sécurisée
Integer id = row.getInteger("id"); Integer id = row.getInteger("id");
String storedHashedPassword = row.getString("password"); String storedHashedPassword = row.getString("password");
Integer nbPointsUser = row.getInteger("points"); Integer nbPointsUser = row.getInteger("points");
String name = row.getString("name"); String name = row.getString("name");
String surname = row.getString("surname"); String surname = row.getString("surname");
// Vérification des champs obligatoires
if (id == null || storedHashedPassword == null) {
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Données utilisateur incomplètes").encode());
return;
}
// Valeur par défaut pour les points si null
if (nbPointsUser == null) {
nbPointsUser = 0;
}
BCrypt.Result verification = BCrypt.verifyer().verify(password.toCharArray(), storedHashedPassword); BCrypt.Result verification = BCrypt.verifyer().verify(password.toCharArray(), storedHashedPassword);
if (verification.verified) { if (verification.verified) {
JsonObject claims = new JsonObject() JsonObject claims = new JsonObject()
.put("sub", email) .put("sub", email)
.put("name", name)
.put("surname", surname)
.put("id", id); .put("id", id);
if (nbPointsUser <= 60) { // Ajout de name et surname seulement s'ils ne sont pas null
claims.put("role", "user"); if (name != null) claims.put("name", name);
} else if (nbPointsUser <= 100) { if (surname != null) claims.put("surname", surname);
claims.put("role", "complexe");
} else if (nbPointsUser >= 200) { // Attribution du rôle en fonction des points
claims.put("role", "admin"); String role = "user"; // Rôle par défaut
if (nbPointsUser >= 200) {
role = "admin";
} else if (nbPointsUser >= 100) {
role = "complexe";
} }
claims.put("role", role);
String token = jwtAuth.generateToken(claims); String token = jwtAuth.generateToken(claims);
@ -129,4 +165,5 @@ public class AuthHandler {
.setStatusCode(500) .setStatusCode(500)
.end(new JsonObject().put("error", "Erreur serveur").encode()); .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">
<button
onClick={toggleAdminDropdown}
className="flex items-center text-gray-600 hover:text-indigo-600 focus:outline-none"
>
Admin
<svg
className="ml-1 h-4 w-4 fill-current"
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 <Link
to="/dashboard" to="/dashboard"
onClick={() => setIsMenuOpen(false)} className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
className="text-gray-600 hover:text-indigo-600" onClick={() => setShowAdminDropdown(false)}
> >
Administration Dashboard
</Link> </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>
)} )}
<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);
// Court délai pour montrer le message de succès avant la redirection
setTimeout(() => {
navigate("/"); 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>
)} )}