import Link from "next/link"; import { redirect } from "next/navigation"; import { UserRole } from "@prisma/client"; import { requireUser } from "@/lib/auth/requireUser"; import { db } from "@/lib/prisma"; import { getActiveRecommendations, getMiniGameGrade } from "@/lib/recommendations"; type ProfilePrismaClient = { miniGameAttempt: { findMany: (args: object) => Promise< { miniGameId: string; scorePercent: number; miniGame: { id: string; title: string; slug: string }; }[] >; }; }; function getText(value: unknown): string { if (!value) return ""; if (typeof value === "string") return value; if (typeof value === "object") { const record = value as Record; if (typeof record.en === "string") return record.en; if (typeof record.es === "string") return record.es; } return ""; } const levelLabel: Record = { BEGINNER: "Beginner", INTERMEDIATE: "Intermediate", ADVANCED: "Advanced", EXPERT: "Expert", }; export default async function ProfilePage() { const user = await requireUser(); if (!user?.id) { redirect("/auth/login?redirectTo=/profile"); } const isTeacher = user.role === UserRole.TEACHER || user.role === UserRole.SUPER_ADMIN; if (isTeacher) { const authoredCourses = await db.course .findMany({ where: { authorId: user.id }, select: { id: true, title: true, slug: true, tags: true, level: true, status: true, learningOutcomes: true, updatedAt: true, _count: { select: { modules: true, enrollments: true, }, }, modules: { select: { lessons: { select: { id: true, videoUrl: true, youtubeUrl: true, isFreePreview: true, }, }, }, }, }, orderBy: { updatedAt: "desc" }, }) .catch((error) => { console.error("Failed to load authored courses for profile.", error); return []; }); const totalCourses = authoredCourses.length; const publishedCourses = authoredCourses.filter((course) => course.status === "PUBLISHED").length; const totalModules = authoredCourses.reduce((acc, course) => acc + course._count.modules, 0); const totalStudents = authoredCourses.reduce((acc, course) => acc + course._count.enrollments, 0); const lessons = authoredCourses.flatMap((course) => course.modules.flatMap((module) => module.lessons)); const totalLessons = lessons.length; const uploadedLessons = lessons.filter((lesson) => lesson.videoUrl || lesson.youtubeUrl).length; const previewLessons = lessons.filter((lesson) => lesson.isFreePreview).length; const topicSet = new Set(); const outcomeSet = new Set(); for (const course of authoredCourses) { for (const tag of course.tags) { if (tag.trim()) topicSet.add(tag.trim()); } if (Array.isArray(course.learningOutcomes)) { for (const outcome of course.learningOutcomes) { if (typeof outcome === "string" && outcome.trim()) { outcomeSet.add(outcome.trim()); } } } } const masteredTopics = topicSet.size > 0 ? [...topicSet] : [...new Set(authoredCourses.map((course) => levelLabel[course.level] ?? course.level))]; const strengths = [...outcomeSet].slice(0, 8); return (

Profile

{user.fullName || user.email}

Teacher account | {user.email}

Courses created

{totalCourses}

Published

{publishedCourses}

Students

{totalStudents}

Content units

{totalModules + totalLessons}

{totalModules} modules + {totalLessons} lessons

Topics mastered

{masteredTopics.length === 0 ? (

No topic metadata yet. Add tags to your courses.

) : (
{masteredTopics.map((topic) => ( {topic} ))}
)} {strengths.length > 0 ? (
    {strengths.map((item) => (
  • {item}
  • ))}
) : null}

Upload information

  • Lessons with media: {uploadedLessons}/{totalLessons || 0}
  • Preview lessons enabled: {previewLessons}
  • Lessons pending upload: {Math.max(totalLessons - uploadedLessons, 0)}
Open upload library

Recent course activity

{authoredCourses.length === 0 ? (

No courses yet. Start by creating a new course.

) : (
    {authoredCourses.slice(0, 6).map((course) => (
  • {getText(course.title) || "Untitled course"}

    {course.status} | {course._count.modules} modules | {course._count.enrollments} students | updated{" "} {new Date(course.updatedAt).toLocaleDateString()}

    Open editor
  • ))}
)}
); } const [grade, recommendations, certificates] = await Promise.all([ getMiniGameGrade(user.id).catch((error) => { console.error("Failed to load mini-game grade.", error); return 0; }), getActiveRecommendations(user.id).catch((error) => { console.error("Failed to load recommendations.", error); return []; }), db.certificate .findMany({ where: { userId: user.id }, select: { id: true, issuedAt: true, metadataSnapshot: true, course: { select: { title: true, slug: true, }, }, }, orderBy: { issuedAt: "desc" }, }) .catch((error) => { console.error("Failed to load certificates for profile.", error); return []; }), ]); let attempts: { miniGameId: string; scorePercent: number; miniGame: { id: string; title: string; slug: string }; }[] = []; try { attempts = await (db as unknown as ProfilePrismaClient).miniGameAttempt.findMany({ where: { userId: user.id }, include: { miniGame: { select: { id: true, title: true, slug: true, }, }, }, orderBy: { completedAt: "desc" }, }); } catch { attempts = []; } const latestByGame = new Map(); const bestByGame = new Map(); const titleByGame = new Map(); for (const attempt of attempts) { if (!latestByGame.has(attempt.miniGameId)) { latestByGame.set(attempt.miniGameId, attempt.scorePercent); } const best = bestByGame.get(attempt.miniGameId) ?? 0; if (attempt.scorePercent > best) { bestByGame.set(attempt.miniGameId, attempt.scorePercent); } titleByGame.set(attempt.miniGameId, attempt.miniGame.title); } const gameRows = [...titleByGame.keys()].map((gameId) => ({ gameId, title: titleByGame.get(gameId) ?? "Mini-game", latest: latestByGame.get(gameId) ?? 0, best: bestByGame.get(gameId) ?? 0, })); return (

Profile

{user.fullName || user.email}

{user.email}

Mini-game grade

{grade}%

Average of latest attempts across mini-games.

Attempts

{attempts.length}

Play mini-games

Certificates

{certificates.length}

View my courses

Mini-game breakdown

{gameRows.length === 0 ? (

No attempts yet. Complete a mini-game to generate your grade.

) : (
    {gameRows.map((row) => (
  • {row.title} | latest: {row.latest}% | best: {row.best}%
  • ))}
)}

Recommended next

{recommendations.length === 0 ? (

No recommendations yet. Complete a mini-game or enroll in a course first.

) : (
    {recommendations.map((recommendation) => (
  • {recommendation.title}

    {recommendation.reason}

    Open course
  • ))}
)}

Certificates

{certificates.length === 0 ? (

No certificates issued yet.

) : (
    {certificates.map((certificate) => { const certificateNumber = (certificate as Record).certificateNumber ?? (certificate.metadataSnapshot as Record | null)?.certificateNumber ?? "N/A"; return (
  • {getText(certificate.course.title) || "Course"}

    Certificate #{String(certificateNumber)} | issued {new Date(certificate.issuedAt).toLocaleDateString()}

    Download PDF
  • ); })}
)}
); }