Made some of the code a bit more responsive
This commit is contained in:
@@ -44,20 +44,20 @@ function WordsPopout({words, isOpen, onCancel, onAnswer}: WordsPopoutProps) {
|
|||||||
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-900">
|
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-900">
|
||||||
List of words
|
List of words
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-4 grid grid-cols-3 gap-4">
|
<div className="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{words.map((word) => (
|
{words.map((word) => (
|
||||||
<button
|
<button
|
||||||
key={word.word}
|
key={word.word}
|
||||||
onClick={() => onAnswer(word.word)}
|
onClick={() => onAnswer(word.word)}
|
||||||
disabled={word.isDisabled}
|
disabled={word.isDisabled}
|
||||||
className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)}>
|
className={clsx("btn sm:btn-wide gap-4 relative text-white", infoButtonStyle)}>
|
||||||
{word.word}
|
{word.word}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 self-end">
|
<div className="mt-4 self-end">
|
||||||
<button onClick={onCancel} className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)}>
|
<button onClick={onCancel} className={clsx("btn md:btn-wide gap-4 relative text-white", errorButtonStyle)}>
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,7 +100,7 @@ export default function FillBlanks({id, allowRepetition, prompt, solutions, text
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col gap-4">
|
||||||
<WordsPopout
|
<WordsPopout
|
||||||
words={words.map((word) => ({word, isDisabled: allowRepetition ? false : userSolutions.map((x) => x.solution).includes(word)}))}
|
words={words.map((word) => ({word, isDisabled: allowRepetition ? false : userSolutions.map((x) => x.solution).includes(word)}))}
|
||||||
isOpen={!!currentBlankId}
|
isOpen={!!currentBlankId}
|
||||||
@@ -110,7 +110,7 @@ export default function FillBlanks({id, allowRepetition, prompt, solutions, text
|
|||||||
setCurrentBlankId(undefined);
|
setCurrentBlankId(undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="text-lg font-medium text-center px-48">
|
<span className="text-base md:text-lg font-medium text-center px-2 md:px-4 lg:px-48">
|
||||||
{prompt.split("\\n").map((line, index) => (
|
{prompt.split("\\n").map((line, index) => (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
{line}
|
{line}
|
||||||
@@ -128,7 +128,7 @@ export default function FillBlanks({id, allowRepetition, prompt, solutions, text
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex gap-8">
|
<div className="self-end flex flex-col items-center md:items-start md:flex-row gap-8">
|
||||||
<button className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)} onClick={onBack}>
|
<button className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)} onClick={onBack}>
|
||||||
<div className="absolute left-4">
|
<div className="absolute left-4">
|
||||||
<Icon path={mdiArrowLeft} color="white" size={1} />
|
<Icon path={mdiArrowLeft} color="white" size={1} />
|
||||||
|
|||||||
@@ -8,5 +8,5 @@ interface Props {
|
|||||||
|
|
||||||
export default function LevelLabel({experience, className}: Props) {
|
export default function LevelLabel({experience, className}: Props) {
|
||||||
const {label} = levelCalculator(experience);
|
const {label} = levelCalculator(experience);
|
||||||
return <span className={clsx("text-xl font-semibold text-success", className)}>{label}</span>;
|
return <span className={clsx("text-base md:text-xl font-semibold text-success", className)}>{label}</span>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default function LevelProgressBar({experience, className, progressBarWidt
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx("flex flex-col items-center", className)}>
|
<div className={clsx("flex flex-col items-center", className)}>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center text-sm md:text-base">
|
||||||
<span>Lvl. {levelResult.currentLevel}</span>
|
<span>Lvl. {levelResult.currentLevel}</span>
|
||||||
<progress className={clsx("progress progress-success", progressBarWidth)} value={levelResult.percentage} max="100" />
|
<progress className={clsx("progress progress-success", progressBarWidth)} value={levelResult.percentage} max="100" />
|
||||||
<span>Lvl. {levelResult.nextLevel}</span>
|
<span>Lvl. {levelResult.nextLevel}</span>
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {useRouter} from "next/router";
|
import {useRouter} from "next/router";
|
||||||
|
import {Button} from "primereact/button";
|
||||||
import {Menubar} from "primereact/menubar";
|
import {Menubar} from "primereact/menubar";
|
||||||
import {MenuItem} from "primereact/menuitem";
|
import {MenuItem} from "primereact/menuitem";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
profilePicture: string;
|
profilePicture: string;
|
||||||
timer?: number;
|
timer?: number;
|
||||||
|
showExamEnd?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable @next/next/no-img-element */
|
/* eslint-disable @next/next/no-img-element */
|
||||||
export default function Navbar({profilePicture, timer}: Props) {
|
export default function Navbar({profilePicture, timer, showExamEnd = false}: Props) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
@@ -50,7 +52,7 @@ export default function Navbar({profilePicture, timer}: Props) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const end = timer && (
|
const endTimer = timer && (
|
||||||
<span className="pr-2 font-semibold">
|
<span className="pr-2 font-semibold">
|
||||||
{Math.floor(timer / 60) < 10 ? "0" : ""}
|
{Math.floor(timer / 60) < 10 ? "0" : ""}
|
||||||
{Math.floor(timer / 60)}:{timer % 60 < 10 ? "0" : ""}
|
{Math.floor(timer / 60)}:{timer % 60 < 10 ? "0" : ""}
|
||||||
@@ -58,9 +60,15 @@ export default function Navbar({profilePicture, timer}: Props) {
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const endNewExam = (
|
||||||
|
<Link href="/exam" className="pr-2">
|
||||||
|
<Button text label="Exam" severity="secondary" size="small" />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-neutral-100 z-10 w-full p-2">
|
<div className="bg-neutral-100 z-10 w-full p-2">
|
||||||
<Menubar model={items} end={end} />
|
<Menubar model={items} end={showExamEnd ? endNewExam : endTimer} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ interface Props {
|
|||||||
|
|
||||||
export default function ProfileCard({user, className}: Props) {
|
export default function ProfileCard({user, className}: Props) {
|
||||||
return (
|
return (
|
||||||
<div className={clsx("bg-white drop-shadow-xl p-8 rounded-xl w-full flex flex-col gap-6", className)}>
|
<div className={clsx("bg-white drop-shadow-xl p-4 md:p-8 rounded-xl w-full flex flex-col gap-6", className)}>
|
||||||
<div className="flex w-full items-center gap-8">
|
<div className="flex w-full items-center gap-8">
|
||||||
<div className="w-24 rounded-full border-4 border-white drop-shadow-xl">
|
<div className="w-16 md:w-24 rounded-full border-2 md:border-4 border-white drop-shadow-md md:drop-shadow-xl">
|
||||||
<img src={user.profilePicture} alt="Profile picture" className="rounded-full" />
|
<img src={user.profilePicture} alt="Profile picture" className="rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col justify-center">
|
<div className="flex flex-col justify-center">
|
||||||
<span className="text-neutral-600 font-bold text-2xl">{user.name}</span>
|
<span className="text-neutral-600 font-bold text-xl lg:text-2xl">{user.name}</span>
|
||||||
<LevelLabel experience={user.experience} />
|
<LevelLabel experience={user.experience} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<LevelProgressBar experience={user.experience} progressBarWidth="w-96" />
|
<LevelProgressBar experience={user.experience} progressBarWidth="w-32 md:w-96" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,10 +52,9 @@ export default function Finish({user, scores, modules, onViewResults}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full relative">
|
<div className="w-full h-full relative">
|
||||||
<section className="h-full w-full flex flex-col items-center justify-center">
|
<section className="h-full w-full flex flex-col items-center justify-center gap-4">
|
||||||
<ProfileLevel user={user} className="h-1/2" />
|
<ProfileLevel user={user} className="h-1/2" />
|
||||||
<div className="h-2/3 w-1/2 flex flex-col items-center gap-4">
|
<div className="h-2/3 w-1/2 flex flex-col items-center gap-4">
|
||||||
<h1>You have finished the exam!</h1>
|
|
||||||
<div className="rounded-xl p-4 items-center flex justify-center gap-4">
|
<div className="rounded-xl p-4 items-center flex justify-center gap-4">
|
||||||
{modules.map((module) => (
|
{modules.map((module) => (
|
||||||
<div
|
<div
|
||||||
@@ -70,7 +69,7 @@ export default function Finish({user, scores, modules, onViewResults}: Props) {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex justify-between mt-24">
|
<div className="w-full flex flex-col gap-4 lg:gap-0 md:flex-row justify-center items-center lg:justify-between">
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<button className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)}>
|
<button className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)}>
|
||||||
<div className="absolute left-4">
|
<div className="absolute left-4">
|
||||||
|
|||||||
@@ -100,27 +100,27 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderText = () => (
|
const renderText = () => (
|
||||||
<>
|
<div className="flex flex-col gap-4 w-full">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col w-full gap-2">
|
||||||
<span className="text-lg font-semibold">
|
<span className="text-base md:text-lg font-semibold">
|
||||||
Please read the following excerpt attentively, you will then be asked questions about the text you've read.
|
Please read the following excerpt attentively, you will then be asked questions about the text you've read.
|
||||||
</span>
|
</span>
|
||||||
<span className="self-end text-sm">You will be allowed to read the text while doing the exercises</span>
|
<span className="self-end text-sm">You will be allowed to read the text while doing the exercises</span>
|
||||||
</div>
|
</div>
|
||||||
<Panel header={exam.text.title} className="overflow-scroll">
|
<Panel header={exam.text.title}>
|
||||||
<p className="overflow-scroll">
|
<p className="overflow-auto">
|
||||||
{exam.text.content.split("\\n").map((line, index) => (
|
{exam.text.content.split("\\n").map((line, index) => (
|
||||||
<p key={index}>{line}</p>
|
<p key={index}>{line}</p>
|
||||||
))}
|
))}
|
||||||
</p>
|
</p>
|
||||||
</Panel>
|
</Panel>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TextModal {...exam.text} isOpen={showTextModal} onClose={() => setShowTextModal(false)} />
|
<TextModal {...exam.text} isOpen={showTextModal} onClose={() => setShowTextModal(false)} />
|
||||||
<div className="w-full h-full relative flex flex-col gap-8 items-center justify-center p-8 px-16 overflow-hidden">
|
<div className="w-full h-full relative flex flex-col gap-8 items-center justify-center p-2 md:p-8 px-4 md:px-16 overflow-hidden">
|
||||||
{exerciseIndex === -1 && renderText()}
|
{exerciseIndex === -1 && renderText()}
|
||||||
{exerciseIndex > -1 &&
|
{exerciseIndex > -1 &&
|
||||||
exerciseIndex < exam.exercises.length &&
|
exerciseIndex < exam.exercises.length &&
|
||||||
@@ -130,7 +130,8 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
|
|||||||
exerciseIndex < exam.exercises.length &&
|
exerciseIndex < exam.exercises.length &&
|
||||||
showSolutions &&
|
showSolutions &&
|
||||||
renderSolution(exam.exercises[exerciseIndex], nextExercise, previousExercise)}
|
renderSolution(exam.exercises[exerciseIndex], nextExercise, previousExercise)}
|
||||||
<div className={clsx("flex gap-8", exerciseIndex > -1 ? "w-full justify-between" : "self-end")}>
|
<div
|
||||||
|
className={clsx("flex gap-8", exerciseIndex > -1 ? "w-full justify-center md:justify-between" : "self-end w-full md:w-fit flex")}>
|
||||||
{exerciseIndex > -1 && (
|
{exerciseIndex > -1 && (
|
||||||
<button
|
<button
|
||||||
className={clsx(
|
className={clsx(
|
||||||
@@ -145,7 +146,9 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{exerciseIndex === -1 && (
|
{exerciseIndex === -1 && (
|
||||||
<button className={clsx("btn btn-wide gap-4 relative text-white self-end", infoButtonStyle)} onClick={() => nextExercise()}>
|
<button
|
||||||
|
className={clsx("btn w-full md:btn-wide gap-4 relative text-white self-end", infoButtonStyle)}
|
||||||
|
onClick={() => nextExercise()}>
|
||||||
Next
|
Next
|
||||||
<div className="absolute right-4">
|
<div className="absolute right-4">
|
||||||
<Icon path={mdiArrowRight} color="white" size={1} />
|
<Icon path={mdiArrowRight} color="white" size={1} />
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default function Selection({user, onStart}: Props) {
|
|||||||
<section className="h-full w-full flex flex-col items-center justify-center gap-8">
|
<section className="h-full w-full flex flex-col items-center justify-center gap-8">
|
||||||
<ProfileLevel user={user} className="h-1/2" />
|
<ProfileLevel user={user} className="h-1/2" />
|
||||||
<div className="h-1/2 flex flex-col gap-8">
|
<div className="h-1/2 flex flex-col gap-8">
|
||||||
<div className="h-1/2 items-center flex flex-col md:flex-row gap-8">
|
<div className="h-1/2 items-center flex flex-col lg:flex-row gap-8">
|
||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default function Page({user}: {user: User}) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (exam) {
|
if (exam) {
|
||||||
setTimer(exam.minTimer * 60);
|
setTimer(exam.minTimer * 60);
|
||||||
const timerInterval = setInterval(() => setTimer((prev) => prev && prev - 1), 1000);
|
const timerInterval = setInterval(() => setTimer((prev) => (prev && prev > 0 ? prev - 1 : 0)), 1000);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(timerInterval);
|
clearInterval(timerInterval);
|
||||||
@@ -158,9 +158,9 @@ export default function Page({user}: {user: User}) {
|
|||||||
<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-full md:h-screen flex flex-col items-center bg-neutral-100 text-black">
|
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
<Navbar profilePicture={user.profilePicture} timer={exam && timer} />
|
<main className="w-full h-full min-h-[100vh] flex flex-col items-center bg-neutral-100 text-black pb-4 gap-4">
|
||||||
|
<Navbar profilePicture={user.profilePicture} timer={exam ? timer : undefined} />
|
||||||
{renderScreen()}
|
{renderScreen()}
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import JSON_RESULTS from "@/demo/user_results.json";
|
|||||||
import {withIronSessionSsr} from "iron-session/next";
|
import {withIronSessionSsr} from "iron-session/next";
|
||||||
import {sessionOptions} from "@/lib/session";
|
import {sessionOptions} from "@/lib/session";
|
||||||
import {User} from "@/interfaces/user";
|
import {User} from "@/interfaces/user";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
|
||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
@@ -36,6 +37,10 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
|||||||
}, sessionOptions);
|
}, sessionOptions);
|
||||||
|
|
||||||
export default function Home({user}: {user: User}) {
|
export default function Home({user}: {user: User}) {
|
||||||
|
const [showEndExam, setShowEndExam] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => setShowEndExam(window.innerWidth <= 960), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@@ -47,20 +52,14 @@ export default function Home({user}: {user: User}) {
|
|||||||
<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-screen flex flex-col items-center bg-neutral-100">
|
<main className="w-full h-full min-h-[100vh] flex flex-col items-center bg-neutral-100">
|
||||||
<Navbar profilePicture={user.profilePicture} />
|
<Navbar profilePicture={user.profilePicture} showExamEnd={showEndExam} />
|
||||||
<div className="w-full h-full p-4 relative">
|
<div className="w-full h-full p-4 relative">
|
||||||
<Link href="/exam">
|
<section className="h-full w-full flex flex-col lg:flex-row gap-12 justify-center items-center md:items-start">
|
||||||
<button className={clsx("btn gap-2 top-12 right-12 absolute", infoButtonStyle)}>
|
<section className="w-full lg:w-1/2 h-full flex items-center">
|
||||||
<Icon path={mdiPlus} color="white" size={1} />
|
|
||||||
New Exam
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
<section className="h-full w-full flex items-center p-8 gap-12 justify-center">
|
|
||||||
<section className="w-1/2 h-full flex items-center">
|
|
||||||
<ProfileCard user={user} className="text-black self-start" />
|
<ProfileCard user={user} className="text-black self-start" />
|
||||||
</section>
|
</section>
|
||||||
<section className="w-1/2 h-full flex items-center justify-center">
|
<section className="w-full lg:w-1/3 h-full flex items-center justify-center">
|
||||||
<UserResultChart results={JSON_RESULTS} resultKey="total" label="Total exams" className="w-2/3" />
|
<UserResultChart results={JSON_RESULTS} resultKey="total" label="Total exams" className="w-2/3" />
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user