This commit is contained in:
Mathis 2025-04-08 10:19:52 +02:00
commit 7357323695
19 changed files with 968 additions and 196 deletions

BIN
Back-end/keystore.jceks Normal file

Binary file not shown.

BIN
Back-end/keystore.jceks.old Normal file

Binary file not shown.

0
Back-end/mvnw vendored Normal file → Executable file
View File

View File

@ -82,6 +82,18 @@
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency> <!--Dependy pour hacher le mdp-->
<groupId>at.favre.lib</groupId>
<artifactId>bcrypt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-jwt</artifactId>
<version>4.5.13</version>
</dependency>
</dependencies>
<build>

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.jdbcclient.JDBCConnectOptions;
import io.vertx.jdbcclient.JDBCPool;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.auth.jwt.JWTAuthOptions;
import io.vertx.sqlclient.PoolOptions;
public class DatabaseService {
@ -11,7 +13,7 @@ public class DatabaseService {
public DatabaseService(Vertx vertx) {
pool = JDBCPool.pool(vertx,
new JDBCConnectOptions()
.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres?useUnicode=true&amp;amp;characterEncoding=UTF-8") // URL de la base de données
.setJdbcUrl("jdbc:postgresql://localhost:5432/users?useUnicode=true&characterEncoding=UTF-8") //Url de la bdd
.setUser("postgres") // Nom d'utilisateur PostgreSQL
.setPassword("admin"), // Mot de passe PostgreSQL
new PoolOptions()

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,15 +1,26 @@
package com.example.starter;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.CorsHandler;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.CorsHandler;
import io.vertx.ext.auth.jwt.JWTAuth;
import com.example.starter.auth.JwtAuthProvider;
import io.vertx.ext.web.handler.JWTAuthHandler;
public class MainVerticle extends AbstractVerticle {
private DatabaseService databaseService;
private 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 {
@ -35,7 +46,24 @@ public class MainVerticle extends AbstractVerticle {
router.post("/addObject").handler(setObjects::newObject);
router.get("/getRange").handler(queryWeather::getRangeData);
router.post("/modifRangeData").handler(setWeatherData::setRangeData);
// Routes d'authentification
router.post("/signup").handler(authHandler::handleSignup);
router.post("/login").handler(authHandler::handleLogin);
// Protéger toutes les routes commençant par "/api/"
router.route("/api/*").handler(JWTAuthHandler.create(jwtAuth));
// Initialisation des handlers de requêtes
QueryObjects queryObjects = new QueryObjects(databaseService);
QueryWeatherData queryWeather = new QueryWeatherData(databaseService);
SetObjects setObjects = new SetObjects(databaseService);
AuthHandler authHandler = new AuthHandler(databaseService, jwtAuth);
// Création du serveur HTTP
vertx.createHttpServer()
.requestHandler(router)
.listen(8888)
@ -49,3 +77,4 @@ public class MainVerticle extends AbstractVerticle {
});
}
}
}

View File

@ -1975,7 +1975,6 @@
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@ -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 About from "./pages/About.jsx";
import Gestion from "./pages/Gestion/Gestion.jsx";
@ -6,9 +7,13 @@ import Header from "./components/Header.jsx";
import ObjectManagement from "./pages/Gestion/ObjectManagement.jsx";
import Objet from "./pages/Gestion/Objet.jsx";
import AddObject from "./pages/Gestion/AddObject.jsx";
function App() {
import Signup from './pages/Signup.jsx';
import Login from './pages/Login.jsx';
import Settings from './pages/Settings.jsx';
function App() {
return (
<AuthProvider> {/* Enveloppe l'application avec AuthProvider */}
<Router>
<div>
<Header />
@ -18,10 +23,14 @@ function App() {
<Route path="/gestion" element={<Gestion />} />
<Route path="/gestionObjets" element={<ObjectManagement />} />
<Route path="/objet" element={<Objet />} />
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
<Route path="/ajouterObjet" element={<AddObject />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</div>
</Router>
</AuthProvider>
);
}

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

View File

@ -1,66 +1,57 @@
import React, { useState } from "react";
import { LogIn, UserPlus, Menu, X } from "lucide-react";
import React, { useState, useEffect } from "react";
import { LogIn, UserPlus, LogOut, Settings } from "lucide-react";
import { Link } from "react-router-dom";
import { useAuth } from "../AuthContext";
function Header() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const { token, logout } = useAuth();
return (
<header className="bg-white shadow-sm">
<div className=" max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold text-indigo-600">VigiMétéo</h1>
<button
className="sm:hidden text-gray-600 hover:text-indigo-600"
onClick={() => setIsMenuOpen(!isMenuOpen)}
>
{isMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
<nav
className={`${
isMenuOpen ? "block" : "hidden"
} absolute top-16 left-0 w-full bg-white shadow-md sm:static sm:w-auto sm:flex sm:gap-6 sm:shadow-none`}
>
<ul className="flex flex-col sm:flex-row gap-4 sm:gap-6 text-gray-600 p-4 sm:p-0">
<nav>
<ul className="flex gap-4">
<li>
<a href="/" className="hover:text-indigo-600">
Accueil
</a>
<Link to="/" className="text-gray-600 hover:text-indigo-600">Accueil</Link>
</li>
<li>
<a href="/about" className="hover:text-indigo-600">
À propos
</a>
<Link to="/about" className="text-gray-600 hover:text-indigo-600">A propos</Link>
</li>
<li>
<a href="/gestion" className="hover:text-indigo-600">
Gestion
</a>
</li>
<li className="sm:hidden">
<a href="/login" className="hover:text-indigo-600 flex items-center gap-2">
<LogIn size={20} />
Connexion
</a>
</li>
<li className="sm:hidden">
<a href="/signup" className="hover:text-indigo-600 flex items-center gap-2">
<UserPlus size={20} />
Inscription
</a>
<Link to="/gestion" className="text-gray-600 hover:text-indigo-600">Gestion</Link>
</li>
</ul>
</nav>
<div className="hidden sm:flex gap-4">
<button className="flex items-center gap-2 text-gray-600 hover:text-indigo-600">
<div className="flex gap-4">
{token ? (
<>
<Link to="/settings" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600">
<Settings size={20} />
<span></span>
</Link>
<button
onClick={logout}
className="flex items-center gap-2 text-gray-600 hover:text-red-600"
>
<LogOut size={20} />
<span>Déconnexion</span>
</button>
</>
) : (
<>
<Link to="/login" className="flex items-center gap-2 text-gray-600 hover:text-indigo-600">
<LogIn size={20} />
<span>Connexion</span>
</button>
<button className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700">
</Link>
<Link to="/signup" className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700">
<UserPlus size={20} />
<span>Inscription</span>
</button>
</Link>
</>
)}
</div>
</div>
</div>
</header>

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

View File

@ -1,19 +1,26 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { Search, MapPin, Calendar, Bus, ArrowRight, LogIn, UserPlus } from 'lucide-react';
import { useEffect, useState } from 'react';
import axios from 'axios';
import { useAuth } from "../AuthContext";
function Home() {
const [searchQuery, setSearchQuery] = useState('');
const [activeFilter, setActiveFilter] = useState('all');
const [name, setName] = useState([]);
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>
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>
@ -113,3 +120,4 @@ function Home() {
}
export default Home;

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

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/settings", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || "Erreur lors de la modification");
}
alert("Modification réussie !");
// Redirection vers la page d'accueil après une inscription réussie
navigate("/home"); // Remplace "/home" par l'URL de ta page d'accueil
} catch (error) {
alert(error.message);
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="w-96 bg-white rounded-lg shadow-md p-6 mx-auto">
<h2 className="text-2xl font-bold text-gray-800 mb-6 text-center">Settings</h2>
<form onSubmit={handleSubmit} className="space-y-4">
{/* (Formulaire changement Email, Mot de passe) */}
{/* Email */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Modifier votre email:
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Mail className="h-5 w-5 text-gray-400" />
</div>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
required
/>
</div>
</div>
{/* Mot de passe */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Ancien mot de passe:
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
required
minLength="8"
/>
</div>
</div>
{/* nouveau mot de passe */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Nouveau mot de passe:
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input
type="password"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
required
minLength="8"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Confirmer le nouveau mot de passe:
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input
type="password"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
className="pl-10 block w-full rounded-lg border-gray-300 border p-2.5 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
required
minLength="8"
/>
</div>
</div>
{/* Bouton d'inscription */}
<div className="pt-4">
<button
type="submit"
className="w-full flex justify-center py-2.5 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Sauvegarder
</button>
</div>
</form>
</div>
</div>
);
}
export default Settings;

View File

@ -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
View File

@ -0,0 +1,6 @@
{
"name": "Projet-Dev-Web-Ing1",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}