diff --git a/src/components/Exercises/FillBlanks/index.tsx b/src/components/Exercises/FillBlanks/index.tsx index 17253084..9060bba2 100644 --- a/src/components/Exercises/FillBlanks/index.tsx +++ b/src/components/Exercises/FillBlanks/index.tsx @@ -8,6 +8,7 @@ import { CommonProps } from "../types"; import { v4 } from "uuid"; import MCDropdown from "./MCDropdown"; import useExamStore, { usePersistentExamStore } from "@/stores/exam"; +import PracticeBadge from "@/components/Low/PracticeBadge"; const FillBlanks: React.FC = ({ id, @@ -166,7 +167,7 @@ const FillBlanks: React.FC = ({ return (
{headerButtons} -
+
{variant !== "mc" && ( {prompt.split("\\n").map((line, index) => ( @@ -177,6 +178,7 @@ const FillBlanks: React.FC = ({ ))} )} + {isPractice && } {memoizedLines} {variant !== "mc" && (
diff --git a/src/components/Exercises/InteractiveSpeaking/old.tsx b/src/components/Exercises/InteractiveSpeaking/old.tsx deleted file mode 100644 index e629a8c6..00000000 --- a/src/components/Exercises/InteractiveSpeaking/old.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import { InteractiveSpeakingExercise } from "@/interfaces/exam"; -import { CommonProps } from "../types"; -import { useEffect, useState } from "react"; -import { BsCheckCircleFill, BsMicFill, BsPauseCircle, BsPlayCircle, BsTrashFill } from "react-icons/bs"; -import dynamic from "next/dynamic"; -import useExamStore, { usePersistentExamStore } from "@/stores/exam"; - -const Waveform = dynamic(() => import("../../Waveform"), { ssr: false }); -const ReactMediaRecorder = dynamic(() => import("react-media-recorder").then((mod) => mod.ReactMediaRecorder), { - ssr: false, -}); - -const InteractiveSpeaking: React.FC = ({ - id, - title, - first_title, - second_title, - examID, - type, - prompts, - userSolutions, - isPractice = false, - registerSolution, - headerButtons, - footerButtons, - preview, -}) => { - const [recordingDuration, setRecordingDuration] = useState(0); - const [isRecording, setIsRecording] = useState(false); - const [mediaBlob, setMediaBlob] = useState(); - const [answers, setAnswers] = useState<{ prompt: string; blob: string; questionIndex: number }[]>([]); - - const examState = useExamStore((state) => state); - const persistentExamState = usePersistentExamStore((state) => state); - - const { questionIndex } = !preview ? examState : persistentExamState; - - useEffect(() => { - setAnswers((prev) => [...prev.filter(x => x.questionIndex !== questionIndex), { - questionIndex: questionIndex, - prompt: prompts[questionIndex].text, - blob: mediaBlob!, - }]); - setMediaBlob(undefined); - }, [answers, mediaBlob, prompts, questionIndex]); - - useEffect(() => { - registerSolution(() => ({ - exercise: id, - solutions: answers, - score: { correct: 100, total: 100, missing: 0 }, - type, - isPractice - })); - }, [id, answers, mediaBlob, type, isPractice, prompts, registerSolution]); - - useEffect(() => { - if (userSolutions.length > 0 && answers.length === 0) { - const solutions = userSolutions as unknown as typeof answers; - setAnswers(solutions); - - if (!mediaBlob) setMediaBlob(solutions.find((x) => x.questionIndex === questionIndex)?.blob); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [userSolutions, mediaBlob, answers]); - - useEffect(() => { - let recordingInterval: NodeJS.Timer | undefined = undefined; - if (isRecording) { - recordingInterval = setInterval(() => setRecordingDuration((prev) => prev + 1), 1000); - } else if (recordingInterval) { - clearInterval(recordingInterval); - } - - return () => { - if (recordingInterval) clearInterval(recordingInterval); - }; - }, [isRecording]); - - return ( -
- {headerButtons} -
-
-
- {!!first_title && !!second_title ? `${first_title} & ${second_title}` : title} -
- {prompts && prompts.length > 0 && ( -
- -
- )} -
- - setMediaBlob(blob)} - render={({ status, startRecording, stopRecording, pauseRecording, resumeRecording, clearBlobUrl, mediaBlobUrl }) => ( -
-

Record your answer:

-
- {status === "idle" && ( - <> -
- {status === "idle" && ( - { - setRecordingDuration(0); - startRecording(); - setIsRecording(true); - }} - className="h-5 w-5 text-mti-gray-cool cursor-pointer" - /> - )} - - )} - {status === "recording" && ( - <> -
- - {Math.floor(recordingDuration / 60) - .toString(10) - .padStart(2, "0")} - : - {Math.floor(recordingDuration % 60) - .toString(10) - .padStart(2, "0")} - -
-
-
- { - setIsRecording(false); - pauseRecording(); - }} - className="text-red-500 w-8 h-8 cursor-pointer" - /> - { - setIsRecording(false); - stopRecording(); - }} - className="text-mti-purple-light w-8 h-8 cursor-pointer" - /> -
- - )} - {status === "paused" && ( - <> -
- - {Math.floor(recordingDuration / 60) - .toString(10) - .padStart(2, "0")} - : - {Math.floor(recordingDuration % 60) - .toString(10) - .padStart(2, "0")} - -
-
-
- { - setIsRecording(true); - resumeRecording(); - }} - className="text-mti-purple-light w-8 h-8 cursor-pointer" - /> - { - setIsRecording(false); - stopRecording(); - }} - className="text-mti-purple-light w-8 h-8 cursor-pointer" - /> -
- - )} - {status === "stopped" && mediaBlobUrl && ( - <> - -
- { - setRecordingDuration(0); - clearBlobUrl(); - setMediaBlob(undefined); - }} - /> - - { - clearBlobUrl(); - setRecordingDuration(0); - startRecording(); - setIsRecording(true); - setMediaBlob(undefined); - }} - className="h-5 w-5 text-mti-gray-cool cursor-pointer" - /> -
- - )} -
-
- )} - /> -
- {footerButtons} -
- ); -} - -export default InteractiveSpeaking; diff --git a/src/components/Exercises/MatchSentences/index.tsx b/src/components/Exercises/MatchSentences/index.tsx index 4e93384d..1e6c51d9 100644 --- a/src/components/Exercises/MatchSentences/index.tsx +++ b/src/components/Exercises/MatchSentences/index.tsx @@ -4,6 +4,7 @@ import { Fragment, useCallback, useEffect, useState } from "react"; import { CommonProps } from "../types"; import { DndContext, DragEndEvent } from "@dnd-kit/core"; import { DraggableOptionArea, DroppableQuestionArea } from "./DragNDrop"; +import PracticeBadge from "../../Low/PracticeBadge"; const MatchSentences: React.FC = ({ id, @@ -61,7 +62,7 @@ const MatchSentences: React.FC = ({ ))} - + {isPractice && }
diff --git a/src/components/Exercises/MultipleChoice/Question.tsx b/src/components/Exercises/MultipleChoice/Question.tsx index ea1eb64a..e13ca07a 100644 --- a/src/components/Exercises/MultipleChoice/Question.tsx +++ b/src/components/Exercises/MultipleChoice/Question.tsx @@ -1,4 +1,5 @@ /* eslint-disable @next/next/no-img-element */ +import PracticeBadge from "@/components/Low/PracticeBadge"; import { MultipleChoiceQuestion } from "@/interfaces/exam"; import clsx from "clsx"; import reactStringReplace from "react-string-replace"; @@ -8,6 +9,7 @@ interface Props { userSolution: string | undefined; onSelectOption?: (option: string) => void; showSolution?: boolean; + isPractice?: boolean } const Question: React.FC = ({ @@ -17,6 +19,7 @@ const Question: React.FC = ({ options, userSolution, onSelectOption, + isPractice, }) => { const renderPrompt = (prompt: string) => { return reactStringReplace(prompt, /(.*?<\/u>)/g, (match) => { @@ -26,11 +29,12 @@ const Question: React.FC = ({ }; return ( -
+
+ {isPractice && } {isNaN(Number(id)) ? ( - {renderPrompt(prompt).filter((x) => x?.toString() !== "")} + {renderPrompt(prompt).filter((x) => x?.toString() !== "")} ) : ( - + <> {id} - {renderPrompt(prompt).filter((x) => x?.toString() !== "")} diff --git a/src/components/Exercises/MultipleChoice/index.tsx b/src/components/Exercises/MultipleChoice/index.tsx index 85a003b9..b0937122 100644 --- a/src/components/Exercises/MultipleChoice/index.tsx +++ b/src/components/Exercises/MultipleChoice/index.tsx @@ -4,6 +4,7 @@ import clsx from "clsx"; import { useCallback, useEffect, useState } from "react"; import { CommonProps } from "../types"; import Question from "./Question"; +import PracticeBadge from "../../Low/PracticeBadge"; const MultipleChoice: React.FC = ({ @@ -81,6 +82,7 @@ const MultipleChoice: React.FC = ({ key={question.id} className="flex flex-col gap-8 h-fit w-full bg-mti-gray-smoke rounded-xl px-16 py-8"> question.id === x.question)?.option} onSelectOption={(option) => onSelectOption(option, question)} /> @@ -93,6 +95,7 @@ const MultipleChoice: React.FC = ({ {questionIndex < questions.length && ( questions[questionIndex].id === x.question)?.option} onSelectOption={(option) => onSelectOption(option, questions[questionIndex])} /> @@ -103,6 +106,7 @@ const MultipleChoice: React.FC = ({
questions[questionIndex + 1].id === x.question)?.option} onSelectOption={(option) => onSelectOption(option, questions[questionIndex + 1])} /> diff --git a/src/components/Exercises/Speaking.tsx b/src/components/Exercises/Speaking.tsx index 45f77906..052af8a4 100644 --- a/src/components/Exercises/Speaking.tsx +++ b/src/components/Exercises/Speaking.tsx @@ -6,6 +6,7 @@ import dynamic from "next/dynamic"; import Button from "../Low/Button"; import Modal from "../Modal"; import useExamStore, { usePersistentExamStore } from "@/stores/exam"; +import PracticeBadge from "../Low/PracticeBadge"; const Waveform = dynamic(() => import("../Waveform"), { ssr: false }); const ReactMediaRecorder = dynamic(() => import("react-media-recorder").then((mod) => mod.ReactMediaRecorder), { @@ -133,6 +134,8 @@ const Speaking: React.FC = ({
+ {isPractice && } + {prompts && prompts.length > 0 && (