From 077fce74ec42cd6f2d402bad69e53d1b694468f5 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 05:34:11 +0000 Subject: [PATCH] Implement functional Student Portal features and fix UI/logic bugs - Implemented functional QuizView component and integrated it into the course player. - Updated Student Dashboard to display real course progress and correctly filter recommended courses. - Implemented dynamic Arena Leaderboard by aggregating student XP from all organizations in Firebase. - Refined gamification logic for points and XP calculation to match requirements. - Fixed activity count calculation on Course Detail page. - Normalized directory names to resolve case-sensitivity routing issues. - Cleaned up redundant code and addressed code review feedback. Co-authored-by: oshadhashiro404 <91681163+oshadhashiro404@users.noreply.github.com> --- .../CoursePlayerWrapper.tsx | 21 +- .../PDFViewModule.tsx | 0 .../activities/[activityId]/QuizView.tsx | 92 ++++++++ .../VideoPlayerModule.tsx | 0 .../activities/[activityId]/page.tsx | 105 +-------- app/StudentPortal/courses/[id]/page.tsx | 3 +- app/StudentPortal/dashboard/page.tsx | 54 +++-- .../organizations/OrganizationContext.tsx | 21 +- app/leaderboard/page.tsx | 213 +++++++++++++----- 9 files changed, 308 insertions(+), 201 deletions(-) rename app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/{[activityid] => [activityId]}/CoursePlayerWrapper.tsx (89%) rename app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/{[activityid] => [activityId]}/PDFViewModule.tsx (100%) create mode 100644 app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityId]/QuizView.tsx rename app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/{[activityid] => [activityId]}/VideoPlayerModule.tsx (100%) diff --git a/app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityid]/CoursePlayerWrapper.tsx b/app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityId]/CoursePlayerWrapper.tsx similarity index 89% rename from app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityid]/CoursePlayerWrapper.tsx rename to app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityId]/CoursePlayerWrapper.tsx index a0104c6..972391f 100644 --- a/app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityid]/CoursePlayerWrapper.tsx +++ b/app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityId]/CoursePlayerWrapper.tsx @@ -7,7 +7,7 @@ import { Loader2 } from "lucide-react"; // slow loading const VideoPlayerModule = lazy(() => import("./VideoPlayerModule")); const PDFViewerModule = lazy(() => import("./PDFViewModule")); -// quiz view is inline btw +const QuizView = lazy(() => import("./QuizView")); interface CoursePlayerWrapperProps { activity: { type: string; @@ -95,16 +95,15 @@ export default function CoursePlayerWrapper({ activity, onComplete, submitting } ); case "quiz": - // can import and use the exisiting quiz view - const quizConfig = content as QuizBlockConfig; - if (!quizConfig.quizQuestions?.length) { - return ( -
-

No quiz questions have been added to this block yet.

-
- ); - } - return
Quiz renderer — wire in QuizView component here.
; + return ( + }> + + + ); case "storyline": return (
diff --git a/app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityid]/PDFViewModule.tsx b/app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityId]/PDFViewModule.tsx similarity index 100% rename from app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityid]/PDFViewModule.tsx rename to app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityId]/PDFViewModule.tsx diff --git a/app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityId]/QuizView.tsx b/app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityId]/QuizView.tsx new file mode 100644 index 0000000..864404d --- /dev/null +++ b/app/StudentPortal/courses/[id]/modules/[moduleId]/activities/[activityId]/activities/[activityId]/QuizView.tsx @@ -0,0 +1,92 @@ +"use client"; + +import React, { useState } from "react"; +import { CheckCircle, Loader2 } from "lucide-react"; +import { QuizBlockConfig } from "@/app/lib/course.types"; + +interface QuizViewProps { + config: QuizBlockConfig; + onComplete: (points?: number) => void; + submitting: boolean; +} + +export default function QuizView({ config, onComplete, submitting }: QuizViewProps) { + const [answers, setAnswers] = useState>({}); + const [submitted, setSubmitted] = useState(false); + + const questions = config.quizQuestions || []; + + const handleSubmit = () => { + setSubmitted(true); + const correct = questions.filter((q, i) => + answers[i]?.trim().toLowerCase() === q.answer?.trim().toLowerCase() + ).length; + + const accuracy = questions.length > 0 ? (correct / questions.length) * 100 : 0; + // accuracy is used by completeActivity via handleComplete's overridePoints mapping + // handleComplete in page.tsx: accuracy = overridePoints ? Math.round((overridePoints / 30) * 100) : 100 + // So we pass points equivalent to accuracy out of 30. + // Wait, the page.tsx expects overridePoints out of 30 if we want to influence accuracy. + // If we pass 10 here, page.tsx will calculate accuracy as (10/30)*100 = 33%. + // So we should pass the accuracy percentage scaled to 30. + const accuracyScaledTo30 = Math.round((accuracy / 100) * 30); + onComplete(accuracyScaledTo30); + }; + + const allAnswered = questions.every((_, i) => answers[i]?.trim()); + + if (questions.length === 0) { + return ( +
+

No quiz questions have been added to this block yet.

+
+ ); + } + + return ( +
+ {questions.map((q, i) => ( +
+

{i + 1}. {q.question}

+ {q.options && q.options.length > 0 ? ( +
+ {q.options.map((opt: string, oi: number) => ( + + ))} +
+ ) : ( +