Checkpoint: Ajout d'une page de connexion locale permettant aux utilisateurs de se connecter avec leur identifiant et mot de passe (username/password) au lieu de l'OAuth Manus. Implémentation complète avec route d'authentification backend, hashage bcrypt, session JWT, et reconnaissance automatique des sessions locales vs OAuth. Tests unitaires et manuels réussis avec l'utilisateur adminServFormation.
This commit is contained in:
@@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { APP_LOGO, APP_TITLE, getLoginUrl } from "@/const";
|
||||
import { GraduationCap, Calendar, Users, CheckCircle, Mail, Download } from "lucide-react";
|
||||
import { useLocation } from "wouter";
|
||||
import { useLocation, Link } from "wouter";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function Home() {
|
||||
@@ -71,9 +71,16 @@ export default function Home() {
|
||||
<h1 className="text-xl font-bold text-gray-900">{APP_TITLE}</h1>
|
||||
</div>
|
||||
{!isAuthenticated && (
|
||||
<Button onClick={() => window.location.href = getLoginUrl()}>
|
||||
Connexion Administrateur
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Link href="/login">
|
||||
<Button variant="outline">
|
||||
Connexion locale
|
||||
</Button>
|
||||
</Link>
|
||||
<Button onClick={() => window.location.href = getLoginUrl()}>
|
||||
Connexion OAuth
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
115
client/src/pages/Login.tsx
Normal file
115
client/src/pages/Login.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import { useState } from "react";
|
||||
import { useLocation } from "wouter";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { toast } from "sonner";
|
||||
import { APP_LOGO, APP_TITLE } from "@/const";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
export default function Login() {
|
||||
const [, setLocation] = useLocation();
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!username || !password) {
|
||||
toast.error("Veuillez remplir tous les champs");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/auth/local/login", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.success) {
|
||||
toast.success("Connexion réussie !");
|
||||
// Rediriger vers le tableau de bord
|
||||
setLocation("/admin");
|
||||
} else {
|
||||
toast.error(data.message || "Identifiant ou mot de passe incorrect");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la connexion:", error);
|
||||
toast.error("Erreur lors de la connexion");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100 p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="space-y-4">
|
||||
<div className="flex justify-center">
|
||||
<img src={APP_LOGO} alt="Logo" className="h-16 w-auto" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<CardTitle className="text-2xl font-bold">{APP_TITLE}</CardTitle>
|
||||
<CardDescription className="mt-2">
|
||||
Connectez-vous avec votre identifiant et mot de passe
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="username">Identifiant</Label>
|
||||
<Input
|
||||
id="username"
|
||||
type="text"
|
||||
placeholder="Votre identifiant"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Mot de passe</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="Votre mot de passe"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full" disabled={loading}>
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Connexion en cours...
|
||||
</>
|
||||
) : (
|
||||
"Se connecter"
|
||||
)}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center text-sm text-gray-600">
|
||||
<p>Vous n'avez pas de compte ?</p>
|
||||
<p className="mt-1">Contactez l'administrateur</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user