@burdenoff/microfe-technospam 2026.709.3 → 2026.709.4

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 (46) hide show
  1. package/dist/index.js +4 -4
  2. package/dist/technospam/TechnoSpamRoutes.d.ts.map +1 -1
  3. package/dist/technospam/TechnoSpamRoutes.js +100 -74
  4. package/dist/technospam/TechnoSpamRoutes.js.map +1 -1
  5. package/dist/technospam/components/PageLayout.d.ts.map +1 -1
  6. package/dist/technospam/components/PageLayout.js +26 -23
  7. package/dist/technospam/components/PageLayout.js.map +1 -1
  8. package/dist/technospam/components/TagInput.d.ts +18 -0
  9. package/dist/technospam/components/TagInput.d.ts.map +1 -0
  10. package/dist/technospam/components/TagInput.js +58 -0
  11. package/dist/technospam/components/TagInput.js.map +1 -0
  12. package/dist/technospam/components/TagStrip.d.ts +12 -0
  13. package/dist/technospam/components/TagStrip.d.ts.map +1 -0
  14. package/dist/technospam/components/TagStrip.js +20 -0
  15. package/dist/technospam/components/TagStrip.js.map +1 -0
  16. package/dist/technospam/components/index.d.ts +2 -0
  17. package/dist/technospam/components/index.d.ts.map +1 -1
  18. package/dist/technospam/components/index.js +2 -0
  19. package/dist/technospam/hooks/useNativeControls.d.ts +27 -0
  20. package/dist/technospam/hooks/useNativeControls.d.ts.map +1 -0
  21. package/dist/technospam/hooks/useNativeControls.js +18 -0
  22. package/dist/technospam/hooks/useNativeControls.js.map +1 -0
  23. package/dist/technospam/hooks/useTechnoSpamAnalytics.d.ts +1 -1
  24. package/dist/technospam/hooks/useTechnoSpamAnalytics.d.ts.map +1 -1
  25. package/dist/technospam/hooks/useTechnoSpamAnalytics.js.map +1 -1
  26. package/dist/technospam/hooks/useTechnoSpamPageView.d.ts +14 -0
  27. package/dist/technospam/hooks/useTechnoSpamPageView.d.ts.map +1 -0
  28. package/dist/technospam/hooks/useTechnoSpamPageView.js +26 -0
  29. package/dist/technospam/hooks/useTechnoSpamPageView.js.map +1 -0
  30. package/dist/technospam/index.js +1 -1
  31. package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts.map +1 -1
  32. package/dist/technospam/pages/AdminAdminDashboardPagePage.js +408 -399
  33. package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
  34. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.d.ts.map +1 -1
  35. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +50 -55
  36. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
  37. package/dist/technospam/pages/ApplicationsProblemsPagePage.js +25 -25
  38. package/dist/technospam/pages/CoursesCourseDetailsPagePage.d.ts.map +1 -1
  39. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +142 -144
  40. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
  41. package/dist/technospam/pages/CoursesCoursesPagePage.js +30 -30
  42. package/dist/technospam/pages/HackathonsHackathonsPagePage.js +29 -29
  43. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +54 -54
  44. package/dist/technospam/pages/MentorshipMentorsPagePage.js +61 -61
  45. package/dist/technospam/pages/MentorshipSessionsPagePage.js +42 -42
  46. package/package.json +2 -2
@@ -1,95 +1,96 @@
1
1
  import { useTechnoSpam as e } from "../providers/TechnoSpamProvider.js";
2
- import { cn as t } from "../utils/cn.js";
3
- import { PageLayout as n } from "../components/PageLayout.js";
4
- import { StatCard as r } from "../components/StatCard.js";
5
- import { MediaCover as i, topicIcon as a } from "../components/MediaCover.js";
6
- import { formatDuration as o, humanizeEnum as s } from "../utils/format.js";
7
- import { EmptyView as ee, ErrorView as c } from "../components/StateViews.js";
2
+ import { useTechnoSpamAnalytics as t } from "../hooks/useTechnoSpamAnalytics.js";
3
+ import { cn as n } from "../utils/cn.js";
4
+ import { PageLayout as r } from "../components/PageLayout.js";
5
+ import { StatCard as i } from "../components/StatCard.js";
6
+ import { MediaCover as a, topicIcon as o } from "../components/MediaCover.js";
7
+ import { formatDuration as s, humanizeEnum as c } from "../utils/format.js";
8
+ import { EmptyView as l, ErrorView as u } from "../components/StateViews.js";
9
+ import { TagStrip as d } from "../components/TagStrip.js";
8
10
  import "../components/index.js";
9
- import { notify as l } from "../lib/toast.js";
10
- import { useCertificatesQuery as u, useCourseQuery as d, useEnroll as f, useIssueCertificate as p, useUpdateProgress as te } from "../api/technospamApi.js";
11
- import { useTechnoSpamAnalytics as ne } from "../hooks/useTechnoSpamAnalytics.js";
12
- import { useEffect as re } from "react";
13
- import { Award as m, BookOpen as h, CheckCircle2 as g, Clock as _, Eye as v, FileText as y, Layers as b, Lock as x, PlayCircle as S, ShieldCheck as C, Sparkles as w, TrendingUp as T, Trophy as ie, Users as E } from "lucide-react";
14
- import { useSearchParams as D } from "react-router-dom";
15
- import { jsx as O, jsxs as k } from "react/jsx-runtime";
16
- import { Badge as A, Button as j, GlassCard as M, Skeleton as N } from "@burdenoff/fe-libs/ui";
11
+ import { notify as f } from "../lib/toast.js";
12
+ import { useCertificatesQuery as p, useCourseQuery as ee, useEnroll as te, useIssueCertificate as ne, useUpdateProgress as m } from "../api/technospamApi.js";
13
+ import { useEffect as h } from "react";
14
+ import { Award as g, BookOpen as _, CheckCircle2 as v, Clock as y, Eye as b, FileText as x, Layers as S, Lock as C, PlayCircle as w, ShieldCheck as re, Sparkles as T, TrendingUp as E, Trophy as ie, Users as D } from "lucide-react";
15
+ import { useSearchParams as O } from "react-router-dom";
16
+ import { jsx as k, jsxs as A } from "react/jsx-runtime";
17
+ import { Button as j, GlassCard as M, Skeleton as N } from "@burdenoff/fe-libs/ui";
17
18
  //#region src/technospam/pages/CoursesCourseDetailsPagePage.tsx
18
19
  var P = {
19
20
  VIDEO: {
20
- icon: S,
21
+ icon: w,
21
22
  chip: "bg-accent-primary/15 text-accent-primary",
22
23
  badge: "border-accent-primary/25 bg-accent-primary/10 text-accent-primary"
23
24
  },
24
25
  ARTICLE: {
25
- icon: y,
26
+ icon: x,
26
27
  chip: "bg-accent-indigo/15 text-accent-indigo",
27
28
  badge: "border-accent-indigo/25 bg-accent-indigo/10 text-accent-indigo"
28
29
  },
29
30
  INTERACTIVE: {
30
- icon: w,
31
+ icon: T,
31
32
  chip: "bg-accent-teal/15 text-accent-teal",
32
33
  badge: "border-accent-teal/25 bg-accent-teal/10 text-accent-teal"
33
34
  },
34
35
  QUIZ: {
35
- icon: g,
36
+ icon: v,
36
37
  chip: "bg-accent-purple/15 text-accent-purple",
37
38
  badge: "border-accent-purple/25 bg-accent-purple/10 text-accent-purple"
38
39
  },
39
40
  PROJECT: {
40
- icon: b,
41
+ icon: S,
41
42
  chip: "bg-status-warning-bg-subtle text-status-warning-text",
42
43
  badge: "border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text"
43
44
  }
44
45
  };
45
46
  function F({ icon: e, children: t }) {
46
- return /* @__PURE__ */ k("span", {
47
+ return /* @__PURE__ */ A("span", {
47
48
  className: "inline-flex items-center gap-1 rounded-full bg-white/15 px-2.5 py-1 text-xs font-medium text-white ring-1 ring-white/25 backdrop-blur-sm",
48
- children: [e ? /* @__PURE__ */ O(e, { size: 12 }) : null, t]
49
+ children: [e ? /* @__PURE__ */ k(e, { size: 12 }) : null, t]
49
50
  });
50
51
  }
51
- function I({ lesson: e, index: n, done: r }) {
52
+ function I({ lesson: e, index: t, done: r }) {
52
53
  let i = P[e.kind] ?? P.ARTICLE, a = i.icon;
53
- return /* @__PURE__ */ k("div", {
54
+ return /* @__PURE__ */ A("div", {
54
55
  className: "group flex items-center gap-3 rounded-xl px-2 py-3 transition-colors hover:bg-bg-sunken/60",
55
56
  children: [
56
- /* @__PURE__ */ O("span", {
57
+ /* @__PURE__ */ k("span", {
57
58
  className: "hidden w-6 shrink-0 text-center text-sm font-medium tabular-nums text-text-muted sm:block",
58
- children: n + 1
59
+ children: t + 1
59
60
  }),
60
- /* @__PURE__ */ O("span", {
61
- className: t("flex h-9 w-9 shrink-0 items-center justify-center rounded-lg", r ? "bg-status-success-bg-subtle text-status-success-text" : i.chip),
62
- children: O(r ? g : a, { size: 17 })
61
+ /* @__PURE__ */ k("span", {
62
+ className: n("flex h-9 w-9 shrink-0 items-center justify-center rounded-lg", r ? "bg-status-success-bg-subtle text-status-success-text" : i.chip),
63
+ children: k(r ? v : a, { size: 17 })
63
64
  }),
64
- /* @__PURE__ */ k("div", {
65
+ /* @__PURE__ */ A("div", {
65
66
  className: "min-w-0 flex-1",
66
- children: [/* @__PURE__ */ O("span", {
67
+ children: [/* @__PURE__ */ k("span", {
67
68
  className: "block truncate text-sm font-medium text-text-primary",
68
69
  children: e.title
69
- }), /* @__PURE__ */ k("div", {
70
+ }), /* @__PURE__ */ A("div", {
70
71
  className: "mt-1 flex flex-wrap items-center gap-2",
71
- children: [/* @__PURE__ */ O("span", {
72
- className: t("inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] font-medium", i.badge),
73
- children: s(e.kind)
74
- }), /* @__PURE__ */ k("span", {
72
+ children: [/* @__PURE__ */ k("span", {
73
+ className: n("inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] font-medium", i.badge),
74
+ children: c(e.kind)
75
+ }), /* @__PURE__ */ A("span", {
75
76
  className: "inline-flex items-center gap-1 text-xs text-text-muted",
76
77
  children: [
77
- /* @__PURE__ */ O(_, { size: 12 }),
78
+ /* @__PURE__ */ k(y, { size: 12 }),
78
79
  " ",
79
- o(e.estimatedMinutes)
80
+ s(e.estimatedMinutes)
80
81
  ]
81
82
  })]
82
83
  })]
83
84
  }),
84
- /* @__PURE__ */ O("div", {
85
+ /* @__PURE__ */ k("div", {
85
86
  className: "shrink-0",
86
- children: r ? /* @__PURE__ */ O(g, {
87
+ children: r ? /* @__PURE__ */ k(v, {
87
88
  size: 18,
88
89
  className: "text-status-success-text"
89
- }) : e.isPreview ? /* @__PURE__ */ k("span", {
90
+ }) : e.isPreview ? /* @__PURE__ */ A("span", {
90
91
  className: "inline-flex items-center gap-1 rounded-full border border-accent-primary/30 bg-accent-primary/10 px-2 py-0.5 text-[11px] font-medium text-accent-primary",
91
- children: [/* @__PURE__ */ O(v, { size: 12 }), " Preview"]
92
- }) : /* @__PURE__ */ O(x, {
92
+ children: [/* @__PURE__ */ k(b, { size: 12 }), " Preview"]
93
+ }) : /* @__PURE__ */ k(C, {
93
94
  size: 15,
94
95
  className: "text-text-muted"
95
96
  })
@@ -98,41 +99,41 @@ function I({ lesson: e, index: n, done: r }) {
98
99
  });
99
100
  }
100
101
  function L() {
101
- let [t] = D(), v = t.get("id") ?? "", { currentUser: x, workspaceId: S } = e(), { emit: w } = ne(), { data: P, loading: L, error: R, refetch: z } = d(v);
102
- re(() => {
103
- v && w("technospam.course.viewed", {
104
- workspaceId: S,
105
- entityId: v,
102
+ let [n] = O(), b = n.get("id") ?? "", { currentUser: C, workspaceId: w } = e(), { emit: T } = t(), { data: P, loading: L, error: R, refetch: z } = ee(b);
103
+ h(() => {
104
+ b && T("technospam.course.viewed", {
105
+ workspaceId: w,
106
+ entityId: b,
106
107
  entityType: "TEC_COURSE"
107
108
  });
108
109
  }, [
109
- v,
110
- S,
111
- w
110
+ b,
111
+ w,
112
+ T
112
113
  ]);
113
- let [B, { loading: V }] = f(), [H, { loading: U }] = te(), { data: W, refetch: G } = u(x?.id && v ? {
114
- learnerId: x.id,
115
- courseId: v
116
- } : {}), [K, { loading: q }] = p(), J = P?.tecCourse ?? null, Y = [...J?.lessons ?? []].sort((e, t) => e.position - t.position), X = (J?.enrollments ?? []).find((e) => e.learnerId === x?.id), Z = (W?.tecListCertificates ?? []).find((e) => e.status === "ISSUED"), ae = !!X && X.progressPct >= 100, oe = async () => {
117
- !v || !x?.id || (w("technospam.course.certificate_claimed", {
118
- workspaceId: S,
119
- entityId: v,
114
+ let [B, { loading: V }] = te(), [H, { loading: U }] = m(), { data: W, refetch: G } = p(C?.id && b ? {
115
+ learnerId: C.id,
116
+ courseId: b
117
+ } : {}), [K, { loading: q }] = ne(), J = P?.tecCourse ?? null, Y = [...J?.lessons ?? []].sort((e, t) => e.position - t.position), X = (J?.enrollments ?? []).find((e) => e.learnerId === C?.id), Z = (W?.tecListCertificates ?? []).find((e) => e.status === "ISSUED"), ae = !!X && X.progressPct >= 100, oe = async () => {
118
+ !b || !C?.id || (T("technospam.course.certificate_claimed", {
119
+ workspaceId: w,
120
+ entityId: b,
120
121
  entityType: "TEC_COURSE"
121
122
  }), (await K({ variables: {
122
- courseId: v,
123
- learnerId: x.id,
123
+ courseId: b,
124
+ learnerId: C.id,
124
125
  score: 100
125
- } })).data?.tecIssueCertificate && (l.success("Certificate issued", "Your verifiable certificate is ready in My Learning."), await G()));
126
+ } })).data?.tecIssueCertificate && (f.success("Certificate issued", "Your verifiable certificate is ready in My Learning."), await G()));
126
127
  }, se = async () => {
127
- v && (w("technospam.course.enroll_clicked", {
128
- workspaceId: S,
129
- entityId: v,
128
+ b && (T("technospam.course.enroll_clicked", {
129
+ workspaceId: w,
130
+ entityId: b,
130
131
  entityType: "TEC_COURSE"
131
- }), (await B({ variables: { input: { courseId: v } } })).data?.tecEnroll && (w("technospam.course.enrolled", {
132
- workspaceId: S,
133
- entityId: v,
132
+ }), (await B({ variables: { input: { courseId: b } } })).data?.tecEnroll && (T("technospam.course.enrolled", {
133
+ workspaceId: w,
134
+ entityId: b,
134
135
  entityType: "TEC_COURSE"
135
- }), l.success("Enrolled", `You're enrolled in "${J?.title}".`), await z()));
136
+ }), f.success("Enrolled", `You're enrolled in "${J?.title}".`), await z()));
136
137
  }, ce = async () => {
137
138
  if (!X) return;
138
139
  let e = Math.min(Y.length, X.lessonsCompleted + 1), t = Y.length ? Math.round(e / Y.length * 100) : 100;
@@ -140,77 +141,77 @@ function L() {
140
141
  enrollmentId: X.id,
141
142
  lessonsCompleted: e,
142
143
  progressPct: t
143
- } } })).data?.tecUpdateProgress && (w("technospam.course.progress_updated", {
144
- workspaceId: S,
144
+ } } })).data?.tecUpdateProgress && (T("technospam.course.progress_updated", {
145
+ workspaceId: w,
145
146
  entityId: X.id,
146
147
  entityType: "TEC_ENROLLMENT",
147
148
  progressPct: t
148
- }), l.success("Progress saved", `${t}% complete`), await z());
149
+ }), f.success("Progress saved", `${t}% complete`), await z());
149
150
  };
150
- if (L && !J) return /* @__PURE__ */ k(n, {
151
+ if (L && !J) return /* @__PURE__ */ A(r, {
151
152
  title: "Course",
152
153
  backPath: "/courses/courses-page",
153
- children: [/* @__PURE__ */ O(N, { className: "mb-4 h-40 w-full rounded-2xl" }), /* @__PURE__ */ O(N, { className: "h-64 w-full rounded-xl" })]
154
+ children: [/* @__PURE__ */ k(N, { className: "mb-4 h-40 w-full rounded-2xl" }), /* @__PURE__ */ k(N, { className: "h-64 w-full rounded-xl" })]
154
155
  });
155
- if (R || !J) return /* @__PURE__ */ O(n, {
156
+ if (R || !J) return /* @__PURE__ */ k(r, {
156
157
  title: "Course",
157
158
  backPath: "/courses/courses-page",
158
- children: R ? /* @__PURE__ */ O(c, {
159
+ children: R ? /* @__PURE__ */ k(u, {
159
160
  title: "Couldn't load course",
160
161
  error: R
161
- }) : /* @__PURE__ */ O(ee, {
162
+ }) : /* @__PURE__ */ k(l, {
162
163
  title: "Course not found",
163
164
  description: "This course may have been unpublished."
164
165
  })
165
166
  });
166
167
  let Q = X?.lessonsCompleted ?? 0, $ = Math.max(0, Y.length - Q), le = X && X.progressPct >= 100 ? "Course complete — claim your certificate below." : $ === 1 ? "1 lesson to go — almost there!" : `${$} lessons to go — keep it up!`, ue = `${J.topic ?? ""} ${J.title}`;
167
- return /* @__PURE__ */ k(n, {
168
+ return /* @__PURE__ */ A(r, {
168
169
  backPath: "/courses/courses-page",
169
170
  title: J.title,
170
171
  description: J.summary ?? void 0,
171
- actions: X ? /* @__PURE__ */ k(j, {
172
+ actions: X ? /* @__PURE__ */ A(j, {
172
173
  onClick: ce,
173
174
  disabled: U || Q >= Y.length,
174
- children: [/* @__PURE__ */ O(g, { size: 16 }), Q >= Y.length ? "Completed" : "Complete next lesson"]
175
- }) : /* @__PURE__ */ k(j, {
175
+ children: [/* @__PURE__ */ k(v, { size: 16 }), Q >= Y.length ? "Completed" : "Complete next lesson"]
176
+ }) : /* @__PURE__ */ A(j, {
176
177
  onClick: se,
177
178
  disabled: V,
178
179
  children: [
179
- /* @__PURE__ */ O(h, { size: 16 }),
180
+ /* @__PURE__ */ k(_, { size: 16 }),
180
181
  " ",
181
182
  V ? "Enrolling…" : "Enroll"
182
183
  ]
183
184
  }),
184
185
  children: [
185
- /* @__PURE__ */ O(i, {
186
+ /* @__PURE__ */ k(a, {
186
187
  height: "h-40",
187
188
  seed: ue,
188
- icon: a(J.topic ?? J.title),
189
+ icon: o(J.topic ?? J.title),
189
190
  className: "mb-5 rounded-2xl",
190
- children: /* @__PURE__ */ k("div", {
191
+ children: /* @__PURE__ */ A("div", {
191
192
  className: "flex w-full flex-col gap-2",
192
193
  children: [
193
- /* @__PURE__ */ O("span", {
194
+ /* @__PURE__ */ k("span", {
194
195
  className: "text-xs font-medium uppercase tracking-wide text-white/80",
195
196
  children: J.topic ?? "Course"
196
197
  }),
197
- /* @__PURE__ */ O("h2", {
198
+ /* @__PURE__ */ k("h2", {
198
199
  className: "text-2xl font-bold leading-tight text-white drop-shadow-sm sm:text-3xl",
199
200
  children: J.title
200
201
  }),
201
- /* @__PURE__ */ k("div", {
202
+ /* @__PURE__ */ A("div", {
202
203
  className: "flex flex-wrap items-center gap-2",
203
204
  children: [
204
- /* @__PURE__ */ O(F, {
205
- icon: T,
206
- children: s(J.level)
205
+ /* @__PURE__ */ k(F, {
206
+ icon: E,
207
+ children: c(J.level)
207
208
  }),
208
- /* @__PURE__ */ O(F, {
209
- icon: g,
210
- children: s(J.status)
209
+ /* @__PURE__ */ k(F, {
210
+ icon: v,
211
+ children: c(J.status)
211
212
  }),
212
- J.topic ? /* @__PURE__ */ O(F, {
213
- icon: b,
213
+ J.topic ? /* @__PURE__ */ k(F, {
214
+ icon: S,
214
215
  children: J.topic
215
216
  }) : null
216
217
  ]
@@ -218,57 +219,54 @@ function L() {
218
219
  ]
219
220
  })
220
221
  }),
221
- J.tags.length ? /* @__PURE__ */ O("div", {
222
- className: "mb-5 flex flex-wrap items-center gap-2",
223
- children: J.tags.map((e) => /* @__PURE__ */ O(A, {
224
- variant: "secondary",
225
- children: e
226
- }, e))
227
- }) : null,
228
- /* @__PURE__ */ k("div", {
222
+ /* @__PURE__ */ k(d, {
223
+ tags: J.tags,
224
+ className: "mb-5"
225
+ }),
226
+ /* @__PURE__ */ A("div", {
229
227
  className: "mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4",
230
228
  children: [
231
- /* @__PURE__ */ O(r, {
229
+ /* @__PURE__ */ k(i, {
232
230
  label: "Lessons",
233
231
  value: Y.length,
234
- icon: y,
232
+ icon: x,
235
233
  tone: "blue"
236
234
  }),
237
- /* @__PURE__ */ O(r, {
235
+ /* @__PURE__ */ k(i, {
238
236
  label: "Duration",
239
- value: o(J.estimatedMinutes),
240
- icon: _,
237
+ value: s(J.estimatedMinutes),
238
+ icon: y,
241
239
  tone: "teal"
242
240
  }),
243
- /* @__PURE__ */ O(r, {
241
+ /* @__PURE__ */ k(i, {
244
242
  label: "Enrolled",
245
243
  value: J.enrollments?.length ?? 0,
246
- icon: E,
244
+ icon: D,
247
245
  tone: "indigo"
248
246
  }),
249
- /* @__PURE__ */ O(r, {
247
+ /* @__PURE__ */ k(i, {
250
248
  label: "Your progress",
251
249
  value: X ? `${X.progressPct}%` : "Not started",
252
- icon: T,
250
+ icon: E,
253
251
  tone: "purple"
254
252
  })
255
253
  ]
256
254
  }),
257
- X ? /* @__PURE__ */ k(M, {
255
+ X ? /* @__PURE__ */ A(M, {
258
256
  className: "mb-6 p-5",
259
257
  children: [
260
- /* @__PURE__ */ k("div", {
258
+ /* @__PURE__ */ A("div", {
261
259
  className: "mb-3 flex items-center gap-2",
262
260
  children: [
263
- /* @__PURE__ */ O("span", {
261
+ /* @__PURE__ */ k("span", {
264
262
  className: "flex h-8 w-8 items-center justify-center rounded-lg bg-accent-primary/15 text-accent-primary",
265
- children: /* @__PURE__ */ O(T, { size: 16 })
263
+ children: /* @__PURE__ */ k(E, { size: 16 })
266
264
  }),
267
- /* @__PURE__ */ O("span", {
265
+ /* @__PURE__ */ k("span", {
268
266
  className: "text-sm font-semibold text-text-primary",
269
267
  children: "Your progress"
270
268
  }),
271
- /* @__PURE__ */ k("span", {
269
+ /* @__PURE__ */ A("span", {
272
270
  className: "ml-auto text-sm text-text-secondary",
273
271
  children: [
274
272
  Q,
@@ -281,32 +279,32 @@ function L() {
281
279
  })
282
280
  ]
283
281
  }),
284
- /* @__PURE__ */ O("div", {
282
+ /* @__PURE__ */ k("div", {
285
283
  className: "h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken",
286
- children: /* @__PURE__ */ O("div", {
284
+ children: /* @__PURE__ */ k("div", {
287
285
  className: "h-full rounded-full bg-gradient-to-r from-accent-primary via-accent-indigo to-accent-purple transition-all duration-500",
288
286
  style: { width: `${X.progressPct}%` }
289
287
  })
290
288
  }),
291
- /* @__PURE__ */ O("p", {
289
+ /* @__PURE__ */ k("p", {
292
290
  className: "mt-2 text-xs text-text-muted",
293
291
  children: le
294
292
  })
295
293
  ]
296
294
  }) : null,
297
- Z ? /* @__PURE__ */ k(M, {
295
+ Z ? /* @__PURE__ */ A(M, {
298
296
  className: "mb-6 flex flex-wrap items-center gap-3 border border-status-success-border/60 bg-status-success-bg-subtle/30 p-4",
299
297
  children: [
300
- /* @__PURE__ */ O("span", {
298
+ /* @__PURE__ */ k("span", {
301
299
  className: "flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-status-warning-bg-subtle text-status-warning-text ring-1 ring-status-warning-border/30",
302
- children: /* @__PURE__ */ O(m, { size: 22 })
300
+ children: /* @__PURE__ */ k(g, { size: 22 })
303
301
  }),
304
- /* @__PURE__ */ k("div", {
302
+ /* @__PURE__ */ A("div", {
305
303
  className: "min-w-0 flex-1",
306
- children: [/* @__PURE__ */ O("div", {
304
+ children: [/* @__PURE__ */ k("div", {
307
305
  className: "text-sm font-semibold text-text-primary",
308
306
  children: "Certificate earned"
309
- }), /* @__PURE__ */ k("div", {
307
+ }), /* @__PURE__ */ A("div", {
310
308
  className: "text-xs text-text-muted",
311
309
  children: [
312
310
  "Serial ",
@@ -316,46 +314,46 @@ function L() {
316
314
  ]
317
315
  })]
318
316
  }),
319
- /* @__PURE__ */ k("span", {
317
+ /* @__PURE__ */ A("span", {
320
318
  className: "inline-flex items-center gap-1 rounded-full border border-status-success-border bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text",
321
- children: [/* @__PURE__ */ O(C, { size: 13 }), " Verified"]
319
+ children: [/* @__PURE__ */ k(re, { size: 13 }), " Verified"]
322
320
  })
323
321
  ]
324
- }) : ae ? /* @__PURE__ */ k(M, {
322
+ }) : ae ? /* @__PURE__ */ A(M, {
325
323
  className: "mb-6 flex flex-col items-start gap-4 border border-status-success-border/60 bg-status-success-bg-subtle/40 p-5 sm:flex-row sm:items-center sm:justify-between",
326
- children: [/* @__PURE__ */ k("div", {
324
+ children: [/* @__PURE__ */ A("div", {
327
325
  className: "flex items-center gap-4",
328
- children: [/* @__PURE__ */ O("span", {
326
+ children: [/* @__PURE__ */ k("span", {
329
327
  className: "flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-status-warning-bg-subtle text-status-warning-text ring-1 ring-status-warning-border/40",
330
- children: /* @__PURE__ */ O(ie, { size: 26 })
331
- }), /* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("div", {
328
+ children: /* @__PURE__ */ k(ie, { size: 26 })
329
+ }), /* @__PURE__ */ A("div", { children: [/* @__PURE__ */ k("div", {
332
330
  className: "text-base font-semibold text-text-primary",
333
331
  children: "You finished this course"
334
- }), /* @__PURE__ */ O("div", {
332
+ }), /* @__PURE__ */ k("div", {
335
333
  className: "text-sm text-text-muted",
336
334
  children: "Claim your verifiable certificate of completion."
337
335
  })] })]
338
- }), /* @__PURE__ */ k(j, {
336
+ }), /* @__PURE__ */ A(j, {
339
337
  onClick: oe,
340
338
  disabled: q,
341
339
  children: [
342
- /* @__PURE__ */ O(m, { size: 16 }),
340
+ /* @__PURE__ */ k(g, { size: 16 }),
343
341
  " ",
344
342
  q ? "Issuing…" : "Claim certificate"
345
343
  ]
346
344
  })]
347
345
  }) : null,
348
- /* @__PURE__ */ k(M, {
346
+ /* @__PURE__ */ A(M, {
349
347
  className: "p-5",
350
- children: [/* @__PURE__ */ k("div", {
348
+ children: [/* @__PURE__ */ A("div", {
351
349
  className: "mb-3 flex items-center justify-between gap-2",
352
- children: [/* @__PURE__ */ k("h2", {
350
+ children: [/* @__PURE__ */ A("h2", {
353
351
  className: "flex items-center gap-2 text-base font-semibold text-text-primary",
354
- children: [/* @__PURE__ */ O(h, {
352
+ children: [/* @__PURE__ */ k(_, {
355
353
  size: 18,
356
354
  className: "text-accent-primary"
357
355
  }), " Curriculum"]
358
- }), Y.length ? /* @__PURE__ */ k("span", {
356
+ }), Y.length ? /* @__PURE__ */ A("span", {
359
357
  className: "rounded-full bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text",
360
358
  children: [
361
359
  Q,
@@ -364,12 +362,12 @@ function L() {
364
362
  " completed"
365
363
  ]
366
364
  }) : null]
367
- }), Y.length === 0 ? /* @__PURE__ */ O("p", {
365
+ }), Y.length === 0 ? /* @__PURE__ */ k("p", {
368
366
  className: "py-6 text-center text-sm text-text-muted",
369
367
  children: "Lessons for this course are being prepared."
370
- }) : /* @__PURE__ */ O("div", {
368
+ }) : /* @__PURE__ */ k("div", {
371
369
  className: "flex flex-col",
372
- children: Y.map((e, t) => /* @__PURE__ */ O(I, {
370
+ children: Y.map((e, t) => /* @__PURE__ */ k(I, {
373
371
  lesson: e,
374
372
  index: t,
375
373
  done: t < Q
@@ -1 +1 @@
1
- {"version":3,"file":"CoursesCourseDetailsPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesCourseDetailsPagePage.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { useEffect } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport {\n Award,\n BookOpen,\n CheckCircle2,\n Clock,\n Eye,\n FileText,\n Lock,\n PlayCircle,\n Layers,\n ShieldCheck,\n Sparkles,\n Trophy,\n TrendingUp,\n Users,\n type LucideIcon,\n} from 'lucide-react';\nimport { Badge, Button, GlassCard, Skeleton } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n MediaCover,\n PageLayout,\n StatCard,\n StatusBadge,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n useCertificatesQuery,\n useCourseQuery,\n useEnroll,\n useIssueCertificate,\n useUpdateProgress,\n type TecLesson,\n type TecEnrollment,\n} from '../api/technospamApi';\nimport { formatDuration, humanizeEnum } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { cn } from '../utils/cn';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\n/** Per-lesson-kind icon + on-theme tint (chip) and pill (badge) classes. */\nconst KIND_META: Record<string, { icon: LucideIcon; chip: string; badge: string }> = {\n VIDEO: {\n icon: PlayCircle,\n chip: 'bg-accent-primary/15 text-accent-primary',\n badge: 'border-accent-primary/25 bg-accent-primary/10 text-accent-primary',\n },\n ARTICLE: {\n icon: FileText,\n chip: 'bg-accent-indigo/15 text-accent-indigo',\n badge: 'border-accent-indigo/25 bg-accent-indigo/10 text-accent-indigo',\n },\n INTERACTIVE: {\n icon: Sparkles,\n chip: 'bg-accent-teal/15 text-accent-teal',\n badge: 'border-accent-teal/25 bg-accent-teal/10 text-accent-teal',\n },\n QUIZ: {\n icon: CheckCircle2,\n chip: 'bg-accent-purple/15 text-accent-purple',\n badge: 'border-accent-purple/25 bg-accent-purple/10 text-accent-purple',\n },\n PROJECT: {\n icon: Layers,\n chip: 'bg-status-warning-bg-subtle text-status-warning-text',\n badge: 'border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text',\n },\n};\n\nfunction HeroBadge({ icon: Icon, children }: { icon?: LucideIcon; children: ReactNode }) {\n return (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-white/15 px-2.5 py-1 text-xs font-medium text-white ring-1 ring-white/25 backdrop-blur-sm\">\n {Icon ? <Icon size={12} /> : null}\n {children}\n </span>\n );\n}\n\nfunction LessonRow({ lesson, index, done }: { lesson: TecLesson; index: number; done: boolean }) {\n const meta = KIND_META[lesson.kind] ?? KIND_META.ARTICLE!;\n const Icon = meta.icon;\n return (\n <div className=\"group flex items-center gap-3 rounded-xl px-2 py-3 transition-colors hover:bg-bg-sunken/60\">\n <span className=\"hidden w-6 shrink-0 text-center text-sm font-medium tabular-nums text-text-muted sm:block\">\n {index + 1}\n </span>\n <span\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg',\n done ? 'bg-status-success-bg-subtle text-status-success-text' : meta.chip\n )}\n >\n {done ? <CheckCircle2 size={17} /> : <Icon size={17} />}\n </span>\n <div className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">{lesson.title}</span>\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n <span\n className={cn(\n 'inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] font-medium',\n meta.badge\n )}\n >\n {humanizeEnum(lesson.kind)}\n </span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Clock size={12} /> {formatDuration(lesson.estimatedMinutes)}\n </span>\n </div>\n </div>\n <div className=\"shrink-0\">\n {done ? (\n <CheckCircle2 size={18} className=\"text-status-success-text\" />\n ) : lesson.isPreview ? (\n <span className=\"inline-flex items-center gap-1 rounded-full border border-accent-primary/30 bg-accent-primary/10 px-2 py-0.5 text-[11px] font-medium text-accent-primary\">\n <Eye size={12} /> Preview\n </span>\n ) : (\n <Lock size={15} className=\"text-text-muted\" />\n )}\n </div>\n </div>\n );\n}\n\nexport function CoursesCourseDetailsPagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n const { currentUser, workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const { data, loading, error, refetch } = useCourseQuery(id);\n\n useEffect(() => {\n if (id) {\n emit('technospam.course.viewed', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n }\n }, [id, workspaceId, emit]);\n\n const [enroll, { loading: enrolling }] = useEnroll();\n const [updateProgress, { loading: savingProgress }] = useUpdateProgress();\n const { data: certData, refetch: refetchCerts } = useCertificatesQuery(\n currentUser?.id && id ? { learnerId: currentUser.id, courseId: id } : {}\n );\n const [issueCertificate, { loading: claiming }] = useIssueCertificate();\n\n const course = data?.tecCourse ?? null;\n const lessons = [...(course?.lessons ?? [])].sort((a, b) => a.position - b.position);\n const myEnrollment: TecEnrollment | undefined = (course?.enrollments ?? []).find(\n (e) => e.learnerId === currentUser?.id\n );\n const myCertificate = (certData?.tecListCertificates ?? []).find((c) => c.status === 'ISSUED');\n const isComplete = !!myEnrollment && myEnrollment.progressPct >= 100;\n\n const handleClaimCertificate = async () => {\n if (!id || !currentUser?.id) return;\n emit('technospam.course.certificate_claimed', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n const res = await issueCertificate({\n variables: { courseId: id, learnerId: currentUser.id, score: 100 },\n });\n if (res.data?.tecIssueCertificate) {\n notify.success('Certificate issued', 'Your verifiable certificate is ready in My Learning.');\n await refetchCerts();\n }\n };\n\n const handleEnroll = async () => {\n if (!id) return;\n emit('technospam.course.enroll_clicked', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n const res = await enroll({ variables: { input: { courseId: id } } });\n if (res.data?.tecEnroll) {\n emit('technospam.course.enrolled', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n notify.success('Enrolled', `You're enrolled in \"${course?.title}\".`);\n await refetch();\n }\n };\n\n const handleProgress = async () => {\n if (!myEnrollment) return;\n const nextCompleted = Math.min(lessons.length, myEnrollment.lessonsCompleted + 1);\n const pct = lessons.length ? Math.round((nextCompleted / lessons.length) * 100) : 100;\n const res = await updateProgress({\n variables: {\n input: { enrollmentId: myEnrollment.id, lessonsCompleted: nextCompleted, progressPct: pct },\n },\n });\n if (res.data?.tecUpdateProgress) {\n emit('technospam.course.progress_updated', {\n workspaceId,\n entityId: myEnrollment.id,\n entityType: 'TEC_ENROLLMENT',\n progressPct: pct,\n });\n notify.success('Progress saved', `${pct}% complete`);\n await refetch();\n }\n };\n\n if (loading && !course) {\n return (\n <PageLayout title=\"Course\" backPath=\"/courses/courses-page\">\n <Skeleton className=\"mb-4 h-40 w-full rounded-2xl\" />\n <Skeleton className=\"h-64 w-full rounded-xl\" />\n </PageLayout>\n );\n }\n\n if (error || !course) {\n return (\n <PageLayout title=\"Course\" backPath=\"/courses/courses-page\">\n {error ? (\n <ErrorView title=\"Couldn't load course\" error={error} />\n ) : (\n <EmptyView\n title=\"Course not found\"\n description=\"This course may have been unpublished.\"\n />\n )}\n </PageLayout>\n );\n }\n\n const completedLessons = myEnrollment?.lessonsCompleted ?? 0;\n const remaining = Math.max(0, lessons.length - completedLessons);\n const milestone =\n myEnrollment && myEnrollment.progressPct >= 100\n ? 'Course complete — claim your certificate below.'\n : remaining === 1\n ? '1 lesson to go — almost there!'\n : `${remaining} lessons to go — keep it up!`;\n const heroSeed = `${course.topic ?? ''} ${course.title}`;\n\n return (\n <PageLayout\n backPath=\"/courses/courses-page\"\n title={course.title}\n description={course.summary ?? undefined}\n actions={\n myEnrollment ? (\n <Button\n onClick={handleProgress}\n disabled={savingProgress || completedLessons >= lessons.length}\n >\n <CheckCircle2 size={16} />\n {completedLessons >= lessons.length ? 'Completed' : 'Complete next lesson'}\n </Button>\n ) : (\n <Button onClick={handleEnroll} disabled={enrolling}>\n <BookOpen size={16} /> {enrolling ? 'Enrolling…' : 'Enroll'}\n </Button>\n )\n }\n >\n {/* Bold hero banner */}\n <MediaCover\n height=\"h-40\"\n seed={heroSeed}\n icon={topicIcon(course.topic ?? course.title)}\n className=\"mb-5 rounded-2xl\"\n >\n <div className=\"flex w-full flex-col gap-2\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-white/80\">\n {course.topic ?? 'Course'}\n </span>\n <h2 className=\"text-2xl font-bold leading-tight text-white drop-shadow-sm sm:text-3xl\">\n {course.title}\n </h2>\n <div className=\"flex flex-wrap items-center gap-2\">\n <HeroBadge icon={TrendingUp}>{humanizeEnum(course.level)}</HeroBadge>\n <HeroBadge icon={CheckCircle2}>{humanizeEnum(course.status)}</HeroBadge>\n {course.topic ? <HeroBadge icon={Layers}>{course.topic}</HeroBadge> : null}\n </div>\n </div>\n </MediaCover>\n\n {course.tags.length ? (\n <div className=\"mb-5 flex flex-wrap items-center gap-2\">\n {course.tags.map((t) => (\n <Badge key={t} variant=\"secondary\">\n {t}\n </Badge>\n ))}\n </div>\n ) : null}\n\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard label=\"Lessons\" value={lessons.length} icon={FileText} tone=\"blue\" />\n <StatCard\n label=\"Duration\"\n value={formatDuration(course.estimatedMinutes)}\n icon={Clock}\n tone=\"teal\"\n />\n <StatCard\n label=\"Enrolled\"\n value={course.enrollments?.length ?? 0}\n icon={Users}\n tone=\"indigo\"\n />\n <StatCard\n label=\"Your progress\"\n value={myEnrollment ? `${myEnrollment.progressPct}%` : 'Not started'}\n icon={TrendingUp}\n tone=\"purple\"\n />\n </div>\n\n {myEnrollment ? (\n <GlassCard className=\"mb-6 p-5\">\n <div className=\"mb-3 flex items-center gap-2\">\n <span className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-accent-primary/15 text-accent-primary\">\n <TrendingUp size={16} />\n </span>\n <span className=\"text-sm font-semibold text-text-primary\">Your progress</span>\n <span className=\"ml-auto text-sm text-text-secondary\">\n {completedLessons}/{lessons.length} lessons · {myEnrollment.progressPct}%\n </span>\n </div>\n <div className=\"h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-gradient-to-r from-accent-primary via-accent-indigo to-accent-purple transition-all duration-500\"\n style={{ width: `${myEnrollment.progressPct}%` }}\n />\n </div>\n <p className=\"mt-2 text-xs text-text-muted\">{milestone}</p>\n </GlassCard>\n ) : null}\n\n {myCertificate ? (\n <GlassCard className=\"mb-6 flex flex-wrap items-center gap-3 border border-status-success-border/60 bg-status-success-bg-subtle/30 p-4\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-status-warning-bg-subtle text-status-warning-text ring-1 ring-status-warning-border/30\">\n <Award size={22} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-sm font-semibold text-text-primary\">Certificate earned</div>\n <div className=\"text-xs text-text-muted\">\n Serial {myCertificate.serial} · Score {myCertificate.score}\n </div>\n </div>\n <span className=\"inline-flex items-center gap-1 rounded-full border border-status-success-border bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text\">\n <ShieldCheck size={13} /> Verified\n </span>\n </GlassCard>\n ) : isComplete ? (\n <GlassCard className=\"mb-6 flex flex-col items-start gap-4 border border-status-success-border/60 bg-status-success-bg-subtle/40 p-5 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-4\">\n <span className=\"flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-status-warning-bg-subtle text-status-warning-text ring-1 ring-status-warning-border/40\">\n <Trophy size={26} />\n </span>\n <div>\n <div className=\"text-base font-semibold text-text-primary\">\n You finished this course\n </div>\n <div className=\"text-sm text-text-muted\">\n Claim your verifiable certificate of completion.\n </div>\n </div>\n </div>\n <Button onClick={handleClaimCertificate} disabled={claiming}>\n <Award size={16} /> {claiming ? 'Issuing…' : 'Claim certificate'}\n </Button>\n </GlassCard>\n ) : null}\n\n <GlassCard className=\"p-5\">\n <div className=\"mb-3 flex items-center justify-between gap-2\">\n <h2 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <BookOpen size={18} className=\"text-accent-primary\" /> Curriculum\n </h2>\n {lessons.length ? (\n <span className=\"rounded-full bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text\">\n {completedLessons}/{lessons.length} completed\n </span>\n ) : null}\n </div>\n {lessons.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-muted\">\n Lessons for this course are being prepared.\n </p>\n ) : (\n <div className=\"flex flex-col\">\n {lessons.map((lesson, i) => (\n <LessonRow key={lesson.id} lesson={lesson} index={i} done={i < completedLessons} />\n ))}\n </div>\n )}\n </GlassCard>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA8CA,IAAM,IAA+E;CACnF,OAAO;EACL,MAAM;EACN,MAAM;EACN,OAAO;EACR;CACD,SAAS;EACP,MAAM;EACN,MAAM;EACN,OAAO;EACR;CACD,aAAa;EACX,MAAM;EACN,MAAM;EACN,OAAO;EACR;CACD,MAAM;EACJ,MAAM;EACN,MAAM;EACN,OAAO;EACR;CACD,SAAS;EACP,MAAM;EACN,MAAM;EACN,OAAO;EACR;CACF;AAED,SAAS,EAAU,EAAE,MAAM,GAAM,eAAwD;AACvF,QACE,kBAAC,QAAD;EAAM,WAAU;YAAhB,CACG,IAAO,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,GAAG,MAC5B,EACI;;;AAIX,SAAS,EAAU,EAAE,WAAQ,UAAO,WAA6D;CAC/F,IAAM,IAAO,EAAU,EAAO,SAAS,EAAU,SAC3C,IAAO,EAAK;AAClB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,QAAD;IAAM,WAAU;cACb,IAAQ;IACJ,CAAA;GACP,kBAAC,QAAD;IACE,WAAW,EACT,gEACA,IAAO,yDAAyD,EAAK,KACtE;cAEO,EAAP,IAAQ,IAA6B,GAA9B,EAAc,MAAM,IAAM,CAAqB;IAClD,CAAA;GACP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAwD,EAAO;KAAa,CAAA,EAC5F,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MACE,WAAW,EACT,oFACA,EAAK,MACN;gBAEA,EAAa,EAAO,KAAK;MACrB,CAAA,EACP,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;;OAAE,EAAe,EAAO,iBAAiB;OACvD;QACH;OACF;;GACN,kBAAC,OAAD;IAAK,WAAU;cACZ,IACC,kBAAC,GAAD;KAAc,MAAM;KAAI,WAAU;KAA6B,CAAA,GAC7D,EAAO,YACT,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,GAAD,EAAK,MAAM,IAAM,CAAA,EAAA,WACZ;SAEP,kBAAC,GAAD;KAAM,MAAM;KAAI,WAAU;KAAoB,CAAA;IAE5C,CAAA;GACF;;;AAIV,SAAgB,IAA+B;CAC7C,IAAM,CAAC,KAAU,GAAiB,EAC5B,IAAK,EAAO,IAAI,KAAK,IAAI,IACzB,EAAE,gBAAa,mBAAgB,GAAe,EAC9C,EAAE,YAAS,IAAwB,EACnC,EAAE,SAAM,YAAS,UAAO,eAAY,EAAe,EAAG;AAE5D,UAAgB;AACd,EAAI,KACF,EAAK,4BAA4B;GAC/B;GACA,UAAU;GACV,YAAY;GACb,CAAC;IAEH;EAAC;EAAI;EAAa;EAAK,CAAC;CAE3B,IAAM,CAAC,GAAQ,EAAE,SAAS,OAAe,GAAW,EAC9C,CAAC,GAAgB,EAAE,SAAS,OAAoB,IAAmB,EACnE,EAAE,MAAM,GAAU,SAAS,MAAiB,EAChD,GAAa,MAAM,IAAK;EAAE,WAAW,EAAY;EAAI,UAAU;EAAI,GAAG,EAAE,CACzE,EACK,CAAC,GAAkB,EAAE,SAAS,OAAc,GAAqB,EAEjE,IAAS,GAAM,aAAa,MAC5B,IAAU,CAAC,GAAI,GAAQ,WAAW,EAAE,CAAE,CAAC,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS,EAC9E,KAA2C,GAAQ,eAAe,EAAE,EAAE,MACzE,MAAM,EAAE,cAAc,GAAa,GACrC,EACK,KAAiB,GAAU,uBAAuB,EAAE,EAAE,MAAM,MAAM,EAAE,WAAW,SAAS,EACxF,KAAa,CAAC,CAAC,KAAgB,EAAa,eAAe,KAE3D,KAAyB,YAAY;AACrC,GAAC,KAAM,CAAC,GAAa,OACzB,EAAK,yCAAyC;GAC5C;GACA,UAAU;GACV,YAAY;GACb,CAAC,GACU,MAAM,EAAiB,EACjC,WAAW;GAAE,UAAU;GAAI,WAAW,EAAY;GAAI,OAAO;GAAK,EACnE,CAAC,EACM,MAAM,wBACZ,EAAO,QAAQ,sBAAsB,uDAAuD,EAC5F,MAAM,GAAc;IAIlB,KAAe,YAAY;AAC1B,QACL,EAAK,oCAAoC;GACvC;GACA,UAAU;GACV,YAAY;GACb,CAAC,GACU,MAAM,EAAO,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,GAAI,EAAE,EAAE,CAAC,EAC5D,MAAM,cACZ,EAAK,8BAA8B;GACjC;GACA,UAAU;GACV,YAAY;GACb,CAAC,EACF,EAAO,QAAQ,YAAY,uBAAuB,GAAQ,MAAM,IAAI,EACpE,MAAM,GAAS;IAIb,KAAiB,YAAY;AACjC,MAAI,CAAC,EAAc;EACnB,IAAM,IAAgB,KAAK,IAAI,EAAQ,QAAQ,EAAa,mBAAmB,EAAE,EAC3E,IAAM,EAAQ,SAAS,KAAK,MAAO,IAAgB,EAAQ,SAAU,IAAI,GAAG;AAMlF,GALY,MAAM,EAAe,EAC/B,WAAW,EACT,OAAO;GAAE,cAAc,EAAa;GAAI,kBAAkB;GAAe,aAAa;GAAK,EAC5F,EACF,CAAC,EACM,MAAM,sBACZ,EAAK,sCAAsC;GACzC;GACA,UAAU,EAAa;GACvB,YAAY;GACZ,aAAa;GACd,CAAC,EACF,EAAO,QAAQ,kBAAkB,GAAG,EAAI,YAAY,EACpD,MAAM,GAAS;;AAInB,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,GAAD;EAAY,OAAM;EAAS,UAAS;YAApC,CACE,kBAAC,GAAD,EAAU,WAAU,gCAAiC,CAAA,EACrD,kBAAC,GAAD,EAAU,WAAU,0BAA2B,CAAA,CACpC;;AAIjB,KAAI,KAAS,CAAC,EACZ,QACE,kBAAC,GAAD;EAAY,OAAM;EAAS,UAAS;YACjC,IACC,kBAAC,GAAD;GAAW,OAAM;GAA8B;GAAS,CAAA,GAExD,kBAAC,IAAD;GACE,OAAM;GACN,aAAY;GACZ,CAAA;EAEO,CAAA;CAIjB,IAAM,IAAmB,GAAc,oBAAoB,GACrD,IAAY,KAAK,IAAI,GAAG,EAAQ,SAAS,EAAiB,EAC1D,KACJ,KAAgB,EAAa,eAAe,MACxC,oDACA,MAAc,IACZ,mCACA,GAAG,EAAU,+BACf,KAAW,GAAG,EAAO,SAAS,GAAG,GAAG,EAAO;AAEjD,QACE,kBAAC,GAAD;EACE,UAAS;EACT,OAAO,EAAO;EACd,aAAa,EAAO,WAAW,KAAA;EAC/B,SACE,IACE,kBAAC,GAAD;GACE,SAAS;GACT,UAAU,KAAkB,KAAoB,EAAQ;aAF1D,CAIE,kBAAC,GAAD,EAAc,MAAM,IAAM,CAAA,EACzB,KAAoB,EAAQ,SAAS,cAAc,uBAC7C;OAET,kBAAC,GAAD;GAAQ,SAAS;GAAc,UAAU;aAAzC;IACE,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;;IAAE,IAAY,eAAe;IAC5C;;YAhBf;GAqBE,kBAAC,GAAD;IACE,QAAO;IACP,MAAM;IACN,MAAM,EAAU,EAAO,SAAS,EAAO,MAAM;IAC7C,WAAU;cAEV,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAO,SAAS;OACZ,CAAA;MACP,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAO;OACL,CAAA;MACL,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAW,MAAM;mBAAa,EAAa,EAAO,MAAM;SAAa,CAAA;QACrE,kBAAC,GAAD;SAAW,MAAM;mBAAe,EAAa,EAAO,OAAO;SAAa,CAAA;QACvE,EAAO,QAAQ,kBAAC,GAAD;SAAW,MAAM;mBAAS,EAAO;SAAkB,CAAA,GAAG;QAClE;;MACF;;IACK,CAAA;GAEZ,EAAO,KAAK,SACX,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAO,KAAK,KAAK,MAChB,kBAAC,GAAD;KAAe,SAAQ;eACpB;KACK,EAFI,EAEJ,CACR;IACE,CAAA,GACJ;GAEJ,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAU,OAAM;MAAU,OAAO,EAAQ;MAAQ,MAAM;MAAU,MAAK;MAAS,CAAA;KAC/E,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAe,EAAO,iBAAiB;MAC9C,MAAM;MACN,MAAK;MACL,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAO,aAAa,UAAU;MACrC,MAAM;MACN,MAAK;MACL,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,IAAe,GAAG,EAAa,YAAY,KAAK;MACvD,MAAM;MACN,MAAK;MACL,CAAA;KACE;;GAEL,IACC,kBAAC,GAAD;IAAW,WAAU;cAArB;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,QAAD;QAAM,WAAU;kBACd,kBAAC,GAAD,EAAY,MAAM,IAAM,CAAA;QACnB,CAAA;OACP,kBAAC,QAAD;QAAM,WAAU;kBAA0C;QAAoB,CAAA;OAC9E,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACG;SAAiB;SAAE,EAAQ;SAAO;SAAY,EAAa;SAAY;SACnE;;OACH;;KACN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OACE,WAAU;OACV,OAAO,EAAE,OAAO,GAAG,EAAa,YAAY,IAAI;OAChD,CAAA;MACE,CAAA;KACN,kBAAC,KAAD;MAAG,WAAU;gBAAgC;MAAc,CAAA;KACjD;QACV;GAEH,IACC,kBAAC,GAAD;IAAW,WAAU;cAArB;KACE,kBAAC,QAAD;MAAM,WAAU;gBACd,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;MACd,CAAA;KACP,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAA0C;OAAwB,CAAA,EACjF,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAAyC;QAC/B,EAAc;QAAO;QAAU,EAAc;QACjD;SACF;;KACN,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,GAAD,EAAa,MAAM,IAAM,CAAA,EAAA,YACpB;;KACG;QACV,KACF,kBAAC,GAAD;IAAW,WAAU;cAArB,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBACd,kBAAC,IAAD,EAAQ,MAAM,IAAM,CAAA;MACf,CAAA,EACP,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;gBAA4C;MAErD,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAA0B;MAEnC,CAAA,CACF,EAAA,CAAA,CACF;QACN,kBAAC,GAAD;KAAQ,SAAS;KAAwB,UAAU;eAAnD;MACE,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;;MAAE,IAAW,aAAa;MACtC;OACC;QACV;GAEJ,kBAAC,GAAD;IAAW,WAAU;cAArB,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAAd,CACE,kBAAC,GAAD;OAAU,MAAM;OAAI,WAAU;OAAwB,CAAA,EAAA,cACnD;SACJ,EAAQ,SACP,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACG;OAAiB;OAAE,EAAQ;OAAO;OAC9B;UACL,KACA;QACL,EAAQ,WAAW,IAClB,kBAAC,KAAD;KAAG,WAAU;eAA2C;KAEpD,CAAA,GAEJ,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAQ,KAAK,GAAQ,MACpB,kBAAC,GAAD;MAAmC;MAAQ,OAAO;MAAG,MAAM,IAAI;MAAoB,EAAnE,EAAO,GAA4D,CACnF;KACE,CAAA,CAEE;;GACD"}
1
+ {"version":3,"file":"CoursesCourseDetailsPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesCourseDetailsPagePage.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { useEffect } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport {\n Award,\n BookOpen,\n CheckCircle2,\n Clock,\n Eye,\n FileText,\n Lock,\n PlayCircle,\n Layers,\n ShieldCheck,\n Sparkles,\n Trophy,\n TrendingUp,\n Users,\n type LucideIcon,\n} from 'lucide-react';\nimport { Badge, Button, GlassCard, Skeleton } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n MediaCover,\n PageLayout,\n StatCard,\n StatusBadge,\n TagStrip,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n useCertificatesQuery,\n useCourseQuery,\n useEnroll,\n useIssueCertificate,\n useUpdateProgress,\n type TecLesson,\n type TecEnrollment,\n} from '../api/technospamApi';\nimport { formatDuration, humanizeEnum } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { cn } from '../utils/cn';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\n/** Per-lesson-kind icon + on-theme tint (chip) and pill (badge) classes. */\nconst KIND_META: Record<string, { icon: LucideIcon; chip: string; badge: string }> = {\n VIDEO: {\n icon: PlayCircle,\n chip: 'bg-accent-primary/15 text-accent-primary',\n badge: 'border-accent-primary/25 bg-accent-primary/10 text-accent-primary',\n },\n ARTICLE: {\n icon: FileText,\n chip: 'bg-accent-indigo/15 text-accent-indigo',\n badge: 'border-accent-indigo/25 bg-accent-indigo/10 text-accent-indigo',\n },\n INTERACTIVE: {\n icon: Sparkles,\n chip: 'bg-accent-teal/15 text-accent-teal',\n badge: 'border-accent-teal/25 bg-accent-teal/10 text-accent-teal',\n },\n QUIZ: {\n icon: CheckCircle2,\n chip: 'bg-accent-purple/15 text-accent-purple',\n badge: 'border-accent-purple/25 bg-accent-purple/10 text-accent-purple',\n },\n PROJECT: {\n icon: Layers,\n chip: 'bg-status-warning-bg-subtle text-status-warning-text',\n badge: 'border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text',\n },\n};\n\nfunction HeroBadge({ icon: Icon, children }: { icon?: LucideIcon; children: ReactNode }) {\n return (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-white/15 px-2.5 py-1 text-xs font-medium text-white ring-1 ring-white/25 backdrop-blur-sm\">\n {Icon ? <Icon size={12} /> : null}\n {children}\n </span>\n );\n}\n\nfunction LessonRow({ lesson, index, done }: { lesson: TecLesson; index: number; done: boolean }) {\n const meta = KIND_META[lesson.kind] ?? KIND_META.ARTICLE!;\n const Icon = meta.icon;\n return (\n <div className=\"group flex items-center gap-3 rounded-xl px-2 py-3 transition-colors hover:bg-bg-sunken/60\">\n <span className=\"hidden w-6 shrink-0 text-center text-sm font-medium tabular-nums text-text-muted sm:block\">\n {index + 1}\n </span>\n <span\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg',\n done ? 'bg-status-success-bg-subtle text-status-success-text' : meta.chip\n )}\n >\n {done ? <CheckCircle2 size={17} /> : <Icon size={17} />}\n </span>\n <div className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">{lesson.title}</span>\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n <span\n className={cn(\n 'inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] font-medium',\n meta.badge\n )}\n >\n {humanizeEnum(lesson.kind)}\n </span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Clock size={12} /> {formatDuration(lesson.estimatedMinutes)}\n </span>\n </div>\n </div>\n <div className=\"shrink-0\">\n {done ? (\n <CheckCircle2 size={18} className=\"text-status-success-text\" />\n ) : lesson.isPreview ? (\n <span className=\"inline-flex items-center gap-1 rounded-full border border-accent-primary/30 bg-accent-primary/10 px-2 py-0.5 text-[11px] font-medium text-accent-primary\">\n <Eye size={12} /> Preview\n </span>\n ) : (\n <Lock size={15} className=\"text-text-muted\" />\n )}\n </div>\n </div>\n );\n}\n\nexport function CoursesCourseDetailsPagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n const { currentUser, workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const { data, loading, error, refetch } = useCourseQuery(id);\n\n useEffect(() => {\n if (id) {\n emit('technospam.course.viewed', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n }\n }, [id, workspaceId, emit]);\n\n const [enroll, { loading: enrolling }] = useEnroll();\n const [updateProgress, { loading: savingProgress }] = useUpdateProgress();\n const { data: certData, refetch: refetchCerts } = useCertificatesQuery(\n currentUser?.id && id ? { learnerId: currentUser.id, courseId: id } : {}\n );\n const [issueCertificate, { loading: claiming }] = useIssueCertificate();\n\n const course = data?.tecCourse ?? null;\n const lessons = [...(course?.lessons ?? [])].sort((a, b) => a.position - b.position);\n const myEnrollment: TecEnrollment | undefined = (course?.enrollments ?? []).find(\n (e) => e.learnerId === currentUser?.id\n );\n const myCertificate = (certData?.tecListCertificates ?? []).find((c) => c.status === 'ISSUED');\n const isComplete = !!myEnrollment && myEnrollment.progressPct >= 100;\n\n const handleClaimCertificate = async () => {\n if (!id || !currentUser?.id) return;\n emit('technospam.course.certificate_claimed', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n const res = await issueCertificate({\n variables: { courseId: id, learnerId: currentUser.id, score: 100 },\n });\n if (res.data?.tecIssueCertificate) {\n notify.success('Certificate issued', 'Your verifiable certificate is ready in My Learning.');\n await refetchCerts();\n }\n };\n\n const handleEnroll = async () => {\n if (!id) return;\n emit('technospam.course.enroll_clicked', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n const res = await enroll({ variables: { input: { courseId: id } } });\n if (res.data?.tecEnroll) {\n emit('technospam.course.enrolled', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n notify.success('Enrolled', `You're enrolled in \"${course?.title}\".`);\n await refetch();\n }\n };\n\n const handleProgress = async () => {\n if (!myEnrollment) return;\n const nextCompleted = Math.min(lessons.length, myEnrollment.lessonsCompleted + 1);\n const pct = lessons.length ? Math.round((nextCompleted / lessons.length) * 100) : 100;\n const res = await updateProgress({\n variables: {\n input: { enrollmentId: myEnrollment.id, lessonsCompleted: nextCompleted, progressPct: pct },\n },\n });\n if (res.data?.tecUpdateProgress) {\n emit('technospam.course.progress_updated', {\n workspaceId,\n entityId: myEnrollment.id,\n entityType: 'TEC_ENROLLMENT',\n progressPct: pct,\n });\n notify.success('Progress saved', `${pct}% complete`);\n await refetch();\n }\n };\n\n if (loading && !course) {\n return (\n <PageLayout title=\"Course\" backPath=\"/courses/courses-page\">\n <Skeleton className=\"mb-4 h-40 w-full rounded-2xl\" />\n <Skeleton className=\"h-64 w-full rounded-xl\" />\n </PageLayout>\n );\n }\n\n if (error || !course) {\n return (\n <PageLayout title=\"Course\" backPath=\"/courses/courses-page\">\n {error ? (\n <ErrorView title=\"Couldn't load course\" error={error} />\n ) : (\n <EmptyView\n title=\"Course not found\"\n description=\"This course may have been unpublished.\"\n />\n )}\n </PageLayout>\n );\n }\n\n const completedLessons = myEnrollment?.lessonsCompleted ?? 0;\n const remaining = Math.max(0, lessons.length - completedLessons);\n const milestone =\n myEnrollment && myEnrollment.progressPct >= 100\n ? 'Course complete — claim your certificate below.'\n : remaining === 1\n ? '1 lesson to go — almost there!'\n : `${remaining} lessons to go — keep it up!`;\n const heroSeed = `${course.topic ?? ''} ${course.title}`;\n\n return (\n <PageLayout\n backPath=\"/courses/courses-page\"\n title={course.title}\n description={course.summary ?? undefined}\n actions={\n myEnrollment ? (\n <Button\n onClick={handleProgress}\n disabled={savingProgress || completedLessons >= lessons.length}\n >\n <CheckCircle2 size={16} />\n {completedLessons >= lessons.length ? 'Completed' : 'Complete next lesson'}\n </Button>\n ) : (\n <Button onClick={handleEnroll} disabled={enrolling}>\n <BookOpen size={16} /> {enrolling ? 'Enrolling…' : 'Enroll'}\n </Button>\n )\n }\n >\n {/* Bold hero banner */}\n <MediaCover\n height=\"h-40\"\n seed={heroSeed}\n icon={topicIcon(course.topic ?? course.title)}\n className=\"mb-5 rounded-2xl\"\n >\n <div className=\"flex w-full flex-col gap-2\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-white/80\">\n {course.topic ?? 'Course'}\n </span>\n <h2 className=\"text-2xl font-bold leading-tight text-white drop-shadow-sm sm:text-3xl\">\n {course.title}\n </h2>\n <div className=\"flex flex-wrap items-center gap-2\">\n <HeroBadge icon={TrendingUp}>{humanizeEnum(course.level)}</HeroBadge>\n <HeroBadge icon={CheckCircle2}>{humanizeEnum(course.status)}</HeroBadge>\n {course.topic ? <HeroBadge icon={Layers}>{course.topic}</HeroBadge> : null}\n </div>\n </div>\n </MediaCover>\n\n <TagStrip tags={course.tags} className=\"mb-5\" />\n\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard label=\"Lessons\" value={lessons.length} icon={FileText} tone=\"blue\" />\n <StatCard\n label=\"Duration\"\n value={formatDuration(course.estimatedMinutes)}\n icon={Clock}\n tone=\"teal\"\n />\n <StatCard\n label=\"Enrolled\"\n value={course.enrollments?.length ?? 0}\n icon={Users}\n tone=\"indigo\"\n />\n <StatCard\n label=\"Your progress\"\n value={myEnrollment ? `${myEnrollment.progressPct}%` : 'Not started'}\n icon={TrendingUp}\n tone=\"purple\"\n />\n </div>\n\n {myEnrollment ? (\n <GlassCard className=\"mb-6 p-5\">\n <div className=\"mb-3 flex items-center gap-2\">\n <span className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-accent-primary/15 text-accent-primary\">\n <TrendingUp size={16} />\n </span>\n <span className=\"text-sm font-semibold text-text-primary\">Your progress</span>\n <span className=\"ml-auto text-sm text-text-secondary\">\n {completedLessons}/{lessons.length} lessons · {myEnrollment.progressPct}%\n </span>\n </div>\n <div className=\"h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-gradient-to-r from-accent-primary via-accent-indigo to-accent-purple transition-all duration-500\"\n style={{ width: `${myEnrollment.progressPct}%` }}\n />\n </div>\n <p className=\"mt-2 text-xs text-text-muted\">{milestone}</p>\n </GlassCard>\n ) : null}\n\n {myCertificate ? (\n <GlassCard className=\"mb-6 flex flex-wrap items-center gap-3 border border-status-success-border/60 bg-status-success-bg-subtle/30 p-4\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-status-warning-bg-subtle text-status-warning-text ring-1 ring-status-warning-border/30\">\n <Award size={22} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-sm font-semibold text-text-primary\">Certificate earned</div>\n <div className=\"text-xs text-text-muted\">\n Serial {myCertificate.serial} · Score {myCertificate.score}\n </div>\n </div>\n <span className=\"inline-flex items-center gap-1 rounded-full border border-status-success-border bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text\">\n <ShieldCheck size={13} /> Verified\n </span>\n </GlassCard>\n ) : isComplete ? (\n <GlassCard className=\"mb-6 flex flex-col items-start gap-4 border border-status-success-border/60 bg-status-success-bg-subtle/40 p-5 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-4\">\n <span className=\"flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-status-warning-bg-subtle text-status-warning-text ring-1 ring-status-warning-border/40\">\n <Trophy size={26} />\n </span>\n <div>\n <div className=\"text-base font-semibold text-text-primary\">\n You finished this course\n </div>\n <div className=\"text-sm text-text-muted\">\n Claim your verifiable certificate of completion.\n </div>\n </div>\n </div>\n <Button onClick={handleClaimCertificate} disabled={claiming}>\n <Award size={16} /> {claiming ? 'Issuing…' : 'Claim certificate'}\n </Button>\n </GlassCard>\n ) : null}\n\n <GlassCard className=\"p-5\">\n <div className=\"mb-3 flex items-center justify-between gap-2\">\n <h2 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <BookOpen size={18} className=\"text-accent-primary\" /> Curriculum\n </h2>\n {lessons.length ? (\n <span className=\"rounded-full bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text\">\n {completedLessons}/{lessons.length} completed\n </span>\n ) : null}\n </div>\n {lessons.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-muted\">\n Lessons for this course are being prepared.\n </p>\n ) : (\n <div className=\"flex flex-col\">\n {lessons.map((lesson, i) => (\n <LessonRow key={lesson.id} lesson={lesson} index={i} done={i < completedLessons} />\n ))}\n </div>\n )}\n </GlassCard>\n </PageLayout>\n );\n}\n"],"mappings":"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"}