@burdenoff/microfe-buildmyiq 2026.1030.6 → 2026.1030.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"LessonPlayerPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/learning/LessonPlayerPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAiEhC,eAAO,MAAM,gBAAgB,EAAE,EAglB9B,CAAC"}
1
+ {"version":3,"file":"LessonPlayerPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/learning/LessonPlayerPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAiEhC,eAAO,MAAM,gBAAgB,EAAE,EAmlB9B,CAAC"}
@@ -317,7 +317,8 @@ var w = {
317
317
  })
318
318
  })] })
319
319
  })
320
- }), /* @__PURE__ */ x("main", {
320
+ }), /* @__PURE__ */ x("section", {
321
+ "aria-label": y("buildmyiq.lessonPlayer.lessonRegion", "Lesson"),
321
322
  className: "min-w-0 flex-1",
322
323
  children: [q ? /* @__PURE__ */ x(C, {
323
324
  treatment: "plain",
@@ -1 +1 @@
1
- {"version":3,"file":"LessonPlayerPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/learning/LessonPlayerPage.tsx"],"sourcesContent":["/**\n * LessonPlayerPage — the two-pane lesson player for a single enrollment.\n * @module buildmyiq/pages/learning\n *\n * Left pane: the course's module/lesson tree (from `buiCourse`) rendered as a\n * completion timeline driven by the enrollment's `lessonProgress`. Right pane:\n * the selected lesson (title, type, content text rendered safely, and a\n * `FileLink` for any attached media) with Prev / Mark-complete / Next controls.\n * Opening a lesson records IN_PROGRESS once; \"Mark complete\" records COMPLETED;\n * finishing every lesson transitions the enrollment to COMPLETED. All data is\n * live — no fabrication.\n */\n\nimport type { FC } from 'react';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { useParams, useSearchParams } from 'react-router';\nimport type { LucideIcon } from 'lucide-react';\nimport {\n ArrowLeft,\n BookOpen,\n Check,\n CheckCircle2,\n ChevronLeft,\n ChevronRight,\n FileText,\n HelpCircle,\n Loader2,\n ListChecks,\n MousePointerClick,\n PenLine,\n PlayCircle,\n Radio,\n Trophy,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n MiniBar,\n ProgressRing,\n LoadingView,\n ErrorView,\n EmptyState,\n FileLink,\n OfflineLessonControl,\n TutorPanel,\n} from '../../components';\nimport { useBuildmyiqAnalytics } from '../../analytics';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiMyEnrollment,\n useBuiCourse,\n useBuiRecordMyLessonProgress,\n useBuiCompleteMyEnrollment,\n} from '../../hooks';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { cn } from '../../utils/cn';\nimport { formatDuration, formatEnumLabel } from '../../utils/formatters';\nimport type { BuiLesson, BuiLessonType, BuiModule, BuiProgressStatus } from '../../types';\n\ninterface LessonGroup {\n id: string;\n title: string;\n lessons: BuiLesson[];\n}\n\nconst LESSON_ICON: Record<BuiLessonType, LucideIcon> = {\n ARTICLE: FileText,\n VIDEO: PlayCircle,\n QUIZ: HelpCircle,\n ASSIGNMENT: PenLine,\n INTERACTIVE: MousePointerClick,\n LIVE_SESSION: Radio,\n};\n\nconst sortByPosition = <T extends { position: number }>(items: T[]): T[] =>\n [...items].sort((a, b) => a.position - b.position);\n\nexport const LessonPlayerPage: FC = () => {\n const { enrollmentId } = useParams<{ enrollmentId: string }>();\n // `?lesson=<id>` deep-links straight to one lesson (used by the \"what's next\"\n // suggestion on My Learning). Purely an INITIAL anchor — once the learner\n // picks a lesson themselves, their pick wins.\n const [searchParams] = useSearchParams();\n const requestedLessonId = searchParams.get('lesson');\n const { navigateTo } = useBuildmyiq();\n const tr = useTr();\n\n const enrollmentQuery = useBuiMyEnrollment(enrollmentId);\n const enrollment = enrollmentQuery.data ?? null;\n const courseId = enrollment?.courseId;\n const courseQuery = useBuiCourse(courseId);\n const course = courseQuery.data ?? null;\n\n const recordProgress = useBuiRecordMyLessonProgress();\n const completeEnrollment = useBuiCompleteMyEnrollment();\n const { trackLessonStarted, trackLessonCompleted, trackCourseCompleted } =\n useBuildmyiqAnalytics();\n\n // react-query's mutate is referentially stable across renders, so it can be\n // used directly in effects without a render-time ref write.\n const recordMutate = recordProgress.mutate;\n const autoRecordedRef = useRef<Set<string>>(new Set());\n\n const [userSelectedId, setUserSelectedId] = useState<string | null>(null);\n const [showTree, setShowTree] = useState(false);\n // Error from the last \"Mark complete\" / \"Finish course\" attempt. Without it a\n // rejected mutation surfaced only as an unhandled promise rejection and the\n // button silently did nothing.\n const [actionError, setActionError] = useState<string | null>(null);\n\n // Grouped tree (modules → lessons) plus any standalone lessons, de-duplicated.\n const groups = useMemo<LessonGroup[]>(() => {\n if (!course) return [];\n const seen = new Set<string>();\n const modules: BuiModule[] = sortByPosition(course.modules ?? []);\n const result: LessonGroup[] = [];\n for (const m of modules) {\n const lessons = sortByPosition(m.lessons ?? []).filter((l) => {\n if (seen.has(l.id)) return false;\n seen.add(l.id);\n return true;\n });\n if (lessons.length > 0) result.push({ id: m.id, title: m.title, lessons });\n }\n const standalone = sortByPosition(course.lessons ?? []).filter((l) => {\n if (seen.has(l.id)) return false;\n seen.add(l.id);\n return true;\n });\n if (standalone.length > 0) {\n result.push({\n id: '__standalone',\n title: tr('common.lessons', 'Lessons'),\n lessons: standalone,\n });\n }\n return result;\n }, [course, tr]);\n\n // Flat, ordered lesson list for prev/next + completion checks.\n const orderedLessons = useMemo<BuiLesson[]>(() => groups.flatMap((g) => g.lessons), [groups]);\n // Lesson id → title for this course, so the tutor panel can NAME the lessons\n // its answers were grounded in instead of showing opaque ids.\n const lessonTitlesById = useMemo<ReadonlyMap<string, string>>(\n () => new Map(orderedLessons.map((lesson) => [lesson.id, lesson.title])),\n [orderedLessons]\n );\n\n const progressByLesson = useMemo(() => {\n const map = new Map<string, BuiProgressStatus>();\n for (const p of enrollment?.lessonProgress ?? []) map.set(p.lessonId, p.status);\n return map;\n }, [enrollment]);\n\n // Resolve the active lesson as DERIVED state: the user's pick if it still\n // exists in the course, otherwise the first lesson. Avoids a setState-in-\n // effect re-anchor loop.\n const selectedLessonId = useMemo<string | null>(() => {\n if (orderedLessons.length === 0) return null;\n if (userSelectedId && orderedLessons.some((l) => l.id === userSelectedId)) {\n return userSelectedId;\n }\n if (requestedLessonId && orderedLessons.some((l) => l.id === requestedLessonId)) {\n return requestedLessonId;\n }\n return orderedLessons[0].id;\n }, [orderedLessons, userSelectedId, requestedLessonId]);\n\n // Record IN_PROGRESS once when a not-yet-started lesson is opened. That\n // first successful write is also the `buildmyiq.lesson.started` fact, so a\n // re-opened lesson is never counted as a second start.\n useEffect(() => {\n if (!enrollmentId || !selectedLessonId) return;\n const status = progressByLesson.get(selectedLessonId);\n if (status === 'COMPLETED' || status === 'IN_PROGRESS') return;\n if (autoRecordedRef.current.has(selectedLessonId)) return;\n autoRecordedRef.current.add(selectedLessonId);\n const lessonType = orderedLessons.find((l) => l.id === selectedLessonId)?.type;\n recordMutate(\n {\n enrollmentId,\n lessonId: selectedLessonId,\n status: 'IN_PROGRESS',\n },\n {\n onSuccess: () =>\n trackLessonStarted({ courseId, enrollmentId, lessonId: selectedLessonId, lessonType }),\n }\n );\n }, [\n enrollmentId,\n selectedLessonId,\n progressByLesson,\n recordMutate,\n orderedLessons,\n courseId,\n trackLessonStarted,\n ]);\n\n const selectedIndex = orderedLessons.findIndex((l) => l.id === selectedLessonId);\n const selectedLesson = selectedIndex >= 0 ? orderedLessons[selectedIndex] : null;\n const selectedStatus = selectedLesson ? progressByLesson.get(selectedLesson.id) : undefined;\n const isSelectedComplete = selectedStatus === 'COMPLETED';\n\n const completedCount = orderedLessons.filter(\n (l) => progressByLesson.get(l.id) === 'COMPLETED'\n ).length;\n const allComplete = orderedLessons.length > 0 && completedCount === orderedLessons.length;\n const alreadyFinished = enrollment?.status === 'COMPLETED';\n\n const handleSelect = (lessonId: string) => {\n setUserSelectedId(lessonId);\n setShowTree(false);\n setActionError(null);\n };\n\n const goTo = (offset: number) => {\n if (selectedIndex < 0) return;\n const next = orderedLessons[selectedIndex + offset];\n if (next) handleSelect(next.id);\n };\n\n const handleMarkComplete = async () => {\n if (!enrollmentId || !selectedLesson) return;\n setActionError(null);\n try {\n await recordProgress.mutateAsync({\n enrollmentId,\n lessonId: selectedLesson.id,\n status: 'COMPLETED',\n });\n } catch (err) {\n setActionError(\n err instanceof Error && err.message\n ? err.message\n : tr('buildmyiq.lessonPlayer.markCompleteError', 'Could not mark this lesson complete.')\n );\n return;\n }\n trackLessonCompleted({\n courseId,\n enrollmentId,\n lessonId: selectedLesson.id,\n lessonType: selectedLesson.type,\n });\n // Advance to the next incomplete lesson for a natural flow.\n const next = orderedLessons[selectedIndex + 1];\n if (next) setUserSelectedId(next.id);\n };\n\n const handleFinishCourse = async () => {\n if (!enrollmentId) return;\n setActionError(null);\n try {\n const completed = await completeEnrollment.mutateAsync({ id: enrollmentId });\n trackCourseCompleted({ courseId: completed.courseId, enrollmentId: completed.id });\n } catch (err) {\n setActionError(\n err instanceof Error && err.message\n ? err.message\n : tr('buildmyiq.lessonPlayer.finishCourseError', 'Could not finish this course.')\n );\n }\n };\n\n // ---- top-level states ----------------------------------------------------\n if (!enrollmentId) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')}>\n <EmptyState\n illustration=\"empty-data\"\n title={tr('buildmyiq.lessonPlayer.noEnrollmentTitle', 'No enrollment selected')}\n description={tr(\n 'buildmyiq.lessonPlayer.noEnrollmentDescription',\n 'Open a course from My Learning to start the lesson player.'\n )}\n action={\n <Button onClick={() => navigateTo('learning')}>\n {tr('buildmyiq.lessonPlayer.backToLearning', 'Back to My Learning')}\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const isLoading = enrollmentQuery.isLoading || (Boolean(courseId) && courseQuery.isLoading);\n\n const backAction = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('learning')}>\n <ArrowLeft className=\"me-1 h-4 w-4 rtl:-scale-x-100\" />{' '}\n {tr('buildmyiq.nav.learning', 'My Learning')}\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <LoadingView label={tr('buildmyiq.lessonPlayer.loading', 'Loading your lesson…')} />\n </PageLayout>\n );\n }\n\n if (enrollmentQuery.isError) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <ErrorView\n title={tr('buildmyiq.lessonPlayer.enrollmentErrorTitle', \"Couldn't load this enrollment\")}\n message={\n enrollmentQuery.error instanceof Error ? enrollmentQuery.error.message : undefined\n }\n onRetry={() => enrollmentQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!enrollment) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <EmptyState\n illustration=\"empty-data\"\n title={tr('buildmyiq.lessonPlayer.enrollmentNotFoundTitle', 'Enrollment not found')}\n description={tr(\n 'buildmyiq.lessonPlayer.enrollmentNotFoundDescription',\n 'This enrollment may have been removed or is not available to you.'\n )}\n action={\n <Button onClick={() => navigateTo('learning')}>\n {tr('buildmyiq.lessonPlayer.backToLearning', 'Back to My Learning')}\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n if (courseQuery.isError) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <ErrorView\n title={tr('buildmyiq.lessonPlayer.courseErrorTitle', \"Couldn't load the course\")}\n message={courseQuery.error instanceof Error ? courseQuery.error.message : undefined}\n onRetry={() => courseQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n const progressPct = Math.round(enrollment.progressPct ?? 0);\n const SelectedIcon = selectedLesson ? LESSON_ICON[selectedLesson.type] : FileText;\n\n return (\n <PageLayout\n title={course?.title ?? tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')}\n actions={backAction}\n >\n <div className=\"flex flex-col gap-5\">\n {/* Header progress — ProgressRing hero + completion bar */}\n <GlassCard\n treatment=\"glass\"\n glow={progressPct > 0}\n className=\"flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:gap-5\"\n >\n <ProgressRing value={progressPct} size={64} className=\"shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={enrollment.status} />\n <span className=\"text-sm text-text-secondary tabular-nums\">\n {tr(\n 'buildmyiq.lessonPlayer.progressSummary',\n '{{completed}} of {{total}} lessons complete',\n { completed: completedCount, total: orderedLessons.length }\n )}\n </span>\n </div>\n <MiniBar value={progressPct} className=\"mt-3\" />\n </div>\n {allComplete && !alreadyFinished && (\n <Button\n className=\"shrink-0 max-sm:w-full\"\n onClick={handleFinishCourse}\n disabled={completeEnrollment.isPending}\n >\n {completeEnrollment.isPending ? (\n <Loader2 className=\"me-1 h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"me-1 h-4 w-4\" />\n )}\n {tr('buildmyiq.lessonPlayer.finishCourse', 'Finish course')}\n </Button>\n )}\n </GlassCard>\n\n {actionError && (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {actionError}\n </p>\n )}\n\n {/* Mobile tree toggle — collapses the outline into a top sheet */}\n <Button\n variant=\"secondary\"\n size=\"sm\"\n className=\"w-full lg:hidden\"\n onClick={() => setShowTree((v) => !v)}\n >\n <ListChecks className=\"me-1 h-4 w-4\" />\n {showTree\n ? tr('buildmyiq.lessonPlayer.hideLessons', 'Hide lessons')\n : tr('buildmyiq.lessonPlayer.showLessons', 'Lessons ({{completed}}/{{total}})', {\n completed: completedCount,\n total: orderedLessons.length,\n })}\n </Button>\n\n <div className=\"flex flex-col gap-5 lg:flex-row\">\n {/* Left: module / lesson completion timeline */}\n <aside className={cn('lg:block lg:w-80 lg:shrink-0', showTree ? 'block' : 'hidden')}>\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0 lg:sticky lg:top-4\">\n {orderedLessons.length === 0 ? (\n <div className=\"p-4 text-sm text-text-secondary\">\n {tr('buildmyiq.lessonPlayer.noLessons', 'This course has no lessons yet.')}\n </div>\n ) : (\n <>\n <nav className=\"max-h-[68vh] overflow-auto p-3\">\n {groups.map((group) => {\n const groupDone = group.lessons.filter(\n (l) => progressByLesson.get(l.id) === 'COMPLETED'\n ).length;\n return (\n <div key={group.id} className=\"mb-4 last:mb-0\">\n <div className=\"mb-2 flex items-center justify-between gap-2 px-1\">\n <p className=\"min-w-0 truncate text-sm font-semibold text-text-primary\">\n {group.title}\n </p>\n <span className=\"shrink-0 text-xs font-medium tabular-nums text-text-secondary\">\n {groupDone} / {group.lessons.length}\n </span>\n </div>\n <ul className=\"flex flex-col\">\n {group.lessons.map((lesson, i) => {\n const status = progressByLesson.get(lesson.id);\n const done = status === 'COMPLETED';\n const active = lesson.id === selectedLessonId;\n const isLast = i === group.lessons.length - 1;\n const Icon = LESSON_ICON[lesson.type];\n return (\n <li key={lesson.id} className=\"flex gap-3\">\n {/* Timeline node + connector */}\n <div className=\"flex flex-col items-center pt-1.5\">\n {done ? (\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--color-status-success-text)]\">\n <Check className=\"h-3.5 w-3.5 text-status-success-bg-subtle\" />\n </span>\n ) : active ? (\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)] ring-2 ring-[var(--color-accent-teal)]\">\n <Icon className=\"h-3.5 w-3.5 text-[var(--color-accent-teal)]\" />\n </span>\n ) : (\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-border-seam bg-bg-sunken\">\n <Icon className=\"h-3.5 w-3.5 text-text-secondary opacity-70\" />\n </span>\n )}\n {!isLast && (\n <span\n className={cn(\n 'my-1 w-px flex-1',\n done\n ? 'bg-[color-mix(in_srgb,var(--color-status-success-text)_45%,transparent)]'\n : 'bg-border-seam'\n )}\n />\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => handleSelect(lesson.id)}\n className={cn(\n 'mb-1 min-w-0 flex-1 rounded-lg px-3 py-2 text-start transition-colors',\n active\n ? 'bg-[var(--color-accent-soft)]'\n : 'hover:bg-bg-sunken'\n )}\n aria-current={active ? 'true' : undefined}\n >\n <span\n className={cn(\n 'block truncate text-sm',\n active\n ? 'font-medium text-text-primary'\n : done\n ? 'text-text-secondary'\n : 'text-text-primary'\n )}\n >\n {lesson.title}\n </span>\n <span className=\"mt-0.5 block truncate text-xs text-text-secondary\">\n {formatEnumLabel(lesson.type)}\n {lesson.durationMin > 0\n ? ` · ${formatDuration(lesson.durationMin)}`\n : ''}\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n );\n })}\n </nav>\n {courseId && (\n <div className=\"border-t border-border-seam p-3\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full\"\n onClick={() => navigateTo(`catalog/${courseId}`)}\n >\n <BookOpen className=\"me-1.5 h-4 w-4\" />{' '}\n {tr('buildmyiq.lessonPlayer.courseOverview', 'Course overview')}\n </Button>\n </div>\n )}\n </>\n )}\n </GlassCard>\n </aside>\n\n {/* Right: selected lesson */}\n <main className=\"min-w-0 flex-1\">\n {!selectedLesson ? (\n <EmptyState\n illustration=\"empty-data\"\n title={tr('buildmyiq.lessonPlayer.noLessonTitle', 'No lesson to show')}\n description={tr(\n 'buildmyiq.lessonPlayer.noLessonDescription',\n \"This course doesn't have any lessons yet. Check back once the author adds content.\"\n )}\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"flex flex-col gap-4 p-5 sm:p-7\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-[var(--color-accent-soft)] px-2.5 py-1 text-xs font-medium text-text-primary\">\n <SelectedIcon className=\"h-3.5 w-3.5 text-[var(--color-accent-teal)]\" />\n {formatEnumLabel(selectedLesson.type)}\n </span>\n {selectedLesson.durationMin > 0 && (\n <span className=\"text-xs text-text-secondary\">\n {formatDuration(selectedLesson.durationMin)}\n </span>\n )}\n {isSelectedComplete && (\n <span className=\"inline-flex items-center gap-1 text-xs font-medium text-status-success-text\">\n <CheckCircle2 className=\"h-3.5 w-3.5\" />{' '}\n {tr('buildmyiq.lessonPlayer.completedLabel', 'Completed')}\n </span>\n )}\n <OfflineLessonControl key={selectedLesson.id} lesson={selectedLesson} />\n <span className=\"ms-auto text-xs tabular-nums text-text-secondary\">\n {tr('buildmyiq.lessonPlayer.lessonPosition', 'Lesson {{n}} of {{total}}', {\n n: selectedIndex + 1,\n total: orderedLessons.length,\n })}\n </span>\n </div>\n\n <h2 className=\"text-2xl font-semibold tracking-tight text-text-primary\">\n {selectedLesson.title}\n </h2>\n\n {selectedLesson.media ? (\n <div className=\"rounded-xl border border-border-seam bg-bg-sunken p-3\">\n <p className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n {tr('buildmyiq.lessonPlayer.resources', 'Resources')}\n </p>\n <FileLink fileId={selectedLesson.media.id} />\n </div>\n ) : null}\n\n {selectedLesson.content ? (\n <p className=\"max-w-prose whitespace-pre-wrap text-[15px] leading-7 text-text-primary\">\n {selectedLesson.content}\n </p>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n {selectedLesson.media\n ? tr(\n 'buildmyiq.lessonPlayer.noContentWithMedia',\n 'This lesson has no written content beyond the attached media.'\n )\n : tr(\n 'buildmyiq.lessonPlayer.noContent',\n 'This lesson has no written content'\n )}\n </p>\n )}\n\n {/* Actions — Prev · Mark complete · Next */}\n <div className=\"mt-2 flex flex-col gap-3 border-t border-border-seam pt-5 sm:flex-row sm:items-center sm:justify-between\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"max-sm:w-full\"\n onClick={() => goTo(-1)}\n disabled={selectedIndex <= 0}\n >\n <ChevronLeft className=\"me-1 h-4 w-4 rtl:-scale-x-100\" />{' '}\n {tr('buildmyiq.lessonPlayer.previous', 'Previous')}\n </Button>\n\n {isSelectedComplete ? (\n <span className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-status-success-bg-subtle px-4 py-2 text-sm font-medium text-status-success-text max-sm:w-full\">\n <CheckCircle2 className=\"h-4 w-4\" />{' '}\n {tr('buildmyiq.lessonPlayer.lessonComplete', 'Lesson complete')}\n </span>\n ) : (\n <Button\n size=\"lg\"\n className=\"max-sm:w-full\"\n onClick={handleMarkComplete}\n disabled={recordProgress.isPending}\n >\n {recordProgress.isPending ? (\n <Loader2 className=\"me-1.5 h-4 w-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"me-1.5 h-4 w-4\" />\n )}\n {recordProgress.isPending\n ? tr('buildmyiq.lessonPlayer.markingComplete', 'Marking complete…')\n : tr('buildmyiq.lessonPlayer.markComplete', 'Mark complete')}\n </Button>\n )}\n\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"max-sm:w-full\"\n onClick={() => goTo(1)}\n disabled={selectedIndex >= orderedLessons.length - 1}\n >\n {tr('buildmyiq.lessonPlayer.next', 'Next')}{' '}\n <ChevronRight className=\"ms-1 h-4 w-4 rtl:-scale-x-100\" />\n </Button>\n </div>\n </GlassCard>\n )}\n\n {selectedLesson && courseId && enrollmentId && (\n <div className=\"mt-5\">\n {/* Course changes reset local state; lesson navigation keeps the course conversation. */}\n <TutorPanel\n key={courseId}\n courseId={courseId}\n enrollmentId={enrollmentId}\n lessonId={selectedLesson.id}\n lessonTitle={selectedLesson.title}\n lessonTitlesById={lessonTitlesById}\n />\n </div>\n )}\n </main>\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"LessonPlayerPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/learning/LessonPlayerPage.tsx"],"sourcesContent":["/**\n * LessonPlayerPage — the two-pane lesson player for a single enrollment.\n * @module buildmyiq/pages/learning\n *\n * Left pane: the course's module/lesson tree (from `buiCourse`) rendered as a\n * completion timeline driven by the enrollment's `lessonProgress`. Right pane:\n * the selected lesson (title, type, content text rendered safely, and a\n * `FileLink` for any attached media) with Prev / Mark-complete / Next controls.\n * Opening a lesson records IN_PROGRESS once; \"Mark complete\" records COMPLETED;\n * finishing every lesson transitions the enrollment to COMPLETED. All data is\n * live — no fabrication.\n */\n\nimport type { FC } from 'react';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { useParams, useSearchParams } from 'react-router';\nimport type { LucideIcon } from 'lucide-react';\nimport {\n ArrowLeft,\n BookOpen,\n Check,\n CheckCircle2,\n ChevronLeft,\n ChevronRight,\n FileText,\n HelpCircle,\n Loader2,\n ListChecks,\n MousePointerClick,\n PenLine,\n PlayCircle,\n Radio,\n Trophy,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n MiniBar,\n ProgressRing,\n LoadingView,\n ErrorView,\n EmptyState,\n FileLink,\n OfflineLessonControl,\n TutorPanel,\n} from '../../components';\nimport { useBuildmyiqAnalytics } from '../../analytics';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiMyEnrollment,\n useBuiCourse,\n useBuiRecordMyLessonProgress,\n useBuiCompleteMyEnrollment,\n} from '../../hooks';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { cn } from '../../utils/cn';\nimport { formatDuration, formatEnumLabel } from '../../utils/formatters';\nimport type { BuiLesson, BuiLessonType, BuiModule, BuiProgressStatus } from '../../types';\n\ninterface LessonGroup {\n id: string;\n title: string;\n lessons: BuiLesson[];\n}\n\nconst LESSON_ICON: Record<BuiLessonType, LucideIcon> = {\n ARTICLE: FileText,\n VIDEO: PlayCircle,\n QUIZ: HelpCircle,\n ASSIGNMENT: PenLine,\n INTERACTIVE: MousePointerClick,\n LIVE_SESSION: Radio,\n};\n\nconst sortByPosition = <T extends { position: number }>(items: T[]): T[] =>\n [...items].sort((a, b) => a.position - b.position);\n\nexport const LessonPlayerPage: FC = () => {\n const { enrollmentId } = useParams<{ enrollmentId: string }>();\n // `?lesson=<id>` deep-links straight to one lesson (used by the \"what's next\"\n // suggestion on My Learning). Purely an INITIAL anchor — once the learner\n // picks a lesson themselves, their pick wins.\n const [searchParams] = useSearchParams();\n const requestedLessonId = searchParams.get('lesson');\n const { navigateTo } = useBuildmyiq();\n const tr = useTr();\n\n const enrollmentQuery = useBuiMyEnrollment(enrollmentId);\n const enrollment = enrollmentQuery.data ?? null;\n const courseId = enrollment?.courseId;\n const courseQuery = useBuiCourse(courseId);\n const course = courseQuery.data ?? null;\n\n const recordProgress = useBuiRecordMyLessonProgress();\n const completeEnrollment = useBuiCompleteMyEnrollment();\n const { trackLessonStarted, trackLessonCompleted, trackCourseCompleted } =\n useBuildmyiqAnalytics();\n\n // react-query's mutate is referentially stable across renders, so it can be\n // used directly in effects without a render-time ref write.\n const recordMutate = recordProgress.mutate;\n const autoRecordedRef = useRef<Set<string>>(new Set());\n\n const [userSelectedId, setUserSelectedId] = useState<string | null>(null);\n const [showTree, setShowTree] = useState(false);\n // Error from the last \"Mark complete\" / \"Finish course\" attempt. Without it a\n // rejected mutation surfaced only as an unhandled promise rejection and the\n // button silently did nothing.\n const [actionError, setActionError] = useState<string | null>(null);\n\n // Grouped tree (modules → lessons) plus any standalone lessons, de-duplicated.\n const groups = useMemo<LessonGroup[]>(() => {\n if (!course) return [];\n const seen = new Set<string>();\n const modules: BuiModule[] = sortByPosition(course.modules ?? []);\n const result: LessonGroup[] = [];\n for (const m of modules) {\n const lessons = sortByPosition(m.lessons ?? []).filter((l) => {\n if (seen.has(l.id)) return false;\n seen.add(l.id);\n return true;\n });\n if (lessons.length > 0) result.push({ id: m.id, title: m.title, lessons });\n }\n const standalone = sortByPosition(course.lessons ?? []).filter((l) => {\n if (seen.has(l.id)) return false;\n seen.add(l.id);\n return true;\n });\n if (standalone.length > 0) {\n result.push({\n id: '__standalone',\n title: tr('common.lessons', 'Lessons'),\n lessons: standalone,\n });\n }\n return result;\n }, [course, tr]);\n\n // Flat, ordered lesson list for prev/next + completion checks.\n const orderedLessons = useMemo<BuiLesson[]>(() => groups.flatMap((g) => g.lessons), [groups]);\n // Lesson id → title for this course, so the tutor panel can NAME the lessons\n // its answers were grounded in instead of showing opaque ids.\n const lessonTitlesById = useMemo<ReadonlyMap<string, string>>(\n () => new Map(orderedLessons.map((lesson) => [lesson.id, lesson.title])),\n [orderedLessons]\n );\n\n const progressByLesson = useMemo(() => {\n const map = new Map<string, BuiProgressStatus>();\n for (const p of enrollment?.lessonProgress ?? []) map.set(p.lessonId, p.status);\n return map;\n }, [enrollment]);\n\n // Resolve the active lesson as DERIVED state: the user's pick if it still\n // exists in the course, otherwise the first lesson. Avoids a setState-in-\n // effect re-anchor loop.\n const selectedLessonId = useMemo<string | null>(() => {\n if (orderedLessons.length === 0) return null;\n if (userSelectedId && orderedLessons.some((l) => l.id === userSelectedId)) {\n return userSelectedId;\n }\n if (requestedLessonId && orderedLessons.some((l) => l.id === requestedLessonId)) {\n return requestedLessonId;\n }\n return orderedLessons[0].id;\n }, [orderedLessons, userSelectedId, requestedLessonId]);\n\n // Record IN_PROGRESS once when a not-yet-started lesson is opened. That\n // first successful write is also the `buildmyiq.lesson.started` fact, so a\n // re-opened lesson is never counted as a second start.\n useEffect(() => {\n if (!enrollmentId || !selectedLessonId) return;\n const status = progressByLesson.get(selectedLessonId);\n if (status === 'COMPLETED' || status === 'IN_PROGRESS') return;\n if (autoRecordedRef.current.has(selectedLessonId)) return;\n autoRecordedRef.current.add(selectedLessonId);\n const lessonType = orderedLessons.find((l) => l.id === selectedLessonId)?.type;\n recordMutate(\n {\n enrollmentId,\n lessonId: selectedLessonId,\n status: 'IN_PROGRESS',\n },\n {\n onSuccess: () =>\n trackLessonStarted({ courseId, enrollmentId, lessonId: selectedLessonId, lessonType }),\n }\n );\n }, [\n enrollmentId,\n selectedLessonId,\n progressByLesson,\n recordMutate,\n orderedLessons,\n courseId,\n trackLessonStarted,\n ]);\n\n const selectedIndex = orderedLessons.findIndex((l) => l.id === selectedLessonId);\n const selectedLesson = selectedIndex >= 0 ? orderedLessons[selectedIndex] : null;\n const selectedStatus = selectedLesson ? progressByLesson.get(selectedLesson.id) : undefined;\n const isSelectedComplete = selectedStatus === 'COMPLETED';\n\n const completedCount = orderedLessons.filter(\n (l) => progressByLesson.get(l.id) === 'COMPLETED'\n ).length;\n const allComplete = orderedLessons.length > 0 && completedCount === orderedLessons.length;\n const alreadyFinished = enrollment?.status === 'COMPLETED';\n\n const handleSelect = (lessonId: string) => {\n setUserSelectedId(lessonId);\n setShowTree(false);\n setActionError(null);\n };\n\n const goTo = (offset: number) => {\n if (selectedIndex < 0) return;\n const next = orderedLessons[selectedIndex + offset];\n if (next) handleSelect(next.id);\n };\n\n const handleMarkComplete = async () => {\n if (!enrollmentId || !selectedLesson) return;\n setActionError(null);\n try {\n await recordProgress.mutateAsync({\n enrollmentId,\n lessonId: selectedLesson.id,\n status: 'COMPLETED',\n });\n } catch (err) {\n setActionError(\n err instanceof Error && err.message\n ? err.message\n : tr('buildmyiq.lessonPlayer.markCompleteError', 'Could not mark this lesson complete.')\n );\n return;\n }\n trackLessonCompleted({\n courseId,\n enrollmentId,\n lessonId: selectedLesson.id,\n lessonType: selectedLesson.type,\n });\n // Advance to the next incomplete lesson for a natural flow.\n const next = orderedLessons[selectedIndex + 1];\n if (next) setUserSelectedId(next.id);\n };\n\n const handleFinishCourse = async () => {\n if (!enrollmentId) return;\n setActionError(null);\n try {\n const completed = await completeEnrollment.mutateAsync({ id: enrollmentId });\n trackCourseCompleted({ courseId: completed.courseId, enrollmentId: completed.id });\n } catch (err) {\n setActionError(\n err instanceof Error && err.message\n ? err.message\n : tr('buildmyiq.lessonPlayer.finishCourseError', 'Could not finish this course.')\n );\n }\n };\n\n // ---- top-level states ----------------------------------------------------\n if (!enrollmentId) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')}>\n <EmptyState\n illustration=\"empty-data\"\n title={tr('buildmyiq.lessonPlayer.noEnrollmentTitle', 'No enrollment selected')}\n description={tr(\n 'buildmyiq.lessonPlayer.noEnrollmentDescription',\n 'Open a course from My Learning to start the lesson player.'\n )}\n action={\n <Button onClick={() => navigateTo('learning')}>\n {tr('buildmyiq.lessonPlayer.backToLearning', 'Back to My Learning')}\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const isLoading = enrollmentQuery.isLoading || (Boolean(courseId) && courseQuery.isLoading);\n\n const backAction = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('learning')}>\n <ArrowLeft className=\"me-1 h-4 w-4 rtl:-scale-x-100\" />{' '}\n {tr('buildmyiq.nav.learning', 'My Learning')}\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <LoadingView label={tr('buildmyiq.lessonPlayer.loading', 'Loading your lesson…')} />\n </PageLayout>\n );\n }\n\n if (enrollmentQuery.isError) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <ErrorView\n title={tr('buildmyiq.lessonPlayer.enrollmentErrorTitle', \"Couldn't load this enrollment\")}\n message={\n enrollmentQuery.error instanceof Error ? enrollmentQuery.error.message : undefined\n }\n onRetry={() => enrollmentQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!enrollment) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <EmptyState\n illustration=\"empty-data\"\n title={tr('buildmyiq.lessonPlayer.enrollmentNotFoundTitle', 'Enrollment not found')}\n description={tr(\n 'buildmyiq.lessonPlayer.enrollmentNotFoundDescription',\n 'This enrollment may have been removed or is not available to you.'\n )}\n action={\n <Button onClick={() => navigateTo('learning')}>\n {tr('buildmyiq.lessonPlayer.backToLearning', 'Back to My Learning')}\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n if (courseQuery.isError) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <ErrorView\n title={tr('buildmyiq.lessonPlayer.courseErrorTitle', \"Couldn't load the course\")}\n message={courseQuery.error instanceof Error ? courseQuery.error.message : undefined}\n onRetry={() => courseQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n const progressPct = Math.round(enrollment.progressPct ?? 0);\n const SelectedIcon = selectedLesson ? LESSON_ICON[selectedLesson.type] : FileText;\n\n return (\n <PageLayout\n title={course?.title ?? tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')}\n actions={backAction}\n >\n <div className=\"flex flex-col gap-5\">\n {/* Header progress — ProgressRing hero + completion bar */}\n <GlassCard\n treatment=\"glass\"\n glow={progressPct > 0}\n className=\"flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:gap-5\"\n >\n <ProgressRing value={progressPct} size={64} className=\"shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={enrollment.status} />\n <span className=\"text-sm text-text-secondary tabular-nums\">\n {tr(\n 'buildmyiq.lessonPlayer.progressSummary',\n '{{completed}} of {{total}} lessons complete',\n { completed: completedCount, total: orderedLessons.length }\n )}\n </span>\n </div>\n <MiniBar value={progressPct} className=\"mt-3\" />\n </div>\n {allComplete && !alreadyFinished && (\n <Button\n className=\"shrink-0 max-sm:w-full\"\n onClick={handleFinishCourse}\n disabled={completeEnrollment.isPending}\n >\n {completeEnrollment.isPending ? (\n <Loader2 className=\"me-1 h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"me-1 h-4 w-4\" />\n )}\n {tr('buildmyiq.lessonPlayer.finishCourse', 'Finish course')}\n </Button>\n )}\n </GlassCard>\n\n {actionError && (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {actionError}\n </p>\n )}\n\n {/* Mobile tree toggle — collapses the outline into a top sheet */}\n <Button\n variant=\"secondary\"\n size=\"sm\"\n className=\"w-full lg:hidden\"\n onClick={() => setShowTree((v) => !v)}\n >\n <ListChecks className=\"me-1 h-4 w-4\" />\n {showTree\n ? tr('buildmyiq.lessonPlayer.hideLessons', 'Hide lessons')\n : tr('buildmyiq.lessonPlayer.showLessons', 'Lessons ({{completed}}/{{total}})', {\n completed: completedCount,\n total: orderedLessons.length,\n })}\n </Button>\n\n <div className=\"flex flex-col gap-5 lg:flex-row\">\n {/* Left: module / lesson completion timeline */}\n <aside className={cn('lg:block lg:w-80 lg:shrink-0', showTree ? 'block' : 'hidden')}>\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0 lg:sticky lg:top-4\">\n {orderedLessons.length === 0 ? (\n <div className=\"p-4 text-sm text-text-secondary\">\n {tr('buildmyiq.lessonPlayer.noLessons', 'This course has no lessons yet.')}\n </div>\n ) : (\n <>\n <nav className=\"max-h-[68vh] overflow-auto p-3\">\n {groups.map((group) => {\n const groupDone = group.lessons.filter(\n (l) => progressByLesson.get(l.id) === 'COMPLETED'\n ).length;\n return (\n <div key={group.id} className=\"mb-4 last:mb-0\">\n <div className=\"mb-2 flex items-center justify-between gap-2 px-1\">\n <p className=\"min-w-0 truncate text-sm font-semibold text-text-primary\">\n {group.title}\n </p>\n <span className=\"shrink-0 text-xs font-medium tabular-nums text-text-secondary\">\n {groupDone} / {group.lessons.length}\n </span>\n </div>\n <ul className=\"flex flex-col\">\n {group.lessons.map((lesson, i) => {\n const status = progressByLesson.get(lesson.id);\n const done = status === 'COMPLETED';\n const active = lesson.id === selectedLessonId;\n const isLast = i === group.lessons.length - 1;\n const Icon = LESSON_ICON[lesson.type];\n return (\n <li key={lesson.id} className=\"flex gap-3\">\n {/* Timeline node + connector */}\n <div className=\"flex flex-col items-center pt-1.5\">\n {done ? (\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--color-status-success-text)]\">\n <Check className=\"h-3.5 w-3.5 text-status-success-bg-subtle\" />\n </span>\n ) : active ? (\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)] ring-2 ring-[var(--color-accent-teal)]\">\n <Icon className=\"h-3.5 w-3.5 text-[var(--color-accent-teal)]\" />\n </span>\n ) : (\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-border-seam bg-bg-sunken\">\n <Icon className=\"h-3.5 w-3.5 text-text-secondary opacity-70\" />\n </span>\n )}\n {!isLast && (\n <span\n className={cn(\n 'my-1 w-px flex-1',\n done\n ? 'bg-[color-mix(in_srgb,var(--color-status-success-text)_45%,transparent)]'\n : 'bg-border-seam'\n )}\n />\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => handleSelect(lesson.id)}\n className={cn(\n 'mb-1 min-w-0 flex-1 rounded-lg px-3 py-2 text-start transition-colors',\n active\n ? 'bg-[var(--color-accent-soft)]'\n : 'hover:bg-bg-sunken'\n )}\n aria-current={active ? 'true' : undefined}\n >\n <span\n className={cn(\n 'block truncate text-sm',\n active\n ? 'font-medium text-text-primary'\n : done\n ? 'text-text-secondary'\n : 'text-text-primary'\n )}\n >\n {lesson.title}\n </span>\n <span className=\"mt-0.5 block truncate text-xs text-text-secondary\">\n {formatEnumLabel(lesson.type)}\n {lesson.durationMin > 0\n ? ` · ${formatDuration(lesson.durationMin)}`\n : ''}\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n );\n })}\n </nav>\n {courseId && (\n <div className=\"border-t border-border-seam p-3\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full\"\n onClick={() => navigateTo(`catalog/${courseId}`)}\n >\n <BookOpen className=\"me-1.5 h-4 w-4\" />{' '}\n {tr('buildmyiq.lessonPlayer.courseOverview', 'Course overview')}\n </Button>\n </div>\n )}\n </>\n )}\n </GlassCard>\n </aside>\n\n {/* Right: selected lesson */}\n <section\n aria-label={tr('buildmyiq.lessonPlayer.lessonRegion', 'Lesson')}\n className=\"min-w-0 flex-1\"\n >\n {!selectedLesson ? (\n <EmptyState\n illustration=\"empty-data\"\n title={tr('buildmyiq.lessonPlayer.noLessonTitle', 'No lesson to show')}\n description={tr(\n 'buildmyiq.lessonPlayer.noLessonDescription',\n \"This course doesn't have any lessons yet. Check back once the author adds content.\"\n )}\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"flex flex-col gap-4 p-5 sm:p-7\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-[var(--color-accent-soft)] px-2.5 py-1 text-xs font-medium text-text-primary\">\n <SelectedIcon className=\"h-3.5 w-3.5 text-[var(--color-accent-teal)]\" />\n {formatEnumLabel(selectedLesson.type)}\n </span>\n {selectedLesson.durationMin > 0 && (\n <span className=\"text-xs text-text-secondary\">\n {formatDuration(selectedLesson.durationMin)}\n </span>\n )}\n {isSelectedComplete && (\n <span className=\"inline-flex items-center gap-1 text-xs font-medium text-status-success-text\">\n <CheckCircle2 className=\"h-3.5 w-3.5\" />{' '}\n {tr('buildmyiq.lessonPlayer.completedLabel', 'Completed')}\n </span>\n )}\n <OfflineLessonControl key={selectedLesson.id} lesson={selectedLesson} />\n <span className=\"ms-auto text-xs tabular-nums text-text-secondary\">\n {tr('buildmyiq.lessonPlayer.lessonPosition', 'Lesson {{n}} of {{total}}', {\n n: selectedIndex + 1,\n total: orderedLessons.length,\n })}\n </span>\n </div>\n\n <h2 className=\"text-2xl font-semibold tracking-tight text-text-primary\">\n {selectedLesson.title}\n </h2>\n\n {selectedLesson.media ? (\n <div className=\"rounded-xl border border-border-seam bg-bg-sunken p-3\">\n <p className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n {tr('buildmyiq.lessonPlayer.resources', 'Resources')}\n </p>\n <FileLink fileId={selectedLesson.media.id} />\n </div>\n ) : null}\n\n {selectedLesson.content ? (\n <p className=\"max-w-prose whitespace-pre-wrap text-[15px] leading-7 text-text-primary\">\n {selectedLesson.content}\n </p>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n {selectedLesson.media\n ? tr(\n 'buildmyiq.lessonPlayer.noContentWithMedia',\n 'This lesson has no written content beyond the attached media.'\n )\n : tr(\n 'buildmyiq.lessonPlayer.noContent',\n 'This lesson has no written content'\n )}\n </p>\n )}\n\n {/* Actions — Prev · Mark complete · Next */}\n <div className=\"mt-2 flex flex-col gap-3 border-t border-border-seam pt-5 sm:flex-row sm:items-center sm:justify-between\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"max-sm:w-full\"\n onClick={() => goTo(-1)}\n disabled={selectedIndex <= 0}\n >\n <ChevronLeft className=\"me-1 h-4 w-4 rtl:-scale-x-100\" />{' '}\n {tr('buildmyiq.lessonPlayer.previous', 'Previous')}\n </Button>\n\n {isSelectedComplete ? (\n <span className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-status-success-bg-subtle px-4 py-2 text-sm font-medium text-status-success-text max-sm:w-full\">\n <CheckCircle2 className=\"h-4 w-4\" />{' '}\n {tr('buildmyiq.lessonPlayer.lessonComplete', 'Lesson complete')}\n </span>\n ) : (\n <Button\n size=\"lg\"\n className=\"max-sm:w-full\"\n onClick={handleMarkComplete}\n disabled={recordProgress.isPending}\n >\n {recordProgress.isPending ? (\n <Loader2 className=\"me-1.5 h-4 w-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"me-1.5 h-4 w-4\" />\n )}\n {recordProgress.isPending\n ? tr('buildmyiq.lessonPlayer.markingComplete', 'Marking complete…')\n : tr('buildmyiq.lessonPlayer.markComplete', 'Mark complete')}\n </Button>\n )}\n\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"max-sm:w-full\"\n onClick={() => goTo(1)}\n disabled={selectedIndex >= orderedLessons.length - 1}\n >\n {tr('buildmyiq.lessonPlayer.next', 'Next')}{' '}\n <ChevronRight className=\"ms-1 h-4 w-4 rtl:-scale-x-100\" />\n </Button>\n </div>\n </GlassCard>\n )}\n\n {selectedLesson && courseId && enrollmentId && (\n <div className=\"mt-5\">\n {/* Course changes reset local state; lesson navigation keeps the course conversation. */}\n <TutorPanel\n key={courseId}\n courseId={courseId}\n enrollmentId={enrollmentId}\n lessonId={selectedLesson.id}\n lessonTitle={selectedLesson.title}\n lessonTitlesById={lessonTitlesById}\n />\n </div>\n )}\n </section>\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-buildmyiq",
3
- "version": "2026.1030.6",
3
+ "version": "2026.1030.7",
4
4
  "description": "Burdenoff BuildMyIQ Microfrontend — education, learning & development",
5
5
  "type": "module",
6
6
  "files": [