feat(login): add LoginView + router

This commit is contained in:
Marc 2023-12-27 20:25:55 -05:00
parent 7b7544c78e
commit 5aa82b88af
Signed by: marc
GPG key ID: 048E042F22B5DC79
3 changed files with 187 additions and 0 deletions

View file

@ -14,6 +14,7 @@ import { Router, Route } from "./router"
import FileListView from "./components/FileListView"
import RegisterView from "./components/RegisterView"
import LoginView from "./components/LoginView"
const routeLabels = {
ITEM_DETAILS: "item-details",
@ -43,6 +44,9 @@ const App = () => {
<Route path="/register">
<RegisterView />
</Route>
<Route path="/login">
<LoginView />
</Route>
</Router>
</Box>
</Box>

View file

@ -0,0 +1,82 @@
import { render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { QueryClientProvider, QueryClient } from "@tanstack/react-query"
import AxiosMockAdapter from "axios-mock-adapter"
import axios from "../../axios"
import LoginView from "."
function renderComponent() {
return {
...render(
<QueryClientProvider client={new QueryClient()}>
<LoginView />
</QueryClientProvider>,
),
user: userEvent.setup(),
}
}
describe("LoginView", () => {
it("renders an email and password field", () => {
renderComponent()
expect(screen.getByLabelText("Email")).toBeInTheDocument()
expect(
screen.getByLabelText(/email address login input/i),
).toBeInTheDocument()
expect(screen.getByLabelText("Password")).toBeInTheDocument()
expect(screen.getByLabelText(/password login input/i)).toBeInTheDocument()
})
it("renders a submit button", () => {
renderComponent()
expect(
screen.getByText("Log in", { selector: "button" }),
).toBeInTheDocument()
expect(screen.getByLabelText(/submit login/i)).toBeInTheDocument()
})
it("renders a registration link", async () => {
const mock = jest.fn()
const { user } = renderComponent()
expect(screen.getByText(/don\'t have an account yet?/i)).toBeInTheDocument()
const registrationLink = screen.getByText(/create one/i)
expect(registrationLink).toBeInTheDocument()
expect(registrationLink.getAttribute("href")).toEqual("/register")
})
it("sends a request to the authentication API on submit", async () => {
const axiosMockAdapter = new AxiosMockAdapter(axios)
axiosMockAdapter.onPost("/auth/session/").reply(201, { token: "testtoken" })
const { user } = renderComponent()
const testInput = {
username: "test@domain.com",
password: "password",
}
const emailInput = screen.getByLabelText(/email address login input/i)
await user.type(emailInput, testInput.username)
const passwordInput = screen.getByLabelText(/password login input/i)
await user.type(passwordInput, testInput.password)
const submitButton = screen.getByText("Log in", { selector: "button" })
await user.click(submitButton)
expect(axiosMockAdapter.history.post.length).toEqual(1)
const requestBody = JSON.parse(axiosMockAdapter.history.post[0].data)
expect(requestBody).toEqual(testInput)
})
})

View file

@ -0,0 +1,101 @@
import React from "react"
import { useMutation } from "@tanstack/react-query"
import Typography from "@mui/material/Typography"
import Box from "@mui/material/Box"
import FormGroup from "@mui/material/FormGroup"
import FormControl from "@mui/material/FormControl"
import TextField from "@mui/material/TextField"
import InputLabel from "@mui/material/InputLabel"
import Button from "@mui/material/Button"
import Link from "@mui/material/Link"
import axiosWithDefaults from "../../axios"
import TextInput from "../TextInput"
function LoginView() {
const [emailAddress, setEmailAddress] = React.useState<string>("")
const [password, setPassword] = React.useState<string>("")
const { mutate } = useMutation({
mutationFn: async ({
email,
password,
}: { email: string; password: string }) => {
const response = await axiosWithDefaults.post("/auth/session/", {
username: email,
password,
})
return response
},
})
const emailField = React.useMemo(
() => (
<TextInput
label="Email"
ariaLabel="email address login input"
onChange={setEmailAddress}
value={emailAddress}
inputType="email"
/>
),
[emailAddress, setEmailAddress],
)
const passwordField = React.useMemo(
() => (
<TextInput
label="Password"
ariaLabel="password login input"
onChange={setPassword}
value={password}
inputType="password"
/>
),
[setPassword, password],
)
const isFormValid = React.useMemo(() => {
return Boolean(emailAddress) && Boolean(password)
}, [emailAddress, password])
const onLoginClick = React.useCallback(() => {
if (!isFormValid) return
mutate({ email: emailAddress, password })
}, [mutate, emailAddress, password, isFormValid])
return (
<Box sx={{ display: "flex", justifyContent: "center", width: "100%" }}>
<FormGroup
sx={{
flexGrow: 0.1,
display: "flex",
gap: "10px",
textAlign: "center",
}}
>
<Typography variant="h1" sx={{ fontSize: "2rem" }}>
Log in
</Typography>
{emailField}
{passwordField}
<Button
variant="contained"
onClick={onLoginClick}
aria-label="submit login"
disabled={!isFormValid}
>
Log in
</Button>
<Typography>
Don't have an account yet? <Link href="/register">Create one!</Link>
</Typography>
</FormGroup>
</Box>
)
}
export default LoginView