diff --git a/.devcontainer.json b/.devcontainer.json
index 697ab8b..9cb651b 100644
--- a/.devcontainer.json
+++ b/.devcontainer.json
@@ -47,7 +47,7 @@
"source=./codeforlife-package-python,target=/workspace/codeforlife-package-python,type=bind,consistency=cached"
],
"name": "portal-frontend",
- "postCreateCommand": "sudo chmod u+x ./setup && ./setup",
+ "postCreateCommand": "sudo chmod u+x scripts/setup && scripts/setup",
"remoteUser": "root",
"service": "base-service",
"shutdownAction": "none",
diff --git a/.env b/.env
index 0a6ea68..a868567 100644
--- a/.env
+++ b/.env
@@ -1 +1,2 @@
-VITE_API_BASE_URL=REPLACE_ME
+VITE_API_BASE_URL=http://localhost:8000/api/
+VITE_SERVICE_NAME=portal
diff --git a/.gcloudignore b/.gcloudignore
index 09c35ea..8fb98f4 100644
--- a/.gcloudignore
+++ b/.gcloudignore
@@ -10,7 +10,7 @@
/codecov.yml
/*.code-*
/*.md
-/setup
+/scripts
/tsconfig.json
/tsconfig.node.json
/vite.config.ts
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 0d97f88..9f1a90e 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,8 +1,8 @@
{
"configurations": [
{
- "name": "React Dev Server",
- "preLaunchTask": "start-react-dev-server",
+ "name": "Vite Server",
+ "preLaunchTask": "start-vite-server",
"request": "launch",
"type": "chrome",
"url": "http://localhost:5173"
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 6a0dd90..c9c2abe 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -1,16 +1,22 @@
{
"tasks": [
{
+ "command": "sudo chmod u+x scripts/hard-install && scripts/hard-install",
+ "label": "hard-install",
+ "problemMatcher": [],
+ "type": "shell"
+ },
+ {
+ "command": "sudo chmod u+x scripts/run && scripts/run",
"isBackground": true,
- "label": "start-react-dev-server",
+ "label": "start-vite-server",
"options": {
"env": {
"BROWSER": "none"
}
},
"problemMatcher": [],
- "script": "start",
- "type": "npm"
+ "type": "shell"
}
],
"version": "2.0.0"
diff --git a/package.json b/package.json
index 419e3b0..831bdc3 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
},
"//": "🚫 Don't add `dependencies` below that are inherited from the CFL package.",
"dependencies": {
- "codeforlife": "github:ocadotechnology/codeforlife-package-javascript#v2.0.0"
+ "codeforlife": "github:ocadotechnology/codeforlife-package-javascript#v2.0.3"
},
"//": "✅ Do add `devDependencies` below that are `peerDependencies` in the CFL package.",
"devDependencies": {
diff --git a/scripts/hard-install b/scripts/hard-install
new file mode 100755
index 0000000..ea46059
--- /dev/null
+++ b/scripts/hard-install
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -e
+
+cd "${BASH_SOURCE%/*}"
+
+rm -f ../yarn.lock
+rm -rf ../node_modules
+yarn cache clean codeforlife
+yarn install --production=false
diff --git a/scripts/run b/scripts/run
new file mode 100755
index 0000000..8ce071e
--- /dev/null
+++ b/scripts/run
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -e
+
+cd "${BASH_SOURCE%/*}"
+
+source ./setup
+
+yarn dev
diff --git a/setup b/scripts/setup
similarity index 62%
rename from setup
rename to scripts/setup
index 1272bff..ce6aabd 100755
--- a/setup
+++ b/scripts/setup
@@ -3,6 +3,4 @@ set -e
cd "${BASH_SOURCE%/*}"
-printf "Setting up Node.js environment\n\n"
-
yarn install --production=false
diff --git a/src/App.css b/src/App.css
deleted file mode 100644
index 01cc586..0000000
--- a/src/App.css
+++ /dev/null
@@ -1,39 +0,0 @@
-.App {
- text-align: center;
-}
-
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-float infinite 3s ease-in-out;
- }
-}
-
-.App-header {
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
-}
-
-.App-link {
- color: rgb(112, 76, 182);
-}
-
-@keyframes App-logo-float {
- 0% {
- transform: translateY(0);
- }
- 50% {
- transform: translateY(10px);
- }
- 100% {
- transform: translateY(0px);
- }
-}
diff --git a/src/App.test.tsx b/src/App.test.tsx
deleted file mode 100644
index 06b45be..0000000
--- a/src/App.test.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import { screen, waitFor } from "@testing-library/react"
-import App from "./App"
-import { renderWithProviders } from "./utils/test-utils"
-
-test("App should have correct initial render", () => {
- renderWithProviders()
-
- // The app should be rendered correctly
- expect(screen.getByText(/learn/i)).toBeInTheDocument()
-
- // Initial state: count should be 0, incrementValue should be 2
- expect(screen.getByLabelText("Count")).toHaveTextContent("0")
- expect(screen.getByLabelText("Set increment amount")).toHaveValue(2)
-})
-
-test("Increment value and Decrement value should work as expected", async () => {
- const { user } = renderWithProviders()
-
- // Click on "+" => Count should be 1
- await user.click(screen.getByLabelText("Increment value"))
- expect(screen.getByLabelText("Count")).toHaveTextContent("1")
-
- // Click on "-" => Count should be 0
- await user.click(screen.getByLabelText("Decrement value"))
- expect(screen.getByLabelText("Count")).toHaveTextContent("0")
-})
-
-test("Add Amount should work as expected", async () => {
- const { user } = renderWithProviders()
-
- // "Add Amount" button is clicked => Count should be 2
- await user.click(screen.getByText("Add Amount"))
- expect(screen.getByLabelText("Count")).toHaveTextContent("2")
-
- const incrementValueInput = screen.getByLabelText("Set increment amount")
- // incrementValue is 2, click on "Add Amount" => Count should be 4
- await user.clear(incrementValueInput)
- await user.type(incrementValueInput, "2")
- await user.click(screen.getByText("Add Amount"))
- expect(screen.getByLabelText("Count")).toHaveTextContent("4")
-
- // [Negative number] incrementValue is -1, click on "Add Amount" => Count should be 3
- await user.clear(incrementValueInput)
- await user.type(incrementValueInput, "-1")
- await user.click(screen.getByText("Add Amount"))
- expect(screen.getByLabelText("Count")).toHaveTextContent("3")
-})
-
-it("Add Async should work as expected", async () => {
- const { user } = renderWithProviders()
-
- // "Add Async" button is clicked => Count should be 2
- await user.click(screen.getByText("Add Async"))
-
- await waitFor(() =>
- expect(screen.getByLabelText("Count")).toHaveTextContent("2"),
- )
-
- const incrementValueInput = screen.getByLabelText("Set increment amount")
- // incrementValue is 2, click on "Add Async" => Count should be 4
- await user.clear(incrementValueInput)
- await user.type(incrementValueInput, "2")
-
- await user.click(screen.getByText("Add Async"))
- await waitFor(() =>
- expect(screen.getByLabelText("Count")).toHaveTextContent("4"),
- )
-
- // [Negative number] incrementValue is -1, click on "Add Async" => Count should be 3
- await user.clear(incrementValueInput)
- await user.type(incrementValueInput, "-1")
- await user.click(screen.getByText("Add Async"))
- await waitFor(() =>
- expect(screen.getByLabelText("Count")).toHaveTextContent("3"),
- )
-})
-
-test("Add If Odd should work as expected", async () => {
- const { user } = renderWithProviders()
-
- // "Add If Odd" button is clicked => Count should stay 0
- await user.click(screen.getByText("Add If Odd"))
- expect(screen.getByLabelText("Count")).toHaveTextContent("0")
-
- // Click on "+" => Count should be updated to 1
- await user.click(screen.getByLabelText("Increment value"))
- expect(screen.getByLabelText("Count")).toHaveTextContent("1")
-
- // "Add If Odd" button is clicked => Count should be updated to 3
- await user.click(screen.getByText("Add If Odd"))
- expect(screen.getByLabelText("Count")).toHaveTextContent("3")
-
- const incrementValueInput = screen.getByLabelText("Set increment amount")
- // incrementValue is 1, click on "Add If Odd" => Count should be updated to 4
- await user.clear(incrementValueInput)
- await user.type(incrementValueInput, "1")
- await user.click(screen.getByText("Add If Odd"))
- expect(screen.getByLabelText("Count")).toHaveTextContent("4")
-
- // click on "Add If Odd" => Count should stay 4
- await user.clear(incrementValueInput)
- await user.type(incrementValueInput, "-1")
- await user.click(screen.getByText("Add If Odd"))
- expect(screen.getByLabelText("Count")).toHaveTextContent("4")
-})
diff --git a/src/App.tsx b/src/App.tsx
index 08ec755..27ec12f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,67 +1,15 @@
-import "./App.css"
-import { Counter } from "./features/counter/Counter"
-import { Quotes } from "./features/quotes/Quotes"
-import logo from "./logo.svg"
+import { CssBaseline, ThemeProvider } from "@mui/material"
+import type { FC } from "react"
-const App = () => {
+import theme from "./app/theme"
+import Router from "./router"
+
+const App: FC = () => {
return (
-
+
+
+
+
)
}
diff --git a/src/api/authFactor.ts b/src/api/authFactor.ts
index 1daaa2d..f4bc253 100644
--- a/src/api/authFactor.ts
+++ b/src/api/authFactor.ts
@@ -8,7 +8,7 @@ import {
type DestroyResult,
type ListArg,
type ListResult,
-} from "codeforlife/utils/rtkQuery"
+} from "codeforlife/utils/api"
import api from "."
diff --git a/src/api/index.ts b/src/api/index.ts
index 57bca53..c5734da 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -1,12 +1,71 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
+import Cookies from "js-cookie"
+
import { tagTypes } from "codeforlife/api"
+// https://docs.djangoproject.com/en/3.2/ref/csrf/
+const getCsrfCookie = () =>
+ Cookies.get(`${import.meta.env.VITE_SERVICE_NAME}_csrftoken`)
+
+const fetch = fetchBaseQuery({
+ baseUrl: import.meta.env.VITE_API_BASE_URL,
+ credentials: "include",
+ prepareHeaders: (headers, { type }) => {
+ if (type === "mutation") {
+ let csrfToken = getCsrfCookie()
+ if (csrfToken) headers.set("x-csrftoken", csrfToken)
+ }
+
+ return headers
+ },
+})
+
const api = createApi({
- baseQuery: fetchBaseQuery({
- baseUrl: import.meta.env.VITE_API_BASE_URL,
- }),
+ 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: [...tagTypes, "SchoolTeacherInvitation"],
- endpoints: () => ({}),
+ endpoints: build => ({
+ logout: build.mutation({
+ query: () => ({
+ url: "session/logout/",
+ method: "POST",
+ }),
+ async onQueryStarted(_, { dispatch, queryFulfilled }) {
+ try {
+ await queryFulfilled
+ } catch (error) {
+ console.error("Failed to log out...", error)
+ } finally {
+ Cookies.remove("session_key")
+ Cookies.remove("session_metadata")
+ dispatch(api.util.resetApiState())
+ }
+ },
+ }),
+ }),
})
export default api
+export const { useLogoutMutation } = api
diff --git a/src/api/klass.ts b/src/api/klass.ts
index 7e59ead..71f980c 100644
--- a/src/api/klass.ts
+++ b/src/api/klass.ts
@@ -12,7 +12,7 @@ import {
type RetrieveResult,
type UpdateArg,
type UpdateResult,
-} from "codeforlife/utils/rtkQuery"
+} from "codeforlife/utils/api"
import api from "."
diff --git a/src/api/school.ts b/src/api/school.ts
index aead92e..67dd433 100644
--- a/src/api/school.ts
+++ b/src/api/school.ts
@@ -8,7 +8,7 @@ import {
type RetrieveResult,
type UpdateArg,
type UpdateResult,
-} from "codeforlife/utils/rtkQuery"
+} from "codeforlife/utils/api"
import api from "."
diff --git a/src/api/schoolTeacherInvitation.ts b/src/api/schoolTeacherInvitation.ts
index e568a32..36a34e4 100644
--- a/src/api/schoolTeacherInvitation.ts
+++ b/src/api/schoolTeacherInvitation.ts
@@ -14,7 +14,7 @@ import {
type RetrieveResult,
type UpdateArg,
type UpdateResult,
-} from "codeforlife/utils/rtkQuery"
+} from "codeforlife/utils/api"
import api from "."
diff --git a/src/api/sso.ts b/src/api/sso.ts
new file mode 100644
index 0000000..8ecd99d
--- /dev/null
+++ b/src/api/sso.ts
@@ -0,0 +1,78 @@
+// TODO: rename this file to session.ts and move to codeforlife-sso-frontend.
+
+import {
+ type Class,
+ type OtpBypassToken,
+ type Student,
+ type User,
+} from "codeforlife/api"
+import { type SessionMetadata } from "codeforlife/hooks"
+import { type Arg } from "codeforlife/utils/api"
+
+import api from "."
+
+const baseUrl = "sso/session/"
+
+const ssoApi = api.injectEndpoints({
+ endpoints: build => ({
+ loginWithEmail: build.mutation<
+ SessionMetadata,
+ Arg
+ >({
+ query: body => ({
+ url: baseUrl + "login-with-email/",
+ method: "POST",
+ body,
+ }),
+ }),
+ loginWithOtp: build.mutation({
+ query: body => ({
+ url: baseUrl + "login-with-otp/",
+ method: "POST",
+ body,
+ }),
+ }),
+ loginWithOtpBypassToken: build.mutation<
+ SessionMetadata,
+ Arg
+ >({
+ query: body => ({
+ url: baseUrl + "login-with-otp-bypass-token/",
+ method: "POST",
+ body,
+ }),
+ }),
+ loginAsStudent: build.mutation<
+ SessionMetadata,
+ Arg & { class_id: Class["id"] }
+ >({
+ query: body => ({
+ url: baseUrl + "login-as-student/",
+ method: "POST",
+ body,
+ }),
+ }),
+ autoLoginAsStudent: build.mutation<
+ SessionMetadata,
+ {
+ student_id: Student["id"]
+ auto_gen_password: string
+ }
+ >({
+ query: body => ({
+ url: baseUrl + "auto-login-as-student/",
+ method: "POST",
+ body,
+ }),
+ }),
+ }),
+})
+
+export default ssoApi
+export const {
+ useLoginWithEmailMutation,
+ useLoginWithOtpMutation,
+ useLoginWithOtpBypassTokenMutation,
+ useLoginAsStudentMutation,
+ useAutoLoginAsStudentMutation,
+} = ssoApi
diff --git a/src/api/student.ts b/src/api/student.ts
index 36fb51d..d74cb29 100644
--- a/src/api/student.ts
+++ b/src/api/student.ts
@@ -8,7 +8,7 @@ import {
type BulkDestroyResult,
type BulkUpdateArg,
type BulkUpdateResult,
-} from "codeforlife/utils/rtkQuery"
+} from "codeforlife/utils/api"
import api from "."
diff --git a/src/api/teacher.ts b/src/api/teacher.ts
index f3b6a9a..ab228ce 100644
--- a/src/api/teacher.ts
+++ b/src/api/teacher.ts
@@ -8,7 +8,7 @@ import {
type DestroyResult,
type UpdateArg,
type UpdateResult,
-} from "codeforlife/utils/rtkQuery"
+} from "codeforlife/utils/api"
import api from "."
diff --git a/src/api/user.ts b/src/api/user.ts
index 7f037de..e64f1c9 100644
--- a/src/api/user.ts
+++ b/src/api/user.ts
@@ -13,7 +13,7 @@ import {
type RetrieveResult,
type UpdateArg,
type UpdateResult,
-} from "codeforlife/utils/rtkQuery"
+} from "codeforlife/utils/api"
import api from "."
diff --git a/src/app/schemas.ts b/src/app/schemas.ts
new file mode 100644
index 0000000..96a8f77
--- /dev/null
+++ b/src/app/schemas.ts
@@ -0,0 +1,35 @@
+import * as yup from "yup"
+
+export const classIdSchema = yup
+ .string()
+ .matches(/^[A-Z0-9]{5}$/, "Invalid class code")
+
+const passwordSchema = yup.string().required("required")
+
+export const teacherPasswordSchema = passwordSchema.test({
+ message: "too-weak",
+ test: password =>
+ password.length >= 10 &&
+ !(
+ password.search(/[A-Z]/) === -1 ||
+ password.search(/[a-z]/) === -1 ||
+ password.search(/[0-9]/) === -1 ||
+ password.search(/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/) === -1
+ ),
+})
+
+export const studentPasswordSchema = passwordSchema.test({
+ message: "too-weak",
+ test: password => password.length >= 6,
+})
+
+export const indyPasswordSchema = passwordSchema.test({
+ message: "too-weak",
+ test: password =>
+ password.length >= 8 &&
+ !(
+ password.search(/[A-Z]/) === -1 ||
+ password.search(/[a-z]/) === -1 ||
+ password.search(/[0-9]/) === -1
+ ),
+})
diff --git a/src/app/store.ts b/src/app/store.ts
index 9de8802..6435007 100644
--- a/src/app/store.ts
+++ b/src/app/store.ts
@@ -1,38 +1,23 @@
import type { Action, ThunkAction } from "@reduxjs/toolkit"
-import { combineSlices, configureStore } from "@reduxjs/toolkit"
-import { setupListeners } from "@reduxjs/toolkit/query"
-import { counterSlice } from "../features/counter/counterSlice"
-import { quotesApiSlice } from "../features/quotes/quotesApiSlice"
+import { combineSlices } from "@reduxjs/toolkit"
+
+import { makeStore } from "codeforlife/utils/store"
+
+import api from "../api"
// `combineSlices` automatically combines the reducers using
// their `reducerPath`s, therefore we no longer need to call `combineReducers`.
-const rootReducer = combineSlices(counterSlice, quotesApiSlice)
-// Infer the `RootState` type from the root reducer
-export type RootState = ReturnType
+const reducer = combineSlices(api)
-// The store setup is wrapped in `makeStore` to allow reuse
-// when setting up tests that need the same store config
-export const makeStore = (preloadedState?: Partial) => {
- const store = configureStore({
- reducer: rootReducer,
- // Adding the api middleware enables caching, invalidation, polling,
- // and other useful features of `rtk-query`.
- middleware: getDefaultMiddleware => {
- return getDefaultMiddleware().concat(quotesApiSlice.middleware)
- },
- preloadedState,
- })
- // configure listeners using the provided defaults
- // optional, but required for `refetchOnFocus`/`refetchOnReconnect` behaviors
- setupListeners(store.dispatch)
- return store
-}
+// Infer the `RootState` type from the root reducer
+export type RootState = ReturnType
-export const store = makeStore()
+// TODO: create middleware for api errors.
+// https://redux-toolkit.js.org/rtk-query/usage/error-handling#handling-errors-at-a-macro-level
+const store = makeStore({ reducer, middlewares: [api.middleware] })
-// Infer the type of `store`
+export default store
export type AppStore = typeof store
-// Infer the `AppDispatch` type from the store itself
export type AppDispatch = AppStore["dispatch"]
export type AppThunk = ThunkAction<
ThunkReturnType,
diff --git a/src/app/theme.ts b/src/app/theme.ts
new file mode 100644
index 0000000..e0abe14
--- /dev/null
+++ b/src/app/theme.ts
@@ -0,0 +1,16 @@
+import {
+ createTheme,
+ responsiveFontSizes,
+ type ThemeOptions,
+} from "@mui/material"
+
+import { themeOptions as cflThemeOptions } from "codeforlife/theme"
+
+// Unpack the base options to extend the theme
+export const themeOptions: ThemeOptions = {
+ ...cflThemeOptions,
+}
+
+const theme = responsiveFontSizes(createTheme(themeOptions))
+
+export default theme
diff --git a/src/features/counter/Counter.module.css b/src/features/counter/Counter.module.css
deleted file mode 100644
index a0e619d..0000000
--- a/src/features/counter/Counter.module.css
+++ /dev/null
@@ -1,81 +0,0 @@
-.row {
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.row > button {
- margin-left: 4px;
- margin-right: 8px;
-}
-
-.row:not(:last-child) {
- margin-bottom: 16px;
-}
-
-.value {
- font-size: 78px;
- padding-left: 16px;
- padding-right: 16px;
- margin-top: 2px;
- font-family: "Courier New", Courier, monospace;
-}
-
-.button {
- appearance: none;
- background: none;
- font-size: 32px;
- padding-left: 12px;
- padding-right: 12px;
- outline: none;
- border: 2px solid transparent;
- color: rgb(112, 76, 182);
- padding-bottom: 4px;
- cursor: pointer;
- background-color: rgba(112, 76, 182, 0.1);
- border-radius: 2px;
- transition: all 0.15s;
-}
-
-.textbox {
- font-size: 32px;
- padding: 2px;
- width: 64px;
- text-align: center;
- margin-right: 4px;
-}
-
-.button:hover,
-.button:focus {
- border: 2px solid rgba(112, 76, 182, 0.4);
-}
-
-.button:active {
- background-color: rgba(112, 76, 182, 0.2);
-}
-
-.asyncButton {
- composes: button;
- position: relative;
-}
-
-.asyncButton:after {
- content: "";
- background-color: rgba(112, 76, 182, 0.15);
- display: block;
- position: absolute;
- width: 100%;
- height: 100%;
- left: 0;
- top: 0;
- opacity: 0;
- transition:
- width 1s linear,
- opacity 0.5s ease 1s;
-}
-
-.asyncButton:active:after {
- width: 0%;
- opacity: 1;
- transition: 0s;
-}
diff --git a/src/features/counter/Counter.tsx b/src/features/counter/Counter.tsx
deleted file mode 100644
index a286d80..0000000
--- a/src/features/counter/Counter.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import { useState } from "react"
-
-import { useAppDispatch, useAppSelector } from "../../app/hooks"
-import styles from "./Counter.module.css"
-import {
- decrement,
- increment,
- incrementAsync,
- incrementByAmount,
- incrementIfOdd,
- selectCount,
- selectStatus,
-} from "./counterSlice"
-
-export const Counter = () => {
- const dispatch = useAppDispatch()
- const count = useAppSelector(selectCount)
- const status = useAppSelector(selectStatus)
- const [incrementAmount, setIncrementAmount] = useState("2")
-
- const incrementValue = Number(incrementAmount) || 0
-
- return (
-
-
-
-
- {count}
-
-
-
-
- {
- setIncrementAmount(e.target.value)
- }}
- />
-
-
-
-
-
- )
-}
diff --git a/src/features/counter/counterAPI.ts b/src/features/counter/counterAPI.ts
deleted file mode 100644
index aca3ef6..0000000
--- a/src/features/counter/counterAPI.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-// A mock function to mimic making an async request for data
-export const fetchCount = (amount = 1) => {
- return new Promise<{ data: number }>(resolve =>
- setTimeout(() => resolve({ data: amount }), 500),
- )
-}
diff --git a/src/features/counter/counterSlice.test.ts b/src/features/counter/counterSlice.test.ts
deleted file mode 100644
index 12eafe1..0000000
--- a/src/features/counter/counterSlice.test.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import type { AppStore } from "../../app/store"
-import { makeStore } from "../../app/store"
-import type { CounterSliceState } from "./counterSlice"
-import {
- counterSlice,
- decrement,
- increment,
- incrementByAmount,
- selectCount,
-} from "./counterSlice"
-
-interface LocalTestContext {
- store: AppStore
-}
-
-describe("counter reducer", it => {
- beforeEach(context => {
- const initialState: CounterSliceState = {
- value: 3,
- status: "idle",
- }
-
- const store = makeStore({ counter: initialState })
-
- context.store = store
- })
-
- it("should handle initial state", () => {
- expect(counterSlice.reducer(undefined, { type: "unknown" })).toStrictEqual({
- value: 0,
- status: "idle",
- })
- })
-
- it("should handle increment", ({ store }) => {
- expect(selectCount(store.getState())).toBe(3)
-
- store.dispatch(increment())
-
- expect(selectCount(store.getState())).toBe(4)
- })
-
- it("should handle decrement", ({ store }) => {
- expect(selectCount(store.getState())).toBe(3)
-
- store.dispatch(decrement())
-
- expect(selectCount(store.getState())).toBe(2)
- })
-
- it("should handle incrementByAmount", ({ store }) => {
- expect(selectCount(store.getState())).toBe(3)
-
- store.dispatch(incrementByAmount(2))
-
- expect(selectCount(store.getState())).toBe(5)
- })
-})
diff --git a/src/features/counter/counterSlice.ts b/src/features/counter/counterSlice.ts
deleted file mode 100644
index 07bc1f5..0000000
--- a/src/features/counter/counterSlice.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import type { PayloadAction } from "@reduxjs/toolkit"
-import { createAppSlice } from "../../app/createAppSlice"
-import type { AppThunk } from "../../app/store"
-import { fetchCount } from "./counterAPI"
-
-export interface CounterSliceState {
- value: number
- status: "idle" | "loading" | "failed"
-}
-
-const initialState: CounterSliceState = {
- value: 0,
- status: "idle",
-}
-
-// If you are not using async thunks you can use the standalone `createSlice`.
-export const counterSlice = createAppSlice({
- name: "counter",
- // `createSlice` will infer the state type from the `initialState` argument
- initialState,
- // The `reducers` field lets us define reducers and generate associated actions
- reducers: create => ({
- increment: create.reducer(state => {
- // Redux Toolkit allows us to write "mutating" logic in reducers. It
- // doesn't actually mutate the state because it uses the Immer library,
- // which detects changes to a "draft state" and produces a brand new
- // immutable state based off those changes
- state.value += 1
- }),
- decrement: create.reducer(state => {
- state.value -= 1
- }),
- // Use the `PayloadAction` type to declare the contents of `action.payload`
- incrementByAmount: create.reducer(
- (state, action: PayloadAction) => {
- state.value += action.payload
- },
- ),
- // The function below is called a thunk and allows us to perform async logic. It
- // can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
- // will call the thunk with the `dispatch` function as the first argument. Async
- // code can then be executed and other actions can be dispatched. Thunks are
- // typically used to make async requests.
- incrementAsync: create.asyncThunk(
- async (amount: number) => {
- const response = await fetchCount(amount)
- // The value we return becomes the `fulfilled` action payload
- return response.data
- },
- {
- pending: state => {
- state.status = "loading"
- },
- fulfilled: (state, action) => {
- state.status = "idle"
- state.value += action.payload
- },
- rejected: state => {
- state.status = "failed"
- },
- },
- ),
- }),
- // You can define your selectors here. These selectors receive the slice
- // state as their first argument.
- selectors: {
- selectCount: counter => counter.value,
- selectStatus: counter => counter.status,
- },
-})
-
-// Action creators are generated for each case reducer function.
-export const { decrement, increment, incrementByAmount, incrementAsync } =
- counterSlice.actions
-
-// Selectors returned by `slice.selectors` take the root state as their first argument.
-export const { selectCount, selectStatus } = counterSlice.selectors
-
-// We can also write thunks by hand, which may contain both sync and async logic.
-// Here's an example of conditionally dispatching actions based on current state.
-export const incrementIfOdd =
- (amount: number): AppThunk =>
- (dispatch, getState) => {
- const currentValue = selectCount(getState())
-
- if (currentValue % 2 === 1 || currentValue % 2 === -1) {
- dispatch(incrementByAmount(amount))
- }
- }
diff --git a/src/features/quotes/Quotes.module.css b/src/features/quotes/Quotes.module.css
deleted file mode 100644
index 1f85690..0000000
--- a/src/features/quotes/Quotes.module.css
+++ /dev/null
@@ -1,20 +0,0 @@
-.select {
- font-size: 25px;
- padding: 5px;
- padding-top: 2px;
- padding-bottom: 2px;
- size: 50;
- outline: none;
- border: 2px solid transparent;
- color: rgb(112, 76, 182);
- cursor: pointer;
- background-color: rgba(112, 76, 182, 0.1);
- border-radius: 5px;
- transition: all 0.15s;
-}
-
-.container {
- display: flex;
- flex-direction: column;
- align-items: center;
-}
diff --git a/src/features/quotes/Quotes.tsx b/src/features/quotes/Quotes.tsx
deleted file mode 100644
index c490c4a..0000000
--- a/src/features/quotes/Quotes.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { useState } from "react"
-import styles from "./Quotes.module.css"
-import { useGetQuotesQuery } from "./quotesApiSlice"
-
-const options = [5, 10, 20, 30]
-
-export const Quotes = () => {
- const [numberOfQuotes, setNumberOfQuotes] = useState(10)
- // Using a query hook automatically fetches data and returns query values
- const { data, isError, isLoading, isSuccess } =
- useGetQuotesQuery(numberOfQuotes)
-
- if (isError) {
- return (
-
-
There was an error!!!
-
- )
- }
-
- if (isLoading) {
- return (
-
-
Loading...
-
- )
- }
-
- if (isSuccess) {
- return (
-
-
Select the Quantity of Quotes to Fetch:
-
- {data.quotes.map(({ author, quote, id }) => (
-
- “{quote}”
-
-
- ))}
-
- )
- }
-
- return null
-}
diff --git a/src/features/quotes/quotesApiSlice.ts b/src/features/quotes/quotesApiSlice.ts
deleted file mode 100644
index a1c7b5a..0000000
--- a/src/features/quotes/quotesApiSlice.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-// Need to use the React-specific entry point to import `createApi`
-import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
-
-interface Quote {
- id: number
- quote: string
- author: string
-}
-
-interface QuotesApiResponse {
- quotes: Quote[]
- total: number
- skip: number
- limit: number
-}
-
-// Define a service using a base URL and expected endpoints
-export const quotesApiSlice = createApi({
- baseQuery: fetchBaseQuery({ baseUrl: "https://dummyjson.com/quotes" }),
- reducerPath: "quotesApi",
- // Tag types are used for caching and invalidation.
- tagTypes: ["Quotes"],
- endpoints: build => ({
- // Supply generics for the return type (in this case `QuotesApiResponse`)
- // and the expected query argument. If there is no argument, use `void`
- // for the argument type instead.
- getQuotes: build.query({
- query: (limit = 10) => `?limit=${limit}`,
- // `providesTags` determines which 'tag' is attached to the
- // cached data returned by the query.
- providesTags: (result, error, id) => [{ type: "Quotes", id }],
- }),
- }),
-})
-
-// Hooks are auto-generated by RTK-Query
-// Same as `quotesApiSlice.endpoints.getQuotes.useQuery`
-export const { useGetQuotesQuery } = quotesApiSlice
diff --git a/src/index.css b/src/index.css
deleted file mode 100644
index 4a1df4d..0000000
--- a/src/index.css
+++ /dev/null
@@ -1,13 +0,0 @@
-body {
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
- "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
- sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
- monospace;
-}
diff --git a/src/logo.svg b/src/logo.svg
deleted file mode 100644
index 8466738..0000000
--- a/src/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/main.tsx b/src/main.tsx
index 45c0705..ef92ba8 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,9 +1,9 @@
import React from "react"
import { createRoot } from "react-dom/client"
import { Provider } from "react-redux"
+
import App from "./App"
-import { store } from "./app/store"
-import "./index.css"
+import store from "./app/store"
const container = document.getElementById("root")
diff --git a/src/pages/login/BaseForm.tsx b/src/pages/login/BaseForm.tsx
new file mode 100644
index 0000000..eb516bf
--- /dev/null
+++ b/src/pages/login/BaseForm.tsx
@@ -0,0 +1,45 @@
+import { Stack, Typography, useTheme } from "@mui/material"
+import { type FormikValues } from "formik"
+
+import { Form, type FormProps } from "codeforlife/components/form"
+import { ThemedBox, type ThemedBoxProps } from "codeforlife/theme"
+
+import { themeOptions } from "../../app/theme"
+
+export interface BaseFormProps extends FormProps {
+ themedBoxProps: Omit
+ header: string
+ subheader?: string
+}
+
+const BaseForm = ({
+ themedBoxProps,
+ header,
+ subheader,
+ ...formProps
+}: BaseFormProps): JSX.Element => {
+ const theme = useTheme()
+
+ return (
+
+
+
+ {header}
+
+ {subheader && (
+
+ {subheader}
+
+ )}
+
+
+
+ )
+}
+
+export default BaseForm
diff --git a/src/pages/login/IndyForm.tsx b/src/pages/login/IndyForm.tsx
new file mode 100644
index 0000000..7eb1209
--- /dev/null
+++ b/src/pages/login/IndyForm.tsx
@@ -0,0 +1,60 @@
+import { Stack, Typography } from "@mui/material"
+import type { FC } from "react"
+
+import * as form from "codeforlife/components/form"
+import { Link } from "codeforlife/components/router"
+import { useNavigate } from "codeforlife/hooks"
+import { submitForm } from "codeforlife/utils/form"
+
+import { useLoginWithEmailMutation } from "../../api/sso"
+import { paths } from "../../router"
+import BaseForm from "./BaseForm"
+
+export interface IndyFormProps {}
+
+const IndyForm: FC = () => {
+ const [loginWithEmail] = useLoginWithEmailMutation()
+ const navigate = useNavigate()
+
+ return (
+ {
+ navigate(paths.indy.dashboard._)
+ },
+ })}
+ >
+
+
+
+
+ Forgotten your password?
+
+
+ Don't worry, you can
+
+ reset your password
+
+ .
+
+
+
+
+ Part of a school or club?
+
+ Log in here
+
+
+
+
+ Log in
+
+
+ )
+}
+
+export default IndyForm
diff --git a/src/pages/login/Login.test.tsx b/src/pages/login/Login.test.tsx
new file mode 100644
index 0000000..0eceb8c
--- /dev/null
+++ b/src/pages/login/Login.test.tsx
@@ -0,0 +1,2 @@
+// TODO: replace with proper tests
+test("Dummy test", async () => {})
diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx
new file mode 100644
index 0000000..0bafb8b
--- /dev/null
+++ b/src/pages/login/Login.tsx
@@ -0,0 +1,83 @@
+import { useEffect, type FC } from "react"
+import * as yup from "yup"
+
+import * as page from "codeforlife/components/page"
+import {
+ useNavigate,
+ useSearchParamEntries,
+ useSessionMetadata,
+} from "codeforlife/hooks"
+import { tryValidateSync } from "codeforlife/utils/schema"
+
+import { paths } from "../../router"
+import IndyForm from "./IndyForm"
+import * as studentForms from "./studentForms"
+import * as teacherForms from "./teacherForms"
+
+export interface LoginProps {
+ form:
+ | "teacher-email"
+ | "teacher-otp"
+ | "teacher-otp-bypass-token"
+ | "student-class"
+ | "student-first-name"
+ | "indy"
+}
+
+const Login: FC = ({ form }) => {
+ const sessionMetadata = useSessionMetadata()
+ const navigate = useNavigate()
+
+ const searchParams = tryValidateSync(
+ useSearchParamEntries(),
+ yup.object({
+ verifyEmail: yup.boolean().default(false),
+ }),
+ )
+
+ useEffect(() => {
+ if (sessionMetadata) {
+ if (
+ sessionMetadata.user_type === "teacher" &&
+ sessionMetadata.auth_factors.includes("otp") &&
+ form !== "teacher-otp" &&
+ form !== "teacher-otp-bypass-token"
+ ) {
+ navigate(paths.login.teacher.otp._, { replace: true })
+ } else {
+ navigate(
+ {
+ teacher: paths.teacher.dashboard.school._,
+ student: paths.student.dashboard._,
+ indy: paths.indy.dashboard._,
+ }[sessionMetadata.user_type],
+ { replace: true },
+ )
+ }
+ }
+ }, [sessionMetadata, navigate, form])
+
+ return (
+
+ {searchParams?.verifyEmail && (
+
+ Your email address was successfully verified, please log in.
+
+ )}
+
+ {
+ {
+ "teacher-email": ,
+ "teacher-otp": ,
+ "teacher-otp-bypass-token": ,
+ "student-class": ,
+ "student-first-name": ,
+ indy: ,
+ }[form]
+ }
+
+
+ )
+}
+
+export default Login
diff --git a/src/pages/login/studentForms/Class.tsx b/src/pages/login/studentForms/Class.tsx
new file mode 100644
index 0000000..9ff4e4e
--- /dev/null
+++ b/src/pages/login/studentForms/Class.tsx
@@ -0,0 +1,93 @@
+import { ChevronRight as ChevronRightIcon } from "@mui/icons-material"
+import { Stack, Typography } from "@mui/material"
+import { useEffect, type FC } from "react"
+import { generatePath } from "react-router-dom"
+import * as yup from "yup"
+
+import * as form from "codeforlife/components/form"
+import { useNavigate, useSearchParamEntries } from "codeforlife/hooks"
+import { tryValidateSync } from "codeforlife/utils/schema"
+
+import { useAutoLoginAsStudentMutation } from "../../../api/sso"
+import { classIdSchema } from "../../../app/schemas"
+import { paths } from "../../../router"
+import BaseForm from "../BaseForm"
+
+export interface ClassProps {}
+
+const Class: FC = () => {
+ const [autoLoginAsStudent] = useAutoLoginAsStudentMutation()
+ const navigate = useNavigate()
+
+ const searchParams = tryValidateSync(
+ useSearchParamEntries(),
+ yup.object({
+ id: yup.number().required(),
+ agp: yup.string().required(),
+ }),
+ )
+
+ useEffect(() => {
+ if (searchParams) {
+ autoLoginAsStudent({
+ student_id: searchParams.id,
+ auto_gen_password: searchParams.agp,
+ })
+ .unwrap()
+ .then(() => {
+ navigate(paths.student.dashboard._)
+ })
+ .catch(() => {
+ navigate(".", {
+ replace: true,
+ state: {
+ notifications: [
+ {
+ props: {
+ error: true,
+ children:
+ "Failed to automatically log in student. Please log" +
+ "in manually.",
+ },
+ },
+ ],
+ },
+ })
+ })
+ }
+ }, [searchParams, autoLoginAsStudent, navigate])
+
+ return (
+ <>
+ {!searchParams && (
+ {
+ navigate(generatePath(paths.login.student.class._, { classId }))
+ }}
+ >
+
+
+ Forgotten your login details? Please check with your teacher.
+
+
+ }>
+ Next
+
+
+
+ )}
+ >
+ )
+}
+
+export default Class
diff --git a/src/pages/login/studentForms/FirstName.tsx b/src/pages/login/studentForms/FirstName.tsx
new file mode 100644
index 0000000..b1aa6bf
--- /dev/null
+++ b/src/pages/login/studentForms/FirstName.tsx
@@ -0,0 +1,76 @@
+import { ChevronRight as ChevronRightIcon } from "@mui/icons-material"
+import { Stack } from "@mui/material"
+import { useEffect, type FC } from "react"
+import { useParams } from "react-router-dom"
+import * as yup from "yup"
+
+import * as form from "codeforlife/components/form"
+import { useNavigate } from "codeforlife/hooks"
+import { submitForm } from "codeforlife/utils/form"
+import { tryValidateSync } from "codeforlife/utils/schema"
+
+import { useLoginAsStudentMutation } from "../../../api/sso"
+import { classIdSchema } from "../../../app/schemas"
+import { paths } from "../../../router"
+import BaseForm from "../BaseForm"
+
+export interface FirstNameProps {}
+
+const FirstName: FC = () => {
+ const [loginAsStudent] = useLoginAsStudentMutation()
+ const navigate = useNavigate()
+
+ const params = tryValidateSync(
+ useParams(),
+ yup.object({ classId: classIdSchema.required() }),
+ )
+
+ useEffect(() => {
+ if (!params) {
+ navigate(paths.login.student._, {
+ state: {
+ notifications: [
+ {
+ props: {
+ error: true,
+ children: "Please provide the correct code for your class.",
+ },
+ },
+ ],
+ },
+ })
+ }
+ }, [navigate, params])
+
+ return (
+ <>
+ {params && (
+ {
+ navigate(paths.student.dashboard._)
+ },
+ })}
+ >
+
+
+
+ }>
+ Log in
+
+
+
+ )}
+ >
+ )
+}
+
+export default FirstName
diff --git a/src/pages/login/studentForms/index.tsx b/src/pages/login/studentForms/index.tsx
new file mode 100644
index 0000000..662cdb3
--- /dev/null
+++ b/src/pages/login/studentForms/index.tsx
@@ -0,0 +1,4 @@
+import Class, { type ClassProps } from "./Class"
+import FirstName, { type FirstNameProps } from "./FirstName"
+
+export { Class, FirstName, type ClassProps, type FirstNameProps }
diff --git a/src/pages/login/teacherForms/Email.tsx b/src/pages/login/teacherForms/Email.tsx
new file mode 100644
index 0000000..2e742f1
--- /dev/null
+++ b/src/pages/login/teacherForms/Email.tsx
@@ -0,0 +1,53 @@
+import { Stack, Typography } from "@mui/material"
+import type { FC } from "react"
+
+import * as form from "codeforlife/components/form"
+import { Link } from "codeforlife/components/router"
+import { useNavigate } from "codeforlife/hooks"
+import { submitForm } from "codeforlife/utils/form"
+
+import { useLoginWithEmailMutation } from "../../../api/sso"
+import { paths } from "../../../router"
+import BaseForm from "../BaseForm"
+
+export interface EmailProps {}
+
+const Email: FC = () => {
+ const [loginWithEmail] = useLoginWithEmailMutation()
+ const navigate = useNavigate()
+
+ return (
+ {
+ navigate(
+ auth_factors.includes("otp")
+ ? paths.login.teacher.otp._
+ : paths.teacher.dashboard.school._,
+ )
+ },
+ })}
+ >
+
+
+
+
+ Forgotten your password?
+
+
+ Don't worry, you can
+ reset your password.
+
+
+
+ Log in
+
+
+ )
+}
+
+export default Email
diff --git a/src/pages/login/teacherForms/Otp.tsx b/src/pages/login/teacherForms/Otp.tsx
new file mode 100644
index 0000000..72397d8
--- /dev/null
+++ b/src/pages/login/teacherForms/Otp.tsx
@@ -0,0 +1,53 @@
+import { Stack } from "@mui/material"
+import { type FC } from "react"
+
+import * as form from "codeforlife/components/form"
+import { LinkButton } from "codeforlife/components/router"
+import { useNavigate, useSession } from "codeforlife/hooks"
+import { submitForm } from "codeforlife/utils/form"
+
+import { useLoginWithOtpMutation } from "../../../api/sso"
+import { paths } from "../../../router"
+import BaseForm from "../BaseForm"
+
+export interface OtpProps {}
+
+const Otp: FC = () => {
+ const [loginWithOtp] = useLoginWithOtpMutation()
+ const navigate = useNavigate()
+
+ return useSession(
+ ({ otp_bypass_token_exists }) => (
+ {
+ navigate(paths.teacher.dashboard.school._)
+ },
+ })}
+ >
+
+ {otp_bypass_token_exists && (
+
+ Use an otp-bypass token
+
+ )}
+
+
+ Cancel
+
+ Log in
+
+
+ ),
+ { userType: "teacher", next: false },
+ )
+}
+
+export default Otp
diff --git a/src/pages/login/teacherForms/OtpBypassToken.tsx b/src/pages/login/teacherForms/OtpBypassToken.tsx
new file mode 100644
index 0000000..4a8cb8d
--- /dev/null
+++ b/src/pages/login/teacherForms/OtpBypassToken.tsx
@@ -0,0 +1,61 @@
+import { Stack, Typography, useTheme } from "@mui/material"
+import type { FC } from "react"
+import * as yup from "yup"
+
+import * as form from "codeforlife/components/form"
+import { LinkButton } from "codeforlife/components/router"
+import { useNavigate, useSession } from "codeforlife/hooks"
+import { submitForm } from "codeforlife/utils/form"
+
+import { useLoginWithOtpBypassTokenMutation } from "../../../api/sso"
+import { paths } from "../../../router"
+import BaseForm from "../BaseForm"
+
+export interface OtpBypassTokenProps {}
+
+const OtpBypassToken: FC = () => {
+ const [loginWithOtpBypassToken] = useLoginWithOtpBypassTokenMutation()
+ const navigate = useNavigate()
+ const theme = useTheme()
+
+ return useSession(
+ {
+ navigate(paths.teacher.dashboard.school._)
+ },
+ })}
+ >
+
+ Use this form for entering backup tokens for logging in. These tokens
+ have been generated for you to print and keep safe. Please enter one of
+ these backup tokens to login to your account.
+
+
+
+
+ Cancel
+
+ Log in
+
+ ,
+ { userType: "teacher", next: false },
+ )
+}
+
+export default OtpBypassToken
diff --git a/src/pages/login/teacherForms/index.tsx b/src/pages/login/teacherForms/index.tsx
new file mode 100644
index 0000000..78d66a2
--- /dev/null
+++ b/src/pages/login/teacherForms/index.tsx
@@ -0,0 +1,12 @@
+import Email, { type EmailProps } from "./Email"
+import Otp, { type OtpProps } from "./Otp"
+import OtpBypassToken, { type OtpBypassTokenProps } from "./OtpBypassToken"
+
+export {
+ Email,
+ Otp,
+ OtpBypassToken,
+ type EmailProps,
+ type OtpBypassTokenProps,
+ type OtpProps,
+}
diff --git a/src/router/Router.tsx b/src/router/Router.tsx
new file mode 100644
index 0000000..5758aab
--- /dev/null
+++ b/src/router/Router.tsx
@@ -0,0 +1,28 @@
+import type { FC } from "react"
+import { BrowserRouter, Routes } from "react-router-dom"
+
+// import Header from '../../features/header/Header';
+// import Footer from '../../features/footer/Footer';
+// import general from './routes/general';
+import authentication from "./routes/authentication"
+// import teacher from './routes/teacher';
+// import student from './routes/student';
+// import error from './routes/error';
+
+export interface RouterProps {}
+
+const Router: FC = () => (
+
+ {/* */}
+
+ {/* {general} */}
+ {authentication}
+ {/* {teacher} */}
+ {/* {student} */}
+ {/* {error} */} {/* this must be last */}
+
+ {/* */}
+
+)
+
+export default Router
diff --git a/src/router/index.ts b/src/router/index.ts
new file mode 100644
index 0000000..6b8116d
--- /dev/null
+++ b/src/router/index.ts
@@ -0,0 +1,5 @@
+import Router, { type RouterProps } from "./Router"
+import paths from "./paths"
+
+export default Router
+export { paths, type RouterProps }
diff --git a/src/router/paths.ts b/src/router/paths.ts
new file mode 100644
index 0000000..327104d
--- /dev/null
+++ b/src/router/paths.ts
@@ -0,0 +1,92 @@
+import { path as _ } from "codeforlife/utils/router"
+
+const paths = _("", {
+ login: _("/login", {
+ teacher: _("/teacher", {
+ otp: _("/otp", {
+ bypassToken: _("/bypass-token"),
+ }),
+ }),
+ student: _("/student", {
+ class: _("/:classId"),
+ }),
+ independent: _("/independent"),
+ }),
+ resetPassword: _("/reset-password", {
+ teacher: _("/teacher"),
+ independent: _("/independent"),
+ }),
+ teacher: _("/teacher", {
+ onboarding: _("/onboarding"),
+ dashboard: _("/dashboard", {
+ school: _("/school", {
+ leave: _("/leave"),
+ }),
+ classes: _("/classes", {
+ editClass: _("/:accessCode", {
+ additional: _("/additional"),
+ studentCredentials: _("/student-credentials"),
+ editStudent: _("/edit/?studentIds={studentIds}"),
+ resetStudents: _("/reset/?studentIds={studentIds}"),
+ moveStudents: _("/move/?studentIds={studentIds}"),
+ releaseStudents: _("/release/?studentIds={studentIds}"),
+ }),
+ }),
+ account: _("/account", {
+ setup2FA: _("/setup-2fa"),
+ backupTokens: _("/backup-tokens"),
+ }),
+ student: _("/student", {
+ accept: _("/accept/:studentId"),
+ added: _("/added"),
+ }),
+ }),
+ }),
+ student: _("/student", {
+ dashboard: _("/dashboard", {
+ account: _("/account"),
+ }),
+ }),
+ indy: _("/independent", {
+ dashboard: _("/dashboard", {
+ account: _("/account"),
+ joinClass: _("/join-class"),
+ }),
+ }),
+ register: _("/register", {
+ emailVerification: _("/email-verification", {
+ teacher: _("/teacher"),
+ student: _("/student"),
+ indy: _("/independent"),
+ }),
+ }),
+ aboutUs: _("/about-us"),
+ codingClubs: _("/coding-clubs"),
+ getInvolved: _("/get-involved"),
+ contribute: _("/contribute"),
+ homeLearning: _("/home-learning"),
+ privacyNotice: _("/privacy-notice", {
+ privacyNotice: _("/privacy-notice"),
+ childFriendly: _("/child-friendly"),
+ }),
+ termsOfUse: _("/terms-of-use", {
+ termsOfUse: _("/terms-of-use"),
+ childFriendly: _("/child-friendly"),
+ }),
+ communicationPreferences: _("/communication-preferences"),
+ error: _("/error", {
+ forbidden: _("/forbidden"),
+ pageNotFound: _("/page-not-found"),
+ tooManyRequests: _("/too-many-requests", {
+ teacher: _("/teacher"),
+ indy: _("/independent"),
+ student: _("/student"),
+ }),
+ internalServerError: _("/internal-server-error"),
+ }),
+ rapidRouter: _("/rapid-router", {
+ scoreboard: _("/scoreboard"),
+ }),
+})
+
+export default paths
diff --git a/src/router/routes/authentication.tsx b/src/router/routes/authentication.tsx
new file mode 100644
index 0000000..df70e3b
--- /dev/null
+++ b/src/router/routes/authentication.tsx
@@ -0,0 +1,49 @@
+import { Route } from "react-router-dom"
+
+// eslint-disable-next-line max-len
+// import EmailVerification from '../../../pages/emailVerification/EmailVerification'
+import Login from "../../pages/login/Login"
+// import Register from '../../../pages/register/Register'
+// import ResetPassword from '../../../pages/resetPassword/ResetPassword'
+import paths from "../paths"
+
+// }
+// />
+// }
+// />
+// }
+// />
+
+const authentication = (
+ <>
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ } />
+ >
+)
+
+export default authentication
diff --git a/src/utils/test-utils.tsx b/src/utils/test-utils.tsx
deleted file mode 100644
index 90e184a..0000000
--- a/src/utils/test-utils.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import type { RenderOptions } from "@testing-library/react"
-import { render } from "@testing-library/react"
-import userEvent from "@testing-library/user-event"
-import type { PropsWithChildren, ReactElement } from "react"
-import { Provider } from "react-redux"
-import type { AppStore, RootState } from "../app/store"
-import { makeStore } from "../app/store"
-
-/**
- * This type extends the default options for
- * React Testing Library's render function. It allows for
- * additional configuration such as specifying an initial Redux state and
- * a custom store instance.
- */
-interface ExtendedRenderOptions extends Omit {
- /**
- * Defines a specific portion or the entire initial state for the Redux store.
- * This is particularly useful for initializing the state in a
- * controlled manner during testing, allowing components to be rendered
- * with predetermined state conditions.
- */
- preloadedState?: Partial
-
- /**
- * Allows the use of a specific Redux store instance instead of a
- * default or global store. This flexibility is beneficial when
- * testing components with unique store requirements or when isolating
- * tests from a global store state. The custom store should be configured
- * to match the structure and middleware of the store used by the application.
- *
- * @default makeStore(preloadedState)
- */
- store?: AppStore
-}
-
-/**
- * Renders the given React element with Redux Provider and custom store.
- * This function is useful for testing components that are connected to the Redux store.
- *
- * @param ui - The React component or element to render.
- * @param extendedRenderOptions - Optional configuration options for rendering. This includes `preloadedState` for initial Redux state and `store` for a specific Redux store instance. Any additional properties are passed to React Testing Library's render function.
- * @returns An object containing the Redux store used in the render, User event API for simulating user interactions in tests, and all of React Testing Library's query functions for testing the component.
- */
-export const renderWithProviders = (
- ui: ReactElement,
- extendedRenderOptions: ExtendedRenderOptions = {},
-) => {
- const {
- preloadedState = {},
- // Automatically create a store instance if no store was passed in
- store = makeStore(preloadedState),
- ...renderOptions
- } = extendedRenderOptions
-
- const Wrapper = ({ children }: PropsWithChildren) => (
- {children}
- )
-
- // Return an object with the store and all of RTL's query functions
- return {
- store,
- user: userEvent.setup(),
- ...render(ui, { wrapper: Wrapper, ...renderOptions }),
- }
-}
diff --git a/vite.config.ts b/vite.config.ts
index cda7433..ca274c8 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -24,4 +24,8 @@ export default defineConfig({
reporter: ["html", "cobertura"],
},
},
+ optimizeDeps: {
+ // TODO: investigate which of these are needed
+ include: ["@mui/icons-material", "yup", "formik"],
+ },
})
diff --git a/yarn.lock b/yarn.lock
index 9d57099..7809a22 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,7 +2,7 @@
# yarn lockfile v1
-"@adobe/css-tools@^4.3.2":
+"@adobe/css-tools@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.0.tgz#728c484f4e10df03d5a3acd0d8adcbbebff8ad63"
integrity sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==
@@ -1266,120 +1266,120 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6"
integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
-"@esbuild/aix-ppc64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
- integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==
-
-"@esbuild/android-arm64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9"
- integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==
-
-"@esbuild/android-arm@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995"
- integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==
-
-"@esbuild/android-x64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98"
- integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==
-
-"@esbuild/darwin-arm64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb"
- integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==
-
-"@esbuild/darwin-x64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0"
- integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==
-
-"@esbuild/freebsd-arm64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911"
- integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==
-
-"@esbuild/freebsd-x64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c"
- integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==
-
-"@esbuild/linux-arm64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5"
- integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==
-
-"@esbuild/linux-arm@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c"
- integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==
-
-"@esbuild/linux-ia32@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa"
- integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==
-
-"@esbuild/linux-loong64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5"
- integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==
-
-"@esbuild/linux-mips64el@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa"
- integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==
-
-"@esbuild/linux-ppc64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20"
- integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==
-
-"@esbuild/linux-riscv64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300"
- integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==
-
-"@esbuild/linux-s390x@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685"
- integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==
-
-"@esbuild/linux-x64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff"
- integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==
-
-"@esbuild/netbsd-x64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6"
- integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==
-
-"@esbuild/openbsd-x64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf"
- integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==
-
-"@esbuild/sunos-x64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f"
- integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==
-
-"@esbuild/win32-arm64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90"
- integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==
-
-"@esbuild/win32-ia32@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23"
- integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==
-
-"@esbuild/win32-x64@0.20.2":
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc"
- integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==
+"@esbuild/aix-ppc64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
+ integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
+
+"@esbuild/android-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
+ integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
+
+"@esbuild/android-arm@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
+ integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
+
+"@esbuild/android-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
+ integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
+
+"@esbuild/darwin-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
+ integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
+
+"@esbuild/darwin-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
+ integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
+
+"@esbuild/freebsd-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
+ integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
+
+"@esbuild/freebsd-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
+ integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
+
+"@esbuild/linux-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
+ integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
+
+"@esbuild/linux-arm@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
+ integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
+
+"@esbuild/linux-ia32@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
+ integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
+
+"@esbuild/linux-loong64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
+ integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
+
+"@esbuild/linux-mips64el@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
+ integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
+
+"@esbuild/linux-ppc64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
+ integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
+
+"@esbuild/linux-riscv64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
+ integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
+
+"@esbuild/linux-s390x@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
+ integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
+
+"@esbuild/linux-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0"
+ integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
+
+"@esbuild/netbsd-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
+ integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
+
+"@esbuild/openbsd-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
+ integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
+
+"@esbuild/sunos-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
+ integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
+
+"@esbuild/win32-arm64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
+ integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
+
+"@esbuild/win32-ia32@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
+ integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
+
+"@esbuild/win32-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c"
+ integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
"@eslint-community/eslint-utils@^4.2.0":
version "4.4.0"
@@ -1516,29 +1516,29 @@
clsx "^2.1.0"
prop-types "^15.8.1"
-"@mui/core-downloads-tracker@^5.15.19":
- version "5.15.19"
- resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.19.tgz#7af0025c871f126367a55219486681954e4821d7"
- integrity sha512-tCHSi/Tomez9ERynFhZRvFO6n9ATyrPs+2N80DMDzp6xDVirbBjEwhPcE+x7Lj+nwYw0SqFkOxyvMP0irnm55w==
+"@mui/core-downloads-tracker@^5.15.20":
+ version "5.15.20"
+ resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.20.tgz#6ede705770797c32f5a4fc0d3002ad0b758d23e8"
+ integrity sha512-DoL2ppgldL16utL8nNyj/P12f8mCNdx/Hb/AJnX9rLY4b52hCMIx1kH83pbXQ6uMy6n54M3StmEbvSGoj2OFuA==
"@mui/icons-material@^5.11.11":
- version "5.15.19"
- resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.19.tgz#0602da80d814af662812659eab891e435ec0d5c0"
- integrity sha512-RsEiRxA5azN9b8gI7JRqekkgvxQUlitoBOtZglflb8cUDyP12/cP4gRwhb44Ea1/zwwGGjAj66ZJpGHhKfibNA==
+ version "5.15.20"
+ resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.20.tgz#dbd45d635d82b034fb10dadb5c258c26e3311618"
+ integrity sha512-oGcKmCuHaYbAAoLN67WKSXtHmEgyWcJToT1uRtmPyxMj9N5uqwc/mRtEnst4Wj/eGr+zYH2FiZQ79v9k7kSk1Q==
dependencies:
"@babel/runtime" "^7.23.9"
"@mui/material@^5.11.12":
- version "5.15.19"
- resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.19.tgz#a5bd50b6e68cee4ed39ea91dbecede5a020aaa97"
- integrity sha512-lp5xQBbcRuxNtjpWU0BWZgIrv2XLUz4RJ0RqFXBdESIsKoGCQZ6P3wwU5ZPuj5TjssNiKv9AlM+vHopRxZhvVQ==
+ version "5.15.20"
+ resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.20.tgz#87737404603ca2802a8e074b059f8329e013e615"
+ integrity sha512-tVq3l4qoXx/NxUgIx/x3lZiPn/5xDbdTE8VrLczNpfblLYZzlrbxA7kb9mI8NoBF6+w9WE9IrxWnKK5KlPI2bg==
dependencies:
"@babel/runtime" "^7.23.9"
"@mui/base" "5.0.0-beta.40"
- "@mui/core-downloads-tracker" "^5.15.19"
- "@mui/system" "^5.15.15"
+ "@mui/core-downloads-tracker" "^5.15.20"
+ "@mui/system" "^5.15.20"
"@mui/types" "^7.2.14"
- "@mui/utils" "^5.15.14"
+ "@mui/utils" "^5.15.20"
"@types/react-transition-group" "^4.4.10"
clsx "^2.1.0"
csstype "^3.1.3"
@@ -1546,13 +1546,13 @@
react-is "^18.2.0"
react-transition-group "^4.4.5"
-"@mui/private-theming@^5.15.14":
- version "5.15.14"
- resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.14.tgz#edd9a82948ed01586a01c842eb89f0e3f68970ee"
- integrity sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==
+"@mui/private-theming@^5.15.20":
+ version "5.15.20"
+ resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.20.tgz#028c4e3c717a13691ac2c8c98e29aa819d89001a"
+ integrity sha512-BK8F94AIqSrnaPYXf2KAOjGZJgWfvqAVQ2gVR3EryvQFtuBnG6RwodxrCvd3B48VuMy6Wsk897+lQMUxJyk+6g==
dependencies:
"@babel/runtime" "^7.23.9"
- "@mui/utils" "^5.15.14"
+ "@mui/utils" "^5.15.20"
prop-types "^15.8.1"
"@mui/styled-engine@^5.15.14":
@@ -1565,16 +1565,16 @@
csstype "^3.1.3"
prop-types "^15.8.1"
-"@mui/system@^5.15.15":
- version "5.15.15"
- resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.15.tgz#658771b200ce3c4a0f28e58169f02e5e718d1c53"
- integrity sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==
+"@mui/system@^5.15.20":
+ version "5.15.20"
+ resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.20.tgz#f1933aabc4c10f8580c7a951ca3b88542ef0f76b"
+ integrity sha512-LoMq4IlAAhxzL2VNUDBTQxAb4chnBe8JvRINVNDiMtHE2PiPOoHlhOPutSxEbaL5mkECPVWSv6p8JEV+uykwIA==
dependencies:
"@babel/runtime" "^7.23.9"
- "@mui/private-theming" "^5.15.14"
+ "@mui/private-theming" "^5.15.20"
"@mui/styled-engine" "^5.15.14"
"@mui/types" "^7.2.14"
- "@mui/utils" "^5.15.14"
+ "@mui/utils" "^5.15.20"
clsx "^2.1.0"
csstype "^3.1.3"
prop-types "^15.8.1"
@@ -1584,10 +1584,10 @@
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.14.tgz#8a02ac129b70f3d82f2f9b76ded2c8d48e3fc8c9"
integrity sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==
-"@mui/utils@^5.15.14":
- version "5.15.14"
- resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.14.tgz#e414d7efd5db00bfdc875273a40c0a89112ade3a"
- integrity sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==
+"@mui/utils@^5.15.14", "@mui/utils@^5.15.20":
+ version "5.15.20"
+ resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.20.tgz#92778d749ce5ded1598639b4e684aaedb1146e08"
+ integrity sha512-mAbYx0sovrnpAu1zHc3MDIhPqL8RPVC5W5xcO1b7PiSCJPtckIZmBkp8hefamAvUiAV8gpfMOM6Zb+eSisbI2A==
dependencies:
"@babel/runtime" "^7.23.9"
"@types/prop-types" "^15.7.11"
@@ -1757,11 +1757,11 @@
pretty-format "^27.0.2"
"@testing-library/jest-dom@^6.2.0":
- version "6.4.5"
- resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.5.tgz#badb40296477149136dabef32b572ddd3b56adf1"
- integrity sha512-AguB9yvTXmCnySBP1lWjfNNUwpbElsaQ567lt2VdGqAdHtpieLgjmcVyv1q7PMIvLbgpDdkWV5Ydv3FEejyp2A==
+ version "6.4.6"
+ resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.6.tgz#ec1df8108651bed5475534955565bed88c6732ce"
+ integrity sha512-8qpnGVincVDLEcQXWaHOf6zmlbwTKc6Us6PPu4CRnPXCzo2OGBS5cwgMMOWdxDpEz1mkbvXHpEy99M5Yvt682w==
dependencies:
- "@adobe/css-tools" "^4.3.2"
+ "@adobe/css-tools" "^4.4.0"
"@babel/runtime" "^7.9.2"
aria-query "^5.0.0"
chalk "^3.0.0"
@@ -1851,9 +1851,9 @@
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/node@^20.14.2":
- version "20.14.2"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.2.tgz#a5f4d2bcb4b6a87bffcaa717718c5a0f208f4a18"
- integrity sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==
+ version "20.14.5"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.5.tgz#fe35e3022ebe58b8f201580eb24e1fcfc0f2487d"
+ integrity sha512-aoRR+fJkZT2l0aGOJhuA8frnCSoNX6W7U2mpNq63+BxBIj5BQFt8rHy627kijCmm63ijdSdwvGgpUsU6MBsZZA==
dependencies:
undici-types "~5.26.4"
@@ -2001,9 +2001,9 @@
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
"@vitejs/plugin-react@^4.2.1":
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.0.tgz#f20ec2369a92d8abaaefa60da8b7157819d20481"
- integrity sha512-KcEbMsn4Dpk+LIbHMj7gDPRKaTMStxxWRkRmxsg/jVdFdJCZWt1SchZcf0M4t8lIKdwwMsEyzhrcOXRrDPtOBw==
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz#d0be6594051ded8957df555ff07a991fb618b48e"
+ integrity sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==
dependencies:
"@babel/core" "^7.24.5"
"@babel/plugin-transform-react-jsx-self" "^7.24.5"
@@ -2102,14 +2102,16 @@ acorn-jsx@^5.3.2:
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn-walk@^8.3.2:
- version "8.3.2"
- resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa"
- integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==
+ version "8.3.3"
+ resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e"
+ integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==
+ dependencies:
+ acorn "^8.11.0"
-acorn@^8.11.3, acorn@^8.9.0:
- version "8.11.3"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
- integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
+acorn@^8.11.0, acorn@^8.11.3, acorn@^8.9.0:
+ version "8.12.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c"
+ integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==
agent-base@^7.0.2, agent-base@^7.1.0:
version "7.1.1"
@@ -2287,7 +2289,7 @@ array.prototype.toreversed@^1.1.2:
es-abstract "^1.22.1"
es-shim-unscopables "^1.0.0"
-array.prototype.tosorted@^1.1.3:
+array.prototype.tosorted@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc"
integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==
@@ -2448,14 +2450,14 @@ braces@^3.0.3:
fill-range "^7.1.1"
browserslist@^4.22.2, browserslist@^4.23.0:
- version "4.23.0"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab"
- integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==
+ version "4.23.1"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.1.tgz#ce4af0534b3d37db5c1a4ca98b9080f985041e96"
+ integrity sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==
dependencies:
- caniuse-lite "^1.0.30001587"
- electron-to-chromium "^1.4.668"
+ caniuse-lite "^1.0.30001629"
+ electron-to-chromium "^1.4.796"
node-releases "^2.0.14"
- update-browserslist-db "^1.0.13"
+ update-browserslist-db "^1.0.16"
bytes@3.0.0:
version "3.0.0"
@@ -2488,10 +2490,10 @@ camelcase@^7.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048"
integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==
-caniuse-lite@^1.0.30001587:
- version "1.0.30001629"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001629.tgz#907a36f4669031bd8a1a8dbc2fa08b29e0db297e"
- integrity sha512-c3dl911slnQhmxUIT4HhYzT7wnBK/XYpGnYLOj4nJBaRiw52Ibe7YxlDaAeRECvA786zCuExhxIUJ2K7nHMrBw==
+caniuse-lite@^1.0.30001629:
+ version "1.0.30001636"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz#b15f52d2bdb95fad32c2f53c0b68032b85188a78"
+ integrity sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==
chai@^4.3.10:
version "4.4.1"
@@ -2574,9 +2576,9 @@ clsx@^2.1.0:
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
-"codeforlife@github:ocadotechnology/codeforlife-package-javascript#v2.0.0":
- version "2.0.0"
- resolved "https://codeload.github.com/ocadotechnology/codeforlife-package-javascript/tar.gz/f4fd11652ba53870e899b6e2340cb5a5afa8e3d9"
+"codeforlife@github:ocadotechnology/codeforlife-package-javascript#v2.0.3":
+ version "2.0.3"
+ resolved "https://codeload.github.com/ocadotechnology/codeforlife-package-javascript/tar.gz/de5f0defa524aa2e8c6543771718045ae649821a"
dependencies:
"@emotion/react" "^11.10.6"
"@emotion/styled" "^11.10.6"
@@ -2915,10 +2917,10 @@ eastasianwidth@^0.2.0:
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
-electron-to-chromium@^1.4.668:
- version "1.4.792"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.792.tgz#738712f99d02f70c5754ca4264782915fa946849"
- integrity sha512-rkg5/N3L+Y844JyfgPUyuKK0Hk0efo3JNxUDKvz3HgP6EmN4rNGhr2D8boLsfTV/hGo7ZGAL8djw+jlg99zQyA==
+electron-to-chromium@^1.4.796:
+ version "1.4.806"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.806.tgz#2cb046631cbabceb26fc72be68d273fa183e36bc"
+ integrity sha512-nkoEX2QIB8kwCOtvtgwhXWy2IHVcOLQZu9Qo36uaGB835mdX/h8uLRlosL6QIhLVUnAiicXRW00PwaPZC74Nrg==
emoji-regex@^8.0.0:
version "8.0.0"
@@ -3073,34 +3075,34 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
-esbuild@^0.20.1:
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1"
- integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==
+esbuild@^0.21.3:
+ version "0.21.5"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
+ integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
optionalDependencies:
- "@esbuild/aix-ppc64" "0.20.2"
- "@esbuild/android-arm" "0.20.2"
- "@esbuild/android-arm64" "0.20.2"
- "@esbuild/android-x64" "0.20.2"
- "@esbuild/darwin-arm64" "0.20.2"
- "@esbuild/darwin-x64" "0.20.2"
- "@esbuild/freebsd-arm64" "0.20.2"
- "@esbuild/freebsd-x64" "0.20.2"
- "@esbuild/linux-arm" "0.20.2"
- "@esbuild/linux-arm64" "0.20.2"
- "@esbuild/linux-ia32" "0.20.2"
- "@esbuild/linux-loong64" "0.20.2"
- "@esbuild/linux-mips64el" "0.20.2"
- "@esbuild/linux-ppc64" "0.20.2"
- "@esbuild/linux-riscv64" "0.20.2"
- "@esbuild/linux-s390x" "0.20.2"
- "@esbuild/linux-x64" "0.20.2"
- "@esbuild/netbsd-x64" "0.20.2"
- "@esbuild/openbsd-x64" "0.20.2"
- "@esbuild/sunos-x64" "0.20.2"
- "@esbuild/win32-arm64" "0.20.2"
- "@esbuild/win32-ia32" "0.20.2"
- "@esbuild/win32-x64" "0.20.2"
+ "@esbuild/aix-ppc64" "0.21.5"
+ "@esbuild/android-arm" "0.21.5"
+ "@esbuild/android-arm64" "0.21.5"
+ "@esbuild/android-x64" "0.21.5"
+ "@esbuild/darwin-arm64" "0.21.5"
+ "@esbuild/darwin-x64" "0.21.5"
+ "@esbuild/freebsd-arm64" "0.21.5"
+ "@esbuild/freebsd-x64" "0.21.5"
+ "@esbuild/linux-arm" "0.21.5"
+ "@esbuild/linux-arm64" "0.21.5"
+ "@esbuild/linux-ia32" "0.21.5"
+ "@esbuild/linux-loong64" "0.21.5"
+ "@esbuild/linux-mips64el" "0.21.5"
+ "@esbuild/linux-ppc64" "0.21.5"
+ "@esbuild/linux-riscv64" "0.21.5"
+ "@esbuild/linux-s390x" "0.21.5"
+ "@esbuild/linux-x64" "0.21.5"
+ "@esbuild/netbsd-x64" "0.21.5"
+ "@esbuild/openbsd-x64" "0.21.5"
+ "@esbuild/sunos-x64" "0.21.5"
+ "@esbuild/win32-arm64" "0.21.5"
+ "@esbuild/win32-ia32" "0.21.5"
+ "@esbuild/win32-x64" "0.21.5"
escalade@^3.1.2:
version "3.1.2"
@@ -3232,15 +3234,15 @@ eslint-plugin-react-hooks@^4.3.0:
integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==
eslint-plugin-react@^7.27.1:
- version "7.34.2"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.2.tgz#2780a1a35a51aca379d86d29b9a72adc6bfe6b66"
- integrity sha512-2HCmrU+/JNigDN6tg55cRDKCQWicYAPB38JGSFDQt95jDm8rrvSUo7YPkOIm5l6ts1j1zCvysNcasvfTMQzUOw==
+ version "7.34.3"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz#9965f27bd1250a787b5d4cfcc765e5a5d58dcb7b"
+ integrity sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA==
dependencies:
array-includes "^3.1.8"
array.prototype.findlast "^1.2.5"
array.prototype.flatmap "^1.3.2"
array.prototype.toreversed "^1.1.2"
- array.prototype.tosorted "^1.1.3"
+ array.prototype.tosorted "^1.1.4"
doctrine "^2.1.0"
es-iterator-helpers "^1.0.19"
estraverse "^5.3.0"
@@ -4727,9 +4729,9 @@ prettier-linter-helpers@^1.0.0:
fast-diff "^1.1.2"
prettier@^3.2.1:
- version "3.3.1"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.1.tgz#e68935518dd90bb7ec4821ba970e68f8de16e1ac"
- integrity sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg==
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a"
+ integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==
pretty-format@^27.0.2:
version "27.5.1"
@@ -5641,7 +5643,7 @@ universalify@^0.2.0:
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
-update-browserslist-db@^1.0.13:
+update-browserslist-db@^1.0.16:
version "1.0.16"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356"
integrity sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==
@@ -5694,11 +5696,11 @@ vite-node@1.6.0:
vite "^5.0.0"
vite@^5.0.0, vite@^5.0.11:
- version "5.2.12"
- resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.12.tgz#3536c93c58ba18edea4915a2ac573e6537409d97"
- integrity sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.1.tgz#bb2ca6b5fd7483249d3e86b25026e27ba8a663e6"
+ integrity sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==
dependencies:
- esbuild "^0.20.1"
+ esbuild "^0.21.3"
postcss "^8.4.38"
rollup "^4.13.0"
optionalDependencies:
@@ -5854,9 +5856,9 @@ wrappy@1:
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
ws@^8.16.0:
- version "8.17.0"
- resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea"
- integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==
+ version "8.17.1"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
+ integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==
xml-name-validator@^5.0.0:
version "5.0.0"