@burdenoff/microfe-technospam 2026.1030.4 → 2026.1030.6
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.
- package/dist/generated/wspace-types.d.ts +1501 -11
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/Brush.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/ErrorBar.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/ReferenceArea.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/ReferenceDot.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/ReferenceLine.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/XAxis.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/YAxis.js.map +1 -1
- package/dist/node_modules/recharts/es6/component/ResponsiveContainer.js.map +1 -1
- package/dist/node_modules/sonner/dist/index.js.map +1 -1
- package/dist/stories/fixtures/technospamFixtures.d.ts +1 -0
- package/dist/stories/fixtures/technospamFixtures.d.ts.map +1 -1
- package/dist/stories/journeys/Courses.stories.d.ts +5 -2
- package/dist/stories/journeys/Courses.stories.d.ts.map +1 -1
- package/dist/stories/journeys/Dashboard.stories.d.ts +2 -2
- package/dist/stories/journeys/Dashboard.stories.d.ts.map +1 -1
- package/dist/technospam/TechnoSpamRoutes.d.ts.map +1 -1
- package/dist/technospam/TechnoSpamRoutes.js +31 -27
- package/dist/technospam/TechnoSpamRoutes.js.map +1 -1
- package/dist/technospam/api/technospamApi.d.ts +101 -5
- package/dist/technospam/api/technospamApi.d.ts.map +1 -1
- package/dist/technospam/api/technospamApi.js +278 -146
- package/dist/technospam/api/technospamApi.js.map +1 -1
- package/dist/technospam/components/lesson/InteractiveWorkspace.d.ts +15 -0
- package/dist/technospam/components/lesson/InteractiveWorkspace.d.ts.map +1 -0
- package/dist/technospam/components/lesson/InteractiveWorkspace.js +198 -0
- package/dist/technospam/components/lesson/InteractiveWorkspace.js.map +1 -0
- package/dist/technospam/components/lesson/LessonKindFields.d.ts +15 -0
- package/dist/technospam/components/lesson/LessonKindFields.d.ts.map +1 -0
- package/dist/technospam/components/lesson/LessonKindFields.js +150 -0
- package/dist/technospam/components/lesson/LessonKindFields.js.map +1 -0
- package/dist/technospam/components/lesson/QuizLesson.d.ts +14 -0
- package/dist/technospam/components/lesson/QuizLesson.d.ts.map +1 -0
- package/dist/technospam/components/lesson/QuizLesson.js +105 -0
- package/dist/technospam/components/lesson/QuizLesson.js.map +1 -0
- package/dist/technospam/components/lesson/lessonAuthoring.d.ts +47 -0
- package/dist/technospam/components/lesson/lessonAuthoring.d.ts.map +1 -0
- package/dist/technospam/components/lesson/lessonAuthoring.js +80 -0
- package/dist/technospam/components/lesson/lessonAuthoring.js.map +1 -0
- package/dist/technospam/hooks/useTechnoSpamAnalytics.d.ts +1 -1
- package/dist/technospam/hooks/useTechnoSpamAnalytics.d.ts.map +1 -1
- package/dist/technospam/hooks/useTechnoSpamAnalytics.js.map +1 -1
- package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js +230 -207
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +130 -132
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesLessonPlayerPagePage.d.ts +9 -0
- package/dist/technospam/pages/CoursesLessonPlayerPagePage.d.ts.map +1 -0
- package/dist/technospam/pages/CoursesLessonPlayerPagePage.js +291 -0
- package/dist/technospam/pages/CoursesLessonPlayerPagePage.js.map +1 -0
- package/dist/technospam/pages/CoursesMyLearningPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesMyLearningPagePage.js +80 -78
- package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
- package/dist/technospam/pages/DashboardDashboardPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/DashboardDashboardPagePage.js +136 -133
- package/dist/technospam/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/technospam/utils/lessonPath.d.ts +15 -0
- package/dist/technospam/utils/lessonPath.d.ts.map +1 -0
- package/dist/technospam/utils/lessonPath.js +15 -0
- package/dist/technospam/utils/lessonPath.js.map +1 -0
- package/dist/technospam/utils/pageContext.d.ts.map +1 -1
- package/dist/technospam/utils/pageContext.js +10 -0
- package/dist/technospam/utils/pageContext.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,188 +6,190 @@ import { coverGradient as a, topicIcon as o } from "../components/MediaCover.js"
|
|
|
6
6
|
import { formatDate as s, relativeTime as c } from "../utils/format.js";
|
|
7
7
|
import { StatusBadge as l } from "../components/StatusBadge.js";
|
|
8
8
|
import { EmptyView as u, ErrorView as d, LoadingRows as f } from "../components/StateViews.js";
|
|
9
|
-
import { useEnrollmentsQuery as p, useMyCertificatesQuery as m } from "../api/technospamApi.js";
|
|
9
|
+
import { useEnrollmentsQuery as p, useMyCertificatesQuery as m, useMyNextLessonsQuery as h } from "../api/technospamApi.js";
|
|
10
10
|
import "../components/index.js";
|
|
11
|
-
import { FOCUS_PARAMS as
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
11
|
+
import { FOCUS_PARAMS as g } from "../utils/pageContext.js";
|
|
12
|
+
import { resumePath as _ } from "../utils/lessonPath.js";
|
|
13
|
+
import { focusableCardProps as v, useFocusedRecordParam as y } from "../hooks/useFocusedRecordParam.js";
|
|
14
|
+
import { createElement as b } from "react";
|
|
15
|
+
import { Award as x, BookOpen as S, GraduationCap as C, PlayCircle as w, ShieldCheck as T } from "lucide-react";
|
|
16
|
+
import { jsx as E, jsxs as D } from "react/jsx-runtime";
|
|
17
|
+
import { Button as O, GlassCard as k } from "@burdenoff/fe-libs/ui";
|
|
17
18
|
//#region src/technospam/pages/CoursesMyLearningPagePage.tsx
|
|
18
|
-
function
|
|
19
|
+
function A({ e, onOpen: t, focused: r, onToggle: i }) {
|
|
19
20
|
let s = `${e.course?.topic ?? ""} ${e.course?.title ?? "Course"}`;
|
|
20
|
-
return /* @__PURE__ */
|
|
21
|
+
return /* @__PURE__ */ D(k, {
|
|
21
22
|
className: n("group flex cursor-pointer flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md sm:flex-row sm:items-center sm:gap-4", r && "ring-2 ring-accent-primary"),
|
|
22
|
-
...
|
|
23
|
+
...v(r, i),
|
|
23
24
|
children: [
|
|
24
|
-
/* @__PURE__ */
|
|
25
|
+
/* @__PURE__ */ E("span", {
|
|
25
26
|
className: n("flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br text-white shadow-sm transition-transform duration-200 group-hover:scale-105", a(s)),
|
|
26
|
-
children:
|
|
27
|
+
children: b(o(s), { size: 20 })
|
|
27
28
|
}),
|
|
28
|
-
/* @__PURE__ */
|
|
29
|
+
/* @__PURE__ */ D("div", {
|
|
29
30
|
className: "min-w-0 flex-1",
|
|
30
31
|
children: [
|
|
31
|
-
/* @__PURE__ */
|
|
32
|
+
/* @__PURE__ */ D("div", {
|
|
32
33
|
className: "flex flex-wrap items-center gap-2",
|
|
33
|
-
children: [/* @__PURE__ */
|
|
34
|
+
children: [/* @__PURE__ */ E("button", {
|
|
34
35
|
type: "button",
|
|
35
36
|
"aria-pressed": r,
|
|
36
37
|
onClick: i,
|
|
37
38
|
className: "truncate text-left text-sm font-semibold text-text-primary group-hover:text-accent-primary",
|
|
38
39
|
children: e.course?.title ?? "Course"
|
|
39
|
-
}), /* @__PURE__ */
|
|
40
|
+
}), /* @__PURE__ */ E(l, { status: e.status })]
|
|
40
41
|
}),
|
|
41
|
-
/* @__PURE__ */
|
|
42
|
+
/* @__PURE__ */ D("div", {
|
|
42
43
|
className: "mt-1 flex flex-wrap items-center gap-2 text-xs text-text-muted",
|
|
43
44
|
children: [
|
|
44
|
-
/* @__PURE__ */
|
|
45
|
-
/* @__PURE__ */
|
|
45
|
+
/* @__PURE__ */ D("span", { children: [e.lessonsCompleted, " lessons done"] }),
|
|
46
|
+
/* @__PURE__ */ E("span", {
|
|
46
47
|
"aria-hidden": !0,
|
|
47
48
|
children: "·"
|
|
48
49
|
}),
|
|
49
|
-
/* @__PURE__ */
|
|
50
|
+
/* @__PURE__ */ D("span", { children: ["Active ", c(e.lastActiveAt ?? e.startedAt)] })
|
|
50
51
|
]
|
|
51
52
|
}),
|
|
52
|
-
/* @__PURE__ */
|
|
53
|
+
/* @__PURE__ */ E("div", {
|
|
53
54
|
className: "mt-2 h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken",
|
|
54
|
-
children: /* @__PURE__ */
|
|
55
|
+
children: /* @__PURE__ */ E("div", {
|
|
55
56
|
className: "h-full rounded-full bg-gradient-to-r from-accent-primary via-accent-indigo to-accent-purple transition-all duration-500",
|
|
56
57
|
style: { width: `${e.progressPct}%` }
|
|
57
58
|
})
|
|
58
59
|
})
|
|
59
60
|
]
|
|
60
61
|
}),
|
|
61
|
-
/* @__PURE__ */
|
|
62
|
+
/* @__PURE__ */ D("div", {
|
|
62
63
|
className: "flex items-center justify-between gap-3 sm:flex-col sm:items-end sm:justify-center",
|
|
63
|
-
children: [/* @__PURE__ */
|
|
64
|
+
children: [/* @__PURE__ */ D("div", {
|
|
64
65
|
className: "text-lg font-semibold tabular-nums text-text-primary",
|
|
65
66
|
children: [e.progressPct, "%"]
|
|
66
|
-
}), /* @__PURE__ */
|
|
67
|
+
}), /* @__PURE__ */ D(O, {
|
|
67
68
|
variant: "ghost",
|
|
68
69
|
size: "sm",
|
|
69
70
|
onClick: t,
|
|
70
|
-
|
|
71
|
+
"data-testid": `resume-${e.courseId}`,
|
|
72
|
+
children: [/* @__PURE__ */ E(w, { size: 15 }), " Resume"]
|
|
71
73
|
})]
|
|
72
74
|
})
|
|
73
75
|
]
|
|
74
76
|
});
|
|
75
77
|
}
|
|
76
|
-
var
|
|
77
|
-
function
|
|
78
|
-
let a = t(), { currentUser: o } = e(), c = o?.id, { data:
|
|
79
|
-
return /* @__PURE__ */
|
|
78
|
+
var j = [g.certificate], M = [g.enrollment];
|
|
79
|
+
function N() {
|
|
80
|
+
let a = t(), { currentUser: o } = e(), c = o?.id, { data: b, loading: N, error: P } = p(c ? { learnerId: c } : {}), { data: F } = m(), { data: I } = h(), L = new Map((I?.tecMyEnrollments?.items ?? []).map((e) => [e.courseId, e.nextLessonId ?? null])), [R, z] = y(g.enrollment, j), [B, V] = y(g.certificate, M), H = b?.tecListEnrollments?.items ?? [], U = F?.tecMyCertificates ?? [], W = H.filter((e) => e.status === "COMPLETED").length, G = H.filter((e) => e.status === "IN_PROGRESS" || e.status === "ENROLLED").length;
|
|
81
|
+
return /* @__PURE__ */ D(r, {
|
|
80
82
|
hero: !0,
|
|
81
83
|
title: "My Learning",
|
|
82
84
|
description: "Track your enrolled courses, progress, and earned certificates.",
|
|
83
|
-
actions: /* @__PURE__ */
|
|
85
|
+
actions: /* @__PURE__ */ D(O, {
|
|
84
86
|
variant: "outline",
|
|
85
87
|
onClick: () => a("/courses/courses-page"),
|
|
86
|
-
children: [/* @__PURE__ */
|
|
88
|
+
children: [/* @__PURE__ */ E(S, { size: 16 }), " Browse catalog"]
|
|
87
89
|
}),
|
|
88
90
|
children: [
|
|
89
|
-
/* @__PURE__ */
|
|
91
|
+
/* @__PURE__ */ D("div", {
|
|
90
92
|
className: "mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4",
|
|
91
93
|
children: [
|
|
92
|
-
/* @__PURE__ */
|
|
94
|
+
/* @__PURE__ */ E(i, {
|
|
93
95
|
label: "Enrolled",
|
|
94
|
-
value:
|
|
96
|
+
value: H.length,
|
|
95
97
|
hint: "Courses you've enrolled in",
|
|
96
|
-
icon:
|
|
98
|
+
icon: S,
|
|
97
99
|
tone: "blue"
|
|
98
100
|
}),
|
|
99
|
-
/* @__PURE__ */
|
|
101
|
+
/* @__PURE__ */ E(i, {
|
|
100
102
|
label: "In progress",
|
|
101
|
-
value:
|
|
103
|
+
value: G,
|
|
102
104
|
hint: "Courses in progress",
|
|
103
|
-
icon:
|
|
105
|
+
icon: w,
|
|
104
106
|
tone: "indigo"
|
|
105
107
|
}),
|
|
106
|
-
/* @__PURE__ */
|
|
108
|
+
/* @__PURE__ */ E(i, {
|
|
107
109
|
label: "Completed",
|
|
108
|
-
value:
|
|
110
|
+
value: W,
|
|
109
111
|
hint: "Courses completed",
|
|
110
|
-
icon:
|
|
112
|
+
icon: C,
|
|
111
113
|
tone: "green"
|
|
112
114
|
}),
|
|
113
|
-
/* @__PURE__ */
|
|
115
|
+
/* @__PURE__ */ E(i, {
|
|
114
116
|
label: "Certificates",
|
|
115
|
-
value:
|
|
117
|
+
value: U.length,
|
|
116
118
|
hint: "Certificates earned",
|
|
117
|
-
icon:
|
|
119
|
+
icon: x,
|
|
118
120
|
tone: "purple"
|
|
119
121
|
})
|
|
120
122
|
]
|
|
121
123
|
}),
|
|
122
|
-
|
|
124
|
+
N && !H.length ? /* @__PURE__ */ E(f, {
|
|
123
125
|
count: 3,
|
|
124
126
|
height: "h-24"
|
|
125
|
-
}) :
|
|
127
|
+
}) : P ? /* @__PURE__ */ E(d, {
|
|
126
128
|
title: "Couldn't load your learning",
|
|
127
|
-
error:
|
|
128
|
-
}) :
|
|
129
|
-
icon: /* @__PURE__ */
|
|
129
|
+
error: P
|
|
130
|
+
}) : H.length === 0 ? /* @__PURE__ */ E(u, {
|
|
131
|
+
icon: /* @__PURE__ */ E(S, { size: 26 }),
|
|
130
132
|
title: "You haven't enrolled yet",
|
|
131
133
|
description: "Browse the catalog and enroll in a course to start building skill by doing.",
|
|
132
|
-
action: /* @__PURE__ */
|
|
134
|
+
action: /* @__PURE__ */ E(O, {
|
|
133
135
|
onClick: () => a("/courses/courses-page"),
|
|
134
136
|
children: "Browse courses"
|
|
135
137
|
})
|
|
136
|
-
}) : /* @__PURE__ */
|
|
138
|
+
}) : /* @__PURE__ */ E("div", {
|
|
137
139
|
className: "space-y-3",
|
|
138
|
-
children:
|
|
140
|
+
children: H.map((e) => /* @__PURE__ */ E(A, {
|
|
139
141
|
e,
|
|
140
|
-
onOpen: () => a(
|
|
141
|
-
focused:
|
|
142
|
-
onToggle: () =>
|
|
142
|
+
onOpen: () => a(_(e.courseId, L.get(e.courseId))),
|
|
143
|
+
focused: R === e.id,
|
|
144
|
+
onToggle: () => z(R === e.id ? null : e.id)
|
|
143
145
|
}, e.id))
|
|
144
146
|
}),
|
|
145
|
-
|
|
147
|
+
U.length > 0 ? /* @__PURE__ */ D("div", {
|
|
146
148
|
className: "mt-8",
|
|
147
|
-
children: [/* @__PURE__ */
|
|
149
|
+
children: [/* @__PURE__ */ D("h2", {
|
|
148
150
|
className: "mb-3 flex items-center gap-2 text-base font-semibold text-text-primary",
|
|
149
|
-
children: [/* @__PURE__ */
|
|
151
|
+
children: [/* @__PURE__ */ E(x, {
|
|
150
152
|
size: 18,
|
|
151
153
|
className: "text-accent-teal"
|
|
152
154
|
}), " Certificates"]
|
|
153
|
-
}), /* @__PURE__ */
|
|
155
|
+
}), /* @__PURE__ */ E("div", {
|
|
154
156
|
className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3",
|
|
155
|
-
children:
|
|
156
|
-
className: n("group flex cursor-pointer flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-1 hover:shadow-lg",
|
|
157
|
-
...
|
|
158
|
-
children: [/* @__PURE__ */
|
|
157
|
+
children: U.map((e) => /* @__PURE__ */ D(k, {
|
|
158
|
+
className: n("group flex cursor-pointer flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-1 hover:shadow-lg", B === e.id && "ring-2 ring-accent-primary"),
|
|
159
|
+
...v(B === e.id, () => V(B === e.id ? null : e.id)),
|
|
160
|
+
children: [/* @__PURE__ */ D("div", {
|
|
159
161
|
className: "flex items-center gap-3",
|
|
160
|
-
children: [/* @__PURE__ */
|
|
162
|
+
children: [/* @__PURE__ */ E("span", {
|
|
161
163
|
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 transition-transform duration-200 group-hover:scale-105",
|
|
162
|
-
children: /* @__PURE__ */
|
|
163
|
-
}), /* @__PURE__ */
|
|
164
|
+
children: /* @__PURE__ */ E(x, { size: 22 })
|
|
165
|
+
}), /* @__PURE__ */ D("div", {
|
|
164
166
|
className: "min-w-0",
|
|
165
|
-
children: [/* @__PURE__ */
|
|
167
|
+
children: [/* @__PURE__ */ E("button", {
|
|
166
168
|
type: "button",
|
|
167
|
-
"aria-pressed":
|
|
168
|
-
onClick: () =>
|
|
169
|
+
"aria-pressed": B === e.id,
|
|
170
|
+
onClick: () => V(B === e.id ? null : e.id),
|
|
169
171
|
className: "block max-w-full truncate text-left text-sm font-medium text-text-primary",
|
|
170
172
|
children: e.course?.title ?? "Certificate"
|
|
171
|
-
}), /* @__PURE__ */
|
|
173
|
+
}), /* @__PURE__ */ D("div", {
|
|
172
174
|
className: "truncate text-xs text-text-muted",
|
|
173
175
|
children: ["Serial ", e.serial]
|
|
174
176
|
})]
|
|
175
177
|
})]
|
|
176
|
-
}), /* @__PURE__ */
|
|
178
|
+
}), /* @__PURE__ */ D("div", {
|
|
177
179
|
className: "mt-auto flex items-center justify-between border-t border-border-seam pt-3 text-xs text-text-muted",
|
|
178
|
-
children: [/* @__PURE__ */
|
|
180
|
+
children: [/* @__PURE__ */ D("span", { children: [
|
|
179
181
|
"Score ",
|
|
180
|
-
/* @__PURE__ */
|
|
182
|
+
/* @__PURE__ */ E("span", {
|
|
181
183
|
className: "font-semibold text-text-primary",
|
|
182
184
|
children: e.score
|
|
183
185
|
}),
|
|
184
186
|
" ·",
|
|
185
187
|
" ",
|
|
186
188
|
s(e.issuedAt)
|
|
187
|
-
] }), e.status === "ISSUED" ? /* @__PURE__ */
|
|
189
|
+
] }), e.status === "ISSUED" ? /* @__PURE__ */ D("span", {
|
|
188
190
|
className: "inline-flex items-center gap-1 rounded-full border border-status-success-border bg-status-success-bg-subtle px-2 py-0.5 font-medium text-status-success-text",
|
|
189
|
-
children: [/* @__PURE__ */
|
|
190
|
-
}) : /* @__PURE__ */
|
|
191
|
+
children: [/* @__PURE__ */ E(T, { size: 12 }), " Verified"]
|
|
192
|
+
}) : /* @__PURE__ */ E(l, { status: e.status })]
|
|
191
193
|
})]
|
|
192
194
|
}, e.id))
|
|
193
195
|
})]
|
|
@@ -196,6 +198,6 @@ function j() {
|
|
|
196
198
|
});
|
|
197
199
|
}
|
|
198
200
|
//#endregion
|
|
199
|
-
export {
|
|
201
|
+
export { N as CoursesMyLearningPagePage };
|
|
200
202
|
|
|
201
203
|
//# sourceMappingURL=CoursesMyLearningPagePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoursesMyLearningPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesMyLearningPagePage.tsx"],"sourcesContent":["import { createElement } from 'react';\nimport { Award, BookOpen, GraduationCap, PlayCircle, ShieldCheck } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n coverGradient,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useEnrollmentsQuery,\n useMyCertificatesQuery,\n type TecEnrollment,\n} from '../api/technospamApi';\nimport { formatDate, relativeTime } from '../utils/format';\nimport { cn } from '../utils/cn';\nimport {\n FOCUSED_CARD_CLASS,\n focusableCardProps,\n useFocusedRecordParam,\n} from '../hooks/useFocusedRecordParam';\nimport { FOCUS_PARAMS } from '../utils/pageContext';\n\nfunction EnrollmentRow({\n e,\n onOpen,\n focused,\n onToggle,\n}: {\n e: TecEnrollment;\n onOpen: () => void;\n /** The focused enrollment (`?enrollmentId=`) is the one the assistant acts on. */\n focused: boolean;\n onToggle: () => void;\n}) {\n const seed = `${e.course?.topic ?? ''} ${e.course?.title ?? 'Course'}`;\n return (\n <GlassCard\n className={cn(\n 'group flex cursor-pointer flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md sm:flex-row sm:items-center sm:gap-4',\n focused && FOCUSED_CARD_CLASS\n )}\n {...focusableCardProps(focused, onToggle)}\n >\n <span\n className={cn(\n 'flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br text-white shadow-sm transition-transform duration-200 group-hover:scale-105',\n coverGradient(seed)\n )}\n >\n {createElement(topicIcon(seed), { size: 20 })}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n aria-pressed={focused}\n onClick={onToggle}\n className=\"truncate text-left text-sm font-semibold text-text-primary group-hover:text-accent-primary\"\n >\n {e.course?.title ?? 'Course'}\n </button>\n <StatusBadge status={e.status} />\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-2 text-xs text-text-muted\">\n <span>{e.lessonsCompleted} lessons done</span>\n <span aria-hidden>·</span>\n <span>Active {relativeTime(e.lastActiveAt ?? e.startedAt)}</span>\n </div>\n <div className=\"mt-2 h-1.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: `${e.progressPct}%` }}\n />\n </div>\n </div>\n <div className=\"flex items-center justify-between gap-3 sm:flex-col sm:items-end sm:justify-center\">\n <div className=\"text-lg font-semibold tabular-nums text-text-primary\">{e.progressPct}%</div>\n <Button variant=\"ghost\" size=\"sm\" onClick={onOpen}>\n <PlayCircle size={15} /> Resume\n </Button>\n </div>\n </GlassCard>\n );\n}\n\n// Stable sibling lists: focusing an enrollment clears a focused certificate and vice versa.\nconst CERTIFICATE_SIBLING = [FOCUS_PARAMS.certificate] as const;\nconst ENROLLMENT_SIBLING = [FOCUS_PARAMS.enrollment] as const;\n\nexport function CoursesMyLearningPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n\n const { data, loading, error } = useEnrollmentsQuery(learnerId ? { learnerId } : {});\n // Self-scoped read — `tecListCertificates` needs the cross-learner\n // \"list-all\" action a plain learner does not hold, so their own earned\n // certificates never arrived here.\n const { data: certData } = useMyCertificatesQuery();\n // The focused enrollment / certificate live in the URL so the assistant's\n // page context names them (\"how far am I in this course?\").\n const [focusedEnrollmentId, setFocusedEnrollmentId] = useFocusedRecordParam(\n FOCUS_PARAMS.enrollment,\n CERTIFICATE_SIBLING\n );\n const [focusedCertificateId, setFocusedCertificateId] = useFocusedRecordParam(\n FOCUS_PARAMS.certificate,\n ENROLLMENT_SIBLING\n );\n\n const enrollments = data?.tecListEnrollments?.items ?? [];\n const certificates = certData?.tecMyCertificates ?? [];\n const completed = enrollments.filter((e) => e.status === 'COMPLETED').length;\n const inProgress = enrollments.filter(\n (e) => e.status === 'IN_PROGRESS' || e.status === 'ENROLLED'\n ).length;\n\n return (\n <PageLayout\n hero\n title=\"My Learning\"\n description=\"Track your enrolled courses, progress, and earned certificates.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/courses-page')}>\n <BookOpen size={16} /> Browse catalog\n </Button>\n }\n >\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard\n label=\"Enrolled\"\n value={enrollments.length}\n hint=\"Courses you've enrolled in\"\n icon={BookOpen}\n tone=\"blue\"\n />\n <StatCard\n label=\"In progress\"\n value={inProgress}\n hint=\"Courses in progress\"\n icon={PlayCircle}\n tone=\"indigo\"\n />\n <StatCard\n label=\"Completed\"\n value={completed}\n hint=\"Courses completed\"\n icon={GraduationCap}\n tone=\"green\"\n />\n <StatCard\n label=\"Certificates\"\n value={certificates.length}\n hint=\"Certificates earned\"\n icon={Award}\n tone=\"purple\"\n />\n </div>\n\n {loading && !enrollments.length ? (\n <LoadingRows count={3} height=\"h-24\" />\n ) : error ? (\n <ErrorView title=\"Couldn't load your learning\" error={error} />\n ) : enrollments.length === 0 ? (\n <EmptyView\n icon={<BookOpen size={26} />}\n title=\"You haven't enrolled yet\"\n description=\"Browse the catalog and enroll in a course to start building skill by doing.\"\n action={<Button onClick={() => navigate('/courses/courses-page')}>Browse courses</Button>}\n />\n ) : (\n <div className=\"space-y-3\">\n {enrollments.map((e) => (\n <EnrollmentRow\n key={e.id}\n e={e}\n onOpen={() => navigate(`/courses/course-details-page?id=${e.courseId}`)}\n focused={focusedEnrollmentId === e.id}\n onToggle={() => setFocusedEnrollmentId(focusedEnrollmentId === e.id ? null : e.id)}\n />\n ))}\n </div>\n )}\n\n {certificates.length > 0 ? (\n <div className=\"mt-8\">\n <h2 className=\"mb-3 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <Award size={18} className=\"text-accent-teal\" /> Certificates\n </h2>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {certificates.map((c) => (\n <GlassCard\n key={c.id}\n className={cn(\n 'group flex cursor-pointer flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-1 hover:shadow-lg',\n focusedCertificateId === c.id && FOCUSED_CARD_CLASS\n )}\n {...focusableCardProps(focusedCertificateId === c.id, () =>\n setFocusedCertificateId(focusedCertificateId === c.id ? null : c.id)\n )}\n >\n <div className=\"flex items-center gap-3\">\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 transition-transform duration-200 group-hover:scale-105\">\n <Award size={22} />\n </span>\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n aria-pressed={focusedCertificateId === c.id}\n onClick={() =>\n setFocusedCertificateId(focusedCertificateId === c.id ? null : c.id)\n }\n className=\"block max-w-full truncate text-left text-sm font-medium text-text-primary\"\n >\n {c.course?.title ?? 'Certificate'}\n </button>\n <div className=\"truncate text-xs text-text-muted\">Serial {c.serial}</div>\n </div>\n </div>\n <div className=\"mt-auto flex items-center justify-between border-t border-border-seam pt-3 text-xs text-text-muted\">\n <span>\n Score <span className=\"font-semibold text-text-primary\">{c.score}</span> ·{' '}\n {formatDate(c.issuedAt)}\n </span>\n {c.status === 'ISSUED' ? (\n <span className=\"inline-flex items-center gap-1 rounded-full border border-status-success-border bg-status-success-bg-subtle px-2 py-0.5 font-medium text-status-success-text\">\n <ShieldCheck size={12} /> Verified\n </span>\n ) : (\n <StatusBadge status={c.status} />\n )}\n </div>\n </GlassCard>\n ))}\n </div>\n </div>\n ) : null}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA4BA,SAAS,EAAc,EACrB,GACA,WACA,YACA,eAOC;CACD,IAAM,IAAO,GAAG,EAAE,QAAQ,SAAS,GAAG,GAAG,EAAE,QAAQ,SAAS;AAC5D,QACE,kBAAC,GAAD;EACE,WAAW,EACT,wJACA,KAAA,6BACD;EACD,GAAI,EAAmB,GAAS,EAAS;YAL3C;GAOE,kBAAC,QAAD;IACE,WAAW,EACT,iKACA,EAAc,EAAK,CACpB;cAEA,EAAc,EAAU,EAAK,EAAE,EAAE,MAAM,IAAI,CAAC;IACxC,CAAA;GACP,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,gBAAc;OACd,SAAS;OACT,WAAU;iBAET,EAAE,QAAQ,SAAS;OACb,CAAA,EACT,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA,CAC7B;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAE,kBAAiB,gBAAoB,EAAA,CAAA;OAC9C,kBAAC,QAAD;QAAM,eAAA;kBAAY;QAAQ,CAAA;OAC1B,kBAAC,QAAD,EAAA,UAAA,CAAM,WAAQ,EAAa,EAAE,gBAAgB,EAAE,UAAU,CAAQ,EAAA,CAAA;OAC7D;;KACN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OACE,WAAU;OACV,OAAO,EAAE,OAAO,GAAG,EAAE,YAAY,IAAI;OACrC,CAAA;MACE,CAAA;KACF;;GACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CAAuE,EAAE,aAAY,IAAO;QAC5F,kBAAC,GAAD;KAAQ,SAAQ;KAAQ,MAAK;KAAK,SAAS;eAA3C,CACE,kBAAC,GAAD,EAAY,MAAM,IAAM,CAAA,EAAA,UACjB;OACL;;GACI;;;AAKhB,IAAM,IAAsB,CAAC,EAAa,YAAY,EAChD,IAAqB,CAAC,EAAa,WAAW;AAEpD,SAAgB,IAA4B;CAC1C,IAAM,IAAW,GAAuB,EAClC,EAAE,mBAAgB,GAAe,EACjC,IAAY,GAAa,IAEzB,EAAE,SAAM,YAAS,aAAU,EAAoB,IAAY,EAAE,cAAW,GAAG,EAAE,CAAC,EAI9E,EAAE,MAAM,MAAa,GAAwB,EAG7C,CAAC,GAAqB,KAA0B,EACpD,EAAa,YACb,EACD,EACK,CAAC,GAAsB,KAA2B,EACtD,EAAa,aACb,EACD,EAEK,IAAc,GAAM,oBAAoB,SAAS,EAAE,EACnD,IAAe,GAAU,qBAAqB,EAAE,EAChD,IAAY,EAAY,QAAQ,MAAM,EAAE,WAAW,YAAY,CAAC,QAChE,IAAa,EAAY,QAC5B,MAAM,EAAE,WAAW,iBAAiB,EAAE,WAAW,WACnD,CAAC;AAEF,QACE,kBAAC,GAAD;EACE,MAAA;EACA,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,eAAe,EAAS,wBAAwB;aAA1E,CACE,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA,EAAA,kBACf;;YAPb;GAUE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAY;MACnB,MAAK;MACL,MAAM;MACN,MAAK;MACL,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO;MACP,MAAK;MACL,MAAM;MACN,MAAK;MACL,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO;MACP,MAAK;MACL,MAAM;MACN,MAAK;MACL,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAa;MACpB,MAAK;MACL,MAAM;MACN,MAAK;MACL,CAAA;KACE;;GAEL,KAAW,CAAC,EAAY,SACvB,kBAAC,GAAD;IAAa,OAAO;IAAG,QAAO;IAAS,CAAA,GACrC,IACF,kBAAC,GAAD;IAAW,OAAM;IAAqC;IAAS,CAAA,GAC7D,EAAY,WAAW,IACzB,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;IAC5B,OAAM;IACN,aAAY;IACZ,QAAQ,kBAAC,GAAD;KAAQ,eAAe,EAAS,wBAAwB;eAAE;KAAuB,CAAA;IACzF,CAAA,GAEF,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAY,KAAK,MAChB,kBAAC,GAAD;KAEK;KACH,cAAc,EAAS,mCAAmC,EAAE,WAAW;KACvE,SAAS,MAAwB,EAAE;KACnC,gBAAgB,EAAuB,MAAwB,EAAE,KAAK,OAAO,EAAE,GAAG;KAClF,EALK,EAAE,GAKP,CACF;IACE,CAAA;GAGP,EAAa,SAAS,IACrB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAAd,CACE,kBAAC,GAAD;MAAO,MAAM;MAAI,WAAU;MAAqB,CAAA,EAAA,gBAC7C;QACL,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAa,KAAK,MACjB,kBAAC,GAAD;MAEE,WAAW,EACT,iHACA,MAAyB,EAAE,MAAA,6BAC5B;MACD,GAAI,EAAmB,MAAyB,EAAE,UAChD,EAAwB,MAAyB,EAAE,KAAK,OAAO,EAAE,GAAG,CACrE;gBARH,CAUE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBACd,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;QACd,CAAA,EACP,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,UAAD;SACE,MAAK;SACL,gBAAc,MAAyB,EAAE;SACzC,eACE,EAAwB,MAAyB,EAAE,KAAK,OAAO,EAAE,GAAG;SAEtE,WAAU;mBAET,EAAE,QAAQ,SAAS;SACb,CAAA,EACT,kBAAC,OAAD;SAAK,WAAU;mBAAf,CAAkD,WAAQ,EAAE,OAAa;WACrE;UACF;UACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD,EAAA,UAAA;QAAM;QACE,kBAAC,QAAD;SAAM,WAAU;mBAAmC,EAAE;SAAa,CAAA;;QAAG;QAC1E,EAAW,EAAE,SAAS;QAClB,EAAA,CAAA,EACN,EAAE,WAAW,WACZ,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,kBAAC,GAAD,EAAa,MAAM,IAAM,CAAA,EAAA,YACpB;YAEP,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA,CAE/B;SACI;QAxCL,EAAE,GAwCG,CACZ;KACE,CAAA,CACF;QACJ;GACO"}
|
|
1
|
+
{"version":3,"file":"CoursesMyLearningPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesMyLearningPagePage.tsx"],"sourcesContent":["import { createElement } from 'react';\nimport { Award, BookOpen, GraduationCap, PlayCircle, ShieldCheck } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n coverGradient,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useEnrollmentsQuery,\n useMyCertificatesQuery,\n useMyNextLessonsQuery,\n type TecEnrollment,\n} from '../api/technospamApi';\nimport { formatDate, relativeTime } from '../utils/format';\nimport { cn } from '../utils/cn';\nimport {\n FOCUSED_CARD_CLASS,\n focusableCardProps,\n useFocusedRecordParam,\n} from '../hooks/useFocusedRecordParam';\nimport { FOCUS_PARAMS } from '../utils/pageContext';\nimport { resumePath } from '../utils/lessonPath';\n\nfunction EnrollmentRow({\n e,\n onOpen,\n focused,\n onToggle,\n}: {\n e: TecEnrollment;\n onOpen: () => void;\n /** The focused enrollment (`?enrollmentId=`) is the one the assistant acts on. */\n focused: boolean;\n onToggle: () => void;\n}) {\n const seed = `${e.course?.topic ?? ''} ${e.course?.title ?? 'Course'}`;\n return (\n <GlassCard\n className={cn(\n 'group flex cursor-pointer flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md sm:flex-row sm:items-center sm:gap-4',\n focused && FOCUSED_CARD_CLASS\n )}\n {...focusableCardProps(focused, onToggle)}\n >\n <span\n className={cn(\n 'flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br text-white shadow-sm transition-transform duration-200 group-hover:scale-105',\n coverGradient(seed)\n )}\n >\n {createElement(topicIcon(seed), { size: 20 })}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n aria-pressed={focused}\n onClick={onToggle}\n className=\"truncate text-left text-sm font-semibold text-text-primary group-hover:text-accent-primary\"\n >\n {e.course?.title ?? 'Course'}\n </button>\n <StatusBadge status={e.status} />\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-2 text-xs text-text-muted\">\n <span>{e.lessonsCompleted} lessons done</span>\n <span aria-hidden>·</span>\n <span>Active {relativeTime(e.lastActiveAt ?? e.startedAt)}</span>\n </div>\n <div className=\"mt-2 h-1.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: `${e.progressPct}%` }}\n />\n </div>\n </div>\n <div className=\"flex items-center justify-between gap-3 sm:flex-col sm:items-end sm:justify-center\">\n <div className=\"text-lg font-semibold tabular-nums text-text-primary\">{e.progressPct}%</div>\n <Button variant=\"ghost\" size=\"sm\" onClick={onOpen} data-testid={`resume-${e.courseId}`}>\n <PlayCircle size={15} /> Resume\n </Button>\n </div>\n </GlassCard>\n );\n}\n\n// Stable sibling lists: focusing an enrollment clears a focused certificate and vice versa.\nconst CERTIFICATE_SIBLING = [FOCUS_PARAMS.certificate] as const;\nconst ENROLLMENT_SIBLING = [FOCUS_PARAMS.enrollment] as const;\n\nexport function CoursesMyLearningPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n\n const { data, loading, error } = useEnrollmentsQuery(learnerId ? { learnerId } : {});\n // Self-scoped read — `tecListCertificates` needs the cross-learner\n // \"list-all\" action a plain learner does not hold, so their own earned\n // certificates never arrived here.\n const { data: certData } = useMyCertificatesQuery();\n // BOFF-4107: \"Resume\" deep-links into the next incomplete lesson.\n const { data: nextData } = useMyNextLessonsQuery();\n const nextLessonByCourse = new Map(\n (nextData?.tecMyEnrollments?.items ?? []).map((n) => [n.courseId, n.nextLessonId ?? null])\n );\n // The focused enrollment / certificate live in the URL so the assistant's\n // page context names them (\"how far am I in this course?\").\n const [focusedEnrollmentId, setFocusedEnrollmentId] = useFocusedRecordParam(\n FOCUS_PARAMS.enrollment,\n CERTIFICATE_SIBLING\n );\n const [focusedCertificateId, setFocusedCertificateId] = useFocusedRecordParam(\n FOCUS_PARAMS.certificate,\n ENROLLMENT_SIBLING\n );\n\n const enrollments = data?.tecListEnrollments?.items ?? [];\n const certificates = certData?.tecMyCertificates ?? [];\n const completed = enrollments.filter((e) => e.status === 'COMPLETED').length;\n const inProgress = enrollments.filter(\n (e) => e.status === 'IN_PROGRESS' || e.status === 'ENROLLED'\n ).length;\n\n return (\n <PageLayout\n hero\n title=\"My Learning\"\n description=\"Track your enrolled courses, progress, and earned certificates.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/courses-page')}>\n <BookOpen size={16} /> Browse catalog\n </Button>\n }\n >\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard\n label=\"Enrolled\"\n value={enrollments.length}\n hint=\"Courses you've enrolled in\"\n icon={BookOpen}\n tone=\"blue\"\n />\n <StatCard\n label=\"In progress\"\n value={inProgress}\n hint=\"Courses in progress\"\n icon={PlayCircle}\n tone=\"indigo\"\n />\n <StatCard\n label=\"Completed\"\n value={completed}\n hint=\"Courses completed\"\n icon={GraduationCap}\n tone=\"green\"\n />\n <StatCard\n label=\"Certificates\"\n value={certificates.length}\n hint=\"Certificates earned\"\n icon={Award}\n tone=\"purple\"\n />\n </div>\n\n {loading && !enrollments.length ? (\n <LoadingRows count={3} height=\"h-24\" />\n ) : error ? (\n <ErrorView title=\"Couldn't load your learning\" error={error} />\n ) : enrollments.length === 0 ? (\n <EmptyView\n icon={<BookOpen size={26} />}\n title=\"You haven't enrolled yet\"\n description=\"Browse the catalog and enroll in a course to start building skill by doing.\"\n action={<Button onClick={() => navigate('/courses/courses-page')}>Browse courses</Button>}\n />\n ) : (\n <div className=\"space-y-3\">\n {enrollments.map((e) => (\n <EnrollmentRow\n key={e.id}\n e={e}\n onOpen={() => navigate(resumePath(e.courseId, nextLessonByCourse.get(e.courseId)))}\n focused={focusedEnrollmentId === e.id}\n onToggle={() => setFocusedEnrollmentId(focusedEnrollmentId === e.id ? null : e.id)}\n />\n ))}\n </div>\n )}\n\n {certificates.length > 0 ? (\n <div className=\"mt-8\">\n <h2 className=\"mb-3 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <Award size={18} className=\"text-accent-teal\" /> Certificates\n </h2>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {certificates.map((c) => (\n <GlassCard\n key={c.id}\n className={cn(\n 'group flex cursor-pointer flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-1 hover:shadow-lg',\n focusedCertificateId === c.id && FOCUSED_CARD_CLASS\n )}\n {...focusableCardProps(focusedCertificateId === c.id, () =>\n setFocusedCertificateId(focusedCertificateId === c.id ? null : c.id)\n )}\n >\n <div className=\"flex items-center gap-3\">\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 transition-transform duration-200 group-hover:scale-105\">\n <Award size={22} />\n </span>\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n aria-pressed={focusedCertificateId === c.id}\n onClick={() =>\n setFocusedCertificateId(focusedCertificateId === c.id ? null : c.id)\n }\n className=\"block max-w-full truncate text-left text-sm font-medium text-text-primary\"\n >\n {c.course?.title ?? 'Certificate'}\n </button>\n <div className=\"truncate text-xs text-text-muted\">Serial {c.serial}</div>\n </div>\n </div>\n <div className=\"mt-auto flex items-center justify-between border-t border-border-seam pt-3 text-xs text-text-muted\">\n <span>\n Score <span className=\"font-semibold text-text-primary\">{c.score}</span> ·{' '}\n {formatDate(c.issuedAt)}\n </span>\n {c.status === 'ISSUED' ? (\n <span className=\"inline-flex items-center gap-1 rounded-full border border-status-success-border bg-status-success-bg-subtle px-2 py-0.5 font-medium text-status-success-text\">\n <ShieldCheck size={12} /> Verified\n </span>\n ) : (\n <StatusBadge status={c.status} />\n )}\n </div>\n </GlassCard>\n ))}\n </div>\n </div>\n ) : null}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardDashboardPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/DashboardDashboardPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DashboardDashboardPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/DashboardDashboardPagePage.tsx"],"names":[],"mappings":"AAsNA,wBAAgB,0BAA0B,gCA6VzC"}
|