Added abandon popup
This commit is contained in:
62
src/components/AbandonPopup.tsx
Normal file
62
src/components/AbandonPopup.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import {Dialog, Transition} from "@headlessui/react";
|
||||||
|
import {Fragment} from "react";
|
||||||
|
import Button from "./Low/Button";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isOpen: boolean;
|
||||||
|
abandonPopupTitle: string;
|
||||||
|
abandonPopupDescription: string;
|
||||||
|
abandonConfirmButtonText: string;
|
||||||
|
onAbandon: Function;
|
||||||
|
onCancel: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AbandonPopup({
|
||||||
|
isOpen,
|
||||||
|
abandonPopupTitle,
|
||||||
|
abandonPopupDescription,
|
||||||
|
abandonConfirmButtonText,
|
||||||
|
onAbandon,
|
||||||
|
onCancel,
|
||||||
|
}: Props) {
|
||||||
|
return (
|
||||||
|
<Transition show={isOpen} as={Fragment}>
|
||||||
|
<Dialog onClose={onCancel} className="relative z-50">
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0">
|
||||||
|
<div className="fixed inset-0 bg-black/30" />
|
||||||
|
</Transition.Child>
|
||||||
|
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0 scale-95"
|
||||||
|
enterTo="opacity-100 scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100 scale-100"
|
||||||
|
leaveTo="opacity-0 scale-95">
|
||||||
|
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||||
|
<Dialog.Panel className="w-full max-w-2xl h-fit p-8 rounded-xl bg-white flex flex-col gap-4">
|
||||||
|
<Dialog.Title className="font-bold text-xl">{abandonPopupTitle}</Dialog.Title>
|
||||||
|
<span>{abandonPopupDescription}</span>
|
||||||
|
<div className="w-full flex justify-between mt-8">
|
||||||
|
<Button color="purple" onClick={onCancel} variant="outline" className="max-w-[200px] self-end w-full">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button color="purple" onClick={onAbandon} className="max-w-[200px] self-end w-full">
|
||||||
|
{abandonConfirmButtonText}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</div>
|
||||||
|
</Transition.Child>
|
||||||
|
</Dialog>
|
||||||
|
</Transition>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
export default function FocusLayer() {
|
import {useEffect, useState} from "react";
|
||||||
return <div className="bg-gray-700 bg-opacity-30 absolute top-0 left-0 bottom-0 right-0" />
|
|
||||||
|
interface Props {
|
||||||
|
onFocusLayerMouseEnter: Function,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FocusLayer({
|
||||||
|
onFocusLayerMouseEnter,
|
||||||
|
}: Props) {
|
||||||
|
return (
|
||||||
|
<div className="bg-gray-700 bg-opacity-30 absolute top-0 left-0 bottom-0 right-0" onMouseEnter={onFocusLayerMouseEnter}/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,16 +10,17 @@ interface Props {
|
|||||||
className?: string;
|
className?: string;
|
||||||
navDisabled?: boolean;
|
navDisabled?: boolean;
|
||||||
focusMode?: boolean
|
focusMode?: boolean
|
||||||
|
onFocusLayerMouseEnter?: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Layout({user, children, className, navDisabled = false, focusMode = false}: Props) {
|
export default function Layout({user, children, className, navDisabled = false, focusMode = false, onFocusLayerMouseEnter }: Props) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="w-full min-h-full h-screen flex flex-col bg-mti-gray-smoke">
|
<main className="w-full min-h-full h-screen flex flex-col bg-mti-gray-smoke">
|
||||||
<Navbar user={user} navDisabled={navDisabled} focusMode={focusMode} />
|
<Navbar user={user} navDisabled={navDisabled} focusMode={focusMode} onFocusLayerMouseEnter={onFocusLayerMouseEnter} />
|
||||||
<div className="h-full w-full flex gap-2">
|
<div className="h-full w-full flex gap-2">
|
||||||
<Sidebar path={router.pathname} navDisabled={navDisabled} focusMode={focusMode} />
|
<Sidebar path={router.pathname} navDisabled={navDisabled} focusMode={focusMode} onFocusLayerMouseEnter={onFocusLayerMouseEnter}/>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"w-5/6 min-h-full h-fit mr-8 bg-white shadow-md rounded-2xl p-12 pb-8 flex flex-col gap-12 relative overflow-hidden mt-2",
|
"w-5/6 min-h-full h-fit mr-8 bg-white shadow-md rounded-2xl p-12 pb-8 flex flex-col gap-12 relative overflow-hidden mt-2",
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ interface Props {
|
|||||||
user: User;
|
user: User;
|
||||||
navDisabled?: boolean;
|
navDisabled?: boolean;
|
||||||
focusMode?: boolean;
|
focusMode?: boolean;
|
||||||
|
onFocusLayerMouseEnter?: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable @next/next/no-img-element */
|
/* eslint-disable @next/next/no-img-element */
|
||||||
export default function Navbar({user, navDisabled = false, focusMode = false}: Props) {
|
export default function Navbar({user, navDisabled = false, focusMode = false, onFocusLayerMouseEnter}: Props) {
|
||||||
const disableNavigation = preventNavigation(navDisabled, focusMode);
|
const disableNavigation = preventNavigation(navDisabled, focusMode);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -25,7 +26,7 @@ export default function Navbar({user, navDisabled = false, focusMode = false}: P
|
|||||||
<span className="text-right">{user.name}</span>
|
<span className="text-right">{user.name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
{focusMode && <FocusLayer/>}
|
{focusMode && <FocusLayer onFocusLayerMouseEnter={onFocusLayerMouseEnter}/>}
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ interface Props {
|
|||||||
path: string;
|
path: string;
|
||||||
navDisabled?: boolean;
|
navDisabled?: boolean;
|
||||||
focusMode?: boolean;
|
focusMode?: boolean;
|
||||||
|
onFocusLayerMouseEnter?: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NavProps {
|
interface NavProps {
|
||||||
@@ -36,7 +37,7 @@ const Nav = ({Icon, label, path, keyPath, disabled = false}: NavProps) => (
|
|||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function Sidebar({path, navDisabled = false, focusMode = false }: Props) {
|
export default function Sidebar({path, navDisabled = false, focusMode = false, onFocusLayerMouseEnter}: Props) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
@@ -68,7 +69,7 @@ export default function Sidebar({path, navDisabled = false, focusMode = false }:
|
|||||||
<RiLogoutBoxFill size={20} />
|
<RiLogoutBoxFill size={20} />
|
||||||
<span className="text-lg font-medium">Log Out</span>
|
<span className="text-lg font-medium">Log Out</span>
|
||||||
</div>
|
</div>
|
||||||
{focusMode && <FocusLayer />}
|
{focusMode && <FocusLayer onFocusLayerMouseEnter={onFocusLayerMouseEnter} />}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import useUser from "@/hooks/useUser";
|
|||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
import Layout from "@/components/High/Layout";
|
import Layout from "@/components/High/Layout";
|
||||||
import {speakingReverseMarking, writingReverseMarking} from "@/utils/score";
|
import {speakingReverseMarking, writingReverseMarking} from "@/utils/score";
|
||||||
|
import AbandonPopup from "@/components/AbandonPopup";
|
||||||
|
|
||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
@@ -61,6 +62,7 @@ export default function Page() {
|
|||||||
const [userSolutions, setUserSolutions] = useExamStore((state) => [state.userSolutions, state.setUserSolutions]);
|
const [userSolutions, setUserSolutions] = useExamStore((state) => [state.userSolutions, state.setUserSolutions]);
|
||||||
const [showSolutions, setShowSolutions] = useExamStore((state) => [state.showSolutions, state.setShowSolutions]);
|
const [showSolutions, setShowSolutions] = useExamStore((state) => [state.showSolutions, state.setShowSolutions]);
|
||||||
const [selectedModules, setSelectedModules] = useExamStore((state) => [state.selectedModules, state.setSelectedModules]);
|
const [selectedModules, setSelectedModules] = useExamStore((state) => [state.selectedModules, state.setSelectedModules]);
|
||||||
|
const [showAbandonPopup, setShowAbandonPopup] = useState(false);
|
||||||
const setHasExamEnded = useExamStore((state) => state.setHasExamEnded);
|
const setHasExamEnded = useExamStore((state) => state.setHasExamEnded);
|
||||||
|
|
||||||
const {user} = useUser({redirectTo: "/login"});
|
const {user} = useUser({redirectTo: "/login"});
|
||||||
@@ -315,8 +317,26 @@ export default function Page() {
|
|||||||
</Head>
|
</Head>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
{user && (
|
{user && (
|
||||||
<Layout user={user} className="justify-between" focusMode={selectedModules.length !== 0}>
|
<Layout
|
||||||
|
user={user}
|
||||||
|
className="justify-between"
|
||||||
|
focusMode={selectedModules.length !== 0}
|
||||||
|
onFocusLayerMouseEnter={() => setShowAbandonPopup(true)}
|
||||||
|
>
|
||||||
|
<>
|
||||||
{renderScreen()}
|
{renderScreen()}
|
||||||
|
<AbandonPopup
|
||||||
|
isOpen={showAbandonPopup}
|
||||||
|
abandonPopupTitle="Leave Exam"
|
||||||
|
abandonPopupDescription="Are you sure you want to leave the exam? You will lose all your progress."
|
||||||
|
abandonConfirmButtonText="Confirm"
|
||||||
|
onAbandon={() => {
|
||||||
|
console.log('TODO: Handle Abandon');
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
|
onCancel={() => setShowAbandonPopup(false)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
</Layout>
|
</Layout>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import Sidebar from "@/components/Sidebar";
|
|||||||
import Layout from "@/components/High/Layout";
|
import Layout from "@/components/High/Layout";
|
||||||
import {sortByModule} from "@/utils/moduleUtils";
|
import {sortByModule} from "@/utils/moduleUtils";
|
||||||
import {speakingReverseMarking, writingReverseMarking} from "@/utils/score";
|
import {speakingReverseMarking, writingReverseMarking} from "@/utils/score";
|
||||||
|
import AbandonPopup from "@/components/AbandonPopup";
|
||||||
|
|
||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
@@ -64,6 +65,7 @@ export default function Page() {
|
|||||||
const [userSolutions, setUserSolutions] = useExamStore((state) => [state.userSolutions, state.setUserSolutions]);
|
const [userSolutions, setUserSolutions] = useExamStore((state) => [state.userSolutions, state.setUserSolutions]);
|
||||||
const [showSolutions, setShowSolutions] = useExamStore((state) => [state.showSolutions, state.setShowSolutions]);
|
const [showSolutions, setShowSolutions] = useExamStore((state) => [state.showSolutions, state.setShowSolutions]);
|
||||||
const [selectedModules, setSelectedModules] = useExamStore((state) => [state.selectedModules, state.setSelectedModules]);
|
const [selectedModules, setSelectedModules] = useExamStore((state) => [state.selectedModules, state.setSelectedModules]);
|
||||||
|
const [showAbandonPopup, setShowAbandonPopup] = useState(false);
|
||||||
|
|
||||||
const {user} = useUser({redirectTo: "/login"});
|
const {user} = useUser({redirectTo: "/login"});
|
||||||
|
|
||||||
@@ -317,8 +319,27 @@ export default function Page() {
|
|||||||
</Head>
|
</Head>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
{user && (
|
{user && (
|
||||||
<Layout user={user} className="justify-between" focusMode={selectedModules.length !== 0}>
|
<Layout
|
||||||
|
user={user}
|
||||||
|
className="justify-between"
|
||||||
|
focusMode={selectedModules.length !== 0}
|
||||||
|
onFocusLayerMouseEnter={() => setShowAbandonPopup(true)}
|
||||||
|
>
|
||||||
|
<>
|
||||||
{renderScreen()}
|
{renderScreen()}
|
||||||
|
<AbandonPopup
|
||||||
|
isOpen={showAbandonPopup}
|
||||||
|
|
||||||
|
abandonPopupTitle="Leave Exercise"
|
||||||
|
abandonPopupDescription="Are you sure you want to leave the exercise? You will lose all your progress."
|
||||||
|
abandonConfirmButtonText="Confirm"
|
||||||
|
onAbandon={() => {
|
||||||
|
console.log('TODO: Handle Abandon');
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
|
onCancel={() => setShowAbandonPopup(false)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
</Layout>
|
</Layout>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user