add authenticated

This commit is contained in:
Do Manh Phuong 2025-12-23 10:57:55 +07:00
parent b0380b7c9f
commit 21a1d0a647
4 changed files with 125 additions and 130 deletions

BIN
public/soict_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,112 @@
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 { 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 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 flex flex-col items-center">
<CardTitle className="text-xl flex items-center gap-3">
<img src="/soict_logo.png" alt="logo" className="size-20" />
<p> Computer Management</p>
</CardTitle>
<CardDescription>Hệ thống quản 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>
</form>
</CardContent>
</Card>
</div>
);
}

View File

@ -1,135 +1,18 @@
import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router'
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Button } from '@/components/ui/button'
import {
formOptions,
useForm,
} from '@tanstack/react-form'
import { useLogin } from '@/hooks/queries'
import { toast } from 'sonner'
interface LoginFormProps {
username: string
password: string
}
const defaultInput: LoginFormProps = {
username: '',
password: '',
}
const formOpts = formOptions({
defaultValues: defaultInput,
})
import { createFileRoute, redirect } from '@tanstack/react-router'
import { LoginForm } from '@/components/forms/login-form'
export const Route = createFileRoute('/(auth)/login/')({
beforeLoad: async ({ context }) => {
const { token } = context.auth
if (token) throw redirect({ to: '/' })
},
component: LoginForm,
})
function LoginForm() {
const navigate = useNavigate()
const loginMutation = useLogin()
const form = useForm({
...formOpts,
onSubmit: async ({ value }) => {
try {
await loginMutation.mutateAsync({
username: value.username,
password: value.password,
})
toast.success('Đăng nhập thành công!')
navigate({ to: '/' })
} catch (error: any) {
console.error('Login error:', error)
toast.error(
error.response?.data?.message || 'Tài khoản hoặc mật khẩu không đúng.'
)
}
},
component: LoginPage,
})
function LoginPage() {
return (
<Card className="max-w-md mx-auto mt-20 p-6">
<CardHeader>
<CardTitle>Đăng nhập</CardTitle>
<CardDescription>
Vui lòng nhập thông tin đăng nhập của bạn.
</CardDescription>
</CardHeader>
<CardContent>
<form
onSubmit={(e) => {
e.preventDefault()
form.handleSubmit()
}}
className="space-y-4"
>
{/* Username */}
<form.Field name="username">
{(field) => (
<div>
<Label htmlFor="username">Tên đăng nhập</Label>
<Input
id="username"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="Tên đăng nhập"
/>
{field.state.meta.isTouched && field.state.meta.errors && (
<p className="text-sm text-red-500 mt-1">
{field.state.meta.errors}
</p>
)}
<div className="flex items-center justify-center min-h-screen bg-gradient-to-br from-background to-muted/20">
<LoginForm className="w-full max-w-md" />
</div>
)}
</form.Field>
{/* Password */}
<form.Field name="password">
{(field) => (
<div>
<Label htmlFor="password">Mật khẩu</Label>
<Input
id="password"
type="password"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="Mật khẩu"
/>
{field.state.meta.isTouched && field.state.meta.errors && (
<p className="text-sm text-red-500 mt-1">
{field.state.meta.errors}
</p>
)}
</div>
)}
</form.Field>
<Button type="submit" className="w-full" disabled={loginMutation.isPending}>
{loginMutation.isPending ? 'Đang đăng nhập...' : 'Đăng nhập'}
</Button>
</form>
</CardContent>
<CardFooter>
<p className="text-sm text-muted-foreground">
Chưa tài khoản? <span className="underline cursor-pointer">Đăng </span>
</p>
</CardFooter>
</Card>
)
}

View File

@ -3,12 +3,12 @@ import AppLayout from '@/layouts/app-layout'
export const Route = createFileRoute('/_auth')({
// beforeLoad: async ({context}) => {
// const {token} = context.auth
// if (token == null) {
// throw redirect({to: '/login'})
// }
// },
beforeLoad: async ({context}) => {
const {token} = context.auth
if (!token) {
throw redirect({to: '/login'})
}
},
component: AuthenticatedLayout,
})