diff --git a/src/dashboards/AssignmentCard.tsx b/src/dashboards/AssignmentCard.tsx
index f08ae396..7316c379 100644
--- a/src/dashboards/AssignmentCard.tsx
+++ b/src/dashboards/AssignmentCard.tsx
@@ -1,126 +1,105 @@
import ProgressBar from "@/components/Low/ProgressBar";
import useUsers from "@/hooks/useUsers";
-import { Module } from "@/interfaces";
-import { Assignment } from "@/interfaces/results";
-import { calculateBandScore } from "@/utils/score";
+import {Module} from "@/interfaces";
+import {Assignment} from "@/interfaces/results";
+import {calculateBandScore} from "@/utils/score";
import clsx from "clsx";
import moment from "moment";
-import {
- BsBook,
- BsClipboard,
- BsHeadphones,
- BsMegaphone,
- BsPen,
-} from "react-icons/bs";
-import { usePDFDownload } from "@/hooks/usePDFDownload";
-import { useAssignmentArchive } from "@/hooks/useAssignmentArchive";
-import { uniqBy } from "lodash";
+import {BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs";
+import {usePDFDownload} from "@/hooks/usePDFDownload";
+import {useAssignmentArchive} from "@/hooks/useAssignmentArchive";
+import {uniqBy} from "lodash";
+import {useAssignmentUnarchive} from "@/hooks/useAssignmentUnarchive";
interface Props {
- onClick?: () => void;
- allowDownload?: boolean;
- reload?: Function;
- allowArchive?: boolean;
+ onClick?: () => void;
+ allowDownload?: boolean;
+ reload?: Function;
+ allowArchive?: boolean;
+ allowUnarchive?: boolean;
}
export default function AssignmentCard({
- id,
- name,
- assigner,
- startDate,
- endDate,
- assignees,
- results,
- exams,
- archived,
- onClick,
- allowDownload,
- reload,
- allowArchive,
+ id,
+ name,
+ assigner,
+ startDate,
+ endDate,
+ assignees,
+ results,
+ exams,
+ archived,
+ onClick,
+ allowDownload,
+ reload,
+ allowArchive,
+ allowUnarchive,
}: Assignment & Props) {
- const renderPdfIcon = usePDFDownload("assignments");
- const renderArchiveIcon = useAssignmentArchive(id, reload);
+ const renderPdfIcon = usePDFDownload("assignments");
+ const renderArchiveIcon = useAssignmentArchive(id, reload);
+ const renderUnarchiveIcon = useAssignmentUnarchive(id, reload);
- const calculateAverageModuleScore = (module: Module) => {
- const resultModuleBandScores = results.map((r) => {
- const moduleStats = r.stats.filter((s) => s.module === module);
+ const calculateAverageModuleScore = (module: Module) => {
+ const resultModuleBandScores = results.map((r) => {
+ const moduleStats = r.stats.filter((s) => s.module === module);
- const correct = moduleStats.reduce(
- (acc, curr) => acc + curr.score.correct,
- 0
- );
- const total = moduleStats.reduce(
- (acc, curr) => acc + curr.score.total,
- 0
- );
- return calculateBandScore(correct, total, module, r.type);
- });
+ const correct = moduleStats.reduce((acc, curr) => acc + curr.score.correct, 0);
+ const total = moduleStats.reduce((acc, curr) => acc + curr.score.total, 0);
+ return calculateBandScore(correct, total, module, r.type);
+ });
- return resultModuleBandScores.length === 0
- ? -1
- : resultModuleBandScores.reduce((acc, curr) => acc + curr, 0) /
- results.length;
- };
+ return resultModuleBandScores.length === 0 ? -1 : resultModuleBandScores.reduce((acc, curr) => acc + curr, 0) / results.length;
+ };
- return (
-
-
-
-
{name}
-
- {allowDownload &&
- renderPdfIcon(id, "text-mti-gray-dim", "text-mti-gray-dim")}
- {allowArchive &&
- !archived &&
- renderArchiveIcon("text-mti-gray-dim", "text-mti-gray-dim")}
-
-
-
-
-
- {moment(startDate).format("DD/MM/YY, HH:mm")}
- -
- {moment(endDate).format("DD/MM/YY, HH:mm")}
-
-
- {uniqBy(exams, (x) => x.module).map(({ module }) => (
-
- {module === "reading" && }
- {module === "listening" && }
- {module === "writing" && }
- {module === "speaking" && }
- {module === "level" && }
- {calculateAverageModuleScore(module) > -1 && (
-
- {calculateAverageModuleScore(module).toFixed(1)}
-
- )}
-
- ))}
-
-
- );
+ return (
+
+
+
+
{name}
+
+ {allowDownload && renderPdfIcon(id, "text-mti-gray-dim", "text-mti-gray-dim")}
+ {allowArchive && !archived && renderArchiveIcon("text-mti-gray-dim", "text-mti-gray-dim")}
+ {allowUnarchive && archived && renderUnarchiveIcon("text-mti-gray-dim", "text-mti-gray-dim")}
+
+
+
+
+
+ {moment(startDate).format("DD/MM/YY, HH:mm")}
+ -
+ {moment(endDate).format("DD/MM/YY, HH:mm")}
+
+
+ {uniqBy(exams, (x) => x.module).map(({module}) => (
+
+ {module === "reading" && }
+ {module === "listening" && }
+ {module === "writing" && }
+ {module === "speaking" && }
+ {module === "level" && }
+ {calculateAverageModuleScore(module) > -1 && (
+ {calculateAverageModuleScore(module).toFixed(1)}
+ )}
+
+ ))}
+
+
+ );
}
diff --git a/src/dashboards/Teacher.tsx b/src/dashboards/Teacher.tsx
index e7f3036d..f5c64125 100644
--- a/src/dashboards/Teacher.tsx
+++ b/src/dashboards/Teacher.tsx
@@ -151,8 +151,10 @@ export default function TeacherDashboard({user}: Props) {
};
const AssignmentsPage = () => {
- const activeFilter = (a: Assignment) => moment(a.endDate).isAfter(moment()) && moment(a.startDate).isBefore(moment()) && a.assignees.length > a.results.length;
+ const activeFilter = (a: Assignment) =>
+ moment(a.endDate).isAfter(moment()) && moment(a.startDate).isBefore(moment()) && a.assignees.length > a.results.length;
const pastFilter = (a: Assignment) => (moment(a.endDate).isBefore(moment()) || a.assignees.length === a.results.length) && !a.archived;
+ const archivedFilter = (a: Assignment) => a.archived;
const futureFilter = (a: Assignment) => moment(a.startDate).isAfter(moment());
return (
@@ -234,7 +236,29 @@ export default function TeacherDashboard({user}: Props) {
Past Assignments ({assignments.filter(pastFilter).length})
{assignments.filter(pastFilter).map((a) => (
-
setSelectedAssignment(a)} key={a.id} allowDownload reload={reloadAssignments} allowArchive/>
+ setSelectedAssignment(a)}
+ key={a.id}
+ allowDownload
+ reload={reloadAssignments}
+ allowArchive
+ />
+ ))}
+
+
+
+ Archived Assignments ({assignments.filter(archivedFilter).length})
+
+ {assignments.filter(archivedFilter).map((a) => (
+
setSelectedAssignment(a)}
+ key={a.id}
+ allowDownload
+ reload={reloadAssignments}
+ allowUnarchive
+ />
))}
diff --git a/src/hooks/useAssignmentArchive.tsx b/src/hooks/useAssignmentArchive.tsx
index 67879189..397ee2a2 100644
--- a/src/hooks/useAssignmentArchive.tsx
+++ b/src/hooks/useAssignmentArchive.tsx
@@ -1,45 +1,42 @@
import React from "react";
import axios from "axios";
-import { toast } from "react-toastify";
-import { BsArchive } from "react-icons/bs";
+import {toast} from "react-toastify";
+import {BsArchive} from "react-icons/bs";
-export const useAssignmentArchive = (
- assignmentId: string,
- reload?: Function
-) => {
- const [loading, setLoading] = React.useState(false);
- const archive = () => {
- // archive assignment
- setLoading(true);
- axios
- .post(`/api/assignments/${assignmentId}/archive`)
- .then((res) => {
- toast.success("Assignment archived!");
- if(reload) reload();
- setLoading(false);
- })
- .catch((err) => {
- toast.error("Failed to archive the assignment!");
- setLoading(false);
- });
- };
+export const useAssignmentArchive = (assignmentId: string, reload?: Function) => {
+ const [loading, setLoading] = React.useState(false);
+ const archive = () => {
+ // archive assignment
+ setLoading(true);
+ axios
+ .post(`/api/assignments/${assignmentId}/archive`)
+ .then((res) => {
+ toast.success("Assignment archived!");
+ if (reload) reload();
+ setLoading(false);
+ })
+ .catch((err) => {
+ toast.error("Failed to archive the assignment!");
+ setLoading(false);
+ });
+ };
- const renderIcon = (downloadClasses: string, loadingClasses: string) => {
- if (loading) {
- return (
-
- );
- }
- return (
- {
- e.stopPropagation();
- archive();
- }}
- />
- );
- };
+ const renderIcon = (downloadClasses: string, loadingClasses: string) => {
+ if (loading) {
+ return ;
+ }
+ return (
+ {
+ e.stopPropagation();
+ archive();
+ }}>
+
+
+ );
+ };
- return renderIcon;
+ return renderIcon;
};
diff --git a/src/hooks/useAssignmentUnarchive.tsx b/src/hooks/useAssignmentUnarchive.tsx
new file mode 100644
index 00000000..7612b504
--- /dev/null
+++ b/src/hooks/useAssignmentUnarchive.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+import axios from "axios";
+import {toast} from "react-toastify";
+import {BsArchive, BsFileEarmarkCheck, BsFileEarmarkCheckFill} from "react-icons/bs";
+
+export const useAssignmentUnarchive = (assignmentId: string, reload?: Function) => {
+ const [loading, setLoading] = React.useState(false);
+ const archive = () => {
+ // archive assignment
+ setLoading(true);
+ axios
+ .post(`/api/assignments/${assignmentId}/unarchive`)
+ .then((res) => {
+ toast.success("Assignment unarchived!");
+ if (reload) reload();
+ setLoading(false);
+ })
+ .catch((err) => {
+ toast.error("Failed to unarchive the assignment!");
+ setLoading(false);
+ });
+ };
+
+ const renderIcon = (downloadClasses: string, loadingClasses: string) => {
+ if (loading) {
+ return ;
+ }
+ return (
+ {
+ e.stopPropagation();
+ archive();
+ }}>
+
+
+ );
+ };
+
+ return renderIcon;
+};
diff --git a/src/pages/(admin)/Lists/CodeList.tsx b/src/pages/(admin)/Lists/CodeList.tsx
index 0783dd38..88c4455b 100644
--- a/src/pages/(admin)/Lists/CodeList.tsx
+++ b/src/pages/(admin)/Lists/CodeList.tsx
@@ -4,259 +4,319 @@ import Select from "@/components/Low/Select";
import useCodes from "@/hooks/useCodes";
import useUser from "@/hooks/useUser";
import useUsers from "@/hooks/useUsers";
-import {Code, User} from "@/interfaces/user";
-import {USER_TYPE_LABELS} from "@/resources/user";
-import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table";
+import { Code, User } from "@/interfaces/user";
+import { USER_TYPE_LABELS } from "@/resources/user";
+import {
+ createColumnHelper,
+ flexRender,
+ getCoreRowModel,
+ useReactTable,
+} from "@tanstack/react-table";
import axios from "axios";
import moment from "moment";
-import {useEffect, useState} from "react";
-import {BsTrash} from "react-icons/bs";
-import {toast} from "react-toastify";
+import { useEffect, useState } from "react";
+import { BsTrash } from "react-icons/bs";
+import { toast } from "react-toastify";
const columnHelper = createColumnHelper();
-const CreatorCell = ({id, users}: {id: string; users: User[]}) => {
- const [creatorUser, setCreatorUser] = useState();
+const CreatorCell = ({ id, users }: { id: string; users: User[] }) => {
+ const [creatorUser, setCreatorUser] = useState();
- useEffect(() => {
- setCreatorUser(users.find((x) => x.id === id));
- }, [id, users]);
+ useEffect(() => {
+ setCreatorUser(users.find((x) => x.id === id));
+ }, [id, users]);
- return (
- <>
- {(creatorUser?.type === "corporate" ? creatorUser?.corporateInformation?.companyInformation?.name : creatorUser?.name || "N/A") || "N/A"}{" "}
- {creatorUser && `(${USER_TYPE_LABELS[creatorUser.type]})`}
- >
- );
+ return (
+ <>
+ {(creatorUser?.type === "corporate"
+ ? creatorUser?.corporateInformation?.companyInformation?.name
+ : creatorUser?.name || "N/A") || "N/A"}{" "}
+ {creatorUser && `(${USER_TYPE_LABELS[creatorUser.type]})`}
+ >
+ );
};
-export default function CodeList({user}: {user: User}) {
- const [selectedCodes, setSelectedCodes] = useState([]);
+export default function CodeList({ user }: { user: User }) {
+ const [selectedCodes, setSelectedCodes] = useState([]);
- const [filteredCorporate, setFilteredCorporate] = useState(user?.type === "corporate" ? user : undefined);
- const [filterAvailability, setFilterAvailability] = useState<"in-use" | "unused">();
+ const [filteredCorporate, setFilteredCorporate] = useState(
+ user?.type === "corporate" ? user : undefined,
+ );
+ const [filterAvailability, setFilterAvailability] = useState<
+ "in-use" | "unused"
+ >();
- const [filteredCodes, setFilteredCodes] = useState([]);
+ const [filteredCodes, setFilteredCodes] = useState([]);
- const {users} = useUsers();
- const {codes, reload} = useCodes(user?.type === "corporate" ? user?.id : undefined);
+ const { users } = useUsers();
+ const { codes, reload } = useCodes(
+ user?.type === "corporate" ? user?.id : undefined,
+ );
- useEffect(() => {
- let result = [...codes];
- if (filteredCorporate) result = result.filter((x) => x.creator === filteredCorporate.id);
- if (filterAvailability) result = result.filter((x) => (filterAvailability === "in-use" ? !!x.userId : !x.userId));
+ useEffect(() => {
+ let result = [...codes];
+ if (filteredCorporate)
+ result = result.filter((x) => x.creator === filteredCorporate.id);
+ if (filterAvailability)
+ result = result.filter((x) =>
+ filterAvailability === "in-use" ? !!x.userId : !x.userId,
+ );
- setFilteredCodes(result);
- }, [codes, filteredCorporate, filterAvailability]);
+ setFilteredCodes(result);
+ }, [codes, filteredCorporate, filterAvailability]);
- const toggleCode = (id: string) => {
- setSelectedCodes((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));
- };
+ const toggleCode = (id: string) => {
+ setSelectedCodes((prev) =>
+ prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id],
+ );
+ };
- const toggleAllCodes = (checked: boolean) => {
- if (checked) return setSelectedCodes(filteredCodes.filter((x) => !x.userId).map((x) => x.code));
+ const toggleAllCodes = (checked: boolean) => {
+ if (checked)
+ return setSelectedCodes(
+ filteredCodes.filter((x) => !x.userId).map((x) => x.code),
+ );
- return setSelectedCodes([]);
- };
+ return setSelectedCodes([]);
+ };
- const deleteCodes = async (codes: string[]) => {
- if (!confirm(`Are you sure you want to delete these ${codes.length} code(s)?`)) return;
+ const deleteCodes = async (codes: string[]) => {
+ if (
+ !confirm(`Are you sure you want to delete these ${codes.length} code(s)?`)
+ )
+ return;
- const params = new URLSearchParams();
- codes.forEach((code) => params.append("code", code));
+ const params = new URLSearchParams();
+ codes.forEach((code) => params.append("code", code));
- axios
- .delete(`/api/code?${params.toString()}`)
- .then(() => toast.success(`Deleted the codes!`))
- .catch((reason) => {
- if (reason.response.status === 404) {
- toast.error("Code not found!");
- return;
- }
+ axios
+ .delete(`/api/code?${params.toString()}`)
+ .then(() => {
+ toast.success(`Deleted the codes!`);
+ setSelectedCodes([]);
+ })
+ .catch((reason) => {
+ if (reason.response.status === 404) {
+ toast.error("Code not found!");
+ return;
+ }
- if (reason.response.status === 403) {
- toast.error("You do not have permission to delete this code!");
- return;
- }
+ if (reason.response.status === 403) {
+ toast.error("You do not have permission to delete this code!");
+ return;
+ }
- toast.error("Something went wrong, please try again later.");
- })
- .finally(reload);
- };
+ toast.error("Something went wrong, please try again later.");
+ })
+ .finally(reload);
+ };
- const deleteCode = async (code: Code) => {
- if (!confirm(`Are you sure you want to delete this "${code.code}" code?`)) return;
+ const deleteCode = async (code: Code) => {
+ if (!confirm(`Are you sure you want to delete this "${code.code}" code?`))
+ return;
- axios
- .delete(`/api/code/${code.code}`)
- .then(() => toast.success(`Deleted the "${code.code}" exam`))
- .catch((reason) => {
- if (reason.response.status === 404) {
- toast.error("Code not found!");
- return;
- }
+ axios
+ .delete(`/api/code/${code.code}`)
+ .then(() => toast.success(`Deleted the "${code.code}" exam`))
+ .catch((reason) => {
+ if (reason.response.status === 404) {
+ toast.error("Code not found!");
+ return;
+ }
- if (reason.response.status === 403) {
- toast.error("You do not have permission to delete this code!");
- return;
- }
+ if (reason.response.status === 403) {
+ toast.error("You do not have permission to delete this code!");
+ return;
+ }
- toast.error("Something went wrong, please try again later.");
- })
- .finally(reload);
- };
+ toast.error("Something went wrong, please try again later.");
+ })
+ .finally(reload);
+ };
- const defaultColumns = [
- columnHelper.accessor("code", {
- id: "code",
- header: () => (
- !x.userId).length === 0}
- isChecked={
- selectedCodes.length === filteredCodes.filter((x) => !x.userId).length && filteredCodes.filter((x) => !x.userId).length > 0
- }
- onChange={(checked) => toggleAllCodes(checked)}>
- {""}
-
- ),
- cell: (info) =>
- !info.row.original.userId ? (
- toggleCode(info.getValue())}>
- {""}
-
- ) : null,
- }),
- columnHelper.accessor("code", {
- header: "Code",
- cell: (info) => info.getValue(),
- }),
- columnHelper.accessor("creationDate", {
- header: "Creation Date",
- cell: (info) => (info.getValue() ? moment(info.getValue()).format("DD/MM/YYYY") : "N/A"),
- }),
- columnHelper.accessor("email", {
- header: "Invited E-mail",
- cell: (info) => info.getValue() || "N/A",
- }),
- columnHelper.accessor("creator", {
- header: "Creator",
- cell: (info) => ,
- }),
- columnHelper.accessor("userId", {
- header: "Availability",
- cell: (info) =>
- info.getValue() ? (
-
- In Use
-
- ) : (
-
- Unused
-
- ),
- }),
- {
- header: "",
- id: "actions",
- cell: ({row}: {row: {original: Code}}) => {
- return (
-
- {!row.original.userId && (
-
deleteCode(row.original)}>
-
-
- )}
-
- );
- },
- },
- ];
+ const defaultColumns = [
+ columnHelper.accessor("code", {
+ id: "code",
+ header: () => (
+ !x.userId).length === 0}
+ isChecked={
+ selectedCodes.length ===
+ filteredCodes.filter((x) => !x.userId).length &&
+ filteredCodes.filter((x) => !x.userId).length > 0
+ }
+ onChange={(checked) => toggleAllCodes(checked)}
+ >
+ {""}
+
+ ),
+ cell: (info) =>
+ !info.row.original.userId ? (
+ toggleCode(info.getValue())}
+ >
+ {""}
+
+ ) : null,
+ }),
+ columnHelper.accessor("code", {
+ header: "Code",
+ cell: (info) => info.getValue(),
+ }),
+ columnHelper.accessor("creationDate", {
+ header: "Creation Date",
+ cell: (info) =>
+ info.getValue() ? moment(info.getValue()).format("DD/MM/YYYY") : "N/A",
+ }),
+ columnHelper.accessor("email", {
+ header: "Invited E-mail",
+ cell: (info) => info.getValue() || "N/A",
+ }),
+ columnHelper.accessor("creator", {
+ header: "Creator",
+ cell: (info) => ,
+ }),
+ columnHelper.accessor("userId", {
+ header: "Availability",
+ cell: (info) =>
+ info.getValue() ? (
+
+ In Use
+
+ ) : (
+
+ Unused
+
+ ),
+ }),
+ {
+ header: "",
+ id: "actions",
+ cell: ({ row }: { row: { original: Code } }) => {
+ return (
+
+ {!row.original.userId && (
+
deleteCode(row.original)}
+ >
+
+
+ )}
+
+ );
+ },
+ },
+ ];
- const table = useReactTable({
- data: filteredCodes,
- columns: defaultColumns,
- getCoreRowModel: getCoreRowModel(),
- });
+ const table = useReactTable({
+ data: filteredCodes,
+ columns: defaultColumns,
+ getCoreRowModel: getCoreRowModel(),
+ });
- return (
- <>
-
-
-
-
- {selectedCodes.length} code(s) selected
-
-
-
-
-
- {table.getHeaderGroups().map((headerGroup) => (
-
- {headerGroup.headers.map((header) => (
- |
- {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
- |
- ))}
-
- ))}
-
-
- {table.getRowModel().rows.map((row) => (
-
- {row.getVisibleCells().map((cell) => (
- |
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
- |
- ))}
-
- ))}
-
-
- >
- );
+ return (
+ <>
+
+
+
+ ["admin", "developer", "corporate"].includes(x.type),
+ )
+ .map((x) => ({
+ label: `${x.type === "corporate" ? x.corporateInformation?.companyInformation?.name || x.name : x.name} (${
+ USER_TYPE_LABELS[x.type]
+ })`,
+ value: x.id,
+ user: x,
+ }))}
+ onChange={(value) =>
+ setFilteredCorporate(
+ value ? users.find((x) => x.id === value?.value) : undefined,
+ )
+ }
+ />
+
+ setFilterAvailability(
+ value ? (value.value as typeof filterAvailability) : undefined,
+ )
+ }
+ />
+
+
+ {selectedCodes.length} code(s) selected
+
+
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+ |
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+ |
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+ |
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ |
+ ))}
+
+ ))}
+
+
+ >
+ );
}
diff --git a/src/pages/api/assignments/[id]/unarchive.tsx b/src/pages/api/assignments/[id]/unarchive.tsx
new file mode 100644
index 00000000..cda7498f
--- /dev/null
+++ b/src/pages/api/assignments/[id]/unarchive.tsx
@@ -0,0 +1,33 @@
+import type {NextApiRequest, NextApiResponse} from "next";
+import {app} from "@/firebase";
+import {getFirestore, doc, getDoc, setDoc} from "firebase/firestore";
+import {withIronSessionApiRoute} from "iron-session/next";
+import {sessionOptions} from "@/lib/session";
+
+const db = getFirestore(app);
+
+export default withIronSessionApiRoute(handler, sessionOptions);
+
+async function post(req: NextApiRequest, res: NextApiResponse) {
+ // verify if it's a logged user that is trying to archive
+ if (req.session.user) {
+ const {id} = req.query as {id: string};
+ const docSnap = await getDoc(doc(db, "assignments", id));
+
+ if (!docSnap.exists()) {
+ res.status(404).json({ok: false});
+ return;
+ }
+
+ await setDoc(docSnap.ref, {archived: false}, {merge: true});
+ res.status(200).json({ok: true});
+ return;
+ }
+
+ res.status(401).json({ok: false});
+}
+
+async function handler(req: NextApiRequest, res: NextApiResponse) {
+ if (req.method === "POST") return post(req, res);
+ res.status(404).json({ok: false});
+}
diff --git a/src/pages/api/code/index.ts b/src/pages/api/code/index.ts
index 10f22e98..937803ee 100644
--- a/src/pages/api/code/index.ts
+++ b/src/pages/api/code/index.ts
@@ -1,143 +1,174 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
-import type {NextApiRequest, NextApiResponse} from "next";
-import {app} from "@/firebase";
-import {getFirestore, setDoc, doc, query, collection, where, getDocs, getDoc, deleteDoc} from "firebase/firestore";
-import {withIronSessionApiRoute} from "iron-session/next";
-import {sessionOptions} from "@/lib/session";
-import {Type} from "@/interfaces/user";
-import {PERMISSIONS} from "@/constants/userPermissions";
-import {uuidv4} from "@firebase/util";
-import {prepareMailer, prepareMailOptions} from "@/email";
+import type { NextApiRequest, NextApiResponse } from "next";
+import { app } from "@/firebase";
+import {
+ getFirestore,
+ setDoc,
+ doc,
+ query,
+ collection,
+ where,
+ getDocs,
+ getDoc,
+ deleteDoc,
+} from "firebase/firestore";
+import { withIronSessionApiRoute } from "iron-session/next";
+import { sessionOptions } from "@/lib/session";
+import { Code, Type } from "@/interfaces/user";
+import { PERMISSIONS } from "@/constants/userPermissions";
+import { uuidv4 } from "@firebase/util";
+import { prepareMailer, prepareMailOptions } from "@/email";
const db = getFirestore(app);
export default withIronSessionApiRoute(handler, sessionOptions);
async function handler(req: NextApiRequest, res: NextApiResponse) {
- if (req.method === "GET") return get(req, res);
- if (req.method === "POST") return post(req, res);
- if (req.method === "DELETE") return del(req, res);
+ if (req.method === "GET") return get(req, res);
+ if (req.method === "POST") return post(req, res);
+ if (req.method === "DELETE") return del(req, res);
- return res.status(404).json({ok: false});
+ return res.status(404).json({ ok: false });
}
async function get(req: NextApiRequest, res: NextApiResponse) {
- if (!req.session.user) {
- res.status(401).json({ok: false, reason: "You must be logged in to generate a code!"});
- return;
- }
+ if (!req.session.user) {
+ res
+ .status(401)
+ .json({ ok: false, reason: "You must be logged in to generate a code!" });
+ return;
+ }
- const {creator} = req.query as {creator?: string};
- const q = query(collection(db, "codes"), where("creator", "==", creator || ""));
- const snapshot = await getDocs(creator ? q : collection(db, "codes"));
+ const { creator } = req.query as { creator?: string };
+ const q = query(
+ collection(db, "codes"),
+ where("creator", "==", creator || ""),
+ );
+ const snapshot = await getDocs(creator ? q : collection(db, "codes"));
- res.status(200).json(snapshot.docs.map((doc) => doc.data()));
+ res.status(200).json(snapshot.docs.map((doc) => doc.data()));
}
async function post(req: NextApiRequest, res: NextApiResponse) {
- if (!req.session.user) {
- res.status(401).json({ok: false, reason: "You must be logged in to generate a code!"});
- return;
- }
+ if (!req.session.user) {
+ res
+ .status(401)
+ .json({ ok: false, reason: "You must be logged in to generate a code!" });
+ return;
+ }
- const {type, codes, infos, expiryDate} = req.body as {
- type: Type;
- codes: string[];
- infos?: {email: string; name: string; passport_id?: string}[];
- expiryDate: null | Date;
- };
- const permission = PERMISSIONS.generateCode[type];
+ const { type, codes, infos, expiryDate } = req.body as {
+ type: Type;
+ codes: string[];
+ infos?: { email: string; name: string; passport_id?: string }[];
+ expiryDate: null | Date;
+ };
+ const permission = PERMISSIONS.generateCode[type];
- if (!permission.includes(req.session.user.type)) {
- res.status(403).json({
- ok: false,
- reason: "Your account type does not have permissions to generate a code for that type of user!",
- });
- return;
- }
+ if (!permission.includes(req.session.user.type)) {
+ res.status(403).json({
+ ok: false,
+ reason:
+ "Your account type does not have permissions to generate a code for that type of user!",
+ });
+ return;
+ }
- if (req.session.user.type === "corporate") {
- const codesGeneratedByUserSnapshot = await getDocs(query(collection(db, "codes"), where("creator", "==", req.session.user.id)));
- const totalCodes = codesGeneratedByUserSnapshot.docs.length + codes.length;
- const allowedCodes = req.session.user.corporateInformation?.companyInformation.userAmount || 0;
+ const codesGeneratedByUserSnapshot = await getDocs(
+ query(collection(db, "codes"), where("creator", "==", req.session.user.id)),
+ );
+ const userCodes = codesGeneratedByUserSnapshot.docs.map((x) => ({
+ ...x.data(),
+ }));
- if (totalCodes > allowedCodes) {
- res.status(403).json({
- ok: false,
- reason: `You have or would have exceeded your amount of allowed codes, you currently are allowed to generate ${
- allowedCodes - codesGeneratedByUserSnapshot.docs.length
- } codes.`,
- });
- return;
- }
- }
+ if (req.session.user.type === "corporate") {
+ const totalCodes = codesGeneratedByUserSnapshot.docs.length + codes.length;
+ const allowedCodes =
+ req.session.user.corporateInformation?.companyInformation.userAmount || 0;
- const codePromises = codes.map(async (code, index) => {
- const codeRef = doc(db, "codes", code);
- const codeInformation = {
- type,
- code,
- creator: req.session.user!.id,
- creationDate: new Date().toISOString(),
- expiryDate,
- };
+ if (totalCodes > allowedCodes) {
+ res.status(403).json({
+ ok: false,
+ reason: `You have or would have exceeded your amount of allowed codes, you currently are allowed to generate ${
+ allowedCodes - codesGeneratedByUserSnapshot.docs.length
+ } codes.`,
+ });
+ return;
+ }
+ }
- if (infos && infos.length > index) {
- const {email, name, passport_id} = infos[index];
+ const codePromises = codes.map(async (code, index) => {
+ const codeRef = doc(db, "codes", code);
+ let codeInformation = {
+ type,
+ code,
+ creator: req.session.user!.id,
+ creationDate: new Date().toISOString(),
+ expiryDate,
+ };
- const transport = prepareMailer();
- const mailOptions = prepareMailOptions(
- {
- type,
- code,
- environment: process.env.ENVIRONMENT,
- },
- [email.toLowerCase().trim()],
- "EnCoach Registration",
- "main",
- );
+ if (infos && infos.length > index) {
+ const { email, name, passport_id } = infos[index];
+ const previousCode = userCodes.find((x) => x.email === email) as Code;
- try {
- await transport.sendMail(mailOptions);
- await setDoc(
- codeRef,
- {
- ...codeInformation,
- email: email.trim().toLowerCase(),
- name: name.trim(),
- ...(passport_id ? {passport_id: passport_id.trim()} : {}),
- },
- {merge: true},
- );
+ const transport = prepareMailer();
+ const mailOptions = prepareMailOptions(
+ {
+ type,
+ code: previousCode ? previousCode.code : code,
+ environment: process.env.ENVIRONMENT,
+ },
+ [email.toLowerCase().trim()],
+ "EnCoach Registration",
+ "main",
+ );
- return true;
- } catch (e) {
- return false;
- }
- } else {
- await setDoc(codeRef, codeInformation);
- }
- });
+ try {
+ await transport.sendMail(mailOptions);
- Promise.all(codePromises).then((results) => {
- res.status(200).json({ok: true, valid: results.filter((x) => x).length});
- });
+ if (!previousCode) {
+ await setDoc(
+ codeRef,
+ {
+ ...codeInformation,
+ email: email.trim().toLowerCase(),
+ name: name.trim(),
+ ...(passport_id ? { passport_id: passport_id.trim() } : {}),
+ },
+ { merge: true },
+ );
+ }
+
+ return true;
+ } catch (e) {
+ return false;
+ }
+ } else {
+ await setDoc(codeRef, codeInformation);
+ }
+ });
+
+ Promise.all(codePromises).then((results) => {
+ res.status(200).json({ ok: true, valid: results.filter((x) => x).length });
+ });
}
async function del(req: NextApiRequest, res: NextApiResponse) {
- if (!req.session.user) {
- res.status(401).json({ok: false, reason: "You must be logged in to generate a code!"});
- return;
- }
+ if (!req.session.user) {
+ res
+ .status(401)
+ .json({ ok: false, reason: "You must be logged in to generate a code!" });
+ return;
+ }
- const codes = req.query.code as string[];
+ const codes = req.query.code as string[];
- for (const code of codes) {
- const snapshot = await getDoc(doc(db, "codes", code as string));
- if (!snapshot.exists()) continue;
+ for (const code of codes) {
+ const snapshot = await getDoc(doc(db, "codes", code as string));
+ if (!snapshot.exists()) continue;
- await deleteDoc(snapshot.ref);
- }
+ await deleteDoc(snapshot.ref);
+ }
- res.status(200).json({codes});
+ res.status(200).json({ codes });
}