Merge et création page de settings
This commit is contained in:
parent
37236d9ce3
commit
00c5b9ce53
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 {
|
||||
|
||||
@ -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,274 +1,71 @@
|
||||
package com.example.starter;
|
||||
|
||||
import io.vertx.ext.web.handler.BodyHandler;
|
||||
import io.vertx.ext.web.handler.BodyHandler;
|
||||
import io.vertx.ext.web.handler.CorsHandler;
|
||||
import io.vertx.core.http.HttpMethod;
|
||||
import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
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.RoutingContext;
|
||||
import at.favre.lib.crypto.bcrypt.BCrypt;
|
||||
import io.vertx.ext.web.handler.BodyHandler;
|
||||
import io.vertx.ext.web.handler.CorsHandler;
|
||||
import io.vertx.ext.auth.jwt.JWTAuth;
|
||||
import io.vertx.ext.auth.jwt.JWTAuthOptions;
|
||||
import io.vertx.ext.auth.KeyStoreOptions;
|
||||
import io.vertx.ext.auth.authentication.TokenCredentials;
|
||||
import com.example.starter.auth.JwtAuthProvider;
|
||||
import io.vertx.ext.web.handler.JWTAuthHandler;
|
||||
|
||||
|
||||
public class MainVerticle extends AbstractVerticle {
|
||||
private DatabaseService databaseService;
|
||||
private Router router; // Déclaration du router en variable de classe
|
||||
private JWTAuth jwtAuth; // Déclaration au niveau de la classe
|
||||
private DatabaseService databaseService;
|
||||
private Router router;
|
||||
|
||||
@Override
|
||||
public void start(Promise<Void> startPromise) throws Exception {
|
||||
databaseService = new DatabaseService(vertx);
|
||||
|
||||
// Initialisation du fournisseur JWT
|
||||
JWTAuth jwtAuth = JwtAuthProvider.createJwtAuth(vertx);
|
||||
|
||||
|
||||
@Override
|
||||
public void start(Promise<Void> startPromise) throws Exception {
|
||||
databaseService = new DatabaseService(vertx);
|
||||
// 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));
|
||||
|
||||
|
||||
this.jwtAuth = JWTAuth.create(vertx, new JWTAuthOptions()
|
||||
.setKeyStore(new KeyStoreOptions()
|
||||
.setPath("keystore.jceks")
|
||||
.setPassword("secret")));
|
||||
|
||||
|
||||
|
||||
// Initialisation du router
|
||||
router = Router.router(vertx);
|
||||
|
||||
// Activation du BodyHandler pour pouvoir lire les requêtes JSON (important pour POST)
|
||||
router.route().handler(BodyHandler.create());
|
||||
|
||||
// Gestion des CORS
|
||||
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("*")
|
||||
.allowedMethod(HttpMethod.GET)
|
||||
.allowedMethod(HttpMethod.POST)
|
||||
.allowedHeader("Content-Type")
|
||||
.allowedHeader("Authorization"));
|
||||
|
||||
// Déclaration des routes
|
||||
router.get("/objets").handler(this::getObjects);
|
||||
router.get("/objet").handler(this::getParticularObject);
|
||||
router.post("/signup").handler(this::handleSignup); // Route pour l'inscription
|
||||
router.post("/login").handler(this::handleLogin); // Route pour la connexion
|
||||
// Protéger toutes les routes commençant par "/api/"
|
||||
router.route("/api/*").handler(JWTAuthHandler.create(jwtAuth));
|
||||
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);
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Récupération des objets
|
||||
private void getObjects(RoutingContext context) {
|
||||
databaseService.pool
|
||||
.query("SELECT * FROM weather_objects;")
|
||||
.execute()
|
||||
.onFailure(e -> {
|
||||
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
|
||||
context.response()
|
||||
.setStatusCode(500)
|
||||
.end(new JsonObject().put("error", "Erreur de récupération de la BDD").encode());
|
||||
})
|
||||
.onSuccess(rows -> {
|
||||
context.response()
|
||||
.putHeader("content-type", "application/json; charset=UTF-8")
|
||||
.end(getInfosObjects(rows).encode());
|
||||
});
|
||||
}
|
||||
|
||||
// Récupération d'un objet spécifique
|
||||
private void getParticularObject(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;
|
||||
|
||||
// Initialisation des handlers de requêtes
|
||||
QueryObjects queryObjects = new QueryObjects(databaseService);
|
||||
QueryWeatherData queryWeather = new QueryWeatherData(databaseService);
|
||||
SetObjects setObjects = new SetObjects(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);
|
||||
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
databaseService.pool
|
||||
.preparedQuery("SELECT * FROM weather_objects WHERE id=?")
|
||||
.execute(Tuple.of(Integer.parseInt(id)))
|
||||
.onFailure(e -> {
|
||||
System.err.println("Erreur de récupération de la BDD :" + e.getMessage());
|
||||
context.response()
|
||||
.setStatusCode(500)
|
||||
.end(new JsonObject().put("Erreur", "Erreur de récupération de la BDD").encode());
|
||||
})
|
||||
.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(getInfosObjects(rows).encode());
|
||||
});
|
||||
}
|
||||
|
||||
// Convertit les résultats SQL en JSON
|
||||
private JsonArray getInfosObjects(RowSet<Row> rows) {
|
||||
JsonArray objects = new JsonArray();
|
||||
for (Row row : rows) {
|
||||
JsonObject object = new JsonObject()
|
||||
.put("id", row.getInteger("id"))
|
||||
.put("name", row.getString("name"))
|
||||
.put("description", row.getString("description"))
|
||||
.put("type", row.getString("type"))
|
||||
.put("location", row.getString("location"));
|
||||
objects.add(object);
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
// Log pour vérifier le corps de la requête
|
||||
System.out.println("Received body: " + body.encodePrettily());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Hashage du mot de passe avec BCrypt
|
||||
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());
|
||||
vertx.setTimer(2000, id -> {
|
||||
context.response()
|
||||
.putHeader("Location", "/") // Redirection vers la page d'accueil
|
||||
.setStatusCode(303)
|
||||
.end();
|
||||
});
|
||||
})
|
||||
.onFailure(err -> {
|
||||
// Log pour afficher l'erreur d'insertion
|
||||
System.err.println("Erreur d'inscription : " + err.getMessage());
|
||||
context.response()
|
||||
.setStatusCode(500)
|
||||
.end(new JsonObject().put("error", "Erreur d'inscription").encode());
|
||||
});
|
||||
}
|
||||
|
||||
//Méthode de ocnnexion
|
||||
private 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;
|
||||
}
|
||||
|
||||
System.out.println("Received login request: " + body.encodePrettily());
|
||||
|
||||
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 = ?") // Requête sql qui evite les injections
|
||||
.execute(Tuple.of(email)) // Remplace le ? par la valeur de l'email
|
||||
.onSuccess(result -> { //Si la requête s'exécute bien , on obtient le resultat dans result
|
||||
if (result.rowCount() == 0) { // Cas : Aucun utilisateur trouvé
|
||||
context.response()
|
||||
.setStatusCode(401)
|
||||
.end(new JsonObject().put("error", "Email ou mot de passe incorrect").encode());
|
||||
return;
|
||||
}
|
||||
|
||||
Row row = result.iterator().next();
|
||||
String storedHashedPassword = row.getString("password");
|
||||
|
||||
// Vérification du mot de passe avec BCrypt
|
||||
BCrypt.Result verification = BCrypt.verifyer().verify(password.toCharArray(), storedHashedPassword);
|
||||
|
||||
if (verification.verified) {
|
||||
System.out.println("Connexion réussi");
|
||||
//Génération du token JWT
|
||||
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());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -9,6 +9,7 @@ import Objet from "./pages/Gestion/Objet.jsx";
|
||||
import AddObject from "./pages/Gestion/AddObject.jsx";
|
||||
import Signup from './pages/Signup.jsx';
|
||||
import Login from './pages/Login.jsx';
|
||||
import Settings from './pages/Settings.jsx';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@ -25,6 +26,7 @@ function App() {
|
||||
<Route path="/signup" element={<Signup />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/ajouterObjet" element={<AddObject />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { LogIn, UserPlus, LogOut } from "lucide-react";
|
||||
import { LogIn, UserPlus, LogOut, Settings } from "lucide-react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { useAuth } from "../AuthContext";
|
||||
|
||||
function Header() {
|
||||
@ -11,7 +10,7 @@ function Header() {
|
||||
<header className="bg-white shadow-sm">
|
||||
<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">Hopital de Pau</h1>
|
||||
<h1 className="text-2xl font-bold text-indigo-600">VigiMétéo</h1>
|
||||
<nav>
|
||||
<ul className="flex gap-4">
|
||||
<li>
|
||||
@ -27,6 +26,11 @@ function Header() {
|
||||
</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"
|
||||
@ -34,6 +38,7 @@ function Header() {
|
||||
<LogOut size={20} />
|
||||
<span>Déconnexion</span>
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Link to="/login" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600">
|
||||
|
||||
@ -1,117 +1,123 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Search, MapPin, Calendar, Bus, ArrowRight, LogIn, UserPlus } from 'lucide-react';
|
||||
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>
|
||||
{token ? (
|
||||
<>Home
|
||||
<img src='public/images/snow.jpg' />
|
||||
</>):(
|
||||
<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.
|
||||
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;
|
||||
|
||||
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/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">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;
|
||||
@ -199,6 +199,8 @@ function Signup() {
|
||||
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 ?
|
||||
|
||||
Loading…
Reference in New Issue
Block a user