course completed up to module lesson

This commit is contained in:
5t4l1n
2025-07-29 13:52:35 +05:30
parent ac23a90125
commit f22caf1cb4
4 changed files with 1907 additions and 427 deletions
+313 -22
View File
@@ -54,6 +54,14 @@ def admin_required(f):
return decorated_function return decorated_function
def serialize_document(doc):
"""Convert MongoDB document to JSON-serializable format"""
if doc:
if '_id' in doc:
doc['_id'] = str(doc['_id'])
return doc
return None
def serialize_course(course): def serialize_course(course):
"""Convert MongoDB document to JSON-serializable format""" """Convert MongoDB document to JSON-serializable format"""
if course: if course:
@@ -100,11 +108,13 @@ def admin_dashboard():
try: try:
total_courses = db.courses.count_documents({}) total_courses = db.courses.count_documents({})
total_lessons = db.lessons.count_documents({}) total_lessons = db.lessons.count_documents({})
total_modules = db.modules.count_documents({})
active_students = db.users.count_documents({"status": "active"}) or 2341 active_students = db.users.count_documents({"status": "active"}) or 2341
stats = { stats = {
"total_courses": total_courses, "total_courses": total_courses,
"total_lessons": total_lessons, "total_lessons": total_lessons,
"total_modules": total_modules,
"active_students": active_students, "active_students": active_students,
"completion_rate": 78 "completion_rate": 78
} }
@@ -137,7 +147,7 @@ def create_course():
"""Create new course""" """Create new course"""
try: try:
data = request.json data = request.json
print(f"Creating course with data: {data}") # Debug log print(f"Creating course with data: {data}")
course_id = data.get('id') or f"{data.get('title', '').lower().replace(' ', '-').replace('&', 'and')}-course" course_id = data.get('id') or f"{data.get('title', '').lower().replace(' ', '-').replace('&', 'and')}-course"
@@ -176,10 +186,10 @@ def create_course():
@bp.route("/courses/<course_id>", methods=["PUT"]) @bp.route("/courses/<course_id>", methods=["PUT"])
@admin_required @admin_required
def update_course(course_id): def update_course(course_id):
"""Update existing course - FIXED VERSION""" """Update existing course"""
try: try:
data = request.json data = request.json
print(f"Updating course {course_id} with data: {data}") # Debug log print(f"Updating course {course_id} with data: {data}")
update_data = { update_data = {
"title": data.get('title'), "title": data.get('title'),
@@ -194,14 +204,14 @@ def update_course(course_id):
# Remove None values # Remove None values
update_data = {k: v for k, v in update_data.items() if v is not None} update_data = {k: v for k, v in update_data.items() if v is not None}
print(f"Filtered update data: {update_data}") # Debug log print(f"Filtered update data: {update_data}")
result = db.courses.update_one( result = db.courses.update_one(
{"id": course_id}, {"id": course_id},
{"$set": update_data} {"$set": update_data}
) )
print(f"Update result: matched={result.matched_count}, modified={result.modified_count}") # Debug log print(f"Update result: matched={result.matched_count}, modified={result.modified_count}")
if result.matched_count == 0: if result.matched_count == 0:
return jsonify({"error": "Course not found"}), 404 return jsonify({"error": "Course not found"}), 404
@@ -217,54 +227,323 @@ def update_course(course_id):
@bp.route("/courses/<course_id>", methods=["DELETE"]) @bp.route("/courses/<course_id>", methods=["DELETE"])
@admin_required @admin_required
def delete_course(course_id): def delete_course(course_id):
"""Delete course""" """Delete course and all related modules and lessons"""
try: try:
print(f"Deleting course: {course_id}") # Debug log print(f"Deleting course: {course_id}")
# Delete related lessons first
lesson_result = db.lessons.delete_many({"course_id": course_id})
print(f"Deleted {lesson_result.deleted_count} related lessons")
# Delete related modules
module_result = db.modules.delete_many({"course_id": course_id})
print(f"Deleted {module_result.deleted_count} related modules")
# Delete the course
result = db.courses.delete_one({"id": course_id}) result = db.courses.delete_one({"id": course_id})
if result.deleted_count == 0: if result.deleted_count == 0:
return jsonify({"error": "Course not found"}), 404 return jsonify({"error": "Course not found"}), 404
# Also delete related lessons
lesson_result = db.lessons.delete_many({"course_id": course_id})
print(f"Deleted {lesson_result.deleted_count} related lessons") # Debug log
return jsonify({"success": True, "message": "Course deleted successfully"}) return jsonify({"success": True, "message": "Course deleted successfully"})
except Exception as e: except Exception as e:
print(f"Error deleting course: {e}") print(f"Error deleting course: {e}")
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
# ✅ FIXED: Module Management Endpoints (removed duplicates)
@bp.route("/courses/<course_id>/modules", methods=["GET"])
@admin_required
def get_course_modules(course_id):
"""Get all modules for a specific course"""
try:
print(f"Fetching modules for course: {course_id}")
modules = list(db.modules.find({"course_id": course_id}).sort("order", 1))
# Convert ObjectId to string
for module in modules:
if '_id' in module:
module['id'] = str(module['_id'])
del module['_id']
print(f"Found {len(modules)} modules for course {course_id}")
return jsonify({"success": True, "modules": modules})
except Exception as e:
print(f"Error fetching modules: {str(e)}")
return jsonify({"error": str(e)}), 500
@bp.route("/courses/<course_id>/modules", methods=["POST"]) @bp.route("/courses/<course_id>/modules", methods=["POST"])
@admin_required @admin_required
def add_module(course_id): def create_module(course_id):
"""Add module to course""" """Create a new module for a course"""
try: try:
data = request.json data = request.json
print(f"Creating module for course {course_id} with data: {data}")
# Verify course exists
course = db.courses.find_one({"id": course_id})
if not course:
return jsonify({"error": "Course not found"}), 404
module = { module = {
"id": data.get('id') or str(uuid.uuid4()), "course_id": course_id,
"title": data.get('title'), "title": data.get('title'),
"lessons": [] "description": data.get('description', ''),
"order": data.get('order', 1),
"created_at": datetime.now().isoformat(),
"updated_at": datetime.now().isoformat()
} }
result = db.courses.update_one( result = db.modules.insert_one(module)
{"id": course_id}, module['id'] = str(result.inserted_id)
{"$push": {"modules": module}} if '_id' in module:
del module['_id']
print(f"Module created with ID: {result.inserted_id}")
return jsonify({"success": True, "module": module}), 201
except Exception as e:
print(f"Error creating module: {str(e)}")
return jsonify({"error": str(e)}), 500
@bp.route("/modules/<module_id>", methods=["GET"])
@admin_required
def get_module(module_id):
"""Get a specific module by ID"""
try:
print(f"Fetching module: {module_id}")
module = db.modules.find_one({"_id": ObjectId(module_id)})
if not module:
return jsonify({"error": "Module not found"}), 404
# Convert ObjectId to string
if '_id' in module:
module['id'] = str(module['_id'])
del module['_id']
return jsonify({"success": True, "module": module})
except Exception as e:
print(f"Error fetching module: {str(e)}")
return jsonify({"error": str(e)}), 500
@bp.route("/modules/<module_id>", methods=["PUT"])
@admin_required
def update_module(module_id):
"""Update an existing module"""
try:
data = request.json
print(f"Updating module {module_id} with data: {data}")
update_data = {
"title": data.get('title'),
"description": data.get('description'),
"order": data.get('order'),
"updated_at": datetime.now().isoformat()
}
# Remove None values
update_data = {k: v for k, v in update_data.items() if v is not None}
result = db.modules.update_one(
{"_id": ObjectId(module_id)},
{"$set": update_data}
) )
if result.matched_count == 0: if result.matched_count == 0:
return jsonify({"error": "Course not found"}), 404 return jsonify({"error": "Module not found"}), 404
# Get updated module
updated_module = db.modules.find_one({"_id": ObjectId(module_id)})
if updated_module:
updated_module['id'] = str(updated_module['_id'])
del updated_module['_id']
return jsonify({"success": True, "module": updated_module})
return jsonify({"success": True, "module": module})
except Exception as e: except Exception as e:
print(f"Error updating module: {str(e)}")
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@bp.route("/modules/<module_id>", methods=["DELETE"])
@admin_required
def delete_module(module_id):
"""Delete a module and all its lessons"""
try:
print(f"Deleting module: {module_id}")
# Delete related lessons first
lesson_result = db.lessons.delete_many({"module_id": module_id})
print(f"Deleted {lesson_result.deleted_count} related lessons")
# Delete the module
result = db.modules.delete_one({"_id": ObjectId(module_id)})
if result.deleted_count == 0:
return jsonify({"error": "Module not found"}), 404
return jsonify({"success": True, "message": "Module deleted successfully"})
except Exception as e:
print(f"Error deleting module: {str(e)}")
return jsonify({"error": str(e)}), 500
# ✅ FIXED: Lesson Management Endpoints
@bp.route("/modules/<module_id>/lessons", methods=["GET"])
@admin_required
def get_module_lessons(module_id):
"""Get all lessons for a specific module"""
try:
print(f"Fetching lessons for module: {module_id}")
lessons = list(db.lessons.find({"module_id": module_id}).sort("order", 1))
# Convert ObjectId to string
for lesson in lessons:
if '_id' in lesson:
lesson['id'] = str(lesson['_id'])
del lesson['_id']
print(f"Found {len(lessons)} lessons for module {module_id}")
return jsonify({"success": True, "lessons": lessons})
except Exception as e:
print(f"Error fetching lessons: {str(e)}")
return jsonify({"error": str(e)}), 500
@bp.route("/modules/<module_id>/lessons", methods=["POST"])
@admin_required
def create_lesson(module_id):
"""Create a new lesson for a module"""
try:
data = request.json
print(f"Creating lesson for module {module_id} with data: {data}")
# Verify module exists
module = db.modules.find_one({"_id": ObjectId(module_id)})
if not module:
return jsonify({"error": "Module not found"}), 404
lesson = {
"module_id": module_id,
"course_id": module.get('course_id'),
"title": data.get('title'),
"description": data.get('description', ''),
"video_url": data.get('video_url'),
"embed_url": convert_to_embed_url(data.get('video_url')) if data.get('video_url') else None,
"order": data.get('order', 1),
"duration": data.get('duration'),
"type": data.get('type', 'video'),
"content": data.get('content', ''),
"created_at": datetime.now().isoformat(),
"updated_at": datetime.now().isoformat()
}
result = db.lessons.insert_one(lesson)
lesson['id'] = str(result.inserted_id)
if '_id' in lesson:
del lesson['_id']
print(f"Lesson created with ID: {result.inserted_id}")
return jsonify({"success": True, "lesson": lesson}), 201
except Exception as e:
print(f"Error creating lesson: {str(e)}")
return jsonify({"error": str(e)}), 500
@bp.route("/lessons/<lesson_id>", methods=["GET"])
@admin_required
def get_lesson(lesson_id):
"""Get a specific lesson by ID"""
try:
print(f"Fetching lesson: {lesson_id}")
lesson = db.lessons.find_one({"_id": ObjectId(lesson_id)})
if not lesson:
return jsonify({"error": "Lesson not found"}), 404
# Convert ObjectId to string
if '_id' in lesson:
lesson['id'] = str(lesson['_id'])
del lesson['_id']
return jsonify({"success": True, "lesson": lesson})
except Exception as e:
print(f"Error fetching lesson: {str(e)}")
return jsonify({"error": str(e)}), 500
@bp.route("/lessons/<lesson_id>", methods=["PUT"])
@admin_required
def update_lesson(lesson_id):
"""Update an existing lesson"""
try:
data = request.json
print(f"Updating lesson {lesson_id} with data: {data}")
update_data = {
"title": data.get('title'),
"description": data.get('description'),
"video_url": data.get('video_url'),
"embed_url": convert_to_embed_url(data.get('video_url')) if data.get('video_url') else None,
"order": data.get('order'),
"duration": data.get('duration'),
"type": data.get('type'),
"content": data.get('content'),
"updated_at": datetime.now().isoformat()
}
# Remove None values
update_data = {k: v for k, v in update_data.items() if v is not None}
result = db.lessons.update_one(
{"_id": ObjectId(lesson_id)},
{"$set": update_data}
)
if result.matched_count == 0:
return jsonify({"error": "Lesson not found"}), 404
# Get updated lesson
updated_lesson = db.lessons.find_one({"_id": ObjectId(lesson_id)})
if updated_lesson:
updated_lesson['id'] = str(updated_lesson['_id'])
del updated_lesson['_id']
return jsonify({"success": True, "lesson": updated_lesson})
except Exception as e:
print(f"Error updating lesson: {str(e)}")
return jsonify({"error": str(e)}), 500
@bp.route("/lessons/<lesson_id>", methods=["DELETE"])
@admin_required
def delete_lesson(lesson_id):
"""Delete a lesson"""
try:
print(f"Deleting lesson: {lesson_id}")
result = db.lessons.delete_one({"_id": ObjectId(lesson_id)})
if result.deleted_count == 0:
return jsonify({"error": "Lesson not found"}), 404
return jsonify({"success": True, "message": "Lesson deleted successfully"})
except Exception as e:
print(f"Error deleting lesson: {str(e)}")
return jsonify({"error": str(e)}), 500
# ✅ LEGACY: For backward compatibility with old course structure
@bp.route("/courses/<course_id>/lessons", methods=["POST"]) @bp.route("/courses/<course_id>/lessons", methods=["POST"])
@admin_required @admin_required
def add_lesson(course_id): def add_lesson_legacy(course_id):
"""Add lesson to course""" """Add lesson to course (legacy endpoint)"""
try: try:
data = request.json data = request.json
@@ -382,6 +661,7 @@ def get_admin_stats():
try: try:
total_courses = db.courses.count_documents({}) total_courses = db.courses.count_documents({})
total_lessons = db.lessons.count_documents({}) total_lessons = db.lessons.count_documents({})
total_modules = db.modules.count_documents({})
# Course statistics by subject # Course statistics by subject
pipeline = [ pipeline = [
@@ -398,6 +678,7 @@ def get_admin_stats():
stats = { stats = {
"total_courses": total_courses, "total_courses": total_courses,
"total_lessons": total_lessons, "total_lessons": total_lessons,
"total_modules": total_modules,
"subjects": subjects, "subjects": subjects,
"difficulties": difficulties, "difficulties": difficulties,
"last_updated": datetime.now().isoformat() "last_updated": datetime.now().isoformat()
@@ -421,6 +702,16 @@ def admin_health():
"POST /api/admin/courses", "POST /api/admin/courses",
"PUT /api/admin/courses/<id>", "PUT /api/admin/courses/<id>",
"DELETE /api/admin/courses/<id>", "DELETE /api/admin/courses/<id>",
"GET /api/admin/courses/<course_id>/modules",
"POST /api/admin/courses/<course_id>/modules",
"GET /api/admin/modules/<module_id>",
"PUT /api/admin/modules/<module_id>",
"DELETE /api/admin/modules/<module_id>",
"GET /api/admin/modules/<module_id>/lessons",
"POST /api/admin/modules/<module_id>/lessons",
"GET /api/admin/lessons/<lesson_id>",
"PUT /api/admin/lessons/<lesson_id>",
"DELETE /api/admin/lessons/<lesson_id>",
"POST /api/admin/initialize", "POST /api/admin/initialize",
"GET /api/admin/test", "GET /api/admin/test",
"GET /api/admin/stats" "GET /api/admin/stats"
File diff suppressed because it is too large Load Diff
@@ -8,7 +8,43 @@ import api from "@/lib/api"
import { CourseSidebar } from "@/components/course-sidebar" import { CourseSidebar } from "@/components/course-sidebar"
import { LessonViewer } from "@/components/lesson-viewer" import { LessonViewer } from "@/components/lesson-viewer"
import { Loader2 } from "lucide-react" import { Loader2 } from "lucide-react"
import type { Course } from "@/lib/types"
interface Course {
id: string
title: string
subject: string
description: string
difficulty: string
mentor: string
video_url: string
embed_url: string
students: number
created_at: string
}
interface Module {
id: string
course_id: string
title: string
description: string
order: number
created_at?: string
}
interface 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
created_at?: string
}
export default function LessonDetailPage() { export default function LessonDetailPage() {
const params = useParams() const params = useParams()
@@ -18,6 +54,9 @@ export default function LessonDetailPage() {
const { user, firebaseUser, isLoading: isAuthLoading } = useAuth() const { user, firebaseUser, isLoading: isAuthLoading } = useAuth()
const [course, setCourse] = useState<Course | null>(null) const [course, setCourse] = useState<Course | null>(null)
const [modules, setModules] = useState<Module[]>([])
const [lessons, setLessons] = useState<{ [moduleId: string]: Lesson[] }>({})
const [currentLesson, setCurrentLesson] = useState<Lesson | null>(null)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
@@ -29,23 +68,97 @@ export default function LessonDetailPage() {
} }
if ((user || firebaseUser) && courseId) { if ((user || firebaseUser) && courseId) {
const fetchCourse = async () => { fetchCourseData()
setLoading(true)
setError(null)
try {
const response = await api.get<Course>(`/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)
}
}
fetchCourse()
} }
}, [user, firebaseUser, isAuthLoading, router, courseId]) }, [user, firebaseUser, isAuthLoading, router, courseId])
const fetchCourseData = async () => {
setLoading(true)
setError(null)
try {
console.log('🔍 Fetching course data for:', courseId)
// Fetch course details
const courseResponse = await api.get<Course>(`/api/courses/${courseId}?t=${Date.now()}`)
const courseData = courseResponse.data
console.log('✅ Course data loaded:', courseData)
setCourse(courseData)
// Fetch modules for the course
const modulesResponse = await fetch(`http://127.0.0.1:5000/api/courses/${courseId}/modules`, {
headers: {
'Content-Type': 'application/json'
}
})
if (modulesResponse.ok) {
const modulesData = await modulesResponse.json()
console.log('✅ Modules data loaded:', modulesData)
let modulesList: Module[] = []
if (modulesData.modules && Array.isArray(modulesData.modules)) {
modulesList = modulesData.modules
} else if (Array.isArray(modulesData)) {
modulesList = modulesData
}
setModules(modulesList)
// Fetch lessons for all modules
const lessonsData: { [moduleId: string]: Lesson[] } = {}
let foundCurrentLesson: Lesson | null = null
for (const module of modulesList) {
const lessonsResponse = await fetch(`http://127.0.0.1:5000/api/modules/${module.id}/lessons`, {
headers: {
'Content-Type': 'application/json'
}
})
if (lessonsResponse.ok) {
const lessonData = await lessonsResponse.json()
console.log(`✅ Lessons loaded for module ${module.id}:`, lessonData)
let lessonsList: Lesson[] = []
if (lessonData.lessons && Array.isArray(lessonData.lessons)) {
lessonsList = lessonData.lessons
} else if (Array.isArray(lessonData)) {
lessonsList = lessonData
}
lessonsData[module.id] = lessonsList
// Find the current lesson
if (lessonId && !foundCurrentLesson) {
foundCurrentLesson = lessonsList.find(lesson => lesson.id === lessonId) || null
}
}
}
setLessons(lessonsData)
if (foundCurrentLesson) {
setCurrentLesson(foundCurrentLesson)
console.log('✅ Current lesson found:', foundCurrentLesson)
} else if (lessonId) {
console.log('❌ Lesson not found:', lessonId)
toast.error("Lesson not found")
router.replace(`/courses/${courseId}`)
}
} else {
throw new Error('Failed to fetch modules')
}
} catch (err: any) {
console.error('❌ Error fetching course data:', err)
setError(err.message || "Failed to load course.")
toast.error(err.message || "Failed to load course.")
} finally {
setLoading(false)
}
}
if (isAuthLoading || loading) { if (isAuthLoading || loading) {
return ( return (
<div className="flex justify-center items-center min-h-screen bg-white"> <div className="flex justify-center items-center min-h-screen bg-white">
@@ -57,25 +170,62 @@ export default function LessonDetailPage() {
if (error) { if (error) {
return ( return (
<div className="flex justify-center items-center min-h-screen bg-white text-red-600"> <div className="flex flex-col justify-center items-center min-h-screen bg-white text-red-600">
<p>{error}</p> <p className="text-lg mb-4">{error}</p>
<button
onClick={() => router.back()}
className="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"
>
Go Back
</button>
</div> </div>
) )
} }
if (!course) { if (!course) {
return ( return (
<div className="flex justify-center items-center min-h-screen bg-white text-gray-700"> <div className="flex flex-col justify-center items-center min-h-screen bg-white text-gray-700">
<p>Course not found.</p> <p className="text-lg mb-4">Course not found.</p>
<button
onClick={() => router.push('/courses')}
className="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"
>
Browse Courses
</button>
</div>
)
}
if (!currentLesson) {
return (
<div className="flex flex-col justify-center items-center min-h-screen bg-white text-gray-700">
<p className="text-lg mb-4">Lesson not found.</p>
<button
onClick={() => router.push(`/courses/${courseId}`)}
className="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700"
>
Back to Course
</button>
</div> </div>
) )
} }
return ( return (
<div className="flex flex-col md:flex-row min-h-screen bg-gray-50"> <div className="flex flex-col md:flex-row min-h-screen bg-gray-50">
<CourseSidebar courseId={course.id} modules={course.modules} activeLessonId={lessonId} /> <CourseSidebar
courseId={course.id}
modules={modules}
lessons={lessons}
activeLessonId={lessonId}
currentLesson={currentLesson}
/>
<main className="flex-grow p-8 max-w-4xl mx-auto w-full"> <main className="flex-grow p-8 max-w-4xl mx-auto w-full">
<LessonViewer courseId={course.id} lessonId={lessonId} /> <LessonViewer
courseId={course.id}
lessonId={lessonId}
lesson={currentLesson}
course={course}
/>
</main> </main>
</div> </div>
) )
+668 -188
View File
@@ -2,31 +2,46 @@
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { useRouter, useParams } from "next/navigation" import { useRouter, useParams } from "next/navigation"
import { Loader2 } from "lucide-react" import { Loader2, Play, Clock, BookOpen, ChevronDown, ChevronRight, User, Users, Star } from "lucide-react"
import { toast } from "react-hot-toast" import { toast } from "react-hot-toast"
import api from "@/lib/api" import api from "@/lib/api"
import { useAuth } from "@/context/auth-context" import { useAuth } from "@/context/auth-context"
type Lesson = {
id: string
title: string
description?: string
video_url?: string
}
type Module = {
id: string
title: string
lessons: Lesson[]
}
type Course = { type Course = {
id: string id: string
title: string title: string
description: string description: string
modules: Module[] subject: string
difficulty: string
mentor: string
students: number
embed_url?: string embed_url?: string
video_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() { export default function CoursePage() {
const { user, firebaseUser, isLoading: authLoading } = useAuth() const { user, firebaseUser, isLoading: authLoading } = useAuth()
const params = useParams() const params = useParams()
@@ -34,12 +49,16 @@ export default function CoursePage() {
const courseId = params?.courseId as string const courseId = params?.courseId as string
const [course, setCourse] = useState<Course | null>(null) const [course, setCourse] = useState<Course | null>(null)
const [modules, setModules] = useState<Module[]>([])
const [lessons, setLessons] = useState<{ [moduleId: string]: Lesson[] }>({})
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [modulesLoading, setModulesLoading] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
// Sidebar state: current // Navigation state
const [selectedModuleIdx, setSelectedModuleIdx] = useState(0) const [selectedModuleId, setSelectedModuleId] = useState<string | null>(null)
const [selectedLessonIdx, setSelectedLessonIdx] = useState(0) const [selectedLessonId, setSelectedLessonId] = useState<string | null>(null)
const [expandedModules, setExpandedModules] = useState<{ [moduleId: string]: boolean }>({})
const [completed, setCompleted] = useState(false) const [completed, setCompleted] = useState(false)
useEffect(() => { useEffect(() => {
@@ -49,24 +68,192 @@ export default function CoursePage() {
return return
} }
if ((user || firebaseUser) && courseId) { if ((user || firebaseUser) && courseId) {
;(async () => { fetchCourseData()
setLoading(true)
setError(null)
try {
const resp = await api.get<Course>(`/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]) }, [authLoading, user, firebaseUser, courseId, router])
const fetchCourseData = async () => {
setLoading(true)
setError(null)
try {
console.log('🔍 Starting to fetch course data for:', courseId)
// Fetch course details
const courseResponse = await api.get<Course>(`/api/courses/${courseId}?t=${Date.now()}`)
const courseData = courseResponse.data
console.log('✅ Course data loaded:', courseData)
setCourse(courseData)
// ✅ FIXED: Better module fetching with multiple endpoint attempts
await fetchModulesAndLessons(courseId)
} catch (err: any) {
console.error('❌ Error fetching course data:', err)
setError(err.message || "Failed to load course data.")
toast.error("Failed to load course data.")
} finally {
setLoading(false)
}
}
const fetchModulesAndLessons = async (courseId: string) => {
setModulesLoading(true)
try {
console.log('🔍 Fetching modules for course:', courseId)
// Try multiple endpoints for modules
let modulesData = null
let modulesResponse = null
// Try admin endpoint first (most likely to work based on previous conversation)
try {
modulesResponse = await fetch(`http://127.0.0.1:5000/api/admin/courses/${courseId}/modules`, {
headers: {
'Authorization': 'Bearer admin-secret-key',
'Content-Type': 'application/json'
}
})
if (modulesResponse.ok) {
modulesData = await modulesResponse.json()
console.log('✅ Modules loaded from admin endpoint:', modulesData)
}
} catch (adminError) {
console.log('⚠️ Admin endpoint failed, trying public endpoint')
}
// If admin endpoint failed, try public endpoint
if (!modulesData || !modulesResponse?.ok) {
try {
modulesResponse = await fetch(`http://127.0.0.1:5000/api/courses/${courseId}/modules`, {
headers: {
'Content-Type': 'application/json'
}
})
if (modulesResponse.ok) {
modulesData = await modulesResponse.json()
console.log('✅ Modules loaded from public endpoint:', modulesData)
}
} catch (publicError) {
console.error('❌ Both module endpoints failed')
}
}
if (modulesData) {
let modulesList: Module[] = []
// Handle different response formats
if (modulesData.success && modulesData.modules && Array.isArray(modulesData.modules)) {
modulesList = modulesData.modules
} else if (modulesData.modules && Array.isArray(modulesData.modules)) {
modulesList = modulesData.modules
} else if (Array.isArray(modulesData)) {
modulesList = modulesData
} else if (modulesData.data && Array.isArray(modulesData.data)) {
modulesList = modulesData.data
}
// Sort modules by order
modulesList = modulesList.sort((a, b) => a.order - b.order)
console.log('🔍 Processed modules list:', modulesList)
setModules(modulesList)
// Fetch lessons for all modules
if (modulesList.length > 0) {
await fetchLessonsForAllModules(modulesList)
}
} else {
console.log('⚠️ No modules data received')
setModules([])
setLessons({})
}
} catch (error) {
console.error('❌ Error in fetchModulesAndLessons:', error)
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 {
console.log('🔍 Fetching lessons for module:', module.id)
// Try admin endpoint first
let lessonsResponse = await fetch(`http://127.0.0.1:5000/api/admin/modules/${module.id}/lessons`, {
headers: {
'Authorization': 'Bearer admin-secret-key',
'Content-Type': 'application/json'
}
})
// If admin fails, try public endpoint
if (!lessonsResponse.ok) {
lessonsResponse = await fetch(`http://127.0.0.1:5000/api/modules/${module.id}/lessons`, {
headers: {
'Content-Type': 'application/json'
}
})
}
if (lessonsResponse.ok) {
const lessonData = await lessonsResponse.json()
console.log(`✅ Lessons loaded for module ${module.id}:`, lessonData)
let lessonsList: Lesson[] = []
if (lessonData.success && lessonData.lessons && Array.isArray(lessonData.lessons)) {
lessonsList = lessonData.lessons
} else if (lessonData.lessons && Array.isArray(lessonData.lessons)) {
lessonsList = lessonData.lessons
} else if (Array.isArray(lessonData)) {
lessonsList = lessonData
} else if (lessonData.data && Array.isArray(lessonData.data)) {
lessonsList = lessonData.data
}
// Sort lessons by order
lessonsList = lessonsList.sort((a, b) => a.order - b.order)
lessonsData[module.id] = lessonsList
// Auto-expand first module with lessons
if (!selectedModuleId && lessonsList.length > 0) {
expandedState[module.id] = true
}
} else {
console.log(`⚠️ No lessons found for module ${module.id}`)
lessonsData[module.id] = []
}
} catch (error) {
console.error(`❌ Error fetching lessons for module ${module.id}:`, error)
lessonsData[module.id] = []
}
}
setLessons(lessonsData)
setExpandedModules(expandedState)
// Auto-select first lesson if available
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)
}
}
}
// Helper: embed URL // Helper: embed URL
function getEmbedUrl(url?: string): string | undefined { function getEmbedUrl(url?: string): string | undefined {
if (!url) return undefined if (!url) return undefined
@@ -75,182 +262,475 @@ export default function CoursePage() {
if (match && match[1]) { if (match && match[1]) {
return `https://www.youtube.com/embed/${match[1]}?rel=0&modestbranding=1` return `https://www.youtube.com/embed/${match[1]}?rel=0&modestbranding=1`
} }
// fallback (could already be an embed url or another provider)
return url return url
} }
const modules = course?.modules || [] const toggleModule = (moduleId: string) => {
// Pick first non-empty for fallback if nothing selected setExpandedModules(prev => ({
const selModIdx = modules.length > 0 ? selectedModuleIdx : 0 ...prev,
const lessons = modules.length > 0 ? modules[selModIdx]?.lessons : [] [moduleId]: !prev[moduleId]
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) const selectLesson = (moduleId: string, lessonId: string) => {
else if (selModIdx < modules.length - 1) { setSelectedModuleId(moduleId)
setSelectedModuleIdx(selModIdx + 1) setSelectedLessonId(lessonId)
setSelectedLessonIdx(0) // Auto-expand the module when lesson is selected
setExpandedModules(prev => ({
...prev,
[moduleId]: true
}))
}
const getCurrentLesson = (): Lesson | null => {
if (!selectedModuleId || !selectedLessonId) return null
const moduleLessons = lessons[selectedModuleId] || []
return moduleLessons.find(lesson => lesson.id === selectedLessonId) || null
}
const getAllLessons = (): Lesson[] => {
const allLessons: Lesson[] = []
modules.forEach(module => {
const moduleLessons = lessons[module.id] || []
allLessons.push(...moduleLessons)
})
return allLessons
}
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)
} }
} }
function markComplete() { 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 = () => {
setCompleted(true) setCompleted(true)
toast.success("Course Completed!") toast.success("Course Completed! 🎉")
} }
if (authLoading || loading) return ( const getTotalLessons = () => {
<div className="flex items-center justify-center min-h-screen"> return Object.values(lessons).reduce((total, moduleLessons) => total + moduleLessons.length, 0)
<Loader2 className="h-8 w-8 animate-spin text-indigo-700" /><span className="ml-2">Loading course...</span> }
</div>
)
if (error) return ( const currentLesson = getCurrentLesson()
<div className="flex items-center justify-center min-h-screen text-red-500">{error}</div>
) if (authLoading || loading) {
if (!course) return ( return (
<div className="flex items-center justify-center min-h-screen text-gray-700">Course not found.</div> <div className="min-h-screen bg-gray-50 flex items-center justify-center">
) <div className="text-center">
<Loader2 className="h-12 w-12 animate-spin text-indigo-600 mx-auto mb-4" />
<p className="text-lg text-gray-700">Loading course...</p>
</div>
</div>
)
}
if (error) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center max-w-md mx-auto px-4">
<div className="bg-red-50 border border-red-200 rounded-lg p-6">
<h2 className="text-lg font-semibold text-red-800 mb-2">Error Loading Course</h2>
<p className="text-red-600 mb-4">{error}</p>
<button
onClick={fetchCourseData}
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 transition-colors"
>
Try Again
</button>
</div>
</div>
</div>
)
}
if (!course) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<h2 className="text-xl font-semibold text-gray-800 mb-2">Course Not Found</h2>
<p className="text-gray-600">The course you're looking for doesn't exist.</p>
</div>
</div>
)
}
return ( return (
<div className="flex flex-col md:flex-row gap-6 max-w-7xl mx-auto px-4 py-8"> <div className="min-h-screen bg-gray-50">
{/* Sidebar: Always show all modules and lessons */} {/* Header */}
<aside className="w-full md:w-64 bg-white rounded-xl shadow-md p-4 md:sticky md:top-20 h-fit max-h-[80vh] overflow-y-auto mb-4 md:mb-0"> <header className="bg-white shadow-sm border-b">
<h2 className="text-lg font-bold mb-4 text-indigo-700">{course.title}</h2> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<p className="text-xs text-gray-500 mb-5">{course.description}</p> <div className="flex items-center justify-between h-16">
{modules.length === 0 ? ( <div className="flex items-center space-x-4">
<div className="text-gray-500 italic py-6">No modules yet for this course.</div> <div className="w-10 h-10 bg-indigo-600 rounded-lg flex items-center justify-center">
) : ( <span className="text-white font-bold text-lg">OL</span>
<ul> </div>
{modules.map((mod, mIdx) => ( <div>
<li key={mod.id} className="mb-4"> <h1 className="text-xl font-bold text-gray-900">{course.title}</h1>
<div <p className="text-sm text-gray-600">by {course.mentor}</p>
className={`font-semibold mb-2 cursor-pointer ${
mIdx === selModIdx ? "text-purple-600" : "text-gray-700"
}`}
onClick={() => { setSelectedModuleIdx(mIdx); setSelectedLessonIdx(0); }}
>
{mod.title}
</div>
<ul className="pl-4 border-l-2 border-gray-100">
{mod.lessons.map((lesson, lIdx) => (
<li
key={lesson.id}
className={
`py-1 px-2 rounded mb-1 cursor-pointer text-sm
${mIdx===selModIdx && lIdx===selLesIdx
? "bg-indigo-600 text-white"
: "hover:bg-indigo-100 text-gray-700"}`
}
onClick={() => { setSelectedModuleIdx(mIdx); setSelectedLessonIdx(lIdx); }}
>
{lesson.title}
</li>
))}
{mod.lessons.length === 0 && (
<li className="text-xs text-gray-400 pl-1 py-1">No lessons</li>
)}
</ul>
</li>
))}
</ul>
)}
</aside>
{/* Main: show lesson or course video/desc/mark as read */}
<main className="flex-1 bg-white rounded-xl shadow-md p-6 min-h-80 max-w-2xl mx-auto">
{modules.length > 0 && lessons.length > 0 && currentLesson ? (
<>
<h2 className="text-2xl font-bold mb-2">{currentLesson.title}</h2>
{currentLesson.video_url && (
<div className="aspect-video rounded overflow-hidden my-4 shadow-lg">
<iframe
src={getEmbedUrl(currentLesson.video_url)}
title={currentLesson.title}
allowFullScreen
width="100%"
height="100%"
className="w-full h-full"
/>
</div> </div>
)}
{currentLesson.description && (
<div className="text-gray-700 mb-6">{currentLesson.description}</div>
)}
{/* Navigation / mark as read */}
<div className="flex justify-between gap-2">
<button
className="px-4 py-2 rounded bg-gray-200 hover:bg-gray-300 disabled:opacity-60"
onClick={prev}
disabled={selModIdx===0 && selLesIdx===0}
>
Previous
</button>
{!isEnd ? (
<button className="px-4 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700" onClick={next}>
Next
</button>
) : (
<button
className={`px-4 py-2 rounded font-semibold ${
completed
? "bg-green-600 text-white"
: "bg-purple-600 text-white hover:bg-purple-700"
}`}
onClick={markComplete}
disabled={completed}
>
{completed ? "Course Completed ✓" : "Mark as Read"}
</button>
)}
</div> </div>
{completed && ( <div className="hidden md:flex items-center space-x-6 text-sm">
<div className="mt-6 bg-green-50 border border-green-300 p-4 rounded text-green-700 text-center font-bold shadow"> <div className="flex items-center text-gray-600">
🎉 Course Completed! Certificate coming soon. <BookOpen className="w-4 h-4 mr-2" />
<span>{modules.length} modules</span>
</div> </div>
)} <div className="flex items-center text-gray-600">
</> <Play className="w-4 h-4 mr-2" />
) : <span>{getTotalLessons()} lessons</span>
// Course has no modules or no lessons </div>
( <div className="flex items-center text-gray-600">
<div> <Users className="w-4 h-4 mr-2" />
<h2 className="text-2xl font-bold mb-3">{course.title}</h2> <span>{course.students} students</span>
<p className="mb-6 text-gray-700">{course.description}</p>
{(course.embed_url || course.video_url) ? (
<div className="aspect-video rounded-lg overflow-hidden my-5 shadow-lg">
<iframe
src={getEmbedUrl(course.embed_url || course.video_url)}
title={`Video for ${course.title}`}
allowFullScreen
width="100%"
height="100%"
className="w-full h-full"
/>
</div> </div>
) : (
<div className="text-gray-400 italic mb-4">No video available for this course yet.</div>
)}
<div className="mt-8 text-gray-500 text-center">
No modules or lessons yet.
</div> </div>
</div> </div>
)} </div>
</main> </header>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="lg:grid lg:grid-cols-12 lg:gap-8">
{/* Sidebar */}
<aside className="lg:col-span-4 xl:col-span-3">
<div className="bg-white rounded-xl shadow-sm border p-6 sticky top-8">
<h2 className="text-lg font-semibold text-gray-900 mb-4">Course Content</h2>
{/* Debug Info - Enhanced */}
<div className="mb-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<h3 className="text-sm font-semibold text-blue-800 mb-2">🔍 Debug Info:</h3>
<div className="text-xs space-y-1 text-blue-700">
<p><strong>Course ID:</strong> {courseId}</p>
<p><strong>Modules Loaded:</strong> {modules.length}</p>
<p><strong>Total Lessons:</strong> {getTotalLessons()}</p>
<p><strong>Modules Loading:</strong> {modulesLoading ? 'Yes' : 'No'}</p>
<p><strong>Selected Module:</strong> {selectedModuleId || 'None'}</p>
<p><strong>Selected Lesson:</strong> {currentLesson?.title || 'None'}</p>
<p><strong>Expanded Modules:</strong> {Object.keys(expandedModules).length}</p>
</div>
{modules.length > 0 && (
<details className="mt-2">
<summary className="text-xs cursor-pointer text-blue-600">Show Raw Data</summary>
<pre className="text-xs mt-2 p-2 bg-gray-100 rounded overflow-auto max-h-32">
{JSON.stringify({ modules, lessons }, null, 2)}
</pre>
</details>
)}
</div>
{/* Loading State */}
{modulesLoading && (
<div className="text-center py-8">
<Loader2 className="h-8 w-8 animate-spin text-indigo-600 mx-auto mb-2" />
<p className="text-sm text-gray-600">Loading modules...</p>
</div>
)}
{/* No Modules State */}
{!modulesLoading && modules.length === 0 && (
<div className="text-center py-8">
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<h3 className="text-sm font-semibold text-yellow-800 mb-2">No Modules Found</h3>
<p className="text-xs text-yellow-700 mb-3">
This could mean:
</p>
<ul className="text-xs text-yellow-700 text-left space-y-1">
<li> No modules created for this course yet</li>
<li> API endpoint issues</li>
<li> Course ID mismatch</li>
</ul>
<button
onClick={() => fetchModulesAndLessons(courseId)}
className="mt-3 px-3 py-1 bg-yellow-600 text-white text-xs rounded hover:bg-yellow-700"
>
Retry Loading Modules
</button>
</div>
</div>
)}
{/* Modules List */}
{!modulesLoading && modules.length > 0 && (
<div className="space-y-2">
{modules.map((module, index) => (
<div key={module.id} className="border border-gray-200 rounded-lg overflow-hidden">
{/* Module Header */}
<button
onClick={() => toggleModule(module.id)}
className={`w-full px-4 py-3 text-left hover:bg-gray-50 flex items-center justify-between transition-colors ${
selectedModuleId === module.id ? 'bg-indigo-50 border-indigo-200' : 'bg-white'
}`}
>
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2">
<span className="flex-shrink-0 w-6 h-6 bg-indigo-100 text-indigo-800 rounded-full flex items-center justify-center text-xs font-semibold">
{index + 1}
</span>
<h3 className="font-medium text-sm text-gray-900 truncate">{module.title}</h3>
</div>
<p className="text-xs text-gray-500 mt-1 ml-8">
{lessons[module.id]?.length || 0} lessons
</p>
</div>
<div className="flex-shrink-0 ml-2">
{expandedModules[module.id] ? (
<ChevronDown className="w-4 h-4 text-gray-400" />
) : (
<ChevronRight className="w-4 h-4 text-gray-400" />
)}
</div>
</button>
{/* Lessons */}
{expandedModules[module.id] && (
<div className="bg-gray-50 border-t border-gray-200">
{lessons[module.id] && lessons[module.id].length > 0 ? (
lessons[module.id].map((lesson, lessonIndex) => (
<button
key={lesson.id}
onClick={() => selectLesson(module.id, lesson.id)}
className={`w-full px-6 py-3 text-left hover:bg-gray-100 transition-colors border-l-4 ${
selectedLessonId === lesson.id
? 'border-indigo-500 bg-indigo-50 text-indigo-900'
: 'border-transparent text-gray-700'
}`}
>
<div className="flex items-center space-x-3">
<div className={`flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-xs ${
selectedLessonId === lesson.id
? 'bg-indigo-500 text-white'
: 'bg-gray-300 text-gray-600'
}`}>
<Play className="w-2.5 h-2.5" />
</div>
<div className="flex-1 min-w-0">
<p className="font-medium text-sm truncate">{lesson.title}</p>
{lesson.duration && (
<p className={`text-xs flex items-center mt-1 ${
selectedLessonId === lesson.id ? 'text-indigo-600' : 'text-gray-500'
}`}>
<Clock className="w-3 h-3 mr-1" />
{lesson.duration}
</p>
)}
</div>
</div>
</button>
))
) : (
<div className="px-6 py-4 text-center">
<p className="text-xs text-gray-500">No lessons in this module</p>
</div>
)}
</div>
)}
</div>
))}
</div>
)}
</div>
</aside>
{/* Main Content */}
<main className="mt-8 lg:mt-0 lg:col-span-8 xl:col-span-9">
<div className="bg-white rounded-xl shadow-sm border overflow-hidden">
{currentLesson ? (
<>
{/* Video Player */}
{(currentLesson.embed_url || currentLesson.video_url) && (
<div className="aspect-video bg-black">
<iframe
src={getEmbedUrl(currentLesson.embed_url || currentLesson.video_url)}
title={currentLesson.title}
allowFullScreen
className="w-full h-full"
/>
</div>
)}
{/* Lesson Content */}
<div className="p-8">
{/* Lesson Header */}
<div className="mb-6">
<div className="flex items-center text-sm text-gray-500 mb-2">
<User className="w-4 h-4 mr-1" />
<span>by {course.mentor}</span>
<span className="mx-2"></span>
<span className="bg-indigo-100 text-indigo-800 px-2 py-1 rounded-full text-xs font-medium">
{course.difficulty}
</span>
</div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">{currentLesson.title}</h1>
{currentLesson.duration && (
<div className="flex items-center text-sm text-gray-600">
<Clock className="w-4 h-4 mr-1" />
<span>{currentLesson.duration}</span>
</div>
)}
</div>
{/* Lesson Description */}
{currentLesson.description && (
<div className="prose max-w-none mb-8">
<h2 className="text-xl font-semibold text-gray-900 mb-4">About this lesson</h2>
<div className="text-gray-700 leading-relaxed whitespace-pre-line bg-gray-50 p-4 rounded-lg">
{currentLesson.description}
</div>
</div>
)}
{/* Lesson Content */}
{currentLesson.content && (
<div className="prose max-w-none mb-8">
<h2 className="text-xl font-semibold text-gray-900 mb-4">Lesson Content</h2>
<div className="text-gray-700 leading-relaxed whitespace-pre-line bg-gray-50 p-4 rounded-lg">
{currentLesson.content}
</div>
</div>
)}
{/* Navigation */}
<div className="flex justify-between items-center pt-8 border-t border-gray-200">
<button
onClick={() => navigateLesson('prev')}
disabled={isFirstLesson()}
className="px-6 py-3 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium"
>
Previous Lesson
</button>
{!isLastLesson() ? (
<button
onClick={() => navigateLesson('next')}
className="px-6 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors font-medium"
>
Next Lesson
</button>
) : (
<button
onClick={markComplete}
disabled={completed}
className={`px-6 py-3 rounded-lg font-medium transition-colors ${
completed
? "bg-green-600 text-white cursor-not-allowed"
: "bg-purple-600 text-white hover:bg-purple-700"
}`}
>
{completed ? "✓ Course Completed" : "Mark as Complete"}
</button>
)}
</div>
{/* Completion Message */}
{completed && (
<div className="mt-8 bg-green-50 border border-green-200 rounded-lg p-6 text-center">
<div className="text-green-700">
<div className="text-4xl mb-2">🎉</div>
<h3 className="text-xl font-bold mb-2">Congratulations!</h3>
<p>You have successfully completed this course. Certificate coming soon!</p>
</div>
</div>
)}
</div>
</>
) : (
/* Course Overview */
<div className="p-8 text-center">
<div className="max-w-3xl mx-auto">
<h1 className="text-4xl font-bold text-gray-900 mb-4">{course.title}</h1>
<div className="flex items-center justify-center space-x-4 mb-6 text-sm">
<div className="flex items-center text-gray-600">
<User className="w-4 h-4 mr-1" />
<span>by {course.mentor}</span>
</div>
<div className="flex items-center">
<Star className="w-4 h-4 text-yellow-400 mr-1" />
<span className="text-gray-600">4.8</span>
</div>
<span className="bg-indigo-100 text-indigo-800 px-3 py-1 rounded-full text-sm font-medium">
{course.difficulty}
</span>
</div>
<p className="text-lg text-gray-700 mb-8 leading-relaxed">{course.description}</p>
{/* Course Stats */}
<div className="grid grid-cols-3 gap-8 mb-8">
<div className="text-center">
<div className="text-3xl font-bold text-indigo-600 mb-1">{modules.length}</div>
<div className="text-sm text-gray-600">Modules</div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-indigo-600 mb-1">{getTotalLessons()}</div>
<div className="text-sm text-gray-600">Lessons</div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-indigo-600 mb-1">{course.students}</div>
<div className="text-sm text-gray-600">Students</div>
</div>
</div>
{/* Course Intro Video */}
{(course.embed_url || course.video_url) && (
<div className="aspect-video rounded-xl overflow-hidden mb-8 shadow-lg bg-black">
<iframe
src={getEmbedUrl(course.embed_url || course.video_url)}
title={course.title}
allowFullScreen
className="w-full h-full"
/>
</div>
)}
{getTotalLessons() > 0 ? (
<div>
<p className="text-gray-600 mb-6">
Ready to start learning? Select a lesson from the course content to begin your journey.
</p>
<button
onClick={() => {
const firstModule = modules[0]
const firstLessons = lessons[firstModule?.id] || []
if (firstLessons.length > 0) {
selectLesson(firstModule.id, firstLessons[0].id)
}
}}
className="px-8 py-4 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 font-semibold text-lg transition-colors"
>
Start Learning
</button>
</div>
) : (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-yellow-800 mb-2">Coming Soon</h3>
<p className="text-yellow-700">Lessons are being prepared for this course. Check back soon!</p>
</div>
)}
</div>
</div>
)}
</div>
</main>
</div>
</div>
</div> </div>
) )
} }