diff --git a/src/pages/(admin)/Lists/ExamList.tsx b/src/pages/(admin)/Lists/ExamList.tsx
index 93ffb05c..2c990677 100644
--- a/src/pages/(admin)/Lists/ExamList.tsx
+++ b/src/pages/(admin)/Lists/ExamList.tsx
@@ -13,7 +13,7 @@ import axios from "axios";
import clsx from "clsx";
import {capitalize} from "lodash";
import {useRouter} from "next/router";
-import {BsCheck, BsTrash, BsUpload} from "react-icons/bs";
+import {BsBan, BsBanFill, BsCheck, BsCircle, BsStop, BsTrash, BsUpload, BsX} from "react-icons/bs";
import {toast} from "react-toastify";
import {useListSearch} from "@/hooks/useListSearch";
@@ -72,6 +72,28 @@ export default function ExamList({user}: {user: User}) {
router.push("/exercises");
};
+ const privatizeExam = async (exam: Exam) => {
+ if (!confirm(`Are you sure you want to make this ${capitalize(exam.module)} exam ${exam.private ? "public" : "private"}?`)) return;
+
+ axios
+ .patch(`/api/exam/${exam.module}/${exam.id}`, {private: !exam.private})
+ .then(() => toast.success(`Updated the "${exam.id}" exam`))
+ .catch((reason) => {
+ if (reason.response.status === 404) {
+ toast.error("Exam not found!");
+ return;
+ }
+
+ if (reason.response.status === 403) {
+ toast.error("You do not have permission to update this exam!");
+ return;
+ }
+
+ toast.error("Something went wrong, please try again later.");
+ })
+ .finally(reload);
+ };
+
const deleteExam = async (exam: Exam) => {
if (!confirm(`Are you sure you want to delete this ${capitalize(exam.module)} exam?`)) return;
@@ -119,6 +141,10 @@ export default function ExamList({user}: {user: User}) {
header: "Timer",
cell: (info) => <>{info.getValue()} minute(s)>,
}),
+ columnHelper.accessor("private", {
+ header: "Private",
+ cell: (info) => {!info.getValue() ? : },
+ }),
columnHelper.accessor("createdAt", {
header: "Created At",
cell: (info) => {
@@ -140,12 +166,18 @@ export default function ExamList({user}: {user: User}) {
cell: ({row}: {row: {original: Exam}}) => {
return (
-
await privatizeExam(row.original)}
+ className="cursor-pointer tooltip">
+ {row.original.private ? : }
+
+
+
{PERMISSIONS.examManagement.delete.includes(user.type) && (
deleteExam(row.original)}>
diff --git a/src/pages/api/exam/[module]/[id].ts b/src/pages/api/exam/[module]/[id].ts
index fc80debf..50bf3c7d 100644
--- a/src/pages/api/exam/[module]/[id].ts
+++ b/src/pages/api/exam/[module]/[id].ts
@@ -1,7 +1,7 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type {NextApiRequest, NextApiResponse} from "next";
import {app} from "@/firebase";
-import {getFirestore, doc, getDoc, deleteDoc} from "firebase/firestore";
+import {getFirestore, doc, getDoc, deleteDoc, setDoc} from "firebase/firestore";
import {withIronSessionApiRoute} from "iron-session/next";
import {sessionOptions} from "@/lib/session";
import {PERMISSIONS} from "@/constants/userPermissions";
@@ -12,6 +12,7 @@ export default withIronSessionApiRoute(handler, sessionOptions);
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "GET") return get(req, res);
+ if (req.method === "PATCH") return patch(req, res);
if (req.method === "DELETE") return del(req, res);
}
@@ -37,6 +38,25 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
}
}
+async function patch(req: NextApiRequest, res: NextApiResponse) {
+ if (!req.session.user) {
+ res.status(401).json({ok: false});
+ return;
+ }
+
+ const {module, id} = req.query as {module: string; id: string};
+
+ const docRef = doc(db, module, id);
+ const docSnap = await getDoc(docRef);
+
+ if (docSnap.exists()) {
+ await setDoc(docRef, req.body, {merge: true});
+ res.status(200).json({ok: true});
+ } else {
+ res.status(404).json({ok: false});
+ }
+}
+
async function del(req: NextApiRequest, res: NextApiResponse) {
if (!req.session.user) {
res.status(401).json({ok: false});