2026-01-07 09:31:02 +00:00
|
|
|
import React, { useState } from 'react';
|
|
|
|
|
import { Button } from '../ui/button';
|
|
|
|
|
import { ProgressBar } from '../ui/progress';
|
|
|
|
|
import { Course } from '../../types';
|
2026-01-13 18:47:57 +00:00
|
|
|
import {
|
|
|
|
|
ChevronLeft,
|
|
|
|
|
ChevronRight,
|
|
|
|
|
CheckCircle,
|
|
|
|
|
PlayCircle,
|
|
|
|
|
FileText,
|
|
|
|
|
HelpCircle,
|
|
|
|
|
Menu,
|
|
|
|
|
X,
|
|
|
|
|
} from 'lucide-react';
|
2026-01-26 13:12:17 +00:00
|
|
|
import { useToast } from '../../components/feedback/ToastProvider';
|
2026-01-07 09:31:02 +00:00
|
|
|
import { QuizModal } from './modals/QuizModal';
|
|
|
|
|
import { CertificateModal } from './modals/CertificateModal';
|
|
|
|
|
|
|
|
|
|
interface CourseLearningViewProps {
|
|
|
|
|
course: Course;
|
|
|
|
|
onBack: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
export const CourseLearningView: React.FC<CourseLearningViewProps> = ({
|
|
|
|
|
course,
|
|
|
|
|
onBack,
|
|
|
|
|
}) => {
|
2026-01-07 09:31:02 +00:00
|
|
|
const { addToast } = useToast();
|
2026-01-13 18:47:57 +00:00
|
|
|
const [activeLessonId, setActiveLessonId] = useState<string>(
|
|
|
|
|
course.modules?.[0]?.lessons[0]?.id || '',
|
|
|
|
|
);
|
2026-01-07 09:31:02 +00:00
|
|
|
const [completedLessons, setCompletedLessons] = useState<string[]>([]);
|
|
|
|
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
2026-01-13 18:47:57 +00:00
|
|
|
const [activeTab, setActiveTab] = useState<
|
|
|
|
|
'overview' | 'notes' | 'resources'
|
|
|
|
|
>('overview');
|
|
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
// Quiz State
|
|
|
|
|
const [showQuiz, setShowQuiz] = useState(false);
|
|
|
|
|
const [activeQuiz, setActiveQuiz] = useState<any>(null);
|
|
|
|
|
|
|
|
|
|
// Certificate State
|
|
|
|
|
const [showCertificate, setShowCertificate] = useState(false);
|
|
|
|
|
|
|
|
|
|
// Flattened lessons for navigation
|
2026-01-13 18:47:57 +00:00
|
|
|
const allLessons = course.modules?.flatMap((m) => m.lessons) || [];
|
|
|
|
|
const currentLessonIndex = allLessons.findIndex(
|
|
|
|
|
(l) => l.id === activeLessonId,
|
|
|
|
|
);
|
2026-01-07 09:31:02 +00:00
|
|
|
const currentLesson = allLessons[currentLessonIndex];
|
|
|
|
|
|
|
|
|
|
const handleNext = () => {
|
2026-01-13 18:47:57 +00:00
|
|
|
if (currentLessonIndex < allLessons.length - 1) {
|
|
|
|
|
const nextLesson = allLessons[currentLessonIndex + 1];
|
|
|
|
|
setActiveLessonId(nextLesson.id);
|
|
|
|
|
markComplete(currentLesson.id);
|
|
|
|
|
} else {
|
|
|
|
|
// Course finished
|
|
|
|
|
markComplete(currentLesson.id);
|
|
|
|
|
addToast('Course Completed! 🎉', 'success');
|
|
|
|
|
if (course.certificateAvailable) {
|
|
|
|
|
setShowCertificate(true);
|
2026-01-07 09:31:02 +00:00
|
|
|
}
|
2026-01-13 18:47:57 +00:00
|
|
|
}
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handlePrev = () => {
|
2026-01-13 18:47:57 +00:00
|
|
|
if (currentLessonIndex > 0) {
|
|
|
|
|
setActiveLessonId(allLessons[currentLessonIndex - 1].id);
|
|
|
|
|
}
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const markComplete = (id: string) => {
|
2026-01-13 18:47:57 +00:00
|
|
|
if (!completedLessons.includes(id)) {
|
|
|
|
|
setCompletedLessons([...completedLessons, id]);
|
|
|
|
|
}
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const startQuiz = (quizId: string) => {
|
2026-01-13 18:47:57 +00:00
|
|
|
// Mock Quiz Data
|
|
|
|
|
setActiveQuiz({
|
|
|
|
|
id: quizId,
|
|
|
|
|
title: 'Module Assessment',
|
|
|
|
|
passingScore: 70,
|
|
|
|
|
questions: [
|
|
|
|
|
{
|
|
|
|
|
id: 'q1',
|
|
|
|
|
question: 'What is the frequency range of a sub-bass?',
|
|
|
|
|
options: ['20-60Hz', '200-500Hz', '1-2kHz'],
|
|
|
|
|
correctIndex: 0,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'q2',
|
|
|
|
|
question: 'Which plugin is best for sidechaining?',
|
|
|
|
|
options: ['Reverb', 'Compressor', 'Delay'],
|
|
|
|
|
correctIndex: 1,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
setShowQuiz(true);
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
const progress = Math.round(
|
|
|
|
|
(completedLessons.length / allLessons.length) * 100,
|
|
|
|
|
);
|
2026-01-07 09:31:02 +00:00
|
|
|
|
|
|
|
|
return (
|
aesthetic-improvements: align spacing to 8px grid (Action 11.2.1.3)
- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid
- Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px)
- Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation
- Modified files across all components to ensure consistent 8px grid alignment
- Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
2026-01-16 10:50:46 +00:00
|
|
|
<div className="flex flex-col h-[calc(100vh-6rem)] -m-6 md:-m-12 bg-kodo-void">
|
2026-01-13 18:47:57 +00:00
|
|
|
{/* Header Bar */}
|
|
|
|
|
<div className="h-16 border-b border-kodo-steel bg-kodo-ink px-4 flex items-center justify-between shrink-0 z-20">
|
|
|
|
|
<div className="flex items-center gap-4">
|
|
|
|
|
<Button variant="ghost" size="sm" onClick={onBack}>
|
|
|
|
|
<ChevronLeft className="w-4 h-4 mr-1" /> Back
|
|
|
|
|
</Button>
|
|
|
|
|
<div className="h-6 w-px bg-kodo-steel"></div>
|
|
|
|
|
<h2 className="font-bold text-white text-sm md:text-base truncate max-w-md">
|
|
|
|
|
{course.title}
|
|
|
|
|
</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-4">
|
|
|
|
|
<div className="hidden md:block w-32">
|
|
|
|
|
<ProgressBar value={progress} color="lime" />
|
|
|
|
|
</div>
|
2026-01-16 00:59:31 +00:00
|
|
|
<div className="text-xs text-kodo-content-dim font-mono hidden md:block">
|
2026-01-13 18:47:57 +00:00
|
|
|
{progress}% Complete
|
|
|
|
|
</div>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
onClick={() => setSidebarOpen(!sidebarOpen)}
|
|
|
|
|
>
|
|
|
|
|
{sidebarOpen ? (
|
|
|
|
|
<X className="w-5 h-5" />
|
|
|
|
|
) : (
|
|
|
|
|
<Menu className="w-5 h-5" />
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-1 overflow-hidden">
|
|
|
|
|
{/* Main Content Area */}
|
|
|
|
|
<div className="flex-1 flex flex-col min-w-0 overflow-y-auto custom-scrollbar">
|
|
|
|
|
{/* Player Stage */}
|
|
|
|
|
<div className="bg-black aspect-video w-full flex items-center justify-center relative">
|
|
|
|
|
{currentLesson?.type === 'video' ? (
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<PlayCircle className="w-16 h-16 text-white opacity-50 mx-auto mb-4" />
|
2026-01-16 00:59:31 +00:00
|
|
|
<p className="text-kodo-content-dim">Video Player Placeholder</p>
|
|
|
|
|
<p className="text-xs text-kodo-content-dim mt-2">
|
2026-01-13 18:47:57 +00:00
|
|
|
{currentLesson.title}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
) : currentLesson?.type === 'quiz' ? (
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<HelpCircle className="w-16 h-16 text-kodo-gold mx-auto mb-4" />
|
|
|
|
|
<h3 className="text-white text-xl font-bold mb-4">
|
|
|
|
|
Quiz: {currentLesson.title}
|
|
|
|
|
</h3>
|
|
|
|
|
<Button
|
|
|
|
|
variant="primary"
|
|
|
|
|
onClick={() =>
|
|
|
|
|
currentLesson.quizId && startQuiz(currentLesson.quizId)
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
Start Quiz
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="p-8 max-w-2xl mx-auto text-left w-full h-full overflow-y-auto bg-kodo-graphite">
|
|
|
|
|
<h2 className="text-2xl font-bold text-white mb-4">
|
|
|
|
|
{currentLesson?.title}
|
|
|
|
|
</h2>
|
2026-01-16 00:59:31 +00:00
|
|
|
<p className="text-kodo-text-main leading-relaxed">
|
2026-01-13 18:47:57 +00:00
|
|
|
{currentLesson?.content ||
|
|
|
|
|
'This is a text-based lesson. Content would be rendered here in Markdown.'}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Tabs & Meta */}
|
|
|
|
|
<div className="p-6 md:p-8 max-w-5xl mx-auto w-full">
|
|
|
|
|
<div className="flex justify-between items-center mb-6">
|
2026-01-15 22:54:05 +00:00
|
|
|
<h1 className="text-3xl font-bold text-white">
|
2026-01-13 18:47:57 +00:00
|
|
|
{currentLesson?.title}
|
|
|
|
|
</h1>
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<Button
|
|
|
|
|
variant="secondary"
|
|
|
|
|
onClick={handlePrev}
|
|
|
|
|
disabled={currentLessonIndex === 0}
|
|
|
|
|
icon={<ChevronLeft className="w-4 h-4" />}
|
|
|
|
|
>
|
|
|
|
|
Prev
|
|
|
|
|
</Button>
|
|
|
|
|
<Button variant="primary" onClick={handleNext}>
|
|
|
|
|
{currentLessonIndex === allLessons.length - 1
|
|
|
|
|
? 'Finish Course'
|
|
|
|
|
: 'Next'}{' '}
|
|
|
|
|
<ChevronRight className="w-4 h-4 ml-1" />
|
2026-01-07 09:31:02 +00:00
|
|
|
</Button>
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
<div className="border-b border-kodo-steel flex gap-6 mb-6">
|
|
|
|
|
{['overview', 'notes', 'resources'].map((tab) => (
|
|
|
|
|
<button
|
|
|
|
|
key={tab}
|
|
|
|
|
onClick={() => setActiveTab(tab as any)}
|
2026-01-16 00:59:31 +00:00
|
|
|
className={`pb-3 text-sm font-bold uppercase tracking-wider border-b-2 transition-colors ${activeTab === tab ? 'border-kodo-cyan text-white' : 'border-transparent text-kodo-content-dim hover:text-kodo-text-main'}`}
|
2026-01-13 18:47:57 +00:00
|
|
|
>
|
|
|
|
|
{tab}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{activeTab === 'overview' && (
|
2026-01-16 00:59:31 +00:00
|
|
|
<div className="text-kodo-text-main space-y-4">
|
2026-01-13 18:47:57 +00:00
|
|
|
<p>
|
|
|
|
|
In this lesson, we cover the fundamentals of the topic. Make
|
|
|
|
|
sure to take notes.
|
|
|
|
|
</p>
|
|
|
|
|
<div className="p-4 bg-kodo-ink rounded border border-kodo-steel/50">
|
|
|
|
|
<h4 className="font-bold text-white mb-2">Key Takeaways</h4>
|
|
|
|
|
<ul className="list-disc pl-5 text-sm space-y-1">
|
|
|
|
|
<li>Understanding the core concept</li>
|
|
|
|
|
<li>Applying technique A to situation B</li>
|
|
|
|
|
<li>Common pitfalls to avoid</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{activeTab === 'notes' && (
|
|
|
|
|
<div>
|
|
|
|
|
<textarea
|
2026-01-16 10:40:13 +00:00
|
|
|
className="w-full h-40 bg-kodo-ink border border-kodo-steel rounded p-4 text-white resize-none focus:border-kodo-steel outline-none"
|
2026-01-13 18:47:57 +00:00
|
|
|
placeholder="Type your personal notes here..."
|
|
|
|
|
/>
|
|
|
|
|
<Button variant="secondary" size="sm" className="mt-2">
|
|
|
|
|
Save Note
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{activeTab === 'resources' && (
|
|
|
|
|
<div className="space-y-2">
|
aesthetic-improvements: align spacing to 8px grid (Action 11.2.1.3)
- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid
- Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px)
- Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation
- Modified files across all components to ensure consistent 8px grid alignment
- Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
2026-01-16 10:50:46 +00:00
|
|
|
<div className="flex items-center justify-between p-4 bg-kodo-ink rounded border border-kodo-steel">
|
|
|
|
|
<div className="flex items-center gap-4">
|
aesthetic-improvements: reduce decorative cyan in education and services (80/20 rule, batch 14)
- Education: CertificateModal decorative student name text, CourseLearningView decorative file icon, QuizModal decorative icon (3 instances)
- Services: chatService mock data decorative role color (2 instances)
- Total: ~4 files, ~5 instances replaced
- Preserved: Active/selected states (CourseLearningView active lesson background and icon, QuizModal selected answer, dashboard/TrackList current track indicators and playing animation, ThemeSwitcher selected theme, StatCard cyan color option - design system variant), functional elements (QuizModal progress bar), design system variants, semantic status indicators, interactive states, functional loading indicators, informational alerts/toasts
- Action 11.3.1.3 in progress (fourteenth batch: education and services components)
2026-01-16 10:34:18 +00:00
|
|
|
<FileText className="w-5 h-5 text-kodo-steel" />
|
2026-01-13 18:47:57 +00:00
|
|
|
<span className="text-sm text-white">
|
|
|
|
|
Lesson Slides.pdf
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<Button variant="ghost" size="sm">
|
|
|
|
|
Download
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
aesthetic-improvements: align spacing to 8px grid (Action 11.2.1.3)
- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid
- Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px)
- Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation
- Modified files across all components to ensure consistent 8px grid alignment
- Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
2026-01-16 10:50:46 +00:00
|
|
|
<div className="flex items-center justify-between p-4 bg-kodo-ink rounded border border-kodo-steel">
|
|
|
|
|
<div className="flex items-center gap-4">
|
2026-01-13 18:47:57 +00:00
|
|
|
<FileText className="w-5 h-5 text-kodo-magenta" />
|
|
|
|
|
<span className="text-sm text-white">
|
|
|
|
|
Project Files.zip
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<Button variant="ghost" size="sm">
|
|
|
|
|
Download
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{/* Sidebar (Curriculum) */}
|
|
|
|
|
{sidebarOpen && (
|
|
|
|
|
<div className="w-80 bg-kodo-graphite border-l border-kodo-steel flex flex-col flex-shrink-0 animate-slideInRight">
|
|
|
|
|
<div className="p-4 border-b border-kodo-steel font-bold text-white text-sm bg-kodo-ink">
|
|
|
|
|
Course Content
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex-1 overflow-y-auto custom-scrollbar">
|
|
|
|
|
{course.modules?.map((module, i) => (
|
|
|
|
|
<div key={module.id} className="border-b border-kodo-steel/30">
|
2026-01-16 00:59:31 +00:00
|
|
|
<div className="p-4 bg-kodo-ink/50 text-xs font-bold text-kodo-content-dim uppercase tracking-wider sticky top-0 backdrop-blur-sm z-10">
|
2026-01-13 18:47:57 +00:00
|
|
|
Section {i + 1}: {module.title}
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
{module.lessons.map((lesson) => {
|
|
|
|
|
const isActive = lesson.id === activeLessonId;
|
|
|
|
|
const isCompleted = completedLessons.includes(lesson.id);
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={lesson.id}
|
|
|
|
|
onClick={() => setActiveLessonId(lesson.id)}
|
aesthetic-improvements: align spacing to 8px grid (Action 11.2.1.3)
- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid
- Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px)
- Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation
- Modified files across all components to ensure consistent 8px grid alignment
- Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
2026-01-16 10:50:46 +00:00
|
|
|
className={`flex items-start gap-4 p-4 cursor-pointer border-l-2 transition-all hover:bg-white/5 ${isActive ? 'bg-kodo-cyan/10 border-kodo-cyan' : 'border-transparent'}`}
|
2026-01-13 18:47:57 +00:00
|
|
|
>
|
|
|
|
|
<div className="mt-0.5">
|
|
|
|
|
{isCompleted ? (
|
|
|
|
|
<CheckCircle className="w-4 h-4 text-kodo-lime" />
|
|
|
|
|
) : lesson.type === 'video' ? (
|
|
|
|
|
<PlayCircle
|
2026-01-16 00:59:31 +00:00
|
|
|
className={`w-4 h-4 ${isActive ? 'text-kodo-cyan' : 'text-kodo-content-dim'}`}
|
2026-01-13 18:47:57 +00:00
|
|
|
/>
|
|
|
|
|
) : (
|
2026-01-16 00:59:31 +00:00
|
|
|
<HelpCircle className="w-4 h-4 text-kodo-content-dim" />
|
2026-01-13 18:47:57 +00:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
<div
|
2026-01-16 00:59:31 +00:00
|
|
|
className={`text-sm font-medium leading-snug ${isActive ? 'text-white' : 'text-kodo-text-main'}`}
|
2026-01-07 09:31:02 +00:00
|
|
|
>
|
2026-01-13 18:47:57 +00:00
|
|
|
{lesson.title}
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-16 00:59:31 +00:00
|
|
|
<div className="text-[10px] text-kodo-content-dim mt-1 flex items-center gap-2">
|
2026-01-13 18:47:57 +00:00
|
|
|
<span>{lesson.duration}</span>
|
|
|
|
|
{lesson.type === 'quiz' && (
|
|
|
|
|
<span className="text-kodo-gold">Quiz</span>
|
|
|
|
|
)}
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
))}
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
)}
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{/* Modals */}
|
|
|
|
|
{showQuiz && activeQuiz && (
|
|
|
|
|
<QuizModal
|
|
|
|
|
quiz={activeQuiz}
|
|
|
|
|
onClose={() => setShowQuiz(false)}
|
|
|
|
|
onComplete={(score) => {
|
|
|
|
|
addToast(`Quiz Completed. Score: ${score}%`, 'info');
|
|
|
|
|
markComplete(currentLesson.id);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{showCertificate && (
|
|
|
|
|
<CertificateModal
|
|
|
|
|
studentName="Cyber Producer"
|
|
|
|
|
courseName={course.title}
|
|
|
|
|
completionDate={new Date().toLocaleDateString()}
|
|
|
|
|
onClose={() => setShowCertificate(false)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|