Merge remote-tracking branch 'origin/main' into ruben

This commit is contained in:
Arcade69 2025-04-12 13:05:10 +02:00
commit 40249d000e
28 changed files with 1155 additions and 406 deletions

View File

@ -30,8 +30,9 @@ public class AuthHandler {
String email = body.getString("email"); String email = body.getString("email");
String gender = body.getString("gender"); String gender = body.getString("gender");
String password = body.getString("password"); String password = body.getString("password");
String pseudo = body.getString("pseudo");
if (name == null || surname == null || email == null || gender == null || password == null) { if (name == null || surname == null || email == null || gender == null || password == null || pseudo == null) {
context.response() context.response()
.setStatusCode(400) .setStatusCode(400)
.end(new JsonObject().put("error", "Tous les champs sont requis").encode()); .end(new JsonObject().put("error", "Tous les champs sont requis").encode());
@ -41,8 +42,8 @@ public class AuthHandler {
String hashedPassword = BCrypt.withDefaults().hashToString(12, password.toCharArray()); String hashedPassword = BCrypt.withDefaults().hashToString(12, password.toCharArray());
databaseService.pool databaseService.pool
.preparedQuery("INSERT INTO users (name, surname, email, gender, password) VALUES (?, ?, ?, ?, ?)") .preparedQuery("INSERT INTO users (name, surname, email, gender, password, pseudo) VALUES (?, ?, ?, ?, ?, ?)")
.execute(Tuple.of(name, surname, email, gender, hashedPassword)) .execute(Tuple.of(name, surname, email, gender, hashedPassword,pseudo))
.onSuccess(result -> { .onSuccess(result -> {
context.response() context.response()
.setStatusCode(201) .setStatusCode(201)
@ -77,7 +78,7 @@ public class AuthHandler {
} }
databaseService.pool databaseService.pool
.preparedQuery("SELECT password,points FROM users WHERE email = ?") .preparedQuery("SELECT id,name, surname, password, points FROM users WHERE email = ?") // Ajout de name et surname
.execute(Tuple.of(email)) .execute(Tuple.of(email))
.onSuccess(result -> { .onSuccess(result -> {
if (result.rowCount() == 0) { if (result.rowCount() == 0) {
@ -87,22 +88,32 @@ public class AuthHandler {
return; return;
} }
String storedHashedPassword = result.iterator().next().getString("password"); var row = result.iterator().next();
Integer nbPointsUser = result.iterator().next().getInteger("points"); Integer id = row.getInteger("id");
String storedHashedPassword = row.getString("password");
Integer nbPointsUser = row.getInteger("points");
String name = row.getString("name");
String surname = row.getString("surname");
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().put("sub", email); JsonObject claims = new JsonObject()
if(nbPointsUser<=60){ .put("sub", email)
.put("name", name)
.put("surname", surname)
.put("id", id);
if (nbPointsUser <= 60) {
claims.put("role", "user"); claims.put("role", "user");
}else if(nbPointsUser<=100){ } else if (nbPointsUser <= 100) {
claims.put("role", "complexe"); claims.put("role", "complexe");
}else if(nbPointsUser>=200){ } else if (nbPointsUser >= 200) {
claims.put("role", "admin"); claims.put("role", "admin");
} }
String token = jwtAuth.generateToken(claims); String token = jwtAuth.generateToken(claims);
context.response() context.response()
.setStatusCode(200) .setStatusCode(200)
.end(new JsonObject().put("token", token).encode()); .end(new JsonObject().put("token", token).encode());
@ -118,5 +129,4 @@ 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

@ -43,11 +43,12 @@ public class MainVerticle extends AbstractVerticle {
SetUser setUser = new SetUser(databaseService); SetUser setUser = new SetUser(databaseService);
setObjects.setUserHandler(setUser); setObjects.setUserHandler(setUser);
queryObjects.setUserHandler(setUser); queryObjects.setUserHandler(setUser);
setWeatherData.setUserHandler(setUser);
// Déclaration des routes // Déclaration des routes
router.get("/objets").handler(queryObjects::getObjects); router.get("/objets").handler(queryObjects::getObjects);
router.get("/objet").handler(queryObjects::getParticularObject); router.post("/objet").handler(queryObjects::getParticularObject);
router.post("/modifObjet").handler(setObjects::setInfoObjet); router.post("/modifObjet").handler(setObjects::setInfoObjet);
router.get("/wind").handler(queryWeather::getWindInfos); router.get("/wind").handler(queryWeather::getWindInfos);
router.get("/meteo").handler(queryWeather::getMeteoInfos); router.get("/meteo").handler(queryWeather::getMeteoInfos);
@ -56,13 +57,18 @@ public class MainVerticle extends AbstractVerticle {
router.post("/modifRangeData").handler(setWeatherData::setRangeData); router.post("/modifRangeData").handler(setWeatherData::setRangeData);
router.post("/deleteObject").handler(setObjects::deleteObject); router.post("/deleteObject").handler(setObjects::deleteObject);
router.get("/users").handler(queryUsers::getUsers); router.get("/users").handler(queryUsers::getUsers);
router.post("/user").handler(queryUsers::getUser);
router.post("/setUserPoints").handler(setUser::setUserPoints); router.post("/setUserPoints").handler(setUser::setUserPoints);
router.post("/deleteUser").handler(setUser::deleteUser); router.post("/deleteUser").handler(setUser::deleteUser);
router.post("/updateProfil").handler(setUser::updateUserProfile);
router.post("/changePassword").handler(setUser::changeUserPassword);
router.post("/publicUser").handler(queryUsers::getPublicUser);
router.get("/getCategories").handler(queryObjects::getCategories);
// Routes d'authentification // Routes d'authentification
router.post("/signup").handler(authHandler::handleSignup); router.post("/signup").handler(authHandler::handleSignup);
router.post("/login").handler(authHandler::handleLogin); router.post("/login").handler(authHandler::handleLogin);
// Création du serveur HTTP // Création du serveur HTTP
vertx.createHttpServer() vertx.createHttpServer()
.requestHandler(router) .requestHandler(router)

View File

@ -40,10 +40,37 @@ public class QueryObjects {
}); });
} }
public void getCategories(RoutingContext context) {
databaseService.pool
.query("SELECT DISTINCT type FROM weather_objects;")
.execute()
.onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur de récupération de la BDD").encode());
})
.onSuccess(rows -> {
JsonArray types = new JsonArray();
rows.forEach(row -> types.add(row.getString("type")));
context.response()
.putHeader("content-type", "application/json; charset=UTF-8")
.end(types.encode());
});
}
public void getParticularObject(RoutingContext context) { public void getParticularObject(RoutingContext context) {
String id = context.request().getParam("id"); JsonObject body = context.body().asJsonObject();
// Integer idUser = body.getInteger("idUser"); if (body == null) {
Integer idUser = 4; context.response()
.setStatusCode(400)
.end(new JsonObject().put("error", "Corps de la requête manquant").encode());
return;
}
String id = body.getString("id");
String idUser = body.getString("userId");
if (id == null) { if (id == null) {
context.response() context.response()
.setStatusCode(400) .setStatusCode(400)
@ -66,10 +93,13 @@ public class QueryObjects {
.end(new JsonObject().put("error", "Objet non trouvé").encode()); .end(new JsonObject().put("error", "Objet non trouvé").encode());
return; return;
} }
if (idUser != null) { System.out.println(idUser);
setUser.updateUserPoints(idUser, 1);
Boolean shouldUpdatePoints = body.getBoolean("shouldUpdatePoints", false);
if (Boolean.TRUE.equals(shouldUpdatePoints) && idUser != null) {
setUser.updateUserPoints(Integer.parseInt(idUser), 1);
} }
;
context.response() context.response()
.putHeader("content-type", "application/json: charset=UTF-8") .putHeader("content-type", "application/json: charset=UTF-8")
.end(getInfosObjects(rows).encode()); .end(getInfosObjects(rows).encode());
@ -91,7 +121,7 @@ public class QueryObjects {
.put("status", row.getString("status")) .put("status", row.getString("status"))
.put("batterie", row.getInteger("batterie")) .put("batterie", row.getInteger("batterie"))
.put("type_batterie", row.getString("type_batterie")) .put("type_batterie", row.getString("type_batterie"))
.put("proprio", row.getString("proprio")); .put("proprio_id", row.getInteger("proprio_id"));
objects.add(object); objects.add(object);
} }
return objects; return objects;

View File

@ -4,6 +4,7 @@ import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Row; import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.Tuple;
public class QueryUsers { public class QueryUsers {
private DatabaseService databaseService; private DatabaseService databaseService;
@ -25,19 +26,20 @@ public class QueryUsers {
.onSuccess(rows -> { .onSuccess(rows -> {
JsonArray users = new JsonArray(); JsonArray users = new JsonArray();
for (Row row : rows) { for (Row row : rows) {
int points=row.getInteger("points"); int points = row.getInteger("points");
JsonObject user = new JsonObject() JsonObject user = new JsonObject()
.put("id", row.getInteger("id")) .put("id", row.getInteger("id"))
.put("name", row.getString("name")) .put("name", row.getString("name"))
.put("surname", row.getString("surname")) .put("surname", row.getString("surname"))
.put("email", row.getString("email")) .put("email", row.getString("email"))
.put("gender", row.getString("gender")) .put("gender", row.getString("gender"))
.put("points",points); .put("pseudo",row.getString("pseudo"))
if(points<=60){ .put("points", points);
if (points <= 60) {
user.put("role", "user"); user.put("role", "user");
}else if(points<=100){ } else if (points <= 100) {
user.put("role", "complexe"); user.put("role", "complexe");
}else if(points>=200){ } else if (points >= 200) {
user.put("role", "admin"); user.put("role", "admin");
} }
users.add(user); users.add(user);
@ -48,4 +50,102 @@ public class QueryUsers {
}); });
} }
public void getUser(RoutingContext context) {
JsonObject body = context.body().asJsonObject();
if (body == null) {
context.response()
.setStatusCode(400)
.end(new JsonObject().put("error", "Corps de la requête manquant").encode());
return;
}
Integer idUser = body.getInteger("id");
databaseService.pool
.preparedQuery("SELECT * FROM users WHERE id=?;")
.execute(Tuple.of(idUser))
.onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur de récupération de la BDD").encode());
})
.onSuccess(rows -> {
if (rows.size() == 0) {
context.response()
.setStatusCode(404)
.end(new JsonObject().put("error", "Utilisateur non trouvé").encode());
return;
}
Row row = rows.iterator().next();
int points = row.getInteger("points");
JsonObject user = new JsonObject()
.put("id", row.getInteger("id"))
.put("name", row.getString("name"))
.put("surname", row.getString("surname"))
.put("email", row.getString("email"))
.put("gender", row.getString("gender"))
.put("pseudo",row.getString("pseudo"))
.put("points", points);
if (points <= 60) {
user.put("role", "user");
} else if (points <= 100) {
user.put("role", "complexe");
} else if (points >= 200) {
user.put("role", "admin");
}
context.response()
.putHeader("content-type", "application/json; charset=UTF-8")
.end(user.encode());
});
}
public void getPublicUser(RoutingContext context) {
JsonObject body = context.body().asJsonObject();
if (body == null) {
context.response()
.setStatusCode(400)
.end(new JsonObject().put("error", "Corps de la requête manquant").encode());
return;
}
Integer idUser = body.getInteger("id");
databaseService.pool
.preparedQuery("SELECT * FROM users WHERE id=?;")
.execute(Tuple.of(idUser))
.onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur de récupération de la BDD").encode());
})
.onSuccess(rows -> {
if (rows.size() == 0) {
context.response()
.setStatusCode(404)
.end(new JsonObject().put("error", "Utilisateur non trouvé").encode());
return;
}
Row row = rows.iterator().next();
int points = row.getInteger("points");
JsonObject user = new JsonObject()
.put("id", row.getInteger("id"))
.put("gender", row.getString("gender"))
.put("pseudo",row.getString("pseudo"))
.put("points", points);
if (points <= 60) {
user.put("role", "user");
} else if (points <= 100) {
user.put("role", "complexe");
} else if (points >= 200) {
user.put("role", "admin");
}
context.response()
.putHeader("content-type", "application/json; charset=UTF-8")
.end(user.encode());
});
}
} }

View File

@ -7,12 +7,15 @@ import io.vertx.sqlclient.Tuple;
public class SetObjects { public class SetObjects {
private DatabaseService databaseService; private DatabaseService databaseService;
private SetUser setUser; private SetUser setUser;
public SetObjects(DatabaseService ddbs) { public SetObjects(DatabaseService ddbs) {
this.databaseService = ddbs; this.databaseService = ddbs;
} }
public void setUserHandler(SetUser setUser){
public void setUserHandler(SetUser setUser) {
this.setUser = setUser; this.setUser = setUser;
} }
public void setInfoObjet(RoutingContext context) { public void setInfoObjet(RoutingContext context) {
JsonObject body = context.body().asJsonObject(); JsonObject body = context.body().asJsonObject();
if (body == null) { if (body == null) {
@ -22,8 +25,7 @@ public class SetObjects {
return; return;
} }
Integer id = body.getInteger("id"); Integer id = body.getInteger("id");
// Integer idUser = body.getInteger("idUser"); Integer idUser = body.getInteger("idUser");
Integer idUser = 4;
String description = body.getString("description"); String description = body.getString("description");
String type = body.getString("type"); String type = body.getString("type");
String location = body.getString("location"); String location = body.getString("location");
@ -46,85 +48,90 @@ public class SetObjects {
.end(new JsonObject().put("error", "Objet non trouvé").encode()); .end(new JsonObject().put("error", "Objet non trouvé").encode());
return; return;
} }
if(idUser!=null){ Boolean shouldUpdatePoints = body.getBoolean("shouldUpdatePoints", false);
setUser.updateUserPoints(idUser,1);
}; if (Boolean.TRUE.equals(shouldUpdatePoints) && idUser != null) {
setUser.updateUserPoints(idUser, 1);
}
context.response() context.response()
.putHeader("content-type", "application/json: charset=UTF-8") .putHeader("content-type", "application/json: charset=UTF-8")
.end(new JsonObject().put("success", "L'objet à bien été mis à jour").encode()); .end(new JsonObject().put("success", "L'objet à bien été mis à jour").encode());
return; return;
}); });
} }
public void deleteObject(RoutingContext context){
public void deleteObject(RoutingContext context) {
JsonObject body = context.body().asJsonObject(); JsonObject body = context.body().asJsonObject();
if(body== null){ if (body == null) {
context.response() context.response()
.setStatusCode(400) .setStatusCode(400)
.end(new JsonObject().put("error","Corps de la requête manquant").encode()); .end(new JsonObject().put("error", "Corps de la requête manquant").encode());
return; return;
} }
String id = body.getString("id"); String id = body.getString("id");
databaseService.pool databaseService.pool
.preparedQuery("DELETE FROM weather_objects WHERE id=?") .preparedQuery("DELETE FROM weather_objects WHERE id=?")
.execute(Tuple.of(Integer.parseInt(id))) .execute(Tuple.of(Integer.parseInt(id)))
.onFailure(e->{ .onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :"+e.getMessage()); System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response() context.response()
.setStatusCode(500) .setStatusCode(500)
.end(new JsonObject().put("error","Erreur de récupération de la BDD").encode()); .end(new JsonObject().put("error", "Erreur de récupération de la BDD").encode());
}) })
.onSuccess(rows -> { .onSuccess(rows -> {
if(rows.rowCount()==0){ if (rows.rowCount() == 0) {
context.response() context.response()
.setStatusCode(404) .setStatusCode(404)
.end(new JsonObject().put("error", "Objet non trouvé").encode()); .end(new JsonObject().put("error", "Objet non trouvé").encode());
return; return;
} }
context.response() context.response()
.putHeader("content-type","application/json: charset=UTF-8") .putHeader("content-type", "application/json: charset=UTF-8")
.end(new JsonObject().put("success", "L'objet à bien été supprimé").encode()); .end(new JsonObject().put("success", "L'objet à bien été supprimé").encode());
return; return;
}); });
} }
public void newObject(RoutingContext context){
public void newObject(RoutingContext context) {
JsonObject body = context.body().asJsonObject(); JsonObject body = context.body().asJsonObject();
if(body== null){ if (body == null) {
context.response() context.response()
.setStatusCode(400) .setStatusCode(400)
.end(new JsonObject().put("error","Corps de la requête manquant").encode()); .end(new JsonObject().put("error", "Corps de la requête manquant").encode());
return; return;
} }
// Integer idUser = body.getInteger("idUser"); Integer idUser = body.getInteger("proprio_id");
Integer idUser = 4;
String name = body.getString("nom"); String name = body.getString("nom");
String description = body.getString("description"); String description = body.getString("description");
String type = body.getString("type"); String type = body.getString("type");
String location = body.getString("location"); String location = body.getString("location");
String status = body.getString("status"); String status = body.getString("status");
String batterieType = body.getString("batterieType"); String batterieType = body.getString("batterieType");
String proprio = body.getString("proprio"); Integer proprio_id = body.getInteger("proprio_id");
databaseService.pool databaseService.pool
.preparedQuery("INSERT INTO weather_objects (name,description,type,location,status,type_batterie,proprio) VALUES (?,?,?,?,?,?,?)") .preparedQuery(
.execute(Tuple.of(name,description,type,location,status,batterieType,proprio)) "INSERT INTO weather_objects (name,description,type,location,status,type_batterie,proprio_id) VALUES (?,?,?,?,?,?,?)")
.onFailure(e->{ .execute(Tuple.of(name, description, type, location, status, batterieType, proprio_id))
System.err.println("Erreur de récupération de la BDD :"+e.getMessage()); .onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response() context.response()
.setStatusCode(500) .setStatusCode(500)
.end(new JsonObject().put("error","Erreur de récupération de la BDD").encode()); .end(new JsonObject().put("error", "Erreur de récupération de la BDD").encode());
}) })
.onSuccess(rows -> { .onSuccess(rows -> {
if(rows.rowCount()==0){ if (rows.rowCount() == 0) {
context.response() context.response()
.setStatusCode(404) .setStatusCode(404)
.end(new JsonObject().put("error", "Objet non trouvé").encode()); .end(new JsonObject().put("error", "Objet non trouvé").encode());
return; return;
} }
if(idUser!=null){ if (idUser != null) {
setUser.updateUserPoints(idUser,2); setUser.updateUserPoints(idUser, 2);
}; }
;
context.response() context.response()
.putHeader("content-type","application/json: charset=UTF-8") .putHeader("content-type", "application/json: charset=UTF-8")
.end(new JsonObject().put("success", "L'objet à bien été ajouté").encode()); .end(new JsonObject().put("success", "L'objet à bien été ajouté").encode());
return; return;
}); });

View File

@ -1,5 +1,6 @@
package com.example.starter; package com.example.starter;
import at.favre.lib.crypto.bcrypt.BCrypt;
import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Tuple; import io.vertx.sqlclient.Tuple;
@ -10,6 +11,7 @@ public class SetUser {
public SetUser(DatabaseService ddbs) { public SetUser(DatabaseService ddbs) {
this.databaseService = ddbs; this.databaseService = ddbs;
} }
public void updateUserPoints(Integer userId, Integer points) { public void updateUserPoints(Integer userId, Integer points) {
databaseService.pool databaseService.pool
.preparedQuery("UPDATE users SET points=points+? WHERE id=?") .preparedQuery("UPDATE users SET points=points+? WHERE id=?")
@ -25,6 +27,105 @@ public class SetUser {
} }
}); });
} }
public void changeUserPassword(RoutingContext context) {
JsonObject body = context.body().asJsonObject();
if (body == null) {
context.response()
.setStatusCode(400)
.end(new JsonObject().put("error", "Corps de la requête manquant").encode());
return;
}
Integer id = body.getInteger("id");
String oldPassword = body.getString("oldPassword");
String newPassword = body.getString("newPassword");
databaseService.pool
.preparedQuery("SELECT password FROM users WHERE id=?")
.execute(Tuple.of(id))
.onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur de récupération de la BDD").encode());
})
.onSuccess(rows -> {
if (rows.rowCount() == 0) {
context.response()
.setStatusCode(404)
.end(new JsonObject().put("error", "Utilisateur non trouvé").encode());
return;
}
String currentPassword = rows.iterator().next().getString("password");
BCrypt.Result verification = BCrypt.verifyer().verify(oldPassword.toCharArray(), currentPassword);
if (!verification.verified) {
context.response()
.setStatusCode(401)
.end(new JsonObject().put("error", "Ancien mot de passe incorrect").encode());
return;
}
String hashedPassword = BCrypt.withDefaults().hashToString(12, newPassword.toCharArray());
databaseService.pool
.preparedQuery("UPDATE users SET password=? WHERE id=?")
.execute(Tuple.of(hashedPassword, id))
.onFailure(e -> {
System.err.println("Erreur lors de la mise à jour du mot de passe :" + e.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject()
.put("error", "Erreur lors de la mise à jour du mot de passe")
.encode());
})
.onSuccess(updateRows -> {
context.response()
.putHeader("content-type", "application/json: charset=UTF-8")
.end(new JsonObject().put("success", "Le mot de passe a bien été mis à jour")
.encode());
});
});
}
public void updateUserProfile(RoutingContext context) {
JsonObject body = context.body().asJsonObject();
if (body == null) {
context.response()
.setStatusCode(400)
.end(new JsonObject().put("error", "Corps de la requête manquant").encode());
return;
}
Integer id = body.getInteger("id");
String name = body.getString("name");
String surname = body.getString("surname");
String pseudo = body.getString("pseudo");
databaseService.pool
.preparedQuery("UPDATE users SET name=?, surname=?, pseudo=? WHERE id=?")
.execute(Tuple.of(name, surname,pseudo, id))
.onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur de récupération de la BDD").encode());
})
.onSuccess(rows -> {
if (rows.rowCount() == 0) {
context.response()
.setStatusCode(404)
.end(new JsonObject().put("error", "Utilisateur non trouvé").encode());
return;
}
context.response()
.putHeader("content-type", "application/json: charset=UTF-8")
.end(new JsonObject()
.put("success", "Les informations de l'utilisateur ont bien été mises à jour")
.encode());
return;
});
}
public void setUserPoints(RoutingContext context) { public void setUserPoints(RoutingContext context) {
JsonObject body = context.body().asJsonObject(); JsonObject body = context.body().asJsonObject();
if (body == null) { if (body == null) {
@ -59,33 +160,34 @@ public class SetUser {
return; return;
}); });
} }
public void deleteUser(RoutingContext context){
public void deleteUser(RoutingContext context) {
JsonObject body = context.body().asJsonObject(); JsonObject body = context.body().asJsonObject();
if(body== null){ if (body == null) {
context.response() context.response()
.setStatusCode(400) .setStatusCode(400)
.end(new JsonObject().put("error","Corps de la requête manquant").encode()); .end(new JsonObject().put("error", "Corps de la requête manquant").encode());
return; return;
} }
Integer id = body.getInteger("id"); Integer id = body.getInteger("id");
databaseService.pool databaseService.pool
.preparedQuery("DELETE FROM users WHERE id=?") .preparedQuery("DELETE FROM users WHERE id=?")
.execute(Tuple.of(id)) .execute(Tuple.of(id))
.onFailure(e->{ .onFailure(e -> {
System.err.println("Erreur de récupération de la BDD :"+e.getMessage()); System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
context.response() context.response()
.setStatusCode(500) .setStatusCode(500)
.end(new JsonObject().put("error","Erreur de récupération de la BDD").encode()); .end(new JsonObject().put("error", "Erreur de récupération de la BDD").encode());
}) })
.onSuccess(rows -> { .onSuccess(rows -> {
if(rows.rowCount()==0){ if (rows.rowCount() == 0) {
context.response() context.response()
.setStatusCode(404) .setStatusCode(404)
.end(new JsonObject().put("error", "Utilisateur non trouvé").encode()); .end(new JsonObject().put("error", "Utilisateur non trouvé").encode());
return; return;
} }
context.response() context.response()
.putHeader("content-type","application/json: charset=UTF-8") .putHeader("content-type", "application/json: charset=UTF-8")
.end(new JsonObject().put("success", "L'utilisateur à bien été supprimé").encode()); .end(new JsonObject().put("success", "L'utilisateur à bien été supprimé").encode());
return; return;
}); });

View File

@ -41,8 +41,8 @@ public class SetWeatherData {
return; return;
} }
String query = String.format("UPDATE range_data SET %s_min=?, %s_max=? WHERE station_id=?", type, type); String query = String.format("UPDATE range_data SET %s_min=?, %s_max=? WHERE station_id=?", type, type);
// Integer idUser = body.getInteger("idUser"); Integer idUser = body.getInteger("idUser");
Integer idUser = 4; System.out.println("User : "+idUser);
databaseService.pool databaseService.pool
.preparedQuery( .preparedQuery(
query) query)

View File

@ -13,6 +13,7 @@
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@mui/material": "^7.0.1", "@mui/material": "^7.0.1",
"axios": "^1.8.4", "axios": "^1.8.4",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.427.0", "lucide-react": "^0.427.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-charts": "^3.0.0-beta.57", "react-charts": "^3.0.0-beta.57",
@ -2070,7 +2071,6 @@
"resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
"integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
@ -2819,6 +2819,71 @@
"is-arrayish": "^0.2.1" "is-arrayish": "^0.2.1"
} }
}, },
"node_modules/es-abstract": {
"version": "1.23.9",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
"integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==",
"dev": true,
"dependencies": {
"array-buffer-byte-length": "^1.0.2",
"arraybuffer.prototype.slice": "^1.0.4",
"available-typed-arrays": "^1.0.7",
"call-bind": "^1.0.8",
"call-bound": "^1.0.3",
"data-view-buffer": "^1.0.2",
"data-view-byte-length": "^1.0.2",
"data-view-byte-offset": "^1.0.1",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.0.0",
"es-set-tostringtag": "^2.1.0",
"es-to-primitive": "^1.3.0",
"function.prototype.name": "^1.1.8",
"get-intrinsic": "^1.2.7",
"get-proto": "^1.0.0",
"get-symbol-description": "^1.1.0",
"globalthis": "^1.0.4",
"gopd": "^1.2.0",
"has-property-descriptors": "^1.0.2",
"has-proto": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"internal-slot": "^1.1.0",
"is-array-buffer": "^3.0.5",
"is-callable": "^1.2.7",
"is-data-view": "^1.0.2",
"is-regex": "^1.2.1",
"is-shared-array-buffer": "^1.0.4",
"is-string": "^1.1.1",
"is-typed-array": "^1.1.15",
"is-weakref": "^1.1.0",
"math-intrinsics": "^1.1.0",
"object-inspect": "^1.13.3",
"object-keys": "^1.1.1",
"object.assign": "^4.1.7",
"own-keys": "^1.0.1",
"regexp.prototype.flags": "^1.5.3",
"safe-array-concat": "^1.1.3",
"safe-push-apply": "^1.0.0",
"safe-regex-test": "^1.1.0",
"set-proto": "^1.0.0",
"string.prototype.trim": "^1.2.10",
"string.prototype.trimend": "^1.0.9",
"string.prototype.trimstart": "^1.0.8",
"typed-array-buffer": "^1.0.3",
"typed-array-byte-length": "^1.0.3",
"typed-array-byte-offset": "^1.0.4",
"typed-array-length": "^1.0.7",
"unbox-primitive": "^1.1.0",
"which-typed-array": "^1.1.18"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/es-define-property": { "node_modules/es-define-property": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@ -3788,12 +3853,63 @@
"integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
"integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.8",
"call-bound": "^1.0.3",
"get-intrinsic": "^1.2.6"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-arrayish": { "node_modules/is-arrayish": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/is-async-function": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
"integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
"dev": true,
"dependencies": {
"async-function": "^1.0.0",
"call-bound": "^1.0.3",
"get-proto": "^1.0.1",
"has-tostringtag": "^1.0.2",
"safe-regex-test": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-bigint": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
"integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
"dev": true,
"dependencies": {
"has-bigints": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -4269,6 +4385,15 @@
"node": ">=4.0" "node": ">=4.0"
} }
}, },
"node_modules/jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/keyv": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",

View File

@ -14,6 +14,7 @@
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@mui/material": "^7.0.1", "@mui/material": "^7.0.1",
"axios": "^1.8.4", "axios": "^1.8.4",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.427.0", "lucide-react": "^0.427.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-charts": "^3.0.0-beta.57", "react-charts": "^3.0.0-beta.57",

View File

@ -9,12 +9,12 @@ import Objet from "./pages/Gestion/Objet.jsx";
import AddObject from "./pages/Gestion/AddObject.jsx"; import AddObject from "./pages/Gestion/AddObject.jsx";
import Signup from "./pages/Signup.jsx"; import Signup from "./pages/Signup.jsx";
import Login from "./pages/Login.jsx"; import Login from "./pages/Login.jsx";
import Settings from "./pages/Settings.jsx"; import Profil from "./pages/Profil.jsx";
import Sidebar from "./pages/Admin/sidebar.jsx"; import Sidebar from "./pages/Admin/sidebar.jsx";
import User from "./pages/Admin/User.jsx"; import User from "./pages/Admin/User.jsx";
import Dashboard from "./pages/Admin/Dashboard.jsx"; import Dashboard from "./pages/Admin/Dashboard.jsx";
import AdminObjet from "./pages/Admin/AdminObjet.jsx"; import AdminObjet from "./pages/Admin/AdminObjet.jsx";
import ProtectedRoute from './ProtectedRoute.jsx'; // Correction de l'import import ProtectedRoute from "./ProtectedRoute.jsx";
function App() { function App() {
return ( return (
@ -23,19 +23,27 @@ function App() {
<div> <div>
<Header /> <Header />
<Routes> <Routes>
{/* Routes publiques */}
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/about" element={<About />} /> <Route path="/about" element={<About />} />
<Route path="/gestion" element={<ProtectedRoute element={<Gestion />} />} />
<Route path="/gestionObjets" element={<ProtectedRoute element={<ObjectManagement />} />} />
<Route path="/objet" element={<ProtectedRoute element={<Objet />} />} />
<Route path="/signup" element={<Signup />} /> <Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/ajouterObjet" element={<ProtectedRoute element={<AddObject />} />} />
<Route path="/settings" element={<Settings />} /> {/* Routes protégées pour tous les utilisateurs connectés */}
<Route path="/sidebar" element={<Sidebar />} /> <Route path="/gestion" element={<ProtectedRoute element={<Gestion />} allowedRoles={['admin', 'complexe', 'user']} />} />
<Route path="/user" element={<User />} /> <Route path="/gestionObjets" element={<ProtectedRoute element={<ObjectManagement />} allowedRoles={['admin', 'complexe', 'user']} />} />
<Route path="/dashboard" element={<Dashboard />} /> <Route path="/objet" element={<ProtectedRoute element={<Objet />} allowedRoles={['admin', 'complexe', 'user']} />} />
<Route path="/adminobjet" element={<ProtectedRoute element={<AdminObjet />} />} />
{/* Routes protégées pour les admins et complexes */}
<Route path="/ajouterObjet" element={<ProtectedRoute element={<AddObject />} allowedRoles={['admin', 'complexe']} />} />
<Route path="/profil" element={<ProtectedRoute element={<Profil />} allowedRoles={['admin', 'complexe','user']} />} />
{/* Routes protégées pour tous les utilisateurs connectés */}
<Route path="/sidebar" element={<ProtectedRoute element={<Sidebar />} allowedRoles={['admin', 'complexe', 'user']} />} />
<Route path="/user" element={<ProtectedRoute element={<User />} allowedRoles={['admin', 'complexe', 'user']} />} />
{/* Routes protégées pour les admins uniquement */}
<Route path="/dashboard" element={<ProtectedRoute element={<Dashboard />} allowedRoles={['admin']} />} />
<Route path="/adminobjet" element={<ProtectedRoute element={<AdminObjet />} allowedRoles={['admin']} />} />
</Routes> </Routes>
</div> </div>
</Router> </Router>

View File

@ -1,5 +1,6 @@
// src/AuthContext.js
import React, { createContext, useContext, useState, useEffect } from "react"; import React, { createContext, useContext, useState, useEffect } from "react";
import { jwtDecode } from "jwt-decode";
// Créer le contexte // Créer le contexte
const AuthContext = createContext(); const AuthContext = createContext();
@ -7,22 +8,25 @@ const AuthContext = createContext();
// Hook pour accéder facilement au contexte // Hook pour accéder facilement au contexte
export const useAuth = () => useContext(AuthContext); export const useAuth = () => useContext(AuthContext);
// Fournisseur de contexte qui gère l'état du token // Fournisseur de contexte qui gère l'état du token et de l'utilisateur
export const AuthProvider = ({ children }) => { export const AuthProvider = ({ children }) => {
const [token, setToken] = useState(localStorage.getItem("token")); const [token, setToken] = useState(localStorage.getItem("token"));
const [user, setUser] = useState(null);
// Met à jour le token lorsque localStorage change // Met à jour le token et décode l'utilisateur
useEffect(() => { useEffect(() => {
const handleStorageChange = () => { if (token) {
setToken(localStorage.getItem("token")); try {
}; const decoded = jwtDecode(token);
setUser(decoded);
window.addEventListener("storage", handleStorageChange); } catch (error) {
console.error("Erreur lors du décodage du token:", error);
return () => { setUser(null);
window.removeEventListener("storage", handleStorageChange); }
}; } else {
}, []); setUser(null);
}
}, [token]);
const login = (newToken) => { const login = (newToken) => {
localStorage.setItem("token", newToken); localStorage.setItem("token", newToken);
@ -32,10 +36,11 @@ export const AuthProvider = ({ children }) => {
const logout = () => { const logout = () => {
localStorage.removeItem("token"); localStorage.removeItem("token");
setToken(null); setToken(null);
setUser(null);
}; };
return ( return (
<AuthContext.Provider value={{ token, login, logout }}> <AuthContext.Provider value={{ token, user, login, logout }}>
{children} {children}
</AuthContext.Provider> </AuthContext.Provider>
); );

View File

@ -1,16 +1,20 @@
import { useAuth } from './AuthContext'; // Utilisation du contexte d'authentification import React from "react";
import { Navigate } from 'react-router-dom'; // Utilisation de React Router pour la redirection import { Navigate } from "react-router-dom";
import { useAuth } from "./AuthContext"; // Utilisation du contexte d'authentification
function ProtectedRoute({ element }) { function ProtectedRoute({ element, allowedRoles }) {
const { token } = useAuth(); // Vérifier si un token existe, donc si l'utilisateur est authentifié const { token, user } = useAuth(); // Vérifier si un token existe, donc si l'utilisateur est authentifié
// Si l'utilisateur n'est pas authentifié, redirigez-le vers la page de login // Si l'utilisateur n'est pas authentifié, redirigez-le vers la page de login
if (!token) { if (!token) {
return <Navigate to="/login" />; return <Navigate to="/login" />;
} }
if(user){
// Si l'utilisateur est authentifié, permettez l'accès à la route if (allowedRoles && !allowedRoles.includes(user?.role)) {
return <Navigate to="/" />;
}
return element; return element;
}
} }
export default ProtectedRoute; // Export de la fonction export default ProtectedRoute;

View File

@ -1,9 +1,11 @@
import React, {useState} from "react"; import React, {useState} from "react";
import { TriangleAlert,X } from "lucide-react"; import { TriangleAlert,X } from "lucide-react";
import { useAuth } from "../AuthContext";
function AlertInactive({affAlert,setAffAlert}) { function AlertInactive({affAlert,setAffAlert}) {
const { user } = useAuth();
return ( return (
(affAlert&&( (affAlert&&(user?.role!=="user")&&(
<div className="flex flex-col md:flex-row bg-slate-600 w-full md:w-1/2 lg:w-1/3 fixed top-20 right-1 sm:right-4 rounded-lg p-4 md:p-5 items-center gap-4 md:gap-6 shadow-lg opacity-90"> <div className="flex flex-col md:flex-row bg-slate-600 w-full md:w-1/2 lg:w-1/3 fixed top-20 right-1 sm:right-4 rounded-lg p-4 md:p-5 items-center gap-4 md:gap-6 shadow-lg opacity-90">
<button onClick={()=>setAffAlert(false)}className="absolute top-2 right-2 text-white hover:text-gray-300"> <button onClick={()=>setAffAlert(false)}className="absolute top-2 right-2 text-white hover:text-gray-300">
<X/> <X/>

View File

@ -1,16 +1,17 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { BadgePlus } from "lucide-react"; import { BadgePlus } from "lucide-react";
import axios from "axios"; import axios from "axios";
import { API_BASE_URL } from "../config"; import { API_BASE_URL } from "../config";
import { useAuth } from "../AuthContext";
function FormNewObject({ isAdmin }) { function FormNewObject({ isAdmin }) {
const { user } = useAuth();
const [categorie, setCategorie] = useState({});
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [type, setType] = useState(""); const [type, setType] = useState("");
const [location, setLocalisation] = useState(""); const [location, setLocalisation] = useState("");
const [proprio,setProprio] = useState(""); const [proprio_id, setProprio_id] = useState(user?.id);
const [batterieType,setBatterieType] = useState(""); const [batterieType, setBatterieType] = useState("");
/*TODO*/
/*Definir proprio avec le nom de l'user qui ajoute*/
const [status, setStatus] = useState("active"); const [status, setStatus] = useState("active");
const [nom, setNom] = useState(""); const [nom, setNom] = useState("");
const [Response, setResponse] = useState(null); const [Response, setResponse] = useState(null);
@ -31,7 +32,7 @@ function FormNewObject({ isAdmin }) {
location, location,
status, status,
batterieType, batterieType,
proprio proprio_id,
}) })
.then((response) => { .then((response) => {
setMessRequete("Votre objet à bien été enregistré !"); setMessRequete("Votre objet à bien été enregistré !");
@ -48,6 +49,12 @@ function FormNewObject({ isAdmin }) {
setVerif(true); setVerif(true);
} }
} }
useEffect(() => {
axios.get(`${API_BASE_URL}/getCategories`).then((response) => {
setCategorie(response.data);
console.log(response.data);
});
}, []);
function resetForm() { function resetForm() {
setNom(""); setNom("");
setStatus("active"); setStatus("active");
@ -55,7 +62,7 @@ function FormNewObject({ isAdmin }) {
setType(""); setType("");
setLocalisation(""); setLocalisation("");
setBatterieType(""); setBatterieType("");
if(isAdmin)setProprio(""); if (isAdmin) set_id("");
setActive(true); setActive(true);
} }
function handleCancel() { function handleCancel() {
@ -135,15 +142,21 @@ function FormNewObject({ isAdmin }) {
> >
Type : Type :
</label> </label>
<input <select
id="type" id="type"
className="text-gray-600 border rounded-lg p-2 w-full" className="text-gray-600 border rounded-lg p-2 w-full"
type="text"
value={type} value={type}
onChange={(e) => setType(e.target.value)} onChange={(e) => setType(e.target.value)}
required required
disabled={verif} disabled={verif}
/> >
<option value="">-- Sélectionner un type --</option>
{categorie.map((cat, index) => (
<option key={index} value={cat}>
{cat}
</option>
))}
</select>
</div> </div>
<div className="mb-5"> <div className="mb-5">
@ -182,19 +195,19 @@ function FormNewObject({ isAdmin }) {
</div> </div>
<div className="mb-5"> <div className="mb-5">
<label <label
htmlFor="proprio" htmlFor="proprio_id"
className="block mb-2 text-sm font-medium text-gray-900" className="block mb-2 text-sm font-medium text-gray-900"
> >
Propriétaire : Propriétaire :
</label> </label>
<input <input
id="proprio" id="proprio_id"
className="text-gray-600 border rounded-lg p-2 w-full" className="text-gray-600 border rounded-lg p-2 w-full"
type="text" type="number"
value={proprio} value={proprio_id}
onChange={(e) => setProprio(e.target.value)} onChange={(e) => setProprio_id(e.target.value)}
required required
disabled={verif||!isAdmin} disabled={verif || !isAdmin}
/> />
</div> </div>

View File

@ -1,10 +1,10 @@
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import { X, Menu, LogIn, UserPlus, LogOut, Settings } from "lucide-react"; import { X, Menu, LogIn, UserPlus, LogOut, User } from "lucide-react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useAuth } from "../AuthContext"; import { useAuth } from "../AuthContext";
function Header() { function Header() {
const { token, logout } = useAuth(); const { token, user, logout } = useAuth();
const [isMenuOpen, setIsMenuOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false);
const [showAdminDropdown, setShowAdminDropdown] = useState(false); const [showAdminDropdown, setShowAdminDropdown] = useState(false);
@ -128,22 +128,44 @@ function Header() {
</> </>
) : ( ) : (
<> <>
{user?.role === "user" ? (
<li>
<Link
to="/gestionObjets"
onClick={() => setIsMenuOpen(false)}
className="text-gray-600 hover:text-indigo-600"
>
Visualisation
</Link>
</li>
) : (
<li>
<Link
to="/gestion"
onClick={() => setIsMenuOpen(false)}
className="text-gray-600 hover:text-indigo-600"
>
Gestion
</Link>
</li>
)}
<li className="sm:hidden"> <li className="sm:hidden">
<Link <Link
to="/settings" to="/profil"
onClick={()=>setIsMenuOpen(false)} onClick={() => setIsMenuOpen(false)}
className="flex items-center gap-2 text-gray-600 hover:text-indigo-600" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600"
> >
<Settings size={20} /> <User size={20} />
<span>Paramètres</span> <span>Profil</span>
<span></span> <span></span>
</Link> </Link>
</li> </li>
<li className="sm:hidden"> <li className="sm:hidden">
<button <button
onClick={()=>{ onClick={() => {
logout(); logout();
setIsMenuOpen(false)}} setIsMenuOpen(false);
}}
className="flex items-center gap-2 text-gray-600 hover:text-red-600" className="flex items-center gap-2 text-gray-600 hover:text-red-600"
> >
<LogOut size={20} /> <LogOut size={20} />
@ -158,7 +180,7 @@ function Header() {
<div className="hidden sm:flex gap-4 "> <div className="hidden sm:flex gap-4 ">
<Link <Link
to="/login" to="/login"
onClick={()=>setIsMenuOpen(false)} onClick={() => setIsMenuOpen(false)}
className="hover:text-indigo-600 flex items-center gap-2" className="hover:text-indigo-600 flex items-center gap-2"
> >
<LogIn size={20} /> <LogIn size={20} />
@ -166,7 +188,7 @@ function Header() {
</Link> </Link>
<Link <Link
to="/signup" to="/signup"
onClick={()=>setIsMenuOpen(false)} onClick={() => setIsMenuOpen(false)}
className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700" className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700"
> >
<UserPlus size={20} /> <UserPlus size={20} />
@ -176,11 +198,12 @@ function Header() {
) : ( ) : (
<div className="hidden sm:flex gap-4"> <div className="hidden sm:flex gap-4">
<Link <Link
to="/settings" to="/profil"
onClick={()=>setIsMenuOpen(false)} onClick={() => setIsMenuOpen(false)}
className="flex items-center gap-2 text-gray-600 hover:text-indigo-600" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600"
> >
<Settings size={20} /> <User size={20} />
<span></span>
</Link> </Link>
<button <button
onClick={logout} onClick={logout}

View File

@ -1,7 +1,9 @@
import React from "react"; import React from "react";
import { Info } from "lucide-react"; import { Info } from "lucide-react";
import { useAuth } from "../AuthContext";
function InfoObject({ object,defafficherModif }) { function InfoObject({ object,defafficherModif }) {
const {user} = useAuth();
return ( return (
<div key={object.id} className="bg-white p-6 rounded-xl min-w-5xl"> <div key={object.id} className="bg-white p-6 rounded-xl min-w-5xl">
<div className="flex align-items gap-6 mb-6"> <div className="flex align-items gap-6 mb-6">
@ -36,9 +38,11 @@ function InfoObject({ object,defafficherModif }) {
</p> </p>
<p className="text-gray-600">{object.last_update}</p> <p className="text-gray-600">{object.last_update}</p>
</div> </div>
{user?.role!=="user"&&(
<div className="flex items-center gap-4 mb-1"> <div className="flex items-center gap-4 mb-1">
<a className="text-blue-500 hover:cursor-pointer" onClick={(()=>defafficherModif(true))}>Modifier ces infos</a> <a className="text-blue-500 hover:cursor-pointer" onClick={(()=>defafficherModif(true))}>Modifier ces infos</a>
</div> </div>
)}
</div> </div>
); );
} }

View File

@ -2,8 +2,10 @@ import React, { useState } from "react";
import { Info } from "lucide-react"; import { Info } from "lucide-react";
import axios from "axios"; import axios from "axios";
import { API_BASE_URL } from "../config"; import { API_BASE_URL } from "../config";
import {useAuth} from "../AuthContext";
function ModifObject({ object, defafficherModif }) { function ModifObject({ object, defafficherModif }) {
const {user}=useAuth();
const [description, setDescription] = useState(object.description || ""); const [description, setDescription] = useState(object.description || "");
const [type, setType] = useState(object.type || ""); const [type, setType] = useState(object.type || "");
const [location, setLocalisation] = useState(object.location || ""); const [location, setLocalisation] = useState(object.location || "");
@ -15,10 +17,12 @@ function ModifObject({ object, defafficherModif }) {
axios axios
.post(`${API_BASE_URL}/modifObjet`, { .post(`${API_BASE_URL}/modifObjet`, {
id: object.id, id: object.id,
idUser:user.id,
description, description,
type, type,
location, location,
status, status,
shouldUpdatePoints:true
}) })
.then((response) => { .then((response) => {
console.log("Modification réussie :", response.data); console.log("Modification réussie :", response.data);

View File

@ -4,6 +4,7 @@ import { Bell } from "lucide-react";
import Slider from "@mui/material/Slider"; import Slider from "@mui/material/Slider";
import { API_BASE_URL } from "../config"; import { API_BASE_URL } from "../config";
import axios from "axios"; import axios from "axios";
import { useAuth } from "../AuthContext";
const identifiant = new URLSearchParams(window.location.search).get("id"); const identifiant = new URLSearchParams(window.location.search).get("id");
function ParticularMeteo({ function ParticularMeteo({
@ -16,6 +17,7 @@ function ParticularMeteo({
setGraphStates, setGraphStates,
graphRefs, graphRefs,
}) { }) {
const {user} = useAuth();
const [affRegles, setAffRegles] = useState(false); const [affRegles, setAffRegles] = useState(false);
const [rangeValue, setRangeValue] = useState([0, 0]); const [rangeValue, setRangeValue] = useState([0, 0]);
const [alerteActive, setAlerteActive] = useState(false); const [alerteActive, setAlerteActive] = useState(false);
@ -71,6 +73,7 @@ function ParticularMeteo({
axios axios
.post(`${API_BASE_URL}/modifRangeData`, { .post(`${API_BASE_URL}/modifRangeData`, {
id: identifiant, id: identifiant,
idUser:user.id,
min: parseFloat(rangeValue[0]), min: parseFloat(rangeValue[0]),
max: parseFloat(rangeValue[1]), max: parseFloat(rangeValue[1]),
type, type,
@ -107,6 +110,7 @@ function ParticularMeteo({
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
{user?.role!=="user" && (
<button <button
onClick={() => { onClick={() => {
setAffRegles(!affRegles); setAffRegles(!affRegles);
@ -117,7 +121,7 @@ function ParticularMeteo({
size={30} size={30}
/> />
</button> </button>
)}
<BoutonGraphique <BoutonGraphique
type={type} type={type}
graphStates={graphStates} graphStates={graphStates}

View File

@ -0,0 +1,50 @@
import React,{useEffect, useState} from "react";
import { User } from "lucide-react";
import axios from "axios";
import { API_BASE_URL } from "../config";
function UserInfosObject({ user}) {
const [userInfo,setuserInfo]=useState({});
useEffect(()=>{
console.log(user);
axios
.post(`${API_BASE_URL}/publicUser`, {
id: user,
})
.then((response) => {
setuserInfo(response.data);
console.log("Modification réussie :", response.data);
})
.catch((error) => {
console.error("Erreur lors de la modification :", error);
});
},[user]);
return (
<div className="bg-white p-6 rounded-xl min-w-5xl">
<div className="flex align-items gap-6 mb-6">
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-1">
<User className="text-indigo-600" size={24} />
</div>
<h1 className="text-black text-2xl font-bold mb-1 ">Propriétaire</h1>
</div>
<div className="mb-5">
<p className="text-black-900 font-bold">Pseudo :</p>
<p className="text-gray-600 capitalize">{userInfo.pseudo}</p>
</div>
<div className="mb-5">
<p className="text-black-900 font-bold">Genre :</p>
<p className="text-gray-600 capitalize">{userInfo.gender}</p>
</div>
<div className="mb-5">
<p className="text-black-900 font-bold">Nombre de points :</p>
<p className="text-gray-600">{userInfo.points}</p>
</div>
</div>
);
}
export default UserInfosObject;

View File

@ -205,7 +205,7 @@ function AdminObjet() {
{obj.location} {obj.location}
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{obj.proprio} {obj.proprio_id}
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{obj.status} {obj.status}

View File

@ -9,19 +9,21 @@ import {
UserPlus, UserPlus,
RadioTower, RadioTower,
Binoculars, Binoculars,
Settings,
BadgePlus, BadgePlus,
} from "lucide-react"; } from "lucide-react";
import { useAuth } from "../../AuthContext";
function Gestion() { function Gestion() {
return ( return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50"> <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="text-center mb-12"> <div className="text-center mb-12">
<h2 className="text-4xl font-bold text-gray-900 mb-4"> <h2 className="text-4xl font-bold text-gray-900 mb-4">
Bienvenue dans le module <b>Gestion</b>. Bienvenue dans le module <b>Gestion</b>.
</h2> </h2>
<p className="text-xl text-gray-600 max-w-3xl mx-auto"> <p className="text-xl text-gray-600 max-w-3xl mx-auto">
Ce module vous permet de gérer les capteur et stations connectés de France de manière simple et efficace. Ce module vous permet de gérer les capteurs et stations connectés de France de manière simple et efficace.
</p> </p>
</div> </div>
@ -43,6 +45,7 @@ function Gestion() {
Explorer les objets <ArrowRight size={16} className="ml-2" /> Explorer les objets <ArrowRight size={16} className="ml-2" />
</a> </a>
</div> </div>
<div className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow"> <div className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow">
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4"> <div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
<BadgePlus className="text-indigo-600" size={24} /> <BadgePlus className="text-indigo-600" size={24} />

View File

@ -1,14 +1,17 @@
import React from "react"; import React from "react";
import { Search, ArrowRight, RadioTower,Plus } from "lucide-react"; import { Search, ArrowRight, RadioTower, Plus } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import axios from "axios"; import axios from "axios";
import { API_BASE_URL } from "../../config"; import { API_BASE_URL } from "../../config";
import { useAuth } from "../../AuthContext";
function ObjectManagement() { function ObjectManagement() {
const {user} = useAuth();
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [activeFilter, setActiveFilter] = useState(""); const [activeFilter, setActiveFilter] = useState("");
const [objects, setObjects] = useState([]); const [objects, setObjects] = useState([]);
const [nbAffObject,setnbAffObject] = useState(6); const [nbAffObject, setnbAffObject] = useState(6);
const filteredDATA = objects.filter((node) => { const filteredDATA = objects.filter((node) => {
const matchesSearchQuery = const matchesSearchQuery =
searchQuery === "" || searchQuery === "" ||
@ -29,12 +32,13 @@ function ObjectManagement() {
setObjects(response.data); setObjects(response.data);
}); });
}, []); }, []);
return ( return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50"> <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="text-center mb-12"> <div className="text-center mb-12">
<h2 className="text-4xl font-bold text-gray-900 mb-4"> <h2 className="text-4xl font-bold text-gray-900 mb-4">
Gestion des <b>Objets</b> connectés. {(user?.role!=="user")?("Gestion"):("Visualisation")} des <b>Objets</b> connectés.
</h2> </h2>
</div> </div>
<div className="max-w-3xl mx-auto mb-12"> <div className="max-w-3xl mx-auto mb-12">
@ -51,10 +55,12 @@ function ObjectManagement() {
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
/> />
</div> </div>
<div className="flex gap-4 mt-4 justify-center">
{/* Filtres responsifs - utilisation de flex-wrap et responsive spacing */}
<div className="flex flex-wrap gap-2 mt-4 justify-center">
<button <button
onClick={() => setActiveFilter("")} onClick={() => setActiveFilter("")}
className={`px-4 py-2 rounded-lg ${ className={`px-4 py-2 rounded-lg mb-2 ${
activeFilter === "" activeFilter === ""
? "bg-indigo-600 text-white" ? "bg-indigo-600 text-white"
: "bg-white text-gray-600" : "bg-white text-gray-600"
@ -64,7 +70,7 @@ function ObjectManagement() {
</button> </button>
<button <button
onClick={() => setActiveFilter("Station")} onClick={() => setActiveFilter("Station")}
className={`px-4 py-2 rounded-lg ${ className={`px-4 py-2 rounded-lg mb-2 ${
activeFilter === "Station" activeFilter === "Station"
? "bg-indigo-600 text-white" ? "bg-indigo-600 text-white"
: "bg-white text-gray-600" : "bg-white text-gray-600"
@ -74,7 +80,7 @@ function ObjectManagement() {
</button> </button>
<button <button
onClick={() => setActiveFilter("Capteur")} onClick={() => setActiveFilter("Capteur")}
className={`px-4 py-2 rounded-lg ${ className={`px-4 py-2 rounded-lg mb-2 ${
activeFilter === "Capteur" activeFilter === "Capteur"
? "bg-indigo-600 text-white" ? "bg-indigo-600 text-white"
: "bg-white text-gray-600" : "bg-white text-gray-600"
@ -84,7 +90,7 @@ function ObjectManagement() {
</button> </button>
<button <button
onClick={() => setActiveFilter("Active")} onClick={() => setActiveFilter("Active")}
className={`px-4 py-2 rounded-lg ${ className={`px-4 py-2 rounded-lg mb-2 ${
activeFilter === "Active" activeFilter === "Active"
? "bg-indigo-600 text-white" ? "bg-indigo-600 text-white"
: "bg-white text-gray-600" : "bg-white text-gray-600"
@ -94,7 +100,7 @@ function ObjectManagement() {
</button> </button>
<button <button
onClick={() => setActiveFilter("Inactive")} onClick={() => setActiveFilter("Inactive")}
className={`px-4 py-2 rounded-lg ${ className={`px-4 py-2 rounded-lg mb-2 ${
activeFilter === "Inactive" activeFilter === "Inactive"
? "bg-indigo-600 text-white" ? "bg-indigo-600 text-white"
: "bg-white text-gray-600" : "bg-white text-gray-600"
@ -104,14 +110,16 @@ function ObjectManagement() {
</button> </button>
</div> </div>
</div> </div>
<div className="grid md:grid-cols-3 gap-8">
{/* Grille responsive pour les objets */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8">
{filteredDATA.length === 0 ? ( {filteredDATA.length === 0 ? (
<p>Aucun objet trouvé</p> <p className="text-center col-span-full">Aucun objet trouvé</p>
) : ( ) : (
filteredDATA.slice(0,nbAffObject).map((object) => ( filteredDATA.slice(0, nbAffObject).map((object) => (
<div <div
key={object.id} key={object.id}
className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow " className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow"
> >
{object.status === "active" ? ( {object.status === "active" ? (
<div className="relative w-full"> <div className="relative w-full">
@ -143,14 +151,21 @@ function ObjectManagement() {
)) ))
)} )}
</div> </div>
{(nbAffObject<filteredDATA.length)&&(
{(nbAffObject < filteredDATA.length) && (
<div className="flex items-center flex-col mt-6"> <div className="flex items-center flex-col mt-6">
<button onClick={()=>{setnbAffObject((prev)=>prev+6 )}}><Plus size={40}/></button> <button
<label>Voir plus</label> onClick={() => {setnbAffObject((prev) => prev + 6)}}
className="hover:bg-indigo-50 p-2 rounded-full transition-colors"
>
<Plus size={40} className="text-indigo-600" />
</button>
<label className="text-indigo-600 font-medium">Voir plus</label>
</div> </div>
)} )}
</div> </div>
</div> </div>
); );
} }
export default ObjectManagement; export default ObjectManagement;

View File

@ -12,7 +12,10 @@ import WindInfo from "../../components/WindInfo";
import MeteoInfos from "../../components/MeteoInfos"; import MeteoInfos from "../../components/MeteoInfos";
import MeteoGraph from "../../components/MeteoGraph"; import MeteoGraph from "../../components/MeteoGraph";
import BatterieInfo from "../../components/BatterieInfo"; import BatterieInfo from "../../components/BatterieInfo";
import { useAuth } from "../../AuthContext";
import UserInfosObject from "../../components/UserInfosObject";
function Objet() { function Objet() {
const {user} =useAuth();
const identifiant = new URLSearchParams(window.location.search).get("id"); const identifiant = new URLSearchParams(window.location.search).get("id");
const [object, setObject] = useState({}); const [object, setObject] = useState({});
const [graphStates, setGraphStates] = useState({ const [graphStates, setGraphStates] = useState({
@ -28,12 +31,20 @@ function Objet() {
humidity: useRef(null), humidity: useRef(null),
wind: useRef(null), wind: useRef(null),
}; };
useEffect(() => { useEffect(() => {
axios.get(`${API_BASE_URL}/objet?id=${identifiant}`).then((response) => { axios
.post(`${API_BASE_URL}/objet`, {
id: identifiant,
userId:user.id,
shouldUpdatePoints:true,
})
.then((response) => {
setObject(response.data[0]); setObject(response.data[0]);
})
.catch((error) => {
console.error("Erreur lors de la récupération :", error);
}); });
}, [identifiant]); }, [user]);
return object && object.id ? ( return object && object.id ? (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50"> <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
<div className=" max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12"> <div className=" max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
@ -61,6 +72,8 @@ function Objet() {
graphRefs={graphRefs} graphRefs={graphRefs}
/> />
<BatterieInfo object={object} /> <BatterieInfo object={object} />
<UserInfosObject user={object.proprio_id}/>
</div> </div>
{graphStates.wind && <WindGraph object={object} reference={graphRefs.wind} />} {graphStates.wind && <WindGraph object={object} reference={graphRefs.wind} />}

View File

@ -8,6 +8,7 @@ function Home() {
const [activeFilter, setActiveFilter] = useState('all'); const [activeFilter, setActiveFilter] = useState('all');
const [name, setName] = useState([]); const [name, setName] = useState([]);
const { token, logout } = useAuth(); const { token, logout } = useAuth();
const { user } = useAuth();
return ( return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50"> <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
@ -15,10 +16,15 @@ function Home() {
<div className="text-center mb-12"> <div className="text-center mb-12">
<h2 className="text-4xl font-bold text-gray-900 mb-4"> <h2 className="text-4xl font-bold text-gray-900 mb-4">
Bienvenue dans ta ville intelligente.</h2> Bienvenue dans ta ville intelligente.</h2>
{token ? ( {user ? (
<><h2>Tu es connecté</h2> <>
<h1>Bienvenue, {user.name} {user.surname}!</h1>
<p>Email : {user.sub}</p>
<p>Rôle : {user.role}</p>
<p>Rôle : {user.id}</p>
</>):( </>
):(
<h2>Non connecté</h2> <h2>Non connecté</h2>
)} )}
<p className="text-xl text-gray-600 max-w-3xl mx-auto"> <p className="text-xl text-gray-600 max-w-3xl mx-auto">

View File

@ -1,7 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Mail, Lock } from "lucide-react"; import { Mail, Lock, AlertCircle } from "lucide-react";
import { useNavigate, Link } from "react-router-dom"; import { useNavigate, Link } from "react-router-dom";
import axios from "axios"; // Assurez-vous d'avoir axios importé import axios from "axios";
import { useAuth } from "../AuthContext"; import { useAuth } from "../AuthContext";
import { API_BASE_URL } from "../config"; import { API_BASE_URL } from "../config";
@ -10,8 +10,9 @@ function Login() {
email: "", email: "",
password: "", password: "",
}); });
const { login } = useAuth(); // Utilisation du hook useAuth pour accéder à la fonction login const [error, setError] = useState("");
const navigate = useNavigate(); // Initialisation de useNavigate const { login } = useAuth();
const navigate = useNavigate();
const handleChange = (e) => { const handleChange = (e) => {
const { name, value } = e.target; const { name, value } = e.target;
@ -19,43 +20,68 @@ function Login() {
...prev, ...prev,
[name]: value, [name]: value,
})); }));
if (error) setError("");
}; };
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
setError("");
try { try {
const response = await fetch(`${API_BASE_URL}/login`, { const response = await axios.post(`${API_BASE_URL}/login`, formData, {
method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(formData),
}); });
const data = response.data;
// Récupérer les données JSON de la réponse
const data = await response.json();
// Vérifiez que la réponse contient bien un token
if (data.token) { if (data.token) {
// Appel de la fonction login du contexte pour stocker le token
login(data.token); login(data.token);
// Rediriger vers la page d'accueil après la connexion
navigate("/"); navigate("/");
} else { } else {
console.error("Token manquant dans la réponse"); setError("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.status === 401) {
setError("Email ou mot de passe incorrect");
} else if (error.response.status === 422) {
setError("Données de formulaire invalides");
} else if (error.response.status >= 500) {
setError("Erreur serveur. Veuillez réessayer plus tard.");
} else {
setError(
error.response.data.message ||
"Une erreur s'est produite lors de la connexion"
);
}
} else if (error.request) {
setError(
"Impossible de joindre le serveur. Vérifiez votre connexion internet."
);
} else {
setError("Une erreur s'est produite. Veuillez réessayer.");
}
} }
}; };
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">
<h2 className="text-2xl font-bold text-gray-800 mb-6 text-center"> <h2 className="text-2xl font-bold text-gray-800 mb-6 text-center">
Connexion Connexion
</h2> </h2>
{/* Message d'erreur */}
{error && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 text-red-700 rounded-md flex items-start">
<AlertCircle className="h-5 w-5 mr-2 mt-0.5 flex-shrink-0" />
<span className="text-sm">{error}</span>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
{/* Email */} {/* Email */}
<div> <div>
@ -94,6 +120,7 @@ function Login() {
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
required required
minLength="8" minLength="8"
autoComplete="current-password"
/> />
</div> </div>
</div> </div>

View File

@ -0,0 +1,328 @@
import React, { useState, useEffect } from 'react';
import { Mail, User, Lock, Edit, Save } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { API_BASE_URL } from "../config";
import { useAuth } from "../AuthContext";
import axios from "axios";
function Profil() {
const [userData, setUserData] = useState({});
const { user } = useAuth();
useEffect(() => {
if (user) {
console.log("user.role:", user.id);
}
}, [user]);
const [formData, setFormData] = useState({
oldPassword: '',
newPassword: '',
confirmPassword: ''
});
const [editMode, setEditMode] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [successMessage, setSuccessMessage] = useState('');
const navigate = useNavigate();
useEffect(() => {
axios
.post(`${API_BASE_URL}/user`, {
id: user.id,
})
.then((response) => {
setUserData(response.data);
console.log("Infos récupérées :", response.data);
})
.catch((error) => {
console.error("Erreur lors de la récupération :", error);
});
}, [user]);
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleProfileChange = (e) => {
const { name, value } = e.target;
setUserData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
setErrorMessage('');
setSuccessMessage('');
if (formData.newPassword !== formData.confirmPassword) {
setErrorMessage("Les nouveaux mots de passe ne correspondent pas !");
return;
}
try {
axios
.post(`${API_BASE_URL}/changePassword`, {
id: userData.id,
oldPassword: formData.oldPassword,
newPassword: formData.newPassword
})
.then((response) => {
console.log("Modification du mot de passe réussie :", response.data);
setSuccessMessage("Mot de passe modifié avec succès !");
setFormData({
oldPassword: '',
newPassword: '',
confirmPassword: ''
});
})
.catch((error) => {
console.error("Erreur lors de la modification du mot de passe :", error);
setErrorMessage(error.response?.data?.error || "Une erreur est survenue");
});
setSuccessMessage("Mot de passe modifié avec succès !");
setFormData({
oldPassword: '',
newPassword: '',
confirmPassword: ''
});
} catch (error) {
setErrorMessage(error.message || "Une erreur est survenue");
}
};
const handleProfileSubmit = async (e) => {
e.preventDefault();
setErrorMessage('');
setSuccessMessage('');
axios
.post(`${API_BASE_URL}/updateProfil`, {
id: userData.id,
name: userData.name,
surname: userData.surname,
pseudo:userData.pseudo,
email: userData.email
})
.catch((error) => {
console.error("Erreur lors de la mise à jour du profil :", error);
setErrorMessage(error.response?.data?.error || "Une erreur est survenue");
})
.then((response) => {
console.log("Mise à jour du profil réussie :", response.data);
setSuccessMessage("Profil mis à jour avec succès !");
setEditMode(false);
});
};
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="max-w-3xl mx-auto">
<h1 className="text-3xl font-bold text-gray-900 mb-8 text-center">Mon Profil</h1>
{errorMessage && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4">
{errorMessage}
</div>
)}
{successMessage && (
<div className="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4">
{successMessage}
</div>
)}
<div className="grid md:grid-cols-2 gap-8">
{/* Informations du profil */}
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold text-gray-800">Informations Personnelles</h2>
<button
onClick={() => setEditMode(!editMode)}
className="text-indigo-600 hover:text-indigo-800"
>
{editMode ? <Save className="h-5 w-5" /> : <Edit className="h-5 w-5" />}
</button>
</div>
<form onSubmit={handleProfileSubmit} className="space-y-4">
<div className="flex items-center space-x-4 mb-4">
<div className="w-20 h-20 bg-indigo-100 rounded-full flex items-center justify-center">
<User className="h-10 w-10 text-indigo-600" />
</div>
<div>
<h3 className="text-lg font-medium">{userData.name} {userData.surname} ({userData.pseudo})</h3>
<p className="text-gray-500">{userData.email}</p>
</div>
</div>
<div className="bg-indigo-50 p-3 rounded-lg mb-4">
<p className="text-sm text-gray-700">Points de fidélité: <span className="font-semibold">{userData.points}</span> ({userData.role})</p>
</div>
{editMode ? (
<>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Prénom:</label>
<input
type="text"
name="name"
value={userData.name}
onChange={handleProfileChange}
className="block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Nom:</label>
<input
type="text"
name="surname"
value={userData.surname}
onChange={handleProfileChange}
className="block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Pseudo:</label>
<input
type="text"
name="pseudo"
value={userData.pseudo}
onChange={handleProfileChange}
className="block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Email:</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Mail className="h-5 w-5 text-gray-400" />
</div>
<input
type="email"
name="email"
value={userData.email}
disabled
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
/>
</div>
</div>
<button
type="submit"
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Sauvegarder
</button>
</>
) : (
<div className="space-y-3">
<div>
<p className="text-sm font-medium text-gray-500">Prénom</p>
<p className="mt-1">{userData.name}</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500">Nom</p>
<p className="mt-1">{userData.surname}</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500">Pseudo</p>
<p className="mt-1">{userData.pseudo}</p>
</div>
<div>
<p className="text-sm font-medium text-gray-500">Email</p>
<p className="mt-1">{userData.email}</p>
</div>
</div>
)}
</form>
</div>
{/* Changement de mot de passe */}
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold text-gray-800 mb-4">Modifier le mot de passe</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Mot de passe actuel:
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input
type="password"
name="oldPassword"
value={formData.oldPassword}
onChange={handleChange}
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
required
minLength="8"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Nouveau mot de passe:
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input
type="password"
name="newPassword"
value={formData.newPassword}
onChange={handleChange}
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
required
minLength="8"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Confirmer le nouveau mot de passe:
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input
type="password"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
required
minLength="8"
/>
</div>
</div>
<button
type="submit"
className="w-full flex justify-center py-2.5 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Modifier le mot de passe
</button>
</form>
</div>
</div>
</div>
</div>
);
}
export default Profil;

View File

@ -1,161 +0,0 @@
import React, { useState } from 'react';
import { Mail, User, Lock } from 'lucide-react';
import { useNavigate, Link} from 'react-router-dom'; // Importation du hook useNavigate
function Settings() {
const [formData, setFormData] = useState({
name: '',
surname: '',
email: '',
gender: '',
password: '',
confirmPassword: ''
});
const navigate = useNavigate(); // Initialisation de useNavigate
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
if (formData.password !== formData.confirmPassword) {
alert("Les mots de passe ne correspondent pas !");
return;
}
try {
const response = await fetch(`${API_BASE_URL}/settings`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || "Erreur lors de la modification");
}
alert("Modification réussie !");
// Redirection vers la page d'accueil après une inscription réussie
navigate("/home"); // Remplace "/home" par l'URL de ta page d'accueil
} catch (error) {
alert(error.message);
}
};
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="w-96 bg-white rounded-lg shadow-md p-6 mx-auto">
<h2 className="text-2xl font-bold text-gray-800 mb-6 text-center">Settings</h2>
<form onSubmit={handleSubmit} className="space-y-4">
{/* (Formulaire changement Email, Mot de passe) */}
{/* Email */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Modifier votre email:
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Mail className="h-5 w-5 text-gray-400" />
</div>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
required
/>
</div>
</div>
{/* Mot de passe */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Ancien mot de passe:
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
required
minLength="8"
/>
</div>
</div>
{/* nouveau mot de passe */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Nouveau mot de passe:
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input
type="password"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
required
minLength="8"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Confirmer le nouveau mot de passe:
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input
type="password"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
required
minLength="8"
/>
</div>
</div>
{/* Bouton d'inscription */}
<div className="pt-4">
<button
type="submit"
className="w-full flex justify-center py-2.5 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Sauvegarder
</button>
</div>
</form>
</div>
</div>
);
}
export default Settings;

View File

@ -7,6 +7,7 @@ function Signup() {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: '', name: '',
surname: '', surname: '',
pseudo:'',
email: '', email: '',
gender: '', gender: '',
password: '', password: '',
@ -62,7 +63,7 @@ function Signup() {
{/* Formulaire (Nom, Prénom, Sexe, Email, Mot de passe) */} {/* Formulaire (Nom, Prénom, Sexe, Email, Mot de passe) */}
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">
Nom: Prénom:
</label> </label>
<div className="relative"> <div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
@ -81,7 +82,7 @@ function Signup() {
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">
Prénom: Nom:
</label> </label>
<div className="relative"> <div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
@ -98,6 +99,25 @@ function Signup() {
</div> </div>
</div> </div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Pseudo:
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<User className="h-5 w-5 text-gray-400" />
</div>
<input
type="text"
name="pseudo"
value={formData.pseudo}
onChange={handleChange}
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
required
/>
</div>
</div>
{/* Sexe */} {/* Sexe */}
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">