"use client"; import { useEffect, useMemo, useState, useTransition } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { Check, CircleDashed, ClipboardCheck, FileText, Lock, PlayCircle } from "lucide-react"; import { toast } from "sonner"; import { toggleLessonComplete } from "@/app/(public)/courses/[slug]/learn/actions"; import ProgressBar from "@/components/ProgressBar"; import { markdownToSafeHtml } from "@/lib/courses/lessonMarkdown"; import { getLessonContentTypeLabel, isFinalExam, type LessonActivityMeta, type LessonContentType, } from "@/lib/courses/lessonContent"; type ClassroomLesson = { id: string; title: string; description: string; lectureContent: string; activity: LessonActivityMeta | null; contentType: LessonContentType; materialUrl: string | null; videoUrl: string | null; youtubeUrl: string | null; estimatedDuration: number; isFreePreview: boolean; }; type ClassroomModule = { id: string; title: string; lessons: ClassroomLesson[]; }; type StudentClassroomClientProps = { courseSlug: string; courseTitle: string; modules: ClassroomModule[]; initialSelectedLessonId: string; initialCompletedLessonIds: string[]; isEnrolled: boolean; }; type CompletionCertificate = { certificateId: string; certificateNumber: string | null; }; type ActivityResult = { score: number; correct: number; total: number; passed: boolean; questionResults: Record; }; function getYouTubeEmbedUrl(url: string | null | undefined): string | null { if (!url?.trim()) return null; const trimmed = url.trim(); const watchMatch = trimmed.match(/(?:youtube\.com\/watch\?v=)([a-zA-Z0-9_-]+)/); if (watchMatch) return `https://www.youtube.com/embed/${watchMatch[1]}`; const embedMatch = trimmed.match(/(?:youtube\.com\/embed\/)([a-zA-Z0-9_-]+)/); if (embedMatch) return trimmed; const shortMatch = trimmed.match(/(?:youtu\.be\/)([a-zA-Z0-9_-]+)/); if (shortMatch) return `https://www.youtube.com/embed/${shortMatch[1]}`; return null; } function getIsPdfUrl(url: string | null | undefined): boolean { if (!url) return false; return /\.pdf(?:$|\?)/i.test(url.trim()); } function isAssessmentType(contentType: LessonContentType): boolean { return contentType === "ACTIVITY" || contentType === "QUIZ" || contentType === "FINAL_EXAM"; } export default function StudentClassroomClient({ courseSlug, courseTitle, modules, initialSelectedLessonId, initialCompletedLessonIds, isEnrolled, }: StudentClassroomClientProps) { const router = useRouter(); const [isSaving, startTransition] = useTransition(); const [selectedLessonId, setSelectedLessonId] = useState(initialSelectedLessonId); const [completedLessonIds, setCompletedLessonIds] = useState(initialCompletedLessonIds); const [completionCertificate, setCompletionCertificate] = useState(null); const [activityAnswers, setActivityAnswers] = useState>({}); const [activityResult, setActivityResult] = useState(null); useEffect(() => { setSelectedLessonId(initialSelectedLessonId); }, [initialSelectedLessonId]); useEffect(() => { setCompletedLessonIds(initialCompletedLessonIds); }, [initialCompletedLessonIds]); const flatLessons = useMemo(() => modules.flatMap((module) => module.lessons), [modules]); const completedSet = useMemo(() => new Set(completedLessonIds), [completedLessonIds]); const totalLessons = flatLessons.length; const completedCount = completedLessonIds.length; const progressPercent = totalLessons > 0 ? Math.round((completedCount / totalLessons) * 100) : 0; const selectedLesson = flatLessons.find((lesson) => lesson.id === selectedLessonId) ?? flatLessons[0] ?? null; useEffect(() => { setActivityAnswers({}); setActivityResult(null); }, [selectedLesson?.id]); const selectedLessonTypeLabel = selectedLesson ? getLessonContentTypeLabel(selectedLesson.contentType) : ""; const selectedLessonActivity = selectedLesson?.activity?.questions?.length ? selectedLesson.activity : null; const selectedLectureHtml = markdownToSafeHtml(selectedLesson?.lectureContent || selectedLesson?.description || ""); const isRestricted = (lessonId: string) => { if (!isEnrolled) return false; const lessonIndex = flatLessons.findIndex((lesson) => lesson.id === lessonId); if (lessonIndex <= 0) return false; if (completedSet.has(lessonId)) return false; const previousLesson = flatLessons[lessonIndex - 1]; return !completedSet.has(previousLesson.id); }; const isLockedForUser = (lesson: ClassroomLesson) => !isEnrolled && !lesson.isFreePreview; const navigateToLesson = (lessonId: string) => { if (isRestricted(lessonId)) return; setSelectedLessonId(lessonId); router.push(`/courses/${courseSlug}/learn?lesson=${lessonId}`, { scroll: false }); }; const handleToggleComplete = async () => { if (!selectedLesson || isSaving || !isEnrolled) return; const lessonId = selectedLesson.id; const wasCompleted = completedSet.has(lessonId); setCompletedLessonIds((prev) => wasCompleted ? prev.filter((id) => id !== lessonId) : [...prev, lessonId], ); startTransition(async () => { const result = await toggleLessonComplete({ courseSlug, lessonId }); if (!result.success) { setCompletedLessonIds((prev) => wasCompleted ? [...prev, lessonId] : prev.filter((id) => id !== lessonId), ); return; } setCompletedLessonIds((prev) => { if (result.isCompleted) { return prev.includes(lessonId) ? prev : [...prev, lessonId]; } return prev.filter((id) => id !== lessonId); }); if (result.newlyIssuedCertificate && result.certificateId) { setCompletionCertificate({ certificateId: result.certificateId, certificateNumber: result.certificateNumber ?? null, }); } router.refresh(); }); }; const submitActivity = async () => { if (!selectedLesson || !selectedLessonActivity) return; const total = selectedLessonActivity.questions.length; if (total === 0) return; const unanswered = selectedLessonActivity.questions.filter((question) => !activityAnswers[question.id]); if (unanswered.length > 0) { toast.error("Responde todas las preguntas antes de enviar."); return; } let correct = 0; const questionResults: Record = {}; for (const question of selectedLessonActivity.questions) { const answerId = activityAnswers[question.id]; const selectedOption = question.options.find((option) => option.id === answerId); const isCorrect = Boolean(selectedOption?.isCorrect); if (isCorrect) correct += 1; questionResults[question.id] = isCorrect; } const score = Math.round((correct / total) * 100); const passingScore = selectedLesson.contentType === "ACTIVITY" ? 0 : selectedLessonActivity.passingScorePercent; const passed = selectedLesson.contentType === "ACTIVITY" ? true : score >= passingScore; setActivityResult({ score, correct, total, passed, questionResults }); if (passed && isEnrolled && !completedSet.has(selectedLesson.id)) { await handleToggleComplete(); } }; if (!selectedLesson) { return (

No lessons available yet

This course does not have lessons configured.

); } return (
{completionCertificate ? ( <>
{Array.from({ length: 36 }).map((_, index) => ( ))}

Course completed

Congratulations

You completed all lessons in this course and your ACVE certificate was issued.

Certificate: {completionCertificate.certificateNumber ?? "Issued"}

Download PDF Open Profile
) : null}
{"<-"} Back to Course
{isLockedForUser(selectedLesson) ? (

Contenido premium

Inscríbete en el curso para desbloquear todas las secciones y registrar tu avance.

Ver curso e inscripción
) : selectedLesson.contentType === "VIDEO" ? ( getYouTubeEmbedUrl(selectedLesson.youtubeUrl) ? (