196 lines
5.8 KiB
TypeScript
196 lines
5.8 KiB
TypeScript
import { createLazyFileRoute, useNavigate } from "@tanstack/react-router";
|
|
import { useMutation } from "@tanstack/react-query";
|
|
import { Input } from '@/shadcn/components/ui/input.tsx';
|
|
import { Button } from '@/shadcn/components/ui/button.tsx';
|
|
import { Alert } from '@/shadcn/components/ui/alert.tsx';
|
|
import { Card } from '@/shadcn/components/ui/card.tsx';
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage
|
|
} from '@/shadcn/components/ui/form.tsx';
|
|
import client from "../../honoClient";
|
|
import { useForm } from "react-hook-form";
|
|
import { z } from "zod";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { useEffect, useState } from "react";
|
|
import useAuth from "@/hooks/useAuth";
|
|
import { TbArrowNarrowRight } from "react-icons/tb";
|
|
|
|
export const Route = createLazyFileRoute("/login/")({
|
|
component: LoginPage,
|
|
});
|
|
|
|
type FormSchema = {
|
|
username: string;
|
|
password: string;
|
|
};
|
|
|
|
const formSchema = z.object({
|
|
username: z.string().min(1, "This field is required"),
|
|
password: z.string().min(1, "This field is required"),
|
|
});
|
|
|
|
export default function LoginPage() {
|
|
const [errorMessage, setErrorMessage] = useState("");
|
|
const navigate = useNavigate();
|
|
|
|
const { isAuthenticated, saveAuthData } = useAuth();
|
|
|
|
const form = useForm<FormSchema>({
|
|
resolver: zodResolver(formSchema),
|
|
defaultValues: {
|
|
username: "",
|
|
password: "",
|
|
},
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (isAuthenticated) {
|
|
navigate({
|
|
to: "/dashboard",
|
|
replace: true,
|
|
});
|
|
}
|
|
}, [navigate, isAuthenticated]);
|
|
|
|
const loginMutation = useMutation({
|
|
mutationFn: async (values: FormSchema) => {
|
|
const res = await client.auth.login.$post({
|
|
form: values,
|
|
});
|
|
|
|
if (res.ok) {
|
|
return await res.json();
|
|
}
|
|
|
|
throw res;
|
|
},
|
|
|
|
onSuccess: (data) => {
|
|
saveAuthData(
|
|
{
|
|
id: data.user.id,
|
|
name: data.user.name,
|
|
permissions: data.user.permissions,
|
|
},
|
|
data.accessToken
|
|
);
|
|
},
|
|
|
|
onError: async (error) => {
|
|
console.log("error!");
|
|
if (error instanceof Response) {
|
|
const body = await error.json();
|
|
setErrorMessage(body.message as string);
|
|
return;
|
|
}
|
|
console.log("bukan error");
|
|
},
|
|
});
|
|
|
|
const handleSubmit = (values: FormSchema) => {
|
|
loginMutation.mutate(values);
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col lg:flex-row min-h-screen overflow-hidden">
|
|
<div className="absolute top-6 left-7 text-base font-bold leading-5">
|
|
Amati
|
|
</div>
|
|
<div className="flex w-screen h-screen items-start lg:items-center justify-center lg:justify-end absolute -top-56 lg:top-0 overflow-hidden -z-20">
|
|
<div className="flex absolute border border-slate-300 rounded-2xl w-[455px] h-[455px] lg:w-[45vw] lg:h-[45vw] items-center justify-center -rotate-[15deg]">
|
|
<div className="flex absolute border border-slate-400 rounded-2xl w-2/3 h-2/3 lg:w-4/5 lg:h-4/5 items-center justify-center">
|
|
<div className="flex absolute border border-slate-500 rounded-2xl w-3/5 h-3/5 lg:w-3/4 lg:h-3/4 items-center justify-center">
|
|
<div className="hidden lg:flex absolute border border-slate-600 rounded-2xl w-2/3 h-2/3">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="w-screen h-screen flex ml-0 lg:ml-28 3xl:ml-96 4xl:ml-[48rem] justify-center lg:justify-start items-center">
|
|
<Card className="w-[19.125rem] sm:w-[26.063rem] lg:w-auto h-auto bg-transparent border-none shadow-none">
|
|
<h1 className="mb-2 text-[2.625rem] font-bold leading-[3.177rem] tracking-tighter">Sign In</h1>
|
|
<p className="text-sm mb-10 leading-4 text-muted-foreground">
|
|
New to this app?{' '}
|
|
<a
|
|
href="/register"
|
|
className="text-blue-500 font-bold hover:text-blue-800"
|
|
>
|
|
Register now
|
|
</a>
|
|
</p>
|
|
<Form {...form}>
|
|
<form onSubmit={form.handleSubmit(handleSubmit)}>
|
|
<div className="space-y-5">
|
|
{errorMessage && (
|
|
<Alert variant="destructive">
|
|
<p>{errorMessage}</p>
|
|
</Alert>
|
|
)}
|
|
<FormField
|
|
name="username"
|
|
render={({ field }) => (
|
|
<FormItem className="text-sm">
|
|
<FormLabel className="font-semibold leading-4">Email/Username</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
placeholder="eg; user@mail.com"
|
|
disabled={loginMutation.isPending}
|
|
className={form.formState.errors.username ? "border-red-500" : ""}
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
name="password"
|
|
render={({ field }) => (
|
|
<FormItem className="text-sm">
|
|
<FormLabel className="font-semibold leading-4">Password</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="password"
|
|
placeholder="*****"
|
|
disabled={loginMutation.isPending}
|
|
className={form.formState.errors.password ? "border-red-500" : ""}
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<p className="text-sm">
|
|
<a
|
|
href="/forgot-password"
|
|
className="text-blue-500 font-bold hover:text-blue-800 leading-4"
|
|
>
|
|
Forgot Password?
|
|
</a>
|
|
</p>
|
|
</div>
|
|
<div className="flex justify-between mt-10">
|
|
<Button
|
|
type="submit"
|
|
disabled={loginMutation.isPending}
|
|
variant="default"
|
|
className="w-full flex items-center justify-center space-x-[13.125rem] sm:space-x-[20rem]
|
|
lg:space-x-[23.125rem] bg-[#2555FF] text-white hover:bg-[#1e4ae0]"
|
|
>
|
|
<span className="leading-5">Sign In</span>
|
|
<TbArrowNarrowRight className="h-5 w-5" />
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</Form>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |