@burdenoff/microfe-technospam 2026.705.2 → 2026.709.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 (113) hide show
  1. package/dist/dev/main.d.ts +2 -0
  2. package/dist/dev/main.d.ts.map +1 -0
  3. package/dist/generated/global-operations.d.ts +2 -0
  4. package/dist/generated/global-operations.d.ts.map +1 -0
  5. package/dist/generated/global-types.d.ts +18199 -0
  6. package/dist/generated/global-types.d.ts.map +1 -0
  7. package/dist/generated/wspace-operations.d.ts +267 -0
  8. package/dist/generated/wspace-operations.d.ts.map +1 -0
  9. package/dist/generated/wspace-operations.js +67 -0
  10. package/dist/generated/wspace-operations.js.map +1 -0
  11. package/dist/generated/wspace-types.d.ts +43083 -0
  12. package/dist/generated/wspace-types.d.ts.map +1 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +3 -1
  16. package/dist/technospam/TechnoSpamRoot.d.ts +5 -0
  17. package/dist/technospam/TechnoSpamRoot.d.ts.map +1 -0
  18. package/dist/technospam/TechnoSpamRoutes.d.ts +2 -0
  19. package/dist/technospam/TechnoSpamRoutes.d.ts.map +1 -0
  20. package/dist/technospam/api/technospamApi.d.ts +402 -0
  21. package/dist/technospam/api/technospamApi.d.ts.map +1 -0
  22. package/dist/technospam/components/LoadMore.d.ts +11 -0
  23. package/dist/technospam/components/LoadMore.d.ts.map +1 -0
  24. package/dist/technospam/components/MediaCover.d.ts +20 -0
  25. package/dist/technospam/components/MediaCover.d.ts.map +1 -0
  26. package/dist/technospam/components/PageLayout.d.ts +20 -0
  27. package/dist/technospam/components/PageLayout.d.ts.map +1 -0
  28. package/dist/technospam/components/StatCard.d.ts +20 -0
  29. package/dist/technospam/components/StatCard.d.ts.map +1 -0
  30. package/dist/technospam/components/StateViews.d.ts +30 -0
  31. package/dist/technospam/components/StateViews.d.ts.map +1 -0
  32. package/dist/technospam/components/StatusBadge.d.ts +5 -0
  33. package/dist/technospam/components/StatusBadge.d.ts.map +1 -0
  34. package/dist/technospam/components/index.d.ts +7 -0
  35. package/dist/technospam/components/index.d.ts.map +1 -0
  36. package/dist/technospam/hooks/useTechnoSpamAnalytics.d.ts +20 -0
  37. package/dist/technospam/hooks/useTechnoSpamAnalytics.d.ts.map +1 -0
  38. package/dist/technospam/hooks/useTechnoSpamAnalytics.js +13 -0
  39. package/dist/technospam/hooks/useTechnoSpamAnalytics.js.map +1 -0
  40. package/dist/technospam/index.d.ts +6 -0
  41. package/dist/technospam/index.d.ts.map +1 -0
  42. package/dist/technospam/index.js +2 -0
  43. package/dist/technospam/lib/toast.d.ts +18 -0
  44. package/dist/technospam/lib/toast.d.ts.map +1 -0
  45. package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts +2 -0
  46. package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts.map +1 -0
  47. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.d.ts +2 -0
  48. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -0
  49. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.d.ts +2 -0
  50. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.d.ts.map +1 -0
  51. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +129 -105
  52. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
  53. package/dist/technospam/pages/ApplicationsProblemsPagePage.d.ts +2 -0
  54. package/dist/technospam/pages/ApplicationsProblemsPagePage.d.ts.map +1 -0
  55. package/dist/technospam/pages/ApplicationsProblemsPagePage.js +87 -72
  56. package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
  57. package/dist/technospam/pages/CoursesCourseDetailsPagePage.d.ts +2 -0
  58. package/dist/technospam/pages/CoursesCourseDetailsPagePage.d.ts.map +1 -0
  59. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +174 -143
  60. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
  61. package/dist/technospam/pages/CoursesCoursesPagePage.d.ts +2 -0
  62. package/dist/technospam/pages/CoursesCoursesPagePage.d.ts.map +1 -0
  63. package/dist/technospam/pages/CoursesCoursesPagePage.js +95 -80
  64. package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
  65. package/dist/technospam/pages/CoursesMyLearningPagePage.d.ts +2 -0
  66. package/dist/technospam/pages/CoursesMyLearningPagePage.d.ts.map +1 -0
  67. package/dist/technospam/pages/DashboardDashboardPagePage.d.ts +2 -0
  68. package/dist/technospam/pages/DashboardDashboardPagePage.d.ts.map +1 -0
  69. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.d.ts +2 -0
  70. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.d.ts.map +1 -0
  71. package/dist/technospam/pages/HackathonsHackathonsPagePage.d.ts +2 -0
  72. package/dist/technospam/pages/HackathonsHackathonsPagePage.d.ts.map +1 -0
  73. package/dist/technospam/pages/HackathonsHackathonsPagePage.js +93 -78
  74. package/dist/technospam/pages/HackathonsHackathonsPagePage.js.map +1 -1
  75. package/dist/technospam/pages/MentorshipMentorProfilePagePage.d.ts +2 -0
  76. package/dist/technospam/pages/MentorshipMentorProfilePagePage.d.ts.map +1 -0
  77. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +135 -121
  78. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
  79. package/dist/technospam/pages/MentorshipMentorsPagePage.d.ts +2 -0
  80. package/dist/technospam/pages/MentorshipMentorsPagePage.d.ts.map +1 -0
  81. package/dist/technospam/pages/MentorshipMentorsPagePage.js +156 -142
  82. package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
  83. package/dist/technospam/pages/MentorshipSessionsPagePage.d.ts +2 -0
  84. package/dist/technospam/pages/MentorshipSessionsPagePage.d.ts.map +1 -0
  85. package/dist/technospam/pages/MentorshipSessionsPagePage.js +129 -112
  86. package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
  87. package/dist/technospam/providers/TechnoSpamProvider.d.ts +13 -0
  88. package/dist/technospam/providers/TechnoSpamProvider.d.ts.map +1 -0
  89. package/dist/technospam/services/assistantApi.d.ts +76 -0
  90. package/dist/technospam/services/assistantApi.d.ts.map +1 -0
  91. package/dist/technospam/services/assistantApi.js +222 -0
  92. package/dist/technospam/services/assistantApi.js.map +1 -0
  93. package/dist/technospam/services/createSandboxAssistantTransport.d.ts +40 -0
  94. package/dist/technospam/services/createSandboxAssistantTransport.d.ts.map +1 -0
  95. package/dist/technospam/services/createSandboxAssistantTransport.js +120 -0
  96. package/dist/technospam/services/createSandboxAssistantTransport.js.map +1 -0
  97. package/dist/technospam/types/assistant.d.ts +15 -0
  98. package/dist/technospam/types/assistant.d.ts.map +1 -0
  99. package/dist/technospam/types/index.d.ts +51 -0
  100. package/dist/technospam/types/index.d.ts.map +1 -0
  101. package/dist/technospam/utils/cn.d.ts +4 -0
  102. package/dist/technospam/utils/cn.d.ts.map +1 -0
  103. package/dist/technospam/utils/format.d.ts +9 -0
  104. package/dist/technospam/utils/format.d.ts.map +1 -0
  105. package/dist/technospam/utils/i18n.d.ts +6 -0
  106. package/dist/technospam/utils/i18n.d.ts.map +1 -0
  107. package/dist/technospam/utils/navigation.d.ts +2 -0
  108. package/dist/technospam/utils/navigation.d.ts.map +1 -0
  109. package/dist/technospam/utils/pageContext.d.ts +28 -0
  110. package/dist/technospam/utils/pageContext.d.ts.map +1 -0
  111. package/dist/technospam/utils/pageContext.js +46 -0
  112. package/dist/technospam/utils/pageContext.js.map +1 -0
  113. package/package.json +5 -1
@@ -4,23 +4,25 @@ import { PageLayout as n } from "../components/PageLayout.js";
4
4
  import { StatCard as r } from "../components/StatCard.js";
5
5
  import { MediaCover as i, topicIcon as a } from "../components/MediaCover.js";
6
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
+ import { EmptyView as ee, ErrorView as c } from "../components/StateViews.js";
8
8
  import "../components/index.js";
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";
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";
15
17
  //#region src/technospam/pages/CoursesCourseDetailsPagePage.tsx
16
- var F = {
18
+ var P = {
17
19
  VIDEO: {
18
- icon: C,
20
+ icon: S,
19
21
  chip: "bg-accent-primary/15 text-accent-primary",
20
22
  badge: "border-accent-primary/25 bg-accent-primary/10 text-accent-primary"
21
23
  },
22
24
  ARTICLE: {
23
- icon: b,
25
+ icon: y,
24
26
  chip: "bg-accent-indigo/15 text-accent-indigo",
25
27
  badge: "border-accent-indigo/25 bg-accent-indigo/10 text-accent-indigo"
26
28
  },
@@ -30,64 +32,64 @@ var F = {
30
32
  badge: "border-accent-teal/25 bg-accent-teal/10 text-accent-teal"
31
33
  },
32
34
  QUIZ: {
33
- icon: _,
35
+ icon: g,
34
36
  chip: "bg-accent-purple/15 text-accent-purple",
35
37
  badge: "border-accent-purple/25 bg-accent-purple/10 text-accent-purple"
36
38
  },
37
39
  PROJECT: {
38
- icon: x,
40
+ icon: b,
39
41
  chip: "bg-status-warning-bg-subtle text-status-warning-text",
40
42
  badge: "border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text"
41
43
  }
42
44
  };
43
- function I({ icon: e, children: t }) {
44
- return /* @__PURE__ */ A("span", {
45
+ function F({ icon: e, children: t }) {
46
+ return /* @__PURE__ */ k("span", {
45
47
  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]
48
+ children: [e ? /* @__PURE__ */ O(e, { size: 12 }) : null, t]
47
49
  });
48
50
  }
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", {
51
+ function I({ lesson: e, index: n, done: r }) {
52
+ let i = P[e.kind] ?? P.ARTICLE, a = i.icon;
53
+ return /* @__PURE__ */ k("div", {
52
54
  className: "group flex items-center gap-3 rounded-xl px-2 py-3 transition-colors hover:bg-bg-sunken/60",
53
55
  children: [
54
- /* @__PURE__ */ k("span", {
56
+ /* @__PURE__ */ O("span", {
55
57
  className: "hidden w-6 shrink-0 text-center text-sm font-medium tabular-nums text-text-muted sm:block",
56
58
  children: n + 1
57
59
  }),
58
- /* @__PURE__ */ k("span", {
60
+ /* @__PURE__ */ O("span", {
59
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),
60
- children: k(r ? _ : a, { size: 17 })
62
+ children: O(r ? g : a, { size: 17 })
61
63
  }),
62
- /* @__PURE__ */ A("div", {
64
+ /* @__PURE__ */ k("div", {
63
65
  className: "min-w-0 flex-1",
64
- children: [/* @__PURE__ */ k("span", {
66
+ children: [/* @__PURE__ */ O("span", {
65
67
  className: "block truncate text-sm font-medium text-text-primary",
66
68
  children: e.title
67
- }), /* @__PURE__ */ A("div", {
69
+ }), /* @__PURE__ */ k("div", {
68
70
  className: "mt-1 flex flex-wrap items-center gap-2",
69
- children: [/* @__PURE__ */ k("span", {
71
+ children: [/* @__PURE__ */ O("span", {
70
72
  className: t("inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] font-medium", i.badge),
71
73
  children: s(e.kind)
72
- }), /* @__PURE__ */ A("span", {
74
+ }), /* @__PURE__ */ k("span", {
73
75
  className: "inline-flex items-center gap-1 text-xs text-text-muted",
74
76
  children: [
75
- /* @__PURE__ */ k(v, { size: 12 }),
77
+ /* @__PURE__ */ O(_, { size: 12 }),
76
78
  " ",
77
79
  o(e.estimatedMinutes)
78
80
  ]
79
81
  })]
80
82
  })]
81
83
  }),
82
- /* @__PURE__ */ k("div", {
84
+ /* @__PURE__ */ O("div", {
83
85
  className: "shrink-0",
84
- children: r ? /* @__PURE__ */ k(_, {
86
+ children: r ? /* @__PURE__ */ O(g, {
85
87
  size: 18,
86
88
  className: "text-status-success-text"
87
- }) : e.isPreview ? /* @__PURE__ */ A("span", {
89
+ }) : e.isPreview ? /* @__PURE__ */ k("span", {
88
90
  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
+ children: [/* @__PURE__ */ O(v, { size: 12 }), " Preview"]
92
+ }) : /* @__PURE__ */ O(x, {
91
93
  size: 15,
92
94
  className: "text-text-muted"
93
95
  })
@@ -95,250 +97,279 @@ function L({ lesson: e, index: n, done: r }) {
95
97
  ]
96
98
  });
97
99
  }
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,
100
+ 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,
106
+ entityType: "TEC_COURSE"
107
+ });
108
+ }, [
109
+ v,
110
+ S,
111
+ w
112
+ ]);
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,
120
+ entityType: "TEC_COURSE"
121
+ }), (await K({ variables: {
122
+ courseId: v,
123
+ learnerId: x.id,
106
124
  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,
125
+ } })).data?.tecIssueCertificate && (l.success("Certificate issued", "Your verifiable certificate is ready in My Learning."), await G()));
126
+ }, se = async () => {
127
+ v && (w("technospam.course.enroll_clicked", {
128
+ workspaceId: S,
129
+ entityId: v,
130
+ entityType: "TEC_COURSE"
131
+ }), (await B({ variables: { input: { courseId: v } } })).data?.tecEnroll && (w("technospam.course.enrolled", {
132
+ workspaceId: S,
133
+ entityId: v,
134
+ entityType: "TEC_COURSE"
135
+ }), l.success("Enrolled", `You're enrolled in "${J?.title}".`), await z()));
136
+ }, ce = async () => {
137
+ if (!X) return;
138
+ let e = Math.min(Y.length, X.lessonsCompleted + 1), t = Y.length ? Math.round(e / Y.length * 100) : 100;
139
+ (await H({ variables: { input: {
140
+ enrollmentId: X.id,
115
141
  lessonsCompleted: e,
116
142
  progressPct: t
117
- } } })).data?.tecUpdateProgress && (u.success("Progress saved", `${t}% complete`), await R());
143
+ } } })).data?.tecUpdateProgress && (w("technospam.course.progress_updated", {
144
+ workspaceId: S,
145
+ entityId: X.id,
146
+ entityType: "TEC_ENROLLMENT",
147
+ progressPct: t
148
+ }), l.success("Progress saved", `${t}% complete`), await z());
118
149
  };
119
- if (w && !q) return /* @__PURE__ */ A(n, {
150
+ if (L && !J) return /* @__PURE__ */ k(n, {
120
151
  title: "Course",
121
152
  backPath: "/courses/courses-page",
122
- children: [/* @__PURE__ */ k(P, { className: "mb-4 h-40 w-full rounded-2xl" }), /* @__PURE__ */ k(P, { className: "h-64 w-full rounded-xl" })]
153
+ children: [/* @__PURE__ */ O(N, { className: "mb-4 h-40 w-full rounded-2xl" }), /* @__PURE__ */ O(N, { className: "h-64 w-full rounded-xl" })]
123
154
  });
124
- if (F || !q) return /* @__PURE__ */ k(n, {
155
+ if (R || !J) return /* @__PURE__ */ O(n, {
125
156
  title: "Course",
126
157
  backPath: "/courses/courses-page",
127
- children: F ? /* @__PURE__ */ k(l, {
158
+ children: R ? /* @__PURE__ */ O(c, {
128
159
  title: "Couldn't load course",
129
- error: F
130
- }) : /* @__PURE__ */ k(c, {
160
+ error: R
161
+ }) : /* @__PURE__ */ O(ee, {
131
162
  title: "Course not found",
132
163
  description: "This course may have been unpublished."
133
164
  })
134
165
  });
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, {
166
+ 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, {
137
168
  backPath: "/courses/courses-page",
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,
169
+ title: J.title,
170
+ description: J.summary ?? void 0,
171
+ actions: X ? /* @__PURE__ */ k(j, {
172
+ onClick: ce,
173
+ disabled: U || Q >= Y.length,
174
+ children: [/* @__PURE__ */ O(g, { size: 16 }), Q >= Y.length ? "Completed" : "Complete next lesson"]
175
+ }) : /* @__PURE__ */ k(j, {
176
+ onClick: se,
177
+ disabled: V,
147
178
  children: [
148
- /* @__PURE__ */ k(g, { size: 16 }),
179
+ /* @__PURE__ */ O(h, { size: 16 }),
149
180
  " ",
150
- B ? "Enrolling…" : "Enroll"
181
+ V ? "Enrolling…" : "Enroll"
151
182
  ]
152
183
  }),
153
184
  children: [
154
- /* @__PURE__ */ k(i, {
185
+ /* @__PURE__ */ O(i, {
155
186
  height: "h-40",
156
- seed: oe,
157
- icon: a(q.topic ?? q.title),
187
+ seed: ue,
188
+ icon: a(J.topic ?? J.title),
158
189
  className: "mb-5 rounded-2xl",
159
- children: /* @__PURE__ */ A("div", {
190
+ children: /* @__PURE__ */ k("div", {
160
191
  className: "flex w-full flex-col gap-2",
161
192
  children: [
162
- /* @__PURE__ */ k("span", {
193
+ /* @__PURE__ */ O("span", {
163
194
  className: "text-xs font-medium uppercase tracking-wide text-white/80",
164
- children: q.topic ?? "Course"
195
+ children: J.topic ?? "Course"
165
196
  }),
166
- /* @__PURE__ */ k("h2", {
197
+ /* @__PURE__ */ O("h2", {
167
198
  className: "text-2xl font-bold leading-tight text-white drop-shadow-sm sm:text-3xl",
168
- children: q.title
199
+ children: J.title
169
200
  }),
170
- /* @__PURE__ */ A("div", {
201
+ /* @__PURE__ */ k("div", {
171
202
  className: "flex flex-wrap items-center gap-2",
172
203
  children: [
173
- /* @__PURE__ */ k(I, {
204
+ /* @__PURE__ */ O(F, {
174
205
  icon: T,
175
- children: s(q.level)
206
+ children: s(J.level)
176
207
  }),
177
- /* @__PURE__ */ k(I, {
178
- icon: _,
179
- children: s(q.status)
208
+ /* @__PURE__ */ O(F, {
209
+ icon: g,
210
+ children: s(J.status)
180
211
  }),
181
- q.topic ? /* @__PURE__ */ k(I, {
182
- icon: x,
183
- children: q.topic
212
+ J.topic ? /* @__PURE__ */ O(F, {
213
+ icon: b,
214
+ children: J.topic
184
215
  }) : null
185
216
  ]
186
217
  })
187
218
  ]
188
219
  })
189
220
  }),
190
- q.tags.length ? /* @__PURE__ */ k("div", {
221
+ J.tags.length ? /* @__PURE__ */ O("div", {
191
222
  className: "mb-5 flex flex-wrap items-center gap-2",
192
- children: q.tags.map((e) => /* @__PURE__ */ k(j, {
223
+ children: J.tags.map((e) => /* @__PURE__ */ O(A, {
193
224
  variant: "secondary",
194
225
  children: e
195
226
  }, e))
196
227
  }) : null,
197
- /* @__PURE__ */ A("div", {
228
+ /* @__PURE__ */ k("div", {
198
229
  className: "mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4",
199
230
  children: [
200
- /* @__PURE__ */ k(r, {
231
+ /* @__PURE__ */ O(r, {
201
232
  label: "Lessons",
202
- value: J.length,
203
- icon: b,
233
+ value: Y.length,
234
+ icon: y,
204
235
  tone: "blue"
205
236
  }),
206
- /* @__PURE__ */ k(r, {
237
+ /* @__PURE__ */ O(r, {
207
238
  label: "Duration",
208
- value: o(q.estimatedMinutes),
209
- icon: v,
239
+ value: o(J.estimatedMinutes),
240
+ icon: _,
210
241
  tone: "teal"
211
242
  }),
212
- /* @__PURE__ */ k(r, {
243
+ /* @__PURE__ */ O(r, {
213
244
  label: "Enrolled",
214
- value: q.enrollments?.length ?? 0,
215
- icon: D,
245
+ value: J.enrollments?.length ?? 0,
246
+ icon: E,
216
247
  tone: "indigo"
217
248
  }),
218
- /* @__PURE__ */ k(r, {
249
+ /* @__PURE__ */ O(r, {
219
250
  label: "Your progress",
220
- value: Y ? `${Y.progressPct}%` : "Not started",
251
+ value: X ? `${X.progressPct}%` : "Not started",
221
252
  icon: T,
222
253
  tone: "purple"
223
254
  })
224
255
  ]
225
256
  }),
226
- Y ? /* @__PURE__ */ A(N, {
257
+ X ? /* @__PURE__ */ k(M, {
227
258
  className: "mb-6 p-5",
228
259
  children: [
229
- /* @__PURE__ */ A("div", {
260
+ /* @__PURE__ */ k("div", {
230
261
  className: "mb-3 flex items-center gap-2",
231
262
  children: [
232
- /* @__PURE__ */ k("span", {
263
+ /* @__PURE__ */ O("span", {
233
264
  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 })
265
+ children: /* @__PURE__ */ O(T, { size: 16 })
235
266
  }),
236
- /* @__PURE__ */ k("span", {
267
+ /* @__PURE__ */ O("span", {
237
268
  className: "text-sm font-semibold text-text-primary",
238
269
  children: "Your progress"
239
270
  }),
240
- /* @__PURE__ */ A("span", {
271
+ /* @__PURE__ */ k("span", {
241
272
  className: "ml-auto text-sm text-text-secondary",
242
273
  children: [
243
274
  Q,
244
275
  "/",
245
- J.length,
276
+ Y.length,
246
277
  " lessons · ",
247
- Y.progressPct,
278
+ X.progressPct,
248
279
  "%"
249
280
  ]
250
281
  })
251
282
  ]
252
283
  }),
253
- /* @__PURE__ */ k("div", {
284
+ /* @__PURE__ */ O("div", {
254
285
  className: "h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken",
255
- children: /* @__PURE__ */ k("div", {
286
+ children: /* @__PURE__ */ O("div", {
256
287
  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}%` }
288
+ style: { width: `${X.progressPct}%` }
258
289
  })
259
290
  }),
260
- /* @__PURE__ */ k("p", {
291
+ /* @__PURE__ */ O("p", {
261
292
  className: "mt-2 text-xs text-text-muted",
262
- children: ae
293
+ children: le
263
294
  })
264
295
  ]
265
296
  }) : null,
266
- X ? /* @__PURE__ */ A(N, {
297
+ Z ? /* @__PURE__ */ k(M, {
267
298
  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
299
  children: [
269
- /* @__PURE__ */ k("span", {
300
+ /* @__PURE__ */ O("span", {
270
301
  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 })
302
+ children: /* @__PURE__ */ O(m, { size: 22 })
272
303
  }),
273
- /* @__PURE__ */ A("div", {
304
+ /* @__PURE__ */ k("div", {
274
305
  className: "min-w-0 flex-1",
275
- children: [/* @__PURE__ */ k("div", {
306
+ children: [/* @__PURE__ */ O("div", {
276
307
  className: "text-sm font-semibold text-text-primary",
277
308
  children: "Certificate earned"
278
- }), /* @__PURE__ */ A("div", {
309
+ }), /* @__PURE__ */ k("div", {
279
310
  className: "text-xs text-text-muted",
280
311
  children: [
281
312
  "Serial ",
282
- X.serial,
313
+ Z.serial,
283
314
  " · Score ",
284
- X.score
315
+ Z.score
285
316
  ]
286
317
  })]
287
318
  }),
288
- /* @__PURE__ */ A("span", {
319
+ /* @__PURE__ */ k("span", {
289
320
  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"]
321
+ children: [/* @__PURE__ */ O(C, { size: 13 }), " Verified"]
291
322
  })
292
323
  ]
293
- }) : Z ? /* @__PURE__ */ A(N, {
324
+ }) : ae ? /* @__PURE__ */ k(M, {
294
325
  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", {
326
+ children: [/* @__PURE__ */ k("div", {
296
327
  className: "flex items-center gap-4",
297
- children: [/* @__PURE__ */ k("span", {
328
+ children: [/* @__PURE__ */ O("span", {
298
329
  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", {
330
+ children: /* @__PURE__ */ O(ie, { size: 26 })
331
+ }), /* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("div", {
301
332
  className: "text-base font-semibold text-text-primary",
302
333
  children: "You finished this course"
303
- }), /* @__PURE__ */ k("div", {
334
+ }), /* @__PURE__ */ O("div", {
304
335
  className: "text-sm text-text-muted",
305
336
  children: "Claim your verifiable certificate of completion."
306
337
  })] })]
307
- }), /* @__PURE__ */ A(M, {
308
- onClick: ne,
309
- disabled: K,
338
+ }), /* @__PURE__ */ k(j, {
339
+ onClick: oe,
340
+ disabled: q,
310
341
  children: [
311
- /* @__PURE__ */ k(h, { size: 16 }),
342
+ /* @__PURE__ */ O(m, { size: 16 }),
312
343
  " ",
313
- K ? "Issuing…" : "Claim certificate"
344
+ q ? "Issuing…" : "Claim certificate"
314
345
  ]
315
346
  })]
316
347
  }) : null,
317
- /* @__PURE__ */ A(N, {
348
+ /* @__PURE__ */ k(M, {
318
349
  className: "p-5",
319
- children: [/* @__PURE__ */ A("div", {
350
+ children: [/* @__PURE__ */ k("div", {
320
351
  className: "mb-3 flex items-center justify-between gap-2",
321
- children: [/* @__PURE__ */ A("h2", {
352
+ children: [/* @__PURE__ */ k("h2", {
322
353
  className: "flex items-center gap-2 text-base font-semibold text-text-primary",
323
- children: [/* @__PURE__ */ k(g, {
354
+ children: [/* @__PURE__ */ O(h, {
324
355
  size: 18,
325
356
  className: "text-accent-primary"
326
357
  }), " Curriculum"]
327
- }), J.length ? /* @__PURE__ */ A("span", {
358
+ }), Y.length ? /* @__PURE__ */ k("span", {
328
359
  className: "rounded-full bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text",
329
360
  children: [
330
361
  Q,
331
362
  "/",
332
- J.length,
363
+ Y.length,
333
364
  " completed"
334
365
  ]
335
366
  }) : null]
336
- }), J.length === 0 ? /* @__PURE__ */ k("p", {
367
+ }), Y.length === 0 ? /* @__PURE__ */ O("p", {
337
368
  className: "py-6 text-center text-sm text-text-muted",
338
369
  children: "Lessons for this course are being prepared."
339
- }) : /* @__PURE__ */ k("div", {
370
+ }) : /* @__PURE__ */ O("div", {
340
371
  className: "flex flex-col",
341
- children: J.map((e, t) => /* @__PURE__ */ k(L, {
372
+ children: Y.map((e, t) => /* @__PURE__ */ O(I, {
342
373
  lesson: e,
343
374
  index: t,
344
375
  done: t < Q
@@ -349,6 +380,6 @@ function R() {
349
380
  });
350
381
  }
351
382
  //#endregion
352
- export { R as CoursesCourseDetailsPagePage };
383
+ export { L as CoursesCourseDetailsPagePage };
353
384
 
354
385
  //# sourceMappingURL=CoursesCourseDetailsPagePage.js.map
@@ -1 +1 @@
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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"}
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":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function CoursesCoursesPagePage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=CoursesCoursesPagePage.d.ts.map