diff --git a/src/pages/(generation)/ReadingGeneration.tsx b/src/pages/(generation)/ReadingGeneration.tsx index 4796636a..fd293a79 100644 --- a/src/pages/(generation)/ReadingGeneration.tsx +++ b/src/pages/(generation)/ReadingGeneration.tsx @@ -1,12 +1,16 @@ import Input from "@/components/Low/Input"; -import {ReadingPart} from "@/interfaces/exam"; +import {ReadingExam, ReadingPart} from "@/interfaces/exam"; +import useExamStore from "@/stores/examStore"; +import {getExamById} from "@/utils/exams"; import {convertCamelCaseToReadable} from "@/utils/string"; import {Tab} from "@headlessui/react"; import axios from "axios"; import clsx from "clsx"; +import {useRouter} from "next/router"; import {useState} from "react"; import {BsArrowRepeat} from "react-icons/bs"; import {toast} from "react-toastify"; +import {v4} from "uuid"; const PartTab = ({part, types, index, setPart}: {part?: ReadingPart; types: string[]; index: number; setPart: (part?: ReadingPart) => void}) => { const [topic, setTopic] = useState(""); @@ -80,6 +84,13 @@ const ReadingGeneration = () => { const [part2, setPart2] = useState(); const [part3, setPart3] = useState(); const [types, setTypes] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [resultingExam, setResultingExam] = useState(); + + const router = useRouter(); + + const setExams = useExamStore((state) => state.setExams); + const setSelectedModules = useExamStore((state) => state.setSelectedModules); const availableTypes = [ {type: "fillBlanks", label: "Fill the Blanks"}, @@ -90,6 +101,57 @@ const ReadingGeneration = () => { const toggleType = (type: string) => setTypes((prev) => (prev.includes(type) ? [...prev.filter((x) => x !== type)] : [...prev, type])); + const loadExam = async (examId: string) => { + const exam = await getExamById("reading", examId.trim()); + 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", + }); + + return; + } + + setExams([exam]); + setSelectedModules(["reading"]); + + router.push("/exercises"); + }; + + const submitExam = () => { + if (!part1 || !part2 || !part3) { + toast.error("Please generate all three passages before submitting"); + return; + } + + setIsLoading(true); + const exam: ReadingExam = { + parts: [part1, part2, part3], + isDiagnostic: false, + minTimer: 60, + module: "reading", + id: v4(), + type: "academic", + }; + + axios + .post(`/api/exam/reading`, exam) + .then((result) => { + console.log(`Generated Exam ID: ${result.data.id}`); + toast.success("This new exam has been generated successfully! Check the ID in our browser's console."); + setResultingExam(result.data); + + setPart1(undefined); + setPart2(undefined); + setPart3(undefined); + setTypes([]); + }) + .catch((error) => { + console.log(error); + toast.error("Something went wrong while generating, please try again later."); + }) + .finally(() => setIsLoading(false)); + }; + return ( <>
@@ -156,17 +218,38 @@ const ReadingGeneration = () => { ))} - +
+ {resultingExam && ( + + )} + +
); }; diff --git a/src/pages/(generation)/WritingGeneration.tsx b/src/pages/(generation)/WritingGeneration.tsx index 957b26a2..e528bd9e 100644 --- a/src/pages/(generation)/WritingGeneration.tsx +++ b/src/pages/(generation)/WritingGeneration.tsx @@ -1,63 +1,148 @@ import Input from "@/components/Low/Input"; +import {WritingExam} from "@/interfaces/exam"; +import useExamStore from "@/stores/examStore"; +import {getExamById} from "@/utils/exams"; import {Tab} from "@headlessui/react"; import axios from "axios"; import clsx from "clsx"; +import {useRouter} from "next/router"; import {useState} from "react"; import {BsArrowRepeat} from "react-icons/bs"; import {toast} from "react-toastify"; +import {v4} from "uuid"; + +const TaskTab = ({task, index, setTask}: {task?: string; index: number; setTask: (task: string) => void}) => { + const [isLoading, setIsLoading] = useState(false); + + const generate = () => { + setIsLoading(true); + axios + .get(`/api/exam/writing/generate/writing_task${index}_general`) + .then((result) => setTask(result.data.question)) + .catch((error) => { + console.log(error); + toast.error("Something went wrong!"); + }) + .finally(() => setIsLoading(false)); + }; + + return ( + +
+ +
+ {isLoading && ( +
+ + Generating... +
+ )} + {task && ( +
+ {task} +
+ )} +
+ ); +}; const WritingGeneration = () => { const [task1, setTask1] = useState(); const [task2, setTask2] = useState(); + const [isLoading, setIsLoading] = useState(false); + const [resultingExam, setResultingExam] = useState(); - const TaskTab = ({task, index, setTask}: {task?: string; index: number; setTask: (task: string) => void}) => { - const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); - const generate = () => { - setIsLoading(true); - axios - .get(`/api/exam/writing/generate/writing_task${index}_general`) - .then((result) => setTask(result.data.question)) - .catch((error) => { - console.log(error); - toast.error("Something went wrong!"); - }) - .finally(() => setIsLoading(false)); + const setExams = useExamStore((state) => state.setExams); + const setSelectedModules = useExamStore((state) => state.setSelectedModules); + + const loadExam = async (examId: string) => { + const exam = await getExamById("writing", examId.trim()); + 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", + }); + + return; + } + + setExams([exam]); + setSelectedModules(["writing"]); + + router.push("/exercises"); + }; + + const submitExam = () => { + if (!task1 || !task2) { + toast.error("Please generate all tasks before submitting"); + return; + } + + setIsLoading(true); + const exam: WritingExam = { + isDiagnostic: false, + minTimer: 60, + module: "writing", + exercises: [ + { + id: v4(), + type: "writing", + prefix: `You should spend about 20 minutes on this task.`, + prompt: task1, + userSolutions: [], + suffix: "You should write at least 150 words.", + wordCounter: { + limit: 150, + type: "min", + }, + }, + { + id: v4(), + type: "writing", + prefix: `You should spend about 40 minutes on this task.`, + prompt: task2, + userSolutions: [], + suffix: "You should write at least 250 words.", + wordCounter: { + limit: 250, + type: "min", + }, + }, + ], + id: v4(), }; - return ( - -
- -
- {isLoading && ( -
- - Generating... -
- )} - {task && ( -
- {task} -
- )} -
- ); + axios + .post(`/api/exam/writing`, exam) + .then((result) => { + console.log(`Generated Exam ID: ${result.data.id}`); + toast.success("This new exam has been generated successfully! Check the ID in our browser's console."); + setResultingExam(result.data); + + setTask1(undefined); + setTask2(undefined); + }) + .catch((error) => { + console.log(error); + toast.error("Something went wrong while generating, please try again later."); + }) + .finally(() => setIsLoading(false)); }; return ( @@ -96,17 +181,38 @@ const WritingGeneration = () => { ))} - +
+ {resultingExam && ( + + )} + +
); }; diff --git a/src/pages/api/exam/[module]/index.ts b/src/pages/api/exam/[module]/index.ts index 820cab53..87fd150b 100644 --- a/src/pages/api/exam/[module]/index.ts +++ b/src/pages/api/exam/[module]/index.ts @@ -1,18 +1,26 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type {NextApiRequest, NextApiResponse} from "next"; import {app} from "@/firebase"; -import {getFirestore, collection, getDocs, query, where} from "firebase/firestore"; +import {getFirestore, collection, getDocs, query, where, setDoc, doc} from "firebase/firestore"; import {withIronSessionApiRoute} from "iron-session/next"; import {sessionOptions} from "@/lib/session"; import {shuffle} from "lodash"; import {Exam} from "@/interfaces/exam"; import {Stat} from "@/interfaces/user"; +import {v4} from "uuid"; 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); + + res.status(404).json({ok: false}); +} + +async function GET(req: NextApiRequest, res: NextApiResponse) { if (!req.session.user) { res.status(401).json({ok: false}); return; @@ -45,3 +53,21 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { 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.type !== "developer") { + res.status(403).json({ok: false}); + return; + } + const {module} = req.query as {module: string}; + + const exam = {...req.body, module: module}; + await setDoc(doc(db, module, req.body.id), exam); + + res.status(200).json(exam); +} diff --git a/src/pages/api/exam/index.ts b/src/pages/api/exam/index.ts index e32a52cd..52c09f79 100644 --- a/src/pages/api/exam/index.ts +++ b/src/pages/api/exam/index.ts @@ -13,6 +13,12 @@ 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); + + res.status(404).json({ok: false}); +} + +async function GET(req: NextApiRequest, res: NextApiResponse) { if (!req.session.user) { res.status(401).json({ok: false}); return;