feat: add authentication support and user model

- Updated `env.example` to include NextAuth configuration for authentication.
- Added `next-auth` dependency to manage user sessions.
- Introduced `User` model in Prisma schema with fields for user details and password hashing.
- Integrated `AuthProvider` in layout for session management across the app.
- Enhanced `Header` component with `AuthButton` for user authentication controls.
This commit is contained in:
Julien Froidefond
2025-09-30 21:49:52 +02:00
parent 43c141d3cd
commit 17b86b6087
20 changed files with 1418 additions and 13 deletions

213
src/app/register/page.tsx Normal file
View File

@@ -0,0 +1,213 @@
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import Link from 'next/link'
import { TowerLogo } from '@/components/TowerLogo'
import { TowerBackground } from '@/components/TowerBackground'
export default function RegisterPage() {
const [email, setEmail] = useState('')
const [name, setName] = useState('')
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [error, setError] = useState('')
const [isLoading, setIsLoading] = useState(false)
const router = useRouter()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
setError('')
// Validation côté client
if (password !== confirmPassword) {
setError('Les mots de passe ne correspondent pas')
setIsLoading(false)
return
}
if (password.length < 6) {
setError('Le mot de passe doit contenir au moins 6 caractères')
setIsLoading(false)
return
}
try {
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
name,
firstName,
lastName,
password,
}),
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Une erreur est survenue')
}
// Rediriger vers la page de login avec un message de succès
router.push('/login?message=Compte créé avec succès')
} catch (error) {
setError(error instanceof Error ? error.message : 'Une erreur est survenue')
} finally {
setIsLoading(false)
}
}
return (
<div className="min-h-screen relative overflow-hidden">
<TowerBackground />
{/* Contenu principal */}
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
<div className="w-full max-w-md">
{/* Logo et titre */}
<TowerLogo size="md" className="mb-8" />
{/* Formulaire */}
<div className="bg-[var(--card)]/80 backdrop-blur-sm rounded-2xl shadow-xl border border-[var(--border)] p-8">
<h2 className="text-2xl font-mono font-bold text-[var(--foreground)] text-center mb-6">
Créer un compte
</h2>
<form className="space-y-6" onSubmit={handleSubmit}>
<div className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-[var(--foreground)] mb-2">
Email
</label>
<Input
id="email"
name="email"
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="votre@email.com"
className="w-full"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label htmlFor="firstName" className="block text-sm font-medium text-[var(--foreground)] mb-2">
Prénom
</label>
<Input
id="firstName"
name="firstName"
type="text"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
placeholder="Prénom"
className="w-full"
/>
</div>
<div>
<label htmlFor="lastName" className="block text-sm font-medium text-[var(--foreground)] mb-2">
Nom
</label>
<Input
id="lastName"
name="lastName"
type="text"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
placeholder="Nom"
className="w-full"
/>
</div>
</div>
<div>
<label htmlFor="name" className="block text-sm font-medium text-[var(--foreground)] mb-2">
Nom d&apos;affichage (optionnel)
</label>
<Input
id="name"
name="name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Nom d&apos;affichage personnalisé"
className="w-full"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-[var(--foreground)] mb-2">
Mot de passe
</label>
<Input
id="password"
name="password"
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Minimum 6 caractères"
className="w-full"
/>
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-[var(--foreground)] mb-2">
Confirmer le mot de passe
</label>
<Input
id="confirmPassword"
name="confirmPassword"
type="password"
required
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="Répétez le mot de passe"
className="w-full"
/>
</div>
</div>
{error && (
<div className="text-[var(--destructive)] text-sm text-center bg-[var(--destructive)]/10 p-3 rounded-lg border border-[var(--destructive)]/20">
{error}
</div>
)}
<div>
<Button
type="submit"
disabled={isLoading}
className="w-full py-3 text-lg font-mono"
>
{isLoading ? 'Création du compte...' : 'Créer le compte'}
</Button>
</div>
<div className="text-center text-sm text-[var(--muted-foreground)]">
<p>
Déjà un compte ?{' '}
<Link href="/login" className="text-[var(--primary)] hover:underline font-medium">
Se connecter
</Link>
</p>
</div>
</form>
</div>
</div>
</div>
</div>
)
}