From 15051056ccf86447d23529e1c5f92083fdc06c3e Mon Sep 17 00:00:00 2001 From: khushbu-25 Date: Wed, 25 Mar 2026 13:54:26 +0530 Subject: [PATCH 1/4] Update index.tsx --- .../[courseId]/[instance]/index.tsx | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/alea-frontend/pages/[institutionId]/[courseId]/[instance]/index.tsx b/packages/alea-frontend/pages/[institutionId]/[courseId]/[instance]/index.tsx index f6cba674..f9b1d0c0 100644 --- a/packages/alea-frontend/pages/[institutionId]/[courseId]/[instance]/index.tsx +++ b/packages/alea-frontend/pages/[institutionId]/[courseId]/[instance]/index.tsx @@ -28,6 +28,7 @@ import { ResourceName, pathToCheatSheet, } from '@alea/utils'; +import { getSemesterInfo, SemesterData } from '@alea/spec'; import { SafeFTMLDocument } from '@alea/stex-react-renderer'; import ArticleIcon from '@mui/icons-material/Article'; import AssignmentTurnedInIcon from '@mui/icons-material/AssignmentTurnedIn'; @@ -69,7 +70,7 @@ import { RouteErrorDisplay } from '../../../../components/RouteErrorDisplay'; import InstructorDetails from '../../../../components/InstructorDetails'; import { PersonalCalendarSection } from '../../../../components/PersonalCalendar'; import { RecordedSyllabus } from '../../../../components/RecordedSyllabus'; -import { handleEnrollment, handleUnEnrollment } from '../../../../components/courseHelpers'; +import { handleEnrollment, handleUnEnrollment, getCourseEnrollmentAcl } from '../../../../components/courseHelpers'; import { useRouteValidation } from '../../../../hooks/useRouteValidation'; import { useStudentCount } from '../../../../hooks/useStudentCount'; import { getLocaleObject } from '../../../../lang/utils'; @@ -524,12 +525,15 @@ const CourseHomePage: NextPage = () => { queryKey: ['courses'], queryFn: () => getAllCourses(), }); - const { data: isEnrolled, isFetching } = useCheckAccess({ - resource: ResourceName.COURSE_QUIZ, - action: Action.TAKE, - variables: { - courseId, - instanceId: currentTerm, + const { data: isEnrolled, isFetching } = useQuery({ + queryKey: ['is-enrolled', courseId, currentTerm, userId], + enabled: Boolean(courseId && currentTerm && userId), + retry: false, + queryFn: async () => { + if (!userId) return false; + const aclId = getCourseEnrollmentAcl(courseId, currentTerm); + const response = await fetch(`/api/access-control/is-member?id=${aclId}&userId=${userId}`, { cache: "no-store" }); + return await response.json(); }, }); const enrolled = !isFetching && isEnrolled === true; @@ -557,6 +561,19 @@ const CourseHomePage: NextPage = () => { enabled: Boolean(courseId && currentTerm), queryFn: () => getCourseInfoMetadata(courseId!, currentTerm!), }); + + const { data: semesterInfo } = useQuery({ + queryKey: ['semester-info', institutionId, currentTerm], + enabled: Boolean(institutionId && currentTerm), + queryFn: () => getSemesterInfo(institutionId!, currentTerm!), + }); + + const isSemesterOver = semesterInfo && semesterInfo.length > 0 + ? new Date() > new Date(semesterInfo[0].semesterEnd) + : false; + + const isSemesterOverText = isSemesterOver ? 'Semester is over' : 'Semester is not over'; + if (isValidating) return null; if (validationError) { return ( @@ -615,7 +632,7 @@ const CourseHomePage: NextPage = () => { const enrollmentSuccess = await handleEnrollment(userId, courseId, currentTerm); if (enrollmentSuccess) { queryClient.invalidateQueries({ - queryKey: ['can-access', ResourceName.COURSE_QUIZ, Action.TAKE], + queryKey: ['is-enrolled', courseId, currentTerm, userId], }); } }; @@ -630,7 +647,7 @@ const CourseHomePage: NextPage = () => { const success = await handleUnEnrollment(userId, courseId, currentTerm); if (success) { queryClient.invalidateQueries({ - queryKey: ['can-access', ResourceName.COURSE_QUIZ, Action.TAKE], + queryKey: ['is-enrolled', courseId, currentTerm, userId], }); } }; @@ -749,7 +766,7 @@ const CourseHomePage: NextPage = () => { )} - {enrolled === false && ( + {enrolled === false && !isSemesterOver && ( { )} - {enrolled && ( + {enrolled && !isSemesterOver && ( {studentCount !== null && ( Date: Thu, 26 Mar 2026 10:38:35 +0530 Subject: [PATCH 2/4] minor-fix --- .../alea-frontend/hooks/useStudentCount.ts | 4 +- .../[courseId]/[instance]/index.tsx | 37 ++++++++----------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/packages/alea-frontend/hooks/useStudentCount.ts b/packages/alea-frontend/hooks/useStudentCount.ts index 38ffdadb..ccf3efd1 100644 --- a/packages/alea-frontend/hooks/useStudentCount.ts +++ b/packages/alea-frontend/hooks/useStudentCount.ts @@ -1,7 +1,7 @@ import { getStudentCountInCourse } from '@alea/spec'; import { useEffect, useState } from 'react'; -export function useStudentCount(courseId?: string, instanceId?: string) { +export function useStudentCount(courseId?: string, instanceId?: string, reFetch?: boolean) { const [studentCount, setStudentCount] = useState(null); useEffect(() => { @@ -18,7 +18,7 @@ export function useStudentCount(courseId?: string, instanceId?: string) { console.error('Error fetching student count:', err); setStudentCount(null); }); - }, [courseId]); + }, [courseId, instanceId, reFetch]); return studentCount; } diff --git a/packages/alea-frontend/pages/[institutionId]/[courseId]/[instance]/index.tsx b/packages/alea-frontend/pages/[institutionId]/[courseId]/[instance]/index.tsx index f9b1d0c0..15dd7054 100644 --- a/packages/alea-frontend/pages/[institutionId]/[courseId]/[instance]/index.tsx +++ b/packages/alea-frontend/pages/[institutionId]/[courseId]/[instance]/index.tsx @@ -28,7 +28,7 @@ import { ResourceName, pathToCheatSheet, } from '@alea/utils'; -import { getSemesterInfo, SemesterData } from '@alea/spec'; +import { getSemesterInfo } from '@alea/spec'; import { SafeFTMLDocument } from '@alea/stex-react-renderer'; import ArticleIcon from '@mui/icons-material/Article'; import AssignmentTurnedInIcon from '@mui/icons-material/AssignmentTurnedIn'; @@ -70,7 +70,7 @@ import { RouteErrorDisplay } from '../../../../components/RouteErrorDisplay'; import InstructorDetails from '../../../../components/InstructorDetails'; import { PersonalCalendarSection } from '../../../../components/PersonalCalendar'; import { RecordedSyllabus } from '../../../../components/RecordedSyllabus'; -import { handleEnrollment, handleUnEnrollment, getCourseEnrollmentAcl } from '../../../../components/courseHelpers'; +import { handleEnrollment, handleUnEnrollment } from '../../../../components/courseHelpers'; import { useRouteValidation } from '../../../../hooks/useRouteValidation'; import { useStudentCount } from '../../../../hooks/useStudentCount'; import { getLocaleObject } from '../../../../lang/utils'; @@ -502,7 +502,6 @@ const CourseHomePage: NextPage = () => { const [searchQuery, setSearchQuery] = useState(''); const [seriesId, setSeriesId] = useState(''); - const studentCount = useStudentCount(courseId, currentTerm); const queryClient = useQueryClient(); useEffect(() => { if (!courseId || !currentTerm) return; @@ -525,19 +524,16 @@ const CourseHomePage: NextPage = () => { queryKey: ['courses'], queryFn: () => getAllCourses(), }); - const { data: isEnrolled, isFetching } = useQuery({ - queryKey: ['is-enrolled', courseId, currentTerm, userId], - enabled: Boolean(courseId && currentTerm && userId), - retry: false, - queryFn: async () => { - if (!userId) return false; - const aclId = getCourseEnrollmentAcl(courseId, currentTerm); - const response = await fetch(`/api/access-control/is-member?id=${aclId}&userId=${userId}`, { cache: "no-store" }); - return await response.json(); + const { data: isEnrolled, isFetching } = useCheckAccess({ + resource: ResourceName.COURSE_QUIZ, + action: Action.TAKE, + variables: { + courseId, + instanceId: currentTerm, }, }); const enrolled = !isFetching && isEnrolled === true; - + const studentCount = useStudentCount(courseId, currentTerm, enrolled); const { data: hasInstructorAccess, isFetching: isInstructorFetching } = useQuery({ queryKey: ['is-instructor', courseId, currentTerm], enabled: Boolean(courseId && currentTerm), @@ -568,11 +564,10 @@ const CourseHomePage: NextPage = () => { queryFn: () => getSemesterInfo(institutionId!, currentTerm!), }); - const isSemesterOver = semesterInfo && semesterInfo.length > 0 - ? new Date() > new Date(semesterInfo[0].semesterEnd) - : false; - - const isSemesterOverText = isSemesterOver ? 'Semester is over' : 'Semester is not over'; + const isSemesterOver = + semesterInfo && semesterInfo.length > 0 + ? new Date() > new Date(semesterInfo[0].semesterEnd) + : false; if (isValidating) return null; if (validationError) { @@ -632,7 +627,7 @@ const CourseHomePage: NextPage = () => { const enrollmentSuccess = await handleEnrollment(userId, courseId, currentTerm); if (enrollmentSuccess) { queryClient.invalidateQueries({ - queryKey: ['is-enrolled', courseId, currentTerm, userId], + queryKey: ['can-access', ResourceName.COURSE_QUIZ, Action.TAKE], }); } }; @@ -647,7 +642,7 @@ const CourseHomePage: NextPage = () => { const success = await handleUnEnrollment(userId, courseId, currentTerm); if (success) { queryClient.invalidateQueries({ - queryKey: ['is-enrolled', courseId, currentTerm, userId], + queryKey: ['can-access', ResourceName.COURSE_QUIZ, Action.TAKE], }); } }; @@ -891,4 +886,4 @@ const CourseHomePage: NextPage = () => { ); }; -export default CourseHomePage; \ No newline at end of file +export default CourseHomePage; From 4f1a8d1f18ed3334f71225f301a3fb0a68ed5a55 Mon Sep 17 00:00:00 2001 From: khushbu-25 Date: Thu, 26 Mar 2026 11:55:38 +0530 Subject: [PATCH 3/4] Enrollment-API --- .../pages/api/course/enroll-unenroll.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 packages/alea-frontend/pages/api/course/enroll-unenroll.ts diff --git a/packages/alea-frontend/pages/api/course/enroll-unenroll.ts b/packages/alea-frontend/pages/api/course/enroll-unenroll.ts new file mode 100644 index 00000000..e5dff604 --- /dev/null +++ b/packages/alea-frontend/pages/api/course/enroll-unenroll.ts @@ -0,0 +1,48 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import { + getUserIdOrSetError, + checkIfPostOrSetError, +} from '../comment-utils'; +import { getSemesterInfoFromDb } from '../calendar/create-calendar'; +import { addRemoveMemberOrSetError } from '../access-control/add-remove-member'; + +function getCourseEnrollmentAcl(courseId: string, instanceId: string) { + return `${courseId}-${instanceId}-enrollments`; +} + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (!checkIfPostOrSetError(req, res)) return; + const userId = await getUserIdOrSetError(req, res); + if (!userId) return; + const { courseId, instanceId, action, universityId = 'FAU' } = req.body; + + if (!courseId || !instanceId || !action) { + return res.status(422).send('Missing required fields: courseId, instanceId, or action.'); + } + + const aclId = getCourseEnrollmentAcl(courseId, instanceId); + + if (action === 'enroll') { + const semesterData = await getSemesterInfoFromDb(universityId, instanceId); + if (semesterData?.semesterEnd && new Date() > new Date(semesterData.semesterEnd)) { + return res.status(403).send('Enrollment closed: Semester has ended.'); + } + } else if (action !== 'unenroll') { + return res.status(400).send('Invalid action. Use "enroll" or "unenroll".'); + } + + const success = await addRemoveMemberOrSetError( + { + memberId: userId, + aclId: aclId, + isAclMember: false, + toBeAdded: action === 'enroll', + }, + req, + res + ); + + if (!success) return; + + res.status(200).json({ success: true, action }); +} From 919caa268f2840f2332270d19030f31723696a71 Mon Sep 17 00:00:00 2001 From: khushbu-25 Date: Thu, 26 Mar 2026 14:25:18 +0530 Subject: [PATCH 4/4] Delete packages/alea-frontend/pages/api/course/enroll-unenroll.ts --- .../pages/api/course/enroll-unenroll.ts | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 packages/alea-frontend/pages/api/course/enroll-unenroll.ts diff --git a/packages/alea-frontend/pages/api/course/enroll-unenroll.ts b/packages/alea-frontend/pages/api/course/enroll-unenroll.ts deleted file mode 100644 index e5dff604..00000000 --- a/packages/alea-frontend/pages/api/course/enroll-unenroll.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { NextApiRequest, NextApiResponse } from 'next'; -import { - getUserIdOrSetError, - checkIfPostOrSetError, -} from '../comment-utils'; -import { getSemesterInfoFromDb } from '../calendar/create-calendar'; -import { addRemoveMemberOrSetError } from '../access-control/add-remove-member'; - -function getCourseEnrollmentAcl(courseId: string, instanceId: string) { - return `${courseId}-${instanceId}-enrollments`; -} - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (!checkIfPostOrSetError(req, res)) return; - const userId = await getUserIdOrSetError(req, res); - if (!userId) return; - const { courseId, instanceId, action, universityId = 'FAU' } = req.body; - - if (!courseId || !instanceId || !action) { - return res.status(422).send('Missing required fields: courseId, instanceId, or action.'); - } - - const aclId = getCourseEnrollmentAcl(courseId, instanceId); - - if (action === 'enroll') { - const semesterData = await getSemesterInfoFromDb(universityId, instanceId); - if (semesterData?.semesterEnd && new Date() > new Date(semesterData.semesterEnd)) { - return res.status(403).send('Enrollment closed: Semester has ended.'); - } - } else if (action !== 'unenroll') { - return res.status(400).send('Invalid action. Use "enroll" or "unenroll".'); - } - - const success = await addRemoveMemberOrSetError( - { - memberId: userId, - aclId: aclId, - isAclMember: false, - toBeAdded: action === 'enroll', - }, - req, - res - ); - - if (!success) return; - - res.status(200).json({ success: true, action }); -}