131 lines
5.0 KiB
TypeScript
131 lines
5.0 KiB
TypeScript
import { cn } from "@/lib/utils";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import type { LoginResquest } from "@/types/auth";
|
|
import { useMutation } from "@tanstack/react-query";
|
|
import { buildSsoLoginUrl, login } from "@/services/auth.service";
|
|
import { useState } from "react";
|
|
import { useNavigate, useRouter } from "@tanstack/react-router";
|
|
import { Route } from "@/routes/(auth)/login";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { useAuth } from "@/hooks/useAuth";
|
|
import { LoaderCircle } from "lucide-react";
|
|
|
|
export function LoginForm({ className }: React.ComponentProps<"form">) {
|
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
const [formData, setFormData] = useState<LoginResquest>({
|
|
username: "",
|
|
password: "",
|
|
});
|
|
const auth = useAuth();
|
|
const router = useRouter();
|
|
const navigate = useNavigate();
|
|
const search = Route.useSearch() as { redirect?: string };
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: login,
|
|
async onSuccess(data) {
|
|
localStorage.setItem("accesscontrol.auth.user", data.username!);
|
|
localStorage.setItem("token", data.token!);
|
|
localStorage.setItem("name", data.name!);
|
|
localStorage.setItem("acs", (data.access ?? "").toString());
|
|
localStorage.setItem("role", data.role.roleName ?? "");
|
|
localStorage.setItem("priority", (data.role.priority ?? 0).toString());
|
|
|
|
auth.setAuthenticated(true);
|
|
auth.login(data.username!);
|
|
|
|
await router.invalidate();
|
|
await navigate({ to: search.redirect || "/dashboard" });
|
|
},
|
|
onError(error) {
|
|
setErrorMessage(error.message || "Login failed");
|
|
}
|
|
});
|
|
|
|
const handleSsoLogin = () => {
|
|
const returnUrl = new URL("/sso/callback", window.location.origin);
|
|
if (search.redirect) {
|
|
returnUrl.searchParams.set("redirect", search.redirect);
|
|
}
|
|
window.location.assign(buildSsoLoginUrl(returnUrl.toString()));
|
|
};
|
|
|
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
setErrorMessage(null);
|
|
mutation.mutate(formData);
|
|
};
|
|
|
|
return (
|
|
<div className={cn("flex flex-col gap-6", className)}>
|
|
<Card>
|
|
<CardHeader className="text-center">
|
|
<CardTitle className="text-2xl font-semibold tracking-tight flex items-center justify-center gap-3">
|
|
<img src="/soict_logo.png" alt="SOICT logo" className="h-7 w-auto object-contain" />
|
|
<span>Computer Management</span>
|
|
</CardTitle>
|
|
<CardDescription>Hệ thống quản lý phòng máy thực hành</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="grid gap-6">
|
|
<div className="grid gap-3">
|
|
<Label htmlFor="email">Tên đăng nhập</Label>
|
|
<Input
|
|
id="email"
|
|
type="text"
|
|
autoFocus
|
|
required
|
|
value={formData.username}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, username: e.target.value })
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-3">
|
|
<div className="flex items-center">
|
|
<Label htmlFor="password">Mật khẩu</Label>
|
|
</div>
|
|
<Input
|
|
id="password"
|
|
type="password"
|
|
required
|
|
value={formData.password}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, password: e.target.value })
|
|
}
|
|
/>
|
|
</div>
|
|
{errorMessage && (
|
|
<div className="text-destructive text-sm font-medium">{errorMessage}</div>
|
|
)}
|
|
{mutation.isPending ? (
|
|
<Button className="w-full" disabled>
|
|
<LoaderCircle className="w-4 h-4 mr-1 animate-spin" />
|
|
Đang đăng nhập
|
|
</Button>
|
|
) : (
|
|
<Button type="submit" className="w-full">
|
|
Đăng nhập
|
|
</Button>
|
|
)}
|
|
<div className="text-center text-sm text-muted-foreground">Hoặc</div>
|
|
<Button type="button" variant="outline" className="w-full gap-2" onClick={handleSsoLogin}>
|
|
<svg viewBox="0 0 24 24" aria-hidden="true" className="h-4 w-4">
|
|
<rect x="1" y="1" width="10" height="10" fill="#F25022" />
|
|
<rect x="13" y="1" width="10" height="10" fill="#7FBA00" />
|
|
<rect x="1" y="13" width="10" height="10" fill="#00A4EF" />
|
|
<rect x="13" y="13" width="10" height="10" fill="#FFB900" />
|
|
</svg>
|
|
Đăng nhập với Microsoft
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|