Merge branch 'main' of https://github.com/Charles40130/Projet-Dev-Web-Ing1
This commit is contained in:
commit
7357323695
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>
|
<version>${junit-jupiter.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<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.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 {
|
||||||
@ -11,7 +13,7 @@ public class DatabaseService {
|
|||||||
public DatabaseService(Vertx vertx) {
|
public DatabaseService(Vertx vertx) {
|
||||||
pool = JDBCPool.pool(vertx,
|
pool = JDBCPool.pool(vertx,
|
||||||
new JDBCConnectOptions()
|
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
|
.setUser("postgres") // Nom d'utilisateur PostgreSQL
|
||||||
.setPassword("admin"), // Mot de passe PostgreSQL
|
.setPassword("admin"), // Mot de passe PostgreSQL
|
||||||
new PoolOptions()
|
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,15 +1,26 @@
|
|||||||
package com.example.starter;
|
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.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.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 {
|
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);
|
||||||
|
|
||||||
|
// Initialisation du fournisseur JWT
|
||||||
|
JWTAuth jwtAuth = JwtAuthProvider.createJwtAuth(vertx);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Promise<Void> startPromise) throws Exception {
|
public void start(Promise<Void> startPromise) throws Exception {
|
||||||
@ -35,17 +46,35 @@ public class MainVerticle extends AbstractVerticle {
|
|||||||
router.post("/addObject").handler(setObjects::newObject);
|
router.post("/addObject").handler(setObjects::newObject);
|
||||||
router.get("/getRange").handler(queryWeather::getRangeData);
|
router.get("/getRange").handler(queryWeather::getRangeData);
|
||||||
router.post("/modifRangeData").handler(setWeatherData::setRangeData);
|
router.post("/modifRangeData").handler(setWeatherData::setRangeData);
|
||||||
|
// Routes d'authentification
|
||||||
|
router.post("/signup").handler(authHandler::handleSignup);
|
||||||
|
router.post("/login").handler(authHandler::handleLogin);
|
||||||
|
|
||||||
vertx.createHttpServer()
|
|
||||||
.requestHandler(router)
|
// Protéger toutes les routes commençant par "/api/"
|
||||||
.listen(8888)
|
router.route("/api/*").handler(JWTAuthHandler.create(jwtAuth));
|
||||||
.onSuccess(server -> {
|
|
||||||
System.out.println("HTTP server started on port " + server.actualPort());
|
|
||||||
startPromise.complete();
|
// Initialisation des handlers de requêtes
|
||||||
})
|
QueryObjects queryObjects = new QueryObjects(databaseService);
|
||||||
.onFailure(throwable -> {
|
QueryWeatherData queryWeather = new QueryWeatherData(databaseService);
|
||||||
throwable.printStackTrace();
|
SetObjects setObjects = new SetObjects(databaseService);
|
||||||
startPromise.fail(throwable);
|
AuthHandler authHandler = new AuthHandler(databaseService, jwtAuth);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
1
Front-end/package-lock.json
generated
1
Front-end/package-lock.json
generated
@ -1975,7 +1975,6 @@
|
|||||||
"version": "1.8.4",
|
"version": "1.8.4",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||||
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
|
|||||||
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 { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||||
|
import { AuthProvider } from './AuthContext.jsx';
|
||||||
import Home from "./pages/Home.jsx";
|
import Home from "./pages/Home.jsx";
|
||||||
import About from "./pages/About.jsx";
|
import About from "./pages/About.jsx";
|
||||||
import Gestion from "./pages/Gestion/Gestion.jsx";
|
import Gestion from "./pages/Gestion/Gestion.jsx";
|
||||||
@ -6,22 +7,30 @@ import Header from "./components/Header.jsx";
|
|||||||
import ObjectManagement from "./pages/Gestion/ObjectManagement.jsx";
|
import ObjectManagement from "./pages/Gestion/ObjectManagement.jsx";
|
||||||
import Objet from "./pages/Gestion/Objet.jsx";
|
import Objet from "./pages/Gestion/Objet.jsx";
|
||||||
import AddObject from "./pages/Gestion/AddObject.jsx";
|
import AddObject from "./pages/Gestion/AddObject.jsx";
|
||||||
function App() {
|
import Signup from './pages/Signup.jsx';
|
||||||
|
import Login from './pages/Login.jsx';
|
||||||
|
import Settings from './pages/Settings.jsx';
|
||||||
|
|
||||||
|
function App() {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<AuthProvider> {/* Enveloppe l'application avec AuthProvider */}
|
||||||
<div>
|
<Router>
|
||||||
<Header />
|
<div>
|
||||||
<Routes>
|
<Header />
|
||||||
<Route path="/" element={<Home />} />
|
<Routes>
|
||||||
<Route path="/about" element={<About />} />
|
<Route path="/" element={<Home />} />
|
||||||
<Route path="/gestion" element={<Gestion />} />
|
<Route path="/about" element={<About />} />
|
||||||
<Route path="/gestionObjets" element={<ObjectManagement />} />
|
<Route path="/gestion" element={<Gestion />} />
|
||||||
<Route path="/objet" element={<Objet />} />
|
<Route path="/gestionObjets" element={<ObjectManagement />} />
|
||||||
<Route path="/ajouterObjet" element={<AddObject />} />
|
<Route path="/objet" element={<Objet />} />
|
||||||
</Routes>
|
<Route path="/signup" element={<Signup />} />
|
||||||
</div>
|
<Route path="/login" element={<Login />} />
|
||||||
</Router>
|
<Route path="/ajouterObjet" element={<AddObject />} />
|
||||||
|
<Route path="/settings" element={<Settings />} />
|
||||||
|
</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,66 +1,57 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { LogIn, UserPlus, Menu, X } from "lucide-react";
|
import { LogIn, UserPlus, LogOut, Settings } from "lucide-react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { useAuth } from "../AuthContext";
|
||||||
|
|
||||||
function Header() {
|
function Header() {
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const { token, logout } = useAuth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="bg-white shadow-sm">
|
<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">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||||
<h1 className="text-2xl font-bold text-indigo-600">VigiMétéo</h1>
|
<div className="flex justify-between items-center">
|
||||||
|
<h1 className="text-2xl font-bold text-indigo-600">VigiMétéo</h1>
|
||||||
<button
|
<nav>
|
||||||
className="sm:hidden text-gray-600 hover:text-indigo-600"
|
<ul className="flex gap-4">
|
||||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
<li>
|
||||||
>
|
<Link to="/" className="text-gray-600 hover:text-indigo-600">Accueil</Link>
|
||||||
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
</li>
|
||||||
</button>
|
<li>
|
||||||
|
<Link to="/about" className="text-gray-600 hover:text-indigo-600">A propos</Link>
|
||||||
<nav
|
</li>
|
||||||
className={`${
|
<li>
|
||||||
isMenuOpen ? "block" : "hidden"
|
<Link to="/gestion" className="text-gray-600 hover:text-indigo-600">Gestion</Link>
|
||||||
} absolute top-16 left-0 w-full bg-white shadow-md sm:static sm:w-auto sm:flex sm:gap-6 sm:shadow-none`}
|
</li>
|
||||||
>
|
</ul>
|
||||||
<ul className="flex flex-col sm:flex-row gap-4 sm:gap-6 text-gray-600 p-4 sm:p-0">
|
</nav>
|
||||||
<li>
|
<div className="flex gap-4">
|
||||||
<a href="/" className="hover:text-indigo-600">
|
{token ? (
|
||||||
Accueil
|
<>
|
||||||
</a>
|
<Link to="/settings" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600">
|
||||||
</li>
|
<Settings size={20} />
|
||||||
<li>
|
<span></span>
|
||||||
<a href="/about" className="hover:text-indigo-600">
|
</Link>
|
||||||
À propos
|
<button
|
||||||
</a>
|
onClick={logout}
|
||||||
</li>
|
className="flex items-center gap-2 text-gray-600 hover:text-red-600"
|
||||||
<li>
|
>
|
||||||
<a href="/gestion" className="hover:text-indigo-600">
|
<LogOut size={20} />
|
||||||
Gestion
|
<span>Déconnexion</span>
|
||||||
</a>
|
</button>
|
||||||
</li>
|
</>
|
||||||
<li className="sm:hidden">
|
) : (
|
||||||
<a href="/login" className="hover:text-indigo-600 flex items-center gap-2">
|
<>
|
||||||
<LogIn size={20} />
|
<Link to="/login" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600">
|
||||||
Connexion
|
<LogIn size={20} />
|
||||||
</a>
|
<span>Connexion</span>
|
||||||
</li>
|
</Link>
|
||||||
<li className="sm:hidden">
|
<Link to="/signup" className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700">
|
||||||
<a href="/signup" className="hover:text-indigo-600 flex items-center gap-2">
|
<UserPlus size={20} />
|
||||||
<UserPlus size={20} />
|
<span>Inscription</span>
|
||||||
Inscription
|
</Link>
|
||||||
</a>
|
</>
|
||||||
</li>
|
)}
|
||||||
</ul>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
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,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 { Search, MapPin, Calendar, Bus, ArrowRight, LogIn, UserPlus } from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useAuth } from "../AuthContext";
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
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.
|
<div className="text-center mb-12">
|
||||||
</h2>
|
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
Bienvenue dans ta ville intelligente.</h2>
|
||||||
Découvrez tout ce que votre ville a à offrir - des événements locaux aux horaires de transport, le tout en un seul endroit.
|
{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>
|
</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;
|
||||||
|
|
||||||
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;
|
||||||
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "Projet-Dev-Web-Ing1",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user