import { headers } from "next/headers"; import { auth } from "@/lib/auth"; import { redirect } from "next/navigation"; import { prisma } from "@/lib/prisma"; import Link from "next/link"; import { ArchetypeBanner } from "@/components/student/archetype-banner"; import { ToolboxPromoCard } from "@/components/tools/ToolboxPromoCard"; import { StoreShowcase } from "@/components/student/store-showcase"; import { STORE_CATALOG } from "@/lib/store-catalog"; export default async function StudentDashboard() { const session = await auth.api.getSession({ headers: await headers() }); if (!session) redirect("/login"); const dbUser = await prisma.user.findUnique({ where: { id: session.user.id }, select: { archetype: true }, }); const enrollments = await prisma.courseEnrollment.findMany({ where: { userId: session.user.id }, include: { course: { include: { modules: { include: { lessons: { where: { published: true }, select: { id: true }, }, }, }, _count: { select: { modules: true } }, }, }, }, orderBy: { enrolledAt: "desc" }, }); const now = new Date(); const active = enrollments.filter((e) => !e.expiresAt || e.expiresAt > now); const expired = enrollments.filter((e) => e.expiresAt && e.expiresAt <= now); // Fetch progress for all lessons in active courses const allLessonIds = active.flatMap((e) => e.course.modules.flatMap((m) => m.lessons.map((l) => l.id)) ); const progressRecords = allLessonIds.length > 0 ? await prisma.lessonProgress.findMany({ where: { userId: session.user.id, lessonId: { in: allLessonIds } }, select: { lessonId: true }, }) : []; const completedSet = new Set(progressRecords.map((p) => p.lessonId)); // Витрина: продаваемые курсы, которых у студента нет → заблюренные карточки const enrolledSlugs = new Set(enrollments.map((e) => e.course.slug)); // Не показываем «Всё включено», если у студента уже есть обычная версия того же курса const SUPPRESS_IF_OWNED: Record = { "obsidian-full": "obsidian", // ВВ Obsidian 2025 ← обычный Obsidian 2025 "zotero-full": "zotero", // ВВ Zotero ← обычный Zotero }; const storeCourses = await prisma.course.findMany({ where: { slug: { in: Object.keys(STORE_CATALOG) }, published: true }, select: { id: true, slug: true, title: true, description: true, coverImage: true }, }); const locked = storeCourses.filter( (c) => !enrolledSlugs.has(c.slug) && !(SUPPRESS_IF_OWNED[c.slug] && enrolledSlugs.has(SUPPRESS_IF_OWNED[c.slug])) ); return (

Мои курсы

{active.length} активных курсов

{dbUser?.archetype && } {active.length === 0 ? (

📚

Доступных курсов пока нет

Обратитесь к администратору за доступом

) : (
{active.map(({ course, expiresAt }) => { const totalLessons = course.modules.reduce((s, m) => s + m.lessons.length, 0); const completedLessons = course.modules .flatMap((m) => m.lessons) .filter((l) => completedSet.has(l.id)).length; const progressPct = totalLessons > 0 ? Math.round((completedLessons / totalLessons) * 100) : 0; return ( {course.coverImage ? ( // eslint-disable-next-line @next/next/no-img-element {course.title} ) : (
📚
)}

{course.title}

{course.description && (

{course.description}

)} {/* Progress bar */} {totalLessons > 0 && (
{completedLessons} из {totalLessons} уроков {progressPct === 100 ? "✓ Завершён" : `${progressPct}%`}
0 ? "1px solid var(--foreground)" : "none", }} />
)} {expiresAt && (

Доступ до {new Date(expiresAt).toLocaleDateString("ru-RU")}

)}
); })}
)} {process.env.TOOLBOX_VISIBLE === "true" && (
)} {locked.length > 0 && ( )} {expired.length > 0 && (

Доступ истёк

{expired.map(({ course, expiresAt }) => (
{course.title} Истёк {new Date(expiresAt!).toLocaleDateString("ru-RU")}
))}
)}
); }