Merge remote-tracking branch 'origin/main' into Ruben
This commit is contained in:
commit
922af8455d
BIN
Back-end/keystore.jceks
Normal file
BIN
Back-end/keystore.jceks
Normal file
Binary file not shown.
BIN
Back-end/keystore.jceks.old
Normal file
BIN
Back-end/keystore.jceks.old
Normal file
Binary file not shown.
0
Back-end/mvnw
vendored
Normal file → Executable file
0
Back-end/mvnw
vendored
Normal file → Executable file
@ -82,6 +82,18 @@
|
||||
<version>${junit-jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency> <!--Dependy pour hacher le mdp-->
|
||||
<groupId>at.favre.lib</groupId>
|
||||
<artifactId>bcrypt</artifactId>
|
||||
<version>0.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-auth-jwt</artifactId>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
113
Back-end/src/main/java/com/example/starter/AuthHandler.java
Normal file
113
Back-end/src/main/java/com/example/starter/AuthHandler.java
Normal file
@ -0,0 +1,113 @@
|
||||
package com.example.starter;
|
||||
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import at.favre.lib.crypto.bcrypt.BCrypt;
|
||||
import io.vertx.ext.auth.jwt.JWTAuth;
|
||||
import com.example.starter.auth.JwtAuthProvider;
|
||||
import io.vertx.sqlclient.Tuple;
|
||||
|
||||
public class AuthHandler {
|
||||
private final DatabaseService databaseService;
|
||||
private final JWTAuth jwtAuth;
|
||||
|
||||
public AuthHandler(DatabaseService databaseService, JWTAuth jwtAuth) {
|
||||
this.databaseService = databaseService;
|
||||
this.jwtAuth = jwtAuth;
|
||||
}
|
||||
|
||||
public void handleSignup(RoutingContext context) {
|
||||
JsonObject body = context.body().asJsonObject();
|
||||
|
||||
if (body == null) {
|
||||
context.response()
|
||||
.setStatusCode(400)
|
||||
.end(new JsonObject().put("error", "Requête invalide").encode());
|
||||
return;
|
||||
}
|
||||
|
||||
String name = body.getString("name");
|
||||
String surname = body.getString("surname");
|
||||
String email = body.getString("email");
|
||||
String gender = body.getString("gender");
|
||||
String password = body.getString("password");
|
||||
|
||||
if (name == null || surname == null || email == null || gender == null || password == null) {
|
||||
context.response()
|
||||
.setStatusCode(400)
|
||||
.end(new JsonObject().put("error", "Tous les champs sont requis").encode());
|
||||
return;
|
||||
}
|
||||
|
||||
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))
|
||||
.onSuccess(result -> {
|
||||
context.response()
|
||||
.setStatusCode(201)
|
||||
.end(new JsonObject().put("message", "Utilisateur inscrit avec succès").encode());
|
||||
})
|
||||
.onFailure(err -> {
|
||||
System.err.println("Erreur d'inscription : " + err.getMessage());
|
||||
context.response()
|
||||
.setStatusCode(500)
|
||||
.end(new JsonObject().put("error", "Erreur d'inscription").encode());
|
||||
});
|
||||
}
|
||||
|
||||
public void handleLogin(RoutingContext context) {
|
||||
JsonObject body = context.body().asJsonObject();
|
||||
|
||||
if (body == null) {
|
||||
context.response()
|
||||
.setStatusCode(400)
|
||||
.end(new JsonObject().put("error", "Requête invalide").encode());
|
||||
return;
|
||||
}
|
||||
|
||||
String email = body.getString("email");
|
||||
String password = body.getString("password");
|
||||
|
||||
if (email == null || password == null) {
|
||||
context.response()
|
||||
.setStatusCode(400)
|
||||
.end(new JsonObject().put("error", "Email et mot de passe requis").encode());
|
||||
return;
|
||||
}
|
||||
|
||||
databaseService.pool
|
||||
.preparedQuery("SELECT password FROM users WHERE email = ?")
|
||||
.execute(Tuple.of(email))
|
||||
.onSuccess(result -> {
|
||||
if (result.rowCount() == 0) {
|
||||
context.response()
|
||||
.setStatusCode(401)
|
||||
.end(new JsonObject().put("error", "Email ou mot de passe incorrect").encode());
|
||||
return;
|
||||
}
|
||||
|
||||
String storedHashedPassword = result.iterator().next().getString("password");
|
||||
BCrypt.Result verification = BCrypt.verifyer().verify(password.toCharArray(), storedHashedPassword);
|
||||
|
||||
if (verification.verified) {
|
||||
JsonObject claims = new JsonObject().put("sub", email).put("role", "user");
|
||||
String token = jwtAuth.generateToken(claims);
|
||||
context.response()
|
||||
.setStatusCode(200)
|
||||
.end(new JsonObject().put("token", token).encode());
|
||||
} else {
|
||||
context.response()
|
||||
.setStatusCode(401)
|
||||
.end(new JsonObject().put("error", "Email ou mot de passe incorrect").encode());
|
||||
}
|
||||
})
|
||||
.onFailure(err -> {
|
||||
System.err.println("Erreur de connexion : " + err.getMessage());
|
||||
context.response()
|
||||
.setStatusCode(500)
|
||||
.end(new JsonObject().put("error", "Erreur serveur").encode());
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,8 @@ package com.example.starter;
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.jdbcclient.JDBCConnectOptions;
|
||||
import io.vertx.jdbcclient.JDBCPool;
|
||||
import io.vertx.ext.auth.jwt.JWTAuth;
|
||||
import io.vertx.ext.auth.jwt.JWTAuthOptions;
|
||||
import io.vertx.sqlclient.PoolOptions;
|
||||
|
||||
public class DatabaseService {
|
||||
@ -11,7 +13,7 @@ public class DatabaseService {
|
||||
public DatabaseService(Vertx vertx) {
|
||||
pool = JDBCPool.pool(vertx,
|
||||
new JDBCConnectOptions()
|
||||
.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres?useUnicode=true&amp;characterEncoding=UTF-8") // URL de la base de données
|
||||
.setJdbcUrl("jdbc:postgresql://localhost:5432/users?useUnicode=true&characterEncoding=UTF-8") //Url de la bdd
|
||||
.setUser("postgres") // Nom d'utilisateur PostgreSQL
|
||||
.setPassword("admin"), // Mot de passe PostgreSQL
|
||||
new PoolOptions()
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
package com.example.starter.auth;
|
||||
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.ext.auth.jwt.JWTAuth;
|
||||
import io.vertx.ext.auth.jwt.JWTAuthOptions;
|
||||
import io.vertx.ext.auth.KeyStoreOptions;
|
||||
import com.example.starter.auth.JwtAuthProvider;
|
||||
|
||||
|
||||
public class JwtAuthProvider {
|
||||
|
||||
public static JWTAuth createJwtAuth(Vertx vertx) {
|
||||
return JWTAuth.create(vertx, new JWTAuthOptions()
|
||||
.setKeyStore(new KeyStoreOptions()
|
||||
.setPath("keystore.jceks")
|
||||
.setPassword("secret")));
|
||||
}
|
||||
}
|
||||
@ -1,48 +1,74 @@
|
||||
package com.example.starter;
|
||||
|
||||
import io.vertx.ext.web.handler.BodyHandler;
|
||||
import io.vertx.ext.web.handler.CorsHandler;
|
||||
import io.vertx.core.http.HttpMethod;
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
import io.vertx.core.Promise;
|
||||
|
||||
import io.vertx.core.http.HttpMethod;
|
||||
import io.vertx.ext.web.Router;
|
||||
import io.vertx.ext.web.handler.BodyHandler;
|
||||
import io.vertx.ext.web.handler.CorsHandler;
|
||||
import io.vertx.ext.auth.jwt.JWTAuth;
|
||||
import com.example.starter.auth.JwtAuthProvider;
|
||||
import io.vertx.ext.web.handler.JWTAuthHandler;
|
||||
|
||||
|
||||
public class MainVerticle extends AbstractVerticle {
|
||||
private DatabaseService databaseService;
|
||||
private DatabaseService databaseService;
|
||||
private Router router;
|
||||
|
||||
@Override
|
||||
public void start(Promise<Void> startPromise) throws Exception {
|
||||
databaseService = new DatabaseService(vertx);
|
||||
QueryObjects queryObjects = new QueryObjects(databaseService);
|
||||
QueryWeatherData queryWeather = new QueryWeatherData(databaseService);
|
||||
SetObjects setObjects = new SetObjects(databaseService);
|
||||
// Create a Router
|
||||
Router router = Router.router(vertx);
|
||||
router.route().handler(BodyHandler.create());
|
||||
router.route().handler(CorsHandler.create()
|
||||
.addOrigin("*") // Allow all origins
|
||||
.allowedMethod(HttpMethod.GET) // Allow GET requests
|
||||
.allowedMethod(HttpMethod.POST) // Allow POST requests
|
||||
.allowedHeader("Content-Type") // Allow Content-Type header
|
||||
.allowedHeader("Authorization"));
|
||||
router.get("/objets").handler(queryObjects::getObjects);
|
||||
router.get("/objet").handler(queryObjects::getParticularObject);
|
||||
router.post("/modifObjet").handler(setObjects::setInfoObjet);
|
||||
router.get("/wind").handler(queryWeather::getWindInfos);
|
||||
router.get("/meteo").handler(queryWeather::getMeteoInfos);
|
||||
router.post("/addObject").handler(setObjects::newObject);
|
||||
@Override
|
||||
public void start(Promise<Void> startPromise) throws Exception {
|
||||
databaseService = new DatabaseService(vertx);
|
||||
|
||||
// Initialisation du fournisseur JWT
|
||||
JWTAuth jwtAuth = JwtAuthProvider.createJwtAuth(vertx);
|
||||
|
||||
|
||||
vertx.createHttpServer()
|
||||
.requestHandler(router)
|
||||
.listen(8888)
|
||||
.onSuccess(server -> {
|
||||
System.out.println("HTTP server started on port " + server.actualPort());
|
||||
startPromise.complete();
|
||||
})
|
||||
.onFailure(throwable -> {
|
||||
throwable.printStackTrace();
|
||||
startPromise.fail(throwable);
|
||||
});
|
||||
}
|
||||
}
|
||||
// Initialisation du routeur
|
||||
router = Router.router(vertx);
|
||||
router.route().handler(BodyHandler.create());
|
||||
router.route().handler(CorsHandler.create()
|
||||
.addOrigin("*")
|
||||
.allowedMethod(HttpMethod.GET)
|
||||
.allowedMethod(HttpMethod.POST)
|
||||
.allowedHeader("Content-Type")
|
||||
.allowedHeader("Authorization"));
|
||||
|
||||
// Protéger toutes les routes commençant par "/api/"
|
||||
router.route("/api/*").handler(JWTAuthHandler.create(jwtAuth));
|
||||
|
||||
|
||||
// Initialisation des handlers de requêtes
|
||||
QueryObjects queryObjects = new QueryObjects(databaseService);
|
||||
QueryWeatherData queryWeather = new QueryWeatherData(databaseService);
|
||||
SetObjects setObjects = new SetObjects(databaseService);
|
||||
SetWeatherData setWeatherData = new SetWeatherData(databaseService);
|
||||
AuthHandler authHandler = new AuthHandler(databaseService, jwtAuth);
|
||||
|
||||
// Déclaration des routes
|
||||
router.get("/objets").handler(queryObjects::getObjects);
|
||||
router.get("/objet").handler(queryObjects::getParticularObject);
|
||||
router.post("/modifObjet").handler(setObjects::setInfoObjet);
|
||||
router.get("/wind").handler(queryWeather::getWindInfos);
|
||||
router.get("/meteo").handler(queryWeather::getMeteoInfos);
|
||||
router.post("/addObject").handler(setObjects::newObject);
|
||||
router.get("/getRange").handler(queryWeather::getRangeData);
|
||||
router.post("/modifRangeData").handler(setWeatherData::setRangeData);
|
||||
|
||||
// Routes d'authentification
|
||||
router.post("/signup").handler(authHandler::handleSignup);
|
||||
router.post("/login").handler(authHandler::handleLogin);
|
||||
|
||||
// Création du serveur HTTP
|
||||
vertx.createHttpServer()
|
||||
.requestHandler(router)
|
||||
.listen(8888)
|
||||
.onSuccess(server -> {
|
||||
System.out.println("HTTP server started on port " + server.actualPort());
|
||||
startPromise.complete();
|
||||
})
|
||||
.onFailure(throwable -> {
|
||||
throwable.printStackTrace();
|
||||
startPromise.fail(throwable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,6 +78,36 @@ public class QueryWeatherData {
|
||||
.end((convertRowsToJson(rows)).encode());
|
||||
});
|
||||
}
|
||||
public void getRangeData(RoutingContext context){
|
||||
String id= context.request().getParam("id");
|
||||
if(id==null){
|
||||
context.response()
|
||||
.setStatusCode(400)
|
||||
.end(new JsonObject().put("error","Paramètre 'id' manquant").encode());
|
||||
return;
|
||||
}
|
||||
databaseService.pool
|
||||
.preparedQuery("SELECT temperature_min,temperature_max,pressure_min,pressure_max,humidity_min,humidity_max FROM range_data WHERE station_id=?")
|
||||
.execute(Tuple.of(Integer.parseInt(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());
|
||||
return;
|
||||
})
|
||||
.onSuccess(rows->{
|
||||
if(rows.size() == 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")
|
||||
.end((convertRowsToJson(rows)).encode());
|
||||
});
|
||||
}
|
||||
|
||||
private JsonArray convertRowsToJson(RowSet<Row> rows) {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
package com.example.starter;
|
||||
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.sqlclient.Tuple;
|
||||
|
||||
public class SetWeatherData {
|
||||
private DatabaseService databaseService;
|
||||
|
||||
public SetWeatherData(DatabaseService ddbs) {
|
||||
this.databaseService = ddbs;
|
||||
}
|
||||
|
||||
public void setRangeData(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;
|
||||
}
|
||||
String id = body.getString("id");
|
||||
Double min = body.getDouble("min");
|
||||
Double max = body.getDouble("max");
|
||||
String type = body.getString("type");
|
||||
|
||||
if (id == null || min == null || max == null || type == null) {
|
||||
context.response()
|
||||
.setStatusCode(400)
|
||||
.end(new JsonObject().put("error", "Paramètres manquants ou invalides").encode());
|
||||
return;
|
||||
}
|
||||
if (!type.matches("^[a-zA-Z_]+$")) {
|
||||
context.response()
|
||||
.setStatusCode(400)
|
||||
.end(new JsonObject().put("error", "Type invalide").encode());
|
||||
return;
|
||||
}
|
||||
String query = String.format("UPDATE range_data SET %s_min=?, %s_max=? WHERE station_id=?", type, type);
|
||||
|
||||
databaseService.pool
|
||||
.preparedQuery(
|
||||
query)
|
||||
.execute(Tuple.of(min, max, Integer.parseInt(id)))
|
||||
.onFailure(e -> {
|
||||
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
|
||||
context.response()
|
||||
.setStatusCode(500)
|
||||
.end(new JsonObject().put("Erreur", "Erreur de récupération de la BDD").encode());
|
||||
})
|
||||
.onSuccess(rows -> {
|
||||
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")
|
||||
.end(new JsonObject().put("success", "Les limites ont bien été mis à jour").encode());
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,6 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" href="./src/img/cloud-alert.svg"/>
|
||||
<title>Projet Dev Web</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
673
Front-end/package-lock.json
generated
673
Front-end/package-lock.json
generated
@ -9,6 +9,9 @@
|
||||
"version": "0.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/material": "^7.0.1",
|
||||
"axios": "^1.8.4",
|
||||
"lucide-react": "^0.427.0",
|
||||
"react": "^18.3.1",
|
||||
@ -61,7 +64,6 @@
|
||||
"version": "7.26.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
||||
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.25.9",
|
||||
"js-tokens": "^4.0.0",
|
||||
@ -114,7 +116,6 @@
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz",
|
||||
"integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.26.10",
|
||||
"@babel/types": "^7.26.10",
|
||||
@ -146,7 +147,6 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
|
||||
"integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.25.9",
|
||||
"@babel/types": "^7.25.9"
|
||||
@ -185,7 +185,6 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
|
||||
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@ -194,7 +193,6 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
|
||||
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@ -225,7 +223,6 @@
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
|
||||
"integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.26.10"
|
||||
},
|
||||
@ -282,7 +279,6 @@
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
|
||||
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/parser": "^7.26.9",
|
||||
@ -296,7 +292,6 @@
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.10.tgz",
|
||||
"integrity": "sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/generator": "^7.26.10",
|
||||
@ -314,7 +309,6 @@
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
@ -323,7 +317,6 @@
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
|
||||
"integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.25.9",
|
||||
"@babel/helper-validator-identifier": "^7.25.9"
|
||||
@ -332,6 +325,158 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin": {
|
||||
"version": "11.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
||||
"integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.16.7",
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/hash": "^0.9.2",
|
||||
"@emotion/memoize": "^0.9.0",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"convert-source-map": "^1.5.0",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"find-root": "^1.1.0",
|
||||
"source-map": "^0.5.7",
|
||||
"stylis": "4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
||||
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/cache": {
|
||||
"version": "11.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
|
||||
"integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.9.0",
|
||||
"@emotion/sheet": "^1.4.0",
|
||||
"@emotion/utils": "^1.4.2",
|
||||
"@emotion/weak-memoize": "^0.4.0",
|
||||
"stylis": "4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/hash": {
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
|
||||
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/is-prop-valid": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
|
||||
"integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/memoize": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
|
||||
"integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/react": {
|
||||
"version": "11.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
||||
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/cache": "^11.14.0",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
|
||||
"@emotion/utils": "^1.4.2",
|
||||
"@emotion/weak-memoize": "^0.4.0",
|
||||
"hoist-non-react-statics": "^3.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/serialize": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
|
||||
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/hash": "^0.9.2",
|
||||
"@emotion/memoize": "^0.9.0",
|
||||
"@emotion/unitless": "^0.10.0",
|
||||
"@emotion/utils": "^1.4.2",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/sheet": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
|
||||
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/styled": {
|
||||
"version": "11.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
|
||||
"integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/is-prop-valid": "^1.3.0",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
|
||||
"@emotion/utils": "^1.4.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.0.0-rc.0",
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/unitless": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
|
||||
"integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
|
||||
"integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/utils": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
|
||||
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/weak-memoize": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
|
||||
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
@ -922,7 +1067,6 @@
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
@ -936,7 +1080,6 @@
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
@ -945,7 +1088,6 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
@ -953,19 +1095,236 @@
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/core-downloads-tracker": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.0.1.tgz",
|
||||
"integrity": "sha512-T5DNVnSD9pMbj4Jk/Uphz+yvj9dfpl2+EqsOuJtG12HxEihNG5pd3qzX5yM1Id4dDwKRvM3dPVcxyzavTFhJeA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.0.1.tgz",
|
||||
"integrity": "sha512-tQwjIIsn/UUSCHoCIQVkANuLua67h7Ro9M9gIHoGWaFbJFuF6cSO4Oda2olDVqIs4SWG+PaDChuu6SngxsaoyQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.10",
|
||||
"@mui/core-downloads-tracker": "^7.0.1",
|
||||
"@mui/system": "^7.0.1",
|
||||
"@mui/types": "^7.4.0",
|
||||
"@mui/utils": "^7.0.1",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@types/react-transition-group": "^4.4.12",
|
||||
"clsx": "^2.1.1",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^19.0.0",
|
||||
"react-transition-group": "^4.4.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@mui/material-pigment-css": "^7.0.1",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
},
|
||||
"@mui/material-pigment-css": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/react-is": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
|
||||
"integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mui/private-theming": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.0.1.tgz",
|
||||
"integrity": "sha512-1kQ7REYjjzDukuMfTbAjm3pLEhD7gUMC2bWhg9VD6f6sHzyokKzX0XHzlr3IdzNWBjPytGkzHpPIRQrUOoPLCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.10",
|
||||
"@mui/utils": "^7.0.1",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/styled-engine": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.0.1.tgz",
|
||||
"integrity": "sha512-BeGe4xZmF7tESKhmctYrL54Kl25kGHPKVdZYM5qj5Xz76WM/poY+d8EmAqUesT6k2rbJWPp2gtOAXXinNCGunQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.10",
|
||||
"@emotion/cache": "^11.13.5",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"@emotion/sheet": "^1.4.0",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/system": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.0.1.tgz",
|
||||
"integrity": "sha512-pK+puz0hRPHEKGlcPd80mKYD3jpyi0uVIwWffox1WZgPTQMw2dCKLcD+9ndMDJADnrKzmKlpoH756PPFh2UvWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.10",
|
||||
"@mui/private-theming": "^7.0.1",
|
||||
"@mui/styled-engine": "^7.0.1",
|
||||
"@mui/types": "^7.4.0",
|
||||
"@mui/utils": "^7.0.1",
|
||||
"clsx": "^2.1.1",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/types": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.0.tgz",
|
||||
"integrity": "sha512-TxJ4ezEeedWHBjOmLtxI203a9DII9l4k83RXmz1PYSAmnyEcK2PglTNmJGxswC/wM5cdl9ap2h8lnXvt2swAGQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/utils": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.0.1.tgz",
|
||||
"integrity": "sha512-SJKrrebNpmK9rJCnVL29nGPhPXQYtBZmb7Dsp0f58uIUhQfAKcBXHE4Kjs06SX4CwqeCuwEVgcHY+MgAO6XQ/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.10",
|
||||
"@mui/types": "^7.4.0",
|
||||
"@types/prop-types": "^15.7.14",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^19.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/utils/node_modules/react-is": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
|
||||
"integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@ -1011,6 +1370,16 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.37.0.tgz",
|
||||
@ -1393,6 +1762,12 @@
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
||||
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
||||
@ -1425,6 +1800,15 @@
|
||||
"@types/react": "^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
||||
@ -1754,13 +2138,27 @@
|
||||
"version": "1.8.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-macros": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
|
||||
"integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"cosmiconfig": "^7.0.0",
|
||||
"resolve": "^1.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@ -1886,7 +2284,6 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@ -2041,6 +2438,31 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/parse-json": "^4.0.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"parse-json": "^5.0.0",
|
||||
"path-type": "^4.0.0",
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/cosmiconfig/node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@ -2246,7 +2668,6 @@
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
@ -2389,70 +2810,13 @@
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true
|
||||
},
|
||||
"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,
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
||||
"license": "MIT",
|
||||
"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"
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
@ -2610,7 +2974,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@ -2932,6 +3295,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/find-root": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||
@ -3350,6 +3719,21 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
@ -3363,7 +3747,6 @@
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"parent-module": "^1.0.0",
|
||||
"resolve-from": "^4.0.0"
|
||||
@ -3405,59 +3788,11 @@
|
||||
"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,
|
||||
"license": "MIT",
|
||||
"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-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,
|
||||
"license": "MIT",
|
||||
"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,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-bigints": "^1.0.2"
|
||||
},
|
||||
"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-binary-path": {
|
||||
"version": "2.1.0",
|
||||
@ -3505,7 +3840,6 @@
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
@ -3876,7 +4210,6 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
},
|
||||
@ -3890,6 +4223,12 @@
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
@ -3967,8 +4306,7 @@
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
@ -4102,8 +4440,7 @@
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/mz": {
|
||||
"version": "2.7.0",
|
||||
@ -4354,7 +4691,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"callsites": "^3.0.0"
|
||||
},
|
||||
@ -4362,6 +4698,24 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-json": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"error-ex": "^1.3.1",
|
||||
"json-parse-even-better-errors": "^2.3.0",
|
||||
"lines-and-columns": "^1.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
@ -4383,8 +4737,7 @@
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.11.1",
|
||||
@ -4408,11 +4761,19 @@
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
@ -4953,7 +5314,6 @@
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.0",
|
||||
"path-parse": "^1.0.7",
|
||||
@ -4973,7 +5333,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
@ -5292,6 +5651,15 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@ -5507,6 +5875,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/stylis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
||||
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sucrase": {
|
||||
"version": "3.35.0",
|
||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||
@ -5545,7 +5919,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
|
||||
@ -10,6 +10,9 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/material": "^7.0.1",
|
||||
"axios": "^1.8.4",
|
||||
"lucide-react": "^0.427.0",
|
||||
"react": "^18.3.1",
|
||||
|
||||
BIN
Front-end/public/images/snow.jpg
Normal file
BIN
Front-end/public/images/snow.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
@ -1,4 +1,5 @@
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import { AuthProvider } from "./AuthContext.jsx";
|
||||
import Home from "./pages/Home.jsx";
|
||||
import About from "./pages/About.jsx";
|
||||
import Gestion from "./pages/Gestion/Gestion.jsx";
|
||||
@ -6,32 +7,40 @@ import Header from "./components/Header.jsx";
|
||||
import ObjectManagement from "./pages/Gestion/ObjectManagement.jsx";
|
||||
import Objet from "./pages/Gestion/Objet.jsx";
|
||||
import AddObject from "./pages/Gestion/AddObject.jsx";
|
||||
import Sidebar from './pages/Admin/sidebar.jsx';
|
||||
import User from './pages/Admin/User.jsx';
|
||||
import Signup from "./pages/Signup.jsx";
|
||||
import Login from "./pages/Login.jsx";
|
||||
import Settings from "./pages/Settings.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";
|
||||
|
||||
function App() {
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<div>
|
||||
<Header />
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/gestion" element={<Gestion />} />
|
||||
<Route path="/gestionObjets" element={<ObjectManagement />} />
|
||||
<Route path="/objet" element={<Objet />} />
|
||||
<Route path="/ajouterObjet" element={<AddObject />} />
|
||||
<Route path="/sidebar" element={<Sidebar />} />
|
||||
<Route path="/user" element={<User />} />
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
<Route path="/adminobjet" element={<AdminObjet />} />
|
||||
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
<AuthProvider>
|
||||
{" "}
|
||||
{/* Enveloppe l'application avec AuthProvider */}
|
||||
<Router>
|
||||
<div>
|
||||
<Header />
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/gestion" element={<Gestion />} />
|
||||
<Route path="/gestionObjets" element={<ObjectManagement />} />
|
||||
<Route path="/objet" element={<Objet />} />
|
||||
<Route path="/signup" element={<Signup />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/ajouterObjet" 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={<AdminObjet />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
</AuthProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
42
Front-end/src/AuthContext.jsx
Normal file
42
Front-end/src/AuthContext.jsx
Normal file
@ -0,0 +1,42 @@
|
||||
// src/AuthContext.js
|
||||
import React, { createContext, useContext, useState, useEffect } from "react";
|
||||
|
||||
// Créer le contexte
|
||||
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
|
||||
export const AuthProvider = ({ children }) => {
|
||||
const [token, setToken] = useState(localStorage.getItem("token"));
|
||||
|
||||
// Met à jour le token lorsque localStorage change
|
||||
useEffect(() => {
|
||||
const handleStorageChange = () => {
|
||||
setToken(localStorage.getItem("token"));
|
||||
};
|
||||
|
||||
window.addEventListener("storage", handleStorageChange);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("storage", handleStorageChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const login = (newToken) => {
|
||||
localStorage.setItem("token", newToken);
|
||||
setToken(newToken);
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
localStorage.removeItem("token");
|
||||
setToken(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ token, login, logout }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
@ -1,23 +1,21 @@
|
||||
import React, {useRef} from "react";
|
||||
import React from "react";
|
||||
import { ChartLine } from "lucide-react";
|
||||
function BoutonGraphique({ TypeAff, setAffichage,graphCible}) {
|
||||
const handleClick = (newAffichage) =>{
|
||||
setAffichage(newAffichage);
|
||||
if(graphCible.current){
|
||||
graphCible.current.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
|
||||
function BoutonGraphique({ type, setGraphStates, graphStates, graphCible }) {
|
||||
const handleClick = () => {
|
||||
setGraphStates((prev) => ({ ...prev, [type]: !prev[type] }));
|
||||
};
|
||||
return !TypeAff ? (
|
||||
return !graphStates[type] ? (
|
||||
<button
|
||||
className="bg-blue-200 py-2 my-2 px-4 rounded-full mr-2"
|
||||
onClick={() => handleClick(true)}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<ChartLine className="text-indigo-600" size={24} />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="bg-blue-400 py-2 my-2 px-4 rounded-full mr-2"
|
||||
onClick={() => handleClick(false)}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<ChartLine className="text-indigo-600" size={24} />
|
||||
</button>
|
||||
|
||||
@ -1,70 +1,61 @@
|
||||
import React, { useState } from "react";
|
||||
import { LogIn, UserPlus, Menu, X } from "lucide-react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { LogIn, UserPlus, LogOut, Settings } from "lucide-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useAuth } from "../AuthContext";
|
||||
|
||||
function Header() {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const { token, logout } = useAuth();
|
||||
|
||||
return (
|
||||
<header className="bg-white shadow-sm">
|
||||
<div className=" max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold text-indigo-600">VigiMétéo</h1>
|
||||
|
||||
<button
|
||||
className="sm:hidden text-gray-600 hover:text-indigo-600"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
>
|
||||
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
||||
</button>
|
||||
|
||||
<nav
|
||||
className={`${
|
||||
isMenuOpen ? "block" : "hidden"
|
||||
} absolute top-16 left-0 w-full bg-white shadow-md sm:static sm:w-auto sm:flex sm:gap-6 sm:shadow-none`}
|
||||
>
|
||||
<ul className="flex flex-col sm:flex-row gap-4 sm:gap-6 text-gray-600 p-4 sm:p-0">
|
||||
<li>
|
||||
<a href="/" className="hover:text-indigo-600">
|
||||
Accueil
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/about" className="hover:text-indigo-600">
|
||||
À propos
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/gestion" className="hover:text-indigo-600">
|
||||
Gestion
|
||||
</a>
|
||||
</li>
|
||||
<li className="sm:hidden">
|
||||
<a href="/login" className="hover:text-indigo-600 flex items-center gap-2">
|
||||
<LogIn size={20} />
|
||||
Connexion
|
||||
</a>
|
||||
</li>
|
||||
<li className="sm:hidden">
|
||||
<a href="/signup" className="hover:text-indigo-600 flex items-center gap-2">
|
||||
<UserPlus size={20} />
|
||||
Inscription
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div className="hidden sm:flex gap-4">
|
||||
<button className="flex items-center gap-2 text-gray-600 hover:text-indigo-600">
|
||||
<LogIn size={20} />
|
||||
<span>Connexion</span>
|
||||
</button>
|
||||
<button className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700">
|
||||
<UserPlus size={20} />
|
||||
<span>Inscription</span>
|
||||
</button>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold text-indigo-600">VigiMétéo</h1>
|
||||
<nav>
|
||||
<ul className="flex gap-4">
|
||||
<li>
|
||||
<Link to="/" className="text-gray-600 hover:text-indigo-600">Accueil</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/about" className="text-gray-600 hover:text-indigo-600">A propos</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/gestion" className="text-gray-600 hover:text-indigo-600">Gestion</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="flex gap-4">
|
||||
{token ? (
|
||||
<>
|
||||
<Link to="/settings" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600">
|
||||
<Settings size={20} />
|
||||
<span></span>
|
||||
</Link>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="flex items-center gap-2 text-gray-600 hover:text-red-600"
|
||||
>
|
||||
<LogOut size={20} />
|
||||
<span>Déconnexion</span>
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Link to="/login" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600">
|
||||
<LogIn size={20} />
|
||||
<span>Connexion</span>
|
||||
</Link>
|
||||
<Link to="/signup" className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700">
|
||||
<UserPlus size={20} />
|
||||
<span>Inscription</span>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
export default Header;
|
||||
|
||||
@ -3,8 +3,8 @@ import { Info } from "lucide-react";
|
||||
|
||||
function InfoObject({ object,defafficherModif }) {
|
||||
return (
|
||||
<div key={object.id} className="bg-white p-6 rounded-xl min-w-5xl">
|
||||
<div className="flex align-items gap-6">
|
||||
<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="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-1">
|
||||
<Info className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
|
||||
@ -15,7 +15,7 @@ import { Wind } from "lucide-react";
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../config";
|
||||
|
||||
function MeteoGraph({ object, categorie, Logo }) {
|
||||
function MeteoGraph({ object, categorie, Logo,reference}) {
|
||||
const [rawData, setRawData] = useState([]);
|
||||
const identifiant = object.id;
|
||||
useEffect(() => {
|
||||
@ -23,7 +23,11 @@ function MeteoGraph({ object, categorie, Logo }) {
|
||||
setRawData(response.data);
|
||||
});
|
||||
}, [object]);
|
||||
|
||||
useEffect(() => {
|
||||
if (reference?.current) {
|
||||
reference.current.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
}, [reference]);
|
||||
function getAvg() {
|
||||
let moyenne = 0;
|
||||
rawData.forEach((element) => {
|
||||
@ -35,9 +39,11 @@ function MeteoGraph({ object, categorie, Logo }) {
|
||||
}
|
||||
return (
|
||||
<div
|
||||
ref={reference}
|
||||
key={object.id}
|
||||
className="bg-white mb-6 p-6 rounded-xl min-w-5xl"
|
||||
style={{ width: "100%", height: "400px" }}
|
||||
|
||||
>
|
||||
<div className="flex align-items gap-6">
|
||||
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
|
||||
|
||||
@ -1,39 +1,30 @@
|
||||
import { Thermometer, Sun, CircleGauge, Droplet } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../config";
|
||||
import BoutonGraphique from "./BoutonGraphique";
|
||||
import AlertInactive from "./AlertInactive";
|
||||
import ParticularMeteo from "./ParticularMeteo";
|
||||
|
||||
function MeteoInfos({
|
||||
object,
|
||||
defAffTempGraph,
|
||||
AffTempGraph,
|
||||
defAffPressionGraph,
|
||||
AffPressionGraph,
|
||||
defAffHumiditeGraph,
|
||||
AffHumiditeGraph,
|
||||
graphCible
|
||||
}) {
|
||||
function MeteoInfos({ object, graphStates, setGraphStates, graphRefs }) {
|
||||
const [rawData, setRawData] = useState([]);
|
||||
const [AffAlert,setAffAlert] = useState(false);
|
||||
const [AffAlert, setAffAlert] = useState(false);
|
||||
const [AffRegles, setAffRegles] = useState(false);
|
||||
const identifiant = object.id;
|
||||
|
||||
useEffect(() => {
|
||||
axios.get(`${API_BASE_URL}/meteo?id=${identifiant}`).then((response) => {
|
||||
setRawData(response.data);
|
||||
if(rawData.length <5){
|
||||
if (rawData.length < 5) {
|
||||
setAffAlert(true);
|
||||
}
|
||||
});
|
||||
}, [object]);
|
||||
|
||||
const lastData = rawData.length > 0 ? rawData[rawData.length - 1] : null;
|
||||
console.log(rawData.length);
|
||||
return (
|
||||
<div key={object.id} className="bg-white p-6 rounded-xl min-w-5xl">
|
||||
{(AffAlert&&(object.status==="active")) && (
|
||||
{AffAlert && object.status === "active" && (
|
||||
<AlertInactive affAlert={AffAlert} setAffAlert={setAffAlert} />
|
||||
)}
|
||||
<div className="flex align-items gap-6">
|
||||
@ -44,78 +35,38 @@ function MeteoInfos({
|
||||
</div>
|
||||
{lastData ? (
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
{lastData.temperature && (
|
||||
<div className="bg-indigo-50 rounded-lg flex items-center w-full">
|
||||
<div className="flex align-items gap-3 w-full justify-between">
|
||||
<div className="flex align-items ml-3 gap-2">
|
||||
<div className="flex items-center">
|
||||
<Thermometer className="text-indigo-600" size={40} />
|
||||
</div>
|
||||
<div className="flex flex-col items-start">
|
||||
<h1 className="text-indigo-600 text-xl font-bold ">
|
||||
Température
|
||||
</h1>
|
||||
<h1 className="text-gray-700 text-4xl font-bold">
|
||||
{lastData.temperature} <span className="text-lg">°C</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<BoutonGraphique
|
||||
TypeAff={AffTempGraph}
|
||||
setAffichage={defAffTempGraph}
|
||||
graphCible={graphCible}
|
||||
<ParticularMeteo
|
||||
type="temperature"
|
||||
data={lastData}
|
||||
Icon={Thermometer}
|
||||
texte1="Température"
|
||||
texte2="°C"
|
||||
graphStates={graphStates}
|
||||
setGraphStates={setGraphStates}
|
||||
graphRefs={graphRefs}
|
||||
/>
|
||||
|
||||
<ParticularMeteo
|
||||
type="pressure"
|
||||
data={lastData}
|
||||
Icon={CircleGauge}
|
||||
texte1="Pression"
|
||||
texte2="hPa"
|
||||
graphStates={graphStates}
|
||||
setGraphStates={setGraphStates}
|
||||
graphRefs={graphRefs}
|
||||
/>
|
||||
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{lastData.pressure && (
|
||||
<div className="bg-indigo-50 rounded-lg flex items-center w-full">
|
||||
<div className="flex align-items gap-3 w-full justify-between">
|
||||
<div className="flex align-items ml-3 gap-2">
|
||||
<div className="flex items-center">
|
||||
<CircleGauge className="text-indigo-600" size={40} />
|
||||
</div>
|
||||
<div className="flex flex-col items-start">
|
||||
<h1 className="text-indigo-600 text-xl font-bold ">
|
||||
Pression
|
||||
</h1>
|
||||
<h1 className="text-gray-700 text-4xl font-bold">
|
||||
{lastData.pressure} <span className="text-lg">hPa</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<BoutonGraphique
|
||||
TypeAff={AffPressionGraph}
|
||||
setAffichage={defAffPressionGraph}
|
||||
graphCible={graphCible}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{lastData.humidity && (
|
||||
<div className="bg-indigo-50 rounded-lg flex items-center w-full">
|
||||
<div className="flex align-items gap-3 w-full justify-between">
|
||||
<div className="flex align-items ml-3 gap-2">
|
||||
<div className="flex items-center">
|
||||
<Droplet className="text-indigo-600" size={40} />
|
||||
</div>
|
||||
<div className="flex flex-col items-start">
|
||||
<h1 className="text-indigo-600 text-xl font-bold ">
|
||||
Humidité
|
||||
</h1>
|
||||
<h1 className="text-gray-700 text-4xl font-bold">
|
||||
{lastData.humidity} <span className="text-lg">%</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<BoutonGraphique
|
||||
TypeAff={AffHumiditeGraph}
|
||||
setAffichage={defAffHumiditeGraph}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<ParticularMeteo
|
||||
type="humidity"
|
||||
data={lastData}
|
||||
Icon={Droplet}
|
||||
texte1="Humidité"
|
||||
texte2="%"
|
||||
graphStates={graphStates}
|
||||
setGraphStates={setGraphStates}
|
||||
graphRefs={graphRefs}
|
||||
/>
|
||||
<h1 className="text-gray-500 text-sm">
|
||||
Dernier enregistrement : {lastData.timestamp}
|
||||
</h1>
|
||||
|
||||
164
Front-end/src/components/ParticularMeteo.jsx
Normal file
164
Front-end/src/components/ParticularMeteo.jsx
Normal file
@ -0,0 +1,164 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import BoutonGraphique from "./BoutonGraphique";
|
||||
import { Bell } from "lucide-react";
|
||||
import Slider from "@mui/material/Slider";
|
||||
import { API_BASE_URL } from "../config";
|
||||
import axios from "axios";
|
||||
|
||||
const identifiant = new URLSearchParams(window.location.search).get("id");
|
||||
function ParticularMeteo({
|
||||
type,
|
||||
data,
|
||||
Icon,
|
||||
texte1,
|
||||
texte2,
|
||||
graphStates,
|
||||
setGraphStates,
|
||||
graphRefs,
|
||||
}) {
|
||||
const [affRegles, setAffRegles] = useState(false);
|
||||
const [rangeValue, setRangeValue] = useState([0, 0]);
|
||||
const [alerteActive, setAlerteActive] = useState(false);
|
||||
const minMaxValues = {
|
||||
temperature: [-50, 60],
|
||||
pressure: [940, 1060],
|
||||
humidity: [10, 100],
|
||||
};
|
||||
const MIN = minMaxValues[type][0];
|
||||
const MAX = minMaxValues[type][1];
|
||||
const formatLabel = (value) => {
|
||||
switch (type) {
|
||||
case "temperature":
|
||||
return `${value}°C`;
|
||||
case "pressure":
|
||||
return `${value} hPa`;
|
||||
case "humidity":
|
||||
return `${value}%`;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
};
|
||||
const marks = [
|
||||
{
|
||||
value: MIN,
|
||||
label: formatLabel(MIN),
|
||||
},
|
||||
{
|
||||
value: MAX,
|
||||
label: formatLabel(MAX),
|
||||
},
|
||||
];
|
||||
useEffect(() => {
|
||||
axios.get(`${API_BASE_URL}/getRange?id=${identifiant}`).then((response) => {
|
||||
setRangeValue([
|
||||
response.data[0][type + "_min"],
|
||||
response.data[0][type + "_max"],
|
||||
]);
|
||||
});
|
||||
}, [identifiant]);
|
||||
const color =
|
||||
rangeValue[0] > data[type] || rangeValue[1] < data[type]
|
||||
? "text-red-600"
|
||||
: "text-indigo-600";
|
||||
|
||||
const defRangeData = () => {
|
||||
console.log("Données envoyées :", {
|
||||
id: identifiant,
|
||||
min: rangeValue[0],
|
||||
max: rangeValue[1],
|
||||
type,
|
||||
});
|
||||
axios
|
||||
.post(`${API_BASE_URL}/modifRangeData`, {
|
||||
id: identifiant,
|
||||
min: parseFloat(rangeValue[0]),
|
||||
max: parseFloat(rangeValue[1]),
|
||||
type,
|
||||
})
|
||||
.then((response) => {
|
||||
console.log("Modification réussie :", response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Erreur lors de la modification :", error);
|
||||
});
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
setRangeValue(newValue);
|
||||
};
|
||||
function valuetext(value) {
|
||||
return `${value}°C`;
|
||||
}
|
||||
if (data[type]) {
|
||||
return (
|
||||
<div className="bg-indigo-50 flex flex-col rounded-lg items-center w-full">
|
||||
<div className="flex align-items gap-3 w-full justify-between">
|
||||
<div className="flex align-items ml-3 gap-2">
|
||||
<div className="flex items-center">
|
||||
<Icon className={`${color}`} size={40} />
|
||||
</div>
|
||||
<div className="flex flex-col items-start">
|
||||
<h1 className={`${color} text-xl font-bold `}>{texte1}</h1>
|
||||
<h1 className={`${color} text-4xl font-bold`}>
|
||||
{Math.round(data[type])}{" "}
|
||||
<span className="text-lg">{texte2}</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setAffRegles(!affRegles);
|
||||
}}
|
||||
>
|
||||
<Bell
|
||||
className={`${color} hover:pointer-events-auto`}
|
||||
size={30}
|
||||
/>
|
||||
</button>
|
||||
|
||||
<BoutonGraphique
|
||||
type={type}
|
||||
graphStates={graphStates}
|
||||
setGraphStates={setGraphStates}
|
||||
graphCible={graphRefs[type]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{affRegles && (
|
||||
<div className="p-6">
|
||||
<h1 className="text-red-500 text-l font-semibold">
|
||||
Définissez la valeur seuil pour l'alerte :
|
||||
</h1>
|
||||
<div className="p-4">
|
||||
<Slider
|
||||
getAriaLabel={() => "Temperature range"}
|
||||
value={rangeValue}
|
||||
onChange={handleChange}
|
||||
valueLabelDisplay="auto"
|
||||
min={MIN}
|
||||
max={MAX}
|
||||
marks={marks}
|
||||
getAriaValueText={valuetext}
|
||||
disableSwap
|
||||
/>
|
||||
</div>
|
||||
{color=="text-red-600" &&(
|
||||
<p className="text-red-500 text-l mb-2">Attention, la valeur actuelle est hors des bornes que vous avez définit !</p>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => defRangeData()}
|
||||
className="text-blue-700 hover:text-white border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 mb-2 dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-500 dark:focus:ring-blue-800"
|
||||
>
|
||||
Définir alerte
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ParticularMeteo;
|
||||
@ -5,7 +5,7 @@ import { Wind } from "lucide-react";
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../config";
|
||||
|
||||
function WindGraph({ object }) {
|
||||
function WindGraph({ object,reference }) {
|
||||
const [rawData, setRawData] = useState([]);
|
||||
const identifiant = object.id;
|
||||
useEffect(() => {
|
||||
@ -25,10 +25,15 @@ function WindGraph({ object }) {
|
||||
);
|
||||
}
|
||||
|
||||
return null; // Si aucun point n'est survolé
|
||||
return null;
|
||||
};
|
||||
useEffect(() => {
|
||||
if (reference?.current) {
|
||||
reference.current.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
}, [reference]);
|
||||
return (
|
||||
<div key={object.id} className="bg-white mb-6 p-6 rounded-xl min-w-5xl" style={{width: "100%", height: "400px"}}>
|
||||
<div key={object.id} ref={reference} className="bg-white mb-6 p-6 rounded-xl min-w-5xl" style={{width: "100%", height: "400px"}}>
|
||||
<div className="flex align-items gap-6">
|
||||
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
|
||||
<Wind className="text-indigo-600" size={24} />
|
||||
|
||||
@ -5,7 +5,7 @@ import axios from "axios";
|
||||
import { API_BASE_URL } from "../config";
|
||||
import BoutonGraphique from "./BoutonGraphique";
|
||||
|
||||
function WindInfo({ object, defAffWindGraph, AffWindGraph }) {
|
||||
function WindInfo({ object, setGraphStates, graphStates, graphRefs, reference}) {
|
||||
const [rawData, setRawData] = useState([]);
|
||||
const identifiant = object.id;
|
||||
useEffect(() => {
|
||||
@ -13,6 +13,11 @@ function WindInfo({ object, defAffWindGraph, AffWindGraph }) {
|
||||
setRawData(response.data);
|
||||
});
|
||||
}, [object]);
|
||||
useEffect(() => {
|
||||
if (reference?.current) {
|
||||
reference.current.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
}, [reference]);
|
||||
|
||||
const lastData = rawData.length > 0 ? rawData[rawData.length - 1] : null;
|
||||
|
||||
@ -47,9 +52,11 @@ function WindInfo({ object, defAffWindGraph, AffWindGraph }) {
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<BoutonGraphique
|
||||
TypeAff={AffWindGraph}
|
||||
setAffichage={defAffWindGraph}
|
||||
<BoutonGraphique
|
||||
type="wind"
|
||||
graphStates={graphStates}
|
||||
setGraphStates={setGraphStates}
|
||||
graphCible={graphRefs.wind}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
1
Front-end/src/img/cloud-alert.svg
Normal file
1
Front-end/src/img/cloud-alert.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cloud-alert-icon lucide-cloud-alert"><path d="M12 12v4"/><path d="M12 20h.01"/><path d="M17 18h.5a1 1 0 0 0 0-9h-1.79A7 7 0 1 0 7 17.708"/></svg>
|
||||
|
After Width: | Height: | Size: 347 B |
44
Front-end/src/pages/AdminDashboard.jsx
Normal file
44
Front-end/src/pages/AdminDashboard.jsx
Normal file
@ -0,0 +1,44 @@
|
||||
import React, { useState } from "react";
|
||||
import { Calendar, Settings, LayoutDashboard } from "lucide-react";
|
||||
|
||||
function AdminDashboard() {
|
||||
|
||||
const [activeTab, setActiveTab] = useState('dashboard');
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
{/* SideMenu */}
|
||||
<div className="w-64 bg-white rounded-lg shadow-sm p-4">
|
||||
<h2 className="text-lg font-semibold text-gray-800 mb-4">
|
||||
Administration
|
||||
</h2>
|
||||
<nav className="flex flex-col space-y-2">
|
||||
<button
|
||||
onClick={() => setActiveTab('events')}
|
||||
className={`w-full flex items-center gap-2 px-4 py-2 rounded-lg ${
|
||||
activeTab === 'events'
|
||||
? 'bg-red-50 text-red-600'
|
||||
: 'text-gray-600 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<Calendar size={20} />
|
||||
Events
|
||||
</button>
|
||||
|
||||
<button onClick="" className="">
|
||||
<LayoutDashboard size="15" />
|
||||
Layout
|
||||
</button>
|
||||
|
||||
<button onClick="">
|
||||
<Settings size="15" />
|
||||
Settings
|
||||
</button>
|
||||
</nav>
|
||||
<h2>Test</h2>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AdminDashboard;
|
||||
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { Thermometer, CircleGauge, Droplet } from "lucide-react";
|
||||
|
||||
import { useEffect, useState, useRef} from "react";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../../config";
|
||||
|
||||
@ -14,31 +14,27 @@ import MeteoGraph from "../../components/MeteoGraph";
|
||||
import BatterieInfo from "../../components/BatterieInfo";
|
||||
function Objet() {
|
||||
const identifiant = new URLSearchParams(window.location.search).get("id");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [activeFilter, setActiveFilter] = useState("all");
|
||||
const [object, setObject] = useState({});
|
||||
const [graphStates, setGraphStates] = useState({
|
||||
wind:false,
|
||||
temperature:false,
|
||||
pressure:false,
|
||||
humidity:false,
|
||||
})
|
||||
wind: false,
|
||||
temperature: false,
|
||||
pressure: false,
|
||||
humidity: false,
|
||||
});
|
||||
const [afficherModif, defafficherModif] = useState(false);
|
||||
const [AffWindGraph, defAffWindGraph] = useState(false);
|
||||
const [AffTempGraph, defAffTempGraph] = useState(false);
|
||||
const [AffPressionGraph, defAffPressionGraph] = useState(false);
|
||||
const [AffHumiditeGraph, defAffHumideGraph] = useState(false);
|
||||
const tempGraphRef = useRef(null);
|
||||
const pressureGraphRef = useRef(null);
|
||||
const humidityGraphRef = useRef(null);
|
||||
const windGraphRef = useRef(null);
|
||||
const graphRefs = {
|
||||
temperature: useRef(null),
|
||||
pressure: useRef(null),
|
||||
humidity: useRef(null),
|
||||
wind: useRef(null),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
axios.get(`${API_BASE_URL}/objet?id=${identifiant}`).then((response) => {
|
||||
setObject(response.data[0]);
|
||||
});
|
||||
}, [identifiant]);
|
||||
return (
|
||||
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">
|
||||
<div className="text-center mb-5">
|
||||
@ -52,68 +48,50 @@ function Objet() {
|
||||
) : (
|
||||
<ModifObject object={object} defafficherModif={defafficherModif} />
|
||||
)}
|
||||
|
||||
{object && object.id ? (
|
||||
<WindInfo
|
||||
object={object}
|
||||
defAffWindGraph={defAffWindGraph}
|
||||
AffWindGraph={AffWindGraph}
|
||||
/>
|
||||
) : (
|
||||
<p>Chargement des données...</p>
|
||||
)}
|
||||
{object && object.id ? (
|
||||
<MeteoInfos
|
||||
object={object}
|
||||
defAffTempGraph={defAffTempGraph}
|
||||
AffTempGraph={AffTempGraph}
|
||||
defAffPressionGraph={defAffPressionGraph}
|
||||
AffPressionGraph={AffPressionGraph}
|
||||
defAffHumiditeGraph={defAffHumideGraph}
|
||||
AffHumiditeGraph={AffHumiditeGraph}
|
||||
tempGraphRef={tempGraphRef}
|
||||
pressureGraphRef={pressureGraphRef}
|
||||
humidityGraphRef={humidityGraphRef}
|
||||
/>
|
||||
) : (
|
||||
<p>Chargement des données...</p>
|
||||
)}
|
||||
<WindInfo
|
||||
object={object}
|
||||
setGraphStates={setGraphStates}
|
||||
graphStates={graphStates}
|
||||
graphRefs={graphRefs}
|
||||
/>
|
||||
<MeteoInfos
|
||||
object={object}
|
||||
graphStates={graphStates}
|
||||
setGraphStates={setGraphStates}
|
||||
graphRefs={graphRefs}
|
||||
/>
|
||||
<BatterieInfo object={object} />
|
||||
</div>
|
||||
{AffWindGraph &&
|
||||
(object && object.id ? (
|
||||
<WindGraph object={object} />
|
||||
) : (
|
||||
<p>Chargement des données...</p>
|
||||
))}
|
||||
{AffTempGraph &&
|
||||
(object && object.id ? (
|
||||
<MeteoGraph
|
||||
object={object}
|
||||
categorie={"temperature"}
|
||||
Logo={Thermometer}
|
||||
/>
|
||||
) : (
|
||||
<p>Chargement des données...</p>
|
||||
))}
|
||||
{AffPressionGraph &&
|
||||
(object && object.id ? (
|
||||
<MeteoGraph
|
||||
object={object}
|
||||
categorie={"pressure"}
|
||||
Logo={CircleGauge}
|
||||
/>
|
||||
) : (
|
||||
<p>Chargement des données...</p>
|
||||
))}
|
||||
{AffHumiditeGraph &&
|
||||
(object && object.id ? (
|
||||
<MeteoGraph object={object} categorie={"humidity"} Logo={Droplet} />
|
||||
) : (
|
||||
<p>Chargement des données...</p>
|
||||
))}
|
||||
|
||||
{graphStates.wind && <WindGraph object={object} reference={graphRefs.wind} />}
|
||||
{graphStates.temperature && (
|
||||
<MeteoGraph
|
||||
object={object}
|
||||
categorie={"temperature"}
|
||||
Logo={Thermometer}
|
||||
reference={graphRefs.temperature}
|
||||
/>
|
||||
)}
|
||||
{graphStates.pressure && (
|
||||
<MeteoGraph
|
||||
object={object}
|
||||
categorie={"pressure"}
|
||||
Logo={CircleGauge}
|
||||
reference={graphRefs.pressure}
|
||||
/>
|
||||
)}
|
||||
{graphStates.humidity && (
|
||||
<MeteoGraph
|
||||
object={object}
|
||||
categorie={"humidity"}
|
||||
Logo={Droplet}
|
||||
reference={graphRefs.humidity}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<h1>Erreur de récupération de l'objet</h1>
|
||||
);
|
||||
}
|
||||
export default Objet;
|
||||
|
||||
@ -1,115 +1,123 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Search, MapPin, Calendar, Bus, ArrowRight, LogIn, UserPlus } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import { useAuth } from "../AuthContext";
|
||||
|
||||
|
||||
function Home() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [activeFilter, setActiveFilter] = useState('all');
|
||||
const [name, setName] = useState([]);
|
||||
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 ta ville intelligente.
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Découvrez tout ce que votre ville a à offrir - des événements locaux aux horaires de transport, le tout en un seul endroit.
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [activeFilter, setActiveFilter] = useState('all');
|
||||
const [name, setName] = useState([]);
|
||||
const { token, logout } = useAuth();
|
||||
|
||||
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 ta ville intelligente.</h2>
|
||||
{token ? (
|
||||
<><h2>Tu es connecté</h2>
|
||||
|
||||
</>):(
|
||||
<h2>Non connecté</h2>
|
||||
)}
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||
Découvrez tout ce que votre ville a à offrir - des événements locaux aux horaires de transport, le tout en un seul endroit.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl mx-auto mb-12">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400" size={24} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search for locations, events, or transport..."
|
||||
className="w-full pl-12 pr-4 py-4 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-4 mt-4 justify-center">
|
||||
<button
|
||||
onClick={() => setActiveFilter('all')}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === 'all' ? 'bg-indigo-600 text-white' : 'bg-white text-gray-600'
|
||||
}`}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter('locations')}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === 'locations' ? 'bg-indigo-600 text-white' : 'bg-white text-gray-600'
|
||||
}`}
|
||||
>
|
||||
Locations
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter('events')}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === 'events' ? 'bg-indigo-600 text-white' : 'bg-white text-gray-600'
|
||||
}`}
|
||||
>
|
||||
Events
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter('transport')}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === 'transport' ? 'bg-indigo-600 text-white' : 'bg-white text-gray-600'
|
||||
}`}
|
||||
>
|
||||
Transport
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features Grid */}
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<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">
|
||||
<MapPin className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">Points d'intérêt</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Découvrez les meilleurs endroits de votre ville, qu'il s'agisse de restaurants, de parcs ou de lieux culturels.
|
||||
</p>
|
||||
<a href="#" className="flex items-center text-indigo-600 hover:text-indigo-700">
|
||||
Explorer les lieux <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl mx-auto mb-12">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400" size={24} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search for locations, events, or transport..."
|
||||
className="w-full pl-12 pr-4 py-4 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-4 mt-4 justify-center">
|
||||
<button
|
||||
onClick={() => setActiveFilter('all')}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === 'all' ? 'bg-indigo-600 text-white' : 'bg-white text-gray-600'
|
||||
}`}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter('locations')}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === 'locations' ? 'bg-indigo-600 text-white' : 'bg-white text-gray-600'
|
||||
}`}
|
||||
>
|
||||
Locations
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter('events')}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === 'events' ? 'bg-indigo-600 text-white' : 'bg-white text-gray-600'
|
||||
}`}
|
||||
>
|
||||
Events
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveFilter('transport')}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
activeFilter === 'transport' ? 'bg-indigo-600 text-white' : 'bg-white text-gray-600'
|
||||
}`}
|
||||
>
|
||||
Transport
|
||||
</button>
|
||||
|
||||
<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">
|
||||
<Calendar className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">Evenements locaux</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Restez informé des derniers événements, festivals et rassemblements communautaires dans votre région.
|
||||
</p>
|
||||
<a href="#" className="flex items-center text-indigo-600 hover:text-indigo-700">
|
||||
Voir les événements <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Features Grid */}
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<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">
|
||||
<MapPin className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">Points d'intérêt</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Découvrez les meilleurs endroits de votre ville, qu'il s'agisse de restaurants, de parcs ou de lieux culturels.
|
||||
</p>
|
||||
<a href="#" className="flex items-center text-indigo-600 hover:text-indigo-700">
|
||||
Explorer les lieux <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">
|
||||
<Calendar className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">Evenements locaux</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Restez informé des derniers événements, festivals et rassemblements communautaires dans votre région.
|
||||
</p>
|
||||
<a href="#" className="flex items-center text-indigo-600 hover:text-indigo-700">
|
||||
Voir les événements <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">
|
||||
<Bus className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">Transports publics</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Accédez en temps réel aux horaires et aux itinéraires des bus, des trains et des autres transports publics.
|
||||
</p>
|
||||
<a href="#" className="flex items-center text-indigo-600 hover:text-indigo-700">
|
||||
Vérifier les horaires <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
|
||||
<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">
|
||||
<Bus className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">Transports publics</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Accédez en temps réel aux horaires et aux itinéraires des bus, des trains et des autres transports publics.
|
||||
</p>
|
||||
<a href="#" className="flex items-center text-indigo-600 hover:text-indigo-700">
|
||||
Vérifier les horaires <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
||||
export default Home;
|
||||
|
||||
121
Front-end/src/pages/Login.jsx
Normal file
121
Front-end/src/pages/Login.jsx
Normal file
@ -0,0 +1,121 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Mail, Lock } from 'lucide-react';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import axios from 'axios'; // Assurez-vous d'avoir axios importé
|
||||
import { useAuth } from '../AuthContext';
|
||||
|
||||
function Login() {
|
||||
const [formData, setFormData] = useState({
|
||||
email: '',
|
||||
password: ''
|
||||
});
|
||||
const { login } = useAuth(); // Utilisation du hook useAuth pour accéder à la fonction login
|
||||
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();
|
||||
|
||||
try {
|
||||
const response = await fetch("http://localhost:8888/login", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
// 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');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la connexion', error);
|
||||
}
|
||||
};
|
||||
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">Connexion</h2>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Email */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
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">
|
||||
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>
|
||||
|
||||
{/* Bouton de connexion */}
|
||||
<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"
|
||||
>
|
||||
Se connecter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Lien vers la page d'inscription */}
|
||||
<div className="mt-4 text-sm text-center">
|
||||
<p>
|
||||
Vous n'avez pas de compte ?
|
||||
<Link to="/signup" className="text-indigo-600 hover:text-indigo-700 font-medium"> Inscrivez-vous ici</Link>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
||||
161
Front-end/src/pages/Settings.jsx
Normal file
161
Front-end/src/pages/Settings.jsx
Normal file
@ -0,0 +1,161 @@
|
||||
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("http://localhost:8888/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;
|
||||
217
Front-end/src/pages/Signup.jsx
Normal file
217
Front-end/src/pages/Signup.jsx
Normal file
@ -0,0 +1,217 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Mail, User, Lock } from 'lucide-react';
|
||||
import { useNavigate, Link} from 'react-router-dom'; // Importation du hook useNavigate
|
||||
|
||||
function Signup() {
|
||||
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("http://localhost:8888/signup", {
|
||||
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 l'inscription");
|
||||
}
|
||||
|
||||
alert("Inscription 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">Inscription</h2>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Formulaire (Nom, Prénom, Sexe, Email, Mot de passe) */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Nom:
|
||||
</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="name"
|
||||
value={formData.name}
|
||||
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>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Prénom:
|
||||
</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="surname"
|
||||
value={formData.surname}
|
||||
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">
|
||||
Sexe:
|
||||
</label>
|
||||
<div className="flex gap-6 items-center">
|
||||
<label className="inline-flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
name="gender"
|
||||
value="homme"
|
||||
checked={formData.gender === 'homme'}
|
||||
onChange={handleChange}
|
||||
className="form-radio h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300"
|
||||
/>
|
||||
<span className="ml-2">Homme</span>
|
||||
</label>
|
||||
<label className="inline-flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
name="gender"
|
||||
value="femme"
|
||||
checked={formData.gender === 'femme'}
|
||||
onChange={handleChange}
|
||||
className="form-radio h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300"
|
||||
/>
|
||||
<span className="ml-2">Femme</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
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">
|
||||
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>
|
||||
|
||||
{/* Confirmer mot de passe */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Confirmer le 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"
|
||||
>
|
||||
S'inscrire
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/*Si il a déjà un compte*/}
|
||||
<div className="mt-4 text-sm text-center">
|
||||
<p>
|
||||
Vous avez déjà un compte ?
|
||||
<Link to="/login" className="text-indigo-600 hover:text-indigo-700 font-medium"> Connectez-vous ici</Link>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Signup;
|
||||
318
sql/export.sql
Normal file
318
sql/export.sql
Normal file
@ -0,0 +1,318 @@
|
||||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
-- Dumped from database version 17.4
|
||||
-- Dumped by pg_dump version 17.4
|
||||
|
||||
-- Started on 2025-04-08 10:16:23
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET transaction_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_table_access_method = heap;
|
||||
|
||||
--
|
||||
-- TOC entry 222 (class 1259 OID 32768)
|
||||
-- Name: range_data; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.range_data (
|
||||
station_id integer NOT NULL,
|
||||
temperature_min numeric,
|
||||
temperature_max numeric,
|
||||
pressure_min numeric,
|
||||
pressure_max numeric,
|
||||
humidity_min numeric,
|
||||
humidity_max numeric
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.range_data OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 221 (class 1259 OID 16479)
|
||||
-- Name: weather_data; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.weather_data (
|
||||
id integer NOT NULL,
|
||||
station_id integer NOT NULL,
|
||||
temperature numeric(5,2),
|
||||
humidity numeric(5,2),
|
||||
pressure numeric(7,2),
|
||||
wind_speed numeric(5,2),
|
||||
wind_direction character varying(50),
|
||||
"timestamp" timestamp without time zone DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.weather_data OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 220 (class 1259 OID 16478)
|
||||
-- Name: weather_data_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.weather_data_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER SEQUENCE public.weather_data_id_seq OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 4921 (class 0 OID 0)
|
||||
-- Dependencies: 220
|
||||
-- Name: weather_data_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.weather_data_id_seq OWNED BY public.weather_data.id;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 219 (class 1259 OID 16468)
|
||||
-- Name: weather_objects; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.weather_objects (
|
||||
id integer NOT NULL,
|
||||
name character varying(500) NOT NULL,
|
||||
description text,
|
||||
type character varying(100) NOT NULL,
|
||||
location character varying(255),
|
||||
last_update timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
|
||||
status character varying(50) DEFAULT 'active'::character varying,
|
||||
batterie integer,
|
||||
type_batterie character varying(50),
|
||||
CONSTRAINT weather_objects_batterie_check CHECK (((batterie >= 0) AND (batterie <= 100)))
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.weather_objects OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 217 (class 1259 OID 16466)
|
||||
-- Name: weather_objects_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.weather_objects_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER SEQUENCE public.weather_objects_id_seq OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 218 (class 1259 OID 16467)
|
||||
-- Name: weather_objects_id_seq1; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.weather_objects_id_seq1
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER SEQUENCE public.weather_objects_id_seq1 OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 4922 (class 0 OID 0)
|
||||
-- Dependencies: 218
|
||||
-- Name: weather_objects_id_seq1; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.weather_objects_id_seq1 OWNED BY public.weather_objects.id;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 4755 (class 2604 OID 16482)
|
||||
-- Name: weather_data id; Type: DEFAULT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.weather_data ALTER COLUMN id SET DEFAULT nextval('public.weather_data_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 4752 (class 2604 OID 16471)
|
||||
-- Name: weather_objects id; Type: DEFAULT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.weather_objects ALTER COLUMN id SET DEFAULT nextval('public.weather_objects_id_seq1'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 4915 (class 0 OID 32768)
|
||||
-- Dependencies: 222
|
||||
-- Data for Name: range_data; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COPY public.range_data (station_id, temperature_min, temperature_max, pressure_min, pressure_max, humidity_min, humidity_max) FROM stdin;
|
||||
1 -33 42 980 1040 30 84
|
||||
2 -15 50 980 1040 30 90
|
||||
3 -15 50 980 1040 30 90
|
||||
4 -15 50 980 1040 30 90
|
||||
5 -15 50 980 1040 30 90
|
||||
6 -15 50 980 1040 30 90
|
||||
7 -15 50 980 1040 30 90
|
||||
8 -15 50 980 1040 30 90
|
||||
9 -15 50 980 1040 30 90
|
||||
10 -15 50 980 1040 30 90
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 4914 (class 0 OID 16479)
|
||||
-- Dependencies: 221
|
||||
-- Data for Name: weather_data; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COPY public.weather_data (id, station_id, temperature, humidity, pressure, wind_speed, wind_direction, "timestamp") FROM stdin;
|
||||
1 1 21.50 60.20 1013.10 5.40 Nord-Ouest 2025-03-29 18:47:46.685241
|
||||
2 2 22.30 55.00 1012.50 3.20 Sud-Ouest 2025-03-29 18:47:46.685241
|
||||
3 3 24.10 50.00 1010.80 6.00 Est 2025-03-29 18:47:46.685241
|
||||
4 1 19.80 65.40 1014.00 4.50 Ouest 2025-03-29 18:47:46.685241
|
||||
5 2 20.60 59.30 1013.50 2.80 Nord 2025-03-29 18:47:46.685241
|
||||
6 1 22.50 60.00 1012.50 12.30 Nord-Ouest 2025-03-29 08:00:00
|
||||
7 1 23.00 65.00 1013.25 14.00 Ouest 2025-03-29 09:00:00
|
||||
8 1 24.00 70.00 1014.75 15.20 Nord 2025-03-29 10:00:00
|
||||
9 2 21.50 55.00 1011.30 11.00 Sud 2025-03-29 08:30:00
|
||||
10 2 22.00 60.00 1012.80 13.00 Est 2025-03-29 09:30:00
|
||||
11 2 23.50 63.00 1013.50 14.50 Sud-Est 2025-03-29 10:30:00
|
||||
12 3 26.00 58.00 1012.90 17.00 Ouest 2025-03-29 11:00:00
|
||||
13 3 27.00 60.00 1014.00 18.50 Nord-Ouest 2025-03-29 12:00:00
|
||||
14 3 28.00 62.00 1015.10 16.00 Nord 2025-03-29 13:00:00
|
||||
15 4 19.50 75.00 1010.00 9.50 Sud-Ouest 2025-03-29 08:00:00
|
||||
16 4 20.00 80.00 1010.50 10.00 Sud 2025-03-29 09:00:00
|
||||
17 4 21.50 85.00 1011.00 11.50 Est 2025-03-29 10:00:00
|
||||
18 5 18.00 90.00 1010.70 8.00 Ouest 2025-03-29 08:30:00
|
||||
19 5 18.50 92.00 1011.20 7.00 Nord-Ouest 2025-03-29 09:30:00
|
||||
20 5 19.00 95.00 1011.80 6.50 Nord 2025-03-29 10:30:00
|
||||
21 6 24.50 65.00 1013.90 13.00 Sud 2025-03-29 11:00:00
|
||||
22 6 25.00 66.00 1014.20 14.50 Ouest 2025-03-29 12:00:00
|
||||
23 6 26.50 68.00 1015.50 16.00 Sud-Ouest 2025-03-29 13:00:00
|
||||
24 7 21.00 60.00 1012.50 11.50 Est 2025-03-29 08:00:00
|
||||
25 7 22.50 62.00 1013.00 12.00 Nord-Ouest 2025-03-29 09:00:00
|
||||
26 7 23.00 64.00 1013.75 13.50 Sud-Est 2025-03-29 10:00:00
|
||||
27 8 25.00 58.00 1012.10 16.50 Nord 2025-03-29 08:30:00
|
||||
28 8 26.00 60.00 1013.30 17.50 Ouest 2025-03-29 09:30:00
|
||||
29 8 27.00 62.00 1014.50 18.00 Sud-Ouest 2025-03-29 10:30:00
|
||||
30 9 22.00 67.00 1011.40 14.00 Est 2025-03-29 11:00:00
|
||||
31 9 23.00 69.00 1012.60 15.00 Nord-Ouest 2025-03-29 12:00:00
|
||||
32 9 24.00 72.00 1013.80 16.00 Nord 2025-03-29 13:00:00
|
||||
33 10 18.00 55.00 1010.20 10.00 Ouest 2025-03-29 08:00:00
|
||||
34 10 19.00 58.00 1011.00 11.50 Sud-Ouest 2025-03-29 09:00:00
|
||||
35 10 20.00 60.00 1011.70 12.50 Est 2025-03-29 10:00:00
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 4912 (class 0 OID 16468)
|
||||
-- Dependencies: 219
|
||||
-- Data for Name: weather_objects; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COPY public.weather_objects (id, name, description, type, location, last_update, status, batterie, type_batterie) FROM stdin;
|
||||
1 Station Paris Station météo située à Paris, France. Mesures de température, humidité, pression et vent. station Paris, France 2025-04-07 20:47:06.264847 active 100 solaire
|
||||
9 Station Nice Station météo située à Nice, France. Elle mesure la température, l'humidité et la pression. station Nice, France 2025-03-31 18:31:23.038047 active 100 solaire
|
||||
2 Station Lyon Station météo située à Lyon, France. Mesures de température, humidité, pression et vent. station Lyon, France 2025-03-30 12:16:00.835834 active 100 solaire
|
||||
3 Station Marseille Station météo située à Marseille, France. Mesures de température, humidité, pression et vent. station Marseille, France 2025-03-30 17:01:10.631653 inactive 100 solaire
|
||||
4 Capteur Bordeaux Capteur de température et d'humidité à Bordeaux. capteur Bordeaux, France 2025-03-30 17:53:01.42853 active 100 solaire
|
||||
5 Capteur Lille Capteur de pression atmosphérique à Lille. capteur Lille, France 2025-03-31 21:32:04.955306 inactive 100 solaire
|
||||
6 Capteur Nantes Capteur de vent à Nantes. capteur Nantes, France 2025-03-30 20:10:18.547523 active 100 solaire
|
||||
7 Station Toulouse Station météo à Toulouse mesurant la température, l'humidité, la pression et la vitesse du vent. station Toulouse, France 2025-04-02 15:43:34.803703 active 100 solaire
|
||||
10 Capteur Paris Sud Capteur de température et humidité à Paris Sud. capteur Paris, France 2025-04-02 23:09:38.725522 inactive 100 solaire
|
||||
8 Capteur Grenoble Capteur de température à Grenoble. capteur Grenoble, France 2025-04-04 10:40:08.247433 active 100 solaire
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 4923 (class 0 OID 0)
|
||||
-- Dependencies: 220
|
||||
-- Name: weather_data_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.weather_data_id_seq', 35, true);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 4924 (class 0 OID 0)
|
||||
-- Dependencies: 217
|
||||
-- Name: weather_objects_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.weather_objects_id_seq', 1, false);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 4925 (class 0 OID 0)
|
||||
-- Dependencies: 218
|
||||
-- Name: weather_objects_id_seq1; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.weather_objects_id_seq1', 17, true);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 4763 (class 2606 OID 32774)
|
||||
-- Name: range_data station_meteo_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.range_data
|
||||
ADD CONSTRAINT station_meteo_pkey PRIMARY KEY (station_id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 4761 (class 2606 OID 16485)
|
||||
-- Name: weather_data weather_data_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.weather_data
|
||||
ADD CONSTRAINT weather_data_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 4759 (class 2606 OID 16477)
|
||||
-- Name: weather_objects weather_objects_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.weather_objects
|
||||
ADD CONSTRAINT weather_objects_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 4764 (class 2606 OID 16486)
|
||||
-- Name: weather_data weather_data_station_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.weather_data
|
||||
ADD CONSTRAINT weather_data_station_id_fkey FOREIGN KEY (station_id) REFERENCES public.weather_objects(id);
|
||||
|
||||
|
||||
-- Completed on 2025-04-08 10:16:23
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
Loading…
Reference in New Issue
Block a user