diff --git a/src/interfaces/exam.ts b/src/interfaces/exam.ts index bcc83034..b4197e3a 100644 --- a/src/interfaces/exam.ts +++ b/src/interfaces/exam.ts @@ -28,19 +28,21 @@ export interface LevelExam { } export interface ListeningExam { - parts: { - audio: { - source: string; - repeatableTimes: number; // *The amount of times the user is allowed to repeat the audio, 0 for unlimited - }; - exercises: Exercise[]; - }[]; + parts: ListeningPart[]; id: string; module: "listening"; minTimer: number; isDiagnostic: boolean; } +export interface ListeningPart { + audio: { + source: string; + repeatableTimes: number; // *The amount of times the user is allowed to repeat the audio, 0 for unlimited + }; + exercises: Exercise[]; +} + export interface UserSolution { solutions: any[]; module?: Module; diff --git a/src/pages/(generation)/ListeningGeneration.tsx b/src/pages/(generation)/ListeningGeneration.tsx new file mode 100644 index 00000000..0366f3b7 --- /dev/null +++ b/src/pages/(generation)/ListeningGeneration.tsx @@ -0,0 +1,137 @@ +import Input from "@/components/Low/Input"; +import {ListeningPart} from "@/interfaces/exam"; +import {Tab} from "@headlessui/react"; +import axios from "axios"; +import clsx from "clsx"; +import {useState} from "react"; +import {toast} from "react-toastify"; + +const ListeningGeneration = () => { + const [part1, setPart1] = useState(); + const [part2, setPart2] = useState(); + const [part3, setPart3] = useState(); + const [types, setTypes] = useState([]); + + const availableTypes = [ + {type: "fillBlanks", label: "Fill the Blanks"}, + {type: "multipleChoice", label: "Multiple Choice"}, + {type: "trueFalse", label: "True or False"}, + {type: "writeBlanks", label: "Write the Blanks"}, + ]; + + const toggleType = (type: string) => setTypes((prev) => (prev.includes(type) ? [...prev.filter((x) => x !== type)] : [...prev, type])); + + const PartTab = ({part, index, setPart}: {part?: ListeningPart; index: number; setPart: (part: ListeningPart) => void}) => { + const [topic, setTopic] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const generate = () => { + setIsLoading(true); + axios + .get(`/api/exam/listening/generate/listening_passage_${index}?topic=${topic}&exercises=${types.join("&exercises=")}`) + .then((result) => setPart(result.data)) + .catch((error) => { + console.log(error); + toast.error("Something went wrong!"); + }) + .finally(() => setIsLoading(false)); + }; + + return ( + +
+ + +
+ {isLoading && ( +
+ + Generating... +
+ )} +
+ ); + }; + + return ( + <> +
+ +
+ {availableTypes.map((x) => ( + toggleType(x.type)} + key={x.type} + className={clsx( + "px-6 py-4 w-64 flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer", + "transition duration-300 ease-in-out", + !types.includes(x.type) + ? "bg-white border-mti-gray-platinum" + : "bg-ielts-listening/70 border-ielts-listening text-white", + )}> + {x.label} + + ))} +
+
+ + + + + clsx( + "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70", + "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening focus:outline-none focus:ring-2", + "transition duration-300 ease-in-out", + selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-listening", + ) + }> + Passage 1 + + + clsx( + "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70", + "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening focus:outline-none focus:ring-2", + "transition duration-300 ease-in-out", + selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-listening", + ) + }> + Passage 2 + + + clsx( + "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70", + "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening focus:outline-none focus:ring-2", + "transition duration-300 ease-in-out", + selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-listening", + ) + }> + Passage 3 + + + + {[ + {part: part1, setPart: setPart1}, + {part: part2, setPart: setPart2}, + {part: part3, setPart: setPart3}, + ].map(({part, setPart}, index) => ( + + ))} + + + + ); +}; + +export default ListeningGeneration; diff --git a/src/pages/(generation)/ReadingGeneration.tsx b/src/pages/(generation)/ReadingGeneration.tsx new file mode 100644 index 00000000..b95253d8 --- /dev/null +++ b/src/pages/(generation)/ReadingGeneration.tsx @@ -0,0 +1,173 @@ +import Input from "@/components/Low/Input"; +import {ReadingPart} from "@/interfaces/exam"; +import {convertCamelCaseToReadable} from "@/utils/string"; +import {Tab} from "@headlessui/react"; +import axios from "axios"; +import clsx from "clsx"; +import {useState} from "react"; +import {BsArrowRepeat} from "react-icons/bs"; +import {toast} from "react-toastify"; + +const PartTab = ({part, types, index, setPart}: {part?: ReadingPart; types: string[]; index: number; setPart: (part: ReadingPart) => void}) => { + const [topic, setTopic] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const generate = () => { + setIsLoading(true); + axios + .get(`/api/exam/reading/generate/reading_passage_${index}?topic=${topic}&exercises=${types.join("&exercises=")}`) + .then((result) => setPart(result.data)) + .catch((error) => { + console.log(error); + toast.error("Something went wrong!"); + }) + .finally(() => setIsLoading(false)); + }; + + return ( + +
+ + +
+ {isLoading && ( +
+ + Generating... +
+ )} + {part && ( +
+
+ {part.exercises.map((x) => ( + + {x.type && convertCamelCaseToReadable(x.type)} + + ))} +
+

{part.text.title}

+ {part.text.content} +
+ )} +
+ ); +}; + +const ReadingGeneration = () => { + const [part1, setPart1] = useState(); + const [part2, setPart2] = useState(); + const [part3, setPart3] = useState(); + const [types, setTypes] = useState([]); + + const availableTypes = [ + {type: "fillBlanks", label: "Fill the Blanks"}, + {type: "multipleChoice", label: "Multiple Choice"}, + {type: "trueFalse", label: "True or False"}, + {type: "writeBlanks", label: "Write the Blanks"}, + ]; + + const toggleType = (type: string) => setTypes((prev) => (prev.includes(type) ? [...prev.filter((x) => x !== type)] : [...prev, type])); + + return ( + <> +
+ +
+ {availableTypes.map((x) => ( + toggleType(x.type)} + key={x.type} + className={clsx( + "px-6 py-4 w-64 flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer", + "transition duration-300 ease-in-out", + !types.includes(x.type) ? "bg-white border-mti-gray-platinum" : "bg-ielts-reading/70 border-ielts-reading text-white", + )}> + {x.label} + + ))} +
+
+ + + + + clsx( + "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70", + "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading focus:outline-none focus:ring-2", + "transition duration-300 ease-in-out", + selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-reading", + ) + }> + Passage 1 + + + clsx( + "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70", + "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading focus:outline-none focus:ring-2", + "transition duration-300 ease-in-out", + selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-reading", + ) + }> + Passage 2 + + + clsx( + "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70", + "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading focus:outline-none focus:ring-2", + "transition duration-300 ease-in-out", + selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-reading", + ) + }> + Passage 3 + + + + {[ + {part: part1, setPart: setPart1}, + {part: part2, setPart: setPart2}, + {part: part3, setPart: setPart3}, + ].map(({part, setPart}, index) => ( + + ))} + + + + + ); +}; + +export default ReadingGeneration; diff --git a/src/pages/api/exam/[module]/generate.tsx b/src/pages/api/exam/[module]/generate/[endpoint].ts similarity index 65% rename from src/pages/api/exam/[module]/generate.tsx rename to src/pages/api/exam/[module]/generate/[endpoint].ts index c72f1276..56f3ac5e 100644 --- a/src/pages/api/exam/[module]/generate.tsx +++ b/src/pages/api/exam/[module]/generate/[endpoint].ts @@ -25,17 +25,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { return; } - const {module} = req.query as {module: Module}; + const {endpoint, topic, exercises} = req.query as {module: Module; endpoint: string; topic: string; exercises: string[]}; - switch (module) { - case "reading": - const result = await axios.get( - `${process.env.BACKEND_URL}/reading_passage_1?topic=football manager video game&exercises=multipleChoice&exercises=trueFalse&exercises=fillBlanks&exercises=writeBlanks`, - {headers: {Authorization: `Bearer ${process.env.BACKEND_JWT}`}}, - ); - res.status(200).json(result.data); - return; - } + const result = await axios.get(`${process.env.BACKEND_URL}/${endpoint}?topic=${topic.toLowerCase()}&exercises=${exercises.join("&exercises=")}`, { + headers: {Authorization: `Bearer ${process.env.BACKEND_JWT}`}, + }); - res.status(200).json({ok: true}); + res.status(200).json(result.data); } diff --git a/src/pages/generation.tsx b/src/pages/generation.tsx index 9c19ff6b..aa111d1d 100644 --- a/src/pages/generation.tsx +++ b/src/pages/generation.tsx @@ -3,7 +3,7 @@ import Head from "next/head"; import {withIronSessionSsr} from "iron-session/next"; import {sessionOptions} from "@/lib/session"; import useUser from "@/hooks/useUser"; -import {ToastContainer} from "react-toastify"; +import {toast, ToastContainer} from "react-toastify"; import Layout from "@/components/High/Layout"; import {shouldRedirectHome} from "@/utils/navigation.disabled"; import {useState} from "react"; @@ -15,6 +15,9 @@ import {capitalize} from "lodash"; import Button from "@/components/Low/Button"; import {Exercise, ReadingPart} from "@/interfaces/exam"; import Input from "@/components/Low/Input"; +import axios from "axios"; +import ReadingGeneration from "./(generation)/ReadingGeneration"; +import ListeningGeneration from "./(generation)/ListeningGeneration"; export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; @@ -51,176 +54,6 @@ export default function Generation() { const {user} = useUser({redirectTo: "/login"}); - const ReadingGeneration = () => { - const [part1, setPart1] = useState(); - const [part2, setPart2] = useState(); - const [part3, setPart3] = useState(); - const [types, setTypes] = useState([]); - - const availableTypes = [ - {type: "fillBlanks", label: "Fill the Blanks"}, - {type: "multipleChoice", label: "Multiple Choice"}, - {type: "trueFalse", label: "True or False"}, - {type: "writeBlanks", label: "Write the Blanks"}, - ]; - - const toggleType = (type: string) => setTypes((prev) => (prev.includes(type) ? [...prev.filter((x) => x !== type)] : [...prev, type])); - - return ( - <> -
- -
- {availableTypes.map((x) => ( - toggleType(x.type)} - key={x.type} - className={clsx( - "px-6 py-4 w-64 flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer", - "transition duration-300 ease-in-out", - !types.includes(x.type) - ? "bg-white border-mti-gray-platinum" - : "bg-ielts-reading/70 border-ielts-reading text-white", - )}> - {x.label} - - ))} -
-
- - - - - clsx( - "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70", - "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading focus:outline-none focus:ring-2", - "transition duration-300 ease-in-out", - selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-reading", - ) - }> - Passage 1 - - - clsx( - "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70", - "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading focus:outline-none focus:ring-2", - "transition duration-300 ease-in-out", - selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-reading", - ) - }> - Passage 2 - - - clsx( - "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70", - "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading focus:outline-none focus:ring-2", - "transition duration-300 ease-in-out", - selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-reading", - ) - }> - Passage 3 - - - - {[part1, part2, part3].map((part, index) => ( - -
- {}} roundness="xl" /> - -
-
- ))} -
-
- - ); - }; - - const ListeningGeneration = () => { - const [types, setTypes] = useState([]); - const availableTypes = [ - {type: "fillBlanks", label: "Fill the Blanks"}, - {type: "multipleChoice", label: "Multiple Choice"}, - {type: "trueFalse", label: "True or False"}, - {type: "writeBlanks", label: "Write the Blanks"}, - ]; - - const toggleType = (type: string) => setTypes((prev) => (prev.includes(type) ? [...prev.filter((x) => x !== type)] : [...prev, type])); - - return ( - <> -
- -
- {availableTypes.map((x) => ( - toggleType(x.type)} - key={x.type} - className={clsx( - "px-6 py-4 w-64 flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer", - "transition duration-300 ease-in-out", - !types.includes(x.type) - ? "bg-white border-mti-gray-platinum" - : "bg-ielts-listening/70 border-ielts-listening text-white", - )}> - {x.label} - - ))} -
-
- - - - - clsx( - "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70", - "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening focus:outline-none focus:ring-2", - "transition duration-300 ease-in-out", - selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-listening", - ) - }> - Passage 1 - - - clsx( - "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70", - "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening focus:outline-none focus:ring-2", - "transition duration-300 ease-in-out", - selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-listening", - ) - }> - Passage 2 - - - clsx( - "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70", - "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening focus:outline-none focus:ring-2", - "transition duration-300 ease-in-out", - selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-listening", - ) - }> - Passage 3 - - - - - ); - }; - return ( <>