diff --git a/app/admin/courses/page.jsx b/app/admin/courses/page.jsx index 3e0364f..17d25bb 100644 --- a/app/admin/courses/page.jsx +++ b/app/admin/courses/page.jsx @@ -482,22 +482,26 @@ export default function AdminCoursesPage() { {/* View mode */} -
- -
+
+ +
diff --git a/app/courses/[id]/modules/[moduleId]/lessons/[lessonId]/page.jsx b/app/courses/[id]/modules/[moduleId]/lessons/[lessonId]/page.jsx index 3106df6..6dd09d9 100644 --- a/app/courses/[id]/modules/[moduleId]/lessons/[lessonId]/page.jsx +++ b/app/courses/[id]/modules/[moduleId]/lessons/[lessonId]/page.jsx @@ -2,14 +2,14 @@ import { useState, useEffect, useRef } from 'react'; import { useParams, useRouter } from 'next/navigation'; -import { doc, getDoc } from 'firebase/firestore'; +import { doc, getDoc, updateDoc, arrayUnion, serverTimestamp } from 'firebase/firestore'; import { db } from '@/firebase'; import { auth } from '@/firebase'; import { onAuthStateChanged } from 'firebase/auth'; import { query, collection, where, getDocs } from 'firebase/firestore'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { ArrowLeft, ArrowRight, BookOpen, Download, FileText, File, ShieldCheck, List, Eye, X } from 'lucide-react'; +import { ArrowLeft, ArrowRight, BookOpen, Download, FileText, File, ShieldCheck, List, Eye, X, CheckCircle2 } from 'lucide-react'; import Link from 'next/link'; import jsPDF from 'jspdf'; import html2canvas from 'html2canvas'; @@ -43,6 +43,8 @@ export default function LessonPage() { const [accessChecked, setAccessChecked] = useState(false); const [viewingMaterial, setViewingMaterial] = useState(null); const [textContent, setTextContent] = useState({}); + const [isLessonCompleted, setIsLessonCompleted] = useState(false); + const [completingLesson, setCompletingLesson] = useState(false); useEffect(() => { // Track signed-in user for material downloads and check access @@ -57,13 +59,21 @@ export default function LessonPage() { const role = userDoc.data().role; setUserRole(role); - // For students, check enrollment + // For students, check enrollment and lesson completion if (role === 'student') { try { const enrollmentId = `${currentUser.uid}_${courseId}`; const enrollmentDoc = await getDoc(doc(db, 'enrollment', enrollmentId)); const enrolled = enrollmentDoc.exists(); setIsEnrolled(enrolled); + + // Check if lesson is completed + if (enrolled && lessonId) { + const enrollmentData = enrollmentDoc.data(); + const completedLessons = enrollmentData?.progress?.completedLessons || []; + setIsLessonCompleted(completedLessons.includes(lessonId)); + } + console.log('Enrollment check:', { enrollmentId, enrolled, courseId, userId: currentUser.uid }); } catch (err) { console.error('Error checking enrollment:', err); @@ -414,6 +424,50 @@ export default function LessonPage() { } }, [lesson]); + // Handle lesson completion + async function handleCompleteLesson() { + if (!user || !courseId || !lessonId || userRole !== 'student') return; + + setCompletingLesson(true); + try { + const enrollmentId = `${user.uid}_${courseId}`; + const enrollmentRef = doc(db, 'enrollment', enrollmentId); + const enrollmentDoc = await getDoc(enrollmentRef); + + if (!enrollmentDoc.exists()) { + alert('You must be enrolled in this course to complete lessons.'); + setCompletingLesson(false); + return; + } + + const enrollmentData = enrollmentDoc.data(); + const completedLessons = enrollmentData?.progress?.completedLessons || []; + + // Check if already completed + if (completedLessons.includes(lessonId)) { + setIsLessonCompleted(true); + setCompletingLesson(false); + return; + } + + // Update enrollment with completed lesson + await updateDoc(enrollmentRef, { + 'progress.completedLessons': arrayUnion(lessonId), + updatedAt: serverTimestamp(), + }); + + setIsLessonCompleted(true); + + // Show success message + console.log('Lesson marked as complete'); + } catch (err) { + console.error('Error completing lesson:', err); + alert('Failed to mark lesson as complete. Please try again.'); + } finally { + setCompletingLesson(false); + } + } + // this is for downloading lesson materials from storage async function handleDownload(material) { if (!user) { @@ -542,10 +596,10 @@ export default function LessonPage() {
{/* Lesson Content */} - - + +
- {lesson.title} + {lesson.title}
{/* Download Dropdown */} {lesson.contentHtml && ( @@ -899,34 +953,62 @@ export default function LessonPage() { )} - {/* Lesson Navigation */} -
- {prevLesson ? ( - - - - ) : ( -
+
)} - - {nextLesson ? ( - - - - ) : ( - - - + + {/* Completion Status */} + {userRole === 'student' && isEnrolled && isLessonCompleted && ( +
+
+ + Lesson Completed +
+
)} + + {/* Lesson Navigation */} +
+ {prevLesson ? ( + + + + ) : ( +
+ )} + + {nextLesson ? ( + + + + ) : ( + + + + )} +
diff --git a/app/courses/[id]/page.jsx b/app/courses/[id]/page.jsx index 06c4d4d..4de5591 100644 --- a/app/courses/[id]/page.jsx +++ b/app/courses/[id]/page.jsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import { useParams, useRouter } from 'next/navigation'; -import { doc, getDoc } from 'firebase/firestore'; +import { doc, getDoc, onSnapshot } from 'firebase/firestore'; import { db } from '@/firebase'; import { auth } from '@/firebase'; import { onAuthStateChanged } from 'firebase/auth'; @@ -25,6 +25,7 @@ export default function CourseDetailPage() { const [role, setRole] = useState(null); const [isEnrolled, setIsEnrolled] = useState(false); const [enrollmentLoading, setEnrollmentLoading] = useState(true); + const [enrollmentProgress, setEnrollmentProgress] = useState(null); useEffect(() => { const unsubscribe = onAuthStateChanged(auth, async (user) => { @@ -43,6 +44,17 @@ export default function CourseDetailPage() { const enrollmentDoc = await getDoc(enrollmentRef); const enrolled = enrollmentDoc.exists(); setIsEnrolled(enrolled); + + // Load enrollment progress + if (enrolled && enrollmentDoc.exists()) { + const enrollmentData = enrollmentDoc.data(); + setEnrollmentProgress(enrollmentData.progress || { + completedModules: [], + completedLessons: [], + overallProgress: 0, + }); + } + console.log('Enrollment check:', { enrollmentId, enrolled, courseId, userId: user.uid }); } catch (err) { console.error('Error checking enrollment:', err); @@ -68,6 +80,30 @@ export default function CourseDetailPage() { return () => unsubscribe(); }, [courseId]); + // Listen for enrollment progress updates + useEffect(() => { + if (!userId || !courseId || role !== 'student') return; + + const enrollmentId = `${userId}_${courseId}`; + const enrollmentRef = doc(db, 'enrollment', enrollmentId); + + // Use onSnapshot for real-time updates + const unsubscribe = onSnapshot(enrollmentRef, (doc) => { + if (doc.exists()) { + const enrollmentData = doc.data(); + setEnrollmentProgress(enrollmentData.progress || { + completedModules: [], + completedLessons: [], + overallProgress: 0, + }); + } + }, (err) => { + console.error('Error listening to enrollment:', err); + }); + + return () => unsubscribe(); + }, [userId, courseId, role]); + useEffect(() => { async function loadCourse() { try { @@ -246,37 +282,39 @@ export default function CourseDetailPage() { return (
{/* Course Header */} -
+
- +
-

{course.title}

-

{course.description || 'No description'}

-
-
+

{course.title}

+

{course.description || 'No description'}

+
+
- By: {course.authorName || 'Unknown'} + By: {course.authorName || 'Unknown'}
{course.modules && ( -
+
- {course.modules.length} {course.modules.length === 1 ? 'Module' : 'Modules'} + {course.modules.length} {course.modules.length === 1 ? 'Module' : 'Modules'}
)}
{canEnroll && ( - )} {isEnrolled && ( - - + + Enrolled )} @@ -291,24 +329,34 @@ export default function CourseDetailPage() { {/* Modules & Lessons Structure */} {modules.length > 0 ? (
-

Course Content

+
+ +

Course Content

+
{modules.map((module, moduleIndex) => { const moduleLessons = lessons[module.id] || []; const isModuleLocked = isStudent && !isEnrolled && moduleIndex > 0; + // Calculate completion progress for this module + let completedCount = 0; + if (isStudent && isEnrolled && enrollmentProgress && moduleLessons.length > 0) { + const completedLessons = enrollmentProgress.completedLessons || []; + completedCount = moduleLessons.filter(lesson => completedLessons.includes(lesson.id)).length; + } + return ( - +
-
+
{isModuleLocked ? ( ) : ( -
+
{moduleIndex + 1}
)} -
+
{module.title} {moduleLessons.length > 0 && ( @@ -317,6 +365,14 @@ export default function CourseDetailPage() { )}
+ {/* Progress Badge */} + {isStudent && isEnrolled && moduleLessons.length > 0 && ( +
+ + {completedCount} of {moduleLessons.length} complete + +
+ )}
@@ -324,26 +380,39 @@ export default function CourseDetailPage() {
{moduleLessons.map((lesson, lessonIndex) => { const isLessonLocked = isModuleLocked || (isStudent && !isEnrolled); + const isLessonCompleted = isStudent && isEnrolled && enrollmentProgress && + (enrollmentProgress.completedLessons || []).includes(lesson.id); return ( {isLessonLocked ? ( + ) : isLessonCompleted ? ( + ) : ( -
+
{lessonIndex + 1}
)}
-

{lesson.title}

+
+

{lesson.title}

+ {isLessonCompleted && ( + + Completed + + )} +
{lesson.contentHtml && (

{lesson.contentHtml.replace(/<[^>]*>/g, '').substring(0, 60)}...