@burdenoff/microfe-technospam 2026.704.2 → 2026.705.1
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/technospam/api/technospamApi.js +233 -150
- package/dist/technospam/api/technospamApi.js.map +1 -1
- package/dist/technospam/components/LoadMore.js +18 -2
- package/dist/technospam/components/LoadMore.js.map +1 -0
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js +664 -334
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
- package/dist/technospam/pages/ApplicationsProblemsPagePage.js +79 -72
- package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +140 -87
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesCoursesPagePage.js +75 -68
- package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesMyLearningPagePage.js +3 -0
- package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
- package/dist/technospam/pages/HackathonsHackathonsPagePage.js +76 -53
- package/dist/technospam/pages/HackathonsHackathonsPagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +36 -35
- package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipMentorsPagePage.js +112 -103
- package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipSessionsPagePage.js +219 -110
- package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,15 +3,16 @@ import { PageLayout as t } from "../components/PageLayout.js";
|
|
|
3
3
|
import { StatCard as n } from "../components/StatCard.js";
|
|
4
4
|
import { formatDuration as r } from "../utils/format.js";
|
|
5
5
|
import { StatusBadge as i } from "../components/StatusBadge.js";
|
|
6
|
-
import {
|
|
6
|
+
import { LoadMore as a } from "../components/LoadMore.js";
|
|
7
|
+
import { EmptyView as o, ErrorView as s, LoadingGrid as c } from "../components/StateViews.js";
|
|
7
8
|
import "../components/index.js";
|
|
8
|
-
import { useCoursesQuery as
|
|
9
|
-
import { useMemo as
|
|
10
|
-
import { BookOpen as
|
|
11
|
-
import { jsx as
|
|
12
|
-
import { Badge as
|
|
9
|
+
import { pageArg as l, useCoursesQuery as u, usePageLimit as d } from "../api/technospamApi.js";
|
|
10
|
+
import { useMemo as f, useState as p } from "react";
|
|
11
|
+
import { BookOpen as m, Clock as h, GraduationCap as g, Layers as _, Search as v, Sparkles as y } from "lucide-react";
|
|
12
|
+
import { jsx as b, jsxs as x } from "react/jsx-runtime";
|
|
13
|
+
import { Badge as S, Button as C, GlassCard as w, Input as T, Tabs as E, TabsList as D, TabsTrigger as O } from "@burdenoff/fe-libs/ui";
|
|
13
14
|
//#region src/technospam/pages/CoursesCoursesPagePage.tsx
|
|
14
|
-
var
|
|
15
|
+
var k = [
|
|
15
16
|
{
|
|
16
17
|
value: "ALL",
|
|
17
18
|
label: "All levels"
|
|
@@ -33,7 +34,7 @@ var E = [
|
|
|
33
34
|
label: "Expert"
|
|
34
35
|
}
|
|
35
36
|
];
|
|
36
|
-
function
|
|
37
|
+
function A(e) {
|
|
37
38
|
let t = [
|
|
38
39
|
"from-accent-primary/25 to-accent-teal/20",
|
|
39
40
|
"from-accent-teal/25 to-accent-primary/15",
|
|
@@ -43,53 +44,53 @@ function D(e) {
|
|
|
43
44
|
for (let t = 0; t < e.length; t += 1) n = n * 31 + e.charCodeAt(t) >>> 0;
|
|
44
45
|
return t[n % t.length];
|
|
45
46
|
}
|
|
46
|
-
function
|
|
47
|
-
return /* @__PURE__ */
|
|
47
|
+
function j({ course: e, onOpen: t }) {
|
|
48
|
+
return /* @__PURE__ */ x(w, {
|
|
48
49
|
className: "group flex cursor-pointer flex-col overflow-hidden transition-shadow hover:shadow-md",
|
|
49
50
|
onClick: t,
|
|
50
|
-
children: [/* @__PURE__ */
|
|
51
|
-
className: `relative flex h-28 items-end bg-gradient-to-br ${
|
|
52
|
-
children: [/* @__PURE__ */
|
|
51
|
+
children: [/* @__PURE__ */ x("div", {
|
|
52
|
+
className: `relative flex h-28 items-end bg-gradient-to-br ${A(e.topic ?? e.level)} p-4`,
|
|
53
|
+
children: [/* @__PURE__ */ b(m, {
|
|
53
54
|
className: "absolute right-3 top-3 text-text-muted/60",
|
|
54
55
|
size: 20
|
|
55
|
-
}), /* @__PURE__ */
|
|
56
|
+
}), /* @__PURE__ */ b(i, {
|
|
56
57
|
status: e.level,
|
|
57
58
|
kind: "level"
|
|
58
59
|
})]
|
|
59
|
-
}), /* @__PURE__ */
|
|
60
|
+
}), /* @__PURE__ */ x("div", {
|
|
60
61
|
className: "flex flex-1 flex-col gap-3 p-4",
|
|
61
62
|
children: [
|
|
62
|
-
/* @__PURE__ */
|
|
63
|
+
/* @__PURE__ */ x("div", {
|
|
63
64
|
className: "min-w-0",
|
|
64
|
-
children: [/* @__PURE__ */
|
|
65
|
+
children: [/* @__PURE__ */ b("h3", {
|
|
65
66
|
className: "truncate text-base font-semibold text-text-primary group-hover:text-accent-primary",
|
|
66
67
|
children: e.title
|
|
67
|
-
}), e.summary ? /* @__PURE__ */
|
|
68
|
+
}), e.summary ? /* @__PURE__ */ b("p", {
|
|
68
69
|
className: "mt-1 line-clamp-2 text-sm text-text-secondary",
|
|
69
70
|
children: e.summary
|
|
70
71
|
}) : null]
|
|
71
72
|
}),
|
|
72
|
-
/* @__PURE__ */
|
|
73
|
+
/* @__PURE__ */ x("div", {
|
|
73
74
|
className: "mt-auto flex flex-wrap items-center gap-3 text-xs text-text-muted",
|
|
74
|
-
children: [e.topic ? /* @__PURE__ */
|
|
75
|
+
children: [e.topic ? /* @__PURE__ */ x("span", {
|
|
75
76
|
className: "inline-flex items-center gap-1",
|
|
76
77
|
children: [
|
|
77
|
-
/* @__PURE__ */ _
|
|
78
|
+
/* @__PURE__ */ b(_, { size: 13 }),
|
|
78
79
|
" ",
|
|
79
80
|
e.topic
|
|
80
81
|
]
|
|
81
|
-
}) : null, /* @__PURE__ */
|
|
82
|
+
}) : null, /* @__PURE__ */ x("span", {
|
|
82
83
|
className: "inline-flex items-center gap-1",
|
|
83
84
|
children: [
|
|
84
|
-
/* @__PURE__ */
|
|
85
|
+
/* @__PURE__ */ b(h, { size: 13 }),
|
|
85
86
|
" ",
|
|
86
87
|
r(e.estimatedMinutes)
|
|
87
88
|
]
|
|
88
89
|
})]
|
|
89
90
|
}),
|
|
90
|
-
e.tags.length ? /* @__PURE__ */
|
|
91
|
+
e.tags.length ? /* @__PURE__ */ b("div", {
|
|
91
92
|
className: "flex flex-wrap gap-1.5",
|
|
92
|
-
children: e.tags.slice(0, 3).map((e) => /* @__PURE__ */
|
|
93
|
+
children: e.tags.slice(0, 3).map((e) => /* @__PURE__ */ b(S, {
|
|
93
94
|
variant: "secondary",
|
|
94
95
|
className: "text-[11px]",
|
|
95
96
|
children: e
|
|
@@ -99,89 +100,95 @@ function O({ course: e, onOpen: t }) {
|
|
|
99
100
|
})]
|
|
100
101
|
});
|
|
101
102
|
}
|
|
102
|
-
function
|
|
103
|
-
let i = e(), [
|
|
103
|
+
function M() {
|
|
104
|
+
let i = e(), [_, S] = p("ALL"), [w, A] = p(""), { limit: M, more: N } = d(), { data: P, loading: F, error: I } = u({
|
|
104
105
|
status: "PUBLISHED",
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
pagination: l(M),
|
|
107
|
+
..._ === "ALL" ? {} : { level: _ }
|
|
108
|
+
}), L = P?.tecListCourses.items ?? [], R = P?.tecListCourses.pageInfo.hasMore ?? !1, z = f(() => {
|
|
109
|
+
let e = w.trim().toLowerCase();
|
|
110
|
+
return e ? L.filter((t) => t.title.toLowerCase().includes(e) || (t.topic ?? "").toLowerCase().includes(e) || t.tags.some((t) => t.toLowerCase().includes(e))) : L;
|
|
111
|
+
}, [L, w]);
|
|
112
|
+
return /* @__PURE__ */ x(t, {
|
|
111
113
|
hero: !0,
|
|
112
114
|
title: "Course Catalog",
|
|
113
|
-
description: "Learn technology by doing —
|
|
114
|
-
actions: /* @__PURE__ */
|
|
115
|
+
description: "Learn technology by doing — practical, project-based courses across the stack.",
|
|
116
|
+
actions: /* @__PURE__ */ x(C, {
|
|
115
117
|
variant: "outline",
|
|
116
118
|
onClick: () => i("/courses/my-learning-page"),
|
|
117
|
-
children: [/* @__PURE__ */
|
|
119
|
+
children: [/* @__PURE__ */ b(g, { size: 16 }), " My Learning"]
|
|
118
120
|
}),
|
|
119
121
|
children: [
|
|
120
|
-
/* @__PURE__ */
|
|
122
|
+
/* @__PURE__ */ x("div", {
|
|
121
123
|
className: "mb-5 grid grid-cols-2 gap-3 sm:grid-cols-4",
|
|
122
124
|
children: [
|
|
123
|
-
/* @__PURE__ */
|
|
125
|
+
/* @__PURE__ */ b(n, {
|
|
124
126
|
label: "Courses",
|
|
125
|
-
value:
|
|
126
|
-
icon:
|
|
127
|
+
value: L.length,
|
|
128
|
+
icon: m
|
|
127
129
|
}),
|
|
128
|
-
/* @__PURE__ */
|
|
130
|
+
/* @__PURE__ */ b(n, {
|
|
129
131
|
label: "Beginner-friendly",
|
|
130
|
-
value:
|
|
131
|
-
icon:
|
|
132
|
+
value: L.filter((e) => e.level === "BEGINNER").length,
|
|
133
|
+
icon: y
|
|
132
134
|
}),
|
|
133
|
-
/* @__PURE__ */
|
|
135
|
+
/* @__PURE__ */ b(n, {
|
|
134
136
|
label: "Advanced+",
|
|
135
|
-
value:
|
|
136
|
-
icon:
|
|
137
|
+
value: L.filter((e) => e.level === "ADVANCED" || e.level === "EXPERT").length,
|
|
138
|
+
icon: g
|
|
137
139
|
}),
|
|
138
|
-
/* @__PURE__ */
|
|
140
|
+
/* @__PURE__ */ b(n, {
|
|
139
141
|
label: "Total content",
|
|
140
|
-
value: r(
|
|
141
|
-
icon:
|
|
142
|
+
value: r(L.reduce((e, t) => e + t.estimatedMinutes, 0)),
|
|
143
|
+
icon: h
|
|
142
144
|
})
|
|
143
145
|
]
|
|
144
146
|
}),
|
|
145
|
-
/* @__PURE__ */
|
|
147
|
+
/* @__PURE__ */ x("div", {
|
|
146
148
|
className: "mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
|
|
147
|
-
children: [/* @__PURE__ */
|
|
148
|
-
value:
|
|
149
|
-
onValueChange: (e) =>
|
|
150
|
-
children: /* @__PURE__ */
|
|
149
|
+
children: [/* @__PURE__ */ b(E, {
|
|
150
|
+
value: _,
|
|
151
|
+
onValueChange: (e) => S(e),
|
|
152
|
+
children: /* @__PURE__ */ b(D, { children: k.map((e) => /* @__PURE__ */ b(O, {
|
|
151
153
|
value: e.value,
|
|
152
154
|
children: e.label
|
|
153
155
|
}, e.value)) })
|
|
154
|
-
}), /* @__PURE__ */
|
|
156
|
+
}), /* @__PURE__ */ x("div", {
|
|
155
157
|
className: "relative w-full sm:w-72",
|
|
156
|
-
children: [/* @__PURE__ */
|
|
158
|
+
children: [/* @__PURE__ */ b(v, {
|
|
157
159
|
className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-text-muted",
|
|
158
160
|
size: 16
|
|
159
|
-
}), /* @__PURE__ */
|
|
161
|
+
}), /* @__PURE__ */ b(T, {
|
|
160
162
|
className: "pl-9",
|
|
161
163
|
placeholder: "Search courses…",
|
|
162
|
-
value:
|
|
163
|
-
onChange: (e) =>
|
|
164
|
+
value: w,
|
|
165
|
+
onChange: (e) => A(e.target.value)
|
|
164
166
|
})]
|
|
165
167
|
})]
|
|
166
168
|
}),
|
|
167
|
-
|
|
169
|
+
F && !L.length ? /* @__PURE__ */ b(c, {}) : I ? /* @__PURE__ */ b(s, {
|
|
168
170
|
title: "Couldn't load courses",
|
|
169
|
-
error:
|
|
170
|
-
}) :
|
|
171
|
-
icon: /* @__PURE__ */
|
|
172
|
-
title:
|
|
173
|
-
description:
|
|
174
|
-
}) : /* @__PURE__ */
|
|
171
|
+
error: I
|
|
172
|
+
}) : z.length === 0 ? /* @__PURE__ */ b(o, {
|
|
173
|
+
icon: /* @__PURE__ */ b(m, { size: 26 }),
|
|
174
|
+
title: w || _ !== "ALL" ? "No matching courses" : "No courses published yet",
|
|
175
|
+
description: w || _ !== "ALL" ? "Try a different search or level filter." : "Published courses will appear here. Check back soon — TechnoSpam is pre-launch."
|
|
176
|
+
}) : /* @__PURE__ */ b("div", {
|
|
175
177
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
176
|
-
children:
|
|
178
|
+
children: z.map((e) => /* @__PURE__ */ b(j, {
|
|
177
179
|
course: e,
|
|
178
180
|
onOpen: () => i(`/courses/course-details-page?id=${e.id}`)
|
|
179
181
|
}, e.id))
|
|
182
|
+
}),
|
|
183
|
+
/* @__PURE__ */ b(a, {
|
|
184
|
+
hasMore: R,
|
|
185
|
+
loading: F,
|
|
186
|
+
onClick: N
|
|
180
187
|
})
|
|
181
188
|
]
|
|
182
189
|
});
|
|
183
190
|
}
|
|
184
191
|
//#endregion
|
|
185
|
-
export {
|
|
192
|
+
export { M as CoursesCoursesPagePage };
|
|
186
193
|
|
|
187
194
|
//# 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 TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadingGrid,\n PageLayout,\n StatCard,\n StatusBadge,\n} from '../components';\nimport { useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport { useCoursesQuery, type TecCourse, type TecCourseLevel } from '../api/technospamApi';\nimport { formatDuration } from '../utils/format';\n\nconst LEVELS: Array<{ value: 'ALL' | TecCourseLevel; label: string }> = [\n { value: 'ALL', label: 'All levels' },\n { value: 'BEGINNER', label: 'Beginner' },\n { value: 'INTERMEDIATE', label: 'Intermediate' },\n { value: 'ADVANCED', label: 'Advanced' },\n { value: 'EXPERT', label: 'Expert' },\n];\n\n/** Deterministic token-based cover gradient keyed off the course topic/level. */\nfunction coverGradient(seed: string): string {\n const gradients = [\n 'from-accent-primary/25 to-accent-teal/20',\n 'from-accent-teal/25 to-accent-primary/15',\n 'from-status-success-bg-subtle to-accent-wash',\n 'from-accent-wash to-bg-sunken',\n ];\n let hash = 0;\n for (let i = 0; i < seed.length; i += 1) hash = (hash * 31 + seed.charCodeAt(i)) >>> 0;\n return gradients[hash % gradients.length]!;\n}\n\nfunction CourseCard({ course, onOpen }: { course: TecCourse; onOpen: () => void }) {\n return (\n <GlassCard\n className=\"group flex cursor-pointer flex-col overflow-hidden transition-shadow hover:shadow-md\"\n onClick={onOpen}\n >\n <div\n className={`relative flex h-28 items-end bg-gradient-to-br ${coverGradient(\n course.topic ?? course.level\n )} p-4`}\n >\n <BookOpen className=\"absolute right-3 top-3 text-text-muted/60\" size={20} />\n <StatusBadge status={course.level} kind=\"level\" />\n </div>\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 {course.title}\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 [level, setLevel] = useState<'ALL' | TecCourseLevel>('ALL');\n const [search, setSearch] = useState('');\n\n const { data, loading, error } = useCoursesQuery({\n status: 'PUBLISHED',\n ...(level !== 'ALL' ? { level } : {}),\n });\n\n const courses = data?.tecListCourses.items ?? [];\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=\"Course Catalog\"\n description=\"Learn technology by doing — runnable, project-based courses across the stack.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> My Learning\n </Button>\n }\n >\n <div className=\"mb-5 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard label=\"Courses\" value={courses.length} icon={BookOpen} />\n <StatCard\n label=\"Beginner-friendly\"\n value={courses.filter((c) => c.level === 'BEGINNER').length}\n icon={Sparkles}\n />\n <StatCard\n label=\"Advanced+\"\n value={courses.filter((c) => c.level === 'ADVANCED' || c.level === 'EXPERT').length}\n icon={GraduationCap}\n />\n <StatCard\n label=\"Total content\"\n value={formatDuration(courses.reduce((s, c) => s + c.estimatedMinutes, 0))}\n icon={Clock}\n />\n </div>\n\n <div className=\"mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <Tabs value={level} onValueChange={(v) => setLevel(v as 'ALL' | TecCourseLevel)}>\n <TabsList>\n {LEVELS.map((l) => (\n <TabsTrigger key={l.value} value={l.value}>\n {l.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\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 placeholder=\"Search courses…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n </div>\n </div>\n\n {loading && !courses.length ? (\n <LoadingGrid />\n ) : error ? (\n <ErrorView title=\"Couldn't load courses\" error={error} />\n ) : filtered.length === 0 ? (\n <EmptyView\n icon={<BookOpen size={26} />}\n title={search || level !== 'ALL' ? 'No matching courses' : 'No courses published yet'}\n description={\n search || level !== 'ALL'\n ? 'Try a different search or level filter.'\n : 'Published courses will appear here. Check back soon — TechnoSpam is pre-launch.'\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={() => navigate(`/courses/course-details-page?id=${course.id}`)}\n />\n ))}\n </div>\n )}\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 { BookOpen, Clock, GraduationCap, Layers, Search, Sparkles } from 'lucide-react';\nimport {\n Badge,\n Button,\n GlassCard,\n Input,\n Tabs,\n TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadMore,\n LoadingGrid,\n PageLayout,\n StatCard,\n StatusBadge,\n} from '../components';\nimport { 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';\n\nconst LEVELS: Array<{ value: 'ALL' | TecCourseLevel; label: string }> = [\n { value: 'ALL', label: 'All levels' },\n { value: 'BEGINNER', label: 'Beginner' },\n { value: 'INTERMEDIATE', label: 'Intermediate' },\n { value: 'ADVANCED', label: 'Advanced' },\n { value: 'EXPERT', label: 'Expert' },\n];\n\n/** Deterministic token-based cover gradient keyed off the course topic/level. */\nfunction coverGradient(seed: string): string {\n const gradients = [\n 'from-accent-primary/25 to-accent-teal/20',\n 'from-accent-teal/25 to-accent-primary/15',\n 'from-status-success-bg-subtle to-accent-wash',\n 'from-accent-wash to-bg-sunken',\n ];\n let hash = 0;\n for (let i = 0; i < seed.length; i += 1) hash = (hash * 31 + seed.charCodeAt(i)) >>> 0;\n return gradients[hash % gradients.length]!;\n}\n\nfunction CourseCard({ course, onOpen }: { course: TecCourse; onOpen: () => void }) {\n return (\n <GlassCard\n className=\"group flex cursor-pointer flex-col overflow-hidden transition-shadow hover:shadow-md\"\n onClick={onOpen}\n >\n <div\n className={`relative flex h-28 items-end bg-gradient-to-br ${coverGradient(\n course.topic ?? course.level\n )} p-4`}\n >\n <BookOpen className=\"absolute right-3 top-3 text-text-muted/60\" size={20} />\n <StatusBadge status={course.level} kind=\"level\" />\n </div>\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 {course.title}\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 [level, setLevel] = useState<'ALL' | TecCourseLevel>('ALL');\n const [search, setSearch] = useState('');\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=\"Course Catalog\"\n description=\"Learn technology by doing — practical, project-based courses across the stack.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> My Learning\n </Button>\n }\n >\n <div className=\"mb-5 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard label=\"Courses\" value={courses.length} icon={BookOpen} />\n <StatCard\n label=\"Beginner-friendly\"\n value={courses.filter((c) => c.level === 'BEGINNER').length}\n icon={Sparkles}\n />\n <StatCard\n label=\"Advanced+\"\n value={courses.filter((c) => c.level === 'ADVANCED' || c.level === 'EXPERT').length}\n icon={GraduationCap}\n />\n <StatCard\n label=\"Total content\"\n value={formatDuration(courses.reduce((s, c) => s + c.estimatedMinutes, 0))}\n icon={Clock}\n />\n </div>\n\n <div className=\"mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <Tabs value={level} onValueChange={(v) => setLevel(v as 'ALL' | TecCourseLevel)}>\n <TabsList>\n {LEVELS.map((l) => (\n <TabsTrigger key={l.value} value={l.value}>\n {l.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\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 placeholder=\"Search courses…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n </div>\n </div>\n\n {loading && !courses.length ? (\n <LoadingGrid />\n ) : error ? (\n <ErrorView title=\"Couldn't load courses\" error={error} />\n ) : filtered.length === 0 ? (\n <EmptyView\n icon={<BookOpen size={26} />}\n title={search || level !== 'ALL' ? 'No matching courses' : 'No courses published yet'}\n description={\n search || level !== 'ALL'\n ? 'Try a different search or level filter.'\n : 'Published courses will appear here. Check back soon — TechnoSpam is pre-launch.'\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={() => navigate(`/courses/course-details-page?id=${course.id}`)}\n />\n ))}\n </div>\n )}\n <LoadMore hasMore={hasMore} loading={loading} onClick={more} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoursesMyLearningPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesMyLearningPagePage.tsx"],"sourcesContent":["import { Award, BookOpen, Clock, GraduationCap, PlayCircle } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useEnrollmentsQuery,\n useCertificatesQuery,\n type TecEnrollment,\n} from '../api/technospamApi';\nimport { formatDate, relativeTime } from '../utils/format';\n\nfunction EnrollmentRow({ e, onOpen }: { e: TecEnrollment; onOpen: () => void }) {\n return (\n <GlassCard\n className=\"flex cursor-pointer items-center gap-4 p-4 transition-shadow hover:shadow-md\"\n onClick={onOpen}\n >\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-accent-wash text-accent-primary\">\n <BookOpen size={20} />\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-semibold text-text-primary\">\n {e.course?.title ?? 'Course'}\n </span>\n <StatusBadge status={e.status} />\n </div>\n <div className=\"mt-1 flex items-center gap-2 text-xs text-text-muted\">\n <span>{e.lessonsCompleted} lessons done</span>\n <span>·</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-accent-primary\"\n style={{ width: `${e.progressPct}%` }}\n />\n </div>\n </div>\n <div className=\"shrink-0 text-right\">\n <div className=\"text-lg font-semibold text-text-primary\">{e.progressPct}%</div>\n <Button
|
|
1
|
+
{"version":3,"file":"CoursesMyLearningPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesMyLearningPagePage.tsx"],"sourcesContent":["import { Award, BookOpen, Clock, GraduationCap, PlayCircle } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useEnrollmentsQuery,\n useCertificatesQuery,\n type TecEnrollment,\n} from '../api/technospamApi';\nimport { formatDate, relativeTime } from '../utils/format';\n\nfunction EnrollmentRow({ e, onOpen }: { e: TecEnrollment; onOpen: () => void }) {\n return (\n <GlassCard\n className=\"flex cursor-pointer items-center gap-4 p-4 transition-shadow hover:shadow-md\"\n onClick={onOpen}\n >\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-accent-wash text-accent-primary\">\n <BookOpen size={20} />\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-semibold text-text-primary\">\n {e.course?.title ?? 'Course'}\n </span>\n <StatusBadge status={e.status} />\n </div>\n <div className=\"mt-1 flex items-center gap-2 text-xs text-text-muted\">\n <span>{e.lessonsCompleted} lessons done</span>\n <span>·</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-accent-primary\"\n style={{ width: `${e.progressPct}%` }}\n />\n </div>\n </div>\n <div className=\"shrink-0 text-right\">\n <div className=\"text-lg font-semibold text-text-primary\">{e.progressPct}%</div>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(ev) => {\n ev.stopPropagation();\n onOpen();\n }}\n >\n <PlayCircle size={15} /> Resume\n </Button>\n </div>\n </GlassCard>\n );\n}\n\nexport function CoursesMyLearningPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n\n const { data, loading, error } = useEnrollmentsQuery(learnerId ? { learnerId } : {});\n const { data: certData } = useCertificatesQuery(learnerId ? { learnerId } : {});\n\n const enrollments = data?.tecListEnrollments.items ?? [];\n const certificates = certData?.tecListCertificates ?? [];\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 label=\"Enrolled\" value={enrollments.length} icon={BookOpen} />\n <StatCard label=\"In progress\" value={inProgress} icon={PlayCircle} />\n <StatCard label=\"Completed\" value={completed} icon={GraduationCap} />\n <StatCard label=\"Certificates\" value={certificates.length} icon={Award} />\n </div>\n\n {loading && !enrollments.length ? (\n <LoadingRows count={3} height=\"h-24\" />\n ) : error ? (\n <ErrorView title=\"Couldn't load your learning\" error={error} />\n ) : enrollments.length === 0 ? (\n <EmptyView\n icon={<BookOpen size={26} />}\n title=\"You haven't enrolled yet\"\n description=\"Browse the catalog and enroll in a course to start building skill by doing.\"\n action={<Button onClick={() => navigate('/courses/courses-page')}>Browse courses</Button>}\n />\n ) : (\n <div className=\"space-y-3\">\n {enrollments.map((e) => (\n <EnrollmentRow\n key={e.id}\n e={e}\n onOpen={() => navigate(`/courses/course-details-page?id=${e.courseId}`)}\n />\n ))}\n </div>\n )}\n\n {certificates.length > 0 ? (\n <div className=\"mt-8\">\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">Certificates</h2>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {certificates.map((c) => (\n <GlassCard key={c.id} className=\"flex items-center gap-3 p-4\">\n <Award className=\"text-accent-teal\" size={22} />\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-medium text-text-primary\">\n {c.course?.title ?? 'Certificate'}\n </div>\n <div className=\"text-xs text-text-muted\">\n Score {c.score} · Issued {formatDate(c.issuedAt)}\n </div>\n </div>\n </GlassCard>\n ))}\n </div>\n </div>\n ) : null}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAkBA,SAAS,EAAc,EAAE,GAAG,aAAoD;AAC9E,QACE,kBAAC,GAAD;EACE,WAAU;EACV,SAAS;YAFX;GAIE,kBAAC,QAAD;IAAM,WAAU;cACd,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;IACjB,CAAA;GACP,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,QAAQ,SAAS;OACf,CAAA,EACP,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,EAAA,UAAM,KAAQ,CAAA;OACd,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,CAA0D,EAAE,aAAY,IAAO;QAC/E,kBAAC,GAAD;KACE,SAAQ;KACR,MAAK;KACL,UAAU,MAAO;AAEf,MADA,EAAG,iBAAiB,EACpB,GAAQ;;eALZ,CAQE,kBAAC,GAAD,EAAY,MAAM,IAAM,CAAA,EAAA,UACjB;OACL;;GACI;;;AAIhB,SAAgB,IAA4B;CAC1C,IAAM,IAAW,GAAuB,EAClC,EAAE,mBAAgB,GAAe,EACjC,IAAY,GAAa,IAEzB,EAAE,SAAM,YAAS,aAAU,EAAoB,IAAY,EAAE,cAAW,GAAG,EAAE,CAAC,EAC9E,EAAE,MAAM,MAAa,EAAqB,IAAY,EAAE,cAAW,GAAG,EAAE,CAAC,EAEzE,IAAc,GAAM,mBAAmB,SAAS,EAAE,EAClD,IAAe,GAAU,uBAAuB,EAAE,EAClD,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;MAAU,OAAM;MAAW,OAAO,EAAY;MAAQ,MAAM;MAAY,CAAA;KACxE,kBAAC,GAAD;MAAU,OAAM;MAAc,OAAO;MAAY,MAAM;MAAc,CAAA;KACrE,kBAAC,GAAD;MAAU,OAAM;MAAY,OAAO;MAAW,MAAM;MAAiB,CAAA;KACrE,kBAAC,GAAD;MAAU,OAAM;MAAe,OAAO,EAAa;MAAQ,MAAM;MAAS,CAAA;KACtE;;GAEL,KAAW,CAAC,EAAY,SACvB,kBAAC,GAAD;IAAa,OAAO;IAAG,QAAO;IAAS,CAAA,GACrC,IACF,kBAAC,GAAD;IAAW,OAAM;IAAqC;IAAS,CAAA,GAC7D,EAAY,WAAW,IACzB,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;IAC5B,OAAM;IACN,aAAY;IACZ,QAAQ,kBAAC,GAAD;KAAQ,eAAe,EAAS,wBAAwB;eAAE;KAAuB,CAAA;IACzF,CAAA,GAEF,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAY,KAAK,MAChB,kBAAC,GAAD;KAEK;KACH,cAAc,EAAS,mCAAmC,EAAE,WAAW;KACvE,EAHK,EAAE,GAGP,CACF;IACE,CAAA;GAGP,EAAa,SAAS,IACrB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAAiD;KAAiB,CAAA,EAChF,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAa,KAAK,MACjB,kBAAC,GAAD;MAAsB,WAAU;gBAAhC,CACE,kBAAC,GAAD;OAAO,WAAU;OAAmB,MAAM;OAAM,CAAA,EAChD,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAE,QAAQ,SAAS;QAChB,CAAA,EACN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SAAyC;SAChC,EAAE;SAAM;SAAW,EAAW,EAAE,SAAS;SAC5C;UACF;SACI;QAVI,EAAE,GAUN,CACZ;KACE,CAAA,CACF;QACJ;GACO"}
|
|
@@ -3,15 +3,16 @@ import { PageLayout as t } from "../components/PageLayout.js";
|
|
|
3
3
|
import { StatCard as n } from "../components/StatCard.js";
|
|
4
4
|
import { formatDate as r, formatMoney as i } from "../utils/format.js";
|
|
5
5
|
import { StatusBadge as a } from "../components/StatusBadge.js";
|
|
6
|
-
import {
|
|
6
|
+
import { LoadMore as o } from "../components/LoadMore.js";
|
|
7
|
+
import { EmptyView as s, ErrorView as c, LoadingGrid as l } from "../components/StateViews.js";
|
|
7
8
|
import "../components/index.js";
|
|
8
|
-
import { useHackathonsQuery as
|
|
9
|
-
import { useState as
|
|
10
|
-
import { Award as
|
|
11
|
-
import { jsx as
|
|
12
|
-
import { Badge as
|
|
9
|
+
import { pageArg as u, useHackathonsQuery as d, usePageLimit as f } from "../api/technospamApi.js";
|
|
10
|
+
import { useMemo as p, useState as m } from "react";
|
|
11
|
+
import { Award as h, CalendarDays as g, Search as _, Sparkles as v, Trophy as y, Users as b } from "lucide-react";
|
|
12
|
+
import { jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
13
|
+
import { Badge as C, GlassCard as w, Input as T, Tabs as E, TabsList as D, TabsTrigger as O } from "@burdenoff/fe-libs/ui";
|
|
13
14
|
//#region src/technospam/pages/HackathonsHackathonsPagePage.tsx
|
|
14
|
-
var
|
|
15
|
+
var k = [
|
|
15
16
|
{
|
|
16
17
|
value: "ALL",
|
|
17
18
|
label: "All"
|
|
@@ -33,7 +34,7 @@ var C = [
|
|
|
33
34
|
label: "Completed"
|
|
34
35
|
}
|
|
35
36
|
];
|
|
36
|
-
function
|
|
37
|
+
function A(e) {
|
|
37
38
|
let t = [
|
|
38
39
|
"from-accent-primary/25 to-accent-teal/20",
|
|
39
40
|
"from-accent-teal/25 to-accent-primary/15",
|
|
@@ -43,62 +44,62 @@ function w(e) {
|
|
|
43
44
|
for (let t = 0; t < e.length; t += 1) n = n * 31 + e.charCodeAt(t) >>> 0;
|
|
44
45
|
return t[n % t.length];
|
|
45
46
|
}
|
|
46
|
-
function
|
|
47
|
-
return /* @__PURE__ */
|
|
47
|
+
function j({ hackathon: e, onOpen: t }) {
|
|
48
|
+
return /* @__PURE__ */ S(w, {
|
|
48
49
|
className: "group flex cursor-pointer flex-col overflow-hidden transition-shadow hover:shadow-md",
|
|
49
50
|
onClick: t,
|
|
50
|
-
children: [/* @__PURE__ */
|
|
51
|
-
className: `relative flex h-24 items-end bg-gradient-to-br ${
|
|
52
|
-
children: [/* @__PURE__ */
|
|
51
|
+
children: [/* @__PURE__ */ S("div", {
|
|
52
|
+
className: `relative flex h-24 items-end bg-gradient-to-br ${A(e.theme ?? e.id)} p-4`,
|
|
53
|
+
children: [/* @__PURE__ */ x(y, {
|
|
53
54
|
className: "absolute right-3 top-3 text-text-muted/60",
|
|
54
55
|
size: 20
|
|
55
|
-
}), /* @__PURE__ */
|
|
56
|
-
}), /* @__PURE__ */
|
|
56
|
+
}), /* @__PURE__ */ x(a, { status: e.status })]
|
|
57
|
+
}), /* @__PURE__ */ S("div", {
|
|
57
58
|
className: "flex flex-1 flex-col gap-3 p-4",
|
|
58
59
|
children: [
|
|
59
|
-
/* @__PURE__ */
|
|
60
|
+
/* @__PURE__ */ S("div", {
|
|
60
61
|
className: "min-w-0",
|
|
61
|
-
children: [/* @__PURE__ */
|
|
62
|
+
children: [/* @__PURE__ */ x("h3", {
|
|
62
63
|
className: "truncate text-base font-semibold text-text-primary group-hover:text-accent-primary",
|
|
63
64
|
children: e.title
|
|
64
|
-
}), e.theme ? /* @__PURE__ */
|
|
65
|
+
}), e.theme ? /* @__PURE__ */ x("p", {
|
|
65
66
|
className: "mt-1 line-clamp-2 text-sm text-text-secondary",
|
|
66
67
|
children: e.theme
|
|
67
68
|
}) : null]
|
|
68
69
|
}),
|
|
69
|
-
/* @__PURE__ */
|
|
70
|
+
/* @__PURE__ */ S("div", {
|
|
70
71
|
className: "mt-auto flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-muted",
|
|
71
|
-
children: [/* @__PURE__ */
|
|
72
|
+
children: [/* @__PURE__ */ S("span", {
|
|
72
73
|
className: "inline-flex items-center gap-1",
|
|
73
74
|
children: [
|
|
74
|
-
/* @__PURE__ */ g
|
|
75
|
+
/* @__PURE__ */ x(g, { size: 13 }),
|
|
75
76
|
" ",
|
|
76
77
|
r(e.startAt),
|
|
77
78
|
" –",
|
|
78
79
|
" ",
|
|
79
80
|
r(e.endAt)
|
|
80
81
|
]
|
|
81
|
-
}), /* @__PURE__ */
|
|
82
|
+
}), /* @__PURE__ */ S("span", {
|
|
82
83
|
className: "inline-flex items-center gap-1",
|
|
83
84
|
children: [
|
|
84
|
-
/* @__PURE__ */
|
|
85
|
+
/* @__PURE__ */ x(b, { size: 13 }),
|
|
85
86
|
" Team of ",
|
|
86
87
|
e.maxTeamSize
|
|
87
88
|
]
|
|
88
89
|
})]
|
|
89
90
|
}),
|
|
90
|
-
/* @__PURE__ */
|
|
91
|
+
/* @__PURE__ */ S("div", {
|
|
91
92
|
className: "flex items-center gap-1.5 text-sm font-medium text-accent-teal",
|
|
92
93
|
children: [
|
|
93
|
-
/* @__PURE__ */
|
|
94
|
+
/* @__PURE__ */ x(h, { size: 15 }),
|
|
94
95
|
" ",
|
|
95
96
|
i(e.prizePool, e.currency),
|
|
96
97
|
" prize pool"
|
|
97
98
|
]
|
|
98
99
|
}),
|
|
99
|
-
e.tags.length ? /* @__PURE__ */
|
|
100
|
+
e.tags.length ? /* @__PURE__ */ x("div", {
|
|
100
101
|
className: "flex flex-wrap gap-1.5",
|
|
101
|
-
children: e.tags.slice(0, 4).map((e) => /* @__PURE__ */
|
|
102
|
+
children: e.tags.slice(0, 4).map((e) => /* @__PURE__ */ x(C, {
|
|
102
103
|
variant: "secondary",
|
|
103
104
|
className: "text-[11px]",
|
|
104
105
|
children: e
|
|
@@ -108,62 +109,84 @@ function T({ hackathon: e, onOpen: t }) {
|
|
|
108
109
|
})]
|
|
109
110
|
});
|
|
110
111
|
}
|
|
111
|
-
function
|
|
112
|
-
let r = e(), [a,
|
|
113
|
-
|
|
112
|
+
function M() {
|
|
113
|
+
let r = e(), [a, g] = m("ALL"), [b, C] = m(""), { limit: w, more: A } = f(), { data: M, loading: N, error: P } = d({
|
|
114
|
+
pagination: u(w),
|
|
115
|
+
...a === "ALL" ? {} : { status: a }
|
|
116
|
+
}), F = M?.tecListHackathons.items ?? [], I = M?.tecListHackathons.pageInfo.hasMore ?? !1, L = p(() => {
|
|
117
|
+
let e = b.trim().toLowerCase();
|
|
118
|
+
return e ? F.filter((t) => t.title.toLowerCase().includes(e) || (t.theme ?? "").toLowerCase().includes(e) || t.tags.some((t) => t.toLowerCase().includes(e))) : F;
|
|
119
|
+
}, [F, b]), R = L.filter((e) => e.status === "REGISTRATION_OPEN").length, z = L.reduce((e, t) => e + t.prizePool, 0), B = L[0]?.currency ?? "USD";
|
|
120
|
+
return /* @__PURE__ */ S(t, {
|
|
114
121
|
hero: !0,
|
|
115
122
|
title: "Hackathons",
|
|
116
123
|
description: "Compete, collaborate, and ship real projects with a team in a fixed window.",
|
|
117
124
|
children: [
|
|
118
|
-
/* @__PURE__ */
|
|
125
|
+
/* @__PURE__ */ S("div", {
|
|
119
126
|
className: "mb-5 grid grid-cols-1 gap-3 sm:grid-cols-3",
|
|
120
127
|
children: [
|
|
121
|
-
/* @__PURE__ */
|
|
128
|
+
/* @__PURE__ */ x(n, {
|
|
122
129
|
label: "Hackathons",
|
|
123
|
-
value:
|
|
124
|
-
icon:
|
|
130
|
+
value: L.length,
|
|
131
|
+
icon: y
|
|
125
132
|
}),
|
|
126
|
-
/* @__PURE__ */
|
|
133
|
+
/* @__PURE__ */ x(n, {
|
|
127
134
|
label: "Registration open",
|
|
128
|
-
value:
|
|
129
|
-
icon:
|
|
135
|
+
value: R,
|
|
136
|
+
icon: v
|
|
130
137
|
}),
|
|
131
|
-
/* @__PURE__ */
|
|
138
|
+
/* @__PURE__ */ x(n, {
|
|
132
139
|
label: "Total prize pool",
|
|
133
|
-
value: i(
|
|
134
|
-
icon:
|
|
140
|
+
value: i(z, B),
|
|
141
|
+
icon: h
|
|
135
142
|
})
|
|
136
143
|
]
|
|
137
144
|
}),
|
|
138
|
-
/* @__PURE__ */
|
|
139
|
-
className: "mb-4",
|
|
140
|
-
children: /* @__PURE__ */
|
|
145
|
+
/* @__PURE__ */ S("div", {
|
|
146
|
+
className: "mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
|
|
147
|
+
children: [/* @__PURE__ */ x(E, {
|
|
141
148
|
value: a,
|
|
142
|
-
onValueChange: (e) =>
|
|
143
|
-
children: /* @__PURE__ */
|
|
149
|
+
onValueChange: (e) => g(e),
|
|
150
|
+
children: /* @__PURE__ */ x(D, { children: k.map((e) => /* @__PURE__ */ x(O, {
|
|
144
151
|
value: e.value,
|
|
145
152
|
children: e.label
|
|
146
153
|
}, e.value)) })
|
|
147
|
-
})
|
|
154
|
+
}), /* @__PURE__ */ S("div", {
|
|
155
|
+
className: "relative w-full sm:w-72",
|
|
156
|
+
children: [/* @__PURE__ */ x(_, {
|
|
157
|
+
className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-text-muted",
|
|
158
|
+
size: 16
|
|
159
|
+
}), /* @__PURE__ */ x(T, {
|
|
160
|
+
className: "pl-9",
|
|
161
|
+
placeholder: "Search hackathons…",
|
|
162
|
+
value: b,
|
|
163
|
+
onChange: (e) => C(e.target.value)
|
|
164
|
+
})]
|
|
165
|
+
})]
|
|
148
166
|
}),
|
|
149
|
-
|
|
167
|
+
N && !L.length ? /* @__PURE__ */ x(l, {}) : P ? /* @__PURE__ */ x(c, {
|
|
150
168
|
title: "Couldn't load hackathons",
|
|
151
|
-
error:
|
|
152
|
-
}) :
|
|
153
|
-
icon: /* @__PURE__ */
|
|
169
|
+
error: P
|
|
170
|
+
}) : L.length === 0 ? /* @__PURE__ */ x(s, {
|
|
171
|
+
icon: /* @__PURE__ */ x(y, { size: 26 }),
|
|
154
172
|
title: a === "ALL" ? "No hackathons yet" : "No hackathons in this stage",
|
|
155
173
|
description: a === "ALL" ? "Upcoming hackathons will appear here. Check back soon — TechnoSpam is pre-launch." : "Try a different stage filter."
|
|
156
|
-
}) : /* @__PURE__ */
|
|
174
|
+
}) : /* @__PURE__ */ x("div", {
|
|
157
175
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
158
|
-
children:
|
|
176
|
+
children: L.map((e) => /* @__PURE__ */ x(j, {
|
|
159
177
|
hackathon: e,
|
|
160
178
|
onOpen: () => r(`/hackathons/hackathon-details-page?id=${e.id}`)
|
|
161
179
|
}, e.id))
|
|
180
|
+
}),
|
|
181
|
+
/* @__PURE__ */ x(o, {
|
|
182
|
+
hasMore: I,
|
|
183
|
+
loading: N,
|
|
184
|
+
onClick: A
|
|
162
185
|
})
|
|
163
186
|
]
|
|
164
187
|
});
|
|
165
188
|
}
|
|
166
189
|
//#endregion
|
|
167
|
-
export {
|
|
190
|
+
export { M as HackathonsHackathonsPagePage };
|
|
168
191
|
|
|
169
192
|
//# sourceMappingURL=HackathonsHackathonsPagePage.js.map
|