owlcub-academy/src/app/[locale]/courses/[slug]/page.tsx

358 lines
12 KiB
TypeScript

import { getTranslations } from "next-intl/server";
import { notFound } from "next/navigation";
import Link from "next/link";
import { db } from "@/lib/db";
import { auth } from "@/auth";
import { ProgressBar } from "@/components/ProgressBar";
import { EnrollButton } from "./EnrollButton";
export const dynamic = "force-dynamic";
const levelLabels: Record<string, Record<string, string>> = {
fr: { BEGINNER: "Débutant", INTERMEDIATE: "Intermédiaire", ADVANCED: "Avancé" },
en: { BEGINNER: "Beginner", INTERMEDIATE: "Intermediate", ADVANCED: "Advanced" },
es: { BEGINNER: "Principiante", INTERMEDIATE: "Intermedio", ADVANCED: "Avanzado" },
};
function getLocaleText(
obj: Record<string, string>,
locale: string,
keys: { fr: string; en: string; es: string }
) {
if (locale === "en") return (obj as any)[keys.en] || (obj as any)[keys.fr];
if (locale === "es") return (obj as any)[keys.es] || (obj as any)[keys.fr];
return (obj as any)[keys.fr];
}
export default async function CourseDetailPage({
params,
}: {
params: Promise<{ locale: string; slug: string }>;
}) {
const { locale, slug } = await params;
const t = await getTranslations({ locale, namespace: "course" });
const course = await db.course.findUnique({
where: { slug },
include: {
modules: {
orderBy: { order: "asc" },
include: {
lessons: { orderBy: { order: "asc" } },
quiz: { select: { id: true } },
},
},
},
});
if (!course || !course.published) {
notFound();
}
const session = await auth();
let enrollment = null;
let progress = 0;
let firstLessonId: string | null = null;
if (session?.user) {
const userId = (session.user as any).id;
enrollment = await db.enrollment.findUnique({
where: { userId_courseId: { userId, courseId: course.id } },
});
// Calculate progress
const allLessons = course.modules.flatMap((m) => m.lessons);
firstLessonId = allLessons[0]?.id ?? null;
if (allLessons.length > 0) {
const completedCount = await db.lessonProgress.count({
where: {
userId,
lessonId: { in: allLessons.map((l) => l.id) },
},
});
progress = Math.round((completedCount / allLessons.length) * 100);
}
}
const title = getLocaleText(course as any, locale, { fr: "titleFr", en: "titleEn", es: "titleEs" });
const desc = getLocaleText(course as any, locale, { fr: "descFr", en: "descEn", es: "descEs" });
const lvlLabel = (levelLabels[locale] ?? levelLabels.fr)[course.level];
const totalLessons = course.modules.reduce((s, m) => s + m.lessons.length, 0);
return (
<div style={{ maxWidth: 1000, margin: "0 auto", padding: "40px 24px" }}>
{/* Breadcrumb */}
<div style={{ marginBottom: 24, fontSize: 13, color: "#94a3b8" }}>
<Link href={`/${locale}/courses`} style={{ color: "#60a5fa" }}>
Formations
</Link>{" "}
/ {title}
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 320px", gap: 32, alignItems: "start" }}>
{/* Main content */}
<div>
{/* Course header */}
<div style={{ marginBottom: 32 }}>
<div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
<span
style={{
background: "rgba(29,78,216,0.2)",
color: "#60a5fa",
borderRadius: 999,
padding: "3px 12px",
fontSize: 12,
fontWeight: 700,
}}
>
{course.category}
</span>
<span
style={{
background: "rgba(245,158,11,0.15)",
color: "#fbbf24",
borderRadius: 999,
padding: "3px 12px",
fontSize: 12,
fontWeight: 700,
}}
>
{lvlLabel}
</span>
</div>
<h1 style={{ fontSize: 28, fontWeight: 800, color: "#f1f5f9", marginBottom: 16, lineHeight: 1.3 }}>
{title}
</h1>
<p style={{ fontSize: 16, color: "#94a3b8", lineHeight: 1.7 }}>
{desc}
</p>
</div>
{/* Thumbnail */}
{course.thumbnailUrl && (
<div
style={{
borderRadius: 12,
overflow: "hidden",
marginBottom: 32,
aspectRatio: "16/9",
background: "#1a1f2e",
}}
>
<img
src={course.thumbnailUrl}
alt={title}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
/>
</div>
)}
{/* Module list */}
<div>
<h2 style={{ fontSize: 20, fontWeight: 700, color: "#f1f5f9", marginBottom: 16 }}>
{t("modules")} ({course.modules.length})
</h2>
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
{course.modules.map((module, mIndex) => {
const moduleTitle = getLocaleText(module as any, locale, {
fr: "titleFr",
en: "titleEn",
es: "titleEs",
});
return (
<div
key={module.id}
style={{
background: "#1a1f2e",
border: "1px solid rgba(255,255,255,0.08)",
borderRadius: 10,
padding: "16px 20px",
}}
>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
<div
style={{
width: 28,
height: 28,
background: "rgba(29,78,216,0.2)",
color: "#60a5fa",
borderRadius: 6,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 12,
fontWeight: 700,
flexShrink: 0,
}}
>
{mIndex + 1}
</div>
<span style={{ fontSize: 15, fontWeight: 600, color: "#f1f5f9" }}>
{moduleTitle}
</span>
</div>
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
<span style={{ fontSize: 12, color: "#94a3b8" }}>
{module.lessons.length} {t("lessons")}
</span>
{module.quiz && (
<span
style={{
fontSize: 11,
fontWeight: 600,
background: "rgba(245,158,11,0.15)",
color: "#fbbf24",
borderRadius: 4,
padding: "2px 8px",
}}
>
{t("quiz")}
</span>
)}
</div>
</div>
{module.lessons.length > 0 && (
<div style={{ marginTop: 10, paddingLeft: 40 }}>
{module.lessons.map((lesson) => {
const lessonTitle = getLocaleText(lesson as any, locale, {
fr: "titleFr",
en: "titleEn",
es: "titleEs",
});
return (
<div
key={lesson.id}
style={{
display: "flex",
alignItems: "center",
gap: 8,
padding: "6px 0",
borderTop: "1px solid rgba(255,255,255,0.04)",
}}
>
<span style={{ fontSize: 12, color: "#475569" }}>
{lesson.type === "VIDEO" ? "▶" : "📄"}
</span>
<span style={{ fontSize: 13, color: "#94a3b8" }}>{lessonTitle}</span>
{lesson.duration && (
<span style={{ fontSize: 12, color: "#475569", marginLeft: "auto" }}>
{Math.floor(lesson.duration / 60)}min
</span>
)}
</div>
);
})}
</div>
)}
</div>
);
})}
</div>
</div>
</div>
{/* Sidebar */}
<div style={{ position: "sticky", top: 88 }}>
<div className="card">
{/* Stats */}
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: 16,
marginBottom: 20,
paddingBottom: 20,
borderBottom: "1px solid rgba(255,255,255,0.08)",
}}
>
<div>
<div style={{ fontSize: 22, fontWeight: 700, color: "#f1f5f9" }}>
{course.modules.length}
</div>
<div style={{ fontSize: 12, color: "#94a3b8" }}>{t("modules")}</div>
</div>
<div>
<div style={{ fontSize: 22, fontWeight: 700, color: "#f1f5f9" }}>
{totalLessons}
</div>
<div style={{ fontSize: 12, color: "#94a3b8" }}>{t("lessons")}</div>
</div>
</div>
{/* Progress if enrolled */}
{enrollment && (
<div style={{ marginBottom: 20 }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
fontSize: 13,
color: "#94a3b8",
marginBottom: 8,
}}
>
<span>Progression</span>
<span style={{ fontWeight: 700, color: "#f1f5f9" }}>{progress}%</span>
</div>
<ProgressBar value={progress} height={8} />
{enrollment.completedAt && (
<p
style={{
fontSize: 12,
color: "#4ade80",
marginTop: 8,
display: "flex",
alignItems: "center",
gap: 4,
}}
>
Formation complétée
</p>
)}
</div>
)}
{/* CTA */}
{enrollment ? (
<Link
href={`/${locale}/courses/${slug}/learn`}
className="btn btn-primary"
style={{
display: "flex",
justifyContent: "center",
padding: "12px",
fontSize: 15,
}}
>
{progress > 0 ? t("continue") : t("start")}
</Link>
) : session ? (
<EnrollButton
courseId={course.id}
locale={locale}
slug={slug}
label={t("enroll")}
/>
) : (
<Link
href={`/${locale}/auth/login`}
className="btn btn-primary"
style={{ display: "flex", justifyContent: "center", padding: "12px", fontSize: 15 }}
>
{t("enroll")}
</Link>
)}
<p style={{ fontSize: 12, color: "#475569", textAlign: "center", marginTop: 12 }}>
Accès illimité · Certificat inclus
</p>
</div>
</div>
</div>
</div>
);
}