Files
full-stack-fastapi-template/frontend/src/routes/login.tsx

137 lines
3.3 KiB
TypeScript
Raw Normal View History

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 React from "react"
import { type SubmitHandler, useForm } from "react-hook-form"
2024-01-23 11:48:56 -05:00
2024-03-17 17:28:45 +01:00
import Logo from "../assets/images/fastapi-logo.svg"
import type { ApiError } from "../client"
import type { Body_login_login_access_token as AccessToken } from "../client/models/Body_login_login_access_token"
import useAuth, { isLoggedIn } from "../hooks/useAuth"
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")({
component: Login,
beforeLoad: async () => {
if (isLoggedIn()) {
throw redirect({
2024-03-17 17:28:45 +01:00
to: "/",
})
}
2024-03-08 14:58:36 +01:00
},
})
function Login() {
2024-03-08 14:58:36 +01:00
const [show, setShow] = useBoolean()
const { login } = useAuth()
const [error, setError] = React.useState<string | null>(null)
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<AccessToken>({
2024-03-17 17:28:45 +01:00
mode: "onBlur",
criteriaMode: "all",
defaultValues: {
2024-03-17 17:28:45 +01:00
username: "",
password: "",
2024-03-08 14:58:36 +01:00
},
})
2024-01-23 11:48:56 -05:00
const onSubmit: SubmitHandler<AccessToken> = async (data) => {
try {
2024-03-08 14:58:36 +01:00
await login(data)
} catch (err) {
2024-03-08 14:58:36 +01:00
const errDetail = (err as ApiError).body.detail
setError(errDetail)
}
2024-03-08 14:58:36 +01:00
}
2024-01-23 11:48:56 -05:00
return (
<>
<Container
2024-03-08 14:58:36 +01:00
as="form"
onSubmit={handleSubmit(onSubmit)}
2024-03-08 14:58:36 +01:00
h="100vh"
maxW="sm"
alignItems="stretch"
justifyContent="center"
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", {
pattern: emailPattern,
2024-03-08 14:58:36 +01:00
})}
placeholder="Email"
2024-03-11 16:50:46 +01:00
type="email"
2024-03-08 14:58:36 +01:00
/>
{errors.username && (
<FormErrorMessage>{errors.username.message}</FormErrorMessage>
)}
</FormControl>
2024-03-08 14:58:36 +01:00
<FormControl id="password" isInvalid={!!error}>
<InputGroup>
<Input
2024-03-17 17:28:45 +01:00
{...register("password")}
type={show ? "text" : "password"}
2024-03-08 14:58:36 +01:00
placeholder="Password"
/>
<InputRightElement
2024-03-08 14:58:36 +01:00
color="gray.400"
_hover={{
2024-03-17 17:28:45 +01:00
cursor: "pointer",
}}
>
2024-03-08 14:58:36 +01:00
<Icon
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
>
{show ? <ViewOffIcon /> : <ViewIcon />}
</Icon>
</InputRightElement>
</InputGroup>
2024-03-08 14:58:36 +01:00
{error && <FormErrorMessage>{error}</FormErrorMessage>}
</FormControl>
<Center>
2024-03-08 14:58:36 +01:00
<Link as={RouterLink} to="/recover-password" color="blue.500">
Forgot password?
</Link>
</Center>
<Button variant="primary" type="submit" isLoading={isSubmitting}>
Log In
</Button>
</Container>
</>
2024-03-08 14:58:36 +01:00
)
}
2024-01-23 11:48:56 -05:00
2024-03-08 14:58:36 +01:00
export default Login