185 lines
6.4 KiB
TypeScript
185 lines
6.4 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Loader2 } from "lucide-react";
|
|
import { trpc } from "@/lib/trpc";
|
|
import { toast } from "sonner";
|
|
|
|
const ITINOVA_LOGO = "https://d2xsxph8kpxj0f.cloudfront.net/310519663070627318/fo4DRyBgjsuiigFAgNLuWm/itinova-logo_2a2ba00a.jpg";
|
|
const SANTINOVA_LOGO = "https://d2xsxph8kpxj0f.cloudfront.net/310519663070627318/fo4DRyBgjsuiigFAgNLuWm/santinova-logo_5ae0d248.webp";
|
|
|
|
// Logo Microsoft SVG officiel
|
|
function MicrosoftLogo() {
|
|
return (
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21" width="18" height="18">
|
|
<rect x="1" y="1" width="9" height="9" fill="#f25022"/>
|
|
<rect x="11" y="1" width="9" height="9" fill="#7fba00"/>
|
|
<rect x="1" y="11" width="9" height="9" fill="#00a4ef"/>
|
|
<rect x="11" y="11" width="9" height="9" fill="#ffb900"/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
export default function Login() {
|
|
const [username, setUsername] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [azureLoading, setAzureLoading] = useState(false);
|
|
|
|
// Afficher les erreurs transmises via query param (ex: depuis le callback Azure)
|
|
useEffect(() => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const error = params.get("error");
|
|
if (error) {
|
|
toast.error(decodeURIComponent(error));
|
|
// Nettoyer l'URL
|
|
window.history.replaceState({}, "", "/login");
|
|
}
|
|
}, []);
|
|
|
|
const loginMutation = trpc.auth.loginLocal.useMutation({
|
|
onSuccess: () => {
|
|
toast.success("Connexion réussie");
|
|
window.location.href = "/";
|
|
},
|
|
onError: (error) => {
|
|
toast.error(error.message || "Erreur de connexion");
|
|
},
|
|
});
|
|
|
|
const azureLoginQuery = trpc.auth.getAzureLoginUrl.useQuery(undefined, {
|
|
enabled: false,
|
|
retry: false,
|
|
});
|
|
|
|
const azureAvailableQuery = trpc.auth.isAzureAdAvailable.useQuery();
|
|
|
|
const handleLocalLogin = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
loginMutation.mutate({ email: username, password });
|
|
};
|
|
|
|
const handleMicrosoftLogin = async () => {
|
|
setAzureLoading(true);
|
|
try {
|
|
const result = await azureLoginQuery.refetch();
|
|
if (result.data?.url) {
|
|
window.location.href = result.data.url;
|
|
} else {
|
|
toast.error("Impossible d'obtenir l'URL de connexion Microsoft");
|
|
setAzureLoading(false);
|
|
}
|
|
} catch {
|
|
toast.error("Erreur lors de la connexion Microsoft");
|
|
setAzureLoading(false);
|
|
}
|
|
};
|
|
|
|
const azureAvailable = azureAvailableQuery.data?.available ?? false;
|
|
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-blue-50 p-4">
|
|
<div className="w-full max-w-sm flex flex-col items-center gap-6">
|
|
|
|
{/* Logo Itinova en haut */}
|
|
<div className="w-full flex justify-center">
|
|
<img
|
|
src={ITINOVA_LOGO}
|
|
alt="Itinova"
|
|
className="h-24 object-contain"
|
|
/>
|
|
</div>
|
|
|
|
{/* Carte de connexion */}
|
|
<Card className="w-full shadow-lg border-0">
|
|
<CardContent className="pt-6 pb-6">
|
|
<div className="mb-6 text-center">
|
|
<h1 className="text-2xl font-bold text-slate-800">Gestion facturation</h1>
|
|
<p className="text-sm text-slate-500 mt-1">Connectez-vous pour accéder à l'application</p>
|
|
</div>
|
|
|
|
{/* Bouton Microsoft 365 */}
|
|
{azureAvailable && (
|
|
<>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
className="w-full flex items-center gap-3 border-slate-300 bg-white hover:bg-slate-50 text-slate-700 font-medium mb-4"
|
|
onClick={handleMicrosoftLogin}
|
|
disabled={azureLoading}
|
|
>
|
|
{azureLoading ? (
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
<MicrosoftLogo />
|
|
)}
|
|
Se connecter avec Microsoft 365
|
|
</Button>
|
|
|
|
<div className="relative mb-4">
|
|
<div className="absolute inset-0 flex items-center">
|
|
<span className="w-full border-t border-slate-200" />
|
|
</div>
|
|
<div className="relative flex justify-center text-xs uppercase">
|
|
<span className="bg-white px-2 text-slate-400">ou</span>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
<form onSubmit={handleLocalLogin} 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)}
|
|
required
|
|
autoFocus
|
|
autoComplete="username"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="password">Mot de passe</Label>
|
|
<Input
|
|
id="password"
|
|
type="password"
|
|
placeholder="••••••••"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
required
|
|
autoComplete="current-password"
|
|
/>
|
|
</div>
|
|
|
|
<Button
|
|
type="submit"
|
|
className="w-full bg-[#1e3a8a] hover:bg-[#1e40af] text-white mt-2"
|
|
disabled={loginMutation.isPending}
|
|
>
|
|
{loginMutation.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
Se connecter
|
|
</Button>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Logo Santinova en bas avec "powered by" */}
|
|
<div className="flex flex-col items-center gap-1">
|
|
<span className="text-xs text-slate-400 lowercase tracking-wide">powered by</span>
|
|
<img
|
|
src={SANTINOVA_LOGO}
|
|
alt="Santinova"
|
|
className="h-24 object-contain"
|
|
/>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|