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 gender = body.getString("gender");
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()
.setStatusCode(400)
.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());
databaseService.pool
.preparedQuery("INSERT INTO users (name, surname, email, gender, password) VALUES (?, ?, ?, ?, ?)")
.execute(Tuple.of(name, surname, email, gender, hashedPassword))
.preparedQuery("INSERT INTO users (name, surname, email, gender, password, pseudo) VALUES (?, ?, ?, ?, ?, ?)")
.execute(Tuple.of(name, surname, email, gender, hashedPassword,pseudo))
.onSuccess(result -> {
context.response()
.setStatusCode(201)
@ -77,7 +78,7 @@ public class AuthHandler {
}
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))
.onSuccess(result -> {
if (result.rowCount() == 0) {
@ -87,22 +88,32 @@ public class AuthHandler {
return;
}
String storedHashedPassword = result.iterator().next().getString("password");
Integer nbPointsUser = result.iterator().next().getInteger("points");
var row = result.iterator().next();
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);
if (verification.verified) {
JsonObject claims = new JsonObject().put("sub", email);
if(nbPointsUser<=60){
JsonObject claims = new JsonObject()
.put("sub", email)
.put("name", name)
.put("surname", surname)
.put("id", id);
if (nbPointsUser <= 60) {
claims.put("role", "user");
}else if(nbPointsUser<=100){
} else if (nbPointsUser <= 100) {
claims.put("role", "complexe");
}else if(nbPointsUser>=200){
} else if (nbPointsUser >= 200) {
claims.put("role", "admin");
}
String token = jwtAuth.generateToken(claims);
context.response()
.setStatusCode(200)
.end(new JsonObject().put("token", token).encode());
@ -118,5 +129,4 @@ public class AuthHandler {
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur serveur").encode());
});
}
}
}}

View File

@ -43,11 +43,12 @@ public class MainVerticle extends AbstractVerticle {
SetUser setUser = new SetUser(databaseService);
setObjects.setUserHandler(setUser);
queryObjects.setUserHandler(setUser);
setWeatherData.setUserHandler(setUser);
// Déclaration des routes
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.get("/wind").handler(queryWeather::getWindInfos);
router.get("/meteo").handler(queryWeather::getMeteoInfos);
@ -56,13 +57,18 @@ public class MainVerticle extends AbstractVerticle {
router.post("/modifRangeData").handler(setWeatherData::setRangeData);
router.post("/deleteObject").handler(setObjects::deleteObject);
router.get("/users").handler(queryUsers::getUsers);
router.post("/user").handler(queryUsers::getUser);
router.post("/setUserPoints").handler(setUser::setUserPoints);
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
router.post("/signup").handler(authHandler::handleSignup);
router.post("/login").handler(authHandler::handleLogin);
// Création du serveur HTTP
vertx.createHttpServer()
.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) {
String id = context.request().getParam("id");
// Integer idUser = body.getInteger("idUser");
Integer idUser = 4;
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;
}
String id = body.getString("id");
String idUser = body.getString("userId");
if (id == null) {
context.response()
.setStatusCode(400)
@ -66,10 +93,13 @@ public class QueryObjects {
.end(new JsonObject().put("error", "Objet non trouvé").encode());
return;
}
if (idUser != null) {
setUser.updateUserPoints(idUser, 1);
System.out.println(idUser);
Boolean shouldUpdatePoints = body.getBoolean("shouldUpdatePoints", false);
if (Boolean.TRUE.equals(shouldUpdatePoints) && idUser != null) {
setUser.updateUserPoints(Integer.parseInt(idUser), 1);
}
;
context.response()
.putHeader("content-type", "application/json: charset=UTF-8")
.end(getInfosObjects(rows).encode());
@ -91,7 +121,7 @@ public class QueryObjects {
.put("status", row.getString("status"))
.put("batterie", row.getInteger("batterie"))
.put("type_batterie", row.getString("type_batterie"))
.put("proprio", row.getString("proprio"));
.put("proprio_id", row.getInteger("proprio_id"));
objects.add(object);
}
return objects;

View File

@ -4,6 +4,7 @@ import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.Tuple;
public class QueryUsers {
private DatabaseService databaseService;
@ -25,19 +26,20 @@ public class QueryUsers {
.onSuccess(rows -> {
JsonArray users = new JsonArray();
for (Row row : rows) {
int points=row.getInteger("points");
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("points",points);
if(points<=60){
.put("pseudo",row.getString("pseudo"))
.put("points", points);
if (points <= 60) {
user.put("role", "user");
}else if(points<=100){
} else if (points <= 100) {
user.put("role", "complexe");
}else if(points>=200){
} else if (points >= 200) {
user.put("role", "admin");
}
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 {
private DatabaseService databaseService;
private SetUser setUser;
public SetObjects(DatabaseService ddbs) {
this.databaseService = ddbs;
}
public void setUserHandler(SetUser setUser){
public void setUserHandler(SetUser setUser) {
this.setUser = setUser;
}
public void setInfoObjet(RoutingContext context) {
JsonObject body = context.body().asJsonObject();
if (body == null) {
@ -22,8 +25,7 @@ public class SetObjects {
return;
}
Integer id = body.getInteger("id");
// Integer idUser = body.getInteger("idUser");
Integer idUser = 4;
Integer idUser = body.getInteger("idUser");
String description = body.getString("description");
String type = body.getString("type");
String location = body.getString("location");
@ -46,85 +48,90 @@ public class SetObjects {
.end(new JsonObject().put("error", "Objet non trouvé").encode());
return;
}
if(idUser!=null){
setUser.updateUserPoints(idUser,1);
};
Boolean shouldUpdatePoints = body.getBoolean("shouldUpdatePoints", false);
if (Boolean.TRUE.equals(shouldUpdatePoints) && idUser != null) {
setUser.updateUserPoints(idUser, 1);
}
context.response()
.putHeader("content-type", "application/json: charset=UTF-8")
.end(new JsonObject().put("success", "L'objet à bien été mis à jour").encode());
return;
});
}
public void deleteObject(RoutingContext context){
public void deleteObject(RoutingContext context) {
JsonObject body = context.body().asJsonObject();
if(body== null){
if (body == null) {
context.response()
.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;
}
String id = body.getString("id");
databaseService.pool
.preparedQuery("DELETE FROM weather_objects WHERE id=?")
.execute(Tuple.of(Integer.parseInt(id)))
.onFailure(e->{
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()
.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 -> {
if(rows.rowCount()==0){
if (rows.rowCount() == 0) {
context.response()
.setStatusCode(404)
.end(new JsonObject().put("error", "Objet non trouvé").encode());
return;
}
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());
return;
});
}
public void newObject(RoutingContext context){
public void newObject(RoutingContext context) {
JsonObject body = context.body().asJsonObject();
if(body== null){
if (body == null) {
context.response()
.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;
}
// Integer idUser = body.getInteger("idUser");
Integer idUser = 4;
Integer idUser = body.getInteger("proprio_id");
String name = body.getString("nom");
String description = body.getString("description");
String type = body.getString("type");
String location = body.getString("location");
String status = body.getString("status");
String batterieType = body.getString("batterieType");
String proprio = body.getString("proprio");
Integer proprio_id = body.getInteger("proprio_id");
databaseService.pool
.preparedQuery("INSERT INTO weather_objects (name,description,type,location,status,type_batterie,proprio) VALUES (?,?,?,?,?,?,?)")
.execute(Tuple.of(name,description,type,location,status,batterieType,proprio))
.onFailure(e->{
System.err.println("Erreur de récupération de la BDD :"+e.getMessage());
.preparedQuery(
"INSERT INTO weather_objects (name,description,type,location,status,type_batterie,proprio_id) VALUES (?,?,?,?,?,?,?)")
.execute(Tuple.of(name, description, type, location, status, batterieType, proprio_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());
.end(new JsonObject().put("error", "Erreur de récupération de la BDD").encode());
})
.onSuccess(rows -> {
if(rows.rowCount()==0){
if (rows.rowCount() == 0) {
context.response()
.setStatusCode(404)
.end(new JsonObject().put("error", "Objet non trouvé").encode());
return;
}
if(idUser!=null){
setUser.updateUserPoints(idUser,2);
};
if (idUser != null) {
setUser.updateUserPoints(idUser, 2);
}
;
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());
return;
});

View File

@ -1,5 +1,6 @@
package com.example.starter;
import at.favre.lib.crypto.bcrypt.BCrypt;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Tuple;
@ -10,6 +11,7 @@ public class SetUser {
public SetUser(DatabaseService ddbs) {
this.databaseService = ddbs;
}
public void updateUserPoints(Integer userId, Integer points) {
databaseService.pool
.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) {
JsonObject body = context.body().asJsonObject();
if (body == null) {
@ -59,33 +160,34 @@ public class SetUser {
return;
});
}
public void deleteUser(RoutingContext context){
public void deleteUser(RoutingContext context) {
JsonObject body = context.body().asJsonObject();
if(body== null){
if (body == null) {
context.response()
.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;
}
Integer id = body.getInteger("id");
databaseService.pool
.preparedQuery("DELETE FROM users WHERE id=?")
.execute(Tuple.of(id))
.onFailure(e->{
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()
.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 -> {
if(rows.rowCount()==0){
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")
.putHeader("content-type", "application/json: charset=UTF-8")
.end(new JsonObject().put("success", "L'utilisateur à bien été supprimé").encode());
return;
});

View File

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

View File

@ -13,6 +13,7 @@
"@emotion/styled": "^11.14.0",
"@mui/material": "^7.0.1",
"axios": "^1.8.4",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.427.0",
"react": "^18.3.1",
"react-charts": "^3.0.0-beta.57",
@ -2070,7 +2071,6 @@
"resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
"integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
@ -2819,6 +2819,71 @@
"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": {
"version": "1.0.1",
"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==",
"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": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"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": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -4269,6 +4385,15 @@
"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": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",

View File

@ -14,6 +14,7 @@
"@emotion/styled": "^11.14.0",
"@mui/material": "^7.0.1",
"axios": "^1.8.4",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.427.0",
"react": "^18.3.1",
"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 Signup from "./pages/Signup.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 User from "./pages/Admin/User.jsx";
import Dashboard from "./pages/Admin/Dashboard.jsx";
import AdminObjet from "./pages/Admin/AdminObjet.jsx";
import ProtectedRoute from './ProtectedRoute.jsx'; // Correction de l'import
import ProtectedRoute from "./ProtectedRoute.jsx";
function App() {
return (
@ -23,19 +23,27 @@ function App() {
<div>
<Header />
<Routes>
{/* Routes publiques */}
<Route path="/" element={<Home />} />
<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="/login" element={<Login />} />
<Route path="/ajouterObjet" element={<ProtectedRoute element={<AddObject />} />} />
<Route path="/settings" element={<Settings />} />
<Route path="/sidebar" element={<Sidebar />} />
<Route path="/user" element={<User />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/adminobjet" element={<ProtectedRoute element={<AdminObjet />} />} />
{/* Routes protégées pour tous les utilisateurs connectés */}
<Route path="/gestion" element={<ProtectedRoute element={<Gestion />} allowedRoles={['admin', 'complexe', 'user']} />} />
<Route path="/gestionObjets" element={<ProtectedRoute element={<ObjectManagement />} allowedRoles={['admin', 'complexe', 'user']} />} />
<Route path="/objet" element={<ProtectedRoute element={<Objet />} allowedRoles={['admin', 'complexe', 'user']} />} />
{/* 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>
</div>
</Router>

View File

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

View File

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

View File

@ -1,9 +1,11 @@
import React, {useState} from "react";
import { TriangleAlert,X } from "lucide-react";
import { useAuth } from "../AuthContext";
function AlertInactive({affAlert,setAffAlert}) {
const { user } = useAuth();
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">
<button onClick={()=>setAffAlert(false)}className="absolute top-2 right-2 text-white hover:text-gray-300">
<X/>

View File

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

View File

@ -1,10 +1,10 @@
import React, { useState } from "react";
import { X, Menu, LogIn, UserPlus, LogOut, Settings } from "lucide-react";
import React, { useState, useEffect } from "react";
import { X, Menu, LogIn, UserPlus, LogOut, User } from "lucide-react";
import { Link } from "react-router-dom";
import { useAuth } from "../AuthContext";
function Header() {
const { token, logout } = useAuth();
const { token, user, logout } = useAuth();
const [isMenuOpen, setIsMenuOpen] = 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">
<Link
to="/settings"
onClick={()=>setIsMenuOpen(false)}
to="/profil"
onClick={() => setIsMenuOpen(false)}
className="flex items-center gap-2 text-gray-600 hover:text-indigo-600"
>
<Settings size={20} />
<span>Paramètres</span>
<User size={20} />
<span>Profil</span>
<span></span>
</Link>
</li>
<li className="sm:hidden">
<button
onClick={()=>{
onClick={() => {
logout();
setIsMenuOpen(false)}}
setIsMenuOpen(false);
}}
className="flex items-center gap-2 text-gray-600 hover:text-red-600"
>
<LogOut size={20} />
@ -158,7 +180,7 @@ function Header() {
<div className="hidden sm:flex gap-4 ">
<Link
to="/login"
onClick={()=>setIsMenuOpen(false)}
onClick={() => setIsMenuOpen(false)}
className="hover:text-indigo-600 flex items-center gap-2"
>
<LogIn size={20} />
@ -166,7 +188,7 @@ function Header() {
</Link>
<Link
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"
>
<UserPlus size={20} />
@ -176,11 +198,12 @@ function Header() {
) : (
<div className="hidden sm:flex gap-4">
<Link
to="/settings"
onClick={()=>setIsMenuOpen(false)}
to="/profil"
onClick={() => setIsMenuOpen(false)}
className="flex items-center gap-2 text-gray-600 hover:text-indigo-600"
>
<Settings size={20} />
<User size={20} />
<span></span>
</Link>
<button
onClick={logout}

View File

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

View File

@ -2,8 +2,10 @@ import React, { useState } from "react";
import { Info } from "lucide-react";
import axios from "axios";
import { API_BASE_URL } from "../config";
import {useAuth} from "../AuthContext";
function ModifObject({ object, defafficherModif }) {
const {user}=useAuth();
const [description, setDescription] = useState(object.description || "");
const [type, setType] = useState(object.type || "");
const [location, setLocalisation] = useState(object.location || "");
@ -15,10 +17,12 @@ function ModifObject({ object, defafficherModif }) {
axios
.post(`${API_BASE_URL}/modifObjet`, {
id: object.id,
idUser:user.id,
description,
type,
location,
status,
shouldUpdatePoints:true
})
.then((response) => {
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 { API_BASE_URL } from "../config";
import axios from "axios";
import { useAuth } from "../AuthContext";
const identifiant = new URLSearchParams(window.location.search).get("id");
function ParticularMeteo({
@ -16,6 +17,7 @@ function ParticularMeteo({
setGraphStates,
graphRefs,
}) {
const {user} = useAuth();
const [affRegles, setAffRegles] = useState(false);
const [rangeValue, setRangeValue] = useState([0, 0]);
const [alerteActive, setAlerteActive] = useState(false);
@ -71,6 +73,7 @@ function ParticularMeteo({
axios
.post(`${API_BASE_URL}/modifRangeData`, {
id: identifiant,
idUser:user.id,
min: parseFloat(rangeValue[0]),
max: parseFloat(rangeValue[1]),
type,
@ -107,6 +110,7 @@ function ParticularMeteo({
</div>
</div>
<div className="flex gap-2">
{user?.role!=="user" && (
<button
onClick={() => {
setAffRegles(!affRegles);
@ -117,7 +121,7 @@ function ParticularMeteo({
size={30}
/>
</button>
)}
<BoutonGraphique
type={type}
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}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{obj.proprio}
{obj.proprio_id}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{obj.status}

View File

@ -9,19 +9,21 @@ import {
UserPlus,
RadioTower,
Binoculars,
Settings,
BadgePlus,
} from "lucide-react";
import { useAuth } from "../../AuthContext";
function Gestion() {
return (
<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="text-center mb-12">
<h2 className="text-4xl font-bold text-gray-900 mb-4">
Bienvenue dans le module <b>Gestion</b>.
</h2>
<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>
</div>
@ -43,6 +45,7 @@ function Gestion() {
Explorer les objets <ArrowRight size={16} className="ml-2" />
</a>
</div>
<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">
<BadgePlus className="text-indigo-600" size={24} />

View File

@ -1,14 +1,17 @@
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 axios from "axios";
import { API_BASE_URL } from "../../config";
import { useAuth } from "../../AuthContext";
function ObjectManagement() {
const {user} = useAuth();
const [searchQuery, setSearchQuery] = useState("");
const [activeFilter, setActiveFilter] = useState("");
const [objects, setObjects] = useState([]);
const [nbAffObject,setnbAffObject] = useState(6);
const [nbAffObject, setnbAffObject] = useState(6);
const filteredDATA = objects.filter((node) => {
const matchesSearchQuery =
searchQuery === "" ||
@ -29,12 +32,13 @@ function ObjectManagement() {
setObjects(response.data);
});
}, []);
return (
<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="text-center mb-12">
<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>
</div>
<div className="max-w-3xl mx-auto mb-12">
@ -51,10 +55,12 @@ function ObjectManagement() {
onChange={(e) => setSearchQuery(e.target.value)}
/>
</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
onClick={() => setActiveFilter("")}
className={`px-4 py-2 rounded-lg ${
className={`px-4 py-2 rounded-lg mb-2 ${
activeFilter === ""
? "bg-indigo-600 text-white"
: "bg-white text-gray-600"
@ -64,7 +70,7 @@ function ObjectManagement() {
</button>
<button
onClick={() => setActiveFilter("Station")}
className={`px-4 py-2 rounded-lg ${
className={`px-4 py-2 rounded-lg mb-2 ${
activeFilter === "Station"
? "bg-indigo-600 text-white"
: "bg-white text-gray-600"
@ -74,7 +80,7 @@ function ObjectManagement() {
</button>
<button
onClick={() => setActiveFilter("Capteur")}
className={`px-4 py-2 rounded-lg ${
className={`px-4 py-2 rounded-lg mb-2 ${
activeFilter === "Capteur"
? "bg-indigo-600 text-white"
: "bg-white text-gray-600"
@ -84,7 +90,7 @@ function ObjectManagement() {
</button>
<button
onClick={() => setActiveFilter("Active")}
className={`px-4 py-2 rounded-lg ${
className={`px-4 py-2 rounded-lg mb-2 ${
activeFilter === "Active"
? "bg-indigo-600 text-white"
: "bg-white text-gray-600"
@ -94,7 +100,7 @@ function ObjectManagement() {
</button>
<button
onClick={() => setActiveFilter("Inactive")}
className={`px-4 py-2 rounded-lg ${
className={`px-4 py-2 rounded-lg mb-2 ${
activeFilter === "Inactive"
? "bg-indigo-600 text-white"
: "bg-white text-gray-600"
@ -104,14 +110,16 @@ function ObjectManagement() {
</button>
</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 ? (
<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
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" ? (
<div className="relative w-full">
@ -143,14 +151,21 @@ function ObjectManagement() {
))
)}
</div>
{(nbAffObject<filteredDATA.length)&&(
{(nbAffObject < filteredDATA.length) && (
<div className="flex items-center flex-col mt-6">
<button onClick={()=>{setnbAffObject((prev)=>prev+6 )}}><Plus size={40}/></button>
<label>Voir plus</label>
<button
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>
);
}
export default ObjectManagement;

View File

@ -12,7 +12,10 @@ import WindInfo from "../../components/WindInfo";
import MeteoInfos from "../../components/MeteoInfos";
import MeteoGraph from "../../components/MeteoGraph";
import BatterieInfo from "../../components/BatterieInfo";
import { useAuth } from "../../AuthContext";
import UserInfosObject from "../../components/UserInfosObject";
function Objet() {
const {user} =useAuth();
const identifiant = new URLSearchParams(window.location.search).get("id");
const [object, setObject] = useState({});
const [graphStates, setGraphStates] = useState({
@ -28,12 +31,20 @@ function Objet() {
humidity: useRef(null),
wind: useRef(null),
};
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]);
})
.catch((error) => {
console.error("Erreur lors de la récupération :", error);
});
}, [identifiant]);
}, [user]);
return object && object.id ? (
<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">
@ -61,6 +72,8 @@ function Objet() {
graphRefs={graphRefs}
/>
<BatterieInfo object={object} />
<UserInfosObject user={object.proprio_id}/>
</div>
{graphStates.wind && <WindGraph object={object} reference={graphRefs.wind} />}

View File

@ -8,6 +8,7 @@ function Home() {
const [activeFilter, setActiveFilter] = useState('all');
const [name, setName] = useState([]);
const { token, logout } = useAuth();
const { user } = useAuth();
return (
<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">
<h2 className="text-4xl font-bold text-gray-900 mb-4">
Bienvenue dans ta ville intelligente.</h2>
{token ? (
<><h2>Tu es connecté</h2>
{user ? (
<>
<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>
)}
<p className="text-xl text-gray-600 max-w-3xl mx-auto">

View File

@ -1,7 +1,7 @@
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 axios from "axios"; // Assurez-vous d'avoir axios importé
import axios from "axios";
import { useAuth } from "../AuthContext";
import { API_BASE_URL } from "../config";
@ -10,8 +10,9 @@ function Login() {
email: "",
password: "",
});
const { login } = useAuth(); // Utilisation du hook useAuth pour accéder à la fonction login
const navigate = useNavigate(); // Initialisation de useNavigate
const [error, setError] = useState("");
const { login } = useAuth();
const navigate = useNavigate();
const handleChange = (e) => {
const { name, value } = e.target;
@ -19,43 +20,68 @@ function Login() {
...prev,
[name]: value,
}));
if (error) setError("");
};
const handleSubmit = async (e) => {
e.preventDefault();
setError("");
try {
const response = await fetch(`${API_BASE_URL}/login`, {
method: "POST",
const response = await axios.post(`${API_BASE_URL}/login`, formData, {
headers: {
"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) {
// Appel de la fonction login du contexte pour stocker le token
login(data.token);
// Rediriger vers la page d'accueil après la connexion
navigate("/");
} else {
console.error("Token manquant dans la réponse");
setError("Authentification échouée : token manquant dans la réponse");
}
} catch (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 (
<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">
<h2 className="text-2xl font-bold text-gray-800 mb-6 text-center">
Connexion
</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">
{/* Email */}
<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"
required
minLength="8"
autoComplete="current-password"
/>
</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({
name: '',
surname: '',
pseudo:'',
email: '',
gender: '',
password: '',
@ -62,7 +63,7 @@ function Signup() {
{/* Formulaire (Nom, Prénom, Sexe, Email, Mot de passe) */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Nom:
Prénom:
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
@ -81,7 +82,7 @@ function Signup() {
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Prénom:
Nom:
</label>
<div className="relative">
<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>
<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 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">