- Updated the code to remove the exam pages to components;

- Added some state logic to make it so it keeps track of the current exam the user is in;
This commit is contained in:
Tiago Ribeiro
2023-04-06 11:18:34 +01:00
parent 780d208675
commit 36e6c017b4
7 changed files with 377 additions and 89 deletions

73
src/exams/Listening.tsx Normal file
View File

@@ -0,0 +1,73 @@
import {ListeningExam} from "@/interfaces/exam";
import {useState} from "react";
import Icon from "@mdi/react";
import {mdiArrowRight} from "@mdi/js";
import clsx from "clsx";
import {infoButtonStyle} from "@/constants/buttonStyles";
import {renderExercise} from "@/components/Exercises";
interface Props {
exam: ListeningExam;
onFinish: () => void;
}
export default function Listening({exam, onFinish}: Props) {
const [exerciseIndex, setExerciseIndex] = useState(-1);
const [timesListened, setTimesListened] = useState(0);
const nextExercise = () => {
if (exerciseIndex + 1 < exam.exercises.length) {
setExerciseIndex((prev) => prev + 1);
return;
}
onFinish();
};
const previousExercise = () => {
setExerciseIndex((prev) => prev - 1);
};
const renderAudioPlayer = () => (
<>
{exerciseIndex === -1 && (
<div className="flex flex-col">
<span className="text-lg font-semibold">Please listen to the following audio attentively.</span>
{exam.audio.repeatableTimes > 0 ? (
<span className="self-center text-sm">
You will only be allowed to listen to the audio {exam.audio.repeatableTimes} time(s).
</span>
) : (
<span className="self-center text-sm">You may listen to the audio as many times as you would like.</span>
)}
</div>
)}
<div className="bg-gray-300 rounded-xl p-4 flex flex-col gap-4 items-center w-full overflow-auto">
<span className="text-xl font-semibold">{exam.audio.title}</span>
{exam.audio.repeatableTimes > 0 && (
<>{exam.audio.repeatableTimes <= timesListened && <span>You are no longer allowed to listen to the audio again.</span>}</>
)}
<span>AUDIO WILL GO HERE</span>
</div>
</>
);
return (
<>
<div className="w-full h-full relative flex flex-col gap-8 items-center justify-center p-8 px-16 overflow-hidden">
{renderAudioPlayer()}
{exerciseIndex > -1 &&
exerciseIndex < exam.exercises.length &&
renderExercise(exam.exercises[exerciseIndex], nextExercise, previousExercise)}
{exerciseIndex === -1 && (
<button className={clsx("btn btn-wide gap-4 relative text-white self-end", infoButtonStyle)} onClick={nextExercise}>
Next
<div className="absolute right-4">
<Icon path={mdiArrowRight} color="white" size={1} />
</div>
</button>
)}
</div>
</>
);
}

145
src/exams/Reading.tsx Normal file
View File

@@ -0,0 +1,145 @@
import {ReadingExam} from "@/interfaces/exam";
import {Fragment, useState} from "react";
import Icon from "@mdi/react";
import {mdiArrowRight, mdiNotebook} from "@mdi/js";
import clsx from "clsx";
import {infoButtonStyle} from "@/constants/buttonStyles";
import {Dialog, Transition} from "@headlessui/react";
import {renderExercise} from "@/components/Exercises";
interface Props {
exam: ReadingExam;
onFinish: () => void;
}
function TextModal({isOpen, title, content, onClose}: {isOpen: boolean; title: string; content: string; onClose: () => void}) {
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={onClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0">
<div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95">
<Dialog.Panel className="w-full max-w-4xl transform rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-900">
{title}
</Dialog.Title>
<div className="mt-2 overflow-auto">
<p className="text-sm text-gray-500">
{content.split("\n").map((line, index) => (
<Fragment key={index}>
{line}
<br />
</Fragment>
))}
</p>
</div>
<div className="mt-4">
<button
type="button"
className="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onClose}>
Got it, thanks!
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
);
}
export default function Reading({exam, onFinish}: Props) {
const [exerciseIndex, setExerciseIndex] = useState(-1);
const [showTextModal, setShowTextModal] = useState(false);
const nextExercise = () => {
if (exerciseIndex + 1 < exam.exercises.length) {
setExerciseIndex((prev) => prev + 1);
return;
}
onFinish();
};
const previousExercise = () => {
setExerciseIndex((prev) => prev - 1);
};
const renderText = () => (
<>
<div className="flex flex-col">
<span className="text-lg font-semibold">
Please read the following excerpt attentively, you will then be asked questions about the text you&apos;ve read.
</span>
<span className="self-end text-sm">You will be allowed to read the text while doing the exercises</span>
</div>
<div className="bg-gray-300 rounded-xl p-4 flex flex-col gap-4 items-center w-full overflow-auto">
<span className="text-xl font-semibold">{exam.text.title}</span>
<span>
{exam.text.content.split("\n").map((line, index) => (
<Fragment key={index}>
<span>{line}</span>
<br />
</Fragment>
))}
</span>
</div>
</>
);
return (
<>
<TextModal {...exam.text} isOpen={showTextModal} onClose={() => setShowTextModal(false)} />
<div className="w-full h-full relative flex flex-col gap-8 items-center justify-center p-8 px-16 overflow-hidden">
{exerciseIndex === -1 && renderText()}
{exerciseIndex > -1 &&
exerciseIndex < exam.exercises.length &&
renderExercise(exam.exercises[exerciseIndex], nextExercise, previousExercise)}
<div className={clsx("flex gap-8", exerciseIndex > -1 ? "w-full justify-between" : "self-end")}>
{exerciseIndex > -1 && (
<button
className={clsx(
"btn btn-wide gap-4 relative text-white",
"border-2 border-ielts-reading hover:bg-ielts-reading hover:border-ielts-reading bg-ielts-reading-transparent",
)}
onClick={() => setShowTextModal(true)}>
Read Text
<div className="absolute right-4">
<Icon path={mdiNotebook} color="white" size={1} />
</div>
</button>
)}
{exerciseIndex === -1 && (
<button className={clsx("btn btn-wide gap-4 relative text-white self-end", infoButtonStyle)} onClick={nextExercise}>
Next
<div className="absolute right-4">
<Icon path={mdiArrowRight} color="white" size={1} />
</div>
</button>
)}
</div>
</div>
</>
);
}

107
src/exams/Selection.tsx Normal file
View File

@@ -0,0 +1,107 @@
/* eslint-disable @next/next/no-img-element */
import Icon from "@mdi/react";
import {mdiAccountVoice, mdiArrowLeft, mdiArrowRight, mdiBookOpen, mdiHeadphones, mdiPen} from "@mdi/js";
import {useState} from "react";
import {Module} from "@/interfaces";
import clsx from "clsx";
import {useRouter} from "next/router";
import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
import ProfileLevel from "@/components/ProfileLevel";
import {User} from "@/interfaces/user";
interface Props {
user: User;
onStart: (modules: Module[]) => void;
}
export default function Selection({user, onStart}: Props) {
const [selectedModules, setSelectedModules] = useState<Module[]>([]);
const router = useRouter();
const toggleModule = (module: Module) => {
const modules = selectedModules.filter((x) => x !== module);
setSelectedModules((prev) => (prev.includes(module) ? modules : [...modules, module]));
};
return (
<>
<div className="w-full h-full relative">
<section className="h-full w-full flex flex-col items-center justify-center">
<ProfileLevel user={user} className="h-1/2" />
<div className="h-1/2 flex flex-col">
<div className="h-1/2 flex gap-8">
<div
role="button"
tabIndex={0}
onClick={() => toggleModule("reading")}
className={clsx(
"flex flex-col gap-2 items-center justify-center",
"border-ielts-reading hover:bg-ielts-reading text-white",
"border-2 rounded-xl p-4 h-fit w-48 cursor-pointer",
selectedModules.includes("reading") ? "bg-ielts-reading " : "bg-ielts-reading-transparent ",
)}>
<Icon path={mdiBookOpen} color="white" size={3} />
<span>Reading</span>
</div>
<div
role="button"
tabIndex={0}
onClick={() => toggleModule("listening")}
className={clsx(
"flex flex-col gap-2 items-center justify-center",
"border-ielts-listening hover:bg-ielts-listening text-white",
"border-2 rounded-xl p-4 h-fit w-48 cursor-pointer",
selectedModules.includes("listening") ? "bg-ielts-listening " : "bg-ielts-listening-transparent ",
)}>
<Icon path={mdiHeadphones} color="white" size={3} />
<span>Listening</span>
</div>
<div
role="button"
tabIndex={0}
onClick={() => toggleModule("speaking")}
className={clsx(
"flex flex-col gap-2 items-center justify-center",
"border-ielts-speaking hover:bg-ielts-speaking text-white",
"border-2 rounded-xl p-4 h-fit w-48 cursor-pointer",
selectedModules.includes("speaking") ? "bg-ielts-speaking " : "bg-ielts-speaking-transparent ",
)}>
<Icon path={mdiAccountVoice} color="white" size={3} />
<span>Speaking</span>
</div>
<div
role="button"
tabIndex={0}
onClick={() => toggleModule("writing")}
className={clsx(
"flex flex-col gap-2 items-center justify-center",
"border-ielts-writing hover:bg-ielts-writing text-white",
"border-2 rounded-xl p-4 h-fit w-48 cursor-pointer",
selectedModules.includes("writing") ? "bg-ielts-writing " : "bg-ielts-writing-transparent ",
)}>
<Icon path={mdiPen} color="white" size={3} />
<span>Writing</span>
</div>
</div>
<div className="w-full flex justify-between">
<button onClick={() => router.push("/")} className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)}>
<div className="absolute left-4">
<Icon path={mdiArrowLeft} color="white" size={1} />
</div>
Back
</button>
<button
className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)}
onClick={() => onStart(selectedModules)}>
Start
<div className="absolute right-4">
<Icon path={mdiArrowRight} color="white" size={1} />
</div>
</button>
</div>
</div>
</section>
</div>
</>
);
}