Files
ACVE/components/courses/CourseCard.tsx
2026-03-15 13:52:11 +00:00

110 lines
4.8 KiB
TypeScript

import Link from "next/link";
import { BookOpenCheck, Clock3, UserRound } from "lucide-react";
import { cn } from "@/lib/utils";
import type { CatalogCourseCardView } from "@/lib/courses/publicCourses";
type CourseCardProps = {
course: CatalogCourseCardView;
};
const stageVisuals: Record<CatalogCourseCardView["stageId"], string> = {
base: "from-[#eadbc9] via-[#f4e9dc] to-[#fdf8f2]",
consolidacion: "from-[#e7ddd0] via-[#f3ece2] to-[#fdf9f3]",
especializacion: "from-[#e4d6dd] via-[#f2e8ee] to-[#fdf9fc]",
};
const availabilityClass: Record<CatalogCourseCardView["availabilityState"], string> = {
published: "border-emerald-300/70 bg-emerald-50 text-emerald-800 dark:border-emerald-700/40 dark:bg-emerald-900/30 dark:text-emerald-200",
upcoming: "border-amber-300/70 bg-amber-50 text-amber-800 dark:border-amber-700/50 dark:bg-amber-900/30 dark:text-amber-200",
draft: "border-slate-300/70 bg-slate-100 text-slate-700 dark:border-slate-700/60 dark:bg-slate-800/70 dark:text-slate-200",
};
function getInitials(title: string): string {
return title
.split(" ")
.filter(Boolean)
.slice(0, 2)
.map((chunk) => chunk[0]?.toUpperCase() ?? "")
.join("");
}
function getCtaLabel(course: CatalogCourseCardView): string {
if (course.availabilityState === "upcoming") return "Ver programa";
if (course.isEnrolled && course.progressPercent > 0 && course.progressPercent < 100) return "Continuar";
return "Conocer programa";
}
export default function CourseCard({ course }: CourseCardProps) {
return (
<Link href={`/courses/${course.slug}`} className="group block h-full">
<article className="flex h-full flex-col overflow-hidden rounded-2xl border border-border/80 bg-card shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/45 hover:shadow-md">
<div className="relative aspect-[16/10] overflow-hidden border-b border-border/70">
{course.thumbnailUrl ? (
<img
alt={`Portada del programa ${course.title}`}
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.02]"
loading="lazy"
src={course.thumbnailUrl}
/>
) : (
<div
className={cn(
"flex h-full w-full items-end bg-gradient-to-br p-4 text-3xl font-semibold text-primary/80",
stageVisuals[course.stageId],
)}
>
{getInitials(course.title)}
</div>
)}
<div className="absolute left-3 top-3 flex flex-wrap gap-2">
<span className="rounded-full border border-primary/35 bg-card/95 px-3 py-1 text-xs font-semibold text-primary">
{course.stageLabel}
</span>
<span className={cn("rounded-full border px-3 py-1 text-xs font-semibold", availabilityClass[course.availabilityState])}>
{course.availabilityLabel}
</span>
</div>
</div>
<div className="flex flex-1 flex-col p-4">
<h3 className="text-xl font-semibold leading-tight text-foreground">{course.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">{course.shortDescription}</p>
<div className="mt-4 space-y-2 border-t border-border/70 pt-3 text-xs text-muted-foreground">
<div className="flex items-center gap-2">
<Clock3 className="h-3.5 w-3.5 text-primary/80" />
<span>{course.durationLabel}</span>
</div>
<div className="flex items-center gap-2">
<BookOpenCheck className="h-3.5 w-3.5 text-primary/80" />
<span>{course.lessonCount} lecciones</span>
</div>
<div className="flex items-center gap-2">
<UserRound className="h-3.5 w-3.5 text-primary/80" />
<span>{course.instructor}</span>
</div>
</div>
{course.isEnrolled ? (
<div className="mt-4 rounded-xl border border-primary/25 bg-primary/5 px-3 py-2">
<div className="flex items-center justify-between text-xs font-semibold text-primary">
<span>Progreso</span>
<span>{course.progressPercent}%</span>
</div>
<div className="mt-1 h-1.5 w-full rounded-full bg-primary/15">
<div className="h-1.5 rounded-full bg-primary transition-all" style={{ width: `${course.progressPercent}%` }} />
</div>
</div>
) : null}
<div className="mt-4 flex items-center justify-between border-t border-border/70 pt-3 text-sm">
<span className="text-muted-foreground">{course.studentsCount.toLocaleString()} inscritos</span>
<span className="font-semibold text-primary">{getCtaLabel(course)}</span>
</div>
</div>
</article>
</Link>
);
}