diff --git a/package.json b/package.json
index cf8fff68..1c5b0e76 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"react-dom": "18.2.0",
"react-firebase-hooks": "^5.1.1",
"react-lineto": "^3.3.0",
+ "react-media-recorder": "^1.6.6",
"react-player": "^2.12.0",
"react-string-replace": "^1.1.0",
"react-toastify": "^9.1.2",
diff --git a/src/components/Exercises/Writing.tsx b/src/components/Exercises/Writing.tsx
new file mode 100644
index 00000000..ff2fc813
--- /dev/null
+++ b/src/components/Exercises/Writing.tsx
@@ -0,0 +1,83 @@
+import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
+import {WritingExercise} from "@/interfaces/exam";
+import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
+import Icon from "@mdi/react";
+import clsx from "clsx";
+import {CommonProps} from ".";
+import {Fragment, useEffect, useState} from "react";
+import {toast} from "react-toastify";
+
+export default function WriteBlanks({id, prompt, info, wordCounter, onNext, onBack}: WritingExercise & CommonProps) {
+ const [inputText, setInputText] = useState("");
+ const [isSubmitEnabled, setIsSubmitEnabled] = useState(false);
+
+ useEffect(() => {
+ const words = inputText.split(" ").filter((x) => x !== "");
+
+ if (wordCounter.type === "min") {
+ setIsSubmitEnabled(wordCounter.limit <= words.length);
+ } else {
+ setIsSubmitEnabled(true);
+ if (wordCounter.limit < words.length) {
+ toast.warning(`You have reached your word limit of ${wordCounter.limit} words!`, {toastId: "word-limit"});
+ setInputText(words.slice(0, words.length - 1).join(" "));
+ }
+ }
+ }, [inputText, wordCounter]);
+
+ return (
+
+
+ {info}
+
+ {prompt.split("\\n").map((line, index) => (
+
+ {line}
+
+
+ ))}
+
+
+ You should write {wordCounter.type === "min" ? "at least" : "at most"} {wordCounter.limit} words.
+
+
+
+
+ );
+}
diff --git a/src/components/Exercises/index.tsx b/src/components/Exercises/index.tsx
index 6eb15416..fd265bb7 100644
--- a/src/components/Exercises/index.tsx
+++ b/src/components/Exercises/index.tsx
@@ -1,8 +1,17 @@
-import {Exercise, FillBlanksExercise, MatchSentencesExercise, MultipleChoiceExercise, UserSolution, WriteBlanksExercise} from "@/interfaces/exam";
+import {
+ Exercise,
+ FillBlanksExercise,
+ MatchSentencesExercise,
+ MultipleChoiceExercise,
+ UserSolution,
+ WriteBlanksExercise,
+ WritingExercise,
+} from "@/interfaces/exam";
import dynamic from "next/dynamic";
import FillBlanks from "./FillBlanks";
import MultipleChoice from "./MultipleChoice";
import WriteBlanks from "./WriteBlanks";
+import Writing from "./Writing";
const MatchSentences = dynamic(() => import("@/components/Exercises/MatchSentences"), {ssr: false});
@@ -21,5 +30,7 @@ export const renderExercise = (exercise: Exercise, onNext: (userSolutions: UserS
return ;
case "writeBlanks":
return ;
+ case "writing":
+ return ;
}
};
diff --git a/src/exams/Listening.tsx b/src/exams/Listening.tsx
index 3e10030c..b80b5c0f 100644
--- a/src/exams/Listening.tsx
+++ b/src/exams/Listening.tsx
@@ -39,9 +39,9 @@ export default function Listening({exam, showSolutions = false, onFinish}: Props
}
if (solution) {
- onFinish([...userSolutions.filter((x) => x.id !== solution.id), solution].map((x) => ({...x, module: "listening"})));
+ onFinish([...userSolutions.filter((x) => x.id !== solution.id), solution].map((x) => ({...x, module: "listening", exam: exam.id})));
} else {
- onFinish(userSolutions.map((x) => ({...x, module: "listening"})));
+ onFinish(userSolutions.map((x) => ({...x, module: "listening", exam: exam.id})));
}
};
diff --git a/src/exams/Reading.tsx b/src/exams/Reading.tsx
index f2cf6270..34721926 100644
--- a/src/exams/Reading.tsx
+++ b/src/exams/Reading.tsx
@@ -99,9 +99,9 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
}
if (solution) {
- onFinish([...userSolutions.filter((x) => x.id !== solution.id), solution].map((x) => ({...x, module: "reading"})));
+ onFinish([...userSolutions.filter((x) => x.id !== solution.id), solution].map((x) => ({...x, module: "reading", exam: exam.id})));
} else {
- onFinish(userSolutions.map((x) => ({...x, module: "reading"})));
+ onFinish(userSolutions.map((x) => ({...x, module: "reading", exam: exam.id})));
}
};
diff --git a/src/exams/Writing.tsx b/src/exams/Writing.tsx
index 6b6cbde2..5d35a12a 100644
--- a/src/exams/Writing.tsx
+++ b/src/exams/Writing.tsx
@@ -1,5 +1,9 @@
+import {renderExercise} from "@/components/Exercises";
+import {renderSolution} from "@/components/Solutions";
import {infoButtonStyle} from "@/constants/buttonStyles";
import {UserSolution, WritingExam} from "@/interfaces/exam";
+import {mdiArrowRight} from "@mdi/js";
+import Icon from "@mdi/react";
import clsx from "clsx";
import {Fragment, useEffect, useState} from "react";
import {toast} from "react-toastify";
@@ -11,9 +15,9 @@ interface Props {
}
export default function Writing({exam, showSolutions = false, onFinish}: Props) {
- const [inputText, setInputText] = useState("");
- const [isSubmitEnabled, setIsSubmitEnabled] = useState(false);
+ const [exerciseIndex, setExerciseIndex] = useState(0);
const [timer, setTimer] = useState();
+ const [userSolutions, setUserSolutions] = useState([]);
useEffect(() => {
setTimer(exam.minTimer * 60);
@@ -24,22 +28,29 @@ export default function Writing({exam, showSolutions = false, onFinish}: Props)
};
}, [exam.minTimer]);
- useEffect(() => {
- const words = inputText.split(" ").filter((x) => x !== "");
- const {wordCounter} = exam.text;
- if (wordCounter.type === "min") {
- setIsSubmitEnabled(wordCounter.limit <= words.length);
- } else {
- setIsSubmitEnabled(true);
- if (wordCounter.limit < words.length) {
- toast.warning(`You have reached your word limit of ${wordCounter.limit} words!`, {toastId: "word-limit"});
- setInputText(words.slice(0, words.length - 1).join(" "));
- }
+ const nextExercise = (solution?: UserSolution) => {
+ if (solution) {
+ setUserSolutions((prev) => [...prev.filter((x) => x.id !== solution.id), solution]);
}
- }, [inputText, exam]);
+
+ if (exerciseIndex + 1 < exam.exercises.length) {
+ setExerciseIndex((prev) => prev + 1);
+ return;
+ }
+
+ if (solution) {
+ onFinish([...userSolutions.filter((x) => x.id !== solution.id), solution].map((x) => ({...x, module: "writing", exam: exam.id})));
+ } else {
+ onFinish(userSolutions.map((x) => ({...x, module: "writing", exam: exam.id})));
+ }
+ };
+
+ const previousExercise = () => {
+ setExerciseIndex((prev) => prev - 1);
+ };
return (
-
+
{timer && (
{Math.floor(timer / 60) < 10 ? "0" : ""}
@@ -47,46 +58,22 @@ export default function Writing({exam, showSolutions = false, onFinish}: Props)
{timer % 60}
)}
-
- {exam.text.info}
-
- {exam.text.prompt.split("\\n").map((line, index) => (
-
- {line}
-
-
- ))}
-
-
- You should write {exam.text.wordCounter.type === "min" ? "at least" : "at most"} {exam.text.wordCounter.limit} words.
-
-
-