ENCOA-316 ENCOA-317:
Refactor components to remove Layout wrapper and pass it in the App component , implemented a skeleton feedback while loading page and improved API calls related to Dashboard/User Profile
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import AssignmentCard from "@/components/High/AssignmentCard";
|
||||
import Layout from "@/components/High/Layout";
|
||||
import Button from "@/components/Low/Button";
|
||||
import Separator from "@/components/Low/Separator";
|
||||
import ProfileSummary from "@/components/ProfileSummary";
|
||||
@@ -15,7 +14,10 @@ import { sessionOptions } from "@/lib/session";
|
||||
import useExamStore from "@/stores/exam";
|
||||
import { filterBy, findBy, mapBy, redirect, serialize } from "@/utils";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { activeAssignmentFilter, futureAssignmentFilter } from "@/utils/assignments";
|
||||
import {
|
||||
activeAssignmentFilter,
|
||||
futureAssignmentFilter,
|
||||
} from "@/utils/assignments";
|
||||
import { getAssignmentsByAssignee } from "@/utils/assignments.be";
|
||||
import { getEntitiesWithRoles } from "@/utils/entities.be";
|
||||
import { getExamsByIds } from "@/utils/exams.be";
|
||||
@@ -34,142 +36,187 @@ import { BsArrowRepeat } from "react-icons/bs";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
entities: EntityWithRoles[];
|
||||
assignments: Assignment[];
|
||||
stats: Stat[];
|
||||
exams: Exam[];
|
||||
sessions: Session[];
|
||||
invites: InviteWithEntity[];
|
||||
grading: Grading;
|
||||
user: User;
|
||||
entities: EntityWithRoles[];
|
||||
assignments: Assignment[];
|
||||
stats: Stat[];
|
||||
exams: Exam[];
|
||||
sessions: Session[];
|
||||
invites: InviteWithEntity[];
|
||||
grading: Grading;
|
||||
}
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||
const user = await requestUser(req, res)
|
||||
const destination = Buffer.from(req.url || "/").toString("base64")
|
||||
if (!user) return redirect(`/login?destination=${destination}`)
|
||||
const user = await requestUser(req, res);
|
||||
const destination = Buffer.from(req.url || "/").toString("base64");
|
||||
if (!user) return redirect(`/login?destination=${destination}`);
|
||||
|
||||
if (!checkAccess(user, ["admin", "developer", "student"]))
|
||||
return redirect("/")
|
||||
if (!checkAccess(user, ["admin", "developer", "student"]))
|
||||
return redirect("/");
|
||||
|
||||
const entityIDS = mapBy(user.entities, "id") || [];
|
||||
const entityIDS = mapBy(user.entities, "id") || [];
|
||||
|
||||
const entities = await getEntitiesWithRoles(entityIDS);
|
||||
const assignments = await getAssignmentsByAssignee(user.id, { archived: { $ne: true } });
|
||||
const sessions = await getSessionsByUser(user.id, 0, { "assignment.id": { $in: mapBy(assignments, 'id') } });
|
||||
const entities = await getEntitiesWithRoles(entityIDS);
|
||||
const assignments = await getAssignmentsByAssignee(user.id, {
|
||||
archived: { $ne: true },
|
||||
});
|
||||
const sessions = await getSessionsByUser(user.id, 0, {
|
||||
"assignment.id": { $in: mapBy(assignments, "id") },
|
||||
});
|
||||
|
||||
const examIDs = uniqBy(
|
||||
assignments.flatMap((a) =>
|
||||
filterBy(a.exams, 'assignee', user.id).map((e) => ({ module: e.module, id: e.id, key: `${e.module}_${e.id}` })),
|
||||
),
|
||||
"key",
|
||||
);
|
||||
const exams = await getExamsByIds(examIDs);
|
||||
const examIDs = uniqBy(
|
||||
assignments.flatMap((a) =>
|
||||
filterBy(a.exams, "assignee", user.id).map(
|
||||
(e: any) => ({
|
||||
module: e.module,
|
||||
id: e.id,
|
||||
key: `${e.module}_${e.id}`,
|
||||
})
|
||||
)
|
||||
),
|
||||
"key"
|
||||
);
|
||||
const exams = await getExamsByIds(examIDs);
|
||||
|
||||
return { props: serialize({ user, entities, assignments, exams, sessions }) };
|
||||
return { props: serialize({ user, entities, assignments, exams, sessions }) };
|
||||
}, sessionOptions);
|
||||
|
||||
const destination = Buffer.from("/official-exam").toString("base64")
|
||||
const destination = Buffer.from("/official-exam").toString("base64");
|
||||
|
||||
export default function OfficialExam({ user, entities, assignments, sessions, exams }: Props) {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
export default function OfficialExam({
|
||||
user,
|
||||
entities,
|
||||
assignments,
|
||||
sessions,
|
||||
exams,
|
||||
}: Props) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
const dispatch = useExamStore((state) => state.dispatch);
|
||||
const dispatch = useExamStore((state) => state.dispatch);
|
||||
|
||||
const reload = () => {
|
||||
setIsLoading(true)
|
||||
router.replace(router.asPath)
|
||||
setTimeout(() => setIsLoading(false), 500)
|
||||
}
|
||||
const reload = () => {
|
||||
setIsLoading(true);
|
||||
router.replace(router.asPath);
|
||||
setTimeout(() => setIsLoading(false), 500);
|
||||
};
|
||||
|
||||
const startAssignment = (assignment: Assignment) => {
|
||||
const assignmentExams = exams.filter(e => {
|
||||
const exam = findBy(assignment.exams, 'id', e.id)
|
||||
return !!exam && exam.module === e.module
|
||||
})
|
||||
const startAssignment = (assignment: Assignment) => {
|
||||
const assignmentExams = exams.filter((e) => {
|
||||
const exam = findBy(assignment.exams, "id", e.id);
|
||||
return !!exam && exam.module === e.module;
|
||||
});
|
||||
|
||||
if (assignmentExams.every((x) => !!x)) {
|
||||
dispatch({
|
||||
type: "INIT_EXAM", payload: {
|
||||
exams: assignmentExams.sort(sortByModule),
|
||||
modules: mapBy(assignmentExams.sort(sortByModule), 'module'),
|
||||
assignment
|
||||
}
|
||||
})
|
||||
router.push(`/exam?assignment=${assignment.id}&destination=${destination}`);
|
||||
}
|
||||
};
|
||||
if (assignmentExams.every((x) => !!x)) {
|
||||
dispatch({
|
||||
type: "INIT_EXAM",
|
||||
payload: {
|
||||
exams: assignmentExams.sort(sortByModule),
|
||||
modules: mapBy(assignmentExams.sort(sortByModule), "module"),
|
||||
assignment,
|
||||
},
|
||||
});
|
||||
router.push(
|
||||
`/exam?assignment=${assignment.id}&destination=${destination}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const loadSession = async (session: Session) => {
|
||||
dispatch({type: "SET_SESSION", payload: {session}});
|
||||
router.push(`/exam?assignment=${session.assignment?.id}&destination=${destination}`);
|
||||
};
|
||||
const loadSession = async (session: Session) => {
|
||||
dispatch({ type: "SET_SESSION", payload: { session } });
|
||||
router.push(
|
||||
`/exam?assignment=${session.assignment?.id}&destination=${destination}`
|
||||
);
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
axios.post("/api/logout").finally(() => {
|
||||
setTimeout(() => router.reload(), 500);
|
||||
});
|
||||
};
|
||||
const logout = async () => {
|
||||
axios.post("/api/logout").finally(() => {
|
||||
setTimeout(() => router.reload(), 500);
|
||||
});
|
||||
};
|
||||
|
||||
const studentAssignments = useMemo(() => [
|
||||
...assignments.filter(activeAssignmentFilter), ...assignments.filter(futureAssignmentFilter)],
|
||||
[assignments]
|
||||
);
|
||||
const studentAssignments = useMemo(
|
||||
() => [
|
||||
...assignments.filter(activeAssignmentFilter),
|
||||
...assignments.filter(futureAssignmentFilter),
|
||||
],
|
||||
[assignments]
|
||||
);
|
||||
|
||||
const assignmentSessions = useMemo(() => sessions.filter(s => mapBy(studentAssignments, 'id').includes(s.assignment?.id || "")), [sessions, studentAssignments])
|
||||
const assignmentSessions = useMemo(
|
||||
() =>
|
||||
sessions.filter((s) =>
|
||||
mapBy(studentAssignments, "id").includes(s.assignment?.id || "")
|
||||
),
|
||||
[sessions, studentAssignments]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>EnCoach</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<ToastContainer />
|
||||
<Layout user={user} hideSidebar>
|
||||
{entities.length > 0 && (
|
||||
<div className="absolute right-4 top-4 rounded-lg bg-neutral-200 px-2 py-1">
|
||||
<b>{mapBy(entities, "label")?.join(", ")}</b>
|
||||
</div>
|
||||
)}
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>EnCoach</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<ToastContainer />
|
||||
<>
|
||||
{entities.length > 0 && (
|
||||
<div className="absolute right-4 top-4 rounded-lg bg-neutral-200 px-2 py-1">
|
||||
<b>{mapBy(entities, "label")?.join(", ")}</b>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ProfileSummary user={user} items={[]} removeLevel />
|
||||
<ProfileSummary user={user} items={[]} removeLevel />
|
||||
|
||||
<Separator />
|
||||
<Separator />
|
||||
|
||||
{/* Assignments */}
|
||||
<section className="flex flex-col gap-1 md:gap-3">
|
||||
<div
|
||||
onClick={reload}
|
||||
className="text-mti-purple-light hover:text-mti-purple-dark flex cursor-pointer items-center gap-2 transition duration-300 ease-in-out">
|
||||
<span className="text-mti-black text-lg font-bold">Assignments</span>
|
||||
<BsArrowRepeat className={clsx("text-xl", isLoading && "animate-spin")} />
|
||||
</div>
|
||||
<span className="text-mti-gray-taupe scrollbar-hide flex gap-8 overflow-x-scroll">
|
||||
{studentAssignments.length === 0 && "Assignments will appear here. It seems that for now there are no assignments for you."}
|
||||
{studentAssignments
|
||||
.sort((a, b) => moment(a.startDate).diff(b.startDate))
|
||||
.map((a) =>
|
||||
<AssignmentCard
|
||||
key={a.id}
|
||||
assignment={a}
|
||||
user={user}
|
||||
session={assignmentSessions.find(s => s.assignment?.id === a.id)}
|
||||
startAssignment={startAssignment}
|
||||
resumeAssignment={loadSession}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</section>
|
||||
{/* Assignments */}
|
||||
<section className="flex flex-col gap-1 md:gap-3">
|
||||
<div
|
||||
onClick={reload}
|
||||
className="text-mti-purple-light hover:text-mti-purple-dark flex cursor-pointer items-center gap-2 transition duration-300 ease-in-out"
|
||||
>
|
||||
<span className="text-mti-black text-lg font-bold">
|
||||
Assignments
|
||||
</span>
|
||||
<BsArrowRepeat
|
||||
className={clsx("text-xl", isLoading && "animate-spin")}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-mti-gray-taupe scrollbar-hide flex gap-8 overflow-x-scroll">
|
||||
{studentAssignments.length === 0 &&
|
||||
"Assignments will appear here. It seems that for now there are no assignments for you."}
|
||||
{studentAssignments
|
||||
.sort((a, b) => moment(a.startDate).diff(b.startDate))
|
||||
.map((a) => (
|
||||
<AssignmentCard
|
||||
key={a.id}
|
||||
assignment={a}
|
||||
user={user}
|
||||
session={assignmentSessions.find(
|
||||
(s) => s.assignment?.id === a.id
|
||||
)}
|
||||
startAssignment={startAssignment}
|
||||
resumeAssignment={loadSession}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
</section>
|
||||
|
||||
<Button onClick={logout} variant="outline" color="red" className="max-w-[200px] w-full absolute bottom-8 left-8">Sign out</Button>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
<Button
|
||||
onClick={logout}
|
||||
variant="outline"
|
||||
color="red"
|
||||
className="max-w-[200px] w-full absolute bottom-8 left-8"
|
||||
>
|
||||
Sign out
|
||||
</Button>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user