@burdenoff/microfe-technospam 2026.1031.3 → 2026.1031.5
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/lib/a11y.d.ts +11 -0
- package/dist/technospam/lib/a11y.d.ts.map +1 -0
- package/dist/technospam/lib/a11y.js +6 -0
- package/dist/technospam/lib/a11y.js.map +1 -0
- package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js +150 -148
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
- package/dist/technospam/pages/ApplicationsProblemsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/ApplicationsProblemsPagePage.js +47 -45
- package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesCoursesPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesCoursesPagePage.js +74 -72
- package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
- package/dist/technospam/pages/HackathonsHackathonsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/HackathonsHackathonsPagePage.js +83 -81
- package/dist/technospam/pages/HackathonsHackathonsPagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipSessionsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/MentorshipSessionsPagePage.js +99 -97
- package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -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 {\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\n className=\"group flex cursor-pointer flex-col overflow-hidden transition-all duration-200 hover:-translate-y-1 hover:shadow-lg\"\n onClick={onOpen}\n >\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 {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 { 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}>\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 { 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 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\n className=\"group flex cursor-pointer flex-col overflow-hidden transition-all duration-200 hover:-translate-y-1 hover:shadow-lg\"\n onClick={onOpen}\n >\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 {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 { 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HackathonsHackathonsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/HackathonsHackathonsPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HackathonsHackathonsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/HackathonsHackathonsPagePage.tsx"],"names":[],"mappings":"AAwFA,wBAAgB,4BAA4B,gCA4K3C"}
|
|
@@ -10,66 +10,67 @@ import { LoadMore as u } from "../components/LoadMore.js";
|
|
|
10
10
|
import { EmptyView as d, ErrorView as f, LoadingGrid as p } from "../components/StateViews.js";
|
|
11
11
|
import { pageArg as m, useHackathonsQuery as h, usePageLimit as g } from "../api/technospamApi.js";
|
|
12
12
|
import "../components/index.js";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
13
|
+
import { TAB_PANEL_FOCUS_CLASS as _ } from "../lib/a11y.js";
|
|
14
|
+
import { useMemo as v, useState as y } from "react";
|
|
15
|
+
import { Award as b, CalendarDays as x, Search as S, Sparkles as C, Trophy as w, Users as T } from "lucide-react";
|
|
16
|
+
import { jsx as E, jsxs as D } from "react/jsx-runtime";
|
|
17
|
+
import { Badge as O, GlassCard as k, Input as A, Tabs as j, TabsContent as M, TabsList as N, TabsTrigger as P } from "@burdenoff/fe-libs/ui";
|
|
17
18
|
//#region src/technospam/pages/HackathonsHackathonsPagePage.tsx
|
|
18
|
-
function
|
|
19
|
+
function F({ hackathon: e, onOpen: t }) {
|
|
19
20
|
let n = r();
|
|
20
|
-
return /* @__PURE__ */
|
|
21
|
+
return /* @__PURE__ */ D(k, {
|
|
21
22
|
className: "group flex cursor-pointer flex-col overflow-hidden transition-all duration-200 hover:-translate-y-1 hover:shadow-lg",
|
|
22
23
|
onClick: t,
|
|
23
|
-
children: [/* @__PURE__ */
|
|
24
|
+
children: [/* @__PURE__ */ E(o, {
|
|
24
25
|
height: "h-32",
|
|
25
26
|
seed: e.theme ?? e.id,
|
|
26
|
-
icon:
|
|
27
|
-
children: /* @__PURE__ */
|
|
28
|
-
}), /* @__PURE__ */
|
|
27
|
+
icon: w,
|
|
28
|
+
children: /* @__PURE__ */ E(l, { status: e.status })
|
|
29
|
+
}), /* @__PURE__ */ D("div", {
|
|
29
30
|
className: "flex flex-1 flex-col gap-3 p-4",
|
|
30
31
|
children: [
|
|
31
|
-
/* @__PURE__ */
|
|
32
|
+
/* @__PURE__ */ D("div", {
|
|
32
33
|
className: "min-w-0",
|
|
33
|
-
children: [/* @__PURE__ */
|
|
34
|
+
children: [/* @__PURE__ */ E("h3", {
|
|
34
35
|
className: "truncate text-base font-semibold text-text-primary group-hover:text-accent-primary",
|
|
35
36
|
children: e.title
|
|
36
|
-
}), e.theme ? /* @__PURE__ */
|
|
37
|
+
}), e.theme ? /* @__PURE__ */ E("p", {
|
|
37
38
|
className: "mt-1 line-clamp-2 text-sm text-text-secondary",
|
|
38
39
|
children: e.theme
|
|
39
40
|
}) : null]
|
|
40
41
|
}),
|
|
41
|
-
/* @__PURE__ */
|
|
42
|
+
/* @__PURE__ */ D("div", {
|
|
42
43
|
className: "mt-auto flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-muted",
|
|
43
|
-
children: [/* @__PURE__ */
|
|
44
|
+
children: [/* @__PURE__ */ D("span", {
|
|
44
45
|
className: "inline-flex items-center gap-1",
|
|
45
46
|
children: [
|
|
46
|
-
/* @__PURE__ */
|
|
47
|
+
/* @__PURE__ */ E(x, { size: 13 }),
|
|
47
48
|
" ",
|
|
48
49
|
s(e.startAt),
|
|
49
50
|
" –",
|
|
50
51
|
" ",
|
|
51
52
|
s(e.endAt)
|
|
52
53
|
]
|
|
53
|
-
}), /* @__PURE__ */
|
|
54
|
+
}), /* @__PURE__ */ D("span", {
|
|
54
55
|
className: "inline-flex items-center gap-1",
|
|
55
56
|
children: [
|
|
56
|
-
/* @__PURE__ */ T
|
|
57
|
+
/* @__PURE__ */ E(T, { size: 13 }),
|
|
57
58
|
" ",
|
|
58
59
|
n("technospam.hackathons.teamSize", "Team of {{size}}", { size: e.maxTeamSize })
|
|
59
60
|
]
|
|
60
61
|
})]
|
|
61
62
|
}),
|
|
62
|
-
/* @__PURE__ */
|
|
63
|
+
/* @__PURE__ */ D("div", {
|
|
63
64
|
className: "flex items-center gap-1.5 text-sm font-medium text-accent-teal",
|
|
64
65
|
children: [
|
|
65
|
-
/* @__PURE__ */
|
|
66
|
+
/* @__PURE__ */ E(b, { size: 15 }),
|
|
66
67
|
" ",
|
|
67
68
|
n("technospam.hackathons.prizePool", "{{prize}} prize pool", { prize: c(e.prizePool, e.currency) })
|
|
68
69
|
]
|
|
69
70
|
}),
|
|
70
|
-
e.tags.length ? /* @__PURE__ */
|
|
71
|
+
e.tags.length ? /* @__PURE__ */ E("div", {
|
|
71
72
|
className: "flex flex-wrap gap-1.5",
|
|
72
|
-
children: e.tags.slice(0, 4).map((e) => /* @__PURE__ */
|
|
73
|
+
children: e.tags.slice(0, 4).map((e) => /* @__PURE__ */ E(O, {
|
|
73
74
|
variant: "secondary",
|
|
74
75
|
className: "text-[11px]",
|
|
75
76
|
children: e
|
|
@@ -79,83 +80,83 @@ function P({ hackathon: e, onOpen: t }) {
|
|
|
79
80
|
})]
|
|
80
81
|
});
|
|
81
82
|
}
|
|
82
|
-
function
|
|
83
|
-
let o = t(), { workspaceId: s } = e(), { emit: l } = n(),
|
|
83
|
+
function I() {
|
|
84
|
+
let o = t(), { workspaceId: s } = e(), { emit: l } = n(), x = r(), [T, O] = y("ALL"), [k, I] = y(""), { limit: L, more: R } = g(), z = v(() => [
|
|
84
85
|
{
|
|
85
86
|
value: "ALL",
|
|
86
|
-
label:
|
|
87
|
+
label: x("technospam.hackathons.filterAll", "All")
|
|
87
88
|
},
|
|
88
89
|
{
|
|
89
90
|
value: "REGISTRATION_OPEN",
|
|
90
|
-
label:
|
|
91
|
+
label: x("technospam.hackathons.filterRegistrationOpen", "Registration open")
|
|
91
92
|
},
|
|
92
93
|
{
|
|
93
94
|
value: "IN_PROGRESS",
|
|
94
|
-
label:
|
|
95
|
+
label: x("technospam.hackathons.filterInProgress", "In progress")
|
|
95
96
|
},
|
|
96
97
|
{
|
|
97
98
|
value: "JUDGING",
|
|
98
|
-
label:
|
|
99
|
+
label: x("technospam.hackathons.filterJudging", "Judging")
|
|
99
100
|
},
|
|
100
101
|
{
|
|
101
102
|
value: "COMPLETED",
|
|
102
|
-
label:
|
|
103
|
+
label: x("technospam.hackathons.filterCompleted", "Completed")
|
|
103
104
|
}
|
|
104
|
-
], [
|
|
105
|
-
pagination: m(
|
|
106
|
-
...
|
|
107
|
-
}),
|
|
108
|
-
let e =
|
|
109
|
-
return e ?
|
|
110
|
-
}, [
|
|
111
|
-
return /* @__PURE__ */
|
|
105
|
+
], [x]), { data: B, loading: V, error: H } = h({
|
|
106
|
+
pagination: m(L),
|
|
107
|
+
...T === "ALL" ? {} : { status: T }
|
|
108
|
+
}), U = B?.tecListHackathons?.items ?? [], W = B?.tecListHackathons?.pageInfo?.hasMore ?? !1, G = v(() => {
|
|
109
|
+
let e = k.trim().toLowerCase();
|
|
110
|
+
return e ? U.filter((t) => t.title.toLowerCase().includes(e) || (t.theme ?? "").toLowerCase().includes(e) || t.tags.some((t) => t.toLowerCase().includes(e))) : U;
|
|
111
|
+
}, [U, k]), K = G.filter((e) => e.status === "REGISTRATION_OPEN").length, q = G.reduce((e, t) => e + t.prizePool, 0), J = G[0]?.currency ?? "USD";
|
|
112
|
+
return /* @__PURE__ */ D(i, {
|
|
112
113
|
hero: !0,
|
|
113
|
-
title:
|
|
114
|
-
description:
|
|
115
|
-
children: [/* @__PURE__ */
|
|
114
|
+
title: x("technospam.hackathons.title", "Hackathons"),
|
|
115
|
+
description: x("technospam.hackathons.description", "Compete, collaborate, and ship real projects with a team in a fixed window."),
|
|
116
|
+
children: [/* @__PURE__ */ D("div", {
|
|
116
117
|
className: "mb-5 grid grid-cols-1 gap-3 sm:grid-cols-3",
|
|
117
118
|
children: [
|
|
118
|
-
/* @__PURE__ */
|
|
119
|
-
label:
|
|
120
|
-
value:
|
|
121
|
-
icon:
|
|
119
|
+
/* @__PURE__ */ E(a, {
|
|
120
|
+
label: x("technospam.hackathons.statHackathons", "Hackathons"),
|
|
121
|
+
value: G.length,
|
|
122
|
+
icon: w,
|
|
122
123
|
tone: "purple"
|
|
123
124
|
}),
|
|
124
|
-
/* @__PURE__ */
|
|
125
|
-
label:
|
|
126
|
-
value:
|
|
127
|
-
icon:
|
|
125
|
+
/* @__PURE__ */ E(a, {
|
|
126
|
+
label: x("technospam.hackathons.statRegistrationOpen", "Registration open"),
|
|
127
|
+
value: K,
|
|
128
|
+
icon: C,
|
|
128
129
|
tone: "green"
|
|
129
130
|
}),
|
|
130
|
-
/* @__PURE__ */
|
|
131
|
-
label:
|
|
132
|
-
value: c(
|
|
133
|
-
icon:
|
|
131
|
+
/* @__PURE__ */ E(a, {
|
|
132
|
+
label: x("technospam.hackathons.statTotalPrizePool", "Total prize pool"),
|
|
133
|
+
value: c(q, J),
|
|
134
|
+
icon: b,
|
|
134
135
|
tone: "amber"
|
|
135
136
|
})
|
|
136
137
|
]
|
|
137
|
-
}), /* @__PURE__ */
|
|
138
|
+
}), /* @__PURE__ */ D(j, {
|
|
138
139
|
className: "gap-0",
|
|
139
|
-
value:
|
|
140
|
-
onValueChange: (e) =>
|
|
141
|
-
children: [/* @__PURE__ */
|
|
140
|
+
value: T,
|
|
141
|
+
onValueChange: (e) => O(e),
|
|
142
|
+
children: [/* @__PURE__ */ D("div", {
|
|
142
143
|
className: "mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
|
|
143
|
-
children: [/* @__PURE__ */
|
|
144
|
+
children: [/* @__PURE__ */ E(N, { children: z.map((e) => /* @__PURE__ */ E(P, {
|
|
144
145
|
value: e.value,
|
|
145
146
|
children: e.label
|
|
146
|
-
}, e.value)) }), /* @__PURE__ */
|
|
147
|
+
}, e.value)) }), /* @__PURE__ */ D("div", {
|
|
147
148
|
className: "relative w-full sm:w-72",
|
|
148
|
-
children: [/* @__PURE__ */
|
|
149
|
+
children: [/* @__PURE__ */ E(S, {
|
|
149
150
|
className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-text-muted",
|
|
150
151
|
size: 16
|
|
151
|
-
}), /* @__PURE__ */
|
|
152
|
+
}), /* @__PURE__ */ E(A, {
|
|
152
153
|
className: "pl-9",
|
|
153
|
-
"aria-label":
|
|
154
|
-
placeholder:
|
|
155
|
-
value:
|
|
156
|
-
onChange: (e) =>
|
|
154
|
+
"aria-label": x("technospam.hackathons.searchLabel", "Search hackathons"),
|
|
155
|
+
placeholder: x("technospam.hackathons.searchPlaceholder", "Search hackathons…"),
|
|
156
|
+
value: k,
|
|
157
|
+
onChange: (e) => I(e.target.value),
|
|
157
158
|
onBlur: () => {
|
|
158
|
-
let e =
|
|
159
|
+
let e = k.trim();
|
|
159
160
|
e && l("technospam.search.performed", {
|
|
160
161
|
workspaceId: s,
|
|
161
162
|
entityType: "hackathon",
|
|
@@ -164,18 +165,19 @@ function F() {
|
|
|
164
165
|
}
|
|
165
166
|
})]
|
|
166
167
|
})]
|
|
167
|
-
}), /* @__PURE__ */
|
|
168
|
-
value:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
168
|
+
}), /* @__PURE__ */ D(M, {
|
|
169
|
+
value: T,
|
|
170
|
+
className: _,
|
|
171
|
+
children: [V && !G.length ? /* @__PURE__ */ E(p, {}) : H ? /* @__PURE__ */ E(f, {
|
|
172
|
+
title: x("technospam.common.errorLoadHackathons", "Couldn't load hackathons"),
|
|
173
|
+
error: H
|
|
174
|
+
}) : G.length === 0 ? /* @__PURE__ */ E(d, {
|
|
175
|
+
icon: /* @__PURE__ */ E(w, { size: 26 }),
|
|
176
|
+
title: T === "ALL" ? x("technospam.hackathons.emptyDefaultTitle", "No hackathons yet") : x("technospam.hackathons.emptyFilteredTitle", "No hackathons in this stage"),
|
|
177
|
+
description: T === "ALL" ? x("technospam.hackathons.emptyDefaultDesc", "Upcoming hackathons will appear here. Check back soon — TechnoSpam is pre-launch.") : x("technospam.hackathons.emptyFilteredDesc", "Try a different stage filter.")
|
|
178
|
+
}) : /* @__PURE__ */ E("div", {
|
|
177
179
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
178
|
-
children:
|
|
180
|
+
children: G.map((e) => /* @__PURE__ */ E(F, {
|
|
179
181
|
hackathon: e,
|
|
180
182
|
onOpen: () => {
|
|
181
183
|
l("technospam.hackathon.viewed", {
|
|
@@ -185,16 +187,16 @@ function F() {
|
|
|
185
187
|
}), o(`/hackathons/hackathon-details-page?id=${e.id}`);
|
|
186
188
|
}
|
|
187
189
|
}, e.id))
|
|
188
|
-
}), /* @__PURE__ */
|
|
189
|
-
hasMore:
|
|
190
|
-
loading:
|
|
191
|
-
onClick:
|
|
190
|
+
}), /* @__PURE__ */ E(u, {
|
|
191
|
+
hasMore: W,
|
|
192
|
+
loading: V,
|
|
193
|
+
onClick: R
|
|
192
194
|
})]
|
|
193
195
|
})]
|
|
194
196
|
})]
|
|
195
197
|
});
|
|
196
198
|
}
|
|
197
199
|
//#endregion
|
|
198
|
-
export {
|
|
200
|
+
export { I as HackathonsHackathonsPagePage };
|
|
199
201
|
|
|
200
202
|
//# sourceMappingURL=HackathonsHackathonsPagePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HackathonsHackathonsPagePage.js","names":[],"sources":["../../../src/technospam/pages/HackathonsHackathonsPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Award, CalendarDays, Search, Sparkles, Trophy, Users } from 'lucide-react';\nimport {\n Badge,\n GlassCard,\n Input,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadMore,\n LoadingGrid,\n MediaCover,\n PageLayout,\n StatCard,\n StatusBadge,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n pageArg,\n useHackathonsQuery,\n usePageLimit,\n type TecHackathon,\n type TecHackathonStatus,\n} from '../api/technospamApi';\nimport { formatDate, formatMoney } from '../utils/format';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\nimport { useTr } from '../hooks/useTr';\n\nfunction HackathonCard({ hackathon, onOpen }: { hackathon: TecHackathon; onOpen: () => void }) {\n const tr = useTr();\n return (\n <GlassCard\n className=\"group flex cursor-pointer flex-col overflow-hidden transition-all duration-200 hover:-translate-y-1 hover:shadow-lg\"\n onClick={onOpen}\n >\n <MediaCover height=\"h-32\" seed={hackathon.theme ?? hackathon.id} icon={Trophy}>\n <StatusBadge status={hackathon.status} />\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 {hackathon.title}\n </h3>\n {hackathon.theme ? (\n <p className=\"mt-1 line-clamp-2 text-sm text-text-secondary\">{hackathon.theme}</p>\n ) : null}\n </div>\n\n <div className=\"mt-auto flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-muted\">\n <span className=\"inline-flex items-center gap-1\">\n <CalendarDays size={13} /> {formatDate(hackathon.startAt)} –{' '}\n {formatDate(hackathon.endAt)}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Users size={13} />{' '}\n {tr('technospam.hackathons.teamSize', 'Team of {{size}}', {\n size: hackathon.maxTeamSize,\n })}\n </span>\n </div>\n\n <div className=\"flex items-center gap-1.5 text-sm font-medium text-accent-teal\">\n <Award size={15} />{' '}\n {tr('technospam.hackathons.prizePool', '{{prize}} prize pool', {\n prize: formatMoney(hackathon.prizePool, hackathon.currency),\n })}\n </div>\n\n {hackathon.tags.length ? (\n <div className=\"flex flex-wrap gap-1.5\">\n {hackathon.tags.slice(0, 4).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 HackathonsHackathonsPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const tr = useTr();\n const [status, setStatus] = useState<'ALL' | TecHackathonStatus>('ALL');\n const [search, setSearch] = useState('');\n const { limit, more } = usePageLimit();\n\n const statuses = useMemo(\n () => [\n { value: 'ALL' as const, label: tr('technospam.hackathons.filterAll', 'All') },\n {\n value: 'REGISTRATION_OPEN' as TecHackathonStatus,\n label: tr('technospam.hackathons.filterRegistrationOpen', 'Registration open'),\n },\n {\n value: 'IN_PROGRESS' as TecHackathonStatus,\n label: tr('technospam.hackathons.filterInProgress', 'In progress'),\n },\n {\n value: 'JUDGING' as TecHackathonStatus,\n label: tr('technospam.hackathons.filterJudging', 'Judging'),\n },\n {\n value: 'COMPLETED' as TecHackathonStatus,\n label: tr('technospam.hackathons.filterCompleted', 'Completed'),\n },\n ],\n [tr]\n );\n\n const { data, loading, error } = useHackathonsQuery({\n pagination: pageArg(limit),\n ...(status !== 'ALL' ? { status } : {}),\n });\n\n const allHackathons = data?.tecListHackathons?.items ?? [];\n const hasMore = data?.tecListHackathons?.pageInfo?.hasMore ?? false;\n const hackathons = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return allHackathons;\n return allHackathons.filter(\n (h) =>\n h.title.toLowerCase().includes(q) ||\n (h.theme ?? '').toLowerCase().includes(q) ||\n h.tags.some((t) => t.toLowerCase().includes(q))\n );\n }, [allHackathons, search]);\n const openCount = hackathons.filter((h) => h.status === 'REGISTRATION_OPEN').length;\n const totalPrize = hackathons.reduce((s, h) => s + h.prizePool, 0);\n const prizeCurrency = hackathons[0]?.currency ?? 'USD';\n\n return (\n <PageLayout\n hero\n title={tr('technospam.hackathons.title', 'Hackathons')}\n description={tr(\n 'technospam.hackathons.description',\n 'Compete, collaborate, and ship real projects with a team in a fixed window.'\n )}\n >\n <div className=\"mb-5 grid grid-cols-1 gap-3 sm:grid-cols-3\">\n <StatCard\n label={tr('technospam.hackathons.statHackathons', 'Hackathons')}\n value={hackathons.length}\n icon={Trophy}\n tone=\"purple\"\n />\n <StatCard\n label={tr('technospam.hackathons.statRegistrationOpen', 'Registration open')}\n value={openCount}\n icon={Sparkles}\n tone=\"green\"\n />\n <StatCard\n label={tr('technospam.hackathons.statTotalPrizePool', 'Total prize pool')}\n value={formatMoney(totalPrize, prizeCurrency)}\n icon={Award}\n tone=\"amber\"\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={status}\n onValueChange={(v) => setStatus(v as 'ALL' | TecHackathonStatus)}\n >\n <div className=\"mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <TabsList>\n {statuses.map((s) => (\n <TabsTrigger key={s.value} value={s.value}>\n {s.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.hackathons.searchLabel', 'Search hackathons')}\n placeholder={tr('technospam.hackathons.searchPlaceholder', 'Search hackathons…')}\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: 'hackathon',\n queryLength: value.length,\n });\n }\n }}\n />\n </div>\n </div>\n\n <TabsContent value={status}>\n {loading && !hackathons.length ? (\n <LoadingGrid />\n ) : error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadHackathons', \"Couldn't load hackathons\")}\n error={error}\n />\n ) : hackathons.length === 0 ? (\n <EmptyView\n icon={<Trophy size={26} />}\n title={\n status !== 'ALL'\n ? tr('technospam.hackathons.emptyFilteredTitle', 'No hackathons in this stage')\n : tr('technospam.hackathons.emptyDefaultTitle', 'No hackathons yet')\n }\n description={\n status !== 'ALL'\n ? tr('technospam.hackathons.emptyFilteredDesc', 'Try a different stage filter.')\n : tr(\n 'technospam.hackathons.emptyDefaultDesc',\n 'Upcoming hackathons 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 {hackathons.map((hackathon) => (\n <HackathonCard\n key={hackathon.id}\n hackathon={hackathon}\n onOpen={() => {\n emit('technospam.hackathon.viewed', {\n workspaceId,\n entityId: hackathon.id,\n entityType: 'TEC_HACKATHON',\n });\n navigate(`/hackathons/hackathon-details-page?id=${hackathon.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":";;;;;;;;;;;;;;;;;AAiCA,SAAS,EAAc,EAAE,cAAW,aAA2D;CAC7F,IAAM,IAAK,GAAO;AAClB,QACE,kBAAC,GAAD;EACE,WAAU;EACV,SAAS;YAFX,CAIE,kBAAC,GAAD;GAAY,QAAO;GAAO,MAAM,EAAU,SAAS,EAAU;GAAI,MAAM;aACrE,kBAAC,GAAD,EAAa,QAAQ,EAAU,QAAU,CAAA;GAC9B,CAAA,EACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAU;MACR,CAAA,EACJ,EAAU,QACT,kBAAC,KAAD;MAAG,WAAU;gBAAiD,EAAU;MAAU,CAAA,GAChF,KACA;;IAEN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAc,MAAM,IAAM,CAAA;;OAAE,EAAW,EAAU,QAAQ;OAAC;OAAG;OAC5D,EAAW,EAAU,MAAM;OACvB;SACP,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;OAAC;OACnB,EAAG,kCAAkC,oBAAoB,EACxD,MAAM,EAAU,aACjB,CAAC;OACG;QACH;;IAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;MAAC;MACnB,EAAG,mCAAmC,wBAAwB,EAC7D,OAAO,EAAY,EAAU,WAAW,EAAU,SAAS,EAC5D,CAAC;MACE;;IAEL,EAAU,KAAK,SACd,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAU,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,MAC/B,kBAAC,GAAD;MAAe,SAAQ;MAAY,WAAU;gBAC1C;MACK,EAFI,EAEJ,CACR;KACE,CAAA,GACJ;IACA;KACI;;;AAIhB,SAAgB,IAA+B;CAC7C,IAAM,IAAW,GAAuB,EAClC,EAAE,mBAAgB,GAAe,EACjC,EAAE,YAAS,GAAwB,EACnC,IAAK,GAAO,EACZ,CAAC,GAAQ,KAAa,EAAqC,MAAM,EACjE,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,EAAE,UAAO,YAAS,GAAc,EAEhC,IAAW,QACT;EACJ;GAAE,OAAO;GAAgB,OAAO,EAAG,mCAAmC,MAAM;GAAE;EAC9E;GACE,OAAO;GACP,OAAO,EAAG,gDAAgD,oBAAoB;GAC/E;EACD;GACE,OAAO;GACP,OAAO,EAAG,0CAA0C,cAAc;GACnE;EACD;GACE,OAAO;GACP,OAAO,EAAG,uCAAuC,UAAU;GAC5D;EACD;GACE,OAAO;GACP,OAAO,EAAG,yCAAyC,YAAY;GAChE;EACF,EACD,CAAC,EAAG,CACL,EAEK,EAAE,SAAM,YAAS,aAAU,EAAmB;EAClD,YAAY,EAAQ,EAAM;EAC1B,GAAI,MAAW,QAAqB,EAAE,GAAf,EAAE,WAAQ;EAClC,CAAC,EAEI,IAAgB,GAAM,mBAAmB,SAAS,EAAE,EACpD,IAAU,GAAM,mBAAmB,UAAU,WAAW,IACxD,IAAa,QAAc;EAC/B,IAAM,IAAI,EAAO,MAAM,CAAC,aAAa;AAErC,SADK,IACE,EAAc,QAClB,MACC,EAAE,MAAM,aAAa,CAAC,SAAS,EAAE,KAChC,EAAE,SAAS,IAAI,aAAa,CAAC,SAAS,EAAE,IACzC,EAAE,KAAK,MAAM,MAAM,EAAE,aAAa,CAAC,SAAS,EAAE,CAAC,CAClD,GANc;IAOd,CAAC,GAAe,EAAO,CAAC,EACrB,IAAY,EAAW,QAAQ,MAAM,EAAE,WAAW,oBAAoB,CAAC,QACvE,IAAa,EAAW,QAAQ,GAAG,MAAM,IAAI,EAAE,WAAW,EAAE,EAC5D,IAAgB,EAAW,IAAI,YAAY;AAEjD,QACE,kBAAC,GAAD;EACE,MAAA;EACA,OAAO,EAAG,+BAA+B,aAAa;EACtD,aAAa,EACX,qCACA,8EACD;YANH,CAQE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KACE,OAAO,EAAG,wCAAwC,aAAa;KAC/D,OAAO,EAAW;KAClB,MAAM;KACN,MAAK;KACL,CAAA;IACF,kBAAC,GAAD;KACE,OAAO,EAAG,8CAA8C,oBAAoB;KAC5E,OAAO;KACP,MAAM;KACN,MAAK;KACL,CAAA;IACF,kBAAC,GAAD;KACE,OAAO,EAAG,4CAA4C,mBAAmB;KACzE,OAAO,EAAY,GAAY,EAAc;KAC7C,MAAM;KACN,MAAK;KACL,CAAA;IACE;MAKN,kBAAC,GAAD;GACE,WAAU;GACV,OAAO;GACP,gBAAgB,MAAM,EAAU,EAAgC;aAHlE,CAKE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAA,UACG,EAAS,KAAK,MACb,kBAAC,GAAD;KAA2B,OAAO,EAAE;eACjC,EAAE;KACS,EAFI,EAAE,MAEN,CACd,EACO,CAAA,EACX,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,WAAU;MACV,MAAM;MACN,CAAA,EACF,kBAAC,GAAD;MACE,WAAU;MACV,cAAY,EAAG,qCAAqC,oBAAoB;MACxE,aAAa,EAAG,2CAA2C,qBAAqB;MAChF,OAAO;MACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;MAC1C,cAAc;OACZ,IAAM,IAAQ,EAAO,MAAM;AAC3B,OAAI,KACF,EAAK,+BAA+B;QAClC;QACA,YAAY;QACZ,aAAa,EAAM;QACpB,CAAC;;MAGN,CAAA,CACE;OACF;OAEN,kBAAC,GAAD;IAAa,OAAO;cAApB,CACG,KAAW,CAAC,EAAW,SACtB,kBAAC,GAAD,EAAe,CAAA,GACb,IACF,kBAAC,GAAD;KACE,OAAO,EAAG,yCAAyC,2BAA2B;KACvE;KACP,CAAA,GACA,EAAW,WAAW,IACxB,kBAAC,GAAD;KACE,MAAM,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA;KAC1B,OACE,MAAW,QAEP,EAAG,2CAA2C,oBAAoB,GADlE,EAAG,4CAA4C,8BAA8B;KAGnF,aACE,MAAW,QAEP,EACE,0CACA,oFACD,GAJD,EAAG,2CAA2C,gCAAgC;KAMpF,CAAA,GAEF,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAW,KAAK,MACf,kBAAC,GAAD;MAEa;MACX,cAAc;AAMZ,OALA,EAAK,+BAA+B;QAClC;QACA,UAAU,EAAU;QACpB,YAAY;QACb,CAAC,EACF,EAAS,yCAAyC,EAAU,KAAK;;MAEnE,EAVK,EAAU,GAUf,CACF;KACE,CAAA,EAER,kBAAC,GAAD;KAAmB;KAAkB;KAAS,SAAS;KAAQ,CAAA,CACnD;MACT;KACI"}
|
|
1
|
+
{"version":3,"file":"HackathonsHackathonsPagePage.js","names":[],"sources":["../../../src/technospam/pages/HackathonsHackathonsPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Award, CalendarDays, Search, Sparkles, Trophy, Users } from 'lucide-react';\nimport {\n Badge,\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 EmptyView,\n ErrorView,\n LoadMore,\n LoadingGrid,\n MediaCover,\n PageLayout,\n StatCard,\n StatusBadge,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n pageArg,\n useHackathonsQuery,\n usePageLimit,\n type TecHackathon,\n type TecHackathonStatus,\n} from '../api/technospamApi';\nimport { formatDate, formatMoney } from '../utils/format';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\nimport { useTr } from '../hooks/useTr';\n\nfunction HackathonCard({ hackathon, onOpen }: { hackathon: TecHackathon; onOpen: () => void }) {\n const tr = useTr();\n return (\n <GlassCard\n className=\"group flex cursor-pointer flex-col overflow-hidden transition-all duration-200 hover:-translate-y-1 hover:shadow-lg\"\n onClick={onOpen}\n >\n <MediaCover height=\"h-32\" seed={hackathon.theme ?? hackathon.id} icon={Trophy}>\n <StatusBadge status={hackathon.status} />\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 {hackathon.title}\n </h3>\n {hackathon.theme ? (\n <p className=\"mt-1 line-clamp-2 text-sm text-text-secondary\">{hackathon.theme}</p>\n ) : null}\n </div>\n\n <div className=\"mt-auto flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-muted\">\n <span className=\"inline-flex items-center gap-1\">\n <CalendarDays size={13} /> {formatDate(hackathon.startAt)} –{' '}\n {formatDate(hackathon.endAt)}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Users size={13} />{' '}\n {tr('technospam.hackathons.teamSize', 'Team of {{size}}', {\n size: hackathon.maxTeamSize,\n })}\n </span>\n </div>\n\n <div className=\"flex items-center gap-1.5 text-sm font-medium text-accent-teal\">\n <Award size={15} />{' '}\n {tr('technospam.hackathons.prizePool', '{{prize}} prize pool', {\n prize: formatMoney(hackathon.prizePool, hackathon.currency),\n })}\n </div>\n\n {hackathon.tags.length ? (\n <div className=\"flex flex-wrap gap-1.5\">\n {hackathon.tags.slice(0, 4).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 HackathonsHackathonsPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const tr = useTr();\n const [status, setStatus] = useState<'ALL' | TecHackathonStatus>('ALL');\n const [search, setSearch] = useState('');\n const { limit, more } = usePageLimit();\n\n const statuses = useMemo(\n () => [\n { value: 'ALL' as const, label: tr('technospam.hackathons.filterAll', 'All') },\n {\n value: 'REGISTRATION_OPEN' as TecHackathonStatus,\n label: tr('technospam.hackathons.filterRegistrationOpen', 'Registration open'),\n },\n {\n value: 'IN_PROGRESS' as TecHackathonStatus,\n label: tr('technospam.hackathons.filterInProgress', 'In progress'),\n },\n {\n value: 'JUDGING' as TecHackathonStatus,\n label: tr('technospam.hackathons.filterJudging', 'Judging'),\n },\n {\n value: 'COMPLETED' as TecHackathonStatus,\n label: tr('technospam.hackathons.filterCompleted', 'Completed'),\n },\n ],\n [tr]\n );\n\n const { data, loading, error } = useHackathonsQuery({\n pagination: pageArg(limit),\n ...(status !== 'ALL' ? { status } : {}),\n });\n\n const allHackathons = data?.tecListHackathons?.items ?? [];\n const hasMore = data?.tecListHackathons?.pageInfo?.hasMore ?? false;\n const hackathons = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return allHackathons;\n return allHackathons.filter(\n (h) =>\n h.title.toLowerCase().includes(q) ||\n (h.theme ?? '').toLowerCase().includes(q) ||\n h.tags.some((t) => t.toLowerCase().includes(q))\n );\n }, [allHackathons, search]);\n const openCount = hackathons.filter((h) => h.status === 'REGISTRATION_OPEN').length;\n const totalPrize = hackathons.reduce((s, h) => s + h.prizePool, 0);\n const prizeCurrency = hackathons[0]?.currency ?? 'USD';\n\n return (\n <PageLayout\n hero\n title={tr('technospam.hackathons.title', 'Hackathons')}\n description={tr(\n 'technospam.hackathons.description',\n 'Compete, collaborate, and ship real projects with a team in a fixed window.'\n )}\n >\n <div className=\"mb-5 grid grid-cols-1 gap-3 sm:grid-cols-3\">\n <StatCard\n label={tr('technospam.hackathons.statHackathons', 'Hackathons')}\n value={hackathons.length}\n icon={Trophy}\n tone=\"purple\"\n />\n <StatCard\n label={tr('technospam.hackathons.statRegistrationOpen', 'Registration open')}\n value={openCount}\n icon={Sparkles}\n tone=\"green\"\n />\n <StatCard\n label={tr('technospam.hackathons.statTotalPrizePool', 'Total prize pool')}\n value={formatMoney(totalPrize, prizeCurrency)}\n icon={Award}\n tone=\"amber\"\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={status}\n onValueChange={(v) => setStatus(v as 'ALL' | TecHackathonStatus)}\n >\n <div className=\"mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <TabsList>\n {statuses.map((s) => (\n <TabsTrigger key={s.value} value={s.value}>\n {s.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.hackathons.searchLabel', 'Search hackathons')}\n placeholder={tr('technospam.hackathons.searchPlaceholder', 'Search hackathons…')}\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: 'hackathon',\n queryLength: value.length,\n });\n }\n }}\n />\n </div>\n </div>\n\n <TabsContent value={status} className={TAB_PANEL_FOCUS_CLASS}>\n {loading && !hackathons.length ? (\n <LoadingGrid />\n ) : error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadHackathons', \"Couldn't load hackathons\")}\n error={error}\n />\n ) : hackathons.length === 0 ? (\n <EmptyView\n icon={<Trophy size={26} />}\n title={\n status !== 'ALL'\n ? tr('technospam.hackathons.emptyFilteredTitle', 'No hackathons in this stage')\n : tr('technospam.hackathons.emptyDefaultTitle', 'No hackathons yet')\n }\n description={\n status !== 'ALL'\n ? tr('technospam.hackathons.emptyFilteredDesc', 'Try a different stage filter.')\n : tr(\n 'technospam.hackathons.emptyDefaultDesc',\n 'Upcoming hackathons 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 {hackathons.map((hackathon) => (\n <HackathonCard\n key={hackathon.id}\n hackathon={hackathon}\n onOpen={() => {\n emit('technospam.hackathon.viewed', {\n workspaceId,\n entityId: hackathon.id,\n entityType: 'TEC_HACKATHON',\n });\n navigate(`/hackathons/hackathon-details-page?id=${hackathon.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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MentorshipSessionsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/MentorshipSessionsPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MentorshipSessionsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/MentorshipSessionsPagePage.tsx"],"names":[],"mappings":"AA0SA,wBAAgB,0BAA0B,gCAmPzC"}
|