Updated the code to return to official-exam if they came from that page

This commit is contained in:
Tiago Ribeiro
2024-11-07 22:51:45 +00:00
parent 7ae91d7bc1
commit 065497dfa3
4 changed files with 74 additions and 67 deletions

View File

@@ -1,14 +1,14 @@
import Button from "@/components/Low/Button"; import Button from "@/components/Low/Button";
import ModuleTitle from "@/components/Medium/ModuleTitle"; import ModuleTitle from "@/components/Medium/ModuleTitle";
import {moduleResultText} from "@/constants/ielts"; import { moduleResultText } from "@/constants/ielts";
import {Module} from "@/interfaces"; import { Module } from "@/interfaces";
import {User} from "@/interfaces/user"; import { User } from "@/interfaces/user";
import useExamStore from "@/stores/examStore"; import useExamStore from "@/stores/examStore";
import {calculateBandScore, getGradingLabel} from "@/utils/score"; import { calculateBandScore, getGradingLabel } from "@/utils/score";
import clsx from "clsx"; import clsx from "clsx";
import Link from "next/link"; import Link from "next/link";
import {useRouter} from "next/router"; import { useRouter } from "next/router";
import {Fragment, useEffect, useState} from "react"; import { Fragment, useEffect, useState } from "react";
import { import {
BsArrowCounterclockwise, BsArrowCounterclockwise,
BsBan, BsBan,
@@ -21,14 +21,14 @@ import {
BsPen, BsPen,
BsShareFill, BsShareFill,
} from "react-icons/bs"; } from "react-icons/bs";
import {LevelScore} from "@/constants/ielts"; import { LevelScore } from "@/constants/ielts";
import {getLevelScore} from "@/utils/score"; import { getLevelScore } from "@/utils/score";
import {capitalize} from "lodash"; import { capitalize } from "lodash";
import Modal from "@/components/Modal"; import Modal from "@/components/Modal";
import {UserSolution} from "@/interfaces/exam"; import { UserSolution } from "@/interfaces/exam";
import ai_usage from "@/utils/ai.detection"; import ai_usage from "@/utils/ai.detection";
import useGradingSystem from "@/hooks/useGrading"; import useGradingSystem from "@/hooks/useGrading";
import {Assignment} from "@/interfaces/results"; import { Assignment } from "@/interfaces/results";
interface Score { interface Score {
module: Module; module: Module;
@@ -49,20 +49,23 @@ interface Props {
isLoading: boolean; isLoading: boolean;
assignment?: Assignment; assignment?: Assignment;
onViewResults: (moduleIndex?: number) => void; onViewResults: (moduleIndex?: number) => void;
destination?: string
} }
export default function Finish({user, scores, modules, information, solutions, isLoading, assignment, onViewResults}: Props) { export default function Finish({ user, scores, modules, information, solutions, isLoading, assignment, onViewResults, destination }: Props) {
const [selectedModule, setSelectedModule] = useState(modules[0]); const [selectedModule, setSelectedModule] = useState(modules[0]);
const [selectedScore, setSelectedScore] = useState<Score>(scores.find((x) => x.module === modules[0])!); const [selectedScore, setSelectedScore] = useState<Score>(scores.find((x) => x.module === modules[0])!);
const [isExtraInformationOpen, setIsExtraInformationOpen] = useState(false); const [isExtraInformationOpen, setIsExtraInformationOpen] = useState(false);
const aiUsage = Math.round(ai_usage(solutions) * 100); const aiUsage = Math.round(ai_usage(solutions) * 100);
const exams = useExamStore((state) => state.exams); const exams = useExamStore((state) => state.exams);
const {gradingSystem} = useGradingSystem(); const { gradingSystem } = useGradingSystem();
const router = useRouter()
useEffect(() => setSelectedScore(scores.find((x) => x.module === selectedModule)!), [scores, selectedModule]); useEffect(() => setSelectedScore(scores.find((x) => x.module === selectedModule)!), [scores, selectedModule]);
const moduleColors: {[key in Module]: {progress: string; inner: string}} = { const moduleColors: { [key in Module]: { progress: string; inner: string } } = {
reading: { reading: {
progress: "text-ielts-reading", progress: "text-ielts-reading",
inner: "bg-ielts-reading-light", inner: "bg-ielts-reading-light",
@@ -286,10 +289,8 @@ export default function Finish({user, scores, modules, information, solutions, i
<div className="flex gap-8"> <div className="flex gap-8">
<div className="flex w-fit cursor-pointer flex-col items-center gap-1"> <div className="flex w-fit cursor-pointer flex-col items-center gap-1">
<button <button
onClick={() => window.location.reload()} onClick={() => router.push(destination || "/exam")}
// disabled={user.type === "admin"} disabled={!!assignment}
// TODO: temporarily disabled
disabled
className="bg-mti-purple-light hover:bg-mti-purple flex h-11 w-11 items-center justify-center rounded-full transition duration-300 ease-in-out"> className="bg-mti-purple-light hover:bg-mti-purple flex h-11 w-11 items-center justify-center rounded-full transition duration-300 ease-in-out">
<BsArrowCounterclockwise className="h-7 w-7 text-white" /> <BsArrowCounterclockwise className="h-7 w-7 text-white" />
</button> </button>
@@ -325,7 +326,7 @@ export default function Finish({user, scores, modules, information, solutions, i
)} )}
</div> </div>
<Link href="/" className="w-full max-w-[200px] self-end"> <Link href={destination || "/"} className="w-full max-w-[200px] self-end">
<Button color="purple" className="w-full max-w-[200px] self-end"> <Button color="purple" className="w-full max-w-[200px] self-end">
Dashboard Dashboard
</Button> </Button>

View File

@@ -1,6 +1,6 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import {Module} from "@/interfaces"; import { Module } from "@/interfaces";
import {useEffect, useState} from "react"; import { useEffect, useState } from "react";
import AbandonPopup from "@/components/AbandonPopup"; import AbandonPopup from "@/components/AbandonPopup";
import Layout from "@/components/High/Layout"; import Layout from "@/components/High/Layout";
@@ -12,15 +12,15 @@ import Selection from "@/exams/Selection";
import Speaking from "@/exams/Speaking"; import Speaking from "@/exams/Speaking";
import Writing from "@/exams/Writing"; import Writing from "@/exams/Writing";
import useUser from "@/hooks/useUser"; import useUser from "@/hooks/useUser";
import {Exam, LevelExam, UserSolution, Variant} from "@/interfaces/exam"; import { Exam, LevelExam, UserSolution, Variant } from "@/interfaces/exam";
import {Stat, User} from "@/interfaces/user"; import { Stat, User } from "@/interfaces/user";
import useExamStore from "@/stores/examStore"; import useExamStore from "@/stores/examStore";
import {evaluateSpeakingAnswer, evaluateWritingAnswer} from "@/utils/evaluation"; import { evaluateSpeakingAnswer, evaluateWritingAnswer } from "@/utils/evaluation";
import {defaultExamUserSolutions, getExam} from "@/utils/exams"; import { defaultExamUserSolutions, getExam } from "@/utils/exams";
import axios from "axios"; import axios from "axios";
import {useRouter} from "next/router"; import { useRouter } from "next/router";
import {toast, ToastContainer} from "react-toastify"; import { toast, ToastContainer } from "react-toastify";
import {v4 as uuidv4} from "uuid"; import { v4 as uuidv4 } from "uuid";
import useSessions from "@/hooks/useSessions"; import useSessions from "@/hooks/useSessions";
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
import clsx from "clsx"; import clsx from "clsx";
@@ -31,10 +31,11 @@ import { mapBy } from "@/utils";
interface Props { interface Props {
page: "exams" | "exercises"; page: "exams" | "exercises";
user: User; user: User;
destination?: string
hideSidebar?: boolean hideSidebar?: boolean
} }
export default function ExamPage({page, user, hideSidebar = false}: Props) { export default function ExamPage({ page, user, destination = "/exam", hideSidebar = false }: Props) {
const [variant, setVariant] = useState<Variant>("full"); const [variant, setVariant] = useState<Variant>("full");
const [avoidRepeated, setAvoidRepeated] = useState(false); const [avoidRepeated, setAvoidRepeated] = useState(false);
const [hasBeenUploaded, setHasBeenUploaded] = useState(false); const [hasBeenUploaded, setHasBeenUploaded] = useState(false);
@@ -49,18 +50,18 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
const assignment = useExamStore((state) => state.assignment); const assignment = useExamStore((state) => state.assignment);
const initialTimeSpent = useExamStore((state) => state.timeSpent); const initialTimeSpent = useExamStore((state) => state.timeSpent);
const {exam, setExam} = useExamStore((state) => state); const { exam, setExam } = useExamStore((state) => state);
const {exams, setExams} = useExamStore((state) => state); const { exams, setExams } = useExamStore((state) => state);
const {sessionId, setSessionId} = useExamStore((state) => state); const { sessionId, setSessionId } = useExamStore((state) => state);
const {partIndex, setPartIndex} = useExamStore((state) => state); const { partIndex, setPartIndex } = useExamStore((state) => state);
const {moduleIndex, setModuleIndex} = useExamStore((state) => state); const { moduleIndex, setModuleIndex } = useExamStore((state) => state);
const {questionIndex, setQuestionIndex} = useExamStore((state) => state); const { questionIndex, setQuestionIndex } = useExamStore((state) => state);
const {exerciseIndex, setExerciseIndex} = useExamStore((state) => state); const { exerciseIndex, setExerciseIndex } = useExamStore((state) => state);
const {userSolutions, setUserSolutions} = useExamStore((state) => state); const { userSolutions, setUserSolutions } = useExamStore((state) => state);
const {showSolutions, setShowSolutions} = useExamStore((state) => state); const { showSolutions, setShowSolutions } = useExamStore((state) => state);
const {selectedModules, setSelectedModules} = useExamStore((state) => state); const { selectedModules, setSelectedModules } = useExamStore((state) => state);
const {inactivity, setInactivity} = useExamStore((state) => state); const { inactivity, setInactivity } = useExamStore((state) => state);
const {bgColor, setBgColor} = useExamStore((state) => state); const { bgColor, setBgColor } = useExamStore((state) => state);
const setShuffleMaps = useExamStore((state) => state.setShuffles); const setShuffleMaps = useExamStore((state) => state.setShuffles);
const router = useRouter(); const router = useRouter();
@@ -262,11 +263,11 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
date: new Date().getTime(), date: new Date().getTime(),
isDisabled: solution.isDisabled, isDisabled: solution.isDisabled,
shuffleMaps: solution.shuffleMaps, shuffleMaps: solution.shuffleMaps,
...(assignment ? {assignment: assignment.id} : {}), ...(assignment ? { assignment: assignment.id } : {}),
})); }));
axios axios
.post<{ok: boolean}>("/api/stats", newStats) .post<{ ok: boolean }>("/api/stats", newStats)
.then((response) => setHasBeenUploaded(response.data.ok)) .then((response) => setHasBeenUploaded(response.data.ok))
.catch(() => setHasBeenUploaded(false)); .catch(() => setHasBeenUploaded(false));
} }
@@ -329,7 +330,7 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
), ),
}), }),
); );
return Object.assign(exam, {parts}); return Object.assign(exam, { parts });
} }
const exercises = exam.exercises.map((x) => const exercises = exam.exercises.map((x) =>
@@ -337,7 +338,7 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions, userSolutions: userSolutions.find((y) => x.id === y.exercise)?.solutions,
}), }),
); );
return Object.assign(exam, {exercises}); return Object.assign(exam, { exercises });
}; };
const onFinish = async (solutions: UserSolution[]) => { const onFinish = async (solutions: UserSolution[]) => {
@@ -392,7 +393,7 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
correct: number; correct: number;
}[] => { }[] => {
const scores: { const scores: {
[key in Module]: {total: number; missing: number; correct: number}; [key in Module]: { total: number; missing: number; correct: number };
} = { } = {
reading: { reading: {
total: 0, total: 0,
@@ -434,7 +435,7 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
return Object.keys(scores) return Object.keys(scores)
.filter((x) => scores[x as Module].total > 0) .filter((x) => scores[x as Module].total > 0)
.map((x) => ({module: x as Module, ...scores[x as Module]})); .map((x) => ({ module: x as Module, ...scores[x as Module] }));
}; };
const renderScreen = () => { const renderScreen = () => {
@@ -465,6 +466,7 @@ export default function ExamPage({page, user, hideSidebar = false}: Props) {
timeSpent, timeSpent,
inactivity: totalInactivity, inactivity: totalInactivity,
}} }}
destination={destination}
onViewResults={(index?: number) => { onViewResults={(index?: number) => {
if (exams[0].module === "level") { if (exams[0].module === "level") {
const levelExam = exams[0] as LevelExam; const levelExam = exams[0] as LevelExam;

View File

@@ -1,11 +1,11 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import {withIronSessionSsr} from "iron-session/next"; import { withIronSessionSsr } from "iron-session/next";
import {sessionOptions} from "@/lib/session"; import { sessionOptions } from "@/lib/session";
import {shouldRedirectHome} from "@/utils/navigation.disabled"; import { shouldRedirectHome } from "@/utils/navigation.disabled";
import ExamPage from "./(exam)/ExamPage"; import ExamPage from "./(exam)/ExamPage";
import Head from "next/head"; import Head from "next/head";
import {User} from "@/interfaces/user"; import { User } from "@/interfaces/user";
import { filterBy, findBy, redirect, serialize } from "@/utils"; import { filterBy, findBy, redirect, serialize } from "@/utils";
import { requestUser } from "@/utils/api"; import { requestUser } from "@/utils/api";
import { getAssignment, getAssignments, getAssignmentsByAssignee } from "@/utils/assignments.be"; import { getAssignment, getAssignments, getAssignmentsByAssignee } from "@/utils/assignments.be";
@@ -21,35 +21,36 @@ import { getSessionByAssignment, getSessionsByUser } from "@/utils/sessions.be";
import { Session } from "@/hooks/useSessions"; import { Session } from "@/hooks/useSessions";
import moment from "moment"; import moment from "moment";
export const getServerSideProps = withIronSessionSsr(async ({req, res, query}) => { export const getServerSideProps = withIronSessionSsr(async ({ req, res, query }) => {
const user = await requestUser(req, res) const user = await requestUser(req, res)
const destination = Buffer.from(req.url || "/").toString("base64") const loginDestination = Buffer.from(req.url || "/").toString("base64")
if (!user) return redirect(`/login?destination=${destination}`) if (!user) return redirect(`/login?destination=${loginDestination}`)
if (shouldRedirectHome(user)) return redirect("/") if (shouldRedirectHome(user)) return redirect("/")
const {assignment: assignmentID} = query as {assignment?: string} const { assignment: assignmentID, destination } = query as { assignment?: string, destination?: string }
const destinationURL = !!destination ? Buffer.from(destination, 'base64').toString() : undefined
if (assignmentID) { if (assignmentID) {
const assignment = await getAssignment(assignmentID) const assignment = await getAssignment(assignmentID)
if (!assignment) return redirect("/exam") if (!assignment) return redirect(destinationURL || "/exam")
if (!assignment.assignees.includes(user.id) && !["admin", "developer"].includes(user.type)) if (!assignment.assignees.includes(user.id) && !["admin", "developer"].includes(user.type))
return redirect("/exam") return redirect(destinationURL || "/exam")
if (filterBy(assignment.results, 'user', user.id).length > 0) if (filterBy(assignment.results, 'user', user.id).length > 0)
return redirect("/exam") return redirect(destinationURL || "/exam")
const exams = await getExamsByIds(uniqBy(assignment.exams, "id")) const exams = await getExamsByIds(uniqBy(assignment.exams, "id"))
const session = await getSessionByAssignment(assignmentID) const session = await getSessionByAssignment(assignmentID)
return { return {
props: serialize({user, assignment, exams, session: session ?? undefined}) props: serialize({ user, assignment, exams, destinationURL, session: session ?? undefined })
} }
} }
return { return {
props: serialize({user}), props: serialize({ user, destinationURL }),
}; };
}, sessionOptions); }, sessionOptions);
@@ -58,9 +59,10 @@ interface Props {
assignment?: Assignment assignment?: Assignment
exams?: Exam[] exams?: Exam[]
session?: Session session?: Session
destinationURL?: string
} }
export default function Page({user, assignment, exams = [], session}: Props) { export default function Page({ user, assignment, exams = [], destinationURL = "/exam", session }: Props) {
const router = useRouter() const router = useRouter()
const state = useExamStore((state) => state) const state = useExamStore((state) => state)
@@ -87,7 +89,7 @@ export default function Page({user, assignment, exams = [], session}: Props) {
useEffect(() => { useEffect(() => {
if (assignment && exams.length > 0 && !state.assignment && !!session) { if (assignment && exams.length > 0 && !state.assignment && !!session) {
state.setShuffles(session.userSolutions.map((x) => ({exerciseID: x.exercise, shuffles: x.shuffleMaps ? x.shuffleMaps : []}))); state.setShuffles(session.userSolutions.map((x) => ({ exerciseID: x.exercise, shuffles: x.shuffleMaps ? x.shuffleMaps : [] })));
state.setSelectedModules(session.selectedModules); state.setSelectedModules(session.selectedModules);
state.setExam(session.exam); state.setExam(session.exam);
state.setExams(session.exams); state.setExams(session.exams);
@@ -117,7 +119,7 @@ export default function Page({user, assignment, exams = [], session}: Props) {
<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>
<ExamPage page="exams" user={user} hideSidebar={!!assignment || !!state.assignment} /> <ExamPage page="exams" destination={destinationURL} user={user} hideSidebar={!!assignment || !!state.assignment} />
</> </>
); );
} }

View File

@@ -69,6 +69,8 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
return { props: serialize({ user, entities, assignments, exams, sessions }) }; return { props: serialize({ user, entities, assignments, exams, sessions }) };
}, sessionOptions); }, sessionOptions);
const destination = Buffer.from("/official-exam").toString("base64")
export default function OfficialExam({ user, entities, assignments, sessions, exams }: Props) { export default function OfficialExam({ user, entities, assignments, sessions, exams }: Props) {
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
@@ -93,7 +95,7 @@ export default function OfficialExam({ user, entities, assignments, sessions, ex
state.setSelectedModules(mapBy(assignmentExams.sort(sortByModule), 'module')); state.setSelectedModules(mapBy(assignmentExams.sort(sortByModule), 'module'));
state.setAssignment(assignment); state.setAssignment(assignment);
router.push(`/exam?assignment=${assignment.id}`); router.push(`/exam?assignment=${assignment.id}&destination=${destination}`);
} }
}; };
@@ -112,7 +114,7 @@ export default function OfficialExam({ user, entities, assignments, sessions, ex
state.setShowSolutions(false); state.setShowSolutions(false);
state.setQuestionIndex(session.questionIndex); state.setQuestionIndex(session.questionIndex);
router.push(`/exam?assignment=${session.assignment?.id}`); router.push(`/exam?assignment=${session.assignment?.id}&destination=${destination}`);
}; };
const logout = async () => { const logout = async () => {