first commit

This commit is contained in:
AyrisAI
2026-07-23 16:55:44 +03:00
commit bd543c3bda
61 changed files with 17313 additions and 0 deletions
+351
View File
@@ -0,0 +1,351 @@
'use client';
import { useState, useEffect, use } from 'react';
import Link from 'next/link';
import { notFound } from 'next/navigation';
import { getLessonBySlug, getLessons } from '@/lib/actions/lessonActions';
import { VideoPlayer } from '@/components/VideoPlayer';
import { CodeBlock } from '@/components/CodeBlock';
import { ResourceCard } from '@/components/ResourceCard';
import {
FileText,
Code,
Download,
Clock,
FileArchive,
FileSpreadsheet,
ArrowLeft,
Share2,
Check,
Eye,
Calendar,
Sparkles,
ExternalLink,
Loader2
} from 'lucide-react';
import { YoutubeIcon as Youtube } from '@/components/icons/YoutubeIcon';
import { GithubIcon as Github } from '@/components/icons/BrandIcons';
export default function LessonDetailPage({
params
}: {
params: Promise<{ locale: string; slug: string }>;
}) {
const { locale, slug } = use(params);
const [activeTab, setActiveTab] = useState<'code' | 'notes' | 'downloads' | 'chapters'>('code');
const [copiedLink, setCopiedLink] = useState(false);
const [lesson, setLesson] = useState<any>(null);
const [relatedLessons, setRelatedLessons] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadData() {
setLoading(true);
const data = await getLessonBySlug(slug);
if (data) {
setLesson(data);
// Auto-select first non-empty tab
const hasCode = data.codeSnippets && data.codeSnippets.length > 0;
const hasNotes = data.notesMarkdown && data.notesMarkdown.length > 0;
const hasDownloads = data.downloads && data.downloads.length > 0;
const hasChapters = data.chapters && data.chapters.length > 0;
if (hasCode) setActiveTab('code');
else if (hasNotes) setActiveTab('notes');
else if (hasDownloads) setActiveTab('downloads');
else if (hasChapters) setActiveTab('chapters');
const all = await getLessons();
setRelatedLessons(all.filter((v: any) => v.id !== data.id).slice(0, 3));
}
setLoading(false);
}
loadData();
}, [slug]);
if (!loading && !lesson) {
notFound();
}
if (loading || !lesson) {
return (
<div className="max-w-7xl mx-auto px-4 py-24 text-center text-slate-400 flex flex-col items-center justify-center gap-3">
<Loader2 className="w-8 h-8 animate-spin text-red-500" />
<p className="text-sm font-semibold">Loading tutorial...</p>
</div>
);
}
const hasCode = lesson.codeSnippets && lesson.codeSnippets.length > 0;
const hasNotes = lesson.notesMarkdown && lesson.notesMarkdown.length > 0;
const hasDownloads = lesson.downloads && lesson.downloads.length > 0;
const hasChapters = lesson.chapters && lesson.chapters.length > 0;
const hasAnyResources = hasCode || hasNotes || hasDownloads || hasChapters;
const handleShare = async () => {
try {
await navigator.clipboard.writeText(window.location.href);
setCopiedLink(true);
setTimeout(() => setCopiedLink(false), 2000);
} catch (err) {
console.error('Share error:', err);
}
};
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10 space-y-10 flex-1 w-full">
{/* Breadcrumb & Navigation Back */}
<div className="flex flex-wrap items-center justify-between gap-4">
<div className="flex items-center gap-2 text-xs text-slate-400">
<Link href={`/${locale}`} className="hover:text-white transition-colors">Home</Link>
<span>/</span>
<Link href={`/${locale}/lessons`} className="hover:text-white transition-colors">Lessons</Link>
<span>/</span>
<span className="text-slate-200 font-semibold truncate max-w-xs">{lesson.title}</span>
</div>
<div className="flex items-center gap-2">
<button
onClick={handleShare}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-slate-900 hover:bg-slate-800 text-slate-300 text-xs font-semibold border border-slate-800 transition-colors"
>
{copiedLink ? (
<>
<Check className="w-3.5 h-3.5 text-emerald-400" />
<span className="text-emerald-400">Link Copied</span>
</>
) : (
<>
<Share2 className="w-3.5 h-3.5 text-slate-400" />
<span>Share Page</span>
</>
)}
</button>
<Link
href={`/${locale}/lessons`}
className="flex items-center gap-1 px-3 py-1.5 rounded-xl bg-slate-900 hover:bg-slate-800 text-slate-300 text-xs font-semibold border border-slate-800 transition-colors"
>
<ArrowLeft className="w-3.5 h-3.5" />
<span>Back to Lessons</span>
</Link>
</div>
</div>
{/* Main Header Info */}
<div className="space-y-4">
<div className="flex flex-wrap items-center gap-2">
<span className="px-3 py-1 rounded-full bg-red-500/10 text-red-400 text-xs font-bold border border-red-500/20">
{lesson.category}
</span>
<span className="flex items-center gap-1 text-xs text-slate-400 bg-slate-900 px-3 py-1 rounded-full border border-slate-800">
<Calendar className="w-3.5 h-3.5 text-slate-500" />
{new Date(lesson.publishDate).toLocaleDateString()}
</span>
<span className="flex items-center gap-1 text-xs text-slate-400 bg-slate-900 px-3 py-1 rounded-full border border-slate-800">
<Eye className="w-3.5 h-3.5 text-slate-500" />
{lesson.viewsCount.toLocaleString()} Views
</span>
{hasDownloads && (
<span className="flex items-center gap-1 text-xs text-slate-400 bg-slate-900 px-3 py-1 rounded-full border border-slate-800">
<Download className="w-3.5 h-3.5 text-slate-500" />
{lesson.downloadCount.toLocaleString()} Downloads
</span>
)}
</div>
<h1 className="text-2xl sm:text-4xl font-black text-white tracking-tight leading-tight">
{lesson.title}
</h1>
<p className="text-sm text-slate-300 max-w-3xl leading-relaxed">
{lesson.summary}
</p>
</div>
{/* Embedded Video Player */}
<VideoPlayer
youtubeId={lesson.youtubeId}
youtubeUrl={lesson.youtubeUrl}
title={lesson.title}
chapters={lesson.chapters}
/>
{/* Resource Tabs (Shown ONLY if resources exist) */}
{hasAnyResources && (
<div className="space-y-6">
{/* Tab Header Buttons */}
<div className="flex items-center gap-2 border-b border-slate-800 pb-2 overflow-x-auto scrollbar-none">
{hasCode && (
<button
onClick={() => setActiveTab('code')}
className={`flex items-center gap-2 px-5 py-3 rounded-2xl text-xs font-bold transition-all whitespace-nowrap ${
activeTab === 'code'
? 'bg-red-600 text-white shadow-lg shadow-red-600/30'
: 'bg-slate-900 hover:bg-slate-800 text-slate-400 hover:text-white border border-slate-800'
}`}
>
<Code className="w-4 h-4" />
<span>Code Blocks ({lesson.codeSnippets.length} Files)</span>
</button>
)}
{hasNotes && (
<button
onClick={() => setActiveTab('notes')}
className={`flex items-center gap-2 px-5 py-3 rounded-2xl text-xs font-bold transition-all whitespace-nowrap ${
activeTab === 'notes'
? 'bg-red-600 text-white shadow-lg shadow-red-600/30'
: 'bg-slate-900 hover:bg-slate-800 text-slate-400 hover:text-white border border-slate-800'
}`}
>
<FileText className="w-4 h-4" />
<span>Lesson Notes & Summary</span>
</button>
)}
{hasDownloads && (
<button
onClick={() => setActiveTab('downloads')}
className={`flex items-center gap-2 px-5 py-3 rounded-2xl text-xs font-bold transition-all whitespace-nowrap ${
activeTab === 'downloads'
? 'bg-red-600 text-white shadow-lg shadow-red-600/30'
: 'bg-slate-900 hover:bg-slate-800 text-slate-400 hover:text-white border border-slate-800'
}`}
>
<Download className="w-4 h-4" />
<span>Downloads & Links ({lesson.downloads.length})</span>
</button>
)}
{hasChapters && (
<button
onClick={() => setActiveTab('chapters')}
className={`flex items-center gap-2 px-5 py-3 rounded-2xl text-xs font-bold transition-all whitespace-nowrap ${
activeTab === 'chapters'
? 'bg-red-600 text-white shadow-lg shadow-red-600/30'
: 'bg-slate-900 hover:bg-slate-800 text-slate-400 hover:text-white border border-slate-800'
}`}
>
<Clock className="w-4 h-4" />
<span>Video Chapters ({lesson.chapters.length})</span>
</button>
)}
</div>
{/* Tab Contents */}
<div>
{/* TAB 1: CODE BLOCKS */}
{activeTab === 'code' && hasCode && (
<div className="space-y-4">
<div className="p-4 rounded-xl bg-red-950/20 border border-red-800/30 text-xs text-red-300 flex items-center justify-between">
<span>💡 You can inspect all code snippets by clicking file tabs and copy them directly into your project using "Copy Code".</span>
</div>
<CodeBlock snippets={lesson.codeSnippets} />
</div>
)}
{/* TAB 2: LESSON NOTES */}
{activeTab === 'notes' && hasNotes && (
<div className="p-6 rounded-2xl bg-slate-900/60 border border-slate-800 space-y-4">
<h3 className="font-extrabold text-white text-lg flex items-center gap-2">
<FileText className="w-5 h-5 text-red-400" />
<span>Key Lesson Notes & Takeaways</span>
</h3>
<div className="space-y-3">
{lesson.notesMarkdown.map((note: string, idx: number) => (
<div key={idx} className="p-4 rounded-xl bg-slate-950 border border-slate-800 text-xs text-slate-200 leading-relaxed">
{note}
</div>
))}
</div>
</div>
)}
{/* TAB 3: DOWNLOADS & LINKS */}
{activeTab === 'downloads' && hasDownloads && (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{lesson.downloads.map((item: any, idx: number) => (
<div key={idx} className="p-5 rounded-2xl bg-slate-900/80 border border-slate-800 flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-slate-800 flex items-center justify-center text-red-400 flex-shrink-0">
{item.type === 'zip' && <FileArchive className="w-5 h-5" />}
{item.type === 'github' && <Github className="w-5 h-5 text-white" />}
{item.type === 'pdf' && <FileSpreadsheet className="w-5 h-5 text-emerald-400" />}
{item.type === 'link' && <ExternalLink className="w-5 h-5 text-sky-400" />}
</div>
<div>
<h4 className="font-bold text-white text-xs">{item.title}</h4>
{item.size && <p className="text-[11px] text-slate-500 font-mono">Size: {item.size}</p>}
</div>
</div>
<a
href={item.url}
target="_blank"
rel="noopener noreferrer"
className="px-4 py-2 rounded-xl bg-red-600 hover:bg-red-500 text-white text-xs font-bold flex items-center gap-1.5 transition-colors shadow-md shadow-red-600/20"
>
<Download className="w-3.5 h-3.5" />
<span>Download / Open</span>
</a>
</div>
))}
</div>
)}
{/* TAB 4: CHAPTERS */}
{activeTab === 'chapters' && hasChapters && (
<div className="p-6 rounded-2xl bg-slate-900/60 border border-slate-800 space-y-3">
<h3 className="font-bold text-white text-sm mb-4">Timestamped Video Index</h3>
<div className="space-y-2">
{lesson.chapters.map((chap: any) => (
<div key={chap.time} className="p-3 rounded-xl bg-slate-950 border border-slate-800 flex items-center justify-between text-xs">
<div className="flex items-center gap-3">
<span className="font-mono text-red-400 font-bold bg-slate-900 px-2 py-1 rounded border border-slate-800">
{chap.time}
</span>
<span className="text-white font-medium">{chap.title}</span>
</div>
<a
href={`${lesson.youtubeUrl}&t=${chap.seconds}s`}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-red-400 hover:underline flex items-center gap-1"
>
<Youtube className="w-3.5 h-3.5" />
<span>Watch</span>
</a>
</div>
))}
</div>
</div>
)}
</div>
</div>
)}
{/* Related YouTube Videos */}
<div className="pt-10 border-t border-slate-800 space-y-6">
<h3 className="text-xl font-extrabold text-white flex items-center gap-2">
<Sparkles className="w-5 h-5 text-red-500" />
<span>Other Popular Video Tutorials</span>
</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{relatedLessons.map((rel) => (
<ResourceCard key={rel.id} lesson={rel} locale={locale} />
))}
</div>
</div>
</div>
);
}