@burdenoff/microfe-technospam 2026.720.1 → 2026.730.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 (38) hide show
  1. package/dist/technospam/TechnoSpamRoot.d.ts.map +1 -1
  2. package/dist/technospam/TechnoSpamRoot.js +174 -212
  3. package/dist/technospam/TechnoSpamRoot.js.map +1 -1
  4. package/dist/technospam/TechnoSpamRoutes.d.ts.map +1 -1
  5. package/dist/technospam/TechnoSpamRoutes.js +31 -30
  6. package/dist/technospam/TechnoSpamRoutes.js.map +1 -1
  7. package/dist/technospam/api/technospamApi.d.ts +166 -0
  8. package/dist/technospam/api/technospamApi.d.ts.map +1 -1
  9. package/dist/technospam/api/technospamApi.js +336 -147
  10. package/dist/technospam/api/technospamApi.js.map +1 -1
  11. package/dist/technospam/components/UpgradeGate.d.ts +24 -0
  12. package/dist/technospam/components/UpgradeGate.d.ts.map +1 -0
  13. package/dist/technospam/components/UpgradeGate.js +47 -0
  14. package/dist/technospam/components/UpgradeGate.js.map +1 -0
  15. package/dist/technospam/components/index.d.ts +1 -0
  16. package/dist/technospam/components/index.d.ts.map +1 -1
  17. package/dist/technospam/components/index.js +1 -0
  18. package/dist/technospam/pages/ApplicationsPortfolioPagePage.d.ts +2 -0
  19. package/dist/technospam/pages/ApplicationsPortfolioPagePage.d.ts.map +1 -0
  20. package/dist/technospam/pages/ApplicationsPortfolioPagePage.js +275 -0
  21. package/dist/technospam/pages/ApplicationsPortfolioPagePage.js.map +1 -0
  22. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.d.ts.map +1 -1
  23. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +380 -168
  24. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
  25. package/dist/technospam/pages/ApplicationsProblemsPagePage.d.ts.map +1 -1
  26. package/dist/technospam/pages/ApplicationsProblemsPagePage.js +81 -77
  27. package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
  28. package/dist/technospam/pages/CoursesCourseDetailsPagePage.d.ts.map +1 -1
  29. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +200 -178
  30. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
  31. package/dist/technospam/pages/CoursesCoursesPagePage.d.ts.map +1 -1
  32. package/dist/technospam/pages/CoursesCoursesPagePage.js +66 -62
  33. package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
  34. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.d.ts +2 -0
  35. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.d.ts.map +1 -0
  36. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.js +266 -0
  37. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.js.map +1 -0
  38. package/package.json +1 -1
@@ -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 StatCard,\n StatusBadge,\n TagStrip,\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 ) : (\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 </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 </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 {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
+ {"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":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CoursesCoursesPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/CoursesCoursesPagePage.tsx"],"names":[],"mappings":"AAsFA,wBAAgB,sBAAsB,gCAiIrC"}
1
+ {"version":3,"file":"CoursesCoursesPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/CoursesCoursesPagePage.tsx"],"names":[],"mappings":"AA0FA,wBAAgB,sBAAsB,gCAiIrC"}
@@ -7,14 +7,15 @@ import { formatDuration as s } from "../utils/format.js";
7
7
  import { StatusBadge as c } from "../components/StatusBadge.js";
8
8
  import { LoadMore as l } from "../components/LoadMore.js";
9
9
  import { EmptyView as u, ErrorView as d, LoadingGrid as f } from "../components/StateViews.js";
10
+ import { PremiumBadge as p } from "../components/UpgradeGate.js";
10
11
  import "../components/index.js";
11
- import { pageArg as p, useCoursesQuery as m, usePageLimit as h } from "../api/technospamApi.js";
12
- import { useMemo as g, useState as _ } from "react";
13
- import { BookOpen as v, Clock as y, GraduationCap as b, Layers as x, Search as S, Sparkles as C } from "lucide-react";
14
- import { jsx as w, jsxs as T } from "react/jsx-runtime";
15
- import { Badge as E, Button as D, GlassCard as O, Input as k, Tabs as A, TabsList as j, TabsTrigger as M } from "@burdenoff/fe-libs/ui";
12
+ import { pageArg as m, useCoursesQuery as h, usePageLimit as g } from "../api/technospamApi.js";
13
+ import { useMemo as _, useState as v } from "react";
14
+ import { BookOpen as y, Clock as b, GraduationCap as x, Layers as S, Search as C, Sparkles as w } from "lucide-react";
15
+ import { jsx as T, jsxs as E } from "react/jsx-runtime";
16
+ import { Badge as D, Button as O, GlassCard as k, Input as A, Tabs as j, TabsList as M, TabsTrigger as N } from "@burdenoff/fe-libs/ui";
16
17
  //#region src/technospam/pages/CoursesCoursesPagePage.tsx
17
- var N = [
18
+ var P = [
18
19
  {
19
20
  value: "ALL",
20
21
  label: "All levels"
@@ -36,51 +37,54 @@ var N = [
36
37
  label: "Expert"
37
38
  }
38
39
  ];
39
- function P({ course: e, onOpen: t }) {
40
- return /* @__PURE__ */ T(O, {
40
+ function F({ course: e, onOpen: t }) {
41
+ return /* @__PURE__ */ E(k, {
41
42
  className: "group flex cursor-pointer flex-col overflow-hidden transition-all duration-200 hover:-translate-y-1 hover:shadow-lg",
42
43
  onClick: t,
43
- children: [/* @__PURE__ */ w(a, {
44
+ children: [/* @__PURE__ */ T(a, {
44
45
  seed: `${e.topic ?? ""} ${e.title}`,
45
46
  icon: o(e.topic ?? e.title),
46
- children: /* @__PURE__ */ w(c, {
47
- status: e.level,
48
- kind: "level"
47
+ children: /* @__PURE__ */ E("div", {
48
+ className: "flex flex-wrap items-center gap-1.5",
49
+ children: [/* @__PURE__ */ T(c, {
50
+ status: e.level,
51
+ kind: "level"
52
+ }), e.isPremiumOnly ? /* @__PURE__ */ T(p, { locked: e.isLocked }) : null]
49
53
  })
50
- }), /* @__PURE__ */ T("div", {
54
+ }), /* @__PURE__ */ E("div", {
51
55
  className: "flex flex-1 flex-col gap-3 p-4",
52
56
  children: [
53
- /* @__PURE__ */ T("div", {
57
+ /* @__PURE__ */ E("div", {
54
58
  className: "min-w-0",
55
- children: [/* @__PURE__ */ w("h3", {
59
+ children: [/* @__PURE__ */ T("h3", {
56
60
  className: "truncate text-base font-semibold text-text-primary group-hover:text-accent-primary",
57
61
  children: e.title
58
- }), e.summary ? /* @__PURE__ */ w("p", {
62
+ }), e.summary ? /* @__PURE__ */ T("p", {
59
63
  className: "mt-1 line-clamp-2 text-sm text-text-secondary",
60
64
  children: e.summary
61
65
  }) : null]
62
66
  }),
63
- /* @__PURE__ */ T("div", {
67
+ /* @__PURE__ */ E("div", {
64
68
  className: "mt-auto flex flex-wrap items-center gap-3 text-xs text-text-muted",
65
- children: [e.topic ? /* @__PURE__ */ T("span", {
69
+ children: [e.topic ? /* @__PURE__ */ E("span", {
66
70
  className: "inline-flex items-center gap-1",
67
71
  children: [
68
- /* @__PURE__ */ w(x, { size: 13 }),
72
+ /* @__PURE__ */ T(S, { size: 13 }),
69
73
  " ",
70
74
  e.topic
71
75
  ]
72
- }) : null, /* @__PURE__ */ T("span", {
76
+ }) : null, /* @__PURE__ */ E("span", {
73
77
  className: "inline-flex items-center gap-1",
74
78
  children: [
75
- /* @__PURE__ */ w(y, { size: 13 }),
79
+ /* @__PURE__ */ T(b, { size: 13 }),
76
80
  " ",
77
81
  s(e.estimatedMinutes)
78
82
  ]
79
83
  })]
80
84
  }),
81
- e.tags.length ? /* @__PURE__ */ w("div", {
85
+ e.tags.length ? /* @__PURE__ */ T("div", {
82
86
  className: "flex flex-wrap gap-1.5",
83
- children: e.tags.slice(0, 3).map((e) => /* @__PURE__ */ w(E, {
87
+ children: e.tags.slice(0, 3).map((e) => /* @__PURE__ */ T(D, {
84
88
  variant: "secondary",
85
89
  className: "text-[11px]",
86
90
  children: e
@@ -90,75 +94,75 @@ function P({ course: e, onOpen: t }) {
90
94
  })]
91
95
  });
92
96
  }
93
- function F() {
94
- let a = t(), { workspaceId: o } = e(), { emit: c } = n(), [x, E] = _("ALL"), [O, F] = _(""), { limit: I, more: L } = h(), { data: R, loading: z, error: B } = m({
97
+ function I() {
98
+ let a = t(), { workspaceId: o } = e(), { emit: c } = n(), [p, S] = v("ALL"), [D, k] = v(""), { limit: I, more: L } = g(), { data: R, loading: z, error: B } = h({
95
99
  status: "PUBLISHED",
96
- pagination: p(I),
97
- ...x === "ALL" ? {} : { level: x }
98
- }), V = R?.tecListCourses.items ?? [], H = R?.tecListCourses.pageInfo.hasMore ?? !1, U = g(() => {
99
- let e = O.trim().toLowerCase();
100
+ pagination: m(I),
101
+ ...p === "ALL" ? {} : { level: p }
102
+ }), V = R?.tecListCourses.items ?? [], H = R?.tecListCourses.pageInfo.hasMore ?? !1, U = _(() => {
103
+ let e = D.trim().toLowerCase();
100
104
  return e ? V.filter((t) => t.title.toLowerCase().includes(e) || (t.topic ?? "").toLowerCase().includes(e) || t.tags.some((t) => t.toLowerCase().includes(e))) : V;
101
- }, [V, O]);
102
- return /* @__PURE__ */ T(r, {
105
+ }, [V, D]);
106
+ return /* @__PURE__ */ E(r, {
103
107
  hero: !0,
104
108
  title: "Course Catalog",
105
109
  description: "Learn technology by doing — practical, project-based courses across the stack.",
106
- actions: /* @__PURE__ */ T(D, {
110
+ actions: /* @__PURE__ */ E(O, {
107
111
  variant: "outline",
108
112
  onClick: () => a("/courses/my-learning-page"),
109
- children: [/* @__PURE__ */ w(b, { size: 16 }), " My Learning"]
113
+ children: [/* @__PURE__ */ T(x, { size: 16 }), " My Learning"]
110
114
  }),
111
115
  children: [
112
- /* @__PURE__ */ T("div", {
116
+ /* @__PURE__ */ E("div", {
113
117
  className: "mb-5 grid grid-cols-2 gap-3 sm:grid-cols-4",
114
118
  children: [
115
- /* @__PURE__ */ w(i, {
119
+ /* @__PURE__ */ T(i, {
116
120
  label: "Courses",
117
121
  value: V.length,
118
- icon: v,
122
+ icon: y,
119
123
  tone: "blue"
120
124
  }),
121
- /* @__PURE__ */ w(i, {
125
+ /* @__PURE__ */ T(i, {
122
126
  label: "Beginner-friendly",
123
127
  value: V.filter((e) => e.level === "BEGINNER").length,
124
- icon: C,
128
+ icon: w,
125
129
  tone: "green"
126
130
  }),
127
- /* @__PURE__ */ w(i, {
131
+ /* @__PURE__ */ T(i, {
128
132
  label: "Advanced+",
129
133
  value: V.filter((e) => e.level === "ADVANCED" || e.level === "EXPERT").length,
130
- icon: b,
134
+ icon: x,
131
135
  tone: "purple"
132
136
  }),
133
- /* @__PURE__ */ w(i, {
137
+ /* @__PURE__ */ T(i, {
134
138
  label: "Total content",
135
139
  value: s(V.reduce((e, t) => e + t.estimatedMinutes, 0)),
136
- icon: y,
140
+ icon: b,
137
141
  tone: "teal"
138
142
  })
139
143
  ]
140
144
  }),
141
- /* @__PURE__ */ T("div", {
145
+ /* @__PURE__ */ E("div", {
142
146
  className: "mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
143
- children: [/* @__PURE__ */ w(A, {
144
- value: x,
145
- onValueChange: (e) => E(e),
146
- children: /* @__PURE__ */ w(j, { children: N.map((e) => /* @__PURE__ */ w(M, {
147
+ children: [/* @__PURE__ */ T(j, {
148
+ value: p,
149
+ onValueChange: (e) => S(e),
150
+ children: /* @__PURE__ */ T(M, { children: P.map((e) => /* @__PURE__ */ T(N, {
147
151
  value: e.value,
148
152
  children: e.label
149
153
  }, e.value)) })
150
- }), /* @__PURE__ */ T("div", {
154
+ }), /* @__PURE__ */ E("div", {
151
155
  className: "relative w-full sm:w-72",
152
- children: [/* @__PURE__ */ w(S, {
156
+ children: [/* @__PURE__ */ T(C, {
153
157
  className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-text-muted",
154
158
  size: 16
155
- }), /* @__PURE__ */ w(k, {
159
+ }), /* @__PURE__ */ T(A, {
156
160
  className: "pl-9",
157
161
  placeholder: "Search courses…",
158
- value: O,
159
- onChange: (e) => F(e.target.value),
162
+ value: D,
163
+ onChange: (e) => k(e.target.value),
160
164
  onBlur: () => {
161
- let e = O.trim();
165
+ let e = D.trim();
162
166
  e && c("technospam.search.performed", {
163
167
  workspaceId: o,
164
168
  entityType: "course",
@@ -168,16 +172,16 @@ function F() {
168
172
  })]
169
173
  })]
170
174
  }),
171
- z && !V.length ? /* @__PURE__ */ w(f, {}) : B ? /* @__PURE__ */ w(d, {
175
+ z && !V.length ? /* @__PURE__ */ T(f, {}) : B ? /* @__PURE__ */ T(d, {
172
176
  title: "Couldn't load courses",
173
177
  error: B
174
- }) : U.length === 0 ? /* @__PURE__ */ w(u, {
175
- icon: /* @__PURE__ */ w(v, { size: 26 }),
176
- title: O || x !== "ALL" ? "No matching courses" : "No courses published yet",
177
- description: O || x !== "ALL" ? "Try a different search or level filter." : "Published courses will appear here. Check back soon — TechnoSpam is pre-launch."
178
- }) : /* @__PURE__ */ w("div", {
178
+ }) : U.length === 0 ? /* @__PURE__ */ T(u, {
179
+ icon: /* @__PURE__ */ T(y, { size: 26 }),
180
+ title: D || p !== "ALL" ? "No matching courses" : "No courses published yet",
181
+ description: D || p !== "ALL" ? "Try a different search or level filter." : "Published courses will appear here. Check back soon — TechnoSpam is pre-launch."
182
+ }) : /* @__PURE__ */ T("div", {
179
183
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
180
- children: U.map((e) => /* @__PURE__ */ w(P, {
184
+ children: U.map((e) => /* @__PURE__ */ T(F, {
181
185
  course: e,
182
186
  onOpen: () => {
183
187
  c("technospam.course.viewed", {
@@ -188,7 +192,7 @@ function F() {
188
192
  }
189
193
  }, e.id))
190
194
  }),
191
- /* @__PURE__ */ w(l, {
195
+ /* @__PURE__ */ T(l, {
192
196
  hasMore: H,
193
197
  loading: z,
194
198
  onClick: L
@@ -197,6 +201,6 @@ function F() {
197
201
  });
198
202
  }
199
203
  //#endregion
200
- export { F as CoursesCoursesPagePage };
204
+ export { I as CoursesCoursesPagePage };
201
205
 
202
206
  //# sourceMappingURL=CoursesCoursesPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CoursesCoursesPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesCoursesPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { BookOpen, Clock, GraduationCap, Layers, Search, Sparkles } from 'lucide-react';\nimport {\n Badge,\n Button,\n GlassCard,\n Input,\n Tabs,\n TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadMore,\n LoadingGrid,\n MediaCover,\n PageLayout,\n StatCard,\n StatusBadge,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n pageArg,\n useCoursesQuery,\n usePageLimit,\n type TecCourse,\n type TecCourseLevel,\n} from '../api/technospamApi';\nimport { formatDuration } from '../utils/format';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\nconst LEVELS: Array<{ value: 'ALL' | TecCourseLevel; label: string }> = [\n { value: 'ALL', label: 'All levels' },\n { value: 'BEGINNER', label: 'Beginner' },\n { value: 'INTERMEDIATE', label: 'Intermediate' },\n { value: 'ADVANCED', label: 'Advanced' },\n { value: 'EXPERT', label: 'Expert' },\n];\n\nfunction CourseCard({ course, onOpen }: { course: TecCourse; onOpen: () => void }) {\n return (\n <GlassCard\n className=\"group flex cursor-pointer flex-col overflow-hidden transition-all duration-200 hover:-translate-y-1 hover:shadow-lg\"\n onClick={onOpen}\n >\n <MediaCover\n seed={`${course.topic ?? ''} ${course.title}`}\n icon={topicIcon(course.topic ?? course.title)}\n >\n <StatusBadge status={course.level} kind=\"level\" />\n </MediaCover>\n <div className=\"flex flex-1 flex-col gap-3 p-4\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary group-hover:text-accent-primary\">\n {course.title}\n </h3>\n {course.summary ? (\n <p className=\"mt-1 line-clamp-2 text-sm text-text-secondary\">{course.summary}</p>\n ) : null}\n </div>\n <div className=\"mt-auto flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n {course.topic ? (\n <span className=\"inline-flex items-center gap-1\">\n <Layers size={13} /> {course.topic}\n </span>\n ) : null}\n <span className=\"inline-flex items-center gap-1\">\n <Clock size={13} /> {formatDuration(course.estimatedMinutes)}\n </span>\n </div>\n {course.tags.length ? (\n <div className=\"flex flex-wrap gap-1.5\">\n {course.tags.slice(0, 3).map((t) => (\n <Badge key={t} variant=\"secondary\" className=\"text-[11px]\">\n {t}\n </Badge>\n ))}\n </div>\n ) : null}\n </div>\n </GlassCard>\n );\n}\n\nexport function CoursesCoursesPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const [level, setLevel] = useState<'ALL' | TecCourseLevel>('ALL');\n const [search, setSearch] = useState('');\n\n const { limit, more } = usePageLimit();\n const { data, loading, error } = useCoursesQuery({\n status: 'PUBLISHED',\n pagination: pageArg(limit),\n ...(level !== 'ALL' ? { level } : {}),\n });\n\n const courses = data?.tecListCourses.items ?? [];\n const hasMore = data?.tecListCourses.pageInfo.hasMore ?? false;\n const filtered = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return courses;\n return courses.filter(\n (c) =>\n c.title.toLowerCase().includes(q) ||\n (c.topic ?? '').toLowerCase().includes(q) ||\n c.tags.some((t) => t.toLowerCase().includes(q))\n );\n }, [courses, search]);\n\n return (\n <PageLayout\n hero\n title=\"Course Catalog\"\n description=\"Learn technology by doing — practical, project-based courses across the stack.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> My Learning\n </Button>\n }\n >\n <div className=\"mb-5 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard label=\"Courses\" value={courses.length} icon={BookOpen} tone=\"blue\" />\n <StatCard\n label=\"Beginner-friendly\"\n value={courses.filter((c) => c.level === 'BEGINNER').length}\n icon={Sparkles}\n tone=\"green\"\n />\n <StatCard\n label=\"Advanced+\"\n value={courses.filter((c) => c.level === 'ADVANCED' || c.level === 'EXPERT').length}\n icon={GraduationCap}\n tone=\"purple\"\n />\n <StatCard\n label=\"Total content\"\n value={formatDuration(courses.reduce((s, c) => s + c.estimatedMinutes, 0))}\n icon={Clock}\n tone=\"teal\"\n />\n </div>\n\n <div className=\"mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <Tabs value={level} onValueChange={(v) => setLevel(v as 'ALL' | TecCourseLevel)}>\n <TabsList>\n {LEVELS.map((l) => (\n <TabsTrigger key={l.value} value={l.value}>\n {l.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n <div className=\"relative w-full sm:w-72\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-text-muted\"\n size={16}\n />\n <Input\n className=\"pl-9\"\n placeholder=\"Search courses…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n onBlur={() => {\n const value = search.trim();\n if (value) {\n emit('technospam.search.performed', {\n workspaceId,\n entityType: 'course',\n queryLength: value.length,\n });\n }\n }}\n />\n </div>\n </div>\n\n {loading && !courses.length ? (\n <LoadingGrid />\n ) : error ? (\n <ErrorView title=\"Couldn't load courses\" error={error} />\n ) : filtered.length === 0 ? (\n <EmptyView\n icon={<BookOpen size={26} />}\n title={search || level !== 'ALL' ? 'No matching courses' : 'No courses published yet'}\n description={\n search || level !== 'ALL'\n ? 'Try a different search or level filter.'\n : 'Published courses will appear here. Check back soon — TechnoSpam is pre-launch.'\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {filtered.map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n onOpen={() => {\n emit('technospam.course.viewed', {\n workspaceId,\n entityId: course.id,\n entityType: 'TEC_COURSE',\n });\n navigate(`/courses/course-details-page?id=${course.id}`);\n }}\n />\n ))}\n </div>\n )}\n <LoadMore hasMore={hasMore} loading={loading} onClick={more} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CoursesCoursesPagePage.js","names":[],"sources":["../../../src/technospam/pages/CoursesCoursesPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { BookOpen, Clock, GraduationCap, Layers, Search, Sparkles } from 'lucide-react';\nimport {\n Badge,\n Button,\n GlassCard,\n Input,\n Tabs,\n TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadMore,\n LoadingGrid,\n MediaCover,\n PageLayout,\n PremiumBadge,\n StatCard,\n StatusBadge,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n pageArg,\n useCoursesQuery,\n usePageLimit,\n type TecCourse,\n type TecCourseLevel,\n} from '../api/technospamApi';\nimport { formatDuration } from '../utils/format';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\nconst LEVELS: Array<{ value: 'ALL' | TecCourseLevel; label: string }> = [\n { value: 'ALL', label: 'All levels' },\n { value: 'BEGINNER', label: 'Beginner' },\n { value: 'INTERMEDIATE', label: 'Intermediate' },\n { value: 'ADVANCED', label: 'Advanced' },\n { value: 'EXPERT', label: 'Expert' },\n];\n\nfunction CourseCard({ course, onOpen }: { course: TecCourse; onOpen: () => void }) {\n return (\n <GlassCard\n className=\"group flex cursor-pointer flex-col overflow-hidden transition-all duration-200 hover:-translate-y-1 hover:shadow-lg\"\n onClick={onOpen}\n >\n <MediaCover\n seed={`${course.topic ?? ''} ${course.title}`}\n icon={topicIcon(course.topic ?? course.title)}\n >\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={course.level} kind=\"level\" />\n {course.isPremiumOnly ? <PremiumBadge locked={course.isLocked} /> : null}\n </div>\n </MediaCover>\n <div className=\"flex flex-1 flex-col gap-3 p-4\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary group-hover:text-accent-primary\">\n {course.title}\n </h3>\n {course.summary ? (\n <p className=\"mt-1 line-clamp-2 text-sm text-text-secondary\">{course.summary}</p>\n ) : null}\n </div>\n <div className=\"mt-auto flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n {course.topic ? (\n <span className=\"inline-flex items-center gap-1\">\n <Layers size={13} /> {course.topic}\n </span>\n ) : null}\n <span className=\"inline-flex items-center gap-1\">\n <Clock size={13} /> {formatDuration(course.estimatedMinutes)}\n </span>\n </div>\n {course.tags.length ? (\n <div className=\"flex flex-wrap gap-1.5\">\n {course.tags.slice(0, 3).map((t) => (\n <Badge key={t} variant=\"secondary\" className=\"text-[11px]\">\n {t}\n </Badge>\n ))}\n </div>\n ) : null}\n </div>\n </GlassCard>\n );\n}\n\nexport function CoursesCoursesPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const [level, setLevel] = useState<'ALL' | TecCourseLevel>('ALL');\n const [search, setSearch] = useState('');\n\n const { limit, more } = usePageLimit();\n const { data, loading, error } = useCoursesQuery({\n status: 'PUBLISHED',\n pagination: pageArg(limit),\n ...(level !== 'ALL' ? { level } : {}),\n });\n\n const courses = data?.tecListCourses.items ?? [];\n const hasMore = data?.tecListCourses.pageInfo.hasMore ?? false;\n const filtered = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return courses;\n return courses.filter(\n (c) =>\n c.title.toLowerCase().includes(q) ||\n (c.topic ?? '').toLowerCase().includes(q) ||\n c.tags.some((t) => t.toLowerCase().includes(q))\n );\n }, [courses, search]);\n\n return (\n <PageLayout\n hero\n title=\"Course Catalog\"\n description=\"Learn technology by doing — practical, project-based courses across the stack.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> My Learning\n </Button>\n }\n >\n <div className=\"mb-5 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard label=\"Courses\" value={courses.length} icon={BookOpen} tone=\"blue\" />\n <StatCard\n label=\"Beginner-friendly\"\n value={courses.filter((c) => c.level === 'BEGINNER').length}\n icon={Sparkles}\n tone=\"green\"\n />\n <StatCard\n label=\"Advanced+\"\n value={courses.filter((c) => c.level === 'ADVANCED' || c.level === 'EXPERT').length}\n icon={GraduationCap}\n tone=\"purple\"\n />\n <StatCard\n label=\"Total content\"\n value={formatDuration(courses.reduce((s, c) => s + c.estimatedMinutes, 0))}\n icon={Clock}\n tone=\"teal\"\n />\n </div>\n\n <div className=\"mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <Tabs value={level} onValueChange={(v) => setLevel(v as 'ALL' | TecCourseLevel)}>\n <TabsList>\n {LEVELS.map((l) => (\n <TabsTrigger key={l.value} value={l.value}>\n {l.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n <div className=\"relative w-full sm:w-72\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-text-muted\"\n size={16}\n />\n <Input\n className=\"pl-9\"\n placeholder=\"Search courses…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n onBlur={() => {\n const value = search.trim();\n if (value) {\n emit('technospam.search.performed', {\n workspaceId,\n entityType: 'course',\n queryLength: value.length,\n });\n }\n }}\n />\n </div>\n </div>\n\n {loading && !courses.length ? (\n <LoadingGrid />\n ) : error ? (\n <ErrorView title=\"Couldn't load courses\" error={error} />\n ) : filtered.length === 0 ? (\n <EmptyView\n icon={<BookOpen size={26} />}\n title={search || level !== 'ALL' ? 'No matching courses' : 'No courses published yet'}\n description={\n search || level !== 'ALL'\n ? 'Try a different search or level filter.'\n : 'Published courses will appear here. Check back soon — TechnoSpam is pre-launch.'\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {filtered.map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n onOpen={() => {\n emit('technospam.course.viewed', {\n workspaceId,\n entityId: course.id,\n entityType: 'TEC_COURSE',\n });\n navigate(`/courses/course-details-page?id=${course.id}`);\n }}\n />\n ))}\n </div>\n )}\n <LoadMore hasMore={hasMore} loading={loading} onClick={more} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function StudyPlannerStudyPlannerPagePage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=StudyPlannerStudyPlannerPagePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StudyPlannerStudyPlannerPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/StudyPlannerStudyPlannerPagePage.tsx"],"names":[],"mappings":"AA6LA,wBAAgB,gCAAgC,gCA+G/C"}