add authenticated
This commit is contained in:
parent
b0380b7c9f
commit
21a1d0a647
BIN
public/soict_logo.png
Normal file
BIN
public/soict_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
112
src/components/forms/login-form.tsx
Normal file
112
src/components/forms/login-form.tsx
Normal 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 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>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 có tài khoản? <span className="underline cursor-pointer">Đăng ký</span>
|
||||
</p>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user