Stage 2: student lesson viewer, Kinescope player, PDF files, prev/next nav, My Courses dashboard
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { notFound } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { KinescopePlayer } from "@/components/player/kinescope-player";
|
||||
import { LessonContent } from "@/components/student/lesson-content";
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ slug: string; lessonId: string }>;
|
||||
}
|
||||
|
||||
export default async function LessonPage({ params }: Props) {
|
||||
const { slug, lessonId } = await params;
|
||||
|
||||
const lesson = await prisma.lesson.findUnique({
|
||||
where: { id: lessonId, published: true },
|
||||
include: {
|
||||
files: { orderBy: { createdAt: "asc" } },
|
||||
module: {
|
||||
include: {
|
||||
course: {
|
||||
include: {
|
||||
modules: {
|
||||
orderBy: { order: "asc" },
|
||||
include: {
|
||||
lessons: {
|
||||
where: { published: true },
|
||||
orderBy: { order: "asc" },
|
||||
select: { id: true, title: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!lesson || lesson.module.course.slug !== slug) notFound();
|
||||
|
||||
// Build ordered flat list of all lessons for prev/next
|
||||
const allLessons = lesson.module.course.modules.flatMap((m) => m.lessons);
|
||||
const idx = allLessons.findIndex((l) => l.id === lessonId);
|
||||
const prevLesson = idx > 0 ? allLessons[idx - 1] : null;
|
||||
const nextLesson = idx < allLessons.length - 1 ? allLessons[idx + 1] : null;
|
||||
|
||||
const hasContent = lesson.content && Object.keys(lesson.content as object).length > 0;
|
||||
|
||||
function formatSize(bytes: number) {
|
||||
if (bytes < 1024) return `${bytes} Б`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} КБ`;
|
||||
return `${(bytes / 1024 / 1024).toFixed(1)} МБ`;
|
||||
}
|
||||
|
||||
return (
|
||||
<article className="max-w-3xl mx-auto px-6 py-8">
|
||||
{/* Title */}
|
||||
<h1 className="text-2xl font-bold mb-6 leading-snug">{lesson.title}</h1>
|
||||
|
||||
{/* Video */}
|
||||
{lesson.kinescopeId && (
|
||||
<div className="mb-8">
|
||||
<KinescopePlayer videoId={lesson.kinescopeId} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Text content */}
|
||||
{hasContent && (
|
||||
<div className="mb-8">
|
||||
<LessonContent content={lesson.content as object} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Files */}
|
||||
{lesson.files.length > 0 && (
|
||||
<div className="mb-8">
|
||||
<p className="text-xs font-bold uppercase tracking-widest mb-3" style={{ color: "var(--muted-foreground)" }}>
|
||||
Материалы урока
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
{lesson.files.map((file) => (
|
||||
<a
|
||||
key={file.id}
|
||||
href={file.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-3 px-4 py-3 text-sm transition-colors"
|
||||
style={{ border: "2px solid var(--border)", display: "flex" }}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.borderColor = "var(--foreground)")}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.borderColor = "var(--border)")}
|
||||
>
|
||||
<span className="text-lg">📎</span>
|
||||
<span className="flex-1 font-medium">{file.name}</span>
|
||||
<span className="text-xs" style={{ color: "var(--muted-foreground)" }}>
|
||||
{formatSize(file.size)}
|
||||
</span>
|
||||
<span className="text-xs underline" style={{ color: "var(--muted-foreground)" }}>
|
||||
Скачать
|
||||
</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Prev / Next navigation */}
|
||||
<div
|
||||
className="flex items-center justify-between pt-6 mt-6"
|
||||
style={{ borderTop: "2px solid var(--border)" }}
|
||||
>
|
||||
{prevLesson ? (
|
||||
<Link
|
||||
href={`/courses/${slug}/lessons/${prevLesson.id}`}
|
||||
className="btn-aubade text-sm max-w-[45%]"
|
||||
>
|
||||
← {prevLesson.title}
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
{nextLesson ? (
|
||||
<Link
|
||||
href={`/courses/${slug}/lessons/${nextLesson.id}`}
|
||||
className="btn-aubade btn-aubade-accent text-sm max-w-[45%] text-right"
|
||||
>
|
||||
{nextLesson.title} →
|
||||
</Link>
|
||||
) : (
|
||||
<div className="text-sm" style={{ color: "var(--muted-foreground)" }}>
|
||||
Последний урок курса
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user