Added the permission to update the privacy of an exam
This commit is contained in:
@@ -1,39 +1,40 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import Head from "next/head";
|
||||
import { withIronSessionSsr } from "iron-session/next";
|
||||
import { sessionOptions } from "@/lib/session";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||
import { Radio, RadioGroup } from "@headlessui/react";
|
||||
import {withIronSessionSsr} from "iron-session/next";
|
||||
import {sessionOptions} from "@/lib/session";
|
||||
import {ToastContainer} from "react-toastify";
|
||||
import {shouldRedirectHome} from "@/utils/navigation.disabled";
|
||||
import {Radio, RadioGroup} from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
import { MODULE_ARRAY } from "@/utils/moduleUtils";
|
||||
import { capitalize } from "lodash";
|
||||
import {MODULE_ARRAY} from "@/utils/moduleUtils";
|
||||
import {capitalize} from "lodash";
|
||||
import Input from "@/components/Low/Input";
|
||||
import { findAllowedEntities } from "@/utils/permissions";
|
||||
import { User } from "@/interfaces/user";
|
||||
import {findAllowedEntities} from "@/utils/permissions";
|
||||
import {User} from "@/interfaces/user";
|
||||
import useExamEditorStore from "@/stores/examEditor";
|
||||
import ExamEditorStore from "@/stores/examEditor/types";
|
||||
import ExamEditor from "@/components/ExamEditor";
|
||||
import { mapBy, redirect, serialize } from "@/utils";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { Module } from "@/interfaces";
|
||||
import { getExam, } from "@/utils/exams.be";
|
||||
import { Exam, Exercise, InteractiveSpeakingExercise, ListeningPart, SpeakingExercise } from "@/interfaces/exam";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getEntitiesWithRoles } from "@/utils/entities.be";
|
||||
import { isAdmin } from "@/utils/users";
|
||||
import {mapBy, redirect, serialize} from "@/utils";
|
||||
import {requestUser} from "@/utils/api";
|
||||
import {Module} from "@/interfaces";
|
||||
import {getExam} from "@/utils/exams.be";
|
||||
import {Exam, Exercise, InteractiveSpeakingExercise, ListeningPart, SpeakingExercise} from "@/interfaces/exam";
|
||||
import {useEffect, useState} from "react";
|
||||
import {getEntitiesWithRoles} from "@/utils/entities.be";
|
||||
import {isAdmin} from "@/utils/users";
|
||||
import axios from "axios";
|
||||
import {EntityWithRoles} from "@/interfaces/entity";
|
||||
|
||||
type Permission = { [key in Module]: boolean }
|
||||
type Permission = {[key in Module]: boolean};
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async ({ req, res, query }) => {
|
||||
const user = await requestUser(req, res)
|
||||
if (!user) return redirect("/login")
|
||||
export const getServerSideProps = withIronSessionSsr(async ({req, res, query}) => {
|
||||
const user = await requestUser(req, res);
|
||||
if (!user) return redirect("/login");
|
||||
|
||||
if (shouldRedirectHome(user)) return redirect("/")
|
||||
if (shouldRedirectHome(user)) return redirect("/");
|
||||
|
||||
const entityIDs = mapBy(user.entities, 'id')
|
||||
const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDs)
|
||||
const entityIDs = mapBy(user.entities, "id");
|
||||
const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDs);
|
||||
|
||||
const permissions: Permission = {
|
||||
reading: findAllowedEntities(user, entities, `generate_reading`).length > 0,
|
||||
@@ -41,29 +42,46 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, query })
|
||||
writing: findAllowedEntities(user, entities, `generate_writing`).length > 0,
|
||||
speaking: findAllowedEntities(user, entities, `generate_speaking`).length > 0,
|
||||
level: findAllowedEntities(user, entities, `generate_level`).length > 0,
|
||||
}
|
||||
};
|
||||
|
||||
if (Object.keys(permissions).every(p => !permissions[p as Module])) return redirect("/")
|
||||
const entitiesAllowEditPrivacy = findAllowedEntities(user, entities, "update_exam_privacy");
|
||||
console.log(entitiesAllowEditPrivacy);
|
||||
|
||||
const { id, module: examModule } = query as { id?: string, module?: Module }
|
||||
if (!id || !examModule) return { props: serialize({ user, permissions }) };
|
||||
if (Object.keys(permissions).every((p) => !permissions[p as Module])) return redirect("/");
|
||||
|
||||
const {id, module: examModule} = query as {id?: string; module?: Module};
|
||||
if (!id || !examModule) return {props: serialize({user, permissions})};
|
||||
|
||||
//if (!permissions[module]) return redirect("/generation")
|
||||
|
||||
const exam = await getExam(examModule, id)
|
||||
if (!exam) return redirect("/generation")
|
||||
const exam = await getExam(examModule, id);
|
||||
if (!exam) return redirect("/generation");
|
||||
|
||||
return {
|
||||
props: serialize({ id, user, exam, examModule, permissions }),
|
||||
props: serialize({id, user, exam, examModule, permissions, entitiesAllowEditPrivacy}),
|
||||
};
|
||||
}, sessionOptions);
|
||||
|
||||
export default function Generation({ id, user, exam, examModule, permissions }: { id: string, user: User; exam?: Exam, examModule?: Module, permissions: Permission }) {
|
||||
const { title, currentModule, modules, dispatch } = useExamEditorStore();
|
||||
export default function Generation({
|
||||
id,
|
||||
user,
|
||||
exam,
|
||||
examModule,
|
||||
permissions,
|
||||
entitiesAllowEditPrivacy,
|
||||
}: {
|
||||
id: string;
|
||||
user: User;
|
||||
exam?: Exam;
|
||||
examModule?: Module;
|
||||
permissions: Permission;
|
||||
entitiesAllowEditPrivacy: EntityWithRoles[];
|
||||
}) {
|
||||
const {title, currentModule, modules, dispatch} = useExamEditorStore();
|
||||
const [examLevelParts, setExamLevelParts] = useState<number | undefined>(undefined);
|
||||
|
||||
const updateRoot = (updates: Partial<ExamEditorStore>) => {
|
||||
dispatch({ type: 'UPDATE_ROOT', payload: { updates } });
|
||||
dispatch({type: "UPDATE_ROOT", payload: {updates}});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -71,16 +89,16 @@ export default function Generation({ id, user, exam, examModule, permissions }:
|
||||
if (examModule === "level" && exam.module === "level") {
|
||||
setExamLevelParts(exam.parts.length);
|
||||
}
|
||||
updateRoot({currentModule: examModule})
|
||||
dispatch({ type: "INIT_EXAM_EDIT", payload: { exam, id, examModule } })
|
||||
updateRoot({currentModule: examModule});
|
||||
dispatch({type: "INIT_EXAM_EDIT", payload: {exam, id, examModule}});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id, exam, module])
|
||||
}, [id, exam, module]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAvatars = async () => {
|
||||
const response = await axios.get("/api/exam/avatars");
|
||||
updateRoot({ speakingAvatars: response.data });
|
||||
updateRoot({speakingAvatars: response.data});
|
||||
};
|
||||
|
||||
fetchAvatars();
|
||||
@@ -96,48 +114,61 @@ export default function Generation({ id, user, exam, examModule, permissions }:
|
||||
URL.revokeObjectURL(state.writing.academic_url);
|
||||
}
|
||||
|
||||
state.listening.sections.forEach(section => {
|
||||
state.listening.sections.forEach((section) => {
|
||||
const listeningPart = section.state as ListeningPart;
|
||||
if (listeningPart.audio?.source) {
|
||||
URL.revokeObjectURL(listeningPart.audio.source);
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD", payload: {
|
||||
sectionId: section.sectionId, module: "listening", field: "state", value: { ...listeningPart, audio: undefined }
|
||||
}
|
||||
})
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD",
|
||||
payload: {
|
||||
sectionId: section.sectionId,
|
||||
module: "listening",
|
||||
field: "state",
|
||||
value: {...listeningPart, audio: undefined},
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (state.listening.instructionsState.customInstructionsURL.startsWith('blob:')) {
|
||||
if (state.listening.instructionsState.customInstructionsURL.startsWith("blob:")) {
|
||||
URL.revokeObjectURL(state.listening.instructionsState.customInstructionsURL);
|
||||
}
|
||||
|
||||
state.speaking.sections.forEach(section => {
|
||||
state.speaking.sections.forEach((section) => {
|
||||
const sectionState = section.state as Exercise;
|
||||
if (sectionState.type === 'speaking') {
|
||||
if (sectionState.type === "speaking") {
|
||||
const speakingExercise = sectionState as SpeakingExercise;
|
||||
URL.revokeObjectURL(speakingExercise.video_url);
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD", payload: {
|
||||
sectionId: section.sectionId, module: "listening", field: "state", value: { ...speakingExercise, video_url: undefined }
|
||||
}
|
||||
})
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD",
|
||||
payload: {
|
||||
sectionId: section.sectionId,
|
||||
module: "listening",
|
||||
field: "state",
|
||||
value: {...speakingExercise, video_url: undefined},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (sectionState.type === 'interactiveSpeaking') {
|
||||
if (sectionState.type === "interactiveSpeaking") {
|
||||
const interactiveSpeaking = sectionState as InteractiveSpeakingExercise;
|
||||
interactiveSpeaking.prompts.forEach(prompt => {
|
||||
interactiveSpeaking.prompts.forEach((prompt) => {
|
||||
URL.revokeObjectURL(prompt.video_url);
|
||||
});
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD", payload: {
|
||||
sectionId: section.sectionId, module: "listening", field: "state", value: {
|
||||
...interactiveSpeaking, prompts: interactiveSpeaking.prompts.map((p) => ({ ...p, video_url: undefined }))
|
||||
}
|
||||
}
|
||||
})
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD",
|
||||
payload: {
|
||||
sectionId: section.sectionId,
|
||||
module: "listening",
|
||||
field: "state",
|
||||
value: {
|
||||
...interactiveSpeaking,
|
||||
prompts: interactiveSpeaking.prompts.map((p) => ({...p, video_url: undefined})),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
dispatch({ type: 'FULL_RESET' });
|
||||
dispatch({type: "FULL_RESET"});
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
@@ -163,7 +194,7 @@ export default function Generation({ id, user, exam, examModule, permissions }:
|
||||
placeholder="Insert a title here"
|
||||
name="title"
|
||||
label="Title"
|
||||
onChange={(title) => updateRoot({ title })}
|
||||
onChange={(title) => updateRoot({title})}
|
||||
roundness="xl"
|
||||
value={title}
|
||||
defaultValue={title}
|
||||
@@ -172,44 +203,46 @@ export default function Generation({ id, user, exam, examModule, permissions }:
|
||||
<label className="font-normal text-base text-mti-gray-dim">Module</label>
|
||||
<RadioGroup
|
||||
value={currentModule}
|
||||
onChange={(currentModule) => updateRoot({ currentModule })}
|
||||
onChange={(currentModule) => updateRoot({currentModule})}
|
||||
className="flex flex-row flex-wrap w-full gap-4 -md:justify-center justify-between">
|
||||
{[...MODULE_ARRAY].filter(m => permissions[m]).map((x) => (
|
||||
<Radio value={x} key={x}>
|
||||
{({ checked }) => (
|
||||
<span
|
||||
className={clsx(
|
||||
"px-6 py-4 w-64 h-[72px] flex justify-center items-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
|
||||
"transition duration-300 ease-in-out",
|
||||
x === "reading" &&
|
||||
(!checked
|
||||
? "bg-white border-mti-gray-platinum"
|
||||
: "bg-ielts-reading/70 border-ielts-reading text-white"),
|
||||
x === "listening" &&
|
||||
(!checked
|
||||
? "bg-white border-mti-gray-platinum"
|
||||
: "bg-ielts-listening/70 border-ielts-listening text-white"),
|
||||
x === "writing" &&
|
||||
(!checked
|
||||
? "bg-white border-mti-gray-platinum"
|
||||
: "bg-ielts-writing/70 border-ielts-writing text-white"),
|
||||
x === "speaking" &&
|
||||
(!checked
|
||||
? "bg-white border-mti-gray-platinum"
|
||||
: "bg-ielts-speaking/70 border-ielts-speaking text-white"),
|
||||
x === "level" &&
|
||||
(!checked
|
||||
? "bg-white border-mti-gray-platinum"
|
||||
: "bg-ielts-level/70 border-ielts-level text-white"),
|
||||
)}>
|
||||
{capitalize(x)}
|
||||
</span>
|
||||
)}
|
||||
</Radio>
|
||||
))}
|
||||
{[...MODULE_ARRAY]
|
||||
.filter((m) => permissions[m])
|
||||
.map((x) => (
|
||||
<Radio value={x} key={x}>
|
||||
{({checked}) => (
|
||||
<span
|
||||
className={clsx(
|
||||
"px-6 py-4 w-64 h-[72px] flex justify-center items-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
|
||||
"transition duration-300 ease-in-out",
|
||||
x === "reading" &&
|
||||
(!checked
|
||||
? "bg-white border-mti-gray-platinum"
|
||||
: "bg-ielts-reading/70 border-ielts-reading text-white"),
|
||||
x === "listening" &&
|
||||
(!checked
|
||||
? "bg-white border-mti-gray-platinum"
|
||||
: "bg-ielts-listening/70 border-ielts-listening text-white"),
|
||||
x === "writing" &&
|
||||
(!checked
|
||||
? "bg-white border-mti-gray-platinum"
|
||||
: "bg-ielts-writing/70 border-ielts-writing text-white"),
|
||||
x === "speaking" &&
|
||||
(!checked
|
||||
? "bg-white border-mti-gray-platinum"
|
||||
: "bg-ielts-speaking/70 border-ielts-speaking text-white"),
|
||||
x === "level" &&
|
||||
(!checked
|
||||
? "bg-white border-mti-gray-platinum"
|
||||
: "bg-ielts-level/70 border-ielts-level text-white"),
|
||||
)}>
|
||||
{capitalize(x)}
|
||||
</span>
|
||||
)}
|
||||
</Radio>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<ExamEditor levelParts={examLevelParts} />
|
||||
<ExamEditor levelParts={examLevelParts} entitiesAllowEditPrivacy={entitiesAllowEditPrivacy} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user