- Updated the icons;

- Extracted the Layout into its own component;
This commit is contained in:
Tiago Ribeiro
2023-06-15 09:12:13 +01:00
parent ec3157870e
commit 60217e9a66
5 changed files with 281 additions and 278 deletions

View File

@@ -0,0 +1,20 @@
import {User} from "@/interfaces/user";
import Navbar from "../Navbar";
import Sidebar from "../Sidebar";
interface Props {
user: User;
children: React.ReactNode;
}
export default function Layout({user, children}: Props) {
return (
<main className="w-full h-[100vh] flex flex-col bg-mti-gray-smoke">
<Navbar user={user} />
<div className="h-full w-full flex py-4 pb-8 gap-2">
<Sidebar path={window.location.pathname} />
<div className="w-5/6 h-full mr-8 bg-white shadow-md rounded-2xl p-12 flex flex-col gap-12">{children}</div>
</div>
</main>
);
}

View File

@@ -1,7 +1,7 @@
import clsx from "clsx"; import clsx from "clsx";
import {IconType} from "react-icons"; import {IconType} from "react-icons";
import {MdSpaceDashboard} from "react-icons/md"; import {MdSpaceDashboard} from "react-icons/md";
import {BsFileEarmarkText, BsClockHistory} from "react-icons/bs"; import {BsFileEarmarkText, BsClockHistory, BsPencil, BsGraphUp} from "react-icons/bs";
import {RiLogoutBoxFill} from "react-icons/ri"; import {RiLogoutBoxFill} from "react-icons/ri";
import {SlPencil} from "react-icons/sl"; import {SlPencil} from "react-icons/sl";
import {FaAward} from "react-icons/fa"; import {FaAward} from "react-icons/fa";
@@ -46,8 +46,8 @@ export default function Sidebar({path}: Props) {
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<Nav Icon={MdSpaceDashboard} label="Dashboard" path={path} keyPath="/" /> <Nav Icon={MdSpaceDashboard} label="Dashboard" path={path} keyPath="/" />
<Nav Icon={BsFileEarmarkText} label="Exams" path={path} keyPath="/exam" /> <Nav Icon={BsFileEarmarkText} label="Exams" path={path} keyPath="/exam" />
<Nav Icon={SlPencil} label="Exercises" path={path} keyPath="/exercise" /> <Nav Icon={BsPencil} label="Exercises" path={path} keyPath="/exercise" />
<Nav Icon={FaAward} label="Score" path={path} keyPath="/score" /> <Nav Icon={BsGraphUp} label="Stats" path={path} keyPath="/stats" />
<Nav Icon={BsClockHistory} label="Record" path={path} keyPath="/record" /> <Nav Icon={BsClockHistory} label="Record" path={path} keyPath="/record" />
</div> </div>

View File

@@ -20,6 +20,7 @@ import {v4 as uuidv4} from "uuid";
import useUser from "@/hooks/useUser"; import useUser from "@/hooks/useUser";
import useExamStore from "@/stores/examStore"; import useExamStore from "@/stores/examStore";
import Sidebar from "@/components/Sidebar"; import Sidebar from "@/components/Sidebar";
import Layout from "@/components/High/Layout";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
@@ -208,15 +209,7 @@ export default function Page() {
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<ToastContainer /> <ToastContainer />
{user && ( {user && <Layout user={user}>{renderScreen()}</Layout>}
<main className="w-full h-[100vh] flex flex-col bg-mti-gray-smoke">
<Navbar user={user} />
<div className="h-full w-full flex py-4 pb-8 gap-2">
<Sidebar path="/exam" />
<div className="w-5/6 h-full mr-8 bg-white shadow-md rounded-2xl p-12 flex flex-col gap-12">{renderScreen()}</div>
</div>
</main>
)}
</> </>
); );
} }

View File

@@ -14,6 +14,7 @@ import {ToastContainer} from "react-toastify";
import {capitalize} from "lodash"; import {capitalize} from "lodash";
import {Module} from "@/interfaces"; import {Module} from "@/interfaces";
import ProgressBar from "@/components/Low/ProgressBar"; import ProgressBar from "@/components/Low/ProgressBar";
import Layout from "@/components/High/Layout";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
@@ -79,160 +80,154 @@ export default function Home() {
</Head> </Head>
<ToastContainer /> <ToastContainer />
{user && ( {user && (
<main className="w-full h-[100vh] flex flex-col bg-mti-gray-smoke"> <Layout user={user}>
<Navbar user={user} /> <section className="w-full flex gap-8">
<div className="h-full w-full flex py-4 pb-8 gap-2"> <img src={user.profilePicture} alt={user.name} className="aspect-square h-64 rounded-3xl drop-shadow-xl" />
<Sidebar path="/" /> <div className="flex flex-col gap-4 py-4 w-full">
<div className="w-5/6 h-full mr-8 bg-white shadow-md rounded-2xl p-12 flex flex-col gap-12"> <div className="flex justify-between w-full gap-8">
<section className="w-full flex gap-8"> <div className="flex flex-col gap-2 py-2">
<img src={user.profilePicture} alt={user.name} className="aspect-square h-64 rounded-3xl drop-shadow-xl" /> <h1 className="font-bold text-4xl">{user.name}</h1>
<div className="flex flex-col gap-4 py-4 w-full"> <h6 className="font-normal text-base text-mti-gray-taupe">{capitalize(user.type)}</h6>
<div className="flex justify-between w-full gap-8"> </div>
<div className="flex flex-col gap-2 py-2"> <ProgressBar
<h1 className="font-bold text-4xl">{user.name}</h1> label={`Level ${calculateAverageLevel().toFixed(1)}`}
<h6 className="font-normal text-base text-mti-gray-taupe">{capitalize(user.type)}</h6> percentage={Math.round((calculateAverageLevel() * 100) / 9)}
</div> color="blue"
<ProgressBar className="max-w-xs w-32 self-end h-10"
label={`Level ${calculateAverageLevel().toFixed(1)}`} />
percentage={Math.round((calculateAverageLevel() * 100) / 9)} </div>
color="blue" <ProgressBar label="" percentage={70} color="blue" className="w-full h-3 drop-shadow-lg" />
className="max-w-xs w-32 self-end h-10" <div className="flex justify-between w-full mt-8">
/> <div className="flex gap-4 items-center">
<div className="w-16 h-16 border border-mti-gray-platinum bg-mti-gray-smoke flex items-center justify-center rounded-xl">
<BsFileEarmarkText className="w-8 h-8 text-mti-blue-light" />
</div> </div>
<ProgressBar label="" percentage={70} color="blue" className="w-full h-3 drop-shadow-lg" /> <div className="flex flex-col">
<div className="flex justify-between w-full mt-8"> <span className="font-bold text-xl">{totalExams(stats)}</span>
<div className="flex gap-4 items-center"> <span className="font-normal text-base text-mti-gray-dim">Exams</span>
<div className="w-16 h-16 border border-mti-gray-platinum bg-mti-gray-smoke flex items-center justify-center rounded-xl">
<BsFileEarmarkText className="w-8 h-8 text-mti-blue-light" />
</div>
<div className="flex flex-col">
<span className="font-bold text-xl">{totalExams(stats)}</span>
<span className="font-normal text-base text-mti-gray-dim">Exams</span>
</div>
</div>
<div className="flex gap-4 items-center">
<div className="w-16 h-16 border border-mti-gray-platinum bg-mti-gray-smoke flex items-center justify-center rounded-xl">
<BsPencil className="w-8 h-8 text-mti-blue-light" />
</div>
<div className="flex flex-col">
<span className="font-bold text-xl">{stats.length}</span>
<span className="font-normal text-base text-mti-gray-dim">Exercises</span>
</div>
</div>
<div className="flex gap-4 items-center">
<div className="w-16 h-16 border border-mti-gray-platinum bg-mti-gray-smoke flex items-center justify-center rounded-xl">
<BsStar className="w-8 h-8 text-mti-blue-light" />
</div>
<div className="flex flex-col">
<span className="font-bold text-xl">{averageScore(stats)}%</span>
<span className="font-normal text-base text-mti-gray-dim">Average Score</span>
</div>
</div>
</div> </div>
</div> </div>
</section> <div className="flex gap-4 items-center">
<section className="flex flex-col gap-3"> <div className="w-16 h-16 border border-mti-gray-platinum bg-mti-gray-smoke flex items-center justify-center rounded-xl">
<span className="font-bold text-lg">Bio</span> <BsPencil className="w-8 h-8 text-mti-blue-light" />
<span className="text-mti-gray-taupe">
Patricia Smith is a dedicated and enthusiastic student. Her passion for knowledge drives her to constantly seek
new academic challenges. She is recognized for her exemplary work ethic, active participation in the classroom,
and commitment to helping her peers. Her insatiable curiosity has led her to explore a wide range of areas of
study, making her a versatile and adaptable learner. Patricia is a true academic leader, inspiring other students
to pursue their own educational goals.
</span>
</section>
<section className="flex flex-col gap-3">
<span className="font-bold text-lg">Score History</span>
<div className="grid grid-cols-2 gap-6">
<div className="border border-mti-gray-anti-flash rounded-xl flex flex-col gap-2 p-4">
<div className="flex gap-3 items-center">
<div className="w-12 h-12 bg-mti-gray-smoke flex items-center justify-center rounded-xl">
<BsBook className="text-ielts-reading w-5 h-5" />
</div>
<div className="flex justify-between w-full">
<span className="font-extrabold text-sm">Reading</span>
<span className="text-sm font-normal text-mti-gray-dim">
Level {user.levels.reading} / Level {user.desiredLevels.reading}
</span>
</div>
</div>
<div className="pl-14">
<ProgressBar
color="blue"
label=""
percentage={Math.round((user.levels.reading * 100) / user.desiredLevels.reading)}
className="w-full h-2"
/>
</div>
</div> </div>
<div className="border border-mti-gray-anti-flash rounded-xl flex flex-col gap-2 p-4"> <div className="flex flex-col">
<div className="flex gap-3 items-center"> <span className="font-bold text-xl">{stats.length}</span>
<div className="w-12 h-12 bg-mti-gray-smoke flex items-center justify-center rounded-xl"> <span className="font-normal text-base text-mti-gray-dim">Exercises</span>
<BsPen className="text-ielts-writing w-5 h-5" />
</div>
<div className="flex justify-between w-full">
<span className="font-extrabold text-sm">Writing</span>
<span className="text-sm font-normal text-mti-gray-dim">
Level {user.levels.writing} / Level {user.desiredLevels.writing}
</span>
</div>
</div>
<div className="pl-14">
<ProgressBar
color="blue"
label=""
percentage={Math.round((user.levels.writing * 100) / user.desiredLevels.writing)}
className="w-full h-2"
/>
</div>
</div>
<div className="border border-mti-gray-anti-flash rounded-xl flex flex-col gap-2 p-4">
<div className="flex gap-3 items-center">
<div className="w-12 h-12 bg-mti-gray-smoke flex items-center justify-center rounded-xl">
<BsHeadphones className="text-ielts-listening w-5 h-5" />
</div>
<div className="flex justify-between w-full">
<span className="font-extrabold text-sm">Listening</span>
<span className="text-sm font-normal text-mti-gray-dim">
Level {user.levels.listening} / Level {user.desiredLevels.listening}
</span>
</div>
</div>
<div className="pl-14">
<ProgressBar
color="blue"
label=""
percentage={Math.round((user.levels.listening * 100) / user.desiredLevels.listening)}
className="w-full h-2"
/>
</div>
</div>
<div className="border border-mti-gray-anti-flash rounded-xl flex flex-col gap-2 p-4">
<div className="flex gap-3 items-center">
<div className="w-12 h-12 bg-mti-gray-smoke flex items-center justify-center rounded-xl">
<BsMegaphone className="text-ielts-speaking w-5 h-5" />
</div>
<div className="flex justify-between w-full">
<span className="font-extrabold text-sm">Speaking</span>
<span className="text-sm font-normal text-mti-gray-dim">
Level {user.levels.speaking} / Level {user.desiredLevels.speaking}
</span>
</div>
</div>
<div className="pl-14">
<ProgressBar
color="blue"
label=""
percentage={Math.round((user.levels.speaking * 100) / user.desiredLevels.speaking)}
className="w-full h-2"
/>
</div>
</div> </div>
</div> </div>
</section> <div className="flex gap-4 items-center">
<div className="w-16 h-16 border border-mti-gray-platinum bg-mti-gray-smoke flex items-center justify-center rounded-xl">
<BsStar className="w-8 h-8 text-mti-blue-light" />
</div>
<div className="flex flex-col">
<span className="font-bold text-xl">{averageScore(stats)}%</span>
<span className="font-normal text-base text-mti-gray-dim">Average Score</span>
</div>
</div>
</div>
</div> </div>
</div> </section>
</main> <section className="flex flex-col gap-3">
<span className="font-bold text-lg">Bio</span>
<span className="text-mti-gray-taupe">
Patricia Smith is a dedicated and enthusiastic student. Her passion for knowledge drives her to constantly seek new
academic challenges. She is recognized for her exemplary work ethic, active participation in the classroom, and commitment
to helping her peers. Her insatiable curiosity has led her to explore a wide range of areas of study, making her a
versatile and adaptable learner. Patricia is a true academic leader, inspiring other students to pursue their own
educational goals.
</span>
</section>
<section className="flex flex-col gap-3">
<span className="font-bold text-lg">Score History</span>
<div className="grid grid-cols-2 gap-6">
<div className="border border-mti-gray-anti-flash rounded-xl flex flex-col gap-2 p-4">
<div className="flex gap-3 items-center">
<div className="w-12 h-12 bg-mti-gray-smoke flex items-center justify-center rounded-xl">
<BsBook className="text-ielts-reading w-5 h-5" />
</div>
<div className="flex justify-between w-full">
<span className="font-extrabold text-sm">Reading</span>
<span className="text-sm font-normal text-mti-gray-dim">
Level {user.levels.reading} / Level {user.desiredLevels.reading}
</span>
</div>
</div>
<div className="pl-14">
<ProgressBar
color="blue"
label=""
percentage={Math.round((user.levels.reading * 100) / user.desiredLevels.reading)}
className="w-full h-2"
/>
</div>
</div>
<div className="border border-mti-gray-anti-flash rounded-xl flex flex-col gap-2 p-4">
<div className="flex gap-3 items-center">
<div className="w-12 h-12 bg-mti-gray-smoke flex items-center justify-center rounded-xl">
<BsPen className="text-ielts-writing w-5 h-5" />
</div>
<div className="flex justify-between w-full">
<span className="font-extrabold text-sm">Writing</span>
<span className="text-sm font-normal text-mti-gray-dim">
Level {user.levels.writing} / Level {user.desiredLevels.writing}
</span>
</div>
</div>
<div className="pl-14">
<ProgressBar
color="blue"
label=""
percentage={Math.round((user.levels.writing * 100) / user.desiredLevels.writing)}
className="w-full h-2"
/>
</div>
</div>
<div className="border border-mti-gray-anti-flash rounded-xl flex flex-col gap-2 p-4">
<div className="flex gap-3 items-center">
<div className="w-12 h-12 bg-mti-gray-smoke flex items-center justify-center rounded-xl">
<BsHeadphones className="text-ielts-listening w-5 h-5" />
</div>
<div className="flex justify-between w-full">
<span className="font-extrabold text-sm">Listening</span>
<span className="text-sm font-normal text-mti-gray-dim">
Level {user.levels.listening} / Level {user.desiredLevels.listening}
</span>
</div>
</div>
<div className="pl-14">
<ProgressBar
color="blue"
label=""
percentage={Math.round((user.levels.listening * 100) / user.desiredLevels.listening)}
className="w-full h-2"
/>
</div>
</div>
<div className="border border-mti-gray-anti-flash rounded-xl flex flex-col gap-2 p-4">
<div className="flex gap-3 items-center">
<div className="w-12 h-12 bg-mti-gray-smoke flex items-center justify-center rounded-xl">
<BsMegaphone className="text-ielts-speaking w-5 h-5" />
</div>
<div className="flex justify-between w-full">
<span className="font-extrabold text-sm">Speaking</span>
<span className="text-sm font-normal text-mti-gray-dim">
Level {user.levels.speaking} / Level {user.desiredLevels.speaking}
</span>
</div>
</div>
<div className="pl-14">
<ProgressBar
color="blue"
label=""
percentage={Math.round((user.levels.speaking * 100) / user.desiredLevels.speaking)}
className="w-full h-2"
/>
</div>
</div>
</div>
</section>
</Layout>
)} )}
</> </>
); );

View File

@@ -10,6 +10,7 @@ import Sidebar from "@/components/Sidebar";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import {BsCheckCircleFill, BsMicFill, BsPauseCircle, BsPauseFill, BsPlayCircle, BsPlayFill, BsTrashFill} from "react-icons/bs"; import {BsCheckCircleFill, BsMicFill, BsPauseCircle, BsPauseFill, BsPlayCircle, BsPlayFill, BsTrashFill} from "react-icons/bs";
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import Layout from "@/components/High/Layout";
const Waveform = dynamic(() => import("../components/Waveform"), {ssr: false}); const Waveform = dynamic(() => import("../components/Waveform"), {ssr: false});
const ReactMediaRecorder = dynamic(() => import("react-media-recorder").then((mod) => mod.ReactMediaRecorder), { const ReactMediaRecorder = dynamic(() => import("react-media-recorder").then((mod) => mod.ReactMediaRecorder), {
@@ -67,128 +68,122 @@ export default function Page() {
</Head> </Head>
<ToastContainer /> <ToastContainer />
{user && ( {user && (
<main className="w-full h-[100vh] flex flex-col bg-mti-gray-smoke"> <Layout user={user}>
<Navbar user={user} /> <ReactMediaRecorder
<div className="h-full w-full flex py-4 pb-8 gap-2"> audio
<Sidebar path="/exam" /> onStop={(blob) => setMediaBlob(blob)}
<div className="w-5/6 h-full mr-8 bg-white shadow-md rounded-2xl p-12 flex flex-col gap-12"> render={({status, startRecording, stopRecording, pauseRecording, resumeRecording, clearBlobUrl, mediaBlobUrl}) => (
<ReactMediaRecorder <div className="w-full p-4 px-8 bg-transparent border-2 border-mti-gray-platinum rounded-2xl flex-col gap-8 items-center">
audio <p className="text-base font-normal">Record your answer:</p>
onStop={(blob) => setMediaBlob(blob)} <div className="flex gap-8 items-center justify-center py-8">
render={({status, startRecording, stopRecording, pauseRecording, resumeRecording, clearBlobUrl, mediaBlobUrl}) => ( {status === "idle" && (
<div className="w-full p-4 px-8 bg-transparent border-2 border-mti-gray-platinum rounded-2xl flex-col gap-8 items-center"> <>
<p className="text-base font-normal">Record your answer:</p> <div className="w-full h-2 max-w-4xl bg-mti-gray-smoke rounded-full" />
<div className="flex gap-8 items-center justify-center py-8">
{status === "idle" && ( {status === "idle" && (
<> <BsMicFill
<div className="w-full h-2 max-w-4xl bg-mti-gray-smoke rounded-full" /> onClick={() => {
{status === "idle" && ( setRecordingDuration(0);
<BsMicFill startRecording();
onClick={() => { setIsRecording(true);
setRecordingDuration(0); }}
startRecording(); className="h-5 w-5 text-mti-gray-cool cursor-pointer"
setIsRecording(true); />
}}
className="h-5 w-5 text-mti-gray-cool cursor-pointer"
/>
)}
</>
)} )}
{status === "recording" && ( </>
<> )}
<div className="flex gap-4 items-center"> {status === "recording" && (
<span className="text-xs w-9"> <>
{Math.round(recordingDuration / 60) <div className="flex gap-4 items-center">
.toString(10) <span className="text-xs w-9">
.padStart(2, "0")} {Math.round(recordingDuration / 60)
: .toString(10)
{Math.round(recordingDuration % 60) .padStart(2, "0")}
.toString(10) :
.padStart(2, "0")} {Math.round(recordingDuration % 60)
</span> .toString(10)
</div> .padStart(2, "0")}
<div className="w-full h-2 max-w-4xl bg-mti-gray-smoke rounded-full" /> </span>
<div className="flex gap-4 items-center"> </div>
<BsPauseCircle <div className="w-full h-2 max-w-4xl bg-mti-gray-smoke rounded-full" />
onClick={() => { <div className="flex gap-4 items-center">
setIsRecording(false); <BsPauseCircle
pauseRecording(); onClick={() => {
}} setIsRecording(false);
className="text-red-500 w-8 h-8 cursor-pointer" pauseRecording();
/> }}
<BsCheckCircleFill className="text-red-500 w-8 h-8 cursor-pointer"
onClick={() => { />
setIsRecording(false); <BsCheckCircleFill
stopRecording(); onClick={() => {
}} setIsRecording(false);
className="text-mti-green-light w-8 h-8 cursor-pointer" stopRecording();
/> }}
</div> className="text-mti-green-light w-8 h-8 cursor-pointer"
</> />
)} </div>
{status === "paused" && ( </>
<> )}
<div className="flex gap-4 items-center"> {status === "paused" && (
<span className="text-xs w-9"> <>
{Math.round(recordingDuration / 60) <div className="flex gap-4 items-center">
.toString(10) <span className="text-xs w-9">
.padStart(2, "0")} {Math.round(recordingDuration / 60)
: .toString(10)
{Math.round(recordingDuration % 60) .padStart(2, "0")}
.toString(10) :
.padStart(2, "0")} {Math.round(recordingDuration % 60)
</span> .toString(10)
</div> .padStart(2, "0")}
<div className="w-full h-2 max-w-4xl bg-mti-gray-smoke rounded-full" /> </span>
<div className="flex gap-4 items-center"> </div>
<BsPlayCircle <div className="w-full h-2 max-w-4xl bg-mti-gray-smoke rounded-full" />
onClick={() => { <div className="flex gap-4 items-center">
setIsRecording(true); <BsPlayCircle
resumeRecording(); onClick={() => {
}} setIsRecording(true);
className="text-mti-green-light w-8 h-8 cursor-pointer" resumeRecording();
/> }}
<BsCheckCircleFill className="text-mti-green-light w-8 h-8 cursor-pointer"
onClick={() => { />
setIsRecording(false); <BsCheckCircleFill
stopRecording(); onClick={() => {
}} setIsRecording(false);
className="text-mti-green-light w-8 h-8 cursor-pointer" stopRecording();
/> }}
</div> className="text-mti-green-light w-8 h-8 cursor-pointer"
</> />
)} </div>
{status === "stopped" && mediaBlobUrl && ( </>
<> )}
<Waveform audio={mediaBlobUrl} waveColor="#FCDDEC" progressColor="#EF5DA8" /> {status === "stopped" && mediaBlobUrl && (
<div className="flex gap-4 items-center"> <>
<BsTrashFill <Waveform audio={mediaBlobUrl} waveColor="#FCDDEC" progressColor="#EF5DA8" />
className="text-mti-gray-cool cursor-pointer w-5 h-5" <div className="flex gap-4 items-center">
onClick={() => { <BsTrashFill
setRecordingDuration(0); className="text-mti-gray-cool cursor-pointer w-5 h-5"
clearBlobUrl(); onClick={() => {
}} setRecordingDuration(0);
/> clearBlobUrl();
}}
/>
<BsMicFill <BsMicFill
onClick={() => { onClick={() => {
clearBlobUrl(); clearBlobUrl();
setRecordingDuration(0); setRecordingDuration(0);
startRecording(); startRecording();
setIsRecording(true); setIsRecording(true);
}} }}
className="h-5 w-5 text-mti-gray-cool cursor-pointer" className="h-5 w-5 text-mti-gray-cool cursor-pointer"
/> />
</div> </div>
</> </>
)} )}
</div> </div>
</div> </div>
)} )}
/> />
</div> </Layout>
</div>
</main>
)} )}
</> </>
); );