Alexis Bruneteau 14eff8d0da feat: Rebuild frontend with Next.js and shadcn/ui components
- Migrate from React CRA to Next.js 15 with modern architecture
- Implement comprehensive shadcn/ui component library
- Create complete dashboard system with layouts and navigation
- Build authentication pages (login, register) with proper forms
- Implement vote management pages (active, upcoming, history, archives)
- Add user profile management with security settings
- Configure Tailwind CSS with custom dark theme (accent: #e8704b)
- Setup TypeScript with strict type checking
- Backup old React-based frontend to .backups/frontend-old
- All pages compile successfully and build passes linting

Pages created:
- Home page with hero section and features
- Authentication (login/register)
- Dashboard with stats and vote cards
- Vote management (active, upcoming, history, archives)
- User profile with form validation

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

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

277 lines
9.0 KiB
JavaScript

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { User, Mail, Lock, LogOut, ArrowLeft, Eye, EyeOff } from 'lucide-react';
import Alert from '../components/Alert';
import './ProfilePage.css';
export default function ProfilePage({ voter, onLogout }) {
const navigate = useNavigate();
const [showPassword, setShowPassword] = useState(false);
const [showNewPassword, setShowNewPassword] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
nom: voter?.nom || '',
email: voter?.email || '',
currentPassword: '',
newPassword: '',
confirmPassword: '',
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
const handleUpdateProfile = async (e) => {
e.preventDefault();
setError('');
setSuccess('');
setLoading(true);
try {
const token = localStorage.getItem('token');
const response = await fetch('http://localhost:8000/auth/profile', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
nom: formData.nom,
email: formData.email,
}),
});
if (!response.ok) throw new Error('Erreur lors de la mise à jour');
const data = await response.json();
localStorage.setItem('voter', JSON.stringify(data.voter));
setSuccess('Profil mis à jour avec succès');
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const handleChangePassword = async (e) => {
e.preventDefault();
setError('');
setSuccess('');
if (formData.newPassword !== formData.confirmPassword) {
setError('Les mots de passe ne correspondent pas');
return;
}
if (formData.newPassword.length < 8) {
setError('Le mot de passe doit contenir au moins 8 caractères');
return;
}
setLoading(true);
try {
const token = localStorage.getItem('token');
const response = await fetch('http://localhost:8000/auth/change-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
current_password: formData.currentPassword,
new_password: formData.newPassword,
}),
});
if (!response.ok) throw new Error('Mot de passe actuel incorrect');
setSuccess('Mot de passe changé avec succès');
setFormData(prev => ({
...prev,
currentPassword: '',
newPassword: '',
confirmPassword: '',
}));
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const handleLogout = () => {
localStorage.removeItem('voter');
localStorage.removeItem('token');
onLogout();
navigate('/');
};
return (
<div className="profile-page">
<div className="container">
<button
onClick={() => navigate('/dashboard')}
className="back-button"
>
<ArrowLeft size={20} />
Retour au dashboard
</button>
<div className="profile-container">
<div className="profile-header">
<div className="profile-avatar">
{voter?.nom?.charAt(0).toUpperCase()}
</div>
<div className="profile-info">
<h1>{voter?.nom}</h1>
<p>{voter?.email}</p>
</div>
</div>
<div className="profile-sections">
{/* General Information */}
<div className="profile-section">
<h2>Informations Générales</h2>
{error && (
<Alert type="error" message={error} onClose={() => setError('')} />
)}
{success && (
<Alert type="success" message={success} onClose={() => setSuccess('')} />
)}
<form onSubmit={handleUpdateProfile} className="profile-form">
<div className="form-group">
<label htmlFor="nom">Nom complet</label>
<div className="input-wrapper">
<User size={20} className="input-icon" />
<input
id="nom"
type="text"
name="nom"
value={formData.nom}
onChange={handleChange}
disabled={loading}
/>
</div>
</div>
<div className="form-group">
<label htmlFor="email">Email</label>
<div className="input-wrapper">
<Mail size={20} className="input-icon" />
<input
id="email"
type="email"
name="email"
value={formData.email}
onChange={handleChange}
disabled={loading}
/>
</div>
</div>
<button type="submit" className="btn btn-primary" disabled={loading}>
{loading ? 'Sauvegarde...' : 'Sauvegarder'}
</button>
</form>
</div>
{/* Security Section */}
<div className="profile-section">
<h2>Sécurité</h2>
<form onSubmit={handleChangePassword} className="profile-form">
<div className="form-group">
<label htmlFor="currentPassword">Mot de passe actuel</label>
<div className="input-wrapper">
<Lock size={20} className="input-icon" />
<input
id="currentPassword"
type={showPassword ? 'text' : 'password'}
name="currentPassword"
placeholder="••••••••"
value={formData.currentPassword}
onChange={handleChange}
disabled={loading}
/>
<button
type="button"
className="password-toggle"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOff size={20} /> : <Eye size={20} />}
</button>
</div>
</div>
<div className="form-group">
<label htmlFor="newPassword">Nouveau mot de passe</label>
<div className="input-wrapper">
<Lock size={20} className="input-icon" />
<input
id="newPassword"
type={showNewPassword ? 'text' : 'password'}
name="newPassword"
placeholder="Minimum 8 caractères"
value={formData.newPassword}
onChange={handleChange}
disabled={loading}
/>
<button
type="button"
className="password-toggle"
onClick={() => setShowNewPassword(!showNewPassword)}
>
{showNewPassword ? <EyeOff size={20} /> : <Eye size={20} />}
</button>
</div>
</div>
<div className="form-group">
<label htmlFor="confirmPassword">Confirmer le mot de passe</label>
<div className="input-wrapper">
<Lock size={20} className="input-icon" />
<input
id="confirmPassword"
type="password"
name="confirmPassword"
placeholder="••••••••"
value={formData.confirmPassword}
onChange={handleChange}
disabled={loading}
/>
</div>
</div>
<button type="submit" className="btn btn-primary" disabled={loading}>
{loading ? 'Mise à jour...' : 'Changer le mot de passe'}
</button>
</form>
</div>
{/* Danger Zone */}
<div className="profile-section danger-zone">
<h2>Zone Dangereuse</h2>
<p>Attention: Ces actions sont irréversibles.</p>
<button
onClick={handleLogout}
className="btn btn-danger"
>
<LogOut size={20} />
Déconnexion
</button>
</div>
</div>
</div>
</div>
</div>
);
}