Files
gitlab-instance-0a899031_no…/app/components/content/LessonLayout.tsx
2026-03-12 00:34:50 -05:00

152 lines
5.7 KiB
TypeScript

"use client";
import { useEffect, useCallback } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import type { CourseConfig } from "@/lib/content/config";
type LessonLayoutProps = {
courseConfig: CourseConfig;
moduleIndex: number;
lessonIndex: number;
lessonName: string;
lessonDuration: string;
isFreeTrial: boolean;
unlocked: boolean;
totalLessons: number;
completedCount: number;
prevLesson: { moduleIndex: number; lessonIndex: number } | null;
nextLesson: { moduleIndex: number; lessonIndex: number } | null;
courseHomeHref: string;
payHref: string;
children: React.ReactNode;
};
export function LessonLayout({
courseConfig,
moduleIndex,
lessonIndex,
lessonName,
lessonDuration,
isFreeTrial,
unlocked,
totalLessons,
completedCount,
prevLesson,
nextLesson,
courseHomeHref,
payHref,
children,
}: LessonLayoutProps) {
const router = useRouter();
const courseBase = courseHomeHref.replace(/\/$/, "");
const lessonBase = courseBase.includes("/course") ? courseBase : `${courseBase}/course`;
const progressPercent = totalLessons > 0 ? Math.round((completedCount / totalLessons) * 100) : 0;
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.target && (e.target as HTMLElement).tagName === "INPUT") return;
if (e.key === "ArrowLeft" && prevLesson) {
router.push(`${lessonBase}/${prevLesson.moduleIndex}/${prevLesson.lessonIndex}`);
} else if (e.key === "ArrowRight" && nextLesson) {
router.push(`${lessonBase}/${nextLesson.moduleIndex}/${nextLesson.lessonIndex}`);
}
},
[prevLesson, nextLesson, lessonBase, router]
);
useEffect(() => {
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [handleKeyDown]);
return (
<div className="min-h-screen bg-[var(--background)] text-[var(--foreground)]">
<main className="pt-14 pb-16 md:pt-16">
<div className="mx-auto max-w-4xl px-4 sm:px-6">
{/* 进度条 */}
<div className="mb-6">
<div className="flex justify-between text-xs text-[var(--muted-foreground)] mb-1">
<span></span>
<span>{completedCount}/{totalLessons} · {progressPercent}%</span>
</div>
<div className="h-2 overflow-hidden rounded-full bg-[var(--muted)]">
<div
className="h-full rounded-full bg-[var(--accent)] transition-all duration-300"
style={{ width: `${progressPercent}%` }}
/>
</div>
</div>
{/* 导航 */}
<nav className="mb-6 flex items-center gap-2 text-sm">
<button
type="button"
onClick={() => router.back()}
className="flex items-center gap-1 text-[var(--muted-foreground)] transition hover:text-[var(--foreground)]"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</button>
<span className="text-[var(--muted-foreground)]">/</span>
<Link href={courseHomeHref} className="text-[var(--muted-foreground)] transition hover:text-[var(--foreground)]">
</Link>
</nav>
{/* 标题 */}
<h1 className="mb-2 flex items-center gap-2 text-xl font-bold sm:text-2xl md:text-3xl">
{lessonName}
{isFreeTrial && (
<span className="rounded bg-[var(--accent)]/20 px-2 py-0.5 text-sm font-normal text-[var(--accent)]">
</span>
)}
</h1>
<p className="mb-8 text-sm text-[var(--muted-foreground)]"> {lessonDuration}</p>
{/* 内容区 */}
{children}
{/* 上/下一节 */}
<div className="mt-12 flex flex-col gap-3 sm:flex-row sm:justify-between">
{prevLesson ? (
<Link
href={`${courseBase}/course/${prevLesson.moduleIndex}/${prevLesson.lessonIndex}`}
className="flex items-center gap-2 rounded-xl border border-[var(--border)] bg-[var(--card)] px-4 py-3 text-sm font-medium transition hover:border-[var(--accent)]/50"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</Link>
) : (
<div />
)}
{nextLesson ? (
<Link
href={`${courseBase}/course/${nextLesson.moduleIndex}/${nextLesson.lessonIndex}`}
className="flex items-center gap-2 rounded-xl border border-[var(--border)] bg-[var(--card)] px-4 py-3 text-sm font-medium transition hover:border-[var(--accent)]/50 ml-auto"
>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</Link>
) : (
<Link
href={courseHomeHref}
className="flex items-center gap-2 rounded-xl bg-[var(--accent)] px-4 py-3 text-sm font-medium text-[var(--accent-foreground)] transition hover:opacity-90 ml-auto"
>
</Link>
)}
</div>
</div>
</main>
</div>
);
}