"use client" import { useEffect, useState } from "react" import { useRouter, useParams } from "next/navigation" import { Loader2, Play, Clock, BookOpen, ChevronDown, ChevronRight, User, Users, Star, CheckCircle } from "lucide-react" import { toast } from "react-hot-toast" import api from "@/lib/api" import { useAuth } from "@/context/auth-context" import { CertificateModal } from "@/components/certificate-modal" type Course = { id: string title: string description: string subject: string difficulty: string mentor: string students?: number embed_url?: string video_url?: string } type Module = { id: string course_id: string title: string description: string order: number created_at?: string } type Lesson = { id: string module_id: string course_id: string title: string description: string video_url: string embed_url: string order: number duration?: string type: string content?: string } export default function CoursePage() { const { user, isLoading: authLoading } = useAuth() const params = useParams() const router = useRouter() const courseId = params?.courseId as string const [course, setCourse] = useState(null) const [modules, setModules] = useState([]) const [lessons, setLessons] = useState<{ [moduleId: string]: Lesson[] }>({}) const [loading, setLoading] = useState(true) const [modulesLoading, setModulesLoading] = useState(false) const [error, setError] = useState(null) const [resolvedCourseId, setResolvedCourseId] = useState(null) const [isRegistering, setIsRegistering] = useState(false) const [isRegistered, setIsRegistered] = useState(false) const [selectedModuleId, setSelectedModuleId] = useState(null) const [selectedLessonId, setSelectedLessonId] = useState(null) const [expandedModules, setExpandedModules] = useState<{ [moduleId: string]: boolean }>({}) const [completed, setCompleted] = useState(false) const [showCertificateModal, setShowCertificateModal] = useState(false) const logCourseActivity = async ( action: "view" | "start" | "lesson_view", lessonId?: string, overrideCourseId?: string, ) => { try { const token = localStorage.getItem("openlearnx_jwt_token") || localStorage.getItem("openlearnx_token") const targetCourseId = overrideCourseId || courseId await fetch(`http://127.0.0.1:5000/api/courses/${targetCourseId}/activity`, { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify({ action, lesson_id: lessonId }), }) } catch { // Activity logging should not block course UX. } } useEffect(() => { if (!authLoading && !user) { toast.error("Please login to view courses.") router.replace("/") return } if (user && courseId) { fetchCourseData() } }, [authLoading, user, courseId, router]) const fetchRegistrationStatus = async (targetId: string) => { try { const token = localStorage.getItem("openlearnx_jwt_token") || localStorage.getItem("openlearnx_token") const resp = await fetch(`http://127.0.0.1:5000/api/courses/${targetId}/registration`, { headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, }) if (!resp.ok) return const data = await resp.json() if (data?.success) setIsRegistered(Boolean(data.registered)) } catch { // Ignore registration fetch errors. } } const fetchCourseData = async () => { setLoading(true) setError(null) try { const courseResponse = await api.get(`/api/courses/${courseId}?t=${Date.now()}`) const courseData = courseResponse.data const targetId = courseData.id || courseId setCourse(courseData) setResolvedCourseId(targetId) logCourseActivity("view", undefined, targetId) await fetchRegistrationStatus(targetId) await fetchModulesAndLessons(targetId) } catch (err: any) { setError(err.message || "Failed to load course data.") toast.error("Failed to load course data.") } finally { setLoading(false) } } const fetchModulesAndLessons = async (id: string) => { setModulesLoading(true) try { const modulesResponse = await fetch(`http://127.0.0.1:5000/api/courses/${id}/modules`, { headers: { "Content-Type": "application/json" }, }) if (!modulesResponse.ok) { setModules([]) setLessons({}) return } const modulesData = await modulesResponse.json() let modulesList: Module[] = [] if (modulesData.success && Array.isArray(modulesData.modules)) modulesList = modulesData.modules else if (Array.isArray(modulesData.modules)) modulesList = modulesData.modules else if (Array.isArray(modulesData)) modulesList = modulesData else if (Array.isArray(modulesData.data)) modulesList = modulesData.data modulesList = modulesList.sort((a, b) => a.order - b.order) setModules(modulesList) if (modulesList.length > 0) { await fetchLessonsForAllModules(modulesList) } else { setLessons({}) } } catch { setModules([]) setLessons({}) } finally { setModulesLoading(false) } } const fetchLessonsForAllModules = async (modulesList: Module[]) => { const lessonsData: { [moduleId: string]: Lesson[] } = {} const expandedState: { [moduleId: string]: boolean } = {} for (const module of modulesList) { try { const lessonsResponse = await fetch(`http://127.0.0.1:5000/api/modules/${module.id}/lessons`, { headers: { "Content-Type": "application/json" }, }) if (!lessonsResponse.ok) { lessonsData[module.id] = [] continue } const lessonData = await lessonsResponse.json() let lessonsList: Lesson[] = [] if (lessonData.success && Array.isArray(lessonData.lessons)) lessonsList = lessonData.lessons else if (Array.isArray(lessonData.lessons)) lessonsList = lessonData.lessons else if (Array.isArray(lessonData)) lessonsList = lessonData else if (Array.isArray(lessonData.data)) lessonsList = lessonData.data lessonsData[module.id] = lessonsList.sort((a, b) => a.order - b.order) if (!selectedModuleId && lessonsData[module.id].length > 0) expandedState[module.id] = true } catch { lessonsData[module.id] = [] } } setLessons(lessonsData) setExpandedModules(expandedState) if (!selectedModuleId && modulesList.length > 0) { const firstModule = modulesList[0] const firstModuleLessons = lessonsData[firstModule.id] || [] setSelectedModuleId(firstModule.id) if (firstModuleLessons.length > 0) setSelectedLessonId(firstModuleLessons[0].id) } } const getEmbedUrl = (url?: string): string | undefined => { if (!url) return undefined const regExp = /(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/))([^#&?]{11})/ const match = url.match(regExp) if (match && match[1]) return `https://www.youtube.com/embed/${match[1]}?rel=0&modestbranding=1` return url } const toggleModule = (moduleId: string) => { setExpandedModules((prev) => ({ ...prev, [moduleId]: !prev[moduleId] })) } const selectLesson = (moduleId: string, lessonId: string) => { setSelectedModuleId(moduleId) setSelectedLessonId(lessonId) setExpandedModules((prev) => ({ ...prev, [moduleId]: true })) logCourseActivity("lesson_view", lessonId, resolvedCourseId || undefined) } const handleRegister = async () => { if (!resolvedCourseId) return setIsRegistering(true) try { const token = localStorage.getItem("openlearnx_jwt_token") || localStorage.getItem("openlearnx_token") const resp = await fetch(`http://127.0.0.1:5000/api/courses/${resolvedCourseId}/register`, { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, }) if (!resp.ok) { const err = await resp.json().catch(() => ({ error: "Registration failed" })) toast.error(err.error || "Registration failed") return } setIsRegistered(true) toast.success("Registered for this course") } finally { setIsRegistering(false) } } const getCurrentLesson = (): Lesson | null => { if (!selectedModuleId || !selectedLessonId) return null return (lessons[selectedModuleId] || []).find((lesson) => lesson.id === selectedLessonId) || null } const getAllLessons = (): Lesson[] => { const all: Lesson[] = [] modules.forEach((module) => { all.push(...(lessons[module.id] || [])) }) return all } const navigateLesson = (direction: "prev" | "next") => { const allLessons = getAllLessons() const currentIndex = allLessons.findIndex((lesson) => lesson.id === selectedLessonId) if (direction === "prev" && currentIndex > 0) { const prevLesson = allLessons[currentIndex - 1] selectLesson(prevLesson.module_id, prevLesson.id) } else if (direction === "next" && currentIndex < allLessons.length - 1) { const nextLesson = allLessons[currentIndex + 1] selectLesson(nextLesson.module_id, nextLesson.id) } } const isFirstLesson = () => { const allLessons = getAllLessons() return allLessons.length > 0 && allLessons[0].id === selectedLessonId } const isLastLesson = () => { const allLessons = getAllLessons() return allLessons.length > 0 && allLessons[allLessons.length - 1].id === selectedLessonId } const markComplete = async () => { try { const token = localStorage.getItem("openlearnx_jwt_token") || localStorage.getItem("openlearnx_token") if (selectedLessonId) { await fetch(`http://127.0.0.1:5000/api/courses/${courseId}/lessons/${selectedLessonId}/complete`, { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, }) } } catch { // Keep UX smooth even if completion log write fails. } setCompleted(true) setShowCertificateModal(true) } const getTotalLessons = () => Object.values(lessons).reduce((total, moduleLessons) => total + moduleLessons.length, 0) const currentLesson = getCurrentLesson() if (authLoading || loading) { return (

Loading course...

) } if (error) { return (

Unable to load course

{error}

) } if (!course) { return (

Course not found

This course is unavailable or was removed.

) } return (

{course.title}

by {course.mentor || "OpenLearnX Instructor"}

{!isRegistered && ( )}
{modules.length} modules
{getTotalLessons()} lessons
{Number(course.students || 0).toLocaleString()} students
{currentLesson ? ( <> {(currentLesson.embed_url || currentLesson.video_url) && (