192 lines
5.9 KiB
TypeScript
192 lines
5.9 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import { useI18n } from "@/lib/i18n";
|
|
import { useAuth } from "@/lib/auth";
|
|
import { api } from "@/lib/api";
|
|
import type { DigitalCourse, DigitalLesson } from "@/lib/types";
|
|
import RingNext from "@/components/RingNext";
|
|
import { useRingSteps } from "@/lib/rings";
|
|
|
|
export default function DigitalLessonClient({
|
|
moduleIndex,
|
|
lessonIndex,
|
|
}: {
|
|
moduleIndex: number;
|
|
lessonIndex: number;
|
|
}) {
|
|
const { t } = useI18n();
|
|
const { token } = useAuth();
|
|
const rings = useRingSteps();
|
|
const [lesson, setLesson] = useState<DigitalLesson | null>(null);
|
|
const [locked, setLocked] = useState(false);
|
|
const [missing, setMissing] = useState(false);
|
|
const [loading, setLoading] = useState(true);
|
|
const [hasNext, setHasNext] = useState(false);
|
|
const [nextHref, setNextHref] = useState<string | null>(null);
|
|
const [prevHref, setPrevHref] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
setLoading(true);
|
|
(async () => {
|
|
try {
|
|
const course: DigitalCourse = await api.getDigitalCourse();
|
|
const modules = course.modules || [];
|
|
const mod = modules[moduleIndex];
|
|
const meta = mod?.lessons?.[lessonIndex];
|
|
if (!meta) {
|
|
if (!cancelled) {
|
|
setMissing(true);
|
|
setLocked(false);
|
|
setLesson(null);
|
|
setHasNext(false);
|
|
setNextHref(null);
|
|
setPrevHref(null);
|
|
}
|
|
return;
|
|
}
|
|
let next: string | null = null;
|
|
if (mod.lessons[lessonIndex + 1]) {
|
|
next = `/digital/course/${moduleIndex}/${lessonIndex + 1}`;
|
|
} else if (modules[moduleIndex + 1]?.lessons?.length) {
|
|
next = `/digital/course/${moduleIndex + 1}/0`;
|
|
}
|
|
let prev: string | null = null;
|
|
if (lessonIndex > 0) {
|
|
prev = `/digital/course/${moduleIndex}/${lessonIndex - 1}`;
|
|
} else if (moduleIndex > 0) {
|
|
const prevMod = modules[moduleIndex - 1];
|
|
const last = (prevMod?.lessons?.length || 1) - 1;
|
|
if (prevMod?.lessons?.length) {
|
|
prev = `/digital/course/${moduleIndex - 1}/${last}`;
|
|
}
|
|
}
|
|
if (!cancelled) {
|
|
setHasNext(Boolean(next));
|
|
setNextHref(next);
|
|
setPrevHref(prev);
|
|
}
|
|
try {
|
|
const data = await api.getDigitalLesson(moduleIndex, lessonIndex, token || undefined);
|
|
if (!cancelled) {
|
|
setLesson(data);
|
|
setLocked(false);
|
|
setMissing(false);
|
|
}
|
|
} catch {
|
|
if (!cancelled) {
|
|
setLesson(null);
|
|
setLocked(!meta.free);
|
|
setMissing(meta.free);
|
|
}
|
|
}
|
|
} catch {
|
|
if (!cancelled) {
|
|
setMissing(true);
|
|
setLesson(null);
|
|
}
|
|
} finally {
|
|
if (!cancelled) setLoading(false);
|
|
}
|
|
})();
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [moduleIndex, lessonIndex, token]);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="digital-page">
|
|
<div className="container">
|
|
<p className="dest-empty">{t.digital.loadingLesson}</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (missing) {
|
|
return (
|
|
<div className="digital-page">
|
|
<div className="container">
|
|
<div className="digital-gate reveal">
|
|
<h2>{t.digital.missingLesson}</h2>
|
|
<Link href="/digital/course" className="btn btn-primary">{t.digital.backCourse}</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (locked || !lesson) {
|
|
return (
|
|
<div className="digital-page">
|
|
<div className="container">
|
|
<div className="digital-gate reveal">
|
|
<h2>{t.digital.locked}</h2>
|
|
<p>{t.digital.lockedDesc}</p>
|
|
<div className="dating-empty-actions">
|
|
{!token && (
|
|
<Link href={`/login?next=/digital/course/${moduleIndex}/${lessonIndex}`} className="btn">
|
|
{t.nav.login}
|
|
</Link>
|
|
)}
|
|
<Link href="/join" className="btn btn-primary">{t.join.payBtn}</Link>
|
|
<Link href="/digital/course" className="btn">{t.digital.backCourse}</Link>
|
|
</div>
|
|
</div>
|
|
<RingNext steps={rings.afterGigs} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const prev = prevHref;
|
|
const next = hasNext ? nextHref : null;
|
|
|
|
return (
|
|
<div className="digital-page">
|
|
<div className="container">
|
|
<nav className="detail-nav">
|
|
<Link href="/digital/course">← {t.digital.backCourse}</Link>
|
|
</nav>
|
|
<article className="digital-lesson reveal">
|
|
<span className="section-tag">{lesson.duration}</span>
|
|
<h1>{lesson.title}</h1>
|
|
<div className="digital-lesson-body">
|
|
{lesson.content.split("\n").filter(Boolean).map((p, i) => (
|
|
<p key={`${i}-${p.slice(0, 16)}`}>{p}</p>
|
|
))}
|
|
</div>
|
|
<footer className="digital-lesson-nav">
|
|
{prev && (
|
|
<Link href={prev} className="btn">
|
|
← {t.digital.prev}
|
|
</Link>
|
|
)}
|
|
{next ? (
|
|
<Link href={next} className="btn btn-primary">
|
|
{t.digital.next} →
|
|
</Link>
|
|
) : (
|
|
<div className="dating-empty-actions">
|
|
<Link href="/digital/course" className="btn btn-primary">
|
|
{t.digital.backCatalog}
|
|
</Link>
|
|
<Link href="/gigs" className="btn">
|
|
{t.nav.gigs}
|
|
</Link>
|
|
<Link href="/meetups" className="btn btn-ghost">
|
|
{t.nav.meetups}
|
|
</Link>
|
|
</div>
|
|
)}
|
|
</footer>
|
|
</article>
|
|
<RingNext steps={next ? rings.afterGigs : rings.afterDigital} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|