| | 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>
|
| | );
|
| | }
|
| |
|