SONUM v7 - Évolution v6 (éditeurs/blocs CRUD, tableau de bord stats) + vue liste alternance couleurs

This commit is contained in:
Manus Agent
2026-04-20 11:51:04 -04:00
commit 3bccb0a743
143 changed files with 30933 additions and 0 deletions

View File

@@ -0,0 +1,151 @@
import { trpc } from "@/lib/trpc";
import { getLoginUrl } from "@/const";
import { useState } from "react";
import { useLocation } from "wouter";
import { toast } from "sonner";
import { Eye, EyeOff, Lock, Mail, ArrowLeft, ExternalLink } from "lucide-react";
export default function LoginLocal() {
const [, navigate] = useLocation();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const loginMutation = trpc.auth.loginLocal.useMutation({
onSuccess: () => {
// Forcer un rechargement complet pour réinitialiser le contexte auth
window.location.href = "/";
},
onError: (err) => {
toast.error(err.message || "Email ou mot de passe incorrect");
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!email || !password) {
toast.error("Veuillez renseigner votre email et votre mot de passe");
return;
}
loginMutation.mutate({ email, password });
};
return (
<div className="min-h-screen flex items-center justify-center bg-background px-4">
<div className="w-full max-w-md">
{/* Logo */}
<div className="text-center mb-8">
<div className="inline-flex items-center gap-3 mb-4">
<div className="w-12 h-12 rounded-xl bg-primary flex items-center justify-center shadow-md">
<Lock size={22} className="text-white" />
</div>
<div className="text-left">
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-widest">FEHAP</div>
<div
className="text-2xl font-bold text-primary leading-tight"
style={{ fontFamily: "'Playfair Display', serif" }}
>
SONUM
</div>
</div>
</div>
<h1 className="text-xl font-semibold text-foreground">Connexion locale</h1>
<p className="text-sm text-muted-foreground mt-1">
Connectez-vous avec votre email et votre mot de passe
</p>
</div>
{/* Formulaire */}
<div className="bg-card rounded-2xl border border-border shadow-sm p-8">
<form onSubmit={handleSubmit} className="space-y-5">
{/* Email */}
<div>
<label className="block text-sm font-medium text-foreground mb-1.5">
Adresse email
</label>
<div className="relative">
<Mail size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-muted-foreground" />
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="prenom.nom@etablissement.fr"
autoComplete="email"
className="w-full pl-10 pr-4 py-2.5 text-sm bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition-all"
required
/>
</div>
</div>
{/* Mot de passe */}
<div>
<label className="block text-sm font-medium text-foreground mb-1.5">
Mot de passe
</label>
<div className="relative">
<Lock size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-muted-foreground" />
<input
type={showPassword ? "text" : "password"}
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
autoComplete="current-password"
className="w-full pl-10 pr-10 py-2.5 text-sm bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition-all"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
>
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
</div>
</div>
{/* Bouton connexion */}
<button
type="submit"
disabled={loginMutation.isPending}
className="w-full py-2.5 px-4 bg-primary text-white rounded-lg font-medium text-sm hover:bg-primary/90 transition-colors shadow-sm disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
{loginMutation.isPending ? (
<>
<div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
Connexion en cours...
</>
) : (
"Se connecter"
)}
</button>
</form>
</div>
{/* Liens */}
<div className="mt-6 space-y-3 text-center">
<button
onClick={() => navigate("/login")}
className="flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors mx-auto"
>
<ArrowLeft size={14} />
Retour aux options de connexion
</button>
<div className="flex items-center gap-3">
<div className="flex-1 h-px bg-border" />
<span className="text-xs text-muted-foreground">ou</span>
<div className="flex-1 h-px bg-border" />
</div>
<a
href={getLoginUrl()}
className="flex items-center justify-center gap-2 text-sm text-primary hover:text-primary/80 font-medium transition-colors"
>
<ExternalLink size={14} />
Se connecter via l'espace adhérent FEHAP
</a>
</div>
</div>
</div>
);
}