@burdenoff/microfe-buildmyiq 2026.731.2 → 2026.731.3

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.
@@ -0,0 +1,28 @@
1
+ import { BuiLesson } from '../types';
2
+
3
+ interface OfflineLessonInput {
4
+ id: string;
5
+ courseId: string;
6
+ title: string;
7
+ type: string;
8
+ content: string | null;
9
+ mediaUrl?: string | null;
10
+ }
11
+ interface BuildmyiqOfflineLessonsBridge {
12
+ cache(lesson: OfflineLessonInput): Promise<boolean>;
13
+ isCached(lessonId: string): Promise<boolean>;
14
+ remove(lessonId: string): Promise<boolean>;
15
+ }
16
+ declare global {
17
+ interface Window {
18
+ __buildmyiqNative?: {
19
+ offlineLessons: BuildmyiqOfflineLessonsBridge;
20
+ };
21
+ }
22
+ }
23
+ interface OfflineLessonControlProps {
24
+ lesson: BuiLesson;
25
+ }
26
+ export declare function OfflineLessonControl({ lesson }: OfflineLessonControlProps): import("react").JSX.Element | null;
27
+ export {};
28
+ //# sourceMappingURL=OfflineLessonControl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OfflineLessonControl.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/OfflineLessonControl.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,UAAU,kBAAkB;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,UAAU,6BAA6B;IACrC,KAAK,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5C;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,iBAAiB,CAAC,EAAE;YAClB,cAAc,EAAE,6BAA6B,CAAC;SAC/C,CAAC;KACH;CACF;AASD,UAAU,yBAAyB;IACjC,MAAM,EAAE,SAAS,CAAC;CACnB;AAED,wBAAgB,oBAAoB,CAAC,EAAE,MAAM,EAAE,EAAE,yBAAyB,sCAgHzE"}
@@ -0,0 +1,100 @@
1
+ import { useEcoFile as e } from "../hooks/useBuiFiles.js";
2
+ import "../hooks/index.js";
3
+ import { useEffect as t, useState as n } from "react";
4
+ import { CloudDownload as r, CloudOff as i, HardDriveDownload as a, Loader2 as o, Trash2 as s } from "lucide-react";
5
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
+ import { Button as u } from "@burdenoff/fe-libs/ui";
7
+ //#region src/buildmyiq/components/OfflineLessonControl.tsx
8
+ function d() {
9
+ if (!(typeof window > "u")) return window.__buildmyiqNative?.offlineLessons;
10
+ }
11
+ function f({ lesson: f }) {
12
+ let [p, m] = n(() => d() ? "checking" : "unavailable"), h = e(f.media?.id);
13
+ if (t(() => {
14
+ let e = d();
15
+ if (!e) return;
16
+ let t = !1;
17
+ return e.isCached(f.id).then((e) => {
18
+ t || m(e ? "cached" : "idle");
19
+ }).catch(() => {
20
+ t || m("idle");
21
+ }), () => {
22
+ t = !0;
23
+ };
24
+ }, [f.id]), p === "unavailable" || p === "checking") return null;
25
+ let g = async () => {
26
+ let e = d();
27
+ if (!e) {
28
+ m("unavailable");
29
+ return;
30
+ }
31
+ m("downloading");
32
+ try {
33
+ m(await e.cache({
34
+ id: f.id,
35
+ courseId: f.courseId,
36
+ title: f.title,
37
+ type: f.type,
38
+ content: f.content ?? null,
39
+ mediaUrl: h.data?.downloadUrl ?? null
40
+ }) ? "cached" : "error");
41
+ } catch {
42
+ m("error");
43
+ }
44
+ }, _ = async () => {
45
+ let e = d();
46
+ if (!e) {
47
+ m("unavailable");
48
+ return;
49
+ }
50
+ m("removing");
51
+ try {
52
+ m(await e.remove(f.id) ? "idle" : "error");
53
+ } catch {
54
+ m("error");
55
+ }
56
+ };
57
+ return p === "cached" ? /* @__PURE__ */ l("div", {
58
+ className: "inline-flex items-center gap-1.5",
59
+ children: [/* @__PURE__ */ l("span", {
60
+ 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",
61
+ children: [/* @__PURE__ */ c(a, { className: "h-3.5 w-3.5 text-[var(--color-accent-teal)]" }), "Available offline"]
62
+ }), /* @__PURE__ */ c(u, {
63
+ type: "button",
64
+ variant: "ghost",
65
+ size: "icon-sm",
66
+ onClick: () => void _(),
67
+ "aria-label": "Remove offline copy of this lesson",
68
+ title: "Remove offline copy",
69
+ children: /* @__PURE__ */ c(s, { className: "h-3.5 w-3.5" })
70
+ })]
71
+ }) : p === "downloading" || p === "removing" ? /* @__PURE__ */ l(u, {
72
+ type: "button",
73
+ variant: "outline",
74
+ size: "sm",
75
+ disabled: !0,
76
+ children: [/* @__PURE__ */ c(o, { className: "mr-1.5 h-3.5 w-3.5 animate-spin" }), p === "downloading" ? "Downloading…" : "Removing…"]
77
+ }) : p === "error" ? /* @__PURE__ */ l("div", {
78
+ className: "inline-flex items-center gap-1.5",
79
+ children: [/* @__PURE__ */ l("span", {
80
+ className: "inline-flex items-center gap-1 text-xs text-status-error-text",
81
+ children: [/* @__PURE__ */ c(i, { className: "h-3.5 w-3.5" }), " Could not save offline"]
82
+ }), /* @__PURE__ */ l(u, {
83
+ type: "button",
84
+ variant: "outline",
85
+ size: "sm",
86
+ onClick: () => void g(),
87
+ children: [/* @__PURE__ */ c(r, { className: "mr-1.5 h-3.5 w-3.5" }), " Retry"]
88
+ })]
89
+ }) : /* @__PURE__ */ l(u, {
90
+ type: "button",
91
+ variant: "outline",
92
+ size: "sm",
93
+ onClick: () => void g(),
94
+ children: [/* @__PURE__ */ c(r, { className: "mr-1.5 h-3.5 w-3.5" }), " Download for offline"]
95
+ });
96
+ }
97
+ //#endregion
98
+ export { f as OfflineLessonControl };
99
+
100
+ //# sourceMappingURL=OfflineLessonControl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OfflineLessonControl.js","names":[],"sources":["../../../src/buildmyiq/components/OfflineLessonControl.tsx"],"sourcesContent":["/**\n * OfflineLessonControl — \"Download for offline\" control for the lesson player (BOFF-4421).\n * @module buildmyiq/components/OfflineLessonControl\n *\n * Wires the lesson player to the app shell's offline-lesson cache. The\n * caching logic itself lives in `buildmyiq-app` (`src/lib/native/offline-lessons.ts`,\n * Capacitor Filesystem on native / `localStorage` fallback on web) and is\n * exposed as `window.__buildmyiqNative.offlineLessons` by that repo's\n * `installBuildmyiqNativeBridge()` (`src/lib/native/buildmyiq-bridge.ts`).\n *\n * This MFE never imports from `buildmyiq-app` directly — no MFE-to-shell npm\n * dependency, matching this monorepo's build-time MFE composition rules — it\n * only ever talks to the plain `window` global the shell installs. If that\n * bridge isn't present (e.g. this component somehow rendered outside the\n * buildmyiq app shell), the control quietly renders nothing: offline caching\n * is a UX nicety, never a hard dependency of the lesson player.\n */\n\nimport { useEffect, useState } from 'react';\nimport { CloudDownload, CloudOff, HardDriveDownload, Loader2, Trash2 } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useEcoFile } from '../hooks';\nimport type { BuiLesson } from '../types';\n\ninterface OfflineLessonInput {\n id: string;\n courseId: string;\n title: string;\n type: string;\n content: string | null;\n mediaUrl?: string | null;\n}\n\ninterface BuildmyiqOfflineLessonsBridge {\n cache(lesson: OfflineLessonInput): Promise<boolean>;\n isCached(lessonId: string): Promise<boolean>;\n remove(lessonId: string): Promise<boolean>;\n}\n\ndeclare global {\n interface Window {\n __buildmyiqNative?: {\n offlineLessons: BuildmyiqOfflineLessonsBridge;\n };\n }\n}\n\nfunction getBridge(): BuildmyiqOfflineLessonsBridge | undefined {\n if (typeof window === 'undefined') return undefined;\n return window.__buildmyiqNative?.offlineLessons;\n}\n\ntype Status = 'checking' | 'unavailable' | 'idle' | 'downloading' | 'cached' | 'removing' | 'error';\n\ninterface OfflineLessonControlProps {\n lesson: BuiLesson;\n}\n\nexport function OfflineLessonControl({ lesson }: OfflineLessonControlProps) {\n // Lazy initializer keeps the \"no bridge installed\" branch out of the\n // effect body (only promise-callback setState calls happen there).\n const [status, setStatus] = useState<Status>(() => (getBridge() ? 'checking' : 'unavailable'));\n // Reuses the same React Query cache entry `FileLink` populates for this\n // lesson's `media` (same queryKey) — no extra network round trip.\n const mediaFile = useEcoFile(lesson.media?.id);\n\n useEffect(() => {\n const bridge = getBridge();\n if (!bridge) return;\n let cancelled = false;\n bridge\n .isCached(lesson.id)\n .then((cached) => {\n if (!cancelled) setStatus(cached ? 'cached' : 'idle');\n })\n .catch(() => {\n if (!cancelled) setStatus('idle');\n });\n return () => {\n cancelled = true;\n };\n }, [lesson.id]);\n\n if (status === 'unavailable' || status === 'checking') return null;\n\n const handleDownload = async () => {\n const bridge = getBridge();\n if (!bridge) {\n setStatus('unavailable');\n return;\n }\n setStatus('downloading');\n try {\n const ok = await bridge.cache({\n id: lesson.id,\n courseId: lesson.courseId,\n title: lesson.title,\n type: lesson.type,\n content: lesson.content ?? null,\n mediaUrl: mediaFile.data?.downloadUrl ?? null,\n });\n setStatus(ok ? 'cached' : 'error');\n } catch {\n setStatus('error');\n }\n };\n\n const handleRemove = async () => {\n const bridge = getBridge();\n if (!bridge) {\n setStatus('unavailable');\n return;\n }\n setStatus('removing');\n try {\n const ok = await bridge.remove(lesson.id);\n setStatus(ok ? 'idle' : 'error');\n } catch {\n setStatus('error');\n }\n };\n\n if (status === 'cached') {\n return (\n <div className=\"inline-flex items-center gap-1.5\">\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 <HardDriveDownload className=\"h-3.5 w-3.5 text-[var(--color-accent-teal)]\" />\n Available offline\n </span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={() => void handleRemove()}\n aria-label=\"Remove offline copy of this lesson\"\n title=\"Remove offline copy\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\n );\n }\n\n if (status === 'downloading' || status === 'removing') {\n return (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled>\n <Loader2 className=\"mr-1.5 h-3.5 w-3.5 animate-spin\" />\n {status === 'downloading' ? 'Downloading…' : 'Removing…'}\n </Button>\n );\n }\n\n if (status === 'error') {\n return (\n <div className=\"inline-flex items-center gap-1.5\">\n <span className=\"inline-flex items-center gap-1 text-xs text-status-error-text\">\n <CloudOff className=\"h-3.5 w-3.5\" /> Could not save offline\n </span>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleDownload()}>\n <CloudDownload className=\"mr-1.5 h-3.5 w-3.5\" /> Retry\n </Button>\n </div>\n );\n }\n\n return (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleDownload()}>\n <CloudDownload className=\"mr-1.5 h-3.5 w-3.5\" /> Download for offline\n </Button>\n );\n}\n"],"mappings":";;;;;;;AA+CA,SAAS,IAAuD;CAC1D,aAAO,SAAW,MACtB,OAAO,OAAO,mBAAmB;AACnC;AAQA,SAAgB,EAAqB,EAAE,aAAqC;CAG1E,IAAM,CAAC,GAAQ,KAAa,QAAwB,EAAU,IAAI,aAAa,aAAc,GAGvF,IAAY,EAAW,EAAO,OAAO,EAAE;CAmB7C,IAjBA,QAAgB;EACd,IAAM,IAAS,EAAU;EACzB,IAAI,CAAC,GAAQ;EACb,IAAI,IAAY;EAShB,OARA,EACG,SAAS,EAAO,EAAE,EAClB,MAAM,MAAW;GAChB,AAAK,KAAW,EAAU,IAAS,WAAW,MAAM;EACtD,CAAC,EACA,YAAY;GACX,AAAK,KAAW,EAAU,MAAM;EAClC,CAAC,SACU;GACX,IAAY;EACd;CACF,GAAG,CAAC,EAAO,EAAE,CAAC,GAEV,MAAW,iBAAiB,MAAW,YAAY,OAAO;CAE9D,IAAM,IAAiB,YAAY;EACjC,IAAM,IAAS,EAAU;EACzB,IAAI,CAAC,GAAQ;GACX,EAAU,aAAa;GACvB;EACF;EACA,EAAU,aAAa;EACvB,IAAI;GASF,EAAU,MARO,EAAO,MAAM;IAC5B,IAAI,EAAO;IACX,UAAU,EAAO;IACjB,OAAO,EAAO;IACd,MAAM,EAAO;IACb,SAAS,EAAO,WAAW;IAC3B,UAAU,EAAU,MAAM,eAAe;GAC3C,CAAC,IACc,WAAW,OAAO;EACnC,QAAQ;GACN,EAAU,OAAO;EACnB;CACF,GAEM,IAAe,YAAY;EAC/B,IAAM,IAAS,EAAU;EACzB,IAAI,CAAC,GAAQ;GACX,EAAU,aAAa;GACvB;EACF;EACA,EAAU,UAAU;EACpB,IAAI;GAEF,EAAU,MADO,EAAO,OAAO,EAAO,EAAE,IACzB,SAAS,OAAO;EACjC,QAAQ;GACN,EAAU,OAAO;EACnB;CACF;CA6CA,OA3CI,MAAW,WAEX,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACE,kBAAC,GAAD,EAAmB,WAAU,8CAA+C,CAAA,GAAC,mBAEzE;MACN,kBAAC,GAAD;GACE,MAAK;GACL,SAAQ;GACR,MAAK;GACL,eAAe,KAAK,EAAa;GACjC,cAAW;GACX,OAAM;aAEN,kBAAC,GAAD,EAAQ,WAAU,cAAe,CAAA;EAC3B,CAAA,CACL;MAIL,MAAW,iBAAiB,MAAW,aAEvC,kBAAC,GAAD;EAAQ,MAAK;EAAS,SAAQ;EAAU,MAAK;EAAK,UAAA;YAAlD,CACE,kBAAC,GAAD,EAAS,WAAU,kCAAmC,CAAA,GACrD,MAAW,gBAAgB,iBAAiB,WACvC;MAIR,MAAW,UAEX,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACE,kBAAC,GAAD,EAAU,WAAU,cAAe,CAAA,GAAC,yBAChC;MACN,kBAAC,GAAD;GAAQ,MAAK;GAAS,SAAQ;GAAU,MAAK;GAAK,eAAe,KAAK,EAAe;aAArF,CACE,kBAAC,GAAD,EAAe,WAAU,qBAAsB,CAAA,GAAC,QAC1C;IACL;MAKP,kBAAC,GAAD;EAAQ,MAAK;EAAS,SAAQ;EAAU,MAAK;EAAK,eAAe,KAAK,EAAe;YAArF,CACE,kBAAC,GAAD,EAAe,WAAU,qBAAsB,CAAA,GAAC,uBAC1C;;AAEZ"}
@@ -20,4 +20,5 @@ export { BuiDiscussion } from './BuiDiscussion';
20
20
  export { TutorPanel } from './TutorPanel';
21
21
  export { BuiTags } from './BuiTags';
22
22
  export { BuiPermissionButton, type BuiPermissionButtonProps } from './BuiPermissionButton';
23
+ export { OfflineLessonControl } from './OfflineLessonControl';
23
24
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -16,3 +16,4 @@ import "./BuiDiscussion.js";
16
16
  import "./TutorPanel.js";
17
17
  import "./BuiTags.js";
18
18
  import "./BuiPermissionButton.js";
19
+ import "./OfflineLessonControl.js";
@@ -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;AA8DhC,eAAO,MAAM,gBAAgB,EAAE,EAmd9B,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;AA+DhC,eAAO,MAAM,gBAAgB,EAAE,EAod9B,CAAC"}
@@ -8,208 +8,209 @@ import { EmptyState as o } from "../../components/EmptyState.js";
8
8
  import { ErrorView as s, LoadingView as c } from "../../components/StateViews.js";
9
9
  import { MiniBar as l } from "../../components/MiniBar.js";
10
10
  import { ProgressRing as ee } from "../../components/Viz.js";
11
- import { useBuiCourse as te, useBuiEnrollment as ne, useBuiRecordLessonProgress as re, useBuiTransitionEnrollment as u } from "../../hooks/useBuildmyiqApi.js";
11
+ import { useBuiCourse as te, useBuiEnrollment as u, useBuiRecordLessonProgress as d, useBuiTransitionEnrollment as f } from "../../hooks/useBuildmyiqApi.js";
12
12
  import "../../hooks/index.js";
13
- import { FileLink as ie } from "../../components/FileLink.js";
14
- import { TutorPanel as ae } from "../../components/TutorPanel.js";
13
+ import { FileLink as p } from "../../components/FileLink.js";
14
+ import { TutorPanel as ne } from "../../components/TutorPanel.js";
15
+ import { OfflineLessonControl as re } from "../../components/OfflineLessonControl.js";
15
16
  import "../../components/index.js";
16
- import { useEffect as oe, useMemo as d, useRef as f, useState as p } from "react";
17
- import { ArrowLeft as se, BookOpen as ce, Check as le, CheckCircle2 as m, ChevronLeft as ue, ChevronRight as de, FileText as h, HelpCircle as g, ListChecks as _, Loader2 as v, MousePointerClick as y, PenLine as b, PlayCircle as x, Radio as S, Trophy as C } from "lucide-react";
18
- import { useParams as w } from "react-router-dom";
19
- import { Fragment as fe, jsx as T, jsxs as E } from "react/jsx-runtime";
20
- import { Button as D, GlassCard as O } from "@burdenoff/fe-libs/ui";
17
+ import { useEffect as ie, useMemo as m, useRef as ae, useState as h } from "react";
18
+ import { ArrowLeft as oe, BookOpen as se, Check as g, CheckCircle2 as _, ChevronLeft as ce, ChevronRight as le, FileText as v, HelpCircle as y, ListChecks as ue, Loader2 as b, MousePointerClick as x, PenLine as S, PlayCircle as C, Radio as w, Trophy as de } from "lucide-react";
19
+ import { useParams as fe } from "react-router-dom";
20
+ import { Fragment as T, jsx as E, jsxs as D } from "react/jsx-runtime";
21
+ import { Button as O, GlassCard as k } from "@burdenoff/fe-libs/ui";
21
22
  //#region src/buildmyiq/pages/learning/LessonPlayerPage.tsx
22
- var k = {
23
- ARTICLE: h,
24
- VIDEO: x,
25
- QUIZ: g,
26
- ASSIGNMENT: b,
27
- INTERACTIVE: y,
28
- LIVE_SESSION: S
29
- }, A = (e) => [...e].sort((e, t) => e.position - t.position), j = () => {
30
- let { enrollmentId: g } = w(), { navigateTo: y } = e(), b = ne(g), x = b.data ?? null, S = x?.courseId, j = te(S), M = j.data ?? null, N = re(), P = u(), F = N.mutate, I = f(/* @__PURE__ */ new Set()), [L, R] = p(null), [z, B] = p(!1), V = d(() => {
31
- if (!M) return [];
32
- let e = /* @__PURE__ */ new Set(), t = A(M.modules ?? []), n = [];
23
+ var A = {
24
+ ARTICLE: v,
25
+ VIDEO: C,
26
+ QUIZ: y,
27
+ ASSIGNMENT: S,
28
+ INTERACTIVE: x,
29
+ LIVE_SESSION: w
30
+ }, j = (e) => [...e].sort((e, t) => e.position - t.position), M = () => {
31
+ let { enrollmentId: y } = fe(), { navigateTo: x } = e(), S = u(y), C = S.data ?? null, w = C?.courseId, M = te(w), N = M.data ?? null, P = d(), F = f(), I = P.mutate, L = ae(/* @__PURE__ */ new Set()), [R, z] = h(null), [B, V] = h(!1), H = m(() => {
32
+ if (!N) return [];
33
+ let e = /* @__PURE__ */ new Set(), t = j(N.modules ?? []), n = [];
33
34
  for (let r of t) {
34
- let t = A(r.lessons ?? []).filter((t) => e.has(t.id) ? !1 : (e.add(t.id), !0));
35
+ let t = j(r.lessons ?? []).filter((t) => e.has(t.id) ? !1 : (e.add(t.id), !0));
35
36
  t.length > 0 && n.push({
36
37
  id: r.id,
37
38
  title: r.title,
38
39
  lessons: t
39
40
  });
40
41
  }
41
- let r = A(M.lessons ?? []).filter((t) => e.has(t.id) ? !1 : (e.add(t.id), !0));
42
+ let r = j(N.lessons ?? []).filter((t) => e.has(t.id) ? !1 : (e.add(t.id), !0));
42
43
  return r.length > 0 && n.push({
43
44
  id: "__standalone",
44
45
  title: "Lessons",
45
46
  lessons: r
46
47
  }), n;
47
- }, [M]), H = d(() => V.flatMap((e) => e.lessons), [V]), U = d(() => {
48
+ }, [N]), U = m(() => H.flatMap((e) => e.lessons), [H]), W = m(() => {
48
49
  let e = /* @__PURE__ */ new Map();
49
- for (let t of x?.lessonProgress ?? []) e.set(t.lessonId, t.status);
50
+ for (let t of C?.lessonProgress ?? []) e.set(t.lessonId, t.status);
50
51
  return e;
51
- }, [x]), W = d(() => H.length === 0 ? null : L && H.some((e) => e.id === L) ? L : H[0].id, [H, L]);
52
- oe(() => {
53
- if (!g || !W) return;
54
- let e = U.get(W);
55
- e === "COMPLETED" || e === "IN_PROGRESS" || I.current.has(W) || (I.current.add(W), F({
56
- enrollmentId: g,
57
- lessonId: W,
52
+ }, [C]), G = m(() => U.length === 0 ? null : R && U.some((e) => e.id === R) ? R : U[0].id, [U, R]);
53
+ ie(() => {
54
+ if (!y || !G) return;
55
+ let e = W.get(G);
56
+ e === "COMPLETED" || e === "IN_PROGRESS" || L.current.has(G) || (L.current.add(G), I({
57
+ enrollmentId: y,
58
+ lessonId: G,
58
59
  status: "IN_PROGRESS"
59
60
  }));
60
61
  }, [
61
- g,
62
+ y,
63
+ G,
62
64
  W,
63
- U,
64
- F
65
+ I
65
66
  ]);
66
- let G = H.findIndex((e) => e.id === W), K = G >= 0 ? H[G] : null, q = (K ? U.get(K.id) : void 0) === "COMPLETED", J = H.filter((e) => U.get(e.id) === "COMPLETED").length, Y = H.length > 0 && J === H.length, pe = x?.status === "COMPLETED", X = (e) => {
67
- R(e), B(!1);
67
+ let K = U.findIndex((e) => e.id === G), q = K >= 0 ? U[K] : null, J = (q ? W.get(q.id) : void 0) === "COMPLETED", Y = U.filter((e) => W.get(e.id) === "COMPLETED").length, pe = U.length > 0 && Y === U.length, me = C?.status === "COMPLETED", X = (e) => {
68
+ z(e), V(!1);
68
69
  }, Z = (e) => {
69
- if (G < 0) return;
70
- let t = H[G + e];
70
+ if (K < 0) return;
71
+ let t = U[K + e];
71
72
  t && X(t.id);
72
- }, me = async () => {
73
- if (!g || !K) return;
74
- await N.mutateAsync({
75
- enrollmentId: g,
76
- lessonId: K.id,
73
+ }, he = async () => {
74
+ if (!y || !q) return;
75
+ await P.mutateAsync({
76
+ enrollmentId: y,
77
+ lessonId: q.id,
77
78
  status: "COMPLETED"
78
79
  });
79
- let e = H[G + 1];
80
- e && R(e.id);
81
- }, he = async () => {
82
- g && await P.mutateAsync({
83
- id: g,
80
+ let e = U[K + 1];
81
+ e && z(e.id);
82
+ }, ge = async () => {
83
+ y && await F.mutateAsync({
84
+ id: y,
84
85
  status: "COMPLETED"
85
86
  });
86
87
  };
87
- if (!g) return /* @__PURE__ */ T(n, {
88
+ if (!y) return /* @__PURE__ */ E(n, {
88
89
  title: "Lesson",
89
- children: /* @__PURE__ */ T(o, {
90
+ children: /* @__PURE__ */ E(o, {
90
91
  illustration: "empty-data",
91
92
  title: "No enrollment selected",
92
93
  description: "Open a course from My Learning to start the lesson player.",
93
- action: /* @__PURE__ */ T(D, {
94
- onClick: () => y("learning"),
94
+ action: /* @__PURE__ */ E(O, {
95
+ onClick: () => x("learning"),
95
96
  children: "Back to My Learning"
96
97
  })
97
98
  })
98
99
  });
99
- let ge = b.isLoading || !!S && j.isLoading, Q = /* @__PURE__ */ E(D, {
100
+ let _e = S.isLoading || !!w && M.isLoading, Q = /* @__PURE__ */ D(O, {
100
101
  variant: "ghost",
101
102
  size: "sm",
102
- onClick: () => y("learning"),
103
- children: [/* @__PURE__ */ T(se, { className: "mr-1 h-4 w-4" }), " My Learning"]
103
+ onClick: () => x("learning"),
104
+ children: [/* @__PURE__ */ E(oe, { className: "mr-1 h-4 w-4" }), " My Learning"]
104
105
  });
105
- if (ge) return /* @__PURE__ */ T(n, {
106
+ if (_e) return /* @__PURE__ */ E(n, {
106
107
  title: "Lesson",
107
108
  actions: Q,
108
- children: /* @__PURE__ */ T(c, { label: "Loading your lesson…" })
109
+ children: /* @__PURE__ */ E(c, { label: "Loading your lesson…" })
109
110
  });
110
- if (b.isError) return /* @__PURE__ */ T(n, {
111
+ if (S.isError) return /* @__PURE__ */ E(n, {
111
112
  title: "Lesson",
112
113
  actions: Q,
113
- children: /* @__PURE__ */ T(s, {
114
+ children: /* @__PURE__ */ E(s, {
114
115
  title: "Couldn't load this enrollment",
115
- message: b.error instanceof Error ? b.error.message : void 0,
116
- onRetry: () => b.refetch()
116
+ message: S.error instanceof Error ? S.error.message : void 0,
117
+ onRetry: () => S.refetch()
117
118
  })
118
119
  });
119
- if (!x) return /* @__PURE__ */ T(n, {
120
+ if (!C) return /* @__PURE__ */ E(n, {
120
121
  title: "Lesson",
121
122
  actions: Q,
122
- children: /* @__PURE__ */ T(o, {
123
+ children: /* @__PURE__ */ E(o, {
123
124
  illustration: "empty-data",
124
125
  title: "Enrollment not found",
125
126
  description: "This enrollment may have been removed or is not available to you.",
126
- action: /* @__PURE__ */ T(D, {
127
- onClick: () => y("learning"),
127
+ action: /* @__PURE__ */ E(O, {
128
+ onClick: () => x("learning"),
128
129
  children: "Back to My Learning"
129
130
  })
130
131
  })
131
132
  });
132
- if (j.isError) return /* @__PURE__ */ T(n, {
133
+ if (M.isError) return /* @__PURE__ */ E(n, {
133
134
  title: "Lesson",
134
135
  actions: Q,
135
- children: /* @__PURE__ */ T(s, {
136
+ children: /* @__PURE__ */ E(s, {
136
137
  title: "Couldn't load the course",
137
- message: j.error instanceof Error ? j.error.message : void 0,
138
- onRetry: () => j.refetch()
138
+ message: M.error instanceof Error ? M.error.message : void 0,
139
+ onRetry: () => M.refetch()
139
140
  })
140
141
  });
141
- let $ = Math.round(x.progressPct ?? 0), _e = K ? k[K.type] : h;
142
- return /* @__PURE__ */ T(n, {
143
- title: M?.title ?? "Lesson",
142
+ let $ = Math.round(C.progressPct ?? 0), ve = q ? A[q.type] : v;
143
+ return /* @__PURE__ */ E(n, {
144
+ title: N?.title ?? "Lesson",
144
145
  actions: Q,
145
- children: /* @__PURE__ */ E("div", {
146
+ children: /* @__PURE__ */ D("div", {
146
147
  className: "flex flex-col gap-5",
147
148
  children: [
148
- /* @__PURE__ */ E(O, {
149
+ /* @__PURE__ */ D(k, {
149
150
  treatment: "glass",
150
151
  glow: $ > 0,
151
152
  className: "flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:gap-5",
152
153
  children: [
153
- /* @__PURE__ */ T(ee, {
154
+ /* @__PURE__ */ E(ee, {
154
155
  value: $,
155
156
  size: 64,
156
157
  className: "shrink-0"
157
158
  }),
158
- /* @__PURE__ */ E("div", {
159
+ /* @__PURE__ */ D("div", {
159
160
  className: "min-w-0 flex-1",
160
- children: [/* @__PURE__ */ E("div", {
161
+ children: [/* @__PURE__ */ D("div", {
161
162
  className: "flex flex-wrap items-center gap-2",
162
- children: [/* @__PURE__ */ T(a, { status: x.status }), /* @__PURE__ */ E("span", {
163
+ children: [/* @__PURE__ */ E(a, { status: C.status }), /* @__PURE__ */ D("span", {
163
164
  className: "text-sm text-text-secondary tabular-nums",
164
165
  children: [
165
- J,
166
+ Y,
166
167
  " of ",
167
- H.length,
168
+ U.length,
168
169
  " lessons complete"
169
170
  ]
170
171
  })]
171
- }), /* @__PURE__ */ T(l, {
172
+ }), /* @__PURE__ */ E(l, {
172
173
  value: $,
173
174
  className: "mt-3"
174
175
  })]
175
176
  }),
176
- Y && !pe && /* @__PURE__ */ E(D, {
177
+ pe && !me && /* @__PURE__ */ D(O, {
177
178
  className: "shrink-0 max-sm:w-full",
178
- onClick: he,
179
- disabled: P.isPending,
180
- children: [P.isPending ? /* @__PURE__ */ T(v, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ T(C, { className: "mr-1 h-4 w-4" }), "Finish course"]
179
+ onClick: ge,
180
+ disabled: F.isPending,
181
+ children: [F.isPending ? /* @__PURE__ */ E(b, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ E(de, { className: "mr-1 h-4 w-4" }), "Finish course"]
181
182
  })
182
183
  ]
183
184
  }),
184
- /* @__PURE__ */ E(D, {
185
+ /* @__PURE__ */ D(O, {
185
186
  variant: "secondary",
186
187
  size: "sm",
187
188
  className: "w-full lg:hidden",
188
- onClick: () => B((e) => !e),
189
- children: [/* @__PURE__ */ T(_, { className: "mr-1 h-4 w-4" }), z ? "Hide lessons" : `Lessons (${J}/${H.length})`]
189
+ onClick: () => V((e) => !e),
190
+ children: [/* @__PURE__ */ E(ue, { className: "mr-1 h-4 w-4" }), B ? "Hide lessons" : `Lessons (${Y}/${U.length})`]
190
191
  }),
191
- /* @__PURE__ */ E("div", {
192
+ /* @__PURE__ */ D("div", {
192
193
  className: "flex flex-col gap-5 lg:flex-row",
193
- children: [/* @__PURE__ */ T("aside", {
194
- className: t("lg:block lg:w-80 lg:shrink-0", z ? "block" : "hidden"),
195
- children: /* @__PURE__ */ T(O, {
194
+ children: [/* @__PURE__ */ E("aside", {
195
+ className: t("lg:block lg:w-80 lg:shrink-0", B ? "block" : "hidden"),
196
+ children: /* @__PURE__ */ E(k, {
196
197
  treatment: "plain",
197
198
  className: "overflow-hidden p-0 lg:sticky lg:top-4",
198
- children: H.length === 0 ? /* @__PURE__ */ T("div", {
199
+ children: U.length === 0 ? /* @__PURE__ */ E("div", {
199
200
  className: "p-4 text-sm text-text-secondary",
200
201
  children: "This course has no lessons yet."
201
- }) : /* @__PURE__ */ E(fe, { children: [/* @__PURE__ */ T("nav", {
202
+ }) : /* @__PURE__ */ D(T, { children: [/* @__PURE__ */ E("nav", {
202
203
  className: "max-h-[68vh] overflow-auto p-3",
203
- children: V.map((e) => {
204
- let n = e.lessons.filter((e) => U.get(e.id) === "COMPLETED").length;
205
- return /* @__PURE__ */ E("div", {
204
+ children: H.map((e) => {
205
+ let n = e.lessons.filter((e) => W.get(e.id) === "COMPLETED").length;
206
+ return /* @__PURE__ */ D("div", {
206
207
  className: "mb-4 last:mb-0",
207
- children: [/* @__PURE__ */ E("div", {
208
+ children: [/* @__PURE__ */ D("div", {
208
209
  className: "mb-2 flex items-center justify-between gap-2 px-1",
209
- children: [/* @__PURE__ */ T("p", {
210
+ children: [/* @__PURE__ */ E("p", {
210
211
  className: "min-w-0 truncate text-sm font-semibold text-text-primary",
211
212
  children: e.title
212
- }), /* @__PURE__ */ E("span", {
213
+ }), /* @__PURE__ */ D("span", {
213
214
  className: "shrink-0 text-xs font-medium tabular-nums text-text-secondary",
214
215
  children: [
215
216
  n,
@@ -217,33 +218,33 @@ var k = {
217
218
  e.lessons.length
218
219
  ]
219
220
  })]
220
- }), /* @__PURE__ */ T("ul", {
221
+ }), /* @__PURE__ */ E("ul", {
221
222
  className: "flex flex-col",
222
223
  children: e.lessons.map((n, a) => {
223
- let o = U.get(n.id) === "COMPLETED", s = n.id === W, c = a === e.lessons.length - 1, l = k[n.type];
224
- return /* @__PURE__ */ E("li", {
224
+ let o = W.get(n.id) === "COMPLETED", s = n.id === G, c = a === e.lessons.length - 1, l = A[n.type];
225
+ return /* @__PURE__ */ D("li", {
225
226
  className: "flex gap-3",
226
- children: [/* @__PURE__ */ E("div", {
227
+ children: [/* @__PURE__ */ D("div", {
227
228
  className: "flex flex-col items-center pt-1.5",
228
- children: [o ? /* @__PURE__ */ T("span", {
229
+ children: [o ? /* @__PURE__ */ E("span", {
229
230
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--color-status-success-text)]",
230
- children: /* @__PURE__ */ T(le, { className: "h-3.5 w-3.5 text-status-success-bg-subtle" })
231
- }) : s ? /* @__PURE__ */ T("span", {
231
+ children: /* @__PURE__ */ E(g, { className: "h-3.5 w-3.5 text-status-success-bg-subtle" })
232
+ }) : s ? /* @__PURE__ */ E("span", {
232
233
  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)]",
233
- children: /* @__PURE__ */ T(l, { className: "h-3.5 w-3.5 text-[var(--color-accent-teal)]" })
234
- }) : /* @__PURE__ */ T("span", {
234
+ children: /* @__PURE__ */ E(l, { className: "h-3.5 w-3.5 text-[var(--color-accent-teal)]" })
235
+ }) : /* @__PURE__ */ E("span", {
235
236
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-border-seam bg-bg-sunken",
236
- children: /* @__PURE__ */ T(l, { className: "h-3.5 w-3.5 text-text-secondary opacity-70" })
237
- }), !c && /* @__PURE__ */ T("span", { className: t("my-1 w-px flex-1", o ? "bg-[color-mix(in_srgb,var(--color-status-success-text)_45%,transparent)]" : "bg-border-seam") })]
238
- }), /* @__PURE__ */ E("button", {
237
+ children: /* @__PURE__ */ E(l, { className: "h-3.5 w-3.5 text-text-secondary opacity-70" })
238
+ }), !c && /* @__PURE__ */ E("span", { className: t("my-1 w-px flex-1", o ? "bg-[color-mix(in_srgb,var(--color-status-success-text)_45%,transparent)]" : "bg-border-seam") })]
239
+ }), /* @__PURE__ */ D("button", {
239
240
  type: "button",
240
241
  onClick: () => X(n.id),
241
242
  className: t("mb-1 min-w-0 flex-1 rounded-lg px-3 py-2 text-left transition-colors", s ? "bg-[var(--color-accent-soft)]" : "hover:bg-bg-sunken"),
242
243
  "aria-current": s ? "true" : void 0,
243
- children: [/* @__PURE__ */ T("span", {
244
+ children: [/* @__PURE__ */ E("span", {
244
245
  className: t("block truncate text-sm", s ? "font-medium text-text-primary" : o ? "text-text-secondary" : "text-text-primary"),
245
246
  children: n.title
246
- }), /* @__PURE__ */ E("span", {
247
+ }), /* @__PURE__ */ D("span", {
247
248
  className: "mt-0.5 block truncate text-xs text-text-secondary",
248
249
  children: [i(n.type), n.durationMin > 0 ? ` · ${r(n.durationMin)}` : ""]
249
250
  })]
@@ -253,113 +254,114 @@ var k = {
253
254
  })]
254
255
  }, e.id);
255
256
  })
256
- }), S && /* @__PURE__ */ T("div", {
257
+ }), w && /* @__PURE__ */ E("div", {
257
258
  className: "border-t border-border-seam p-3",
258
- children: /* @__PURE__ */ E(D, {
259
+ children: /* @__PURE__ */ D(O, {
259
260
  variant: "outline",
260
261
  size: "sm",
261
262
  className: "w-full",
262
- onClick: () => y(`catalog/${S}`),
263
- children: [/* @__PURE__ */ T(ce, { className: "mr-1.5 h-4 w-4" }), " Course overview"]
263
+ onClick: () => x(`catalog/${w}`),
264
+ children: [/* @__PURE__ */ E(se, { className: "mr-1.5 h-4 w-4" }), " Course overview"]
264
265
  })
265
266
  })] })
266
267
  })
267
- }), /* @__PURE__ */ E("main", {
268
+ }), /* @__PURE__ */ D("main", {
268
269
  className: "min-w-0 flex-1",
269
- children: [K ? /* @__PURE__ */ E(O, {
270
+ children: [q ? /* @__PURE__ */ D(k, {
270
271
  treatment: "plain",
271
272
  className: "flex flex-col gap-4 p-5 sm:p-7",
272
273
  children: [
273
- /* @__PURE__ */ E("div", {
274
+ /* @__PURE__ */ D("div", {
274
275
  className: "flex flex-wrap items-center gap-2",
275
276
  children: [
276
- /* @__PURE__ */ E("span", {
277
+ /* @__PURE__ */ D("span", {
277
278
  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",
278
- children: [/* @__PURE__ */ T(_e, { className: "h-3.5 w-3.5 text-[var(--color-accent-teal)]" }), i(K.type)]
279
+ children: [/* @__PURE__ */ E(ve, { className: "h-3.5 w-3.5 text-[var(--color-accent-teal)]" }), i(q.type)]
279
280
  }),
280
- K.durationMin > 0 && /* @__PURE__ */ T("span", {
281
+ q.durationMin > 0 && /* @__PURE__ */ E("span", {
281
282
  className: "text-xs text-text-secondary",
282
- children: r(K.durationMin)
283
+ children: r(q.durationMin)
283
284
  }),
284
- q && /* @__PURE__ */ E("span", {
285
+ J && /* @__PURE__ */ D("span", {
285
286
  className: "inline-flex items-center gap-1 text-xs font-medium text-status-success-text",
286
- children: [/* @__PURE__ */ T(m, { className: "h-3.5 w-3.5" }), " Completed"]
287
+ children: [/* @__PURE__ */ E(_, { className: "h-3.5 w-3.5" }), " Completed"]
287
288
  }),
288
- /* @__PURE__ */ E("span", {
289
+ /* @__PURE__ */ E(re, { lesson: q }, q.id),
290
+ /* @__PURE__ */ D("span", {
289
291
  className: "ml-auto text-xs tabular-nums text-text-secondary",
290
292
  children: [
291
293
  "Lesson ",
292
- G + 1,
294
+ K + 1,
293
295
  " of ",
294
- H.length
296
+ U.length
295
297
  ]
296
298
  })
297
299
  ]
298
300
  }),
299
- /* @__PURE__ */ T("h2", {
301
+ /* @__PURE__ */ E("h2", {
300
302
  className: "text-2xl font-semibold tracking-tight text-text-primary",
301
- children: K.title
303
+ children: q.title
302
304
  }),
303
- K.media ? /* @__PURE__ */ E("div", {
305
+ q.media ? /* @__PURE__ */ D("div", {
304
306
  className: "rounded-xl border border-border-seam bg-bg-sunken p-3",
305
- children: [/* @__PURE__ */ T("p", {
307
+ children: [/* @__PURE__ */ E("p", {
306
308
  className: "mb-2 text-xs font-semibold uppercase tracking-wide text-text-secondary",
307
309
  children: "Resources"
308
- }), /* @__PURE__ */ T(ie, { fileId: K.media.id })]
310
+ }), /* @__PURE__ */ E(p, { fileId: q.media.id })]
309
311
  }) : null,
310
- K.content ? /* @__PURE__ */ T("p", {
312
+ q.content ? /* @__PURE__ */ E("p", {
311
313
  className: "max-w-prose whitespace-pre-wrap text-[15px] leading-7 text-text-primary",
312
- children: K.content
313
- }) : /* @__PURE__ */ E("p", {
314
+ children: q.content
315
+ }) : /* @__PURE__ */ D("p", {
314
316
  className: "text-sm text-text-secondary",
315
317
  children: [
316
318
  "This lesson has no written content",
317
- K.media ? " beyond the attached media" : "",
319
+ q.media ? " beyond the attached media" : "",
318
320
  "."
319
321
  ]
320
322
  }),
321
- /* @__PURE__ */ E("div", {
323
+ /* @__PURE__ */ D("div", {
322
324
  className: "mt-2 flex flex-col gap-3 border-t border-border-seam pt-5 sm:flex-row sm:items-center sm:justify-between",
323
325
  children: [
324
- /* @__PURE__ */ E(D, {
326
+ /* @__PURE__ */ D(O, {
325
327
  variant: "outline",
326
328
  size: "sm",
327
329
  className: "max-sm:w-full",
328
330
  onClick: () => Z(-1),
329
- disabled: G <= 0,
330
- children: [/* @__PURE__ */ T(ue, { className: "mr-1 h-4 w-4" }), " Previous"]
331
+ disabled: K <= 0,
332
+ children: [/* @__PURE__ */ E(ce, { className: "mr-1 h-4 w-4" }), " Previous"]
331
333
  }),
332
- q ? /* @__PURE__ */ E("span", {
334
+ J ? /* @__PURE__ */ D("span", {
333
335
  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",
334
- children: [/* @__PURE__ */ T(m, { className: "h-4 w-4" }), " Lesson complete"]
335
- }) : /* @__PURE__ */ E(D, {
336
+ children: [/* @__PURE__ */ E(_, { className: "h-4 w-4" }), " Lesson complete"]
337
+ }) : /* @__PURE__ */ D(O, {
336
338
  size: "lg",
337
339
  className: "max-sm:w-full",
338
- onClick: me,
339
- disabled: N.isPending,
340
- children: [N.isPending ? /* @__PURE__ */ T(v, { className: "mr-1.5 h-4 w-4 animate-spin" }) : /* @__PURE__ */ T(m, { className: "mr-1.5 h-4 w-4" }), "Mark complete"]
340
+ onClick: he,
341
+ disabled: P.isPending,
342
+ children: [P.isPending ? /* @__PURE__ */ E(b, { className: "mr-1.5 h-4 w-4 animate-spin" }) : /* @__PURE__ */ E(_, { className: "mr-1.5 h-4 w-4" }), "Mark complete"]
341
343
  }),
342
- /* @__PURE__ */ E(D, {
344
+ /* @__PURE__ */ D(O, {
343
345
  variant: "outline",
344
346
  size: "sm",
345
347
  className: "max-sm:w-full",
346
348
  onClick: () => Z(1),
347
- disabled: G >= H.length - 1,
348
- children: ["Next ", /* @__PURE__ */ T(de, { className: "ml-1 h-4 w-4" })]
349
+ disabled: K >= U.length - 1,
350
+ children: ["Next ", /* @__PURE__ */ E(le, { className: "ml-1 h-4 w-4" })]
349
351
  })
350
352
  ]
351
353
  })
352
354
  ]
353
- }) : /* @__PURE__ */ T(o, {
355
+ }) : /* @__PURE__ */ E(o, {
354
356
  illustration: "empty-data",
355
357
  title: "No lesson to show",
356
358
  description: "This course doesn't have any lessons yet. Check back once the author adds content."
357
- }), K && /* @__PURE__ */ T("div", {
359
+ }), q && /* @__PURE__ */ E("div", {
358
360
  className: "mt-5",
359
- children: /* @__PURE__ */ T(ae, {
360
- lessonId: K.id,
361
- lessonTitle: K.title
362
- }, K.id)
361
+ children: /* @__PURE__ */ E(ne, {
362
+ lessonId: q.id,
363
+ lessonTitle: q.title
364
+ }, q.id)
363
365
  })]
364
366
  })]
365
367
  })
@@ -368,6 +370,6 @@ var k = {
368
370
  });
369
371
  };
370
372
  //#endregion
371
- export { j as LessonPlayerPage };
373
+ export { M as LessonPlayerPage };
372
374
 
373
375
  //# sourceMappingURL=LessonPlayerPage.js.map
@@ -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 } from 'react-router-dom';\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 TutorPanel,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiEnrollment,\n useBuiCourse,\n useBuiRecordLessonProgress,\n useBuiTransitionEnrollment,\n} from '../../hooks';\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 const { navigateTo } = useBuildmyiq();\n\n const enrollmentQuery = useBuiEnrollment(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 = useBuiRecordLessonProgress();\n const transitionEnrollment = useBuiTransitionEnrollment();\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\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({ id: '__standalone', title: 'Lessons', lessons: standalone });\n }\n return result;\n }, [course]);\n\n // Flat, ordered lesson list for prev/next + completion checks.\n const orderedLessons = useMemo<BuiLesson[]>(() => groups.flatMap((g) => g.lessons), [groups]);\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 return orderedLessons[0].id;\n }, [orderedLessons, userSelectedId]);\n\n // Record IN_PROGRESS once when a not-yet-started lesson is opened.\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 recordMutate({\n enrollmentId,\n lessonId: selectedLessonId,\n status: 'IN_PROGRESS',\n });\n }, [enrollmentId, selectedLessonId, progressByLesson, recordMutate]);\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 };\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 await recordProgress.mutateAsync({\n enrollmentId,\n lessonId: selectedLesson.id,\n status: 'COMPLETED',\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 await transitionEnrollment.mutateAsync({ id: enrollmentId, status: 'COMPLETED' });\n };\n\n // ---- top-level states ----------------------------------------------------\n if (!enrollmentId) {\n return (\n <PageLayout title=\"Lesson\">\n <EmptyState\n illustration=\"empty-data\"\n title=\"No enrollment selected\"\n description=\"Open a course from My Learning to start the lesson player.\"\n action={<Button onClick={() => navigateTo('learning')}>Back to My Learning</Button>}\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=\"mr-1 h-4 w-4\" /> My Learning\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Lesson\" actions={backAction}>\n <LoadingView label=\"Loading your lesson…\" />\n </PageLayout>\n );\n }\n\n if (enrollmentQuery.isError) {\n return (\n <PageLayout title=\"Lesson\" actions={backAction}>\n <ErrorView\n title=\"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=\"Lesson\" actions={backAction}>\n <EmptyState\n illustration=\"empty-data\"\n title=\"Enrollment not found\"\n description=\"This enrollment may have been removed or is not available to you.\"\n action={<Button onClick={() => navigateTo('learning')}>Back to My Learning</Button>}\n />\n </PageLayout>\n );\n }\n\n if (courseQuery.isError) {\n return (\n <PageLayout title=\"Lesson\" actions={backAction}>\n <ErrorView\n title=\"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 title={course?.title ?? 'Lesson'} actions={backAction}>\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 {completedCount} of {orderedLessons.length} lessons complete\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={transitionEnrollment.isPending}\n >\n {transitionEnrollment.isPending ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"mr-1 h-4 w-4\" />\n )}\n Finish course\n </Button>\n )}\n </GlassCard>\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=\"mr-1 h-4 w-4\" />\n {showTree ? 'Hide lessons' : `Lessons (${completedCount}/${orderedLessons.length})`}\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 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-left 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=\"mr-1.5 h-4 w-4\" /> 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=\"No lesson to show\"\n description=\"This course doesn't have any lessons yet. Check back once the author adds content.\"\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\" /> Completed\n </span>\n )}\n <span className=\"ml-auto text-xs tabular-nums text-text-secondary\">\n Lesson {selectedIndex + 1} of {orderedLessons.length}\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 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 This lesson has no written content\n {selectedLesson.media ? ' beyond the attached media' : ''}.\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=\"mr-1 h-4 w-4\" /> 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\" /> 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=\"mr-1.5 h-4 w-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"mr-1.5 h-4 w-4\" />\n )}\n 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 Next <ChevronRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </div>\n </GlassCard>\n )}\n\n {selectedLesson && (\n <div className=\"mt-5\">\n {/* `key` remounts the panel per lesson so its local\n session/draft/error state resets cleanly on switch. */}\n <TutorPanel\n key={selectedLesson.id}\n lessonId={selectedLesson.id}\n lessonTitle={selectedLesson.title}\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 } from 'react-router-dom';\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 { useBuildmyiq } from '../../context';\nimport {\n useBuiEnrollment,\n useBuiCourse,\n useBuiRecordLessonProgress,\n useBuiTransitionEnrollment,\n} from '../../hooks';\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 const { navigateTo } = useBuildmyiq();\n\n const enrollmentQuery = useBuiEnrollment(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 = useBuiRecordLessonProgress();\n const transitionEnrollment = useBuiTransitionEnrollment();\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\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({ id: '__standalone', title: 'Lessons', lessons: standalone });\n }\n return result;\n }, [course]);\n\n // Flat, ordered lesson list for prev/next + completion checks.\n const orderedLessons = useMemo<BuiLesson[]>(() => groups.flatMap((g) => g.lessons), [groups]);\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 return orderedLessons[0].id;\n }, [orderedLessons, userSelectedId]);\n\n // Record IN_PROGRESS once when a not-yet-started lesson is opened.\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 recordMutate({\n enrollmentId,\n lessonId: selectedLessonId,\n status: 'IN_PROGRESS',\n });\n }, [enrollmentId, selectedLessonId, progressByLesson, recordMutate]);\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 };\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 await recordProgress.mutateAsync({\n enrollmentId,\n lessonId: selectedLesson.id,\n status: 'COMPLETED',\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 await transitionEnrollment.mutateAsync({ id: enrollmentId, status: 'COMPLETED' });\n };\n\n // ---- top-level states ----------------------------------------------------\n if (!enrollmentId) {\n return (\n <PageLayout title=\"Lesson\">\n <EmptyState\n illustration=\"empty-data\"\n title=\"No enrollment selected\"\n description=\"Open a course from My Learning to start the lesson player.\"\n action={<Button onClick={() => navigateTo('learning')}>Back to My Learning</Button>}\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=\"mr-1 h-4 w-4\" /> My Learning\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Lesson\" actions={backAction}>\n <LoadingView label=\"Loading your lesson…\" />\n </PageLayout>\n );\n }\n\n if (enrollmentQuery.isError) {\n return (\n <PageLayout title=\"Lesson\" actions={backAction}>\n <ErrorView\n title=\"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=\"Lesson\" actions={backAction}>\n <EmptyState\n illustration=\"empty-data\"\n title=\"Enrollment not found\"\n description=\"This enrollment may have been removed or is not available to you.\"\n action={<Button onClick={() => navigateTo('learning')}>Back to My Learning</Button>}\n />\n </PageLayout>\n );\n }\n\n if (courseQuery.isError) {\n return (\n <PageLayout title=\"Lesson\" actions={backAction}>\n <ErrorView\n title=\"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 title={course?.title ?? 'Lesson'} actions={backAction}>\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 {completedCount} of {orderedLessons.length} lessons complete\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={transitionEnrollment.isPending}\n >\n {transitionEnrollment.isPending ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"mr-1 h-4 w-4\" />\n )}\n Finish course\n </Button>\n )}\n </GlassCard>\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=\"mr-1 h-4 w-4\" />\n {showTree ? 'Hide lessons' : `Lessons (${completedCount}/${orderedLessons.length})`}\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 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-left 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=\"mr-1.5 h-4 w-4\" /> 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=\"No lesson to show\"\n description=\"This course doesn't have any lessons yet. Check back once the author adds content.\"\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\" /> Completed\n </span>\n )}\n <OfflineLessonControl key={selectedLesson.id} lesson={selectedLesson} />\n <span className=\"ml-auto text-xs tabular-nums text-text-secondary\">\n Lesson {selectedIndex + 1} of {orderedLessons.length}\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 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 This lesson has no written content\n {selectedLesson.media ? ' beyond the attached media' : ''}.\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=\"mr-1 h-4 w-4\" /> 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\" /> 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=\"mr-1.5 h-4 w-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"mr-1.5 h-4 w-4\" />\n )}\n 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 Next <ChevronRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </div>\n </GlassCard>\n )}\n\n {selectedLesson && (\n <div className=\"mt-5\">\n {/* `key` remounts the panel per lesson so its local\n session/draft/error state resets cleanly on switch. */}\n <TutorPanel\n key={selectedLesson.id}\n lessonId={selectedLesson.id}\n lessonTitle={selectedLesson.title}\n />\n </div>\n )}\n </main>\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAgEA,IAAM,IAAiD;CACrD,SAAS;CACT,OAAO;CACP,MAAM;CACN,YAAY;CACZ,aAAa;CACb,cAAc;AAChB,GAEM,KAAkD,MACtD,CAAC,GAAG,CAAK,EAAE,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,GAEtC,UAA6B;CACxC,IAAM,EAAE,oBAAiB,GAAoC,GACvD,EAAE,kBAAe,EAAa,GAE9B,IAAkB,EAAiB,CAAY,GAC/C,IAAa,EAAgB,QAAQ,MACrC,IAAW,GAAY,UACvB,IAAc,GAAa,CAAQ,GACnC,IAAS,EAAY,QAAQ,MAE7B,IAAiB,EAA2B,GAC5C,IAAuB,EAA2B,GAIlD,IAAe,EAAe,QAC9B,IAAkB,mBAAoB,IAAI,IAAI,CAAC,GAE/C,CAAC,GAAgB,KAAqB,EAAwB,IAAI,GAClE,CAAC,GAAU,KAAe,EAAS,EAAK,GAGxC,IAAS,QAA6B;EAC1C,IAAI,CAAC,GAAQ,OAAO,CAAC;EACrB,IAAM,oBAAO,IAAI,IAAY,GACvB,IAAuB,EAAe,EAAO,WAAW,CAAC,CAAC,GAC1D,IAAwB,CAAC;EAC/B,KAAK,IAAM,KAAK,GAAS;GACvB,IAAM,IAAU,EAAe,EAAE,WAAW,CAAC,CAAC,EAAE,QAAQ,MAClD,EAAK,IAAI,EAAE,EAAE,IAAU,MAC3B,EAAK,IAAI,EAAE,EAAE,GACN,GACR;GACD,AAAI,EAAQ,SAAS,KAAG,EAAO,KAAK;IAAE,IAAI,EAAE;IAAI,OAAO,EAAE;IAAO;GAAQ,CAAC;EAC3E;EACA,IAAM,IAAa,EAAe,EAAO,WAAW,CAAC,CAAC,EAAE,QAAQ,MAC1D,EAAK,IAAI,EAAE,EAAE,IAAU,MAC3B,EAAK,IAAI,EAAE,EAAE,GACN,GACR;EAID,OAHI,EAAW,SAAS,KACtB,EAAO,KAAK;GAAE,IAAI;GAAgB,OAAO;GAAW,SAAS;EAAW,CAAC,GAEpE;CACT,GAAG,CAAC,CAAM,CAAC,GAGL,IAAiB,QAA2B,EAAO,SAAS,MAAM,EAAE,OAAO,GAAG,CAAC,CAAM,CAAC,GAEtF,IAAmB,QAAc;EACrC,IAAM,oBAAM,IAAI,IAA+B;EAC/C,KAAK,IAAM,KAAK,GAAY,kBAAkB,CAAC,GAAG,EAAI,IAAI,EAAE,UAAU,EAAE,MAAM;EAC9E,OAAO;CACT,GAAG,CAAC,CAAU,CAAC,GAKT,IAAmB,QACnB,EAAe,WAAW,IAAU,OACpC,KAAkB,EAAe,MAAM,MAAM,EAAE,OAAO,CAAc,IAC/D,IAEF,EAAe,GAAG,IACxB,CAAC,GAAgB,CAAc,CAAC;CAGnC,SAAgB;EACd,IAAI,CAAC,KAAgB,CAAC,GAAkB;EACxC,IAAM,IAAS,EAAiB,IAAI,CAAgB;EAChD,MAAW,eAAe,MAAW,iBACrC,EAAgB,QAAQ,IAAI,CAAgB,MAChD,EAAgB,QAAQ,IAAI,CAAgB,GAC5C,EAAa;GACX;GACA,UAAU;GACV,QAAQ;EACV,CAAC;CACH,GAAG;EAAC;EAAc;EAAkB;EAAkB;CAAY,CAAC;CAEnE,IAAM,IAAgB,EAAe,WAAW,MAAM,EAAE,OAAO,CAAgB,GACzE,IAAiB,KAAiB,IAAI,EAAe,KAAiB,MAEtE,KADiB,IAAiB,EAAiB,IAAI,EAAe,EAAE,IAAI,KAAA,OACpC,aAExC,IAAiB,EAAe,QACnC,MAAM,EAAiB,IAAI,EAAE,EAAE,MAAM,WACxC,EAAE,QACI,KAAc,EAAe,SAAS,KAAK,MAAmB,EAAe,QAC7E,KAAkB,GAAY,WAAW,aAEzC,KAAgB,MAAqB;EAEzC,AADA,EAAkB,CAAQ,GAC1B,EAAY,EAAK;CACnB,GAEM,KAAQ,MAAmB;EAC/B,IAAI,IAAgB,GAAG;EACvB,IAAM,IAAO,EAAe,IAAgB;EAC5C,AAAI,KAAM,EAAa,EAAK,EAAE;CAChC,GAEM,KAAqB,YAAY;EACrC,IAAI,CAAC,KAAgB,CAAC,GAAgB;EACtC,MAAM,EAAe,YAAY;GAC/B;GACA,UAAU,EAAe;GACzB,QAAQ;EACV,CAAC;EAED,IAAM,IAAO,EAAe,IAAgB;EAC5C,AAAI,KAAM,EAAkB,EAAK,EAAE;CACrC,GAEM,KAAqB,YAAY;EAChC,KACL,MAAM,EAAqB,YAAY;GAAE,IAAI;GAAc,QAAQ;EAAY,CAAC;CAClF;CAGA,IAAI,CAAC,GACH,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QAAQ,kBAAC,GAAD;IAAQ,eAAe,EAAW,UAAU;cAAG;GAA2B,CAAA;EACnF,CAAA;CACS,CAAA;CAIhB,IAAM,KAAY,EAAgB,aAAc,EAAQ,KAAa,EAAY,WAE3E,IACJ,kBAAC,GAAD;EAAQ,SAAQ;EAAQ,MAAK;EAAK,eAAe,EAAW,UAAU;YAAtE,CACE,kBAAC,IAAD,EAAW,WAAU,eAAgB,CAAA,GAAC,cAChC;;CAGV,IAAI,IACF,OACE,kBAAC,GAAD;EAAY,OAAM;EAAS,SAAS;YAClC,kBAAC,GAAD,EAAa,OAAM,uBAAwB,CAAA;CACjC,CAAA;CAIhB,IAAI,EAAgB,SAClB,OACE,kBAAC,GAAD;EAAY,OAAM;EAAS,SAAS;YAClC,kBAAC,GAAD;GACE,OAAM;GACN,SACE,EAAgB,iBAAiB,QAAQ,EAAgB,MAAM,UAAU,KAAA;GAE3E,eAAe,EAAgB,QAAQ;EACxC,CAAA;CACS,CAAA;CAIhB,IAAI,CAAC,GACH,OACE,kBAAC,GAAD;EAAY,OAAM;EAAS,SAAS;YAClC,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QAAQ,kBAAC,GAAD;IAAQ,eAAe,EAAW,UAAU;cAAG;GAA2B,CAAA;EACnF,CAAA;CACS,CAAA;CAIhB,IAAI,EAAY,SACd,OACE,kBAAC,GAAD;EAAY,OAAM;EAAS,SAAS;YAClC,kBAAC,GAAD;GACE,OAAM;GACN,SAAS,EAAY,iBAAiB,QAAQ,EAAY,MAAM,UAAU,KAAA;GAC1E,eAAe,EAAY,QAAQ;EACpC,CAAA;CACS,CAAA;CAIhB,IAAM,IAAc,KAAK,MAAM,EAAW,eAAe,CAAC,GACpD,KAAe,IAAiB,EAAY,EAAe,QAAQ;CAEzE,OACE,kBAAC,GAAD;EAAY,OAAO,GAAQ,SAAS;EAAU,SAAS;YACrD,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,GAAD;KACE,WAAU;KACV,MAAM,IAAc;KACpB,WAAU;eAHZ;MAKE,kBAAC,IAAD;OAAc,OAAO;OAAa,MAAM;OAAI,WAAU;MAAY,CAAA;MAClE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAW,OAAS,CAAA,GACzC,kBAAC,QAAD;SAAM,WAAU;mBAAhB;UACG;UAAe;UAAK,EAAe;UAAO;SACvC;UACH;WACL,kBAAC,GAAD;QAAS,OAAO;QAAa,WAAU;OAAQ,CAAA,CAC5C;;MACJ,MAAe,CAAC,MACf,kBAAC,GAAD;OACE,WAAU;OACV,SAAS;OACT,UAAU,EAAqB;iBAHjC,CAKG,EAAqB,YACpB,kBAAC,GAAD,EAAS,WAAU,4BAA6B,CAAA,IAEhD,kBAAC,IAAD,EAAQ,WAAU,eAAgB,CAAA,GAClC,eAEI;;KAED;;IAGX,kBAAC,GAAD;KACE,SAAQ;KACR,MAAK;KACL,WAAU;KACV,eAAe,GAAa,MAAM,CAAC,CAAC;eAJtC,CAME,kBAAC,IAAD,EAAY,WAAU,eAAgB,CAAA,GACrC,IAAW,iBAAiB,YAAY,EAAe,GAAG,EAAe,OAAO,EAC3E;;IAER,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,SAAD;MAAO,WAAW,EAAG,gCAAgC,IAAW,UAAU,QAAQ;gBAChF,kBAAC,GAAD;OAAW,WAAU;OAAQ,WAAU;iBACpC,EAAe,WAAW,IACzB,kBAAC,OAAD;QAAK,WAAU;kBAAkC;OAE5C,CAAA,IAEL,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAO,KAAK,MAAU;SACrB,IAAM,IAAY,EAAM,QAAQ,QAC7B,MAAM,EAAiB,IAAI,EAAE,EAAE,MAAM,WACxC,EAAE;SACF,OACE,kBAAC,OAAD;UAAoB,WAAU;oBAA9B,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,KAAD;YAAG,WAAU;sBACV,EAAM;WACN,CAAA,GACH,kBAAC,QAAD;YAAM,WAAU;sBAAhB;aACG;aAAU;aAAI,EAAM,QAAQ;YACzB;aACH;cACL,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAM,QAAQ,KAAK,GAAQ,MAAM;YAEhC,IAAM,IADS,EAAiB,IAAI,EAAO,EAC9B,MAAW,aAClB,IAAS,EAAO,OAAO,GACvB,IAAS,MAAM,EAAM,QAAQ,SAAS,GACtC,IAAO,EAAY,EAAO;YAChC,OACE,kBAAC,MAAD;aAAoB,WAAU;uBAA9B,CAEE,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACG,IACC,kBAAC,QAAD;eAAM,WAAU;yBACd,kBAAC,GAAD,EAAO,WAAU,4CAA6C,CAAA;cAC1D,CAAA,IACJ,IACF,kBAAC,QAAD;eAAM,WAAU;yBACd,kBAAC,GAAD,EAAM,WAAU,8CAA+C,CAAA;cAC3D,CAAA,IAEN,kBAAC,QAAD;eAAM,WAAU;yBACd,kBAAC,GAAD,EAAM,WAAU,6CAA8C,CAAA;cAC1D,CAAA,GAEP,CAAC,KACA,kBAAC,QAAD,EACE,WAAW,EACT,oBACA,IACI,6EACA,gBACN,EACD,CAAA,CAEA;iBACL,kBAAC,UAAD;cACE,MAAK;cACL,eAAe,EAAa,EAAO,EAAE;cACrC,WAAW,EACT,wEACA,IACI,kCACA,oBACN;cACA,gBAAc,IAAS,SAAS,KAAA;wBATlC,CAWE,kBAAC,QAAD;eACE,WAAW,EACT,0BACA,IACI,kCACA,IACE,wBACA,mBACR;yBAEC,EAAO;cACJ,CAAA,GACN,kBAAC,QAAD;eAAM,WAAU;yBAAhB,CACG,EAAgB,EAAO,IAAI,GAC3B,EAAO,cAAc,IAClB,MAAM,EAAe,EAAO,WAAW,MACvC,EACA;gBACA;eACN;eAzDK,EAAO,EAyDZ;WAER,CAAC;UACC,CAAA,CACD;YA9EK,EAAM,EA8EX;QAET,CAAC;OACE,CAAA,GACJ,KACC,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD;SACE,SAAQ;SACR,MAAK;SACL,WAAU;SACV,eAAe,EAAW,WAAW,GAAU;mBAJjD,CAME,kBAAC,IAAD,EAAU,WAAU,iBAAkB,CAAA,GAAC,kBACjC;;OACL,CAAA,CAEP,EAAA,CAAA;MAEK,CAAA;KACN,CAAA,GAGP,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACI,IAOA,kBAAC,GAAD;OAAW,WAAU;OAAQ,WAAU;iBAAvC;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,IAAD,EAAc,WAAU,8CAA+C,CAAA,GACtE,EAAgB,EAAe,IAAI,CAChC;;UACL,EAAe,cAAc,KAC5B,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAe,EAAe,WAAW;UACtC,CAAA;UAEP,KACC,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,GAAD,EAAc,WAAU,cAAe,CAAA,GAAC,YACpC;;UAER,kBAAC,IAAD,EAA8C,QAAQ,EAAiB,GAA5C,EAAe,EAA6B;UACvE,kBAAC,QAAD;WAAM,WAAU;qBAAhB;YAAmE;YACzD,IAAgB;YAAE;YAAK,EAAe;WAC1C;;SACH;;QAEL,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAe;QACd,CAAA;QAEH,EAAe,QACd,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBAAyE;SAEnF,CAAA,GACH,kBAAC,GAAD,EAAU,QAAQ,EAAe,MAAM,GAAK,CAAA,CACzC;aACH;QAEH,EAAe,UACd,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAe;QACf,CAAA,IAEH,kBAAC,KAAD;SAAG,WAAU;mBAAb;UAA2C;UAExC,EAAe,QAAQ,+BAA+B;UAAG;SACzD;;QAIL,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,GAAD;WACE,SAAQ;WACR,MAAK;WACL,WAAU;WACV,eAAe,EAAK,EAAE;WACtB,UAAU,KAAiB;qBAL7B,CAOE,kBAAC,IAAD,EAAa,WAAU,eAAgB,CAAA,GAAC,WAClC;;UAEP,IACC,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA,GAAC,kBAChC;eAEN,kBAAC,GAAD;WACE,MAAK;WACL,WAAU;WACV,SAAS;WACT,UAAU,EAAe;qBAJ3B,CAMG,EAAe,YACd,kBAAC,GAAD,EAAS,WAAU,8BAA+B,CAAA,IAElD,kBAAC,GAAD,EAAc,WAAU,iBAAkB,CAAA,GAC1C,eAEI;;UAGV,kBAAC,GAAD;WACE,SAAQ;WACR,MAAK;WACL,WAAU;WACV,eAAe,EAAK,CAAC;WACrB,UAAU,KAAiB,EAAe,SAAS;qBALrD,CAMC,SACM,kBAAC,IAAD,EAAc,WAAU,eAAgB,CAAA,CACvC;;SACL;;OACI;WA9FX,kBAAC,GAAD;OACE,cAAa;OACb,OAAM;OACN,aAAY;MACb,CAAA,GA6FF,KACC,kBAAC,OAAD;OAAK,WAAU;iBAGb,kBAAC,IAAD;QAEE,UAAU,EAAe;QACzB,aAAa,EAAe;OAC7B,GAHM,EAAe,EAGrB;MACE,CAAA,CAEH;OACH;;GACF;;CACK,CAAA;AAEhB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-buildmyiq",
3
- "version": "2026.731.2",
3
+ "version": "2026.731.3",
4
4
  "description": "Burdenoff BuildMyIQ Microfrontend — education, learning & development",
5
5
  "type": "module",
6
6
  "files": [