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 ( - <> -
-
- setFilterAvailability(value ? (value.value as typeof filterAvailability) : undefined)} - /> -
-
- {selectedCodes.length} code(s) selected - -
-
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - ))} - - ))} - - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - ))} - - ))} - -
- {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
- - ); + return ( + <> +
+
+ + setFilterAvailability( + value ? (value.value as typeof filterAvailability) : undefined, + ) + } + /> +
+
+ {selectedCodes.length} code(s) selected + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} +
+ {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 }); }