- Added a new type of exercise
- Updated all solutions to solve a huge bug where after reviewing, it would reset the score
This commit is contained in:
@@ -83,7 +83,7 @@ export default function MatchSentences({id, options, type, prompt, sentences, us
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{answers.map((solution, index) => (
|
{answers.map((solution, index) => (
|
||||||
<Xarrow key={index} start={solution.question} end={solution.option} lineColor="#307912" showHead={false} />
|
<Xarrow key={index} start={solution.question} end={solution.option} lineColor="#7872BF" showHead={false} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
111
src/components/Exercises/TrueFalse.tsx
Normal file
111
src/components/Exercises/TrueFalse.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import {TrueFalseExercise} from "@/interfaces/exam";
|
||||||
|
import useExamStore from "@/stores/examStore";
|
||||||
|
import {Fragment, useEffect, useState} from "react";
|
||||||
|
import {CommonProps} from ".";
|
||||||
|
import Button from "../Low/Button";
|
||||||
|
|
||||||
|
export default function TrueFalse({id, type, prompt, questions, userSolutions, onNext, onBack}: TrueFalseExercise & CommonProps) {
|
||||||
|
const [answers, setAnswers] = useState<{id: string; solution: "true" | "false" | "not_given"}[]>(userSolutions);
|
||||||
|
|
||||||
|
const hasExamEnded = useExamStore((state) => state.hasExamEnded);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasExamEnded) onNext({exercise: id, solutions: answers, score: calculateScore(), type});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [hasExamEnded]);
|
||||||
|
|
||||||
|
const calculateScore = () => {
|
||||||
|
const total = questions.length || 0;
|
||||||
|
const correct = answers.filter((x) => questions.find((y) => x.id === y.id)?.solution === x.solution.toLowerCase() || false).length;
|
||||||
|
const missing = total - answers.filter((x) => questions.find((y) => x.id === y.id)).length;
|
||||||
|
|
||||||
|
return {total, correct, missing};
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleAnswer = (solution: "true" | "false" | "not_given", questionId: string) => {
|
||||||
|
const answer = answers.find((x) => x.id === questionId);
|
||||||
|
if (answer && answer.solution === solution) {
|
||||||
|
setAnswers((prev) => prev.filter((x) => x.id !== questionId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAnswers((prev) => [...prev.filter((x) => x.id !== questionId), {id: questionId, solution}]);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-4 mt-4 h-full mb-20">
|
||||||
|
<span className="text-sm w-full leading-6">
|
||||||
|
{prompt.split("\\n").map((line, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
{line}
|
||||||
|
<br />
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
<div className="flex flex-col gap-6 mb-4">
|
||||||
|
<p>For each of the questions below, select</p>
|
||||||
|
<div className="pl-8 flex gap-8">
|
||||||
|
<span className="flex flex-col gap-4">
|
||||||
|
<span className="font-bold italic">TRUE</span>
|
||||||
|
<span className="font-bold italic">FALSE</span>
|
||||||
|
<span className="font-bold italic">NOT GIVEN</span>
|
||||||
|
</span>
|
||||||
|
<span className="flex flex-col gap-4">
|
||||||
|
<span>if the statement agrees with the information</span>
|
||||||
|
<span>if the statement contradicts with the information</span>
|
||||||
|
<span>if there is no information on this</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm w-full leading-6">You can click a selected option again to deselect it.</span>
|
||||||
|
<div className="bg-mti-gray-smoke rounded-xl px-5 py-6 flex flex-col gap-8">
|
||||||
|
{questions.map((question, index) => (
|
||||||
|
<div key={question.id} className="flex flex-col gap-4">
|
||||||
|
<span>
|
||||||
|
{index + 1}. {question.prompt}
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant={answers.find((x) => x.id === question.id)?.solution === "true" ? "solid" : "outline"}
|
||||||
|
onClick={() => toggleAnswer("true", question.id)}
|
||||||
|
className="!py-2">
|
||||||
|
True
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={answers.find((x) => x.id === question.id)?.solution === "false" ? "solid" : "outline"}
|
||||||
|
onClick={() => toggleAnswer("false", question.id)}
|
||||||
|
className="!py-2">
|
||||||
|
False
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={answers.find((x) => x.id === question.id)?.solution === "not_given" ? "solid" : "outline"}
|
||||||
|
onClick={() => toggleAnswer("not_given", question.id)}
|
||||||
|
className="!py-2">
|
||||||
|
Not Given
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
|
<Button
|
||||||
|
color="purple"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => onBack({exercise: id, solutions: answers, score: calculateScore(), type})}
|
||||||
|
className="max-w-[200px] w-full">
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
color="purple"
|
||||||
|
onClick={() => onNext({exercise: id, solutions: answers, score: calculateScore(), type})}
|
||||||
|
className="max-w-[200px] self-end w-full">
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
MatchSentencesExercise,
|
MatchSentencesExercise,
|
||||||
MultipleChoiceExercise,
|
MultipleChoiceExercise,
|
||||||
SpeakingExercise,
|
SpeakingExercise,
|
||||||
|
TrueFalseExercise,
|
||||||
UserSolution,
|
UserSolution,
|
||||||
WriteBlanksExercise,
|
WriteBlanksExercise,
|
||||||
WritingExercise,
|
WritingExercise,
|
||||||
@@ -14,6 +15,7 @@ import MultipleChoice from "./MultipleChoice";
|
|||||||
import WriteBlanks from "./WriteBlanks";
|
import WriteBlanks from "./WriteBlanks";
|
||||||
import Writing from "./Writing";
|
import Writing from "./Writing";
|
||||||
import Speaking from "./Speaking";
|
import Speaking from "./Speaking";
|
||||||
|
import TrueFalse from "./TrueFalse";
|
||||||
|
|
||||||
const MatchSentences = dynamic(() => import("@/components/Exercises/MatchSentences"), {ssr: false});
|
const MatchSentences = dynamic(() => import("@/components/Exercises/MatchSentences"), {ssr: false});
|
||||||
|
|
||||||
@@ -26,6 +28,8 @@ export const renderExercise = (exercise: Exercise, onNext: (userSolutions: UserS
|
|||||||
switch (exercise.type) {
|
switch (exercise.type) {
|
||||||
case "fillBlanks":
|
case "fillBlanks":
|
||||||
return <FillBlanks {...(exercise as FillBlanksExercise)} onNext={onNext} onBack={onBack} />;
|
return <FillBlanks {...(exercise as FillBlanksExercise)} onNext={onNext} onBack={onBack} />;
|
||||||
|
case "trueFalse":
|
||||||
|
return <TrueFalse {...(exercise as TrueFalseExercise)} onNext={onNext} onBack={onBack} />;
|
||||||
case "matchSentences":
|
case "matchSentences":
|
||||||
return <MatchSentences {...(exercise as MatchSentencesExercise)} onNext={onNext} onBack={onBack} />;
|
return <MatchSentences {...(exercise as MatchSentencesExercise)} onNext={onNext} onBack={onBack} />;
|
||||||
case "multipleChoice":
|
case "multipleChoice":
|
||||||
|
|||||||
@@ -13,19 +13,19 @@ interface Props {
|
|||||||
export default function Button({color = "purple", variant = "solid", disabled = false, className, children, onClick}: Props) {
|
export default function Button({color = "purple", variant = "solid", disabled = false, className, children, onClick}: Props) {
|
||||||
const colorClassNames: {[key in typeof color]: {[key in typeof variant]: string}} = {
|
const colorClassNames: {[key in typeof color]: {[key in typeof variant]: string}} = {
|
||||||
purple: {
|
purple: {
|
||||||
solid: "bg-mti-purple-light text-white hover:bg-mti-purple disabled:text-mti-purple disabled:bg-mti-purple-ultralight selection:bg-mti-purple-dark",
|
solid: "bg-mti-purple-light text-white border border-mti-purple-light hover:bg-mti-purple disabled:text-mti-purple disabled:bg-mti-purple-ultralight selection:bg-mti-purple-dark",
|
||||||
outline:
|
outline:
|
||||||
"bg-transparent text-mti-purple-light border border-mti-purple-light hover:bg-mti-purple-light disabled:text-mti-purple disabled:bg-mti-purple-ultralight disabled:border-none selection:bg-mti-purple-dark hover:text-white selection:text-white",
|
"bg-transparent text-mti-purple-light border border-mti-purple-light hover:bg-mti-purple-light disabled:text-mti-purple disabled:bg-mti-purple-ultralight disabled:border-none selection:bg-mti-purple-dark hover:text-white selection:text-white",
|
||||||
},
|
},
|
||||||
red: {
|
red: {
|
||||||
solid: "bg-mti-red-light text-white hover:bg-mti-red disabled:text-mti-red disabled:bg-mti-red-ultralight selection:bg-mti-red-dark",
|
solid: "bg-mti-red-light text-white border border-mti-red-light hover:bg-mti-red disabled:text-mti-red disabled:bg-mti-red-ultralight selection:bg-mti-red-dark",
|
||||||
outline:
|
outline:
|
||||||
"bg-transparent text-mti-red-light border border-mti-red-light hover:bg-mti-red-light disabled:text-mti-red disabled:bg-mti-red-ultralight disabled:border-none selection:bg-mti-red-dark hover:text-white selection:text-white",
|
"bg-transparent text-mti-red-light border border-mti-red-light hover:bg-mti-red-light disabled:text-mti-red disabled:bg-mti-red-ultralight disabled:border-none selection:bg-mti-red-dark hover:text-white selection:text-white",
|
||||||
},
|
},
|
||||||
rose: {
|
rose: {
|
||||||
solid: "bg-mti-orange-light text-white hover:bg-mti-orange disabled:text-mti-orange disabled:bg-mti-orange-ultralight selection:bg-mti-orange-dark",
|
solid: "bg-mti-rose-light text-white border border-mti-rose-light hover:bg-mti-rose disabled:text-mti-rose disabled:bg-mti-rose-ultralight selection:bg-mti-rose-dark",
|
||||||
outline:
|
outline:
|
||||||
"bg-transparent text-mti-orange-light border border-mti-orange-light hover:bg-mti-orange-light disabled:text-mti-orange disabled:bg-mti-orange-ultralight disabled:border-none selection:bg-mti-orange-dark hover:text-white selection:text-white",
|
"bg-transparent text-mti-rose-light border border-mti-rose-light hover:bg-mti-rose-light disabled:text-mti-rose disabled:bg-mti-rose-ultralight disabled:border-none selection:bg-mti-rose-dark hover:text-white selection:text-white",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default function Sidebar({path, navDisabled = false}: Props) {
|
|||||||
tabIndex={1}
|
tabIndex={1}
|
||||||
onClick={logout}
|
onClick={logout}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"p-4 px-8 rounded-full flex gap-4 items-center cursor-pointer text-black hover:text-mti-orange transition duration-300 ease-in-out",
|
"p-4 px-8 rounded-full flex gap-4 items-center cursor-pointer text-black hover:text-mti-rose transition duration-300 ease-in-out",
|
||||||
"absolute bottom-8",
|
"absolute bottom-8",
|
||||||
)}>
|
)}>
|
||||||
<RiLogoutBoxFill size={20} />
|
<RiLogoutBoxFill size={20} />
|
||||||
|
|||||||
@@ -5,7 +5,15 @@ import {CommonProps} from ".";
|
|||||||
import {Fragment} from "react";
|
import {Fragment} from "react";
|
||||||
import Button from "../Low/Button";
|
import Button from "../Low/Button";
|
||||||
|
|
||||||
export default function FillBlanksSolutions({prompt, solutions, text, userSolutions, onNext, onBack}: FillBlanksExercise & CommonProps) {
|
export default function FillBlanksSolutions({id, type, prompt, solutions, text, userSolutions, onNext, onBack}: FillBlanksExercise & CommonProps) {
|
||||||
|
const calculateScore = () => {
|
||||||
|
const total = text.match(/({{\d+}})/g)?.length || 0;
|
||||||
|
const correct = userSolutions.filter((x) => solutions.find((y) => x.id === y.id)?.solution === x.solution.toLowerCase() || false).length;
|
||||||
|
const missing = total - userSolutions.filter((x) => solutions.find((y) => x.id === y.id)).length;
|
||||||
|
|
||||||
|
return {total, correct, missing};
|
||||||
|
};
|
||||||
|
|
||||||
const renderLines = (line: string) => {
|
const renderLines = (line: string) => {
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
@@ -42,8 +50,8 @@ export default function FillBlanksSolutions({prompt, solutions, text, userSoluti
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"rounded-full hover:text-white hover:bg-mti-orange transition duration-300 ease-in-out my-1 mr-1",
|
"rounded-full hover:text-white hover:bg-mti-rose transition duration-300 ease-in-out my-1 mr-1",
|
||||||
userSolution && "px-5 py-2 text-center text-white bg-mti-orange-light",
|
userSolution && "px-5 py-2 text-center text-white bg-mti-rose-light",
|
||||||
)}>
|
)}>
|
||||||
{userSolution.solution}
|
{userSolution.solution}
|
||||||
</button>
|
</button>
|
||||||
@@ -92,18 +100,25 @@ export default function FillBlanksSolutions({prompt, solutions, text, userSoluti
|
|||||||
Unanswered
|
Unanswered
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<div className="w-4 h-4 rounded-full bg-mti-orange" />
|
<div className="w-4 h-4 rounded-full bg-mti-rose" />
|
||||||
Wrong
|
Wrong
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Button color="purple" variant="outline" onClick={onBack} className="max-w-[200px] w-full">
|
<Button
|
||||||
|
color="purple"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => onBack({exercise: id, solutions: userSolutions, score: calculateScore(), type})}
|
||||||
|
className="max-w-[200px] w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
<Button
|
||||||
|
color="purple"
|
||||||
|
onClick={() => onNext({exercise: id, solutions: userSolutions, score: calculateScore(), type})}
|
||||||
|
className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,24 @@ import {Fragment} from "react";
|
|||||||
import Button from "../Low/Button";
|
import Button from "../Low/Button";
|
||||||
import Xarrow from "react-xarrows";
|
import Xarrow from "react-xarrows";
|
||||||
|
|
||||||
export default function MatchSentencesSolutions({options, prompt, sentences, userSolutions, onNext, onBack}: MatchSentencesExercise & CommonProps) {
|
export default function MatchSentencesSolutions({
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
options,
|
||||||
|
prompt,
|
||||||
|
sentences,
|
||||||
|
userSolutions,
|
||||||
|
onNext,
|
||||||
|
onBack,
|
||||||
|
}: MatchSentencesExercise & CommonProps) {
|
||||||
|
const calculateScore = () => {
|
||||||
|
const total = sentences.length;
|
||||||
|
const correct = userSolutions.filter((x) => sentences.find((y) => y.id === x.question)?.solution === x.option || false).length;
|
||||||
|
const missing = total - userSolutions.filter((x) => sentences.find((y) => y.id === x.question)).length;
|
||||||
|
|
||||||
|
return {total, correct, missing};
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-4 mt-4 h-full mb-20">
|
<div className="flex flex-col gap-4 mt-4 h-full mb-20">
|
||||||
@@ -33,7 +50,7 @@ export default function MatchSentencesSolutions({options, prompt, sentences, use
|
|||||||
"transition duration-300 ease-in-out",
|
"transition duration-300 ease-in-out",
|
||||||
!userSolutions.find((x) => x.question === id) && "!bg-mti-red",
|
!userSolutions.find((x) => x.question === id) && "!bg-mti-red",
|
||||||
userSolutions.find((x) => x.question === id)?.option === solution && "bg-mti-purple",
|
userSolutions.find((x) => x.question === id)?.option === solution && "bg-mti-purple",
|
||||||
userSolutions.find((x) => x.question === id)?.option !== solution && "bg-mti-orange",
|
userSolutions.find((x) => x.question === id)?.option !== solution && "bg-mti-rose",
|
||||||
)}>
|
)}>
|
||||||
{id}
|
{id}
|
||||||
</button>
|
</button>
|
||||||
@@ -62,10 +79,10 @@ export default function MatchSentencesSolutions({options, prompt, sentences, use
|
|||||||
end={sentence.solution}
|
end={sentence.solution}
|
||||||
lineColor={
|
lineColor={
|
||||||
!userSolutions.find((x) => x.question === sentence.id)
|
!userSolutions.find((x) => x.question === sentence.id)
|
||||||
? "#0696ff"
|
? "#CC5454"
|
||||||
: userSolutions.find((x) => x.question === sentence.id)?.option === sentence.solution
|
: userSolutions.find((x) => x.question === sentence.id)?.option === sentence.solution
|
||||||
? "#307912"
|
? "#7872BF"
|
||||||
: "#FF6000"
|
: "#CC5454"
|
||||||
}
|
}
|
||||||
showHead={false}
|
showHead={false}
|
||||||
/>
|
/>
|
||||||
@@ -79,17 +96,24 @@ export default function MatchSentencesSolutions({options, prompt, sentences, use
|
|||||||
<div className="w-4 h-4 rounded-full bg-mti-red" /> Unanswered
|
<div className="w-4 h-4 rounded-full bg-mti-red" /> Unanswered
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<div className="w-4 h-4 rounded-full bg-mti-orange" /> Wrong
|
<div className="w-4 h-4 rounded-full bg-mti-rose" /> Wrong
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Button color="purple" variant="outline" onClick={() => onBack()} className="max-w-[200px] w-full">
|
<Button
|
||||||
|
color="purple"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => onBack({exercise: id, solutions: userSolutions, score: calculateScore(), type})}
|
||||||
|
className="max-w-[200px] w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
<Button
|
||||||
|
color="purple"
|
||||||
|
onClick={() => onNext({exercise: id, solutions: userSolutions, score: calculateScore(), type})}
|
||||||
|
className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function Question({
|
|||||||
return "!border-mti-purple-light !text-mti-purple-light";
|
return "!border-mti-purple-light !text-mti-purple-light";
|
||||||
}
|
}
|
||||||
|
|
||||||
return userSolution === option ? "!border-mti-orange-light !text-mti-orange-light" : "";
|
return userSolution === option ? "!border-mti-rose-light !text-mti-rose-light" : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -54,12 +54,20 @@ function Question({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MultipleChoice({prompt, questions, userSolutions, onNext, onBack}: MultipleChoiceExercise & CommonProps) {
|
export default function MultipleChoice({id, type, prompt, questions, userSolutions, onNext, onBack}: MultipleChoiceExercise & CommonProps) {
|
||||||
const [questionIndex, setQuestionIndex] = useState(0);
|
const [questionIndex, setQuestionIndex] = useState(0);
|
||||||
|
|
||||||
|
const calculateScore = () => {
|
||||||
|
const total = questions.length;
|
||||||
|
const correct = userSolutions.filter((x) => questions.find((y) => y.id === x.question)?.solution === x.option || false).length;
|
||||||
|
const missing = total - userSolutions.filter((x) => questions.find((y) => y.id === x.question)).length;
|
||||||
|
|
||||||
|
return {total, correct, missing};
|
||||||
|
};
|
||||||
|
|
||||||
const next = () => {
|
const next = () => {
|
||||||
if (questionIndex === questions.length - 1) {
|
if (questionIndex === questions.length - 1) {
|
||||||
onNext();
|
onNext({exercise: id, solutions: userSolutions, score: calculateScore(), type});
|
||||||
} else {
|
} else {
|
||||||
setQuestionIndex((prev) => prev + 1);
|
setQuestionIndex((prev) => prev + 1);
|
||||||
}
|
}
|
||||||
@@ -67,7 +75,7 @@ export default function MultipleChoice({prompt, questions, userSolutions, onNext
|
|||||||
|
|
||||||
const back = () => {
|
const back = () => {
|
||||||
if (questionIndex === 0) {
|
if (questionIndex === 0) {
|
||||||
onBack();
|
onBack({exercise: id, solutions: userSolutions, score: calculateScore(), type});
|
||||||
} else {
|
} else {
|
||||||
setQuestionIndex((prev) => prev - 1);
|
setQuestionIndex((prev) => prev - 1);
|
||||||
}
|
}
|
||||||
@@ -95,7 +103,7 @@ export default function MultipleChoice({prompt, questions, userSolutions, onNext
|
|||||||
Unanswered
|
Unanswered
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<div className="w-4 h-4 rounded-full bg-mti-orange" />
|
<div className="w-4 h-4 rounded-full bg-mti-rose" />
|
||||||
Wrong
|
Wrong
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import axios from "axios";
|
|||||||
|
|
||||||
const Waveform = dynamic(() => import("../Waveform"), {ssr: false});
|
const Waveform = dynamic(() => import("../Waveform"), {ssr: false});
|
||||||
|
|
||||||
export default function Speaking({title, text, prompts, userSolutions, onNext, onBack}: SpeakingExercise & CommonProps) {
|
export default function Speaking({id, type, title, text, prompts, userSolutions, onNext, onBack}: SpeakingExercise & CommonProps) {
|
||||||
const [solutionURL, setSolutionURL] = useState<string>();
|
const [solutionURL, setSolutionURL] = useState<string>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -71,11 +71,31 @@ export default function Speaking({title, text, prompts, userSolutions, onNext, o
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Button color="purple" variant="outline" onClick={onBack} className="max-w-[200px] w-full">
|
<Button
|
||||||
|
color="purple"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() =>
|
||||||
|
onBack({
|
||||||
|
exercise: id,
|
||||||
|
solutions: userSolutions,
|
||||||
|
score: {correct: 1, total: 1, missing: 0},
|
||||||
|
type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="max-w-[200px] self-end w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
<Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
color="purple"
|
||||||
|
onClick={() =>
|
||||||
|
onNext({
|
||||||
|
exercise: id,
|
||||||
|
solutions: userSolutions,
|
||||||
|
score: {correct: 1, total: 1, missing: 0},
|
||||||
|
type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
131
src/components/Solutions/TrueFalse.tsx
Normal file
131
src/components/Solutions/TrueFalse.tsx
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import {FillBlanksExercise, TrueFalseExercise} from "@/interfaces/exam";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import reactStringReplace from "react-string-replace";
|
||||||
|
import {CommonProps} from ".";
|
||||||
|
import {Fragment} from "react";
|
||||||
|
import Button from "../Low/Button";
|
||||||
|
|
||||||
|
type Solution = "true" | "false" | "not_given";
|
||||||
|
|
||||||
|
export default function TrueFalseSolution({prompt, type, id, questions, userSolutions, onNext, onBack}: TrueFalseExercise & CommonProps) {
|
||||||
|
const calculateScore = () => {
|
||||||
|
const total = questions.length || 0;
|
||||||
|
const correct = userSolutions.filter((x) => questions.find((y) => x.id === y.id)?.solution === x.solution.toLowerCase() || false).length;
|
||||||
|
const missing = total - userSolutions.filter((x) => questions.find((y) => x.id === y.id)).length;
|
||||||
|
|
||||||
|
return {total, correct, missing};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getButtonColor = (buttonSolution: Solution, solution: Solution, userSolution: Solution | undefined) => {
|
||||||
|
if (buttonSolution !== userSolution && buttonSolution !== solution) return "purple";
|
||||||
|
|
||||||
|
if (userSolution) {
|
||||||
|
if (userSolution === buttonSolution && solution === buttonSolution) {
|
||||||
|
return "purple";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (solution === buttonSolution) {
|
||||||
|
return "purple";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "rose";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "red";
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-4 mt-4 h-full mb-20">
|
||||||
|
<span className="text-sm w-full leading-6">
|
||||||
|
{prompt.split("\\n").map((line, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
{line}
|
||||||
|
<br />
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
<div className="flex flex-col gap-6 mb-4">
|
||||||
|
<p>For each of the questions below, select</p>
|
||||||
|
<div className="pl-8 flex gap-8">
|
||||||
|
<span className="flex flex-col gap-4">
|
||||||
|
<span className="font-bold italic">TRUE</span>
|
||||||
|
<span className="font-bold italic">FALSE</span>
|
||||||
|
<span className="font-bold italic">NOT GIVEN</span>
|
||||||
|
</span>
|
||||||
|
<span className="flex flex-col gap-4">
|
||||||
|
<span>if the statement agrees with the information</span>
|
||||||
|
<span>if the statement contradicts with the information</span>
|
||||||
|
<span>if there is no information on this</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm w-full leading-6">You can click a selected option again to deselect it.</span>
|
||||||
|
<div className="bg-mti-gray-smoke rounded-xl px-5 py-6 flex flex-col gap-8">
|
||||||
|
{questions.map((question, index) => {
|
||||||
|
const userSolution = userSolutions.find((x) => x.id === question.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={question.id} className="flex flex-col gap-4">
|
||||||
|
<span>
|
||||||
|
{index + 1}. {question.prompt}
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant={question.solution === "true" || userSolution?.solution === "true" ? "solid" : "outline"}
|
||||||
|
className="!py-2"
|
||||||
|
color={getButtonColor("true", question.solution, userSolution?.solution)}>
|
||||||
|
True
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={question.solution === "false" || userSolution?.solution === "false" ? "solid" : "outline"}
|
||||||
|
className="!py-2"
|
||||||
|
color={getButtonColor("false", question.solution, userSolution?.solution)}>
|
||||||
|
False
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={question.solution === "not_given" || userSolution?.solution === "not_given" ? "solid" : "outline"}
|
||||||
|
className="!py-2"
|
||||||
|
color={getButtonColor("not_given", question.solution, userSolution?.solution)}>
|
||||||
|
Not Given
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-4 items-center">
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<div className="w-4 h-4 rounded-full bg-mti-purple" />
|
||||||
|
Correct
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<div className="w-4 h-4 rounded-full bg-mti-red" />
|
||||||
|
Unanswered
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<div className="w-4 h-4 rounded-full bg-mti-rose" />
|
||||||
|
Wrong
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
|
<Button
|
||||||
|
color="purple"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => onBack({exercise: id, solutions: userSolutions, score: calculateScore(), type})}
|
||||||
|
className="max-w-[200px] w-full">
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
color="purple"
|
||||||
|
onClick={() => onNext({exercise: id, solutions: userSolutions, score: calculateScore(), type})}
|
||||||
|
className="max-w-[200px] self-end w-full">
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ function Blank({
|
|||||||
<span className="inline-flex gap-2">
|
<span className="inline-flex gap-2">
|
||||||
{userSolution && !isUserSolutionCorrect() && (
|
{userSolution && !isUserSolutionCorrect() && (
|
||||||
<input
|
<input
|
||||||
className="py-2 px-3 rounded-2xl w-48 focus:outline-none my-2 bg-mti-orange-ultralight text-mti-orange-light"
|
className="py-2 px-3 rounded-2xl w-48 focus:outline-none my-2 bg-mti-rose-ultralight text-mti-rose-light"
|
||||||
placeholder={id}
|
placeholder={id}
|
||||||
onChange={(e) => setUserInput(e.target.value)}
|
onChange={(e) => setUserInput(e.target.value)}
|
||||||
value={userSolution}
|
value={userSolution}
|
||||||
@@ -69,6 +69,7 @@ function Blank({
|
|||||||
|
|
||||||
export default function WriteBlanksSolutions({
|
export default function WriteBlanksSolutions({
|
||||||
id,
|
id,
|
||||||
|
type,
|
||||||
prompt,
|
prompt,
|
||||||
maxWords,
|
maxWords,
|
||||||
solutions,
|
solutions,
|
||||||
@@ -77,6 +78,20 @@ export default function WriteBlanksSolutions({
|
|||||||
onNext,
|
onNext,
|
||||||
onBack,
|
onBack,
|
||||||
}: WriteBlanksExercise & CommonProps) {
|
}: WriteBlanksExercise & CommonProps) {
|
||||||
|
const calculateScore = () => {
|
||||||
|
const total = text.match(/({{\d+}})/g)?.length || 0;
|
||||||
|
const correct = userSolutions.filter(
|
||||||
|
(x) =>
|
||||||
|
solutions
|
||||||
|
.find((y) => x.id === y.id)
|
||||||
|
?.solution.map((y) => y.toLowerCase())
|
||||||
|
.includes(x.solution.toLowerCase()) || false,
|
||||||
|
).length;
|
||||||
|
const missing = total - userSolutions.filter((x) => solutions.find((y) => x.id === y.id)).length;
|
||||||
|
|
||||||
|
return {total, correct, missing};
|
||||||
|
};
|
||||||
|
|
||||||
const renderLines = (line: string) => {
|
const renderLines = (line: string) => {
|
||||||
return (
|
return (
|
||||||
<span className="text-base leading-5">
|
<span className="text-base leading-5">
|
||||||
@@ -120,18 +135,25 @@ export default function WriteBlanksSolutions({
|
|||||||
Unanswered
|
Unanswered
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<div className="w-4 h-4 rounded-full bg-mti-orange" />
|
<div className="w-4 h-4 rounded-full bg-mti-rose" />
|
||||||
Wrong
|
Wrong
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Button color="purple" variant="outline" onClick={() => onBack()} className="max-w-[200px] w-full">
|
<Button
|
||||||
|
color="purple"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => onBack({exercise: id, solutions: userSolutions, score: calculateScore(), type})}
|
||||||
|
className="max-w-[200px] w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
<Button
|
||||||
|
color="purple"
|
||||||
|
onClick={() => onNext({exercise: id, solutions: userSolutions, score: calculateScore(), type})}
|
||||||
|
className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {toast} from "react-toastify";
|
|||||||
import Button from "../Low/Button";
|
import Button from "../Low/Button";
|
||||||
import {Dialog, Transition} from "@headlessui/react";
|
import {Dialog, Transition} from "@headlessui/react";
|
||||||
|
|
||||||
export default function Writing({id, prompt, attachment, userSolutions, onNext, onBack}: WritingExercise & CommonProps) {
|
export default function Writing({id, type, prompt, attachment, userSolutions, onNext, onBack}: WritingExercise & CommonProps) {
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -96,11 +96,17 @@ export default function Writing({id, prompt, attachment, userSolutions, onNext,
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Button color="purple" variant="outline" onClick={onBack} className="max-w-[200px] w-full">
|
<Button
|
||||||
|
color="purple"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => onBack({exercise: id, solutions: userSolutions, score: {correct: 1, total: 1, missing: 0}, type})}
|
||||||
|
className="max-w-[200px] self-end w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
<Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
color="purple"
|
||||||
|
onClick={() => onNext({exercise: id, solutions: userSolutions, score: {correct: 1, total: 1, missing: 0}, type})}
|
||||||
|
className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import {
|
|||||||
MatchSentencesExercise,
|
MatchSentencesExercise,
|
||||||
MultipleChoiceExercise,
|
MultipleChoiceExercise,
|
||||||
SpeakingExercise,
|
SpeakingExercise,
|
||||||
|
TrueFalseExercise,
|
||||||
|
UserSolution,
|
||||||
WriteBlanksExercise,
|
WriteBlanksExercise,
|
||||||
WritingExercise,
|
WritingExercise,
|
||||||
} from "@/interfaces/exam";
|
} from "@/interfaces/exam";
|
||||||
@@ -11,20 +13,23 @@ import dynamic from "next/dynamic";
|
|||||||
import FillBlanks from "./FillBlanks";
|
import FillBlanks from "./FillBlanks";
|
||||||
import MultipleChoice from "./MultipleChoice";
|
import MultipleChoice from "./MultipleChoice";
|
||||||
import Speaking from "./Speaking";
|
import Speaking from "./Speaking";
|
||||||
|
import TrueFalseSolution from "./TrueFalse";
|
||||||
import WriteBlanks from "./WriteBlanks";
|
import WriteBlanks from "./WriteBlanks";
|
||||||
import Writing from "./Writing";
|
import Writing from "./Writing";
|
||||||
|
|
||||||
const MatchSentences = dynamic(() => import("@/components/Solutions/MatchSentences"), {ssr: false});
|
const MatchSentences = dynamic(() => import("@/components/Solutions/MatchSentences"), {ssr: false});
|
||||||
|
|
||||||
export interface CommonProps {
|
export interface CommonProps {
|
||||||
onNext: () => void;
|
onNext: (userSolutions: UserSolution) => void;
|
||||||
onBack: () => void;
|
onBack: (userSolutions: UserSolution) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const renderSolution = (exercise: Exercise, onNext: () => void, onBack: () => void) => {
|
export const renderSolution = (exercise: Exercise, onNext: () => void, onBack: () => void) => {
|
||||||
switch (exercise.type) {
|
switch (exercise.type) {
|
||||||
case "fillBlanks":
|
case "fillBlanks":
|
||||||
return <FillBlanks {...(exercise as FillBlanksExercise)} onNext={onNext} onBack={onBack} />;
|
return <FillBlanks {...(exercise as FillBlanksExercise)} onNext={onNext} onBack={onBack} />;
|
||||||
|
case "trueFalse":
|
||||||
|
return <TrueFalseSolution {...(exercise as TrueFalseExercise)} onNext={onNext} onBack={onBack} />;
|
||||||
case "matchSentences":
|
case "matchSentences":
|
||||||
return <MatchSentences {...(exercise as MatchSentencesExercise)} onNext={onNext} onBack={onBack} />;
|
return <MatchSentences {...(exercise as MatchSentencesExercise)} onNext={onNext} onBack={onBack} />;
|
||||||
case "multipleChoice":
|
case "multipleChoice":
|
||||||
|
|||||||
@@ -155,9 +155,9 @@ export default function Finish({user, scores, modules, isLoading, onViewResults}
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="w-3 h-3 bg-mti-orange-light rounded-full mt-1" />
|
<div className="w-3 h-3 bg-mti-rose-light rounded-full mt-1" />
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="text-mti-orange-light">
|
<span className="text-mti-rose-light">
|
||||||
{(selectedScore.total - selectedScore.correct).toString().padStart(2, "0")}
|
{(selectedScore.total - selectedScore.correct).toString().padStart(2, "0")}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-lg">Wrong</span>
|
<span className="text-lg">Wrong</span>
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export interface SpeakingExam {
|
|||||||
|
|
||||||
export type Exercise =
|
export type Exercise =
|
||||||
| FillBlanksExercise
|
| FillBlanksExercise
|
||||||
|
| TrueFalseExercise
|
||||||
| MatchSentencesExercise
|
| MatchSentencesExercise
|
||||||
| MultipleChoiceExercise
|
| MultipleChoiceExercise
|
||||||
| WriteBlanksExercise
|
| WriteBlanksExercise
|
||||||
@@ -122,6 +123,20 @@ export interface FillBlanksExercise {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TrueFalseExercise {
|
||||||
|
type: "trueFalse";
|
||||||
|
id: string;
|
||||||
|
prompt: string; // *EXAMPLE: "Select the appropriate option."
|
||||||
|
questions: TrueFalseQuestion[];
|
||||||
|
userSolutions: {id: string; solution: "true" | "false" | "not_given"}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrueFalseQuestion {
|
||||||
|
id: string; // *EXAMPLE: "1"
|
||||||
|
prompt: string; // *EXAMPLE: "What does her briefcase look like?"
|
||||||
|
solution: "true" | "false" | "not_given"; // *EXAMPLE: "True"
|
||||||
|
}
|
||||||
|
|
||||||
export interface WriteBlanksExercise {
|
export interface WriteBlanksExercise {
|
||||||
prompt: string; // *EXAMPLE: "Complete the notes below by writing NO MORE THAN THREE WORDS in the spaces provided."
|
prompt: string; // *EXAMPLE: "Complete the notes below by writing NO MORE THAN THREE WORDS in the spaces provided."
|
||||||
maxWords: number; // *EXAMPLE: 3 - The maximum amount of words allowed per blank, 0 for unlimited
|
maxWords: number; // *EXAMPLE: 3 - The maximum amount of words allowed per blank, 0 for unlimited
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export default function Login() {
|
|||||||
<main className="w-full h-[100vh] flex bg-white text-black">
|
<main className="w-full h-[100vh] flex bg-white text-black">
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
<section className="h-full w-fit min-w-fit relative">
|
<section className="h-full w-fit min-w-fit relative">
|
||||||
<div className="absolute h-full w-full bg-mti-orange-light z-10 bg-opacity-50" />
|
<div className="absolute h-full w-full bg-mti-rose-light z-10 bg-opacity-50" />
|
||||||
<img src="/people-talking-tablet.png" alt="People smiling looking at a tablet" className="h-full aspect-auto" />
|
<img src="/people-talking-tablet.png" alt="People smiling looking at a tablet" className="h-full aspect-auto" />
|
||||||
</section>
|
</section>
|
||||||
<section className="h-full w-full flex flex-col items-center justify-center gap-2">
|
<section className="h-full w-full flex flex-col items-center justify-center gap-2">
|
||||||
@@ -79,9 +79,7 @@ export default function Login() {
|
|||||||
</div>
|
</div>
|
||||||
<span>Remember my password</span>
|
<span>Remember my password</span>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/forgot-password" className="text-mti-purple-light text-xs">
|
<span className="text-mti-purple-light text-xs">Forgot Password?</span>
|
||||||
Forgot Password?
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
<Button className="mt-8 w-full" color="purple" disabled={isLoading}>
|
<Button className="mt-8 w-full" color="purple" disabled={isLoading}>
|
||||||
{!isLoading && "Login"}
|
{!isLoading && "Login"}
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ export default function History({user}: {user: User}) {
|
|||||||
"flex flex-col gap-4 border border-mti-gray-platinum p-4 cursor-pointer rounded-xl transition ease-in-out duration-300",
|
"flex flex-col gap-4 border border-mti-gray-platinum p-4 cursor-pointer rounded-xl transition ease-in-out duration-300",
|
||||||
correct / total >= 0.7 && "hover:border-mti-purple",
|
correct / total >= 0.7 && "hover:border-mti-purple",
|
||||||
correct / total >= 0.3 && correct / total < 0.7 && "hover:border-mti-red",
|
correct / total >= 0.3 && correct / total < 0.7 && "hover:border-mti-red",
|
||||||
correct / total < 0.3 && "hover:border-mti-orange",
|
correct / total < 0.3 && "hover:border-mti-rose",
|
||||||
)}
|
)}
|
||||||
onClick={selectExam}
|
onClick={selectExam}
|
||||||
role="button">
|
role="button">
|
||||||
@@ -174,7 +174,7 @@ export default function History({user}: {user: User}) {
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
correct / total >= 0.7 && "text-mti-purple",
|
correct / total >= 0.7 && "text-mti-purple",
|
||||||
correct / total >= 0.3 && correct / total < 0.7 && "text-mti-red",
|
correct / total >= 0.3 && correct / total < 0.7 && "text-mti-red",
|
||||||
correct / total < 0.3 && "text-mti-orange",
|
correct / total < 0.3 && "text-mti-rose",
|
||||||
)}>
|
)}>
|
||||||
Level{" "}
|
Level{" "}
|
||||||
{(aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length).toFixed(1)}
|
{(aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length).toFixed(1)}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export default function Register() {
|
|||||||
<main className="w-full h-[100vh] flex bg-white text-black">
|
<main className="w-full h-[100vh] flex bg-white text-black">
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
<section className="h-full w-fit min-w-fit relative">
|
<section className="h-full w-fit min-w-fit relative">
|
||||||
<div className="absolute h-full w-full bg-mti-orange-light z-10 bg-opacity-50" />
|
<div className="absolute h-full w-full bg-mti-rose-light z-10 bg-opacity-50" />
|
||||||
<img src="/people-talking-tablet.png" alt="People smiling looking at a tablet" className="h-full aspect-auto" />
|
<img src="/people-talking-tablet.png" alt="People smiling looking at a tablet" className="h-full aspect-auto" />
|
||||||
</section>
|
</section>
|
||||||
<section className="h-full w-full flex flex-col items-center justify-center gap-12">
|
<section className="h-full w-full flex flex-col items-center justify-center gap-12">
|
||||||
|
|||||||
Reference in New Issue
Block a user