codeforlife 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +47 -0
- package/.github/workflows/contributing.yaml +8 -0
- package/.github/workflows/main.yml +36 -0
- package/.prettierignore +5 -0
- package/.prettierrc.json +4 -0
- package/.vscode/launch.json +22 -0
- package/.vscode/settings.json +30 -0
- package/CHANGELOG.md +1864 -0
- package/CONTRIBUTING.md +3 -0
- package/LICENSE.md +3 -0
- package/README.md +94 -0
- package/codecov.yml +11 -0
- package/package.json +139 -0
- package/src/api/createApi.ts +84 -0
- package/src/api/endpoints/authFactor.ts +31 -0
- package/src/api/endpoints/index.ts +9 -0
- package/src/api/endpoints/klass.ts +87 -0
- package/src/api/endpoints/school.ts +34 -0
- package/src/api/endpoints/session.ts +40 -0
- package/src/api/endpoints/user.ts +70 -0
- package/src/api/index.ts +4 -0
- package/src/api/models.ts +144 -0
- package/src/api/tagTypes.ts +12 -0
- package/src/api/urls.ts +13 -0
- package/src/components/App.css +38 -0
- package/src/components/App.tsx +150 -0
- package/src/components/ClickableTooltip.tsx +43 -0
- package/src/components/CopyIconButton.test.tsx +16 -0
- package/src/components/CopyIconButton.tsx +27 -0
- package/src/components/Countdown.tsx +42 -0
- package/src/components/ElevatedAppBar.tsx +41 -0
- package/src/components/Image.tsx +41 -0
- package/src/components/InputFileButton.tsx +27 -0
- package/src/components/ItemizedList.tsx +61 -0
- package/src/components/OrderedGrid.tsx +92 -0
- package/src/components/ScrollIntoViewLink.tsx +23 -0
- package/src/components/SyncError.tsx +14 -0
- package/src/components/TablePagination.tsx +132 -0
- package/src/components/YouTubeVideo.tsx +26 -0
- package/src/components/form/ApiAutocompleteField.tsx +180 -0
- package/src/components/form/AutocompleteField.tsx +124 -0
- package/src/components/form/CheckboxField.tsx +81 -0
- package/src/components/form/CountryField.tsx +68 -0
- package/src/components/form/DatePickerField.tsx +119 -0
- package/src/components/form/EmailField.tsx +38 -0
- package/src/components/form/FirstNameField.tsx +40 -0
- package/src/components/form/Form.tsx +82 -0
- package/src/components/form/OtpField.tsx +28 -0
- package/src/components/form/PasswordField.tsx +71 -0
- package/src/components/form/RepeatField.tsx +115 -0
- package/src/components/form/SubmitButton.tsx +47 -0
- package/src/components/form/TextField.tsx +103 -0
- package/src/components/form/UkCountyField.tsx +67 -0
- package/src/components/form/index.tsx +28 -0
- package/src/components/index.ts +26 -0
- package/src/components/page/Banner.tsx +84 -0
- package/src/components/page/Notification.tsx +71 -0
- package/src/components/page/Page.tsx +73 -0
- package/src/components/page/Section.tsx +21 -0
- package/src/components/page/TabBar.tsx +131 -0
- package/src/components/page/index.ts +10 -0
- package/src/components/router/Link.tsx +22 -0
- package/src/components/router/LinkButton.tsx +21 -0
- package/src/components/router/LinkIconButton.tsx +21 -0
- package/src/components/router/LinkListItem.tsx +21 -0
- package/src/components/router/LinkTab.tsx +21 -0
- package/src/components/router/Navigate.tsx +33 -0
- package/src/components/router/index.tsx +12 -0
- package/src/components/table/CellStack.tsx +19 -0
- package/src/components/table/Table.tsx +55 -0
- package/src/components/table/index.tsx +10 -0
- package/src/features/InactiveDialog.tsx +40 -0
- package/src/features/ScreenTimeDialog.tsx +33 -0
- package/src/features/index.ts +4 -0
- package/src/fonts/Inter-VariableFont_slnt,wght.ttf +0 -0
- package/src/fonts/SpaceGrotesk-VariableFont_wght.ttf +0 -0
- package/src/hooks/api.tsx +37 -0
- package/src/hooks/auth.tsx +87 -0
- package/src/hooks/general.ts +110 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/router.tsx +168 -0
- package/src/index.ts +2 -0
- package/src/middlewares/index.ts +1 -0
- package/src/middlewares/session.ts +16 -0
- package/src/public/images/brain.svg +1 -0
- package/src/schemas/user.ts +4 -0
- package/src/scripts/freshDesk.js +473 -0
- package/src/scripts/index.ts +1 -0
- package/src/server.js +181 -0
- package/src/settings/custom.ts +22 -0
- package/src/settings/index.ts +5 -0
- package/src/settings/vite.ts +26 -0
- package/src/setupTests.ts +1 -0
- package/src/slices/createSlice.ts +8 -0
- package/src/slices/index.ts +2 -0
- package/src/slices/session.ts +32 -0
- package/src/theme/ThemedBox.tsx +265 -0
- package/src/theme/colors.ts +57 -0
- package/src/theme/components/MuiAccordion.tsx +13 -0
- package/src/theme/components/MuiAutocomplete.tsx +11 -0
- package/src/theme/components/MuiButton.ts +70 -0
- package/src/theme/components/MuiCardActions.tsx +12 -0
- package/src/theme/components/MuiCheckbox.ts +12 -0
- package/src/theme/components/MuiContainer.ts +19 -0
- package/src/theme/components/MuiDialog.tsx +16 -0
- package/src/theme/components/MuiFormControlLabel.ts +18 -0
- package/src/theme/components/MuiFormHelperText.ts +12 -0
- package/src/theme/components/MuiGrid2.ts +16 -0
- package/src/theme/components/MuiInputBase.ts +14 -0
- package/src/theme/components/MuiLink.ts +41 -0
- package/src/theme/components/MuiList.ts +12 -0
- package/src/theme/components/MuiListItemText.ts +18 -0
- package/src/theme/components/MuiMenu.ts +14 -0
- package/src/theme/components/MuiMenuItem.ts +15 -0
- package/src/theme/components/MuiSelect.ts +16 -0
- package/src/theme/components/MuiTab.ts +29 -0
- package/src/theme/components/MuiTable.ts +29 -0
- package/src/theme/components/MuiTableBody.ts +15 -0
- package/src/theme/components/MuiTableHead.ts +26 -0
- package/src/theme/components/MuiTabs.ts +26 -0
- package/src/theme/components/MuiTextField.ts +86 -0
- package/src/theme/components/MuiToolbar.ts +11 -0
- package/src/theme/components/MuiTypography.ts +12 -0
- package/src/theme/components/_components.ts +93 -0
- package/src/theme/components/index.ts +57 -0
- package/src/theme/index.ts +25 -0
- package/src/theme/palette.ts +98 -0
- package/src/theme/spacing.ts +8 -0
- package/src/theme/typography.ts +101 -0
- package/src/utils/api.test.ts +19 -0
- package/src/utils/api.tsx +327 -0
- package/src/utils/auth.ts +17 -0
- package/src/utils/form.ts +153 -0
- package/src/utils/general.test.ts +42 -0
- package/src/utils/general.ts +498 -0
- package/src/utils/router.test.ts +156 -0
- package/src/utils/router.ts +67 -0
- package/src/utils/schema.ts +80 -0
- package/src/utils/store.ts +31 -0
- package/src/utils/test.tsx +82 -0
- package/src/utils/theme.tsx +82 -0
- package/src/utils/window.ts +9 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +31 -0
- package/tsconfig.node.json +11 -0
- package/types/fixes.d.ts +18 -0
- package/vite.config.ts +21 -0
package/CONTRIBUTING.md
ADDED
package/LICENSE.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# codeforlife-package-javascript
|
|
2
|
+
|
|
3
|
+
This repo hosts Code for Life's Node.js package. This package contains all
|
|
4
|
+
reusable frontend code, meant to be installed across CFL's various frontend
|
|
5
|
+
services.
|
|
6
|
+
|
|
7
|
+
## LICENCE
|
|
8
|
+
In accordance with the [Terms of Use](https://www.codeforlife.education/terms#terms)
|
|
9
|
+
of the Code for Life website, all copyright, trademarks, and other
|
|
10
|
+
intellectual property rights in and relating to Code for Life (including all
|
|
11
|
+
content of the Code for Life website, the Rapid Router application, the
|
|
12
|
+
Kurono application, related software (including any drawn and/or animated
|
|
13
|
+
avatars, whether or not such avatars have any modifications) and any other
|
|
14
|
+
games, applications or any other content that we make available from time to
|
|
15
|
+
time) are owned by Ocado Innovation Limited.
|
|
16
|
+
|
|
17
|
+
The source code of the Code for Life portal, the Rapid Router application
|
|
18
|
+
and the Kurono/aimmo application are [licensed under the GNU Affero General
|
|
19
|
+
Public License](https://github.com/ocadotechnology/codeforlife-workspace/blob/main/LICENSE.md).
|
|
20
|
+
All other assets including images, logos, sounds etc., are not covered by
|
|
21
|
+
this licence and no-one may copy, modify, distribute, show in public or
|
|
22
|
+
create any derivative work from these assets.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
To install this package, do one of the following options.
|
|
27
|
+
|
|
28
|
+
*Remember to replace the version number ("0.0.0") with your
|
|
29
|
+
[desired version](https://github.com/ocadotechnology/codeforlife-package-javascript/releases).*
|
|
30
|
+
|
|
31
|
+
**Option 1:** Run `yarn install` command:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
yarn install git+https://github.com/ocadotechnology/codeforlife-package-javascript.git#v0.0.0
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Option 2:** add the following to `package.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"codeforlife": "github:ocadotechnology/codeforlife-package-javascript#v0.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Making Changes
|
|
48
|
+
|
|
49
|
+
To make changes, you must:
|
|
50
|
+
|
|
51
|
+
1. Branch off of main.
|
|
52
|
+
1. Push your changes on your branch.
|
|
53
|
+
1. Ensure the pipeline runs successfully on your branch.
|
|
54
|
+
1. Have your changes reviewed and approved by a peer.
|
|
55
|
+
1. Merge your branch into the `main` branch.
|
|
56
|
+
1. [Manually trigger](https://github.com/ocadotechnology/codeforlife-package-javascript/actions/workflows/main.yml)
|
|
57
|
+
the `Main` pipeline for the `main` branch.
|
|
58
|
+
|
|
59
|
+
### Installing your branch
|
|
60
|
+
|
|
61
|
+
You may wish to install and integrate your changes into a CFL frontend before
|
|
62
|
+
it's been peer-reviewed.
|
|
63
|
+
|
|
64
|
+
*Remember to replace the branch name ("my-branch") with your
|
|
65
|
+
[branch](https://github.com/ocadotechnology/codeforlife-package-javascript/branches)*.
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"codeforlife": "github:ocadotechnology/codeforlife-package-javascript#my-branch"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Pipeline
|
|
76
|
+
|
|
77
|
+
When pushing to any branch, the pipeline will:
|
|
78
|
+
|
|
79
|
+
1. Check the code for linting errors.
|
|
80
|
+
1. Run tests on the React components.
|
|
81
|
+
1. Build the package.
|
|
82
|
+
1. Save the build to the `lib` directory.
|
|
83
|
+
|
|
84
|
+
When merging to the `main` branch, the pipeline will:
|
|
85
|
+
|
|
86
|
+
1. Update the package version in
|
|
87
|
+
[package.json](https://github.com/ocadotechnology/codeforlife-package-javascript/blob/main/package.json).
|
|
88
|
+
1. Update
|
|
89
|
+
[CHANGELOG.md](https://github.com/ocadotechnology/codeforlife-package-javascript/blob/main/CHANGELOG.md)
|
|
90
|
+
with latest commit messages.
|
|
91
|
+
1. Determine the next version number from the commit messages using
|
|
92
|
+
[semantic versioning](https://semver.org/).
|
|
93
|
+
1. Release a new version
|
|
94
|
+
[on GitHub](https://github.com/ocadotechnology/codeforlife-package-javascript/releases).
|
package/codecov.yml
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
{
|
|
2
|
+
"//": [
|
|
3
|
+
"Based off of:",
|
|
4
|
+
"https://github.com/vitejs/vite/blob/main/packages/create-vite/template-react-ts/package.json",
|
|
5
|
+
"Dependency rules:",
|
|
6
|
+
"`peerDependencies` should contain everything required to build and test a",
|
|
7
|
+
"service's front end.",
|
|
8
|
+
"TODO: make devDependencies the same as peerDependencies"
|
|
9
|
+
],
|
|
10
|
+
"name": "codeforlife",
|
|
11
|
+
"description": "Common frontend code",
|
|
12
|
+
"private": false,
|
|
13
|
+
"version": "2.6.1",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"dev": "vite",
|
|
17
|
+
"start": "serve -s dist",
|
|
18
|
+
"build": "tsc && vite build",
|
|
19
|
+
"preview": "vite preview",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:coverage": "vitest run --coverage",
|
|
22
|
+
"test:verbose": "vitest run --reporter=verbose --coverage.thresholds.lines=90 --coverage.thresholds.functions=90 --coverage.thresholds.branches=90 --coverage.thresholds.statements=90",
|
|
23
|
+
"test:ui": "vitest --ui",
|
|
24
|
+
"format": "prettier --write .",
|
|
25
|
+
"format:check": "prettier --check --write=false .",
|
|
26
|
+
"lint": "eslint --max-warnings=0 .",
|
|
27
|
+
"lint:fix": "eslint --fix .",
|
|
28
|
+
"type-check": "tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/ocadotechnology/codeforlife-package-javascript.git"
|
|
33
|
+
},
|
|
34
|
+
"author": "Ocado",
|
|
35
|
+
"license": "ISC",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/ocadotechnology/codeforlife-package-javascript/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/ocadotechnology/codeforlife-package-javascript#readme",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@emotion/react": "^11.10.6",
|
|
42
|
+
"@emotion/styled": "^11.10.6",
|
|
43
|
+
"@mui/icons-material": "^5.11.11",
|
|
44
|
+
"@mui/material": "^5.11.12",
|
|
45
|
+
"@mui/x-date-pickers": "^7.7.1",
|
|
46
|
+
"@reduxjs/toolkit": "^2.0.1",
|
|
47
|
+
"compression": "^1.7.5",
|
|
48
|
+
"dayjs": "^1.11.11",
|
|
49
|
+
"express": "^4.21.2",
|
|
50
|
+
"formik": "^2.2.9",
|
|
51
|
+
"js-cookie": "^3.0.5",
|
|
52
|
+
"memory-cache": "^0.2.0",
|
|
53
|
+
"qs": "^6.11.2",
|
|
54
|
+
"react": "^18.2.0",
|
|
55
|
+
"react-dom": "^18.2.0",
|
|
56
|
+
"react-redux": "^9.1.0",
|
|
57
|
+
"react-router-dom": "^6.23.1",
|
|
58
|
+
"serve": "^14.2.3",
|
|
59
|
+
"sirv": "^3.0.0",
|
|
60
|
+
"yup": "^1.1.1"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@testing-library/dom": "^9.3.4",
|
|
64
|
+
"@testing-library/jest-dom": "^6.2.0",
|
|
65
|
+
"@testing-library/react": "^14.1.2",
|
|
66
|
+
"@testing-library/user-event": "^14.5.2",
|
|
67
|
+
"@types/express": "^5.0.0",
|
|
68
|
+
"@types/js-cookie": "^3.0.3",
|
|
69
|
+
"@types/node": "^20.14.2",
|
|
70
|
+
"@types/qs": "^6.9.7",
|
|
71
|
+
"@types/react": "^18.2.47",
|
|
72
|
+
"@types/react-dom": "^18.2.18",
|
|
73
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
74
|
+
"@vitest/coverage-istanbul": "^1.6.0",
|
|
75
|
+
"@vitest/ui": "^1.6.0",
|
|
76
|
+
"eslint": "^8.56.0",
|
|
77
|
+
"eslint-config-prettier": "^9.1.0",
|
|
78
|
+
"eslint-config-react-app": "^7.0.1",
|
|
79
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
80
|
+
"jsdom": "^23.2.0",
|
|
81
|
+
"prettier": "^3.2.1",
|
|
82
|
+
"typescript": "^5.3.3",
|
|
83
|
+
"vite": "^5.0.11",
|
|
84
|
+
"vitest": "^1.2.0"
|
|
85
|
+
},
|
|
86
|
+
"peerDependencies": {
|
|
87
|
+
"@eslint/js": "^9.9.0",
|
|
88
|
+
"@testing-library/dom": "^9.3.4",
|
|
89
|
+
"@testing-library/jest-dom": "^6.2.0",
|
|
90
|
+
"@testing-library/react": "^14.1.2",
|
|
91
|
+
"@testing-library/user-event": "^14.5.2",
|
|
92
|
+
"@types/express": "^5.0.0",
|
|
93
|
+
"@types/jest": "^29.5.12",
|
|
94
|
+
"@types/js-cookie": "^3.0.3",
|
|
95
|
+
"@types/node": "^20.14.2",
|
|
96
|
+
"@types/qs": "^6.9.7",
|
|
97
|
+
"@types/react": "^18.2.47",
|
|
98
|
+
"@types/react-dom": "^18.2.18",
|
|
99
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
100
|
+
"@vitest/coverage-istanbul": "^1.6.0",
|
|
101
|
+
"@vitest/ui": "^1.6.0",
|
|
102
|
+
"eslint": "^8.56.0",
|
|
103
|
+
"eslint-config-prettier": "^9.1.0",
|
|
104
|
+
"eslint-config-react-app": "^7.0.1",
|
|
105
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
106
|
+
"eslint-plugin-react": "^7.35.0",
|
|
107
|
+
"eslint-plugin-react-hooks": "^4.6.2",
|
|
108
|
+
"eslint-plugin-react-refresh": "^0.4.9",
|
|
109
|
+
"globals": "^15.9.0",
|
|
110
|
+
"jsdom": "^23.2.0",
|
|
111
|
+
"prettier": "^3.2.1",
|
|
112
|
+
"typescript": "^5.3.3",
|
|
113
|
+
"typescript-eslint": "^8.1.0",
|
|
114
|
+
"vite": "^5.0.11",
|
|
115
|
+
"vitest": "^1.2.0"
|
|
116
|
+
},
|
|
117
|
+
"release": {
|
|
118
|
+
"branches": [
|
|
119
|
+
"main"
|
|
120
|
+
],
|
|
121
|
+
"plugins": [
|
|
122
|
+
"@semantic-release/commit-analyzer",
|
|
123
|
+
"@semantic-release/release-notes-generator",
|
|
124
|
+
"@semantic-release/changelog",
|
|
125
|
+
"@semantic-release/npm",
|
|
126
|
+
[
|
|
127
|
+
"@semantic-release/git",
|
|
128
|
+
{
|
|
129
|
+
"assets": [
|
|
130
|
+
"package.json",
|
|
131
|
+
"CHANGELOG.md"
|
|
132
|
+
],
|
|
133
|
+
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
134
|
+
}
|
|
135
|
+
],
|
|
136
|
+
"@semantic-release/github"
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createApi as _createApi,
|
|
3
|
+
fetchBaseQuery,
|
|
4
|
+
} from "@reduxjs/toolkit/query/react"
|
|
5
|
+
|
|
6
|
+
import { SERVICE_API_URL } from "../settings"
|
|
7
|
+
import defaultTagTypes from "./tagTypes"
|
|
8
|
+
import { buildLogoutEndpoint } from "./endpoints/session"
|
|
9
|
+
import { getCsrfCookie } from "../utils/auth"
|
|
10
|
+
|
|
11
|
+
// TODO: decide if we want to keep any of this.
|
|
12
|
+
// export function handleResponseError(error: FetchBaseQueryError): void {
|
|
13
|
+
// if (
|
|
14
|
+
// error.status === 400 &&
|
|
15
|
+
// typeof error.data === "object" &&
|
|
16
|
+
// error.data !== null
|
|
17
|
+
// ) {
|
|
18
|
+
// // Parse the error's data from snake_case to camelCase.
|
|
19
|
+
// snakeCaseToCamelCase(error.data)
|
|
20
|
+
// } else if (error.status === 401) {
|
|
21
|
+
// // TODO: redirect to appropriate login page based on user type.
|
|
22
|
+
// window.location.href = `${PORTAL_BASE_URL}/login/teacher`
|
|
23
|
+
// } else {
|
|
24
|
+
// // Catch-all error pages by status-code.
|
|
25
|
+
// window.location.href = `${PORTAL_BASE_URL}/error/${
|
|
26
|
+
// [403, 404].includes(error.status as number) ? error.status : 500
|
|
27
|
+
// }`
|
|
28
|
+
// }
|
|
29
|
+
// }
|
|
30
|
+
|
|
31
|
+
export default function createApi<TagTypes extends string = never>({
|
|
32
|
+
tagTypes = [],
|
|
33
|
+
}: {
|
|
34
|
+
tagTypes?: readonly TagTypes[]
|
|
35
|
+
} = {}) {
|
|
36
|
+
const fetch = fetchBaseQuery({
|
|
37
|
+
baseUrl: `${SERVICE_API_URL}/`,
|
|
38
|
+
credentials: "include",
|
|
39
|
+
prepareHeaders: (headers, { type }) => {
|
|
40
|
+
if (type === "mutation") {
|
|
41
|
+
let csrfToken = getCsrfCookie()
|
|
42
|
+
if (csrfToken) headers.set("x-csrftoken", csrfToken)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return headers
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const api = _createApi({
|
|
50
|
+
// https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#implementing-a-custom-basequery
|
|
51
|
+
baseQuery: async (args, api, extraOptions) => {
|
|
52
|
+
if (api.type === "mutation" && getCsrfCookie() === undefined) {
|
|
53
|
+
// Get the CSRF token.
|
|
54
|
+
const { error } = await fetch(
|
|
55
|
+
{ url: "/csrf/cookie", method: "GET" },
|
|
56
|
+
api,
|
|
57
|
+
{},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
// Validate we got the CSRF token.
|
|
61
|
+
if (error !== undefined) {
|
|
62
|
+
console.error(error)
|
|
63
|
+
// TODO
|
|
64
|
+
// window.location.href = `${PORTAL_BASE_URL}/error/500`
|
|
65
|
+
}
|
|
66
|
+
if (getCsrfCookie() === undefined) {
|
|
67
|
+
// TODO
|
|
68
|
+
// window.location.href = `${PORTAL_BASE_URL}/error/500`
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Send the HTTP request and fetch the response.
|
|
73
|
+
return await fetch(args, api, extraOptions)
|
|
74
|
+
},
|
|
75
|
+
tagTypes: [...defaultTagTypes, ...tagTypes],
|
|
76
|
+
endpoints: () => ({}),
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return api.injectEndpoints({
|
|
80
|
+
endpoints: build => ({
|
|
81
|
+
logout: buildLogoutEndpoint<null, null>(api, build),
|
|
82
|
+
}),
|
|
83
|
+
})
|
|
84
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type EndpointBuilder } from "@reduxjs/toolkit/query/react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildUrl,
|
|
5
|
+
tagData,
|
|
6
|
+
type ListArg as _ListArg,
|
|
7
|
+
type ListResult as _ListResult,
|
|
8
|
+
} from "../../utils/api"
|
|
9
|
+
import type { AuthFactor } from "../models"
|
|
10
|
+
import { type TagTypes } from "../tagTypes"
|
|
11
|
+
import urls from "../urls"
|
|
12
|
+
|
|
13
|
+
export const AUTH_FACTOR_TAG: TagTypes = "AuthFactor"
|
|
14
|
+
|
|
15
|
+
export type ListAuthFactorsResult = _ListResult<AuthFactor, "type">
|
|
16
|
+
export type ListAuthFactorsArg = _ListArg
|
|
17
|
+
|
|
18
|
+
export default function getReadAuthFactorEndpoints<
|
|
19
|
+
ListResult extends _ListResult<AuthFactor> = ListAuthFactorsResult,
|
|
20
|
+
ListArg extends _ListArg<AuthFactor> = ListAuthFactorsArg,
|
|
21
|
+
>(build: EndpointBuilder<any, any, any>) {
|
|
22
|
+
return {
|
|
23
|
+
listAuthFactors: build.query<ListResult, ListArg>({
|
|
24
|
+
query: search => ({
|
|
25
|
+
url: buildUrl(urls.authFactor.list, { search }),
|
|
26
|
+
method: "GET",
|
|
27
|
+
}),
|
|
28
|
+
providesTags: tagData(AUTH_FACTOR_TAG, { includeListTag: true }),
|
|
29
|
+
}),
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./authFactor"
|
|
2
|
+
export { default as getReadAuthFactorEndpoints } from "./authFactor"
|
|
3
|
+
export * from "./klass"
|
|
4
|
+
export { default as getReadClassEndpoints } from "./klass"
|
|
5
|
+
export * from "./school"
|
|
6
|
+
export { default as getReadSchoolEndpoints } from "./school"
|
|
7
|
+
export * from "./session"
|
|
8
|
+
export * from "./user"
|
|
9
|
+
export { default as getReadUserEndpoints } from "./user"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type EndpointBuilder } from "@reduxjs/toolkit/query/react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildUrl,
|
|
5
|
+
tagData,
|
|
6
|
+
type ListArg as _ListArg,
|
|
7
|
+
type ListResult as _ListResult,
|
|
8
|
+
type RetrieveArg as _RetrieveArg,
|
|
9
|
+
type RetrieveResult as _RetrieveResult,
|
|
10
|
+
} from "../../utils/api"
|
|
11
|
+
import type {
|
|
12
|
+
Class,
|
|
13
|
+
Teacher,
|
|
14
|
+
SchoolTeacher,
|
|
15
|
+
SchoolTeacherUser,
|
|
16
|
+
} from "../models"
|
|
17
|
+
import { type TagTypes } from "../tagTypes"
|
|
18
|
+
import urls from "../urls"
|
|
19
|
+
|
|
20
|
+
export const CLASS_TAG: TagTypes = "Class"
|
|
21
|
+
|
|
22
|
+
export type RetrieveClassResult = _RetrieveResult<
|
|
23
|
+
Class,
|
|
24
|
+
"name" | "read_classmates_data" | "receive_requests_until" | "school"
|
|
25
|
+
> & {
|
|
26
|
+
teacher: SchoolTeacher & {
|
|
27
|
+
user: Pick<
|
|
28
|
+
SchoolTeacherUser,
|
|
29
|
+
| "id"
|
|
30
|
+
| "first_name"
|
|
31
|
+
| "last_name"
|
|
32
|
+
| "email"
|
|
33
|
+
| "is_active"
|
|
34
|
+
| "date_joined"
|
|
35
|
+
| "requesting_to_join_class"
|
|
36
|
+
>
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export type RetrieveClassArg = _RetrieveArg<Class>
|
|
40
|
+
|
|
41
|
+
export type ListClassesResult = _ListResult<
|
|
42
|
+
Class,
|
|
43
|
+
"name" | "read_classmates_data" | "receive_requests_until" | "school",
|
|
44
|
+
{
|
|
45
|
+
teacher: SchoolTeacher & {
|
|
46
|
+
user: Pick<
|
|
47
|
+
SchoolTeacherUser,
|
|
48
|
+
| "id"
|
|
49
|
+
| "first_name"
|
|
50
|
+
| "last_name"
|
|
51
|
+
| "email"
|
|
52
|
+
| "is_active"
|
|
53
|
+
| "date_joined"
|
|
54
|
+
| "requesting_to_join_class"
|
|
55
|
+
>
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
>
|
|
59
|
+
export type ListClassesArg = _ListArg<{
|
|
60
|
+
teacher: Teacher["id"]
|
|
61
|
+
_id: Class["id"] | Class["id"][]
|
|
62
|
+
id_or_name: string
|
|
63
|
+
}>
|
|
64
|
+
|
|
65
|
+
export default function getReadClassEndpoints<
|
|
66
|
+
RetrieveResult extends _RetrieveResult<Class> = RetrieveClassResult,
|
|
67
|
+
RetrieveArg extends _RetrieveArg<Class> = RetrieveClassArg,
|
|
68
|
+
ListResult extends _ListResult<Class> = ListClassesResult,
|
|
69
|
+
ListArg extends _ListArg<Class> = ListClassesArg,
|
|
70
|
+
>(build: EndpointBuilder<any, any, any>) {
|
|
71
|
+
return {
|
|
72
|
+
retrieveClass: build.query<RetrieveResult, RetrieveArg>({
|
|
73
|
+
query: id => ({
|
|
74
|
+
url: buildUrl(urls.class.detail, { url: { id } }),
|
|
75
|
+
method: "GET",
|
|
76
|
+
}),
|
|
77
|
+
providesTags: tagData(CLASS_TAG),
|
|
78
|
+
}),
|
|
79
|
+
listClasses: build.query<ListResult, ListArg>({
|
|
80
|
+
query: search => ({
|
|
81
|
+
url: buildUrl(urls.class.list, { search }),
|
|
82
|
+
method: "GET",
|
|
83
|
+
}),
|
|
84
|
+
providesTags: tagData(CLASS_TAG, { includeListTag: true }),
|
|
85
|
+
}),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type EndpointBuilder } from "@reduxjs/toolkit/query/react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildUrl,
|
|
5
|
+
tagData,
|
|
6
|
+
type RetrieveArg as _RetrieveArg,
|
|
7
|
+
type RetrieveResult as _RetrieveResult,
|
|
8
|
+
} from "../../utils/api"
|
|
9
|
+
import type { School } from "../models"
|
|
10
|
+
import { type TagTypes } from "../tagTypes"
|
|
11
|
+
import urls from "../urls"
|
|
12
|
+
|
|
13
|
+
export const SCHOOL_TAG: TagTypes = "School"
|
|
14
|
+
|
|
15
|
+
export type RetrieveSchoolResult = _RetrieveResult<
|
|
16
|
+
School,
|
|
17
|
+
"name" | "country" | "uk_county"
|
|
18
|
+
>
|
|
19
|
+
export type RetrieveSchoolArg = _RetrieveArg<School>
|
|
20
|
+
|
|
21
|
+
export default function getReadSchoolEndpoints<
|
|
22
|
+
RetrieveResult extends _RetrieveResult<School> = RetrieveSchoolResult,
|
|
23
|
+
RetrieveArg extends _RetrieveArg<School> = RetrieveSchoolArg,
|
|
24
|
+
>(build: EndpointBuilder<any, any, any>) {
|
|
25
|
+
return {
|
|
26
|
+
retrieveSchool: build.query<RetrieveResult, RetrieveArg>({
|
|
27
|
+
query: id => ({
|
|
28
|
+
url: buildUrl(urls.school.detail, { url: { id } }),
|
|
29
|
+
method: "GET",
|
|
30
|
+
}),
|
|
31
|
+
providesTags: tagData(SCHOOL_TAG),
|
|
32
|
+
}),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type EndpointBuilder } from "@reduxjs/toolkit/query/react"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildUrl,
|
|
5
|
+
tagData,
|
|
6
|
+
type ListArg as _ListArg,
|
|
7
|
+
type ListResult as _ListResult,
|
|
8
|
+
type RetrieveArg as _RetrieveArg,
|
|
9
|
+
type RetrieveResult as _RetrieveResult,
|
|
10
|
+
} from "../../utils/api"
|
|
11
|
+
import type { Class, User } from "../models"
|
|
12
|
+
import { type TagTypes } from "../tagTypes"
|
|
13
|
+
import urls from "../urls"
|
|
14
|
+
|
|
15
|
+
export const USER_TAG: TagTypes = "User"
|
|
16
|
+
|
|
17
|
+
export type RetrieveUserResult = _RetrieveResult<
|
|
18
|
+
User,
|
|
19
|
+
| "first_name"
|
|
20
|
+
| "last_name"
|
|
21
|
+
| "email"
|
|
22
|
+
| "is_active"
|
|
23
|
+
| "date_joined"
|
|
24
|
+
| "requesting_to_join_class"
|
|
25
|
+
| "student"
|
|
26
|
+
| "teacher"
|
|
27
|
+
>
|
|
28
|
+
export type RetrieveUserArg = _RetrieveArg<User>
|
|
29
|
+
|
|
30
|
+
export type ListUsersResult = _ListResult<
|
|
31
|
+
User,
|
|
32
|
+
| "first_name"
|
|
33
|
+
| "last_name"
|
|
34
|
+
| "email"
|
|
35
|
+
| "is_active"
|
|
36
|
+
| "date_joined"
|
|
37
|
+
| "requesting_to_join_class"
|
|
38
|
+
| "student"
|
|
39
|
+
| "teacher"
|
|
40
|
+
>
|
|
41
|
+
export type ListUsersArg = _ListArg<{
|
|
42
|
+
students_in_class: Class["id"]
|
|
43
|
+
_id: User["id"] | User["id"][]
|
|
44
|
+
name: string
|
|
45
|
+
type: "teacher" | "student" | "independent" | "indy"
|
|
46
|
+
}>
|
|
47
|
+
|
|
48
|
+
export default function getReadUserEndpoints<
|
|
49
|
+
RetrieveResult extends _RetrieveResult<User> = RetrieveUserResult,
|
|
50
|
+
RetrieveArg extends _RetrieveArg<User> = RetrieveUserArg,
|
|
51
|
+
ListResult extends _ListResult<User> = ListUsersResult,
|
|
52
|
+
ListArg extends _ListArg<User> = ListUsersArg,
|
|
53
|
+
>(build: EndpointBuilder<any, any, any>) {
|
|
54
|
+
return {
|
|
55
|
+
retrieveUser: build.query<RetrieveResult, RetrieveArg>({
|
|
56
|
+
query: id => ({
|
|
57
|
+
url: buildUrl(urls.user.detail, { url: { id } }),
|
|
58
|
+
method: "GET",
|
|
59
|
+
}),
|
|
60
|
+
providesTags: tagData(USER_TAG),
|
|
61
|
+
}),
|
|
62
|
+
listUsers: build.query<ListResult, ListArg>({
|
|
63
|
+
query: search => ({
|
|
64
|
+
url: buildUrl(urls.user.list, { search }),
|
|
65
|
+
method: "GET",
|
|
66
|
+
}),
|
|
67
|
+
providesTags: tagData(USER_TAG, { includeListTag: true }),
|
|
68
|
+
}),
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/api/index.ts
ADDED