feat(frontend): convertit l'ensemble des pages et composants au DSFR
- composants UI (Input, Card, Badge, Modal, ConfirmDialog, Slider, Skeleton, ThemeToggle) + Sidebar - pages auth (connexion, inscription) - pages publiques (informations, détail, contacts urgence, mentions, RGPD) - espace membre (dashboard, journal, wizard saisie via fr-stepper, rapports, profil) + composants tracker (EditSaisieModal, EmotionPicker, TrackerTimeline, EmotionChart) - back-office admin (dashboard, utilisateurs, contenus, émotions) Suppression de Tailwind candlelight/malachite + lucide-react. Build OK (20 routes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Eye, EyeOff, Mail, Lock } from 'lucide-react';
|
||||
import { useAuthStore } from '@/lib/auth-store';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
@@ -14,10 +13,12 @@ export default function ConnexionPage() {
|
||||
const [password, setPassword] = useState('');
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setErrorMessage('');
|
||||
|
||||
try {
|
||||
await login(email, password);
|
||||
@@ -26,6 +27,7 @@ export default function ConnexionPage() {
|
||||
} catch (error: unknown) {
|
||||
const axiosError = error as import('axios').AxiosError<{ message?: string }>;
|
||||
const message = axiosError.response?.data?.message || 'Identifiants incorrects';
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -33,91 +35,73 @@ export default function ConnexionPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-linear-to-br from-candlelight-50 via-white to-malachite-50 dark:from-gray-950 dark:via-gray-900 dark:to-gray-950 px-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="text-center mb-8">
|
||||
<Link href="/" className="inline-block">
|
||||
<h1 className="font-display text-4xl font-bold">
|
||||
<span className="text-malachite-600 dark:text-malachite-400">CESI</span>
|
||||
<span className="text-candlelight-500">ZEN</span>
|
||||
</h1>
|
||||
</Link>
|
||||
<p className="text-gray-500 dark:text-gray-400 mt-2">Connectez-vous à votre compte</p>
|
||||
<>
|
||||
<h1>Connexion</h1>
|
||||
<p className="fr-text--lead">Connectez-vous à votre compte CESIZen.</p>
|
||||
|
||||
{errorMessage && (
|
||||
<div className="fr-alert fr-alert--error fr-alert--sm fr-mb-3w" role="alert">
|
||||
<p>{errorMessage}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="fr-input-group">
|
||||
<label className="fr-label" htmlFor="email">
|
||||
Adresse email
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="fr-input"
|
||||
placeholder="vous@exemple.fr"
|
||||
autoComplete="email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-white dark:bg-gray-900 rounded-2xl shadow-xl border border-gray-100 dark:border-gray-800 p-8">
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
|
||||
Adresse email
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="w-full pl-11 pr-4 py-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-candlelight-500 focus:border-transparent outline-none transition-all"
|
||||
placeholder="vous@exemple.fr"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
|
||||
Mot de passe
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
|
||||
<input
|
||||
id="password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
className="w-full pl-11 pr-12 py-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-candlelight-500 focus:border-transparent outline-none transition-all"
|
||||
placeholder="Votre mot de passe"
|
||||
/>
|
||||
<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={showPassword ? 'Masquer le mot de passe' : 'Afficher le mot de passe'}
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full bg-candlelight-500 hover:bg-candlelight-600 disabled:opacity-50 disabled:cursor-not-allowed text-black font-semibold py-3 rounded-xl transition-all duration-200 hover:shadow-lg"
|
||||
>
|
||||
{loading ? (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<svg className="animate-spin w-5 h-5" viewBox="0 0 24 24" fill="none">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
Connexion...
|
||||
</span>
|
||||
) : (
|
||||
'Se connecter'
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
Pas encore de compte ?{' '}
|
||||
<Link href="/inscription" className="text-malachite-600 dark:text-malachite-400 font-medium hover:underline">
|
||||
Créer un compte
|
||||
</Link>
|
||||
<div className="fr-password" id="password-group">
|
||||
<label className="fr-label" htmlFor="password">
|
||||
Mot de passe
|
||||
</label>
|
||||
<div className="fr-input-wrap">
|
||||
<input
|
||||
id="password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
className="fr-password__input fr-input"
|
||||
placeholder="Votre mot de passe"
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
<div className="fr-password__checkbox fr-checkbox-group fr-checkbox-group--sm">
|
||||
<input
|
||||
id="password-show"
|
||||
type="checkbox"
|
||||
checked={showPassword}
|
||||
onChange={() => setShowPassword(!showPassword)}
|
||||
/>
|
||||
<label className="fr-label" htmlFor="password-show">
|
||||
Afficher le mot de passe
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" disabled={loading} className="fr-btn fr-btn--lg">
|
||||
{loading ? 'Connexion…' : 'Se connecter'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="fr-mt-4w">
|
||||
Pas encore de compte ?{' '}
|
||||
<Link href="/inscription" className="fr-link">
|
||||
Créer un compte
|
||||
</Link>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user