Aligned the selection text to the left;\nUpdated the service account for the Firebase.

This commit is contained in:
Tiago Ribeiro
2024-02-01 14:00:34 +00:00
parent a872190e1b
commit c0c3e37568
3 changed files with 582 additions and 389 deletions

View File

@@ -1,13 +1,13 @@
{ {
"type": "service_account", "type": "service_account",
"project_id": "mti-ielts", "project_id": "storied-phalanx-349916",
"private_key_id": "22b783a14c760d1215a8d1f5de0fa40a33a840e7", "private_key_id": "c9e05f6fe413b1031a71f981160075ff4b044444",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDoNkd7s/izUBRb\nlmJYWl0xk4X9wEVJU4LKA4HPeha8RFDse4T4suVP08oCP9ODSXF5A83+IqXNMs/N\na7PtFABBAx433JrB7I4NsAUrDSjI4LeYEIqh6YzHsQvBU53HAmPChX525S4i0IBy\ncNnyXut0nmlHz5ZwCPXgqg4eN44C+m0f7sxzivcnPth/zLupnMiDAHFZrxQolWO2\n6JfozMWGw0TmCkUxngzeGBMVYmsGiKRIxEi3MWeuwjYjGO4nR1krEUlcpjCbx4UX\nxYXicJb17HOs9LTcSh9bpDWZPHKXR48hxd2cMLr+XQzw7Otwu2p8fEUOJ+CiTyNz\nlkN9p7OhAgMBAAECggEAB5DsMZdGu1X4wdazr+AK4RCG2UKkZ0wbqvgkCMX4O2xo\n7BmmtqFCmEAk+P+KJWEVW81wTu9jUl0tWOrBVzBThUrEF2seVkL+SmshsfpI6cmr\npb5lO/sTgZau1L7kGU3GQRpvKVHUl+EODFyJt2xZFOjL8qFsjAw4sbgsw1aJT6a4\nFilm6Gapi1qSKOPSlXVmi0NJ9DUtNbKaQK8/coqEJRizeXs9MORvzyKQaV8PBmWI\noEnkxahKOD48U2kmI7rT9/YsCuaP2BlGdLxvANXLjAKcrDccVZkYEH82tPtCicED\noow3i956HPdWSXQgUOU65MfGccjOmqGaGa4zUTICyQKBgQD6zLMwL9YS+n9EKZaK\nEbzRybN2d+eKbXyDJzkDi6FnSGVre2ndShsimoOtwZDLmOF/XhN79YOLJVbI124p\npAWO+WxAfe9Xy3iFEBmL4kSREA873Sd8EN5OfYS2DsN7IbjZkoaLuM8QlyXL9ZRS\nBJDVGjx+wFKRjnClcBNbVMMXiQKBgQDtBumKZS0ZCtJuBeuwLGJ1ZJtYECykIrsD\nUtQ7zxwXJzPGqZ2c5JLpHdDm/bb9nllpLsh4SpDRqxFa2H2FF8x5KWaS7JQUsS8e\ner6x5wUt6wAJqV/ZvttVrLZCa8VYn+K7bTANnkPNJZHTqBTJbxkXMDTtkwWXUN2z\nQP3N9lodWQKBgFBHiewYw9ubV3WIImnbt6cne0ymoPUMitioi3V5Epcu81fuTzrI\nZ9sxvoi19xVUwIm2oWICerLlptvvKZImsKjNajtSlHRz6wYc2zCNowkULOwqpGLw\nO1jAkOR94VDewH7UikDbTVywJSceWvXOBFZSaZ7hDQ0OnTw3ndqUTUaRAoGAd2BG\n2PPyDa28o7sJpBYGlJdSAb1LrnLre1YJHAJIZITS99hPUEhykUP6BYx80CkjYO01\n/BeZ7m9Y80cbmJ+O1Or8BT1vqyg90f0B8/mlSyYTQ8pxQupz7ydoN/WtU+BawgjQ\n7drqzPSCCHab2YPBwEMANTMZ2sbYkcJG0aekZSkCgYBbnFJm8kUy57isxHyvrci+\nR30KQl2Y9okPytF8PpLH+yNjLDoduTOHL/hZoFC0M4Gklx4wPKpsEhImIrWmG9VC\n0UrQC6TT1WoY6/S3YehVmTXo/nBPD1XTUcbF/xxUrWDjmMjnt1IlXBbIzUPD3U4P\niRXzHnXb7yi+/iRxSDts2w==\n-----END PRIVATE KEY-----\n", "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdgavFB63nMHyb\n38ncwijTrUmqU9UyzNJ8wlZCWAWuoz25Gng988fkKNDXnHY+ap9esHyNYg9IdSA7\nAuZeHpzTZmKiWZzFWq61KWSTgIn1JwKHGHJJdmVhTYfCe9I51cFLa5q2lTFzJ0ce\nbP7/X/7kw53odgva+M8AhDTbe60akpemgZc+LFwO0Abm7erH2HiNyjoNZzNw525L\n933PCaQwhZan04s1u0oRdVlBIBwMk+J0ojgVEpUiJOzF7gkN+UpDXujalLYdlR4q\nhkGgScXQhDYJkECC3GuvOnEo1YXGNjW9D73S6sSH+Lvqta4wW1+sTn0kB6goiQBI\n7cA1G6x3AgMBAAECggEAZPMwAX/adb7XS4LWUNH8IVyccg/63kgSteErxtiu3kRv\nYOj7W+C6fPVNGLap/RBCybjNSvIh3PfkVICh1MtG1eGXmj4VAKyvaskOmVq/hQbe\nVAuEKo7W7V2UPcKIsOsGSQUlYYjlHIIOG4O5Q1HQrRmp4cPK62Txkl6uaEkZPz4u\nbvIK2BJI8aHRwxE3Phw09blwlLqQQQ8nrhK29x5puaN+ft++IlzIOVsLz+n4kTdB\n6qkG/dhenn3K8o3+NkmSN6eNRbdJd36zXTo4Oatbvqb7r0E8vYn/3Llawo2X75zn\nec7jMHrOmcwtiu9H3PsrTWtzdSjxPHy0UtEn1HWK4QKBgQD+c/V8tAvbaUGVoZf6\ntKtDSKF6IHuY2vUO33v950mVdjrTursqOG2d+SLfSnKpc+sjDlj7/S5u4uRP+qUN\ng1rb2U7oIA7tsDa2ZTSkIx6HkPUzS+fBOxELLrbgMoJ2RLzgkiPhS95YgXJ/rYG5\nWQTehzCT5roes0RvtgM0gl3EhQKBgQDe2m7PRIU4g3RJ8HTx92B4ja8W9FVCYDG5\nPOAdZB8WB6Bvu4BJHBDLr8vDi930pKj+vYObRqBDQuILW4t8wZQJ834dnoq6EpUz\nhbVEURVBP4A/nEHrQHfq0Lp+cxThy2rw7obRQOLPETtC7p3WFgSHT6PRTcpGzCCX\n+76a30yrywKBgC/5JNtyBppDaf4QDVtTHMb+tpMT9LmI7pLzR6lDJfhr5gNtPURk\nhyY1hoGaw6t3E2n0lopL3alCVdFObDfz//lbKylQggAGLQqOYjJf/K2KgvA862Df\nBgOZtxjl7PrnUsT0SJd9elotbazsxXxwcB6UVnBMG+MV4V0+b7RCr/MRAoGBAIfp\nTcVIs7roqOZjKN9dEE/VkR/9uXW2tvyS/NfP9Ql5c0ZRYwazgCbJOwsyZRZLyek6\naWYsp5b91mA435QhdwiuoI6t30tmA+qdNBTLIpxdfvjMcoNoGPpzfBmcU/L1HW58\n+mnqGalRiAPlBQvI99ASKQWAXMnaulIWrYNEhj0LAoGBALi+QZ2pp+hDeC59ezWr\nbP1zbbONceHKGgJcevChP2k1OJyIOIqmBYeTuM4cPc5ofZYQNaMC31cs8SVeSRX1\nNTxQZmvCjMyTe/WYWYNFXdgkVz4egFXbeochCGzMYo57HV1PCkPBrARRZO8OfdDD\n8sDu//ohb7nCzceEI0DnWs13\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-dyg6p@mti-ielts.iam.gserviceaccount.com", "client_email": "firebase-adminsdk-3ml0u@storied-phalanx-349916.iam.gserviceaccount.com",
"client_id": "104980563453519094431", "client_id": "114163760341944984396",
"auth_uri": "https://accounts.google.com/o/oauth2/auth", "auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token", "token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-dyg6p%40mti-ielts.iam.gserviceaccount.com", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-3ml0u%40storied-phalanx-349916.iam.gserviceaccount.com",
"universe_domain": "googleapis.com" "universe_domain": "googleapis.com"
} }

View File

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

@@ -1,157 +1,227 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import {toast, ToastContainer} from "react-toastify"; import { toast, ToastContainer } from "react-toastify";
import axios from "axios"; import axios from "axios";
import {FormEvent, useEffect, useState} from "react"; import { FormEvent, useEffect, useState } from "react";
import Head from "next/head"; import Head from "next/head";
import useUser from "@/hooks/useUser"; import useUser from "@/hooks/useUser";
import {Divider} from "primereact/divider"; import { Divider } from "primereact/divider";
import Button from "@/components/Low/Button"; import Button from "@/components/Low/Button";
import {BsArrowRepeat} from "react-icons/bs"; import { BsArrowRepeat } from "react-icons/bs";
import Link from "next/link"; import Link from "next/link";
import Input from "@/components/Low/Input"; import Input from "@/components/Low/Input";
import {useRouter} from "next/router"; import { useRouter } from "next/router";
export function getServerSideProps({query, res}: {query: {oobCode: string; mode: string; apiKey?: string; continueUrl?: string}; res: any}) { export function getServerSideProps({
if (!query || !query.oobCode || !query.mode) { query,
res.setHeader("location", "/login"); res,
res.statusCode = 302; }: {
res.end(); query: {
return { oobCode: string;
props: {}, mode: string;
}; continueUrl?: string;
} };
res: any;
}) {
if (!query || !query.oobCode || !query.mode) {
res.setHeader("location", "/login");
res.statusCode = 302;
res.end();
return {
props: {},
};
}
return { return {
props: { props: {
code: query.oobCode, code: query.oobCode,
mode: query.mode, mode: query.mode,
apiKey: query.apiKey, ...(query.continueUrl ? { continueUrl: query.continueUrl } : {}),
...query.continueUrl ? { continueUrl: query.continueUrl } : {}, },
}, };
};
} }
export default function Reset({code, mode, apiKey, continueUrl}: {code: string; mode: string; apiKey?: string; continueUrl?: string}) { export default function Reset({
const [password, setPassword] = useState(""); code,
const [isLoading, setIsLoading] = useState(false); mode,
continueUrl,
}: {
code: string;
mode: string;
continueUrl?: string;
}) {
const [password, setPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
const router = useRouter(); const router = useRouter();
useUser({ useUser({
redirectTo: "/", redirectTo: "/",
redirectIfFound: true, redirectIfFound: true,
}); });
useEffect(() => { useEffect(() => {
if (mode === "signIn") { if (mode === "signIn") {
axios axios
.post<{ok: boolean}>("/api/reset/verify", { .post<{ ok: boolean }>("/api/reset/verify", {
email: continueUrl?.replace("https://platform.encoach.com/", ""), email: continueUrl?.replace("https://platform.encoach.com/", ""),
}) })
.then((response) => { .then((response) => {
if (response.data.ok) { if (response.data.ok) {
toast.success("Your account has been verified!", {toastId: "verify-successful"}); toast.success("Your account has been verified!", {
setTimeout(() => { toastId: "verify-successful",
router.reload(); });
}, 1000); setTimeout(() => {
return; router.reload();
} }, 1000);
return;
}
toast.error("Something went wrong! Please make sure to click the link in your e-mail again and input the correct e-mail!", { toast.error(
toastId: "verify-error", "Something went wrong! Please make sure to click the link in your e-mail again and input the correct e-mail!",
}); {
}) toastId: "verify-error",
.catch(() => { },
toast.error("Something went wrong! Please make sure to click the link in your e-mail again and input the correct e-mail!", { );
toastId: "verify-error", })
}); .catch(() => {
setIsLoading(false); toast.error(
}); "Something went wrong! Please make sure to click the link in your e-mail again and input the correct e-mail!",
} {
}); toastId: "verify-error",
},
);
setIsLoading(false);
});
}
});
const login = (e: FormEvent<HTMLFormElement>) => { const login = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
setIsLoading(true); setIsLoading(true);
axios axios
.post<{ok: boolean}>("/api/reset/confirm", {code, password}) .post<{ ok: boolean }>("/api/reset/confirm", { code, password })
.then((response) => { .then((response) => {
if (response.data.ok) { if (response.data.ok) {
toast.success("Your password has been reset!", {toastId: "reset-successful"}); toast.success("Your password has been reset!", {
setTimeout(() => { toastId: "reset-successful",
router.push("/login"); });
}, 1000); setTimeout(() => {
return; router.push("/login");
} }, 1000);
return;
}
toast.error("Something went wrong! Please make sure to click the link in your e-mail again!", {toastId: "reset-error"}); toast.error(
}) "Something went wrong! Please make sure to click the link in your e-mail again!",
.catch(() => { { toastId: "reset-error" },
toast.error("Something went wrong! Please make sure to click the link in your e-mail again!", {toastId: "reset-error"}); );
}) })
.finally(() => setIsLoading(false)); .catch(() => {
}; toast.error(
"Something went wrong! Please make sure to click the link in your e-mail again!",
{ toastId: "reset-error" },
);
})
.finally(() => setIsLoading(false));
};
return ( return (
<> <>
<Head> <Head>
<title>Reset | EnCoach</title> <title>Reset | EnCoach</title>
<meta name="description" content="Generated by create next app" /> <meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<main className="w-full h-[100vh] flex bg-white text-black"> <main className="flex h-[100vh] w-full bg-white text-black">
<ToastContainer /> <ToastContainer />
<section className="h-full w-fit min-w-fit relative hidden lg:flex"> <section className="relative hidden h-full w-fit min-w-fit lg:flex">
<div className="absolute h-full w-full bg-mti-rose-light z-10 bg-opacity-50" /> <div className="bg-mti-rose-light absolute z-10 h-full w-full bg-opacity-50" />
<img src="/people-talking-tablet.png" alt="People smiling looking at a tablet" className="h-full aspect-auto" /> <img
</section> src="/people-talking-tablet.png"
{mode === "resetPassword" && ( alt="People smiling looking at a tablet"
<section className="h-full w-full flex flex-col items-center justify-center gap-2"> className="aspect-auto h-full"
<div className="flex flex-col gap-2 items-center relative"> />
<img src="/logo_title.png" alt="EnCoach's Logo" className="w-36 lg:w-64 absolute -top-36 lg:-top-64" /> </section>
<h1 className="font-bold text-2xl lg:text-4xl">Reset your password</h1> {mode === "resetPassword" && (
<p className="self-start text-sm lg:text-base font-normal text-mti-gray-cool">to your registered Email Address</p> <section className="flex h-full w-full flex-col items-center justify-center gap-2">
</div> <div className="relative flex flex-col items-center gap-2">
<Divider className="max-w-xs lg:max-w-md" /> <img
<form className="flex flex-col items-center gap-6 w-full -lg:px-8 lg:w-1/2" onSubmit={login}> src="/logo_title.png"
<Input type="password" name="password" onChange={(e) => setPassword(e)} placeholder="Password" /> alt="EnCoach's Logo"
className="absolute -top-36 w-36 lg:-top-64 lg:w-64"
/>
<h1 className="text-2xl font-bold lg:text-4xl">
Reset your password
</h1>
<p className="text-mti-gray-cool self-start text-sm font-normal lg:text-base">
to your registered Email Address
</p>
</div>
<Divider className="max-w-xs lg:max-w-md" />
<form
className="-lg:px-8 flex w-full flex-col items-center gap-6 lg:w-1/2"
onSubmit={login}
>
<Input
type="password"
name="password"
onChange={(e) => setPassword(e)}
placeholder="Password"
/>
<Button className="mt-8 w-full" color="purple" disabled={isLoading}> <Button
{!isLoading && "Reset"} className="mt-8 w-full"
{isLoading && ( color="purple"
<div className="flex items-center justify-center"> disabled={isLoading}
<BsArrowRepeat className="text-white animate-spin" size={25} /> >
</div> {!isLoading && "Reset"}
)} {isLoading && (
</Button> <div className="flex items-center justify-center">
</form> <BsArrowRepeat
<span className="text-mti-gray-cool text-sm font-normal mt-8"> className="animate-spin text-white"
Don&apos;t have an account?{" "} size={25}
<Link className="text-mti-purple-light" href="/register"> />
Sign up </div>
</Link> )}
</span> </Button>
</section> </form>
)} <span className="text-mti-gray-cool mt-8 text-sm font-normal">
{mode === "signIn" && ( Don&apos;t have an account?{" "}
<section className="h-full w-full flex flex-col items-center justify-center gap-2"> <Link className="text-mti-purple-light" href="/register">
<div className="flex flex-col gap-2 items-center relative"> Sign up
<img src="/logo_title.png" alt="EnCoach's Logo" className="w-36 lg:w-64 absolute -top-36 lg:-top-64" /> </Link>
<h1 className="font-bold text-2xl lg:text-4xl">Confirm your account</h1> </span>
<p className="self-start text-sm lg:text-base font-normal text-mti-gray-cool">to your registered Email Address</p> </section>
</div> )}
<Divider className="max-w-xs lg:max-w-md" /> {mode === "signIn" && (
<div className="flex flex-col items-center gap-6 w-full -lg:px-8 lg:w-1/2"> <section className="flex h-full w-full flex-col items-center justify-center gap-2">
<span className="text-center"> <div className="relative flex flex-col items-center gap-2">
Your e-mail is currently being verified, please wait a second. <br /> <br /> <img
Once it has been verified, you will be redirected to the home page. src="/logo_title.png"
</span> alt="EnCoach's Logo"
</div> className="absolute -top-36 w-36 lg:-top-64 lg:w-64"
</section> />
)} <h1 className="text-2xl font-bold lg:text-4xl">
</main> Confirm your account
</> </h1>
); <p className="text-mti-gray-cool self-start text-sm font-normal lg:text-base">
to your registered Email Address
</p>
</div>
<Divider className="max-w-xs lg:max-w-md" />
<div className="-lg:px-8 flex w-full flex-col items-center gap-6 lg:w-1/2">
<span className="text-center">
Your e-mail is currently being verified, please wait a second.{" "}
<br /> <br />
Once it has been verified, you will be redirected to the home
page.
</span>
</div>
</section>
)}
</main>
</>
);
} }