YOUSEF2434's picture
Upload 96 files
a566fb0 verified
raw
history blame
13.8 kB
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
}
];
// 60-minute TTL storage (localStorage has no built-in expiration) [web:17]
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); // remove expired key [web:16]
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);
}
// Return to chapter selection
setSelectedChapter(null);
setCurrentSlide(0);
};
const handlePrev = () => {
if (currentSlide > 0) {
setCurrentSlide(currentSlide - 1);
}
};
const handleHome = () => {
setSelectedChapter(null);
setCurrentSlide(0);
};
const allChaptersCompleted = completedChapters.length === chapters.length;
// Chapter Selection View
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>
);
}
// Lesson View
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>
);
}