Skip to content

Commit

Permalink
feat: template frontend 1 (#54)
Browse files Browse the repository at this point in the history
* sync config

* app

* add router, header and footer

* routes

* model urls

* create api

* fix import

* fix env vars

* default service name

* simplify exports

* handle query state

* fix handleQueryState

* use params required

* on validation success

* get location inside of router

* fix rerenders

* reuse query state handler

* fix api caching

* fix

* prefer cache value

* tagid to modelid

* fix dependencies

* fix type

* add deps

* sync

* fix import

* remove new deps

* fix errors
  • Loading branch information
SKairinos committed Aug 16, 2024
1 parent cdd023d commit 6090daf
Show file tree
Hide file tree
Showing 25 changed files with 500 additions and 571 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"root": true,
"ignorePatterns": [
"dist",
"src/scripts"
"src/scripts",
"**/*.d.ts",
"vite.config.js"
],
"rules": {
"@typescript-eslint/consistent-type-imports": [
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

# production
/build
*.tsbuildinfo

# misc
.DS_Store
Expand All @@ -21,3 +22,7 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Custom
vite.config.d.ts
vite.config.js
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@
"pipenv"
],
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
"source.fixAll.eslint": "always",
"source.organizeImports": "never"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.rulers": [
80
],
"editor.tabSize": 2,
"files.exclude": {
"**/*.tsbuildinfo": true
},
"javascript.format.semicolons": "remove",
"javascript.preferences.quoteStyle": "double",
"prettier.configPath": ".prettierrc.json",
Expand Down
19 changes: 15 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
{
"//": [
"Based off of:",
"https://github.com/vitejs/vite/blob/main/packages/create-vite/template-react-ts/package.json",
"Dependency rules:",
"`peerDependencies` should contain everything required to build and test a",
"service's front end.",
"TODO: make devDependencies the same as peerDependencies"
],
"name": "codeforlife",
"description": "Common frontend code",
"private": true,
Expand Down Expand Up @@ -47,10 +55,6 @@
"serve": "^14.2.3",
"yup": "^1.1.1"
},
"//": [
"`peerDependencies` should contain everything required to build and test a",
"service's front end."
],
"devDependencies": {
"@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^6.2.0",
Expand All @@ -75,10 +79,12 @@
"vitest": "^1.2.0"
},
"peerDependencies": {
"@eslint/js": "^9.9.0",
"@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^6.2.0",
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.12",
"@types/js-cookie": "^3.0.3",
"@types/node": "^20.14.2",
"@types/qs": "^6.9.7",
Expand All @@ -91,9 +97,14 @@
"eslint-config-prettier": "^9.1.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"jsdom": "^23.2.0",
"prettier": "^3.2.1",
"typescript": "^5.3.3",
"typescript-eslint": "^8.1.0",
"vite": "^5.0.11",
"vitest": "^1.2.0"
},
Expand Down
131 changes: 0 additions & 131 deletions src/api/baseQuery.ts

This file was deleted.

96 changes: 96 additions & 0 deletions src/api/createApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {
createApi as _createApi,
fetchBaseQuery,
} from "@reduxjs/toolkit/query/react"

import { SERVICE_API_URL } from "../env"
import { getCsrfCookie, logout } from "../utils/auth"
import defaultTagTypes from "./tagTypes"

// TODO: decide if we want to keep any of this.
// export function handleResponseError(error: FetchBaseQueryError): void {
// if (
// error.status === 400 &&
// typeof error.data === "object" &&
// error.data !== null
// ) {
// // Parse the error's data from snake_case to camelCase.
// snakeCaseToCamelCase(error.data)
// } else if (error.status === 401) {
// // TODO: redirect to appropriate login page based on user type.
// window.location.href = `${PORTAL_BASE_URL}/login/teacher`
// } else {
// // Catch-all error pages by status-code.
// window.location.href = `${PORTAL_BASE_URL}/error/${
// [403, 404].includes(error.status as number) ? error.status : 500
// }`
// }
// }

export default function createApi<TagTypes extends string = never>({
tagTypes = [],
}: {
tagTypes?: readonly TagTypes[]
} = {}) {
const fetch = fetchBaseQuery({
baseUrl: `${SERVICE_API_URL}/`,
credentials: "include",
prepareHeaders: (headers, { type }) => {
if (type === "mutation") {
let csrfToken = getCsrfCookie()
if (csrfToken) headers.set("x-csrftoken", csrfToken)
}

return headers
},
})

const api = _createApi({
// https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#implementing-a-custom-basequery
baseQuery: async (args, api, extraOptions) => {
if (api.type === "mutation" && getCsrfCookie() === undefined) {
// Get the CSRF token.
const { error } = await fetch(
{ url: "/csrf/cookie", method: "GET" },
api,
{},
)

// Validate we got the CSRF token.
if (error !== undefined) {
console.error(error)
// TODO
// window.location.href = `${PORTAL_BASE_URL}/error/500`
}
if (getCsrfCookie() === undefined) {
// TODO
// window.location.href = `${PORTAL_BASE_URL}/error/500`
}
}

// Send the HTTP request and fetch the response.
return await fetch(args, api, extraOptions)
},
tagTypes: [...defaultTagTypes, ...tagTypes],
endpoints: build => ({
logout: build.mutation<null, null>({
query: () => ({
url: "session/logout/",
method: "POST",
}),
async onQueryStarted(_, { dispatch, queryFulfilled }) {
try {
await queryFulfilled
} catch (error) {
console.error("Failed to call logout endpoint...", error)
} finally {
logout()
dispatch(api.util.resetApiState())
}
},
}),
}),
})

return api
}
55 changes: 8 additions & 47 deletions src/api/endpoints/index.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,8 @@
import getReadAuthFactorEndpoints, {
type ListAuthFactorsArg,
type ListAuthFactorsResult,
AUTH_FACTOR_TAG,
} from "./authFactor"
import getReadClassEndpoints, {
type ListClassesArg,
type ListClassesResult,
type RetrieveClassArg,
type RetrieveClassResult,
CLASS_TAG,
} from "./klass"
import getReadSchoolEndpoints, {
type RetrieveSchoolArg,
type RetrieveSchoolResult,
SCHOOL_TAG,
} from "./school"
import getReadUserEndpoints, {
type ListUsersArg,
type ListUsersResult,
type RetrieveUserArg,
type RetrieveUserResult,
USER_TAG,
} from "./user"

export {
AUTH_FACTOR_TAG,
CLASS_TAG,
getReadAuthFactorEndpoints,
getReadClassEndpoints,
getReadSchoolEndpoints,
getReadUserEndpoints,
SCHOOL_TAG,
USER_TAG,
type ListAuthFactorsArg,
type ListAuthFactorsResult,
type ListClassesArg,
type ListClassesResult,
type ListUsersArg,
type ListUsersResult,
type RetrieveClassArg,
type RetrieveClassResult,
type RetrieveSchoolArg,
type RetrieveSchoolResult,
type RetrieveUserArg,
type RetrieveUserResult,
}
export * from "./authFactor"
export { default as getReadAuthFactorEndpoints } from "./authFactor"
export * from "./klass"
export { default as getReadClassEndpoints } from "./klass"
export * from "./school"
export { default as getReadSchoolEndpoints } from "./school"
export * from "./user"
export { default as getReadUserEndpoints } from "./user"
Loading

0 comments on commit 6090daf

Please sign in to comment.