From 21a1d0a647abe22763b69731e4e94c02d3f4ee01 Mon Sep 17 00:00:00 2001 From: phuongdm Date: Tue, 23 Dec 2025 10:57:55 +0700 Subject: [PATCH] add authenticated --- public/soict_logo.png | Bin 0 -> 1890 bytes src/components/forms/login-form.tsx | 112 ++++++++++++++++++++++++ src/routes/(auth)/login/index.tsx | 131 ++-------------------------- src/routes/_auth.tsx | 12 +-- 4 files changed, 125 insertions(+), 130 deletions(-) create mode 100644 public/soict_logo.png create mode 100644 src/components/forms/login-form.tsx diff --git a/public/soict_logo.png b/public/soict_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cc631a241ed22eb6777bab04feb02ded68e39557 GIT binary patch literal 1890 zcmb7F_dgqm1I_1p&Nbz$U!qmTojqdIRcc3TJ0qxBV%PYZG1FRiDt6L-B{kBh8mStI zvx`LA5UaQ|gBUdA9r_W{837ms_t-j zEJa3hcWi4@DNFHC1OR?Wu`<2qge2_XFBjqsKpD;jd5DnreC0k47U5M9-Hh{d zCiJe=cczHfo6Rf=j2RQyAHP_qtsSm)G4o&Hw-cY4rnO3DNmyX($zBgm~U4D6pua(AIe^DQSayVs!dHfBn?SxX(yssQNza$en8Jf(IXbzhH)i01HoNgZC@?gL9XHtxzT_(II+B z9L+zOYHRu{Aduf6Sbe@J*vd1zw=N*ejfiHn;Ri_Shd?* z*L<7UluXu8sI#>1+#cncHV}ebl1F_gGcd!Vc>>Y~zwWD&(a|nFSx}{0NLSlCI@-ey zIm*RdtDOuDVzM7O$feQxhq7cRhQb!y@vY{>YxBCejl7r?vTK!O!k8Y!P=%b3S!y&M zu@*&^E-)V-;kN(O>7p->>sL8xbY{VeRd*~Q&hWchn-h@eJpbh{r0NnPnp87u4z!r>e1IQWD9G_8w1Y#W0G8H?spZv{hk*JI^?l# znx4BAI!pD9l;ib4)r#k@>i*!WRTz>loqF!M}c9# zYzk&H5sv2$=gU`H+(Iy9ja6d_K4(|RH`CMe@#LGcUhqO(|SL0=E!)s_SqM5 zEqenbprV{dujfpGw@W}OF))kh1n$Tl>Xj@=DpoWOb#IQO!XeVoWX>hg*_MPWQv;X; zwhq=6Zf010i>yMhMp?tz{3>CTNiX%_AZ{Nps9bjD%M7rarw_G;y}f%hU%4Nt9G1SQ z-fNdW%yw4>wHm-7(UN$4+z}CoU1#L!GMoZks1w`7v7c*e?AQaldTNjRNsRGMsb+e6ZDAk~WZ=^6iGkX5_iq?cc;2 zR6#GURfbP5hg#M*&2H+rX{iU~!EDL#f53xIK++N+*>8S5p_0YxP&77%ZB+|rlIR(h9^(r2y)OV9uC9Qyp zf;pgUoGEoThS?fKrFV)`(Bh9#XpHy`?zQ>ADCtI1Y~uP=C6AoTx4<-9Gg zM8_snMa!FZ8$CblB;C3qlnDN|Nd@s*y2s@B8#K1{7*C{Z%gn28CAG^mEZ0L}(g@tK z$YK4BN6$`BYU>$aF+Wk@FExeki!qes>b$4d(^iOuNq(xh>xD=;;R&qofcn8za9`wX zc?sKMQ6RTYLkK91lT0bRGQC(7SR-5kBBdGz`O#Tvhh5dwaO~#YHCTMz;fE!?scEg) zs3#D{B@agO;qtsaOvG3)NKIOr~J&Pq>q@sh)#D74ECq&W8{mWICt#h9nu2-JgQ);7qQLic_13$H{$xOzWwL0 nPM1V(pP|6Nx`T`GZ=t6`yKjI0X5tA&|I@WHvoj?d`=tF13e%vL literal 0 HcmV?d00001 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, })