diff --git a/public/soict_logo.png b/public/soict_logo.png new file mode 100644 index 0000000..cc631a2 Binary files /dev/null and b/public/soict_logo.png differ diff --git a/src/components/forms/login-form.tsx b/src/components/forms/login-form.tsx new file mode 100644 index 0000000..ba3032e --- /dev/null +++ b/src/components/forms/login-form.tsx @@ -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(null); + const [formData, setFormData] = useState({ + 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) => { + e.preventDefault(); + setErrorMessage(null); + mutation.mutate(formData); + }; + + return ( +
+ + + + logo +

Computer Management

+
+ Hệ thống quản lý phòng máy thực hành +
+ +
+
+
+ + + setFormData({ ...formData, username: e.target.value }) + } + /> +
+
+
+ +
+ + setFormData({ ...formData, password: e.target.value }) + } + /> +
+ {errorMessage && ( +
{errorMessage}
+ )} + {mutation.isPending ? ( + + ) : ( + + )} +
+
+
+
+
+ ); +} diff --git a/src/routes/(auth)/login/index.tsx b/src/routes/(auth)/login/index.tsx index e09a844..ad5b792 100644 --- a/src/routes/(auth)/login/index.tsx +++ b/src/routes/(auth)/login/index.tsx @@ -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, + component: LoginPage, }) -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.' - ) - } - }, - }) - +function LoginPage() { return ( - - - Đăng nhập - - Vui lòng nhập thông tin đăng nhập của bạn. - - - -
{ - e.preventDefault() - form.handleSubmit() - }} - className="space-y-4" - > - {/* Username */} - - {(field) => ( -
- - field.handleChange(e.target.value)} - placeholder="Tên đăng nhập" - /> - {field.state.meta.isTouched && field.state.meta.errors && ( -

- {field.state.meta.errors} -

- )} -
- )} -
- - {/* Password */} - - {(field) => ( -
- - field.handleChange(e.target.value)} - placeholder="Mật khẩu" - /> - {field.state.meta.isTouched && field.state.meta.errors && ( -

- {field.state.meta.errors} -

- )} -
- )} -
- - -
-
- -

- Chưa có tài khoản? Đăng ký -

-
-
+
+ +
) } diff --git a/src/routes/_auth.tsx b/src/routes/_auth.tsx index 32f4ed4..798f3cf 100644 --- a/src/routes/_auth.tsx +++ b/src/routes/_auth.tsx @@ -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, })