Files
ACVE/app/(public)/courses/[slug]/page.tsx
2026-03-15 13:52:11 +00:00

170 lines
5.1 KiB
TypeScript
Executable File

import { notFound } from "next/navigation";
import { requireUser } from "@/lib/auth/requireUser";
import CourseDetailHeader from "@/components/courses/CourseDetailHeader";
import CourseProgressCard from "@/components/courses/CourseProgressCard";
import ProgramContentList from "@/components/courses/ProgramContentList";
import { getCourseDetailViewModel } from "@/lib/courses/publicCourses";
export const dynamic = "force-dynamic";
type PageProps = {
params: Promise<{ slug: string }>;
};
type DetailActionState = {
primaryAction: {
label: string;
href?: string;
disabled?: boolean;
};
secondaryAction?: {
label: string;
href?: string;
disabled?: boolean;
};
helperText?: string;
};
function buildActionState(args: {
slug: string;
isAuthenticated: boolean;
isEnrolled: boolean;
availabilityState: "published" | "upcoming" | "draft";
progressPercent: number;
firstPreviewLessonId: string | null;
price: number;
}): DetailActionState {
const learnUrl = `/courses/${args.slug}/learn`;
const loginUrl = `/auth/login?redirectTo=${encodeURIComponent(`/courses/${args.slug}`)}`;
const previewUrl = args.firstPreviewLessonId ? `${learnUrl}?lesson=${args.firstPreviewLessonId}` : undefined;
if (args.availabilityState !== "published") {
return {
primaryAction: {
label: "Próximamente",
disabled: true,
},
helperText: "Este programa se encuentra en preparación editorial y estará habilitado en próximas publicaciones.",
};
}
if (args.isAuthenticated && args.isEnrolled) {
const label = args.progressPercent >= 100 ? "Revisar programa" : args.progressPercent > 0 ? "Continuar" : "Comenzar";
return {
primaryAction: {
label,
href: learnUrl,
},
helperText:
args.progressPercent > 0
? "Tu avance se conserva automáticamente. Puedes continuar desde la lección más reciente."
: "Inicia el recorrido académico desde el primer módulo.",
};
}
if (args.isAuthenticated) {
if (args.price <= 0) {
return {
primaryAction: {
label: "Comenzar",
href: learnUrl,
},
helperText: "Programa con acceso abierto para iniciar de inmediato.",
};
}
if (previewUrl) {
return {
primaryAction: {
label: "Ver clase de muestra",
href: previewUrl,
},
helperText: "El contenido completo está disponible para estudiantes inscritos en el programa.",
};
}
return {
primaryAction: {
label: "Inscripción próximamente",
disabled: true,
},
helperText: "La inscripción completa para este programa estará disponible en breve.",
};
}
if (previewUrl) {
return {
primaryAction: {
label: "Ver clase de muestra",
href: previewUrl,
},
secondaryAction: {
label: "Iniciar sesión",
href: loginUrl,
},
helperText: "Puedes revisar una vista previa o iniciar sesión para gestionar tu itinerario académico.",
};
}
return {
primaryAction: {
label: "Iniciar sesión para comenzar",
href: loginUrl,
},
helperText: "Accede con tu cuenta para comenzar este programa y registrar tu progreso.",
};
}
export default async function CourseDetailPage({ params }: PageProps) {
const { slug } = await params;
const user = await requireUser().catch(() => null);
const course = await getCourseDetailViewModel(slug, user?.id ?? null);
if (!course) notFound();
const actions = buildActionState({
slug: course.slug,
isAuthenticated: Boolean(user?.id),
isEnrolled: course.isEnrolled,
availabilityState: course.availabilityState,
progressPercent: course.progressPercent,
firstPreviewLessonId: course.firstPreviewLessonId,
price: course.price,
});
return (
<div className="acve-page">
<section className="grid items-start gap-5 xl:grid-cols-[1.55fr_0.95fr]">
<CourseDetailHeader
availabilityLabel={course.availabilityLabel}
availabilityState={course.availabilityState}
description={course.longDescription}
durationLabel={course.durationLabel}
instructor={course.instructor}
lessonCount={course.lessonCount}
proficiencyLabel={course.proficiencyLabel}
stageLabel={course.stage.levelLabel}
studentsCount={course.studentsCount}
thumbnailUrl={course.thumbnailUrl}
title={course.title}
/>
<CourseProgressCard
availabilityLabel={course.availabilityLabel}
completedLessons={course.completedLessons}
durationLabel={course.durationLabel}
helperText={actions.helperText}
instructor={course.instructor}
primaryAction={actions.primaryAction}
progressPercent={course.progressPercent}
secondaryAction={actions.secondaryAction}
stageLabel={course.stage.levelLabel}
totalLessons={course.totalLessons}
/>
</section>
<ProgramContentList modules={course.modules} />
</div>
);
}