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/src/server.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* © Ocado Group
|
|
3
|
+
* Created on 13/12/2024 at 12:15:05(+00:00).
|
|
4
|
+
*
|
|
5
|
+
* A server for an app in a live environment.
|
|
6
|
+
* Based off: https://github.com/bluwy/create-vite-extra/blob/master/template-ssr-react-ts/server.js
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from "node:fs/promises"
|
|
10
|
+
import express from "express"
|
|
11
|
+
import { Cache } from "memory-cache"
|
|
12
|
+
|
|
13
|
+
export default class Server {
|
|
14
|
+
constructor(
|
|
15
|
+
/** @type {Partial<{ mode: "development" | "staging" | "production"; port: number; base: string }>} */
|
|
16
|
+
{ mode, port, base } = {},
|
|
17
|
+
) {
|
|
18
|
+
/** @type {"development" | "staging" | "production"} */
|
|
19
|
+
this.mode = mode || process.env.MODE || "development"
|
|
20
|
+
/** @type {number} */
|
|
21
|
+
this.port = port || (process.env.PORT ? Number(process.env.PORT) : 5173)
|
|
22
|
+
/** @type {string} */
|
|
23
|
+
this.base = base || process.env.BASE || "/"
|
|
24
|
+
|
|
25
|
+
/** @type {boolean} */
|
|
26
|
+
this.envIsProduction = process.env.NODE_ENV === "production"
|
|
27
|
+
/** @type {string} */
|
|
28
|
+
this.templateHtml = ""
|
|
29
|
+
/** @type {string} */
|
|
30
|
+
this.hostname = this.envIsProduction ? "0.0.0.0" : "127.0.0.1"
|
|
31
|
+
|
|
32
|
+
/** @type {import('express').Express} */
|
|
33
|
+
this.app = express()
|
|
34
|
+
/** @type {import('vite').ViteDevServer | undefined} */
|
|
35
|
+
this.vite = undefined
|
|
36
|
+
/** @type {import('memory-cache').Cache<string, any>} */
|
|
37
|
+
this.cache = new Cache()
|
|
38
|
+
|
|
39
|
+
/** @type {string} */
|
|
40
|
+
this.healthCheckCacheKey = "health-check"
|
|
41
|
+
/** @type {number} */
|
|
42
|
+
this.healthCheckCacheTimeout = 30000
|
|
43
|
+
/** @type {Record<"healthy" | "startingUp" | "shuttingDown" | "unhealthy" | "unknown", number>} */
|
|
44
|
+
this.healthCheckStatusCodes = {
|
|
45
|
+
// The app is running normally.
|
|
46
|
+
healthy: 200,
|
|
47
|
+
// The app is performing app-specific initialisation which must
|
|
48
|
+
// complete before it will serve normal application requests
|
|
49
|
+
// (perhaps the app is warming a cache or something similar). You
|
|
50
|
+
// only need to use this status if your app will be in a start-up
|
|
51
|
+
// mode for a prolonged period of time.
|
|
52
|
+
startingUp: 503,
|
|
53
|
+
// The app is shutting down. As with startingUp, you only need to
|
|
54
|
+
// use this status if your app takes a prolonged amount of time
|
|
55
|
+
// to shutdown, perhaps because it waits for a long-running
|
|
56
|
+
// process to complete before shutting down.
|
|
57
|
+
shuttingDown: 503,
|
|
58
|
+
// The app is not running normally.
|
|
59
|
+
unhealthy: 503,
|
|
60
|
+
// The app is not able to report its own state.
|
|
61
|
+
unknown: 503,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** @type {(request: import('express').Request) => { healthStatus: "healthy" | "startingUp" | "shuttingDown" | "unhealthy" | "unknown"; additionalInfo: string; details?: Array<{ name: string; description: string; health: "healthy" | "startingUp" | "shuttingDown" | "unhealthy" | "unknown" }> }} */
|
|
66
|
+
getHealthCheck(request) {
|
|
67
|
+
return {
|
|
68
|
+
healthStatus: "healthy",
|
|
69
|
+
additionalInfo: "All healthy.",
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @type {(request: import('express').Request, response: import('express').Response) => void} */
|
|
74
|
+
handleHealthCheck(request, response) {
|
|
75
|
+
/** @type {{ appId: string; healthStatus: "healthy" | "startingUp" | "shuttingDown" | "unhealthy" | "unknown"; lastCheckedTimestamp: string; additionalInformation: string; startupTimestamp: string; appVersion: string; details: Array<{ name: string; description: string; health: "healthy" | "startingUp" | "shuttingDown" | "unhealthy" | "unknown" }> }} */
|
|
76
|
+
let value = this.cache.get(this.healthCheckCacheKey)
|
|
77
|
+
if (value === null) {
|
|
78
|
+
const healthCheck = this.getHealthCheck(request)
|
|
79
|
+
|
|
80
|
+
if (healthCheck.healthStatus !== "healthy") {
|
|
81
|
+
console.warn(`health check: ${JSON.stringify(healthCheck)}`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
value = {
|
|
85
|
+
appId: process.env.APP_ID || "REPLACE_ME",
|
|
86
|
+
healthStatus: healthCheck.healthStatus,
|
|
87
|
+
lastCheckedTimestamp: new Date().toISOString(),
|
|
88
|
+
additionalInformation: healthCheck.additionalInfo,
|
|
89
|
+
startupTimestamp: new Date().toISOString(),
|
|
90
|
+
appVersion: process.env.APP_VERSION || "REPLACE_ME",
|
|
91
|
+
details: healthCheck.details || [],
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.cache.put(
|
|
95
|
+
this.healthCheckCacheKey,
|
|
96
|
+
value,
|
|
97
|
+
this.healthCheckCacheTimeout,
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
response.status(this.healthCheckStatusCodes[value.healthStatus]).json(value)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** @type {(request: import('express').Request, response: import('express').Response) => Promise<void>} */
|
|
105
|
+
async handleServeHtml(request, response) {
|
|
106
|
+
try {
|
|
107
|
+
const path = request.originalUrl.replace(this.base, "")
|
|
108
|
+
|
|
109
|
+
/** @type {string} */
|
|
110
|
+
let template
|
|
111
|
+
/** @type {(path: string) => Promise<{ head?: string; html?: string }>} */
|
|
112
|
+
let render
|
|
113
|
+
if (this.envIsProduction) {
|
|
114
|
+
render = (await import("../../../dist/server/entry-server.js")).render
|
|
115
|
+
|
|
116
|
+
// Use cached template.
|
|
117
|
+
template = this.templateHtml
|
|
118
|
+
} else {
|
|
119
|
+
render = (await this.vite.ssrLoadModule("/src/entry-server.tsx")).render
|
|
120
|
+
|
|
121
|
+
// Always read fresh template.
|
|
122
|
+
template = await fs.readFile("./index.html", "utf-8")
|
|
123
|
+
template = await this.vite.transformIndexHtml(path, template)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const rendered = await render(path)
|
|
127
|
+
|
|
128
|
+
const html = template
|
|
129
|
+
.replace(`<!--app-head-->`, rendered.head ?? "")
|
|
130
|
+
.replace(`<!--app-html-->`, rendered.html ?? "")
|
|
131
|
+
|
|
132
|
+
response.status(200).set({ "Content-Type": "text/html" }).send(html)
|
|
133
|
+
} catch (error) {
|
|
134
|
+
this.vite?.ssrFixStacktrace(error)
|
|
135
|
+
console.error(error.stack)
|
|
136
|
+
response.status(500).end(this.envIsProduction ? undefined : error.stack)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async run() {
|
|
141
|
+
this.app.get("/health-check", (request, response) => {
|
|
142
|
+
this.handleHealthCheck(request, response)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
if (this.envIsProduction) {
|
|
146
|
+
const compression = (await import("compression")).default
|
|
147
|
+
const sirv = (await import("sirv")).default
|
|
148
|
+
|
|
149
|
+
this.templateHtml = await fs.readFile("./dist/client/index.html", "utf-8")
|
|
150
|
+
|
|
151
|
+
this.app.use(compression())
|
|
152
|
+
this.app.use(this.base, sirv("./dist/client", { extensions: [] }))
|
|
153
|
+
} else {
|
|
154
|
+
const { createServer } = await import("vite")
|
|
155
|
+
|
|
156
|
+
this.vite = await createServer({
|
|
157
|
+
server: { middlewareMode: true },
|
|
158
|
+
appType: "custom",
|
|
159
|
+
base: this.base,
|
|
160
|
+
mode: this.mode,
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
this.app.use(this.vite.middlewares)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.app.get("*", async (request, response) => {
|
|
167
|
+
await this.handleServeHtml(request, response)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
this.app.listen(this.port, this.hostname, () => {
|
|
171
|
+
let startMessage =
|
|
172
|
+
"Server started.\n" +
|
|
173
|
+
`url: http://${this.hostname}:${this.port}\n` +
|
|
174
|
+
`environment: ${process.env.NODE_ENV}\n`
|
|
175
|
+
|
|
176
|
+
if (!this.envIsProduction) startMessage += `mode: ${this.mode}\n`
|
|
177
|
+
|
|
178
|
+
console.log(startMessage)
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains all of our custom settings we define for our own purposes.
|
|
3
|
+
*
|
|
4
|
+
* This file is based on:
|
|
5
|
+
* https://github.com/ocadotechnology/codeforlife-package-python/blob/main/codeforlife/settings/custom.py
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Shorthand to access environment variables.
|
|
9
|
+
const env = import.meta.env as Record<string, string | undefined>
|
|
10
|
+
|
|
11
|
+
// The name of the current service.
|
|
12
|
+
export const SERVICE_NAME = env.VITE_SERVICE_NAME ?? "REPLACE_ME"
|
|
13
|
+
|
|
14
|
+
// The api url of the current service.
|
|
15
|
+
export const SERVICE_API_URL =
|
|
16
|
+
env.VITE_SERVICE_API_URL ?? "http://localhost:8000"
|
|
17
|
+
|
|
18
|
+
// The names of cookies.
|
|
19
|
+
export const CSRF_COOKIE_NAME = `${SERVICE_NAME}_csrftoken`
|
|
20
|
+
export const SESSION_COOKIE_NAME = env.VITE_SESSION_COOKIE_NAME ?? "session_key"
|
|
21
|
+
export const SESSION_METADATA_COOKIE_NAME =
|
|
22
|
+
env.VITE_SESSION_METADATA_COOKIE_NAME ?? "session_metadata"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains all of vite's environment variables.
|
|
3
|
+
*
|
|
4
|
+
* https://vite.dev/guide/env-and-mode#env-variables
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Shorthand to access environment variables.
|
|
8
|
+
const env = import.meta.env
|
|
9
|
+
|
|
10
|
+
// The mode the app is running in.
|
|
11
|
+
export const MODE = env.MODE
|
|
12
|
+
|
|
13
|
+
// The base url the app is being served from.
|
|
14
|
+
// This is determined by the base config option.
|
|
15
|
+
export const BASE_URL = env.BASE_URL
|
|
16
|
+
|
|
17
|
+
// Whether the app is running in production (running the dev server with
|
|
18
|
+
// NODE_ENV='production' or running an app built with NODE_ENV='production').
|
|
19
|
+
export const PROD = env.PROD
|
|
20
|
+
|
|
21
|
+
// Whether the app is running in development (always the opposite of
|
|
22
|
+
// import.meta.env.PROD)
|
|
23
|
+
export const DEV = env.DEV
|
|
24
|
+
|
|
25
|
+
// Whether the app is running in the server.
|
|
26
|
+
export const SSR = env.SSR
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "@testing-library/jest-dom/vitest"
|
|
@@ -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
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Cookies from "js-cookie"
|
|
2
|
+
|
|
3
|
+
import { SESSION_METADATA_COOKIE_NAME } from "../settings"
|
|
4
|
+
import createSlice from "./createSlice"
|
|
5
|
+
|
|
6
|
+
export interface SessionState {
|
|
7
|
+
isLoggedIn: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const initialState: SessionState = {
|
|
11
|
+
isLoggedIn: Boolean(Cookies.get(SESSION_METADATA_COOKIE_NAME)),
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const sessionSlice = createSlice({
|
|
15
|
+
name: "session",
|
|
16
|
+
initialState,
|
|
17
|
+
reducers: create => ({
|
|
18
|
+
login: create.reducer(state => {
|
|
19
|
+
state.isLoggedIn = true
|
|
20
|
+
}),
|
|
21
|
+
logout: create.reducer(state => {
|
|
22
|
+
state.isLoggedIn = false
|
|
23
|
+
}),
|
|
24
|
+
}),
|
|
25
|
+
selectors: {
|
|
26
|
+
selectIsLoggedIn: session => session.isLoggedIn,
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export default sessionSlice
|
|
31
|
+
export const { login, logout } = sessionSlice.actions
|
|
32
|
+
export const { selectIsLoggedIn } = sessionSlice.selectors
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Circle as CircleIcon,
|
|
3
|
+
Hexagon as HexagonIcon,
|
|
4
|
+
} from "@mui/icons-material"
|
|
5
|
+
import {
|
|
6
|
+
Box,
|
|
7
|
+
ThemeProvider,
|
|
8
|
+
buttonClasses,
|
|
9
|
+
createTheme,
|
|
10
|
+
responsiveFontSizes,
|
|
11
|
+
type BoxProps,
|
|
12
|
+
type CSSObject,
|
|
13
|
+
type PaletteColor,
|
|
14
|
+
type SxProps,
|
|
15
|
+
type ThemeOptions,
|
|
16
|
+
} from "@mui/material"
|
|
17
|
+
import { type CommonProps } from "@mui/material/OverridableComponent"
|
|
18
|
+
import type React from "react"
|
|
19
|
+
|
|
20
|
+
import { themeOptions } from "."
|
|
21
|
+
import { getStyleOverrides, includesClassNames } from "../utils/theme"
|
|
22
|
+
import { primary, secondary, tertiary } from "./colors"
|
|
23
|
+
import type Components from "./components/_components"
|
|
24
|
+
import palette from "./palette"
|
|
25
|
+
|
|
26
|
+
export interface ThemedBoxProps extends BoxProps {
|
|
27
|
+
options?: ThemeOptions
|
|
28
|
+
withShapes?: boolean
|
|
29
|
+
userType: "teacher" | "student" | "independent"
|
|
30
|
+
bgcolor?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const ThemedBox: React.FC<ThemedBoxProps> = ({
|
|
34
|
+
options = themeOptions,
|
|
35
|
+
withShapes = false,
|
|
36
|
+
userType,
|
|
37
|
+
bgcolor,
|
|
38
|
+
children,
|
|
39
|
+
sx,
|
|
40
|
+
...otherBoxProps
|
|
41
|
+
}) => {
|
|
42
|
+
let circleColor: "primary" | "secondary" | "tertiary"
|
|
43
|
+
let hexagonColor: "primary" | "secondary" | "tertiary"
|
|
44
|
+
let contrastText: string
|
|
45
|
+
switch (userType) {
|
|
46
|
+
case "teacher":
|
|
47
|
+
bgcolor = bgcolor ?? primary[400]
|
|
48
|
+
circleColor = "tertiary"
|
|
49
|
+
hexagonColor = "secondary"
|
|
50
|
+
contrastText = (palette.primary as PaletteColor).contrastText
|
|
51
|
+
break
|
|
52
|
+
case "student":
|
|
53
|
+
bgcolor = bgcolor ?? tertiary[500]
|
|
54
|
+
circleColor = "secondary"
|
|
55
|
+
hexagonColor = "primary"
|
|
56
|
+
contrastText = palette.tertiary.contrastText
|
|
57
|
+
break
|
|
58
|
+
case "independent":
|
|
59
|
+
bgcolor = bgcolor ?? secondary[500]
|
|
60
|
+
circleColor = "primary"
|
|
61
|
+
hexagonColor = "tertiary"
|
|
62
|
+
contrastText = (palette.secondary as PaletteColor).contrastText
|
|
63
|
+
break
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const commonIconSxProps: SxProps = {
|
|
67
|
+
display: { xs: "none", md: "block" },
|
|
68
|
+
fontSize: "180px",
|
|
69
|
+
position: "absolute",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const fontStyleOverrides = {
|
|
73
|
+
color: contrastText,
|
|
74
|
+
textDecorationColor: contrastText,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function overrideStyles(
|
|
78
|
+
ownerState: CommonProps,
|
|
79
|
+
styleOverrides: CSSObject,
|
|
80
|
+
componentKey: keyof Components,
|
|
81
|
+
muiClassName: string = "root",
|
|
82
|
+
): CSSObject {
|
|
83
|
+
return {
|
|
84
|
+
// Get the original styles.
|
|
85
|
+
...getStyleOverrides(
|
|
86
|
+
ownerState,
|
|
87
|
+
componentKey,
|
|
88
|
+
muiClassName,
|
|
89
|
+
options.components,
|
|
90
|
+
),
|
|
91
|
+
// Override styles unless the class name 'no-override' is set.
|
|
92
|
+
...(!includesClassNames(ownerState, ["no-override"]) && styleOverrides),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const theme = responsiveFontSizes(
|
|
97
|
+
createTheme({
|
|
98
|
+
...options,
|
|
99
|
+
components: {
|
|
100
|
+
...options.components,
|
|
101
|
+
MuiTypography: {
|
|
102
|
+
...options.components?.MuiTypography,
|
|
103
|
+
styleOverrides: {
|
|
104
|
+
...options.components?.MuiTypography?.styleOverrides,
|
|
105
|
+
root: ({ ownerState }) =>
|
|
106
|
+
overrideStyles(
|
|
107
|
+
ownerState,
|
|
108
|
+
{
|
|
109
|
+
...fontStyleOverrides,
|
|
110
|
+
},
|
|
111
|
+
"MuiTypography",
|
|
112
|
+
),
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
MuiFormHelperText: {
|
|
116
|
+
...options.components?.MuiFormHelperText,
|
|
117
|
+
styleOverrides: {
|
|
118
|
+
...options.components?.MuiFormHelperText?.styleOverrides,
|
|
119
|
+
root: ({ ownerState }) =>
|
|
120
|
+
overrideStyles(
|
|
121
|
+
ownerState,
|
|
122
|
+
{
|
|
123
|
+
...fontStyleOverrides,
|
|
124
|
+
},
|
|
125
|
+
"MuiFormHelperText",
|
|
126
|
+
),
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
MuiLink: {
|
|
130
|
+
...options.components?.MuiLink,
|
|
131
|
+
styleOverrides: {
|
|
132
|
+
...options.components?.MuiLink?.styleOverrides,
|
|
133
|
+
root: ({ ownerState }) =>
|
|
134
|
+
overrideStyles(
|
|
135
|
+
ownerState,
|
|
136
|
+
{
|
|
137
|
+
...fontStyleOverrides,
|
|
138
|
+
},
|
|
139
|
+
"MuiLink",
|
|
140
|
+
),
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
MuiButton: {
|
|
144
|
+
...options.components?.MuiButton,
|
|
145
|
+
styleOverrides: {
|
|
146
|
+
...options.components?.MuiButton?.styleOverrides,
|
|
147
|
+
contained: ({ ownerState }) =>
|
|
148
|
+
overrideStyles(
|
|
149
|
+
ownerState,
|
|
150
|
+
{
|
|
151
|
+
...(userType === "independent" && {
|
|
152
|
+
backgroundColor: "white",
|
|
153
|
+
"&:hover": {
|
|
154
|
+
backgroundColor: "#f6f5f5",
|
|
155
|
+
boxShadow: [
|
|
156
|
+
"0px 6px 10px 0px rgba(0, 0, 0, 0.14)",
|
|
157
|
+
"0px 1px 18px 0px rgba(0, 0, 0, 0.12)",
|
|
158
|
+
"0px 3px 5px 0px rgba(0, 0, 0, 0.2);",
|
|
159
|
+
].join(),
|
|
160
|
+
},
|
|
161
|
+
[`&.${buttonClasses.disabled}`]: {
|
|
162
|
+
backgroundColor: "white",
|
|
163
|
+
color: contrastText,
|
|
164
|
+
},
|
|
165
|
+
}),
|
|
166
|
+
},
|
|
167
|
+
"MuiButton",
|
|
168
|
+
"contained",
|
|
169
|
+
),
|
|
170
|
+
outlined: ({ ownerState }) =>
|
|
171
|
+
overrideStyles(
|
|
172
|
+
ownerState,
|
|
173
|
+
{
|
|
174
|
+
...fontStyleOverrides,
|
|
175
|
+
border: `2px solid ${contrastText}`,
|
|
176
|
+
"&:hover": {
|
|
177
|
+
border: `2px solid ${contrastText}`,
|
|
178
|
+
backgroundColor: "transparent",
|
|
179
|
+
textDecoration: "underline",
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
"MuiButton",
|
|
183
|
+
"outlined",
|
|
184
|
+
),
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
MuiCheckbox: {
|
|
188
|
+
...options.components?.MuiCheckbox,
|
|
189
|
+
styleOverrides: {
|
|
190
|
+
...options.components?.MuiCheckbox?.styleOverrides,
|
|
191
|
+
root: ({ ownerState }) =>
|
|
192
|
+
overrideStyles(
|
|
193
|
+
ownerState,
|
|
194
|
+
{
|
|
195
|
+
color: `${contrastText} !important`,
|
|
196
|
+
},
|
|
197
|
+
"MuiCheckbox",
|
|
198
|
+
),
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
MuiSvgIcon: {
|
|
202
|
+
...options.components?.MuiSvgIcon,
|
|
203
|
+
styleOverrides: {
|
|
204
|
+
...options.components?.MuiSvgIcon?.styleOverrides,
|
|
205
|
+
root: ({ ownerState }) =>
|
|
206
|
+
overrideStyles(
|
|
207
|
+
ownerState,
|
|
208
|
+
{
|
|
209
|
+
"&.checkbox-error": {
|
|
210
|
+
color: `${contrastText} !important`,
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
"MuiSvgIcon",
|
|
214
|
+
),
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
}),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<ThemeProvider theme={theme}>
|
|
223
|
+
<Box
|
|
224
|
+
sx={{
|
|
225
|
+
...sx,
|
|
226
|
+
...(withShapes && {
|
|
227
|
+
paddingY: { xs: 2, sm: 3, md: 5 },
|
|
228
|
+
paddingX: { xs: 2, sm: 5, md: 10 },
|
|
229
|
+
marginX: { md: "90px" },
|
|
230
|
+
}),
|
|
231
|
+
bgcolor,
|
|
232
|
+
alignItems: "center",
|
|
233
|
+
position: "relative",
|
|
234
|
+
}}
|
|
235
|
+
{...otherBoxProps}
|
|
236
|
+
>
|
|
237
|
+
{withShapes && (
|
|
238
|
+
<>
|
|
239
|
+
<CircleIcon
|
|
240
|
+
color={circleColor}
|
|
241
|
+
sx={{
|
|
242
|
+
...commonIconSxProps,
|
|
243
|
+
top: "5%",
|
|
244
|
+
left: "0%",
|
|
245
|
+
transform: "translate(-60%, 0%)",
|
|
246
|
+
}}
|
|
247
|
+
/>
|
|
248
|
+
<HexagonIcon
|
|
249
|
+
color={hexagonColor}
|
|
250
|
+
sx={{
|
|
251
|
+
...commonIconSxProps,
|
|
252
|
+
bottom: "5%",
|
|
253
|
+
right: "0%",
|
|
254
|
+
transform: "translate(60%, 0%) rotate(90deg)",
|
|
255
|
+
}}
|
|
256
|
+
/>
|
|
257
|
+
</>
|
|
258
|
+
)}
|
|
259
|
+
{children}
|
|
260
|
+
</Box>
|
|
261
|
+
</ThemeProvider>
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export default ThemedBox
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export default interface Color {
|
|
2
|
+
900: string
|
|
3
|
+
800: string
|
|
4
|
+
700: string
|
|
5
|
+
600: string
|
|
6
|
+
500: string
|
|
7
|
+
400: string
|
|
8
|
+
300: string
|
|
9
|
+
200: string
|
|
10
|
+
100: string
|
|
11
|
+
50: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// primary / teacher
|
|
15
|
+
export const primary: Color = {
|
|
16
|
+
900: "#A60039",
|
|
17
|
+
800: "#BE0947",
|
|
18
|
+
700: "#C90548",
|
|
19
|
+
600: "#D40149",
|
|
20
|
+
500: "#E0004D",
|
|
21
|
+
400: "#EE0857",
|
|
22
|
+
300: "#FA1664",
|
|
23
|
+
200: "#FF397C",
|
|
24
|
+
100: "#FF6699",
|
|
25
|
+
50: "#FF9ABC",
|
|
26
|
+
}
|
|
27
|
+
export { primary as teacher }
|
|
28
|
+
|
|
29
|
+
// secondary / indy
|
|
30
|
+
export const secondary: Color = {
|
|
31
|
+
900: "#BF9400",
|
|
32
|
+
800: "#CFA30B",
|
|
33
|
+
700: "#DEAD06",
|
|
34
|
+
600: "#EAB502",
|
|
35
|
+
500: "#F6BE00",
|
|
36
|
+
400: "#FFC709",
|
|
37
|
+
300: "#FFD23D",
|
|
38
|
+
200: "#FFDA5C",
|
|
39
|
+
100: "#FFE382",
|
|
40
|
+
50: "#FFEDAD",
|
|
41
|
+
}
|
|
42
|
+
export { secondary as indy }
|
|
43
|
+
|
|
44
|
+
// tertiary / student
|
|
45
|
+
export const tertiary: Color = {
|
|
46
|
+
900: "#01668C",
|
|
47
|
+
800: "#007FAF",
|
|
48
|
+
700: "#008CC1",
|
|
49
|
+
600: "#0099D2",
|
|
50
|
+
500: "#00A3E0",
|
|
51
|
+
400: "#04AFEF",
|
|
52
|
+
300: "#08BAFC",
|
|
53
|
+
200: "#27C4FF",
|
|
54
|
+
100: "#4DCEFF",
|
|
55
|
+
50: "#85DDFF",
|
|
56
|
+
}
|
|
57
|
+
export { tertiary as student }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type Components from "./_components"
|
|
2
|
+
|
|
3
|
+
const MuiAccordion: Components["MuiAccordion"] = {
|
|
4
|
+
styleOverrides: {
|
|
5
|
+
root: {
|
|
6
|
+
borderRadius: "0px !important",
|
|
7
|
+
margin: "0px !important",
|
|
8
|
+
width: "100%",
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default MuiAccordion
|