@burdenoff/microfe-technospam 2026.1102.2 → 2026.1102.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/global-types.d.ts +307 -41
- package/dist/generated/global-types.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +1730 -8
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/stories/fixtures/technospamFixtures.d.ts.map +1 -1
- package/dist/technospam/TechnoSpamRoot.d.ts.map +1 -1
- package/dist/technospam/TechnoSpamRoot.js +187 -178
- package/dist/technospam/TechnoSpamRoot.js.map +1 -1
- package/dist/technospam/TechnoSpamRoutes.d.ts.map +1 -1
- package/dist/technospam/TechnoSpamRoutes.js +32 -28
- package/dist/technospam/TechnoSpamRoutes.js.map +1 -1
- package/dist/technospam/api/assessmentApi.d.ts +183 -0
- package/dist/technospam/api/assessmentApi.d.ts.map +1 -0
- package/dist/technospam/api/assessmentApi.js +278 -0
- package/dist/technospam/api/assessmentApi.js.map +1 -0
- package/dist/technospam/api/technospamApi.js +141 -141
- package/dist/technospam/api/technospamApi.js.map +1 -1
- package/dist/technospam/components/CourseAssessments.d.ts +9 -0
- package/dist/technospam/components/CourseAssessments.d.ts.map +1 -0
- package/dist/technospam/components/CourseAssessments.js +66 -0
- package/dist/technospam/components/CourseAssessments.js.map +1 -0
- package/dist/technospam/components/MediaCover.js +2 -2
- package/dist/technospam/components/MediaCover.js.map +1 -1
- package/dist/technospam/components/MobileSectionTabs.d.ts +9 -0
- package/dist/technospam/components/MobileSectionTabs.d.ts.map +1 -0
- package/dist/technospam/components/MobileSectionTabs.js +43 -0
- package/dist/technospam/components/MobileSectionTabs.js.map +1 -0
- package/dist/technospam/components/PageLayout.d.ts +7 -1
- package/dist/technospam/components/PageLayout.d.ts.map +1 -1
- package/dist/technospam/components/PageLayout.js +40 -28
- package/dist/technospam/components/PageLayout.js.map +1 -1
- package/dist/technospam/components/StatusBadge.d.ts.map +1 -1
- package/dist/technospam/components/StatusBadge.js +8 -1
- package/dist/technospam/components/StatusBadge.js.map +1 -1
- package/dist/technospam/components/admin/AssessmentsAdminTab.d.ts +7 -0
- package/dist/technospam/components/admin/AssessmentsAdminTab.d.ts.map +1 -0
- package/dist/technospam/components/admin/AssessmentsAdminTab.js +421 -0
- package/dist/technospam/components/admin/AssessmentsAdminTab.js.map +1 -0
- package/dist/technospam/components/lesson/LessonKindFields.d.ts +3 -1
- package/dist/technospam/components/lesson/LessonKindFields.d.ts.map +1 -1
- package/dist/technospam/components/lesson/LessonKindFields.js +2 -2
- package/dist/technospam/components/lesson/LessonKindFields.js.map +1 -1
- package/dist/technospam/components/lesson/SaveCourseOffline.js +6 -6
- package/dist/technospam/hooks/useAssessmentIntegrity.d.ts +29 -0
- package/dist/technospam/hooks/useAssessmentIntegrity.d.ts.map +1 -0
- package/dist/technospam/hooks/useAssessmentIntegrity.js +57 -0
- package/dist/technospam/hooks/useAssessmentIntegrity.js.map +1 -0
- package/dist/technospam/hooks/usePhoneLayout.d.ts +18 -0
- package/dist/technospam/hooks/usePhoneLayout.d.ts.map +1 -0
- package/dist/technospam/hooks/usePhoneLayout.js +14 -0
- package/dist/technospam/hooks/usePhoneLayout.js.map +1 -0
- package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js +365 -360
- 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 +89 -87
- package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesAssessmentPagePage.d.ts +9 -0
- package/dist/technospam/pages/CoursesAssessmentPagePage.d.ts.map +1 -0
- package/dist/technospam/pages/CoursesAssessmentPagePage.js +464 -0
- package/dist/technospam/pages/CoursesAssessmentPagePage.js.map +1 -0
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +213 -211
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesCoursesPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesCoursesPagePage.js +117 -81
- package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesMyLearningPagePage.js +1 -1
- package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
- package/dist/technospam/pages/DashboardDashboardPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/DashboardDashboardPagePage.js +21 -17
- package/dist/technospam/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +3 -3
- package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +4 -4
- package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipMentorsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/MentorshipMentorsPagePage.js +6 -4
- package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipSessionsPagePage.js +1 -1
- package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
- package/dist/technospam/types/index.d.ts +7 -0
- package/dist/technospam/types/index.d.ts.map +1 -1
- package/dist/technospam/utils/assessment.d.ts +64 -0
- package/dist/technospam/utils/assessment.d.ts.map +1 -0
- package/dist/technospam/utils/assessment.js +122 -0
- package/dist/technospam/utils/assessment.js.map +1 -0
- package/dist/technospam/utils/mobileTabs.d.ts +45 -0
- package/dist/technospam/utils/mobileTabs.d.ts.map +1 -0
- package/dist/technospam/utils/mobileTabs.js +70 -0
- package/dist/technospam/utils/mobileTabs.js.map +1 -0
- package/dist/test/matchMedia.d.ts +6 -0
- package/dist/test/matchMedia.d.ts.map +1 -0
- package/package.json +3 -3
|
@@ -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":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardDashboardPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/DashboardDashboardPagePage.tsx"],"names":[],"mappings":"AAqNA,wBAAgB,0BAA0B,
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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: "
|
|
288
|
+
className: "flex flex-col gap-6 lg:grid lg:grid-cols-3",
|
|
286
289
|
children: [/* @__PURE__ */ P("div", {
|
|
287
|
-
className: "
|
|
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
|
-
|
|
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", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardDashboardPagePage.js","names":[],"sources":["../../../src/technospam/pages/DashboardDashboardPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n ArrowRight,\n Award,\n BookOpen,\n Calendar,\n CalendarClock,\n CheckCircle2,\n Clock,\n Code2,\n GraduationCap,\n PlayCircle,\n Trophy,\n Users,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadingRows,\n MediaCover,\n PageLayout,\n StatCard,\n StatusBadge,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useCoursesQuery,\n useHackathonsQuery,\n useMyCertificatesQuery,\n useMyEnrollmentsQuery,\n useSessionsQuery,\n useSubmissionsQuery,\n type TecCourse,\n type TecEnrollment,\n type TecHackathon,\n type TecMentorSession,\n} from '../api/technospamApi';\nimport { formatDate, formatDateTime, formatMoney } from '../utils/format';\nimport { useTr } from '../hooks/useTr';\nimport { resumePath } from '../utils/lessonPath';\n\n/* --------------------------------------------------------------- helpers */\n\nfunction SectionHeader({\n title,\n actionLabel,\n onAction,\n}: {\n title: string;\n actionLabel?: string;\n onAction?: () => void;\n}) {\n return (\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n {actionLabel && onAction ? (\n <Button variant=\"ghost\" size=\"sm\" className=\"text-xs\" onClick={onAction}>\n {actionLabel} <ArrowRight size={13} />\n </Button>\n ) : null}\n </div>\n );\n}\n\nfunction ProgressBar({ pct }: { pct: number }) {\n return (\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-accent-primary transition-all\"\n style={{ width: `${Math.max(0, Math.min(100, pct))}%` }}\n />\n </div>\n );\n}\n\nfunction ContinueLearningCard({\n e,\n resumeLessonId,\n onOpen,\n}: {\n e: TecEnrollment;\n /** The lesson this card resumes into, once known (BOFF-4107). */\n resumeLessonId?: string | null;\n onOpen: () => void;\n}) {\n const tr = useTr();\n const title = e.course?.title ?? tr('technospam.common.courseFallback', 'Course');\n const seed = `${e.course?.topic ?? ''} ${title}`;\n return (\n <button\n type=\"button\"\n onClick={onOpen}\n data-resume-lesson={resumeLessonId ?? undefined}\n className=\"group flex flex-col overflow-hidden rounded-xl border border-border-seam bg-bg-surface text-left transition-all duration-200 hover:-translate-y-1 hover:shadow-lg focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <MediaCover seed={seed} icon={topicIcon(e.course?.topic ?? title)} height=\"h-20\">\n {e.course?.level ? <StatusBadge status={e.course.level} kind=\"level\" /> : <span />}\n <span className=\"flex h-7 w-7 items-center justify-center rounded-full bg-white/15 text-white backdrop-blur transition-transform duration-200 group-hover:scale-110\">\n <PlayCircle size={16} />\n </span>\n </MediaCover>\n <div className=\"flex flex-1 flex-col gap-2.5 p-3.5\">\n <div className=\"flex items-start gap-2\">\n <h3 className=\"line-clamp-2 flex-1 text-sm font-semibold text-text-primary group-hover:text-accent-primary\">\n {title}\n </h3>\n <StatusBadge status={e.status} />\n </div>\n <div className=\"mt-auto flex items-center gap-2.5\">\n <div className=\"flex-1\">\n <ProgressBar pct={e.progressPct} />\n </div>\n <span className=\"shrink-0 text-xs font-semibold text-text-secondary\">\n {e.progressPct}%\n </span>\n </div>\n </div>\n </button>\n );\n}\n\nfunction RecommendedCourseCard({ course, onOpen }: { course: TecCourse; onOpen: () => void }) {\n return (\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"group flex w-full flex-col overflow-hidden rounded-xl border border-border-seam bg-bg-surface text-left transition-all duration-200 hover:-translate-y-1 hover:shadow-lg focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <MediaCover\n seed={`${course.topic ?? ''} ${course.title}`}\n icon={topicIcon(course.topic ?? course.title)}\n height=\"h-20\"\n >\n <StatusBadge status={course.level} kind=\"level\" />\n </MediaCover>\n <div className=\"flex flex-1 flex-col gap-1.5 p-3.5\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary group-hover:text-accent-primary\">\n {course.title}\n </h3>\n {course.summary ? (\n <p className=\"line-clamp-2 text-xs text-text-secondary\">{course.summary}</p>\n ) : null}\n {course.topic ? (\n <span className=\"mt-auto truncate pt-0.5 text-xs text-text-muted\">{course.topic}</span>\n ) : null}\n </div>\n </button>\n );\n}\n\nfunction SessionRow({ s }: { s: TecMentorSession }) {\n return (\n <div className=\"-mx-2 flex items-start gap-3 rounded-lg border-b border-border-seam px-2 py-3 transition-colors last:border-0 hover:bg-bg-sunken\">\n <span className=\"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-accent-teal/15 text-accent-teal ring-1 ring-accent-teal/20\">\n <CalendarClock size={16} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"truncate text-sm font-medium text-text-primary\">{s.topic}</span>\n <StatusBadge status={s.status} />\n </div>\n {s.mentor?.headline ? (\n <div className=\"truncate text-xs text-text-muted\">{s.mentor.headline}</div>\n ) : null}\n <div className=\"mt-0.5 flex items-center gap-1.5 text-xs text-text-secondary\">\n <Clock size={12} className=\"text-text-muted\" />\n {formatDateTime(s.scheduledAt)}\n </div>\n </div>\n </div>\n );\n}\n\nfunction HackathonBanner({ h, onOpen }: { h: TecHackathon; onOpen: () => void }) {\n const tr = useTr();\n return (\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"group block w-full overflow-hidden rounded-xl border border-border-seam text-left transition-all duration-200 hover:-translate-y-1 hover:shadow-lg focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <MediaCover seed={`${h.theme ?? ''} ${h.title}`} icon={Trophy} height=\"h-24\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-white/80\">\n <Trophy size={12} /> {tr('technospam.common.openHackathon', 'Open hackathon')}\n </div>\n <h3 className=\"mt-0.5 truncate text-sm font-semibold text-white drop-shadow-sm\">\n {h.title}\n </h3>\n </div>\n {h.prizePool > 0 ? (\n <span className=\"shrink-0 rounded-full bg-white/20 px-2.5 py-1 text-xs font-semibold text-white backdrop-blur\">\n {formatMoney(h.prizePool, h.currency)}\n </span>\n ) : null}\n </MediaCover>\n <div className=\"flex items-center justify-between gap-2 bg-bg-surface px-3.5 py-2.5\">\n <span className=\"flex items-center gap-1.5 text-xs text-text-muted\">\n <Calendar size={12} />\n {formatDate(h.startAt)} – {formatDate(h.endAt)}\n </span>\n <span className=\"flex items-center gap-1 text-xs font-medium text-accent-primary opacity-0 transition-opacity group-hover:opacity-100\">\n {tr('technospam.common.register', 'Register')} <ArrowRight size={12} />\n </span>\n </div>\n </button>\n );\n}\n\n/* ------------------------------------------------------------------ page */\n\nexport function DashboardDashboardPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const tr = useTr();\n const learnerId = currentUser?.id;\n\n // BOFF-10481: the learner's own enrollments (each with its next lesson, for\n // the BOFF-4107 Continue Learning deep link) and certificates come from the\n // canonical, self-scoped Buildmyiq fields, not the cross-learner list-all\n // reads a plain learner is not granted.\n const enrollmentsQ = useMyEnrollmentsQuery();\n const submissionsQ = useSubmissionsQuery(learnerId ? { learnerId } : {});\n const sessionsQ = useSessionsQuery(learnerId ? { menteeId: learnerId } : {});\n const certificatesQ = useMyCertificatesQuery();\n const coursesQ = useCoursesQuery({ status: 'PUBLISHED' });\n const hackathonsQ = useHackathonsQuery({ status: 'REGISTRATION_OPEN' });\n\n const enrollments = enrollmentsQ.enrollments;\n const submissions = submissionsQ.data?.tecListSubmissions?.items ?? [];\n const sessions = sessionsQ.data?.tecListSessions?.items ?? [];\n const certificates = certificatesQ.certificates;\n const courses = coursesQ.data?.tecListCourses?.items ?? [];\n const hackathons = hackathonsQ.data?.tecListHackathons?.items ?? [];\n\n const greetingName =\n currentUser?.firstName || currentUser?.name || currentUser?.email?.split('@')[0] || 'there';\n\n const inProgress = useMemo(\n () => enrollments.filter((e) => e.status === 'IN_PROGRESS' || e.status === 'ENROLLED'),\n [enrollments]\n );\n\n const continueLearning = useMemo(\n () =>\n [...inProgress]\n .sort(\n (a, b) =>\n new Date(b.lastActiveAt ?? b.startedAt).getTime() -\n new Date(a.lastActiveAt ?? a.startedAt).getTime()\n )\n .slice(0, 3),\n [inProgress]\n );\n\n const problemsSolved = useMemo(\n () => new Set(submissions.filter((s) => s.verdict === 'ACCEPTED').map((s) => s.problemId)).size,\n [submissions]\n );\n\n // Stable \"now\" captured once on mount (state initializers may call impure fns).\n const [now] = useState(() => Date.now());\n const upcomingSessions = useMemo(() => {\n return [...sessions]\n .filter(\n (s) =>\n (s.status === 'CONFIRMED' || s.status === 'REQUESTED') &&\n new Date(s.scheduledAt).getTime() > now\n )\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime());\n }, [sessions, now]);\n\n const recommended = useMemo(() => {\n const enrolledIds = new Set(enrollments.map((e) => e.courseId));\n return courses.filter((c) => !enrolledIds.has(c.id)).slice(0, 4);\n }, [courses, enrollments]);\n\n const openHackathons = hackathons.slice(0, 4);\n\n return (\n <PageLayout\n hero\n title={tr('technospam.dashboard.title', 'Welcome back, {{name}}', { name: greetingName })}\n description={tr(\n 'technospam.dashboard.description',\n \"Your learning hub — pick up where you left off, keep your skills sharp, and jump into what's next.\"\n )}\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> {tr('technospam.dashboard.myLearning', 'My Learning')}\n </Button>\n }\n >\n {/* Top metrics */}\n <div className=\"mb-5 grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <StatCard\n label={tr('technospam.dashboard.coursesInProgress', 'Courses in progress')}\n value={inProgress.length}\n icon={BookOpen}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.dashboard.problemsSolved', 'Problems solved')}\n value={problemsSolved}\n icon={Code2}\n tone=\"green\"\n />\n <StatCard\n label={tr('technospam.dashboard.upcomingSessions', 'Upcoming sessions')}\n value={upcomingSessions.length}\n icon={CalendarClock}\n tone=\"amber\"\n />\n <StatCard\n label={tr('technospam.dashboard.certificates', 'Certificates')}\n value={certificates.length}\n icon={Award}\n tone=\"purple\"\n />\n </div>\n\n {/* Quick actions */}\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/courses/courses-page')}\n >\n <BookOpen size={18} />\n <span className=\"text-xs\">{tr('technospam.common.browseCourses', 'Browse courses')}</span>\n </Button>\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/applications/problems-page')}\n >\n <Code2 size={18} />\n <span className=\"text-xs\">\n {tr('technospam.common.practiceProblems', 'Practice problems')}\n </span>\n </Button>\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/mentorship/mentors-page')}\n >\n <Users size={18} />\n <span className=\"text-xs\">{tr('technospam.common.findMentor', 'Find a mentor')}</span>\n </Button>\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/hackathons/hackathons-page')}\n >\n <Trophy size={18} />\n <span className=\"text-xs\">{tr('technospam.common.hackathons', 'Hackathons')}</span>\n </Button>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Main column */}\n <div className=\"space-y-6 lg:col-span-2\">\n {/* Continue learning */}\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.continueLearning', 'Continue learning')}\n actionLabel={tr('technospam.dashboard.myLearning', 'My Learning')}\n onAction={() => navigate('/courses/my-learning-page')}\n />\n {enrollmentsQ.loading && !enrollments.length ? (\n <LoadingRows count={2} height=\"h-16\" />\n ) : enrollmentsQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadCourses', \"Couldn't load your courses\")}\n error={enrollmentsQ.error}\n />\n ) : continueLearning.length === 0 ? (\n <EmptyView\n icon={<BookOpen size={24} />}\n title={tr(\n 'technospam.dashboard.noCoursesInProgressTitle',\n 'No courses in progress'\n )}\n description={tr(\n 'technospam.dashboard.noCoursesInProgressDesc',\n 'Enroll in a course to start building skill by doing — your active courses show up here.'\n )}\n action={\n <Button onClick={() => navigate('/courses/courses-page')}>\n {tr('technospam.common.browseCourses', 'Browse courses')}\n </Button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {continueLearning.map((e) => (\n <ContinueLearningCard\n key={e.id}\n e={e}\n resumeLessonId={e.nextLessonId}\n onOpen={() => navigate(resumePath(e.courseId, e.nextLessonId))}\n />\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Recommended courses */}\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.recommendedForYou', 'Recommended for you')}\n actionLabel={tr('technospam.common.viewAll', 'View all')}\n onAction={() => navigate('/courses/courses-page')}\n />\n {coursesQ.loading && !courses.length ? (\n <LoadingRows count={2} height=\"h-20\" />\n ) : coursesQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadCourses', \"Couldn't load courses\")}\n error={coursesQ.error}\n />\n ) : recommended.length === 0 ? (\n <EmptyView\n icon={<GraduationCap size={24} />}\n title={tr(\n 'technospam.dashboard.noRecommendationsTitle',\n 'No courses to recommend yet'\n )}\n description={tr(\n 'technospam.dashboard.noRecommendationsDesc',\n 'New courses are on the way. TechnoSpam is pre-launch — check back soon.'\n )}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {recommended.map((c) => (\n <RecommendedCourseCard\n key={c.id}\n course={c}\n onOpen={() => navigate(`/courses/course-details-page?id=${c.id}`)}\n />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n\n {/* Side column */}\n <div className=\"space-y-6\">\n {/* Upcoming sessions */}\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.upcomingSessionsTitle', 'Upcoming sessions')}\n actionLabel={tr('technospam.common.allSessions', 'All sessions')}\n onAction={() => navigate('/mentorship/sessions-page')}\n />\n {sessionsQ.loading && !sessions.length ? (\n <LoadingRows count={2} height=\"h-14\" />\n ) : sessionsQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadSessions', \"Couldn't load sessions\")}\n error={sessionsQ.error}\n />\n ) : upcomingSessions.length === 0 ? (\n <EmptyView\n icon={<CalendarClock size={24} />}\n title={tr('technospam.dashboard.noSessionsBookedTitle', 'No sessions booked')}\n description={tr(\n 'technospam.dashboard.noSessionsBookedDesc',\n 'Book time with a mentor to get unblocked — your upcoming sessions appear here.'\n )}\n action={\n <Button variant=\"outline\" onClick={() => navigate('/mentorship/mentors-page')}>\n {tr('technospam.common.findMentor', 'Find a mentor')}\n </Button>\n }\n />\n ) : (\n <div>\n {upcomingSessions.slice(0, 5).map((s) => (\n <SessionRow key={s.id} s={s} />\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Open hackathons */}\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.openHackathons', 'Open hackathons')}\n actionLabel={tr('technospam.common.viewAll', 'View all')}\n onAction={() => navigate('/hackathons/hackathons-page')}\n />\n {hackathonsQ.loading && !hackathons.length ? (\n <LoadingRows count={2} height=\"h-14\" />\n ) : hackathonsQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadHackathons', \"Couldn't load hackathons\")}\n error={hackathonsQ.error}\n />\n ) : openHackathons.length === 0 ? (\n <EmptyView\n icon={<Trophy size={24} />}\n title={tr('technospam.dashboard.noOpenHackathonsTitle', 'No open hackathons')}\n description={tr(\n 'technospam.dashboard.noOpenHackathonsDesc',\n 'When registration opens for a hackathon, it will show up here to join.'\n )}\n />\n ) : (\n <div className=\"space-y-3\">\n {openHackathons.map((h) => (\n <HackathonBanner\n key={h.id}\n h={h}\n onOpen={() => navigate(`/hackathons/hackathon-details-page?id=${h.id}`)}\n />\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Certificates summary */}\n {certificates.length > 0 ? (\n <GlassCard className=\"p-5\">\n <SectionHeader\n title={tr('technospam.common.latestCertificate', 'Latest certificate')}\n />\n <div className=\"flex items-center gap-3\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-accent-primary/12 text-accent-teal\">\n <CheckCircle2 size={20} />\n </span>\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-medium text-text-primary\">\n {certificates[0]?.course?.title ??\n tr('technospam.common.courseFallback', 'Course')}\n </div>\n <div className=\"text-xs text-text-muted\">\n {tr('technospam.common.score', 'Score')} {certificates[0]?.score} ·{' '}\n {tr('technospam.common.issued', 'Issued')}{' '}\n {formatDate(certificates[0]?.issuedAt)}\n </div>\n </div>\n </div>\n </GlassCard>\n ) : null}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DashboardDashboardPagePage.js","names":[],"sources":["../../../src/technospam/pages/DashboardDashboardPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n ArrowRight,\n Award,\n BookOpen,\n Calendar,\n CalendarClock,\n CheckCircle2,\n Clock,\n Code2,\n GraduationCap,\n PlayCircle,\n Trophy,\n Users,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadingRows,\n MediaCover,\n PageLayout,\n StatCard,\n StatusBadge,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useCoursesQuery,\n useHackathonsQuery,\n useMyCertificatesQuery,\n useMyEnrollmentsQuery,\n useSessionsQuery,\n useSubmissionsQuery,\n type TecCourse,\n type TecEnrollment,\n type TecHackathon,\n type TecMentorSession,\n} from '../api/technospamApi';\nimport { formatDate, formatDateTime, formatMoney } from '../utils/format';\nimport { useTr } from '../hooks/useTr';\nimport { resumePath } from '../utils/lessonPath';\n\n/* --------------------------------------------------------------- helpers */\n\nfunction SectionHeader({\n title,\n actionLabel,\n onAction,\n}: {\n title: string;\n actionLabel?: string;\n onAction?: () => void;\n}) {\n return (\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n {actionLabel && onAction ? (\n <Button variant=\"ghost\" size=\"sm\" className=\"text-xs\" onClick={onAction}>\n {actionLabel} <ArrowRight size={13} />\n </Button>\n ) : null}\n </div>\n );\n}\n\nfunction ProgressBar({ pct }: { pct: number }) {\n return (\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-accent-primary transition-all\"\n style={{ width: `${Math.max(0, Math.min(100, pct))}%` }}\n />\n </div>\n );\n}\n\nfunction ContinueLearningCard({\n e,\n resumeLessonId,\n onOpen,\n}: {\n e: TecEnrollment;\n /** The lesson this card resumes into, once known (BOFF-4107). */\n resumeLessonId?: string | null;\n onOpen: () => void;\n}) {\n const tr = useTr();\n const title = e.course?.title ?? tr('technospam.common.courseFallback', 'Course');\n const seed = `${e.course?.topic ?? ''} ${title}`;\n return (\n <button\n type=\"button\"\n onClick={onOpen}\n data-resume-lesson={resumeLessonId ?? undefined}\n className=\"group flex 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)]\"\n >\n <MediaCover seed={seed} icon={topicIcon(e.course?.topic ?? title)} height=\"h-20\">\n {e.course?.level ? <StatusBadge status={e.course.level} kind=\"level\" /> : <span />}\n <span className=\"flex h-7 w-7 items-center justify-center rounded-full bg-action-primary-text/15 text-action-primary-text backdrop-blur transition-transform duration-200 group-hover:scale-110\">\n <PlayCircle size={16} />\n </span>\n </MediaCover>\n <div className=\"flex flex-1 flex-col gap-2.5 p-3.5\">\n <div className=\"flex items-start gap-2\">\n <h3 className=\"line-clamp-2 flex-1 text-sm font-semibold text-text-primary group-hover:text-accent-primary\">\n {title}\n </h3>\n <StatusBadge status={e.status} />\n </div>\n <div className=\"mt-auto flex items-center gap-2.5\">\n <div className=\"flex-1\">\n <ProgressBar pct={e.progressPct} />\n </div>\n <span className=\"shrink-0 text-xs font-semibold text-text-secondary\">\n {e.progressPct}%\n </span>\n </div>\n </div>\n </button>\n );\n}\n\nfunction RecommendedCourseCard({ course, onOpen }: { course: TecCourse; onOpen: () => void }) {\n return (\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"group flex w-full flex-col overflow-hidden rounded-xl border border-border-seam bg-bg-surface text-left transition-all duration-200 hover:-translate-y-1 hover:shadow-lg focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <MediaCover\n seed={`${course.topic ?? ''} ${course.title}`}\n icon={topicIcon(course.topic ?? course.title)}\n height=\"h-20\"\n >\n <StatusBadge status={course.level} kind=\"level\" />\n </MediaCover>\n <div className=\"flex flex-1 flex-col gap-1.5 p-3.5\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary group-hover:text-accent-primary\">\n {course.title}\n </h3>\n {course.summary ? (\n <p className=\"line-clamp-2 text-xs text-text-secondary\">{course.summary}</p>\n ) : null}\n {course.topic ? (\n <span className=\"mt-auto truncate pt-0.5 text-xs text-text-muted\">{course.topic}</span>\n ) : null}\n </div>\n </button>\n );\n}\n\nfunction SessionRow({ s }: { s: TecMentorSession }) {\n return (\n <div className=\"-mx-2 flex items-start gap-3 rounded-lg border-b border-border-seam px-2 py-3 transition-colors last:border-0 hover:bg-bg-sunken\">\n <span className=\"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-accent-teal/15 text-accent-teal ring-1 ring-accent-teal/20\">\n <CalendarClock size={16} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"truncate text-sm font-medium text-text-primary\">{s.topic}</span>\n <StatusBadge status={s.status} />\n </div>\n {s.mentor?.headline ? (\n <div className=\"truncate text-xs text-text-muted\">{s.mentor.headline}</div>\n ) : null}\n <div className=\"mt-0.5 flex items-center gap-1.5 text-xs text-text-secondary\">\n <Clock size={12} className=\"text-text-muted\" />\n {formatDateTime(s.scheduledAt)}\n </div>\n </div>\n </div>\n );\n}\n\nfunction HackathonBanner({ h, onOpen }: { h: TecHackathon; onOpen: () => void }) {\n const tr = useTr();\n return (\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"group block w-full overflow-hidden rounded-xl border border-border-seam text-left transition-all duration-200 hover:-translate-y-1 hover:shadow-lg focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <MediaCover seed={`${h.theme ?? ''} ${h.title}`} icon={Trophy} height=\"h-24\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-action-primary-text/80\">\n <Trophy size={12} /> {tr('technospam.common.openHackathon', 'Open hackathon')}\n </div>\n <h3 className=\"mt-0.5 truncate text-sm font-semibold text-action-primary-text drop-shadow-sm\">\n {h.title}\n </h3>\n </div>\n {h.prizePool > 0 ? (\n <span 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\">\n {formatMoney(h.prizePool, h.currency)}\n </span>\n ) : null}\n </MediaCover>\n <div className=\"flex items-center justify-between gap-2 bg-bg-surface px-3.5 py-2.5\">\n <span className=\"flex items-center gap-1.5 text-xs text-text-muted\">\n <Calendar size={12} />\n {formatDate(h.startAt)} – {formatDate(h.endAt)}\n </span>\n <span className=\"flex items-center gap-1 text-xs font-medium text-accent-primary opacity-0 transition-opacity group-hover:opacity-100\">\n {tr('technospam.common.register', 'Register')} <ArrowRight size={12} />\n </span>\n </div>\n </button>\n );\n}\n\n/* ------------------------------------------------------------------ page */\n\nexport function DashboardDashboardPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const tr = useTr();\n const learnerId = currentUser?.id;\n\n // BOFF-10481: the learner's own enrollments (each with its next lesson, for\n // the BOFF-4107 Continue Learning deep link) and certificates come from the\n // canonical, self-scoped Buildmyiq fields, not the cross-learner list-all\n // reads a plain learner is not granted.\n const enrollmentsQ = useMyEnrollmentsQuery();\n const submissionsQ = useSubmissionsQuery(learnerId ? { learnerId } : {});\n const sessionsQ = useSessionsQuery(learnerId ? { menteeId: learnerId } : {});\n const certificatesQ = useMyCertificatesQuery();\n const coursesQ = useCoursesQuery({ status: 'PUBLISHED' });\n const hackathonsQ = useHackathonsQuery({ status: 'REGISTRATION_OPEN' });\n\n const enrollments = enrollmentsQ.enrollments;\n const submissions = submissionsQ.data?.tecListSubmissions?.items ?? [];\n const sessions = sessionsQ.data?.tecListSessions?.items ?? [];\n const certificates = certificatesQ.certificates;\n const courses = coursesQ.data?.tecListCourses?.items ?? [];\n const hackathons = hackathonsQ.data?.tecListHackathons?.items ?? [];\n\n const greetingName =\n currentUser?.firstName || currentUser?.name || currentUser?.email?.split('@')[0] || 'there';\n\n const inProgress = useMemo(\n () => enrollments.filter((e) => e.status === 'IN_PROGRESS' || e.status === 'ENROLLED'),\n [enrollments]\n );\n\n const continueLearning = useMemo(\n () =>\n [...inProgress]\n .sort(\n (a, b) =>\n new Date(b.lastActiveAt ?? b.startedAt).getTime() -\n new Date(a.lastActiveAt ?? a.startedAt).getTime()\n )\n .slice(0, 3),\n [inProgress]\n );\n\n const problemsSolved = useMemo(\n () => new Set(submissions.filter((s) => s.verdict === 'ACCEPTED').map((s) => s.problemId)).size,\n [submissions]\n );\n\n // Stable \"now\" captured once on mount (state initializers may call impure fns).\n const [now] = useState(() => Date.now());\n const upcomingSessions = useMemo(() => {\n return [...sessions]\n .filter(\n (s) =>\n (s.status === 'CONFIRMED' || s.status === 'REQUESTED') &&\n new Date(s.scheduledAt).getTime() > now\n )\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime());\n }, [sessions, now]);\n\n const recommended = useMemo(() => {\n const enrolledIds = new Set(enrollments.map((e) => e.courseId));\n return courses.filter((c) => !enrolledIds.has(c.id)).slice(0, 4);\n }, [courses, enrollments]);\n\n const openHackathons = hackathons.slice(0, 4);\n\n return (\n <PageLayout\n hero\n mobileSection\n title={tr('technospam.dashboard.title', 'Welcome back, {{name}}', { name: greetingName })}\n description={tr(\n 'technospam.dashboard.description',\n \"Your learning hub — pick up where you left off, keep your skills sharp, and jump into what's next.\"\n )}\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> {tr('technospam.dashboard.myLearning', 'My Learning')}\n </Button>\n }\n >\n {/* Top metrics */}\n <div className=\"mb-5 grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <StatCard\n label={tr('technospam.dashboard.coursesInProgress', 'Courses in progress')}\n value={inProgress.length}\n icon={BookOpen}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.dashboard.problemsSolved', 'Problems solved')}\n value={problemsSolved}\n icon={Code2}\n tone=\"green\"\n />\n <StatCard\n label={tr('technospam.dashboard.upcomingSessions', 'Upcoming sessions')}\n value={upcomingSessions.length}\n icon={CalendarClock}\n tone=\"amber\"\n className=\"hidden sm:flex\"\n />\n <StatCard\n label={tr('technospam.dashboard.certificates', 'Certificates')}\n value={certificates.length}\n icon={Award}\n tone=\"purple\"\n className=\"hidden sm:flex\"\n />\n </div>\n\n {/* Quick actions — phones get the section tabs (bottom bar in the app,\n in-page switcher in the browser) and the hackathon card instead. */}\n <div className=\"mb-6 hidden grid-cols-2 gap-3 sm:grid sm:grid-cols-4\">\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/courses/courses-page')}\n >\n <BookOpen size={18} />\n <span className=\"text-xs\">{tr('technospam.common.browseCourses', 'Browse courses')}</span>\n </Button>\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/applications/problems-page')}\n >\n <Code2 size={18} />\n <span className=\"text-xs\">\n {tr('technospam.common.practiceProblems', 'Practice problems')}\n </span>\n </Button>\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/mentorship/mentors-page')}\n >\n <Users size={18} />\n <span className=\"text-xs\">{tr('technospam.common.findMentor', 'Find a mentor')}</span>\n </Button>\n <Button\n variant=\"outline\"\n className=\"h-auto flex-col gap-2 py-4\"\n onClick={() => navigate('/hackathons/hackathons-page')}\n >\n <Trophy size={18} />\n <span className=\"text-xs\">{tr('technospam.common.hackathons', 'Hackathons')}</span>\n </Button>\n </div>\n\n {/* Phones stack the cards in the mock's order (continue learning, the\n upcoming session and open hackathons, then recommendations): the two\n columns become `display: contents` below lg so `order` can interleave\n their cards; from lg up they are ordinary block columns again. */}\n <div className=\"flex flex-col gap-6 lg:grid lg:grid-cols-3\">\n {/* Main column */}\n <div className=\"contents lg:col-span-2 lg:block lg:space-y-6\">\n {/* Continue learning */}\n <GlassCard className=\"order-1 p-4 sm:p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.continueLearning', 'Continue learning')}\n actionLabel={tr('technospam.dashboard.myLearning', 'My Learning')}\n onAction={() => navigate('/courses/my-learning-page')}\n />\n {enrollmentsQ.loading && !enrollments.length ? (\n <LoadingRows count={2} height=\"h-16\" />\n ) : enrollmentsQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadCourses', \"Couldn't load your courses\")}\n error={enrollmentsQ.error}\n />\n ) : continueLearning.length === 0 ? (\n <EmptyView\n icon={<BookOpen size={24} />}\n title={tr(\n 'technospam.dashboard.noCoursesInProgressTitle',\n 'No courses in progress'\n )}\n description={tr(\n 'technospam.dashboard.noCoursesInProgressDesc',\n 'Enroll in a course to start building skill by doing — your active courses show up here.'\n )}\n action={\n <Button onClick={() => navigate('/courses/courses-page')}>\n {tr('technospam.common.browseCourses', 'Browse courses')}\n </Button>\n }\n />\n ) : (\n <div\n data-testid=\"continue-learning-list\"\n 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\"\n >\n {continueLearning.map((e) => (\n <ContinueLearningCard\n key={e.id}\n e={e}\n resumeLessonId={e.nextLessonId}\n onOpen={() => navigate(resumePath(e.courseId, e.nextLessonId))}\n />\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Recommended courses */}\n <GlassCard className=\"order-3 p-4 sm:p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.recommendedForYou', 'Recommended for you')}\n actionLabel={tr('technospam.common.viewAll', 'View all')}\n onAction={() => navigate('/courses/courses-page')}\n />\n {coursesQ.loading && !courses.length ? (\n <LoadingRows count={2} height=\"h-20\" />\n ) : coursesQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadCourses', \"Couldn't load courses\")}\n error={coursesQ.error}\n />\n ) : recommended.length === 0 ? (\n <EmptyView\n icon={<GraduationCap size={24} />}\n title={tr(\n 'technospam.dashboard.noRecommendationsTitle',\n 'No courses to recommend yet'\n )}\n description={tr(\n 'technospam.dashboard.noRecommendationsDesc',\n 'New courses are on the way. TechnoSpam is pre-launch — check back soon.'\n )}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {recommended.map((c) => (\n <RecommendedCourseCard\n key={c.id}\n course={c}\n onOpen={() => navigate(`/courses/course-details-page?id=${c.id}`)}\n />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n\n {/* Side column */}\n <div className=\"contents lg:block lg:space-y-6\">\n {/* Upcoming sessions */}\n <GlassCard className=\"order-2 p-4 sm:p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.upcomingSessionsTitle', 'Upcoming sessions')}\n actionLabel={tr('technospam.common.allSessions', 'All sessions')}\n onAction={() => navigate('/mentorship/sessions-page')}\n />\n {sessionsQ.loading && !sessions.length ? (\n <LoadingRows count={2} height=\"h-14\" />\n ) : sessionsQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadSessions', \"Couldn't load sessions\")}\n error={sessionsQ.error}\n />\n ) : upcomingSessions.length === 0 ? (\n <EmptyView\n icon={<CalendarClock size={24} />}\n title={tr('technospam.dashboard.noSessionsBookedTitle', 'No sessions booked')}\n description={tr(\n 'technospam.dashboard.noSessionsBookedDesc',\n 'Book time with a mentor to get unblocked — your upcoming sessions appear here.'\n )}\n action={\n <Button variant=\"outline\" onClick={() => navigate('/mentorship/mentors-page')}>\n {tr('technospam.common.findMentor', 'Find a mentor')}\n </Button>\n }\n />\n ) : (\n <div>\n {upcomingSessions.slice(0, 5).map((s) => (\n <SessionRow key={s.id} s={s} />\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Open hackathons */}\n <GlassCard className=\"order-2 p-4 sm:p-5\">\n <SectionHeader\n title={tr('technospam.dashboard.openHackathons', 'Open hackathons')}\n actionLabel={tr('technospam.common.viewAll', 'View all')}\n onAction={() => navigate('/hackathons/hackathons-page')}\n />\n {hackathonsQ.loading && !hackathons.length ? (\n <LoadingRows count={2} height=\"h-14\" />\n ) : hackathonsQ.error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadHackathons', \"Couldn't load hackathons\")}\n error={hackathonsQ.error}\n />\n ) : openHackathons.length === 0 ? (\n <EmptyView\n icon={<Trophy size={24} />}\n title={tr('technospam.dashboard.noOpenHackathonsTitle', 'No open hackathons')}\n description={tr(\n 'technospam.dashboard.noOpenHackathonsDesc',\n 'When registration opens for a hackathon, it will show up here to join.'\n )}\n />\n ) : (\n <div className=\"space-y-3\">\n {openHackathons.map((h) => (\n <HackathonBanner\n key={h.id}\n h={h}\n onOpen={() => navigate(`/hackathons/hackathon-details-page?id=${h.id}`)}\n />\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Certificates summary */}\n {certificates.length > 0 ? (\n <GlassCard className=\"order-4 p-4 sm:p-5\">\n <SectionHeader\n title={tr('technospam.common.latestCertificate', 'Latest certificate')}\n />\n <div className=\"flex items-center gap-3\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-accent-primary/12 text-accent-teal\">\n <CheckCircle2 size={20} />\n </span>\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-medium text-text-primary\">\n {certificates[0]?.course?.title ??\n tr('technospam.common.courseFallback', 'Course')}\n </div>\n <div className=\"text-xs text-text-muted\">\n {tr('technospam.common.score', 'Score')} {certificates[0]?.score} ·{' '}\n {tr('technospam.common.issued', 'Issued')}{' '}\n {formatDate(certificates[0]?.issuedAt)}\n </div>\n </div>\n </div>\n </GlassCard>\n ) : null}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -18,7 +18,7 @@ import { Badge as w, Button as T, GlassCard as E, Input as D, Select as ue, Sele
|
|
|
18
18
|
//#region src/technospam/pages/HackathonsHackathonDetailsPagePage.tsx
|
|
19
19
|
function k({ icon: e, label: t }) {
|
|
20
20
|
return /* @__PURE__ */ C("span", {
|
|
21
|
-
className: "inline-flex items-center gap-1.5 rounded-lg bg-
|
|
21
|
+
className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-text/15 px-3 py-1.5 text-sm font-medium text-action-primary-text ring-1 ring-action-primary-text/25 backdrop-blur-sm",
|
|
22
22
|
children: [
|
|
23
23
|
/* @__PURE__ */ S(e, { size: 15 }),
|
|
24
24
|
" ",
|
|
@@ -37,7 +37,7 @@ function ge({ team: e, isMine: n, focused: r, onToggle: a }) {
|
|
|
37
37
|
children: [/* @__PURE__ */ C("div", {
|
|
38
38
|
className: "flex min-w-0 items-start gap-3",
|
|
39
39
|
children: [/* @__PURE__ */ S("span", {
|
|
40
|
-
className: t("flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br text-
|
|
40
|
+
className: t("flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br text-action-primary-text shadow-sm ring-1 ring-action-primary-text/10 transition-transform duration-200 group-hover:scale-105", i(e.name)),
|
|
41
41
|
children: S(o ? y : m, { size: 19 })
|
|
42
42
|
}), /* @__PURE__ */ C("div", {
|
|
43
43
|
className: "min-w-0",
|
|
@@ -139,7 +139,7 @@ function A() {
|
|
|
139
139
|
className: "flex flex-wrap items-center gap-2",
|
|
140
140
|
children: [/* @__PURE__ */ S(s, { status: Y.status }), Y.tags.map((e) => /* @__PURE__ */ S(w, {
|
|
141
141
|
variant: "secondary",
|
|
142
|
-
className: "border-
|
|
142
|
+
className: "border-action-primary-text/20 bg-action-primary-text/15 text-[11px] text-action-primary-text",
|
|
143
143
|
children: e
|
|
144
144
|
}, e))]
|
|
145
145
|
}), /* @__PURE__ */ C("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HackathonsHackathonDetailsPagePage.js","names":[],"sources":["../../../src/technospam/pages/HackathonsHackathonDetailsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport {\n Award,\n CalendarClock,\n CalendarDays,\n Code2,\n ExternalLink,\n Flag,\n Github,\n Send,\n Trophy,\n UserPlus,\n Users,\n type LucideIcon,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n GlassCard,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Skeleton,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n MediaCover,\n PageLayout,\n StatusBadge,\n coverGradient,\n} from '../components';\nimport { cn } from '../utils/cn';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n useHackathonQuery,\n useRegisterTeam,\n useSubmitProject,\n useTeamsQuery,\n type TecHackathonTeam,\n} from '../api/technospamApi';\nimport { formatDate, formatDateTime, formatMoney } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport {\n FOCUSED_CARD_CLASS,\n focusableCardProps,\n useFocusedRecordParam,\n} from '../hooks/useFocusedRecordParam';\nimport { FOCUS_PARAMS } from '../utils/pageContext';\n\n/** Translucent glass chip used for stats overlaid on the vivid MediaCover hero. */\nfunction HeroChip({ icon: Icon, label }: { icon: LucideIcon; label: string }) {\n return (\n <span className=\"inline-flex items-center gap-1.5 rounded-lg bg-white/15 px-3 py-1.5 text-sm font-medium text-white ring-1 ring-white/25 backdrop-blur-sm\">\n <Icon size={15} /> {label}\n </span>\n );\n}\n\nfunction TeamCard({\n team,\n isMine,\n focused,\n onToggle,\n}: {\n team: TecHackathonTeam;\n isMine: boolean;\n /** The focused team (`?teamId=`) is the one the assistant acts on. */\n focused: boolean;\n onToggle: () => void;\n}) {\n const isWinner = team.status === 'WINNER';\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-1 hover:shadow-lg',\n focused && FOCUSED_CARD_CLASS\n )}\n {...focusableCardProps(focused, onToggle)}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <span\n className={cn(\n 'flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br text-white shadow-sm ring-1 ring-white/10 transition-transform duration-200 group-hover:scale-105',\n coverGradient(team.name)\n )}\n >\n {isWinner ? <Trophy size={19} /> : <Code2 size={19} />}\n </span>\n <div className=\"min-w-0\">\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 hover:text-accent-primary\"\n >\n {team.name}\n </button>\n {isMine ? (\n <Badge variant=\"secondary\" className=\"text-[11px]\">\n Your team\n </Badge>\n ) : null}\n </div>\n {team.projectName ? (\n <p className=\"mt-0.5 truncate text-sm text-text-secondary\">{team.projectName}</p>\n ) : (\n <p className=\"mt-0.5 text-sm text-text-muted\">No project submitted yet</p>\n )}\n </div>\n </div>\n <StatusBadge status={team.status} />\n </div>\n\n {team.pitch ? <p className=\"line-clamp-3 text-sm text-text-secondary\">{team.pitch}</p> : null}\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 <Users size={13} /> {team.memberIds.length} member{team.memberIds.length === 1 ? '' : 's'}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Flag size={13} /> Score {team.score}\n </span>\n </div>\n\n {team.repoUrl || team.projectUrl ? (\n <div className=\"flex flex-wrap items-center gap-3 border-t border-border-seam pt-3\">\n {team.repoUrl ? (\n <a\n href={team.repoUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-flex items-center gap-1 text-xs font-medium text-accent-primary hover:underline\"\n >\n <Github size={13} /> Repository\n </a>\n ) : null}\n {team.projectUrl ? (\n <a\n href={team.projectUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-flex items-center gap-1 text-xs font-medium text-accent-primary hover:underline\"\n >\n <ExternalLink size={13} /> Live project\n </a>\n ) : null}\n </div>\n ) : null}\n </GlassCard>\n );\n}\n\nexport function HackathonsHackathonDetailsPagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n const { currentUser } = useTechnoSpam();\n // The focused team lives in `?teamId=` so the assistant's page context names it.\n const [focusedTeamId, setFocusedTeamId] = useFocusedRecordParam(FOCUS_PARAMS.team);\n\n const { data, loading, error, refetch } = useHackathonQuery(id);\n const {\n data: teamsData,\n loading: teamsLoading,\n error: teamsError,\n refetch: refetchTeams,\n } = useTeamsQuery(id);\n\n const [registerTeam, { loading: registering }] = useRegisterTeam();\n const [submitProject, { loading: submitting }] = useSubmitProject();\n\n // register-team form state\n const [teamName, setTeamName] = useState('');\n const [memberIdsRaw, setMemberIdsRaw] = useState('');\n\n // submit-project form state\n const [projectTeamId, setProjectTeamId] = useState('');\n const [projectName, setProjectName] = useState('');\n const [projectUrl, setProjectUrl] = useState('');\n const [repoUrl, setRepoUrl] = useState('');\n const [pitch, setPitch] = useState('');\n\n const hackathon = data?.tecHackathon ?? null;\n const teams = teamsData?.tecListTeams?.items ?? [];\n const isRegistrationOpen = hackathon?.status === 'REGISTRATION_OPEN';\n const captainedTeams = currentUser?.id ? teams.filter((t) => t.captainId === currentUser.id) : [];\n const activeProjectTeamId = projectTeamId || captainedTeams[0]?.id || '';\n\n const handleRegister = async () => {\n if (!id || !teamName.trim()) return;\n const memberIds = memberIdsRaw\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n const res = await registerTeam({\n variables: { input: { hackathonId: id, name: teamName.trim(), memberIds } },\n });\n if (res.data?.tecRegisterTeam) {\n notify.success('Team registered', `\"${teamName.trim()}\" is in the running.`);\n setTeamName('');\n setMemberIdsRaw('');\n await Promise.all([refetchTeams(), refetch()]);\n }\n };\n\n const handleSubmitProject = async () => {\n if (!activeProjectTeamId || !projectName.trim()) return;\n const res = await submitProject({\n variables: {\n input: {\n teamId: activeProjectTeamId,\n projectName: projectName.trim(),\n projectUrl: projectUrl.trim() || undefined,\n repoUrl: repoUrl.trim() || undefined,\n pitch: pitch.trim() || undefined,\n },\n },\n });\n if (res.data?.tecSubmitProject) {\n notify.success('Project submitted', `\"${projectName.trim()}\" is submitted for judging.`);\n setProjectName('');\n setProjectUrl('');\n setRepoUrl('');\n setPitch('');\n await Promise.all([refetchTeams(), refetch()]);\n }\n };\n\n if (loading && !hackathon) {\n return (\n <PageLayout title=\"Hackathon\" backPath=\"/hackathons/hackathons-page\">\n <Skeleton className=\"mb-4 h-32 w-full rounded-xl\" />\n <Skeleton className=\"h-64 w-full rounded-xl\" />\n </PageLayout>\n );\n }\n\n if (error || !hackathon) {\n return (\n <PageLayout title=\"Hackathon\" backPath=\"/hackathons/hackathons-page\">\n {error ? (\n <ErrorView title=\"Couldn't load hackathon\" error={error} />\n ) : (\n <EmptyView\n title=\"Hackathon not found\"\n description=\"This hackathon may no longer be listed.\"\n />\n )}\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n backPath=\"/hackathons/hackathons-page\"\n title={hackathon.title}\n description={hackathon.theme ?? undefined}\n >\n <MediaCover\n height=\"h-40\"\n seed={hackathon.theme ?? hackathon.id}\n icon={Trophy}\n className=\"mb-6 rounded-xl\"\n >\n <div className=\"flex w-full flex-col gap-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={hackathon.status} />\n {hackathon.tags.map((t) => (\n <Badge\n key={t}\n variant=\"secondary\"\n className=\"border-white/20 bg-white/15 text-[11px] text-white\"\n >\n {t}\n </Badge>\n ))}\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <HeroChip\n icon={Award}\n label={`${formatMoney(hackathon.prizePool, hackathon.currency)} prize pool`}\n />\n <HeroChip\n icon={CalendarDays}\n label={`${formatDate(hackathon.startAt)} – ${formatDate(hackathon.endAt)}`}\n />\n <HeroChip icon={Users} label={`Team of ${hackathon.maxTeamSize}`} />\n <HeroChip\n icon={Trophy}\n label={`${teams.length} team${teams.length === 1 ? '' : 's'} registered`}\n />\n </div>\n </div>\n </MediaCover>\n\n {hackathon.description ? (\n <GlassCard className=\"mb-6 p-5\">\n <h2 className=\"mb-2 text-base font-semibold text-text-primary\">About</h2>\n <p className=\"whitespace-pre-line text-sm text-text-secondary\">{hackathon.description}</p>\n </GlassCard>\n ) : null}\n\n <GlassCard className=\"mb-6 p-5\">\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">Schedule</h2>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-3\">\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-sunken p-3\">\n <CalendarDays size={16} className=\"text-accent-teal\" />\n <div className=\"min-w-0\">\n <div className=\"text-xs text-text-secondary\">Starts</div>\n <div className=\"truncate text-sm font-medium text-text-primary\">\n {formatDate(hackathon.startAt)}\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-sunken p-3\">\n <CalendarDays size={16} className=\"text-accent-teal\" />\n <div className=\"min-w-0\">\n <div className=\"text-xs text-text-secondary\">Ends</div>\n <div className=\"truncate text-sm font-medium text-text-primary\">\n {formatDate(hackathon.endAt)}\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-sunken p-3\">\n <CalendarClock size={16} className=\"text-accent-teal\" />\n <div className=\"min-w-0\">\n <div className=\"text-xs text-text-secondary\">Submission deadline</div>\n <div className=\"truncate text-sm font-medium text-text-primary\">\n {formatDateTime(hackathon.submissionDeadline)}\n </div>\n </div>\n </div>\n </div>\n </GlassCard>\n\n {isRegistrationOpen ? (\n <GlassCard className=\"mb-6 p-5\">\n <div className=\"mb-3 flex items-center gap-3\">\n <span className=\"flex h-9 w-9 items-center justify-center rounded-xl bg-accent-primary/15 text-accent-primary ring-1 ring-accent-primary/20\">\n <UserPlus size={18} />\n </span>\n <h2 className=\"text-base font-semibold text-text-primary\">Register a team</h2>\n </div>\n <p className=\"mb-4 text-sm text-text-secondary\">\n You'll be the team captain. Add teammates by their user id (optional) — teams can\n be up to {hackathon.maxTeamSize} members.\n </p>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">\n Team name\n </label>\n <Input\n placeholder=\"e.g. Byte Builders\"\n value={teamName}\n onChange={(e) => setTeamName(e.target.value)}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">\n Teammate user ids{' '}\n <span className=\"text-text-muted\">(optional, comma-separated)</span>\n </label>\n <Input\n placeholder=\"user-id-1, user-id-2\"\n value={memberIdsRaw}\n onChange={(e) => setMemberIdsRaw(e.target.value)}\n />\n </div>\n </div>\n <div className=\"mt-4 flex justify-end\">\n <Button onClick={handleRegister} disabled={registering || !teamName.trim()}>\n <UserPlus size={16} /> {registering ? 'Registering…' : 'Register team'}\n </Button>\n </div>\n </GlassCard>\n ) : null}\n\n {captainedTeams.length > 0 ? (\n <GlassCard className=\"mb-6 p-5\">\n <div className=\"mb-3 flex items-center gap-3\">\n <span className=\"flex h-9 w-9 items-center justify-center rounded-xl bg-accent-teal/15 text-accent-teal ring-1 ring-accent-teal/20\">\n <Send size={18} />\n </span>\n <h2 className=\"text-base font-semibold text-text-primary\">Submit your project</h2>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {captainedTeams.length > 1 ? (\n <div className=\"sm:col-span-2\">\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">Team</label>\n <Select value={activeProjectTeamId} onValueChange={setProjectTeamId}>\n <SelectTrigger>\n <SelectValue placeholder=\"Choose a team\" />\n </SelectTrigger>\n <SelectContent>\n {captainedTeams.map((t) => (\n <SelectItem key={t.id} value={t.id}>\n {t.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n ) : (\n <div className=\"sm:col-span-2 text-sm text-text-secondary\">\n Submitting for{' '}\n <span className=\"font-medium text-text-primary\">{captainedTeams[0]?.name}</span>\n </div>\n )}\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">\n Project name\n </label>\n <Input\n placeholder=\"e.g. RealtimeCanvas\"\n value={projectName}\n onChange={(e) => setProjectName(e.target.value)}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">\n Live project URL <span className=\"text-text-muted\">(optional)</span>\n </label>\n <Input\n placeholder=\"https://…\"\n value={projectUrl}\n onChange={(e) => setProjectUrl(e.target.value)}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">\n Repository URL <span className=\"text-text-muted\">(optional)</span>\n </label>\n <Input\n placeholder=\"https://github.com/…\"\n value={repoUrl}\n onChange={(e) => setRepoUrl(e.target.value)}\n />\n </div>\n <div className=\"sm:col-span-2\">\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">\n Pitch <span className=\"text-text-muted\">(optional)</span>\n </label>\n <Textarea\n rows={3}\n placeholder=\"What did you build, and why does it matter?\"\n value={pitch}\n onChange={(e) => setPitch(e.target.value)}\n />\n </div>\n </div>\n <div className=\"mt-4 flex justify-end\">\n <Button\n onClick={handleSubmitProject}\n disabled={submitting || !activeProjectTeamId || !projectName.trim()}\n >\n <Send size={16} /> {submitting ? 'Submitting…' : 'Submit project'}\n </Button>\n </div>\n </GlassCard>\n ) : null}\n\n <div>\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">\n Teams {teams.length ? <span className=\"text-text-muted\">({teams.length})</span> : null}\n </h2>\n {teamsLoading && !teams.length ? (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <Skeleton className=\"h-40 w-full rounded-xl\" />\n <Skeleton className=\"h-40 w-full rounded-xl\" />\n </div>\n ) : teamsError ? (\n <ErrorView title=\"Couldn't load teams\" error={teamsError} />\n ) : teams.length === 0 ? (\n <EmptyView\n icon={<Users size={26} />}\n title=\"No teams registered yet\"\n description={\n isRegistrationOpen\n ? 'Be the first to register a team above.'\n : 'Teams will appear here once registration opens.'\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {teams.map((team) => (\n <TeamCard\n key={team.id}\n team={team}\n isMine={!!currentUser?.id && team.captainId === currentUser.id}\n focused={focusedTeamId === team.id}\n onToggle={() => setFocusedTeamId(focusedTeamId === team.id ? null : team.id)}\n />\n ))}\n </div>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HackathonsHackathonDetailsPagePage.js","names":[],"sources":["../../../src/technospam/pages/HackathonsHackathonDetailsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport {\n Award,\n CalendarClock,\n CalendarDays,\n Code2,\n ExternalLink,\n Flag,\n Github,\n Send,\n Trophy,\n UserPlus,\n Users,\n type LucideIcon,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n GlassCard,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Skeleton,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n MediaCover,\n PageLayout,\n StatusBadge,\n coverGradient,\n} from '../components';\nimport { cn } from '../utils/cn';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n useHackathonQuery,\n useRegisterTeam,\n useSubmitProject,\n useTeamsQuery,\n type TecHackathonTeam,\n} from '../api/technospamApi';\nimport { formatDate, formatDateTime, formatMoney } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport {\n FOCUSED_CARD_CLASS,\n focusableCardProps,\n useFocusedRecordParam,\n} from '../hooks/useFocusedRecordParam';\nimport { FOCUS_PARAMS } from '../utils/pageContext';\n\n/** Translucent glass chip used for stats overlaid on the vivid MediaCover hero. */\nfunction HeroChip({ icon: Icon, label }: { icon: LucideIcon; label: string }) {\n return (\n <span className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-text/15 px-3 py-1.5 text-sm font-medium text-action-primary-text ring-1 ring-action-primary-text/25 backdrop-blur-sm\">\n <Icon size={15} /> {label}\n </span>\n );\n}\n\nfunction TeamCard({\n team,\n isMine,\n focused,\n onToggle,\n}: {\n team: TecHackathonTeam;\n isMine: boolean;\n /** The focused team (`?teamId=`) is the one the assistant acts on. */\n focused: boolean;\n onToggle: () => void;\n}) {\n const isWinner = team.status === 'WINNER';\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-1 hover:shadow-lg',\n focused && FOCUSED_CARD_CLASS\n )}\n {...focusableCardProps(focused, onToggle)}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <span\n className={cn(\n 'flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br text-action-primary-text shadow-sm ring-1 ring-action-primary-text/10 transition-transform duration-200 group-hover:scale-105',\n coverGradient(team.name)\n )}\n >\n {isWinner ? <Trophy size={19} /> : <Code2 size={19} />}\n </span>\n <div className=\"min-w-0\">\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 hover:text-accent-primary\"\n >\n {team.name}\n </button>\n {isMine ? (\n <Badge variant=\"secondary\" className=\"text-[11px]\">\n Your team\n </Badge>\n ) : null}\n </div>\n {team.projectName ? (\n <p className=\"mt-0.5 truncate text-sm text-text-secondary\">{team.projectName}</p>\n ) : (\n <p className=\"mt-0.5 text-sm text-text-muted\">No project submitted yet</p>\n )}\n </div>\n </div>\n <StatusBadge status={team.status} />\n </div>\n\n {team.pitch ? <p className=\"line-clamp-3 text-sm text-text-secondary\">{team.pitch}</p> : null}\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 <Users size={13} /> {team.memberIds.length} member{team.memberIds.length === 1 ? '' : 's'}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Flag size={13} /> Score {team.score}\n </span>\n </div>\n\n {team.repoUrl || team.projectUrl ? (\n <div className=\"flex flex-wrap items-center gap-3 border-t border-border-seam pt-3\">\n {team.repoUrl ? (\n <a\n href={team.repoUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-flex items-center gap-1 text-xs font-medium text-accent-primary hover:underline\"\n >\n <Github size={13} /> Repository\n </a>\n ) : null}\n {team.projectUrl ? (\n <a\n href={team.projectUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-flex items-center gap-1 text-xs font-medium text-accent-primary hover:underline\"\n >\n <ExternalLink size={13} /> Live project\n </a>\n ) : null}\n </div>\n ) : null}\n </GlassCard>\n );\n}\n\nexport function HackathonsHackathonDetailsPagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n const { currentUser } = useTechnoSpam();\n // The focused team lives in `?teamId=` so the assistant's page context names it.\n const [focusedTeamId, setFocusedTeamId] = useFocusedRecordParam(FOCUS_PARAMS.team);\n\n const { data, loading, error, refetch } = useHackathonQuery(id);\n const {\n data: teamsData,\n loading: teamsLoading,\n error: teamsError,\n refetch: refetchTeams,\n } = useTeamsQuery(id);\n\n const [registerTeam, { loading: registering }] = useRegisterTeam();\n const [submitProject, { loading: submitting }] = useSubmitProject();\n\n // register-team form state\n const [teamName, setTeamName] = useState('');\n const [memberIdsRaw, setMemberIdsRaw] = useState('');\n\n // submit-project form state\n const [projectTeamId, setProjectTeamId] = useState('');\n const [projectName, setProjectName] = useState('');\n const [projectUrl, setProjectUrl] = useState('');\n const [repoUrl, setRepoUrl] = useState('');\n const [pitch, setPitch] = useState('');\n\n const hackathon = data?.tecHackathon ?? null;\n const teams = teamsData?.tecListTeams?.items ?? [];\n const isRegistrationOpen = hackathon?.status === 'REGISTRATION_OPEN';\n const captainedTeams = currentUser?.id ? teams.filter((t) => t.captainId === currentUser.id) : [];\n const activeProjectTeamId = projectTeamId || captainedTeams[0]?.id || '';\n\n const handleRegister = async () => {\n if (!id || !teamName.trim()) return;\n const memberIds = memberIdsRaw\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n const res = await registerTeam({\n variables: { input: { hackathonId: id, name: teamName.trim(), memberIds } },\n });\n if (res.data?.tecRegisterTeam) {\n notify.success('Team registered', `\"${teamName.trim()}\" is in the running.`);\n setTeamName('');\n setMemberIdsRaw('');\n await Promise.all([refetchTeams(), refetch()]);\n }\n };\n\n const handleSubmitProject = async () => {\n if (!activeProjectTeamId || !projectName.trim()) return;\n const res = await submitProject({\n variables: {\n input: {\n teamId: activeProjectTeamId,\n projectName: projectName.trim(),\n projectUrl: projectUrl.trim() || undefined,\n repoUrl: repoUrl.trim() || undefined,\n pitch: pitch.trim() || undefined,\n },\n },\n });\n if (res.data?.tecSubmitProject) {\n notify.success('Project submitted', `\"${projectName.trim()}\" is submitted for judging.`);\n setProjectName('');\n setProjectUrl('');\n setRepoUrl('');\n setPitch('');\n await Promise.all([refetchTeams(), refetch()]);\n }\n };\n\n if (loading && !hackathon) {\n return (\n <PageLayout title=\"Hackathon\" backPath=\"/hackathons/hackathons-page\">\n <Skeleton className=\"mb-4 h-32 w-full rounded-xl\" />\n <Skeleton className=\"h-64 w-full rounded-xl\" />\n </PageLayout>\n );\n }\n\n if (error || !hackathon) {\n return (\n <PageLayout title=\"Hackathon\" backPath=\"/hackathons/hackathons-page\">\n {error ? (\n <ErrorView title=\"Couldn't load hackathon\" error={error} />\n ) : (\n <EmptyView\n title=\"Hackathon not found\"\n description=\"This hackathon may no longer be listed.\"\n />\n )}\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n backPath=\"/hackathons/hackathons-page\"\n title={hackathon.title}\n description={hackathon.theme ?? undefined}\n >\n <MediaCover\n height=\"h-40\"\n seed={hackathon.theme ?? hackathon.id}\n icon={Trophy}\n className=\"mb-6 rounded-xl\"\n >\n <div className=\"flex w-full flex-col gap-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={hackathon.status} />\n {hackathon.tags.map((t) => (\n <Badge\n key={t}\n variant=\"secondary\"\n className=\"border-action-primary-text/20 bg-action-primary-text/15 text-[11px] text-action-primary-text\"\n >\n {t}\n </Badge>\n ))}\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <HeroChip\n icon={Award}\n label={`${formatMoney(hackathon.prizePool, hackathon.currency)} prize pool`}\n />\n <HeroChip\n icon={CalendarDays}\n label={`${formatDate(hackathon.startAt)} – ${formatDate(hackathon.endAt)}`}\n />\n <HeroChip icon={Users} label={`Team of ${hackathon.maxTeamSize}`} />\n <HeroChip\n icon={Trophy}\n label={`${teams.length} team${teams.length === 1 ? '' : 's'} registered`}\n />\n </div>\n </div>\n </MediaCover>\n\n {hackathon.description ? (\n <GlassCard className=\"mb-6 p-5\">\n <h2 className=\"mb-2 text-base font-semibold text-text-primary\">About</h2>\n <p className=\"whitespace-pre-line text-sm text-text-secondary\">{hackathon.description}</p>\n </GlassCard>\n ) : null}\n\n <GlassCard className=\"mb-6 p-5\">\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">Schedule</h2>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-3\">\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-sunken p-3\">\n <CalendarDays size={16} className=\"text-accent-teal\" />\n <div className=\"min-w-0\">\n <div className=\"text-xs text-text-secondary\">Starts</div>\n <div className=\"truncate text-sm font-medium text-text-primary\">\n {formatDate(hackathon.startAt)}\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-sunken p-3\">\n <CalendarDays size={16} className=\"text-accent-teal\" />\n <div className=\"min-w-0\">\n <div className=\"text-xs text-text-secondary\">Ends</div>\n <div className=\"truncate text-sm font-medium text-text-primary\">\n {formatDate(hackathon.endAt)}\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-sunken p-3\">\n <CalendarClock size={16} className=\"text-accent-teal\" />\n <div className=\"min-w-0\">\n <div className=\"text-xs text-text-secondary\">Submission deadline</div>\n <div className=\"truncate text-sm font-medium text-text-primary\">\n {formatDateTime(hackathon.submissionDeadline)}\n </div>\n </div>\n </div>\n </div>\n </GlassCard>\n\n {isRegistrationOpen ? (\n <GlassCard className=\"mb-6 p-5\">\n <div className=\"mb-3 flex items-center gap-3\">\n <span className=\"flex h-9 w-9 items-center justify-center rounded-xl bg-accent-primary/15 text-accent-primary ring-1 ring-accent-primary/20\">\n <UserPlus size={18} />\n </span>\n <h2 className=\"text-base font-semibold text-text-primary\">Register a team</h2>\n </div>\n <p className=\"mb-4 text-sm text-text-secondary\">\n You'll be the team captain. Add teammates by their user id (optional) — teams can\n be up to {hackathon.maxTeamSize} members.\n </p>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">\n Team name\n </label>\n <Input\n placeholder=\"e.g. Byte Builders\"\n value={teamName}\n onChange={(e) => setTeamName(e.target.value)}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">\n Teammate user ids{' '}\n <span className=\"text-text-muted\">(optional, comma-separated)</span>\n </label>\n <Input\n placeholder=\"user-id-1, user-id-2\"\n value={memberIdsRaw}\n onChange={(e) => setMemberIdsRaw(e.target.value)}\n />\n </div>\n </div>\n <div className=\"mt-4 flex justify-end\">\n <Button onClick={handleRegister} disabled={registering || !teamName.trim()}>\n <UserPlus size={16} /> {registering ? 'Registering…' : 'Register team'}\n </Button>\n </div>\n </GlassCard>\n ) : null}\n\n {captainedTeams.length > 0 ? (\n <GlassCard className=\"mb-6 p-5\">\n <div className=\"mb-3 flex items-center gap-3\">\n <span className=\"flex h-9 w-9 items-center justify-center rounded-xl bg-accent-teal/15 text-accent-teal ring-1 ring-accent-teal/20\">\n <Send size={18} />\n </span>\n <h2 className=\"text-base font-semibold text-text-primary\">Submit your project</h2>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {captainedTeams.length > 1 ? (\n <div className=\"sm:col-span-2\">\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">Team</label>\n <Select value={activeProjectTeamId} onValueChange={setProjectTeamId}>\n <SelectTrigger>\n <SelectValue placeholder=\"Choose a team\" />\n </SelectTrigger>\n <SelectContent>\n {captainedTeams.map((t) => (\n <SelectItem key={t.id} value={t.id}>\n {t.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n ) : (\n <div className=\"sm:col-span-2 text-sm text-text-secondary\">\n Submitting for{' '}\n <span className=\"font-medium text-text-primary\">{captainedTeams[0]?.name}</span>\n </div>\n )}\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">\n Project name\n </label>\n <Input\n placeholder=\"e.g. RealtimeCanvas\"\n value={projectName}\n onChange={(e) => setProjectName(e.target.value)}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">\n Live project URL <span className=\"text-text-muted\">(optional)</span>\n </label>\n <Input\n placeholder=\"https://…\"\n value={projectUrl}\n onChange={(e) => setProjectUrl(e.target.value)}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">\n Repository URL <span className=\"text-text-muted\">(optional)</span>\n </label>\n <Input\n placeholder=\"https://github.com/…\"\n value={repoUrl}\n onChange={(e) => setRepoUrl(e.target.value)}\n />\n </div>\n <div className=\"sm:col-span-2\">\n <label className=\"mb-1.5 block text-sm font-medium text-text-secondary\">\n Pitch <span className=\"text-text-muted\">(optional)</span>\n </label>\n <Textarea\n rows={3}\n placeholder=\"What did you build, and why does it matter?\"\n value={pitch}\n onChange={(e) => setPitch(e.target.value)}\n />\n </div>\n </div>\n <div className=\"mt-4 flex justify-end\">\n <Button\n onClick={handleSubmitProject}\n disabled={submitting || !activeProjectTeamId || !projectName.trim()}\n >\n <Send size={16} /> {submitting ? 'Submitting…' : 'Submit project'}\n </Button>\n </div>\n </GlassCard>\n ) : null}\n\n <div>\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">\n Teams {teams.length ? <span className=\"text-text-muted\">({teams.length})</span> : null}\n </h2>\n {teamsLoading && !teams.length ? (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <Skeleton className=\"h-40 w-full rounded-xl\" />\n <Skeleton className=\"h-40 w-full rounded-xl\" />\n </div>\n ) : teamsError ? (\n <ErrorView title=\"Couldn't load teams\" error={teamsError} />\n ) : teams.length === 0 ? (\n <EmptyView\n icon={<Users size={26} />}\n title=\"No teams registered yet\"\n description={\n isRegistrationOpen\n ? 'Be the first to register a team above.'\n : 'Teams will appear here once registration opens.'\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {teams.map((team) => (\n <TeamCard\n key={team.id}\n team={team}\n isMine={!!currentUser?.id && team.captainId === currentUser.id}\n focused={focusedTeamId === team.id}\n onToggle={() => setFocusedTeamId(focusedTeamId === team.id ? null : team.id)}\n />\n ))}\n </div>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@ function I({ mentorId: e, disabled: n, onBooked: r, workspaceId: i }) {
|
|
|
24
24
|
/* @__PURE__ */ k("div", {
|
|
25
25
|
className: "mb-4 flex items-start gap-3",
|
|
26
26
|
children: [/* @__PURE__ */ O("span", {
|
|
27
|
-
className: "flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-accent-primary to-accent-purple text-
|
|
27
|
+
className: "flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-accent-primary to-accent-purple text-action-primary-text shadow-sm",
|
|
28
28
|
children: /* @__PURE__ */ O(b, { size: 20 })
|
|
29
29
|
}), /* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("h2", {
|
|
30
30
|
className: "text-base font-semibold text-text-primary",
|
|
@@ -224,10 +224,10 @@ function R() {
|
|
|
224
224
|
children: [/* @__PURE__ */ k("div", {
|
|
225
225
|
className: n("relative h-24 overflow-hidden bg-gradient-to-br", a(x.headline)),
|
|
226
226
|
children: [_(D, {
|
|
227
|
-
className: "pointer-events-none absolute -right-3 -top-4 text-
|
|
227
|
+
className: "pointer-events-none absolute -right-3 -top-4 text-action-primary-text/15",
|
|
228
228
|
size: 120,
|
|
229
229
|
strokeWidth: 1.3
|
|
230
|
-
}), /* @__PURE__ */ O("div", { className: "pointer-events-none absolute inset-0 bg-gradient-to-t from-
|
|
230
|
+
}), /* @__PURE__ */ O("div", { className: "pointer-events-none absolute inset-0 bg-gradient-to-t from-bg-overlay/60 to-transparent" })]
|
|
231
231
|
}), /* @__PURE__ */ k("div", {
|
|
232
232
|
className: "px-6 pb-6",
|
|
233
233
|
children: [
|
|
@@ -236,7 +236,7 @@ function R() {
|
|
|
236
236
|
children: [/* @__PURE__ */ k("div", {
|
|
237
237
|
className: "relative shrink-0",
|
|
238
238
|
children: [/* @__PURE__ */ O("span", {
|
|
239
|
-
className: n("flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br text-
|
|
239
|
+
className: n("flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br text-action-primary-text shadow-lg ring-4 ring-bg-surface", a(x.headline)),
|
|
240
240
|
children: _(D, {
|
|
241
241
|
size: 34,
|
|
242
242
|
strokeWidth: 1.6
|