Alexis Bruneteau 546785ef67 feat: Integrate backend API with frontend - Authentication & Elections
Core Integration:
- Create API client with TypeScript types for all endpoints
- Implement authentication context provider for user state management
- Add protected route component for dashboard access control
- Connect login/register pages to backend authentication endpoints
- Implement user session persistence with localStorage tokens

Authentication:
- Login page now connects to /api/auth/login endpoint
- Register page connects to /api/auth/register with validation
- Password strength requirements (min 8 chars)
- Form validation and error handling
- Automatic redirect to dashboard on successful auth
- Logout functionality with session cleanup

Protected Routes:
- Dashboard pages require authentication
- Non-authenticated users redirected to login
- Loading spinner during auth verification
- User name displayed in dashboard header
- Proper session management

Election/Vote APIs:
- Dashboard fetches active elections from /api/elections/active
- Display real election data with candidates count
- Handle loading and error states
- Skeleton loaders for better UX

Type Safety:
- Full TypeScript interfaces for all API responses
- Proper error handling with try-catch blocks
- API response types: AuthToken, VoterProfile, Election, Candidate, Vote, VoteHistory

Environment:
- API URL configurable via NEXT_PUBLIC_API_URL env variable
- Default to http://localhost:8000 for local development

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 17:15:34 +01:00

221 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import Link from "next/link"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Mail, Lock, AlertCircle, CheckCircle } from "lucide-react"
import { useAuth } from "@/lib/auth-context"
export default function RegisterPage() {
const router = useRouter()
const { register, isLoading } = useAuth()
const [formData, setFormData] = useState({
firstName: "",
lastName: "",
email: "",
password: "",
passwordConfirm: "",
})
const [error, setError] = useState("")
const [success, setSuccess] = useState(false)
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target
setFormData(prev => ({ ...prev, [name]: value }))
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError("")
setSuccess(false)
if (!formData.firstName || !formData.lastName || !formData.email || !formData.password) {
setError("Tous les champs sont requis")
return
}
if (formData.password !== formData.passwordConfirm) {
setError("Les mots de passe ne correspondent pas")
return
}
if (formData.password.length < 8) {
setError("Le mot de passe doit contenir au moins 8 caractères")
return
}
try {
await register(formData.email, formData.password, formData.firstName, formData.lastName)
setSuccess(true)
setTimeout(() => {
router.push("/dashboard")
}, 500)
} catch (err) {
setError("Une erreur s'est produite lors de l'inscription")
}
}
return (
<div className="min-h-screen grid grid-cols-1 md:grid-cols-2">
{/* Left side - Illustration */}
<div className="hidden md:flex items-center justify-center p-8 bg-gradient-to-br from-card to-background">
<div className="text-center space-y-8 max-w-sm">
<div className="text-7xl">🗳</div>
<div className="space-y-4">
<h2 className="text-3xl font-bold">Rejoignez-nous</h2>
<p className="text-muted-foreground leading-relaxed">
Créez un compte pour participer à des élections sécurisées et transparentes
</p>
</div>
<div className="space-y-4 pt-4">
<div className="flex items-center gap-4 text-left">
<CheckCircle className="w-6 h-6 text-accent flex-shrink-0" />
<p className="font-semibold text-foreground">Inscription gratuite</p>
</div>
<div className="flex items-center gap-4 text-left">
<CheckCircle className="w-6 h-6 text-accent flex-shrink-0" />
<p className="font-semibold text-foreground">Sécurité maximale</p>
</div>
<div className="flex items-center gap-4 text-left">
<CheckCircle className="w-6 h-6 text-accent flex-shrink-0" />
<p className="font-semibold text-foreground">Aucune données</p>
</div>
</div>
</div>
</div>
{/* Right side - Form */}
<div className="flex items-center justify-center p-4 md:p-8 bg-background">
<div className="w-full max-w-sm space-y-8">
<div className="space-y-2 text-center">
<h1 className="text-3xl font-bold">S'inscrire</h1>
<p className="text-muted-foreground">Créez votre compte E-Voting</p>
</div>
<Card>
<CardContent className="pt-6">
{error && (
<div className="mb-4 p-4 rounded-md bg-destructive/10 border border-destructive/50 flex gap-3">
<AlertCircle className="w-5 h-5 text-destructive flex-shrink-0 mt-0.5" />
<div>
<p className="text-sm font-medium text-destructive">Erreur</p>
<p className="text-sm text-destructive/80">{error}</p>
</div>
</div>
)}
{success && (
<div className="mb-4 p-4 rounded-md bg-accent/10 border border-accent/50 flex gap-3">
<CheckCircle className="w-5 h-5 text-accent flex-shrink-0 mt-0.5" />
<div>
<p className="text-sm font-medium text-accent">Succès</p>
<p className="text-sm text-accent/80">Votre compte a été créé avec succès</p>
</div>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="firstName">Prénom</Label>
<Input
id="firstName"
name="firstName"
placeholder="Jean"
value={formData.firstName}
onChange={handleChange}
required
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="lastName">Nom</Label>
<Input
id="lastName"
name="lastName"
placeholder="Dupont"
value={formData.lastName}
onChange={handleChange}
required
disabled={isLoading}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<div className="relative">
<Mail className="absolute left-3 top-3 w-5 h-5 text-muted-foreground" />
<Input
id="email"
name="email"
type="email"
placeholder="votre@email.com"
value={formData.email}
onChange={handleChange}
required
disabled={isLoading}
className="pl-10"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password">Mot de passe</Label>
<div className="relative">
<Lock className="absolute left-3 top-3 w-5 h-5 text-muted-foreground" />
<Input
id="password"
name="password"
type="password"
placeholder="••••••••"
value={formData.password}
onChange={handleChange}
required
disabled={isLoading}
className="pl-10"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="passwordConfirm">Confirmer le mot de passe</Label>
<div className="relative">
<Lock className="absolute left-3 top-3 w-5 h-5 text-muted-foreground" />
<Input
id="passwordConfirm"
name="passwordConfirm"
type="password"
placeholder="••••••••"
value={formData.passwordConfirm}
onChange={handleChange}
required
disabled={isLoading}
className="pl-10"
/>
</div>
</div>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? "Inscription en cours..." : "S'inscrire"}
</Button>
</form>
<p className="text-center text-sm text-muted-foreground mt-6">
Déjà un compte?{" "}
<Link href="/auth/login" className="text-accent font-medium hover:underline">
Se connecter
</Link>
</p>
</CardContent>
</Card>
</div>
</div>
</div>
)
}