diff --git a/package.json b/package.json
index 6ae3f361..512991e0 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"react-lineto": "^3.3.0",
"react-player": "^2.12.0",
"react-string-replace": "^1.1.0",
+ "react-toastify": "^9.1.2",
"typescript": "4.9.5",
"zustand": "^4.3.6"
},
diff --git a/src/demo/writing.json b/src/demo/writing.json
new file mode 100644
index 00000000..ad980986
--- /dev/null
+++ b/src/demo/writing.json
@@ -0,0 +1,11 @@
+{
+ "module": "writing",
+ "text": {
+ "info": "You should spend about 20 minutes on this task.",
+ "prompt": "The charts below show the results of a survey of adult education. The first chart shows the reasons why adults decide to study. The pie chart shows how people think the costs of adult education should be shared.\nWrite a report for a university lecturer, describing the information shown below.",
+ "wordCounter": {
+ "type": "min",
+ "limit": 5
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/exams/Writing.tsx b/src/exams/Writing.tsx
new file mode 100644
index 00000000..e4631268
--- /dev/null
+++ b/src/exams/Writing.tsx
@@ -0,0 +1,67 @@
+import {infoButtonStyle} from "@/constants/buttonStyles";
+import {WritingExam} from "@/interfaces/exam";
+import clsx from "clsx";
+import {Fragment, useEffect, useState} from "react";
+import {toast} from "react-toastify";
+
+interface Props {
+ exam: WritingExam;
+}
+
+export default function Writing({exam}: Props) {
+ const [inputText, setInputText] = useState("");
+ const [isSubmitEnabled, setIsSubmitEnabled] = useState(false);
+
+ 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!`);
+ setInputText(words.slice(0, words.length - 1).join(" "));
+ }
+ }
+ }, [inputText, exam]);
+
+ return (
+
+
+ {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.
+
+
+
+ );
+}
diff --git a/src/interfaces/exam.ts b/src/interfaces/exam.ts
index 3d1cede8..4d58b0ad 100644
--- a/src/interfaces/exam.ts
+++ b/src/interfaces/exam.ts
@@ -1,4 +1,4 @@
-export type Exam = ReadingExam | ListeningExam;
+export type Exam = ReadingExam | ListeningExam | WritingExam;
export interface ReadingExam {
text: {
@@ -20,6 +20,20 @@ export interface ListeningExam {
module: "listening";
}
+export interface WritingExam {
+ module: "writing";
+ text: {
+ info: string; //* The information about the task, like the amount of time they should spend on it
+ prompt: string; //* The context given to the user containing what they should write about
+ wordCounter: WordCounter; //* The minimum or maximum amount of words that should be written
+ };
+}
+
+interface WordCounter {
+ type: "min" | "max";
+ limit: number;
+}
+
export type Exercise = FillBlanksExercise | MatchSentencesExercise | MultipleChoiceExercise | WriteBlanksExercise;
export interface FillBlanksExercise {
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 021681f4..196e3d6b 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -1,6 +1,7 @@
-import '@/styles/globals.css'
-import type { AppProps } from 'next/app'
+import "@/styles/globals.css";
+import "react-toastify/dist/ReactToastify.css";
+import type {AppProps} from "next/app";
-export default function App({ Component, pageProps }: AppProps) {
- return
+export default function App({Component, pageProps}: AppProps) {
+ return ;
}
diff --git a/src/pages/exam/index.tsx b/src/pages/exam/index.tsx
index 41e0c0fa..09d77de6 100644
--- a/src/pages/exam/index.tsx
+++ b/src/pages/exam/index.tsx
@@ -8,11 +8,14 @@ import {Module} from "@/interfaces";
import JSON_USER from "@/demo/user.json";
import JSON_READING from "@/demo/reading.json";
import JSON_LISTENING from "@/demo/listening.json";
+import JSON_WRITING from "@/demo/writing.json";
import Selection from "@/exams/Selection";
import Reading from "@/exams/Reading";
-import {Exam, ListeningExam, ReadingExam} from "@/interfaces/exam";
+import {Exam, ListeningExam, ReadingExam, WritingExam} from "@/interfaces/exam";
import Listening from "@/exams/Listening";
+import Writing from "@/exams/Writing";
+import {ToastContainer} from "react-toastify";
export default function Home() {
const [selectedModules, setSelectedModules] = useState([]);
@@ -31,6 +34,8 @@ export default function Home() {
return JSON_READING as ReadingExam;
case "listening":
return JSON_LISTENING as ListeningExam;
+ case "writing":
+ return JSON_WRITING as WritingExam;
}
return undefined;
@@ -53,6 +58,10 @@ export default function Home() {
return setModuleIndex((prev) => prev + 1)} />;
}
+ if (exam && exam.module === "writing") {
+ return ;
+ }
+
return <>Loading...>;
};
@@ -65,6 +74,7 @@ export default function Home() {
+
{renderScreen()}
diff --git a/yarn.lock b/yarn.lock
index 523ee7eb..904c1e61 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -594,7 +594,7 @@ client-only@0.0.1, client-only@^0.0.1:
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
-clsx@^1.2.1:
+clsx@^1.1.1, clsx@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
@@ -2129,6 +2129,13 @@ react-string-replace@^1.1.0:
resolved "https://registry.yarnpkg.com/react-string-replace/-/react-string-replace-1.1.0.tgz#a3f7b458e697e77d70b0ea663caf38ab38f7cc17"
integrity sha512-N6RalSDFGbOHs0IJi1H611WbZsvk3ZT47Jl2JEXFbiS3kTwsdCYij70Keo/tWtLy7sfhDsYm7CwNM/WmjXIaMw==
+react-toastify@^9.1.2:
+ version "9.1.2"
+ resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-9.1.2.tgz#293aa1f952240129fe485ae5cb2f8d09c652cf3f"
+ integrity sha512-PBfzXO5jMGEtdYR5jxrORlNZZe/EuOkwvwKijMatsZZm8IZwLj01YvobeJYNjFcA6uy6CVrx2fzL9GWbhWPTDA==
+ dependencies:
+ clsx "^1.1.1"
+
react@17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"