@burdenoff/microfe-technospam 2026.1031.1 → 2026.1031.3

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 (45) hide show
  1. package/dist/generated/global-types.d.ts +128 -7
  2. package/dist/generated/global-types.d.ts.map +1 -1
  3. package/dist/generated/wspace-types.d.ts +2629 -50
  4. package/dist/generated/wspace-types.d.ts.map +1 -1
  5. package/dist/technospam/components/StatusBadge.js +1 -1
  6. package/dist/technospam/components/StatusBadge.js.map +1 -1
  7. package/dist/technospam/components/SubmissionViews.js +1 -1
  8. package/dist/technospam/components/SubmissionViews.js.map +1 -1
  9. package/dist/technospam/components/lesson/InteractiveWorkspace.js +1 -1
  10. package/dist/technospam/components/lesson/InteractiveWorkspace.js.map +1 -1
  11. package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts.map +1 -1
  12. package/dist/technospam/pages/AdminAdminDashboardPagePage.js +194 -186
  13. package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
  14. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  15. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js +5 -2
  16. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  17. package/dist/technospam/pages/ApplicationsPortfolioPagePage.js +1 -0
  18. package/dist/technospam/pages/ApplicationsPortfolioPagePage.js.map +1 -1
  19. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +2 -1
  20. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
  21. package/dist/technospam/pages/ApplicationsProblemsPagePage.d.ts.map +1 -1
  22. package/dist/technospam/pages/ApplicationsProblemsPagePage.js +167 -162
  23. package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
  24. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +2 -2
  25. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
  26. package/dist/technospam/pages/CoursesCoursesPagePage.d.ts.map +1 -1
  27. package/dist/technospam/pages/CoursesCoursesPagePage.js +77 -77
  28. package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
  29. package/dist/technospam/pages/CoursesLessonPlayerPagePage.js +2 -2
  30. package/dist/technospam/pages/CoursesLessonPlayerPagePage.js.map +1 -1
  31. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +3 -3
  32. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
  33. package/dist/technospam/pages/HackathonsHackathonsPagePage.d.ts.map +1 -1
  34. package/dist/technospam/pages/HackathonsHackathonsPagePage.js +71 -71
  35. package/dist/technospam/pages/HackathonsHackathonsPagePage.js.map +1 -1
  36. package/dist/technospam/pages/MentorshipMentorProfilePagePage.d.ts.map +1 -1
  37. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +6 -1
  38. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
  39. package/dist/technospam/pages/MentorshipMentorsPagePage.d.ts.map +1 -1
  40. package/dist/technospam/pages/MentorshipMentorsPagePage.js +1 -0
  41. package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
  42. package/dist/technospam/pages/MentorshipSessionsPagePage.d.ts.map +1 -1
  43. package/dist/technospam/pages/MentorshipSessionsPagePage.js +97 -98
  44. package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
  45. package/package.json +7 -6
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/technospam/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useMemo, useState, type CSSProperties, type SVGProps } from 'react';\nimport {\n Activity,\n Award,\n BarChart3,\n BookOpen,\n CalendarRange,\n Code2,\n GraduationCap,\n Library,\n Percent,\n Target,\n TrendingUp,\n Trophy,\n Users,\n X,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Cell,\n Legend,\n Pie,\n PieChart,\n ResponsiveContainer,\n Tooltip as ReTooltip,\n XAxis,\n YAxis,\n} from 'recharts';\nimport {\n EmptyView,\n ErrorView,\n LoadingGrid,\n LoadingRows,\n PageLayout,\n StatCard,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useCertificatesQuery,\n useCoursesQuery,\n useEnrollmentsQuery,\n useHackathonsQuery,\n useProblemsQuery,\n useSessionsQuery,\n useSubmissionsQuery,\n type TecEnrollmentStatus,\n type TecSubmissionVerdict,\n} from '../api/technospamApi';\nimport { humanizeEnum } from '../utils/format';\nimport { useTr } from '../hooks/useTr';\n\n/* ----------------------------------------------------------- chart config */\n\nconst ENROLL_STATUS_ORDER: TecEnrollmentStatus[] = [\n 'ENROLLED',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'DROPPED',\n];\n\nconst ENROLL_STATUS_COLOR: Record<TecEnrollmentStatus, string> = {\n ENROLLED: 'var(--color-accent-primary)',\n IN_PROGRESS: 'var(--color-accent-teal)',\n COMPLETED: 'var(--color-status-success)',\n DROPPED: 'var(--color-status-warning)',\n};\n\nconst VERDICT_ORDER: TecSubmissionVerdict[] = [\n 'ACCEPTED',\n 'WRONG_ANSWER',\n 'TIME_LIMIT_EXCEEDED',\n 'RUNTIME_ERROR',\n 'COMPILE_ERROR',\n 'PENDING',\n];\n\nconst VERDICT_LABEL: Record<TecSubmissionVerdict, string> = {\n ACCEPTED: 'Accepted',\n WRONG_ANSWER: 'Wrong',\n TIME_LIMIT_EXCEEDED: 'TLE',\n RUNTIME_ERROR: 'Runtime',\n COMPILE_ERROR: 'Compile',\n PENDING: 'Pending',\n};\n\nconst VERDICT_COLOR: Record<TecSubmissionVerdict, string> = {\n ACCEPTED: 'var(--color-status-success)',\n WRONG_ANSWER: 'var(--color-status-error)',\n TIME_LIMIT_EXCEEDED: 'var(--color-status-warning)',\n RUNTIME_ERROR: 'var(--color-status-error)',\n COMPILE_ERROR: 'var(--color-status-error)',\n PENDING: 'var(--color-text-muted)',\n};\n\nconst TOOLTIP_STYLE: CSSProperties = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 12,\n color: 'var(--color-text-primary)',\n fontSize: 12,\n};\nconst TOOLTIP_LABEL_STYLE: CSSProperties = { color: 'var(--color-text-secondary)' };\nconst AXIS_TICK: SVGProps<SVGTextElement> = { fill: 'var(--color-text-muted)', fontSize: 12 };\n\ninterface StatusDatum {\n name: string;\n value: number;\n color: string;\n}\ninterface VerdictDatum {\n label: string;\n count: number;\n color: string;\n}\ninterface ActivityDatum {\n label: string;\n submissions: number;\n enrollments: number;\n}\ninterface TopicRateDatum {\n topic: string;\n accepted: number;\n attempted: number;\n rate: number;\n}\n\n/** Normalize an ISO timestamp to the start of its (Sunday-based) week. */\nfunction weekBucket(iso: string): { key: number; label: string } {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return { key: 0, label: '—' };\n const start = new Date(d.getFullYear(), d.getMonth(), d.getDate() - d.getDay());\n return {\n key: start.getTime(),\n label: start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }),\n };\n}\n\n/* -------------------------------------------------------- chart wrappers */\n\ntype ChipTone = 'blue' | 'teal' | 'purple' | 'indigo';\n\nconst CHIP: Record<ChipTone, string> = {\n blue: 'bg-accent-primary/15 text-accent-primary ring-accent-primary/20',\n teal: 'bg-accent-teal/15 text-accent-teal ring-accent-teal/20',\n purple: 'bg-accent-purple/15 text-accent-purple ring-accent-purple/20',\n indigo: 'bg-accent-indigo/15 text-accent-indigo ring-accent-indigo/20',\n};\n\nfunction ChartHeader({\n icon: Icon,\n title,\n subtitle,\n tone,\n}: {\n icon: LucideIcon;\n title: string;\n subtitle?: string;\n tone: ChipTone;\n}) {\n return (\n <div className=\"mb-4 flex items-center gap-3\">\n <span\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-xl ring-1',\n CHIP[tone]\n )}\n >\n <Icon size={17} />\n </span>\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary\">{title}</h3>\n {subtitle ? <p className=\"truncate text-xs text-text-muted\">{subtitle}</p> : null}\n </div>\n </div>\n );\n}\n\nfunction ChartCard({\n title,\n subtitle,\n icon,\n tone = 'teal',\n isEmpty,\n emptyLabel,\n children,\n}: {\n title: string;\n subtitle?: string;\n icon: LucideIcon;\n tone?: ChipTone;\n isEmpty: boolean;\n emptyLabel: string;\n children: React.ReactElement;\n}) {\n return (\n <GlassCard className=\"p-5 transition-shadow duration-200 hover:shadow-lg\">\n <ChartHeader icon={icon} title={title} subtitle={subtitle} tone={tone} />\n {isEmpty ? (\n <div className=\"flex h-[240px] items-center justify-center text-center text-sm text-text-muted\">\n {emptyLabel}\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height={260}>\n {children}\n </ResponsiveContainer>\n )}\n </GlassCard>\n );\n}\n\n/**\n * Accepted rate per problem topic.\n *\n * Built entirely from the submissions this page already loads: each\n * TecSubmission carries its `problem { topic }` and its `verdict`, so the\n * rate is `ACCEPTED / attempts` per topic -- a count, not an inference. The\n * lowest row is called out as exactly that (the lowest measured rate), never\n * as a diagnosis or a recommendation.\n */\nfunction TopicAcceptanceCard({\n data,\n title,\n subtitle,\n noData,\n lowestRateLabel,\n}: {\n data: TopicRateDatum[];\n title: string;\n subtitle: string;\n noData: string;\n lowestRateLabel: (topic: string, accepted: number, attempted: number) => string;\n}) {\n const weakest = data.length > 1 ? data[data.length - 1] : undefined;\n return (\n <GlassCard className=\"p-5 transition-shadow duration-200 hover:shadow-lg\">\n <ChartHeader icon={Target} title={title} subtitle={subtitle} tone=\"indigo\" />\n {data.length === 0 ? (\n <div className=\"flex h-[240px] items-center justify-center text-center text-sm text-text-muted\">\n {noData}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {data.map((row) => {\n const lowest = weakest?.topic === row.topic;\n return (\n <div key={row.topic}>\n <div className=\"mb-1 flex flex-wrap items-baseline gap-x-2\">\n <span className=\"truncate text-sm font-medium text-text-primary\">\n {row.topic}\n </span>\n <span\n className={cn(\n 'text-sm font-semibold tabular-nums',\n lowest ? 'text-status-warning-text' : 'text-text-secondary'\n )}\n >\n {row.rate}%\n </span>\n <span className=\"ml-auto shrink-0 rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-muted\">\n {row.accepted} of {row.attempted} accepted\n </span>\n </div>\n <div className=\"h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className={cn(\n 'h-full rounded-full transition-all duration-500',\n lowest ? 'bg-status-warning-text' : 'bg-accent-primary'\n )}\n style={{ width: `${row.rate}%` }}\n />\n </div>\n </div>\n );\n })}\n {weakest ? (\n <p className=\"pt-1 text-xs text-text-muted\">\n {lowestRateLabel(weakest.topic, weakest.accepted, weakest.attempted)}\n </p>\n ) : null}\n </div>\n )}\n </GlassCard>\n );\n}\n\n/* ---------------------------------------------------------- date range */\n\n/** Convert a native `<input type=\"date\">` value (YYYY-MM-DD) into inclusive\n * ISO bounds so the server-side createdAt filter covers the whole day. */\nfunction dayStartIso(date: string): string | undefined {\n return date ? `${date}T00:00:00.000Z` : undefined;\n}\nfunction dayEndIso(date: string): string | undefined {\n return date ? `${date}T23:59:59.999Z` : undefined;\n}\n\nfunction DateRangeControls({\n start,\n end,\n onStart,\n onEnd,\n onPreset,\n onClear,\n tr,\n}: {\n start: string;\n end: string;\n onStart: (v: string) => void;\n onEnd: (v: string) => void;\n onPreset: (days: number) => void;\n onClear: () => void;\n tr: ReturnType<typeof useTr>;\n}) {\n const active = Boolean(start || end);\n return (\n <GlassCard className=\"mb-6 flex flex-col gap-3 p-4 lg:flex-row lg:items-end lg:justify-between\">\n <div className=\"flex flex-wrap items-end gap-3\">\n <div className=\"flex items-center gap-2 self-center text-sm font-medium text-text-secondary\">\n <CalendarRange size={16} className=\"text-accent-teal\" />\n {tr('technospam.analytics.dateRangeTitle', 'Date range')}\n </div>\n <div>\n <label\n className=\"mb-1 block text-xs font-medium text-text-muted\"\n htmlFor=\"tec-range-from\"\n >\n {tr('technospam.analytics.from', 'From')}\n </label>\n <Input\n id=\"tec-range-from\"\n type=\"date\"\n value={start}\n max={end || undefined}\n onChange={(e) => onStart(e.target.value)}\n className=\"w-40\"\n />\n </div>\n <div>\n <label className=\"mb-1 block text-xs font-medium text-text-muted\" htmlFor=\"tec-range-to\">\n {tr('technospam.analytics.to', 'To')}\n </label>\n <Input\n id=\"tec-range-to\"\n type=\"date\"\n value={end}\n min={start || undefined}\n onChange={(e) => onEnd(e.target.value)}\n className=\"w-40\"\n />\n </div>\n {active ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={onClear}>\n <X size={14} /> {tr('technospam.analytics.clear', 'Clear')}\n </Button>\n ) : null}\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => onPreset(7)}>\n {tr('technospam.analytics.last7Days', 'Last 7 days')}\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => onPreset(30)}>\n {tr('technospam.analytics.last30Days', 'Last 30 days')}\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => onPreset(90)}>\n {tr('technospam.analytics.last90Days', 'Last 90 days')}\n </Button>\n </div>\n </GlassCard>\n );\n}\n\n/* --------------------------------------------------------------- page */\n\nexport function AnalyticsAnalyticsPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const tr = useTr();\n const learnerId = currentUser?.id;\n\n const [rangeStart, setRangeStart] = useState('');\n const [rangeEnd, setRangeEnd] = useState('');\n const startDate = dayStartIso(rangeStart);\n const endDate = dayEndIso(rangeEnd);\n\n const applyPreset = (days: number) => {\n const now = new Date();\n const from = new Date();\n from.setDate(from.getDate() - days);\n setRangeStart(from.toISOString().slice(0, 10));\n setRangeEnd(now.toISOString().slice(0, 10));\n };\n const clearRange = () => {\n setRangeStart('');\n setRangeEnd('');\n };\n\n const dateVars = startDate || endDate ? { startDate, endDate } : {};\n const enrollmentsQ = useEnrollmentsQuery(learnerId ? { learnerId, ...dateVars } : {});\n const submissionsQ = useSubmissionsQuery(learnerId ? { learnerId, ...dateVars } : {});\n const sessionsQ = useSessionsQuery(learnerId ? { menteeId: learnerId } : {});\n const certificatesQ = useCertificatesQuery(learnerId ? { learnerId } : {});\n const coursesQ = useCoursesQuery({ status: 'PUBLISHED' });\n const problemsQ = useProblemsQuery({ status: 'PUBLISHED' });\n const hackathonsQ = useHackathonsQuery({});\n\n const enrollments = useMemo(\n () => enrollmentsQ.data?.tecListEnrollments?.items ?? [],\n [enrollmentsQ.data]\n );\n const submissions = useMemo(\n () => submissionsQ.data?.tecListSubmissions?.items ?? [],\n [submissionsQ.data]\n );\n const sessions = sessionsQ.data?.tecListSessions?.items ?? [];\n const certificates = certificatesQ.data?.tecListCertificates ?? [];\n const coursesTotal = coursesQ.data?.tecListCourses?.items.length ?? 0;\n const problemsTotal = problemsQ.data?.tecListProblems?.items.length ?? 0;\n const hackathonsTotal = hackathonsQ.data?.tecListHackathons?.items.length ?? 0;\n\n const metrics = useMemo(() => {\n const coursesCompleted = enrollments.filter((e) => e.status === 'COMPLETED').length;\n const avgProgress = enrollments.length\n ? Math.round(enrollments.reduce((s, e) => s + e.progressPct, 0) / enrollments.length)\n : 0;\n const acceptedSubs = submissions.filter((s) => s.verdict === 'ACCEPTED');\n const problemsSolved = new Set(acceptedSubs.map((s) => s.problemId)).size;\n const acceptanceRate = submissions.length\n ? Math.round((acceptedSubs.length / submissions.length) * 100)\n : 0;\n return { coursesCompleted, avgProgress, problemsSolved, acceptanceRate };\n }, [enrollments, submissions]);\n\n const enrollmentsByStatus = useMemo<StatusDatum[]>(\n () =>\n ENROLL_STATUS_ORDER.map((s) => ({\n name: humanizeEnum(s),\n value: enrollments.filter((e) => e.status === s).length,\n color: ENROLL_STATUS_COLOR[s],\n })).filter((d) => d.value > 0),\n [enrollments]\n );\n\n const submissionsByVerdict = useMemo<VerdictDatum[]>(\n () =>\n VERDICT_ORDER.map((v) => ({\n label: VERDICT_LABEL[v],\n count: submissions.filter((s) => s.verdict === v).length,\n color: VERDICT_COLOR[v],\n })).filter((d) => d.count > 0),\n [submissions]\n );\n\n const acceptedRateByTopic = useMemo<TopicRateDatum[]>(() => {\n const buckets = new Map<string, { accepted: number; attempted: number }>();\n submissions.forEach((s) => {\n const topic = s.problem?.topic?.trim() || 'Untagged';\n const bucket = buckets.get(topic) ?? { accepted: 0, attempted: 0 };\n bucket.attempted += 1;\n if (s.verdict === 'ACCEPTED') bucket.accepted += 1;\n buckets.set(topic, bucket);\n });\n return [...buckets.entries()]\n .map(([topic, bucket]) => ({\n topic,\n accepted: bucket.accepted,\n attempted: bucket.attempted,\n rate: Math.round((bucket.accepted / bucket.attempted) * 100),\n }))\n .sort((a, b) => b.rate - a.rate || b.attempted - a.attempted);\n }, [submissions]);\n\n const activityData = useMemo<ActivityDatum[]>(() => {\n const buckets = new Map<number, ActivityDatum>();\n const add = (iso: string, key: 'submissions' | 'enrollments') => {\n const { key: bucketKey, label } = weekBucket(iso);\n const existing = buckets.get(bucketKey) ?? { label, submissions: 0, enrollments: 0 };\n existing[key] += 1;\n buckets.set(bucketKey, existing);\n };\n submissions.forEach((s) => add(s.createdAt, 'submissions'));\n enrollments.forEach((e) => add(e.createdAt, 'enrollments'));\n return [...buckets.entries()]\n .sort((a, b) => a[0] - b[0])\n .slice(-8)\n .map(([, v]) => v);\n }, [submissions, enrollments]);\n\n const hasActivity =\n enrollments.length > 0 ||\n submissions.length > 0 ||\n sessions.length > 0 ||\n certificates.length > 0;\n\n const learnerLoading =\n enrollmentsQ.loading || submissionsQ.loading || sessionsQ.loading || certificatesQ.loading;\n const learnerError =\n enrollmentsQ.error || submissionsQ.error || sessionsQ.error || certificatesQ.error;\n const showLoading = learnerLoading && !hasActivity;\n\n const workspaceOverview = (\n <div>\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">\n {tr('technospam.analytics.workspaceOverviewTitle', 'Workspace overview')}\n </h2>\n <p className=\"mb-4 max-w-2xl text-sm text-text-secondary\">\n {tr(\n 'technospam.analytics.workspaceOverviewDesc',\n 'Live content available across the TechnoSpam learning workspace right now.'\n )}\n </p>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-3\">\n <StatCard\n label={tr('technospam.analytics.statPublishedCourses', 'Published courses')}\n value={coursesTotal}\n icon={Library}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.analytics.statPublishedProblems', 'Published problems')}\n value={problemsTotal}\n icon={Code2}\n tone=\"indigo\"\n />\n <StatCard\n label={tr('technospam.analytics.statHackathons', 'Hackathons')}\n value={hackathonsTotal}\n icon={Trophy}\n tone=\"purple\"\n />\n </div>\n </div>\n );\n\n return (\n <PageLayout\n hero\n title={tr('technospam.analytics.title', 'Analytics')}\n description={tr(\n 'technospam.analytics.description',\n 'Your real learning performance across courses, problems, mentoring and certificates.'\n )}\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> {tr('technospam.common.myLearning', 'My Learning')}\n </Button>\n }\n >\n {!learnerId ? (\n <div className=\"space-y-8\">\n <EmptyView\n icon={<BarChart3 size={26} />}\n title={tr('technospam.analytics.signInTitle', 'Sign in to view your analytics')}\n description={tr(\n 'technospam.analytics.signInDesc',\n \"Your personal learning metrics appear here once you're signed in.\"\n )}\n />\n {workspaceOverview}\n </div>\n ) : showLoading ? (\n <div className=\"space-y-6\">\n <LoadingGrid count={4} height=\"h-24\" />\n <LoadingRows count={2} height=\"h-72\" />\n </div>\n ) : learnerError && !hasActivity ? (\n <ErrorView\n title={tr('technospam.common.errorLoadAnalytics', \"Couldn't load your analytics\")}\n error={learnerError}\n />\n ) : (\n <div className=\"space-y-8\">\n <DateRangeControls\n start={rangeStart}\n end={rangeEnd}\n onStart={setRangeStart}\n onEnd={setRangeEnd}\n onPreset={applyPreset}\n onClear={clearRange}\n tr={tr}\n />\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3 xl:grid-cols-4\">\n <StatCard\n label={tr('technospam.analytics.statCoursesEnrolled', 'Courses enrolled')}\n value={enrollments.length}\n icon={BookOpen}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.analytics.statCoursesCompleted', 'Courses completed')}\n value={metrics.coursesCompleted}\n icon={GraduationCap}\n tone=\"green\"\n />\n <StatCard\n label={tr('technospam.analytics.statAvgProgress', 'Avg progress')}\n value={`${metrics.avgProgress}%`}\n icon={TrendingUp}\n tone=\"purple\"\n />\n <StatCard\n label={tr('technospam.analytics.statProblemsSolved', 'Problems solved')}\n value={metrics.problemsSolved}\n icon={Code2}\n tone=\"indigo\"\n />\n <StatCard\n label={tr('technospam.analytics.statAcceptanceRate', 'Acceptance rate')}\n value={`${metrics.acceptanceRate}%`}\n icon={Percent}\n tone=\"teal\"\n />\n <StatCard\n label={tr('technospam.analytics.statMentorSessions', 'Mentor sessions')}\n value={sessions.length}\n icon={Users}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.analytics.statCertificates', 'Certificates')}\n value={certificates.length}\n icon={Award}\n tone=\"amber\"\n />\n </div>\n\n {!hasActivity ? (\n <EmptyView\n icon={<Activity size={26} />}\n title={tr('technospam.analytics.noActivityTitle', 'No learning activity yet')}\n description={tr(\n 'technospam.analytics.noActivityDesc',\n 'Enroll in a course or solve a problem and your progress charts will build here. TechnoSpam is pre-launch — start learning to see your data.'\n )}\n action={\n <Button onClick={() => navigate('/courses/courses-page')}>\n {tr('technospam.common.browseCourses', 'Browse courses')}\n </Button>\n }\n />\n ) : (\n <div className=\"space-y-4\">\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <ChartCard\n title={tr(\n 'technospam.analytics.enrollmentsByStatusTitle',\n 'Enrollments by status'\n )}\n subtitle={tr(\n 'technospam.analytics.enrollmentsByStatusSubtitle',\n 'How your courses are progressing'\n )}\n icon={BookOpen}\n tone=\"blue\"\n isEmpty={enrollmentsByStatus.length === 0}\n emptyLabel={tr('technospam.analytics.noEnrollments', 'No enrollments yet')}\n >\n <PieChart>\n <Pie\n data={enrollmentsByStatus}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={60}\n outerRadius={92}\n paddingAngle={2}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {enrollmentsByStatus.map((d) => (\n <Cell key={d.name} fill={d.color} />\n ))}\n </Pie>\n <ReTooltip contentStyle={TOOLTIP_STYLE} labelStyle={TOOLTIP_LABEL_STYLE} />\n <Legend iconType=\"circle\" wrapperStyle={{ fontSize: 12 }} />\n </PieChart>\n </ChartCard>\n\n <ChartCard\n title={tr(\n 'technospam.analytics.submissionsByVerdictTitle',\n 'Submissions by verdict'\n )}\n subtitle={tr(\n 'technospam.analytics.submissionsByVerdictSubtitle',\n 'Outcomes across your attempts'\n )}\n icon={Code2}\n tone=\"purple\"\n isEmpty={submissionsByVerdict.length === 0}\n emptyLabel={tr('technospam.analytics.noSubmissions', 'No submissions yet')}\n >\n <BarChart\n data={submissionsByVerdict}\n margin={{ top: 8, right: 8, left: -18, bottom: 0 }}\n >\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"var(--color-border-seam)\"\n vertical={false}\n />\n <XAxis\n dataKey=\"label\"\n tick={AXIS_TICK}\n axisLine={{ stroke: 'var(--color-border-seam)' }}\n tickLine={false}\n />\n <YAxis\n allowDecimals={false}\n tick={AXIS_TICK}\n axisLine={false}\n tickLine={false}\n />\n <ReTooltip\n contentStyle={TOOLTIP_STYLE}\n labelStyle={TOOLTIP_LABEL_STYLE}\n cursor={{ fill: 'var(--color-accent-primary)', fillOpacity: 0.08 }}\n />\n <Bar dataKey=\"count\" name=\"Submissions\" radius={[6, 6, 0, 0]} maxBarSize={56}>\n {submissionsByVerdict.map((d) => (\n <Cell key={d.label} fill={d.color} />\n ))}\n </Bar>\n </BarChart>\n </ChartCard>\n </div>\n\n <TopicAcceptanceCard\n data={acceptedRateByTopic}\n title={tr('technospam.analytics.topicAcceptanceTitle', 'Accepted rate by topic')}\n subtitle={tr(\n 'technospam.analytics.topicAcceptanceSubtitle',\n \"Where your solutions land, and where they don't\"\n )}\n noData={tr(\n 'technospam.analytics.noTopicData',\n 'Solve a practice problem and your per-topic rate builds here'\n )}\n lowestRateLabel={(topic, accepted, attempted) =>\n tr(\n 'technospam.analytics.lowestMeasuredRate',\n 'Lowest measured rate: {{topic}} — {{accepted}} of {{attempted}} attempts accepted in the loaded range.',\n { topic, accepted, attempted }\n )\n }\n />\n\n <GlassCard className=\"p-5 transition-shadow duration-200 hover:shadow-lg\">\n <ChartHeader\n icon={Activity}\n title={tr(\n 'technospam.analytics.activityOverTimeTitle',\n 'Learning activity over time'\n )}\n subtitle={tr(\n 'technospam.analytics.activityOverTimeSubtitle',\n 'Submissions and enrollments per week'\n )}\n tone=\"teal\"\n />\n {activityData.length === 0 ? (\n <div className=\"flex h-[240px] items-center justify-center text-sm text-text-muted\">\n {tr('technospam.analytics.noDatedActivity', 'No dated activity yet')}\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height={280}>\n <AreaChart\n data={activityData}\n margin={{ top: 8, right: 12, left: -18, bottom: 0 }}\n >\n <defs>\n <linearGradient id=\"tec-grad-submissions\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop\n offset=\"0%\"\n stopColor=\"var(--color-accent-primary)\"\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor=\"var(--color-accent-primary)\"\n stopOpacity={0}\n />\n </linearGradient>\n <linearGradient id=\"tec-grad-enrollments\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop\n offset=\"0%\"\n stopColor=\"var(--color-accent-teal)\"\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor=\"var(--color-accent-teal)\"\n stopOpacity={0}\n />\n </linearGradient>\n </defs>\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"var(--color-border-seam)\"\n vertical={false}\n />\n <XAxis\n dataKey=\"label\"\n tick={AXIS_TICK}\n axisLine={{ stroke: 'var(--color-border-seam)' }}\n tickLine={false}\n />\n <YAxis\n allowDecimals={false}\n tick={AXIS_TICK}\n axisLine={false}\n tickLine={false}\n />\n <ReTooltip contentStyle={TOOLTIP_STYLE} labelStyle={TOOLTIP_LABEL_STYLE} />\n <Legend iconType=\"circle\" wrapperStyle={{ fontSize: 12 }} />\n <Area\n type=\"monotone\"\n dataKey=\"submissions\"\n name=\"Submissions\"\n stroke=\"var(--color-accent-primary)\"\n strokeWidth={2}\n fill=\"url(#tec-grad-submissions)\"\n />\n <Area\n type=\"monotone\"\n dataKey=\"enrollments\"\n name=\"Enrollments\"\n stroke=\"var(--color-accent-teal)\"\n strokeWidth={2}\n fill=\"url(#tec-grad-enrollments)\"\n />\n </AreaChart>\n </ResponsiveContainer>\n )}\n </GlassCard>\n </div>\n )}\n\n {workspaceOverview}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/technospam/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useMemo, useState, type CSSProperties, type SVGProps } from 'react';\nimport {\n Activity,\n Award,\n BarChart3,\n BookOpen,\n CalendarRange,\n Code2,\n GraduationCap,\n Library,\n Percent,\n Target,\n TrendingUp,\n Trophy,\n Users,\n X,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Cell,\n Legend,\n Pie,\n PieChart,\n ResponsiveContainer,\n Tooltip as ReTooltip,\n XAxis,\n YAxis,\n} from 'recharts';\nimport {\n EmptyView,\n ErrorView,\n LoadingGrid,\n LoadingRows,\n PageLayout,\n StatCard,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useCertificatesQuery,\n useCoursesQuery,\n useEnrollmentsQuery,\n useHackathonsQuery,\n useProblemsQuery,\n useSessionsQuery,\n useSubmissionsQuery,\n type TecEnrollmentStatus,\n type TecSubmissionVerdict,\n} from '../api/technospamApi';\nimport { humanizeEnum } from '../utils/format';\nimport { useTr } from '../hooks/useTr';\n\n/* ----------------------------------------------------------- chart config */\n\nconst ENROLL_STATUS_ORDER: TecEnrollmentStatus[] = [\n 'ENROLLED',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'DROPPED',\n];\n\nconst ENROLL_STATUS_COLOR: Record<TecEnrollmentStatus, string> = {\n ENROLLED: 'var(--color-accent-primary)',\n IN_PROGRESS: 'var(--color-accent-teal)',\n COMPLETED: 'var(--color-status-success)',\n DROPPED: 'var(--color-status-warning)',\n};\n\nconst VERDICT_ORDER: TecSubmissionVerdict[] = [\n 'ACCEPTED',\n 'WRONG_ANSWER',\n 'TIME_LIMIT_EXCEEDED',\n 'RUNTIME_ERROR',\n 'COMPILE_ERROR',\n 'PENDING',\n];\n\nconst VERDICT_LABEL: Record<TecSubmissionVerdict, string> = {\n ACCEPTED: 'Accepted',\n WRONG_ANSWER: 'Wrong',\n TIME_LIMIT_EXCEEDED: 'TLE',\n RUNTIME_ERROR: 'Runtime',\n COMPILE_ERROR: 'Compile',\n PENDING: 'Pending',\n};\n\nconst VERDICT_COLOR: Record<TecSubmissionVerdict, string> = {\n ACCEPTED: 'var(--color-status-success)',\n WRONG_ANSWER: 'var(--color-status-error)',\n TIME_LIMIT_EXCEEDED: 'var(--color-status-warning)',\n RUNTIME_ERROR: 'var(--color-status-error)',\n COMPILE_ERROR: 'var(--color-status-error)',\n PENDING: 'var(--color-text-muted)',\n};\n\nconst TOOLTIP_STYLE: CSSProperties = {\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 12,\n color: 'var(--color-text-primary)',\n fontSize: 12,\n};\nconst TOOLTIP_LABEL_STYLE: CSSProperties = { color: 'var(--color-text-secondary)' };\nconst AXIS_TICK: SVGProps<SVGTextElement> = { fill: 'var(--color-text-muted)', fontSize: 12 };\n\ninterface StatusDatum {\n name: string;\n value: number;\n color: string;\n}\ninterface VerdictDatum {\n label: string;\n count: number;\n color: string;\n}\ninterface ActivityDatum {\n label: string;\n submissions: number;\n enrollments: number;\n}\ninterface TopicRateDatum {\n topic: string;\n accepted: number;\n attempted: number;\n rate: number;\n}\n\n/** Normalize an ISO timestamp to the start of its (Sunday-based) week. */\nfunction weekBucket(iso: string): { key: number; label: string } {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return { key: 0, label: '—' };\n const start = new Date(d.getFullYear(), d.getMonth(), d.getDate() - d.getDay());\n return {\n key: start.getTime(),\n label: start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }),\n };\n}\n\n/* -------------------------------------------------------- chart wrappers */\n\ntype ChipTone = 'blue' | 'teal' | 'purple' | 'indigo';\n\nconst CHIP: Record<ChipTone, string> = {\n blue: 'bg-accent-primary/15 text-accent-primary ring-accent-primary/20',\n teal: 'bg-accent-teal/15 text-accent-teal ring-accent-teal/20',\n purple: 'bg-accent-purple/15 text-accent-purple ring-accent-purple/20',\n indigo: 'bg-accent-indigo/15 text-accent-indigo ring-accent-indigo/20',\n};\n\nfunction ChartHeader({\n icon: Icon,\n title,\n subtitle,\n tone,\n}: {\n icon: LucideIcon;\n title: string;\n subtitle?: string;\n tone: ChipTone;\n}) {\n return (\n <div className=\"mb-4 flex items-center gap-3\">\n <span\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-xl ring-1',\n CHIP[tone]\n )}\n >\n <Icon size={17} />\n </span>\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary\">{title}</h3>\n {subtitle ? <p className=\"truncate text-xs text-text-muted\">{subtitle}</p> : null}\n </div>\n </div>\n );\n}\n\nfunction ChartCard({\n title,\n subtitle,\n icon,\n tone = 'teal',\n isEmpty,\n emptyLabel,\n children,\n}: {\n title: string;\n subtitle?: string;\n icon: LucideIcon;\n tone?: ChipTone;\n isEmpty: boolean;\n emptyLabel: string;\n children: React.ReactElement;\n}) {\n return (\n <GlassCard className=\"p-5 transition-shadow duration-200 hover:shadow-lg\">\n <ChartHeader icon={icon} title={title} subtitle={subtitle} tone={tone} />\n {isEmpty ? (\n <div className=\"flex h-[240px] items-center justify-center text-center text-sm text-text-muted\">\n {emptyLabel}\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height={260}>\n {children}\n </ResponsiveContainer>\n )}\n </GlassCard>\n );\n}\n\n/**\n * Accepted rate per problem topic.\n *\n * Built entirely from the submissions this page already loads: each\n * TecSubmission carries its `problem { topic }` and its `verdict`, so the\n * rate is `ACCEPTED / attempts` per topic -- a count, not an inference. The\n * lowest row is called out as exactly that (the lowest measured rate), never\n * as a diagnosis or a recommendation.\n */\nfunction TopicAcceptanceCard({\n data,\n title,\n subtitle,\n noData,\n lowestRateLabel,\n}: {\n data: TopicRateDatum[];\n title: string;\n subtitle: string;\n noData: string;\n lowestRateLabel: (topic: string, accepted: number, attempted: number) => string;\n}) {\n const weakest = data.length > 1 ? data[data.length - 1] : undefined;\n return (\n <GlassCard className=\"p-5 transition-shadow duration-200 hover:shadow-lg\">\n <ChartHeader icon={Target} title={title} subtitle={subtitle} tone=\"indigo\" />\n {data.length === 0 ? (\n <div className=\"flex h-[240px] items-center justify-center text-center text-sm text-text-muted\">\n {noData}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {data.map((row) => {\n const lowest = weakest?.topic === row.topic;\n return (\n <div key={row.topic}>\n <div className=\"mb-1 flex flex-wrap items-baseline gap-x-2\">\n <span className=\"truncate text-sm font-medium text-text-primary\">\n {row.topic}\n </span>\n <span\n className={cn(\n 'text-sm font-semibold tabular-nums',\n lowest ? 'text-status-warning-text' : 'text-text-secondary'\n )}\n >\n {row.rate}%\n </span>\n <span className=\"ml-auto shrink-0 rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {row.accepted} of {row.attempted} accepted\n </span>\n </div>\n <div className=\"h-2.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className={cn(\n 'h-full rounded-full transition-all duration-500',\n lowest ? 'bg-status-warning-text' : 'bg-accent-primary'\n )}\n style={{ width: `${row.rate}%` }}\n />\n </div>\n </div>\n );\n })}\n {weakest ? (\n <p className=\"pt-1 text-xs text-text-muted\">\n {lowestRateLabel(weakest.topic, weakest.accepted, weakest.attempted)}\n </p>\n ) : null}\n </div>\n )}\n </GlassCard>\n );\n}\n\n/* ---------------------------------------------------------- date range */\n\n/** Convert a native `<input type=\"date\">` value (YYYY-MM-DD) into inclusive\n * ISO bounds so the server-side createdAt filter covers the whole day. */\nfunction dayStartIso(date: string): string | undefined {\n return date ? `${date}T00:00:00.000Z` : undefined;\n}\nfunction dayEndIso(date: string): string | undefined {\n return date ? `${date}T23:59:59.999Z` : undefined;\n}\n\nfunction DateRangeControls({\n start,\n end,\n onStart,\n onEnd,\n onPreset,\n onClear,\n tr,\n}: {\n start: string;\n end: string;\n onStart: (v: string) => void;\n onEnd: (v: string) => void;\n onPreset: (days: number) => void;\n onClear: () => void;\n tr: ReturnType<typeof useTr>;\n}) {\n const active = Boolean(start || end);\n return (\n <GlassCard className=\"mb-6 flex flex-col gap-3 p-4 lg:flex-row lg:items-end lg:justify-between\">\n <div className=\"flex flex-wrap items-end gap-3\">\n <div className=\"flex items-center gap-2 self-center text-sm font-medium text-text-secondary\">\n <CalendarRange size={16} className=\"text-accent-teal\" />\n {tr('technospam.analytics.dateRangeTitle', 'Date range')}\n </div>\n <div>\n <label\n className=\"mb-1 block text-xs font-medium text-text-muted\"\n htmlFor=\"tec-range-from\"\n >\n {tr('technospam.analytics.from', 'From')}\n </label>\n <Input\n id=\"tec-range-from\"\n type=\"date\"\n value={start}\n max={end || undefined}\n onChange={(e) => onStart(e.target.value)}\n className=\"w-40\"\n />\n </div>\n <div>\n <label className=\"mb-1 block text-xs font-medium text-text-muted\" htmlFor=\"tec-range-to\">\n {tr('technospam.analytics.to', 'To')}\n </label>\n <Input\n id=\"tec-range-to\"\n type=\"date\"\n value={end}\n min={start || undefined}\n onChange={(e) => onEnd(e.target.value)}\n className=\"w-40\"\n />\n </div>\n {active ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={onClear}>\n <X size={14} /> {tr('technospam.analytics.clear', 'Clear')}\n </Button>\n ) : null}\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => onPreset(7)}>\n {tr('technospam.analytics.last7Days', 'Last 7 days')}\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => onPreset(30)}>\n {tr('technospam.analytics.last30Days', 'Last 30 days')}\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => onPreset(90)}>\n {tr('technospam.analytics.last90Days', 'Last 90 days')}\n </Button>\n </div>\n </GlassCard>\n );\n}\n\n/* --------------------------------------------------------------- page */\n\nexport function AnalyticsAnalyticsPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const tr = useTr();\n const learnerId = currentUser?.id;\n\n const [rangeStart, setRangeStart] = useState('');\n const [rangeEnd, setRangeEnd] = useState('');\n const startDate = dayStartIso(rangeStart);\n const endDate = dayEndIso(rangeEnd);\n\n const applyPreset = (days: number) => {\n const now = new Date();\n const from = new Date();\n from.setDate(from.getDate() - days);\n setRangeStart(from.toISOString().slice(0, 10));\n setRangeEnd(now.toISOString().slice(0, 10));\n };\n const clearRange = () => {\n setRangeStart('');\n setRangeEnd('');\n };\n\n const dateVars = startDate || endDate ? { startDate, endDate } : {};\n const enrollmentsQ = useEnrollmentsQuery(learnerId ? { learnerId, ...dateVars } : {});\n const submissionsQ = useSubmissionsQuery(learnerId ? { learnerId, ...dateVars } : {});\n const sessionsQ = useSessionsQuery(learnerId ? { menteeId: learnerId } : {});\n const certificatesQ = useCertificatesQuery(learnerId ? { learnerId } : {});\n const coursesQ = useCoursesQuery({ status: 'PUBLISHED' });\n const problemsQ = useProblemsQuery({ status: 'PUBLISHED' });\n const hackathonsQ = useHackathonsQuery({});\n\n const enrollments = useMemo(\n () => enrollmentsQ.data?.tecListEnrollments?.items ?? [],\n [enrollmentsQ.data]\n );\n const submissions = useMemo(\n () => submissionsQ.data?.tecListSubmissions?.items ?? [],\n [submissionsQ.data]\n );\n const sessions = sessionsQ.data?.tecListSessions?.items ?? [];\n const certificates = certificatesQ.data?.tecListCertificates ?? [];\n const coursesTotal = coursesQ.data?.tecListCourses?.items.length ?? 0;\n const problemsTotal = problemsQ.data?.tecListProblems?.items.length ?? 0;\n const hackathonsTotal = hackathonsQ.data?.tecListHackathons?.items.length ?? 0;\n\n const metrics = useMemo(() => {\n const coursesCompleted = enrollments.filter((e) => e.status === 'COMPLETED').length;\n const avgProgress = enrollments.length\n ? Math.round(enrollments.reduce((s, e) => s + e.progressPct, 0) / enrollments.length)\n : 0;\n const acceptedSubs = submissions.filter((s) => s.verdict === 'ACCEPTED');\n const problemsSolved = new Set(acceptedSubs.map((s) => s.problemId)).size;\n const acceptanceRate = submissions.length\n ? Math.round((acceptedSubs.length / submissions.length) * 100)\n : 0;\n return { coursesCompleted, avgProgress, problemsSolved, acceptanceRate };\n }, [enrollments, submissions]);\n\n const enrollmentsByStatus = useMemo<StatusDatum[]>(\n () =>\n ENROLL_STATUS_ORDER.map((s) => ({\n name: humanizeEnum(s),\n value: enrollments.filter((e) => e.status === s).length,\n color: ENROLL_STATUS_COLOR[s],\n })).filter((d) => d.value > 0),\n [enrollments]\n );\n\n const submissionsByVerdict = useMemo<VerdictDatum[]>(\n () =>\n VERDICT_ORDER.map((v) => ({\n label: VERDICT_LABEL[v],\n count: submissions.filter((s) => s.verdict === v).length,\n color: VERDICT_COLOR[v],\n })).filter((d) => d.count > 0),\n [submissions]\n );\n\n const acceptedRateByTopic = useMemo<TopicRateDatum[]>(() => {\n const buckets = new Map<string, { accepted: number; attempted: number }>();\n submissions.forEach((s) => {\n const topic = s.problem?.topic?.trim() || 'Untagged';\n const bucket = buckets.get(topic) ?? { accepted: 0, attempted: 0 };\n bucket.attempted += 1;\n if (s.verdict === 'ACCEPTED') bucket.accepted += 1;\n buckets.set(topic, bucket);\n });\n return [...buckets.entries()]\n .map(([topic, bucket]) => ({\n topic,\n accepted: bucket.accepted,\n attempted: bucket.attempted,\n rate: Math.round((bucket.accepted / bucket.attempted) * 100),\n }))\n .sort((a, b) => b.rate - a.rate || b.attempted - a.attempted);\n }, [submissions]);\n\n const activityData = useMemo<ActivityDatum[]>(() => {\n const buckets = new Map<number, ActivityDatum>();\n const add = (iso: string, key: 'submissions' | 'enrollments') => {\n const { key: bucketKey, label } = weekBucket(iso);\n const existing = buckets.get(bucketKey) ?? { label, submissions: 0, enrollments: 0 };\n existing[key] += 1;\n buckets.set(bucketKey, existing);\n };\n submissions.forEach((s) => add(s.createdAt, 'submissions'));\n enrollments.forEach((e) => add(e.createdAt, 'enrollments'));\n return [...buckets.entries()]\n .sort((a, b) => a[0] - b[0])\n .slice(-8)\n .map(([, v]) => v);\n }, [submissions, enrollments]);\n\n const hasActivity =\n enrollments.length > 0 ||\n submissions.length > 0 ||\n sessions.length > 0 ||\n certificates.length > 0;\n\n const learnerLoading =\n enrollmentsQ.loading || submissionsQ.loading || sessionsQ.loading || certificatesQ.loading;\n const learnerError =\n enrollmentsQ.error || submissionsQ.error || sessionsQ.error || certificatesQ.error;\n const showLoading = learnerLoading && !hasActivity;\n\n const workspaceOverview = (\n <div>\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">\n {tr('technospam.analytics.workspaceOverviewTitle', 'Workspace overview')}\n </h2>\n <p className=\"mb-4 max-w-2xl text-sm text-text-secondary\">\n {tr(\n 'technospam.analytics.workspaceOverviewDesc',\n 'Live content available across the TechnoSpam learning workspace right now.'\n )}\n </p>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-3\">\n <StatCard\n label={tr('technospam.analytics.statPublishedCourses', 'Published courses')}\n value={coursesTotal}\n icon={Library}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.analytics.statPublishedProblems', 'Published problems')}\n value={problemsTotal}\n icon={Code2}\n tone=\"indigo\"\n />\n <StatCard\n label={tr('technospam.analytics.statHackathons', 'Hackathons')}\n value={hackathonsTotal}\n icon={Trophy}\n tone=\"purple\"\n />\n </div>\n </div>\n );\n\n return (\n <PageLayout\n hero\n title={tr('technospam.analytics.title', 'Analytics')}\n description={tr(\n 'technospam.analytics.description',\n 'Your real learning performance across courses, problems, mentoring and certificates.'\n )}\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> {tr('technospam.common.myLearning', 'My Learning')}\n </Button>\n }\n >\n {!learnerId ? (\n <div className=\"space-y-8\">\n <EmptyView\n icon={<BarChart3 size={26} />}\n title={tr('technospam.analytics.signInTitle', 'Sign in to view your analytics')}\n description={tr(\n 'technospam.analytics.signInDesc',\n \"Your personal learning metrics appear here once you're signed in.\"\n )}\n />\n {workspaceOverview}\n </div>\n ) : showLoading ? (\n <div className=\"space-y-6\">\n <LoadingGrid count={4} height=\"h-24\" />\n <LoadingRows count={2} height=\"h-72\" />\n </div>\n ) : learnerError && !hasActivity ? (\n <ErrorView\n title={tr('technospam.common.errorLoadAnalytics', \"Couldn't load your analytics\")}\n error={learnerError}\n />\n ) : (\n <div className=\"space-y-8\">\n <DateRangeControls\n start={rangeStart}\n end={rangeEnd}\n onStart={setRangeStart}\n onEnd={setRangeEnd}\n onPreset={applyPreset}\n onClear={clearRange}\n tr={tr}\n />\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3 xl:grid-cols-4\">\n <StatCard\n label={tr('technospam.analytics.statCoursesEnrolled', 'Courses enrolled')}\n value={enrollments.length}\n icon={BookOpen}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.analytics.statCoursesCompleted', 'Courses completed')}\n value={metrics.coursesCompleted}\n icon={GraduationCap}\n tone=\"green\"\n />\n <StatCard\n label={tr('technospam.analytics.statAvgProgress', 'Avg progress')}\n value={`${metrics.avgProgress}%`}\n icon={TrendingUp}\n tone=\"purple\"\n />\n <StatCard\n label={tr('technospam.analytics.statProblemsSolved', 'Problems solved')}\n value={metrics.problemsSolved}\n icon={Code2}\n tone=\"indigo\"\n />\n <StatCard\n label={tr('technospam.analytics.statAcceptanceRate', 'Acceptance rate')}\n value={`${metrics.acceptanceRate}%`}\n icon={Percent}\n tone=\"teal\"\n />\n <StatCard\n label={tr('technospam.analytics.statMentorSessions', 'Mentor sessions')}\n value={sessions.length}\n icon={Users}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.analytics.statCertificates', 'Certificates')}\n value={certificates.length}\n icon={Award}\n tone=\"amber\"\n />\n </div>\n\n {!hasActivity ? (\n <EmptyView\n icon={<Activity size={26} />}\n title={tr('technospam.analytics.noActivityTitle', 'No learning activity yet')}\n description={tr(\n 'technospam.analytics.noActivityDesc',\n 'Enroll in a course or solve a problem and your progress charts will build here. TechnoSpam is pre-launch — start learning to see your data.'\n )}\n action={\n <Button onClick={() => navigate('/courses/courses-page')}>\n {tr('technospam.common.browseCourses', 'Browse courses')}\n </Button>\n }\n />\n ) : (\n <div className=\"space-y-4\">\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <ChartCard\n title={tr(\n 'technospam.analytics.enrollmentsByStatusTitle',\n 'Enrollments by status'\n )}\n subtitle={tr(\n 'technospam.analytics.enrollmentsByStatusSubtitle',\n 'How your courses are progressing'\n )}\n icon={BookOpen}\n tone=\"blue\"\n isEmpty={enrollmentsByStatus.length === 0}\n emptyLabel={tr('technospam.analytics.noEnrollments', 'No enrollments yet')}\n >\n <PieChart>\n <Pie\n data={enrollmentsByStatus}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={60}\n outerRadius={92}\n paddingAngle={2}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n >\n {enrollmentsByStatus.map((d) => (\n // Recharts renders each slice as an <svg role=\"img\">; name it\n // so screen readers hear the status and count (axe svg-img-alt).\n <Cell key={d.name} fill={d.color} aria-label={`${d.name}: ${d.value}`} />\n ))}\n </Pie>\n <ReTooltip contentStyle={TOOLTIP_STYLE} labelStyle={TOOLTIP_LABEL_STYLE} />\n <Legend iconType=\"circle\" wrapperStyle={{ fontSize: 12 }} />\n </PieChart>\n </ChartCard>\n\n <ChartCard\n title={tr(\n 'technospam.analytics.submissionsByVerdictTitle',\n 'Submissions by verdict'\n )}\n subtitle={tr(\n 'technospam.analytics.submissionsByVerdictSubtitle',\n 'Outcomes across your attempts'\n )}\n icon={Code2}\n tone=\"purple\"\n isEmpty={submissionsByVerdict.length === 0}\n emptyLabel={tr('technospam.analytics.noSubmissions', 'No submissions yet')}\n >\n <BarChart\n data={submissionsByVerdict}\n margin={{ top: 8, right: 8, left: -18, bottom: 0 }}\n >\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"var(--color-border-seam)\"\n vertical={false}\n />\n <XAxis\n dataKey=\"label\"\n tick={AXIS_TICK}\n axisLine={{ stroke: 'var(--color-border-seam)' }}\n tickLine={false}\n />\n <YAxis\n allowDecimals={false}\n tick={AXIS_TICK}\n axisLine={false}\n tickLine={false}\n />\n <ReTooltip\n contentStyle={TOOLTIP_STYLE}\n labelStyle={TOOLTIP_LABEL_STYLE}\n cursor={{ fill: 'var(--color-accent-primary)', fillOpacity: 0.08 }}\n />\n <Bar dataKey=\"count\" name=\"Submissions\" radius={[6, 6, 0, 0]} maxBarSize={56}>\n {submissionsByVerdict.map((d) => (\n <Cell key={d.label} fill={d.color} />\n ))}\n </Bar>\n </BarChart>\n </ChartCard>\n </div>\n\n <TopicAcceptanceCard\n data={acceptedRateByTopic}\n title={tr('technospam.analytics.topicAcceptanceTitle', 'Accepted rate by topic')}\n subtitle={tr(\n 'technospam.analytics.topicAcceptanceSubtitle',\n \"Where your solutions land, and where they don't\"\n )}\n noData={tr(\n 'technospam.analytics.noTopicData',\n 'Solve a practice problem and your per-topic rate builds here'\n )}\n lowestRateLabel={(topic, accepted, attempted) =>\n tr(\n 'technospam.analytics.lowestMeasuredRate',\n 'Lowest measured rate: {{topic}} — {{accepted}} of {{attempted}} attempts accepted in the loaded range.',\n { topic, accepted, attempted }\n )\n }\n />\n\n <GlassCard className=\"p-5 transition-shadow duration-200 hover:shadow-lg\">\n <ChartHeader\n icon={Activity}\n title={tr(\n 'technospam.analytics.activityOverTimeTitle',\n 'Learning activity over time'\n )}\n subtitle={tr(\n 'technospam.analytics.activityOverTimeSubtitle',\n 'Submissions and enrollments per week'\n )}\n tone=\"teal\"\n />\n {activityData.length === 0 ? (\n <div className=\"flex h-[240px] items-center justify-center text-sm text-text-muted\">\n {tr('technospam.analytics.noDatedActivity', 'No dated activity yet')}\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height={280}>\n <AreaChart\n data={activityData}\n margin={{ top: 8, right: 12, left: -18, bottom: 0 }}\n >\n <defs>\n <linearGradient id=\"tec-grad-submissions\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop\n offset=\"0%\"\n stopColor=\"var(--color-accent-primary)\"\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor=\"var(--color-accent-primary)\"\n stopOpacity={0}\n />\n </linearGradient>\n <linearGradient id=\"tec-grad-enrollments\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop\n offset=\"0%\"\n stopColor=\"var(--color-accent-teal)\"\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor=\"var(--color-accent-teal)\"\n stopOpacity={0}\n />\n </linearGradient>\n </defs>\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"var(--color-border-seam)\"\n vertical={false}\n />\n <XAxis\n dataKey=\"label\"\n tick={AXIS_TICK}\n axisLine={{ stroke: 'var(--color-border-seam)' }}\n tickLine={false}\n />\n <YAxis\n allowDecimals={false}\n tick={AXIS_TICK}\n axisLine={false}\n tickLine={false}\n />\n <ReTooltip contentStyle={TOOLTIP_STYLE} labelStyle={TOOLTIP_LABEL_STYLE} />\n <Legend iconType=\"circle\" wrapperStyle={{ fontSize: 12 }} />\n <Area\n type=\"monotone\"\n dataKey=\"submissions\"\n name=\"Submissions\"\n stroke=\"var(--color-accent-primary)\"\n strokeWidth={2}\n fill=\"url(#tec-grad-submissions)\"\n />\n <Area\n type=\"monotone\"\n dataKey=\"enrollments\"\n name=\"Enrollments\"\n stroke=\"var(--color-accent-teal)\"\n strokeWidth={2}\n fill=\"url(#tec-grad-enrollments)\"\n />\n </AreaChart>\n </ResponsiveContainer>\n )}\n </GlassCard>\n </div>\n )}\n\n {workspaceOverview}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -135,6 +135,7 @@ function U({ onAdded: t }) {
135
135
  onValueChange: l,
136
136
  children: [/* @__PURE__ */ O(F, {
137
137
  className: "w-full",
138
+ "aria-label": "Hackathon",
138
139
  children: /* @__PURE__ */ O(I, { placeholder: "Choose a hackathon" })
139
140
  }), /* @__PURE__ */ O(N, { children: a.map((e) => /* @__PURE__ */ O(P, {
140
141
  value: e.id,
@@ -1 +1 @@
1
- {"version":3,"file":"ApplicationsPortfolioPagePage.js","names":[],"sources":["../../../src/technospam/pages/ApplicationsPortfolioPagePage.tsx"],"sourcesContent":["/**\n * Learner Portfolio (BOFF-4432).\n *\n * Aggregates real completed work — issued certificates, completed courses,\n * and submitted hackathon projects — into a single showcase the learner\n * controls. Every item's title/description/link/completion date is copied\n * server-side from the real source record at add-time; nothing here is\n * free-form/fabricated.\n *\n * DEFERRED SCOPE (documented, not silently dropped): the backend already\n * exposes a public, unauthenticated `tecPublicPortfolio(slug)` field\n * (@rbac public: true) that returns real published-portfolio data. This\n * page generates the slug and lets the learner copy the public link, but\n * there is no unauthenticated *page* yet that renders it — the MFE only\n * mounts inside the authenticated app shell, and alpha gates every web\n * hostname behind Cloudflare Access. Standing up a real public viewer\n * needs app-shell + CF Access allow-list work coordinated with\n * infra/tf-cloudflare, which is out of scope for this pass.\n */\nimport { useState } from 'react';\nimport {\n Award,\n Briefcase,\n Check,\n Copy,\n ExternalLink,\n GraduationCap,\n Link2,\n Plus,\n Trash2,\n Trophy,\n X,\n} from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { EmptyView, ErrorView, LoadingRows, PageLayout } from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n type TecPortfolioItem,\n type TecPortfolioItemKind,\n useAddPortfolioItem,\n useCertificatesQuery,\n useEnrollmentsQuery,\n useHackathonsQuery,\n useMyPortfolioQuery,\n usePublishPortfolio,\n useRemovePortfolioItem,\n useTeamsQuery,\n useUnpublishPortfolio,\n} from '../api/technospamApi';\nimport { formatDate } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { nativeConfirm } from '@burdenoff/fe-libs/shared/native';\n\nconst KIND_ICON: Record<TecPortfolioItemKind, typeof Award> = {\n HACKATHON_PROJECT: Trophy,\n CERTIFICATE: Award,\n COURSE_COMPLETION: GraduationCap,\n};\n\nconst KIND_LABEL: Record<TecPortfolioItemKind, string> = {\n HACKATHON_PROJECT: 'Hackathon project',\n CERTIFICATE: 'Certificate',\n COURSE_COMPLETION: 'Completed course',\n};\n\nfunction ItemCard({ item, onRemove }: { item: TecPortfolioItem; onRemove: () => void }) {\n const Icon = KIND_ICON[item.kind];\n return (\n <GlassCard className=\"group flex flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex min-w-0 gap-3\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-accent-purple/15 text-accent-purple ring-1 ring-accent-purple/20\">\n <Icon size={18} />\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\">{item.title}</span>\n <span className=\"rounded-full border border-border-seam px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-text-muted\">\n {KIND_LABEL[item.kind]}\n </span>\n </div>\n {item.description ? (\n <p className=\"mt-1 line-clamp-2 text-xs text-text-secondary\">{item.description}</p>\n ) : null}\n <div className=\"mt-2 flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n <span>Completed {formatDate(item.completionDate)}</span>\n {item.link ? (\n <a\n href={item.link}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-flex items-center gap-1 text-accent-primary hover:underline\"\n >\n <ExternalLink size={12} /> View\n </a>\n ) : null}\n </div>\n </div>\n </div>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"shrink-0 text-status-error-text hover:text-status-error-text\"\n onClick={onRemove}\n >\n <Trash2 size={14} />\n </Button>\n </GlassCard>\n );\n}\n\nfunction AddFromCertificates({ onAdded }: { onAdded: () => void }) {\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n const { data } = useCertificatesQuery(learnerId ? { learnerId } : {});\n const [addItem, { loading }] = useAddPortfolioItem();\n const certificates = (data?.tecListCertificates ?? []).filter((c) => c.status === 'ISSUED');\n\n if (certificates.length === 0) {\n return <p className=\"text-xs text-text-muted\">No issued certificates yet.</p>;\n }\n return (\n <div className=\"space-y-2\">\n {certificates.map((c) => (\n <div\n key={c.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam p-2\"\n >\n <span className=\"min-w-0 truncate text-xs text-text-primary\">\n {c.course?.title ?? 'Certificate'} · Serial {c.serial}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={loading}\n onClick={async () => {\n const res = await addItem({\n variables: { input: { kind: 'CERTIFICATE', sourceId: c.id } },\n });\n if (res.data?.tecAddPortfolioItem) {\n notify.success('Added to portfolio');\n onAdded();\n }\n }}\n >\n <Plus size={12} /> Add\n </Button>\n </div>\n ))}\n </div>\n );\n}\n\nfunction AddFromCourses({ onAdded }: { onAdded: () => void }) {\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n const { data } = useEnrollmentsQuery(learnerId ? { learnerId } : {});\n const [addItem, { loading }] = useAddPortfolioItem();\n const completed = (data?.tecListEnrollments?.items ?? []).filter((e) => e.status === 'COMPLETED');\n\n if (completed.length === 0) {\n return <p className=\"text-xs text-text-muted\">No completed courses yet.</p>;\n }\n return (\n <div className=\"space-y-2\">\n {completed.map((e) => (\n <div\n key={e.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam p-2\"\n >\n <span className=\"min-w-0 truncate text-xs text-text-primary\">\n {e.course?.title ?? 'Course'}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={loading}\n onClick={async () => {\n const res = await addItem({\n variables: { input: { kind: 'COURSE_COMPLETION', sourceId: e.id } },\n });\n if (res.data?.tecAddPortfolioItem) {\n notify.success('Added to portfolio');\n onAdded();\n }\n }}\n >\n <Plus size={12} /> Add\n </Button>\n </div>\n ))}\n </div>\n );\n}\n\nfunction AddFromHackathons({ onAdded }: { onAdded: () => void }) {\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n const { data: hackathonData } = useHackathonsQuery({});\n const hackathons = hackathonData?.tecListHackathons?.items ?? [];\n const [hackathonId, setHackathonId] = useState<string>('');\n const { data: teamsData } = useTeamsQuery(hackathonId);\n const [addItem, { loading }] = useAddPortfolioItem();\n\n const myTeams = (teamsData?.tecListTeams?.items ?? []).filter(\n (t) =>\n (t.status === 'SUBMITTED' || t.status === 'WINNER') &&\n (t.captainId === learnerId || t.memberIds.includes(learnerId ?? ''))\n );\n\n return (\n <div className=\"space-y-2\">\n <Select value={hackathonId} onValueChange={setHackathonId}>\n <SelectTrigger className=\"w-full\">\n <SelectValue placeholder=\"Choose a hackathon\" />\n </SelectTrigger>\n <SelectContent>\n {hackathons.map((h) => (\n <SelectItem key={h.id} value={h.id}>\n {h.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {hackathonId && myTeams.length === 0 ? (\n <p className=\"text-xs text-text-muted\">No submitted team of yours in this hackathon.</p>\n ) : null}\n {myTeams.map((t) => (\n <div\n key={t.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam p-2\"\n >\n <span className=\"min-w-0 truncate text-xs text-text-primary\">\n {t.projectName || t.name}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={loading}\n onClick={async () => {\n const res = await addItem({\n variables: { input: { kind: 'HACKATHON_PROJECT', sourceId: t.id } },\n });\n if (res.data?.tecAddPortfolioItem) {\n notify.success('Added to portfolio');\n onAdded();\n }\n }}\n >\n <Plus size={12} /> Add\n </Button>\n </div>\n ))}\n </div>\n );\n}\n\nexport function ApplicationsPortfolioPagePage() {\n const { data, loading, error, refetch } = useMyPortfolioQuery();\n const [removeItem] = useRemovePortfolioItem();\n const [publish, { loading: publishing }] = usePublishPortfolio();\n const [unpublish, { loading: unpublishing }] = useUnpublishPortfolio();\n const [showAdd, setShowAdd] = useState(false);\n const [copied, setCopied] = useState(false);\n\n const portfolio = data?.tecMyPortfolio;\n const items = portfolio?.items ?? [];\n\n const publicUrl = portfolio?.publicSlug\n ? `${typeof window !== 'undefined' ? window.location.origin : ''}/technospam/portfolio/${portfolio.publicSlug}`\n : null;\n\n const handleRemove = async (id: string, title: string) => {\n const confirmed = await nativeConfirm({\n title: 'Remove from portfolio',\n message: `Remove \"${title}\" from your portfolio? This does not delete the underlying record.`,\n okButtonTitle: 'Remove',\n cancelButtonTitle: 'Cancel',\n });\n if (!confirmed) return;\n const res = await removeItem({ variables: { id } });\n if (res.data?.tecRemovePortfolioItem) notify.success('Removed from portfolio');\n };\n\n const handleTogglePublish = async () => {\n if (!portfolio) return;\n if (portfolio.isPublished) {\n const res = await unpublish();\n if (res.data?.tecUnpublishPortfolio) notify.success('Portfolio unpublished');\n } else {\n const res = await publish();\n if (res.data?.tecPublishPortfolio) notify.success('Portfolio published');\n }\n };\n\n const handleCopyLink = async () => {\n if (!publicUrl) return;\n await navigator.clipboard.writeText(publicUrl);\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n };\n\n return (\n <PageLayout\n hero\n title=\"Portfolio\"\n description=\"Showcase your real completed courses, certificates, and hackathon projects.\"\n actions={\n portfolio ? (\n <Button\n variant={portfolio.isPublished ? 'outline' : 'default'}\n onClick={() => void handleTogglePublish()}\n disabled={publishing || unpublishing || (!portfolio.isPublished && items.length === 0)}\n >\n {portfolio.isPublished ? 'Unpublish' : 'Publish'}\n </Button>\n ) : null\n }\n >\n {portfolio?.isPublished && publicUrl ? (\n <GlassCard className=\"mb-6 flex flex-wrap items-center justify-between gap-3 p-3\">\n <div className=\"flex min-w-0 items-center gap-2 text-xs text-text-secondary\">\n <Link2 size={14} className=\"shrink-0 text-accent-teal\" />\n <span className=\"truncate\">{publicUrl}</span>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void handleCopyLink()}>\n {copied ? <Check size={14} /> : <Copy size={14} />} {copied ? 'Copied' : 'Copy link'}\n </Button>\n </GlassCard>\n ) : null}\n\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Your items</h2>\n <Button\n size=\"sm\"\n variant={showAdd ? 'outline' : 'default'}\n onClick={() => setShowAdd((v) => !v)}\n >\n {showAdd ? <X size={14} /> : <Plus size={14} />} {showAdd ? 'Close' : 'Add item'}\n </Button>\n </div>\n\n {showAdd ? (\n <GlassCard className=\"mb-6 space-y-4 p-4\">\n <div>\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted\">\n From certificates\n </h3>\n <AddFromCertificates onAdded={() => void refetch()} />\n </div>\n <div>\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted\">\n From completed courses\n </h3>\n <AddFromCourses onAdded={() => void refetch()} />\n </div>\n <div>\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted\">\n From hackathon projects\n </h3>\n <AddFromHackathons onAdded={() => void refetch()} />\n </div>\n </GlassCard>\n ) : null}\n\n {loading && !portfolio ? (\n <LoadingRows count={3} height=\"h-24\" />\n ) : error ? (\n <ErrorView title=\"Couldn't load your portfolio\" error={error} />\n ) : items.length === 0 ? (\n <EmptyView\n icon={<Briefcase size={26} />}\n title=\"Your portfolio is empty\"\n description=\"Add a certificate, completed course, or submitted hackathon project to start building your showcase.\"\n action={\n <Button onClick={() => setShowAdd(true)}>\n <Plus size={15} /> Add your first item\n </Button>\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {items.map((item) => (\n <ItemCard\n key={item.id}\n item={item}\n onRemove={() => void handleRemove(item.id, item.title)}\n />\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ApplicationsPortfolioPagePage.js","names":[],"sources":["../../../src/technospam/pages/ApplicationsPortfolioPagePage.tsx"],"sourcesContent":["/**\n * Learner Portfolio (BOFF-4432).\n *\n * Aggregates real completed work — issued certificates, completed courses,\n * and submitted hackathon projects — into a single showcase the learner\n * controls. Every item's title/description/link/completion date is copied\n * server-side from the real source record at add-time; nothing here is\n * free-form/fabricated.\n *\n * DEFERRED SCOPE (documented, not silently dropped): the backend already\n * exposes a public, unauthenticated `tecPublicPortfolio(slug)` field\n * (@rbac public: true) that returns real published-portfolio data. This\n * page generates the slug and lets the learner copy the public link, but\n * there is no unauthenticated *page* yet that renders it — the MFE only\n * mounts inside the authenticated app shell, and alpha gates every web\n * hostname behind Cloudflare Access. Standing up a real public viewer\n * needs app-shell + CF Access allow-list work coordinated with\n * infra/tf-cloudflare, which is out of scope for this pass.\n */\nimport { useState } from 'react';\nimport {\n Award,\n Briefcase,\n Check,\n Copy,\n ExternalLink,\n GraduationCap,\n Link2,\n Plus,\n Trash2,\n Trophy,\n X,\n} from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { EmptyView, ErrorView, LoadingRows, PageLayout } from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n type TecPortfolioItem,\n type TecPortfolioItemKind,\n useAddPortfolioItem,\n useCertificatesQuery,\n useEnrollmentsQuery,\n useHackathonsQuery,\n useMyPortfolioQuery,\n usePublishPortfolio,\n useRemovePortfolioItem,\n useTeamsQuery,\n useUnpublishPortfolio,\n} from '../api/technospamApi';\nimport { formatDate } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { nativeConfirm } from '@burdenoff/fe-libs/shared/native';\n\nconst KIND_ICON: Record<TecPortfolioItemKind, typeof Award> = {\n HACKATHON_PROJECT: Trophy,\n CERTIFICATE: Award,\n COURSE_COMPLETION: GraduationCap,\n};\n\nconst KIND_LABEL: Record<TecPortfolioItemKind, string> = {\n HACKATHON_PROJECT: 'Hackathon project',\n CERTIFICATE: 'Certificate',\n COURSE_COMPLETION: 'Completed course',\n};\n\nfunction ItemCard({ item, onRemove }: { item: TecPortfolioItem; onRemove: () => void }) {\n const Icon = KIND_ICON[item.kind];\n return (\n <GlassCard className=\"group flex flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex min-w-0 gap-3\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-accent-purple/15 text-accent-purple ring-1 ring-accent-purple/20\">\n <Icon size={18} />\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\">{item.title}</span>\n <span className=\"rounded-full border border-border-seam px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-text-muted\">\n {KIND_LABEL[item.kind]}\n </span>\n </div>\n {item.description ? (\n <p className=\"mt-1 line-clamp-2 text-xs text-text-secondary\">{item.description}</p>\n ) : null}\n <div className=\"mt-2 flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n <span>Completed {formatDate(item.completionDate)}</span>\n {item.link ? (\n <a\n href={item.link}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-flex items-center gap-1 text-accent-primary hover:underline\"\n >\n <ExternalLink size={12} /> View\n </a>\n ) : null}\n </div>\n </div>\n </div>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"shrink-0 text-status-error-text hover:text-status-error-text\"\n onClick={onRemove}\n >\n <Trash2 size={14} />\n </Button>\n </GlassCard>\n );\n}\n\nfunction AddFromCertificates({ onAdded }: { onAdded: () => void }) {\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n const { data } = useCertificatesQuery(learnerId ? { learnerId } : {});\n const [addItem, { loading }] = useAddPortfolioItem();\n const certificates = (data?.tecListCertificates ?? []).filter((c) => c.status === 'ISSUED');\n\n if (certificates.length === 0) {\n return <p className=\"text-xs text-text-muted\">No issued certificates yet.</p>;\n }\n return (\n <div className=\"space-y-2\">\n {certificates.map((c) => (\n <div\n key={c.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam p-2\"\n >\n <span className=\"min-w-0 truncate text-xs text-text-primary\">\n {c.course?.title ?? 'Certificate'} · Serial {c.serial}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={loading}\n onClick={async () => {\n const res = await addItem({\n variables: { input: { kind: 'CERTIFICATE', sourceId: c.id } },\n });\n if (res.data?.tecAddPortfolioItem) {\n notify.success('Added to portfolio');\n onAdded();\n }\n }}\n >\n <Plus size={12} /> Add\n </Button>\n </div>\n ))}\n </div>\n );\n}\n\nfunction AddFromCourses({ onAdded }: { onAdded: () => void }) {\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n const { data } = useEnrollmentsQuery(learnerId ? { learnerId } : {});\n const [addItem, { loading }] = useAddPortfolioItem();\n const completed = (data?.tecListEnrollments?.items ?? []).filter((e) => e.status === 'COMPLETED');\n\n if (completed.length === 0) {\n return <p className=\"text-xs text-text-muted\">No completed courses yet.</p>;\n }\n return (\n <div className=\"space-y-2\">\n {completed.map((e) => (\n <div\n key={e.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam p-2\"\n >\n <span className=\"min-w-0 truncate text-xs text-text-primary\">\n {e.course?.title ?? 'Course'}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={loading}\n onClick={async () => {\n const res = await addItem({\n variables: { input: { kind: 'COURSE_COMPLETION', sourceId: e.id } },\n });\n if (res.data?.tecAddPortfolioItem) {\n notify.success('Added to portfolio');\n onAdded();\n }\n }}\n >\n <Plus size={12} /> Add\n </Button>\n </div>\n ))}\n </div>\n );\n}\n\nfunction AddFromHackathons({ onAdded }: { onAdded: () => void }) {\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n const { data: hackathonData } = useHackathonsQuery({});\n const hackathons = hackathonData?.tecListHackathons?.items ?? [];\n const [hackathonId, setHackathonId] = useState<string>('');\n const { data: teamsData } = useTeamsQuery(hackathonId);\n const [addItem, { loading }] = useAddPortfolioItem();\n\n const myTeams = (teamsData?.tecListTeams?.items ?? []).filter(\n (t) =>\n (t.status === 'SUBMITTED' || t.status === 'WINNER') &&\n (t.captainId === learnerId || t.memberIds.includes(learnerId ?? ''))\n );\n\n return (\n <div className=\"space-y-2\">\n <Select value={hackathonId} onValueChange={setHackathonId}>\n <SelectTrigger className=\"w-full\" aria-label=\"Hackathon\">\n <SelectValue placeholder=\"Choose a hackathon\" />\n </SelectTrigger>\n <SelectContent>\n {hackathons.map((h) => (\n <SelectItem key={h.id} value={h.id}>\n {h.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {hackathonId && myTeams.length === 0 ? (\n <p className=\"text-xs text-text-muted\">No submitted team of yours in this hackathon.</p>\n ) : null}\n {myTeams.map((t) => (\n <div\n key={t.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam p-2\"\n >\n <span className=\"min-w-0 truncate text-xs text-text-primary\">\n {t.projectName || t.name}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={loading}\n onClick={async () => {\n const res = await addItem({\n variables: { input: { kind: 'HACKATHON_PROJECT', sourceId: t.id } },\n });\n if (res.data?.tecAddPortfolioItem) {\n notify.success('Added to portfolio');\n onAdded();\n }\n }}\n >\n <Plus size={12} /> Add\n </Button>\n </div>\n ))}\n </div>\n );\n}\n\nexport function ApplicationsPortfolioPagePage() {\n const { data, loading, error, refetch } = useMyPortfolioQuery();\n const [removeItem] = useRemovePortfolioItem();\n const [publish, { loading: publishing }] = usePublishPortfolio();\n const [unpublish, { loading: unpublishing }] = useUnpublishPortfolio();\n const [showAdd, setShowAdd] = useState(false);\n const [copied, setCopied] = useState(false);\n\n const portfolio = data?.tecMyPortfolio;\n const items = portfolio?.items ?? [];\n\n const publicUrl = portfolio?.publicSlug\n ? `${typeof window !== 'undefined' ? window.location.origin : ''}/technospam/portfolio/${portfolio.publicSlug}`\n : null;\n\n const handleRemove = async (id: string, title: string) => {\n const confirmed = await nativeConfirm({\n title: 'Remove from portfolio',\n message: `Remove \"${title}\" from your portfolio? This does not delete the underlying record.`,\n okButtonTitle: 'Remove',\n cancelButtonTitle: 'Cancel',\n });\n if (!confirmed) return;\n const res = await removeItem({ variables: { id } });\n if (res.data?.tecRemovePortfolioItem) notify.success('Removed from portfolio');\n };\n\n const handleTogglePublish = async () => {\n if (!portfolio) return;\n if (portfolio.isPublished) {\n const res = await unpublish();\n if (res.data?.tecUnpublishPortfolio) notify.success('Portfolio unpublished');\n } else {\n const res = await publish();\n if (res.data?.tecPublishPortfolio) notify.success('Portfolio published');\n }\n };\n\n const handleCopyLink = async () => {\n if (!publicUrl) return;\n await navigator.clipboard.writeText(publicUrl);\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n };\n\n return (\n <PageLayout\n hero\n title=\"Portfolio\"\n description=\"Showcase your real completed courses, certificates, and hackathon projects.\"\n actions={\n portfolio ? (\n <Button\n variant={portfolio.isPublished ? 'outline' : 'default'}\n onClick={() => void handleTogglePublish()}\n disabled={publishing || unpublishing || (!portfolio.isPublished && items.length === 0)}\n >\n {portfolio.isPublished ? 'Unpublish' : 'Publish'}\n </Button>\n ) : null\n }\n >\n {portfolio?.isPublished && publicUrl ? (\n <GlassCard className=\"mb-6 flex flex-wrap items-center justify-between gap-3 p-3\">\n <div className=\"flex min-w-0 items-center gap-2 text-xs text-text-secondary\">\n <Link2 size={14} className=\"shrink-0 text-accent-teal\" />\n <span className=\"truncate\">{publicUrl}</span>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void handleCopyLink()}>\n {copied ? <Check size={14} /> : <Copy size={14} />} {copied ? 'Copied' : 'Copy link'}\n </Button>\n </GlassCard>\n ) : null}\n\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Your items</h2>\n <Button\n size=\"sm\"\n variant={showAdd ? 'outline' : 'default'}\n onClick={() => setShowAdd((v) => !v)}\n >\n {showAdd ? <X size={14} /> : <Plus size={14} />} {showAdd ? 'Close' : 'Add item'}\n </Button>\n </div>\n\n {showAdd ? (\n <GlassCard className=\"mb-6 space-y-4 p-4\">\n <div>\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted\">\n From certificates\n </h3>\n <AddFromCertificates onAdded={() => void refetch()} />\n </div>\n <div>\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted\">\n From completed courses\n </h3>\n <AddFromCourses onAdded={() => void refetch()} />\n </div>\n <div>\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted\">\n From hackathon projects\n </h3>\n <AddFromHackathons onAdded={() => void refetch()} />\n </div>\n </GlassCard>\n ) : null}\n\n {loading && !portfolio ? (\n <LoadingRows count={3} height=\"h-24\" />\n ) : error ? (\n <ErrorView title=\"Couldn't load your portfolio\" error={error} />\n ) : items.length === 0 ? (\n <EmptyView\n icon={<Briefcase size={26} />}\n title=\"Your portfolio is empty\"\n description=\"Add a certificate, completed course, or submitted hackathon project to start building your showcase.\"\n action={\n <Button onClick={() => setShowAdd(true)}>\n <Plus size={15} /> Add your first item\n </Button>\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {items.map((item) => (\n <ItemCard\n key={item.id}\n item={item}\n onRemove={() => void handleRemove(item.id, item.title)}\n />\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -288,6 +288,7 @@ function A() {
288
288
  onValueChange: W,
289
289
  children: [/* @__PURE__ */ w(Se, {
290
290
  className: "w-full sm:w-48",
291
+ "aria-label": "Language",
291
292
  children: /* @__PURE__ */ w(Ce, { placeholder: "Choose a language" })
292
293
  }), /* @__PURE__ */ w(be, { children: H.map((e) => /* @__PURE__ */ w(xe, {
293
294
  value: e,
@@ -308,7 +309,7 @@ function A() {
308
309
  /* @__PURE__ */ w("span", { className: "h-2.5 w-2.5 rounded-full bg-status-success-border" })
309
310
  ]
310
311
  }), /* @__PURE__ */ T("span", {
311
- className: "inline-flex items-center gap-1.5 font-mono text-xs text-text-muted",
312
+ className: "inline-flex items-center gap-1.5 font-mono text-xs text-text-secondary",
312
313
  children: [
313
314
  /* @__PURE__ */ w(y, { size: 13 }),
314
315
  " ",
@@ -1 +1 @@
1
- {"version":3,"file":"ApplicationsProblemDetailsPagePage.js","names":[],"sources":["../../../src/technospam/pages/ApplicationsProblemDetailsPagePage.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport {\n Clock,\n Code2,\n FileText,\n FlaskConical,\n Layers,\n RefreshCw,\n Send,\n Sparkles,\n Tag,\n Trophy,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n GlassCard,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Skeleton,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport {\n CodePanel,\n EmptyView,\n ErrorView,\n IoBlock,\n LoadingRows,\n PageLayout,\n PremiumBadge,\n StatCard,\n StatusBadge,\n SubmissionReviewPanel,\n TagStrip,\n TestResultsPanel,\n UpgradeGate,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useProblemQuery,\n useRegradeSubmission,\n useSubmissionsQuery,\n useSubmitSolution,\n type TecSubmission,\n} from '../api/technospamApi';\nimport { formatDateTime, humanizeEnum } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\nimport {\n FOCUSED_CARD_CLASS,\n focusableCardProps,\n useFocusedRecordParam,\n} from '../hooks/useFocusedRecordParam';\nimport { FOCUS_PARAMS } from '../utils/pageContext';\nimport { cn } from '../utils/cn';\n\nconst FALLBACK_LANGUAGES = ['javascript', 'python', 'typescript'];\n\n/**\n * A submission made while the grading sandbox is cold comes back PENDING and is\n * graded in the background (BOFF-4414). Poll the submissions list until the\n * verdict lands, giving up after a few minutes so a genuinely failed grade\n * falls back to the manual re-run button.\n */\nconst PENDING_POLL_INTERVAL_MS = 3_000;\nconst PENDING_POLL_MAX_MS = 3 * 60_000;\n\nfunction SubmissionRow({\n s,\n focused,\n onToggle,\n}: {\n s: TecSubmission;\n /** The focused submission (`?submissionId=`) is the one the assistant acts on. */\n focused: boolean;\n onToggle: () => void;\n}) {\n return (\n <div\n className={cn(\n 'cursor-pointer rounded-lg border border-transparent px-2 py-3 transition-colors hover:border-border-seam hover:bg-bg-sunken',\n focused && FOCUSED_CARD_CLASS\n )}\n {...focusableCardProps(focused, onToggle)}\n >\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1\">\n <button\n type=\"button\"\n aria-pressed={focused}\n aria-label={`Focus submission from ${formatDateTime(s.createdAt)}`}\n onClick={onToggle}\n className=\"rounded-full\"\n >\n <StatusBadge status={s.verdict} />\n </button>\n <span className=\"inline-flex items-center gap-1 text-sm font-medium text-text-primary\">\n <Code2 size={13} /> {humanizeEnum(s.language)}\n </span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Clock size={12} /> {s.runtimeMs} ms\n </span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Trophy size={12} /> {s.pointsAwarded} pts\n </span>\n <span className=\"ml-auto text-xs text-text-muted\">{formatDateTime(s.createdAt)}</span>\n </div>\n {s.totalCount > 0 ? (\n <p className=\"mt-1 px-0.5 text-xs text-text-muted\">\n Passed {s.passedCount} of {s.totalCount} test cases\n </p>\n ) : null}\n </div>\n );\n}\n\nexport function ApplicationsProblemDetailsPagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n // The focused submission lives in `?submissionId=` so the assistant's page\n // context names it (\"explain this submission\").\n const [focusedSubmissionId, setFocusedSubmissionId] = useFocusedRecordParam(\n FOCUS_PARAMS.submission\n );\n const { currentUser, workspaceId } = useTechnoSpam();\n const navigate = useTechnoSpamNavigate();\n const learnerId = currentUser?.id;\n const { emit } = useTechnoSpamAnalytics();\n\n const { data, loading, error, refetch } = useProblemQuery(id);\n\n useEffect(() => {\n if (id) {\n emit('technospam.problem.viewed', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_PROBLEM',\n });\n }\n }, [id, workspaceId, emit]);\n const {\n data: subsData,\n loading: subsLoading,\n error: subsError,\n refetch: refetchSubs,\n } = useSubmissionsQuery({ ...(learnerId ? { learnerId } : {}), problemId: id });\n const [submit, { loading: submitting }] = useSubmitSolution();\n const [regrade, { loading: regrading }] = useRegradeSubmission();\n\n const problem = data?.tecProblem ?? null;\n\n const languages = useMemo(\n () => (problem?.languages?.length ? problem.languages : FALLBACK_LANGUAGES),\n [problem]\n );\n\n const [language, setLanguage] = useState('');\n const [code, setCode] = useState('');\n const initedRef = useRef(false);\n\n useEffect(() => {\n if (problem && !initedRef.current) {\n setLanguage(problem.languages[0] ?? FALLBACK_LANGUAGES[0]!);\n setCode(problem.starterCode ?? '');\n initedRef.current = true;\n }\n }, [problem]);\n\n const submissions = useMemo(\n () =>\n [...(subsData?.tecListSubmissions?.items ?? [])].sort(\n (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()\n ),\n [subsData]\n );\n\n const handleSubmit = async () => {\n if (!id) return;\n if (!language) {\n notify.error('Pick a language', 'Choose a language before submitting.');\n return;\n }\n if (!code.trim()) {\n notify.error('Add your solution', 'Write some code before submitting.');\n return;\n }\n emit('technospam.problem.submit_clicked', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_PROBLEM',\n language,\n });\n const res = await submit({ variables: { input: { problemId: id, language, code } } });\n if (res.data?.tecSubmitSolution) {\n emit('technospam.problem.submitted', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_PROBLEM',\n submissionId: res.data.tecSubmitSolution.id,\n });\n const graded = res.data.tecSubmitSolution;\n if (graded.verdict === 'PENDING') {\n notify.info(\n 'Solution submitted',\n 'Grading has started — your verdict will appear here in a moment.'\n );\n } else if (graded.verdict === 'ACCEPTED') {\n notify.success('Accepted', `Passed all ${graded.totalCount} test cases.`);\n } else {\n notify.error(\n humanizeEnum(graded.verdict),\n `Passed ${graded.passedCount} of ${graded.totalCount} test cases.`\n );\n }\n await Promise.all([refetch(), refetchSubs()]);\n }\n };\n\n const handleRegrade = async (submissionId: string) => {\n const res = await regrade({ variables: { id: submissionId } });\n const graded = res.data?.tecRegradeSubmission;\n if (!graded) return;\n if (graded.verdict === 'PENDING') {\n notify.error('Still pending', 'The grader is not available yet. Please try again shortly.');\n } else {\n notify.success('Grading complete', `Verdict: ${humanizeEnum(graded.verdict)}.`);\n }\n await Promise.all([refetch(), refetchSubs()]);\n };\n\n /** Most recent submission that actually carries grading detail. */\n const latestGraded = submissions.find((s) => (s.testResults?.length ?? 0) > 0) ?? null;\n const latestSubmission = submissions[0] ?? null;\n const pendingId = latestSubmission?.verdict === 'PENDING' ? latestSubmission.id : null;\n\n // Poll while the newest submission is still being graded in the background.\n const [pollExpiredFor, setPollExpiredFor] = useState<string | null>(null);\n const polling = pendingId !== null && pollExpiredFor !== pendingId;\n useEffect(() => {\n if (!pendingId || pollExpiredFor === pendingId) return undefined;\n const startedAt = Date.now();\n const timer = window.setInterval(() => {\n if (Date.now() - startedAt > PENDING_POLL_MAX_MS) {\n window.clearInterval(timer);\n setPollExpiredFor(pendingId);\n return;\n }\n void refetchSubs();\n }, PENDING_POLL_INTERVAL_MS);\n return () => window.clearInterval(timer);\n }, [pendingId, pollExpiredFor, refetchSubs]);\n\n // Announce the verdict once a background-graded submission settles.\n const awaitedIdRef = useRef<string | null>(null);\n useEffect(() => {\n if (pendingId) {\n awaitedIdRef.current = pendingId;\n return;\n }\n const awaitedId = awaitedIdRef.current;\n if (!awaitedId) return;\n const settled = submissions.find((s) => s.id === awaitedId);\n if (!settled || settled.verdict === 'PENDING') return;\n awaitedIdRef.current = null;\n if (settled.verdict === 'ACCEPTED') {\n notify.success('Accepted', `Passed all ${settled.totalCount} test cases.`);\n } else {\n notify.error(\n humanizeEnum(settled.verdict),\n `Passed ${settled.passedCount} of ${settled.totalCount} test cases.`\n );\n }\n void refetch();\n }, [pendingId, submissions, refetch]);\n\n if (loading && !problem) {\n return (\n <PageLayout title=\"Problem\" backPath=\"/applications/problems-page\">\n <Skeleton className=\"mb-4 h-28 w-full rounded-xl\" />\n <Skeleton className=\"h-72 w-full rounded-xl\" />\n </PageLayout>\n );\n }\n\n if (error || !problem) {\n return (\n <PageLayout title=\"Problem\" backPath=\"/applications/problems-page\">\n {error ? (\n <ErrorView title=\"Couldn't load problem\" error={error} />\n ) : (\n <EmptyView\n title=\"Problem not found\"\n description=\"This problem may have been unpublished.\"\n />\n )}\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n hero\n backPath=\"/applications/problems-page\"\n title={problem.title}\n description={problem.topic ?? undefined}\n >\n <div className=\"mb-5 flex flex-wrap items-center gap-2\">\n <StatusBadge status={problem.difficulty} kind=\"level\" />\n <StatusBadge status={problem.status} />\n {problem.topic ? (\n <Badge variant=\"outline\" className=\"gap-1\">\n <Layers size={12} /> {problem.topic}\n </Badge>\n ) : null}\n {problem.isPremiumOnly ? <PremiumBadge locked={problem.isLocked} /> : null}\n <TagStrip tags={problem.tags} />\n </div>\n\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard label=\"Points\" value={problem.points} icon={Trophy} tone=\"amber\" />\n <StatCard label=\"Languages\" value={languages.length} icon={Code2} tone=\"blue\" />\n <StatCard\n label=\"Difficulty\"\n value={humanizeEnum(problem.difficulty)}\n icon={Layers}\n tone=\"purple\"\n />\n <StatCard label=\"My submissions\" value={submissions.length} icon={FileText} tone=\"teal\" />\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 lg:grid-cols-3\">\n <div className=\"space-y-5 lg:col-span-2\">\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-2 text-base font-semibold text-text-primary\">Problem statement</h2>\n {problem.isLocked ? (\n // BOFF-4416: the API already nulled `statement` for a locked\n // premium problem — this explains why, rather than showing the\n // generic \"being prepared\" message used for a real content gap.\n <UpgradeGate description=\"This problem's statement is part of a paid TechnoSpam plan. Ask a workspace admin to upgrade to unlock it.\" />\n ) : problem.statement ? (\n <p className=\"whitespace-pre-wrap text-sm leading-relaxed text-text-secondary\">\n {problem.statement}\n </p>\n ) : (\n <p className=\"py-4 text-center text-sm text-text-muted\">\n The statement for this problem is being prepared.\n </p>\n )}\n </GlassCard>\n\n {problem.isLocked ? (\n <UpgradeGate\n compact\n description=\"Starter code and sample test cases unlock with a paid TechnoSpam plan.\"\n />\n ) : problem.starterCode ? (\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">Starter code</h2>\n <CodePanel label={humanizeEnum(languages[0] ?? 'code')} bodyClassName=\"max-h-72\">\n {problem.starterCode}\n </CodePanel>\n </GlassCard>\n ) : null}\n\n <GlassCard className=\"p-5\">\n <div className=\"mb-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <h2 className=\"text-base font-semibold text-text-primary\">Submit a solution</h2>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">Language</span>\n <Select value={language} onValueChange={setLanguage}>\n <SelectTrigger className=\"w-full sm:w-48\">\n <SelectValue placeholder=\"Choose a language\" />\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l} value={l}>\n {humanizeEnum(l)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"overflow-hidden rounded-xl border border-border-strong bg-bg-sunken shadow-sm\">\n <div className=\"flex items-center justify-between gap-2 border-b border-border-seam bg-bg-canvas/40 px-4 py-2\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-error-border\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-warning-border\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-success-border\" />\n </div>\n <span className=\"inline-flex items-center gap-1.5 font-mono text-xs text-text-muted\">\n <Code2 size={13} /> {language ? humanizeEnum(language) : 'solution'}\n </span>\n </div>\n <Textarea\n value={code}\n onChange={(e) => setCode(e.target.value)}\n placeholder=\"Write your solution…\"\n className=\"min-h-[280px] resize-y rounded-none border-0 bg-transparent font-mono text-xs leading-relaxed focus-visible:ring-0 focus-visible:ring-offset-0\"\n spellCheck={false}\n />\n </div>\n\n <Button\n className=\"mt-4 w-full\"\n onClick={handleSubmit}\n disabled={submitting || !code.trim()}\n >\n <Send size={16} /> {submitting ? 'Running test cases…' : 'Run & submit'}\n </Button>\n <p className=\"mt-2 text-center text-xs text-text-muted\">\n Your code runs against this problem&apos;s test cases in an isolated sandbox.\n </p>\n </GlassCard>\n\n {problem.publicTestCases?.length ? (\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-1 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <FlaskConical size={16} /> Sample test cases\n </h2>\n <p className=\"mb-3 text-sm text-text-secondary\">\n Your solution reads each input from stdin and must print the expected output. Hidden\n test cases are also run when you submit.\n </p>\n <div className=\"space-y-3\">\n {problem.publicTestCases.map((testCase) => (\n <div key={testCase.id} className=\"rounded-xl border border-border-seam p-3\">\n <div className=\"mb-2 flex flex-wrap items-center gap-x-3 gap-y-1\">\n <span className=\"text-sm font-medium text-text-primary\">{testCase.name}</span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Clock size={12} /> {testCase.timeLimitMs} ms limit\n </span>\n </div>\n <div className=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n <IoBlock label=\"Input\" value={testCase.input} />\n <IoBlock label=\"Expected output\" value={testCase.expectedOutput} />\n </div>\n </div>\n ))}\n </div>\n </GlassCard>\n ) : null}\n\n {pendingId ? (\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-2 text-base font-semibold text-text-primary\">Latest run</h2>\n {polling ? (\n <p className=\"mb-3 inline-flex items-center gap-2 text-sm text-text-secondary\">\n <RefreshCw size={14} className=\"animate-spin\" />\n Grading your submission — the grader is starting up. Your verdict will appear here\n automatically.\n </p>\n ) : (\n <p className=\"mb-3 text-sm text-text-secondary\">\n Your most recent submission has not been graded yet — the grader was unavailable.\n You can re-run it without resubmitting your code.\n </p>\n )}\n <Button\n variant=\"outline\"\n onClick={() => void handleRegrade(pendingId)}\n disabled={regrading}\n >\n <RefreshCw size={16} /> {regrading ? 'Re-running…' : 'Re-run grader'}\n </Button>\n </GlassCard>\n ) : latestGraded ? (\n <>\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">Latest run</h2>\n <TestResultsPanel submission={latestGraded} />\n </GlassCard>\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-3 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <Sparkles size={16} /> TechnoSage review\n </h2>\n <SubmissionReviewPanel\n submission={latestGraded}\n onOpenProblem={(problemId) =>\n navigate(`/applications/problem-details-page?id=${problemId}`)\n }\n />\n </GlassCard>\n </>\n ) : null}\n </div>\n\n <div className=\"lg:col-span-1\">\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">My submissions</h2>\n {subsLoading && !submissions.length ? (\n <LoadingRows count={3} height=\"h-12\" />\n ) : subsError ? (\n <ErrorView title=\"Couldn't load submissions\" error={subsError} />\n ) : submissions.length === 0 ? (\n <EmptyView\n icon={<FileText size={24} />}\n title=\"No submissions yet\"\n description=\"Submit a solution above to see your verdicts and points here.\"\n />\n ) : (\n <div className=\"space-y-1\">\n {submissions.map((s) => (\n <SubmissionRow\n key={s.id}\n s={s}\n focused={focusedSubmissionId === s.id}\n onToggle={() =>\n setFocusedSubmissionId(focusedSubmissionId === s.id ? null : s.id)\n }\n />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ApplicationsProblemDetailsPagePage.js","names":[],"sources":["../../../src/technospam/pages/ApplicationsProblemDetailsPagePage.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport {\n Clock,\n Code2,\n FileText,\n FlaskConical,\n Layers,\n RefreshCw,\n Send,\n Sparkles,\n Tag,\n Trophy,\n} from 'lucide-react';\nimport {\n Badge,\n Button,\n GlassCard,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Skeleton,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport {\n CodePanel,\n EmptyView,\n ErrorView,\n IoBlock,\n LoadingRows,\n PageLayout,\n PremiumBadge,\n StatCard,\n StatusBadge,\n SubmissionReviewPanel,\n TagStrip,\n TestResultsPanel,\n UpgradeGate,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useProblemQuery,\n useRegradeSubmission,\n useSubmissionsQuery,\n useSubmitSolution,\n type TecSubmission,\n} from '../api/technospamApi';\nimport { formatDateTime, humanizeEnum } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\nimport {\n FOCUSED_CARD_CLASS,\n focusableCardProps,\n useFocusedRecordParam,\n} from '../hooks/useFocusedRecordParam';\nimport { FOCUS_PARAMS } from '../utils/pageContext';\nimport { cn } from '../utils/cn';\n\nconst FALLBACK_LANGUAGES = ['javascript', 'python', 'typescript'];\n\n/**\n * A submission made while the grading sandbox is cold comes back PENDING and is\n * graded in the background (BOFF-4414). Poll the submissions list until the\n * verdict lands, giving up after a few minutes so a genuinely failed grade\n * falls back to the manual re-run button.\n */\nconst PENDING_POLL_INTERVAL_MS = 3_000;\nconst PENDING_POLL_MAX_MS = 3 * 60_000;\n\nfunction SubmissionRow({\n s,\n focused,\n onToggle,\n}: {\n s: TecSubmission;\n /** The focused submission (`?submissionId=`) is the one the assistant acts on. */\n focused: boolean;\n onToggle: () => void;\n}) {\n return (\n <div\n className={cn(\n 'cursor-pointer rounded-lg border border-transparent px-2 py-3 transition-colors hover:border-border-seam hover:bg-bg-sunken',\n focused && FOCUSED_CARD_CLASS\n )}\n {...focusableCardProps(focused, onToggle)}\n >\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1\">\n <button\n type=\"button\"\n aria-pressed={focused}\n aria-label={`Focus submission from ${formatDateTime(s.createdAt)}`}\n onClick={onToggle}\n className=\"rounded-full\"\n >\n <StatusBadge status={s.verdict} />\n </button>\n <span className=\"inline-flex items-center gap-1 text-sm font-medium text-text-primary\">\n <Code2 size={13} /> {humanizeEnum(s.language)}\n </span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Clock size={12} /> {s.runtimeMs} ms\n </span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Trophy size={12} /> {s.pointsAwarded} pts\n </span>\n <span className=\"ml-auto text-xs text-text-muted\">{formatDateTime(s.createdAt)}</span>\n </div>\n {s.totalCount > 0 ? (\n <p className=\"mt-1 px-0.5 text-xs text-text-muted\">\n Passed {s.passedCount} of {s.totalCount} test cases\n </p>\n ) : null}\n </div>\n );\n}\n\nexport function ApplicationsProblemDetailsPagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n // The focused submission lives in `?submissionId=` so the assistant's page\n // context names it (\"explain this submission\").\n const [focusedSubmissionId, setFocusedSubmissionId] = useFocusedRecordParam(\n FOCUS_PARAMS.submission\n );\n const { currentUser, workspaceId } = useTechnoSpam();\n const navigate = useTechnoSpamNavigate();\n const learnerId = currentUser?.id;\n const { emit } = useTechnoSpamAnalytics();\n\n const { data, loading, error, refetch } = useProblemQuery(id);\n\n useEffect(() => {\n if (id) {\n emit('technospam.problem.viewed', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_PROBLEM',\n });\n }\n }, [id, workspaceId, emit]);\n const {\n data: subsData,\n loading: subsLoading,\n error: subsError,\n refetch: refetchSubs,\n } = useSubmissionsQuery({ ...(learnerId ? { learnerId } : {}), problemId: id });\n const [submit, { loading: submitting }] = useSubmitSolution();\n const [regrade, { loading: regrading }] = useRegradeSubmission();\n\n const problem = data?.tecProblem ?? null;\n\n const languages = useMemo(\n () => (problem?.languages?.length ? problem.languages : FALLBACK_LANGUAGES),\n [problem]\n );\n\n const [language, setLanguage] = useState('');\n const [code, setCode] = useState('');\n const initedRef = useRef(false);\n\n useEffect(() => {\n if (problem && !initedRef.current) {\n setLanguage(problem.languages[0] ?? FALLBACK_LANGUAGES[0]!);\n setCode(problem.starterCode ?? '');\n initedRef.current = true;\n }\n }, [problem]);\n\n const submissions = useMemo(\n () =>\n [...(subsData?.tecListSubmissions?.items ?? [])].sort(\n (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()\n ),\n [subsData]\n );\n\n const handleSubmit = async () => {\n if (!id) return;\n if (!language) {\n notify.error('Pick a language', 'Choose a language before submitting.');\n return;\n }\n if (!code.trim()) {\n notify.error('Add your solution', 'Write some code before submitting.');\n return;\n }\n emit('technospam.problem.submit_clicked', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_PROBLEM',\n language,\n });\n const res = await submit({ variables: { input: { problemId: id, language, code } } });\n if (res.data?.tecSubmitSolution) {\n emit('technospam.problem.submitted', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_PROBLEM',\n submissionId: res.data.tecSubmitSolution.id,\n });\n const graded = res.data.tecSubmitSolution;\n if (graded.verdict === 'PENDING') {\n notify.info(\n 'Solution submitted',\n 'Grading has started — your verdict will appear here in a moment.'\n );\n } else if (graded.verdict === 'ACCEPTED') {\n notify.success('Accepted', `Passed all ${graded.totalCount} test cases.`);\n } else {\n notify.error(\n humanizeEnum(graded.verdict),\n `Passed ${graded.passedCount} of ${graded.totalCount} test cases.`\n );\n }\n await Promise.all([refetch(), refetchSubs()]);\n }\n };\n\n const handleRegrade = async (submissionId: string) => {\n const res = await regrade({ variables: { id: submissionId } });\n const graded = res.data?.tecRegradeSubmission;\n if (!graded) return;\n if (graded.verdict === 'PENDING') {\n notify.error('Still pending', 'The grader is not available yet. Please try again shortly.');\n } else {\n notify.success('Grading complete', `Verdict: ${humanizeEnum(graded.verdict)}.`);\n }\n await Promise.all([refetch(), refetchSubs()]);\n };\n\n /** Most recent submission that actually carries grading detail. */\n const latestGraded = submissions.find((s) => (s.testResults?.length ?? 0) > 0) ?? null;\n const latestSubmission = submissions[0] ?? null;\n const pendingId = latestSubmission?.verdict === 'PENDING' ? latestSubmission.id : null;\n\n // Poll while the newest submission is still being graded in the background.\n const [pollExpiredFor, setPollExpiredFor] = useState<string | null>(null);\n const polling = pendingId !== null && pollExpiredFor !== pendingId;\n useEffect(() => {\n if (!pendingId || pollExpiredFor === pendingId) return undefined;\n const startedAt = Date.now();\n const timer = window.setInterval(() => {\n if (Date.now() - startedAt > PENDING_POLL_MAX_MS) {\n window.clearInterval(timer);\n setPollExpiredFor(pendingId);\n return;\n }\n void refetchSubs();\n }, PENDING_POLL_INTERVAL_MS);\n return () => window.clearInterval(timer);\n }, [pendingId, pollExpiredFor, refetchSubs]);\n\n // Announce the verdict once a background-graded submission settles.\n const awaitedIdRef = useRef<string | null>(null);\n useEffect(() => {\n if (pendingId) {\n awaitedIdRef.current = pendingId;\n return;\n }\n const awaitedId = awaitedIdRef.current;\n if (!awaitedId) return;\n const settled = submissions.find((s) => s.id === awaitedId);\n if (!settled || settled.verdict === 'PENDING') return;\n awaitedIdRef.current = null;\n if (settled.verdict === 'ACCEPTED') {\n notify.success('Accepted', `Passed all ${settled.totalCount} test cases.`);\n } else {\n notify.error(\n humanizeEnum(settled.verdict),\n `Passed ${settled.passedCount} of ${settled.totalCount} test cases.`\n );\n }\n void refetch();\n }, [pendingId, submissions, refetch]);\n\n if (loading && !problem) {\n return (\n <PageLayout title=\"Problem\" backPath=\"/applications/problems-page\">\n <Skeleton className=\"mb-4 h-28 w-full rounded-xl\" />\n <Skeleton className=\"h-72 w-full rounded-xl\" />\n </PageLayout>\n );\n }\n\n if (error || !problem) {\n return (\n <PageLayout title=\"Problem\" backPath=\"/applications/problems-page\">\n {error ? (\n <ErrorView title=\"Couldn't load problem\" error={error} />\n ) : (\n <EmptyView\n title=\"Problem not found\"\n description=\"This problem may have been unpublished.\"\n />\n )}\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n hero\n backPath=\"/applications/problems-page\"\n title={problem.title}\n description={problem.topic ?? undefined}\n >\n <div className=\"mb-5 flex flex-wrap items-center gap-2\">\n <StatusBadge status={problem.difficulty} kind=\"level\" />\n <StatusBadge status={problem.status} />\n {problem.topic ? (\n <Badge variant=\"outline\" className=\"gap-1\">\n <Layers size={12} /> {problem.topic}\n </Badge>\n ) : null}\n {problem.isPremiumOnly ? <PremiumBadge locked={problem.isLocked} /> : null}\n <TagStrip tags={problem.tags} />\n </div>\n\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard label=\"Points\" value={problem.points} icon={Trophy} tone=\"amber\" />\n <StatCard label=\"Languages\" value={languages.length} icon={Code2} tone=\"blue\" />\n <StatCard\n label=\"Difficulty\"\n value={humanizeEnum(problem.difficulty)}\n icon={Layers}\n tone=\"purple\"\n />\n <StatCard label=\"My submissions\" value={submissions.length} icon={FileText} tone=\"teal\" />\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 lg:grid-cols-3\">\n <div className=\"space-y-5 lg:col-span-2\">\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-2 text-base font-semibold text-text-primary\">Problem statement</h2>\n {problem.isLocked ? (\n // BOFF-4416: the API already nulled `statement` for a locked\n // premium problem — this explains why, rather than showing the\n // generic \"being prepared\" message used for a real content gap.\n <UpgradeGate description=\"This problem's statement is part of a paid TechnoSpam plan. Ask a workspace admin to upgrade to unlock it.\" />\n ) : problem.statement ? (\n <p className=\"whitespace-pre-wrap text-sm leading-relaxed text-text-secondary\">\n {problem.statement}\n </p>\n ) : (\n <p className=\"py-4 text-center text-sm text-text-muted\">\n The statement for this problem is being prepared.\n </p>\n )}\n </GlassCard>\n\n {problem.isLocked ? (\n <UpgradeGate\n compact\n description=\"Starter code and sample test cases unlock with a paid TechnoSpam plan.\"\n />\n ) : problem.starterCode ? (\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">Starter code</h2>\n <CodePanel label={humanizeEnum(languages[0] ?? 'code')} bodyClassName=\"max-h-72\">\n {problem.starterCode}\n </CodePanel>\n </GlassCard>\n ) : null}\n\n <GlassCard className=\"p-5\">\n <div className=\"mb-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <h2 className=\"text-base font-semibold text-text-primary\">Submit a solution</h2>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">Language</span>\n <Select value={language} onValueChange={setLanguage}>\n <SelectTrigger className=\"w-full sm:w-48\" aria-label=\"Language\">\n <SelectValue placeholder=\"Choose a language\" />\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l} value={l}>\n {humanizeEnum(l)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"overflow-hidden rounded-xl border border-border-strong bg-bg-sunken shadow-sm\">\n <div className=\"flex items-center justify-between gap-2 border-b border-border-seam bg-bg-canvas/40 px-4 py-2\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-error-border\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-warning-border\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-success-border\" />\n </div>\n <span className=\"inline-flex items-center gap-1.5 font-mono text-xs text-text-secondary\">\n <Code2 size={13} /> {language ? humanizeEnum(language) : 'solution'}\n </span>\n </div>\n <Textarea\n value={code}\n onChange={(e) => setCode(e.target.value)}\n placeholder=\"Write your solution…\"\n className=\"min-h-[280px] resize-y rounded-none border-0 bg-transparent font-mono text-xs leading-relaxed focus-visible:ring-0 focus-visible:ring-offset-0\"\n spellCheck={false}\n />\n </div>\n\n <Button\n className=\"mt-4 w-full\"\n onClick={handleSubmit}\n disabled={submitting || !code.trim()}\n >\n <Send size={16} /> {submitting ? 'Running test cases…' : 'Run & submit'}\n </Button>\n <p className=\"mt-2 text-center text-xs text-text-muted\">\n Your code runs against this problem&apos;s test cases in an isolated sandbox.\n </p>\n </GlassCard>\n\n {problem.publicTestCases?.length ? (\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-1 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <FlaskConical size={16} /> Sample test cases\n </h2>\n <p className=\"mb-3 text-sm text-text-secondary\">\n Your solution reads each input from stdin and must print the expected output. Hidden\n test cases are also run when you submit.\n </p>\n <div className=\"space-y-3\">\n {problem.publicTestCases.map((testCase) => (\n <div key={testCase.id} className=\"rounded-xl border border-border-seam p-3\">\n <div className=\"mb-2 flex flex-wrap items-center gap-x-3 gap-y-1\">\n <span className=\"text-sm font-medium text-text-primary\">{testCase.name}</span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Clock size={12} /> {testCase.timeLimitMs} ms limit\n </span>\n </div>\n <div className=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n <IoBlock label=\"Input\" value={testCase.input} />\n <IoBlock label=\"Expected output\" value={testCase.expectedOutput} />\n </div>\n </div>\n ))}\n </div>\n </GlassCard>\n ) : null}\n\n {pendingId ? (\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-2 text-base font-semibold text-text-primary\">Latest run</h2>\n {polling ? (\n <p className=\"mb-3 inline-flex items-center gap-2 text-sm text-text-secondary\">\n <RefreshCw size={14} className=\"animate-spin\" />\n Grading your submission — the grader is starting up. Your verdict will appear here\n automatically.\n </p>\n ) : (\n <p className=\"mb-3 text-sm text-text-secondary\">\n Your most recent submission has not been graded yet — the grader was unavailable.\n You can re-run it without resubmitting your code.\n </p>\n )}\n <Button\n variant=\"outline\"\n onClick={() => void handleRegrade(pendingId)}\n disabled={regrading}\n >\n <RefreshCw size={16} /> {regrading ? 'Re-running…' : 'Re-run grader'}\n </Button>\n </GlassCard>\n ) : latestGraded ? (\n <>\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">Latest run</h2>\n <TestResultsPanel submission={latestGraded} />\n </GlassCard>\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-3 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <Sparkles size={16} /> TechnoSage review\n </h2>\n <SubmissionReviewPanel\n submission={latestGraded}\n onOpenProblem={(problemId) =>\n navigate(`/applications/problem-details-page?id=${problemId}`)\n }\n />\n </GlassCard>\n </>\n ) : null}\n </div>\n\n <div className=\"lg:col-span-1\">\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">My submissions</h2>\n {subsLoading && !submissions.length ? (\n <LoadingRows count={3} height=\"h-12\" />\n ) : subsError ? (\n <ErrorView title=\"Couldn't load submissions\" error={subsError} />\n ) : submissions.length === 0 ? (\n <EmptyView\n icon={<FileText size={24} />}\n title=\"No submissions yet\"\n description=\"Submit a solution above to see your verdicts and points here.\"\n />\n ) : (\n <div className=\"space-y-1\">\n {submissions.map((s) => (\n <SubmissionRow\n key={s.id}\n s={s}\n focused={focusedSubmissionId === s.id}\n onToggle={() =>\n setFocusedSubmissionId(focusedSubmissionId === s.id ? null : s.id)\n }\n />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ApplicationsProblemsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/ApplicationsProblemsPagePage.tsx"],"names":[],"mappings":"AA+IA,wBAAgB,4BAA4B,gCAuP3C"}
1
+ {"version":3,"file":"ApplicationsProblemsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/ApplicationsProblemsPagePage.tsx"],"names":[],"mappings":"AAgJA,wBAAgB,4BAA4B,gCAoQ3C"}