Merge et création page de settings

This commit is contained in:
Charles Mendiburu 2025-04-07 00:10:27 +02:00
parent 37236d9ce3
commit 00c5b9ce53
9 changed files with 473 additions and 367 deletions

View 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());
});
}
}

View File

@ -3,6 +3,8 @@ package com.example.starter;
import io.vertx.core.Vertx; import io.vertx.core.Vertx;
import io.vertx.jdbcclient.JDBCConnectOptions; import io.vertx.jdbcclient.JDBCConnectOptions;
import io.vertx.jdbcclient.JDBCPool; import io.vertx.jdbcclient.JDBCPool;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.auth.jwt.JWTAuthOptions;
import io.vertx.sqlclient.PoolOptions; import io.vertx.sqlclient.PoolOptions;
public class DatabaseService { public class DatabaseService {

View File

@ -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")));
}
}

View File

@ -1,274 +1,71 @@
package com.example.starter; 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.AbstractVerticle;
import io.vertx.core.Promise; import io.vertx.core.Promise;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.web.Router; import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.BodyHandler;
import at.favre.lib.crypto.bcrypt.BCrypt; import io.vertx.ext.web.handler.CorsHandler;
import io.vertx.ext.auth.jwt.JWTAuth; import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.auth.jwt.JWTAuthOptions; import com.example.starter.auth.JwtAuthProvider;
import io.vertx.ext.auth.KeyStoreOptions;
import io.vertx.ext.auth.authentication.TokenCredentials;
import io.vertx.ext.web.handler.JWTAuthHandler; import io.vertx.ext.web.handler.JWTAuthHandler;
public class MainVerticle extends AbstractVerticle { public class MainVerticle extends AbstractVerticle {
private DatabaseService databaseService; private DatabaseService databaseService;
private Router router; // Déclaration du router en variable de classe private Router router;
private JWTAuth jwtAuth; // Déclaration au niveau de la classe
@Override
public void start(Promise<Void> startPromise) throws Exception {
databaseService = new DatabaseService(vertx);
// Initialisation du fournisseur JWT
JWTAuth jwtAuth = JwtAuthProvider.createJwtAuth(vertx);
@Override // Initialisation du routeur
public void start(Promise<Void> startPromise) throws Exception { router = Router.router(vertx);
databaseService = new DatabaseService(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() // Initialisation des handlers de requêtes
.setKeyStore(new KeyStoreOptions() QueryObjects queryObjects = new QueryObjects(databaseService);
.setPath("keystore.jceks") QueryWeatherData queryWeather = new QueryWeatherData(databaseService);
.setPassword("secret"))); 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);
// Initialisation du router // Création du serveur HTTP
router = Router.router(vertx); vertx.createHttpServer()
.requestHandler(router)
// Activation du BodyHandler pour pouvoir lire les requêtes JSON (important pour POST) .listen(8888)
router.route().handler(BodyHandler.create()); .onSuccess(server -> {
System.out.println("HTTP server started on port " + server.actualPort());
// Gestion des CORS startPromise.complete();
QueryObjects queryObjects = new QueryObjects(databaseService); })
QueryWeatherData queryWeather = new QueryWeatherData(databaseService); .onFailure(throwable -> {
SetObjects setObjects = new SetObjects(databaseService); throwable.printStackTrace();
// Create a Router startPromise.fail(throwable);
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;
} }
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());
});
}
}
} }

View File

@ -9,6 +9,7 @@ import Objet from "./pages/Gestion/Objet.jsx";
import AddObject from "./pages/Gestion/AddObject.jsx"; import AddObject from "./pages/Gestion/AddObject.jsx";
import Signup from './pages/Signup.jsx'; import Signup from './pages/Signup.jsx';
import Login from './pages/Login.jsx'; import Login from './pages/Login.jsx';
import Settings from './pages/Settings.jsx';
function App() { function App() {
return ( return (
@ -25,6 +26,7 @@ function App() {
<Route path="/signup" element={<Signup />} /> <Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/ajouterObjet" element={<AddObject />} /> <Route path="/ajouterObjet" element={<AddObject />} />
<Route path="/settings" element={<Settings />} />
</Routes> </Routes>
</div> </div>
</Router> </Router>

View File

@ -1,7 +1,6 @@
import React, { useState, useEffect } from "react"; 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 { Link } from "react-router-dom";
import { useAuth } from "../AuthContext"; import { useAuth } from "../AuthContext";
function Header() { function Header() {
@ -11,7 +10,7 @@ function Header() {
<header className="bg-white shadow-sm"> <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="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div className="flex justify-between items-center"> <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> <nav>
<ul className="flex gap-4"> <ul className="flex gap-4">
<li> <li>
@ -27,6 +26,11 @@ function Header() {
</nav> </nav>
<div className="flex gap-4"> <div className="flex gap-4">
{token ? ( {token ? (
<>
<Link to="/settings" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600">
<Settings size={20} />
<span></span>
</Link>
<button <button
onClick={logout} onClick={logout}
className="flex items-center gap-2 text-gray-600 hover:text-red-600" className="flex items-center gap-2 text-gray-600 hover:text-red-600"
@ -34,6 +38,7 @@ function Header() {
<LogOut size={20} /> <LogOut size={20} />
<span>Déconnexion</span> <span>Déconnexion</span>
</button> </button>
</>
) : ( ) : (
<> <>
<Link to="/login" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600"> <Link to="/login" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600">

View File

@ -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() { function Home() {
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [activeFilter, setActiveFilter] = useState('all'); const [activeFilter, setActiveFilter] = useState('all');
const [name, setName] = useState([]); const [name, setName] = useState([]);
return ( const { token, logout } = useAuth();
<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"> return (
<div className="text-center mb-12"> <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
<h2 className="text-4xl font-bold text-gray-900 mb-4"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
Bienvenue dans ta ville intelligente.</h2> <div className="text-center mb-12">
{token ? ( <h2 className="text-4xl font-bold text-gray-900 mb-4">
<>Home Bienvenue dans ta ville intelligente.</h2>
<img src='public/images/snow.jpg' /> {token ? (
</>):( <><h2>Tu es connecté</h2>
<h2>Non connecté</h2>
)} </>):(
<p className="text-xl text-gray-600 max-w-3xl mx-auto"> <h2>Non connecté</h2>
Découvrez tout ce que votre ville a à offrir - des événements locaux aux horaires de transport, le tout en un seul endroit. )}
<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> </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>
<div className="max-w-3xl mx-auto mb-12"> <div className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow">
<div className="relative"> <div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400" size={24} /> <Calendar className="text-indigo-600" 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>
<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>
{/* Features Grid */} <div className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow">
<div className="grid md:grid-cols-3 gap-8"> <div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
<div className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-shadow"> <Bus className="text-indigo-600" size={24} />
<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> </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>
</div> </div>
); </div>
);
} }
export default Home; export default Home;

View 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;

View File

@ -199,6 +199,8 @@ function Signup() {
S'inscrire S'inscrire
</button> </button>
</div> </div>
{/*Si il a déjà un compte*/}
<div className="mt-4 text-sm text-center"> <div className="mt-4 text-sm text-center">
<p> <p>
Vous avez déjà un compte ? Vous avez déjà un compte ?