Pending course, rest ready for launch
This commit is contained in:
192
app/(public)/courses/[slug]/learn/page.tsx
Normal file
192
app/(public)/courses/[slug]/learn/page.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { db } from "@/lib/prisma";
|
||||
import { requireUser } from "@/lib/auth/requireUser";
|
||||
import StudentClassroomClient from "@/components/courses/StudentClassroomClient";
|
||||
import { parseLessonDescriptionMeta } from "@/lib/courses/lessonContent";
|
||||
|
||||
type LessonSelect = {
|
||||
id: string;
|
||||
title: unknown;
|
||||
description: unknown;
|
||||
videoUrl: string | null;
|
||||
youtubeUrl: string | null;
|
||||
estimatedDuration: number;
|
||||
isFreePreview: boolean;
|
||||
};
|
||||
type ModuleSelect = { id: string; title: unknown; lessons: LessonSelect[] };
|
||||
type CourseWithModules = { id: string; slug: string; title: unknown; price: unknown; modules: ModuleSelect[] };
|
||||
|
||||
function getText(value: unknown): string {
|
||||
if (!value) return "";
|
||||
if (typeof value === "string") return value;
|
||||
if (typeof value === "object") {
|
||||
const record = value as Record<string, unknown>;
|
||||
if (typeof record.es === "string") return record.es;
|
||||
if (typeof record.en === "string") return record.en;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
type PageProps = {
|
||||
params: Promise<{ slug: string }>;
|
||||
searchParams: Promise<{ lesson?: string }>;
|
||||
};
|
||||
|
||||
export default async function CourseLearnPage({ params, searchParams }: PageProps) {
|
||||
const { slug } = await params;
|
||||
const { lesson: requestedLessonId } = await searchParams;
|
||||
|
||||
const user = await requireUser();
|
||||
|
||||
const courseSelect = {
|
||||
id: true,
|
||||
slug: true,
|
||||
title: true,
|
||||
price: true,
|
||||
modules: {
|
||||
orderBy: { orderIndex: "asc" as const },
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
lessons: {
|
||||
orderBy: { orderIndex: "asc" as const },
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
videoUrl: true,
|
||||
youtubeUrl: true,
|
||||
estimatedDuration: true,
|
||||
isFreePreview: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const course = (await db.course.findUnique({
|
||||
where: { slug },
|
||||
select: courseSelect,
|
||||
})) as CourseWithModules | null;
|
||||
|
||||
if (!course) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
let enrollment: { id: string } | null = null;
|
||||
let isEnrolled: boolean;
|
||||
|
||||
if (!user?.id) {
|
||||
// Anonymous: no enrollment, preview-only access
|
||||
const allLessons = course.modules.flatMap((m) =>
|
||||
m.lessons.map((l) => ({ id: l.id, isFreePreview: l.isFreePreview }))
|
||||
);
|
||||
const firstPreviewLesson = allLessons.find((l) => l.isFreePreview);
|
||||
if (!firstPreviewLesson) {
|
||||
redirect(`/courses/${slug}`);
|
||||
}
|
||||
isEnrolled = false;
|
||||
} else {
|
||||
enrollment = await db.enrollment.findUnique({
|
||||
where: {
|
||||
userId_courseId: {
|
||||
userId: user.id,
|
||||
courseId: course.id,
|
||||
},
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
const isFree = Number(course.price) === 0;
|
||||
|
||||
if (!enrollment) {
|
||||
if (isFree) {
|
||||
enrollment = await db.enrollment.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
courseId: course.id,
|
||||
amountPaid: 0,
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
isEnrolled = true;
|
||||
} else {
|
||||
// Paid course, no enrollment: allow only if there are preview lessons
|
||||
const allLessons = course.modules.flatMap((m) =>
|
||||
m.lessons.map((l) => ({ id: l.id, isFreePreview: l.isFreePreview }))
|
||||
);
|
||||
const firstPreviewLesson = allLessons.find((l) => l.isFreePreview);
|
||||
if (!firstPreviewLesson) {
|
||||
redirect(`/courses/${slug}`);
|
||||
}
|
||||
isEnrolled = false;
|
||||
}
|
||||
} else {
|
||||
isEnrolled = true;
|
||||
}
|
||||
}
|
||||
|
||||
const completedProgress =
|
||||
isEnrolled && user
|
||||
? await db.userProgress.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
isCompleted: true,
|
||||
lesson: {
|
||||
module: { courseId: course.id },
|
||||
},
|
||||
},
|
||||
select: { lessonId: true },
|
||||
})
|
||||
: [];
|
||||
|
||||
const modules = course.modules.map((module) => ({
|
||||
id: module.id,
|
||||
title: getText(module.title) || "Untitled module",
|
||||
lessons: module.lessons.map((lesson) => {
|
||||
const lessonMeta = parseLessonDescriptionMeta(lesson.description);
|
||||
return {
|
||||
id: lesson.id,
|
||||
title: getText(lesson.title) || "Untitled lesson",
|
||||
description: lessonMeta.text,
|
||||
contentType: lessonMeta.contentType,
|
||||
materialUrl: lessonMeta.materialUrl,
|
||||
videoUrl: lesson.videoUrl,
|
||||
youtubeUrl: lesson.youtubeUrl,
|
||||
estimatedDuration: lesson.estimatedDuration,
|
||||
isFreePreview: lesson.isFreePreview,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
const flattenedLessons = modules.flatMap((module) => module.lessons);
|
||||
const flattenedLessonIds = flattenedLessons.map((l) => l.id);
|
||||
|
||||
let initialSelectedLessonId: string;
|
||||
if (isEnrolled) {
|
||||
initialSelectedLessonId =
|
||||
requestedLessonId && flattenedLessonIds.includes(requestedLessonId)
|
||||
? requestedLessonId
|
||||
: flattenedLessonIds[0] ?? "";
|
||||
} else {
|
||||
const firstPreview = flattenedLessons.find((l) => l.isFreePreview);
|
||||
const requestedLesson = requestedLessonId
|
||||
? flattenedLessons.find((l) => l.id === requestedLessonId)
|
||||
: null;
|
||||
if (requestedLesson?.isFreePreview) {
|
||||
initialSelectedLessonId = requestedLessonId!;
|
||||
} else {
|
||||
initialSelectedLessonId = firstPreview?.id ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<StudentClassroomClient
|
||||
courseSlug={course.slug}
|
||||
courseTitle={getText(course.title) || "Untitled course"}
|
||||
modules={modules}
|
||||
initialSelectedLessonId={initialSelectedLessonId}
|
||||
initialCompletedLessonIds={completedProgress.map((p) => p.lessonId)}
|
||||
isEnrolled={isEnrolled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user