incription , connection
This commit is contained in:
BIN
frontend/Design/image.png
Normal file
BIN
frontend/Design/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -1,73 +1,149 @@
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Link, useNavigate, useLocation } from 'react-router-dom'
|
||||
import { loginApi } from '../services/auth'
|
||||
|
||||
export default function Connexion() {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const justRegistered = location.state?.registered === true
|
||||
const [form, setForm] = useState({ email: '', password: '' })
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleChange = (e) => setForm({ ...form, [e.target.name]: e.target.value })
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
// TODO: connecter au backend
|
||||
alert(`Connexion avec ${form.email}`)
|
||||
setError('')
|
||||
setLoading(true)
|
||||
try {
|
||||
const { token, user } = await loginApi(form)
|
||||
localStorage.setItem('token', token)
|
||||
localStorage.setItem('user', JSON.stringify(user))
|
||||
if (user.role === 'ADMIN') navigate('/admin')
|
||||
else if (user.role === 'EMPLOYEE') navigate('/employe')
|
||||
else navigate('/participation')
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-green-50 flex items-center justify-center p-4">
|
||||
<div className="bg-white rounded-2xl shadow-lg w-full max-w-md p-8">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-green-800">Thé Tip Top</h1>
|
||||
<p className="text-gray-500 mt-1">Con
|
||||
|
||||
nexion à votre espace</p>
|
||||
<div className="min-h-screen bg-white flex flex-col justify-center px-8 py-12 max-w-sm mx-auto">
|
||||
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold italic font-serif text-[#006B6B]">Se connecter</h1>
|
||||
<p className="text-gray-500 mt-1 text-sm">Connexion à votre espace</p>
|
||||
</div>
|
||||
|
||||
{justRegistered && (
|
||||
<div className="mb-4 px-4 py-3 rounded-lg bg-green-50 border border-green-200 text-green-700 text-sm">
|
||||
Compte créé avec succès ! Connectez-vous.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 px-4 py-3 rounded-lg bg-red-50 border border-red-200 text-red-600 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-800 mb-1">
|
||||
Adresse email <span className="text-gray-800">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={form.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Votre email"
|
||||
className="w-full border border-gray-200 rounded-lg px-4 py-3 text-sm text-gray-700 placeholder-gray-300 focus:outline-none focus:ring-1 focus:ring-[#006B6B] focus:border-[#006B6B]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email
|
||||
</label>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-800 mb-1">
|
||||
Mot de passe <span className="text-gray-800">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={form.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="exemple@email.com"
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Mot de passe
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
name="password"
|
||||
value={form.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="••••••••"
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
placeholder="Votre mot de passe"
|
||||
className="w-full border border-gray-200 rounded-lg px-4 py-3 pr-12 text-sm text-gray-700 placeholder-gray-300 focus:outline-none focus:ring-1 focus:ring-[#006B6B] focus:border-[#006B6B]"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
aria-label="Afficher/masquer le mot de passe"
|
||||
>
|
||||
<div className="w-6 h-6 rounded-full border-2 border-gray-400 flex items-center justify-center">
|
||||
{showPassword && (
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-right mt-1">
|
||||
<button type="button" className="text-xs text-gray-400 hover:text-[#006B6B] hover:underline">
|
||||
Mot de passe oublié ?
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-green-700 hover:bg-green-800 text-white font-semibold py-2.5 rounded-lg transition-colors"
|
||||
>
|
||||
Se connecter
|
||||
</button>
|
||||
</form>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full bg-[#006B6B] hover:bg-[#005959] disabled:opacity-60 text-white font-medium py-3 rounded-lg transition-colors"
|
||||
>
|
||||
{loading ? 'Connexion...' : 'Se connecter'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="text-center text-sm text-gray-500 mt-6">
|
||||
Pas encore de compte ?{' '}
|
||||
<Link to="/inscription" className="text-green-700 font-medium hover:underline">
|
||||
S'inscrire
|
||||
</Link>
|
||||
</p>
|
||||
<div className="flex items-center gap-3 my-6">
|
||||
<div className="flex-1 h-px bg-gray-200" />
|
||||
<span className="text-sm text-gray-500">Ou</span>
|
||||
<div className="flex-1 h-px bg-gray-200" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center justify-center gap-2 border border-gray-200 rounded-lg py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<div className="w-5 h-5 rounded-full border border-gray-400 flex items-center justify-center text-[10px] font-bold text-gray-500">
|
||||
G
|
||||
</div>
|
||||
Google
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center justify-center gap-2 border border-gray-200 rounded-lg py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<div className="w-5 h-5 rounded-full border border-gray-400 flex items-center justify-center text-[10px] font-bold text-gray-500">
|
||||
f
|
||||
</div>
|
||||
Facebook
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-center text-sm text-gray-500 mt-8">
|
||||
Pas encore de compte ?{' '}
|
||||
<Link to="/inscription" className="text-gray-700 font-medium hover:underline">
|
||||
S'inscrire
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,116 +1,175 @@
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { registerApi } from '../services/auth'
|
||||
|
||||
export default function Inscription() {
|
||||
const navigate = useNavigate()
|
||||
const [form, setForm] = useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
lastName: '',
|
||||
firstName: '',
|
||||
password: '',
|
||||
acceptMarketing: false,
|
||||
})
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value, type, checked } = e.target
|
||||
setForm({ ...form, [name]: type === 'checkbox' ? checked : value })
|
||||
const { name, value } = e.target
|
||||
setForm({ ...form, [name]: value })
|
||||
}
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
// TODO: connecter au backend
|
||||
alert(`Inscription de ${form.firstName} ${form.lastName}`)
|
||||
setError('')
|
||||
setLoading(true)
|
||||
try {
|
||||
await registerApi({
|
||||
email: form.email,
|
||||
firstname: form.firstName,
|
||||
lastname: form.lastName,
|
||||
password: form.password,
|
||||
})
|
||||
navigate('/connexion', { state: { registered: true } })
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-green-50 flex items-center justify-center p-4">
|
||||
<div className="bg-white rounded-2xl shadow-lg w-full max-w-md p-8">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-green-800">Thé Tip Top</h1>
|
||||
<p className="text-gray-500 mt-1">Créer votre compte</p>
|
||||
<div className="min-h-screen bg-white flex flex-col justify-center px-8 py-12 max-w-sm mx-auto">
|
||||
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold italic font-serif text-[#006B6B]">S'inscrire</h1>
|
||||
<p className="text-gray-500 mt-1 text-sm">Créer votre compte</p>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 px-4 py-3 rounded-lg bg-red-50 border border-red-200 text-red-600 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-800 mb-1">
|
||||
Adresse email <span className="text-gray-800">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={form.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Votre email"
|
||||
className="w-full border border-gray-200 rounded-lg px-4 py-3 text-sm text-gray-700 placeholder-gray-300 focus:outline-none focus:ring-1 focus:ring-[#006B6B] focus:border-[#006B6B]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Prénom</label>
|
||||
<input
|
||||
type="text"
|
||||
name="firstName"
|
||||
value={form.firstName}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Jean"
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Nom</label>
|
||||
<input
|
||||
type="text"
|
||||
name="lastName"
|
||||
value={form.lastName}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Dupont"
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-800 mb-1">
|
||||
Nom <span className="text-gray-800">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="lastName"
|
||||
value={form.lastName}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Votre nom"
|
||||
className="w-full border border-gray-200 rounded-lg px-4 py-3 text-sm text-gray-700 placeholder-gray-300 focus:outline-none focus:ring-1 focus:ring-[#006B6B] focus:border-[#006B6B]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={form.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="exemple@email.com"
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-800 mb-1">
|
||||
Prénom <span className="text-gray-800">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="firstName"
|
||||
value={form.firstName}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Votre prénom"
|
||||
className="w-full border border-gray-200 rounded-lg px-4 py-3 text-sm text-gray-700 placeholder-gray-300 focus:outline-none focus:ring-1 focus:ring-[#006B6B] focus:border-[#006B6B]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Mot de passe</label>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-800 mb-1">
|
||||
Mot de passe <span className="text-gray-800">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
name="password"
|
||||
value={form.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="••••••••"
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
placeholder="Votre mot de passe"
|
||||
className="w-full border border-gray-200 rounded-lg px-4 py-3 pr-12 text-sm text-gray-700 placeholder-gray-300 focus:outline-none focus:ring-1 focus:ring-[#006B6B] focus:border-[#006B6B]"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
aria-label="Afficher/masquer le mot de passe"
|
||||
>
|
||||
<div className="w-6 h-6 rounded-full border-2 border-gray-400 flex items-center justify-center">
|
||||
{showPassword && (
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label className="flex items-start gap-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="acceptMarketing"
|
||||
checked={form.acceptMarketing}
|
||||
onChange={handleChange}
|
||||
className="mt-1 accent-green-700"
|
||||
/>
|
||||
<span className="text-sm text-gray-600">
|
||||
J'accepte de recevoir des communications marketing de Thé Tip Top
|
||||
</span>
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full bg-[#006B6B] hover:bg-[#005959] disabled:opacity-60 text-white font-medium py-3 rounded-lg transition-colors"
|
||||
>
|
||||
{loading ? 'Inscription...' : "S'inscrire"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-green-700 hover:bg-green-800 text-white font-semibold py-2.5 rounded-lg transition-colors"
|
||||
>
|
||||
Créer mon compte
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="text-center text-sm text-gray-500 mt-6">
|
||||
Déjà un compte ?{' '}
|
||||
<Link to="/connexion" className="text-green-700 font-medium hover:underline">
|
||||
Se connecter
|
||||
</Link>
|
||||
</p>
|
||||
<div className="flex items-center gap-3 my-6">
|
||||
<div className="flex-1 h-px bg-gray-200" />
|
||||
<span className="text-sm text-gray-500">Ou</span>
|
||||
<div className="flex-1 h-px bg-gray-200" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center justify-center gap-2 border border-gray-200 rounded-lg py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<div className="w-5 h-5 rounded-full border border-gray-400 flex items-center justify-center text-[10px] font-bold text-gray-500">
|
||||
G
|
||||
</div>
|
||||
Google
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center justify-center gap-2 border border-gray-200 rounded-lg py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<div className="w-5 h-5 rounded-full border border-gray-400 flex items-center justify-center text-[10px] font-bold text-gray-500">
|
||||
f
|
||||
</div>
|
||||
Facebook
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-center text-sm text-gray-500 mt-8">
|
||||
Vous avez déjà un compte ?{' '}
|
||||
<Link to="/connexion" className="text-gray-700 font-medium hover:underline">
|
||||
Se connecter
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
23
frontend/src/services/auth.js
Normal file
23
frontend/src/services/auth.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const BASE = '/api/auth'
|
||||
|
||||
export async function loginApi({ email, password }) {
|
||||
const res = await fetch(`${BASE}/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
})
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw new Error(data.message || 'Erreur de connexion.')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function registerApi({ email, firstname, lastname, password }) {
|
||||
const res = await fetch(`${BASE}/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, firstname, lastname, password }),
|
||||
})
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw new Error(data.message || "Erreur lors de l'inscription.")
|
||||
return data
|
||||
}
|
||||
@@ -4,4 +4,9 @@ import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': 'http://localhost:5000',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user