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(() => {
|
useEffect(() => {
|
||||||
console.log({hasExamEnded});
|
|
||||||
if (hasExamEnded) onNext({exercise: id, solutions: answers, score: calculateScore(), type});
|
if (hasExamEnded) onNext({exercise: id, solutions: answers, score: calculateScore(), type});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [hasExamEnded]);
|
}, [hasExamEnded]);
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import {Module} from "@/interfaces";
|
import {Module} from "@/interfaces";
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
import {moduleLabels} from "@/utils/moduleUtils";
|
import {moduleLabels} from "@/utils/moduleUtils";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import {motion} from "framer-motion";
|
||||||
import {ReactNode, useEffect, useState} from "react";
|
import {ReactNode, useEffect, useState} from "react";
|
||||||
import {BsBook, BsHeadphones, BsMegaphone, BsPen, BsStopwatch} from "react-icons/bs";
|
import {BsBook, BsHeadphones, BsMegaphone, BsPen, BsStopwatch} from "react-icons/bs";
|
||||||
import ProgressBar from "../Low/ProgressBar";
|
import ProgressBar from "../Low/ProgressBar";
|
||||||
|
import TimerEndedModal from "../TimerEndedModal";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
minTimer: number;
|
minTimer: number;
|
||||||
@@ -16,6 +19,8 @@ interface Props {
|
|||||||
|
|
||||||
export default function ModuleTitle({minTimer, module, label, exerciseIndex, totalExercises, disableTimer = false}: Props) {
|
export default function ModuleTitle({minTimer, module, label, exerciseIndex, totalExercises, disableTimer = false}: Props) {
|
||||||
const [timer, setTimer] = useState(minTimer * 60);
|
const [timer, setTimer] = useState(minTimer * 60);
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const [warningMode, setWarningMode] = useState(false);
|
||||||
const setHasExamEnded = useExamStore((state) => state.setHasExamEnded);
|
const setHasExamEnded = useExamStore((state) => state.setHasExamEnded);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -29,8 +34,12 @@ export default function ModuleTitle({minTimer, module, label, exerciseIndex, tot
|
|||||||
}, [disableTimer, minTimer]);
|
}, [disableTimer, minTimer]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (timer <= 0) setHasExamEnded(true);
|
if (timer <= 0) setShowModal(true);
|
||||||
}, [setHasExamEnded, timer]);
|
}, [timer]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (timer < 300 && !warningMode) setWarningMode(true);
|
||||||
|
}, [timer, warningMode]);
|
||||||
|
|
||||||
const moduleIcon: {[key in Module]: ReactNode} = {
|
const moduleIcon: {[key in Module]: ReactNode} = {
|
||||||
reading: <BsBook className="text-ielts-reading w-6 h-6" />,
|
reading: <BsBook className="text-ielts-reading w-6 h-6" />,
|
||||||
@@ -41,7 +50,21 @@ export default function ModuleTitle({minTimer, module, label, exerciseIndex, tot
|
|||||||
|
|
||||||
return (
|
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" />
|
<BsStopwatch className="w-4 h-4" />
|
||||||
<span className="text-sm font-semibold w-11">
|
<span className="text-sm font-semibold w-11">
|
||||||
{timer > 0 && (
|
{timer > 0 && (
|
||||||
@@ -57,7 +80,7 @@ export default function ModuleTitle({minTimer, module, label, exerciseIndex, tot
|
|||||||
)}
|
)}
|
||||||
{timer <= 0 && <>00:00</>}
|
{timer <= 0 && <>00:00</>}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</motion.div>
|
||||||
<div className="flex gap-6 w-full h-fit items-center mt-5">
|
<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="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">
|
<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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full min-h-full h-fit flex flex-col items-center justify-between gap-8">
|
<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]);
|
const [hasExamEnded, setHasExamEnded] = useExamStore((state) => [state.hasExamEnded, state.setHasExamEnded]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasExamEnded && exerciseIndex === -1) {
|
||||||
|
setExerciseIndex((prev) => prev + 1);
|
||||||
|
}
|
||||||
|
}, [hasExamEnded, exerciseIndex]);
|
||||||
|
|
||||||
const confirmFinishModule = (keepGoing?: boolean) => {
|
const confirmFinishModule = (keepGoing?: boolean) => {
|
||||||
if (!keepGoing) {
|
if (!keepGoing) {
|
||||||
setShowBlankModal(false);
|
setShowBlankModal(false);
|
||||||
|
|||||||
@@ -87,6 +87,12 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
|
|||||||
|
|
||||||
const [hasExamEnded, setHasExamEnded] = useExamStore((state) => [state.hasExamEnded, state.setHasExamEnded]);
|
const [hasExamEnded, setHasExamEnded] = useExamStore((state) => [state.hasExamEnded, state.setHasExamEnded]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasExamEnded && exerciseIndex === -1) {
|
||||||
|
setExerciseIndex((prev) => prev + 1);
|
||||||
|
}
|
||||||
|
}, [hasExamEnded, exerciseIndex]);
|
||||||
|
|
||||||
const confirmFinishModule = (keepGoing?: boolean) => {
|
const confirmFinishModule = (keepGoing?: boolean) => {
|
||||||
if (!keepGoing) {
|
if (!keepGoing) {
|
||||||
setShowBlankModal(false);
|
setShowBlankModal(false);
|
||||||
|
|||||||
@@ -203,8 +203,6 @@ export default function Page() {
|
|||||||
const solutionIds = solutions.map((x) => x.exercise);
|
const solutionIds = solutions.map((x) => x.exercise);
|
||||||
const solutionExams = solutions.map((x) => x.exam);
|
const solutionExams = solutions.map((x) => x.exam);
|
||||||
|
|
||||||
console.log(solutionExams, exam?.id, moduleIndex, selectedModules);
|
|
||||||
|
|
||||||
if (exam && !solutionExams.includes(exam.id)) return;
|
if (exam && !solutionExams.includes(exam.id)) return;
|
||||||
|
|
||||||
if (exam && (exam.module === "writing" || exam.module === "speaking") && solutions.length > 0 && !showSolutions) {
|
if (exam && (exam.module === "writing" || exam.module === "speaking") && solutions.length > 0 && !showSolutions) {
|
||||||
|
|||||||
Reference in New Issue
Block a user