@burdenoff/microfe-technospam 2026.704.2 → 2026.705.2

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 (40) hide show
  1. package/dist/technospam/api/technospamApi.js +233 -150
  2. package/dist/technospam/api/technospamApi.js.map +1 -1
  3. package/dist/technospam/components/LoadMore.js +18 -2
  4. package/dist/technospam/components/LoadMore.js.map +1 -0
  5. package/dist/technospam/components/MediaCover.js +57 -0
  6. package/dist/technospam/components/MediaCover.js.map +1 -0
  7. package/dist/technospam/components/PageLayout.js +1 -1
  8. package/dist/technospam/components/PageLayout.js.map +1 -1
  9. package/dist/technospam/components/StatCard.js +19 -11
  10. package/dist/technospam/components/StatCard.js.map +1 -1
  11. package/dist/technospam/components/StatusBadge.js +1 -1
  12. package/dist/technospam/components/StatusBadge.js.map +1 -1
  13. package/dist/technospam/components/index.js +1 -0
  14. package/dist/technospam/pages/AdminAdminDashboardPagePage.js +748 -352
  15. package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
  16. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js +167 -127
  17. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  18. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +178 -125
  19. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
  20. package/dist/technospam/pages/ApplicationsProblemsPagePage.js +89 -93
  21. package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
  22. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +295 -141
  23. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
  24. package/dist/technospam/pages/CoursesCoursesPagePage.js +86 -86
  25. package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
  26. package/dist/technospam/pages/CoursesMyLearningPagePage.js +122 -88
  27. package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
  28. package/dist/technospam/pages/DashboardDashboardPagePage.js +232 -199
  29. package/dist/technospam/pages/DashboardDashboardPagePage.js.map +1 -1
  30. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +128 -106
  31. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
  32. package/dist/technospam/pages/HackathonsHackathonsPagePage.js +89 -73
  33. package/dist/technospam/pages/HackathonsHackathonsPagePage.js.map +1 -1
  34. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +171 -115
  35. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
  36. package/dist/technospam/pages/MentorshipMentorsPagePage.js +214 -151
  37. package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
  38. package/dist/technospam/pages/MentorshipSessionsPagePage.js +241 -116
  39. package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
  40. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/technospam/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useMemo, type CSSProperties, type SVGProps } from 'react';\nimport {\n Activity,\n Award,\n BarChart3,\n BookOpen,\n Code2,\n GraduationCap,\n Library,\n Percent,\n TrendingUp,\n Trophy,\n Users,\n} from 'lucide-react';\nimport { GlassCard, Button } from '@burdenoff/fe-libs/ui';\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';\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}\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\nfunction ChartCard({\n title,\n icon: Icon,\n isEmpty,\n emptyLabel,\n children,\n}: {\n title: string;\n icon: typeof Activity;\n isEmpty: boolean;\n emptyLabel: string;\n children: React.ReactElement;\n}) {\n return (\n <GlassCard className=\"p-5\">\n <h3 className=\"mb-4 flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Icon size={16} className=\"text-accent-teal\" />\n {title}\n </h3>\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/* --------------------------------------------------------------- page */\n\nexport function AnalyticsAnalyticsPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n\n const enrollmentsQ = useEnrollmentsQuery(learnerId ? { learnerId } : {});\n const submissionsQ = useSubmissionsQuery(learnerId ? { learnerId } : {});\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 ?? [];\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 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\">Workspace overview</h2>\n <p className=\"mb-4 max-w-2xl text-sm text-text-secondary\">\n Live content available across the TechnoSpam learning workspace right now.\n </p>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-3\">\n <StatCard label=\"Published courses\" value={coursesTotal} icon={Library} />\n <StatCard label=\"Published problems\" value={problemsTotal} icon={Code2} />\n <StatCard label=\"Hackathons\" value={hackathonsTotal} icon={Trophy} />\n </div>\n </div>\n );\n\n return (\n <PageLayout\n hero\n title=\"Analytics\"\n description=\"Your real learning performance across courses, problems, mentoring and certificates.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> My Learning\n </Button>\n }\n >\n {!learnerId ? (\n <div className=\"space-y-8\">\n <EmptyView\n icon={<BarChart3 size={26} />}\n title=\"Sign in to view your analytics\"\n description=\"Your personal learning metrics appear here once you're signed in.\"\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 title=\"Couldn't load your analytics\" error={learnerError} />\n ) : (\n <div className=\"space-y-8\">\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3 xl:grid-cols-4\">\n <StatCard label=\"Courses enrolled\" value={enrollments.length} icon={BookOpen} />\n <StatCard\n label=\"Courses completed\"\n value={metrics.coursesCompleted}\n icon={GraduationCap}\n />\n <StatCard label=\"Avg progress\" value={`${metrics.avgProgress}%`} icon={TrendingUp} />\n <StatCard label=\"Problems solved\" value={metrics.problemsSolved} icon={Code2} />\n <StatCard label=\"Acceptance rate\" value={`${metrics.acceptanceRate}%`} icon={Percent} />\n <StatCard label=\"Mentor sessions\" value={sessions.length} icon={Users} />\n <StatCard label=\"Certificates\" value={certificates.length} icon={Award} />\n </div>\n\n {!hasActivity ? (\n <EmptyView\n icon={<Activity size={26} />}\n title=\"No learning activity yet\"\n description=\"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 action={\n <Button onClick={() => navigate('/courses/courses-page')}>Browse courses</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=\"Enrollments by status\"\n icon={BookOpen}\n isEmpty={enrollmentsByStatus.length === 0}\n emptyLabel=\"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=\"Submissions by verdict\"\n icon={Code2}\n isEmpty={submissionsByVerdict.length === 0}\n emptyLabel=\"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-wash)' }}\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 <GlassCard className=\"p-5\">\n <h3 className=\"mb-4 flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Activity size={16} className=\"text-accent-teal\" />\n Learning activity over time\n </h3>\n {activityData.length === 0 ? (\n <div className=\"flex h-[240px] items-center justify-center text-sm text-text-muted\">\n 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, type CSSProperties, type SVGProps } from 'react';\nimport {\n Activity,\n Award,\n BarChart3,\n BookOpen,\n Code2,\n GraduationCap,\n Library,\n Percent,\n TrendingUp,\n Trophy,\n Users,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard, Button } 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';\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}\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/* --------------------------------------------------------------- page */\n\nexport function AnalyticsAnalyticsPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n\n const enrollmentsQ = useEnrollmentsQuery(learnerId ? { learnerId } : {});\n const submissionsQ = useSubmissionsQuery(learnerId ? { learnerId } : {});\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 ?? [];\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 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\">Workspace overview</h2>\n <p className=\"mb-4 max-w-2xl text-sm text-text-secondary\">\n Live content available across the TechnoSpam learning workspace right now.\n </p>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-3\">\n <StatCard label=\"Published courses\" value={coursesTotal} icon={Library} tone=\"blue\" />\n <StatCard label=\"Published problems\" value={problemsTotal} icon={Code2} tone=\"indigo\" />\n <StatCard label=\"Hackathons\" value={hackathonsTotal} icon={Trophy} tone=\"purple\" />\n </div>\n </div>\n );\n\n return (\n <PageLayout\n hero\n title=\"Analytics\"\n description=\"Your real learning performance across courses, problems, mentoring and certificates.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/my-learning-page')}>\n <GraduationCap size={16} /> My Learning\n </Button>\n }\n >\n {!learnerId ? (\n <div className=\"space-y-8\">\n <EmptyView\n icon={<BarChart3 size={26} />}\n title=\"Sign in to view your analytics\"\n description=\"Your personal learning metrics appear here once you're signed in.\"\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 title=\"Couldn't load your analytics\" error={learnerError} />\n ) : (\n <div className=\"space-y-8\">\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3 xl:grid-cols-4\">\n <StatCard\n label=\"Courses enrolled\"\n value={enrollments.length}\n icon={BookOpen}\n tone=\"blue\"\n />\n <StatCard\n label=\"Courses completed\"\n value={metrics.coursesCompleted}\n icon={GraduationCap}\n tone=\"green\"\n />\n <StatCard\n label=\"Avg progress\"\n value={`${metrics.avgProgress}%`}\n icon={TrendingUp}\n tone=\"purple\"\n />\n <StatCard\n label=\"Problems solved\"\n value={metrics.problemsSolved}\n icon={Code2}\n tone=\"indigo\"\n />\n <StatCard\n label=\"Acceptance rate\"\n value={`${metrics.acceptanceRate}%`}\n icon={Percent}\n tone=\"teal\"\n />\n <StatCard label=\"Mentor sessions\" value={sessions.length} icon={Users} tone=\"blue\" />\n <StatCard label=\"Certificates\" value={certificates.length} icon={Award} tone=\"amber\" />\n </div>\n\n {!hasActivity ? (\n <EmptyView\n icon={<Activity size={26} />}\n title=\"No learning activity yet\"\n description=\"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 action={\n <Button onClick={() => navigate('/courses/courses-page')}>Browse courses</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=\"Enrollments by status\"\n subtitle=\"How your courses are progressing\"\n icon={BookOpen}\n tone=\"blue\"\n isEmpty={enrollmentsByStatus.length === 0}\n emptyLabel=\"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=\"Submissions by verdict\"\n subtitle=\"Outcomes across your attempts\"\n icon={Code2}\n tone=\"purple\"\n isEmpty={submissionsByVerdict.length === 0}\n emptyLabel=\"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 <GlassCard className=\"p-5 transition-shadow duration-200 hover:shadow-lg\">\n <ChartHeader\n icon={Activity}\n title=\"Learning activity over time\"\n subtitle=\"Submissions and enrollments per week\"\n tone=\"teal\"\n />\n {activityData.length === 0 ? (\n <div className=\"flex h-[240px] items-center justify-center text-sm text-text-muted\">\n 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,251 +1,304 @@
1
1
  import { useTechnoSpam as e } from "../providers/TechnoSpamProvider.js";
2
- import { PageLayout as t } from "../components/PageLayout.js";
3
- import { StatCard as n } from "../components/StatCard.js";
4
- import { formatDateTime as r, humanizeEnum as i } from "../utils/format.js";
5
- import { StatusBadge as a } from "../components/StatusBadge.js";
6
- import { EmptyView as o, ErrorView as s, LoadingRows as ee } from "../components/StateViews.js";
2
+ import { cn as t } from "../utils/cn.js";
3
+ import { PageLayout as n } from "../components/PageLayout.js";
4
+ import { StatCard as r } from "../components/StatCard.js";
5
+ import { formatDateTime as i, humanizeEnum as a } from "../utils/format.js";
6
+ import { StatusBadge as o } from "../components/StatusBadge.js";
7
+ import { EmptyView as s, ErrorView as c, LoadingRows as l } from "../components/StateViews.js";
7
8
  import "../components/index.js";
8
- import { notify as c } from "../lib/toast.js";
9
- import { useProblemQuery as l, useSubmissionsQuery as u, useSubmitSolution as d } from "../api/technospamApi.js";
10
- import { useEffect as f, useMemo as p, useRef as m, useState as h } from "react";
11
- import { Clock as g, Code2 as _, FileText as v, Layers as y, Send as b, Tag as x, Trophy as S } from "lucide-react";
12
- import { useSearchParams as C } from "react-router-dom";
13
- import { jsx as w, jsxs as T } from "react/jsx-runtime";
14
- import { Badge as E, Button as D, GlassCard as O, Select as k, SelectContent as A, SelectItem as te, SelectTrigger as ne, SelectValue as re, Skeleton as j, Textarea as ie } from "@burdenoff/fe-libs/ui";
9
+ import { notify as u } from "../lib/toast.js";
10
+ import { useProblemQuery as d, useSubmissionsQuery as ee, useSubmitSolution as f } from "../api/technospamApi.js";
11
+ import { useEffect as p, useMemo as m, useRef as h, useState as g } from "react";
12
+ import { Clock as _, Code2 as v, FileText as y, Layers as b, Send as x, Tag as S, Trophy as C } from "lucide-react";
13
+ import { useSearchParams as w } from "react-router-dom";
14
+ import { jsx as T, jsxs as E } from "react/jsx-runtime";
15
+ import { Badge as D, Button as O, GlassCard as k, Select as te, SelectContent as ne, SelectItem as re, SelectTrigger as ie, SelectValue as ae, Skeleton as A, Textarea as j } from "@burdenoff/fe-libs/ui";
15
16
  //#region src/technospam/pages/ApplicationsProblemDetailsPagePage.tsx
16
17
  var M = [
17
18
  "javascript",
18
19
  "python",
19
20
  "typescript"
20
21
  ];
21
- function N({ s: e }) {
22
- return /* @__PURE__ */ T("div", {
23
- className: "flex flex-wrap items-center gap-x-4 gap-y-1 border-b border-border-seam py-3 last:border-0",
22
+ function N({ label: e, children: n, bodyClassName: r }) {
23
+ return /* @__PURE__ */ E("div", {
24
+ className: "overflow-hidden rounded-xl border border-border-strong bg-bg-sunken shadow-sm",
25
+ children: [/* @__PURE__ */ E("div", {
26
+ className: "flex items-center justify-between gap-2 border-b border-border-seam bg-bg-canvas/40 px-4 py-2",
27
+ children: [/* @__PURE__ */ E("div", {
28
+ className: "flex items-center gap-1.5",
29
+ children: [
30
+ /* @__PURE__ */ T("span", { className: "h-2.5 w-2.5 rounded-full bg-status-error-border" }),
31
+ /* @__PURE__ */ T("span", { className: "h-2.5 w-2.5 rounded-full bg-status-warning-border" }),
32
+ /* @__PURE__ */ T("span", { className: "h-2.5 w-2.5 rounded-full bg-status-success-border" })
33
+ ]
34
+ }), /* @__PURE__ */ E("span", {
35
+ className: "inline-flex items-center gap-1.5 font-mono text-xs text-text-muted",
36
+ children: [
37
+ /* @__PURE__ */ T(v, { size: 13 }),
38
+ " ",
39
+ e
40
+ ]
41
+ })]
42
+ }), /* @__PURE__ */ T("pre", {
43
+ className: t("overflow-auto p-4 font-mono text-xs leading-relaxed text-text-primary", r),
44
+ children: n
45
+ })]
46
+ });
47
+ }
48
+ function P({ s: e }) {
49
+ return /* @__PURE__ */ E("div", {
50
+ className: "flex flex-wrap items-center gap-x-4 gap-y-1 rounded-lg border border-transparent px-2 py-3 transition-colors hover:border-border-seam hover:bg-bg-sunken",
24
51
  children: [
25
- /* @__PURE__ */ w(a, { status: e.verdict }),
26
- /* @__PURE__ */ T("span", {
52
+ /* @__PURE__ */ T(o, { status: e.verdict }),
53
+ /* @__PURE__ */ E("span", {
27
54
  className: "inline-flex items-center gap-1 text-sm font-medium text-text-primary",
28
55
  children: [
29
- /* @__PURE__ */ w(_, { size: 13 }),
56
+ /* @__PURE__ */ T(v, { size: 13 }),
30
57
  " ",
31
- i(e.language)
58
+ a(e.language)
32
59
  ]
33
60
  }),
34
- /* @__PURE__ */ T("span", {
61
+ /* @__PURE__ */ E("span", {
35
62
  className: "inline-flex items-center gap-1 text-xs text-text-muted",
36
63
  children: [
37
- /* @__PURE__ */ w(g, { size: 12 }),
64
+ /* @__PURE__ */ T(_, { size: 12 }),
38
65
  " ",
39
66
  e.runtimeMs,
40
67
  " ms"
41
68
  ]
42
69
  }),
43
- /* @__PURE__ */ T("span", {
70
+ /* @__PURE__ */ E("span", {
44
71
  className: "inline-flex items-center gap-1 text-xs text-text-muted",
45
72
  children: [
46
- /* @__PURE__ */ w(S, { size: 12 }),
73
+ /* @__PURE__ */ T(C, { size: 12 }),
47
74
  " ",
48
75
  e.pointsAwarded,
49
76
  " pts"
50
77
  ]
51
78
  }),
52
- /* @__PURE__ */ w("span", {
79
+ /* @__PURE__ */ T("span", {
53
80
  className: "ml-auto text-xs text-text-muted",
54
- children: r(e.createdAt)
81
+ children: i(e.createdAt)
55
82
  })
56
83
  ]
57
84
  });
58
85
  }
59
- function P() {
60
- let [r] = C(), g = r.get("id") ?? "", { currentUser: P } = e(), F = P?.id, { data: I, loading: L, error: R, refetch: z } = l(g), { data: B, loading: V, error: H, refetch: U } = u({
86
+ function F() {
87
+ let [t] = w(), i = t.get("id") ?? "", { currentUser: _ } = e(), F = _?.id, { data: I, loading: L, error: R, refetch: z } = d(i), { data: B, loading: V, error: H, refetch: U } = ee({
61
88
  ...F ? { learnerId: F } : {},
62
- problemId: g
63
- }), [W, { loading: G }] = d(), K = I?.tecProblem ?? null, q = p(() => K?.languages?.length ? K.languages : M, [K]), [J, Y] = h(""), [X, Z] = h(""), Q = m(!1);
64
- f(() => {
89
+ problemId: i
90
+ }), [W, { loading: G }] = f(), K = I?.tecProblem ?? null, q = m(() => K?.languages?.length ? K.languages : M, [K]), [J, Y] = g(""), [X, Z] = g(""), Q = h(!1);
91
+ p(() => {
65
92
  K && !Q.current && (Y(K.languages[0] ?? M[0]), Z(K.starterCode ?? ""), Q.current = !0);
66
93
  }, [K]);
67
- let $ = p(() => [...B?.tecListSubmissions.items ?? []].sort((e, t) => new Date(t.createdAt).getTime() - new Date(e.createdAt).getTime()), [B]);
68
- return L && !K ? /* @__PURE__ */ T(t, {
94
+ let $ = m(() => [...B?.tecListSubmissions.items ?? []].sort((e, t) => new Date(t.createdAt).getTime() - new Date(e.createdAt).getTime()), [B]);
95
+ return L && !K ? /* @__PURE__ */ E(n, {
69
96
  title: "Problem",
70
97
  backPath: "/applications/problems-page",
71
- children: [/* @__PURE__ */ w(j, { className: "mb-4 h-28 w-full rounded-xl" }), /* @__PURE__ */ w(j, { className: "h-72 w-full rounded-xl" })]
72
- }) : R || !K ? /* @__PURE__ */ w(t, {
98
+ children: [/* @__PURE__ */ T(A, { className: "mb-4 h-28 w-full rounded-xl" }), /* @__PURE__ */ T(A, { className: "h-72 w-full rounded-xl" })]
99
+ }) : R || !K ? /* @__PURE__ */ T(n, {
73
100
  title: "Problem",
74
101
  backPath: "/applications/problems-page",
75
- children: R ? /* @__PURE__ */ w(s, {
102
+ children: R ? /* @__PURE__ */ T(c, {
76
103
  title: "Couldn't load problem",
77
104
  error: R
78
- }) : /* @__PURE__ */ w(o, {
105
+ }) : /* @__PURE__ */ T(s, {
79
106
  title: "Problem not found",
80
107
  description: "This problem may have been unpublished."
81
108
  })
82
- }) : /* @__PURE__ */ T(t, {
109
+ }) : /* @__PURE__ */ E(n, {
83
110
  hero: !0,
84
111
  backPath: "/applications/problems-page",
85
112
  title: K.title,
86
113
  description: K.topic ?? void 0,
87
114
  children: [
88
- /* @__PURE__ */ T("div", {
115
+ /* @__PURE__ */ E("div", {
89
116
  className: "mb-5 flex flex-wrap items-center gap-2",
90
117
  children: [
91
- /* @__PURE__ */ w(a, {
118
+ /* @__PURE__ */ T(o, {
92
119
  status: K.difficulty,
93
120
  kind: "level"
94
121
  }),
95
- /* @__PURE__ */ w(a, { status: K.status }),
96
- K.topic ? /* @__PURE__ */ T(E, {
122
+ /* @__PURE__ */ T(o, { status: K.status }),
123
+ K.topic ? /* @__PURE__ */ E(D, {
97
124
  variant: "outline",
98
125
  className: "gap-1",
99
126
  children: [
100
- /* @__PURE__ */ w(y, { size: 12 }),
127
+ /* @__PURE__ */ T(b, { size: 12 }),
101
128
  " ",
102
129
  K.topic
103
130
  ]
104
131
  }) : null,
105
- K.tags.map((e) => /* @__PURE__ */ T(E, {
132
+ K.tags.map((e) => /* @__PURE__ */ E(D, {
106
133
  variant: "secondary",
107
- children: [/* @__PURE__ */ w(x, {
134
+ children: [/* @__PURE__ */ T(S, {
108
135
  size: 10,
109
136
  className: "mr-1"
110
137
  }), e]
111
138
  }, e))
112
139
  ]
113
140
  }),
114
- /* @__PURE__ */ T("div", {
141
+ /* @__PURE__ */ E("div", {
115
142
  className: "mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4",
116
143
  children: [
117
- /* @__PURE__ */ w(n, {
144
+ /* @__PURE__ */ T(r, {
118
145
  label: "Points",
119
146
  value: K.points,
120
- icon: S
147
+ icon: C,
148
+ tone: "amber"
121
149
  }),
122
- /* @__PURE__ */ w(n, {
150
+ /* @__PURE__ */ T(r, {
123
151
  label: "Languages",
124
152
  value: q.length,
125
- icon: _
153
+ icon: v,
154
+ tone: "blue"
126
155
  }),
127
- /* @__PURE__ */ w(n, {
156
+ /* @__PURE__ */ T(r, {
128
157
  label: "Difficulty",
129
- value: i(K.difficulty),
130
- icon: y
158
+ value: a(K.difficulty),
159
+ icon: b,
160
+ tone: "purple"
131
161
  }),
132
- /* @__PURE__ */ w(n, {
162
+ /* @__PURE__ */ T(r, {
133
163
  label: "My submissions",
134
164
  value: $.length,
135
- icon: v
165
+ icon: y,
166
+ tone: "teal"
136
167
  })
137
168
  ]
138
169
  }),
139
- /* @__PURE__ */ T("div", {
170
+ /* @__PURE__ */ E("div", {
140
171
  className: "grid grid-cols-1 gap-5 lg:grid-cols-3",
141
- children: [/* @__PURE__ */ T("div", {
172
+ children: [/* @__PURE__ */ E("div", {
142
173
  className: "space-y-5 lg:col-span-2",
143
174
  children: [
144
- /* @__PURE__ */ T(O, {
175
+ /* @__PURE__ */ E(k, {
145
176
  className: "p-5",
146
- children: [/* @__PURE__ */ w("h2", {
177
+ children: [/* @__PURE__ */ T("h2", {
147
178
  className: "mb-2 text-base font-semibold text-text-primary",
148
179
  children: "Problem statement"
149
- }), K.statement ? /* @__PURE__ */ w("p", {
180
+ }), K.statement ? /* @__PURE__ */ T("p", {
150
181
  className: "whitespace-pre-wrap text-sm leading-relaxed text-text-secondary",
151
182
  children: K.statement
152
- }) : /* @__PURE__ */ w("p", {
183
+ }) : /* @__PURE__ */ T("p", {
153
184
  className: "py-4 text-center text-sm text-text-muted",
154
185
  children: "The statement for this problem is being prepared."
155
186
  })]
156
187
  }),
157
- K.starterCode ? /* @__PURE__ */ T(O, {
188
+ K.starterCode ? /* @__PURE__ */ E(k, {
158
189
  className: "p-5",
159
- children: [/* @__PURE__ */ w("h2", {
160
- className: "mb-2 text-base font-semibold text-text-primary",
190
+ children: [/* @__PURE__ */ T("h2", {
191
+ className: "mb-3 text-base font-semibold text-text-primary",
161
192
  children: "Starter code"
162
- }), /* @__PURE__ */ w("pre", {
163
- className: "max-h-72 overflow-auto rounded-xl bg-bg-sunken p-4 font-mono text-xs leading-relaxed text-text-primary",
193
+ }), /* @__PURE__ */ T(N, {
194
+ label: a(q[0] ?? "code"),
195
+ bodyClassName: "max-h-72",
164
196
  children: K.starterCode
165
197
  })]
166
198
  }) : null,
167
- /* @__PURE__ */ T(O, {
199
+ /* @__PURE__ */ E(k, {
168
200
  className: "p-5",
169
- children: [/* @__PURE__ */ w("h2", {
170
- className: "mb-3 text-base font-semibold text-text-primary",
171
- children: "Submit a solution"
172
- }), /* @__PURE__ */ T("div", {
173
- className: "space-y-3",
174
- children: [
175
- /* @__PURE__ */ T("div", {
176
- className: "flex flex-col gap-2 sm:flex-row sm:items-center",
177
- children: [/* @__PURE__ */ w("span", {
178
- className: "text-sm text-text-secondary sm:w-28",
201
+ children: [
202
+ /* @__PURE__ */ E("div", {
203
+ className: "mb-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between",
204
+ children: [/* @__PURE__ */ T("h2", {
205
+ className: "text-base font-semibold text-text-primary",
206
+ children: "Submit a solution"
207
+ }), /* @__PURE__ */ E("div", {
208
+ className: "flex items-center gap-2",
209
+ children: [/* @__PURE__ */ T("span", {
210
+ className: "text-sm text-text-secondary",
179
211
  children: "Language"
180
- }), /* @__PURE__ */ T(k, {
212
+ }), /* @__PURE__ */ E(te, {
181
213
  value: J,
182
214
  onValueChange: Y,
183
- children: [/* @__PURE__ */ w(ne, {
184
- className: "w-full sm:w-56",
185
- children: /* @__PURE__ */ w(re, { placeholder: "Choose a language" })
186
- }), /* @__PURE__ */ w(A, { children: q.map((e) => /* @__PURE__ */ w(te, {
215
+ children: [/* @__PURE__ */ T(ie, {
216
+ className: "w-full sm:w-48",
217
+ children: /* @__PURE__ */ T(ae, { placeholder: "Choose a language" })
218
+ }), /* @__PURE__ */ T(ne, { children: q.map((e) => /* @__PURE__ */ T(re, {
187
219
  value: e,
188
- children: i(e)
220
+ children: a(e)
189
221
  }, e)) })]
190
222
  })]
191
- }),
192
- /* @__PURE__ */ w(ie, {
223
+ })]
224
+ }),
225
+ /* @__PURE__ */ E("div", {
226
+ className: "overflow-hidden rounded-xl border border-border-strong bg-bg-sunken shadow-sm",
227
+ children: [/* @__PURE__ */ E("div", {
228
+ className: "flex items-center justify-between gap-2 border-b border-border-seam bg-bg-canvas/40 px-4 py-2",
229
+ children: [/* @__PURE__ */ E("div", {
230
+ className: "flex items-center gap-1.5",
231
+ children: [
232
+ /* @__PURE__ */ T("span", { className: "h-2.5 w-2.5 rounded-full bg-status-error-border" }),
233
+ /* @__PURE__ */ T("span", { className: "h-2.5 w-2.5 rounded-full bg-status-warning-border" }),
234
+ /* @__PURE__ */ T("span", { className: "h-2.5 w-2.5 rounded-full bg-status-success-border" })
235
+ ]
236
+ }), /* @__PURE__ */ E("span", {
237
+ className: "inline-flex items-center gap-1.5 font-mono text-xs text-text-muted",
238
+ children: [
239
+ /* @__PURE__ */ T(v, { size: 13 }),
240
+ " ",
241
+ J ? a(J) : "solution"
242
+ ]
243
+ })]
244
+ }), /* @__PURE__ */ T(j, {
193
245
  value: X,
194
246
  onChange: (e) => Z(e.target.value),
195
247
  placeholder: "Write your solution…",
196
- className: "min-h-[240px] font-mono text-xs leading-relaxed",
248
+ 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",
197
249
  spellCheck: !1
198
- }),
199
- /* @__PURE__ */ w("div", {
200
- className: "flex justify-end",
201
- children: /* @__PURE__ */ T(D, {
202
- onClick: async () => {
203
- if (g) {
204
- if (!J) {
205
- c.error("Pick a language", "Choose a language before submitting.");
206
- return;
207
- }
208
- if (!X.trim()) {
209
- c.error("Add your solution", "Write some code before submitting.");
210
- return;
211
- }
212
- (await W({ variables: { input: {
213
- problemId: g,
214
- language: J,
215
- code: X
216
- } } })).data?.tecSubmitSolution && (c.success("Solution submitted", "Your submission was recorded."), await Promise.all([z(), U()]));
217
- }
218
- },
219
- disabled: G || !X.trim(),
220
- children: [
221
- /* @__PURE__ */ w(b, { size: 16 }),
222
- " ",
223
- G ? "Submitting…" : "Submit solution"
224
- ]
225
- })
226
- })
227
- ]
228
- })]
250
+ })]
251
+ }),
252
+ /* @__PURE__ */ E(O, {
253
+ className: "mt-4 w-full",
254
+ onClick: async () => {
255
+ if (i) {
256
+ if (!J) {
257
+ u.error("Pick a language", "Choose a language before submitting.");
258
+ return;
259
+ }
260
+ if (!X.trim()) {
261
+ u.error("Add your solution", "Write some code before submitting.");
262
+ return;
263
+ }
264
+ (await W({ variables: { input: {
265
+ problemId: i,
266
+ language: J,
267
+ code: X
268
+ } } })).data?.tecSubmitSolution && (u.success("Solution submitted", "Your submission was recorded."), await Promise.all([z(), U()]));
269
+ }
270
+ },
271
+ disabled: G || !X.trim(),
272
+ children: [
273
+ /* @__PURE__ */ T(x, { size: 16 }),
274
+ " ",
275
+ G ? "Submitting…" : "Submit solution"
276
+ ]
277
+ })
278
+ ]
229
279
  })
230
280
  ]
231
- }), /* @__PURE__ */ w("div", {
281
+ }), /* @__PURE__ */ T("div", {
232
282
  className: "lg:col-span-1",
233
- children: /* @__PURE__ */ T(O, {
283
+ children: /* @__PURE__ */ E(k, {
234
284
  className: "p-5",
235
- children: [/* @__PURE__ */ w("h2", {
285
+ children: [/* @__PURE__ */ T("h2", {
236
286
  className: "mb-3 text-base font-semibold text-text-primary",
237
287
  children: "My submissions"
238
- }), V && !$.length ? /* @__PURE__ */ w(ee, {
288
+ }), V && !$.length ? /* @__PURE__ */ T(l, {
239
289
  count: 3,
240
290
  height: "h-12"
241
- }) : H ? /* @__PURE__ */ w(s, {
291
+ }) : H ? /* @__PURE__ */ T(c, {
242
292
  title: "Couldn't load submissions",
243
293
  error: H
244
- }) : $.length === 0 ? /* @__PURE__ */ w(o, {
245
- icon: /* @__PURE__ */ w(v, { size: 24 }),
294
+ }) : $.length === 0 ? /* @__PURE__ */ T(s, {
295
+ icon: /* @__PURE__ */ T(y, { size: 24 }),
246
296
  title: "No submissions yet",
247
297
  description: "Submit a solution above to see your verdicts and points here."
248
- }) : /* @__PURE__ */ w("div", { children: $.map((e) => /* @__PURE__ */ w(N, { s: e }, e.id)) })]
298
+ }) : /* @__PURE__ */ T("div", {
299
+ className: "space-y-1",
300
+ children: $.map((e) => /* @__PURE__ */ T(P, { s: e }, e.id))
301
+ })]
249
302
  })
250
303
  })]
251
304
  })
@@ -253,6 +306,6 @@ function P() {
253
306
  });
254
307
  }
255
308
  //#endregion
256
- export { P as ApplicationsProblemDetailsPagePage };
309
+ export { F as ApplicationsProblemDetailsPagePage };
257
310
 
258
311
  //# sourceMappingURL=ApplicationsProblemDetailsPagePage.js.map