@burdenoff/microfe-technospam 2026.731.1 → 2026.827.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/node_modules/recharts/es6/cartesian/Brush.js.map +1 -1
  2. package/dist/node_modules/recharts/es6/cartesian/ErrorBar.js.map +1 -1
  3. package/dist/node_modules/recharts/es6/cartesian/ReferenceArea.js.map +1 -1
  4. package/dist/node_modules/recharts/es6/cartesian/ReferenceDot.js.map +1 -1
  5. package/dist/node_modules/recharts/es6/cartesian/ReferenceLine.js.map +1 -1
  6. package/dist/node_modules/recharts/es6/cartesian/XAxis.js.map +1 -1
  7. package/dist/node_modules/recharts/es6/cartesian/YAxis.js.map +1 -1
  8. package/dist/node_modules/recharts/es6/component/ResponsiveContainer.js.map +1 -1
  9. package/dist/node_modules/sonner/dist/index.js.map +1 -1
  10. package/dist/technospam/TechnoSpamRoutes.js +1 -1
  11. package/dist/technospam/TechnoSpamRoutes.js.map +1 -1
  12. package/dist/technospam/api/technospamApi.d.ts +19 -0
  13. package/dist/technospam/api/technospamApi.d.ts.map +1 -1
  14. package/dist/technospam/api/technospamApi.js +171 -150
  15. package/dist/technospam/api/technospamApi.js.map +1 -1
  16. package/dist/technospam/components/SubmissionViews.d.ts +31 -0
  17. package/dist/technospam/components/SubmissionViews.d.ts.map +1 -0
  18. package/dist/technospam/components/SubmissionViews.js +149 -0
  19. package/dist/technospam/components/SubmissionViews.js.map +1 -0
  20. package/dist/technospam/components/index.d.ts +1 -0
  21. package/dist/technospam/components/index.d.ts.map +1 -1
  22. package/dist/technospam/components/index.js +1 -0
  23. package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts.map +1 -1
  24. package/dist/technospam/pages/AdminAdminDashboardPagePage.js +725 -500
  25. package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
  26. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  27. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js +343 -262
  28. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  29. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.d.ts.map +1 -1
  30. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +97 -235
  31. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
  32. package/dist/technospam/pages/CoursesCourseDetailsPagePage.d.ts.map +1 -1
  33. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +294 -203
  34. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
  35. package/dist/technospam/pages/CoursesMyLearningPagePage.d.ts.map +1 -1
  36. package/dist/technospam/pages/CoursesMyLearningPagePage.js +2 -2
  37. package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
  38. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +1 -1
  39. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
  40. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +2 -3
  41. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
  42. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.d.ts.map +1 -1
  43. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.js +180 -121
  44. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.js.map +1 -1
  45. package/dist/technospam/providers/TechnoSpamProvider.js +1 -1
  46. package/dist/technospam/providers/TechnoSpamProvider.js.map +1 -1
  47. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"CoursesCourseDetailsPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesCourseDetailsPagePage.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { useEffect } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport {\n Award,\n BookOpen,\n CheckCircle2,\n Clock,\n Eye,\n FileText,\n Lock,\n PlayCircle,\n Layers,\n ShieldCheck,\n Sparkles,\n Trophy,\n TrendingUp,\n Users,\n type LucideIcon,\n} from 'lucide-react';\nimport { Badge, Button, GlassCard, Skeleton } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n MediaCover,\n PageLayout,\n PremiumBadge,\n StatCard,\n StatusBadge,\n TagStrip,\n UpgradeGate,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n useCertificatesQuery,\n useCourseQuery,\n useEnroll,\n useIssueCertificate,\n useUpdateProgress,\n type TecLesson,\n type TecEnrollment,\n} from '../api/technospamApi';\nimport { formatDuration, humanizeEnum } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { cn } from '../utils/cn';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\n/** Per-lesson-kind icon + on-theme tint (chip) and pill (badge) classes. */\nconst KIND_META: Record<string, { icon: LucideIcon; chip: string; badge: string }> = {\n VIDEO: {\n icon: PlayCircle,\n chip: 'bg-accent-primary/15 text-accent-primary',\n badge: 'border-accent-primary/25 bg-accent-primary/10 text-accent-primary',\n },\n ARTICLE: {\n icon: FileText,\n chip: 'bg-accent-indigo/15 text-accent-indigo',\n badge: 'border-accent-indigo/25 bg-accent-indigo/10 text-accent-indigo',\n },\n INTERACTIVE: {\n icon: Sparkles,\n chip: 'bg-accent-teal/15 text-accent-teal',\n badge: 'border-accent-teal/25 bg-accent-teal/10 text-accent-teal',\n },\n QUIZ: {\n icon: CheckCircle2,\n chip: 'bg-accent-purple/15 text-accent-purple',\n badge: 'border-accent-purple/25 bg-accent-purple/10 text-accent-purple',\n },\n PROJECT: {\n icon: Layers,\n chip: 'bg-status-warning-bg-subtle text-status-warning-text',\n badge: 'border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text',\n },\n};\n\nfunction HeroBadge({ icon: Icon, children }: { icon?: LucideIcon; children: ReactNode }) {\n return (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-white/15 px-2.5 py-1 text-xs font-medium text-white ring-1 ring-white/25 backdrop-blur-sm\">\n {Icon ? <Icon size={12} /> : null}\n {children}\n </span>\n );\n}\n\nfunction LessonRow({ lesson, index, done }: { lesson: TecLesson; index: number; done: boolean }) {\n const meta = KIND_META[lesson.kind] ?? KIND_META.ARTICLE!;\n const Icon = meta.icon;\n return (\n <div className=\"group flex items-center gap-3 rounded-xl px-2 py-3 transition-colors hover:bg-bg-sunken/60\">\n <span className=\"hidden w-6 shrink-0 text-center text-sm font-medium tabular-nums text-text-muted sm:block\">\n {index + 1}\n </span>\n <span\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg',\n done ? 'bg-status-success-bg-subtle text-status-success-text' : meta.chip\n )}\n >\n {done ? <CheckCircle2 size={17} /> : <Icon size={17} />}\n </span>\n <div className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">{lesson.title}</span>\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n <span\n className={cn(\n 'inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] font-medium',\n meta.badge\n )}\n >\n {humanizeEnum(lesson.kind)}\n </span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Clock size={12} /> {formatDuration(lesson.estimatedMinutes)}\n </span>\n </div>\n </div>\n <div className=\"shrink-0\">\n {done ? (\n <CheckCircle2 size={18} className=\"text-status-success-text\" />\n ) : lesson.isPreview ? (\n <span className=\"inline-flex items-center gap-1 rounded-full border border-accent-primary/30 bg-accent-primary/10 px-2 py-0.5 text-[11px] font-medium text-accent-primary\">\n <Eye size={12} /> Preview\n </span>\n ) : lesson.isLocked ? (\n // BOFF-4416: distinct from the plain \"not completed yet\" Lock icon\n // below — this lesson's content is gated behind a paid plan, not\n // just not-yet-watched. The API already nulled `content`/`file`.\n <span className=\"inline-flex items-center gap-1 rounded-full border border-status-warning-border/60 bg-status-warning-bg-subtle px-2 py-0.5 text-[11px] font-medium text-status-warning-text\">\n <Lock size={12} /> Upgrade to unlock\n </span>\n ) : (\n <Lock size={15} className=\"text-text-muted\" />\n )}\n </div>\n </div>\n );\n}\n\nexport function CoursesCourseDetailsPagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n const { currentUser, workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const { data, loading, error, refetch } = useCourseQuery(id);\n\n useEffect(() => {\n if (id) {\n emit('technospam.course.viewed', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n }\n }, [id, workspaceId, emit]);\n\n const [enroll, { loading: enrolling }] = useEnroll();\n const [updateProgress, { loading: savingProgress }] = useUpdateProgress();\n const { data: certData, refetch: refetchCerts } = useCertificatesQuery(\n currentUser?.id && id ? { learnerId: currentUser.id, courseId: id } : {}\n );\n const [issueCertificate, { loading: claiming }] = useIssueCertificate();\n\n const course = data?.tecCourse ?? null;\n const lessons = [...(course?.lessons ?? [])].sort((a, b) => a.position - b.position);\n const myEnrollment: TecEnrollment | undefined = (course?.enrollments ?? []).find(\n (e) => e.learnerId === currentUser?.id\n );\n const myCertificate = (certData?.tecListCertificates ?? []).find((c) => c.status === 'ISSUED');\n const isComplete = !!myEnrollment && myEnrollment.progressPct >= 100;\n\n const handleClaimCertificate = async () => {\n if (!id || !currentUser?.id) return;\n emit('technospam.course.certificate_claimed', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n const res = await issueCertificate({\n variables: { courseId: id, learnerId: currentUser.id, score: 100 },\n });\n if (res.data?.tecIssueCertificate) {\n notify.success('Certificate issued', 'Your verifiable certificate is ready in My Learning.');\n await refetchCerts();\n }\n };\n\n const handleEnroll = async () => {\n if (!id) return;\n emit('technospam.course.enroll_clicked', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n const res = await enroll({ variables: { input: { courseId: id } } });\n if (res.data?.tecEnroll) {\n emit('technospam.course.enrolled', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n notify.success('Enrolled', `You're enrolled in \"${course?.title}\".`);\n await refetch();\n }\n };\n\n const handleProgress = async () => {\n if (!myEnrollment) return;\n const nextCompleted = Math.min(lessons.length, myEnrollment.lessonsCompleted + 1);\n const pct = lessons.length ? Math.round((nextCompleted / lessons.length) * 100) : 100;\n const res = await updateProgress({\n variables: {\n input: { enrollmentId: myEnrollment.id, lessonsCompleted: nextCompleted, progressPct: pct },\n },\n });\n if (res.data?.tecUpdateProgress) {\n emit('technospam.course.progress_updated', {\n workspaceId,\n entityId: myEnrollment.id,\n entityType: 'TEC_ENROLLMENT',\n progressPct: pct,\n });\n notify.success('Progress saved', `${pct}% complete`);\n await refetch();\n }\n };\n\n if (loading && !course) {\n return (\n <PageLayout title=\"Course\" backPath=\"/courses/courses-page\">\n <Skeleton className=\"mb-4 h-40 w-full rounded-2xl\" />\n <Skeleton className=\"h-64 w-full rounded-xl\" />\n </PageLayout>\n );\n }\n\n if (error || !course) {\n return (\n <PageLayout title=\"Course\" backPath=\"/courses/courses-page\">\n {error ? (\n <ErrorView title=\"Couldn't load course\" error={error} />\n ) : (\n <EmptyView\n title=\"Course not found\"\n description=\"This course may have been unpublished.\"\n />\n )}\n </PageLayout>\n );\n }\n\n const completedLessons = myEnrollment?.lessonsCompleted ?? 0;\n const remaining = Math.max(0, lessons.length - completedLessons);\n const milestone =\n myEnrollment && myEnrollment.progressPct >= 100\n ? 'Course complete — claim your certificate below.'\n : remaining === 1\n ? '1 lesson to go — almost there!'\n : `${remaining} lessons to go — keep it up!`;\n const heroSeed = `${course.topic ?? ''} ${course.title}`;\n\n return (\n <PageLayout\n backPath=\"/courses/courses-page\"\n title={course.title}\n description={course.summary ?? undefined}\n actions={\n myEnrollment ? (\n <Button\n onClick={handleProgress}\n disabled={savingProgress || completedLessons >= lessons.length}\n >\n <CheckCircle2 size={16} />\n {completedLessons >= lessons.length ? 'Completed' : 'Complete next lesson'}\n </Button>\n ) : (\n <Button onClick={handleEnroll} disabled={enrolling}>\n <BookOpen size={16} /> {enrolling ? 'Enrolling…' : 'Enroll'}\n </Button>\n )\n }\n >\n {/* Bold hero banner */}\n <MediaCover\n height=\"h-40\"\n seed={heroSeed}\n icon={topicIcon(course.topic ?? course.title)}\n className=\"mb-5 rounded-2xl\"\n >\n <div className=\"flex w-full flex-col gap-2\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-white/80\">\n {course.topic ?? 'Course'}\n </span>\n <h2 className=\"text-2xl font-bold leading-tight text-white drop-shadow-sm sm:text-3xl\">\n {course.title}\n </h2>\n <div className=\"flex flex-wrap items-center gap-2\">\n <HeroBadge icon={TrendingUp}>{humanizeEnum(course.level)}</HeroBadge>\n <HeroBadge icon={CheckCircle2}>{humanizeEnum(course.status)}</HeroBadge>\n {course.topic ? <HeroBadge icon={Layers}>{course.topic}</HeroBadge> : null}\n {course.isPremiumOnly ? <HeroBadge icon={Lock}>Premium</HeroBadge> : null}\n </div>\n </div>\n </MediaCover>\n\n <TagStrip tags={course.tags} className=\"mb-5\" />\n\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard label=\"Lessons\" value={lessons.length} icon={FileText} tone=\"blue\" />\n <StatCard\n label=\"Duration\"\n value={formatDuration(course.estimatedMinutes)}\n icon={Clock}\n tone=\"teal\"\n />\n <StatCard\n label=\"Enrolled\"\n value={course.enrollments?.length ?? 0}\n icon={Users}\n tone=\"indigo\"\n />\n <StatCard\n label=\"Your progress\"\n value={myEnrollment ? `${myEnrollment.progressPct}%` : 'Not started'}\n icon={TrendingUp}\n tone=\"purple\"\n />\n </div>\n\n {myEnrollment ? (\n <GlassCard className=\"mb-6 p-5\">\n <div className=\"mb-3 flex items-center gap-2\">\n <span className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-accent-primary/15 text-accent-primary\">\n <TrendingUp size={16} />\n </span>\n <span className=\"text-sm font-semibold text-text-primary\">Your progress</span>\n <span className=\"ml-auto text-sm text-text-secondary\">\n {completedLessons}/{lessons.length} lessons · {myEnrollment.progressPct}%\n </span>\n </div>\n <div className=\"h-2.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: `${myEnrollment.progressPct}%` }}\n />\n </div>\n <p className=\"mt-2 text-xs text-text-muted\">{milestone}</p>\n </GlassCard>\n ) : null}\n\n {myCertificate ? (\n <GlassCard className=\"mb-6 flex flex-wrap items-center gap-3 border border-status-success-border/60 bg-status-success-bg-subtle/30 p-4\">\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\">\n <Award size={22} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-sm font-semibold text-text-primary\">Certificate earned</div>\n <div className=\"text-xs text-text-muted\">\n Serial {myCertificate.serial} · Score {myCertificate.score}\n </div>\n </div>\n <span className=\"inline-flex items-center gap-1 rounded-full border border-status-success-border bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text\">\n <ShieldCheck size={13} /> Verified\n </span>\n </GlassCard>\n ) : isComplete ? (\n <GlassCard className=\"mb-6 flex flex-col items-start gap-4 border border-status-success-border/60 bg-status-success-bg-subtle/40 p-5 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-4\">\n <span className=\"flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-status-warning-bg-subtle text-status-warning-text ring-1 ring-status-warning-border/40\">\n <Trophy size={26} />\n </span>\n <div>\n <div className=\"text-base font-semibold text-text-primary\">\n You finished this course\n </div>\n <div className=\"text-sm text-text-muted\">\n Claim your verifiable certificate of completion.\n </div>\n </div>\n </div>\n <Button onClick={handleClaimCertificate} disabled={claiming}>\n <Award size={16} /> {claiming ? 'Issuing…' : 'Claim certificate'}\n </Button>\n </GlassCard>\n ) : null}\n\n <GlassCard className=\"p-5\">\n <div className=\"mb-3 flex items-center justify-between gap-2\">\n <h2 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <BookOpen size={18} className=\"text-accent-primary\" /> Curriculum\n {course.isPremiumOnly ? <PremiumBadge locked={course.isLocked} /> : null}\n </h2>\n {lessons.length ? (\n <span className=\"rounded-full bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text\">\n {completedLessons}/{lessons.length} completed\n </span>\n ) : null}\n </div>\n {course.isLocked ? (\n <div className=\"mb-4\">\n <UpgradeGate\n compact\n description=\"This course is on a paid TechnoSpam plan. Preview lessons below stay free — the rest unlock once your workspace upgrades.\"\n />\n </div>\n ) : null}\n {lessons.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-muted\">\n Lessons for this course are being prepared.\n </p>\n ) : (\n <div className=\"flex flex-col\">\n {lessons.map((lesson, i) => (\n <LessonRow key={lesson.id} lesson={lesson} index={i} done={i < completedLessons} />\n ))}\n </div>\n )}\n </GlassCard>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiDA,IAAM,IAA+E;CACnF,OAAO;EACL,MAAM;EACN,MAAM;EACN,OAAO;EACR;CACD,SAAS;EACP,MAAM;EACN,MAAM;EACN,OAAO;EACR;CACD,aAAa;EACX,MAAM;EACN,MAAM;EACN,OAAO;EACR;CACD,MAAM;EACJ,MAAM;EACN,MAAM;EACN,OAAO;EACR;CACD,SAAS;EACP,MAAM;EACN,MAAM;EACN,OAAO;EACR;CACF;AAED,SAAS,EAAU,EAAE,MAAM,GAAM,eAAwD;AACvF,QACE,kBAAC,QAAD;EAAM,WAAU;YAAhB,CACG,IAAO,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,GAAG,MAC5B,EACI;;;AAIX,SAAS,EAAU,EAAE,WAAQ,UAAO,WAA6D;CAC/F,IAAM,IAAO,EAAU,EAAO,SAAS,EAAU,SAC3C,IAAO,EAAK;AAClB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,QAAD;IAAM,WAAU;cACb,IAAQ;IACJ,CAAA;GACP,kBAAC,QAAD;IACE,WAAW,EACT,gEACA,IAAO,yDAAyD,EAAK,KACtE;cAEO,EAAP,IAAQ,IAA6B,GAA9B,EAAc,MAAM,IAAM,CAAqB;IAClD,CAAA;GACP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAwD,EAAO;KAAa,CAAA,EAC5F,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MACE,WAAW,EACT,oFACA,EAAK,MACN;gBAEA,EAAa,EAAO,KAAK;MACrB,CAAA,EACP,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;;OAAE,EAAe,EAAO,iBAAiB;OACvD;QACH;OACF;;GACN,kBAAC,OAAD;IAAK,WAAU;cACZ,IACC,kBAAC,GAAD;KAAc,MAAM;KAAI,WAAU;KAA6B,CAAA,GAC7D,EAAO,YACT,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,GAAD,EAAK,MAAM,IAAM,CAAA,EAAA,WACZ;SACL,EAAO,WAIT,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EAAA,qBACb;SAEP,kBAAC,GAAD;KAAM,MAAM;KAAI,WAAU;KAAoB,CAAA;IAE5C,CAAA;GACF;;;AAIV,SAAgB,IAA+B;CAC7C,IAAM,CAAC,KAAU,IAAiB,EAC5B,IAAK,EAAO,IAAI,KAAK,IAAI,IACzB,EAAE,gBAAa,mBAAgB,GAAe,EAC9C,EAAE,YAAS,GAAwB,EACnC,EAAE,SAAM,YAAS,UAAO,eAAY,EAAe,EAAG;AAE5D,SAAgB;AACd,EAAI,KACF,EAAK,4BAA4B;GAC/B;GACA,UAAU;GACV,YAAY;GACb,CAAC;IAEH;EAAC;EAAI;EAAa;EAAK,CAAC;CAE3B,IAAM,CAAC,GAAQ,EAAE,SAAS,OAAe,IAAW,EAC9C,CAAC,GAAgB,EAAE,SAAS,OAAoB,GAAmB,EACnE,EAAE,MAAM,GAAU,SAAS,MAAiB,GAChD,GAAa,MAAM,IAAK;EAAE,WAAW,EAAY;EAAI,UAAU;EAAI,GAAG,EAAE,CACzE,EACK,CAAC,IAAkB,EAAE,SAAS,OAAc,GAAqB,EAEjE,IAAS,GAAM,aAAa,MAC5B,IAAU,CAAC,GAAI,GAAQ,WAAW,EAAE,CAAE,CAAC,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS,EAC9E,KAA2C,GAAQ,eAAe,EAAE,EAAE,MACzE,MAAM,EAAE,cAAc,GAAa,GACrC,EACK,KAAiB,GAAU,uBAAuB,EAAE,EAAE,MAAM,MAAM,EAAE,WAAW,SAAS,EACxF,KAAa,CAAC,CAAC,KAAgB,EAAa,eAAe,KAE3D,IAAyB,YAAY;AACrC,GAAC,KAAM,CAAC,GAAa,OACzB,EAAK,yCAAyC;GAC5C;GACA,UAAU;GACV,YAAY;GACb,CAAC,GACU,MAAM,GAAiB,EACjC,WAAW;GAAE,UAAU;GAAI,WAAW,EAAY;GAAI,OAAO;GAAK,EACnE,CAAC,EACM,MAAM,wBACZ,EAAO,QAAQ,sBAAsB,uDAAuD,EAC5F,MAAM,GAAc;IAIlB,KAAe,YAAY;AAC1B,QACL,EAAK,oCAAoC;GACvC;GACA,UAAU;GACV,YAAY;GACb,CAAC,GACU,MAAM,EAAO,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,GAAI,EAAE,EAAE,CAAC,EAC5D,MAAM,cACZ,EAAK,8BAA8B;GACjC;GACA,UAAU;GACV,YAAY;GACb,CAAC,EACF,EAAO,QAAQ,YAAY,uBAAuB,GAAQ,MAAM,IAAI,EACpE,MAAM,GAAS;IAIb,KAAiB,YAAY;AACjC,MAAI,CAAC,EAAc;EACnB,IAAM,IAAgB,KAAK,IAAI,EAAQ,QAAQ,EAAa,mBAAmB,EAAE,EAC3E,IAAM,EAAQ,SAAS,KAAK,MAAO,IAAgB,EAAQ,SAAU,IAAI,GAAG;AAMlF,GALY,MAAM,EAAe,EAC/B,WAAW,EACT,OAAO;GAAE,cAAc,EAAa;GAAI,kBAAkB;GAAe,aAAa;GAAK,EAC5F,EACF,CAAC,EACM,MAAM,sBACZ,EAAK,sCAAsC;GACzC;GACA,UAAU,EAAa;GACvB,YAAY;GACZ,aAAa;GACd,CAAC,EACF,EAAO,QAAQ,kBAAkB,GAAG,EAAI,YAAY,EACpD,MAAM,GAAS;;AAInB,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,GAAD;EAAY,OAAM;EAAS,UAAS;YAApC,CACE,kBAAC,GAAD,EAAU,WAAU,gCAAiC,CAAA,EACrD,kBAAC,GAAD,EAAU,WAAU,0BAA2B,CAAA,CACpC;;AAIjB,KAAI,KAAS,CAAC,EACZ,QACE,kBAAC,GAAD;EAAY,OAAM;EAAS,UAAS;YACjC,IACC,kBAAC,GAAD;GAAW,OAAM;GAA8B;GAAS,CAAA,GAExD,kBAAC,IAAD;GACE,OAAM;GACN,aAAY;GACZ,CAAA;EAEO,CAAA;CAIjB,IAAM,IAAmB,GAAc,oBAAoB,GACrD,IAAY,KAAK,IAAI,GAAG,EAAQ,SAAS,EAAiB,EAC1D,KACJ,KAAgB,EAAa,eAAe,MACxC,oDACA,MAAc,IACZ,mCACA,GAAG,EAAU,+BACf,KAAW,GAAG,EAAO,SAAS,GAAG,GAAG,EAAO;AAEjD,QACE,kBAAC,GAAD;EACE,UAAS;EACT,OAAO,EAAO;EACd,aAAa,EAAO,WAAW,KAAA;EAC/B,SACE,IACE,kBAAC,GAAD;GACE,SAAS;GACT,UAAU,KAAkB,KAAoB,EAAQ;aAF1D,CAIE,kBAAC,GAAD,EAAc,MAAM,IAAM,CAAA,EACzB,KAAoB,EAAQ,SAAS,cAAc,uBAC7C;OAET,kBAAC,GAAD;GAAQ,SAAS;GAAc,UAAU;aAAzC;IACE,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;;IAAE,IAAY,eAAe;IAC5C;;YAhBf;GAqBE,kBAAC,GAAD;IACE,QAAO;IACP,MAAM;IACN,MAAM,GAAU,EAAO,SAAS,EAAO,MAAM;IAC7C,WAAU;cAEV,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAO,SAAS;OACZ,CAAA;MACP,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAO;OACL,CAAA;MACL,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAW,MAAM;mBAAa,EAAa,EAAO,MAAM;SAAa,CAAA;QACrE,kBAAC,GAAD;SAAW,MAAM;mBAAe,EAAa,EAAO,OAAO;SAAa,CAAA;QACvE,EAAO,QAAQ,kBAAC,GAAD;SAAW,MAAM;mBAAS,EAAO;SAAkB,CAAA,GAAG;QACrE,EAAO,gBAAgB,kBAAC,GAAD;SAAW,MAAM;mBAAM;SAAmB,CAAA,GAAG;QACjE;;MACF;;IACK,CAAA;GAEb,kBAAC,GAAD;IAAU,MAAM,EAAO;IAAM,WAAU;IAAS,CAAA;GAEhD,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAU,OAAM;MAAU,OAAO,EAAQ;MAAQ,MAAM;MAAU,MAAK;MAAS,CAAA;KAC/E,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAe,EAAO,iBAAiB;MAC9C,MAAM;MACN,MAAK;MACL,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAO,aAAa,UAAU;MACrC,MAAM;MACN,MAAK;MACL,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,IAAe,GAAG,EAAa,YAAY,KAAK;MACvD,MAAM;MACN,MAAK;MACL,CAAA;KACE;;GAEL,IACC,kBAAC,GAAD;IAAW,WAAU;cAArB;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,QAAD;QAAM,WAAU;kBACd,kBAAC,GAAD,EAAY,MAAM,IAAM,CAAA;QACnB,CAAA;OACP,kBAAC,QAAD;QAAM,WAAU;kBAA0C;QAAoB,CAAA;OAC9E,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACG;SAAiB;SAAE,EAAQ;SAAO;SAAY,EAAa;SAAY;SACnE;;OACH;;KACN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OACE,WAAU;OACV,OAAO,EAAE,OAAO,GAAG,EAAa,YAAY,IAAI;OAChD,CAAA;MACE,CAAA;KACN,kBAAC,KAAD;MAAG,WAAU;gBAAgC;MAAc,CAAA;KACjD;QACV;GAEH,IACC,kBAAC,GAAD;IAAW,WAAU;cAArB;KACE,kBAAC,QAAD;MAAM,WAAU;gBACd,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;MACd,CAAA;KACP,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAA0C;OAAwB,CAAA,EACjF,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAAyC;QAC/B,EAAc;QAAO;QAAU,EAAc;QACjD;SACF;;KACN,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,IAAD,EAAa,MAAM,IAAM,CAAA,EAAA,YACpB;;KACG;QACV,KACF,kBAAC,GAAD;IAAW,WAAU;cAArB,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBACd,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA;MACf,CAAA,EACP,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;gBAA4C;MAErD,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAA0B;MAEnC,CAAA,CACF,EAAA,CAAA,CACF;QACN,kBAAC,GAAD;KAAQ,SAAS;KAAwB,UAAU;eAAnD;MACE,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;;MAAE,IAAW,aAAa;MACtC;OACC;QACV;GAEJ,kBAAC,GAAD;IAAW,WAAU;cAArB;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAd;QACE,kBAAC,GAAD;SAAU,MAAM;SAAI,WAAU;SAAwB,CAAA;;QACrD,EAAO,gBAAgB,kBAAC,IAAD,EAAc,QAAQ,EAAO,UAAY,CAAA,GAAG;QACjE;UACJ,EAAQ,SACP,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACG;QAAiB;QAAE,EAAQ;QAAO;QAC9B;WACL,KACA;;KACL,EAAO,WACN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,IAAD;OACE,SAAA;OACA,aAAY;OACZ,CAAA;MACE,CAAA,GACJ;KACH,EAAQ,WAAW,IAClB,kBAAC,KAAD;MAAG,WAAU;gBAA2C;MAEpD,CAAA,GAEJ,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAQ,KAAK,GAAQ,MACpB,kBAAC,GAAD;OAAmC;OAAQ,OAAO;OAAG,MAAM,IAAI;OAAoB,EAAnE,EAAO,GAA4D,CACnF;MACE,CAAA;KAEE;;GACD"}
1
+ {"version":3,"file":"CoursesCourseDetailsPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesCourseDetailsPagePage.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { useEffect, useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport {\n Award,\n BookOpen,\n CheckCircle2,\n ChevronDown,\n Clock,\n Eye,\n FileText,\n Lock,\n PlayCircle,\n Layers,\n ShieldCheck,\n Sparkles,\n Trophy,\n TrendingUp,\n Users,\n type LucideIcon,\n} from 'lucide-react';\nimport { Button, GlassCard, Skeleton } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n MediaCover,\n PageLayout,\n PremiumBadge,\n StatCard,\n TagStrip,\n UpgradeGate,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n useCourseQuery,\n useEnroll,\n useIssueCertificate,\n useMyCertificatesQuery,\n useUpdateProgress,\n type TecCertificate,\n type TecLesson,\n type TecEnrollment,\n} from '../api/technospamApi';\nimport { formatDate, formatDuration, humanizeEnum } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { cn } from '../utils/cn';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\n/** Per-lesson-kind icon + on-theme tint (chip) and pill (badge) classes. */\nconst KIND_META: Record<string, { icon: LucideIcon; chip: string; badge: string }> = {\n VIDEO: {\n icon: PlayCircle,\n chip: 'bg-accent-primary/15 text-accent-primary',\n badge: 'border-accent-primary/25 bg-accent-primary/10 text-accent-primary',\n },\n ARTICLE: {\n icon: FileText,\n chip: 'bg-accent-indigo/15 text-accent-indigo',\n badge: 'border-accent-indigo/25 bg-accent-indigo/10 text-accent-indigo',\n },\n INTERACTIVE: {\n icon: Sparkles,\n chip: 'bg-accent-teal/15 text-accent-teal',\n badge: 'border-accent-teal/25 bg-accent-teal/10 text-accent-teal',\n },\n QUIZ: {\n icon: CheckCircle2,\n chip: 'bg-accent-purple/15 text-accent-purple',\n badge: 'border-accent-purple/25 bg-accent-purple/10 text-accent-purple',\n },\n PROJECT: {\n icon: Layers,\n chip: 'bg-status-warning-bg-subtle text-status-warning-text',\n badge: 'border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text',\n },\n};\n\nfunction HeroBadge({ icon: Icon, children }: { icon?: LucideIcon; children: ReactNode }) {\n return (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-white/15 px-2.5 py-1 text-xs font-medium text-white ring-1 ring-white/25 backdrop-blur-sm\">\n {Icon ? <Icon size={12} /> : null}\n {children}\n </span>\n );\n}\n\n/**\n * One curriculum row. Expands in place to reveal the lesson body.\n *\n * `TecLesson.content` is the only lesson body this service exposes to a\n * learner, and the API nulls it server-side whenever `isLocked` is true\n * (BOFF-4416), so the three states below are exhaustive and never guessed:\n * locked → upgrade note, content present → the body, content null on an\n * unlocked lesson → the author simply hasn't written it yet.\n */\nfunction LessonRow({ lesson, index, done }: { lesson: TecLesson; index: number; done: boolean }) {\n const [open, setOpen] = useState(false);\n const meta = KIND_META[lesson.kind] ?? KIND_META.ARTICLE!;\n const Icon = meta.icon;\n const bodyId = `tec-lesson-body-${lesson.id}`;\n\n return (\n <div className=\"border-b border-border-seam last:border-b-0\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n aria-expanded={open}\n aria-controls={bodyId}\n className=\"group flex w-full items-center gap-3 rounded-xl px-2 py-3 text-left transition-colors hover:bg-bg-sunken/60\"\n >\n <span className=\"hidden w-6 shrink-0 text-center text-sm font-medium tabular-nums text-text-muted sm:block\">\n {index + 1}\n </span>\n <span\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg',\n done ? 'bg-status-success-bg-subtle text-status-success-text' : meta.chip\n )}\n >\n {done ? <CheckCircle2 size={17} /> : <Icon size={17} />}\n </span>\n <div className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">\n {lesson.title}\n </span>\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n <span\n className={cn(\n 'inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] font-medium',\n meta.badge\n )}\n >\n {humanizeEnum(lesson.kind)}\n </span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Clock size={12} /> {formatDuration(lesson.estimatedMinutes)}\n </span>\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {done ? (\n <CheckCircle2 size={18} className=\"text-status-success-text\" />\n ) : lesson.isPreview ? (\n <span className=\"inline-flex items-center gap-1 rounded-full border border-accent-primary/30 bg-accent-primary/10 px-2 py-0.5 text-[11px] font-medium text-accent-primary\">\n <Eye size={12} /> Preview\n </span>\n ) : lesson.isLocked ? (\n // BOFF-4416: distinct from the plain \"not completed yet\" Lock icon\n // below — this lesson's content is gated behind a paid plan, not\n // just not-yet-watched. The API already nulled `content`/`file`.\n <span className=\"inline-flex items-center gap-1 rounded-full border border-status-warning-border/60 bg-status-warning-bg-subtle px-2 py-0.5 text-[11px] font-medium text-status-warning-text\">\n <Lock size={12} /> Upgrade to unlock\n </span>\n ) : (\n <Lock size={15} className=\"text-text-muted\" />\n )}\n <ChevronDown\n size={16}\n aria-hidden\n className={cn(\n 'text-text-muted transition-transform duration-200',\n open ? 'rotate-180' : ''\n )}\n />\n </div>\n </button>\n\n {open ? (\n <div id={bodyId} className=\"px-2 pb-4 sm:pl-11\">\n {lesson.isLocked ? (\n <UpgradeGate\n compact\n description=\"This lesson is part of a paid TechnoSpam plan — its content unlocks once your workspace upgrades.\"\n />\n ) : lesson.content ? (\n <div className=\"rounded-xl border border-border-seam bg-bg-sunken p-4\">\n <p className=\"whitespace-pre-wrap text-sm leading-relaxed text-text-secondary\">\n {lesson.content}\n </p>\n </div>\n ) : (\n <p className=\"rounded-xl border border-dashed border-border-seam px-4 py-3 text-sm text-text-muted\">\n No content published for this lesson yet.\n </p>\n )}\n </div>\n ) : null}\n </div>\n );\n}\n\n/**\n * Verifiable certificate face for an already-issued certificate. Every value\n * shown is a real field on the TecCertificate row (`serial`, `score`,\n * `issuedAt`, `status`) plus the course title it was issued against — the\n * learner name comes from the signed-in actor the shell handed us, and is\n * simply omitted when the shell did not supply one.\n */\nfunction CertificatePreview({\n certificate,\n learnerName,\n courseTitle,\n}: {\n certificate: TecCertificate;\n learnerName?: string;\n courseTitle: string;\n}) {\n const revoked = certificate.status === 'REVOKED';\n return (\n <GlassCard className=\"overflow-hidden p-0\">\n <div className=\"border-b border-border-seam px-5 py-3\">\n <span className=\"text-xs font-semibold uppercase tracking-wide text-text-muted\">\n Certificate preview\n </span>\n </div>\n <div className=\"space-y-4 bg-bg-sunken/50 px-5 py-6 text-center\">\n <div className=\"flex items-center justify-center gap-2 text-xs font-semibold uppercase tracking-[0.2em] text-text-muted\">\n <Award size={14} className=\"text-status-warning-text\" /> Certificate of completion\n </div>\n <h3 className=\"text-lg font-bold text-text-primary\">{courseTitle}</h3>\n {learnerName ? (\n <p className=\"text-sm text-text-secondary\">\n Awarded to <span className=\"font-semibold text-text-primary\">{learnerName}</span>\n </p>\n ) : null}\n <div className=\"grid grid-cols-1 gap-3 border-t border-border-seam pt-4 text-left sm:grid-cols-3\">\n <div>\n <div className=\"text-[11px] font-medium uppercase tracking-wide text-text-muted\">\n Serial number\n </div>\n <div className=\"truncate font-mono text-sm text-text-primary\">{certificate.serial}</div>\n </div>\n <div>\n <div className=\"text-[11px] font-medium uppercase tracking-wide text-text-muted\">\n Score\n </div>\n <div className=\"text-sm font-semibold text-text-primary\">{certificate.score}</div>\n </div>\n <div>\n <div className=\"text-[11px] font-medium uppercase tracking-wide text-text-muted\">\n Issued on\n </div>\n <div className=\"text-sm text-text-primary\">{formatDate(certificate.issuedAt)}</div>\n </div>\n </div>\n {revoked ? (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-left text-xs text-status-error-text\">\n This certificate was revoked\n {certificate.revokedAt ? ` on ${formatDate(certificate.revokedAt)}` : ''}\n {certificate.revokeReason ? ` — ${certificate.revokeReason}` : '.'}\n </div>\n ) : (\n <span className=\"inline-flex items-center gap-1 rounded-full border border-status-success-border bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text\">\n <ShieldCheck size={13} /> Verified\n </span>\n )}\n </div>\n </GlassCard>\n );\n}\n\nexport function CoursesCourseDetailsPagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n const { currentUser, workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const { data, loading, error, refetch } = useCourseQuery(id);\n\n useEffect(() => {\n if (id) {\n emit('technospam.course.viewed', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n }\n }, [id, workspaceId, emit]);\n\n const [enroll, { loading: enrolling }] = useEnroll();\n const [updateProgress, { loading: savingProgress }] = useUpdateProgress();\n // Self-scoped read: `tecMyCertificates` filters by the authenticated actor\n // server-side. `tecListCertificates` (the previous call) is gated behind the\n // cross-learner \"list-all\" action, which a plain learner does not hold — so\n // an already-claimed certificate came back empty and the page kept offering\n // \"Claim certificate\" instead of showing it.\n const { data: certData, refetch: refetchCerts } = useMyCertificatesQuery(\n id ? { courseId: id } : {}\n );\n const [issueCertificate, { loading: claiming }] = useIssueCertificate();\n\n const course = data?.tecCourse ?? null;\n const lessons = [...(course?.lessons ?? [])].sort((a, b) => a.position - b.position);\n const myEnrollment: TecEnrollment | undefined = (course?.enrollments ?? []).find(\n (e) => e.learnerId === currentUser?.id\n );\n const myCertificates = certData?.tecMyCertificates ?? [];\n const myCertificate = myCertificates.find((c) => c.status === 'ISSUED') ?? myCertificates[0];\n const isComplete = !!myEnrollment && myEnrollment.progressPct >= 100;\n\n const handleClaimCertificate = async () => {\n if (!id || !currentUser?.id) return;\n emit('technospam.course.certificate_claimed', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n const res = await issueCertificate({\n variables: { courseId: id, learnerId: currentUser.id, score: 100 },\n });\n if (res.data?.tecIssueCertificate) {\n notify.success('Certificate issued', 'Your verifiable certificate is ready in My Learning.');\n await refetchCerts();\n }\n };\n\n const handleEnroll = async () => {\n if (!id) return;\n emit('technospam.course.enroll_clicked', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n const res = await enroll({ variables: { input: { courseId: id } } });\n if (res.data?.tecEnroll) {\n emit('technospam.course.enrolled', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_COURSE',\n });\n notify.success('Enrolled', `You're enrolled in \"${course?.title}\".`);\n await refetch();\n }\n };\n\n const handleProgress = async () => {\n if (!myEnrollment) return;\n const nextCompleted = Math.min(lessons.length, myEnrollment.lessonsCompleted + 1);\n const pct = lessons.length ? Math.round((nextCompleted / lessons.length) * 100) : 100;\n const res = await updateProgress({\n variables: {\n input: { enrollmentId: myEnrollment.id, lessonsCompleted: nextCompleted, progressPct: pct },\n },\n });\n if (res.data?.tecUpdateProgress) {\n emit('technospam.course.progress_updated', {\n workspaceId,\n entityId: myEnrollment.id,\n entityType: 'TEC_ENROLLMENT',\n progressPct: pct,\n });\n notify.success('Progress saved', `${pct}% complete`);\n await refetch();\n }\n };\n\n if (loading && !course) {\n return (\n <PageLayout title=\"Course\" backPath=\"/courses/courses-page\">\n <Skeleton className=\"mb-4 h-40 w-full rounded-2xl\" />\n <Skeleton className=\"h-64 w-full rounded-xl\" />\n </PageLayout>\n );\n }\n\n if (error || !course) {\n return (\n <PageLayout title=\"Course\" backPath=\"/courses/courses-page\">\n {error ? (\n <ErrorView title=\"Couldn't load course\" error={error} />\n ) : (\n <EmptyView\n title=\"Course not found\"\n description=\"This course may have been unpublished.\"\n />\n )}\n </PageLayout>\n );\n }\n\n const completedLessons = myEnrollment?.lessonsCompleted ?? 0;\n const remaining = Math.max(0, lessons.length - completedLessons);\n const milestone =\n myEnrollment && myEnrollment.progressPct >= 100\n ? 'Course complete — claim your certificate below.'\n : remaining === 1\n ? '1 lesson to go — almost there!'\n : `${remaining} lessons to go — keep it up!`;\n const heroSeed = `${course.topic ?? ''} ${course.title}`;\n\n return (\n <PageLayout\n backPath=\"/courses/courses-page\"\n title={course.title}\n description={course.summary ?? undefined}\n actions={\n myEnrollment ? (\n <Button\n onClick={handleProgress}\n disabled={savingProgress || completedLessons >= lessons.length}\n >\n <CheckCircle2 size={16} />\n {completedLessons >= lessons.length ? 'Completed' : 'Complete next lesson'}\n </Button>\n ) : (\n <Button onClick={handleEnroll} disabled={enrolling}>\n <BookOpen size={16} /> {enrolling ? 'Enrolling…' : 'Enroll'}\n </Button>\n )\n }\n >\n {/* Bold hero banner */}\n <MediaCover\n height=\"h-40\"\n seed={heroSeed}\n icon={topicIcon(course.topic ?? course.title)}\n className=\"mb-5 rounded-2xl\"\n >\n <div className=\"flex w-full flex-col gap-2\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-white/80\">\n {course.topic ?? 'Course'}\n </span>\n <h2 className=\"text-2xl font-bold leading-tight text-white drop-shadow-sm sm:text-3xl\">\n {course.title}\n </h2>\n <div className=\"flex flex-wrap items-center gap-2\">\n <HeroBadge icon={TrendingUp}>{humanizeEnum(course.level)}</HeroBadge>\n <HeroBadge icon={CheckCircle2}>{humanizeEnum(course.status)}</HeroBadge>\n {course.topic ? <HeroBadge icon={Layers}>{course.topic}</HeroBadge> : null}\n {course.isPremiumOnly ? <HeroBadge icon={Lock}>Premium</HeroBadge> : null}\n </div>\n </div>\n </MediaCover>\n\n <TagStrip tags={course.tags} className=\"mb-5\" />\n\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard label=\"Lessons\" value={lessons.length} icon={FileText} tone=\"blue\" />\n <StatCard\n label=\"Duration\"\n value={formatDuration(course.estimatedMinutes)}\n icon={Clock}\n tone=\"teal\"\n />\n <StatCard\n label=\"Enrolled\"\n value={course.enrollments?.length ?? 0}\n icon={Users}\n tone=\"indigo\"\n />\n <StatCard\n label=\"Your progress\"\n value={myEnrollment ? `${myEnrollment.progressPct}%` : 'Not started'}\n icon={TrendingUp}\n tone=\"purple\"\n />\n </div>\n\n {myEnrollment ? (\n <GlassCard className=\"mb-6 p-5\">\n <div className=\"mb-3 flex items-center gap-2\">\n <span className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-accent-primary/15 text-accent-primary\">\n <TrendingUp size={16} />\n </span>\n <span className=\"text-sm font-semibold text-text-primary\">Your progress</span>\n <span className=\"ml-auto text-sm text-text-secondary\">\n {completedLessons}/{lessons.length} lessons · {myEnrollment.progressPct}%\n </span>\n </div>\n <div className=\"h-2.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: `${myEnrollment.progressPct}%` }}\n />\n </div>\n <p className=\"mt-2 text-xs text-text-muted\">{milestone}</p>\n </GlassCard>\n ) : null}\n\n {myCertificate ? (\n <div className=\"mb-6 grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <GlassCard\n className={cn(\n 'flex items-center gap-4 border p-5',\n myCertificate.status === 'REVOKED'\n ? 'border-status-error-border/60 bg-status-error-bg-subtle/20'\n : 'border-status-success-border/60 bg-status-success-bg-subtle/30'\n )}\n >\n <span className=\"flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-status-warning-bg-subtle text-status-warning-text ring-1 ring-status-warning-border/40\">\n <Trophy size={26} />\n </span>\n <div className=\"min-w-0\">\n <div className=\"text-base font-semibold text-text-primary\">\n {myCertificate.status === 'REVOKED'\n ? 'Your certificate was revoked'\n : 'You finished this course'}\n </div>\n <div className=\"text-sm text-text-muted\">\n {myCertificate.status === 'REVOKED'\n ? 'Contact a workspace admin if you believe this was a mistake.'\n : 'Your verifiable certificate of completion is issued — it also appears in My Learning.'}\n </div>\n </div>\n </GlassCard>\n <CertificatePreview\n certificate={myCertificate}\n learnerName={currentUser?.name ?? undefined}\n courseTitle={myCertificate.course?.title ?? course.title}\n />\n </div>\n ) : isComplete ? (\n <GlassCard className=\"mb-6 flex flex-col items-start gap-4 border border-status-success-border/60 bg-status-success-bg-subtle/40 p-5 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-4\">\n <span className=\"flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-status-warning-bg-subtle text-status-warning-text ring-1 ring-status-warning-border/40\">\n <Trophy size={26} />\n </span>\n <div>\n <div className=\"text-base font-semibold text-text-primary\">\n You finished this course\n </div>\n <div className=\"text-sm text-text-muted\">\n Claim your verifiable certificate of completion.\n </div>\n </div>\n </div>\n <Button onClick={handleClaimCertificate} disabled={claiming}>\n <Award size={16} /> {claiming ? 'Issuing…' : 'Claim certificate'}\n </Button>\n </GlassCard>\n ) : null}\n\n <GlassCard className=\"p-5\">\n <div className=\"mb-3 flex items-center justify-between gap-2\">\n <h2 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <BookOpen size={18} className=\"text-accent-primary\" /> Curriculum\n {course.isPremiumOnly ? <PremiumBadge locked={course.isLocked} /> : null}\n </h2>\n {lessons.length ? (\n <span className=\"rounded-full bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text\">\n {completedLessons}/{lessons.length} completed\n </span>\n ) : null}\n </div>\n {course.isLocked ? (\n <div className=\"mb-4\">\n <UpgradeGate\n compact\n description=\"This course is on a paid TechnoSpam plan. Preview lessons below stay free — the rest unlock once your workspace upgrades.\"\n />\n </div>\n ) : null}\n {lessons.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-muted\">\n Lessons for this course are being prepared.\n </p>\n ) : (\n <div className=\"flex flex-col\">\n {lessons.map((lesson, i) => (\n <LessonRow key={lesson.id} lesson={lesson} index={i} done={i < completedLessons} />\n ))}\n </div>\n )}\n </GlassCard>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CoursesMyLearningPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/CoursesMyLearningPagePage.tsx"],"names":[],"mappings":"AAyEA,wBAAgB,yBAAyB,gCA0HxC"}
1
+ {"version":3,"file":"CoursesMyLearningPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/CoursesMyLearningPagePage.tsx"],"names":[],"mappings":"AAyEA,wBAAgB,yBAAyB,gCA6HxC"}
@@ -7,7 +7,7 @@ import { formatDate as s, relativeTime as c } from "../utils/format.js";
7
7
  import { StatusBadge as l } from "../components/StatusBadge.js";
8
8
  import { EmptyView as u, ErrorView as d, LoadingRows as f } from "../components/StateViews.js";
9
9
  import "../components/index.js";
10
- import { useCertificatesQuery as p, useEnrollmentsQuery as m } from "../api/technospamApi.js";
10
+ import { useEnrollmentsQuery as p, useMyCertificatesQuery as m } from "../api/technospamApi.js";
11
11
  import { createElement as h } from "react";
12
12
  import { Award as g, BookOpen as _, GraduationCap as v, PlayCircle as y, ShieldCheck as b } from "lucide-react";
13
13
  import { jsx as x, jsxs as S } from "react/jsx-runtime";
@@ -71,7 +71,7 @@ function T({ e, onOpen: t }) {
71
71
  });
72
72
  }
73
73
  function E() {
74
- let n = t(), { currentUser: a } = e(), o = a?.id, { data: c, loading: h, error: E } = m(o ? { learnerId: o } : {}), { data: D } = p(o ? { learnerId: o } : {}), O = c?.tecListEnrollments.items ?? [], k = D?.tecListCertificates ?? [], A = O.filter((e) => e.status === "COMPLETED").length, j = O.filter((e) => e.status === "IN_PROGRESS" || e.status === "ENROLLED").length;
74
+ let n = t(), { currentUser: a } = e(), o = a?.id, { data: c, loading: h, error: E } = p(o ? { learnerId: o } : {}), { data: D } = m(), O = c?.tecListEnrollments.items ?? [], k = D?.tecMyCertificates ?? [], A = O.filter((e) => e.status === "COMPLETED").length, j = O.filter((e) => e.status === "IN_PROGRESS" || e.status === "ENROLLED").length;
75
75
  return /* @__PURE__ */ S(r, {
76
76
  hero: !0,
77
77
  title: "My Learning",
@@ -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 EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n coverGradient,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useEnrollmentsQuery,\n useCertificatesQuery,\n type TecEnrollment,\n} from '../api/technospamApi';\nimport { formatDate, relativeTime } from '../utils/format';\nimport { cn } from '../utils/cn';\n\nfunction EnrollmentRow({ e, onOpen }: { e: TecEnrollment; onOpen: () => void }) {\n const seed = `${e.course?.topic ?? ''} ${e.course?.title ?? 'Course'}`;\n return (\n <GlassCard\n className=\"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 onClick={onOpen}\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 <span className=\"truncate text-sm font-semibold text-text-primary group-hover:text-accent-primary\">\n {e.course?.title ?? 'Course'}\n </span>\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\n variant=\"ghost\"\n size=\"sm\"\n onClick={(ev) => {\n ev.stopPropagation();\n onOpen();\n }}\n >\n <PlayCircle size={15} /> Resume\n </Button>\n </div>\n </GlassCard>\n );\n}\n\nexport function CoursesMyLearningPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n\n const { data, loading, error } = useEnrollmentsQuery(learnerId ? { learnerId } : {});\n const { data: certData } = useCertificatesQuery(learnerId ? { learnerId } : {});\n\n const enrollments = data?.tecListEnrollments.items ?? [];\n const certificates = certData?.tecListCertificates ?? [];\n const completed = enrollments.filter((e) => e.status === 'COMPLETED').length;\n const inProgress = enrollments.filter(\n (e) => e.status === 'IN_PROGRESS' || e.status === 'ENROLLED'\n ).length;\n\n return (\n <PageLayout\n hero\n title=\"My Learning\"\n description=\"Track your enrolled courses, progress, and earned certificates.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/courses-page')}>\n <BookOpen size={16} /> Browse catalog\n </Button>\n }\n >\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard\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(`/courses/course-details-page?id=${e.courseId}`)}\n />\n ))}\n </div>\n )}\n\n {certificates.length > 0 ? (\n <div className=\"mt-8\">\n <h2 className=\"mb-3 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=\"group flex flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-1 hover:shadow-lg\"\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 <div className=\"truncate text-sm font-medium text-text-primary\">\n {c.course?.title ?? 'Certificate'}\n </div>\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 </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 EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n coverGradient,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useEnrollmentsQuery,\n useMyCertificatesQuery,\n type TecEnrollment,\n} from '../api/technospamApi';\nimport { formatDate, relativeTime } from '../utils/format';\nimport { cn } from '../utils/cn';\n\nfunction EnrollmentRow({ e, onOpen }: { e: TecEnrollment; onOpen: () => void }) {\n const seed = `${e.course?.topic ?? ''} ${e.course?.title ?? 'Course'}`;\n return (\n <GlassCard\n className=\"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 onClick={onOpen}\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 <span className=\"truncate text-sm font-semibold text-text-primary group-hover:text-accent-primary\">\n {e.course?.title ?? 'Course'}\n </span>\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\n variant=\"ghost\"\n size=\"sm\"\n onClick={(ev) => {\n ev.stopPropagation();\n onOpen();\n }}\n >\n <PlayCircle size={15} /> Resume\n </Button>\n </div>\n </GlassCard>\n );\n}\n\nexport function CoursesMyLearningPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n\n const { data, loading, error } = useEnrollmentsQuery(learnerId ? { learnerId } : {});\n // Self-scoped read — `tecListCertificates` needs the cross-learner\n // \"list-all\" action a plain learner does not hold, so their own earned\n // certificates never arrived here.\n const { data: certData } = useMyCertificatesQuery();\n\n const enrollments = data?.tecListEnrollments.items ?? [];\n const certificates = certData?.tecMyCertificates ?? [];\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(`/courses/course-details-page?id=${e.courseId}`)}\n />\n ))}\n </div>\n )}\n\n {certificates.length > 0 ? (\n <div className=\"mt-8\">\n <h2 className=\"mb-3 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=\"group flex flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-1 hover:shadow-lg\"\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 <div className=\"truncate text-sm font-medium text-text-primary\">\n {c.course?.title ?? 'Certificate'}\n </div>\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 </GlassCard>\n ))}\n </div>\n </div>\n ) : null}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -10,7 +10,7 @@ import { notify as l } from "../lib/toast.js";
10
10
  import { useHackathonQuery as ne, useRegisterTeam as re, useSubmitProject as ie, useTeamsQuery as ae } from "../api/technospamApi.js";
11
11
  import { useState as u } from "react";
12
12
  import { Award as oe, CalendarClock as se, CalendarDays as d, Code2 as f, ExternalLink as p, Flag as m, Github as h, Send as g, Trophy as _, UserPlus as v, Users as y } from "lucide-react";
13
- import { useSearchParams as ce } from "react-router-dom";
13
+ import { useSearchParams as ce } from "react-router";
14
14
  import { jsx as b, jsxs as x } from "react/jsx-runtime";
15
15
  import { Badge as S, Button as C, GlassCard as w, Input as T, Select as E, SelectContent as D, SelectItem as O, SelectTrigger as le, SelectValue as ue, Skeleton as k, Textarea as de } from "@burdenoff/fe-libs/ui";
16
16
  //#region src/technospam/pages/HackathonsHackathonDetailsPagePage.tsx
@@ -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-dom';\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';\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({ team, isMine }: { team: TecHackathonTeam; isMine: boolean }) {\n const isWinner = team.status === 'WINNER';\n return (\n <GlassCard className=\"group flex flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-1 hover:shadow-lg\">\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 <span className=\"truncate text-sm font-semibold text-text-primary\">{team.name}</span>\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\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-muted\">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-muted\">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-muted\">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&apos;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 />\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';\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({ team, isMine }: { team: TecHackathonTeam; isMine: boolean }) {\n const isWinner = team.status === 'WINNER';\n return (\n <GlassCard className=\"group flex flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-1 hover:shadow-lg\">\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 <span className=\"truncate text-sm font-semibold text-text-primary\">{team.name}</span>\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\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-muted\">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-muted\">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-muted\">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&apos;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 />\n ))}\n </div>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -12,7 +12,7 @@ import { notify as p } from "../lib/toast.js";
12
12
  import { useBookSession as m, useMentorQuery as h, useSessionCostEstimate as g } from "../api/technospamApi.js";
13
13
  import { createElement as _, useState as v } from "react";
14
14
  import { Award as y, CalendarClock as b, CreditCard as x, DollarSign as S, MessageSquare as C, Star as w, Video as T } from "lucide-react";
15
- import { useSearchParams as E } from "react-router-dom";
15
+ import { useSearchParams as E } from "react-router";
16
16
  import { Fragment as D, jsx as O, jsxs as k } from "react/jsx-runtime";
17
17
  import { Badge as A, Button as j, GlassCard as M, Input as N, Skeleton as P, Textarea as F } from "@burdenoff/fe-libs/ui";
18
18
  //#region src/technospam/pages/MentorshipMentorProfilePagePage.tsx
@@ -106,8 +106,7 @@ function I({ mentorId: e, disabled: n, onBooked: r, workspaceId: i }) {
106
106
  (L.platformFeeBps / 100).toFixed(0),
107
107
  "% platform fee (",
108
108
  l(L.platformFeeAmount, L.currency),
109
- ") — the mentor keeps",
110
- " ",
109
+ ") — the mentor keeps ",
111
110
  l(L.netAmount, L.currency),
112
111
  "."
113
112
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"MentorshipMentorProfilePagePage.js","names":[],"sources":["../../../src/technospam/pages/MentorshipMentorProfilePagePage.tsx"],"sourcesContent":["import { createElement, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport {\n Award,\n CalendarClock,\n CreditCard,\n DollarSign,\n MessageSquare,\n Star,\n Video,\n} from 'lucide-react';\nimport { Badge, Button, GlassCard, Input, Skeleton, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n PageLayout,\n StatCard,\n StatusBadge,\n coverGradient,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n useBookSession,\n useMentorQuery,\n useSessionCostEstimate,\n type TecMentorSession,\n} from '../api/technospamApi';\nimport { formatDateTime, formatDuration, formatMoneyFromMinorUnits } from '../utils/format';\nimport { cn } from '../utils/cn';\nimport { notify } from '../lib/toast';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\nfunction BookSessionForm({\n mentorId,\n disabled,\n onBooked,\n workspaceId,\n}: {\n mentorId: string;\n disabled: boolean;\n onBooked?: () => void;\n workspaceId?: string;\n}) {\n const [topic, setTopic] = useState('');\n const [agenda, setAgenda] = useState('');\n const [scheduledAt, setScheduledAt] = useState('');\n const [durationMinutes, setDurationMinutes] = useState('60');\n const [bookSession, { loading }] = useBookSession();\n const { emit } = useTechnoSpamAnalytics();\n\n const parsedDate = scheduledAt ? new Date(scheduledAt) : null;\n const validDate = parsedDate != null && !Number.isNaN(parsedDate.getTime());\n const canSubmit = topic.trim().length > 0 && validDate && !loading && !disabled;\n\n // Live cost preview -- computed server-side from the mentor's real rate,\n // the exact same calculation tecBookSession snapshots onto the session.\n // Shown BEFORE booking so the mentee sees the real price up front.\n const parsedDuration = Number.parseInt(durationMinutes, 10);\n const { data: costData, loading: costLoading } = useSessionCostEstimate(\n mentorId,\n Number.isNaN(parsedDuration) ? 0 : parsedDuration\n );\n const estimate = costData?.tecCalculateSessionCost;\n\n const handleSubmit = async () => {\n if (!canSubmit || !parsedDate) return;\n const input: {\n mentorId: string;\n topic: string;\n agenda?: string;\n scheduledAt: string;\n durationMinutes?: number;\n } = {\n mentorId,\n topic: topic.trim(),\n scheduledAt: parsedDate.toISOString(),\n };\n if (agenda.trim()) input.agenda = agenda.trim();\n const dur = Number.parseInt(durationMinutes, 10);\n if (!Number.isNaN(dur) && dur > 0) input.durationMinutes = dur;\n\n emit('technospam.mentor.book_clicked', {\n workspaceId,\n entityId: mentorId,\n entityType: 'TEC_MENTOR',\n });\n const res = await bookSession({ variables: { input } });\n if (res.data?.tecBookSession) {\n emit('technospam.mentor.booked', {\n workspaceId,\n entityId: mentorId,\n entityType: 'TEC_MENTOR',\n sessionId: res.data.tecBookSession.id,\n });\n notify.success('Session requested', 'Your mentor will confirm the booking soon.');\n setTopic('');\n setAgenda('');\n setScheduledAt('');\n setDurationMinutes('60');\n onBooked?.();\n }\n };\n\n return (\n <GlassCard className=\"p-5\">\n <div className=\"mb-4 flex items-start gap-3\">\n <span 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-white shadow-sm\">\n <CalendarClock size={20} />\n </span>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Book a session</h2>\n <p className=\"text-sm text-text-secondary\">Schedule 1:1 time with your mentor.</p>\n </div>\n </div>\n\n {disabled ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n This mentor isn&apos;t accepting new mentees right now.\n </p>\n ) : null}\n\n <div className=\"mt-4 grid grid-cols-1 gap-4\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Topic <span className=\"text-status-error-text\">*</span>\n </label>\n <Input\n placeholder=\"e.g. System design interview prep\"\n value={topic}\n onChange={(e) => setTopic(e.target.value)}\n disabled={disabled}\n />\n </div>\n\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Agenda</label>\n <Textarea\n rows={3}\n placeholder=\"What would you like to cover? Share context so your mentor can prepare.\"\n value={agenda}\n onChange={(e) => setAgenda(e.target.value)}\n disabled={disabled}\n />\n </div>\n\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-primary\">\n When <span className=\"text-status-error-text\">*</span>\n </label>\n <Input\n type=\"datetime-local\"\n value={scheduledAt}\n onChange={(e) => setScheduledAt(e.target.value)}\n disabled={disabled}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Duration (min)\n </label>\n <Input\n type=\"number\"\n min={15}\n step={15}\n value={durationMinutes}\n onChange={(e) => setDurationMinutes(e.target.value)}\n disabled={disabled}\n />\n </div>\n </div>\n\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2.5\">\n {costLoading && !estimate ? (\n <Skeleton className=\"h-5 w-40 rounded\" />\n ) : estimate ? (\n <>\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">Session cost</span>\n <span className=\"font-semibold text-text-primary\">\n {formatMoneyFromMinorUnits(estimate.costAmount, estimate.currency)}\n </span>\n </div>\n <p className=\"mt-0.5 text-xs text-text-muted\">\n Includes a {(estimate.platformFeeBps / 100).toFixed(0)}% platform fee (\n {formatMoneyFromMinorUnits(estimate.platformFeeAmount, estimate.currency)}) — the mentor keeps{' '}\n {formatMoneyFromMinorUnits(estimate.netAmount, estimate.currency)}.\n </p>\n </>\n ) : (\n <p className=\"text-sm text-text-muted\">Enter a duration to preview the session cost.</p>\n )}\n </div>\n\n <Button\n className=\"w-full bg-gradient-to-r from-accent-primary to-accent-purple\"\n onClick={handleSubmit}\n disabled={!canSubmit}\n >\n <CalendarClock size={16} /> {loading ? 'Requesting…' : 'Request session'}\n </Button>\n\n <div className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam px-3 py-2\">\n <span className=\"flex items-center gap-1.5 text-xs text-text-muted\">\n <CreditCard size={14} /> Payment integration pending\n </span>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled\n title=\"Online payment collection is not live yet\"\n >\n Pay now\n </Button>\n </div>\n </div>\n </GlassCard>\n );\n}\n\nfunction SessionSummaryRow({ session }: { session: TecMentorSession }) {\n return (\n <div className=\"flex items-center gap-3 rounded-lg px-1 py-3 transition-colors hover:bg-bg-sunken\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-accent-purple/12 text-accent-purple\">\n <Video size={16} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-sm font-medium text-text-primary\">{session.topic}</div>\n <div className=\"text-xs text-text-muted\">\n {formatDateTime(session.scheduledAt)} · {formatDuration(session.durationMinutes)}\n </div>\n </div>\n <StatusBadge status={session.status} />\n </div>\n );\n}\n\nexport function MentorshipMentorProfilePagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n const { workspaceId } = useTechnoSpam();\n const { data, loading, error, refetch } = useMentorQuery(id);\n\n const mentor = data?.tecMentor ?? null;\n\n if (loading && !mentor) {\n return (\n <PageLayout title=\"Mentor\" backPath=\"/mentorship/mentors-page\">\n <Skeleton className=\"mb-4 h-40 w-full rounded-xl\" />\n <Skeleton className=\"h-64 w-full rounded-xl\" />\n </PageLayout>\n );\n }\n\n if (error || !mentor) {\n return (\n <PageLayout title=\"Mentor\" backPath=\"/mentorship/mentors-page\">\n {error ? (\n <ErrorView title=\"Couldn't load mentor\" error={error} />\n ) : (\n <EmptyView\n title=\"Mentor not found\"\n description=\"This mentor profile may no longer be available.\"\n />\n )}\n </PageLayout>\n );\n }\n\n const sessions = mentor.sessions ?? [];\n const recentSessions = [...sessions]\n .sort((a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime())\n .slice(0, 5);\n\n const HeroIcon = topicIcon(mentor.expertise.join(' ') || mentor.headline);\n\n return (\n <PageLayout hero backPath=\"/mentorship/mentors-page\" title=\"Mentor Profile\">\n <GlassCard className=\"mb-6 overflow-hidden p-0\">\n <div\n className={cn(\n 'relative h-24 overflow-hidden bg-gradient-to-br',\n coverGradient(mentor.headline)\n )}\n >\n {createElement(HeroIcon, {\n className: 'pointer-events-none absolute -right-3 -top-4 text-white/15',\n size: 120,\n strokeWidth: 1.3,\n })}\n <div className=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-black/25 to-transparent\" />\n </div>\n <div className=\"px-6 pb-6\">\n <div className=\"-mt-10 flex flex-col gap-4 sm:flex-row sm:items-end\">\n <div className=\"relative shrink-0\">\n <span\n className={cn(\n 'flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br text-white shadow-lg ring-4 ring-bg-surface',\n coverGradient(mentor.headline)\n )}\n >\n {createElement(HeroIcon, { size: 34, strokeWidth: 1.6 })}\n </span>\n {mentor.isAcceptingMentees ? (\n <span className=\"absolute bottom-1 right-1 h-4 w-4 rounded-full border-2 border-bg-surface bg-status-success-text\" />\n ) : null}\n </div>\n <div className=\"min-w-0 flex-1 sm:pb-1\">\n <h1 className=\"text-xl font-semibold text-text-primary sm:text-2xl\">\n {mentor.headline}\n </h1>\n {mentor.role ? (\n <p className=\"mt-1 text-sm font-medium text-text-secondary\">{mentor.role}</p>\n ) : null}\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <StatusBadge status={mentor.status} />\n {mentor.isAcceptingMentees ? (\n <Badge\n variant=\"outline\"\n className=\"inline-flex items-center gap-1.5 border-status-success-border bg-status-success-bg-subtle text-status-success-text\"\n >\n <span className=\"h-1.5 w-1.5 rounded-full bg-status-success-text\" /> Accepting\n mentees\n </Badge>\n ) : null}\n </div>\n </div>\n </div>\n\n {mentor.bio ? (\n <p className=\"mt-4 max-w-2xl text-sm leading-relaxed text-text-secondary\">\n {mentor.bio}\n </p>\n ) : null}\n\n {mentor.expertise.length ? (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {mentor.expertise.map((skill) => (\n <Badge key={skill} variant=\"secondary\">\n {skill}\n </Badge>\n ))}\n </div>\n ) : null}\n </div>\n </GlassCard>\n\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard\n label=\"Rate\"\n value={`${formatMoneyFromMinorUnits(mentor.hourlyRate, mentor.currency)}/hr`}\n icon={DollarSign}\n tone=\"blue\"\n />\n <StatCard\n label=\"Rating\"\n value={mentor.ratingAvg > 0 ? mentor.ratingAvg.toFixed(1) : 'New'}\n icon={Star}\n tone=\"amber\"\n />\n <StatCard\n label=\"Sessions\"\n value={mentor.sessionsCount}\n icon={MessageSquare}\n tone=\"purple\"\n />\n <StatCard\n label=\"Status\"\n value={mentor.status === 'ACTIVE' ? 'Active' : 'Pending'}\n icon={Award}\n tone=\"teal\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <BookSessionForm\n mentorId={mentor.id}\n disabled={!mentor.isAcceptingMentees}\n onBooked={() => void refetch()}\n workspaceId={workspaceId}\n />\n\n <GlassCard className=\"p-5\">\n <div className=\"mb-3 flex items-center gap-2\">\n <CalendarClock size={18} className=\"text-accent-teal\" />\n <h2 className=\"text-base font-semibold text-text-primary\">Recent sessions</h2>\n </div>\n {recentSessions.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-muted\">\n No sessions booked yet. Be the first to request time with this mentor.\n </p>\n ) : (\n <div className=\"divide-y divide-border-seam\">\n {recentSessions.map((session) => (\n <SessionSummaryRow key={session.id} session={session} />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MentorshipMentorProfilePagePage.js","names":[],"sources":["../../../src/technospam/pages/MentorshipMentorProfilePagePage.tsx"],"sourcesContent":["import { createElement, useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport {\n Award,\n CalendarClock,\n CreditCard,\n DollarSign,\n MessageSquare,\n Star,\n Video,\n} from 'lucide-react';\nimport { Badge, Button, GlassCard, Input, Skeleton, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n PageLayout,\n StatCard,\n StatusBadge,\n coverGradient,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n useBookSession,\n useMentorQuery,\n useSessionCostEstimate,\n type TecMentorSession,\n} from '../api/technospamApi';\nimport { formatDateTime, formatDuration, formatMoneyFromMinorUnits } from '../utils/format';\nimport { cn } from '../utils/cn';\nimport { notify } from '../lib/toast';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\nfunction BookSessionForm({\n mentorId,\n disabled,\n onBooked,\n workspaceId,\n}: {\n mentorId: string;\n disabled: boolean;\n onBooked?: () => void;\n workspaceId?: string;\n}) {\n const [topic, setTopic] = useState('');\n const [agenda, setAgenda] = useState('');\n const [scheduledAt, setScheduledAt] = useState('');\n const [durationMinutes, setDurationMinutes] = useState('60');\n const [bookSession, { loading }] = useBookSession();\n const { emit } = useTechnoSpamAnalytics();\n\n const parsedDate = scheduledAt ? new Date(scheduledAt) : null;\n const validDate = parsedDate != null && !Number.isNaN(parsedDate.getTime());\n const canSubmit = topic.trim().length > 0 && validDate && !loading && !disabled;\n\n // Live cost preview -- computed server-side from the mentor's real rate,\n // the exact same calculation tecBookSession snapshots onto the session.\n // Shown BEFORE booking so the mentee sees the real price up front.\n const parsedDuration = Number.parseInt(durationMinutes, 10);\n const { data: costData, loading: costLoading } = useSessionCostEstimate(\n mentorId,\n Number.isNaN(parsedDuration) ? 0 : parsedDuration\n );\n const estimate = costData?.tecCalculateSessionCost;\n\n const handleSubmit = async () => {\n if (!canSubmit || !parsedDate) return;\n const input: {\n mentorId: string;\n topic: string;\n agenda?: string;\n scheduledAt: string;\n durationMinutes?: number;\n } = {\n mentorId,\n topic: topic.trim(),\n scheduledAt: parsedDate.toISOString(),\n };\n if (agenda.trim()) input.agenda = agenda.trim();\n const dur = Number.parseInt(durationMinutes, 10);\n if (!Number.isNaN(dur) && dur > 0) input.durationMinutes = dur;\n\n emit('technospam.mentor.book_clicked', {\n workspaceId,\n entityId: mentorId,\n entityType: 'TEC_MENTOR',\n });\n const res = await bookSession({ variables: { input } });\n if (res.data?.tecBookSession) {\n emit('technospam.mentor.booked', {\n workspaceId,\n entityId: mentorId,\n entityType: 'TEC_MENTOR',\n sessionId: res.data.tecBookSession.id,\n });\n notify.success('Session requested', 'Your mentor will confirm the booking soon.');\n setTopic('');\n setAgenda('');\n setScheduledAt('');\n setDurationMinutes('60');\n onBooked?.();\n }\n };\n\n return (\n <GlassCard className=\"p-5\">\n <div className=\"mb-4 flex items-start gap-3\">\n <span 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-white shadow-sm\">\n <CalendarClock size={20} />\n </span>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Book a session</h2>\n <p className=\"text-sm text-text-secondary\">Schedule 1:1 time with your mentor.</p>\n </div>\n </div>\n\n {disabled ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n This mentor isn&apos;t accepting new mentees right now.\n </p>\n ) : null}\n\n <div className=\"mt-4 grid grid-cols-1 gap-4\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Topic <span className=\"text-status-error-text\">*</span>\n </label>\n <Input\n placeholder=\"e.g. System design interview prep\"\n value={topic}\n onChange={(e) => setTopic(e.target.value)}\n disabled={disabled}\n />\n </div>\n\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Agenda</label>\n <Textarea\n rows={3}\n placeholder=\"What would you like to cover? Share context so your mentor can prepare.\"\n value={agenda}\n onChange={(e) => setAgenda(e.target.value)}\n disabled={disabled}\n />\n </div>\n\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-primary\">\n When <span className=\"text-status-error-text\">*</span>\n </label>\n <Input\n type=\"datetime-local\"\n value={scheduledAt}\n onChange={(e) => setScheduledAt(e.target.value)}\n disabled={disabled}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Duration (min)\n </label>\n <Input\n type=\"number\"\n min={15}\n step={15}\n value={durationMinutes}\n onChange={(e) => setDurationMinutes(e.target.value)}\n disabled={disabled}\n />\n </div>\n </div>\n\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2.5\">\n {costLoading && !estimate ? (\n <Skeleton className=\"h-5 w-40 rounded\" />\n ) : estimate ? (\n <>\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">Session cost</span>\n <span className=\"font-semibold text-text-primary\">\n {formatMoneyFromMinorUnits(estimate.costAmount, estimate.currency)}\n </span>\n </div>\n <p className=\"mt-0.5 text-xs text-text-muted\">\n Includes a {(estimate.platformFeeBps / 100).toFixed(0)}% platform fee (\n {formatMoneyFromMinorUnits(estimate.platformFeeAmount, estimate.currency)}) — the\n mentor keeps {formatMoneyFromMinorUnits(estimate.netAmount, estimate.currency)}.\n </p>\n </>\n ) : (\n <p className=\"text-sm text-text-muted\">Enter a duration to preview the session cost.</p>\n )}\n </div>\n\n <Button\n className=\"w-full bg-gradient-to-r from-accent-primary to-accent-purple\"\n onClick={handleSubmit}\n disabled={!canSubmit}\n >\n <CalendarClock size={16} /> {loading ? 'Requesting…' : 'Request session'}\n </Button>\n\n <div className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam px-3 py-2\">\n <span className=\"flex items-center gap-1.5 text-xs text-text-muted\">\n <CreditCard size={14} /> Payment integration pending\n </span>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled\n title=\"Online payment collection is not live yet\"\n >\n Pay now\n </Button>\n </div>\n </div>\n </GlassCard>\n );\n}\n\nfunction SessionSummaryRow({ session }: { session: TecMentorSession }) {\n return (\n <div className=\"flex items-center gap-3 rounded-lg px-1 py-3 transition-colors hover:bg-bg-sunken\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-accent-purple/12 text-accent-purple\">\n <Video size={16} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-sm font-medium text-text-primary\">{session.topic}</div>\n <div className=\"text-xs text-text-muted\">\n {formatDateTime(session.scheduledAt)} · {formatDuration(session.durationMinutes)}\n </div>\n </div>\n <StatusBadge status={session.status} />\n </div>\n );\n}\n\nexport function MentorshipMentorProfilePagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n const { workspaceId } = useTechnoSpam();\n const { data, loading, error, refetch } = useMentorQuery(id);\n\n const mentor = data?.tecMentor ?? null;\n\n if (loading && !mentor) {\n return (\n <PageLayout title=\"Mentor\" backPath=\"/mentorship/mentors-page\">\n <Skeleton className=\"mb-4 h-40 w-full rounded-xl\" />\n <Skeleton className=\"h-64 w-full rounded-xl\" />\n </PageLayout>\n );\n }\n\n if (error || !mentor) {\n return (\n <PageLayout title=\"Mentor\" backPath=\"/mentorship/mentors-page\">\n {error ? (\n <ErrorView title=\"Couldn't load mentor\" error={error} />\n ) : (\n <EmptyView\n title=\"Mentor not found\"\n description=\"This mentor profile may no longer be available.\"\n />\n )}\n </PageLayout>\n );\n }\n\n const sessions = mentor.sessions ?? [];\n const recentSessions = [...sessions]\n .sort((a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime())\n .slice(0, 5);\n\n const HeroIcon = topicIcon(mentor.expertise.join(' ') || mentor.headline);\n\n return (\n <PageLayout hero backPath=\"/mentorship/mentors-page\" title=\"Mentor Profile\">\n <GlassCard className=\"mb-6 overflow-hidden p-0\">\n <div\n className={cn(\n 'relative h-24 overflow-hidden bg-gradient-to-br',\n coverGradient(mentor.headline)\n )}\n >\n {createElement(HeroIcon, {\n className: 'pointer-events-none absolute -right-3 -top-4 text-white/15',\n size: 120,\n strokeWidth: 1.3,\n })}\n <div className=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-black/25 to-transparent\" />\n </div>\n <div className=\"px-6 pb-6\">\n <div className=\"-mt-10 flex flex-col gap-4 sm:flex-row sm:items-end\">\n <div className=\"relative shrink-0\">\n <span\n className={cn(\n 'flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br text-white shadow-lg ring-4 ring-bg-surface',\n coverGradient(mentor.headline)\n )}\n >\n {createElement(HeroIcon, { size: 34, strokeWidth: 1.6 })}\n </span>\n {mentor.isAcceptingMentees ? (\n <span className=\"absolute bottom-1 right-1 h-4 w-4 rounded-full border-2 border-bg-surface bg-status-success-text\" />\n ) : null}\n </div>\n <div className=\"min-w-0 flex-1 sm:pb-1\">\n <h1 className=\"text-xl font-semibold text-text-primary sm:text-2xl\">\n {mentor.headline}\n </h1>\n {mentor.role ? (\n <p className=\"mt-1 text-sm font-medium text-text-secondary\">{mentor.role}</p>\n ) : null}\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <StatusBadge status={mentor.status} />\n {mentor.isAcceptingMentees ? (\n <Badge\n variant=\"outline\"\n className=\"inline-flex items-center gap-1.5 border-status-success-border bg-status-success-bg-subtle text-status-success-text\"\n >\n <span className=\"h-1.5 w-1.5 rounded-full bg-status-success-text\" /> Accepting\n mentees\n </Badge>\n ) : null}\n </div>\n </div>\n </div>\n\n {mentor.bio ? (\n <p className=\"mt-4 max-w-2xl text-sm leading-relaxed text-text-secondary\">\n {mentor.bio}\n </p>\n ) : null}\n\n {mentor.expertise.length ? (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {mentor.expertise.map((skill) => (\n <Badge key={skill} variant=\"secondary\">\n {skill}\n </Badge>\n ))}\n </div>\n ) : null}\n </div>\n </GlassCard>\n\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard\n label=\"Rate\"\n value={`${formatMoneyFromMinorUnits(mentor.hourlyRate, mentor.currency)}/hr`}\n icon={DollarSign}\n tone=\"blue\"\n />\n <StatCard\n label=\"Rating\"\n value={mentor.ratingAvg > 0 ? mentor.ratingAvg.toFixed(1) : 'New'}\n icon={Star}\n tone=\"amber\"\n />\n <StatCard\n label=\"Sessions\"\n value={mentor.sessionsCount}\n icon={MessageSquare}\n tone=\"purple\"\n />\n <StatCard\n label=\"Status\"\n value={mentor.status === 'ACTIVE' ? 'Active' : 'Pending'}\n icon={Award}\n tone=\"teal\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <BookSessionForm\n mentorId={mentor.id}\n disabled={!mentor.isAcceptingMentees}\n onBooked={() => void refetch()}\n workspaceId={workspaceId}\n />\n\n <GlassCard className=\"p-5\">\n <div className=\"mb-3 flex items-center gap-2\">\n <CalendarClock size={18} className=\"text-accent-teal\" />\n <h2 className=\"text-base font-semibold text-text-primary\">Recent sessions</h2>\n </div>\n {recentSessions.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-muted\">\n No sessions booked yet. Be the first to request time with this mentor.\n </p>\n ) : (\n <div className=\"divide-y divide-border-seam\">\n {recentSessions.map((session) => (\n <SessionSummaryRow key={session.id} session={session} />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"StudyPlannerStudyPlannerPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/StudyPlannerStudyPlannerPagePage.tsx"],"names":[],"mappings":"AA6LA,wBAAgB,gCAAgC,gCA+G/C"}
1
+ {"version":3,"file":"StudyPlannerStudyPlannerPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/StudyPlannerStudyPlannerPagePage.tsx"],"names":[],"mappings":"AA6OA,wBAAgB,gCAAgC,gCAkJ/C"}