@burdenoff/microfe-crewfoundry 2026.704.5 → 2026.704.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.
Files changed (155) hide show
  1. package/dist/crewfoundry/CrewFoundryRoutes.js +51 -43
  2. package/dist/crewfoundry/CrewFoundryRoutes.js.map +1 -1
  3. package/dist/crewfoundry/components/forms/formValues.js +5 -2
  4. package/dist/crewfoundry/components/forms/formValues.js.map +1 -1
  5. package/dist/crewfoundry/components/viz/AllocationBar.js +68 -0
  6. package/dist/crewfoundry/components/viz/AllocationBar.js.map +1 -0
  7. package/dist/crewfoundry/components/viz/AreaChart.js +102 -0
  8. package/dist/crewfoundry/components/viz/AreaChart.js.map +1 -0
  9. package/dist/crewfoundry/components/viz/AvatarStack.js +36 -0
  10. package/dist/crewfoundry/components/viz/AvatarStack.js.map +1 -0
  11. package/dist/crewfoundry/components/viz/BarChart.js +83 -0
  12. package/dist/crewfoundry/components/viz/BarChart.js.map +1 -0
  13. package/dist/crewfoundry/components/viz/CertificateCard.js +118 -0
  14. package/dist/crewfoundry/components/viz/CertificateCard.js.map +1 -0
  15. package/dist/crewfoundry/components/viz/DonutChart.js +109 -0
  16. package/dist/crewfoundry/components/viz/DonutChart.js.map +1 -0
  17. package/dist/crewfoundry/components/viz/HBarFunnel.js +54 -0
  18. package/dist/crewfoundry/components/viz/HBarFunnel.js.map +1 -0
  19. package/dist/crewfoundry/components/viz/Heatmap.js +91 -0
  20. package/dist/crewfoundry/components/viz/Heatmap.js.map +1 -0
  21. package/dist/crewfoundry/components/viz/KanbanBoard.js +50 -0
  22. package/dist/crewfoundry/components/viz/KanbanBoard.js.map +1 -0
  23. package/dist/crewfoundry/components/viz/LineChart.js +123 -0
  24. package/dist/crewfoundry/components/viz/LineChart.js.map +1 -0
  25. package/dist/crewfoundry/components/viz/OrgTree.js +83 -0
  26. package/dist/crewfoundry/components/viz/OrgTree.js.map +1 -0
  27. package/dist/crewfoundry/components/viz/PathSteps.js +68 -0
  28. package/dist/crewfoundry/components/viz/PathSteps.js.map +1 -0
  29. package/dist/crewfoundry/components/viz/ProgressRing.js +68 -0
  30. package/dist/crewfoundry/components/viz/ProgressRing.js.map +1 -0
  31. package/dist/crewfoundry/components/viz/RatingStars.js +51 -0
  32. package/dist/crewfoundry/components/viz/RatingStars.js.map +1 -0
  33. package/dist/crewfoundry/components/viz/SegmentedTabs.js +69 -0
  34. package/dist/crewfoundry/components/viz/SegmentedTabs.js.map +1 -0
  35. package/dist/crewfoundry/components/viz/SkillBar.js +3 -0
  36. package/dist/crewfoundry/components/viz/Sparkline.js +74 -0
  37. package/dist/crewfoundry/components/viz/Sparkline.js.map +1 -0
  38. package/dist/crewfoundry/components/viz/StatCard.js +60 -0
  39. package/dist/crewfoundry/components/viz/StatCard.js.map +1 -0
  40. package/dist/crewfoundry/components/viz/StepProgress.js +3 -0
  41. package/dist/crewfoundry/components/viz/Timeline.js +66 -0
  42. package/dist/crewfoundry/components/viz/Timeline.js.map +1 -0
  43. package/dist/crewfoundry/components/viz/index.js +23 -0
  44. package/dist/crewfoundry/components/viz/lib/Avatar.js +30 -0
  45. package/dist/crewfoundry/components/viz/lib/Avatar.js.map +1 -0
  46. package/dist/crewfoundry/components/viz/lib/CartesianFrame.js +119 -0
  47. package/dist/crewfoundry/components/viz/lib/CartesianFrame.js.map +1 -0
  48. package/dist/crewfoundry/components/viz/lib/ResponsiveSvg.js +35 -0
  49. package/dist/crewfoundry/components/viz/lib/ResponsiveSvg.js.map +1 -0
  50. package/dist/crewfoundry/components/viz/lib/format.js +21 -0
  51. package/dist/crewfoundry/components/viz/lib/format.js.map +1 -0
  52. package/dist/crewfoundry/components/viz/lib/geometry.js +69 -0
  53. package/dist/crewfoundry/components/viz/lib/geometry.js.map +1 -0
  54. package/dist/crewfoundry/components/viz/lib/hooks.js +39 -0
  55. package/dist/crewfoundry/components/viz/lib/hooks.js.map +1 -0
  56. package/dist/crewfoundry/components/viz/lib/palette.js +27 -0
  57. package/dist/crewfoundry/components/viz/lib/palette.js.map +1 -0
  58. package/dist/crewfoundry/lib/analytics.js +323 -0
  59. package/dist/crewfoundry/lib/analytics.js.map +1 -0
  60. package/dist/crewfoundry/lib/companyFinance.js +16 -0
  61. package/dist/crewfoundry/lib/companyFinance.js.map +1 -0
  62. package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js +226 -283
  63. package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  64. package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js +107 -120
  65. package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js.map +1 -1
  66. package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js +122 -162
  67. package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
  68. package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js +121 -158
  69. package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
  70. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js +316 -211
  71. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
  72. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +75 -27
  73. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +1 -1
  74. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +167 -165
  75. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
  76. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +168 -109
  77. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
  78. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js +401 -290
  79. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
  80. package/dist/crewfoundry/pages/HiringApplicationsPagePage.js +84 -75
  81. package/dist/crewfoundry/pages/HiringApplicationsPagePage.js.map +1 -1
  82. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +368 -174
  83. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
  84. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js +236 -201
  85. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
  86. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js +369 -196
  87. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
  88. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +252 -249
  89. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
  90. package/dist/crewfoundry/pages/HiringJobsPagePage.js +210 -110
  91. package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
  92. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +216 -178
  93. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
  94. package/dist/crewfoundry/pages/HrEmployeeProfilePagePage.js +385 -0
  95. package/dist/crewfoundry/pages/HrEmployeeProfilePagePage.js.map +1 -0
  96. package/dist/crewfoundry/pages/HrEmployeesPagePage.js +189 -130
  97. package/dist/crewfoundry/pages/HrEmployeesPagePage.js.map +1 -1
  98. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js +397 -241
  99. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
  100. package/dist/crewfoundry/pages/HrPayrollPagePage.js +262 -147
  101. package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
  102. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js +345 -167
  103. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
  104. package/dist/crewfoundry/pages/LearningCertificatesPagePage.js +98 -24
  105. package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +1 -1
  106. package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js +35 -31
  107. package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js.map +1 -1
  108. package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js +289 -108
  109. package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js.map +1 -1
  110. package/dist/crewfoundry/pages/LearningCoursesPagePage.js +221 -114
  111. package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
  112. package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js +136 -133
  113. package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js.map +1 -1
  114. package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js +148 -97
  115. package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +1 -1
  116. package/dist/crewfoundry/pages/LearningMyLearningPagePage.js +264 -157
  117. package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +1 -1
  118. package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.js.map +1 -1
  119. package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js +85 -93
  120. package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js.map +1 -1
  121. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js +211 -122
  122. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
  123. package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js +94 -54
  124. package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js.map +1 -1
  125. package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js +28 -25
  126. package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js.map +1 -1
  127. package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js +290 -138
  128. package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
  129. package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js +169 -79
  130. package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +1 -1
  131. package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js.map +1 -1
  132. package/dist/crewfoundry/pages/ProjectsProjectDetailsPagePage.js +65 -61
  133. package/dist/crewfoundry/pages/ProjectsProjectDetailsPagePage.js.map +1 -1
  134. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js +296 -125
  135. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
  136. package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js +187 -0
  137. package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js.map +1 -0
  138. package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js +142 -76
  139. package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js.map +1 -1
  140. package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js +93 -43
  141. package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js.map +1 -1
  142. package/dist/crewfoundry/pages/TeamsTeamSettingsPagePage.js.map +1 -1
  143. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js +250 -98
  144. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
  145. package/dist/crewfoundry/store/crewfoundryStore.js +1 -1
  146. package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
  147. package/dist/crewfoundry/utils/cn.js +10 -0
  148. package/dist/crewfoundry/utils/cn.js.map +1 -0
  149. package/dist/crewfoundry/utils/courseProgress.js +46 -0
  150. package/dist/crewfoundry/utils/courseProgress.js.map +1 -0
  151. package/dist/node_modules/clsx/dist/clsx.js +18 -0
  152. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  153. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
  154. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  155. package/package.json +1 -1
@@ -1,128 +1,309 @@
1
1
  import { useCrewFoundryStore as e } from "../store/crewfoundryStore.js";
2
2
  import { formatEnumLabel as t } from "../utils/formatEnumLabel.js";
3
- import { BookOpen as n, Clock as r, GraduationCap as i, Users as a } from "lucide-react";
4
- import { useNavigate as o, useSearchParams as s } from "react-router-dom";
5
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
- import { Badge as u, Button as d, Card as f, CardContent as p, GlassCard as m, GradientText as h, IllustratedEmptyState as g, PagePurpose as _ } from "@burdenoff/fe-libs/ui";
3
+ import { SegmentedTabs as n } from "../components/viz/SegmentedTabs.js";
4
+ import "../components/viz/index.js";
5
+ import { progressPercent as r, readCourseProgress as i, writeCourseProgress as a } from "../utils/courseProgress.js";
6
+ import { useMemo as o, useState as s } from "react";
7
+ import { Check as c, ChevronLeft as l, ChevronRight as u, CircleDashed as d, FileText as f, Play as p } from "lucide-react";
8
+ import { useNavigate as m, useSearchParams as h } from "react-router-dom";
9
+ import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
10
+ import { Badge as y, Button as b, Card as x, CardContent as S, GlassCard as C, IllustratedEmptyState as w, Spinner as T, Textarea as E } from "@burdenoff/fe-libs/ui";
11
+ import { useGlobalUi as D } from "@burdenoff/fe-libs/shared/providers/shell";
7
12
  //#region src/crewfoundry/pages/LearningCourseLearningPagePage.tsx
8
- function v() {
9
- let v = o(), [y] = s(), { courses: b } = e(), x = y.get("id"), S = x ? b.find((e) => e.id === x) : b[0];
10
- return S ? /* @__PURE__ */ l("div", {
11
- className: "max-w-3xl space-y-6 p-6",
12
- children: [
13
- /* @__PURE__ */ l("div", { children: [
14
- /* @__PURE__ */ l("div", {
15
- className: "flex items-center gap-2",
16
- children: [/* @__PURE__ */ c("h1", {
17
- className: "text-2xl font-semibold",
18
- children: /* @__PURE__ */ c(h, { children: S.title })
19
- }), /* @__PURE__ */ c(u, {
20
- variant: S.level === "beginner" ? "secondary" : S.level === "intermediate" ? "default" : "destructive",
21
- children: t(S.level)
22
- })]
13
+ var O = "crewfoundry:course-enrolled:v1", k = "crewfoundry:course-notes:v1";
14
+ function A(e) {
15
+ if (typeof window > "u") return !1;
16
+ try {
17
+ let t = window.localStorage.getItem(O), n = t ? JSON.parse(t) : [];
18
+ return Array.isArray(n) && n.includes(e);
19
+ } catch {
20
+ return !1;
21
+ }
22
+ }
23
+ function ee(e) {
24
+ if (!(typeof window > "u")) try {
25
+ let t = window.localStorage.getItem(O), n = t ? JSON.parse(t) : [], r = Array.isArray(n) ? n.filter((e) => typeof e == "string") : [];
26
+ r.includes(e) || (r.push(e), window.localStorage.setItem(O, JSON.stringify(r)));
27
+ } catch {}
28
+ }
29
+ function te(e) {
30
+ if (typeof window > "u") return "";
31
+ try {
32
+ let t = window.localStorage.getItem(k), n = t ? JSON.parse(t) : {};
33
+ if (n && typeof n == "object") {
34
+ let t = n[e];
35
+ return typeof t == "string" ? t : "";
36
+ }
37
+ return "";
38
+ } catch {
39
+ return "";
40
+ }
41
+ }
42
+ function ne(e, t) {
43
+ if (!(typeof window > "u")) try {
44
+ let n = window.localStorage.getItem(k), r = n ? JSON.parse(n) : {}, i = r && typeof r == "object" ? r : {};
45
+ i[e] = t, window.localStorage.setItem(k, JSON.stringify(i));
46
+ } catch {}
47
+ }
48
+ function j() {
49
+ let O = m(), [k] = h(), { courses: j, enrollCourse: M, initialized: N } = e(), { showToast: P } = D(), F = k.get("id"), I = F ? j.find((e) => e.id === F) : j[0], L = I ? Math.max(I.modules, 1) : 1, [R, z] = s(() => I ? i(I.id).completed : []), [B, V] = s(() => {
50
+ if (!I) return 0;
51
+ let e = new Set(i(I.id).completed);
52
+ for (let t = 0; t < Math.max(I.modules, 1); t += 1) if (!e.has(t)) return t;
53
+ return 0;
54
+ }), [H, U] = s("overview"), [W, G] = s(() => I ? te(I.id) : ""), K = o(() => new Set(R), [R]), q = r({ completed: R }, L);
55
+ if (!N && !I) return /* @__PURE__ */ _("div", {
56
+ className: "flex min-h-[60vh] items-center justify-center p-6",
57
+ children: /* @__PURE__ */ _(T, { size: "lg" })
58
+ });
59
+ if (!I) return /* @__PURE__ */ _("div", {
60
+ className: "p-6",
61
+ children: /* @__PURE__ */ _(w, {
62
+ illustration: "empty-search",
63
+ title: "Course not found",
64
+ description: "This course is no longer available.",
65
+ action: /* @__PURE__ */ _(b, {
66
+ variant: "outline",
67
+ onClick: () => O("../learning/courses-page"),
68
+ children: "Browse courses"
69
+ })
70
+ })
71
+ });
72
+ let J = (e) => {
73
+ z(e), a(I.id, e, L);
74
+ }, Y = async () => {
75
+ if (!A(I.id)) {
76
+ ee(I.id);
77
+ try {
78
+ await M(I.id);
79
+ } catch {}
80
+ }
81
+ }, X = async () => {
82
+ if (!K.has(B)) {
83
+ let e = R.length === 0, t = [...R, B];
84
+ J(t), e && await Y(), t.length >= L && P(`You completed ${I.title}`, "success");
85
+ }
86
+ B < L - 1 && (V(B + 1), U("overview"));
87
+ }, Z = (e) => {
88
+ V(e), U("overview");
89
+ }, re = () => B > 0 && Z(B - 1), ie = () => B < L - 1 && Z(B + 1), Q = K.has(B), $ = `Module ${B + 1}`;
90
+ return /* @__PURE__ */ v("div", {
91
+ className: "space-y-5 p-6",
92
+ children: [/* @__PURE__ */ v("div", {
93
+ className: "flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-text-secondary",
94
+ children: [
95
+ /* @__PURE__ */ _("button", {
96
+ type: "button",
97
+ onClick: () => O("../learning/courses-page"),
98
+ className: "hover:text-text-primary",
99
+ children: "Learning"
100
+ }),
101
+ /* @__PURE__ */ _(u, { className: "h-3.5 w-3.5" }),
102
+ /* @__PURE__ */ _("button", {
103
+ type: "button",
104
+ onClick: () => O(`../learning/course-details-page?id=${encodeURIComponent(I.id)}`),
105
+ className: "hover:text-text-primary",
106
+ children: "Courses"
23
107
  }),
24
- /* @__PURE__ */ l("p", {
25
- className: "text-sm text-text-secondary",
108
+ /* @__PURE__ */ _(u, { className: "h-3.5 w-3.5" }),
109
+ /* @__PURE__ */ _("span", {
110
+ className: "font-medium text-text-primary",
111
+ children: I.title
112
+ })
113
+ ]
114
+ }), /* @__PURE__ */ v("div", {
115
+ className: "grid grid-cols-1 gap-5 lg:grid-cols-[minmax(0,20rem)_minmax(0,1fr)]",
116
+ children: [/* @__PURE__ */ _(x, {
117
+ className: "lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:overflow-y-auto",
118
+ children: /* @__PURE__ */ v(S, {
119
+ className: "p-4",
26
120
  children: [
27
- "by ",
28
- S.instructor,
29
- " · ",
30
- S.category
121
+ /* @__PURE__ */ v("div", {
122
+ className: "mb-3 flex items-baseline justify-between gap-2",
123
+ children: [/* @__PURE__ */ _("h2", {
124
+ className: "text-sm font-semibold",
125
+ children: "Course content"
126
+ }), /* @__PURE__ */ v("span", {
127
+ className: "text-xs font-medium text-text-secondary tabular-nums",
128
+ children: [q, "% complete"]
129
+ })]
130
+ }),
131
+ /* @__PURE__ */ _("div", {
132
+ className: "mb-4 h-2 overflow-hidden rounded-full bg-bg-sunken",
133
+ children: /* @__PURE__ */ _("div", {
134
+ className: "h-full rounded-full bg-action-primary-bg transition-[width] duration-500",
135
+ style: { width: `${q}%` }
136
+ })
137
+ }),
138
+ /* @__PURE__ */ _("ol", {
139
+ className: "space-y-1",
140
+ children: Array.from({ length: L }, (e, t) => {
141
+ let n = K.has(t), r = t === B;
142
+ return /* @__PURE__ */ _("li", { children: /* @__PURE__ */ v("button", {
143
+ type: "button",
144
+ onClick: () => Z(t),
145
+ "aria-current": r ? "step" : void 0,
146
+ className: `flex w-full items-center gap-3 rounded-md px-2.5 py-2 text-left text-sm transition-colors ${r ? "bg-action-primary-bg/10 font-medium text-text-primary" : "text-text-secondary hover:bg-bg-sunken hover:text-text-primary"}`,
147
+ children: [/* @__PURE__ */ _("span", {
148
+ className: `flex h-5 w-5 shrink-0 items-center justify-center rounded-full border ${n ? "border-transparent bg-status-success-bg text-status-success-text" : r ? "border-action-primary-bg text-action-primary-bg" : "border-border-default text-transparent"}`,
149
+ children: _(n ? c : d, { className: "h-3 w-3" })
150
+ }), /* @__PURE__ */ _("span", {
151
+ className: "truncate",
152
+ children: `Module ${t + 1}`
153
+ })]
154
+ }) }, t);
155
+ })
156
+ })
31
157
  ]
32
- }),
33
- /* @__PURE__ */ c(_, {
34
- className: "mt-1",
35
- children: "Work through a course lesson-by-lesson — track your progress and complete modules to build the skill at hand."
36
158
  })
37
- ] }),
38
- /* @__PURE__ */ l("div", {
39
- className: "grid gap-4 sm:grid-cols-3",
40
- children: [
41
- /* @__PURE__ */ c(f, { children: /* @__PURE__ */ l(p, {
42
- className: "p-4 text-center",
43
- children: [
44
- /* @__PURE__ */ c(n, { className: "mx-auto mb-1 h-5 w-5 text-text-secondary" }),
45
- /* @__PURE__ */ c("p", {
46
- className: "text-lg font-bold",
47
- children: S.modules
48
- }),
49
- /* @__PURE__ */ c("p", {
50
- className: "text-xs text-text-secondary",
51
- children: "Modules"
159
+ }), /* @__PURE__ */ v("div", {
160
+ className: "space-y-5",
161
+ children: [/* @__PURE__ */ v(C, {
162
+ treatment: "plain",
163
+ className: "overflow-hidden",
164
+ children: [/* @__PURE__ */ v("button", {
165
+ type: "button",
166
+ onClick: () => void X(),
167
+ "aria-label": Q ? "Next module" : "Mark this module complete",
168
+ className: "group relative flex aspect-video w-full items-center justify-center outline-none focus-visible:shadow-[var(--shadow-focus)]",
169
+ style: { background: "linear-gradient(135deg, color-mix(in srgb, var(--color-accent-primary) 24%, var(--color-bg-sunken)), var(--color-bg-sunken))" },
170
+ children: [/* @__PURE__ */ _("span", {
171
+ className: "absolute left-4 top-4",
172
+ children: /* @__PURE__ */ _(y, {
173
+ variant: "secondary",
174
+ className: "text-[10px]",
175
+ children: $
52
176
  })
53
- ]
54
- }) }),
55
- /* @__PURE__ */ c(f, { children: /* @__PURE__ */ l(p, {
56
- className: "p-4 text-center",
177
+ }), /* @__PURE__ */ _("span", {
178
+ className: "flex h-16 w-16 items-center justify-center rounded-full bg-action-primary-bg text-action-primary-text shadow-elevation-2 transition-transform group-hover:scale-105",
179
+ children: Q ? /* @__PURE__ */ _(c, { className: "h-7 w-7" }) : /* @__PURE__ */ _(p, { className: "ml-1 h-7 w-7" })
180
+ })]
181
+ }), /* @__PURE__ */ v(S, {
182
+ className: "space-y-4 p-5",
57
183
  children: [
58
- /* @__PURE__ */ c(r, { className: "mx-auto mb-1 h-5 w-5 text-text-secondary" }),
59
- /* @__PURE__ */ c("p", {
60
- className: "text-lg font-bold",
61
- children: S.duration
184
+ /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ v("div", {
185
+ className: "flex flex-wrap items-center gap-2",
186
+ children: [
187
+ /* @__PURE__ */ _("h1", {
188
+ className: "text-xl font-semibold",
189
+ children: $
190
+ }),
191
+ /* @__PURE__ */ _(y, {
192
+ variant: I.level === "beginner" ? "secondary" : I.level === "intermediate" ? "default" : "destructive",
193
+ className: "text-[10px]",
194
+ children: t(I.level)
195
+ }),
196
+ Q && /* @__PURE__ */ v("span", {
197
+ className: "inline-flex items-center gap-1 text-xs font-medium text-status-success-text",
198
+ children: [/* @__PURE__ */ _(c, { className: "h-3.5 w-3.5" }), " Completed"]
199
+ })
200
+ ]
201
+ }), /* @__PURE__ */ v("p", {
202
+ className: "mt-1 text-sm text-text-secondary",
203
+ children: [
204
+ I.title,
205
+ " · by ",
206
+ I.instructor
207
+ ]
208
+ })] }),
209
+ /* @__PURE__ */ _(n, {
210
+ variant: "underline",
211
+ "aria-label": "Lesson detail",
212
+ value: H,
213
+ onChange: (e) => U(e),
214
+ tabs: [
215
+ {
216
+ id: "overview",
217
+ label: "Overview"
218
+ },
219
+ {
220
+ id: "resources",
221
+ label: "Resources"
222
+ },
223
+ {
224
+ id: "notes",
225
+ label: "Notes"
226
+ }
227
+ ]
62
228
  }),
63
- /* @__PURE__ */ c("p", {
64
- className: "text-xs text-text-secondary",
65
- children: "Duration"
66
- })
67
- ]
68
- }) }),
69
- /* @__PURE__ */ c(f, { children: /* @__PURE__ */ l(p, {
70
- className: "p-4 text-center",
71
- children: [
72
- /* @__PURE__ */ c(a, { className: "mx-auto mb-1 h-5 w-5 text-text-secondary" }),
73
- /* @__PURE__ */ c("p", {
74
- className: "text-lg font-bold",
75
- children: S.enrolled
229
+ H === "overview" && /* @__PURE__ */ v("div", {
230
+ className: "space-y-3",
231
+ children: [/* @__PURE__ */ _("p", {
232
+ className: "text-sm text-text-secondary",
233
+ children: I.description
234
+ }), I.skills.length > 0 && /* @__PURE__ */ _("div", {
235
+ className: "flex flex-wrap gap-1",
236
+ children: I.skills.map((e) => /* @__PURE__ */ _(y, {
237
+ variant: "outline",
238
+ className: "text-[10px]",
239
+ children: e
240
+ }, e))
241
+ })]
76
242
  }),
77
- /* @__PURE__ */ c("p", {
78
- className: "text-xs text-text-secondary",
79
- children: "Enrolled"
243
+ H === "resources" && /* @__PURE__ */ v("div", {
244
+ className: "rounded-lg border border-dashed border-border-default p-6 text-center",
245
+ children: [
246
+ /* @__PURE__ */ _(f, { className: "mx-auto mb-2 h-6 w-6 text-text-muted" }),
247
+ /* @__PURE__ */ _("p", {
248
+ className: "text-sm font-medium",
249
+ children: "No downloadable resources yet"
250
+ }),
251
+ /* @__PURE__ */ _("p", {
252
+ className: "text-xs text-text-secondary",
253
+ children: "Slides and attachments for this module will appear here when the instructor adds them."
254
+ })
255
+ ]
256
+ }),
257
+ H === "notes" && /* @__PURE__ */ v("div", {
258
+ className: "space-y-2",
259
+ children: [/* @__PURE__ */ _(E, {
260
+ value: W,
261
+ onChange: (e) => {
262
+ G(e.target.value), ne(I.id, e.target.value);
263
+ },
264
+ placeholder: "Jot down your notes for this course. They are saved to this device automatically.",
265
+ rows: 5
266
+ }), /* @__PURE__ */ _("p", {
267
+ className: "text-xs text-text-muted",
268
+ children: "Notes are saved automatically on this device."
269
+ })]
80
270
  })
81
271
  ]
82
- }) })
83
- ]
84
- }),
85
- /* @__PURE__ */ c(m, {
86
- treatment: "glass",
87
- glow: !0,
88
- className: "border-border-seam",
89
- children: /* @__PURE__ */ c(p, {
90
- className: "p-6",
91
- children: /* @__PURE__ */ c(g, {
92
- illustration: "empty-generic",
93
- title: "Interactive lessons are coming soon",
94
- description: "The lesson player and per-module progress tracking are being built. In the meantime you can enroll from the course page and review what this course covers.",
95
- action: /* @__PURE__ */ l("div", {
96
- className: "flex flex-wrap justify-center gap-2",
97
- children: [/* @__PURE__ */ l(d, {
98
- variant: "outline",
99
- onClick: () => v(`../../learning/course-details-page?id=${encodeURIComponent(S.id)}`),
100
- children: [/* @__PURE__ */ c(i, { className: "mr-1 h-4 w-4" }), "Course details"]
101
- }), /* @__PURE__ */ c(d, {
272
+ })]
273
+ }), /* @__PURE__ */ _(x, { children: /* @__PURE__ */ v(S, {
274
+ className: "space-y-4 p-4",
275
+ children: [/* @__PURE__ */ _("div", {
276
+ className: "h-2 overflow-hidden rounded-full bg-bg-sunken",
277
+ children: /* @__PURE__ */ _("div", {
278
+ className: "h-full rounded-full bg-status-success-bg transition-[width] duration-500",
279
+ style: { width: `${q}%` }
280
+ })
281
+ }), /* @__PURE__ */ v("div", {
282
+ className: "flex items-center justify-between gap-2",
283
+ children: [/* @__PURE__ */ v(b, {
284
+ variant: "outline",
285
+ onClick: re,
286
+ disabled: B === 0,
287
+ children: [/* @__PURE__ */ _(l, { className: "mr-1 h-4 w-4" }), "Previous"]
288
+ }), /* @__PURE__ */ v("div", {
289
+ className: "flex items-center gap-2",
290
+ children: [/* @__PURE__ */ _(b, {
291
+ onClick: () => void X(),
292
+ children: Q ? B < L - 1 ? "Next module" : /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _(c, { className: "mr-1 h-4 w-4" }), " Completed"] }) : /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _(c, { className: "mr-1 h-4 w-4" }), " Mark Complete"] })
293
+ }), /* @__PURE__ */ v(b, {
102
294
  variant: "outline",
103
- onClick: () => v("../learning/my-learning-page"),
104
- children: "My learning"
295
+ onClick: ie,
296
+ disabled: B >= L - 1,
297
+ children: ["Next", /* @__PURE__ */ _(u, { className: "ml-1 h-4 w-4" })]
105
298
  })]
106
- })
107
- })
108
- })
109
- })
110
- ]
111
- }) : /* @__PURE__ */ c("div", {
112
- className: "p-6",
113
- children: /* @__PURE__ */ c(g, {
114
- illustration: "empty-search",
115
- title: "Course not found",
116
- description: "This course is no longer available.",
117
- action: /* @__PURE__ */ c(d, {
118
- variant: "outline",
119
- onClick: () => v("../learning/courses-page"),
120
- children: "Browse courses"
121
- })
122
- })
299
+ })]
300
+ })]
301
+ }) })]
302
+ })]
303
+ })]
123
304
  });
124
305
  }
125
306
  //#endregion
126
- export { v as LearningCourseLearningPagePage };
307
+ export { j as LearningCourseLearningPagePage };
127
308
 
128
309
  //# sourceMappingURL=LearningCourseLearningPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LearningCourseLearningPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningCourseLearningPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { BookOpen, Clock, GraduationCap, Users } from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router-dom';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\n\nexport function LearningCourseLearningPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { courses } = useCrewFoundryStore();\n\n const courseId = searchParams.get('id');\n const course = courseId ? courses.find((c) => c.id === courseId) : courses[0];\n\n if (!course) {\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"Course not found\"\n description=\"This course is no longer available.\"\n action={\n <Button variant=\"outline\" onClick={() => navigate('../learning/courses-page')}>\n Browse courses\n </Button>\n }\n />\n </div>\n );\n }\n\n return (\n <div className=\"max-w-3xl space-y-6 p-6\">\n <div>\n <div className=\"flex items-center gap-2\">\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{course.title}</GradientText>\n </h1>\n <Badge\n variant={\n course.level === 'beginner'\n ? 'secondary'\n : course.level === 'intermediate'\n ? 'default'\n : 'destructive'\n }\n >\n {formatEnumLabel(course.level)}\n </Badge>\n </div>\n <p className=\"text-sm text-text-secondary\">\n by {course.instructor} · {course.category}\n </p>\n <PagePurpose className=\"mt-1\">\n Work through a course lesson-by-lesson — track your progress and complete modules to build\n the skill at hand.\n </PagePurpose>\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-3\">\n <Card>\n <CardContent className=\"p-4 text-center\">\n <BookOpen className=\"mx-auto mb-1 h-5 w-5 text-text-secondary\" />\n <p className=\"text-lg font-bold\">{course.modules}</p>\n <p className=\"text-xs text-text-secondary\">Modules</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Clock className=\"mx-auto mb-1 h-5 w-5 text-text-secondary\" />\n <p className=\"text-lg font-bold\">{course.duration}</p>\n <p className=\"text-xs text-text-secondary\">Duration</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Users className=\"mx-auto mb-1 h-5 w-5 text-text-secondary\" />\n <p className=\"text-lg font-bold\">{course.enrolled}</p>\n <p className=\"text-xs text-text-secondary\">Enrolled</p>\n </CardContent>\n </Card>\n </div>\n\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardContent className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title=\"Interactive lessons are coming soon\"\n description=\"The lesson player and per-module progress tracking are being built. In the meantime you can enroll from the course page and review what this course covers.\"\n action={\n <div className=\"flex flex-wrap justify-center gap-2\">\n <Button\n variant=\"outline\"\n onClick={() =>\n navigate(\n `../../learning/course-details-page?id=${encodeURIComponent(course.id)}`\n )\n }\n >\n <GraduationCap className=\"mr-1 h-4 w-4\" />\n Course details\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => navigate('../learning/my-learning-page')}\n >\n My learning\n </Button>\n </div>\n }\n />\n </CardContent>\n </GlassCard>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAeA,SAAgB,IAAiC;CAC/C,IAAM,IAAW,GAAa,EACxB,CAAC,KAAgB,GAAiB,EAClC,EAAE,eAAY,GAAqB,EAEnC,IAAW,EAAa,IAAI,KAAK,EACjC,IAAS,IAAW,EAAQ,MAAM,MAAM,EAAE,OAAO,EAAS,GAAG,EAAQ;AAmB3E,QAjBK,IAkBH,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBACZ,kBAAC,GAAD,EAAA,UAAe,EAAO,OAAqB,CAAA;MACxC,CAAA,EACL,kBAAC,GAAD;MACE,SACE,EAAO,UAAU,aACb,cACA,EAAO,UAAU,iBACf,YACA;gBAGP,EAAgB,EAAO,MAAM;MACxB,CAAA,CACJ;;IACN,kBAAC,KAAD;KAAG,WAAU;eAAb;MAA2C;MACrC,EAAO;MAAW;MAAI,EAAO;MAC/B;;IACJ,kBAAC,GAAD;KAAa,WAAU;eAAO;KAGhB,CAAA;IACV,EAAA,CAAA;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,GAAD,EAAU,WAAU,4CAA6C,CAAA;OACjE,kBAAC,KAAD;QAAG,WAAU;kBAAqB,EAAO;QAAY,CAAA;OACrD,kBAAC,KAAD;QAAG,WAAU;kBAA8B;QAAW,CAAA;OAC1C;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,GAAD,EAAO,WAAU,4CAA6C,CAAA;OAC9D,kBAAC,KAAD;QAAG,WAAU;kBAAqB,EAAO;QAAa,CAAA;OACtD,kBAAC,KAAD;QAAG,WAAU;kBAA8B;QAAY,CAAA;OAC3C;SACT,CAAA;KACP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,GAAD,EAAO,WAAU,4CAA6C,CAAA;OAC9D,kBAAC,KAAD;QAAG,WAAU;kBAAqB,EAAO;QAAa,CAAA;OACtD,kBAAC,KAAD;QAAG,WAAU;kBAA8B;QAAY,CAAA;OAC3C;SACT,CAAA;KACH;;GAEN,kBAAC,GAAD;IAAW,WAAU;IAAQ,MAAA;IAAK,WAAU;cAC1C,kBAAC,GAAD;KAAa,WAAU;eACrB,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,QACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,SAAQ;QACR,eACE,EACE,yCAAyC,mBAAmB,EAAO,GAAG,GACvE;kBALL,CAQE,kBAAC,GAAD,EAAe,WAAU,gBAAiB,CAAA,EAAA,iBAEnC;WACT,kBAAC,GAAD;QACE,SAAQ;QACR,eAAe,EAAS,+BAA+B;kBACxD;QAEQ,CAAA,CACL;;MAER,CAAA;KACU,CAAA;IACJ,CAAA;GACR;MAjGJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,GAAD;IAAQ,SAAQ;IAAU,eAAe,EAAS,2BAA2B;cAAE;IAEtE,CAAA;GAEX,CAAA;EACE,CAAA"}
1
+ {"version":3,"file":"LearningCourseLearningPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningCourseLearningPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n GlassCard,\n IllustratedEmptyState,\n Spinner,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n Check,\n ChevronLeft,\n ChevronRight,\n CircleDashed,\n FileText,\n Play,\n} from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router-dom';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\nimport { SegmentedTabs } from '../components/viz';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { readCourseProgress, writeCourseProgress, progressPercent } from '../utils/courseProgress';\n\nconst ENROLLED_KEY = 'crewfoundry:course-enrolled:v1';\nconst NOTES_KEY = 'crewfoundry:course-notes:v1';\n\nfunction hasEnrolledLocally(courseId: string): boolean {\n if (typeof window === 'undefined') return false;\n try {\n const raw = window.localStorage.getItem(ENROLLED_KEY);\n const ids: unknown = raw ? JSON.parse(raw) : [];\n return Array.isArray(ids) && ids.includes(courseId);\n } catch {\n return false;\n }\n}\n\nfunction markEnrolledLocally(courseId: string): void {\n if (typeof window === 'undefined') return;\n try {\n const raw = window.localStorage.getItem(ENROLLED_KEY);\n const ids: unknown = raw ? JSON.parse(raw) : [];\n const list = Array.isArray(ids) ? ids.filter((x): x is string => typeof x === 'string') : [];\n if (!list.includes(courseId)) {\n list.push(courseId);\n window.localStorage.setItem(ENROLLED_KEY, JSON.stringify(list));\n }\n } catch {\n /* best-effort */\n }\n}\n\nfunction readNotes(courseId: string): string {\n if (typeof window === 'undefined') return '';\n try {\n const raw = window.localStorage.getItem(NOTES_KEY);\n const map: unknown = raw ? JSON.parse(raw) : {};\n if (map && typeof map === 'object') {\n const value = (map as Record<string, unknown>)[courseId];\n return typeof value === 'string' ? value : '';\n }\n return '';\n } catch {\n return '';\n }\n}\n\nfunction writeNotes(courseId: string, notes: string): void {\n if (typeof window === 'undefined') return;\n try {\n const raw = window.localStorage.getItem(NOTES_KEY);\n const parsed: unknown = raw ? JSON.parse(raw) : {};\n const map: Record<string, string> =\n parsed && typeof parsed === 'object' ? (parsed as Record<string, string>) : {};\n map[courseId] = notes;\n window.localStorage.setItem(NOTES_KEY, JSON.stringify(map));\n } catch {\n /* best-effort */\n }\n}\n\ntype PlayerTab = 'overview' | 'resources' | 'notes';\n\nexport function LearningCourseLearningPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { courses, enrollCourse, initialized } = useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n\n const courseId = searchParams.get('id');\n const course = courseId ? courses.find((c) => c.id === courseId) : courses[0];\n\n const totalModules = course ? Math.max(course.modules, 1) : 1;\n\n const [completed, setCompleted] = useState<number[]>(() =>\n course ? readCourseProgress(course.id).completed : []\n );\n const [current, setCurrent] = useState<number>(() => {\n if (!course) return 0;\n const done = new Set(readCourseProgress(course.id).completed);\n for (let i = 0; i < Math.max(course.modules, 1); i += 1) {\n if (!done.has(i)) return i;\n }\n return 0;\n });\n const [tab, setTab] = useState<PlayerTab>('overview');\n const [notes, setNotes] = useState<string>(() => (course ? readNotes(course.id) : ''));\n\n const completedSet = useMemo(() => new Set(completed), [completed]);\n const pct = progressPercent({ completed }, totalModules);\n\n if (!initialized && !course) {\n return (\n <div className=\"flex min-h-[60vh] items-center justify-center p-6\">\n <Spinner size=\"lg\" />\n </div>\n );\n }\n\n if (!course) {\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"Course not found\"\n description=\"This course is no longer available.\"\n action={\n <Button variant=\"outline\" onClick={() => navigate('../learning/courses-page')}>\n Browse courses\n </Button>\n }\n />\n </div>\n );\n }\n\n const persist = (next: number[]) => {\n setCompleted(next);\n writeCourseProgress(course.id, next, totalModules);\n };\n\n const ensureEnrolled = async () => {\n if (hasEnrolledLocally(course.id)) return;\n markEnrolledLocally(course.id);\n try {\n await enrollCourse(course.id);\n } catch {\n /* enrolment is a best-effort side effect of starting the course */\n }\n };\n\n const handleMarkComplete = async () => {\n if (!completedSet.has(current)) {\n const wasEmpty = completed.length === 0;\n const next = [...completed, current];\n persist(next);\n if (wasEmpty) {\n await ensureEnrolled();\n }\n if (next.length >= totalModules) {\n showToast(`You completed ${course.title}`, 'success');\n }\n }\n if (current < totalModules - 1) {\n setCurrent(current + 1);\n setTab('overview');\n }\n };\n\n const goTo = (index: number) => {\n setCurrent(index);\n setTab('overview');\n };\n\n const goPrev = () => current > 0 && goTo(current - 1);\n const goNext = () => current < totalModules - 1 && goTo(current + 1);\n\n const currentComplete = completedSet.has(current);\n const moduleTitle = `Module ${current + 1}`;\n\n return (\n <div className=\"space-y-5 p-6\">\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-text-secondary\">\n <button\n type=\"button\"\n onClick={() => navigate('../learning/courses-page')}\n className=\"hover:text-text-primary\"\n >\n Learning\n </button>\n <ChevronRight className=\"h-3.5 w-3.5\" />\n <button\n type=\"button\"\n onClick={() => navigate(`../learning/course-details-page?id=${encodeURIComponent(course.id)}`)}\n className=\"hover:text-text-primary\"\n >\n Courses\n </button>\n <ChevronRight className=\"h-3.5 w-3.5\" />\n <span className=\"font-medium text-text-primary\">{course.title}</span>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 lg:grid-cols-[minmax(0,20rem)_minmax(0,1fr)]\">\n {/* Course content sidebar */}\n <Card className=\"lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:overflow-y-auto\">\n <CardContent className=\"p-4\">\n <div className=\"mb-3 flex items-baseline justify-between gap-2\">\n <h2 className=\"text-sm font-semibold\">Course content</h2>\n <span className=\"text-xs font-medium text-text-secondary tabular-nums\">\n {pct}% complete\n </span>\n </div>\n <div className=\"mb-4 h-2 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg transition-[width] duration-500\"\n style={{ width: `${pct}%` }}\n />\n </div>\n <ol className=\"space-y-1\">\n {Array.from({ length: totalModules }, (_, i) => {\n const done = completedSet.has(i);\n const isCurrent = i === current;\n return (\n <li key={i}>\n <button\n type=\"button\"\n onClick={() => goTo(i)}\n aria-current={isCurrent ? 'step' : undefined}\n className={`flex w-full items-center gap-3 rounded-md px-2.5 py-2 text-left text-sm transition-colors ${\n isCurrent\n ? 'bg-action-primary-bg/10 font-medium text-text-primary'\n : 'text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n }`}\n >\n <span\n className={`flex h-5 w-5 shrink-0 items-center justify-center rounded-full border ${\n done\n ? 'border-transparent bg-status-success-bg text-status-success-text'\n : isCurrent\n ? 'border-action-primary-bg text-action-primary-bg'\n : 'border-border-default text-transparent'\n }`}\n >\n {done ? (\n <Check className=\"h-3 w-3\" />\n ) : (\n <CircleDashed className=\"h-3 w-3\" />\n )}\n </span>\n <span className=\"truncate\">{`Module ${i + 1}`}</span>\n </button>\n </li>\n );\n })}\n </ol>\n </CardContent>\n </Card>\n\n {/* Main pane */}\n <div className=\"space-y-5\">\n <GlassCard treatment=\"plain\" className=\"overflow-hidden\">\n <button\n type=\"button\"\n onClick={() => void handleMarkComplete()}\n aria-label={currentComplete ? 'Next module' : 'Mark this module complete'}\n className=\"group relative flex aspect-video w-full items-center justify-center outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n style={{\n background:\n 'linear-gradient(135deg, color-mix(in srgb, var(--color-accent-primary) 24%, var(--color-bg-sunken)), var(--color-bg-sunken))',\n }}\n >\n <span className=\"absolute left-4 top-4\">\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {moduleTitle}\n </Badge>\n </span>\n <span className=\"flex h-16 w-16 items-center justify-center rounded-full bg-action-primary-bg text-action-primary-text shadow-elevation-2 transition-transform group-hover:scale-105\">\n {currentComplete ? (\n <Check className=\"h-7 w-7\" />\n ) : (\n <Play className=\"ml-1 h-7 w-7\" />\n )}\n </span>\n </button>\n <CardContent className=\"space-y-4 p-5\">\n <div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <h1 className=\"text-xl font-semibold\">{moduleTitle}</h1>\n <Badge\n variant={\n course.level === 'beginner'\n ? 'secondary'\n : course.level === 'intermediate'\n ? 'default'\n : 'destructive'\n }\n className=\"text-[10px]\"\n >\n {formatEnumLabel(course.level)}\n </Badge>\n {currentComplete && (\n <span className=\"inline-flex items-center gap-1 text-xs font-medium text-status-success-text\">\n <Check className=\"h-3.5 w-3.5\" /> Completed\n </span>\n )}\n </div>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {course.title} · by {course.instructor}\n </p>\n </div>\n\n <SegmentedTabs\n variant=\"underline\"\n aria-label=\"Lesson detail\"\n value={tab}\n onChange={(id) => setTab(id as PlayerTab)}\n tabs={[\n { id: 'overview', label: 'Overview' },\n { id: 'resources', label: 'Resources' },\n { id: 'notes', label: 'Notes' },\n ]}\n />\n\n {tab === 'overview' && (\n <div className=\"space-y-3\">\n <p className=\"text-sm text-text-secondary\">{course.description}</p>\n {course.skills.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {course.skills.map((s) => (\n <Badge key={s} variant=\"outline\" className=\"text-[10px]\">\n {s}\n </Badge>\n ))}\n </div>\n )}\n </div>\n )}\n\n {tab === 'resources' && (\n <div className=\"rounded-lg border border-dashed border-border-default p-6 text-center\">\n <FileText className=\"mx-auto mb-2 h-6 w-6 text-text-muted\" />\n <p className=\"text-sm font-medium\">No downloadable resources yet</p>\n <p className=\"text-xs text-text-secondary\">\n Slides and attachments for this module will appear here when the instructor adds\n them.\n </p>\n </div>\n )}\n\n {tab === 'notes' && (\n <div className=\"space-y-2\">\n <Textarea\n value={notes}\n onChange={(e) => {\n setNotes(e.target.value);\n writeNotes(course.id, e.target.value);\n }}\n placeholder=\"Jot down your notes for this course. They are saved to this device automatically.\"\n rows={5}\n />\n <p className=\"text-xs text-text-muted\">Notes are saved automatically on this device.</p>\n </div>\n )}\n </CardContent>\n </GlassCard>\n\n {/* Footer controls */}\n <Card>\n <CardContent className=\"space-y-4 p-4\">\n <div className=\"h-2 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-status-success-bg transition-[width] duration-500\"\n style={{ width: `${pct}%` }}\n />\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <Button variant=\"outline\" onClick={goPrev} disabled={current === 0}>\n <ChevronLeft className=\"mr-1 h-4 w-4\" />\n Previous\n </Button>\n <div className=\"flex items-center gap-2\">\n <Button onClick={() => void handleMarkComplete()}>\n {currentComplete ? (\n current < totalModules - 1 ? (\n 'Next module'\n ) : (\n <>\n <Check className=\"mr-1 h-4 w-4\" /> Completed\n </>\n )\n ) : (\n <>\n <Check className=\"mr-1 h-4 w-4\" /> Mark Complete\n </>\n )}\n </Button>\n <Button variant=\"outline\" onClick={goNext} disabled={current >= totalModules - 1}>\n Next\n <ChevronRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AA0BA,IAAM,IAAe,kCACf,IAAY;AAElB,SAAS,EAAmB,GAA2B;AACrD,KAAI,OAAO,SAAW,IAAa,QAAO;AAC1C,KAAI;EACF,IAAM,IAAM,OAAO,aAAa,QAAQ,EAAa,EAC/C,IAAe,IAAM,KAAK,MAAM,EAAI,GAAG,EAAE;AAC/C,SAAO,MAAM,QAAQ,EAAI,IAAI,EAAI,SAAS,EAAS;SAC7C;AACN,SAAO;;;AAIX,SAAS,GAAoB,GAAwB;AAC/C,cAAO,SAAW,KACtB,KAAI;EACF,IAAM,IAAM,OAAO,aAAa,QAAQ,EAAa,EAC/C,IAAe,IAAM,KAAK,MAAM,EAAI,GAAG,EAAE,EACzC,IAAO,MAAM,QAAQ,EAAI,GAAG,EAAI,QAAQ,MAAmB,OAAO,KAAM,SAAS,GAAG,EAAE;AAC5F,EAAK,EAAK,SAAS,EAAS,KAC1B,EAAK,KAAK,EAAS,EACnB,OAAO,aAAa,QAAQ,GAAc,KAAK,UAAU,EAAK,CAAC;SAE3D;;AAKV,SAAS,GAAU,GAA0B;AAC3C,KAAI,OAAO,SAAW,IAAa,QAAO;AAC1C,KAAI;EACF,IAAM,IAAM,OAAO,aAAa,QAAQ,EAAU,EAC5C,IAAe,IAAM,KAAK,MAAM,EAAI,GAAG,EAAE;AAC/C,MAAI,KAAO,OAAO,KAAQ,UAAU;GAClC,IAAM,IAAS,EAAgC;AAC/C,UAAO,OAAO,KAAU,WAAW,IAAQ;;AAE7C,SAAO;SACD;AACN,SAAO;;;AAIX,SAAS,GAAW,GAAkB,GAAqB;AACrD,cAAO,SAAW,KACtB,KAAI;EACF,IAAM,IAAM,OAAO,aAAa,QAAQ,EAAU,EAC5C,IAAkB,IAAM,KAAK,MAAM,EAAI,GAAG,EAAE,EAC5C,IACJ,KAAU,OAAO,KAAW,WAAY,IAAoC,EAAE;AAEhF,EADA,EAAI,KAAY,GAChB,OAAO,aAAa,QAAQ,GAAW,KAAK,UAAU,EAAI,CAAC;SACrD;;AAOV,SAAgB,IAAiC;CAC/C,IAAM,IAAW,GAAa,EACxB,CAAC,KAAgB,GAAiB,EAClC,EAAE,YAAS,iBAAc,mBAAgB,GAAqB,EAC9D,EAAE,iBAAc,GAAa,EAE7B,IAAW,EAAa,IAAI,KAAK,EACjC,IAAS,IAAW,EAAQ,MAAM,MAAM,EAAE,OAAO,EAAS,GAAG,EAAQ,IAErE,IAAe,IAAS,KAAK,IAAI,EAAO,SAAS,EAAE,GAAG,GAEtD,CAAC,GAAW,KAAgB,QAChC,IAAS,EAAmB,EAAO,GAAG,CAAC,YAAY,EAAE,CACtD,EACK,CAAC,GAAS,KAAc,QAAuB;AACnD,MAAI,CAAC,EAAQ,QAAO;EACpB,IAAM,IAAO,IAAI,IAAI,EAAmB,EAAO,GAAG,CAAC,UAAU;AAC7D,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,IAAI,EAAO,SAAS,EAAE,EAAE,KAAK,EACpD,KAAI,CAAC,EAAK,IAAI,EAAE,CAAE,QAAO;AAE3B,SAAO;GACP,EACI,CAAC,GAAK,KAAU,EAAoB,WAAW,EAC/C,CAAC,GAAO,KAAY,QAAwB,IAAS,GAAU,EAAO,GAAG,GAAG,GAAI,EAEhF,IAAe,QAAc,IAAI,IAAI,EAAU,EAAE,CAAC,EAAU,CAAC,EAC7D,IAAM,EAAgB,EAAE,cAAW,EAAE,EAAa;AAExD,KAAI,CAAC,KAAe,CAAC,EACnB,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,MAAK,MAAO,CAAA;EACjB,CAAA;AAIV,KAAI,CAAC,EACH,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,GAAD;IAAQ,SAAQ;IAAU,eAAe,EAAS,2BAA2B;cAAE;IAEtE,CAAA;GAEX,CAAA;EACE,CAAA;CAIV,IAAM,KAAW,MAAmB;AAElC,EADA,EAAa,EAAK,EAClB,EAAoB,EAAO,IAAI,GAAM,EAAa;IAG9C,IAAiB,YAAY;AAC7B,SAAmB,EAAO,GAAG,EACjC;MAAoB,EAAO,GAAG;AAC9B,OAAI;AACF,UAAM,EAAa,EAAO,GAAG;WACvB;;IAKJ,IAAqB,YAAY;AACrC,MAAI,CAAC,EAAa,IAAI,EAAQ,EAAE;GAC9B,IAAM,IAAW,EAAU,WAAW,GAChC,IAAO,CAAC,GAAG,GAAW,EAAQ;AAKpC,GAJA,EAAQ,EAAK,EACT,KACF,MAAM,GAAgB,EAEpB,EAAK,UAAU,KACjB,EAAU,iBAAiB,EAAO,SAAS,UAAU;;AAGzD,EAAI,IAAU,IAAe,MAC3B,EAAW,IAAU,EAAE,EACvB,EAAO,WAAW;IAIhB,KAAQ,MAAkB;AAE9B,EADA,EAAW,EAAM,EACjB,EAAO,WAAW;IAGd,WAAe,IAAU,KAAK,EAAK,IAAU,EAAE,EAC/C,WAAe,IAAU,IAAe,KAAK,EAAK,IAAU,EAAE,EAE9D,IAAkB,EAAa,IAAI,EAAQ,EAC3C,IAAc,UAAU,IAAU;AAExC,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAS,2BAA2B;KACnD,WAAU;eACX;KAEQ,CAAA;IACT,kBAAC,GAAD,EAAc,WAAU,eAAgB,CAAA;IACxC,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAS,sCAAsC,mBAAmB,EAAO,GAAG,GAAG;KAC9F,WAAU;eACX;KAEQ,CAAA;IACT,kBAAC,GAAD,EAAc,WAAU,eAAgB,CAAA;IACxC,kBAAC,QAAD;KAAM,WAAU;eAAiC,EAAO;KAAa,CAAA;IACjE;MAEN,kBAAC,OAAD;GAAK,WAAU;aAAf,CAEE,kBAAC,GAAD;IAAM,WAAU;cACd,kBAAC,GAAD;KAAa,WAAU;eAAvB;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAAwB;QAAmB,CAAA,EACzD,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACG,GAAI,aACA;UACH;;MACN,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QACE,WAAU;QACV,OAAO,EAAE,OAAO,GAAG,EAAI,IAAI;QAC3B,CAAA;OACE,CAAA;MACN,kBAAC,MAAD;OAAI,WAAU;iBACX,MAAM,KAAK,EAAE,QAAQ,GAAc,GAAG,GAAG,MAAM;QAC9C,IAAM,IAAO,EAAa,IAAI,EAAE,EAC1B,IAAY,MAAM;AACxB,eACE,kBAAC,MAAD,EAAA,UACE,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAK,EAAE;SACtB,gBAAc,IAAY,SAAS,KAAA;SACnC,WAAW,6FACT,IACI,0DACA;mBAPR,CAUE,kBAAC,QAAD;UACE,WAAW,yEACT,IACI,qEACA,IACE,oDACA;oBAIN,EADD,IACE,IAEA,GAFD,EAAO,WAAU,WAAY,CAEO;UAEjC,CAAA,EACP,kBAAC,QAAD;UAAM,WAAU;oBAAY,UAAU,IAAI;UAAW,CAAA,CAC9C;YACN,EA5BI,EA4BJ;SAEP;OACC,CAAA;MACO;;IACT,CAAA,EAGP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eAAvC,CACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,KAAK,GAAoB;MACxC,cAAY,IAAkB,gBAAgB;MAC9C,WAAU;MACV,OAAO,EACL,YACE,gIACH;gBARH,CAUE,kBAAC,QAAD;OAAM,WAAU;iBACd,kBAAC,GAAD;QAAO,SAAQ;QAAY,WAAU;kBAClC;QACK,CAAA;OACH,CAAA,EACP,kBAAC,QAAD;OAAM,WAAU;iBACb,IACC,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA,GAE7B,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA;OAE9B,CAAA,CACA;SACT,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,MAAD;UAAI,WAAU;oBAAyB;UAAiB,CAAA;SACxD,kBAAC,GAAD;UACE,SACE,EAAO,UAAU,aACb,cACA,EAAO,UAAU,iBACf,YACA;UAER,WAAU;oBAET,EAAgB,EAAO,MAAM;UACxB,CAAA;SACP,KACC,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CACE,kBAAC,GAAD,EAAO,WAAU,eAAgB,CAAA,EAAA,aAC5B;;SAEL;WACN,kBAAC,KAAD;QAAG,WAAU;kBAAb;SACG,EAAO;SAAM;SAAO,EAAO;SAC1B;UACA,EAAA,CAAA;OAEN,kBAAC,GAAD;QACE,SAAQ;QACR,cAAW;QACX,OAAO;QACP,WAAW,MAAO,EAAO,EAAgB;QACzC,MAAM;SACJ;UAAE,IAAI;UAAY,OAAO;UAAY;SACrC;UAAE,IAAI;UAAa,OAAO;UAAa;SACvC;UAAE,IAAI;UAAS,OAAO;UAAS;SAChC;QACD,CAAA;OAED,MAAQ,cACP,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAA+B,EAAO;SAAgB,CAAA,EAClE,EAAO,OAAO,SAAS,KACtB,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAO,OAAO,KAAK,MAClB,kBAAC,GAAD;UAAe,SAAQ;UAAU,WAAU;oBACxC;UACK,EAFI,EAEJ,CACR;SACE,CAAA,CAEJ;;OAGP,MAAQ,eACP,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD,EAAU,WAAU,wCAAyC,CAAA;SAC7D,kBAAC,KAAD;UAAG,WAAU;oBAAsB;UAAiC,CAAA;SACpE,kBAAC,KAAD;UAAG,WAAU;oBAA8B;UAGvC,CAAA;SACA;;OAGP,MAAQ,WACP,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SACE,OAAO;SACP,WAAW,MAAM;AAEf,UADA,EAAS,EAAE,OAAO,MAAM,EACxB,GAAW,EAAO,IAAI,EAAE,OAAO,MAAM;;SAEvC,aAAY;SACZ,MAAM;SACN,CAAA,EACF,kBAAC,KAAD;SAAG,WAAU;mBAA0B;SAAiD,CAAA,CACpF;;OAEI;QACJ;QAGZ,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KAAa,WAAU;eAAvB,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OACE,WAAU;OACV,OAAO,EAAE,OAAO,GAAG,EAAI,IAAI;OAC3B,CAAA;MACE,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAQ,SAAQ;OAAU,SAAS;OAAQ,UAAU,MAAY;iBAAjE,CACE,kBAAC,GAAD,EAAa,WAAU,gBAAiB,CAAA,EAAA,WAEjC;UACT,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAQ,eAAe,KAAK,GAAoB;kBAC7C,IACC,IAAU,IAAe,IACvB,gBAEA,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAO,WAAU,gBAAiB,CAAA,EAAA,aACjC,EAAA,CAAA,GAGL,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAO,WAAU,gBAAiB,CAAA,EAAA,iBACjC,EAAA,CAAA;QAEE,CAAA,EACT,kBAAC,GAAD;QAAQ,SAAQ;QAAU,SAAS;QAAQ,UAAU,KAAW,IAAe;kBAA/E,CAAkF,QAEhF,kBAAC,GAAD,EAAc,WAAU,gBAAiB,CAAA,CAClC;UACL;SACF;QACM;QACT,CAAA,CACH;MACF;KACF"}