diff --git a/src/pages/(generation)/LevelGeneration.tsx b/src/pages/(generation)/LevelGeneration.tsx index 3ebf73c0..09b1615a 100644 --- a/src/pages/(generation)/LevelGeneration.tsx +++ b/src/pages/(generation)/LevelGeneration.tsx @@ -230,7 +230,11 @@ const TaskTab = ({section, setSection}: {section: LevelSection; setSection: (sec ); }; -const LevelGeneration = () => { +interface Props { + id: string; +} + +const LevelGeneration = ({ id } : Props) => { const [generatedExam, setGeneratedExam] = useState(); const [isLoading, setIsLoading] = useState(false); const [resultingExam, setResultingExam] = useState(); @@ -420,10 +424,16 @@ const LevelGeneration = () => { return; } + if(!id) { + toast.error("Please insert a title before submitting"); + return; + } + setIsLoading(true); const exam = { ...generatedExam, + id, parts: generatedExam.parts.map((p, i) => ({...p, exercises: parts[i].part!.exercises})), }; diff --git a/src/pages/(generation)/ListeningGeneration.tsx b/src/pages/(generation)/ListeningGeneration.tsx index 85fd1064..5388b183 100644 --- a/src/pages/(generation)/ListeningGeneration.tsx +++ b/src/pages/(generation)/ListeningGeneration.tsx @@ -228,7 +228,11 @@ interface ListeningPart { | string; } -const ListeningGeneration = () => { +interface Props { + id: string; +} + +const ListeningGeneration = ({ id } : Props) => { const [part1, setPart1] = useState(); const [part2, setPart2] = useState(); const [part3, setPart3] = useState(); @@ -258,11 +262,16 @@ const ListeningGeneration = () => { console.log({parts}); if (parts.length === 0) return toast.error("Please generate at least one section!"); + if(!id) { + toast.error("Please insert a title before submitting"); + return; + } + setIsLoading(true); axios .post(`/api/exam/listening/generate/listening`, { - id: generate({minLength: 4, maxLength: 8, min: 3, max: 5, join: " ", formatter: capitalize}), + id, parts, minTimer, difficulty, diff --git a/src/pages/(generation)/ReadingGeneration.tsx b/src/pages/(generation)/ReadingGeneration.tsx index de4a0caf..6bd81dde 100644 --- a/src/pages/(generation)/ReadingGeneration.tsx +++ b/src/pages/(generation)/ReadingGeneration.tsx @@ -258,7 +258,11 @@ const PartTab = ({ ); }; -const ReadingGeneration = () => { +interface Props { + id: string; +} + +const ReadingGeneration = ({ id } : Props) => { const [part1, setPart1] = useState(); const [part2, setPart2] = useState(); const [part3, setPart3] = useState(); @@ -300,13 +304,18 @@ const ReadingGeneration = () => { return; } + if(!id) { + toast.error("Please insert a title before submitting"); + return; + } + setIsLoading(true); const exam: ReadingExam = { parts, isDiagnostic: false, minTimer, module: "reading", - id: generate({minLength: 4, maxLength: 8, min: 3, max: 5, join: " ", formatter: capitalize}), + id, type: "academic", variant: parts.length === 3 ? "full" : "partial", difficulty, @@ -328,7 +337,7 @@ const ReadingGeneration = () => { }) .catch((error) => { console.log(error); - toast.error("Something went wrong while generating, please try again later."); + toast.error(error.response.data.error || "Something went wrong while generating, please try again later."); }) .finally(() => setIsLoading(false)); }; diff --git a/src/pages/(generation)/SpeakingGeneration.tsx b/src/pages/(generation)/SpeakingGeneration.tsx index 7b6b26ea..c4959f6c 100644 --- a/src/pages/(generation)/SpeakingGeneration.tsx +++ b/src/pages/(generation)/SpeakingGeneration.tsx @@ -221,7 +221,11 @@ interface SpeakingPart { avatar?: (typeof AVATARS)[number]; } -const SpeakingGeneration = () => { +interface Props { + id: string; +} + +const SpeakingGeneration = ({ id } : Props) => { const [part1, setPart1] = useState(); const [part2, setPart2] = useState(); const [part3, setPart3] = useState(); @@ -243,6 +247,11 @@ const SpeakingGeneration = () => { const submitExam = () => { if (!part1?.result && !part2?.result && !part3?.result) return toast.error("Please generate at least one task!"); + if(!id) { + toast.error("Please insert a title before submitting"); + return; + } + setIsLoading(true); const genders = [part1?.gender, part2?.gender, part3?.gender].filter((x) => !!x); @@ -256,7 +265,7 @@ const SpeakingGeneration = () => { })); const exam: SpeakingExam = { - id: generate({minLength: 4, maxLength: 8, min: 3, max: 5, join: " ", formatter: capitalize}), + id, isDiagnostic: false, exercises: exercises as (SpeakingExercise | InteractiveSpeakingExercise)[], minTimer, diff --git a/src/pages/(generation)/WritingGeneration.tsx b/src/pages/(generation)/WritingGeneration.tsx index 6555614a..be4e12cd 100644 --- a/src/pages/(generation)/WritingGeneration.tsx +++ b/src/pages/(generation)/WritingGeneration.tsx @@ -75,7 +75,11 @@ const TaskTab = ({task, index, difficulty, setTask}: {task?: string; difficulty: ); }; -const WritingGeneration = () => { +interface Props { + id: string; +} + +const WritingGeneration = ({ id } : Props) => { const [task1, setTask1] = useState(); const [task2, setTask2] = useState(); const [minTimer, setMinTimer] = useState(60); @@ -116,6 +120,11 @@ const WritingGeneration = () => { return; } + if(!id) { + toast.error("Please insert a title before submitting"); + return; + } + const exercise1 = task1 ? ({ id: v4(), @@ -152,7 +161,7 @@ const WritingGeneration = () => { minTimer, module: "writing", exercises: [...(exercise1 ? [exercise1] : []), ...(exercise2 ? [exercise2] : [])], - id: generate({minLength: 4, maxLength: 8, min: 3, max: 5, join: " ", formatter: capitalize}), + id, variant: exercise1 && exercise2 ? "full" : "partial", difficulty, }; diff --git a/src/pages/api/exam/[module]/index.ts b/src/pages/api/exam/[module]/index.ts index 1c845cc7..0c5af8de 100644 --- a/src/pages/api/exam/[module]/index.ts +++ b/src/pages/api/exam/[module]/index.ts @@ -1,54 +1,89 @@ // 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} from "firebase/firestore"; -import {withIronSessionApiRoute} from "iron-session/next"; -import {sessionOptions} from "@/lib/session"; -import {Exam, InstructorGender, Variant} from "@/interfaces/exam"; -import {getExams} from "@/utils/exams.be"; -import {Module} from "@/interfaces"; +import type { NextApiRequest, NextApiResponse } from "next"; +import { app } from "@/firebase"; +import { + getFirestore, + setDoc, + doc, + runTransaction, + collection, + query, + where, + getDocs, +} from "firebase/firestore"; +import { withIronSessionApiRoute } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; +import { Exam, InstructorGender, Variant } from "@/interfaces/exam"; +import { getExams } from "@/utils/exams.be"; +import { Module } from "@/interfaces"; const db = getFirestore(app); export default withIronSessionApiRoute(handler, sessionOptions); async function handler(req: NextApiRequest, res: NextApiResponse) { - if (req.method === "GET") return await GET(req, res); - if (req.method === "POST") return await POST(req, res); + if (req.method === "GET") return await GET(req, res); + if (req.method === "POST") return await POST(req, res); - res.status(404).json({ok: false}); + res.status(404).json({ ok: false }); } async function GET(req: NextApiRequest, res: NextApiResponse) { - if (!req.session.user) { - res.status(401).json({ok: false}); - return; - } + if (!req.session.user) { + res.status(401).json({ ok: false }); + return; + } - const {module, avoidRepeated, variant, instructorGender} = req.query as { - module: Module; - avoidRepeated: string; - variant?: Variant; - instructorGender?: InstructorGender; - }; + const { module, avoidRepeated, variant, instructorGender } = req.query as { + module: Module; + avoidRepeated: string; + variant?: Variant; + instructorGender?: InstructorGender; + }; - const exams: Exam[] = await getExams(db, module, avoidRepeated, req.session.user.id, variant, instructorGender); - res.status(200).json(exams); + const exams: Exam[] = await getExams( + db, + module, + avoidRepeated, + req.session.user.id, + variant, + instructorGender + ); + res.status(200).json(exams); } async function POST(req: NextApiRequest, res: NextApiResponse) { - if (!req.session.user) { - res.status(401).json({ok: false}); - return; - } + if (!req.session.user) { + res.status(401).json({ ok: false }); + return; + } - if (req.session.user.type !== "developer") { - res.status(403).json({ok: false}); - return; - } - const {module} = req.query as {module: string}; + if (req.session.user.type !== "developer") { + res.status(403).json({ ok: false }); + return; + } + const { module } = req.query as { module: string }; - const exam = {...req.body, module: module, createdBy: req.session.user.id, createdAt: new Date().toISOString()}; - await setDoc(doc(db, module, req.body.id), exam); + try { + const exam = { + ...req.body, + module: module, + createdBy: req.session.user.id, + createdAt: new Date().toISOString(), + }; + await runTransaction(db, async (transaction) => { + const docRef = doc(db, module, req.body.id); + const docSnap = await transaction.get(docRef); - res.status(200).json(exam); + if (docSnap.exists()) { + throw new Error("Name already exists"); + } + + const newDocRef = doc(db, module, req.body.id); + transaction.set(newDocRef, exam); + }); + res.status(200).json(exam); + } catch (error) { + console.error("Transaction failed: ", error); + res.status(500).json({ ok: false, error: (error as any).message }); + } } diff --git a/src/pages/generation.tsx b/src/pages/generation.tsx index c2a0de83..2fd4660d 100644 --- a/src/pages/generation.tsx +++ b/src/pages/generation.tsx @@ -57,6 +57,7 @@ export default function Generation() { const { user } = useUser({ redirectTo: "/login" }); + const [title, setTitle] = useState(""); return ( <> @@ -73,6 +74,17 @@ export default function Generation() {

Exam Generation

+ + @@ -117,11 +129,11 @@ export default function Generation() { ))}
- {module === "reading" && } - {module === "listening" && } - {module === "writing" && } - {module === "speaking" && } - {module === "level" && } + {module === "reading" && } + {module === "listening" && } + {module === "writing" && } + {module === "speaking" && } + {module === "level" && }
)}