@burdenoff/microfe-technospam 2026.1031.5 → 2026.1031.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/wspace-types.d.ts +1100 -147
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/stories/fixtures/technospamFixtures.d.ts.map +1 -1
- package/dist/technospam/api/canonicalLearning.d.ts +53 -0
- package/dist/technospam/api/canonicalLearning.d.ts.map +1 -0
- package/dist/technospam/api/canonicalLearning.js +57 -0
- package/dist/technospam/api/canonicalLearning.js.map +1 -0
- package/dist/technospam/api/technospamApi.d.ts +448 -16
- package/dist/technospam/api/technospamApi.d.ts.map +1 -1
- package/dist/technospam/api/technospamApi.js +417 -287
- package/dist/technospam/api/technospamApi.js.map +1 -1
- package/dist/technospam/components/StatusBadge.d.ts.map +1 -1
- package/dist/technospam/components/StatusBadge.js +2 -0
- package/dist/technospam/components/StatusBadge.js.map +1 -1
- package/dist/technospam/components/admin/EnterpriseIntegrationsTab.d.ts +6 -0
- package/dist/technospam/components/admin/EnterpriseIntegrationsTab.d.ts.map +1 -0
- package/dist/technospam/components/admin/EnterpriseIntegrationsTab.js +256 -0
- package/dist/technospam/components/admin/EnterpriseIntegrationsTab.js.map +1 -0
- package/dist/technospam/lib/enterprise.d.ts +21 -0
- package/dist/technospam/lib/enterprise.d.ts.map +1 -0
- package/dist/technospam/lib/enterprise.js +36 -0
- package/dist/technospam/lib/enterprise.js.map +1 -0
- package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js +206 -201
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
- package/dist/technospam/pages/ApplicationsPortfolioPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/ApplicationsPortfolioPagePage.js +26 -26
- package/dist/technospam/pages/ApplicationsPortfolioPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +196 -192
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesLessonPlayerPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesLessonPlayerPagePage.js +125 -121
- package/dist/technospam/pages/CoursesLessonPlayerPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesMyLearningPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesMyLearningPagePage.js +95 -95
- 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 +30 -30
- package/dist/technospam/pages/DashboardDashboardPagePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,11 +6,11 @@ import { MediaCover as a, topicIcon as o } from "../components/MediaCover.js";
|
|
|
6
6
|
import { formatDate as s, formatDateTime as c, formatMoney as l } from "../utils/format.js";
|
|
7
7
|
import { StatusBadge as u } from "../components/StatusBadge.js";
|
|
8
8
|
import { EmptyView as d, ErrorView as f, LoadingRows as p } from "../components/StateViews.js";
|
|
9
|
-
import {
|
|
9
|
+
import { useCoursesQuery as m, useHackathonsQuery as ee, useMyCertificatesQuery as h, useMyEnrollmentsQuery as g, useSessionsQuery as _, useSubmissionsQuery as v } from "../api/technospamApi.js";
|
|
10
10
|
import "../components/index.js";
|
|
11
11
|
import { resumePath as y } from "../utils/lessonPath.js";
|
|
12
|
-
import { useMemo as b, useState as
|
|
13
|
-
import { ArrowRight as
|
|
12
|
+
import { useMemo as b, useState as x } from "react";
|
|
13
|
+
import { ArrowRight as S, Award as te, BookOpen as C, Calendar as w, CalendarClock as T, CheckCircle2 as E, Clock as D, Code2 as O, GraduationCap as k, PlayCircle as A, Trophy as j, Users as M } from "lucide-react";
|
|
14
14
|
import { jsx as N, jsxs as P } from "react/jsx-runtime";
|
|
15
15
|
import { Button as F, GlassCard as I } from "@burdenoff/fe-libs/ui";
|
|
16
16
|
//#region src/technospam/pages/DashboardDashboardPagePage.tsx
|
|
@@ -28,7 +28,7 @@ function L({ title: e, actionLabel: t, onAction: n }) {
|
|
|
28
28
|
children: [
|
|
29
29
|
t,
|
|
30
30
|
" ",
|
|
31
|
-
/* @__PURE__ */ N(
|
|
31
|
+
/* @__PURE__ */ N(S, { size: 13 })
|
|
32
32
|
]
|
|
33
33
|
}) : null]
|
|
34
34
|
});
|
|
@@ -42,7 +42,7 @@ function R({ pct: e }) {
|
|
|
42
42
|
})
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
|
-
function
|
|
45
|
+
function z({ e, resumeLessonId: t, onOpen: r }) {
|
|
46
46
|
let i = n(), s = e.course?.title ?? i("technospam.common.courseFallback", "Course"), c = `${e.course?.topic ?? ""} ${s}`;
|
|
47
47
|
return /* @__PURE__ */ P("button", {
|
|
48
48
|
type: "button",
|
|
@@ -81,7 +81,7 @@ function re({ e, resumeLessonId: t, onOpen: r }) {
|
|
|
81
81
|
})]
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
|
-
function
|
|
84
|
+
function B({ course: e, onOpen: t }) {
|
|
85
85
|
return /* @__PURE__ */ P("button", {
|
|
86
86
|
type: "button",
|
|
87
87
|
onClick: t,
|
|
@@ -113,7 +113,7 @@ function ie({ course: e, onOpen: t }) {
|
|
|
113
113
|
})]
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
|
-
function
|
|
116
|
+
function V({ s: e }) {
|
|
117
117
|
return /* @__PURE__ */ P("div", {
|
|
118
118
|
className: "-mx-2 flex items-start gap-3 rounded-lg border-b border-border-seam px-2 py-3 transition-colors last:border-0 hover:bg-bg-sunken",
|
|
119
119
|
children: [/* @__PURE__ */ N("span", {
|
|
@@ -144,7 +144,7 @@ function z({ s: e }) {
|
|
|
144
144
|
})]
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
|
-
function
|
|
147
|
+
function ne({ h: e, onOpen: t }) {
|
|
148
148
|
let r = n();
|
|
149
149
|
return /* @__PURE__ */ P("button", {
|
|
150
150
|
type: "button",
|
|
@@ -186,20 +186,20 @@ function ae({ h: e, onOpen: t }) {
|
|
|
186
186
|
children: [
|
|
187
187
|
r("technospam.common.register", "Register"),
|
|
188
188
|
" ",
|
|
189
|
-
/* @__PURE__ */ N(
|
|
189
|
+
/* @__PURE__ */ N(S, { size: 12 })
|
|
190
190
|
]
|
|
191
191
|
})]
|
|
192
192
|
})]
|
|
193
193
|
});
|
|
194
194
|
}
|
|
195
|
-
function
|
|
196
|
-
let a = t(), { currentUser: o } = e(), c = n(), l = o?.id, u =
|
|
195
|
+
function H() {
|
|
196
|
+
let a = t(), { currentUser: o } = e(), c = n(), l = o?.id, u = g(), S = v(l ? { learnerId: l } : {}), w = _(l ? { menteeId: l } : {}), D = h(), A = m({ status: "PUBLISHED" }), R = ee({ status: "REGISTRATION_OPEN" }), H = u.enrollments, U = S.data?.tecListSubmissions?.items ?? [], W = w.data?.tecListSessions?.items ?? [], G = D.certificates, K = A.data?.tecListCourses?.items ?? [], q = R.data?.tecListHackathons?.items ?? [], re = o?.firstName || o?.name || o?.email?.split("@")[0] || "there", J = b(() => H.filter((e) => e.status === "IN_PROGRESS" || e.status === "ENROLLED"), [H]), Y = b(() => [...J].sort((e, t) => new Date(t.lastActiveAt ?? t.startedAt).getTime() - new Date(e.lastActiveAt ?? e.startedAt).getTime()).slice(0, 3), [J]), ie = b(() => new Set(U.filter((e) => e.verdict === "ACCEPTED").map((e) => e.problemId)).size, [U]), [X] = x(() => Date.now()), Z = b(() => [...W].filter((e) => (e.status === "CONFIRMED" || e.status === "REQUESTED") && new Date(e.scheduledAt).getTime() > X).sort((e, t) => new Date(e.scheduledAt).getTime() - new Date(t.scheduledAt).getTime()), [W, X]), Q = b(() => {
|
|
197
197
|
let e = new Set(H.map((e) => e.courseId));
|
|
198
198
|
return K.filter((t) => !e.has(t.id)).slice(0, 4);
|
|
199
199
|
}, [K, H]), $ = q.slice(0, 4);
|
|
200
200
|
return /* @__PURE__ */ P(r, {
|
|
201
201
|
hero: !0,
|
|
202
|
-
title: c("technospam.dashboard.title", "Welcome back, {{name}}", { name:
|
|
202
|
+
title: c("technospam.dashboard.title", "Welcome back, {{name}}", { name: re }),
|
|
203
203
|
description: c("technospam.dashboard.description", "Your learning hub — pick up where you left off, keep your skills sharp, and jump into what's next."),
|
|
204
204
|
actions: /* @__PURE__ */ P(F, {
|
|
205
205
|
variant: "outline",
|
|
@@ -222,7 +222,7 @@ function B() {
|
|
|
222
222
|
}),
|
|
223
223
|
/* @__PURE__ */ N(i, {
|
|
224
224
|
label: c("technospam.dashboard.problemsSolved", "Problems solved"),
|
|
225
|
-
value:
|
|
225
|
+
value: ie,
|
|
226
226
|
icon: O,
|
|
227
227
|
tone: "green"
|
|
228
228
|
}),
|
|
@@ -235,7 +235,7 @@ function B() {
|
|
|
235
235
|
/* @__PURE__ */ N(i, {
|
|
236
236
|
label: c("technospam.dashboard.certificates", "Certificates"),
|
|
237
237
|
value: G.length,
|
|
238
|
-
icon:
|
|
238
|
+
icon: te,
|
|
239
239
|
tone: "purple"
|
|
240
240
|
})
|
|
241
241
|
]
|
|
@@ -307,10 +307,10 @@ function B() {
|
|
|
307
307
|
})
|
|
308
308
|
}) : /* @__PURE__ */ N("div", {
|
|
309
309
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
310
|
-
children: Y.map((e) => /* @__PURE__ */ N(
|
|
310
|
+
children: Y.map((e) => /* @__PURE__ */ N(z, {
|
|
311
311
|
e,
|
|
312
|
-
resumeLessonId:
|
|
313
|
-
onOpen: () => a(y(e.courseId,
|
|
312
|
+
resumeLessonId: e.nextLessonId,
|
|
313
|
+
onOpen: () => a(y(e.courseId, e.nextLessonId))
|
|
314
314
|
}, e.id))
|
|
315
315
|
})]
|
|
316
316
|
}), /* @__PURE__ */ P(I, {
|
|
@@ -319,19 +319,19 @@ function B() {
|
|
|
319
319
|
title: c("technospam.dashboard.recommendedForYou", "Recommended for you"),
|
|
320
320
|
actionLabel: c("technospam.common.viewAll", "View all"),
|
|
321
321
|
onAction: () => a("/courses/courses-page")
|
|
322
|
-
}),
|
|
322
|
+
}), A.loading && !K.length ? /* @__PURE__ */ N(p, {
|
|
323
323
|
count: 2,
|
|
324
324
|
height: "h-20"
|
|
325
|
-
}) :
|
|
325
|
+
}) : A.error ? /* @__PURE__ */ N(f, {
|
|
326
326
|
title: c("technospam.common.errorLoadCourses", "Couldn't load courses"),
|
|
327
|
-
error:
|
|
327
|
+
error: A.error
|
|
328
328
|
}) : Q.length === 0 ? /* @__PURE__ */ N(d, {
|
|
329
329
|
icon: /* @__PURE__ */ N(k, { size: 24 }),
|
|
330
330
|
title: c("technospam.dashboard.noRecommendationsTitle", "No courses to recommend yet"),
|
|
331
331
|
description: c("technospam.dashboard.noRecommendationsDesc", "New courses are on the way. TechnoSpam is pre-launch — check back soon.")
|
|
332
332
|
}) : /* @__PURE__ */ N("div", {
|
|
333
333
|
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
334
|
-
children: Q.map((e) => /* @__PURE__ */ N(
|
|
334
|
+
children: Q.map((e) => /* @__PURE__ */ N(B, {
|
|
335
335
|
course: e,
|
|
336
336
|
onOpen: () => a(`/courses/course-details-page?id=${e.id}`)
|
|
337
337
|
}, e.id))
|
|
@@ -346,12 +346,12 @@ function B() {
|
|
|
346
346
|
title: c("technospam.dashboard.upcomingSessionsTitle", "Upcoming sessions"),
|
|
347
347
|
actionLabel: c("technospam.common.allSessions", "All sessions"),
|
|
348
348
|
onAction: () => a("/mentorship/sessions-page")
|
|
349
|
-
}),
|
|
349
|
+
}), w.loading && !W.length ? /* @__PURE__ */ N(p, {
|
|
350
350
|
count: 2,
|
|
351
351
|
height: "h-14"
|
|
352
|
-
}) :
|
|
352
|
+
}) : w.error ? /* @__PURE__ */ N(f, {
|
|
353
353
|
title: c("technospam.common.errorLoadSessions", "Couldn't load sessions"),
|
|
354
|
-
error:
|
|
354
|
+
error: w.error
|
|
355
355
|
}) : Z.length === 0 ? /* @__PURE__ */ N(d, {
|
|
356
356
|
icon: /* @__PURE__ */ N(T, { size: 24 }),
|
|
357
357
|
title: c("technospam.dashboard.noSessionsBookedTitle", "No sessions booked"),
|
|
@@ -361,7 +361,7 @@ function B() {
|
|
|
361
361
|
onClick: () => a("/mentorship/mentors-page"),
|
|
362
362
|
children: c("technospam.common.findMentor", "Find a mentor")
|
|
363
363
|
})
|
|
364
|
-
}) : /* @__PURE__ */ N("div", { children: Z.slice(0, 5).map((e) => /* @__PURE__ */ N(
|
|
364
|
+
}) : /* @__PURE__ */ N("div", { children: Z.slice(0, 5).map((e) => /* @__PURE__ */ N(V, { s: e }, e.id)) })]
|
|
365
365
|
}),
|
|
366
366
|
/* @__PURE__ */ P(I, {
|
|
367
367
|
className: "p-5",
|
|
@@ -369,19 +369,19 @@ function B() {
|
|
|
369
369
|
title: c("technospam.dashboard.openHackathons", "Open hackathons"),
|
|
370
370
|
actionLabel: c("technospam.common.viewAll", "View all"),
|
|
371
371
|
onAction: () => a("/hackathons/hackathons-page")
|
|
372
|
-
}),
|
|
372
|
+
}), R.loading && !q.length ? /* @__PURE__ */ N(p, {
|
|
373
373
|
count: 2,
|
|
374
374
|
height: "h-14"
|
|
375
|
-
}) :
|
|
375
|
+
}) : R.error ? /* @__PURE__ */ N(f, {
|
|
376
376
|
title: c("technospam.common.errorLoadHackathons", "Couldn't load hackathons"),
|
|
377
|
-
error:
|
|
377
|
+
error: R.error
|
|
378
378
|
}) : $.length === 0 ? /* @__PURE__ */ N(d, {
|
|
379
379
|
icon: /* @__PURE__ */ N(j, { size: 24 }),
|
|
380
380
|
title: c("technospam.dashboard.noOpenHackathonsTitle", "No open hackathons"),
|
|
381
381
|
description: c("technospam.dashboard.noOpenHackathonsDesc", "When registration opens for a hackathon, it will show up here to join.")
|
|
382
382
|
}) : /* @__PURE__ */ N("div", {
|
|
383
383
|
className: "space-y-3",
|
|
384
|
-
children: $.map((e) => /* @__PURE__ */ N(
|
|
384
|
+
children: $.map((e) => /* @__PURE__ */ N(ne, {
|
|
385
385
|
h: e,
|
|
386
386
|
onOpen: () => a(`/hackathons/hackathon-details-page?id=${e.id}`)
|
|
387
387
|
}, e.id))
|
|
@@ -422,6 +422,6 @@ function B() {
|
|
|
422
422
|
});
|
|
423
423
|
}
|
|
424
424
|
//#endregion
|
|
425
|
-
export {
|
|
425
|
+
export { H as DashboardDashboardPagePage };
|
|
426
426
|
|
|
427
427
|
//# sourceMappingURL=DashboardDashboardPagePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardDashboardPagePage.js","names":[],"sources":["../../../src/technospam/pages/DashboardDashboardPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n ArrowRight,\n Award,\n BookOpen,\n Calendar,\n CalendarClock,\n CheckCircle2,\n Clock,\n Code2,\n GraduationCap,\n PlayCircle,\n Trophy,\n Users,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadingRows,\n MediaCover,\n PageLayout,\n StatCard,\n StatusBadge,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useCertificatesQuery,\n useCoursesQuery,\n useEnrollmentsQuery,\n useHackathonsQuery,\n useMyNextLessonsQuery,\n useSessionsQuery,\n useSubmissionsQuery,\n type TecCourse,\n type TecEnrollment,\n type TecHackathon,\n type TecMentorSession,\n} from '../api/technospamApi';\nimport { formatDate, formatDateTime, formatMoney } from '../utils/format';\nimport { useTr } from '../hooks/useTr';\nimport { resumePath } from '../utils/lessonPath';\n\n/* --------------------------------------------------------------- helpers */\n\nfunction SectionHeader({\n title,\n actionLabel,\n onAction,\n}: {\n title: string;\n actionLabel?: string;\n onAction?: () => void;\n}) {\n return (\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n {actionLabel && onAction ? (\n <Button variant=\"ghost\" size=\"sm\" className=\"text-xs\" onClick={onAction}>\n {actionLabel} <ArrowRight size={13} />\n </Button>\n ) : null}\n </div>\n );\n}\n\nfunction ProgressBar({ pct }: { pct: number }) {\n return (\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-accent-primary transition-all\"\n style={{ width: `${Math.max(0, Math.min(100, pct))}%` }}\n />\n </div>\n );\n}\n\nfunction ContinueLearningCard({\n e,\n resumeLessonId,\n onOpen,\n}: {\n e: TecEnrollment;\n /** The lesson this card resumes into, once known (BOFF-4107). */\n resumeLessonId?: string | null;\n onOpen: () => void;\n}) {\n const tr = useTr();\n const title = e.course?.title ?? tr('technospam.common.courseFallback', 'Course');\n const seed = `${e.course?.topic ?? ''} ${title}`;\n return (\n <button\n type=\"button\"\n onClick={onOpen}\n data-resume-lesson={resumeLessonId ?? undefined}\n className=\"group flex flex-col overflow-hidden rounded-xl border border-border-seam bg-bg-surface text-left transition-all duration-200 hover:-translate-y-1 hover:shadow-lg focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <MediaCover seed={seed} icon={topicIcon(e.course?.topic ?? title)} height=\"h-20\">\n {e.course?.level ? <StatusBadge status={e.course.level} kind=\"level\" /> : <span />}\n <span className=\"flex h-7 w-7 items-center justify-center rounded-full bg-white/15 text-white backdrop-blur transition-transform duration-200 group-hover:scale-110\">\n <PlayCircle size={16} />\n </span>\n </MediaCover>\n <div className=\"flex flex-1 flex-col gap-2.5 p-3.5\">\n <div className=\"flex items-start gap-2\">\n <h3 className=\"line-clamp-2 flex-1 text-sm font-semibold text-text-primary group-hover:text-accent-primary\">\n {title}\n </h3>\n <StatusBadge status={e.status} />\n </div>\n <div className=\"mt-auto flex items-center gap-2.5\">\n <div className=\"flex-1\">\n <ProgressBar pct={e.progressPct} />\n </div>\n <span className=\"shrink-0 text-xs font-semibold text-text-secondary\">\n {e.progressPct}%\n </span>\n </div>\n </div>\n </button>\n );\n}\n\nfunction RecommendedCourseCard({ course, onOpen }: { course: TecCourse; onOpen: () => void }) {\n return (\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"group flex w-full flex-col overflow-hidden rounded-xl border border-border-seam bg-bg-surface text-left transition-all duration-200 hover:-translate-y-1 hover:shadow-lg focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <MediaCover\n seed={`${course.topic ?? ''} ${course.title}`}\n icon={topicIcon(course.topic ?? course.title)}\n height=\"h-20\"\n >\n <StatusBadge status={course.level} kind=\"level\" />\n </MediaCover>\n <div className=\"flex flex-1 flex-col gap-1.5 p-3.5\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary group-hover:text-accent-primary\">\n {course.title}\n </h3>\n {course.summary ? (\n <p className=\"line-clamp-2 text-xs text-text-secondary\">{course.summary}</p>\n ) : null}\n {course.topic ? (\n <span className=\"mt-auto truncate pt-0.5 text-xs text-text-muted\">{course.topic}</span>\n ) : null}\n </div>\n </button>\n );\n}\n\nfunction SessionRow({ s }: { s: TecMentorSession }) {\n return (\n <div className=\"-mx-2 flex items-start gap-3 rounded-lg border-b border-border-seam px-2 py-3 transition-colors last:border-0 hover:bg-bg-sunken\">\n <span className=\"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-accent-teal/15 text-accent-teal ring-1 ring-accent-teal/20\">\n <CalendarClock size={16} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"truncate text-sm font-medium text-text-primary\">{s.topic}</span>\n <StatusBadge status={s.status} />\n </div>\n {s.mentor?.headline ? (\n <div className=\"truncate text-xs text-text-muted\">{s.mentor.headline}</div>\n ) : null}\n <div className=\"mt-0.5 flex items-center gap-1.5 text-xs text-text-secondary\">\n <Clock size={12} className=\"text-text-muted\" />\n {formatDateTime(s.scheduledAt)}\n </div>\n </div>\n </div>\n );\n}\n\nfunction HackathonBanner({ h, onOpen }: { h: TecHackathon; onOpen: () => void }) {\n const tr = useTr();\n return (\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"group block w-full overflow-hidden rounded-xl border border-border-seam text-left transition-all duration-200 hover:-translate-y-1 hover:shadow-lg focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <MediaCover seed={`${h.theme ?? ''} ${h.title}`} icon={Trophy} height=\"h-24\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-white/80\">\n <Trophy size={12} /> {tr('technospam.common.openHackathon', 'Open hackathon')}\n </div>\n <h3 className=\"mt-0.5 truncate text-sm font-semibold text-white drop-shadow-sm\">\n {h.title}\n </h3>\n </div>\n {h.prizePool > 0 ? (\n <span className=\"shrink-0 rounded-full bg-white/20 px-2.5 py-1 text-xs font-semibold text-white backdrop-blur\">\n {formatMoney(h.prizePool, h.currency)}\n </span>\n ) : null}\n </MediaCover>\n <div className=\"flex items-center justify-between gap-2 bg-bg-surface px-3.5 py-2.5\">\n <span className=\"flex items-center gap-1.5 text-xs text-text-muted\">\n <Calendar size={12} />\n {formatDate(h.startAt)} – {formatDate(h.endAt)}\n </span>\n <span className=\"flex items-center gap-1 text-xs font-medium text-accent-primary opacity-0 transition-opacity group-hover:opacity-100\">\n {tr('technospam.common.register', 'Register')} <ArrowRight size={12} />\n </span>\n </div>\n </button>\n );\n}\n\n/* ------------------------------------------------------------------ page */\n\nexport function DashboardDashboardPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const tr = useTr();\n const learnerId = currentUser?.id;\n\n const enrollmentsQ = useEnrollmentsQuery(learnerId ? { learnerId } : {});\n // BOFF-4107: Continue Learning cards resume into the next incomplete lesson.\n const nextLessonsQ = useMyNextLessonsQuery();\n const nextLessonByCourse = useMemo(\n () =>\n new Map(\n (nextLessonsQ.data?.tecMyEnrollments?.items ?? []).map((n) => [\n n.courseId,\n n.nextLessonId ?? null,\n ])\n ),\n [nextLessonsQ.data]\n );\n const submissionsQ = useSubmissionsQuery(learnerId ? { learnerId } : {});\n const sessionsQ = useSessionsQuery(learnerId ? { menteeId: learnerId } : {});\n const certificatesQ = useCertificatesQuery(learnerId ? { learnerId } : {});\n const coursesQ = useCoursesQuery({ status: 'PUBLISHED' });\n const hackathonsQ = useHackathonsQuery({ status: 'REGISTRATION_OPEN' });\n\n const enrollments = enrollmentsQ.data?.tecListEnrollments?.items ?? [];\n const submissions = submissionsQ.data?.tecListSubmissions?.items ?? [];\n const sessions = sessionsQ.data?.tecListSessions?.items ?? [];\n const certificates = certificatesQ.data?.tecListCertificates ?? [];\n const courses = coursesQ.data?.tecListCourses?.items ?? [];\n const hackathons = hackathonsQ.data?.tecListHackathons?.items ?? [];\n\n const greetingName =\n currentUser?.firstName || currentUser?.name || currentUser?.email?.split('@')[0] || 'there';\n\n const inProgress = useMemo(\n () => enrollments.filter((e) => e.status === 'IN_PROGRESS' || e.status === 'ENROLLED'),\n [enrollments]\n );\n\n const continueLearning = useMemo(\n () =>\n [...inProgress]\n .sort(\n (a, b) =>\n new Date(b.lastActiveAt ?? b.startedAt).getTime() -\n new Date(a.lastActiveAt ?? a.startedAt).getTime()\n )\n .slice(0, 3),\n [inProgress]\n );\n\n const problemsSolved = useMemo(\n () => new Set(submissions.filter((s) => s.verdict === 'ACCEPTED').map((s) => s.problemId)).size,\n [submissions]\n );\n\n // Stable \"now\" captured once on mount (state initializers may call impure fns).\n const [now] = useState(() => Date.now());\n const upcomingSessions = useMemo(() => {\n return [...sessions]\n .filter(\n (s) =>\n (s.status === 'CONFIRMED' || s.status === 'REQUESTED') &&\n new Date(s.scheduledAt).getTime() > now\n )\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime());\n }, [sessions, now]);\n\n const recommended = useMemo(() => {\n const enrolledIds = new Set(enrollments.map((e) => e.courseId));\n return courses.filter((c) => !enrolledIds.has(c.id)).slice(0, 4);\n }, [courses, enrollments]);\n\n const openHackathons = hackathons.slice(0, 4);\n\n return (\n <PageLayout\n hero\n title={tr('technospam.dashboard.title', 'Welcome back, {{name}}', { name: greetingName })}\n description={tr(\n 'technospam.dashboard.description',\n \"Your learning hub — pick up where you left off, keep your skills sharp, and jump into what's next.\"\n )}\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> {tr('technospam.dashboard.myLearning', 'My Learning')}\n </Button>\n }\n >\n {/* Top metrics */}\n <div className=\"mb-5 grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <StatCard\n label={tr('technospam.dashboard.coursesInProgress', 'Courses in progress')}\n value={inProgress.length}\n icon={BookOpen}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.dashboard.problemsSolved', 'Problems solved')}\n value={problemsSolved}\n icon={Code2}\n tone=\"green\"\n />\n <StatCard\n label={tr('technospam.dashboard.upcomingSessions', 'Upcoming sessions')}\n value={upcomingSessions.length}\n icon={CalendarClock}\n tone=\"amber\"\n />\n <StatCard\n label={tr('technospam.dashboard.certificates', 'Certificates')}\n value={certificates.length}\n icon={Award}\n tone=\"purple\"\n />\n </div>\n\n {/* Quick actions */}\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/courses/courses-page')}\n >\n <BookOpen size={18} />\n <span className=\"text-xs\">{tr('technospam.common.browseCourses', 'Browse courses')}</span>\n </Button>\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/applications/problems-page')}\n >\n <Code2 size={18} />\n <span className=\"text-xs\">\n {tr('technospam.common.practiceProblems', 'Practice problems')}\n </span>\n </Button>\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/mentorship/mentors-page')}\n >\n <Users size={18} />\n <span className=\"text-xs\">{tr('technospam.common.findMentor', 'Find a mentor')}</span>\n </Button>\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/hackathons/hackathons-page')}\n >\n <Trophy size={18} />\n <span className=\"text-xs\">{tr('technospam.common.hackathons', 'Hackathons')}</span>\n </Button>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Main column */}\n <div className=\"space-y-6 lg:col-span-2\">\n {/* Continue learning */}\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.continueLearning', 'Continue learning')}\n actionLabel={tr('technospam.dashboard.myLearning', 'My Learning')}\n onAction={() => navigate('/courses/my-learning-page')}\n />\n {enrollmentsQ.loading && !enrollments.length ? (\n <LoadingRows count={2} height=\"h-16\" />\n ) : enrollmentsQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadCourses', \"Couldn't load your courses\")}\n error={enrollmentsQ.error}\n />\n ) : continueLearning.length === 0 ? (\n <EmptyView\n icon={<BookOpen size={24} />}\n title={tr(\n 'technospam.dashboard.noCoursesInProgressTitle',\n 'No courses in progress'\n )}\n description={tr(\n 'technospam.dashboard.noCoursesInProgressDesc',\n 'Enroll in a course to start building skill by doing — your active courses show up here.'\n )}\n action={\n <Button onClick={() => navigate('/courses/courses-page')}>\n {tr('technospam.common.browseCourses', 'Browse courses')}\n </Button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {continueLearning.map((e) => (\n <ContinueLearningCard\n key={e.id}\n e={e}\n resumeLessonId={nextLessonByCourse.get(e.courseId)}\n onOpen={() =>\n navigate(resumePath(e.courseId, nextLessonByCourse.get(e.courseId)))\n }\n />\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Recommended courses */}\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.recommendedForYou', 'Recommended for you')}\n actionLabel={tr('technospam.common.viewAll', 'View all')}\n onAction={() => navigate('/courses/courses-page')}\n />\n {coursesQ.loading && !courses.length ? (\n <LoadingRows count={2} height=\"h-20\" />\n ) : coursesQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadCourses', \"Couldn't load courses\")}\n error={coursesQ.error}\n />\n ) : recommended.length === 0 ? (\n <EmptyView\n icon={<GraduationCap size={24} />}\n title={tr(\n 'technospam.dashboard.noRecommendationsTitle',\n 'No courses to recommend yet'\n )}\n description={tr(\n 'technospam.dashboard.noRecommendationsDesc',\n 'New courses are on the way. TechnoSpam is pre-launch — check back soon.'\n )}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {recommended.map((c) => (\n <RecommendedCourseCard\n key={c.id}\n course={c}\n onOpen={() => navigate(`/courses/course-details-page?id=${c.id}`)}\n />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n\n {/* Side column */}\n <div className=\"space-y-6\">\n {/* Upcoming sessions */}\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.upcomingSessionsTitle', 'Upcoming sessions')}\n actionLabel={tr('technospam.common.allSessions', 'All sessions')}\n onAction={() => navigate('/mentorship/sessions-page')}\n />\n {sessionsQ.loading && !sessions.length ? (\n <LoadingRows count={2} height=\"h-14\" />\n ) : sessionsQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadSessions', \"Couldn't load sessions\")}\n error={sessionsQ.error}\n />\n ) : upcomingSessions.length === 0 ? (\n <EmptyView\n icon={<CalendarClock size={24} />}\n title={tr('technospam.dashboard.noSessionsBookedTitle', 'No sessions booked')}\n description={tr(\n 'technospam.dashboard.noSessionsBookedDesc',\n 'Book time with a mentor to get unblocked — your upcoming sessions appear here.'\n )}\n action={\n <Button variant=\"outline\" onClick={() => navigate('/mentorship/mentors-page')}>\n {tr('technospam.common.findMentor', 'Find a mentor')}\n </Button>\n }\n />\n ) : (\n <div>\n {upcomingSessions.slice(0, 5).map((s) => (\n <SessionRow key={s.id} s={s} />\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Open hackathons */}\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.openHackathons', 'Open hackathons')}\n actionLabel={tr('technospam.common.viewAll', 'View all')}\n onAction={() => navigate('/hackathons/hackathons-page')}\n />\n {hackathonsQ.loading && !hackathons.length ? (\n <LoadingRows count={2} height=\"h-14\" />\n ) : hackathonsQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadHackathons', \"Couldn't load hackathons\")}\n error={hackathonsQ.error}\n />\n ) : openHackathons.length === 0 ? (\n <EmptyView\n icon={<Trophy size={24} />}\n title={tr('technospam.dashboard.noOpenHackathonsTitle', 'No open hackathons')}\n description={tr(\n 'technospam.dashboard.noOpenHackathonsDesc',\n 'When registration opens for a hackathon, it will show up here to join.'\n )}\n />\n ) : (\n <div className=\"space-y-3\">\n {openHackathons.map((h) => (\n <HackathonBanner\n key={h.id}\n h={h}\n onOpen={() => navigate(`/hackathons/hackathon-details-page?id=${h.id}`)}\n />\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Certificates summary */}\n {certificates.length > 0 ? (\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.common.latestCertificate', 'Latest certificate')}\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-accent-primary/12 text-accent-teal\">\n <CheckCircle2 size={20} />\n </span>\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-medium text-text-primary\">\n {certificates[0]?.course?.title ??\n tr('technospam.common.courseFallback', 'Course')}\n </div>\n <div className=\"text-xs text-text-muted\">\n {tr('technospam.common.score', 'Score')} {certificates[0]?.score} ·{' '}\n {tr('technospam.common.issued', 'Issued')}{' '}\n {formatDate(certificates[0]?.issuedAt)}\n </div>\n </div>\n </div>\n </GlassCard>\n ) : null}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DashboardDashboardPagePage.js","names":[],"sources":["../../../src/technospam/pages/DashboardDashboardPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n ArrowRight,\n Award,\n BookOpen,\n Calendar,\n CalendarClock,\n CheckCircle2,\n Clock,\n Code2,\n GraduationCap,\n PlayCircle,\n Trophy,\n Users,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadingRows,\n MediaCover,\n PageLayout,\n StatCard,\n StatusBadge,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useCoursesQuery,\n useHackathonsQuery,\n useMyCertificatesQuery,\n useMyEnrollmentsQuery,\n useSessionsQuery,\n useSubmissionsQuery,\n type TecCourse,\n type TecEnrollment,\n type TecHackathon,\n type TecMentorSession,\n} from '../api/technospamApi';\nimport { formatDate, formatDateTime, formatMoney } from '../utils/format';\nimport { useTr } from '../hooks/useTr';\nimport { resumePath } from '../utils/lessonPath';\n\n/* --------------------------------------------------------------- helpers */\n\nfunction SectionHeader({\n title,\n actionLabel,\n onAction,\n}: {\n title: string;\n actionLabel?: string;\n onAction?: () => void;\n}) {\n return (\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n {actionLabel && onAction ? (\n <Button variant=\"ghost\" size=\"sm\" className=\"text-xs\" onClick={onAction}>\n {actionLabel} <ArrowRight size={13} />\n </Button>\n ) : null}\n </div>\n );\n}\n\nfunction ProgressBar({ pct }: { pct: number }) {\n return (\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-accent-primary transition-all\"\n style={{ width: `${Math.max(0, Math.min(100, pct))}%` }}\n />\n </div>\n );\n}\n\nfunction ContinueLearningCard({\n e,\n resumeLessonId,\n onOpen,\n}: {\n e: TecEnrollment;\n /** The lesson this card resumes into, once known (BOFF-4107). */\n resumeLessonId?: string | null;\n onOpen: () => void;\n}) {\n const tr = useTr();\n const title = e.course?.title ?? tr('technospam.common.courseFallback', 'Course');\n const seed = `${e.course?.topic ?? ''} ${title}`;\n return (\n <button\n type=\"button\"\n onClick={onOpen}\n data-resume-lesson={resumeLessonId ?? undefined}\n className=\"group flex flex-col overflow-hidden rounded-xl border border-border-seam bg-bg-surface text-left transition-all duration-200 hover:-translate-y-1 hover:shadow-lg focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <MediaCover seed={seed} icon={topicIcon(e.course?.topic ?? title)} height=\"h-20\">\n {e.course?.level ? <StatusBadge status={e.course.level} kind=\"level\" /> : <span />}\n <span className=\"flex h-7 w-7 items-center justify-center rounded-full bg-white/15 text-white backdrop-blur transition-transform duration-200 group-hover:scale-110\">\n <PlayCircle size={16} />\n </span>\n </MediaCover>\n <div className=\"flex flex-1 flex-col gap-2.5 p-3.5\">\n <div className=\"flex items-start gap-2\">\n <h3 className=\"line-clamp-2 flex-1 text-sm font-semibold text-text-primary group-hover:text-accent-primary\">\n {title}\n </h3>\n <StatusBadge status={e.status} />\n </div>\n <div className=\"mt-auto flex items-center gap-2.5\">\n <div className=\"flex-1\">\n <ProgressBar pct={e.progressPct} />\n </div>\n <span className=\"shrink-0 text-xs font-semibold text-text-secondary\">\n {e.progressPct}%\n </span>\n </div>\n </div>\n </button>\n );\n}\n\nfunction RecommendedCourseCard({ course, onOpen }: { course: TecCourse; onOpen: () => void }) {\n return (\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"group flex w-full flex-col overflow-hidden rounded-xl border border-border-seam bg-bg-surface text-left transition-all duration-200 hover:-translate-y-1 hover:shadow-lg focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <MediaCover\n seed={`${course.topic ?? ''} ${course.title}`}\n icon={topicIcon(course.topic ?? course.title)}\n height=\"h-20\"\n >\n <StatusBadge status={course.level} kind=\"level\" />\n </MediaCover>\n <div className=\"flex flex-1 flex-col gap-1.5 p-3.5\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary group-hover:text-accent-primary\">\n {course.title}\n </h3>\n {course.summary ? (\n <p className=\"line-clamp-2 text-xs text-text-secondary\">{course.summary}</p>\n ) : null}\n {course.topic ? (\n <span className=\"mt-auto truncate pt-0.5 text-xs text-text-muted\">{course.topic}</span>\n ) : null}\n </div>\n </button>\n );\n}\n\nfunction SessionRow({ s }: { s: TecMentorSession }) {\n return (\n <div className=\"-mx-2 flex items-start gap-3 rounded-lg border-b border-border-seam px-2 py-3 transition-colors last:border-0 hover:bg-bg-sunken\">\n <span className=\"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-accent-teal/15 text-accent-teal ring-1 ring-accent-teal/20\">\n <CalendarClock size={16} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"truncate text-sm font-medium text-text-primary\">{s.topic}</span>\n <StatusBadge status={s.status} />\n </div>\n {s.mentor?.headline ? (\n <div className=\"truncate text-xs text-text-muted\">{s.mentor.headline}</div>\n ) : null}\n <div className=\"mt-0.5 flex items-center gap-1.5 text-xs text-text-secondary\">\n <Clock size={12} className=\"text-text-muted\" />\n {formatDateTime(s.scheduledAt)}\n </div>\n </div>\n </div>\n );\n}\n\nfunction HackathonBanner({ h, onOpen }: { h: TecHackathon; onOpen: () => void }) {\n const tr = useTr();\n return (\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"group block w-full overflow-hidden rounded-xl border border-border-seam text-left transition-all duration-200 hover:-translate-y-1 hover:shadow-lg focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <MediaCover seed={`${h.theme ?? ''} ${h.title}`} icon={Trophy} height=\"h-24\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-white/80\">\n <Trophy size={12} /> {tr('technospam.common.openHackathon', 'Open hackathon')}\n </div>\n <h3 className=\"mt-0.5 truncate text-sm font-semibold text-white drop-shadow-sm\">\n {h.title}\n </h3>\n </div>\n {h.prizePool > 0 ? (\n <span className=\"shrink-0 rounded-full bg-white/20 px-2.5 py-1 text-xs font-semibold text-white backdrop-blur\">\n {formatMoney(h.prizePool, h.currency)}\n </span>\n ) : null}\n </MediaCover>\n <div className=\"flex items-center justify-between gap-2 bg-bg-surface px-3.5 py-2.5\">\n <span className=\"flex items-center gap-1.5 text-xs text-text-muted\">\n <Calendar size={12} />\n {formatDate(h.startAt)} – {formatDate(h.endAt)}\n </span>\n <span className=\"flex items-center gap-1 text-xs font-medium text-accent-primary opacity-0 transition-opacity group-hover:opacity-100\">\n {tr('technospam.common.register', 'Register')} <ArrowRight size={12} />\n </span>\n </div>\n </button>\n );\n}\n\n/* ------------------------------------------------------------------ page */\n\nexport function DashboardDashboardPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const tr = useTr();\n const learnerId = currentUser?.id;\n\n // BOFF-10481: the learner's own enrollments (each with its next lesson, for\n // the BOFF-4107 Continue Learning deep link) and certificates come from the\n // canonical, self-scoped Buildmyiq fields, not the cross-learner list-all\n // reads a plain learner is not granted.\n const enrollmentsQ = useMyEnrollmentsQuery();\n const submissionsQ = useSubmissionsQuery(learnerId ? { learnerId } : {});\n const sessionsQ = useSessionsQuery(learnerId ? { menteeId: learnerId } : {});\n const certificatesQ = useMyCertificatesQuery();\n const coursesQ = useCoursesQuery({ status: 'PUBLISHED' });\n const hackathonsQ = useHackathonsQuery({ status: 'REGISTRATION_OPEN' });\n\n const enrollments = enrollmentsQ.enrollments;\n const submissions = submissionsQ.data?.tecListSubmissions?.items ?? [];\n const sessions = sessionsQ.data?.tecListSessions?.items ?? [];\n const certificates = certificatesQ.certificates;\n const courses = coursesQ.data?.tecListCourses?.items ?? [];\n const hackathons = hackathonsQ.data?.tecListHackathons?.items ?? [];\n\n const greetingName =\n currentUser?.firstName || currentUser?.name || currentUser?.email?.split('@')[0] || 'there';\n\n const inProgress = useMemo(\n () => enrollments.filter((e) => e.status === 'IN_PROGRESS' || e.status === 'ENROLLED'),\n [enrollments]\n );\n\n const continueLearning = useMemo(\n () =>\n [...inProgress]\n .sort(\n (a, b) =>\n new Date(b.lastActiveAt ?? b.startedAt).getTime() -\n new Date(a.lastActiveAt ?? a.startedAt).getTime()\n )\n .slice(0, 3),\n [inProgress]\n );\n\n const problemsSolved = useMemo(\n () => new Set(submissions.filter((s) => s.verdict === 'ACCEPTED').map((s) => s.problemId)).size,\n [submissions]\n );\n\n // Stable \"now\" captured once on mount (state initializers may call impure fns).\n const [now] = useState(() => Date.now());\n const upcomingSessions = useMemo(() => {\n return [...sessions]\n .filter(\n (s) =>\n (s.status === 'CONFIRMED' || s.status === 'REQUESTED') &&\n new Date(s.scheduledAt).getTime() > now\n )\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime());\n }, [sessions, now]);\n\n const recommended = useMemo(() => {\n const enrolledIds = new Set(enrollments.map((e) => e.courseId));\n return courses.filter((c) => !enrolledIds.has(c.id)).slice(0, 4);\n }, [courses, enrollments]);\n\n const openHackathons = hackathons.slice(0, 4);\n\n return (\n <PageLayout\n hero\n title={tr('technospam.dashboard.title', 'Welcome back, {{name}}', { name: greetingName })}\n description={tr(\n 'technospam.dashboard.description',\n \"Your learning hub — pick up where you left off, keep your skills sharp, and jump into what's next.\"\n )}\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> {tr('technospam.dashboard.myLearning', 'My Learning')}\n </Button>\n }\n >\n {/* Top metrics */}\n <div className=\"mb-5 grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <StatCard\n label={tr('technospam.dashboard.coursesInProgress', 'Courses in progress')}\n value={inProgress.length}\n icon={BookOpen}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.dashboard.problemsSolved', 'Problems solved')}\n value={problemsSolved}\n icon={Code2}\n tone=\"green\"\n />\n <StatCard\n label={tr('technospam.dashboard.upcomingSessions', 'Upcoming sessions')}\n value={upcomingSessions.length}\n icon={CalendarClock}\n tone=\"amber\"\n />\n <StatCard\n label={tr('technospam.dashboard.certificates', 'Certificates')}\n value={certificates.length}\n icon={Award}\n tone=\"purple\"\n />\n </div>\n\n {/* Quick actions */}\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/courses/courses-page')}\n >\n <BookOpen size={18} />\n <span className=\"text-xs\">{tr('technospam.common.browseCourses', 'Browse courses')}</span>\n </Button>\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/applications/problems-page')}\n >\n <Code2 size={18} />\n <span className=\"text-xs\">\n {tr('technospam.common.practiceProblems', 'Practice problems')}\n </span>\n </Button>\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/mentorship/mentors-page')}\n >\n <Users size={18} />\n <span className=\"text-xs\">{tr('technospam.common.findMentor', 'Find a mentor')}</span>\n </Button>\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/hackathons/hackathons-page')}\n >\n <Trophy size={18} />\n <span className=\"text-xs\">{tr('technospam.common.hackathons', 'Hackathons')}</span>\n </Button>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Main column */}\n <div className=\"space-y-6 lg:col-span-2\">\n {/* Continue learning */}\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.continueLearning', 'Continue learning')}\n actionLabel={tr('technospam.dashboard.myLearning', 'My Learning')}\n onAction={() => navigate('/courses/my-learning-page')}\n />\n {enrollmentsQ.loading && !enrollments.length ? (\n <LoadingRows count={2} height=\"h-16\" />\n ) : enrollmentsQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadCourses', \"Couldn't load your courses\")}\n error={enrollmentsQ.error}\n />\n ) : continueLearning.length === 0 ? (\n <EmptyView\n icon={<BookOpen size={24} />}\n title={tr(\n 'technospam.dashboard.noCoursesInProgressTitle',\n 'No courses in progress'\n )}\n description={tr(\n 'technospam.dashboard.noCoursesInProgressDesc',\n 'Enroll in a course to start building skill by doing — your active courses show up here.'\n )}\n action={\n <Button onClick={() => navigate('/courses/courses-page')}>\n {tr('technospam.common.browseCourses', 'Browse courses')}\n </Button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {continueLearning.map((e) => (\n <ContinueLearningCard\n key={e.id}\n e={e}\n resumeLessonId={e.nextLessonId}\n onOpen={() => navigate(resumePath(e.courseId, e.nextLessonId))}\n />\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Recommended courses */}\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.recommendedForYou', 'Recommended for you')}\n actionLabel={tr('technospam.common.viewAll', 'View all')}\n onAction={() => navigate('/courses/courses-page')}\n />\n {coursesQ.loading && !courses.length ? (\n <LoadingRows count={2} height=\"h-20\" />\n ) : coursesQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadCourses', \"Couldn't load courses\")}\n error={coursesQ.error}\n />\n ) : recommended.length === 0 ? (\n <EmptyView\n icon={<GraduationCap size={24} />}\n title={tr(\n 'technospam.dashboard.noRecommendationsTitle',\n 'No courses to recommend yet'\n )}\n description={tr(\n 'technospam.dashboard.noRecommendationsDesc',\n 'New courses are on the way. TechnoSpam is pre-launch — check back soon.'\n )}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {recommended.map((c) => (\n <RecommendedCourseCard\n key={c.id}\n course={c}\n onOpen={() => navigate(`/courses/course-details-page?id=${c.id}`)}\n />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n\n {/* Side column */}\n <div className=\"space-y-6\">\n {/* Upcoming sessions */}\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.upcomingSessionsTitle', 'Upcoming sessions')}\n actionLabel={tr('technospam.common.allSessions', 'All sessions')}\n onAction={() => navigate('/mentorship/sessions-page')}\n />\n {sessionsQ.loading && !sessions.length ? (\n <LoadingRows count={2} height=\"h-14\" />\n ) : sessionsQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadSessions', \"Couldn't load sessions\")}\n error={sessionsQ.error}\n />\n ) : upcomingSessions.length === 0 ? (\n <EmptyView\n icon={<CalendarClock size={24} />}\n title={tr('technospam.dashboard.noSessionsBookedTitle', 'No sessions booked')}\n description={tr(\n 'technospam.dashboard.noSessionsBookedDesc',\n 'Book time with a mentor to get unblocked — your upcoming sessions appear here.'\n )}\n action={\n <Button variant=\"outline\" onClick={() => navigate('/mentorship/mentors-page')}>\n {tr('technospam.common.findMentor', 'Find a mentor')}\n </Button>\n }\n />\n ) : (\n <div>\n {upcomingSessions.slice(0, 5).map((s) => (\n <SessionRow key={s.id} s={s} />\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Open hackathons */}\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.openHackathons', 'Open hackathons')}\n actionLabel={tr('technospam.common.viewAll', 'View all')}\n onAction={() => navigate('/hackathons/hackathons-page')}\n />\n {hackathonsQ.loading && !hackathons.length ? (\n <LoadingRows count={2} height=\"h-14\" />\n ) : hackathonsQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadHackathons', \"Couldn't load hackathons\")}\n error={hackathonsQ.error}\n />\n ) : openHackathons.length === 0 ? (\n <EmptyView\n icon={<Trophy size={24} />}\n title={tr('technospam.dashboard.noOpenHackathonsTitle', 'No open hackathons')}\n description={tr(\n 'technospam.dashboard.noOpenHackathonsDesc',\n 'When registration opens for a hackathon, it will show up here to join.'\n )}\n />\n ) : (\n <div className=\"space-y-3\">\n {openHackathons.map((h) => (\n <HackathonBanner\n key={h.id}\n h={h}\n onOpen={() => navigate(`/hackathons/hackathon-details-page?id=${h.id}`)}\n />\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Certificates summary */}\n {certificates.length > 0 ? (\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.common.latestCertificate', 'Latest certificate')}\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-accent-primary/12 text-accent-teal\">\n <CheckCircle2 size={20} />\n </span>\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-medium text-text-primary\">\n {certificates[0]?.course?.title ??\n tr('technospam.common.courseFallback', 'Course')}\n </div>\n <div className=\"text-xs text-text-muted\">\n {tr('technospam.common.score', 'Score')} {certificates[0]?.score} ·{' '}\n {tr('technospam.common.issued', 'Issued')}{' '}\n {formatDate(certificates[0]?.issuedAt)}\n </div>\n </div>\n </div>\n </GlassCard>\n ) : null}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|