nomadweb/frontend/src/components/DigitalLessonClient.tsx
eric e23cf9a03c Close Explore/Grow loops: visa wizard, jobs, lessons, gigs notify, map.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-04 09:26:56 -05:00

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>
);
}