@burdenoff/microfe-technospam 2026.1102.1 → 2026.1102.3

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 (133) hide show
  1. package/dist/generated/wspace-types.d.ts +7468 -141
  2. package/dist/generated/wspace-types.d.ts.map +1 -1
  3. package/dist/node_modules/@codemirror/autocomplete/dist/index.js +527 -0
  4. package/dist/node_modules/@codemirror/autocomplete/dist/index.js.map +1 -0
  5. package/dist/node_modules/@codemirror/commands/dist/index.js +1108 -0
  6. package/dist/node_modules/@codemirror/commands/dist/index.js.map +1 -0
  7. package/dist/node_modules/@codemirror/lang-go/dist/index.js +206 -0
  8. package/dist/node_modules/@codemirror/lang-go/dist/index.js.map +1 -0
  9. package/dist/node_modules/@codemirror/lang-javascript/dist/index.js +321 -0
  10. package/dist/node_modules/@codemirror/lang-javascript/dist/index.js.map +1 -0
  11. package/dist/node_modules/@codemirror/lang-python/dist/index.js +265 -0
  12. package/dist/node_modules/@codemirror/lang-python/dist/index.js.map +1 -0
  13. package/dist/node_modules/@codemirror/language/dist/index.js +957 -0
  14. package/dist/node_modules/@codemirror/language/dist/index.js.map +1 -0
  15. package/dist/node_modules/@codemirror/legacy-modes/mode/ruby.js +164 -0
  16. package/dist/node_modules/@codemirror/legacy-modes/mode/ruby.js.map +1 -0
  17. package/dist/node_modules/@codemirror/legacy-modes/mode/shell.js +109 -0
  18. package/dist/node_modules/@codemirror/legacy-modes/mode/shell.js.map +1 -0
  19. package/dist/node_modules/@codemirror/state/dist/index.js +1739 -0
  20. package/dist/node_modules/@codemirror/state/dist/index.js.map +1 -0
  21. package/dist/node_modules/@codemirror/streamparser/dist/index.js +70 -0
  22. package/dist/node_modules/@codemirror/streamparser/dist/index.js.map +1 -0
  23. package/dist/node_modules/@codemirror/view/dist/index.js +5427 -0
  24. package/dist/node_modules/@codemirror/view/dist/index.js.map +1 -0
  25. package/dist/node_modules/@lezer/common/dist/index.js +896 -0
  26. package/dist/node_modules/@lezer/common/dist/index.js.map +1 -0
  27. package/dist/node_modules/@lezer/go/dist/index.js +215 -0
  28. package/dist/node_modules/@lezer/go/dist/index.js.map +1 -0
  29. package/dist/node_modules/@lezer/highlight/dist/index.js +395 -0
  30. package/dist/node_modules/@lezer/highlight/dist/index.js.map +1 -0
  31. package/dist/node_modules/@lezer/javascript/dist/index.js +437 -0
  32. package/dist/node_modules/@lezer/javascript/dist/index.js.map +1 -0
  33. package/dist/node_modules/@lezer/lr/dist/index.js +842 -0
  34. package/dist/node_modules/@lezer/lr/dist/index.js.map +1 -0
  35. package/dist/node_modules/@lezer/python/dist/index.js +352 -0
  36. package/dist/node_modules/@lezer/python/dist/index.js.map +1 -0
  37. package/dist/node_modules/@marijn/find-cluster-break/src/index.js +66 -0
  38. package/dist/node_modules/@marijn/find-cluster-break/src/index.js.map +1 -0
  39. package/dist/node_modules/style-mod/src/style-mod.js +74 -0
  40. package/dist/node_modules/style-mod/src/style-mod.js.map +1 -0
  41. package/dist/node_modules/w3c-keyname/index.js +90 -0
  42. package/dist/node_modules/w3c-keyname/index.js.map +1 -0
  43. package/dist/stories/fixtures/technospamFixtures.d.ts.map +1 -1
  44. package/dist/stories/journeys/Problems.stories.d.ts.map +1 -1
  45. package/dist/technospam/TechnoSpamRoot.d.ts.map +1 -1
  46. package/dist/technospam/TechnoSpamRoot.js +187 -178
  47. package/dist/technospam/TechnoSpamRoot.js.map +1 -1
  48. package/dist/technospam/api/technospamApi.d.ts +39 -0
  49. package/dist/technospam/api/technospamApi.d.ts.map +1 -1
  50. package/dist/technospam/api/technospamApi.js +184 -150
  51. package/dist/technospam/api/technospamApi.js.map +1 -1
  52. package/dist/technospam/components/MediaCover.js +2 -2
  53. package/dist/technospam/components/MediaCover.js.map +1 -1
  54. package/dist/technospam/components/MobileSectionTabs.d.ts +9 -0
  55. package/dist/technospam/components/MobileSectionTabs.d.ts.map +1 -0
  56. package/dist/technospam/components/MobileSectionTabs.js +43 -0
  57. package/dist/technospam/components/MobileSectionTabs.js.map +1 -0
  58. package/dist/technospam/components/PageLayout.d.ts +7 -1
  59. package/dist/technospam/components/PageLayout.d.ts.map +1 -1
  60. package/dist/technospam/components/PageLayout.js +40 -28
  61. package/dist/technospam/components/PageLayout.js.map +1 -1
  62. package/dist/technospam/components/SubmissionViews.d.ts +6 -2
  63. package/dist/technospam/components/SubmissionViews.d.ts.map +1 -1
  64. package/dist/technospam/components/SubmissionViews.js +6 -5
  65. package/dist/technospam/components/SubmissionViews.js.map +1 -1
  66. package/dist/technospam/components/TechnoSpamErrorBoundary.d.ts +6 -0
  67. package/dist/technospam/components/TechnoSpamErrorBoundary.d.ts.map +1 -1
  68. package/dist/technospam/components/TechnoSpamErrorBoundary.js +2 -2
  69. package/dist/technospam/components/TechnoSpamErrorBoundary.js.map +1 -1
  70. package/dist/technospam/components/editor/CodeEditor.d.ts +26 -0
  71. package/dist/technospam/components/editor/CodeEditor.d.ts.map +1 -0
  72. package/dist/technospam/components/editor/CodeEditor.js +102 -0
  73. package/dist/technospam/components/editor/CodeEditor.js.map +1 -0
  74. package/dist/technospam/components/editor/RunOutputPanel.d.ts +14 -0
  75. package/dist/technospam/components/editor/RunOutputPanel.d.ts.map +1 -0
  76. package/dist/technospam/components/editor/RunOutputPanel.js +127 -0
  77. package/dist/technospam/components/editor/RunOutputPanel.js.map +1 -0
  78. package/dist/technospam/components/editor/editorLanguages.d.ts +11 -0
  79. package/dist/technospam/components/editor/editorLanguages.d.ts.map +1 -0
  80. package/dist/technospam/components/editor/editorLanguages.js +30 -0
  81. package/dist/technospam/components/editor/editorLanguages.js.map +1 -0
  82. package/dist/technospam/components/editor/editorTheme.d.ts +11 -0
  83. package/dist/technospam/components/editor/editorTheme.d.ts.map +1 -0
  84. package/dist/technospam/components/editor/editorTheme.js +120 -0
  85. package/dist/technospam/components/editor/editorTheme.js.map +1 -0
  86. package/dist/technospam/components/lesson/SaveCourseOffline.js +6 -6
  87. package/dist/technospam/hooks/usePhoneLayout.d.ts +18 -0
  88. package/dist/technospam/hooks/usePhoneLayout.d.ts.map +1 -0
  89. package/dist/technospam/hooks/usePhoneLayout.js +14 -0
  90. package/dist/technospam/hooks/usePhoneLayout.js.map +1 -0
  91. package/dist/technospam/hooks/useSolutionRunner.d.ts +35 -0
  92. package/dist/technospam/hooks/useSolutionRunner.d.ts.map +1 -0
  93. package/dist/technospam/hooks/useSolutionRunner.js +66 -0
  94. package/dist/technospam/hooks/useSolutionRunner.js.map +1 -0
  95. package/dist/technospam/hooks/useTechnoSpamAnalytics.d.ts +1 -1
  96. package/dist/technospam/hooks/useTechnoSpamAnalytics.d.ts.map +1 -1
  97. package/dist/technospam/hooks/useTechnoSpamAnalytics.js.map +1 -1
  98. package/dist/technospam/pages/AdminAdminDashboardPagePage.js +1 -1
  99. package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
  100. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.d.ts.map +1 -1
  101. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +416 -263
  102. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
  103. package/dist/technospam/pages/ApplicationsProblemsPagePage.d.ts.map +1 -1
  104. package/dist/technospam/pages/ApplicationsProblemsPagePage.js +89 -87
  105. package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
  106. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +3 -3
  107. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
  108. package/dist/technospam/pages/CoursesCoursesPagePage.d.ts.map +1 -1
  109. package/dist/technospam/pages/CoursesCoursesPagePage.js +117 -81
  110. package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
  111. package/dist/technospam/pages/CoursesMyLearningPagePage.js +1 -1
  112. package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
  113. package/dist/technospam/pages/DashboardDashboardPagePage.d.ts.map +1 -1
  114. package/dist/technospam/pages/DashboardDashboardPagePage.js +21 -17
  115. package/dist/technospam/pages/DashboardDashboardPagePage.js.map +1 -1
  116. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +3 -3
  117. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
  118. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +4 -4
  119. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
  120. package/dist/technospam/pages/MentorshipMentorsPagePage.d.ts.map +1 -1
  121. package/dist/technospam/pages/MentorshipMentorsPagePage.js +6 -4
  122. package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
  123. package/dist/technospam/pages/MentorshipSessionsPagePage.js +1 -1
  124. package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
  125. package/dist/technospam/types/index.d.ts +7 -0
  126. package/dist/technospam/types/index.d.ts.map +1 -1
  127. package/dist/technospam/utils/mobileTabs.d.ts +45 -0
  128. package/dist/technospam/utils/mobileTabs.d.ts.map +1 -0
  129. package/dist/technospam/utils/mobileTabs.js +70 -0
  130. package/dist/technospam/utils/mobileTabs.js.map +1 -0
  131. package/dist/test/matchMedia.d.ts +6 -0
  132. package/dist/test/matchMedia.d.ts.map +1 -0
  133. package/package.json +16 -6
@@ -14,60 +14,85 @@ import { CardOpenButton as v } from "../components/CardOpenButton.js";
14
14
  import "../components/index.js";
15
15
  import { TAB_PANEL_FOCUS_CLASS as y } from "../lib/a11y.js";
16
16
  import { useMemo as b, useState as x } from "react";
17
- import { BookOpen as S, Clock as C, GraduationCap as w, Layers as T, Search as E, Sparkles as D } from "lucide-react";
18
- import { jsx as O, jsxs as k } from "react/jsx-runtime";
19
- import { Badge as A, Button as j, GlassCard as M, Input as N, Tabs as P, TabsContent as F, TabsList as I, TabsTrigger as L } from "@burdenoff/fe-libs/ui";
17
+ import { BookOpen as S, ChevronRight as C, Clock as w, GraduationCap as T, Layers as E, Search as D, Sparkles as O } from "lucide-react";
18
+ import { jsx as k, jsxs as A } from "react/jsx-runtime";
19
+ import { Badge as j, Button as M, GlassCard as N, Input as P, Tabs as F, TabsContent as I, TabsList as L, TabsTrigger as R } from "@burdenoff/fe-libs/ui";
20
20
  //#region src/technospam/pages/CoursesCoursesPagePage.tsx
21
- function R({ course: e, onOpen: t }) {
22
- return /* @__PURE__ */ k(M, {
23
- className: "group flex cursor-pointer flex-col overflow-hidden transition-all duration-200 hover:-translate-y-1 hover:shadow-lg",
24
- children: [/* @__PURE__ */ O(o, {
21
+ var z = {
22
+ BEGINNER: "bg-status-success-text",
23
+ INTERMEDIATE: "bg-accent-primary",
24
+ ADVANCED: "bg-status-warning-text",
25
+ EXPERT: "bg-status-error-text"
26
+ };
27
+ function B({ course: e, onOpen: t }) {
28
+ return /* @__PURE__ */ A(N, {
29
+ "data-testid": "course-card",
30
+ className: "group flex cursor-pointer flex-row overflow-hidden transition-all duration-200 hover:-translate-y-1 hover:shadow-lg sm:flex-col",
31
+ children: [/* @__PURE__ */ k(o, {
25
32
  seed: `${e.topic ?? ""} ${e.title}`,
26
33
  icon: s(e.topic ?? e.title),
27
- children: /* @__PURE__ */ k("div", {
28
- className: "flex flex-wrap items-center gap-1.5",
29
- children: [/* @__PURE__ */ O(l, {
34
+ className: "h-auto min-h-28 w-24 shrink-0 p-2 sm:h-28 sm:min-h-0 sm:w-auto sm:p-4",
35
+ children: /* @__PURE__ */ A("div", {
36
+ className: "hidden flex-wrap items-center gap-1.5 sm:flex",
37
+ children: [/* @__PURE__ */ k(l, {
30
38
  status: e.level,
31
39
  kind: "level"
32
- }), e.isPremiumOnly ? /* @__PURE__ */ O(u, { locked: e.isLocked }) : null]
40
+ }), e.isPremiumOnly ? /* @__PURE__ */ k(u, { locked: e.isLocked }) : null]
33
41
  })
34
- }), /* @__PURE__ */ k("div", {
35
- className: "flex flex-1 flex-col gap-3 p-4",
42
+ }), /* @__PURE__ */ A("div", {
43
+ className: "flex min-w-0 flex-1 flex-col gap-2 p-3 sm:gap-3 sm:p-4",
36
44
  children: [
37
- /* @__PURE__ */ k("div", {
45
+ /* @__PURE__ */ A("div", {
38
46
  className: "min-w-0",
39
- children: [/* @__PURE__ */ O("h3", {
40
- className: "truncate text-base font-semibold text-text-primary group-hover:text-accent-primary",
41
- children: /* @__PURE__ */ O(v, {
42
- onOpen: t,
43
- children: e.title
44
- })
45
- }), e.summary ? /* @__PURE__ */ O("p", {
46
- className: "mt-1 line-clamp-2 text-sm text-text-secondary",
47
- children: e.summary
48
- }) : null]
47
+ children: [
48
+ /* @__PURE__ */ A("div", {
49
+ className: "flex items-start gap-2",
50
+ children: [/* @__PURE__ */ k("h3", {
51
+ className: "min-w-0 flex-1 truncate text-base font-semibold text-text-primary group-hover:text-accent-primary",
52
+ children: /* @__PURE__ */ k(v, {
53
+ onOpen: t,
54
+ children: e.title
55
+ })
56
+ }), /* @__PURE__ */ k(C, {
57
+ size: 18,
58
+ "aria-hidden": "true",
59
+ className: "mt-0.5 shrink-0 text-accent-primary sm:hidden"
60
+ })]
61
+ }),
62
+ /* @__PURE__ */ A("div", {
63
+ className: "mt-1 flex flex-wrap items-center gap-1.5 sm:hidden",
64
+ children: [/* @__PURE__ */ k(l, {
65
+ status: e.level,
66
+ kind: "level"
67
+ }), e.isPremiumOnly ? /* @__PURE__ */ k(u, { locked: e.isLocked }) : null]
68
+ }),
69
+ e.summary ? /* @__PURE__ */ k("p", {
70
+ className: "mt-1 line-clamp-2 text-sm text-text-secondary",
71
+ children: e.summary
72
+ }) : null
73
+ ]
49
74
  }),
50
- /* @__PURE__ */ k("div", {
75
+ /* @__PURE__ */ A("div", {
51
76
  className: "mt-auto flex flex-wrap items-center gap-3 text-xs text-text-muted",
52
- children: [e.topic ? /* @__PURE__ */ k("span", {
77
+ children: [e.topic ? /* @__PURE__ */ A("span", {
53
78
  className: "inline-flex items-center gap-1",
54
79
  children: [
55
- /* @__PURE__ */ O(T, { size: 13 }),
80
+ /* @__PURE__ */ k(E, { size: 13 }),
56
81
  " ",
57
82
  e.topic
58
83
  ]
59
- }) : null, /* @__PURE__ */ k("span", {
84
+ }) : null, /* @__PURE__ */ A("span", {
60
85
  className: "inline-flex items-center gap-1",
61
86
  children: [
62
- /* @__PURE__ */ O(C, { size: 13 }),
87
+ /* @__PURE__ */ k(w, { size: 13 }),
63
88
  " ",
64
89
  c(e.estimatedMinutes)
65
90
  ]
66
91
  })]
67
92
  }),
68
- e.tags.length ? /* @__PURE__ */ O("div", {
93
+ e.tags.length ? /* @__PURE__ */ k("div", {
69
94
  className: "flex flex-wrap gap-1.5",
70
- children: e.tags.slice(0, 3).map((e) => /* @__PURE__ */ O(A, {
95
+ children: e.tags.slice(0, 3).map((e) => /* @__PURE__ */ k(j, {
71
96
  variant: "secondary",
72
97
  className: "text-[11px]",
73
98
  children: e
@@ -77,8 +102,8 @@ function R({ course: e, onOpen: t }) {
77
102
  })]
78
103
  });
79
104
  }
80
- function z() {
81
- let o = t(), { workspaceId: s } = e(), { emit: l } = n(), u = r(), [v, T] = x("ALL"), [A, M] = x(""), z = b(() => [
105
+ function V() {
106
+ let o = t(), { workspaceId: s } = e(), { emit: l } = n(), u = r(), [v, C] = x("ALL"), [E, j] = x(""), N = b(() => [
82
107
  {
83
108
  value: "ALL",
84
109
  label: u("technospam.courses.filterAllLevels", "All levels")
@@ -99,77 +124,88 @@ function z() {
99
124
  value: "EXPERT",
100
125
  label: u("technospam.courses.filterExpert", "Expert")
101
126
  }
102
- ], [u]), { limit: B, more: V } = _(), { data: H, loading: U, error: W } = g({
127
+ ], [u]), { limit: V, more: H } = _(), { data: U, loading: W, error: G } = g({
103
128
  status: "PUBLISHED",
104
- pagination: h(B),
129
+ pagination: h(V),
105
130
  ...v === "ALL" ? {} : { level: v }
106
- }), G = H?.tecListCourses?.items ?? [], K = H?.tecListCourses?.pageInfo?.hasMore ?? !1, q = b(() => {
107
- let e = A.trim().toLowerCase();
108
- return e ? G.filter((t) => t.title.toLowerCase().includes(e) || (t.topic ?? "").toLowerCase().includes(e) || t.tags.some((t) => t.toLowerCase().includes(e))) : G;
109
- }, [G, A]);
110
- return /* @__PURE__ */ k(i, {
131
+ }), K = U?.tecListCourses?.items ?? [], q = U?.tecListCourses?.pageInfo?.hasMore ?? !1, J = b(() => {
132
+ let e = E.trim().toLowerCase();
133
+ return e ? K.filter((t) => t.title.toLowerCase().includes(e) || (t.topic ?? "").toLowerCase().includes(e) || t.tags.some((t) => t.toLowerCase().includes(e))) : K;
134
+ }, [K, E]);
135
+ return /* @__PURE__ */ A(i, {
111
136
  hero: !0,
137
+ mobileSection: !0,
112
138
  title: u("technospam.courses.title", "Course Catalog"),
113
139
  description: u("technospam.courses.description", "Learn technology by doing — practical, project-based courses across the stack."),
114
- actions: /* @__PURE__ */ k(j, {
140
+ actions: /* @__PURE__ */ A(M, {
115
141
  variant: "outline",
116
142
  onClick: () => o("/courses/my-learning-page"),
117
143
  children: [
118
- /* @__PURE__ */ O(w, { size: 16 }),
144
+ /* @__PURE__ */ k(T, { size: 16 }),
119
145
  " ",
120
146
  u("technospam.common.myLearning", "My Learning")
121
147
  ]
122
148
  }),
123
- children: [/* @__PURE__ */ k("div", {
124
- className: "mb-5 grid grid-cols-2 gap-3 sm:grid-cols-4",
149
+ children: [/* @__PURE__ */ A("div", {
150
+ className: "mb-5 hidden grid-cols-2 gap-3 sm:grid sm:grid-cols-4",
125
151
  children: [
126
- /* @__PURE__ */ O(a, {
152
+ /* @__PURE__ */ k(a, {
127
153
  label: u("technospam.courses.statCourses", "Courses"),
128
- value: G.length,
154
+ value: K.length,
129
155
  icon: S,
130
156
  tone: "blue"
131
157
  }),
132
- /* @__PURE__ */ O(a, {
158
+ /* @__PURE__ */ k(a, {
133
159
  label: u("technospam.courses.statBeginnerFriendly", "Beginner-friendly"),
134
- value: G.filter((e) => e.level === "BEGINNER").length,
135
- icon: D,
160
+ value: K.filter((e) => e.level === "BEGINNER").length,
161
+ icon: O,
136
162
  tone: "green"
137
163
  }),
138
- /* @__PURE__ */ O(a, {
164
+ /* @__PURE__ */ k(a, {
139
165
  label: u("technospam.courses.statAdvancedPlus", "Advanced+"),
140
- value: G.filter((e) => e.level === "ADVANCED" || e.level === "EXPERT").length,
141
- icon: w,
166
+ value: K.filter((e) => e.level === "ADVANCED" || e.level === "EXPERT").length,
167
+ icon: T,
142
168
  tone: "purple"
143
169
  }),
144
- /* @__PURE__ */ O(a, {
170
+ /* @__PURE__ */ k(a, {
145
171
  label: u("technospam.courses.statTotalContent", "Total content"),
146
- value: c(G.reduce((e, t) => e + t.estimatedMinutes, 0)),
147
- icon: C,
172
+ value: c(K.reduce((e, t) => e + t.estimatedMinutes, 0)),
173
+ icon: w,
148
174
  tone: "teal"
149
175
  })
150
176
  ]
151
- }), /* @__PURE__ */ k(P, {
177
+ }), /* @__PURE__ */ A(F, {
152
178
  className: "gap-0",
153
179
  value: v,
154
- onValueChange: (e) => T(e),
155
- children: [/* @__PURE__ */ k("div", {
180
+ onValueChange: (e) => C(e),
181
+ children: [/* @__PURE__ */ A("div", {
156
182
  className: "mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
157
- children: [/* @__PURE__ */ O(I, { children: z.map((e) => /* @__PURE__ */ O(L, {
158
- value: e.value,
159
- children: e.label
160
- }, e.value)) }), /* @__PURE__ */ k("div", {
183
+ children: [/* @__PURE__ */ k("div", {
184
+ className: "-mx-4 overflow-x-auto px-4 sm:mx-0 sm:overflow-visible sm:px-0",
185
+ children: /* @__PURE__ */ k(L, {
186
+ className: "h-auto w-max gap-1.5 bg-transparent p-0 sm:h-9 sm:gap-0 sm:bg-bg-sunken sm:p-[3px]",
187
+ children: N.map((e) => /* @__PURE__ */ A(R, {
188
+ value: e.value,
189
+ className: "min-h-11 flex-none gap-1.5 rounded-full border-border-subtle px-4 data-[state=active]:border-accent-primary sm:min-h-0 sm:flex-1 sm:rounded-button sm:border-transparent sm:px-3 sm:data-[state=active]:border-transparent",
190
+ children: [z[e.value] ? /* @__PURE__ */ k("span", {
191
+ "aria-hidden": "true",
192
+ className: `h-2 w-2 rounded-full sm:hidden ${z[e.value]}`
193
+ }) : null, e.label]
194
+ }, e.value))
195
+ })
196
+ }), /* @__PURE__ */ A("div", {
161
197
  className: "relative w-full sm:w-72",
162
- children: [/* @__PURE__ */ O(E, {
198
+ children: [/* @__PURE__ */ k(D, {
163
199
  className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-text-muted",
164
200
  size: 16
165
- }), /* @__PURE__ */ O(N, {
201
+ }), /* @__PURE__ */ k(P, {
166
202
  className: "pl-9",
167
203
  "aria-label": u("technospam.courses.searchLabel", "Search courses"),
168
204
  placeholder: u("technospam.courses.searchPlaceholder", "Search courses…"),
169
- value: A,
170
- onChange: (e) => M(e.target.value),
205
+ value: E,
206
+ onChange: (e) => j(e.target.value),
171
207
  onBlur: () => {
172
- let e = A.trim();
208
+ let e = E.trim();
173
209
  e && l("technospam.search.performed", {
174
210
  workspaceId: s,
175
211
  entityType: "course",
@@ -178,19 +214,19 @@ function z() {
178
214
  }
179
215
  })]
180
216
  })]
181
- }), /* @__PURE__ */ k(F, {
217
+ }), /* @__PURE__ */ A(I, {
182
218
  value: v,
183
219
  className: y,
184
- children: [U && !G.length ? /* @__PURE__ */ O(m, {}) : W ? /* @__PURE__ */ O(p, {
220
+ children: [W && !K.length ? /* @__PURE__ */ k(m, {}) : G ? /* @__PURE__ */ k(p, {
185
221
  title: u("technospam.common.errorLoadCourses", "Couldn't load courses"),
186
- error: W
187
- }) : q.length === 0 ? /* @__PURE__ */ O(f, {
188
- icon: /* @__PURE__ */ O(S, { size: 26 }),
189
- title: A || v !== "ALL" ? u("technospam.courses.emptySearchTitle", "No matching courses") : u("technospam.courses.emptyDefaultTitle", "No courses published yet"),
190
- description: A || v !== "ALL" ? u("technospam.courses.emptySearchDesc", "Try a different search or level filter.") : u("technospam.courses.emptyDefaultDesc", "Published courses will appear here. Check back soon — TechnoSpam is pre-launch.")
191
- }) : /* @__PURE__ */ O("div", {
222
+ error: G
223
+ }) : J.length === 0 ? /* @__PURE__ */ k(f, {
224
+ icon: /* @__PURE__ */ k(S, { size: 26 }),
225
+ title: E || v !== "ALL" ? u("technospam.courses.emptySearchTitle", "No matching courses") : u("technospam.courses.emptyDefaultTitle", "No courses published yet"),
226
+ description: E || v !== "ALL" ? u("technospam.courses.emptySearchDesc", "Try a different search or level filter.") : u("technospam.courses.emptyDefaultDesc", "Published courses will appear here. Check back soon — TechnoSpam is pre-launch.")
227
+ }) : /* @__PURE__ */ k("div", {
192
228
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
193
- children: q.map((e) => /* @__PURE__ */ O(R, {
229
+ children: J.map((e) => /* @__PURE__ */ k(B, {
194
230
  course: e,
195
231
  onOpen: () => {
196
232
  l("technospam.course.viewed", {
@@ -200,16 +236,16 @@ function z() {
200
236
  }), o(`/courses/course-details-page?id=${e.id}`);
201
237
  }
202
238
  }, e.id))
203
- }), /* @__PURE__ */ O(d, {
204
- hasMore: K,
205
- loading: U,
206
- onClick: V
239
+ }), /* @__PURE__ */ k(d, {
240
+ hasMore: q,
241
+ loading: W,
242
+ onClick: H
207
243
  })]
208
244
  })]
209
245
  })]
210
246
  });
211
247
  }
212
248
  //#endregion
213
- export { z as CoursesCoursesPagePage };
249
+ export { V as CoursesCoursesPagePage };
214
250
 
215
251
  //# sourceMappingURL=CoursesCoursesPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CoursesCoursesPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesCoursesPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { BookOpen, Clock, GraduationCap, Layers, Search, Sparkles } from 'lucide-react';\nimport {\n Badge,\n Button,\n GlassCard,\n Input,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { TAB_PANEL_FOCUS_CLASS } from '../lib/a11y';\nimport {\n CardOpenButton,\n EmptyView,\n ErrorView,\n LoadMore,\n LoadingGrid,\n MediaCover,\n PageLayout,\n PremiumBadge,\n StatCard,\n StatusBadge,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n pageArg,\n useCoursesQuery,\n usePageLimit,\n type TecCourse,\n type TecCourseLevel,\n} from '../api/technospamApi';\nimport { formatDuration } from '../utils/format';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\nimport { useTr } from '../hooks/useTr';\n\nfunction CourseCard({ course, onOpen }: { course: TecCourse; onOpen: () => void }) {\n return (\n <GlassCard className=\"group flex cursor-pointer flex-col overflow-hidden transition-all duration-200 hover:-translate-y-1 hover:shadow-lg\">\n <MediaCover\n seed={`${course.topic ?? ''} ${course.title}`}\n icon={topicIcon(course.topic ?? course.title)}\n >\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={course.level} kind=\"level\" />\n {course.isPremiumOnly ? <PremiumBadge locked={course.isLocked} /> : null}\n </div>\n </MediaCover>\n <div className=\"flex flex-1 flex-col gap-3 p-4\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary group-hover:text-accent-primary\">\n <CardOpenButton onOpen={onOpen}>{course.title}</CardOpenButton>\n </h3>\n {course.summary ? (\n <p className=\"mt-1 line-clamp-2 text-sm text-text-secondary\">{course.summary}</p>\n ) : null}\n </div>\n <div className=\"mt-auto flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n {course.topic ? (\n <span className=\"inline-flex items-center gap-1\">\n <Layers size={13} /> {course.topic}\n </span>\n ) : null}\n <span className=\"inline-flex items-center gap-1\">\n <Clock size={13} /> {formatDuration(course.estimatedMinutes)}\n </span>\n </div>\n {course.tags.length ? (\n <div className=\"flex flex-wrap gap-1.5\">\n {course.tags.slice(0, 3).map((t) => (\n <Badge key={t} variant=\"secondary\" className=\"text-[11px]\">\n {t}\n </Badge>\n ))}\n </div>\n ) : null}\n </div>\n </GlassCard>\n );\n}\n\nexport function CoursesCoursesPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const tr = useTr();\n const [level, setLevel] = useState<'ALL' | TecCourseLevel>('ALL');\n const [search, setSearch] = useState('');\n\n const levels = useMemo(\n () => [\n { value: 'ALL' as const, label: tr('technospam.courses.filterAllLevels', 'All levels') },\n {\n value: 'BEGINNER' as TecCourseLevel,\n label: tr('technospam.courses.filterBeginner', 'Beginner'),\n },\n {\n value: 'INTERMEDIATE' as TecCourseLevel,\n label: tr('technospam.courses.filterIntermediate', 'Intermediate'),\n },\n {\n value: 'ADVANCED' as TecCourseLevel,\n label: tr('technospam.courses.filterAdvanced', 'Advanced'),\n },\n { value: 'EXPERT' as TecCourseLevel, label: tr('technospam.courses.filterExpert', 'Expert') },\n ],\n [tr]\n );\n\n const { limit, more } = usePageLimit();\n const { data, loading, error } = useCoursesQuery({\n status: 'PUBLISHED',\n pagination: pageArg(limit),\n ...(level !== 'ALL' ? { level } : {}),\n });\n\n const courses = data?.tecListCourses?.items ?? [];\n const hasMore = data?.tecListCourses?.pageInfo?.hasMore ?? false;\n const filtered = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return courses;\n return courses.filter(\n (c) =>\n c.title.toLowerCase().includes(q) ||\n (c.topic ?? '').toLowerCase().includes(q) ||\n c.tags.some((t) => t.toLowerCase().includes(q))\n );\n }, [courses, search]);\n\n return (\n <PageLayout\n hero\n title={tr('technospam.courses.title', 'Course Catalog')}\n description={tr(\n 'technospam.courses.description',\n 'Learn technology by doing — practical, project-based courses across the stack.'\n )}\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> {tr('technospam.common.myLearning', 'My Learning')}\n </Button>\n }\n >\n <div className=\"mb-5 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard\n label={tr('technospam.courses.statCourses', 'Courses')}\n value={courses.length}\n icon={BookOpen}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.courses.statBeginnerFriendly', 'Beginner-friendly')}\n value={courses.filter((c) => c.level === 'BEGINNER').length}\n icon={Sparkles}\n tone=\"green\"\n />\n <StatCard\n label={tr('technospam.courses.statAdvancedPlus', 'Advanced+')}\n value={courses.filter((c) => c.level === 'ADVANCED' || c.level === 'EXPERT').length}\n icon={GraduationCap}\n tone=\"purple\"\n />\n <StatCard\n label={tr('technospam.courses.statTotalContent', 'Total content')}\n value={formatDuration(courses.reduce((s, c) => s + c.estimatedMinutes, 0))}\n icon={Clock}\n tone=\"teal\"\n />\n </div>\n\n {/* The tabs filter one list, so that list is their tab panel: Radix points every\n trigger's aria-controls at a panel id, and without a TabsContent the id\n resolved to nothing (axe aria-valid-attr-value, critical; BOFF-3157). */}\n <Tabs\n className=\"gap-0\"\n value={level}\n onValueChange={(v) => setLevel(v as 'ALL' | TecCourseLevel)}\n >\n <div className=\"mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <TabsList>\n {levels.map((l) => (\n <TabsTrigger key={l.value} value={l.value}>\n {l.label}\n </TabsTrigger>\n ))}\n </TabsList>\n <div className=\"relative w-full sm:w-72\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-text-muted\"\n size={16}\n />\n <Input\n className=\"pl-9\"\n aria-label={tr('technospam.courses.searchLabel', 'Search courses')}\n placeholder={tr('technospam.courses.searchPlaceholder', 'Search courses…')}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n onBlur={() => {\n const value = search.trim();\n if (value) {\n emit('technospam.search.performed', {\n workspaceId,\n entityType: 'course',\n queryLength: value.length,\n });\n }\n }}\n />\n </div>\n </div>\n\n <TabsContent value={level} className={TAB_PANEL_FOCUS_CLASS}>\n {loading && !courses.length ? (\n <LoadingGrid />\n ) : error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadCourses', \"Couldn't load courses\")}\n error={error}\n />\n ) : filtered.length === 0 ? (\n <EmptyView\n icon={<BookOpen size={26} />}\n title={\n search || level !== 'ALL'\n ? tr('technospam.courses.emptySearchTitle', 'No matching courses')\n : tr('technospam.courses.emptyDefaultTitle', 'No courses published yet')\n }\n description={\n search || level !== 'ALL'\n ? tr(\n 'technospam.courses.emptySearchDesc',\n 'Try a different search or level filter.'\n )\n : tr(\n 'technospam.courses.emptyDefaultDesc',\n 'Published courses will appear here. Check back soon — TechnoSpam is pre-launch.'\n )\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {filtered.map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n onOpen={() => {\n emit('technospam.course.viewed', {\n workspaceId,\n entityId: course.id,\n entityType: 'TEC_COURSE',\n });\n navigate(`/courses/course-details-page?id=${course.id}`);\n }}\n />\n ))}\n </div>\n )}\n <LoadMore hasMore={hasMore} loading={loading} onClick={more} />\n </TabsContent>\n </Tabs>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CoursesCoursesPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesCoursesPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n BookOpen,\n ChevronRight,\n Clock,\n GraduationCap,\n Layers,\n Search,\n Sparkles,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n GlassCard,\n Input,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { TAB_PANEL_FOCUS_CLASS } from '../lib/a11y';\nimport {\n CardOpenButton,\n EmptyView,\n ErrorView,\n LoadMore,\n LoadingGrid,\n MediaCover,\n PageLayout,\n PremiumBadge,\n StatCard,\n StatusBadge,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n pageArg,\n useCoursesQuery,\n usePageLimit,\n type TecCourse,\n type TecCourseLevel,\n} from '../api/technospamApi';\nimport { formatDuration } from '../utils/format';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\nimport { useTr } from '../hooks/useTr';\n\n/** Dot colour per level for the phone filter chips (mock technospam-19). */\nconst LEVEL_DOT: Partial<Record<'ALL' | TecCourseLevel, string>> = {\n BEGINNER: 'bg-status-success-text',\n INTERMEDIATE: 'bg-accent-primary',\n ADVANCED: 'bg-status-warning-text',\n EXPERT: 'bg-status-error-text',\n};\n\n/**\n * Catalog card. Phones get the mock's compact row (cover tile on the left,\n * level chip, summary, duration and tags on the right, a chevron affordance);\n * from `sm` up it is the vertical grid card.\n */\nfunction CourseCard({ course, onOpen }: { course: TecCourse; onOpen: () => void }) {\n return (\n <GlassCard\n data-testid=\"course-card\"\n className=\"group flex cursor-pointer flex-row overflow-hidden transition-all duration-200 hover:-translate-y-1 hover:shadow-lg sm:flex-col\"\n >\n <MediaCover\n seed={`${course.topic ?? ''} ${course.title}`}\n icon={topicIcon(course.topic ?? course.title)}\n className=\"h-auto min-h-28 w-24 shrink-0 p-2 sm:h-28 sm:min-h-0 sm:w-auto sm:p-4\"\n >\n <div className=\"hidden flex-wrap items-center gap-1.5 sm:flex\">\n <StatusBadge status={course.level} kind=\"level\" />\n {course.isPremiumOnly ? <PremiumBadge locked={course.isLocked} /> : null}\n </div>\n </MediaCover>\n <div className=\"flex min-w-0 flex-1 flex-col gap-2 p-3 sm:gap-3 sm:p-4\">\n <div className=\"min-w-0\">\n <div className=\"flex items-start gap-2\">\n <h3 className=\"min-w-0 flex-1 truncate text-base font-semibold text-text-primary group-hover:text-accent-primary\">\n <CardOpenButton onOpen={onOpen}>{course.title}</CardOpenButton>\n </h3>\n <ChevronRight\n size={18}\n aria-hidden=\"true\"\n className=\"mt-0.5 shrink-0 text-accent-primary sm:hidden\"\n />\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-1.5 sm:hidden\">\n <StatusBadge status={course.level} kind=\"level\" />\n {course.isPremiumOnly ? <PremiumBadge locked={course.isLocked} /> : null}\n </div>\n {course.summary ? (\n <p className=\"mt-1 line-clamp-2 text-sm text-text-secondary\">{course.summary}</p>\n ) : null}\n </div>\n <div className=\"mt-auto flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n {course.topic ? (\n <span className=\"inline-flex items-center gap-1\">\n <Layers size={13} /> {course.topic}\n </span>\n ) : null}\n <span className=\"inline-flex items-center gap-1\">\n <Clock size={13} /> {formatDuration(course.estimatedMinutes)}\n </span>\n </div>\n {course.tags.length ? (\n <div className=\"flex flex-wrap gap-1.5\">\n {course.tags.slice(0, 3).map((t) => (\n <Badge key={t} variant=\"secondary\" className=\"text-[11px]\">\n {t}\n </Badge>\n ))}\n </div>\n ) : null}\n </div>\n </GlassCard>\n );\n}\n\nexport function CoursesCoursesPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const tr = useTr();\n const [level, setLevel] = useState<'ALL' | TecCourseLevel>('ALL');\n const [search, setSearch] = useState('');\n\n const levels = useMemo(\n () => [\n { value: 'ALL' as const, label: tr('technospam.courses.filterAllLevels', 'All levels') },\n {\n value: 'BEGINNER' as TecCourseLevel,\n label: tr('technospam.courses.filterBeginner', 'Beginner'),\n },\n {\n value: 'INTERMEDIATE' as TecCourseLevel,\n label: tr('technospam.courses.filterIntermediate', 'Intermediate'),\n },\n {\n value: 'ADVANCED' as TecCourseLevel,\n label: tr('technospam.courses.filterAdvanced', 'Advanced'),\n },\n { value: 'EXPERT' as TecCourseLevel, label: tr('technospam.courses.filterExpert', 'Expert') },\n ],\n [tr]\n );\n\n const { limit, more } = usePageLimit();\n const { data, loading, error } = useCoursesQuery({\n status: 'PUBLISHED',\n pagination: pageArg(limit),\n ...(level !== 'ALL' ? { level } : {}),\n });\n\n const courses = data?.tecListCourses?.items ?? [];\n const hasMore = data?.tecListCourses?.pageInfo?.hasMore ?? false;\n const filtered = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return courses;\n return courses.filter(\n (c) =>\n c.title.toLowerCase().includes(q) ||\n (c.topic ?? '').toLowerCase().includes(q) ||\n c.tags.some((t) => t.toLowerCase().includes(q))\n );\n }, [courses, search]);\n\n return (\n <PageLayout\n hero\n mobileSection\n title={tr('technospam.courses.title', 'Course Catalog')}\n description={tr(\n 'technospam.courses.description',\n 'Learn technology by doing — practical, project-based courses across the stack.'\n )}\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> {tr('technospam.common.myLearning', 'My Learning')}\n </Button>\n }\n >\n {/* Catalog stats are a desktop summary; phones go straight to the list. */}\n <div className=\"mb-5 hidden grid-cols-2 gap-3 sm:grid sm:grid-cols-4\">\n <StatCard\n label={tr('technospam.courses.statCourses', 'Courses')}\n value={courses.length}\n icon={BookOpen}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.courses.statBeginnerFriendly', 'Beginner-friendly')}\n value={courses.filter((c) => c.level === 'BEGINNER').length}\n icon={Sparkles}\n tone=\"green\"\n />\n <StatCard\n label={tr('technospam.courses.statAdvancedPlus', 'Advanced+')}\n value={courses.filter((c) => c.level === 'ADVANCED' || c.level === 'EXPERT').length}\n icon={GraduationCap}\n tone=\"purple\"\n />\n <StatCard\n label={tr('technospam.courses.statTotalContent', 'Total content')}\n value={formatDuration(courses.reduce((s, c) => s + c.estimatedMinutes, 0))}\n icon={Clock}\n tone=\"teal\"\n />\n </div>\n\n {/* The tabs filter one list, so that list is their tab panel: Radix points every\n trigger's aria-controls at a panel id, and without a TabsContent the id\n resolved to nothing (axe aria-valid-attr-value, critical; BOFF-3157). */}\n <Tabs\n className=\"gap-0\"\n value={level}\n onValueChange={(v) => setLevel(v as 'ALL' | TecCourseLevel)}\n >\n <div className=\"mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n {/* Phones: a horizontally scrolling row of level chips with 44px\n touch targets; from sm up the regular segmented tabs. */}\n <div className=\"-mx-4 overflow-x-auto px-4 sm:mx-0 sm:overflow-visible sm:px-0\">\n <TabsList className=\"h-auto w-max gap-1.5 bg-transparent p-0 sm:h-9 sm:gap-0 sm:bg-bg-sunken sm:p-[3px]\">\n {levels.map((l) => (\n <TabsTrigger\n key={l.value}\n value={l.value}\n className=\"min-h-11 flex-none gap-1.5 rounded-full border-border-subtle px-4 data-[state=active]:border-accent-primary sm:min-h-0 sm:flex-1 sm:rounded-button sm:border-transparent sm:px-3 sm:data-[state=active]:border-transparent\"\n >\n {LEVEL_DOT[l.value] ? (\n <span\n aria-hidden=\"true\"\n className={`h-2 w-2 rounded-full sm:hidden ${LEVEL_DOT[l.value]}`}\n />\n ) : null}\n {l.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </div>\n <div className=\"relative w-full sm:w-72\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-text-muted\"\n size={16}\n />\n <Input\n className=\"pl-9\"\n aria-label={tr('technospam.courses.searchLabel', 'Search courses')}\n placeholder={tr('technospam.courses.searchPlaceholder', 'Search courses…')}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n onBlur={() => {\n const value = search.trim();\n if (value) {\n emit('technospam.search.performed', {\n workspaceId,\n entityType: 'course',\n queryLength: value.length,\n });\n }\n }}\n />\n </div>\n </div>\n\n <TabsContent value={level} className={TAB_PANEL_FOCUS_CLASS}>\n {loading && !courses.length ? (\n <LoadingGrid />\n ) : error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadCourses', \"Couldn't load courses\")}\n error={error}\n />\n ) : filtered.length === 0 ? (\n <EmptyView\n icon={<BookOpen size={26} />}\n title={\n search || level !== 'ALL'\n ? tr('technospam.courses.emptySearchTitle', 'No matching courses')\n : tr('technospam.courses.emptyDefaultTitle', 'No courses published yet')\n }\n description={\n search || level !== 'ALL'\n ? tr(\n 'technospam.courses.emptySearchDesc',\n 'Try a different search or level filter.'\n )\n : tr(\n 'technospam.courses.emptyDefaultDesc',\n 'Published courses will appear here. Check back soon — TechnoSpam is pre-launch.'\n )\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {filtered.map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n onOpen={() => {\n emit('technospam.course.viewed', {\n workspaceId,\n entityId: course.id,\n entityType: 'TEC_COURSE',\n });\n navigate(`/courses/course-details-page?id=${course.id}`);\n }}\n />\n ))}\n </div>\n )}\n <LoadMore hasMore={hasMore} loading={loading} onClick={more} />\n </TabsContent>\n </Tabs>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -24,7 +24,7 @@ function k({ e, onOpen: n, focused: r, onToggle: o }) {
24
24
  ..._(r, o),
25
25
  children: [
26
26
  /* @__PURE__ */ T("span", {
27
- className: t("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", i(l)),
27
+ className: t("flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br text-action-primary-text shadow-sm transition-transform duration-200 group-hover:scale-105", i(l)),
28
28
  children: y(a(l), { size: 20 })
29
29
  }),
30
30
  /* @__PURE__ */ E("div", {
@@ -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 CertificateVerifyLink,\n EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n coverGradient,\n topicIcon,\n} from '../components';\nimport { useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useMyCertificatesQuery,\n useMyEnrollmentsQuery,\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\n // BOFF-10481: the learner's own enrollments and certificates come from the\n // canonical, self-scoped Buildmyiq fields. The previous cross-learner\n // `tecListEnrollments` / list-all reads needed an instructor permission a\n // plain learner does not hold. Each enrollment carries its next lesson, so\n // \"Resume\" (BOFF-4107) deep-links into the next incomplete lesson.\n const { enrollments, loading, error } = useMyEnrollmentsQuery();\n const { certificates } = 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 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, e.nextLessonId))}\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 <CertificateVerifyLink serial={c.serial} compact />\n </GlassCard>\n ))}\n </div>\n </div>\n ) : null}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
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 CertificateVerifyLink,\n EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n coverGradient,\n topicIcon,\n} from '../components';\nimport { useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useMyCertificatesQuery,\n useMyEnrollmentsQuery,\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-action-primary-text 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\n // BOFF-10481: the learner's own enrollments and certificates come from the\n // canonical, self-scoped Buildmyiq fields. The previous cross-learner\n // `tecListEnrollments` / list-all reads needed an instructor permission a\n // plain learner does not hold. Each enrollment carries its next lesson, so\n // \"Resume\" (BOFF-4107) deep-links into the next incomplete lesson.\n const { enrollments, loading, error } = useMyEnrollmentsQuery();\n const { certificates } = 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 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, e.nextLessonId))}\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 <CertificateVerifyLink serial={c.serial} compact />\n </GlassCard>\n ))}\n </div>\n </div>\n ) : null}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA8BA,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,+KACA,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;KAAQ,eAAa,UAAU,EAAE;eAA5E,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,EAOlC,EAAE,gBAAa,YAAS,aAAU,GAAuB,EACzD,EAAE,oBAAiB,GAAwB,EAG3C,CAAC,GAAqB,KAA0B,EACpD,EAAa,YACb,EACD,EACK,CAAC,GAAsB,KAA2B,EACtD,EAAa,aACb,EACD,EAEK,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,EAAW,EAAE,UAAU,EAAE,aAAa,CAAC;KAC9D,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;OAUE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBACd,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;SACd,CAAA,EACP,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,UAAD;UACE,MAAK;UACL,gBAAc,MAAyB,EAAE;UACzC,eACE,EAAwB,MAAyB,EAAE,KAAK,OAAO,EAAE,GAAG;UAEtE,WAAU;oBAET,EAAE,QAAQ,SAAS;UACb,CAAA,EACT,kBAAC,OAAD;UAAK,WAAU;oBAAf,CAAkD,WAAQ,EAAE,OAAa;YACrE;WACF;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD,EAAA,UAAA;SAAM;SACE,kBAAC,QAAD;UAAM,WAAU;oBAAmC,EAAE;UAAa,CAAA;;SAAG;SAC1E,EAAW,EAAE,SAAS;SAClB,EAAA,CAAA,EACN,EAAE,WAAW,WACZ,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CACE,kBAAC,GAAD,EAAa,MAAM,IAAM,CAAA,EAAA,YACpB;aAEP,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA,CAE/B;;OACN,kBAAC,GAAD;QAAuB,QAAQ,EAAE;QAAQ,SAAA;QAAU,CAAA;OACzC;QAzCL,EAAE,GAyCG,CACZ;KACE,CAAA,CACF;QACJ;GACO"}
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardDashboardPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/DashboardDashboardPagePage.tsx"],"names":[],"mappings":"AAqNA,wBAAgB,0BAA0B,gCAmVzC"}
1
+ {"version":3,"file":"DashboardDashboardPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/DashboardDashboardPagePage.tsx"],"names":[],"mappings":"AAqNA,wBAAgB,0BAA0B,gCA8VzC"}
@@ -48,7 +48,7 @@ function z({ e, resumeLessonId: t, onOpen: r }) {
48
48
  type: "button",
49
49
  onClick: r,
50
50
  "data-resume-lesson": t ?? void 0,
51
- 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)]",
51
+ className: "group flex w-[72%] shrink-0 snap-start flex-col overflow-hidden rounded-xl sm:w-auto sm:shrink 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)]",
52
52
  children: [/* @__PURE__ */ P(a, {
53
53
  seed: c,
54
54
  icon: o(e.course?.topic ?? s),
@@ -57,7 +57,7 @@ function z({ e, resumeLessonId: t, onOpen: r }) {
57
57
  status: e.course.level,
58
58
  kind: "level"
59
59
  }) : /* @__PURE__ */ N("span", {}), /* @__PURE__ */ N("span", {
60
- 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",
60
+ className: "flex h-7 w-7 items-center justify-center rounded-full bg-action-primary-text/15 text-action-primary-text backdrop-blur transition-transform duration-200 group-hover:scale-110",
61
61
  children: /* @__PURE__ */ N(A, { size: 16 })
62
62
  })]
63
63
  }), /* @__PURE__ */ P("div", {
@@ -157,18 +157,18 @@ function ne({ h: e, onOpen: t }) {
157
157
  children: [/* @__PURE__ */ P("div", {
158
158
  className: "min-w-0",
159
159
  children: [/* @__PURE__ */ P("div", {
160
- className: "flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-white/80",
160
+ className: "flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-action-primary-text/80",
161
161
  children: [
162
162
  /* @__PURE__ */ N(j, { size: 12 }),
163
163
  " ",
164
164
  r("technospam.common.openHackathon", "Open hackathon")
165
165
  ]
166
166
  }), /* @__PURE__ */ N("h3", {
167
- className: "mt-0.5 truncate text-sm font-semibold text-white drop-shadow-sm",
167
+ className: "mt-0.5 truncate text-sm font-semibold text-action-primary-text drop-shadow-sm",
168
168
  children: e.title
169
169
  })]
170
170
  }), e.prizePool > 0 ? /* @__PURE__ */ N("span", {
171
- className: "shrink-0 rounded-full bg-white/20 px-2.5 py-1 text-xs font-semibold text-white backdrop-blur",
171
+ className: "shrink-0 rounded-full bg-action-primary-text/20 px-2.5 py-1 text-xs font-semibold text-action-primary-text backdrop-blur",
172
172
  children: l(e.prizePool, e.currency)
173
173
  }) : null]
174
174
  }), /* @__PURE__ */ P("div", {
@@ -199,6 +199,7 @@ function H() {
199
199
  }, [K, H]), $ = q.slice(0, 4);
200
200
  return /* @__PURE__ */ P(r, {
201
201
  hero: !0,
202
+ mobileSection: !0,
202
203
  title: c("technospam.dashboard.title", "Welcome back, {{name}}", { name: re }),
203
204
  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
205
  actions: /* @__PURE__ */ P(F, {
@@ -230,18 +231,20 @@ function H() {
230
231
  label: c("technospam.dashboard.upcomingSessions", "Upcoming sessions"),
231
232
  value: Z.length,
232
233
  icon: T,
233
- tone: "amber"
234
+ tone: "amber",
235
+ className: "hidden sm:flex"
234
236
  }),
235
237
  /* @__PURE__ */ N(i, {
236
238
  label: c("technospam.dashboard.certificates", "Certificates"),
237
239
  value: G.length,
238
240
  icon: te,
239
- tone: "purple"
241
+ tone: "purple",
242
+ className: "hidden sm:flex"
240
243
  })
241
244
  ]
242
245
  }),
243
246
  /* @__PURE__ */ P("div", {
244
- className: "mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4",
247
+ className: "mb-6 hidden grid-cols-2 gap-3 sm:grid sm:grid-cols-4",
245
248
  children: [
246
249
  /* @__PURE__ */ P(F, {
247
250
  variant: "outline",
@@ -282,11 +285,11 @@ function H() {
282
285
  ]
283
286
  }),
284
287
  /* @__PURE__ */ P("div", {
285
- className: "grid grid-cols-1 gap-6 lg:grid-cols-3",
288
+ className: "flex flex-col gap-6 lg:grid lg:grid-cols-3",
286
289
  children: [/* @__PURE__ */ P("div", {
287
- className: "space-y-6 lg:col-span-2",
290
+ className: "contents lg:col-span-2 lg:block lg:space-y-6",
288
291
  children: [/* @__PURE__ */ P(I, {
289
- className: "p-5",
292
+ className: "order-1 p-4 sm:p-5",
290
293
  children: [/* @__PURE__ */ N(L, {
291
294
  title: c("technospam.dashboard.continueLearning", "Continue learning"),
292
295
  actionLabel: c("technospam.dashboard.myLearning", "My Learning"),
@@ -306,7 +309,8 @@ function H() {
306
309
  children: c("technospam.common.browseCourses", "Browse courses")
307
310
  })
308
311
  }) : /* @__PURE__ */ N("div", {
309
- className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
312
+ "data-testid": "continue-learning-list",
313
+ className: "-mx-1 flex snap-x snap-mandatory gap-3 overflow-x-auto px-1 pb-1 sm:mx-0 sm:grid sm:snap-none sm:grid-cols-2 sm:gap-4 sm:overflow-visible sm:px-0 sm:pb-0 lg:grid-cols-3",
310
314
  children: Y.map((e) => /* @__PURE__ */ N(z, {
311
315
  e,
312
316
  resumeLessonId: e.nextLessonId,
@@ -314,7 +318,7 @@ function H() {
314
318
  }, e.id))
315
319
  })]
316
320
  }), /* @__PURE__ */ P(I, {
317
- className: "p-5",
321
+ className: "order-3 p-4 sm:p-5",
318
322
  children: [/* @__PURE__ */ N(L, {
319
323
  title: c("technospam.dashboard.recommendedForYou", "Recommended for you"),
320
324
  actionLabel: c("technospam.common.viewAll", "View all"),
@@ -338,10 +342,10 @@ function H() {
338
342
  })]
339
343
  })]
340
344
  }), /* @__PURE__ */ P("div", {
341
- className: "space-y-6",
345
+ className: "contents lg:block lg:space-y-6",
342
346
  children: [
343
347
  /* @__PURE__ */ P(I, {
344
- className: "p-5",
348
+ className: "order-2 p-4 sm:p-5",
345
349
  children: [/* @__PURE__ */ N(L, {
346
350
  title: c("technospam.dashboard.upcomingSessionsTitle", "Upcoming sessions"),
347
351
  actionLabel: c("technospam.common.allSessions", "All sessions"),
@@ -364,7 +368,7 @@ function H() {
364
368
  }) : /* @__PURE__ */ N("div", { children: Z.slice(0, 5).map((e) => /* @__PURE__ */ N(V, { s: e }, e.id)) })]
365
369
  }),
366
370
  /* @__PURE__ */ P(I, {
367
- className: "p-5",
371
+ className: "order-2 p-4 sm:p-5",
368
372
  children: [/* @__PURE__ */ N(L, {
369
373
  title: c("technospam.dashboard.openHackathons", "Open hackathons"),
370
374
  actionLabel: c("technospam.common.viewAll", "View all"),
@@ -388,7 +392,7 @@ function H() {
388
392
  })]
389
393
  }),
390
394
  G.length > 0 ? /* @__PURE__ */ P(I, {
391
- className: "p-5",
395
+ className: "order-4 p-4 sm:p-5",
392
396
  children: [/* @__PURE__ */ N(L, { title: c("technospam.common.latestCertificate", "Latest certificate") }), /* @__PURE__ */ P("div", {
393
397
  className: "flex items-center gap-3",
394
398
  children: [/* @__PURE__ */ N("span", {