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
+ {showAdmin && (
+
+ )}
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 (
+
+
+
+
+ );
+};
+
+export default function Admin() {
+ const {user} = useUser({redirectTo: "/login"});
+
+ return (
+ <>
+
+ Admin Panel | EnCoach
+
+
+
+
+
+ {user && (
+
+
+
+ )}
+ >
+ );
+}