2024-03-17 17:28:45 +01:00
|
|
|
import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons"
|
2024-03-08 14:58:36 +01:00
|
|
|
import {
|
|
|
|
Button,
|
|
|
|
Center,
|
|
|
|
Container,
|
|
|
|
FormControl,
|
|
|
|
FormErrorMessage,
|
|
|
|
Icon,
|
|
|
|
Image,
|
|
|
|
Input,
|
|
|
|
InputGroup,
|
|
|
|
InputRightElement,
|
|
|
|
Link,
|
|
|
|
useBoolean,
|
2024-03-17 17:28:45 +01:00
|
|
|
} from "@chakra-ui/react"
|
2024-03-08 14:58:36 +01:00
|
|
|
import {
|
|
|
|
Link as RouterLink,
|
|
|
|
createFileRoute,
|
|
|
|
redirect,
|
2024-03-17 17:28:45 +01:00
|
|
|
} from "@tanstack/react-router"
|
|
|
|
import { type SubmitHandler, useForm } from "react-hook-form"
|
2024-01-23 11:48:56 -05:00
|
|
|
|
2024-05-20 13:15:23 -05:00
|
|
|
import Logo from "/assets/images/fastapi-logo.svg"
|
2024-04-03 16:40:33 -05:00
|
|
|
import type { Body_login_login_access_token as AccessToken } from "../client"
|
2024-03-17 17:28:45 +01:00
|
|
|
import useAuth, { isLoggedIn } from "../hooks/useAuth"
|
2024-03-28 19:49:02 -05:00
|
|
|
import { emailPattern } from "../utils"
|
2024-01-23 11:48:56 -05:00
|
|
|
|
2024-03-17 17:28:45 +01:00
|
|
|
export const Route = createFileRoute("/login")({
|
2024-03-07 19:16:23 +01:00
|
|
|
component: Login,
|
|
|
|
beforeLoad: async () => {
|
|
|
|
if (isLoggedIn()) {
|
|
|
|
throw redirect({
|
2024-03-17 17:28:45 +01:00
|
|
|
to: "/",
|
2024-03-07 19:16:23 +01:00
|
|
|
})
|
|
|
|
}
|
2024-03-08 14:58:36 +01:00
|
|
|
},
|
2024-03-07 19:16:23 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
function Login() {
|
2024-03-08 14:58:36 +01:00
|
|
|
const [show, setShow] = useBoolean()
|
2024-04-09 16:12:19 +02:00
|
|
|
const { loginMutation, error, resetError } = useAuth()
|
2024-03-08 14:58:36 +01:00
|
|
|
const {
|
|
|
|
register,
|
|
|
|
handleSubmit,
|
|
|
|
formState: { errors, isSubmitting },
|
|
|
|
} = useForm<AccessToken>({
|
2024-03-17 17:28:45 +01:00
|
|
|
mode: "onBlur",
|
|
|
|
criteriaMode: "all",
|
2024-02-28 18:30:59 -05:00
|
|
|
defaultValues: {
|
2024-03-17 17:28:45 +01:00
|
|
|
username: "",
|
|
|
|
password: "",
|
2024-03-08 14:58:36 +01:00
|
|
|
},
|
|
|
|
})
|
2024-02-26 09:39:09 -05:00
|
|
|
|
2024-01-23 11:48:56 -05:00
|
|
|
const onSubmit: SubmitHandler<AccessToken> = async (data) => {
|
2024-04-09 16:12:19 +02:00
|
|
|
if (isSubmitting) return
|
|
|
|
|
|
|
|
resetError()
|
|
|
|
|
|
|
|
try {
|
|
|
|
await loginMutation.mutateAsync(data)
|
|
|
|
} catch {
|
|
|
|
// error is handled by useAuth hook
|
|
|
|
}
|
2024-03-08 14:58:36 +01:00
|
|
|
}
|
2024-01-23 11:48:56 -05:00
|
|
|
|
|
|
|
return (
|
2024-02-12 16:46:51 -05:00
|
|
|
<>
|
|
|
|
<Container
|
2024-03-08 14:58:36 +01:00
|
|
|
as="form"
|
2024-02-12 16:46:51 -05:00
|
|
|
onSubmit={handleSubmit(onSubmit)}
|
2024-03-08 14:58:36 +01:00
|
|
|
h="100vh"
|
|
|
|
maxW="sm"
|
|
|
|
alignItems="stretch"
|
|
|
|
justifyContent="center"
|
2024-02-12 16:46:51 -05:00
|
|
|
gap={4}
|
|
|
|
centerContent
|
|
|
|
>
|
2024-03-08 14:58:36 +01:00
|
|
|
<Image
|
|
|
|
src={Logo}
|
|
|
|
alt="FastAPI logo"
|
|
|
|
height="auto"
|
|
|
|
maxW="2xs"
|
|
|
|
alignSelf="center"
|
|
|
|
mb={4}
|
|
|
|
/>
|
|
|
|
<FormControl id="username" isInvalid={!!errors.username || !!error}>
|
|
|
|
<Input
|
|
|
|
id="username"
|
2024-03-17 17:28:45 +01:00
|
|
|
{...register("username", {
|
2024-07-18 22:24:51 +02:00
|
|
|
required: "Username is required",
|
2024-03-28 19:49:02 -05:00
|
|
|
pattern: emailPattern,
|
2024-03-08 14:58:36 +01:00
|
|
|
})}
|
|
|
|
placeholder="Email"
|
2024-03-11 16:50:46 +01:00
|
|
|
type="email"
|
2024-04-12 17:50:14 +02:00
|
|
|
required
|
2024-03-08 14:58:36 +01:00
|
|
|
/>
|
|
|
|
{errors.username && (
|
|
|
|
<FormErrorMessage>{errors.username.message}</FormErrorMessage>
|
|
|
|
)}
|
2024-02-12 16:46:51 -05:00
|
|
|
</FormControl>
|
2024-03-08 14:58:36 +01:00
|
|
|
<FormControl id="password" isInvalid={!!error}>
|
2024-02-12 16:46:51 -05:00
|
|
|
<InputGroup>
|
|
|
|
<Input
|
2024-07-18 22:24:51 +02:00
|
|
|
{...register("password", {
|
|
|
|
required: "Password is required",
|
|
|
|
})}
|
2024-03-17 17:28:45 +01:00
|
|
|
type={show ? "text" : "password"}
|
2024-03-08 14:58:36 +01:00
|
|
|
placeholder="Password"
|
2024-04-12 17:50:14 +02:00
|
|
|
required
|
2024-02-12 16:46:51 -05:00
|
|
|
/>
|
|
|
|
<InputRightElement
|
2024-04-01 22:53:33 -05:00
|
|
|
color="ui.dim"
|
2024-02-12 16:46:51 -05:00
|
|
|
_hover={{
|
2024-03-17 17:28:45 +01:00
|
|
|
cursor: "pointer",
|
2024-02-12 16:46:51 -05:00
|
|
|
}}
|
|
|
|
>
|
2024-03-08 14:58:36 +01:00
|
|
|
<Icon
|
2024-07-18 22:24:51 +02:00
|
|
|
as={show ? ViewOffIcon : ViewIcon}
|
2024-03-08 14:58:36 +01:00
|
|
|
onClick={setShow.toggle}
|
2024-03-17 17:28:45 +01:00
|
|
|
aria-label={show ? "Hide password" : "Show password"}
|
2024-03-08 14:58:36 +01:00
|
|
|
>
|
2024-02-12 16:46:51 -05:00
|
|
|
{show ? <ViewOffIcon /> : <ViewIcon />}
|
|
|
|
</Icon>
|
|
|
|
</InputRightElement>
|
|
|
|
</InputGroup>
|
2024-03-08 14:58:36 +01:00
|
|
|
{error && <FormErrorMessage>{error}</FormErrorMessage>}
|
2024-02-12 16:46:51 -05:00
|
|
|
</FormControl>
|
2024-02-28 18:30:59 -05:00
|
|
|
<Center>
|
2024-03-08 14:58:36 +01:00
|
|
|
<Link as={RouterLink} to="/recover-password" color="blue.500">
|
2024-02-28 18:30:59 -05:00
|
|
|
Forgot password?
|
|
|
|
</Link>
|
|
|
|
</Center>
|
2024-03-15 01:05:28 +01:00
|
|
|
<Button variant="primary" type="submit" isLoading={isSubmitting}>
|
2024-02-12 16:46:51 -05:00
|
|
|
Log In
|
|
|
|
</Button>
|
|
|
|
</Container>
|
|
|
|
</>
|
2024-03-08 14:58:36 +01:00
|
|
|
)
|
|
|
|
}
|