|
|
import React, { useState, useEffect } from 'react';
|
|
|
import { Link } from 'react-router-dom';
|
|
|
import { createPageUrl } from '@/utils';
|
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
|
import { Eye, Activity, Smile, ArrowLeft, BookOpen } from 'lucide-react';
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
import SlideNavigation from '@/components/lessons/SlideNavigation';
|
|
|
import SlideContainer from '@/components/lessons/SlideContainer';
|
|
|
import { chapter1Slides } from '@/components/lessons/Chapter1Slides';
|
|
|
import { chapter2Slides } from '@/components/lessons/Chapter2Slides';
|
|
|
import { chapter3Slides } from '@/components/lessons/Chapter3Slides';
|
|
|
|
|
|
const chapters = [
|
|
|
{
|
|
|
id: 1,
|
|
|
title: "Object Detection",
|
|
|
subtitle: "How computers find things",
|
|
|
icon: Eye,
|
|
|
color: "from-blue-500 to-cyan-500",
|
|
|
slides: chapter1Slides
|
|
|
},
|
|
|
{
|
|
|
id: 2,
|
|
|
title: "Pose Estimation",
|
|
|
subtitle: "Tracking body movement",
|
|
|
icon: Activity,
|
|
|
color: "from-purple-500 to-pink-500",
|
|
|
slides: chapter2Slides
|
|
|
},
|
|
|
{
|
|
|
id: 3,
|
|
|
title: "Emotion Recognition",
|
|
|
subtitle: "Understanding feelings",
|
|
|
icon: Smile,
|
|
|
color: "from-emerald-500 to-teal-500",
|
|
|
slides: chapter3Slides
|
|
|
}
|
|
|
];
|
|
|
|
|
|
|
|
|
const COMPLETED_KEY = 'completedChapters_v1';
|
|
|
const TTL_MS = 60 * 60 * 1000;
|
|
|
|
|
|
function loadCompletedChapters() {
|
|
|
try {
|
|
|
const raw = localStorage.getItem(COMPLETED_KEY);
|
|
|
if (!raw) return [];
|
|
|
|
|
|
const parsed = JSON.parse(raw);
|
|
|
const savedAt = parsed?.savedAt;
|
|
|
const value = parsed?.value;
|
|
|
|
|
|
if (!Array.isArray(value) || typeof savedAt !== 'number') return [];
|
|
|
|
|
|
if (Date.now() - savedAt > TTL_MS) {
|
|
|
localStorage.removeItem(COMPLETED_KEY);
|
|
|
return [];
|
|
|
}
|
|
|
|
|
|
return value;
|
|
|
} catch {
|
|
|
return [];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function saveCompletedChapters(next) {
|
|
|
localStorage.setItem(
|
|
|
COMPLETED_KEY,
|
|
|
JSON.stringify({ value: next, savedAt: Date.now() })
|
|
|
);
|
|
|
}
|
|
|
|
|
|
export default function Lessons() {
|
|
|
const [selectedChapter, setSelectedChapter] = useState(null);
|
|
|
const [currentSlide, setCurrentSlide] = useState(0);
|
|
|
const [completedChapters, setCompletedChapters] = useState(loadCompletedChapters);
|
|
|
|
|
|
useEffect(() => {
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
|
const chapterParam = urlParams.get('chapter');
|
|
|
if (chapterParam) {
|
|
|
const chapterIndex = parseInt(chapterParam) - 1;
|
|
|
if (chapterIndex >= 0 && chapterIndex < chapters.length) {
|
|
|
setSelectedChapter(chapterIndex);
|
|
|
}
|
|
|
}
|
|
|
}, []);
|
|
|
|
|
|
const handleChapterSelect = (index) => {
|
|
|
setSelectedChapter(index);
|
|
|
setCurrentSlide(0);
|
|
|
};
|
|
|
|
|
|
const handleNext = () => {
|
|
|
const chapter = chapters[selectedChapter];
|
|
|
if (currentSlide < chapter.slides.length - 1) {
|
|
|
setCurrentSlide(currentSlide + 1);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const handleMarkComplete = () => {
|
|
|
if (!completedChapters.includes(selectedChapter)) {
|
|
|
const updated = [...completedChapters, selectedChapter];
|
|
|
setCompletedChapters(updated);
|
|
|
saveCompletedChapters(updated);
|
|
|
}
|
|
|
|
|
|
setSelectedChapter(null);
|
|
|
setCurrentSlide(0);
|
|
|
};
|
|
|
|
|
|
const handlePrev = () => {
|
|
|
if (currentSlide > 0) {
|
|
|
setCurrentSlide(currentSlide - 1);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const handleHome = () => {
|
|
|
setSelectedChapter(null);
|
|
|
setCurrentSlide(0);
|
|
|
};
|
|
|
|
|
|
const allChaptersCompleted = completedChapters.length === chapters.length;
|
|
|
|
|
|
|
|
|
if (selectedChapter === null) {
|
|
|
return (
|
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 pt-24 pb-12 px-6">
|
|
|
{/* Background Effects */}
|
|
|
<div className="fixed inset-0 overflow-hidden pointer-events-none">
|
|
|
<div className="absolute top-20 left-10 w-72 h-72 bg-blue-500/20 rounded-full blur-3xl animate-pulse" />
|
|
|
<div className="absolute bottom-20 right-10 w-96 h-96 bg-purple-500/20 rounded-full blur-3xl animate-pulse" />
|
|
|
</div>
|
|
|
|
|
|
<div className="max-w-4xl mx-auto relative">
|
|
|
<motion.div
|
|
|
initial={{ opacity: 0, y: -20 }}
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
className="text-center mb-12"
|
|
|
>
|
|
|
<Link to={createPageUrl('Home')}>
|
|
|
<Button variant="ghost" className="text-white/70 hover:text-white mb-6">
|
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
|
Back to Home
|
|
|
</Button>
|
|
|
</Link>
|
|
|
|
|
|
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-white/10 backdrop-blur-sm border border-white/20 mb-6">
|
|
|
<BookOpen className="w-4 h-4 text-cyan-400" />
|
|
|
<span className="text-white/90 text-sm font-medium">Choose a Lesson</span>
|
|
|
</div>
|
|
|
|
|
|
<h1 className="text-4xl md:text-5xl font-black text-white mb-4">
|
|
|
AI Vision Lessons
|
|
|
</h1>
|
|
|
<p className="text-xl text-white/70">
|
|
|
Select a chapter to start learning
|
|
|
</p>
|
|
|
</motion.div>
|
|
|
|
|
|
<div className="grid gap-6">
|
|
|
{chapters.map((chapter, index) => {
|
|
|
const isCompleted = completedChapters.includes(index);
|
|
|
return (
|
|
|
<motion.div
|
|
|
key={chapter.id}
|
|
|
initial={{ opacity: 0, y: 30 }}
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
transition={{ delay: 0.1 + index * 0.1 }}
|
|
|
>
|
|
|
<motion.button
|
|
|
onClick={() => handleChapterSelect(index)}
|
|
|
className="w-full text-left"
|
|
|
whileHover={{ scale: 1.02 }}
|
|
|
whileTap={{ scale: 0.98 }}
|
|
|
>
|
|
|
<div className={`relative overflow-hidden rounded-3xl p-8 bg-gradient-to-br ${chapter.color} shadow-2xl`}>
|
|
|
<div className="absolute top-0 right-0 w-40 h-40 bg-white/10 rounded-full -translate-y-1/2 translate-x-1/2" />
|
|
|
|
|
|
{isCompleted && (
|
|
|
<div className="absolute top-4 right-4 w-10 h-10 rounded-full bg-emerald-500 flex items-center justify-center shadow-lg">
|
|
|
<svg className="w-6 h-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
|
|
</svg>
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
|
<div className="flex items-center gap-6">
|
|
|
<div className="w-20 h-20 rounded-2xl bg-white/20 flex items-center justify-center">
|
|
|
<chapter.icon className="w-10 h-10 text-white" />
|
|
|
</div>
|
|
|
<div className="flex-1">
|
|
|
<div className="text-white/80 text-sm font-medium mb-1">
|
|
|
Chapter {chapter.id}
|
|
|
</div>
|
|
|
<h2 className="text-2xl md:text-3xl font-bold text-white mb-2">
|
|
|
{chapter.title}
|
|
|
</h2>
|
|
|
<p className="text-white/80">
|
|
|
{chapter.subtitle} • {chapter.slides.length} slides
|
|
|
</p>
|
|
|
</div>
|
|
|
<div className="hidden md:block">
|
|
|
<div className="w-12 h-12 rounded-full bg-white/20 flex items-center justify-center">
|
|
|
<ArrowLeft className="w-6 h-6 text-white rotate-180" />
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</motion.button>
|
|
|
</motion.div>
|
|
|
);
|
|
|
})}
|
|
|
</div>
|
|
|
|
|
|
{/* Quick Quiz Link */}
|
|
|
<motion.div
|
|
|
initial={{ opacity: 0 }}
|
|
|
animate={{ opacity: 1 }}
|
|
|
transition={{ delay: 0.5 }}
|
|
|
className="mt-16 text-center"
|
|
|
>
|
|
|
{allChaptersCompleted ? (
|
|
|
<>
|
|
|
<div className="inline-flex items-center gap-2 px-5 py-2 rounded-full bg-emerald-500/20 border border-emerald-500/30 mb-6">
|
|
|
<span className="text-2xl">🎉</span>
|
|
|
<span className="text-emerald-400 font-semibold">All Lessons Completed!</span>
|
|
|
</div>
|
|
|
<p className="text-white/80 mb-6 text-lg">Ready to test your knowledge?</p>
|
|
|
<Link to={createPageUrl('Quiz')}>
|
|
|
<Button className="bg-gradient-to-r from-yellow-500 to-orange-500 hover:from-yellow-600 hover:to-orange-600 rounded-2xl px-12 py-7 text-xl font-bold text-white border-0 shadow-2xl shadow-orange-500/30 transition-all hover:scale-105">
|
|
|
🎯 Take the Quiz Now
|
|
|
</Button>
|
|
|
</Link>
|
|
|
</>
|
|
|
) : (
|
|
|
<>
|
|
|
<div className="inline-flex items-center gap-2 px-5 py-2 rounded-full bg-blue-500/20 border border-blue-500/30 mb-6">
|
|
|
<span className="text-lg">📚</span>
|
|
|
<span className="text-blue-400 font-semibold">
|
|
|
{completedChapters.length} / {chapters.length} Chapters Completed
|
|
|
</span>
|
|
|
</div>
|
|
|
<p className="text-white/60 mb-6">Complete all lessons to unlock the quiz!</p>
|
|
|
<Button disabled className="bg-slate-700 text-slate-400 rounded-2xl px-12 py-7 text-xl font-bold border-0 cursor-not-allowed opacity-60">
|
|
|
🔒 Quiz Locked
|
|
|
</Button>
|
|
|
</>
|
|
|
)}
|
|
|
</motion.div>
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
|
|
|
const chapter = chapters[selectedChapter];
|
|
|
const slide = chapter.slides[currentSlide];
|
|
|
|
|
|
return (
|
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900">
|
|
|
{/* Background Effects */}
|
|
|
<div className="fixed inset-0 overflow-hidden pointer-events-none">
|
|
|
<div className="absolute top-20 left-10 w-72 h-72 bg-blue-500/20 rounded-full blur-3xl animate-pulse" />
|
|
|
<div className="absolute bottom-20 right-10 w-96 h-96 bg-purple-500/20 rounded-full blur-3xl animate-pulse" />
|
|
|
</div>
|
|
|
|
|
|
{/* Chapter Header */}
|
|
|
<div className="fixed top-0 left-0 right-0 z-40 bg-slate-900/80 backdrop-blur-xl border-b border-white/10">
|
|
|
<div className="max-w-4xl mx-auto px-6 py-4">
|
|
|
<div className="flex items-center gap-4">
|
|
|
<div className={`w-10 h-10 rounded-xl bg-gradient-to-br ${chapter.color} flex items-center justify-center`}>
|
|
|
<chapter.icon className="w-5 h-5 text-white" />
|
|
|
</div>
|
|
|
<div>
|
|
|
<p className="text-white/60 text-sm">Chapter {chapter.id}</p>
|
|
|
<h2 className="text-white font-bold">{chapter.title}</h2>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
{/* Slide Content */}
|
|
|
<SlideContainer slideKey={currentSlide}>
|
|
|
{slide.content}
|
|
|
|
|
|
{/* Mark as Complete Button - Only on last slide */}
|
|
|
{currentSlide === chapter.slides.length - 1 && (
|
|
|
<motion.div
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
transition={{ delay: 0.5 }}
|
|
|
className="mt-12 text-center"
|
|
|
>
|
|
|
{completedChapters.includes(selectedChapter) ? (
|
|
|
<div className="inline-flex items-center gap-3 px-8 py-4 rounded-2xl bg-emerald-500/20 border-2 border-emerald-500/40">
|
|
|
<motion.div
|
|
|
initial={{ scale: 0 }}
|
|
|
animate={{ scale: 1 }}
|
|
|
transition={{ type: "spring" }}
|
|
|
>
|
|
|
<svg className="w-8 h-8 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
|
|
</svg>
|
|
|
</motion.div>
|
|
|
<span className="text-emerald-400 font-bold text-xl">Chapter Already Completed!</span>
|
|
|
</div>
|
|
|
) : (
|
|
|
<motion.button
|
|
|
onClick={handleMarkComplete}
|
|
|
className="px-12 py-6 rounded-2xl bg-gradient-to-r from-emerald-500 to-teal-500 hover:from-emerald-600 hover:to-teal-600 text-white font-bold text-xl border-0 shadow-2xl shadow-emerald-500/30 transition-all"
|
|
|
whileHover={{ scale: 1.05 }}
|
|
|
whileTap={{ scale: 0.95 }}
|
|
|
>
|
|
|
<span className="flex items-center gap-3">
|
|
|
<svg className="w-7 h-7" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
|
</svg>
|
|
|
Mark as Complete
|
|
|
</span>
|
|
|
</motion.button>
|
|
|
)}
|
|
|
</motion.div>
|
|
|
)}
|
|
|
</SlideContainer>
|
|
|
|
|
|
{/* Navigation */}
|
|
|
<SlideNavigation
|
|
|
currentSlide={currentSlide}
|
|
|
totalSlides={chapter.slides.length}
|
|
|
onPrev={handlePrev}
|
|
|
onNext={handleNext}
|
|
|
onHome={handleHome}
|
|
|
chapterTitle={chapter.title}
|
|
|
/>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
|