diff --git a/src/components/Low/Input.tsx b/src/components/Low/Input.tsx
index bd1fb466..4d513835 100644
--- a/src/components/Low/Input.tsx
+++ b/src/components/Low/Input.tsx
@@ -3,6 +3,7 @@ import {useState} from "react";
interface Props {
type: "email" | "text" | "password" | "tel" | "number";
+ roundness?: "full" | "xl";
required?: boolean;
label?: string;
placeholder?: string;
@@ -13,7 +14,18 @@ interface Props {
onChange: (value: string) => void;
}
-export default function Input({type, label, placeholder, name, required = false, defaultValue, className, disabled = false, onChange}: Props) {
+export default function Input({
+ type,
+ label,
+ placeholder,
+ name,
+ required = false,
+ defaultValue,
+ className,
+ roundness = "full",
+ disabled = false,
+ onChange,
+}: Props) {
const [showPassword, setShowPassword] = useState(false);
if (type === "password") {
@@ -59,7 +71,11 @@ export default function Input({type, label, placeholder, name, required = false,
disabled={disabled}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
- className="px-8 py-6 text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed bg-white rounded-full border border-mti-gray-platinum focus:outline-none"
+ className={clsx(
+ "px-8 py-6 text-sm font-normal bg-white border border-mti-gray-platinum focus:outline-none",
+ "placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed",
+ roundness === "full" ? "rounded-full" : "rounded-xl",
+ )}
required={required}
defaultValue={defaultValue}
/>
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index 77c419b1..83144a56 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -1,7 +1,7 @@
import clsx from "clsx";
import {IconType} from "react-icons";
import {MdSpaceDashboard} from "react-icons/md";
-import {BsFileEarmarkText, BsClockHistory, BsPencil, BsGraphUp, BsChevronBarRight, BsChevronBarLeft, BsShieldFill} from "react-icons/bs";
+import {BsFileEarmarkText, BsClockHistory, BsPencil, BsGraphUp, BsChevronBarRight, BsChevronBarLeft, BsShieldFill, BsCloudFill} from "react-icons/bs";
import {RiLogoutBoxFill} from "react-icons/ri";
import {SlPencil} from "react-icons/sl";
import {FaAward} from "react-icons/fa";
@@ -92,6 +92,16 @@ export default function Sidebar({path, navDisabled = false, focusMode = false, u
{userType !== "student" && (
)}
+ {userType === "developer" && (
+
+ )}
@@ -102,6 +112,9 @@ export default function Sidebar({path, navDisabled = false, focusMode = false, u
{userType !== "student" && (
)}
+ {userType === "developer" && (
+
+ )}
diff --git a/src/interfaces/exam.ts b/src/interfaces/exam.ts
index a33b19cd..bcc83034 100644
--- a/src/interfaces/exam.ts
+++ b/src/interfaces/exam.ts
@@ -3,13 +3,7 @@ import {Module} from ".";
export type Exam = ReadingExam | ListeningExam | WritingExam | SpeakingExam | LevelExam;
export interface ReadingExam {
- parts: {
- text: {
- title: string;
- content: string;
- };
- exercises: Exercise[];
- }[];
+ parts: ReadingPart[];
id: string;
module: "reading";
minTimer: number;
@@ -17,6 +11,14 @@ export interface ReadingExam {
isDiagnostic: boolean;
}
+export interface ReadingPart {
+ text: {
+ title: string;
+ content: string;
+ };
+ exercises: Exercise[];
+}
+
export interface LevelExam {
module: "level";
id: string;
diff --git a/src/pages/generation.tsx b/src/pages/generation.tsx
new file mode 100644
index 00000000..9c19ff6b
--- /dev/null
+++ b/src/pages/generation.tsx
@@ -0,0 +1,286 @@
+/* eslint-disable @next/next/no-img-element */
+import Head from "next/head";
+import {withIronSessionSsr} from "iron-session/next";
+import {sessionOptions} from "@/lib/session";
+import useUser from "@/hooks/useUser";
+import {ToastContainer} from "react-toastify";
+import Layout from "@/components/High/Layout";
+import {shouldRedirectHome} from "@/utils/navigation.disabled";
+import {useState} from "react";
+import {Module} from "@/interfaces";
+import {RadioGroup, Tab} from "@headlessui/react";
+import clsx from "clsx";
+import {MODULE_ARRAY} from "@/utils/moduleUtils";
+import {capitalize} from "lodash";
+import Button from "@/components/Low/Button";
+import {Exercise, ReadingPart} from "@/interfaces/exam";
+import Input from "@/components/Low/Input";
+
+export const getServerSideProps = withIronSessionSsr(({req, res}) => {
+ const user = req.session.user;
+
+ if (!user || !user.isVerified) {
+ res.setHeader("location", "/login");
+ res.statusCode = 302;
+ res.end();
+ return {
+ props: {
+ user: null,
+ },
+ };
+ }
+
+ if (shouldRedirectHome(user) || user.type !== "developer") {
+ res.setHeader("location", "/");
+ res.statusCode = 302;
+ res.end();
+ return {
+ props: {
+ user: null,
+ },
+ };
+ }
+
+ return {
+ props: {user: req.session.user},
+ };
+}, sessionOptions);
+
+export default function Generation() {
+ const [module, setModule] = useState
("reading");
+
+ const {user} = useUser({redirectTo: "/login"});
+
+ const ReadingGeneration = () => {
+ const [part1, setPart1] = useState();
+ const [part2, setPart2] = useState();
+ const [part3, setPart3] = useState();
+ const [types, setTypes] = useState([]);
+
+ const availableTypes = [
+ {type: "fillBlanks", label: "Fill the Blanks"},
+ {type: "multipleChoice", label: "Multiple Choice"},
+ {type: "trueFalse", label: "True or False"},
+ {type: "writeBlanks", label: "Write the Blanks"},
+ ];
+
+ const toggleType = (type: string) => setTypes((prev) => (prev.includes(type) ? [...prev.filter((x) => x !== type)] : [...prev, type]));
+
+ return (
+ <>
+
+
+
+ {availableTypes.map((x) => (
+ toggleType(x.type)}
+ key={x.type}
+ className={clsx(
+ "px-6 py-4 w-64 flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
+ "transition duration-300 ease-in-out",
+ !types.includes(x.type)
+ ? "bg-white border-mti-gray-platinum"
+ : "bg-ielts-reading/70 border-ielts-reading text-white",
+ )}>
+ {x.label}
+
+ ))}
+
+
+
+
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading focus:outline-none focus:ring-2",
+ "transition duration-300 ease-in-out",
+ selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-reading",
+ )
+ }>
+ Passage 1
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading focus:outline-none focus:ring-2",
+ "transition duration-300 ease-in-out",
+ selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-reading",
+ )
+ }>
+ Passage 2
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading focus:outline-none focus:ring-2",
+ "transition duration-300 ease-in-out",
+ selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-reading",
+ )
+ }>
+ Passage 3
+
+
+
+ {[part1, part2, part3].map((part, index) => (
+
+
+ {}} roundness="xl" />
+
+
+
+ ))}
+
+
+ >
+ );
+ };
+
+ const ListeningGeneration = () => {
+ const [types, setTypes] = useState([]);
+ const availableTypes = [
+ {type: "fillBlanks", label: "Fill the Blanks"},
+ {type: "multipleChoice", label: "Multiple Choice"},
+ {type: "trueFalse", label: "True or False"},
+ {type: "writeBlanks", label: "Write the Blanks"},
+ ];
+
+ const toggleType = (type: string) => setTypes((prev) => (prev.includes(type) ? [...prev.filter((x) => x !== type)] : [...prev, type]));
+
+ return (
+ <>
+
+
+
+ {availableTypes.map((x) => (
+ toggleType(x.type)}
+ key={x.type}
+ className={clsx(
+ "px-6 py-4 w-64 flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
+ "transition duration-300 ease-in-out",
+ !types.includes(x.type)
+ ? "bg-white border-mti-gray-platinum"
+ : "bg-ielts-listening/70 border-ielts-listening text-white",
+ )}>
+ {x.label}
+
+ ))}
+
+
+
+
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening focus:outline-none focus:ring-2",
+ "transition duration-300 ease-in-out",
+ selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-listening",
+ )
+ }>
+ Passage 1
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening focus:outline-none focus:ring-2",
+ "transition duration-300 ease-in-out",
+ selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-listening",
+ )
+ }>
+ Passage 2
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening focus:outline-none focus:ring-2",
+ "transition duration-300 ease-in-out",
+ selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-listening",
+ )
+ }>
+ Passage 3
+
+
+
+ >
+ );
+ };
+
+ return (
+ <>
+
+ Exam Generation | EnCoach
+
+
+
+
+
+ {user && (
+
+ Exam Generation
+
+
+
+ {[...MODULE_ARRAY, "level"].map((x) => (
+
+ {({checked}) => (
+
+ {capitalize(x)}
+
+ )}
+
+ ))}
+
+
+ {module === "reading" && }
+ {module === "listening" && }
+
+ )}
+ >
+ );
+}