refactor(fe-auth): move login login to useLogin query hook
docs: additional documentation for auth hooks wip: useCurrentUser hook documentation and coverage
This commit is contained in:
parent
39d71e2032
commit
68f34deeee
4 changed files with 123 additions and 23 deletions
|
@ -1,5 +1,4 @@
|
|||
import React from "react"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
|
||||
import Typography from "@mui/material/Typography"
|
||||
import Box from "@mui/material/Box"
|
||||
|
@ -11,29 +10,14 @@ import Button from "@mui/material/Button"
|
|||
import Link from "@mui/material/Link"
|
||||
import Alert from "@mui/material/Alert"
|
||||
|
||||
import { useLogin } from "../../queries/auth"
|
||||
import axiosWithDefaults from "../../axios"
|
||||
import TextInput from "../TextInput"
|
||||
import { useLocationContext } from "../../contexts/LocationContext"
|
||||
|
||||
function LoginView() {
|
||||
const [emailAddress, setEmailAddress] = React.useState<string>("")
|
||||
const [password, setPassword] = React.useState<string>("")
|
||||
const { navigate } = useLocationContext()
|
||||
|
||||
const { mutate, isError, isPending } = useMutation({
|
||||
mutationFn: async ({
|
||||
email,
|
||||
password,
|
||||
}: { email: string; password: string }) => {
|
||||
return axiosWithDefaults.post("/auth/session/", {
|
||||
username: email,
|
||||
password,
|
||||
})
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
navigate("/")
|
||||
},
|
||||
})
|
||||
const { login, isError, isPending } = useLogin()
|
||||
|
||||
const emailField = React.useMemo(
|
||||
() => (
|
||||
|
@ -68,8 +52,8 @@ function LoginView() {
|
|||
const onLoginClick = React.useCallback(() => {
|
||||
if (!isFormValid) return
|
||||
|
||||
mutate({ email: emailAddress, password })
|
||||
}, [mutate, emailAddress, password, isFormValid])
|
||||
login({ email: emailAddress, password })
|
||||
}, [login, emailAddress, password, isFormValid])
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex", justifyContent: "center", width: "100%" }}>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* affect the user's session.
|
||||
*
|
||||
*/
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { useQueryClient, useMutation } from "@tanstack/react-query"
|
||||
import { useLocationContext } from "../contexts/LocationContext"
|
||||
import axiosWithDefaults from "../axios"
|
||||
|
||||
|
@ -15,14 +15,19 @@ import axiosWithDefaults from "../axios"
|
|||
* Using `logout` will instruct the application to invalidate
|
||||
* the current authentication token and will redirect the user
|
||||
* to the login page.
|
||||
*
|
||||
* On success, the current-user query is invalidated so that the
|
||||
* application is made aware that the user is no longer authenticated.
|
||||
*/
|
||||
function useLogout() {
|
||||
const { navigate } = useLocationContext()
|
||||
const queryClient = useQueryClient()
|
||||
const logoutMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
return axiosWithDefaults.delete("/auth/session/")
|
||||
},
|
||||
onSuccess: () => {
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["current-user"] })
|
||||
navigate("/login")
|
||||
},
|
||||
})
|
||||
|
@ -36,8 +41,37 @@ function useLogout() {
|
|||
}
|
||||
}
|
||||
|
||||
export { useLogout }
|
||||
/*
|
||||
* Handles the log-in interaction.
|
||||
*
|
||||
* Using `login` will send a request to create a session and on success,
|
||||
* refetch the current user data.
|
||||
*/
|
||||
function useLogin() {
|
||||
const queryClient = useQueryClient()
|
||||
const { navigate } = useLocationContext()
|
||||
const { mutate, isError, failureReason, isPending } = useMutation({
|
||||
mutationFn: async ({
|
||||
email,
|
||||
password,
|
||||
}: { email: string; password: string }) => {
|
||||
return axiosWithDefaults.post("/auth/session/", {
|
||||
username: email,
|
||||
password,
|
||||
})
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await queryClient.refetchQueries({ queryKey: ["current-user"] })
|
||||
navigate("/")
|
||||
},
|
||||
})
|
||||
|
||||
return { login: mutate, isError, isPending }
|
||||
}
|
||||
|
||||
export { useLogout, useLogin }
|
||||
|
||||
export default {
|
||||
useLogout,
|
||||
useLogin,
|
||||
}
|
||||
|
|
54
frontend/src/queries/user.test.tsx
Normal file
54
frontend/src/queries/user.test.tsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
import React from "react"
|
||||
import { describe, it, expect, vi } from "vitest"
|
||||
import { renderHook, waitFor } from "@testing-library/react"
|
||||
import { QueryClientProvider, QueryClient } from "@tanstack/react-query"
|
||||
import AxiosMockAdapter from "axios-mock-adapter"
|
||||
|
||||
import axiosWithDefaults from "../axios"
|
||||
import { useCurrentUser } from "./user"
|
||||
|
||||
function WithProviders({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<QueryClientProvider client={new QueryClient()}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
describe("useCurrentUser", () => {
|
||||
it("sends a request to the currentUser api", async () => {
|
||||
const axios = new AxiosMockAdapter(axiosWithDefaults)
|
||||
|
||||
axios.onGet("/auth/user/").reply(204)
|
||||
|
||||
const { result } = renderHook(() => useCurrentUser(), {
|
||||
wrapper: WithProviders,
|
||||
})
|
||||
|
||||
await waitFor(() => expect(axios.history.get.length).toEqual(1))
|
||||
|
||||
const getRequest = axios.history.get[0]
|
||||
|
||||
expect(getRequest.url).toEqual("/auth/user/")
|
||||
})
|
||||
|
||||
it.each`
|
||||
isAuthenticated | returnCode
|
||||
${false} | ${403}
|
||||
${true} | ${200}
|
||||
`(
|
||||
"sets isAuthenticated ($isAuthenticated) when the api returns $returnCode",
|
||||
async ({ isAuthenticated, returnCode }) => {
|
||||
const axios = new AxiosMockAdapter(axiosWithDefaults)
|
||||
|
||||
axios.onGet("/auth/user/").reply(returnCode, { status: returnCode })
|
||||
|
||||
const { result } = renderHook(() => useCurrentUser(), {
|
||||
wrapper: WithProviders,
|
||||
})
|
||||
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false))
|
||||
expect(result.current.isAuthenticated).toBe(isAuthenticated)
|
||||
},
|
||||
)
|
||||
})
|
28
frontend/src/queries/user.ts
Normal file
28
frontend/src/queries/user.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { useQuery } from "@tanstack/react-query"
|
||||
|
||||
import axiosWithDefaults from "../axios"
|
||||
|
||||
/*
|
||||
* Current user data fetch.
|
||||
*
|
||||
* This hook fetches data about the currently-authenticated user
|
||||
* if there is one and returns basic information about them as well
|
||||
* as a `isAuthenticated` flag that determines if we are logged in or not.
|
||||
*/
|
||||
function useCurrentUser() {
|
||||
const { data, isLoading, isError, isSuccess } = useQuery({
|
||||
queryKey: ["current-user"],
|
||||
queryFn: async () => {
|
||||
return axiosWithDefaults.get("/auth/user/")
|
||||
},
|
||||
retry: false,
|
||||
})
|
||||
|
||||
const isAuthenticated = !isLoading && isSuccess && data.status === 200
|
||||
|
||||
return { currentUser: data, isSuccess, isError, isLoading, isAuthenticated }
|
||||
}
|
||||
|
||||
export { useCurrentUser }
|
||||
|
||||
export default { useCurrentUser }
|
Reference in a new issue