mirror of
https://github.com/th30d4y/OpenLearnX.git
synced 2026-05-26 19:26:33 +00:00
docker
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { useRouter, useParams } from "next/navigation"
|
||||
import { Loader2, Play, Clock, BookOpen, ChevronDown, ChevronRight, User, Users, Star } from "lucide-react"
|
||||
import { Loader2, Play, Clock, BookOpen, ChevronDown, ChevronRight, User, Users, Star, Award, TrendingUp, CheckCircle, ArrowRight } from "lucide-react"
|
||||
import { toast } from "react-hot-toast"
|
||||
import api from "@/lib/api"
|
||||
import { useAuth } from "@/context/auth-context"
|
||||
@@ -62,7 +62,7 @@ export default function CoursePage() {
|
||||
const [expandedModules, setExpandedModules] = useState<{ [moduleId: string]: boolean }>({})
|
||||
const [completed, setCompleted] = useState(false)
|
||||
|
||||
// ✅ Certificate Modal State
|
||||
// Certificate Modal State
|
||||
const [showCertificateModal, setShowCertificateModal] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -83,13 +83,11 @@ export default function CoursePage() {
|
||||
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) {
|
||||
@@ -107,11 +105,9 @@ export default function CoursePage() {
|
||||
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: {
|
||||
@@ -128,7 +124,6 @@ export default function CoursePage() {
|
||||
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`, {
|
||||
@@ -149,7 +144,6 @@ export default function CoursePage() {
|
||||
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)) {
|
||||
@@ -160,13 +154,11 @@ export default function CoursePage() {
|
||||
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)
|
||||
}
|
||||
@@ -193,7 +185,6 @@ export default function CoursePage() {
|
||||
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',
|
||||
@@ -201,7 +192,6 @@ export default function CoursePage() {
|
||||
}
|
||||
})
|
||||
|
||||
// If admin fails, try public endpoint
|
||||
if (!lessonsResponse.ok) {
|
||||
lessonsResponse = await fetch(`http://127.0.0.1:5000/api/modules/${module.id}/lessons`, {
|
||||
headers: {
|
||||
@@ -225,11 +215,9 @@ export default function CoursePage() {
|
||||
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
|
||||
}
|
||||
@@ -246,7 +234,6 @@ export default function CoursePage() {
|
||||
setLessons(lessonsData)
|
||||
setExpandedModules(expandedState)
|
||||
|
||||
// Auto-select first lesson if available
|
||||
if (!selectedModuleId && modulesList.length > 0) {
|
||||
const firstModule = modulesList[0]
|
||||
const firstModuleLessons = lessonsData[firstModule.id] || []
|
||||
@@ -258,7 +245,6 @@ export default function CoursePage() {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: embed URL
|
||||
function getEmbedUrl(url?: string): string | undefined {
|
||||
if (!url) return undefined
|
||||
const regExp = /(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/))([^#&?]{11})/
|
||||
@@ -279,7 +265,6 @@ export default function CoursePage() {
|
||||
const selectLesson = (moduleId: string, lessonId: string) => {
|
||||
setSelectedModuleId(moduleId)
|
||||
setSelectedLessonId(lessonId)
|
||||
// Auto-expand the module when lesson is selected
|
||||
setExpandedModules(prev => ({
|
||||
...prev,
|
||||
[moduleId]: true
|
||||
@@ -324,10 +309,9 @@ export default function CoursePage() {
|
||||
return allLessons.length > 0 && allLessons[allLessons.length - 1].id === selectedLessonId
|
||||
}
|
||||
|
||||
// ✅ Updated markComplete function to show certificate modal
|
||||
const markComplete = () => {
|
||||
setCompleted(true)
|
||||
setShowCertificateModal(true) // Show certificate modal instead of just toast
|
||||
setShowCertificateModal(true)
|
||||
}
|
||||
|
||||
const getTotalLessons = () => {
|
||||
@@ -338,10 +322,24 @@ export default function CoursePage() {
|
||||
|
||||
if (authLoading || loading) {
|
||||
return (
|
||||
<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 className="min-h-screen bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900 flex items-center justify-center relative overflow-hidden">
|
||||
{/* Animated background elements */}
|
||||
<div className="absolute inset-0">
|
||||
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-purple-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse"></div>
|
||||
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-yellow-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse animation-delay-1000"></div>
|
||||
<div className="absolute bottom-1/4 left-1/3 w-96 h-96 bg-pink-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse animation-delay-2000"></div>
|
||||
</div>
|
||||
<div className="text-center z-10">
|
||||
<div className="relative">
|
||||
<Loader2 className="h-16 w-16 animate-spin text-white mx-auto mb-6 drop-shadow-lg" />
|
||||
<div className="absolute inset-0 h-16 w-16 border-4 border-transparent border-t-purple-400 rounded-full animate-ping mx-auto"></div>
|
||||
</div>
|
||||
<p className="text-xl text-white font-semibold tracking-wide animate-pulse">Loading your learning journey...</p>
|
||||
<div className="mt-4 flex justify-center space-x-1">
|
||||
<div className="w-2 h-2 bg-white rounded-full animate-bounce"></div>
|
||||
<div className="w-2 h-2 bg-white rounded-full animate-bounce animation-delay-200"></div>
|
||||
<div className="w-2 h-2 bg-white rounded-full animate-bounce animation-delay-400"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -349,14 +347,17 @@ export default function CoursePage() {
|
||||
|
||||
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>
|
||||
<div className="min-h-screen bg-gradient-to-br from-red-900 via-pink-900 to-purple-900 flex items-center justify-center p-4">
|
||||
<div className="text-center max-w-md mx-auto px-6">
|
||||
<div className="bg-white/10 backdrop-blur-lg border border-red-300/30 rounded-3xl p-10 shadow-2xl animate-bounce">
|
||||
<div className="w-20 h-20 bg-red-500 rounded-full flex items-center justify-center mx-auto mb-6 animate-pulse">
|
||||
<span className="text-3xl">⚠️</span>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-white mb-4">Oops! Something went wrong</h2>
|
||||
<p className="text-red-200 mb-8 leading-relaxed">{error}</p>
|
||||
<button
|
||||
onClick={fetchCourseData}
|
||||
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 transition-colors"
|
||||
className="px-8 py-4 bg-gradient-to-r from-red-500 to-pink-500 text-white rounded-2xl hover:from-red-600 hover:to-pink-600 shadow-lg transition-all duration-300 transform hover:scale-105 font-semibold text-lg"
|
||||
>
|
||||
Try Again
|
||||
</button>
|
||||
@@ -368,382 +369,429 @@ export default function CoursePage() {
|
||||
|
||||
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 className="min-h-screen bg-gradient-to-br from-gray-800 to-gray-900 flex items-center justify-center p-4">
|
||||
<div className="text-center max-w-sm bg-white/10 backdrop-blur-lg rounded-3xl shadow-2xl p-10 animate-fadeIn">
|
||||
<div className="w-24 h-24 bg-gray-500 rounded-full flex items-center justify-center mx-auto mb-6 animate-bounce">
|
||||
<span className="text-4xl">🔍</span>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-white mb-4">Course Not Found</h2>
|
||||
<p className="text-gray-300 leading-relaxed">The course you're looking for doesn't exist or may have been removed.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className="min-h-screen bg-gradient-to-br from-indigo-50 via-purple-50 to-pink-50">
|
||||
{/* Animated Background Elements */}
|
||||
<div className="fixed inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute -top-40 -right-40 w-96 h-96 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-30 animate-float"></div>
|
||||
<div className="absolute -bottom-40 -left-40 w-96 h-96 bg-yellow-300 rounded-full mix-blend-multiply filter blur-xl opacity-30 animate-float animation-delay-2000"></div>
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-pink-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse"></div>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<header className="bg-white shadow-sm border-b">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
<div className="flex items-center space-x-4">
|
||||
<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>
|
||||
<header className="bg-white/80 backdrop-blur-lg shadow-xl border-b border-purple-200 sticky top-0 z-50">
|
||||
<div className="w-full px-6 sm:px-10 lg:px-16 xl:px-20">
|
||||
<div className="flex items-center justify-between h-20">
|
||||
<div className="flex items-center space-x-6 animate-slideInLeft">
|
||||
<div className="w-14 h-14 bg-gradient-to-br from-purple-600 to-indigo-600 rounded-2xl flex items-center justify-center shadow-lg transform hover:scale-110 transition-transform duration-300">
|
||||
<span className="text-white font-extrabold text-2xl">OL</span>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-gray-900">{course.title}</h1>
|
||||
<p className="text-sm text-gray-600">by {course.mentor}</p>
|
||||
<h1 className="text-2xl font-extrabold bg-gradient-to-r from-purple-600 to-indigo-600 bg-clip-text text-transparent tracking-tight">{course.title}</h1>
|
||||
<p className="text-sm text-purple-700 font-semibold tracking-wide">by {course.mentor}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:flex items-center space-x-6 text-sm">
|
||||
<div className="flex items-center text-gray-600">
|
||||
<BookOpen className="w-4 h-4 mr-2" />
|
||||
<span>{modules.length} modules</span>
|
||||
<div className="hidden md:flex items-center space-x-8 text-sm text-purple-700 animate-slideInRight">
|
||||
<div className="flex items-center space-x-2 bg-purple-100 px-4 py-2 rounded-full">
|
||||
<BookOpen className="w-5 h-5 text-purple-600" />
|
||||
<span className="font-semibold">{modules.length} modules</span>
|
||||
</div>
|
||||
<div className="flex items-center text-gray-600">
|
||||
<Play className="w-4 h-4 mr-2" />
|
||||
<span>{getTotalLessons()} lessons</span>
|
||||
<div className="flex items-center space-x-2 bg-indigo-100 px-4 py-2 rounded-full">
|
||||
<Play className="w-5 h-5 text-indigo-600" />
|
||||
<span className="font-semibold">{getTotalLessons()} lessons</span>
|
||||
</div>
|
||||
<div className="flex items-center text-gray-600">
|
||||
<Users className="w-4 h-4 mr-2" />
|
||||
<span>{course.students} students</span>
|
||||
<div className="flex items-center space-x-2 bg-pink-100 px-4 py-2 rounded-full">
|
||||
<Users className="w-5 h-5 text-pink-600" />
|
||||
<span className="font-semibold">{course.students.toLocaleString()} students</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
)}
|
||||
<main className="w-full px-6 sm:px-10 lg:px-16 xl:px-20 py-12 grid grid-cols-1 lg:grid-cols-5 gap-12 relative z-10">
|
||||
|
||||
{/* Sidebar - Now takes up 2 columns on large screens */}
|
||||
<aside className="lg:col-span-2 animate-slideInLeft">
|
||||
<div className="bg-white/80 backdrop-blur-lg rounded-3xl shadow-2xl border border-purple-200 p-10 sticky top-28">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<h2 className="text-2xl font-extrabold bg-gradient-to-r from-purple-600 to-indigo-600 bg-clip-text text-transparent">Course Content</h2>
|
||||
<div className="w-8 h-8 bg-gradient-to-r from-purple-500 to-indigo-500 rounded-full flex items-center justify-center">
|
||||
<BookOpen className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
</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>
|
||||
)}
|
||||
{/* Enhanced Progress Bar */}
|
||||
<div className="mb-8 p-4 bg-gradient-to-r from-purple-50 to-indigo-50 rounded-2xl border border-purple-200">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-sm font-semibold text-purple-700">Progress</span>
|
||||
<span className="text-sm font-bold text-indigo-600">25%</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-3 overflow-hidden">
|
||||
<div className="bg-gradient-to-r from-purple-500 to-indigo-500 h-3 rounded-full transition-all duration-1000 ease-out animate-pulse" style={{width: '25%'}}></div>
|
||||
</div>
|
||||
</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>
|
||||
{/* Debug Info - Enhanced */}
|
||||
<div className="mb-8 p-5 bg-gradient-to-r from-blue-50 to-cyan-50 border border-blue-200 rounded-2xl animate-fadeIn">
|
||||
<h3 className="text-sm font-bold text-blue-800 mb-4 flex items-center">
|
||||
<TrendingUp className="w-4 h-4 mr-2" />
|
||||
🔍 Debug Info:
|
||||
</h3>
|
||||
<div className="text-xs space-y-2 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-4 border-t border-blue-200 pt-4">
|
||||
<summary className="text-xs cursor-pointer text-blue-600 font-semibold hover:text-blue-800 transition-colors">Show Raw Data</summary>
|
||||
<pre className="mt-3 text-xs p-4 bg-white rounded-xl shadow max-h-40 overflow-auto">
|
||||
{JSON.stringify({ modules, lessons }, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
{/* Loading State */}
|
||||
{modulesLoading && (
|
||||
<div className="text-center py-10 animate-pulse">
|
||||
<Loader2 className="h-12 w-12 animate-spin text-purple-500 mx-auto mb-4" />
|
||||
<p className="text-lg text-purple-700 font-semibold">Loading modules...</p>
|
||||
</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>
|
||||
{/* No Modules State */}
|
||||
{!modulesLoading && modules.length === 0 && (
|
||||
<div className="text-center py-8 animate-bounce">
|
||||
<div className="bg-gradient-to-r from-yellow-50 to-orange-50 border border-yellow-300 rounded-2xl p-6 text-yellow-800">
|
||||
<div className="w-16 h-16 bg-yellow-500 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-2xl">📚</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-bold mb-3">No Modules Found</h3>
|
||||
<p className="text-sm mb-4 leading-relaxed">
|
||||
This could mean:<br />
|
||||
• No modules created yet<br />
|
||||
• API endpoint issues<br />
|
||||
• Course ID mismatch
|
||||
</p>
|
||||
<button
|
||||
onClick={() => fetchModulesAndLessons(courseId)}
|
||||
className="px-6 py-3 bg-gradient-to-r from-yellow-500 to-orange-500 rounded-2xl text-white font-bold hover:from-yellow-600 hover:to-orange-600 transition-all duration-300 transform hover:scale-105 shadow-lg"
|
||||
>
|
||||
Retry Loading Modules
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modules List */}
|
||||
{!modulesLoading && modules.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
{modules.map((module, index) => (
|
||||
<div key={module.id} className="border border-purple-200 rounded-2xl overflow-hidden shadow-lg bg-white/60 backdrop-blur-sm hover:shadow-xl transition-all duration-300 animate-fadeInUp" style={{animationDelay: `${index * 100}ms`}}>
|
||||
{/* Module Header */}
|
||||
<button
|
||||
onClick={() => toggleModule(module.id)}
|
||||
className={`w-full px-6 py-5 text-left hover:bg-gradient-to-r hover:from-purple-50 hover:to-indigo-50 flex items-center justify-between transition-all duration-300 ${
|
||||
selectedModuleId === module.id ? 'bg-gradient-to-r from-purple-100 to-indigo-100 border-purple-300' : 'bg-white/80'
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="flex-shrink-0 w-10 h-10 bg-gradient-to-r from-purple-500 to-indigo-500 text-white rounded-full flex items-center justify-center font-bold text-sm shadow-lg transform hover:scale-110 transition-transform duration-300">
|
||||
{index + 1}
|
||||
</span>
|
||||
<h3 className="font-bold text-purple-900 truncate text-lg">{module.title}</h3>
|
||||
</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}
|
||||
<p className="text-sm text-purple-600 mt-2 ml-14 flex items-center">
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
{(lessons[module.id]?.length ?? 0) + (lessons[module.id]?.length === 1 ? ' lesson' : ' lessons')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0 ml-4">
|
||||
<div className={`transform transition-transform duration-300 ${expandedModules[module.id] ? 'rotate-180' : ''}`}>
|
||||
{expandedModules[module.id] ? (
|
||||
<ChevronDown className="w-6 h-6 text-purple-500" />
|
||||
) : (
|
||||
<ChevronRight className="w-6 h-6 text-purple-400" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* 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>
|
||||
|
||||
{/* ✅ Updated Completion Message */}
|
||||
{completed && !showCertificateModal && (
|
||||
<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 className="mb-4">You have successfully completed this course!</p>
|
||||
<button
|
||||
onClick={() => setShowCertificateModal(true)}
|
||||
className="px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium"
|
||||
>
|
||||
Get Your Certificate 🏆
|
||||
</button>
|
||||
</div>
|
||||
{/* Lessons */}
|
||||
{expandedModules[module.id] && (
|
||||
<div className="bg-gradient-to-r from-purple-50 to-indigo-50 border-t border-purple-200 animate-slideDown">
|
||||
{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-8 py-4 text-left hover:bg-gradient-to-r hover:from-purple-100 hover:to-indigo-100 transition-all duration-300 border-l-4 group ${
|
||||
selectedLessonId === lesson.id
|
||||
? 'border-purple-500 bg-gradient-to-r from-purple-100 to-indigo-100 text-purple-900 font-bold shadow-inner'
|
||||
: 'border-transparent text-purple-700 hover:border-purple-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-xs transition-all duration-300 group-hover:scale-110 ${
|
||||
selectedLessonId === lesson.id
|
||||
? 'bg-gradient-to-r from-purple-500 to-indigo-500 text-white shadow-lg'
|
||||
: 'bg-purple-200 text-purple-700 group-hover:bg-purple-300'
|
||||
}`}>
|
||||
<Play className="w-4 h-4" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="truncate font-semibold">{lesson.title}</p>
|
||||
{lesson.duration && (
|
||||
<p className={`text-xs flex items-center mt-1 ${
|
||||
selectedLessonId === lesson.id ? 'text-purple-700 font-semibold' : 'text-purple-500'
|
||||
}`}>
|
||||
<Clock className="w-4 h-4 mr-2" />
|
||||
{lesson.duration}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<ArrowRight className={`w-4 h-4 transition-all duration-300 ${
|
||||
selectedLessonId === lesson.id ? 'text-purple-600 transform scale-110' : 'text-transparent group-hover:text-purple-400'
|
||||
}`} />
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<p className="px-8 py-6 text-purple-600 text-sm italic text-center">No lessons in this module</p>
|
||||
)}
|
||||
</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>
|
||||
</aside>
|
||||
|
||||
{/* Main Content - Now takes up 3 columns on large screens for full width */}
|
||||
<section className="lg:col-span-3 animate-slideInRight">
|
||||
<div className="bg-white/80 backdrop-blur-lg rounded-3xl shadow-2xl border border-purple-200 overflow-hidden">
|
||||
{currentLesson ? (
|
||||
<>
|
||||
{/* Video Player */}
|
||||
{(currentLesson.embed_url || currentLesson.video_url) && (
|
||||
<div className="aspect-video bg-black rounded-t-3xl overflow-hidden relative group">
|
||||
<iframe
|
||||
src={getEmbedUrl(currentLesson.embed_url || currentLesson.video_url)}
|
||||
title={currentLesson.title}
|
||||
allowFullScreen
|
||||
className="w-full h-full"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Lesson Content */}
|
||||
<div className="p-16">
|
||||
{/* Lesson Header */}
|
||||
<div className="mb-12 animate-fadeInUp">
|
||||
<div className="flex items-center text-purple-600 space-x-4 mb-6">
|
||||
<div className="flex items-center space-x-2 bg-purple-100 px-6 py-3 rounded-full">
|
||||
<User className="w-6 h-6" />
|
||||
<span className="font-bold text-lg">{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">
|
||||
<span className="text-purple-300">•</span>
|
||||
<span className="bg-gradient-to-r from-purple-500 to-indigo-500 text-white px-6 py-3 rounded-full text-lg font-bold uppercase tracking-widest shadow-lg">
|
||||
{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>
|
||||
<h1 className="text-6xl font-extrabold bg-gradient-to-r from-purple-600 to-indigo-600 bg-clip-text text-transparent leading-tight mb-6 drop-shadow-sm">{currentLesson.title}</h1>
|
||||
{currentLesson.duration && (
|
||||
<div className="flex items-center text-purple-600 space-x-3 text-xl font-semibold">
|
||||
<div className="flex items-center space-x-2 bg-purple-100 px-6 py-3 rounded-full">
|
||||
<Clock className="w-6 h-6" />
|
||||
<span>{currentLesson.duration}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ✅ Certificate Modal */}
|
||||
{/* Lesson Description */}
|
||||
{currentLesson.description && (
|
||||
<section className="mb-16 animate-fadeInUp animation-delay-200">
|
||||
<h2 className="text-4xl font-bold bg-gradient-to-r from-purple-600 to-indigo-600 bg-clip-text text-transparent mb-8 border-b-2 border-purple-200 pb-4">
|
||||
About this lesson
|
||||
</h2>
|
||||
<article className="bg-gradient-to-r from-purple-50 to-indigo-50 rounded-3xl p-10 text-purple-900 prose max-w-none shadow-inner border border-purple-200 text-lg leading-relaxed">
|
||||
{currentLesson.description}
|
||||
</article>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Lesson Content */}
|
||||
{currentLesson.content && (
|
||||
<section className="mb-16 animate-fadeInUp animation-delay-400">
|
||||
<h2 className="text-4xl font-bold bg-gradient-to-r from-purple-600 to-indigo-600 bg-clip-text text-transparent mb-8 border-b-2 border-purple-200 pb-4">
|
||||
Lesson Content
|
||||
</h2>
|
||||
<article className="bg-gradient-to-r from-purple-50 to-indigo-50 rounded-3xl p-10 text-purple-900 prose max-w-none whitespace-pre-line shadow-inner border border-purple-200 text-lg leading-relaxed">
|
||||
{currentLesson.content}
|
||||
</article>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex justify-between items-center pt-12 border-t-2 border-purple-200 animate-fadeInUp animation-delay-600">
|
||||
<button
|
||||
onClick={() => navigateLesson('prev')}
|
||||
disabled={isFirstLesson()}
|
||||
className="px-12 py-5 bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 rounded-3xl hover:from-gray-200 hover:to-gray-300 disabled:opacity-50 disabled:cursor-not-allowed font-bold transition-all duration-300 transform hover:scale-105 shadow-lg text-xl"
|
||||
>
|
||||
← Previous Lesson
|
||||
</button>
|
||||
|
||||
{!isLastLesson() ? (
|
||||
<button
|
||||
onClick={() => navigateLesson('next')}
|
||||
className="px-12 py-5 bg-gradient-to-r from-purple-600 to-indigo-600 text-white rounded-3xl hover:from-purple-700 hover:to-indigo-700 font-bold transition-all duration-300 transform hover:scale-105 shadow-xl text-xl"
|
||||
>
|
||||
Next Lesson →
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={markComplete}
|
||||
disabled={completed}
|
||||
className={`px-12 py-5 rounded-3xl font-bold transition-all duration-300 transform hover:scale-105 shadow-xl text-xl ${
|
||||
completed
|
||||
? "bg-gradient-to-r from-green-500 to-emerald-500 text-white cursor-not-allowed shadow-inner"
|
||||
: "bg-gradient-to-r from-purple-600 to-pink-600 text-white hover:from-purple-700 hover:to-pink-700"
|
||||
}`}
|
||||
>
|
||||
{completed ? "✓ Course Completed" : "Mark as Complete"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Completion Message */}
|
||||
{completed && !showCertificateModal && (
|
||||
<div className="mt-16 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-3xl p-12 text-center shadow-2xl animate-bounce">
|
||||
<div className="text-green-700">
|
||||
<div className="text-8xl mb-8 animate-pulse">🎉</div>
|
||||
<h3 className="text-4xl font-extrabold mb-6 bg-gradient-to-r from-green-600 to-emerald-600 bg-clip-text text-transparent">Congratulations!</h3>
|
||||
<p className="mb-10 text-green-800 font-semibold text-2xl">
|
||||
You have successfully completed this course!
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setShowCertificateModal(true)}
|
||||
className="px-16 py-6 bg-gradient-to-r from-green-600 to-emerald-600 text-white rounded-3xl hover:from-green-700 hover:to-emerald-700 transition-all duration-300 transform hover:scale-105 font-bold text-xl shadow-xl"
|
||||
>
|
||||
Get Your Certificate 🏆
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
/* Course Overview */
|
||||
<div className="p-20 text-center max-w-5xl mx-auto text-purple-900 animate-fadeIn">
|
||||
<h1 className="text-7xl font-extrabold mb-10 bg-gradient-to-r from-purple-600 via-pink-600 to-indigo-600 bg-clip-text text-transparent drop-shadow-lg">{course.title}</h1>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-8 mb-16 text-purple-700 font-bold text-xl">
|
||||
<div className="flex items-center space-x-4 bg-purple-100 px-8 py-4 rounded-full shadow-lg transform hover:scale-105 transition-transform duration-300">
|
||||
<User className="w-8 h-8" />
|
||||
<span>by {course.mentor}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 bg-yellow-100 px-8 py-4 rounded-full shadow-lg transform hover:scale-105 transition-transform duration-300">
|
||||
<Star className="w-8 h-8 text-yellow-500" />
|
||||
<span>4.8 Rating</span>
|
||||
</div>
|
||||
<span className="bg-gradient-to-r from-purple-500 to-indigo-500 text-white px-8 py-4 rounded-full text-xl uppercase font-extrabold tracking-widest shadow-lg transform hover:scale-105 transition-transform duration-300">
|
||||
{course.difficulty}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-3xl max-w-5xl mx-auto mb-16 leading-relaxed tracking-wide text-purple-800">{course.description}</p>
|
||||
|
||||
<div className="grid grid-cols-3 gap-16 mb-16">
|
||||
<div className="text-center transform hover:scale-110 transition-transform duration-300">
|
||||
<div className="w-32 h-32 bg-gradient-to-r from-purple-500 to-indigo-500 rounded-full flex items-center justify-center mx-auto mb-6 shadow-xl">
|
||||
<span className="text-5xl font-extrabold text-white">{modules.length}</span>
|
||||
</div>
|
||||
<div className="uppercase text-purple-700 font-bold tracking-wide text-xl">Modules</div>
|
||||
</div>
|
||||
<div className="text-center transform hover:scale-110 transition-transform duration-300">
|
||||
<div className="w-32 h-32 bg-gradient-to-r from-pink-500 to-purple-500 rounded-full flex items-center justify-center mx-auto mb-6 shadow-xl">
|
||||
<span className="text-5xl font-extrabold text-white">{getTotalLessons()}</span>
|
||||
</div>
|
||||
<div className="uppercase text-purple-700 font-bold tracking-wide text-xl">Lessons</div>
|
||||
</div>
|
||||
<div className="text-center transform hover:scale-110 transition-transform duration-300">
|
||||
<div className="w-32 h-32 bg-gradient-to-r from-indigo-500 to-blue-500 rounded-full flex items-center justify-center mx-auto mb-6 shadow-xl">
|
||||
<span className="text-5xl font-extrabold text-white">{course.students.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="uppercase text-purple-700 font-bold tracking-wide text-xl">Students</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(course.embed_url || course.video_url) && (
|
||||
<div className="aspect-video rounded-3xl overflow-hidden shadow-2xl mx-auto max-w-6xl bg-black border-4 border-purple-600 mb-16 transform hover:scale-105 transition-transform duration-500">
|
||||
<iframe
|
||||
src={getEmbedUrl(course.embed_url || course.video_url)}
|
||||
title={course.title}
|
||||
allowFullScreen
|
||||
className="w-full h-full"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{getTotalLessons() > 0 ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
const firstModule = modules[0]
|
||||
const firstLessons = lessons[firstModule?.id] || []
|
||||
if (firstLessons.length > 0) {
|
||||
selectLesson(firstModule.id, firstLessons[0].id)
|
||||
}
|
||||
}}
|
||||
className="mt-12 px-20 py-8 bg-gradient-to-r from-purple-600 via-pink-600 to-indigo-600 text-white rounded-3xl hover:from-purple-700 hover:via-pink-700 hover:to-indigo-700 font-extrabold text-3xl shadow-2xl transition-all duration-300 transform hover:scale-110 hover:shadow-purple-500/25"
|
||||
>
|
||||
🚀 Start Learning Journey
|
||||
</button>
|
||||
) : (
|
||||
<div className="bg-gradient-to-r from-yellow-50 to-orange-50 border-2 border-yellow-300 rounded-3xl p-12 text-yellow-800 text-2xl font-bold max-w-lg mx-auto shadow-xl">
|
||||
<div className="w-24 h-24 bg-yellow-500 rounded-full flex items-center justify-center mx-auto mb-8">
|
||||
<span className="text-4xl">🚧</span>
|
||||
</div>
|
||||
<h3 className="text-3xl mb-6">Coming Soon</h3>
|
||||
<p className="font-normal text-yellow-700 text-xl">
|
||||
Amazing lessons are being crafted for this course. Check back soon!
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
{/* Certificate Modal */}
|
||||
{showCertificateModal && course && (
|
||||
<CertificateModal
|
||||
isOpen={showCertificateModal}
|
||||
@@ -752,9 +800,48 @@ export default function CoursePage() {
|
||||
courseMentor={course.mentor}
|
||||
courseId={course.id}
|
||||
userId={user?.uid || firebaseUser?.uid || 'anonymous'}
|
||||
walletId={user?.wallet || firebaseUser?.uid || 'no-wallet'} // Adjust based on your user structure
|
||||
walletId={user?.wallet || firebaseUser?.uid || 'no-wallet'}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Custom CSS for animations */}
|
||||
<style jsx>{`
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px); }
|
||||
50% { transform: translateY(-20px); }
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@keyframes slideInLeft {
|
||||
from { opacity: 0; transform: translateX(-50px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
@keyframes slideInRight {
|
||||
from { opacity: 0; transform: translateX(50px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
@keyframes slideDown {
|
||||
from { opacity: 0; max-height: 0; }
|
||||
to { opacity: 1; max-height: 500px; }
|
||||
}
|
||||
.animate-float { animation: float 6s ease-in-out infinite; }
|
||||
.animate-fadeIn { animation: fadeIn 1s ease-out; }
|
||||
.animate-fadeInUp { animation: fadeInUp 0.8s ease-out; }
|
||||
.animate-slideInLeft { animation: slideInLeft 0.8s ease-out; }
|
||||
.animate-slideInRight { animation: slideInRight 0.8s ease-out; }
|
||||
.animate-slideDown { animation: slideDown 0.3s ease-out; }
|
||||
.animation-delay-200 { animation-delay: 0.2s; }
|
||||
.animation-delay-400 { animation-delay: 0.4s; }
|
||||
.animation-delay-600 { animation-delay: 0.6s; }
|
||||
.animation-delay-1000 { animation-delay: 1s; }
|
||||
.animation-delay-2000 { animation-delay: 2s; }
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user