From ac23a90125443ecbe0531263ee7149afc0625436 Mon Sep 17 00:00:00 2001 From: 5t4l1n Date: Tue, 29 Jul 2025 11:50:32 +0530 Subject: [PATCH] course video --- .../[courseId]/lesson/[lessonId]/page.tsx | 90 +++-- frontend/app/courses/[courseId]/page.tsx | 310 +++++++++++++----- 2 files changed, 267 insertions(+), 133 deletions(-) diff --git a/frontend/app/courses/[courseId]/lesson/[lessonId]/page.tsx b/frontend/app/courses/[courseId]/lesson/[lessonId]/page.tsx index 31c86fa..35970e0 100644 --- a/frontend/app/courses/[courseId]/lesson/[lessonId]/page.tsx +++ b/frontend/app/courses/[courseId]/lesson/[lessonId]/page.tsx @@ -1,69 +1,63 @@ "use client" +import { useEffect, useState } from "react" +import { useRouter, useParams } from "next/navigation" +import { toast } from "react-hot-toast" +import { useAuth } from "@/context/auth-context" +import api from "@/lib/api" import { CourseSidebar } from "@/components/course-sidebar" import { LessonViewer } from "@/components/lesson-viewer" import { Loader2 } from "lucide-react" -import { useAuth } from "@/context/auth-context" -import { useRouter } from "next/navigation" -import { toast } from "react-hot-toast" -import { useState, useEffect, use } from "react" // ✅ Added 'use' import import type { Course } from "@/lib/types" -import api from "@/lib/api" -interface CourseDetailPageProps { - params: Promise<{ // ✅ Changed to Promise - courseId: string - lessonId: string - }> -} - -export default function CourseDetailPage({ params }: CourseDetailPageProps) { - const { courseId, lessonId } = use(params) // ✅ Unwrap params using React.use() - const { user, firebaseUser, isLoadingAuth } = useAuth() +export default function LessonDetailPage() { + const params = useParams() const router = useRouter() + const courseId = params?.courseId ?? '' + const lessonId = params?.lessonId ?? '' + const { user, firebaseUser, isLoading: isAuthLoading } = useAuth() + const [course, setCourse] = useState(null) - const [isLoadingCourse, setIsLoadingCourse] = useState(true) + const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { - if (!isLoadingAuth && !user && !firebaseUser) { - toast.error("Please login to view courses.") - router.push("/") + if (!isAuthLoading && !user && !firebaseUser) { + toast.error("Please login to view lessons.") + router.replace("/") return } - const fetchCourse = async () => { - setIsLoadingCourse(true) - setError(null) - try { - const response = await api.get(`/api/courses/${courseId}`) - setCourse(response.data) - } catch (err: any) { - console.error("Failed to fetch course details:", err) - setError(err.response?.data?.message || "Failed to load course details.") - toast.error(err.response?.data?.message || "Failed to load course details.") - } finally { - setIsLoadingCourse(false) + if ((user || firebaseUser) && courseId) { + const fetchCourse = async () => { + setLoading(true) + setError(null) + try { + const response = await api.get(`/api/courses/${courseId}?t=${Date.now()}`) + setCourse(response.data) + } catch (err: any) { + setError(err.message || "Failed to load course.") + toast.error(err.message || "Failed to load course.") + } finally { + setLoading(false) + } } - } - - if (user || firebaseUser) { fetchCourse() } - }, [user, firebaseUser, isLoadingAuth, router, courseId]) + }, [user, firebaseUser, isAuthLoading, router, courseId]) - if (isLoadingAuth || isLoadingCourse) { + if (isAuthLoading || loading) { return ( -
- - Loading course... +
+ + Loading lesson...
) } if (error) { return ( -
+

{error}

) @@ -71,22 +65,18 @@ export default function CourseDetailPage({ params }: CourseDetailPageProps) { if (!course) { return ( -
-

Course not found.

+
+

Course not found.

) } return ( -
- -
+
+ +
-
+
) } diff --git a/frontend/app/courses/[courseId]/page.tsx b/frontend/app/courses/[courseId]/page.tsx index 686c571..03f5316 100644 --- a/frontend/app/courses/[courseId]/page.tsx +++ b/frontend/app/courses/[courseId]/page.tsx @@ -1,112 +1,256 @@ "use client" -import { CourseSidebar } from "@/components/course-sidebar" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { useEffect, useState } from "react" +import { useRouter, useParams } from "next/navigation" import { Loader2 } from "lucide-react" -import { useAuth } from "@/context/auth-context" -import { useRouter } from "next/navigation" import { toast } from "react-hot-toast" -import { useState, useEffect, use } from "react" // ✅ Added 'use' import -import type { Course } from "@/lib/types" import api from "@/lib/api" +import { useAuth } from "@/context/auth-context" -interface CourseOverviewPageProps { - params: Promise<{ // ✅ Changed to Promise - courseId: string - }> +type Lesson = { + id: string + title: string + description?: string + video_url?: string +} +type Module = { + id: string + title: string + lessons: Lesson[] +} +type Course = { + id: string + title: string + description: string + modules: Module[] + embed_url?: string + video_url?: string } -export default function CourseOverviewPage({ params }: CourseOverviewPageProps) { - const { courseId } = use(params) // ✅ Unwrap params using React.use() - const { user, firebaseUser, isLoadingAuth } = useAuth() +export default function CoursePage() { + const { user, firebaseUser, isLoading: authLoading } = useAuth() + const params = useParams() const router = useRouter() + const courseId = params?.courseId as string + const [course, setCourse] = useState(null) - const [isLoadingCourse, setIsLoadingCourse] = useState(true) + const [loading, setLoading] = useState(true) const [error, setError] = useState(null) + // Sidebar state: current + const [selectedModuleIdx, setSelectedModuleIdx] = useState(0) + const [selectedLessonIdx, setSelectedLessonIdx] = useState(0) + const [completed, setCompleted] = useState(false) + useEffect(() => { - if (!isLoadingAuth && !user && !firebaseUser) { + if (!authLoading && !user && !firebaseUser) { toast.error("Please login to view courses.") - router.push("/") + router.replace("/") return } - - const fetchCourse = async () => { - setIsLoadingCourse(true) - setError(null) - try { - const response = await api.get(`/api/courses/${courseId}`) - setCourse(response.data) - } catch (err: any) { - console.error("Failed to fetch course details:", err) - setError(err.response?.data?.message || "Failed to load course details.") - toast.error(err.response?.data?.message || "Failed to load course details.") - } finally { - setIsLoadingCourse(false) - } + if ((user || firebaseUser) && courseId) { + ;(async () => { + setLoading(true) + setError(null) + try { + const resp = await api.get(`/api/courses/${courseId}?t=${Date.now()}`) + setCourse(resp.data) + setSelectedModuleIdx(0) + setSelectedLessonIdx(0) + setCompleted(false) + } catch { + setError("Failed to load course data.") + } finally { + setLoading(false) + } + })() } + }, [authLoading, user, firebaseUser, courseId, router]) - if (user || firebaseUser) { - fetchCourse() + // Helper: embed URL + function 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` } - }, [user, firebaseUser, isLoadingAuth, router, courseId]) - - useEffect(() => { - if (course && course.modules.length > 0 && course.modules[0].lessons.length > 0) { - // Redirect to the first lesson of the course - router.replace(`/courses/${courseId}/lesson/${course.modules[0].lessons[0].id}`) - } - }, [course, courseId, router]) - - if (isLoadingAuth || isLoadingCourse) { - return ( -
- - Loading course... -
- ) + // fallback (could already be an embed url or another provider) + return url } - if (error) { - return ( -
-

{error}

-
- ) + const modules = course?.modules || [] + // Pick first non-empty for fallback if nothing selected + const selModIdx = modules.length > 0 ? selectedModuleIdx : 0 + const lessons = modules.length > 0 ? modules[selModIdx]?.lessons : [] + const selLesIdx = lessons.length > 0 ? selectedLessonIdx : 0 + const currentLesson = lessons.length > 0 ? lessons[selLesIdx] : undefined + + // for navigation + const isEnd = + modules.length > 0 && + selModIdx === modules.length - 1 && + lessons.length > 0 && + selLesIdx === lessons.length - 1 + + function prev() { + if (selLesIdx > 0) setSelectedLessonIdx(selLesIdx - 1) + else if (selModIdx > 0) { + const prevLessons = modules[selModIdx - 1].lessons + setSelectedModuleIdx(selModIdx - 1) + setSelectedLessonIdx(Math.max(prevLessons.length - 1, 0)) + } + } + function next() { + if (lessons.length && selLesIdx < lessons.length - 1) setSelectedLessonIdx(selLesIdx + 1) + else if (selModIdx < modules.length - 1) { + setSelectedModuleIdx(selModIdx + 1) + setSelectedLessonIdx(0) + } } - if (!course) { - return ( -
-

Course not found.

-
- ) + function markComplete() { + setCompleted(true) + toast.success("Course Completed!") } + if (authLoading || loading) return ( +
+ Loading course... +
+ ) + + if (error) return ( +
{error}
+ ) + if (!course) return ( +
Course not found.
+ ) + return ( -
- -
- - - - {course.title} Overview - - - -

- {course.description} -

-

- Select a lesson from the sidebar to begin. -

-
-
-
+
+ {/* Sidebar: Always show all modules and lessons */} + + + {/* Main: show lesson or course video/desc/mark as read */} +
+ {modules.length > 0 && lessons.length > 0 && currentLesson ? ( + <> +

{currentLesson.title}

+ {currentLesson.video_url && ( +
+