226 lines
13 KiB
TypeScript
226 lines
13 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
|
import { Button } from '../ui/button';
|
|
import { Card } from '../ui/card';
|
|
import { Course } from '../../types';
|
|
import { PlayCircle, Star, Users, CheckCircle, Clock, Globe, ShieldCheck, Lock, ChevronDown, ChevronUp } from 'lucide-react';
|
|
import { useToast } from '../../context/ToastContext';
|
|
|
|
interface CourseDetailViewProps {
|
|
course: Course;
|
|
onBack: () => void;
|
|
onEnroll: () => void;
|
|
isEnrolled?: boolean;
|
|
}
|
|
|
|
export const CourseDetailView: React.FC<CourseDetailViewProps> = ({ course, onBack, onEnroll, isEnrolled }) => {
|
|
const { addToast: _addToast } = useToast();
|
|
const [activeTab, setActiveTab] = useState<'overview' | 'curriculum' | 'reviews'>('overview');
|
|
const [expandedModule, setExpandedModule] = useState<string | null>(course.modules?.[0].id || null);
|
|
|
|
const toggleModule = (id: string) => {
|
|
setExpandedModule(expandedModule === id ? null : id);
|
|
};
|
|
|
|
return (
|
|
<div className="animate-fadeIn pb-20 max-w-7xl mx-auto">
|
|
|
|
{/* Breadcrumb */}
|
|
<div className="mb-6">
|
|
<Button variant="ghost" onClick={onBack} className="pl-0 text-gray-400 hover:text-white">← Back to Courses</Button>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
|
|
{/* Left Content */}
|
|
<div className="lg:col-span-2 space-y-8">
|
|
|
|
{/* Header */}
|
|
<div>
|
|
<h1 className="text-3xl md:text-4xl font-display font-bold text-white mb-4">{course.title}</h1>
|
|
<p className="text-xl text-gray-300 mb-6 font-light">{course.description}</p>
|
|
|
|
<div className="flex flex-wrap items-center gap-6 text-sm text-gray-400 mb-6">
|
|
{course.rating && (
|
|
<span className="flex items-center gap-1 text-kodo-gold font-bold">
|
|
<Star className="w-4 h-4 fill-current" /> {course.rating}
|
|
</span>
|
|
)}
|
|
<span className="flex items-center gap-1">
|
|
<Users className="w-4 h-4" /> {(course.studentCount || 0).toLocaleString()} students
|
|
</span>
|
|
<span className="flex items-center gap-1">
|
|
<Clock className="w-4 h-4" /> {course.duration} total
|
|
</span>
|
|
<span className="flex items-center gap-1">
|
|
<Globe className="w-4 h-4" /> English
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3">
|
|
<img src={`https://ui-avatars.com/api/?name=${course.instructor}&background=random`} className="w-10 h-10 rounded-full" />
|
|
<div>
|
|
<div className="text-xs text-gray-500 uppercase">Created by</div>
|
|
<div className="text-sm font-bold text-white text-kodo-cyan cursor-pointer hover:underline">{course.instructor}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<div className="border-b border-kodo-steel flex gap-6">
|
|
{['overview', 'curriculum', 'reviews'].map(tab => (
|
|
<button
|
|
key={tab}
|
|
onClick={() => setActiveTab(tab as any)}
|
|
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-gray-500 hover:text-gray-300'}`}
|
|
>
|
|
{tab}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Tab Content */}
|
|
{activeTab === 'overview' && (
|
|
<div className="space-y-8 animate-fadeIn">
|
|
<Card variant="default">
|
|
<h3 className="font-bold text-white text-lg mb-4">What you'll learn</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
{course.whatYouWillLearn?.map((item, i) => (
|
|
<div key={i} className="flex gap-3 text-sm text-gray-300">
|
|
<CheckCircle className="w-4 h-4 text-kodo-lime flex-shrink-0 mt-0.5" />
|
|
<span>{item}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
|
|
<div>
|
|
<h3 className="font-bold text-white text-lg mb-4">Requirements</h3>
|
|
<ul className="list-disc pl-5 space-y-1 text-sm text-gray-400">
|
|
{course.requirements?.map((req, i) => (
|
|
<li key={i}>{req}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'curriculum' && (
|
|
<div className="space-y-4 animate-fadeIn">
|
|
<div className="flex justify-between items-center text-sm text-gray-400 mb-2">
|
|
<span>{course.modules?.length} Modules • {course.modules?.reduce((acc, m) => acc + m.lessons.length, 0)} Lessons</span>
|
|
<button className="text-kodo-cyan hover:underline" onClick={() => setExpandedModule(expandedModule ? null : 'all')}>
|
|
{expandedModule === 'all' ? 'Collapse All' : 'Expand All'}
|
|
</button>
|
|
</div>
|
|
|
|
{course.modules?.map((module) => (
|
|
<div key={module.id} className="border border-kodo-steel rounded-lg overflow-hidden bg-kodo-ink/30">
|
|
<div
|
|
className="p-4 flex justify-between items-center cursor-pointer hover:bg-white/5 transition-colors"
|
|
onClick={() => toggleModule(module.id)}
|
|
>
|
|
<h4 className="font-bold text-white flex items-center gap-3">
|
|
{expandedModule === module.id || expandedModule === 'all' ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
|
{module.title}
|
|
</h4>
|
|
<span className="text-xs text-gray-500">{module.lessons.length} lectures</span>
|
|
</div>
|
|
|
|
{(expandedModule === module.id || expandedModule === 'all') && (
|
|
<div className="border-t border-kodo-steel">
|
|
{module.lessons.map((lesson) => (
|
|
<div key={lesson.id} className="p-3 pl-8 flex justify-between items-center hover:bg-white/5 border-b border-kodo-steel/30 last:border-0">
|
|
<div className="flex items-center gap-3 text-sm text-gray-300">
|
|
{lesson.type === 'video' ? <PlayCircle className="w-4 h-4" /> : <ShieldCheck className="w-4 h-4" />}
|
|
{lesson.title}
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
{lesson.isLocked && !isEnrolled && <Lock className="w-3 h-3 text-gray-500" />}
|
|
<span className="text-xs text-gray-500">{lesson.duration}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'reviews' && (
|
|
<div className="space-y-6 animate-fadeIn">
|
|
{course.reviews?.map(review => (
|
|
<div key={review.id} className="border-b border-kodo-steel/50 pb-6">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<img src={review.avatar} className="w-10 h-10 rounded-full" />
|
|
<div>
|
|
<div className="font-bold text-white text-sm">{review.username}</div>
|
|
<div className="flex text-kodo-gold text-xs">
|
|
{[...Array(5)].map((_, i) => <Star key={i} className={`w-3 h-3 ${i < review.rating ? 'fill-current' : 'text-gray-700'}`} />)}
|
|
</div>
|
|
</div>
|
|
<span className="ml-auto text-xs text-gray-500">{review.date}</span>
|
|
</div>
|
|
<p className="text-sm text-gray-300">{review.comment}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Right Sidebar */}
|
|
<div className="relative">
|
|
<div className="sticky top-24 space-y-6">
|
|
<Card variant="default" className="p-0 overflow-hidden border-kodo-cyan/30 shadow-neon-cyan/10">
|
|
{/* Preview Video Placeholder */}
|
|
<div className="relative aspect-video bg-black group cursor-pointer">
|
|
<img src={course.thumbnailUrl} className="w-full h-full object-cover opacity-80" />
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<div className="w-16 h-16 bg-white/90 rounded-full flex items-center justify-center shadow-lg group-hover:scale-110 transition-transform">
|
|
<PlayCircle className="w-8 h-8 text-black fill-current" />
|
|
</div>
|
|
</div>
|
|
<div className="absolute bottom-4 text-center w-full text-white font-bold text-sm drop-shadow-md">Preview Course</div>
|
|
</div>
|
|
|
|
<div className="p-6">
|
|
<div className="text-3xl font-display font-bold text-white mb-2">
|
|
{isEnrolled ? 'Enrolled' : course.price && course.price > 0 ? `$${course.price}` : 'Free'}
|
|
</div>
|
|
{course.price && course.price > 0 && !isEnrolled && (
|
|
<p className="text-gray-400 text-xs mb-6 line-through">$199.99 (85% off)</p>
|
|
)}
|
|
|
|
{isEnrolled ? (
|
|
<Button variant="primary" className="w-full h-12 text-lg" onClick={onEnroll}>
|
|
CONTINUE LEARNING
|
|
</Button>
|
|
) : (
|
|
<div className="space-y-3">
|
|
<Button variant="primary" className="w-full h-12 text-lg" onClick={onEnroll}>
|
|
ENROLL NOW
|
|
</Button>
|
|
<p className="text-center text-xs text-gray-500">30-Day Money-Back Guarantee</p>
|
|
</div>
|
|
)}
|
|
|
|
<div className="mt-6 space-y-3">
|
|
<h4 className="font-bold text-white text-sm">This course includes:</h4>
|
|
<ul className="text-sm text-gray-400 space-y-2">
|
|
<li className="flex items-center gap-3"><PlayCircle className="w-4 h-4" /> {course.duration} on-demand video</li>
|
|
<li className="flex items-center gap-3"><ShieldCheck className="w-4 h-4" /> Full lifetime access</li>
|
|
<li className="flex items-center gap-3"><Globe className="w-4 h-4" /> Access on mobile and TV</li>
|
|
{course.certificateAvailable && (
|
|
<li className="flex items-center gap-3"><Star className="w-4 h-4" /> Certificate of completion</li>
|
|
)}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|