@burdenoff/microfe-technospam 2026.704.2 → 2026.705.2

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 (40) hide show
  1. package/dist/technospam/api/technospamApi.js +233 -150
  2. package/dist/technospam/api/technospamApi.js.map +1 -1
  3. package/dist/technospam/components/LoadMore.js +18 -2
  4. package/dist/technospam/components/LoadMore.js.map +1 -0
  5. package/dist/technospam/components/MediaCover.js +57 -0
  6. package/dist/technospam/components/MediaCover.js.map +1 -0
  7. package/dist/technospam/components/PageLayout.js +1 -1
  8. package/dist/technospam/components/PageLayout.js.map +1 -1
  9. package/dist/technospam/components/StatCard.js +19 -11
  10. package/dist/technospam/components/StatCard.js.map +1 -1
  11. package/dist/technospam/components/StatusBadge.js +1 -1
  12. package/dist/technospam/components/StatusBadge.js.map +1 -1
  13. package/dist/technospam/components/index.js +1 -0
  14. package/dist/technospam/pages/AdminAdminDashboardPagePage.js +748 -352
  15. package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
  16. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js +167 -127
  17. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  18. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +178 -125
  19. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
  20. package/dist/technospam/pages/ApplicationsProblemsPagePage.js +89 -93
  21. package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
  22. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +295 -141
  23. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
  24. package/dist/technospam/pages/CoursesCoursesPagePage.js +86 -86
  25. package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
  26. package/dist/technospam/pages/CoursesMyLearningPagePage.js +122 -88
  27. package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
  28. package/dist/technospam/pages/DashboardDashboardPagePage.js +232 -199
  29. package/dist/technospam/pages/DashboardDashboardPagePage.js.map +1 -1
  30. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +128 -106
  31. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
  32. package/dist/technospam/pages/HackathonsHackathonsPagePage.js +89 -73
  33. package/dist/technospam/pages/HackathonsHackathonsPagePage.js.map +1 -1
  34. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +171 -115
  35. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
  36. package/dist/technospam/pages/MentorshipMentorsPagePage.js +214 -151
  37. package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
  38. package/dist/technospam/pages/MentorshipSessionsPagePage.js +241 -116
  39. package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
  40. package/package.json +1 -1
@@ -1,200 +1,354 @@
1
1
  import { useTechnoSpam as e } from "../providers/TechnoSpamProvider.js";
2
- import { PageLayout as t } from "../components/PageLayout.js";
3
- import { StatCard as n } from "../components/StatCard.js";
4
- import { formatDuration as r, humanizeEnum as i } from "../utils/format.js";
5
- import { StatusBadge as a } from "../components/StatusBadge.js";
6
- import { EmptyView as o, ErrorView as s } from "../components/StateViews.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 c, ErrorView as l } from "../components/StateViews.js";
7
8
  import "../components/index.js";
8
- import { notify as c } from "../lib/toast.js";
9
- import { useCourseQuery as l, useEnroll as u, useUpdateProgress as d } from "../api/technospamApi.js";
10
- import { BookOpen as f, CheckCircle2 as p, Clock as m, FileText as h, Layers as g, Lock as _, PlayCircle as v, Sparkles as y } from "lucide-react";
11
- import { useSearchParams as b } from "react-router-dom";
12
- import { jsx as x, jsxs as S } from "react/jsx-runtime";
13
- import { Badge as C, Button as w, GlassCard as T, Skeleton as E } from "@burdenoff/fe-libs/ui";
9
+ import { notify as u } from "../lib/toast.js";
10
+ import { useCertificatesQuery as d, useCourseQuery as f, useEnroll as p, useIssueCertificate as m, useUpdateProgress as ee } from "../api/technospamApi.js";
11
+ import { Award as h, BookOpen as g, CheckCircle2 as _, Clock as v, Eye as y, FileText as b, Layers as x, Lock as S, PlayCircle as C, ShieldCheck as te, Sparkles as w, TrendingUp as T, Trophy as E, Users as D } from "lucide-react";
12
+ import { useSearchParams as O } from "react-router-dom";
13
+ import { jsx as k, jsxs as A } from "react/jsx-runtime";
14
+ import { Badge as j, Button as M, GlassCard as N, Skeleton as P } from "@burdenoff/fe-libs/ui";
14
15
  //#region src/technospam/pages/CoursesCourseDetailsPagePage.tsx
15
- var D = {
16
- VIDEO: v,
17
- ARTICLE: h,
18
- INTERACTIVE: y,
19
- QUIZ: p,
20
- PROJECT: g
16
+ var F = {
17
+ VIDEO: {
18
+ icon: C,
19
+ chip: "bg-accent-primary/15 text-accent-primary",
20
+ badge: "border-accent-primary/25 bg-accent-primary/10 text-accent-primary"
21
+ },
22
+ ARTICLE: {
23
+ icon: b,
24
+ chip: "bg-accent-indigo/15 text-accent-indigo",
25
+ badge: "border-accent-indigo/25 bg-accent-indigo/10 text-accent-indigo"
26
+ },
27
+ INTERACTIVE: {
28
+ icon: w,
29
+ chip: "bg-accent-teal/15 text-accent-teal",
30
+ badge: "border-accent-teal/25 bg-accent-teal/10 text-accent-teal"
31
+ },
32
+ QUIZ: {
33
+ icon: _,
34
+ chip: "bg-accent-purple/15 text-accent-purple",
35
+ badge: "border-accent-purple/25 bg-accent-purple/10 text-accent-purple"
36
+ },
37
+ PROJECT: {
38
+ icon: x,
39
+ chip: "bg-status-warning-bg-subtle text-status-warning-text",
40
+ badge: "border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text"
41
+ }
21
42
  };
22
- function O({ lesson: e, done: t }) {
23
- let n = D[e.kind] ?? h;
24
- return /* @__PURE__ */ S("div", {
25
- className: "flex items-center gap-3 border-b border-border-seam py-3 last:border-0",
43
+ function I({ icon: e, children: t }) {
44
+ return /* @__PURE__ */ A("span", {
45
+ 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",
46
+ children: [e ? /* @__PURE__ */ k(e, { size: 12 }) : null, t]
47
+ });
48
+ }
49
+ function L({ lesson: e, index: n, done: r }) {
50
+ let i = F[e.kind] ?? F.ARTICLE, a = i.icon;
51
+ return /* @__PURE__ */ A("div", {
52
+ className: "group flex items-center gap-3 rounded-xl px-2 py-3 transition-colors hover:bg-bg-sunken/60",
26
53
  children: [
27
- /* @__PURE__ */ x("span", {
28
- className: `flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${t ? "bg-status-success-bg-subtle text-status-success-text" : "bg-bg-sunken text-text-secondary"}`,
29
- children: x(t ? p : n, { size: 16 })
54
+ /* @__PURE__ */ k("span", {
55
+ className: "hidden w-6 shrink-0 text-center text-sm font-medium tabular-nums text-text-muted sm:block",
56
+ children: n + 1
57
+ }),
58
+ /* @__PURE__ */ k("span", {
59
+ 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),
60
+ children: k(r ? _ : a, { size: 17 })
30
61
  }),
31
- /* @__PURE__ */ S("div", {
62
+ /* @__PURE__ */ A("div", {
32
63
  className: "min-w-0 flex-1",
33
- children: [/* @__PURE__ */ S("div", {
34
- className: "flex items-center gap-2",
35
- children: [/* @__PURE__ */ x("span", {
36
- className: "truncate text-sm font-medium text-text-primary",
37
- children: e.title
38
- }), e.isPreview ? /* @__PURE__ */ x(C, {
39
- variant: "secondary",
40
- className: "text-[11px]",
41
- children: "Preview"
42
- }) : null]
43
- }), /* @__PURE__ */ S("div", {
44
- className: "text-xs text-text-muted",
45
- children: [
46
- i(e.kind),
47
- " · ",
48
- r(e.estimatedMinutes)
49
- ]
64
+ children: [/* @__PURE__ */ k("span", {
65
+ className: "block truncate text-sm font-medium text-text-primary",
66
+ children: e.title
67
+ }), /* @__PURE__ */ A("div", {
68
+ className: "mt-1 flex flex-wrap items-center gap-2",
69
+ children: [/* @__PURE__ */ k("span", {
70
+ className: t("inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] font-medium", i.badge),
71
+ children: s(e.kind)
72
+ }), /* @__PURE__ */ A("span", {
73
+ className: "inline-flex items-center gap-1 text-xs text-text-muted",
74
+ children: [
75
+ /* @__PURE__ */ k(v, { size: 12 }),
76
+ " ",
77
+ o(e.estimatedMinutes)
78
+ ]
79
+ })]
50
80
  })]
51
81
  }),
52
- !e.isPreview && !t ? /* @__PURE__ */ x(_, {
53
- size: 14,
54
- className: "text-text-muted"
55
- }) : null
82
+ /* @__PURE__ */ k("div", {
83
+ className: "shrink-0",
84
+ children: r ? /* @__PURE__ */ k(_, {
85
+ size: 18,
86
+ className: "text-status-success-text"
87
+ }) : e.isPreview ? /* @__PURE__ */ A("span", {
88
+ 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",
89
+ children: [/* @__PURE__ */ k(y, { size: 12 }), " Preview"]
90
+ }) : /* @__PURE__ */ k(S, {
91
+ size: 15,
92
+ className: "text-text-muted"
93
+ })
94
+ })
56
95
  ]
57
96
  });
58
97
  }
59
- function k() {
60
- let [i] = b(), _ = i.get("id") ?? "", { currentUser: v } = e(), { data: y, loading: D, error: k, refetch: A } = l(_), [j, { loading: M }] = u(), [N, { loading: P }] = d(), F = y?.tecCourse ?? null, I = [...F?.lessons ?? []].sort((e, t) => e.position - t.position), L = (F?.enrollments ?? []).find((e) => e.learnerId === v?.id), R = async () => {
61
- _ && (await j({ variables: { input: { courseId: _ } } })).data?.tecEnroll && (c.success("Enrolled", `You're enrolled in "${F?.title}".`), await A());
62
- }, z = async () => {
63
- if (!L) return;
64
- let e = Math.min(I.length, L.lessonsCompleted + 1), t = I.length ? Math.round(e / I.length * 100) : 100;
65
- (await N({ variables: { input: {
66
- enrollmentId: L.id,
98
+ function R() {
99
+ let [t] = O(), y = t.get("id") ?? "", { currentUser: S } = e(), { data: C, loading: w, error: F, refetch: R } = f(y), [z, { loading: B }] = p(), [V, { loading: H }] = ee(), { data: U, refetch: W } = d(S?.id && y ? {
100
+ learnerId: S.id,
101
+ courseId: y
102
+ } : {}), [G, { loading: K }] = m(), q = C?.tecCourse ?? null, J = [...q?.lessons ?? []].sort((e, t) => e.position - t.position), Y = (q?.enrollments ?? []).find((e) => e.learnerId === S?.id), X = (U?.tecListCertificates ?? []).find((e) => e.status === "ISSUED"), Z = !!Y && Y.progressPct >= 100, ne = async () => {
103
+ !y || !S?.id || (await G({ variables: {
104
+ courseId: y,
105
+ learnerId: S.id,
106
+ score: 100
107
+ } })).data?.tecIssueCertificate && (u.success("Certificate issued", "Your verifiable certificate is ready in My Learning."), await W());
108
+ }, re = async () => {
109
+ y && (await z({ variables: { input: { courseId: y } } })).data?.tecEnroll && (u.success("Enrolled", `You're enrolled in "${q?.title}".`), await R());
110
+ }, ie = async () => {
111
+ if (!Y) return;
112
+ let e = Math.min(J.length, Y.lessonsCompleted + 1), t = J.length ? Math.round(e / J.length * 100) : 100;
113
+ (await V({ variables: { input: {
114
+ enrollmentId: Y.id,
67
115
  lessonsCompleted: e,
68
116
  progressPct: t
69
- } } })).data?.tecUpdateProgress && (c.success("Progress saved", `${t}% complete`), await A());
117
+ } } })).data?.tecUpdateProgress && (u.success("Progress saved", `${t}% complete`), await R());
70
118
  };
71
- if (D && !F) return /* @__PURE__ */ S(t, {
119
+ if (w && !q) return /* @__PURE__ */ A(n, {
72
120
  title: "Course",
73
121
  backPath: "/courses/courses-page",
74
- children: [/* @__PURE__ */ x(E, { className: "mb-4 h-40 w-full rounded-xl" }), /* @__PURE__ */ x(E, { className: "h-64 w-full rounded-xl" })]
122
+ children: [/* @__PURE__ */ k(P, { className: "mb-4 h-40 w-full rounded-2xl" }), /* @__PURE__ */ k(P, { className: "h-64 w-full rounded-xl" })]
75
123
  });
76
- if (k || !F) return /* @__PURE__ */ x(t, {
124
+ if (F || !q) return /* @__PURE__ */ k(n, {
77
125
  title: "Course",
78
126
  backPath: "/courses/courses-page",
79
- children: k ? /* @__PURE__ */ x(s, {
127
+ children: F ? /* @__PURE__ */ k(l, {
80
128
  title: "Couldn't load course",
81
- error: k
82
- }) : /* @__PURE__ */ x(o, {
129
+ error: F
130
+ }) : /* @__PURE__ */ k(c, {
83
131
  title: "Course not found",
84
132
  description: "This course may have been unpublished."
85
133
  })
86
134
  });
87
- let B = L?.lessonsCompleted ?? 0;
88
- return /* @__PURE__ */ S(t, {
89
- hero: !0,
135
+ let Q = Y?.lessonsCompleted ?? 0, $ = Math.max(0, J.length - Q), ae = Y && Y.progressPct >= 100 ? "Course complete — claim your certificate below." : $ === 1 ? "1 lesson to go — almost there!" : `${$} lessons to go — keep it up!`, oe = `${q.topic ?? ""} ${q.title}`;
136
+ return /* @__PURE__ */ A(n, {
90
137
  backPath: "/courses/courses-page",
91
- title: F.title,
92
- description: F.summary ?? void 0,
93
- actions: L ? /* @__PURE__ */ S(w, {
94
- onClick: z,
95
- disabled: P || B >= I.length,
96
- children: [/* @__PURE__ */ x(p, { size: 16 }), B >= I.length ? "Completed" : "Complete next lesson"]
97
- }) : /* @__PURE__ */ S(w, {
98
- onClick: R,
99
- disabled: M,
138
+ title: q.title,
139
+ description: q.summary ?? void 0,
140
+ actions: Y ? /* @__PURE__ */ A(M, {
141
+ onClick: ie,
142
+ disabled: H || Q >= J.length,
143
+ children: [/* @__PURE__ */ k(_, { size: 16 }), Q >= J.length ? "Completed" : "Complete next lesson"]
144
+ }) : /* @__PURE__ */ A(M, {
145
+ onClick: re,
146
+ disabled: B,
100
147
  children: [
101
- /* @__PURE__ */ x(f, { size: 16 }),
148
+ /* @__PURE__ */ k(g, { size: 16 }),
102
149
  " ",
103
- M ? "Enrolling…" : "Enroll"
150
+ B ? "Enrolling…" : "Enroll"
104
151
  ]
105
152
  }),
106
153
  children: [
107
- /* @__PURE__ */ S("div", {
108
- className: "mb-5 flex flex-wrap items-center gap-2",
109
- children: [
110
- /* @__PURE__ */ x(a, {
111
- status: F.level,
112
- kind: "level"
113
- }),
114
- /* @__PURE__ */ x(a, { status: F.status }),
115
- F.topic ? /* @__PURE__ */ S(C, {
116
- variant: "outline",
117
- className: "gap-1",
118
- children: [
119
- /* @__PURE__ */ x(g, { size: 12 }),
120
- " ",
121
- F.topic
122
- ]
123
- }) : null,
124
- F.tags.map((e) => /* @__PURE__ */ x(C, {
125
- variant: "secondary",
126
- children: e
127
- }, e))
128
- ]
154
+ /* @__PURE__ */ k(i, {
155
+ height: "h-40",
156
+ seed: oe,
157
+ icon: a(q.topic ?? q.title),
158
+ className: "mb-5 rounded-2xl",
159
+ children: /* @__PURE__ */ A("div", {
160
+ className: "flex w-full flex-col gap-2",
161
+ children: [
162
+ /* @__PURE__ */ k("span", {
163
+ className: "text-xs font-medium uppercase tracking-wide text-white/80",
164
+ children: q.topic ?? "Course"
165
+ }),
166
+ /* @__PURE__ */ k("h2", {
167
+ className: "text-2xl font-bold leading-tight text-white drop-shadow-sm sm:text-3xl",
168
+ children: q.title
169
+ }),
170
+ /* @__PURE__ */ A("div", {
171
+ className: "flex flex-wrap items-center gap-2",
172
+ children: [
173
+ /* @__PURE__ */ k(I, {
174
+ icon: T,
175
+ children: s(q.level)
176
+ }),
177
+ /* @__PURE__ */ k(I, {
178
+ icon: _,
179
+ children: s(q.status)
180
+ }),
181
+ q.topic ? /* @__PURE__ */ k(I, {
182
+ icon: x,
183
+ children: q.topic
184
+ }) : null
185
+ ]
186
+ })
187
+ ]
188
+ })
129
189
  }),
130
- /* @__PURE__ */ S("div", {
190
+ q.tags.length ? /* @__PURE__ */ k("div", {
191
+ className: "mb-5 flex flex-wrap items-center gap-2",
192
+ children: q.tags.map((e) => /* @__PURE__ */ k(j, {
193
+ variant: "secondary",
194
+ children: e
195
+ }, e))
196
+ }) : null,
197
+ /* @__PURE__ */ A("div", {
131
198
  className: "mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4",
132
199
  children: [
133
- /* @__PURE__ */ x(n, {
200
+ /* @__PURE__ */ k(r, {
134
201
  label: "Lessons",
135
- value: I.length,
136
- icon: h
202
+ value: J.length,
203
+ icon: b,
204
+ tone: "blue"
137
205
  }),
138
- /* @__PURE__ */ x(n, {
206
+ /* @__PURE__ */ k(r, {
139
207
  label: "Duration",
140
- value: r(F.estimatedMinutes),
141
- icon: m
208
+ value: o(q.estimatedMinutes),
209
+ icon: v,
210
+ tone: "teal"
142
211
  }),
143
- /* @__PURE__ */ x(n, {
212
+ /* @__PURE__ */ k(r, {
144
213
  label: "Enrolled",
145
- value: F.enrollments?.length ?? 0,
146
- icon: f
214
+ value: q.enrollments?.length ?? 0,
215
+ icon: D,
216
+ tone: "indigo"
147
217
  }),
148
- /* @__PURE__ */ x(n, {
218
+ /* @__PURE__ */ k(r, {
149
219
  label: "Your progress",
150
- value: L ? `${L.progressPct}%` : "Not started",
151
- icon: p
220
+ value: Y ? `${Y.progressPct}%` : "Not started",
221
+ icon: T,
222
+ tone: "purple"
152
223
  })
153
224
  ]
154
225
  }),
155
- L ? /* @__PURE__ */ S(T, {
156
- className: "mb-6 p-4",
157
- children: [/* @__PURE__ */ S("div", {
158
- className: "mb-2 flex items-center justify-between text-sm",
159
- children: [/* @__PURE__ */ x("span", {
160
- className: "font-medium text-text-primary",
161
- children: "Your progress"
162
- }), /* @__PURE__ */ S("span", {
163
- className: "text-text-secondary",
226
+ Y ? /* @__PURE__ */ A(N, {
227
+ className: "mb-6 p-5",
228
+ children: [
229
+ /* @__PURE__ */ A("div", {
230
+ className: "mb-3 flex items-center gap-2",
164
231
  children: [
165
- B,
166
- "/",
167
- I.length,
168
- " lessons · ",
169
- L.progressPct,
170
- "%"
232
+ /* @__PURE__ */ k("span", {
233
+ className: "flex h-8 w-8 items-center justify-center rounded-lg bg-accent-primary/15 text-accent-primary",
234
+ children: /* @__PURE__ */ k(T, { size: 16 })
235
+ }),
236
+ /* @__PURE__ */ k("span", {
237
+ className: "text-sm font-semibold text-text-primary",
238
+ children: "Your progress"
239
+ }),
240
+ /* @__PURE__ */ A("span", {
241
+ className: "ml-auto text-sm text-text-secondary",
242
+ children: [
243
+ Q,
244
+ "/",
245
+ J.length,
246
+ " lessons · ",
247
+ Y.progressPct,
248
+ "%"
249
+ ]
250
+ })
171
251
  ]
172
- })]
173
- }), /* @__PURE__ */ x("div", {
174
- className: "h-2 w-full overflow-hidden rounded-full bg-bg-sunken",
175
- children: /* @__PURE__ */ x("div", {
176
- className: "h-full rounded-full bg-accent-primary transition-all",
177
- style: { width: `${L.progressPct}%` }
252
+ }),
253
+ /* @__PURE__ */ k("div", {
254
+ className: "h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken",
255
+ children: /* @__PURE__ */ k("div", {
256
+ className: "h-full rounded-full bg-gradient-to-r from-accent-primary via-accent-indigo to-accent-purple transition-all duration-500",
257
+ style: { width: `${Y.progressPct}%` }
258
+ })
259
+ }),
260
+ /* @__PURE__ */ k("p", {
261
+ className: "mt-2 text-xs text-text-muted",
262
+ children: ae
263
+ })
264
+ ]
265
+ }) : null,
266
+ X ? /* @__PURE__ */ A(N, {
267
+ className: "mb-6 flex flex-wrap items-center gap-3 border border-status-success-border/60 bg-status-success-bg-subtle/30 p-4",
268
+ children: [
269
+ /* @__PURE__ */ k("span", {
270
+ 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",
271
+ children: /* @__PURE__ */ k(h, { size: 22 })
272
+ }),
273
+ /* @__PURE__ */ A("div", {
274
+ className: "min-w-0 flex-1",
275
+ children: [/* @__PURE__ */ k("div", {
276
+ className: "text-sm font-semibold text-text-primary",
277
+ children: "Certificate earned"
278
+ }), /* @__PURE__ */ A("div", {
279
+ className: "text-xs text-text-muted",
280
+ children: [
281
+ "Serial ",
282
+ X.serial,
283
+ " · Score ",
284
+ X.score
285
+ ]
286
+ })]
287
+ }),
288
+ /* @__PURE__ */ A("span", {
289
+ 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",
290
+ children: [/* @__PURE__ */ k(te, { size: 13 }), " Verified"]
178
291
  })
292
+ ]
293
+ }) : Z ? /* @__PURE__ */ A(N, {
294
+ 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",
295
+ children: [/* @__PURE__ */ A("div", {
296
+ className: "flex items-center gap-4",
297
+ children: [/* @__PURE__ */ k("span", {
298
+ 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",
299
+ children: /* @__PURE__ */ k(E, { size: 26 })
300
+ }), /* @__PURE__ */ A("div", { children: [/* @__PURE__ */ k("div", {
301
+ className: "text-base font-semibold text-text-primary",
302
+ children: "You finished this course"
303
+ }), /* @__PURE__ */ k("div", {
304
+ className: "text-sm text-text-muted",
305
+ children: "Claim your verifiable certificate of completion."
306
+ })] })]
307
+ }), /* @__PURE__ */ A(M, {
308
+ onClick: ne,
309
+ disabled: K,
310
+ children: [
311
+ /* @__PURE__ */ k(h, { size: 16 }),
312
+ " ",
313
+ K ? "Issuing…" : "Claim certificate"
314
+ ]
179
315
  })]
180
316
  }) : null,
181
- /* @__PURE__ */ S(T, {
317
+ /* @__PURE__ */ A(N, {
182
318
  className: "p-5",
183
- children: [/* @__PURE__ */ x("h2", {
184
- className: "mb-2 text-base font-semibold text-text-primary",
185
- children: "Curriculum"
186
- }), I.length === 0 ? /* @__PURE__ */ x("p", {
319
+ children: [/* @__PURE__ */ A("div", {
320
+ className: "mb-3 flex items-center justify-between gap-2",
321
+ children: [/* @__PURE__ */ A("h2", {
322
+ className: "flex items-center gap-2 text-base font-semibold text-text-primary",
323
+ children: [/* @__PURE__ */ k(g, {
324
+ size: 18,
325
+ className: "text-accent-primary"
326
+ }), " Curriculum"]
327
+ }), J.length ? /* @__PURE__ */ A("span", {
328
+ className: "rounded-full bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text",
329
+ children: [
330
+ Q,
331
+ "/",
332
+ J.length,
333
+ " completed"
334
+ ]
335
+ }) : null]
336
+ }), J.length === 0 ? /* @__PURE__ */ k("p", {
187
337
  className: "py-6 text-center text-sm text-text-muted",
188
338
  children: "Lessons for this course are being prepared."
189
- }) : /* @__PURE__ */ x("div", { children: I.map((e, t) => /* @__PURE__ */ x(O, {
190
- lesson: e,
191
- done: t < B
192
- }, e.id)) })]
339
+ }) : /* @__PURE__ */ k("div", {
340
+ className: "flex flex-col",
341
+ children: J.map((e, t) => /* @__PURE__ */ k(L, {
342
+ lesson: e,
343
+ index: t,
344
+ done: t < Q
345
+ }, e.id))
346
+ })]
193
347
  })
194
348
  ]
195
349
  });
196
350
  }
197
351
  //#endregion
198
- export { k as CoursesCourseDetailsPagePage };
352
+ export { R as CoursesCourseDetailsPagePage };
199
353
 
200
354
  //# sourceMappingURL=CoursesCourseDetailsPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CoursesCourseDetailsPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesCourseDetailsPagePage.tsx"],"sourcesContent":["import { useSearchParams } from 'react-router-dom';\nimport {\n BookOpen,\n CheckCircle2,\n Clock,\n FileText,\n Lock,\n PlayCircle,\n Layers,\n Sparkles,\n} from 'lucide-react';\nimport { Badge, Button, GlassCard, Skeleton } from '@burdenoff/fe-libs/ui';\nimport { EmptyView, ErrorView, PageLayout, StatCard, StatusBadge } from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n useCourseQuery,\n useEnroll,\n useUpdateProgress,\n type TecLesson,\n type TecEnrollment,\n} from '../api/technospamApi';\nimport { formatDuration, humanizeEnum } from '../utils/format';\nimport { notify } from '../lib/toast';\n\nconst LESSON_ICON: Record<string, typeof PlayCircle> = {\n VIDEO: PlayCircle,\n ARTICLE: FileText,\n INTERACTIVE: Sparkles,\n QUIZ: CheckCircle2,\n PROJECT: Layers,\n};\n\nfunction LessonRow({ lesson, done }: { lesson: TecLesson; done: boolean }) {\n const Icon = LESSON_ICON[lesson.kind] ?? FileText;\n return (\n <div className=\"flex items-center gap-3 border-b border-border-seam py-3 last:border-0\">\n <span\n className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${\n done\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n {done ? <CheckCircle2 size={16} /> : <Icon size={16} />}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"truncate text-sm font-medium text-text-primary\">{lesson.title}</span>\n {lesson.isPreview ? (\n <Badge variant=\"secondary\" className=\"text-[11px]\">\n Preview\n </Badge>\n ) : null}\n </div>\n <div className=\"text-xs text-text-muted\">\n {humanizeEnum(lesson.kind)} · {formatDuration(lesson.estimatedMinutes)}\n </div>\n </div>\n {!lesson.isPreview && !done ? <Lock size={14} className=\"text-text-muted\" /> : null}\n </div>\n );\n}\n\nexport function CoursesCourseDetailsPagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n const { currentUser } = useTechnoSpam();\n const { data, loading, error, refetch } = useCourseQuery(id);\n const [enroll, { loading: enrolling }] = useEnroll();\n const [updateProgress, { loading: savingProgress }] = useUpdateProgress();\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\n const handleEnroll = async () => {\n if (!id) return;\n const res = await enroll({ variables: { input: { courseId: id } } });\n if (res.data?.tecEnroll) {\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 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-xl\" />\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\n return (\n <PageLayout\n hero\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 <div className=\"mb-5 flex flex-wrap items-center gap-2\">\n <StatusBadge status={course.level} kind=\"level\" />\n <StatusBadge status={course.status} />\n {course.topic ? (\n <Badge variant=\"outline\" className=\"gap-1\">\n <Layers size={12} /> {course.topic}\n </Badge>\n ) : null}\n {course.tags.map((t) => (\n <Badge key={t} variant=\"secondary\">\n {t}\n </Badge>\n ))}\n </div>\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} />\n <StatCard label=\"Duration\" value={formatDuration(course.estimatedMinutes)} icon={Clock} />\n <StatCard label=\"Enrolled\" value={course.enrollments?.length ?? 0} icon={BookOpen} />\n <StatCard\n label=\"Your progress\"\n value={myEnrollment ? `${myEnrollment.progressPct}%` : 'Not started'}\n icon={CheckCircle2}\n />\n </div>\n\n {myEnrollment ? (\n <GlassCard className=\"mb-6 p-4\">\n <div className=\"mb-2 flex items-center justify-between text-sm\">\n <span className=\"font-medium text-text-primary\">Your progress</span>\n <span className=\"text-text-secondary\">\n {completedLessons}/{lessons.length} lessons · {myEnrollment.progressPct}%\n </span>\n </div>\n <div className=\"h-2 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-accent-primary transition-all\"\n style={{ width: `${myEnrollment.progressPct}%` }}\n />\n </div>\n </GlassCard>\n ) : null}\n\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-2 text-base font-semibold text-text-primary\">Curriculum</h2>\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>\n {lessons.map((lesson, i) => (\n <LessonRow key={lesson.id} lesson={lesson} done={i < completedLessons} />\n ))}\n </div>\n )}\n </GlassCard>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CoursesCourseDetailsPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesCourseDetailsPagePage.tsx"],"sourcesContent":["import type { ReactNode } 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';\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 } = useTechnoSpam();\n const { data, loading, error, refetch } = useCourseQuery(id);\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 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 const res = await enroll({ variables: { input: { courseId: id } } });\n if (res.data?.tecEnroll) {\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 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":"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"}