Skip to content

Commit 7512bf9

Browse files
authored
feat: Contributor frontend 11 (#66)
* fix: session state * export * fix: use session metadata * fix: export session slice * indexes * api not func * fix: rename env to settings * fix: unused arg * reinstate auth utils
1 parent 204a118 commit 7512bf9

File tree

12 files changed

+131
-42
lines changed

12 files changed

+131
-42
lines changed

.eslintrc.json

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,6 @@
2626
{
2727
"fixStyle": "inline-type-imports"
2828
}
29-
],
30-
"@typescript-eslint/no-restricted-imports": [
31-
2,
32-
{
33-
"paths": [
34-
{
35-
"name": "react-redux",
36-
"importNames": [
37-
"useSelector",
38-
"useStore",
39-
"useDispatch"
40-
],
41-
"message": "Please use pre-typed versions from `src/app/hooks.ts` instead."
42-
}
43-
]
44-
}
4529
]
4630
},
4731
"overrides": [

src/api/createApi.ts

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import {
33
fetchBaseQuery,
44
} from "@reduxjs/toolkit/query/react"
55

6-
import { SERVICE_API_URL } from "../env"
7-
import { getCsrfCookie, logout } from "../utils/auth"
6+
import { SERVICE_API_URL } from "../settings"
87
import defaultTagTypes from "./tagTypes"
8+
import { buildLogoutEndpoint } from "./endpoints/session"
9+
import { getCsrfCookie } from "../utils/auth"
910

1011
// TODO: decide if we want to keep any of this.
1112
// export function handleResponseError(error: FetchBaseQueryError): void {
@@ -72,25 +73,12 @@ export default function createApi<TagTypes extends string = never>({
7273
return await fetch(args, api, extraOptions)
7374
},
7475
tagTypes: [...defaultTagTypes, ...tagTypes],
76+
endpoints: () => ({}),
77+
})
78+
79+
return api.injectEndpoints({
7580
endpoints: build => ({
76-
logout: build.mutation<null, null>({
77-
query: () => ({
78-
url: "session/logout/",
79-
method: "POST",
80-
}),
81-
async onQueryStarted(_, { dispatch, queryFulfilled }) {
82-
try {
83-
await queryFulfilled
84-
} catch (error) {
85-
console.error("Failed to call logout endpoint...", error)
86-
} finally {
87-
logout()
88-
dispatch(api.util.resetApiState())
89-
}
90-
},
91-
}),
81+
logout: buildLogoutEndpoint<null, null>(api, build),
9282
}),
9383
})
94-
95-
return api
9684
}

src/api/endpoints/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ export * from "./klass"
44
export { default as getReadClassEndpoints } from "./klass"
55
export * from "./school"
66
export { default as getReadSchoolEndpoints } from "./school"
7+
export * from "./session"
78
export * from "./user"
89
export { default as getReadUserEndpoints } from "./user"

src/api/endpoints/session.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { type EndpointBuilder, type Api } from "@reduxjs/toolkit/query/react"
2+
3+
import { login, logout } from "../../slices/session"
4+
5+
export function buildLoginEndpoint<ResultType, QueryArg>(
6+
build: EndpointBuilder<any, any, any>,
7+
url: string = "session/login/",
8+
) {
9+
return build.mutation<ResultType, QueryArg>({
10+
query: body => ({ url, method: "POST", body }),
11+
async onQueryStarted(_, { dispatch, queryFulfilled }) {
12+
try {
13+
await queryFulfilled
14+
dispatch(login())
15+
} catch (error) {
16+
console.error("Failed to call login endpoint...", error)
17+
}
18+
},
19+
})
20+
}
21+
22+
export function buildLogoutEndpoint<ResultType, QueryArg>(
23+
api: Api<any, any, any, any, any>,
24+
build: EndpointBuilder<any, any, any>,
25+
url: string = "session/logout/",
26+
) {
27+
return build.mutation<ResultType, QueryArg>({
28+
query: () => ({ url, method: "POST" }),
29+
async onQueryStarted(_, { dispatch, queryFulfilled }) {
30+
try {
31+
await queryFulfilled
32+
} catch (error) {
33+
console.error("Failed to call logout endpoint...", error)
34+
} finally {
35+
dispatch(logout())
36+
dispatch(api.util.resetApiState())
37+
}
38+
},
39+
})
40+
}

src/hooks/auth.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import Cookies from "js-cookie"
22
import { useEffect, type ReactNode } from "react"
33
import { createSearchParams, useLocation, useNavigate } from "react-router-dom"
4+
import { useSelector } from "react-redux"
45

56
import { type AuthFactor, type User } from "../api"
7+
import { SESSION_METADATA_COOKIE_NAME } from "../settings"
8+
import { selectIsLoggedIn } from "../slices/session"
69

710
export interface SessionMetadata {
811
user_id: User["id"]
@@ -12,9 +15,11 @@ export interface SessionMetadata {
1215
}
1316

1417
export function useSessionMetadata(): SessionMetadata | undefined {
15-
const sessionMetadata = Cookies.get("session_metadata")
16-
17-
return sessionMetadata ? JSON.parse(sessionMetadata) : undefined
18+
return useSelector(selectIsLoggedIn)
19+
? (JSON.parse(
20+
Cookies.get(SESSION_METADATA_COOKIE_NAME)!,
21+
) as SessionMetadata)
22+
: undefined
1823
}
1924

2025
export type UseSessionChildrenFunction<Required extends boolean> = (

src/middlewares/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./session"

src/middlewares/session.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { type Middleware, isAction } from "@reduxjs/toolkit"
2+
3+
import { logout } from "../utils/auth"
4+
5+
export const logoutMiddleware: Middleware = _ => next => action => {
6+
const response = next(action)
7+
8+
// The backend should delete these cookie upon calling the logout endpoint.
9+
// However, as a precaution, we also delete the session cookies in case the
10+
// backend fails to delete the cookies.
11+
if (isAction(action) && action.type === "session/logout") {
12+
logout()
13+
}
14+
15+
return response
16+
}

src/env.ts renamed to src/settings.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,9 @@ export const SERVICE_BASE_URL =
2828

2929
// The api url of the current service.
3030
export const SERVICE_API_URL = `${SERVICE_BASE_URL}/api`
31+
32+
// The names of cookies.
33+
export const CSRF_COOKIE_NAME = `${SERVICE_NAME}_csrftoken`
34+
export const SESSION_COOKIE_NAME = env.VITE_SESSION_COOKIE_NAME ?? "session_key"
35+
export const SESSION_METADATA_COOKIE_NAME =
36+
env.VITE_SESSION_METADATA_COOKIE_NAME ?? "session_metadata"

src/slices/createSlice.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { asyncThunkCreator, buildCreateSlice } from "@reduxjs/toolkit"
2+
3+
// `buildCreateSlice` allows us to create a slice with async thunks.
4+
const createSlice = buildCreateSlice({
5+
creators: { asyncThunk: asyncThunkCreator },
6+
})
7+
8+
export default createSlice

src/slices/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as createSlice } from "./createSlice"
2+
export { default as sessionSlice, type SessionState } from "./session"

0 commit comments

Comments
 (0)