Initial commit: FL-Akademie LMS mit Docker, Admin, Portal und Dokumentation.
Made-with: Cursor
This commit is contained in:
81
app/kurse/[slug]/lektionen/[lessonSlug]/page.tsx
Normal file
81
app/kurse/[slug]/lektionen/[lessonSlug]/page.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import Link from "next/link";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "@/lib/auth-options";
|
||||
import { getLessonContext } from "@/lib/course-queries";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { CompleteLessonButton } from "@/components/complete-lesson-button";
|
||||
|
||||
type Props = { params: Promise<{ slug: string; lessonSlug: string }> };
|
||||
|
||||
export default async function LessonPage({ params }: Props) {
|
||||
const { slug: courseSlug, lessonSlug } = await params;
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
redirect(`/login?callbackUrl=/kurse/${courseSlug}/lektionen/${lessonSlug}`);
|
||||
}
|
||||
|
||||
const ctx = await getLessonContext(courseSlug, lessonSlug);
|
||||
if (!ctx) notFound();
|
||||
|
||||
const enrollment = await prisma.enrollment.findUnique({
|
||||
where: {
|
||||
userId_courseId: { userId: session.user.id, courseId: ctx.course.id },
|
||||
},
|
||||
});
|
||||
if (!enrollment) {
|
||||
redirect(`/kurse/${ctx.course.slug}`);
|
||||
}
|
||||
|
||||
const lessonIds = ctx.course.modules.flatMap((m) => m.lessons.map((l) => l.id));
|
||||
const progressRows = await prisma.lessonProgress.findMany({
|
||||
where: { userId: session.user.id, lessonId: { in: lessonIds } },
|
||||
});
|
||||
const completed = new Set(
|
||||
progressRows.filter((p) => p.completedAt).map((p) => p.lessonId),
|
||||
);
|
||||
const currentDone = !!completed.has(ctx.lesson.id);
|
||||
|
||||
return (
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<p className="muted">
|
||||
<Link href={`/kurse/${ctx.course.slug}`}>← {ctx.course.title}</Link>
|
||||
</p>
|
||||
<div className="two-col">
|
||||
<nav className="panel" aria-label="Kurrikulum">
|
||||
{ctx.course.modules.map((m) => (
|
||||
<div key={m.id}>
|
||||
<div className="module-title">{m.title}</div>
|
||||
<ul className="curriculum">
|
||||
{m.lessons.map((l) => {
|
||||
const href = `/kurse/${ctx.course.slug}/lektionen/${l.slug}`;
|
||||
const active = l.id === ctx.lesson.id;
|
||||
return (
|
||||
<li key={l.id}>
|
||||
<Link href={href} className={active ? "active" : undefined}>
|
||||
{completed.has(l.id) ? "✓ " : ""}
|
||||
{l.title}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
<article className="panel prose">
|
||||
<h1 style={{ marginTop: 0 }}>{ctx.lesson.title}</h1>
|
||||
{ctx.lesson.videoUrl ? (
|
||||
<p className="muted">
|
||||
Video: <a href={ctx.lesson.videoUrl}>{ctx.lesson.videoUrl}</a>
|
||||
</p>
|
||||
) : null}
|
||||
<div dangerouslySetInnerHTML={{ __html: ctx.lesson.contentHtml }} />
|
||||
<CompleteLessonButton lessonId={ctx.lesson.id} initialCompleted={currentDone} />
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user