diff --git a/src/components/High/Layout.tsx b/src/components/High/Layout.tsx index f106f4d8..c15170cf 100644 --- a/src/components/High/Layout.tsx +++ b/src/components/High/Layout.tsx @@ -34,6 +34,7 @@ export default function Layout({user, children, className, navDisabled = false, focusMode={focusMode} onFocusLayerMouseEnter={onFocusLayerMouseEnter} className="-lg:hidden" + showAdmin={user.type === "developer"} />
void; type?: "button" | "reset" | "submit"; } -export default function Button({color = "purple", variant = "solid", disabled = false, className, children, type, onClick}: Props) { +export default function Button({ + color = "purple", + variant = "solid", + disabled = false, + isLoading = false, + className, + children, + type, + onClick, +}: Props) { const colorClassNames: {[key in typeof color]: {[key in typeof variant]: string}} = { purple: { solid: "bg-mti-purple-light text-white border border-mti-purple-light hover:bg-mti-purple disabled:text-mti-purple disabled:bg-mti-purple-ultralight selection:bg-mti-purple-dark", @@ -39,8 +50,13 @@ export default function Button({color = "purple", variant = "solid", disabled = className, colorClassNames[color][variant], )} - disabled={disabled}> - {children} + disabled={disabled || isLoading}> + {!isLoading && children} + {isLoading && ( +
+ +
+ )} ); } diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 1368b569..9581dd45 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,7 +1,7 @@ import clsx from "clsx"; import {IconType} from "react-icons"; import {MdSpaceDashboard} from "react-icons/md"; -import {BsFileEarmarkText, BsClockHistory, BsPencil, BsGraphUp, BsChevronBarRight, BsChevronBarLeft} from "react-icons/bs"; +import {BsFileEarmarkText, BsClockHistory, BsPencil, BsGraphUp, BsChevronBarRight, BsChevronBarLeft, BsShieldFill} from "react-icons/bs"; import {RiLogoutBoxFill} from "react-icons/ri"; import {SlPencil} from "react-icons/sl"; import {FaAward} from "react-icons/fa"; @@ -18,6 +18,7 @@ interface Props { focusMode?: boolean; onFocusLayerMouseEnter?: () => void; className?: string; + showAdmin?: boolean; } interface NavProps { @@ -43,7 +44,7 @@ const Nav = ({Icon, label, path, keyPath, disabled = false, isMinimized = false} ); -export default function Sidebar({path, navDisabled = false, focusMode = false, onFocusLayerMouseEnter, className}: Props) { +export default function Sidebar({path, navDisabled = false, focusMode = false, showAdmin = false, onFocusLayerMouseEnter, className}: Props) { const router = useRouter(); const [isMinimized, toggleMinimize] = usePreferencesStore((state) => [state.isSidebarMinimized, state.toggleSidebarMinimized]); @@ -69,6 +70,9 @@ export default function Sidebar({path, navDisabled = false, focusMode = false, o
diff --git a/src/pages/admin.tsx b/src/pages/admin.tsx new file mode 100644 index 00000000..14a3385a --- /dev/null +++ b/src/pages/admin.tsx @@ -0,0 +1,138 @@ +/* eslint-disable @next/next/no-img-element */ +import Head from "next/head"; +import {withIronSessionSsr} from "iron-session/next"; +import {sessionOptions} from "@/lib/session"; +import useUser from "@/hooks/useUser"; +import {toast, ToastContainer} from "react-toastify"; +import Layout from "@/components/High/Layout"; +import {RadioGroup} from "@headlessui/react"; +import {MODULE_ARRAY} from "@/utils/moduleUtils"; +import clsx from "clsx"; +import {capitalize} from "lodash"; +import {FormEvent, useState} from "react"; +import {Module} from "@/interfaces"; +import Input from "@/components/Low/Input"; +import Button from "@/components/Low/Button"; +import {getExamById} from "@/utils/exams"; +import useExamStore from "@/stores/examStore"; +import {useRouter} from "next/router"; + +export const getServerSideProps = withIronSessionSsr(({req, res}) => { + const user = req.session.user; + + if (!user || !user.isVerified) { + res.setHeader("location", "/login"); + res.statusCode = 302; + res.end(); + return { + props: { + user: null, + }, + }; + } + + if (user.type !== "developer") { + res.setHeader("location", "/"); + res.statusCode = 302; + res.end(); + return { + props: { + user: null, + }, + }; + } + + return { + props: {user: req.session.user}, + }; +}, sessionOptions); + +const ExamLoader = () => { + const [selectedModule, setSelectedModule] = useState(); + const [examId, setExamId] = useState(); + const [isLoading, setIsLoading] = useState(false); + + const setExams = useExamStore((state) => state.setExams); + const setSelectedModules = useExamStore((state) => state.setSelectedModules); + + const router = useRouter(); + + const loadExam = async (e?: FormEvent) => { + if (e) e.preventDefault(); + setIsLoading(true); + + if (selectedModule && examId) { + const exam = await getExamById(selectedModule, examId); + if (!exam) { + toast.error("Unknown Exam ID! Please make sure you selected the right module and entered the right exam ID", { + toastId: "invalid-exam-id", + }); + + setIsLoading(false); + return; + } + + setExams([exam]); + setSelectedModules([selectedModule]); + + router.push("/exercises"); + } + + setIsLoading(false); + }; + + return ( +
+ +
+ + {MODULE_ARRAY.map((module) => ( + + {({checked}) => ( + + {capitalize(module)} + + )} + + ))} + + + +
+
+ ); +}; + +export default function Admin() { + const {user} = useUser({redirectTo: "/login"}); + + return ( + <> + + Admin Panel | EnCoach + + + + + + {user && ( + +
+ +
+
+ )} + + ); +}