Made it so the timer is more dynamic
This commit is contained in:
@@ -31,7 +31,6 @@ export default function MatchSentences({id, options, type, prompt, sentences, us
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log({hasExamEnded});
|
||||
if (hasExamEnded) onNext({exercise: id, solutions: answers, score: calculateScore(), type});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [hasExamEnded]);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import {Module} from "@/interfaces";
|
||||
import useExamStore from "@/stores/examStore";
|
||||
import {moduleLabels} from "@/utils/moduleUtils";
|
||||
import clsx from "clsx";
|
||||
import {motion} from "framer-motion";
|
||||
import {ReactNode, useEffect, useState} from "react";
|
||||
import {BsBook, BsHeadphones, BsMegaphone, BsPen, BsStopwatch} from "react-icons/bs";
|
||||
import ProgressBar from "../Low/ProgressBar";
|
||||
import TimerEndedModal from "../TimerEndedModal";
|
||||
|
||||
interface Props {
|
||||
minTimer: number;
|
||||
@@ -16,6 +19,8 @@ interface Props {
|
||||
|
||||
export default function ModuleTitle({minTimer, module, label, exerciseIndex, totalExercises, disableTimer = false}: Props) {
|
||||
const [timer, setTimer] = useState(minTimer * 60);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [warningMode, setWarningMode] = useState(false);
|
||||
const setHasExamEnded = useExamStore((state) => state.setHasExamEnded);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -29,8 +34,12 @@ export default function ModuleTitle({minTimer, module, label, exerciseIndex, tot
|
||||
}, [disableTimer, minTimer]);
|
||||
|
||||
useEffect(() => {
|
||||
if (timer <= 0) setHasExamEnded(true);
|
||||
}, [setHasExamEnded, timer]);
|
||||
if (timer <= 0) setShowModal(true);
|
||||
}, [timer]);
|
||||
|
||||
useEffect(() => {
|
||||
if (timer < 300 && !warningMode) setWarningMode(true);
|
||||
}, [timer, warningMode]);
|
||||
|
||||
const moduleIcon: {[key in Module]: ReactNode} = {
|
||||
reading: <BsBook className="text-ielts-reading w-6 h-6" />,
|
||||
@@ -41,7 +50,21 @@ export default function ModuleTitle({minTimer, module, label, exerciseIndex, tot
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="absolute top-4 right-6 bg-mti-gray-seasalt px-3 py-2 flex items-center gap-2 rounded-full text-mti-gray-davy">
|
||||
<TimerEndedModal
|
||||
isOpen={showModal}
|
||||
onClose={() => {
|
||||
setHasExamEnded(true);
|
||||
setShowModal(false);
|
||||
}}
|
||||
/>
|
||||
<motion.div
|
||||
className={clsx(
|
||||
"absolute top-4 right-6 bg-mti-gray-seasalt px-3 py-2 flex items-center gap-2 rounded-full text-mti-gray-davy",
|
||||
warningMode && !disableTimer && "bg-mti-red-light text-mti-gray-seasalt",
|
||||
)}
|
||||
initial={{scale: warningMode && !disableTimer ? 0.8 : 1}}
|
||||
animate={{scale: warningMode && !disableTimer ? 1.1 : 1}}
|
||||
transition={{repeat: Infinity, repeatType: "reverse", duration: 0.5, ease: "easeInOut"}}>
|
||||
<BsStopwatch className="w-4 h-4" />
|
||||
<span className="text-sm font-semibold w-11">
|
||||
{timer > 0 && (
|
||||
@@ -57,7 +80,7 @@ export default function ModuleTitle({minTimer, module, label, exerciseIndex, tot
|
||||
)}
|
||||
{timer <= 0 && <>00:00</>}
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
<div className="flex gap-6 w-full h-fit items-center mt-5">
|
||||
<div className="w-12 h-12 bg-mti-gray-smoke flex items-center justify-center rounded-lg">{moduleIcon[module]}</div>
|
||||
<div className="flex flex-col gap-3 w-full">
|
||||
|
||||
49
src/components/TimerEndedModal.tsx
Normal file
49
src/components/TimerEndedModal.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import {Dialog, Transition} from "@headlessui/react";
|
||||
import {Fragment} from "react";
|
||||
import Button from "./Low/Button";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function TimerEndedModal({isOpen, onClose}: Props) {
|
||||
return (
|
||||
<Transition show={isOpen} as={Fragment}>
|
||||
<Dialog onClose={onClose} className="relative z-50">
|
||||
<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/30" />
|
||||
</Transition.Child>
|
||||
|
||||
<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">
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<Dialog.Panel className="w-full max-w-2xl h-fit p-8 rounded-xl bg-white flex flex-col gap-4">
|
||||
<Dialog.Title className="font-bold text-xl">Time's up!</Dialog.Title>
|
||||
<span>
|
||||
The timer has ended! Your answers have been registered and saved, you will now move on to the next module (or to the
|
||||
finish screen, if this was the last one).
|
||||
</span>
|
||||
<Button color="purple" onClick={onClose} className="max-w-[200px] self-end w-full mt-8">
|
||||
Continue
|
||||
</Button>
|
||||
</Dialog.Panel>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
@@ -53,10 +53,6 @@ export default function Finish({user, scores, modules, isLoading, onViewResults}
|
||||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log({selectedScore, selectedModule, scores});
|
||||
}, [scores, selectedModule, selectedScore]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full min-h-full h-fit flex flex-col items-center justify-between gap-8">
|
||||
|
||||
@@ -27,6 +27,12 @@ export default function Listening({exam, showSolutions = false, onFinish}: Props
|
||||
|
||||
const [hasExamEnded, setHasExamEnded] = useExamStore((state) => [state.hasExamEnded, state.setHasExamEnded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasExamEnded && exerciseIndex === -1) {
|
||||
setExerciseIndex((prev) => prev + 1);
|
||||
}
|
||||
}, [hasExamEnded, exerciseIndex]);
|
||||
|
||||
const confirmFinishModule = (keepGoing?: boolean) => {
|
||||
if (!keepGoing) {
|
||||
setShowBlankModal(false);
|
||||
|
||||
@@ -87,6 +87,12 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
|
||||
|
||||
const [hasExamEnded, setHasExamEnded] = useExamStore((state) => [state.hasExamEnded, state.setHasExamEnded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasExamEnded && exerciseIndex === -1) {
|
||||
setExerciseIndex((prev) => prev + 1);
|
||||
}
|
||||
}, [hasExamEnded, exerciseIndex]);
|
||||
|
||||
const confirmFinishModule = (keepGoing?: boolean) => {
|
||||
if (!keepGoing) {
|
||||
setShowBlankModal(false);
|
||||
|
||||
@@ -203,8 +203,6 @@ export default function Page() {
|
||||
const solutionIds = solutions.map((x) => x.exercise);
|
||||
const solutionExams = solutions.map((x) => x.exam);
|
||||
|
||||
console.log(solutionExams, exam?.id, moduleIndex, selectedModules);
|
||||
|
||||
if (exam && !solutionExams.includes(exam.id)) return;
|
||||
|
||||
if (exam && (exam.module === "writing" || exam.module === "speaking") && solutions.length > 0 && !showSolutions) {
|
||||
|
||||
Reference in New Issue
Block a user