- 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>
195 lines
7.3 KiB
JavaScript
195 lines
7.3 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { BarChart3, Clock, CheckCircle, AlertCircle } from 'lucide-react';
|
|
import VoteCard from '../components/VoteCard';
|
|
import ElectionDetailsModal from '../components/ElectionDetailsModal';
|
|
import LoadingSpinner from '../components/LoadingSpinner';
|
|
import './DashboardPage.css';
|
|
|
|
export default function DashboardPage({ voter }) {
|
|
const [votes, setVotes] = useState([]);
|
|
const [userVotes, setUserVotes] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [selectedElectionId, setSelectedElectionId] = useState(null);
|
|
|
|
useEffect(() => {
|
|
fetchVotes();
|
|
}, []);
|
|
|
|
const fetchVotes = async () => {
|
|
try {
|
|
const token = localStorage.getItem('token');
|
|
const [activeRes, upcomingRes, completedRes] = await Promise.all([
|
|
fetch('http://localhost:8000/api/elections/active', { headers: { 'Authorization': `Bearer ${token}` } }),
|
|
fetch('http://localhost:8000/api/elections/upcoming', { headers: { 'Authorization': `Bearer ${token}` } }),
|
|
fetch('http://localhost:8000/api/elections/completed', { headers: { 'Authorization': `Bearer ${token}` } })
|
|
]);
|
|
|
|
const active = activeRes.ok ? await activeRes.json() : [];
|
|
const upcoming = upcomingRes.ok ? await upcomingRes.json() : [];
|
|
const completed = completedRes.ok ? await completedRes.json() : [];
|
|
|
|
const allVotes = [
|
|
...((Array.isArray(active) ? active : [active]).map(v => ({ ...v, status: 'actif' }))),
|
|
...upcoming.map(v => ({ ...v, status: 'futur' })),
|
|
...completed.map(v => ({ ...v, status: 'ferme' }))
|
|
];
|
|
setVotes(allVotes);
|
|
|
|
const votesResponse = await fetch('http://localhost:8000/api/votes/history', {
|
|
headers: { 'Authorization': `Bearer ${token}` },
|
|
});
|
|
|
|
if (votesResponse.ok) {
|
|
const votesData = await votesResponse.json();
|
|
setUserVotes(votesData);
|
|
}
|
|
} catch (err) {
|
|
console.error('Erreur:', err);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const activeVotes = votes.filter(v => v.status === 'actif');
|
|
const futureVotes = votes.filter(v => v.status === 'futur');
|
|
const historyVotes = votes.filter(v => v.status === 'ferme');
|
|
|
|
if (loading) return <LoadingSpinner fullscreen />;
|
|
|
|
return (
|
|
<div className="dashboard-page">
|
|
<div className="container">
|
|
<div className="dashboard-header">
|
|
<h1>Bienvenue, {voter?.nom}! 👋</h1>
|
|
<p>Voici votre tableau de bord personnel</p>
|
|
</div>
|
|
|
|
<div className="stats-grid">
|
|
<div className="stat-card">
|
|
<div className="stat-icon active"><AlertCircle size={24} /></div>
|
|
<div className="stat-content">
|
|
<div className="stat-value">{activeVotes.length}</div>
|
|
<div className="stat-label">Votes Actifs</div>
|
|
<Link to="/dashboard/actifs" className="stat-link">Voir →</Link>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="stat-card">
|
|
<div className="stat-icon future"><Clock size={24} /></div>
|
|
<div className="stat-content">
|
|
<div className="stat-value">{futureVotes.length}</div>
|
|
<div className="stat-label">À Venir</div>
|
|
<Link to="/dashboard/futurs" className="stat-link">Voir →</Link>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="stat-card">
|
|
<div className="stat-icon completed"><CheckCircle size={24} /></div>
|
|
<div className="stat-content">
|
|
<div className="stat-value">{historyVotes.length}</div>
|
|
<div className="stat-label">Votes Terminés</div>
|
|
<Link to="/dashboard/historique" className="stat-link">Voir →</Link>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="stat-card">
|
|
<div className="stat-icon total"><BarChart3 size={24} /></div>
|
|
<div className="stat-content">
|
|
<div className="stat-value">{userVotes.length}</div>
|
|
<div className="stat-label">Votes Effectués</div>
|
|
<Link to="/dashboard/historique" className="stat-link">Voir →</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{activeVotes.length > 0 && (
|
|
<div className="votes-section">
|
|
<h2>⚡ Votes Actifs</h2>
|
|
<p className="section-subtitle">Votes en cours - Participez maintenant!</p>
|
|
<div className="votes-grid">
|
|
{activeVotes.slice(0, 2).map(vote => (
|
|
<VoteCard
|
|
key={vote.id}
|
|
vote={vote}
|
|
userVote={userVotes.find(v => v.election_id === vote.id)?.choix}
|
|
onVote={(id) => window.location.href = `/vote/${id}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
{activeVotes.length > 2 && (
|
|
<Link to="/dashboard/actifs" className="btn btn-secondary">
|
|
Voir tous les votes actifs ({activeVotes.length})
|
|
</Link>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{futureVotes.length > 0 && (
|
|
<div className="votes-section">
|
|
<h2>🔮 Votes à Venir</h2>
|
|
<p className="section-subtitle">Élections qui démarreront bientôt</p>
|
|
<div className="votes-grid">
|
|
{futureVotes.slice(0, 2).map(vote => (
|
|
<VoteCard
|
|
key={vote.id}
|
|
vote={vote}
|
|
context="futur"
|
|
onShowDetails={(id) => setSelectedElectionId(id)}
|
|
/>
|
|
))}
|
|
</div>
|
|
{futureVotes.length > 2 && (
|
|
<Link to="/dashboard/futurs" className="btn btn-secondary">
|
|
Voir tous les votes à venir ({futureVotes.length})
|
|
</Link>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{historyVotes.length > 0 && (
|
|
<div className="votes-section">
|
|
<h2>📋 Mon Historique</h2>
|
|
<p className="section-subtitle">Vos 2 derniers votes</p>
|
|
<div className="votes-grid">
|
|
{historyVotes.slice(0, 2).map(vote => (
|
|
<VoteCard
|
|
key={vote.id}
|
|
vote={vote}
|
|
userVote={userVotes.find(v => v.election_id === vote.id)?.choix}
|
|
showResult={true}
|
|
context="historique"
|
|
onShowDetails={(id) => setSelectedElectionId(id)}
|
|
/>
|
|
))}
|
|
</div>
|
|
{historyVotes.length > 2 && (
|
|
<Link to="/dashboard/historique" className="btn btn-secondary">
|
|
Voir tout mon historique ({historyVotes.length})
|
|
</Link>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{activeVotes.length === 0 && futureVotes.length === 0 && historyVotes.length === 0 && (
|
|
<div className="votes-section">
|
|
<div className="empty-state">
|
|
<div className="empty-icon">📭</div>
|
|
<h3>Aucun vote disponible</h3>
|
|
<p>Il n'y a pas encore de votes disponibles.</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<ElectionDetailsModal
|
|
electionId={selectedElectionId}
|
|
isOpen={!!selectedElectionId}
|
|
onClose={() => setSelectedElectionId(null)}
|
|
voter={voter}
|
|
type="futur"
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|