Updated the use of the Desired Levels to be configurable

This commit is contained in:
Tiago Ribeiro
2024-02-03 14:46:35 +00:00
parent 846d829d10
commit 0053105dd3
6 changed files with 660 additions and 830 deletions

View File

@@ -14,6 +14,7 @@ import {useEffect, useState} from "react";
import {BsBook, BsChevronDown, BsHeadphones, BsMegaphone, BsPen, BsQuestionSquare} from "react-icons/bs"; import {BsBook, BsChevronDown, BsHeadphones, BsMegaphone, BsPen, BsQuestionSquare} from "react-icons/bs";
import {toast} from "react-toastify"; import {toast} from "react-toastify";
import Button from "./Low/Button"; import Button from "./Low/Button";
import ModuleLevelSelector from "./Medium/ModuleLevelSelector";
interface Props { interface Props {
user: User; user: User;
@@ -90,140 +91,44 @@ export default function Diagnostic({onFinish}: Props) {
</div> </div>
</div> </div>
</div> </div>
<div className="flex flex-col items-center justify-center gap-8 w-full"> <div className="flex flex-col items-center justify-center gap-8 w-full">
<h2 className="font-semibold text-xl">What is your current IELTS level?</h2> <h2 className="font-semibold text-xl">What is your current IELTS level?</h2>
<div className="flex flex-col gap-32 w-full mb-20"> <ModuleLevelSelector levels={levels} setLevels={setLevels} />
<div className="grid grid-cols-1 md:grid-cols-2 gap-y-4 gap-x-16 mb-24"> </div>
<div className="w-full flex flex-col gap-3.5 relative">
<span className="text-sm text-mti-gray-dim"> <div className="flex flex-col items-center justify-center gap-8 w-full mb-44">
<span className="font-bold">Reading</span> level <h2 className="font-semibold text-xl">What is your desired IELTS level?</h2>
</span> <ModuleLevelSelector levels={desiredLevels} setLevels={setDesiredLevels} />
<Menu> </div>
<Menu.Button className="w-full border border-mti-gray-platinum rounded-full px-6 py-4 flex justify-between items-center gap-12 bg-white">
<BsBook className="text-ielts-reading" size={34} /> <div className="md:self-end flex -md:flex-col justify-between w-full gap-8 absolute bottom-8 left-0 px-4 md:px-8">
<span className="text-mti-gray-cool text-sm"> <div className="w-full tooltip" data-tip="Your screen size is too small to perform a diagnostic test">
{levels.reading === -1 ? "Select your reading level" : `Level ${levels.reading}`} <Button
</span> color="purple"
<BsChevronDown className="text-mti-gray-cool" size={12} /> variant="outline"
</Menu.Button> className="group flex items-center justify-center gap-6 relative md:max-w-[400px] w-full md:hidden"
<Menu.Items className="absolute overflow-y-scroll scrollbar-hide max-h-[230px] origin-top top-full bg-white flex flex-col items-center w-full z-20 drop-shadow-lg rounded-2xl"> disabled>
{Object.values(writingMarking).map((x) => ( <BsQuestionSquare className="text-mti-purple-light transition duration-300 ease-in-out" size={20} />
<Menu.Item key={x}> <span>Perform diagnostic test instead</span>
<span </Button>
onClick={() => setLevels((prev) => ({...prev, reading: x}))}
className="w-full py-4 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300">
Level {x}
</span>
</Menu.Item>
))}
</Menu.Items>
</Menu>
</div>
<div className="w-full flex flex-col gap-3.5 relative">
<span className="text-sm text-mti-gray-dim">
<span className="font-bold">Listening</span> level
</span>
<Menu>
<Menu.Button className="w-full border border-mti-gray-platinum rounded-full px-6 py-4 flex justify-between items-center gap-12 bg-white">
<BsHeadphones className="text-ielts-listening" size={34} />
<span className="text-mti-gray-cool text-sm">
{levels.listening === -1 ? "Select your listening level" : `Level ${levels.listening}`}
</span>
<BsChevronDown className="text-mti-gray-cool" size={12} />
</Menu.Button>
<Menu.Items className="absolute overflow-y-scroll scrollbar-hide max-h-[230px] origin-top top-full bg-white flex flex-col items-center w-full z-20 drop-shadow-lg rounded-2xl">
{Object.values(writingMarking).map((x) => (
<Menu.Item key={x}>
<span
onClick={() => setLevels((prev) => ({...prev, listening: x}))}
className="w-full py-5 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300">
Level {x}
</span>
</Menu.Item>
))}
</Menu.Items>
</Menu>
</div>
<div className="w-full flex flex-col gap-3.5 relative">
<span className="text-sm text-mti-gray-dim">
<span className="font-bold">Writing</span> level
</span>
<Menu>
<Menu.Button className="w-full border border-mti-gray-platinum rounded-full px-6 py-4 flex justify-between items-center gap-12 bg-white">
<BsPen className="text-ielts-writing" size={34} />
<span className="text-mti-gray-cool text-sm">
{levels.writing === -1 ? "Select your writing level" : `Level ${levels.writing}`}
</span>
<BsChevronDown className="text-mti-gray-cool" size={12} />
</Menu.Button>
<Menu.Items className="absolute overflow-y-scroll scrollbar-hide max-h-[230px] origin-top top-full bg-white flex flex-col items-center w-full z-20 drop-shadow-lg rounded-2xl">
{Object.values(writingMarking).map((x) => (
<Menu.Item key={x}>
<span
onClick={() => setLevels((prev) => ({...prev, writing: x}))}
className="w-full py-5 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300">
Level {x}
</span>
</Menu.Item>
))}
</Menu.Items>
</Menu>
</div>
<div className="w-full flex flex-col gap-3.5 relative">
<span className="text-sm text-mti-gray-dim">
<span className="font-bold">Speaking</span> level
</span>
<Menu>
<Menu.Button className="w-full border border-mti-gray-platinum rounded-full px-6 py-4 flex justify-between items-center gap-12 bg-white">
<BsMegaphone className="text-ielts-speaking" size={34} />
<span className="text-mti-gray-cool text-sm">
{levels.speaking === -1 ? "Select your speaking level" : `Level ${levels.speaking}`}
</span>
<BsChevronDown className="text-mti-gray-cool" size={12} />
</Menu.Button>
<Menu.Items className="absolute overflow-y-scroll scrollbar-hide max-h-[230px] origin-top top-full bg-white flex flex-col items-center w-full z-20 drop-shadow-lg rounded-2xl">
{Object.values(writingMarking).map((x) => (
<Menu.Item key={x}>
<span
onClick={() => setLevels((prev) => ({...prev, speaking: x}))}
className="w-full py-5 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300">
Level {x}
</span>
</Menu.Item>
))}
</Menu.Items>
</Menu>
</div>
</div>
<div className="md:self-end flex -md:flex-col justify-between w-full gap-8 absolute bottom-8 left-0 px-4 md:px-8">
<div className="w-full tooltip" data-tip="Your screen size is too small to perform a diagnostic test">
<Button
color="purple"
variant="outline"
className="group flex items-center justify-center gap-6 relative md:max-w-[400px] w-full md:hidden"
disabled>
<BsQuestionSquare className="text-mti-purple-light transition duration-300 ease-in-out" size={20} />
<span>Perform diagnostic test instead</span>
</Button>
</div>
<Button
onClick={() => updateUser(selectExam)}
color="purple"
variant="outline"
className="group flex items-center justify-center gap-6 relative md:max-w-[400px] w-full -md:hidden"
disabled={!focus}>
<BsQuestionSquare
className="text-mti-purple-light group-hover:text-white transition duration-300 ease-in-out"
size={20}
onClick={() => updateUser(selectExam)}
/>
<span onClick={() => updateUser(selectExam)}>Perform diagnostic test instead</span>
</Button>
<Button color="purple" className="md:max-w-[400px] w-full" onClick={() => updateUser(onFinish)} disabled={isNextDisabled()}>
Next Step
</Button>
</div>
</div> </div>
<Button
onClick={() => updateUser(selectExam)}
color="purple"
variant="outline"
className="group flex items-center justify-center gap-6 relative md:max-w-[400px] w-full -md:hidden"
disabled={!focus}>
<BsQuestionSquare
className="text-mti-purple-light group-hover:text-white transition duration-300 ease-in-out"
size={20}
onClick={() => updateUser(selectExam)}
/>
<span onClick={() => updateUser(selectExam)}>Perform diagnostic test instead</span>
</Button>
<Button color="purple" className="md:max-w-[400px] w-full" onClick={() => updateUser(onFinish)} disabled={isNextDisabled()}>
Next Step
</Button>
</div> </div>
</div> </div>
); );

View File

@@ -5,12 +5,14 @@ interface Props {
label: string; label: string;
percentage: number; percentage: number;
color: "red" | "rose" | "purple" | Module; color: "red" | "rose" | "purple" | Module;
mark?: number;
markLabel?: string;
useColor?: boolean; useColor?: boolean;
className?: string; className?: string;
textClassName?: string; textClassName?: string;
} }
export default function ProgressBar({label, percentage, color, useColor = false, className, textClassName}: Props) { export default function ProgressBar({label, percentage, color, mark, markLabel, useColor = false, className, textClassName}: Props) {
const progressColorClass: {[key in typeof color]: string} = { const progressColorClass: {[key in typeof color]: string} = {
red: "bg-mti-red-light", red: "bg-mti-red-light",
rose: "bg-mti-rose-light", rose: "bg-mti-rose-light",
@@ -30,6 +32,9 @@ export default function ProgressBar({label, percentage, color, useColor = false,
!useColor ? "bg-mti-gray-anti-flash" : progressColorClass[color], !useColor ? "bg-mti-gray-anti-flash" : progressColorClass[color],
useColor && "bg-opacity-20", useColor && "bg-opacity-20",
)}> )}>
{mark && (
<div style={{left: `${mark}%`}} className={clsx("w-3 h-2 bg-mti-gray-davy/60 absolute -translate-x-1/2 top-0 z-20 cursor-pointer")} />
)}
<div <div
style={{width: `${percentage}%`}} style={{width: `${percentage}%`}}
className={clsx("absolute transition-all duration-300 ease-in-out top-0 left-0 h-full overflow-hidden", progressColorClass[color])} className={clsx("absolute transition-all duration-300 ease-in-out top-0 left-0 h-full overflow-hidden", progressColorClass[color])}

View File

@@ -0,0 +1,121 @@
import {Module} from "@/interfaces";
import {writingMarking} from "@/utils/score";
import {Menu} from "@headlessui/react";
import {Dispatch, SetStateAction} from "react";
import {BsBook, BsChevronDown, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs";
type Levels = {[key in Module]: number};
interface Props {
levels: Levels;
setLevels: Dispatch<SetStateAction<Levels>>;
}
export default function ModuleLevelSelector({levels, setLevels}: Props) {
return (
<div className="flex flex-col gap-32 w-full">
<div className="grid grid-cols-1 md:grid-cols-2 gap-y-4 gap-x-16">
<div className="w-full flex flex-col gap-3.5 relative">
<span className="text-sm text-mti-gray-dim">
<span className="font-bold">Reading</span> level
</span>
<Menu>
<Menu.Button className="w-full border border-mti-gray-platinum rounded-full px-6 py-4 flex justify-between items-center gap-12 bg-white">
<BsBook className="text-ielts-reading" size={34} />
<span className="text-mti-gray-cool text-sm">
{levels.reading === -1 ? "Select your reading level" : `Level ${levels.reading}`}
</span>
<BsChevronDown className="text-mti-gray-cool" size={12} />
</Menu.Button>
<Menu.Items className="absolute overflow-y-scroll scrollbar-hide max-h-[230px] origin-top top-full bg-white flex flex-col items-center w-full z-20 drop-shadow-lg rounded-2xl">
{Object.values(writingMarking).map((x) => (
<Menu.Item key={x}>
<span
onClick={() => setLevels((prev) => ({...prev, reading: x}))}
className="w-full py-4 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300">
Level {x}
</span>
</Menu.Item>
))}
</Menu.Items>
</Menu>
</div>
<div className="w-full flex flex-col gap-3.5 relative">
<span className="text-sm text-mti-gray-dim">
<span className="font-bold">Listening</span> level
</span>
<Menu>
<Menu.Button className="w-full border border-mti-gray-platinum rounded-full px-6 py-4 flex justify-between items-center gap-12 bg-white">
<BsHeadphones className="text-ielts-listening" size={34} />
<span className="text-mti-gray-cool text-sm">
{levels.listening === -1 ? "Select your listening level" : `Level ${levels.listening}`}
</span>
<BsChevronDown className="text-mti-gray-cool" size={12} />
</Menu.Button>
<Menu.Items className="absolute overflow-y-scroll scrollbar-hide max-h-[230px] origin-top top-full bg-white flex flex-col items-center w-full z-50 drop-shadow-lg rounded-2xl">
{Object.values(writingMarking).map((x) => (
<Menu.Item key={x}>
<span
onClick={() => setLevels((prev) => ({...prev, listening: x}))}
className="w-full py-5 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300">
Level {x}
</span>
</Menu.Item>
))}
</Menu.Items>
</Menu>
</div>
<div className="w-full flex flex-col gap-3.5 relative">
<span className="text-sm text-mti-gray-dim">
<span className="font-bold">Writing</span> level
</span>
<Menu>
<Menu.Button className="w-full border border-mti-gray-platinum rounded-full px-6 py-4 flex justify-between items-center gap-12 bg-white">
<BsPen className="text-ielts-writing" size={34} />
<span className="text-mti-gray-cool text-sm">
{levels.writing === -1 ? "Select your writing level" : `Level ${levels.writing}`}
</span>
<BsChevronDown className="text-mti-gray-cool" size={12} />
</Menu.Button>
<Menu.Items className="absolute overflow-y-scroll scrollbar-hide max-h-[230px] origin-top top-full bg-white flex flex-col items-center w-full z-20 drop-shadow-lg rounded-2xl">
{Object.values(writingMarking).map((x) => (
<Menu.Item key={x}>
<span
onClick={() => setLevels((prev) => ({...prev, writing: x}))}
className="w-full py-5 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300">
Level {x}
</span>
</Menu.Item>
))}
</Menu.Items>
</Menu>
</div>
<div className="w-full flex flex-col gap-3.5 relative">
<span className="text-sm text-mti-gray-dim">
<span className="font-bold">Speaking</span> level
</span>
<Menu>
<Menu.Button className="w-full border border-mti-gray-platinum rounded-full px-6 py-4 flex justify-between items-center gap-12 bg-white">
<BsMegaphone className="text-ielts-speaking" size={34} />
<span className="text-mti-gray-cool text-sm">
{levels.speaking === -1 ? "Select your speaking level" : `Level ${levels.speaking}`}
</span>
<BsChevronDown className="text-mti-gray-cool" size={12} />
</Menu.Button>
<Menu.Items className="absolute overflow-y-scroll scrollbar-hide max-h-[230px] origin-top top-full bg-white flex flex-col items-center w-full z-20 drop-shadow-lg rounded-2xl">
{Object.values(writingMarking).map((x) => (
<Menu.Item key={x}>
<span
onClick={() => setLevels((prev) => ({...prev, speaking: x}))}
className="w-full py-5 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300">
Level {x}
</span>
</Menu.Item>
))}
</Menu.Items>
</Menu>
</div>
</div>
</div>
);
}

View File

@@ -7,352 +7,254 @@ import useAssignments from "@/hooks/useAssignments";
import useInvites from "@/hooks/useInvites"; import useInvites from "@/hooks/useInvites";
import useStats from "@/hooks/useStats"; import useStats from "@/hooks/useStats";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import { Invite } from "@/interfaces/invite"; import {Invite} from "@/interfaces/invite";
import { Assignment } from "@/interfaces/results"; import {Assignment} from "@/interfaces/results";
import { CorporateUser, User } from "@/interfaces/user"; import {CorporateUser, User} from "@/interfaces/user";
import useExamStore from "@/stores/examStore"; import useExamStore from "@/stores/examStore";
import { getExamById } from "@/utils/exams"; import {getExamById} from "@/utils/exams";
import { getUserCorporate } from "@/utils/groups"; import {getUserCorporate} from "@/utils/groups";
import { import {MODULE_ARRAY, sortByModule, sortByModuleName} from "@/utils/moduleUtils";
MODULE_ARRAY, import {averageScore, groupBySession} from "@/utils/stats";
sortByModule, import {CreateOrderActions, CreateOrderData, OnApproveActions, OnApproveData, OrderResponseBody} from "@paypal/paypal-js";
sortByModuleName, import {PayPalButtons} from "@paypal/react-paypal-js";
} from "@/utils/moduleUtils";
import { averageScore, groupBySession } from "@/utils/stats";
import {
CreateOrderActions,
CreateOrderData,
OnApproveActions,
OnApproveData,
OrderResponseBody,
} from "@paypal/paypal-js";
import { PayPalButtons } from "@paypal/react-paypal-js";
import axios from "axios"; import axios from "axios";
import clsx from "clsx"; import clsx from "clsx";
import { capitalize } from "lodash"; import {capitalize} from "lodash";
import moment from "moment"; import moment from "moment";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import {useRouter} from "next/router";
import { useEffect, useState } from "react"; import {useEffect, useState} from "react";
import { import {BsArrowRepeat, BsBook, BsClipboard, BsFileEarmarkText, BsHeadphones, BsMegaphone, BsPen, BsPencil, BsStar} from "react-icons/bs";
BsArrowRepeat, import {toast} from "react-toastify";
BsBook,
BsClipboard,
BsFileEarmarkText,
BsHeadphones,
BsMegaphone,
BsPen,
BsPencil,
BsStar,
} from "react-icons/bs";
import { toast } from "react-toastify";
interface Props { interface Props {
user: User; user: User;
} }
export default function StudentDashboard({ user }: Props) { export default function StudentDashboard({user}: Props) {
const [corporateUserToShow, setCorporateUserToShow] = const [corporateUserToShow, setCorporateUserToShow] = useState<CorporateUser>();
useState<CorporateUser>();
const { stats } = useStats(user.id); const {stats} = useStats(user.id);
const { users } = useUsers(); const {users} = useUsers();
const { const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assignees: user?.id});
assignments, const {invites, isLoading: isInvitesLoading, reload: reloadInvites} = useInvites({to: user.id});
isLoading: isAssignmentsLoading,
reload: reloadAssignments,
} = useAssignments({ assignees: user?.id });
const {
invites,
isLoading: isInvitesLoading,
reload: reloadInvites,
} = useInvites({ to: user.id });
const router = useRouter(); const router = useRouter();
const setExams = useExamStore((state) => state.setExams); const setExams = useExamStore((state) => state.setExams);
const setShowSolutions = useExamStore((state) => state.setShowSolutions); const setShowSolutions = useExamStore((state) => state.setShowSolutions);
const setUserSolutions = useExamStore((state) => state.setUserSolutions); const setUserSolutions = useExamStore((state) => state.setUserSolutions);
const setSelectedModules = useExamStore((state) => state.setSelectedModules); const setSelectedModules = useExamStore((state) => state.setSelectedModules);
const setAssignment = useExamStore((state) => state.setAssignment); const setAssignment = useExamStore((state) => state.setAssignment);
useEffect(() => { useEffect(() => {
getUserCorporate(user.id).then(setCorporateUserToShow); getUserCorporate(user.id).then(setCorporateUserToShow);
}, [user]); }, [user]);
const startAssignment = (assignment: Assignment) => { const startAssignment = (assignment: Assignment) => {
const examPromises = assignment.exams const examPromises = assignment.exams.filter((e) => e.assignee === user.id).map((e) => getExamById(e.module, e.id));
.filter((e) => e.assignee === user.id)
.map((e) => getExamById(e.module, e.id));
Promise.all(examPromises).then((exams) => { Promise.all(examPromises).then((exams) => {
if (exams.every((x) => !!x)) { if (exams.every((x) => !!x)) {
setUserSolutions([]); setUserSolutions([]);
setShowSolutions(false); setShowSolutions(false);
setExams(exams.map((x) => x!).sort(sortByModule)); setExams(exams.map((x) => x!).sort(sortByModule));
setSelectedModules( setSelectedModules(
exams exams
.map((x) => x!) .map((x) => x!)
.sort(sortByModule) .sort(sortByModule)
.map((x) => x!.module), .map((x) => x!.module),
); );
setAssignment(assignment); setAssignment(assignment);
router.push("/exercises"); router.push("/exercises");
} }
}); });
}; };
return ( return (
<> <>
{corporateUserToShow && ( {corporateUserToShow && (
<div className="absolute right-4 top-4 rounded-lg bg-neutral-200 px-2 py-1"> <div className="absolute right-4 top-4 rounded-lg bg-neutral-200 px-2 py-1">
Linked to:{" "} Linked to: <b>{corporateUserToShow?.corporateInformation?.companyInformation.name || corporateUserToShow.name}</b>
<b> </div>
{corporateUserToShow?.corporateInformation?.companyInformation )}
.name || corporateUserToShow.name} <ProfileSummary
</b> user={user}
</div> items={[
)} {
<ProfileSummary icon: <BsFileEarmarkText className="text-mti-red-light h-6 w-6 md:h-8 md:w-8" />,
user={user} value: Object.keys(groupBySession(stats)).length,
items={[ label: "Exams",
{ },
icon: ( {
<BsFileEarmarkText className="text-mti-red-light h-6 w-6 md:h-8 md:w-8" /> icon: <BsPencil className="text-mti-red-light h-6 w-6 md:h-8 md:w-8" />,
), value: stats.length,
value: Object.keys(groupBySession(stats)).length, label: "Exercises",
label: "Exams", },
}, {
{ icon: <BsStar className="text-mti-red-light h-6 w-6 md:h-8 md:w-8" />,
icon: ( value: `${stats.length > 0 ? averageScore(stats) : 0}%`,
<BsPencil className="text-mti-red-light h-6 w-6 md:h-8 md:w-8" /> label: "Average Score",
), },
value: stats.length, ]}
label: "Exercises", />
},
{
icon: (
<BsStar className="text-mti-red-light h-6 w-6 md:h-8 md:w-8" />
),
value: `${stats.length > 0 ? averageScore(stats) : 0}%`,
label: "Average Score",
},
]}
/>
<section className="flex flex-col gap-1 md:gap-3"> {/* Bio */}
<span className="text-lg font-bold">Bio</span> <section className="flex flex-col gap-1 md:gap-3">
<span className="text-mti-gray-taupe"> <span className="text-lg font-bold">Bio</span>
{user.bio || <span className="text-mti-gray-taupe">
"Your bio will appear here, you can change it by clicking on your name in the top right corner."} {user.bio || "Your bio will appear here, you can change it by clicking on your name in the top right corner."}
</span> </span>
</section> </section>
<section className="flex flex-col gap-1 md:gap-3"> {/* Assignments */}
<div className="flex items-center gap-4"> <section className="flex flex-col gap-1 md:gap-3">
<div <div className="flex items-center gap-4">
onClick={reloadAssignments} <div
className="text-mti-purple-light hover:text-mti-purple-dark flex cursor-pointer items-center gap-2 transition duration-300 ease-in-out" onClick={reloadAssignments}
> className="text-mti-purple-light hover:text-mti-purple-dark flex cursor-pointer items-center gap-2 transition duration-300 ease-in-out">
<span className="text-mti-black text-lg font-bold"> <span className="text-mti-black text-lg font-bold">Assignments</span>
Assignments <BsArrowRepeat className={clsx("text-xl", isAssignmentsLoading && "animate-spin")} />
</span> </div>
<BsArrowRepeat </div>
className={clsx( <span className="text-mti-gray-taupe scrollbar-hide flex gap-8 overflow-x-scroll">
"text-xl", {assignments.filter((a) => moment(a.endDate).isSameOrAfter(moment())).length === 0 &&
isAssignmentsLoading && "animate-spin", "Assignments will appear here. It seems that for now there are no assignments for you."}
)} {assignments
/> .filter((a) => moment(a.endDate).isSameOrAfter(moment()))
</div> .sort((a, b) => moment(a.startDate).diff(b.startDate))
</div> .map((assignment) => (
<span className="text-mti-gray-taupe scrollbar-hide flex gap-8 overflow-x-scroll"> <div
{assignments.filter((a) => moment(a.endDate).isSameOrAfter(moment())) className={clsx(
.length === 0 && "border-mti-gray-anti-flash flex min-w-[300px] flex-col gap-6 rounded-xl border p-4",
"Assignments will appear here. It seems that for now there are no assignments for you."} assignment.results.map((r) => r.user).includes(user.id) && "border-mti-green-light",
{assignments )}
.filter((a) => moment(a.endDate).isSameOrAfter(moment())) key={assignment.id}>
.sort((a, b) => moment(a.startDate).diff(b.startDate)) <div className="flex flex-col gap-1">
.map((assignment) => ( <h3 className="text-mti-black/90 text-xl font-semibold">{assignment.name}</h3>
<div <span className="flex justify-between gap-1">
className={clsx( <span>{moment(assignment.startDate).format("DD/MM/YY, HH:mm")}</span>
"border-mti-gray-anti-flash flex min-w-[300px] flex-col gap-6 rounded-xl border p-4", <span>-</span>
assignment.results.map((r) => r.user).includes(user.id) && <span>{moment(assignment.endDate).format("DD/MM/YY, HH:mm")}</span>
"border-mti-green-light", </span>
)} </div>
key={assignment.id} <div className="flex w-full items-center justify-between">
> <div className="-md:mt-2 grid w-fit min-w-[104px] grid-cols-2 place-items-center justify-center gap-2">
<div className="flex flex-col gap-1"> {assignment.exams
<h3 className="text-mti-black/90 text-xl font-semibold"> .filter((e) => e.assignee === user.id)
{assignment.name} .map((e) => e.module)
</h3> .sort(sortByModuleName)
<span className="flex justify-between gap-1"> .map((module) => (
<span> <div
{moment(assignment.startDate).format("DD/MM/YY, HH:mm")} key={module}
</span> data-tip={capitalize(module)}
<span>-</span> className={clsx(
<span> "-md:px-4 tooltip flex w-fit items-center gap-2 rounded-xl py-2 text-white md:px-2 xl:px-4",
{moment(assignment.endDate).format("DD/MM/YY, HH:mm")} module === "reading" && "bg-ielts-reading",
</span> module === "listening" && "bg-ielts-listening",
</span> module === "writing" && "bg-ielts-writing",
</div> module === "speaking" && "bg-ielts-speaking",
<div className="flex w-full items-center justify-between"> module === "level" && "bg-ielts-level",
<div className="-md:mt-2 grid w-fit min-w-[104px] grid-cols-2 place-items-center justify-center gap-2"> )}>
{assignment.exams {module === "reading" && <BsBook className="h-4 w-4" />}
.filter((e) => e.assignee === user.id) {module === "listening" && <BsHeadphones className="h-4 w-4" />}
.map((e) => e.module) {module === "writing" && <BsPen className="h-4 w-4" />}
.sort(sortByModuleName) {module === "speaking" && <BsMegaphone className="h-4 w-4" />}
.map((module) => ( {module === "level" && <BsClipboard className="h-4 w-4" />}
<div </div>
key={module} ))}
data-tip={capitalize(module)} </div>
className={clsx( {!assignment.results.map((r) => r.user).includes(user.id) && (
"-md:px-4 tooltip flex w-fit items-center gap-2 rounded-xl py-2 text-white md:px-2 xl:px-4", <>
module === "reading" && "bg-ielts-reading", <div
module === "listening" && "bg-ielts-listening", className="tooltip flex h-full w-full items-center justify-end pl-8 md:hidden"
module === "writing" && "bg-ielts-writing", data-tip="Your screen size is too small to perform an assignment">
module === "speaking" && "bg-ielts-speaking", <Button
module === "level" && "bg-ielts-level", disabled={moment(assignment.startDate).isAfter(moment())}
)} className="h-full w-full !rounded-xl"
> variant="outline">
{module === "reading" && ( Start
<BsBook className="h-4 w-4" /> </Button>
)} </div>
{module === "listening" && ( <Button
<BsHeadphones className="h-4 w-4" /> disabled={moment(assignment.startDate).isAfter(moment())}
)} className="-md:hidden h-full w-full max-w-[50%] !rounded-xl"
{module === "writing" && ( onClick={() => startAssignment(assignment)}
<BsPen className="h-4 w-4" /> variant="outline">
)} Start
{module === "speaking" && ( </Button>
<BsMegaphone className="h-4 w-4" /> </>
)} )}
{module === "level" && ( {assignment.results.map((r) => r.user).includes(user.id) && (
<BsClipboard className="h-4 w-4" /> <Button
)} onClick={() => router.push("/record")}
</div> color="green"
))} className="-md:hidden h-full w-full max-w-[50%] !rounded-xl"
</div> variant="outline">
{!assignment.results.map((r) => r.user).includes(user.id) && ( Submitted
<> </Button>
<div )}
className="tooltip flex h-full w-full items-center justify-end pl-8 md:hidden" </div>
data-tip="Your screen size is too small to perform an assignment" </div>
> ))}
<Button </span>
disabled={moment(assignment.startDate).isAfter( </section>
moment(),
)}
className="h-full w-full !rounded-xl"
variant="outline"
>
Start
</Button>
</div>
<Button
disabled={moment(assignment.startDate).isAfter(
moment(),
)}
className="-md:hidden h-full w-full max-w-[50%] !rounded-xl"
onClick={() => startAssignment(assignment)}
variant="outline"
>
Start
</Button>
</>
)}
{assignment.results.map((r) => r.user).includes(user.id) && (
<Button
onClick={() => router.push("/record")}
color="green"
className="-md:hidden h-full w-full max-w-[50%] !rounded-xl"
variant="outline"
>
Submitted
</Button>
)}
</div>
</div>
))}
</span>
</section>
{invites.length > 0 && ( {/* Invites */}
<section className="flex flex-col gap-1 md:gap-3"> {invites.length > 0 && (
<div className="flex items-center gap-4"> <section className="flex flex-col gap-1 md:gap-3">
<div <div className="flex items-center gap-4">
onClick={reloadInvites} <div
className="text-mti-purple-light hover:text-mti-purple-dark flex cursor-pointer items-center gap-2 transition duration-300 ease-in-out" onClick={reloadInvites}
> className="text-mti-purple-light hover:text-mti-purple-dark flex cursor-pointer items-center gap-2 transition duration-300 ease-in-out">
<span className="text-mti-black text-lg font-bold">Invites</span> <span className="text-mti-black text-lg font-bold">Invites</span>
<BsArrowRepeat <BsArrowRepeat className={clsx("text-xl", isInvitesLoading && "animate-spin")} />
className={clsx("text-xl", isInvitesLoading && "animate-spin")} </div>
/> </div>
</div> <span className="text-mti-gray-taupe scrollbar-hide flex gap-8 overflow-x-scroll">
</div> {invites.map((invite) => (
<span className="text-mti-gray-taupe scrollbar-hide flex gap-8 overflow-x-scroll"> <InviteCard key={invite.id} invite={invite} users={users} reload={reloadInvites} />
{invites.map((invite) => ( ))}
<InviteCard </span>
key={invite.id} </section>
invite={invite} )}
users={users}
reload={reloadInvites}
/>
))}
</span>
</section>
)}
<section className="flex flex-col gap-3"> {/* Score History */}
<span className="text-lg font-bold">Score History</span> <section className="flex flex-col gap-3">
<div className="-md:grid-rows-4 grid gap-6 md:grid-cols-2"> <span className="text-lg font-bold">Score History</span>
{MODULE_ARRAY.map((module) => ( <div className="-md:grid-rows-4 grid gap-6 md:grid-cols-2">
<div {MODULE_ARRAY.map((module) => (
className="border-mti-gray-anti-flash flex flex-col gap-2 rounded-xl border p-4" <div className="border-mti-gray-anti-flash flex flex-col gap-2 rounded-xl border p-4" key={module}>
key={module} <div className="flex items-center gap-2 md:gap-3">
> <div className="bg-mti-gray-smoke flex h-8 w-8 items-center justify-center rounded-lg md:h-12 md:w-12 md:rounded-xl">
<div className="flex items-center gap-2 md:gap-3"> {module === "reading" && <BsBook className="text-ielts-reading h-4 w-4 md:h-5 md:w-5" />}
<div className="bg-mti-gray-smoke flex h-8 w-8 items-center justify-center rounded-lg md:h-12 md:w-12 md:rounded-xl"> {module === "listening" && <BsHeadphones className="text-ielts-listening h-4 w-4 md:h-5 md:w-5" />}
{module === "reading" && ( {module === "writing" && <BsPen className="text-ielts-writing h-4 w-4 md:h-5 md:w-5" />}
<BsBook className="text-ielts-reading h-4 w-4 md:h-5 md:w-5" /> {module === "speaking" && <BsMegaphone className="text-ielts-speaking h-4 w-4 md:h-5 md:w-5" />}
)} {module === "level" && <BsClipboard className="text-ielts-level h-4 w-4 md:h-5 md:w-5" />}
{module === "listening" && ( </div>
<BsHeadphones className="text-ielts-listening h-4 w-4 md:h-5 md:w-5" /> <div className="flex w-full justify-between">
)} <span className="text-sm font-bold md:font-extrabold">{capitalize(module)}</span>
{module === "writing" && ( <span className="text-mti-gray-dim text-sm font-normal">
<BsPen className="text-ielts-writing h-4 w-4 md:h-5 md:w-5" /> Level {user.levels[module] || 0} / Level 9 (Desired Level: {user.desiredLevels[module] || 9})
)} </span>
{module === "speaking" && ( </div>
<BsMegaphone className="text-ielts-speaking h-4 w-4 md:h-5 md:w-5" /> </div>
)} <div className="md:pl-14">
{module === "level" && ( <ProgressBar
<BsClipboard className="text-ielts-level h-4 w-4 md:h-5 md:w-5" /> color={module}
)} label=""
</div> mark={Math.round((user.desiredLevels[module] * 100) / 9)}
<div className="flex w-full justify-between"> markLabel={`Desired Level: ${user.desiredLevels[module]}`}
<span className="text-sm font-bold md:font-extrabold"> percentage={Math.round((user.levels[module] * 100) / 9)}
{capitalize(module)} className="h-2 w-full"
</span> />
<span className="text-mti-gray-dim text-sm font-normal"> </div>
Level {user.levels[module] || 0} / Level{" "} </div>
{user.desiredLevels[module] || 9} ))}
</span> </div>
</div> </section>
</div> </>
<div className="md:pl-14"> );
<ProgressBar
color={module}
label=""
percentage={Math.round(
(user.levels[module] * 100) / user.desiredLevels[module],
)}
className="h-2 w-full"
/>
</div>
</div>
))}
</div>
</section>
</>
);
} }

View File

@@ -1,384 +1,261 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import { useState } from "react"; import {useState} from "react";
import { Module } from "@/interfaces"; import {Module} from "@/interfaces";
import clsx from "clsx"; import clsx from "clsx";
import { User } from "@/interfaces/user"; import {User} from "@/interfaces/user";
import ProgressBar from "@/components/Low/ProgressBar"; import ProgressBar from "@/components/Low/ProgressBar";
import { import {BsBook, BsCheck, BsCheckCircle, BsClipboard, BsHeadphones, BsMegaphone, BsPen, BsXCircle} from "react-icons/bs";
BsBook, import {totalExamsByModule} from "@/utils/stats";
BsCheck,
BsCheckCircle,
BsClipboard,
BsHeadphones,
BsMegaphone,
BsPen,
BsXCircle,
} from "react-icons/bs";
import { totalExamsByModule } from "@/utils/stats";
import useStats from "@/hooks/useStats"; import useStats from "@/hooks/useStats";
import Button from "@/components/Low/Button"; import Button from "@/components/Low/Button";
import { calculateAverageLevel } from "@/utils/score"; import {calculateAverageLevel} from "@/utils/score";
import { sortByModuleName } from "@/utils/moduleUtils"; import {sortByModuleName} from "@/utils/moduleUtils";
import { capitalize } from "lodash"; import {capitalize} from "lodash";
import ProfileSummary from "@/components/ProfileSummary"; import ProfileSummary from "@/components/ProfileSummary";
import { Variant } from "@/interfaces/exam"; import {Variant} from "@/interfaces/exam";
interface Props { interface Props {
user: User; user: User;
page: "exercises" | "exams"; page: "exercises" | "exams";
onStart: ( onStart: (modules: Module[], avoidRepeated: boolean, variant: Variant) => void;
modules: Module[], disableSelection?: boolean;
avoidRepeated: boolean,
variant: Variant,
) => void;
disableSelection?: boolean;
} }
export default function Selection({ export default function Selection({user, page, onStart, disableSelection = false}: Props) {
user, const [selectedModules, setSelectedModules] = useState<Module[]>([]);
page, const [avoidRepeatedExams, setAvoidRepeatedExams] = useState(true);
onStart, const [variant, setVariant] = useState<Variant>("full");
disableSelection = false, const {stats} = useStats(user?.id);
}: Props) {
const [selectedModules, setSelectedModules] = useState<Module[]>([]);
const [avoidRepeatedExams, setAvoidRepeatedExams] = useState(true);
const [variant, setVariant] = useState<Variant>("full");
const { stats } = useStats(user?.id);
const toggleModule = (module: Module) => { const toggleModule = (module: Module) => {
const modules = selectedModules.filter((x) => x !== module); const modules = selectedModules.filter((x) => x !== module);
setSelectedModules((prev) => setSelectedModules((prev) => (prev.includes(module) ? modules : [...modules, module]));
prev.includes(module) ? modules : [...modules, module], };
);
};
return ( return (
<> <>
<div className="relative flex h-full w-full flex-col gap-8 md:gap-16"> <div className="relative flex h-full w-full flex-col gap-8 md:gap-16">
{user && ( {user && (
<ProfileSummary <ProfileSummary
user={user} user={user}
items={[ items={[
{ {
icon: ( icon: <BsBook className="text-ielts-reading h-6 w-6 md:h-8 md:w-8" />,
<BsBook className="text-ielts-reading h-6 w-6 md:h-8 md:w-8" /> label: "Reading",
), value: totalExamsByModule(stats, "reading"),
label: "Reading", },
value: totalExamsByModule(stats, "reading"), {
}, icon: <BsHeadphones className="text-ielts-listening h-6 w-6 md:h-8 md:w-8" />,
{ label: "Listening",
icon: ( value: totalExamsByModule(stats, "listening"),
<BsHeadphones className="text-ielts-listening h-6 w-6 md:h-8 md:w-8" /> },
), {
label: "Listening", icon: <BsPen className="text-ielts-writing h-6 w-6 md:h-8 md:w-8" />,
value: totalExamsByModule(stats, "listening"), label: "Writing",
}, value: totalExamsByModule(stats, "writing"),
{ },
icon: ( {
<BsPen className="text-ielts-writing h-6 w-6 md:h-8 md:w-8" /> icon: <BsMegaphone className="text-ielts-speaking h-6 w-6 md:h-8 md:w-8" />,
), label: "Speaking",
label: "Writing", value: totalExamsByModule(stats, "speaking"),
value: totalExamsByModule(stats, "writing"), },
}, {
{ icon: <BsClipboard className="text-ielts-level h-6 w-6 md:h-8 md:w-8" />,
icon: ( label: "Vocab/Grammar",
<BsMegaphone className="text-ielts-speaking h-6 w-6 md:h-8 md:w-8" /> value: totalExamsByModule(stats, "level"),
), },
label: "Speaking", ]}
value: totalExamsByModule(stats, "speaking"), />
}, )}
{
icon: (
<BsClipboard className="text-ielts-level h-6 w-6 md:h-8 md:w-8" />
),
label: "Level",
value: totalExamsByModule(stats, "level"),
},
]}
/>
)}
<section className="flex flex-col gap-3"> <section className="flex flex-col gap-3">
<span className="text-lg font-bold">About {capitalize(page)}</span> <span className="text-lg font-bold">About {capitalize(page)}</span>
<span className="text-mti-gray-taupe"> <span className="text-mti-gray-taupe">
{page === "exercises" && ( {page === "exercises" && (
<> <>
In the realm of language acquisition, practice makes perfect, In the realm of language acquisition, practice makes perfect, and our exercises are the key to unlocking your full
and our exercises are the key to unlocking your full potential. potential. Dive into a world of interactive and engaging exercises that cater to diverse learning styles. From grammar
Dive into a world of interactive and engaging exercises that drills that build a strong foundation to vocabulary challenges that broaden your lexicon, our exercises are carefully
cater to diverse learning styles. From grammar drills that build designed to make learning English both enjoyable and effective. Whether you&apos;re looking to reinforce specific
a strong foundation to vocabulary challenges that broaden your skills or embark on a holistic language journey, our exercises are your companions in the pursuit of excellence.
lexicon, our exercises are carefully designed to make learning Embrace the joy of learning as you navigate through a variety of activities that cater to every facet of language
English both enjoyable and effective. Whether you&apos;re acquisition. Your linguistic adventure starts here!
looking to reinforce specific skills or embark on a holistic </>
language journey, our exercises are your companions in the )}
pursuit of excellence. Embrace the joy of learning as you {page === "exams" && (
navigate through a variety of activities that cater to every <>
facet of language acquisition. Your linguistic adventure starts Welcome to the heart of success on your English language journey! Our exams are crafted with precision to assess and
here! enhance your language skills. Each test is a passport to your linguistic prowess, designed to challenge and elevate
</> your abilities. Whether you&apos;re a beginner or a seasoned learner, our exams cater to all levels, providing a
)} comprehensive evaluation of your reading, writing, speaking, and listening skills. Prepare to embark on a journey of
{page === "exams" && ( self-discovery and language mastery as you navigate through our thoughtfully curated exams. Your success is not just a
<> destination; it&apos;s a testament to your dedication and our commitment to empowering you with the English language.
Welcome to the heart of success on your English language </>
journey! Our exams are crafted with precision to assess and )}
enhance your language skills. Each test is a passport to your </span>
linguistic prowess, designed to challenge and elevate your </section>
abilities. Whether you&apos;re a beginner or a seasoned learner, <section className="-lg:flex-col -lg:items-center -lg:gap-12 mt-8 flex w-full justify-between gap-8">
our exams cater to all levels, providing a comprehensive <div
evaluation of your reading, writing, speaking, and listening onClick={!disableSelection && !selectedModules.includes("level") ? () => toggleModule("reading") : undefined}
skills. Prepare to embark on a journey of self-discovery and className={clsx(
language mastery as you navigate through our thoughtfully "bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out",
curated exams. Your success is not just a destination; it&apos;s selectedModules.includes("reading") || disableSelection ? "border-mti-purple-light" : "border-mti-gray-platinum",
a testament to your dedication and our commitment to empowering )}>
you with the English language. <div className="bg-ielts-reading absolute top-0 flex h-16 w-16 -translate-y-1/2 items-center justify-center rounded-full">
</> <BsBook className="h-7 w-7 text-white" />
)} </div>
</span> <span className="font-semibold">Reading:</span>
</section> <p className="text-left text-xs">
<section className="-lg:flex-col -lg:items-center -lg:gap-12 mt-8 flex w-full justify-between gap-8"> Expand your vocabulary, improve your reading comprehension and improve your ability to interpret texts in English.
<div </p>
onClick={ {!selectedModules.includes("reading") && !selectedModules.includes("level") && !disableSelection && (
!disableSelection && !selectedModules.includes("level") <div className="border-mti-gray-platinum mt-4 h-8 w-8 rounded-full border" />
? () => toggleModule("reading") )}
: undefined {(selectedModules.includes("reading") || disableSelection) && (
} <BsCheckCircle className="text-mti-purple-light mt-4 h-8 w-8" />
className={clsx( )}
"bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out", {selectedModules.includes("level") && <BsXCircle className="text-mti-red-light mt-4 h-8 w-8" />}
selectedModules.includes("reading") || disableSelection </div>
? "border-mti-purple-light" <div
: "border-mti-gray-platinum", onClick={!disableSelection && !selectedModules.includes("level") ? () => toggleModule("listening") : undefined}
)} className={clsx(
> "bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out",
<div className="bg-ielts-reading absolute top-0 flex h-16 w-16 -translate-y-1/2 items-center justify-center rounded-full"> selectedModules.includes("listening") || disableSelection ? "border-mti-purple-light" : "border-mti-gray-platinum",
<BsBook className="h-7 w-7 text-white" /> )}>
</div> <div className="bg-ielts-listening absolute top-0 flex h-16 w-16 -translate-y-1/2 items-center justify-center rounded-full">
<span className="font-semibold">Reading:</span> <BsHeadphones className="h-7 w-7 text-white" />
<p className="text-left text-xs"> </div>
Expand your vocabulary, improve your reading comprehension and <span className="font-semibold">Listening:</span>
improve your ability to interpret texts in English. <p className="text-left text-xs">
</p> Improve your ability to follow conversations in English and your ability to understand different accents and intonations.
{!selectedModules.includes("reading") && </p>
!selectedModules.includes("level") && {!selectedModules.includes("listening") && !selectedModules.includes("level") && !disableSelection && (
!disableSelection && ( <div className="border-mti-gray-platinum mt-4 h-8 w-8 rounded-full border" />
<div className="border-mti-gray-platinum mt-4 h-8 w-8 rounded-full border" /> )}
)} {(selectedModules.includes("listening") || disableSelection) && (
{(selectedModules.includes("reading") || disableSelection) && ( <BsCheckCircle className="text-mti-purple-light mt-4 h-8 w-8" />
<BsCheckCircle className="text-mti-purple-light mt-4 h-8 w-8" /> )}
)} {selectedModules.includes("level") && <BsXCircle className="text-mti-red-light mt-4 h-8 w-8" />}
{selectedModules.includes("level") && ( </div>
<BsXCircle className="text-mti-red-light mt-4 h-8 w-8" /> <div
)} onClick={!disableSelection && !selectedModules.includes("level") ? () => toggleModule("writing") : undefined}
</div> className={clsx(
<div "bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out",
onClick={ selectedModules.includes("writing") || disableSelection ? "border-mti-purple-light" : "border-mti-gray-platinum",
!disableSelection && !selectedModules.includes("level") )}>
? () => toggleModule("listening") <div className="bg-ielts-writing absolute top-0 flex h-16 w-16 -translate-y-1/2 items-center justify-center rounded-full">
: undefined <BsPen className="h-7 w-7 text-white" />
} </div>
className={clsx( <span className="font-semibold">Writing:</span>
"bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out", <p className="text-left text-xs">
selectedModules.includes("listening") || disableSelection Allow you to practice writing in a variety of formats, from simple paragraphs to complex essays.
? "border-mti-purple-light" </p>
: "border-mti-gray-platinum", {!selectedModules.includes("writing") && !selectedModules.includes("level") && !disableSelection && (
)} <div className="border-mti-gray-platinum mt-4 h-8 w-8 rounded-full border" />
> )}
<div className="bg-ielts-listening absolute top-0 flex h-16 w-16 -translate-y-1/2 items-center justify-center rounded-full"> {(selectedModules.includes("writing") || disableSelection) && (
<BsHeadphones className="h-7 w-7 text-white" /> <BsCheckCircle className="text-mti-purple-light mt-4 h-8 w-8" />
</div> )}
<span className="font-semibold">Listening:</span> {selectedModules.includes("level") && <BsXCircle className="text-mti-red-light mt-4 h-8 w-8" />}
<p className="text-left text-xs"> </div>
Improve your ability to follow conversations in English and your <div
ability to understand different accents and intonations. onClick={!disableSelection && !selectedModules.includes("level") ? () => toggleModule("speaking") : undefined}
</p> className={clsx(
{!selectedModules.includes("listening") && "bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out",
!selectedModules.includes("level") && selectedModules.includes("speaking") || disableSelection ? "border-mti-purple-light" : "border-mti-gray-platinum",
!disableSelection && ( )}>
<div className="border-mti-gray-platinum mt-4 h-8 w-8 rounded-full border" /> <div className="bg-ielts-speaking absolute top-0 flex h-16 w-16 -translate-y-1/2 items-center justify-center rounded-full">
)} <BsMegaphone className="h-7 w-7 text-white" />
{(selectedModules.includes("listening") || disableSelection) && ( </div>
<BsCheckCircle className="text-mti-purple-light mt-4 h-8 w-8" /> <span className="font-semibold">Speaking:</span>
)} <p className="text-left text-xs">
{selectedModules.includes("level") && ( You&apos;ll have access to interactive dialogs, pronunciation exercises and speech recordings.
<BsXCircle className="text-mti-red-light mt-4 h-8 w-8" /> </p>
)} {!selectedModules.includes("speaking") && !selectedModules.includes("level") && !disableSelection && (
</div> <div className="border-mti-gray-platinum mt-4 h-8 w-8 rounded-full border" />
<div )}
onClick={ {(selectedModules.includes("speaking") || disableSelection) && (
!disableSelection && !selectedModules.includes("level") <BsCheckCircle className="text-mti-purple-light mt-4 h-8 w-8" />
? () => toggleModule("writing") )}
: undefined {selectedModules.includes("level") && <BsXCircle className="text-mti-red-light mt-4 h-8 w-8" />}
} </div>
className={clsx( {!disableSelection && (
"bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out", <div
selectedModules.includes("writing") || disableSelection onClick={selectedModules.length === 0 || selectedModules.includes("level") ? () => toggleModule("level") : undefined}
? "border-mti-purple-light" className={clsx(
: "border-mti-gray-platinum", "bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out",
)} selectedModules.includes("level") || disableSelection ? "border-mti-purple-light" : "border-mti-gray-platinum",
> )}>
<div className="bg-ielts-writing absolute top-0 flex h-16 w-16 -translate-y-1/2 items-center justify-center rounded-full"> <div className="bg-ielts-level absolute top-0 flex h-16 w-16 -translate-y-1/2 items-center justify-center rounded-full">
<BsPen className="h-7 w-7 text-white" /> <BsClipboard className="h-7 w-7 text-white" />
</div> </div>
<span className="font-semibold">Writing:</span> <span className="font-semibold">Level:</span>
<p className="text-left text-xs"> <p className="text-left text-xs">You&apos;ll be able to test your english level with multiple choice questions.</p>
Allow you to practice writing in a variety of formats, from simple {!selectedModules.includes("level") && selectedModules.length === 0 && !disableSelection && (
paragraphs to complex essays. <div className="border-mti-gray-platinum mt-4 h-8 w-8 rounded-full border" />
</p> )}
{!selectedModules.includes("writing") && {(selectedModules.includes("level") || disableSelection) && (
!selectedModules.includes("level") && <BsCheckCircle className="text-mti-purple-light mt-4 h-8 w-8" />
!disableSelection && ( )}
<div className="border-mti-gray-platinum mt-4 h-8 w-8 rounded-full border" /> {!selectedModules.includes("level") && selectedModules.length > 0 && (
)} <BsXCircle className="text-mti-red-light mt-4 h-8 w-8" />
{(selectedModules.includes("writing") || disableSelection) && ( )}
<BsCheckCircle className="text-mti-purple-light mt-4 h-8 w-8" /> </div>
)} )}
{selectedModules.includes("level") && ( </section>
<BsXCircle className="text-mti-red-light mt-4 h-8 w-8" /> <div className="-md:flex-col -md:gap-4 -md:justify-center flex w-full items-center md:justify-between">
)} <div className="flex w-full flex-col items-center gap-3">
</div> <div
<div className="text-mti-gray-dim -md:justify-center flex w-full cursor-pointer items-center gap-3 text-sm"
onClick={ onClick={() => setAvoidRepeatedExams((prev) => !prev)}>
!disableSelection && !selectedModules.includes("level") <input type="checkbox" className="hidden" />
? () => toggleModule("speaking") <div
: undefined className={clsx(
} "border-mti-purple-light flex h-6 w-6 items-center justify-center rounded-md border bg-white",
className={clsx( "transition duration-300 ease-in-out",
"bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out", avoidRepeatedExams && "!bg-mti-purple-light ",
selectedModules.includes("speaking") || disableSelection )}>
? "border-mti-purple-light" <BsCheck color="white" className="h-full w-full" />
: "border-mti-gray-platinum", </div>
)} <span className="tooltip" data-tip="If possible, the platform will choose exams not yet done.">
> Avoid Repeated Questions
<div className="bg-ielts-speaking absolute top-0 flex h-16 w-16 -translate-y-1/2 items-center justify-center rounded-full"> </span>
<BsMegaphone className="h-7 w-7 text-white" /> </div>
</div> <div
<span className="font-semibold">Speaking:</span> className="text-mti-gray-dim -md:justify-center flex w-full cursor-pointer items-center gap-3 text-sm"
<p className="text-left text-xs"> onClick={() => setVariant((prev) => (prev === "full" ? "partial" : "full"))}>
You&apos;ll have access to interactive dialogs, pronunciation <input type="checkbox" className="hidden" />
exercises and speech recordings. <div
</p> className={clsx(
{!selectedModules.includes("speaking") && "border-mti-purple-light flex h-6 w-6 items-center justify-center rounded-md border bg-white",
!selectedModules.includes("level") && "transition duration-300 ease-in-out",
!disableSelection && ( variant === "full" && "!bg-mti-purple-light ",
<div className="border-mti-gray-platinum mt-4 h-8 w-8 rounded-full border" /> )}>
)} <BsCheck color="white" className="h-full w-full" />
{(selectedModules.includes("speaking") || disableSelection) && ( </div>
<BsCheckCircle className="text-mti-purple-light mt-4 h-8 w-8" /> <span>Full length exams</span>
)} </div>
{selectedModules.includes("level") && ( </div>
<BsXCircle className="text-mti-red-light mt-4 h-8 w-8" /> <div className="tooltip w-full" data-tip={`Your screen size is too small to do ${page}`}>
)} <Button color="purple" className="w-full max-w-xs px-12 md:hidden" disabled>
</div> Start Exam
{!disableSelection && ( </Button>
<div </div>
onClick={ <Button
selectedModules.length === 0 || onClick={() =>
selectedModules.includes("level") onStart(
? () => toggleModule("level") !disableSelection ? selectedModules.sort(sortByModuleName) : ["reading", "listening", "writing", "speaking"],
: undefined avoidRepeatedExams,
} variant,
className={clsx( )
"bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out", }
selectedModules.includes("level") || disableSelection color="purple"
? "border-mti-purple-light" className="-md:hidden w-full max-w-xs px-12 md:self-end"
: "border-mti-gray-platinum", disabled={selectedModules.length === 0 && !disableSelection}>
)} Start Exam
> </Button>
<div className="bg-ielts-level absolute top-0 flex h-16 w-16 -translate-y-1/2 items-center justify-center rounded-full"> </div>
<BsClipboard className="h-7 w-7 text-white" /> </div>
</div> </>
<span className="font-semibold">Level:</span> );
<p className="text-left text-xs">
You&apos;ll be able to test your english level with multiple
choice questions.
</p>
{!selectedModules.includes("level") &&
selectedModules.length === 0 &&
!disableSelection && (
<div className="border-mti-gray-platinum mt-4 h-8 w-8 rounded-full border" />
)}
{(selectedModules.includes("level") || disableSelection) && (
<BsCheckCircle className="text-mti-purple-light mt-4 h-8 w-8" />
)}
{!selectedModules.includes("level") &&
selectedModules.length > 0 && (
<BsXCircle className="text-mti-red-light mt-4 h-8 w-8" />
)}
</div>
)}
</section>
<div className="-md:flex-col -md:gap-4 -md:justify-center flex w-full items-center md:justify-between">
<div className="flex w-full flex-col items-center gap-3">
<div
className="text-mti-gray-dim -md:justify-center flex w-full cursor-pointer items-center gap-3 text-sm"
onClick={() => setAvoidRepeatedExams((prev) => !prev)}
>
<input type="checkbox" className="hidden" />
<div
className={clsx(
"border-mti-purple-light flex h-6 w-6 items-center justify-center rounded-md border bg-white",
"transition duration-300 ease-in-out",
avoidRepeatedExams && "!bg-mti-purple-light ",
)}
>
<BsCheck color="white" className="h-full w-full" />
</div>
<span
className="tooltip"
data-tip="If possible, the platform will choose exams not yet done."
>
Avoid Repeated Questions
</span>
</div>
<div
className="text-mti-gray-dim -md:justify-center flex w-full cursor-pointer items-center gap-3 text-sm"
onClick={() =>
setVariant((prev) => (prev === "full" ? "partial" : "full"))
}
>
<input type="checkbox" className="hidden" />
<div
className={clsx(
"border-mti-purple-light flex h-6 w-6 items-center justify-center rounded-md border bg-white",
"transition duration-300 ease-in-out",
variant === "full" && "!bg-mti-purple-light ",
)}
>
<BsCheck color="white" className="h-full w-full" />
</div>
<span>Full length exams</span>
</div>
</div>
<div
className="tooltip w-full"
data-tip={`Your screen size is too small to do ${page}`}
>
<Button
color="purple"
className="w-full max-w-xs px-12 md:hidden"
disabled
>
Start Exam
</Button>
</div>
<Button
onClick={() =>
onStart(
!disableSelection
? selectedModules.sort(sortByModuleName)
: ["reading", "listening", "writing", "speaking"],
avoidRepeatedExams,
variant,
)
}
color="purple"
className="-md:hidden w-full max-w-xs px-12 md:self-end"
disabled={selectedModules.length === 0 && !disableSelection}
>
Start Exam
</Button>
</div>
</div>
</>
);
} }

View File

@@ -2,7 +2,7 @@
import Head from "next/head"; import Head from "next/head";
import {withIronSessionSsr} from "iron-session/next"; import {withIronSessionSsr} from "iron-session/next";
import {sessionOptions} from "@/lib/session"; import {sessionOptions} from "@/lib/session";
import {ChangeEvent, ReactNode, useEffect, useRef, useState} from "react"; import {ChangeEvent, Dispatch, ReactNode, SetStateAction, useEffect, useRef, useState} from "react";
import useUser from "@/hooks/useUser"; import useUser from "@/hooks/useUser";
import {toast, ToastContainer} from "react-toastify"; import {toast, ToastContainer} from "react-toastify";
import Layout from "@/components/High/Layout"; import Layout from "@/components/High/Layout";
@@ -25,6 +25,9 @@ import {Divider} from "primereact/divider";
import GenderInput from "@/components/High/GenderInput"; import GenderInput from "@/components/High/GenderInput";
import EmploymentStatusInput from "@/components/High/EmploymentStatusInput"; import EmploymentStatusInput from "@/components/High/EmploymentStatusInput";
import TimezoneSelect from "@/components/Low/TImezoneSelect"; import TimezoneSelect from "@/components/Low/TImezoneSelect";
import Modal from "@/components/Modal";
import {Module} from "@/interfaces";
import ModuleLevelSelector from "@/components/Medium/ModuleLevelSelector";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
@@ -69,6 +72,10 @@ function UserProfile({user, mutateUser}: Props) {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [profilePicture, setProfilePicture] = useState(user.profilePicture); const [profilePicture, setProfilePicture] = useState(user.profilePicture);
const [desiredLevels, setDesiredLevels] = useState<{[key in Module]: number} | undefined>(
["developer", "student"].includes(user.type) ? user.desiredLevels : undefined,
);
const [country, setCountry] = useState<string>(user.demographicInformation?.country || ""); const [country, setCountry] = useState<string>(user.demographicInformation?.country || "");
const [phone, setPhone] = useState<string>(user.demographicInformation?.phone || ""); const [phone, setPhone] = useState<string>(user.demographicInformation?.phone || "");
const [gender, setGender] = useState<Gender | undefined>(user.demographicInformation?.gender || undefined); const [gender, setGender] = useState<Gender | undefined>(user.demographicInformation?.gender || undefined);
@@ -138,6 +145,7 @@ function UserProfile({user, mutateUser}: Props) {
password, password,
newPassword, newPassword,
profilePicture, profilePicture,
desiredLevels,
demographicInformation: { demographicInformation: {
phone, phone,
country, country,
@@ -319,6 +327,18 @@ function UserProfile({user, mutateUser}: Props) {
<Divider /> <Divider />
{desiredLevels && ["developer", "student"].includes(user.type) && (
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">Desired Levels</label>
<ModuleLevelSelector
levels={desiredLevels}
setLevels={setDesiredLevels as Dispatch<SetStateAction<{[key in Module]: number}>>}
/>
</div>
)}
<Divider />
{user.type === "corporate" && ( {user.type === "corporate" && (
<> <>
<DoubleColumnRow> <DoubleColumnRow>