@burdenoff/microfe-technospam 2026.705.1 → 2026.709.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/wspace-operations.js +67 -0
- package/dist/generated/wspace-operations.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/technospam/components/MediaCover.js +57 -0
- package/dist/technospam/components/MediaCover.js.map +1 -0
- package/dist/technospam/components/PageLayout.js +1 -1
- package/dist/technospam/components/PageLayout.js.map +1 -1
- package/dist/technospam/components/StatCard.js +19 -11
- package/dist/technospam/components/StatCard.js.map +1 -1
- package/dist/technospam/components/StatusBadge.js +1 -1
- package/dist/technospam/components/StatusBadge.js.map +1 -1
- package/dist/technospam/components/index.js +1 -0
- package/dist/technospam/hooks/useTechnoSpamAnalytics.js +13 -0
- package/dist/technospam/hooks/useTechnoSpamAnalytics.js.map +1 -0
- package/dist/technospam/index.js +2 -0
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js +525 -459
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
- package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js +167 -127
- package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +178 -101
- package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/ApplicationsProblemsPagePage.js +103 -99
- package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +316 -184
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesCoursesPagePage.js +101 -93
- package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesMyLearningPagePage.js +119 -88
- package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
- package/dist/technospam/pages/DashboardDashboardPagePage.js +232 -199
- package/dist/technospam/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +128 -106
- package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/HackathonsHackathonsPagePage.js +99 -91
- package/dist/technospam/pages/HackathonsHackathonsPagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +185 -116
- package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipMentorsPagePage.js +233 -165
- package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipSessionsPagePage.js +146 -113
- package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
- package/dist/technospam/services/assistantApi.js +222 -0
- package/dist/technospam/services/assistantApi.js.map +1 -0
- package/dist/technospam/services/createSandboxAssistantTransport.js +120 -0
- package/dist/technospam/services/createSandboxAssistantTransport.js.map +1 -0
- package/dist/technospam/utils/pageContext.js +46 -0
- package/dist/technospam/utils/pageContext.js.map +1 -0
- package/package.json +5 -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":";;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,IAAM,KAA6C;CACjD;CACA;CACA;CACA;CACD,EAEK,KAA2D;CAC/D,UAAU;CACV,aAAa;CACb,WAAW;CACX,SAAS;CACV,EAEK,IAAwC;CAC5C;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,IAAsD;CAC1D,UAAU;CACV,cAAc;CACd,qBAAqB;CACrB,eAAe;CACf,eAAe;CACf,SAAS;CACV,EAEK,IAAsD;CAC1D,UAAU;CACV,cAAc;CACd,qBAAqB;CACrB,eAAe;CACf,eAAe;CACf,SAAS;CACV,EAEK,IAA+B;CACnC,YAAY;CACZ,QAAQ;CACR,cAAc;CACd,OAAO;CACP,UAAU;CACX,EACK,IAAqC,EAAE,OAAO,+BAA+B,EAC7E,IAAsC;CAAE,MAAM;CAA2B,UAAU;CAAI;AAmB7F,SAAS,EAAW,GAA6C;CAC/D,IAAM,IAAI,IAAI,KAAK,EAAI;AACvB,KAAI,OAAO,MAAM,EAAE,SAAS,CAAC,CAAE,QAAO;EAAE,KAAK;EAAG,OAAO;EAAK;CAC5D,IAAM,IAAQ,IAAI,KAAK,EAAE,aAAa,EAAE,EAAE,UAAU,EAAE,EAAE,SAAS,GAAG,EAAE,QAAQ,CAAC;AAC/E,QAAO;EACL,KAAK,EAAM,SAAS;EACpB,OAAO,EAAM,mBAAmB,KAAA,GAAW;GAAE,OAAO;GAAS,KAAK;GAAW,CAAC;EAC/E;;AAOH,IAAM,IAAiC;CACrC,MAAM;CACN,MAAM;CACN,QAAQ;CACR,QAAQ;CACT;AAED,SAAS,EAAY,EACnB,MAAM,GACN,UACA,aACA,WAMC;AACD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,QAAD;GACE,WAAW,EACT,uEACA,EAAK,GACN;aAED,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;GACb,CAAA,EACP,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cAAoD;IAAW,CAAA,EAC5E,IAAW,kBAAC,KAAD;IAAG,WAAU;cAAoC;IAAa,CAAA,GAAG,KACzE;KACF;;;AAIV,SAAS,EAAU,EACjB,UACA,aACA,SACA,UAAO,QACP,YACA,eACA,eASC;AACD,QACE,kBAAC,GAAD;EAAW,WAAU;YAArB,CACE,kBAAC,GAAD;GAAmB;GAAa;GAAiB;GAAgB;GAAQ,CAAA,EACxE,IACC,kBAAC,OAAD;GAAK,WAAU;aACZ;GACG,CAAA,GAEN,kBAAC,GAAD;GAAqB,OAAM;GAAO,QAAQ;GACvC;GACmB,CAAA,CAEd;;;AAMhB,SAAgB,IAA6B;CAC3C,IAAM,IAAW,GAAuB,EAClC,EAAE,mBAAgB,GAAe,EACjC,IAAY,GAAa,IAEzB,IAAe,GAAoB,IAAY,EAAE,cAAW,GAAG,EAAE,CAAC,EAClE,IAAe,GAAoB,IAAY,EAAE,cAAW,GAAG,EAAE,CAAC,EAClE,IAAY,GAAiB,IAAY,EAAE,UAAU,GAAW,GAAG,EAAE,CAAC,EACtE,IAAgB,GAAqB,IAAY,EAAE,cAAW,GAAG,EAAE,CAAC,EACpE,KAAW,GAAgB,EAAE,QAAQ,aAAa,CAAC,EACnD,KAAY,GAAiB,EAAE,QAAQ,aAAa,CAAC,EACrD,KAAc,GAAmB,EAAE,CAAC,EAEpC,IAAc,QACZ,EAAa,MAAM,mBAAmB,SAAS,EAAE,EACvD,CAAC,EAAa,KAAK,CACpB,EACK,IAAc,QACZ,EAAa,MAAM,mBAAmB,SAAS,EAAE,EACvD,CAAC,EAAa,KAAK,CACpB,EACK,IAAW,EAAU,MAAM,mBAAmB,EAAE,EAChD,IAAe,EAAc,MAAM,uBAAuB,EAAE,EAC5D,KAAe,GAAS,MAAM,eAAe,MAAM,UAAU,GAC7D,KAAgB,GAAU,MAAM,gBAAgB,MAAM,UAAU,GAChE,KAAkB,GAAY,MAAM,kBAAkB,MAAM,UAAU,GAEtE,IAAU,QAAc;EAC5B,IAAM,IAAmB,EAAY,QAAQ,MAAM,EAAE,WAAW,YAAY,CAAC,QACvE,IAAc,EAAY,SAC5B,KAAK,MAAM,EAAY,QAAQ,GAAG,MAAM,IAAI,EAAE,aAAa,EAAE,GAAG,EAAY,OAAO,GACnF,GACE,IAAe,EAAY,QAAQ,MAAM,EAAE,YAAY,WAAW;AAKxE,SAAO;GAAE;GAAkB;GAAa,gBAJjB,IAAI,IAAI,EAAa,KAAK,MAAM,EAAE,UAAU,CAAC,CAAC;GAIb,gBAHjC,EAAY,SAC/B,KAAK,MAAO,EAAa,SAAS,EAAY,SAAU,IAAI,GAC5D;GACoE;IACvE,CAAC,GAAa,EAAY,CAAC,EAExB,IAAsB,QAExB,GAAoB,KAAK,OAAO;EAC9B,MAAM,EAAa,EAAE;EACrB,OAAO,EAAY,QAAQ,MAAM,EAAE,WAAW,EAAE,CAAC;EACjD,OAAO,GAAoB;EAC5B,EAAE,CAAC,QAAQ,MAAM,EAAE,QAAQ,EAAE,EAChC,CAAC,EAAY,CACd,EAEK,IAAuB,QAEzB,EAAc,KAAK,OAAO;EACxB,OAAO,EAAc;EACrB,OAAO,EAAY,QAAQ,MAAM,EAAE,YAAY,EAAE,CAAC;EAClD,OAAO,EAAc;EACtB,EAAE,CAAC,QAAQ,MAAM,EAAE,QAAQ,EAAE,EAChC,CAAC,EAAY,CACd,EAEK,IAAe,QAA+B;EAClD,IAAM,oBAAU,IAAI,KAA4B,EAC1C,KAAO,GAAa,MAAuC;GAC/D,IAAM,EAAE,KAAK,GAAW,aAAU,EAAW,EAAI,EAC3C,IAAW,EAAQ,IAAI,EAAU,IAAI;IAAE;IAAO,aAAa;IAAG,aAAa;IAAG;AAEpF,GADA,EAAS,MAAQ,GACjB,EAAQ,IAAI,GAAW,EAAS;;AAIlC,SAFA,EAAY,SAAS,MAAM,EAAI,EAAE,WAAW,cAAc,CAAC,EAC3D,EAAY,SAAS,MAAM,EAAI,EAAE,WAAW,cAAc,CAAC,EACpD,CAAC,GAAG,EAAQ,SAAS,CAAC,CAC1B,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG,CAC3B,MAAM,GAAG,CACT,KAAK,GAAG,OAAO,EAAE;IACnB,CAAC,GAAa,EAAY,CAAC,EAExB,IACJ,EAAY,SAAS,KACrB,EAAY,SAAS,KACrB,EAAS,SAAS,KAClB,EAAa,SAAS,GAElB,KACJ,EAAa,WAAW,EAAa,WAAW,EAAU,WAAW,EAAc,SAC/E,IACJ,EAAa,SAAS,EAAa,SAAS,EAAU,SAAS,EAAc,OACzE,IAAc,MAAkB,CAAC,GAEjC,IACJ,kBAAC,OAAD,EAAA,UAAA;EACE,kBAAC,MAAD;GAAI,WAAU;aAAiD;GAAuB,CAAA;EACtF,kBAAC,KAAD;GAAG,WAAU;aAA6C;GAEtD,CAAA;EACJ,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KAAU,OAAM;KAAoB,OAAO;KAAc,MAAM;KAAS,MAAK;KAAS,CAAA;IACtF,kBAAC,GAAD;KAAU,OAAM;KAAqB,OAAO;KAAe,MAAM;KAAO,MAAK;KAAW,CAAA;IACxF,kBAAC,GAAD;KAAU,OAAM;KAAa,OAAO;KAAiB,MAAM;KAAQ,MAAK;KAAW,CAAA;IAC/E;;EACF,EAAA,CAAA;AAGR,QACE,kBAAC,GAAD;EACE,MAAA;EACA,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,eAAe,EAAS,4BAA4B;aAA9E,CACE,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA,EAAA,eACpB;;YAGT,IASE,IACF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,IAAD;IAAa,OAAO;IAAG,QAAO;IAAS,CAAA,EACvC,kBAAC,GAAD;IAAa,OAAO;IAAG,QAAO;IAAS,CAAA,CACnC;OACJ,KAAgB,CAAC,IACnB,kBAAC,IAAD;GAAW,OAAM;GAA+B,OAAO;GAAgB,CAAA,GAEvE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,EAAY;OACnB,MAAM;OACN,MAAK;OACL,CAAA;MACF,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,EAAQ;OACf,MAAM;OACN,MAAK;OACL,CAAA;MACF,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,GAAG,EAAQ,YAAY;OAC9B,MAAM;OACN,MAAK;OACL,CAAA;MACF,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,EAAQ;OACf,MAAM;OACN,MAAK;OACL,CAAA;MACF,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,GAAG,EAAQ,eAAe;OACjC,MAAM;OACN,MAAK;OACL,CAAA;MACF,kBAAC,GAAD;OAAU,OAAM;OAAkB,OAAO,EAAS;OAAQ,MAAM;OAAO,MAAK;OAAS,CAAA;MACrF,kBAAC,GAAD;OAAU,OAAM;OAAe,OAAO,EAAa;OAAQ,MAAM;OAAO,MAAK;OAAU,CAAA;MACnF;;IAEJ,IAUA,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,OAAM;OACN,UAAS;OACT,MAAM;OACN,MAAK;OACL,SAAS,EAAoB,WAAW;OACxC,YAAW;iBAEX,kBAAC,IAAD,EAAA,UAAA;QACE,kBAAC,IAAD;SACE,MAAM;SACN,SAAQ;SACR,SAAQ;SACR,aAAa;SACb,aAAa;SACb,cAAc;SACd,QAAO;SACP,aAAa;mBAEZ,EAAoB,KAAK,MACxB,kBAAC,GAAD,EAAmB,MAAM,EAAE,OAAS,EAAzB,EAAE,KAAuB,CACpC;SACE,CAAA;QACN,kBAAC,GAAD;SAAW,cAAc;SAAe,YAAY;SAAuB,CAAA;QAC3E,kBAAC,GAAD;SAAQ,UAAS;SAAS,cAAc,EAAE,UAAU,IAAI;SAAI,CAAA;QACnD,EAAA,CAAA;OACD,CAAA,EAEZ,kBAAC,GAAD;OACE,OAAM;OACN,UAAS;OACT,MAAM;OACN,MAAK;OACL,SAAS,EAAqB,WAAW;OACzC,YAAW;iBAEX,kBAAC,IAAD;QACE,MAAM;QACN,QAAQ;SAAE,KAAK;SAAG,OAAO;SAAG,MAAM;SAAK,QAAQ;SAAG;kBAFpD;SAIE,kBAAC,GAAD;UACE,iBAAgB;UAChB,QAAO;UACP,UAAU;UACV,CAAA;SACF,kBAAC,GAAD;UACE,SAAQ;UACR,MAAM;UACN,UAAU,EAAE,QAAQ,4BAA4B;UAChD,UAAU;UACV,CAAA;SACF,kBAAC,GAAD;UACE,eAAe;UACf,MAAM;UACN,UAAU;UACV,UAAU;UACV,CAAA;SACF,kBAAC,GAAD;UACE,cAAc;UACd,YAAY;UACZ,QAAQ;WAAE,MAAM;WAA+B,aAAa;WAAM;UAClE,CAAA;SACF,kBAAC,IAAD;UAAK,SAAQ;UAAQ,MAAK;UAAc,QAAQ;WAAC;WAAG;WAAG;WAAG;WAAE;UAAE,YAAY;oBACvE,EAAqB,KAAK,MACzB,kBAAC,GAAD,EAAoB,MAAM,EAAE,OAAS,EAA1B,EAAE,MAAwB,CACrC;UACE,CAAA;SACG;;OACD,CAAA,CACR;SAEN,kBAAC,GAAD;MAAW,WAAU;gBAArB,CACE,kBAAC,GAAD;OACE,MAAM;OACN,OAAM;OACN,UAAS;OACT,MAAK;OACL,CAAA,EACD,EAAa,WAAW,IACvB,kBAAC,OAAD;OAAK,WAAU;iBAAqE;OAE9E,CAAA,GAEN,kBAAC,GAAD;OAAqB,OAAM;OAAO,QAAQ;iBACxC,kBAAC,IAAD;QACE,MAAM;QACN,QAAQ;SAAE,KAAK;SAAG,OAAO;SAAI,MAAM;SAAK,QAAQ;SAAG;kBAFrD;SAIE,kBAAC,QAAD,EAAA,UAAA,CACE,kBAAC,kBAAD;UAAgB,IAAG;UAAuB,IAAG;UAAI,IAAG;UAAI,IAAG;UAAI,IAAG;oBAAlE,CACE,kBAAC,QAAD;WACE,QAAO;WACP,WAAU;WACV,aAAa;WACb,CAAA,EACF,kBAAC,QAAD;WACE,QAAO;WACP,WAAU;WACV,aAAa;WACb,CAAA,CACa;aACjB,kBAAC,kBAAD;UAAgB,IAAG;UAAuB,IAAG;UAAI,IAAG;UAAI,IAAG;UAAI,IAAG;oBAAlE,CACE,kBAAC,QAAD;WACE,QAAO;WACP,WAAU;WACV,aAAa;WACb,CAAA,EACF,kBAAC,QAAD;WACE,QAAO;WACP,WAAU;WACV,aAAa;WACb,CAAA,CACa;YACZ,EAAA,CAAA;SACP,kBAAC,GAAD;UACE,iBAAgB;UAChB,QAAO;UACP,UAAU;UACV,CAAA;SACF,kBAAC,GAAD;UACE,SAAQ;UACR,MAAM;UACN,UAAU,EAAE,QAAQ,4BAA4B;UAChD,UAAU;UACV,CAAA;SACF,kBAAC,GAAD;UACE,eAAe;UACf,MAAM;UACN,UAAU;UACV,UAAU;UACV,CAAA;SACF,kBAAC,GAAD;UAAW,cAAc;UAAe,YAAY;UAAuB,CAAA;SAC3E,kBAAC,GAAD;UAAQ,UAAS;UAAS,cAAc,EAAE,UAAU,IAAI;UAAI,CAAA;SAC5D,kBAAC,GAAD;UACE,MAAK;UACL,SAAQ;UACR,MAAK;UACL,QAAO;UACP,aAAa;UACb,MAAK;UACL,CAAA;SACF,kBAAC,GAAD;UACE,MAAK;UACL,SAAQ;UACR,MAAK;UACL,QAAO;UACP,aAAa;UACb,MAAK;UACL,CAAA;SACQ;;OACQ,CAAA,CAEd;QACR;SApKN,kBAAC,GAAD;KACE,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;KAC5B,OAAM;KACN,aAAY;KACZ,QACE,kBAAC,GAAD;MAAQ,eAAe,EAAS,wBAAwB;gBAAE;MAAuB,CAAA;KAEnF,CAAA;IAgKH;IACG;OA7NN,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;IAC7B,OAAM;IACN,aAAY;IACZ,CAAA,EACD,EACG;;EAwNG,CAAA"}
|
|
@@ -1,40 +1,68 @@
|
|
|
1
1
|
import { useTechnoSpam as e } from "../providers/TechnoSpamProvider.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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 ee } from "../components/StateViews.js";
|
|
7
8
|
import "../components/index.js";
|
|
8
|
-
import { notify as
|
|
9
|
-
import { useProblemQuery as
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { notify as l } from "../lib/toast.js";
|
|
10
|
+
import { useProblemQuery as te, useSubmissionsQuery as u, useSubmitSolution as d } from "../api/technospamApi.js";
|
|
11
|
+
import { useTechnoSpamAnalytics as f } from "../hooks/useTechnoSpamAnalytics.js";
|
|
12
|
+
import { useEffect as p, useMemo as m, useRef as h, useState as g } from "react";
|
|
13
|
+
import { Clock as _, Code2 as v, FileText as y, Layers as b, Send as ne, Tag as x, Trophy as S } from "lucide-react";
|
|
12
14
|
import { useSearchParams as C } from "react-router-dom";
|
|
13
15
|
import { jsx as w, jsxs as T } from "react/jsx-runtime";
|
|
14
|
-
import { Badge as E, Button as D, GlassCard as O, Select as
|
|
16
|
+
import { Badge as E, Button as D, GlassCard as O, Select as re, SelectContent as ie, SelectItem as ae, SelectTrigger as oe, SelectValue as se, Skeleton as k, Textarea as ce } from "@burdenoff/fe-libs/ui";
|
|
15
17
|
//#region src/technospam/pages/ApplicationsProblemDetailsPagePage.tsx
|
|
16
|
-
var
|
|
18
|
+
var A = [
|
|
17
19
|
"javascript",
|
|
18
20
|
"python",
|
|
19
21
|
"typescript"
|
|
20
22
|
];
|
|
21
|
-
function
|
|
23
|
+
function j({ label: e, children: n, bodyClassName: r }) {
|
|
22
24
|
return /* @__PURE__ */ T("div", {
|
|
23
|
-
className: "
|
|
25
|
+
className: "overflow-hidden rounded-xl border border-border-strong bg-bg-sunken shadow-sm",
|
|
26
|
+
children: [/* @__PURE__ */ T("div", {
|
|
27
|
+
className: "flex items-center justify-between gap-2 border-b border-border-seam bg-bg-canvas/40 px-4 py-2",
|
|
28
|
+
children: [/* @__PURE__ */ T("div", {
|
|
29
|
+
className: "flex items-center gap-1.5",
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ w("span", { className: "h-2.5 w-2.5 rounded-full bg-status-error-border" }),
|
|
32
|
+
/* @__PURE__ */ w("span", { className: "h-2.5 w-2.5 rounded-full bg-status-warning-border" }),
|
|
33
|
+
/* @__PURE__ */ w("span", { className: "h-2.5 w-2.5 rounded-full bg-status-success-border" })
|
|
34
|
+
]
|
|
35
|
+
}), /* @__PURE__ */ T("span", {
|
|
36
|
+
className: "inline-flex items-center gap-1.5 font-mono text-xs text-text-muted",
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ w(v, { size: 13 }),
|
|
39
|
+
" ",
|
|
40
|
+
e
|
|
41
|
+
]
|
|
42
|
+
})]
|
|
43
|
+
}), /* @__PURE__ */ w("pre", {
|
|
44
|
+
className: t("overflow-auto p-4 font-mono text-xs leading-relaxed text-text-primary", r),
|
|
45
|
+
children: n
|
|
46
|
+
})]
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function M({ s: e }) {
|
|
50
|
+
return /* @__PURE__ */ T("div", {
|
|
51
|
+
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
52
|
children: [
|
|
25
|
-
/* @__PURE__ */ w(
|
|
53
|
+
/* @__PURE__ */ w(o, { status: e.verdict }),
|
|
26
54
|
/* @__PURE__ */ T("span", {
|
|
27
55
|
className: "inline-flex items-center gap-1 text-sm font-medium text-text-primary",
|
|
28
56
|
children: [
|
|
29
|
-
/* @__PURE__ */ w(
|
|
57
|
+
/* @__PURE__ */ w(v, { size: 13 }),
|
|
30
58
|
" ",
|
|
31
|
-
|
|
59
|
+
a(e.language)
|
|
32
60
|
]
|
|
33
61
|
}),
|
|
34
62
|
/* @__PURE__ */ T("span", {
|
|
35
63
|
className: "inline-flex items-center gap-1 text-xs text-text-muted",
|
|
36
64
|
children: [
|
|
37
|
-
/* @__PURE__ */ w(
|
|
65
|
+
/* @__PURE__ */ w(_, { size: 12 }),
|
|
38
66
|
" ",
|
|
39
67
|
e.runtimeMs,
|
|
40
68
|
" ms"
|
|
@@ -51,35 +79,47 @@ function N({ s: e }) {
|
|
|
51
79
|
}),
|
|
52
80
|
/* @__PURE__ */ w("span", {
|
|
53
81
|
className: "ml-auto text-xs text-text-muted",
|
|
54
|
-
children:
|
|
82
|
+
children: i(e.createdAt)
|
|
55
83
|
})
|
|
56
84
|
]
|
|
57
85
|
});
|
|
58
86
|
}
|
|
59
|
-
function
|
|
60
|
-
let [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
87
|
+
function N() {
|
|
88
|
+
let [t] = C(), i = t.get("id") ?? "", { currentUser: _, workspaceId: N } = e(), P = _?.id, { emit: F } = f(), { data: I, loading: L, error: R, refetch: z } = te(i);
|
|
89
|
+
p(() => {
|
|
90
|
+
i && F("technospam.problem.viewed", {
|
|
91
|
+
workspaceId: N,
|
|
92
|
+
entityId: i,
|
|
93
|
+
entityType: "TEC_PROBLEM"
|
|
94
|
+
});
|
|
95
|
+
}, [
|
|
96
|
+
i,
|
|
97
|
+
N,
|
|
98
|
+
F
|
|
99
|
+
]);
|
|
100
|
+
let { data: B, loading: V, error: H, refetch: U } = u({
|
|
101
|
+
...P ? { learnerId: P } : {},
|
|
102
|
+
problemId: i
|
|
103
|
+
}), [W, { loading: G }] = d(), K = I?.tecProblem ?? null, q = m(() => K?.languages?.length ? K.languages : A, [K]), [J, Y] = g(""), [X, Z] = g(""), Q = h(!1);
|
|
104
|
+
p(() => {
|
|
105
|
+
K && !Q.current && (Y(K.languages[0] ?? A[0]), Z(K.starterCode ?? ""), Q.current = !0);
|
|
66
106
|
}, [K]);
|
|
67
|
-
let $ =
|
|
68
|
-
return L && !K ? /* @__PURE__ */ T(
|
|
107
|
+
let $ = m(() => [...B?.tecListSubmissions.items ?? []].sort((e, t) => new Date(t.createdAt).getTime() - new Date(e.createdAt).getTime()), [B]);
|
|
108
|
+
return L && !K ? /* @__PURE__ */ T(n, {
|
|
69
109
|
title: "Problem",
|
|
70
110
|
backPath: "/applications/problems-page",
|
|
71
|
-
children: [/* @__PURE__ */ w(
|
|
72
|
-
}) : R || !K ? /* @__PURE__ */ w(
|
|
111
|
+
children: [/* @__PURE__ */ w(k, { className: "mb-4 h-28 w-full rounded-xl" }), /* @__PURE__ */ w(k, { className: "h-72 w-full rounded-xl" })]
|
|
112
|
+
}) : R || !K ? /* @__PURE__ */ w(n, {
|
|
73
113
|
title: "Problem",
|
|
74
114
|
backPath: "/applications/problems-page",
|
|
75
|
-
children: R ? /* @__PURE__ */ w(
|
|
115
|
+
children: R ? /* @__PURE__ */ w(c, {
|
|
76
116
|
title: "Couldn't load problem",
|
|
77
117
|
error: R
|
|
78
|
-
}) : /* @__PURE__ */ w(
|
|
118
|
+
}) : /* @__PURE__ */ w(s, {
|
|
79
119
|
title: "Problem not found",
|
|
80
120
|
description: "This problem may have been unpublished."
|
|
81
121
|
})
|
|
82
|
-
}) : /* @__PURE__ */ T(
|
|
122
|
+
}) : /* @__PURE__ */ T(n, {
|
|
83
123
|
hero: !0,
|
|
84
124
|
backPath: "/applications/problems-page",
|
|
85
125
|
title: K.title,
|
|
@@ -88,16 +128,16 @@ function P() {
|
|
|
88
128
|
/* @__PURE__ */ T("div", {
|
|
89
129
|
className: "mb-5 flex flex-wrap items-center gap-2",
|
|
90
130
|
children: [
|
|
91
|
-
/* @__PURE__ */ w(
|
|
131
|
+
/* @__PURE__ */ w(o, {
|
|
92
132
|
status: K.difficulty,
|
|
93
133
|
kind: "level"
|
|
94
134
|
}),
|
|
95
|
-
/* @__PURE__ */ w(
|
|
135
|
+
/* @__PURE__ */ w(o, { status: K.status }),
|
|
96
136
|
K.topic ? /* @__PURE__ */ T(E, {
|
|
97
137
|
variant: "outline",
|
|
98
138
|
className: "gap-1",
|
|
99
139
|
children: [
|
|
100
|
-
/* @__PURE__ */ w(
|
|
140
|
+
/* @__PURE__ */ w(b, { size: 12 }),
|
|
101
141
|
" ",
|
|
102
142
|
K.topic
|
|
103
143
|
]
|
|
@@ -114,25 +154,29 @@ function P() {
|
|
|
114
154
|
/* @__PURE__ */ T("div", {
|
|
115
155
|
className: "mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4",
|
|
116
156
|
children: [
|
|
117
|
-
/* @__PURE__ */ w(
|
|
157
|
+
/* @__PURE__ */ w(r, {
|
|
118
158
|
label: "Points",
|
|
119
159
|
value: K.points,
|
|
120
|
-
icon: S
|
|
160
|
+
icon: S,
|
|
161
|
+
tone: "amber"
|
|
121
162
|
}),
|
|
122
|
-
/* @__PURE__ */ w(
|
|
163
|
+
/* @__PURE__ */ w(r, {
|
|
123
164
|
label: "Languages",
|
|
124
165
|
value: q.length,
|
|
125
|
-
icon:
|
|
166
|
+
icon: v,
|
|
167
|
+
tone: "blue"
|
|
126
168
|
}),
|
|
127
|
-
/* @__PURE__ */ w(
|
|
169
|
+
/* @__PURE__ */ w(r, {
|
|
128
170
|
label: "Difficulty",
|
|
129
|
-
value:
|
|
130
|
-
icon:
|
|
171
|
+
value: a(K.difficulty),
|
|
172
|
+
icon: b,
|
|
173
|
+
tone: "purple"
|
|
131
174
|
}),
|
|
132
|
-
/* @__PURE__ */ w(
|
|
175
|
+
/* @__PURE__ */ w(r, {
|
|
133
176
|
label: "My submissions",
|
|
134
177
|
value: $.length,
|
|
135
|
-
icon:
|
|
178
|
+
icon: y,
|
|
179
|
+
tone: "teal"
|
|
136
180
|
})
|
|
137
181
|
]
|
|
138
182
|
}),
|
|
@@ -157,75 +201,105 @@ function P() {
|
|
|
157
201
|
K.starterCode ? /* @__PURE__ */ T(O, {
|
|
158
202
|
className: "p-5",
|
|
159
203
|
children: [/* @__PURE__ */ w("h2", {
|
|
160
|
-
className: "mb-
|
|
204
|
+
className: "mb-3 text-base font-semibold text-text-primary",
|
|
161
205
|
children: "Starter code"
|
|
162
|
-
}), /* @__PURE__ */ w(
|
|
163
|
-
|
|
206
|
+
}), /* @__PURE__ */ w(j, {
|
|
207
|
+
label: a(q[0] ?? "code"),
|
|
208
|
+
bodyClassName: "max-h-72",
|
|
164
209
|
children: K.starterCode
|
|
165
210
|
})]
|
|
166
211
|
}) : null,
|
|
167
212
|
/* @__PURE__ */ T(O, {
|
|
168
213
|
className: "p-5",
|
|
169
|
-
children: [
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
/* @__PURE__ */ T("div", {
|
|
176
|
-
className: "flex
|
|
214
|
+
children: [
|
|
215
|
+
/* @__PURE__ */ T("div", {
|
|
216
|
+
className: "mb-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between",
|
|
217
|
+
children: [/* @__PURE__ */ w("h2", {
|
|
218
|
+
className: "text-base font-semibold text-text-primary",
|
|
219
|
+
children: "Submit a solution"
|
|
220
|
+
}), /* @__PURE__ */ T("div", {
|
|
221
|
+
className: "flex items-center gap-2",
|
|
177
222
|
children: [/* @__PURE__ */ w("span", {
|
|
178
|
-
className: "text-sm text-text-secondary
|
|
223
|
+
className: "text-sm text-text-secondary",
|
|
179
224
|
children: "Language"
|
|
180
|
-
}), /* @__PURE__ */ T(
|
|
225
|
+
}), /* @__PURE__ */ T(re, {
|
|
181
226
|
value: J,
|
|
182
227
|
onValueChange: Y,
|
|
183
|
-
children: [/* @__PURE__ */ w(
|
|
184
|
-
className: "w-full sm:w-
|
|
185
|
-
children: /* @__PURE__ */ w(
|
|
186
|
-
}), /* @__PURE__ */ w(
|
|
228
|
+
children: [/* @__PURE__ */ w(oe, {
|
|
229
|
+
className: "w-full sm:w-48",
|
|
230
|
+
children: /* @__PURE__ */ w(se, { placeholder: "Choose a language" })
|
|
231
|
+
}), /* @__PURE__ */ w(ie, { children: q.map((e) => /* @__PURE__ */ w(ae, {
|
|
187
232
|
value: e,
|
|
188
|
-
children:
|
|
233
|
+
children: a(e)
|
|
189
234
|
}, e)) })]
|
|
190
235
|
})]
|
|
191
|
-
})
|
|
192
|
-
|
|
236
|
+
})]
|
|
237
|
+
}),
|
|
238
|
+
/* @__PURE__ */ T("div", {
|
|
239
|
+
className: "overflow-hidden rounded-xl border border-border-strong bg-bg-sunken shadow-sm",
|
|
240
|
+
children: [/* @__PURE__ */ T("div", {
|
|
241
|
+
className: "flex items-center justify-between gap-2 border-b border-border-seam bg-bg-canvas/40 px-4 py-2",
|
|
242
|
+
children: [/* @__PURE__ */ T("div", {
|
|
243
|
+
className: "flex items-center gap-1.5",
|
|
244
|
+
children: [
|
|
245
|
+
/* @__PURE__ */ w("span", { className: "h-2.5 w-2.5 rounded-full bg-status-error-border" }),
|
|
246
|
+
/* @__PURE__ */ w("span", { className: "h-2.5 w-2.5 rounded-full bg-status-warning-border" }),
|
|
247
|
+
/* @__PURE__ */ w("span", { className: "h-2.5 w-2.5 rounded-full bg-status-success-border" })
|
|
248
|
+
]
|
|
249
|
+
}), /* @__PURE__ */ T("span", {
|
|
250
|
+
className: "inline-flex items-center gap-1.5 font-mono text-xs text-text-muted",
|
|
251
|
+
children: [
|
|
252
|
+
/* @__PURE__ */ w(v, { size: 13 }),
|
|
253
|
+
" ",
|
|
254
|
+
J ? a(J) : "solution"
|
|
255
|
+
]
|
|
256
|
+
})]
|
|
257
|
+
}), /* @__PURE__ */ w(ce, {
|
|
193
258
|
value: X,
|
|
194
259
|
onChange: (e) => Z(e.target.value),
|
|
195
260
|
placeholder: "Write your solution…",
|
|
196
|
-
className: "min-h-[
|
|
261
|
+
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
262
|
spellCheck: !1
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
263
|
+
})]
|
|
264
|
+
}),
|
|
265
|
+
/* @__PURE__ */ T(D, {
|
|
266
|
+
className: "mt-4 w-full",
|
|
267
|
+
onClick: async () => {
|
|
268
|
+
if (!i) return;
|
|
269
|
+
if (!J) {
|
|
270
|
+
l.error("Pick a language", "Choose a language before submitting.");
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (!X.trim()) {
|
|
274
|
+
l.error("Add your solution", "Write some code before submitting.");
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
F("technospam.problem.submit_clicked", {
|
|
278
|
+
workspaceId: N,
|
|
279
|
+
entityId: i,
|
|
280
|
+
entityType: "TEC_PROBLEM",
|
|
281
|
+
language: J
|
|
282
|
+
});
|
|
283
|
+
let e = await W({ variables: { input: {
|
|
284
|
+
problemId: i,
|
|
285
|
+
language: J,
|
|
286
|
+
code: X
|
|
287
|
+
} } });
|
|
288
|
+
e.data?.tecSubmitSolution && (F("technospam.problem.submitted", {
|
|
289
|
+
workspaceId: N,
|
|
290
|
+
entityId: i,
|
|
291
|
+
entityType: "TEC_PROBLEM",
|
|
292
|
+
submissionId: e.data.tecSubmitSolution.id
|
|
293
|
+
}), l.success("Solution submitted", "Your submission was recorded."), await Promise.all([z(), U()]));
|
|
294
|
+
},
|
|
295
|
+
disabled: G || !X.trim(),
|
|
296
|
+
children: [
|
|
297
|
+
/* @__PURE__ */ w(ne, { size: 16 }),
|
|
298
|
+
" ",
|
|
299
|
+
G ? "Submitting…" : "Submit solution"
|
|
300
|
+
]
|
|
301
|
+
})
|
|
302
|
+
]
|
|
229
303
|
})
|
|
230
304
|
]
|
|
231
305
|
}), /* @__PURE__ */ w("div", {
|
|
@@ -238,14 +312,17 @@ function P() {
|
|
|
238
312
|
}), V && !$.length ? /* @__PURE__ */ w(ee, {
|
|
239
313
|
count: 3,
|
|
240
314
|
height: "h-12"
|
|
241
|
-
}) : H ? /* @__PURE__ */ w(
|
|
315
|
+
}) : H ? /* @__PURE__ */ w(c, {
|
|
242
316
|
title: "Couldn't load submissions",
|
|
243
317
|
error: H
|
|
244
|
-
}) : $.length === 0 ? /* @__PURE__ */ w(
|
|
245
|
-
icon: /* @__PURE__ */ w(
|
|
318
|
+
}) : $.length === 0 ? /* @__PURE__ */ w(s, {
|
|
319
|
+
icon: /* @__PURE__ */ w(y, { size: 24 }),
|
|
246
320
|
title: "No submissions yet",
|
|
247
321
|
description: "Submit a solution above to see your verdicts and points here."
|
|
248
|
-
}) : /* @__PURE__ */ w("div", {
|
|
322
|
+
}) : /* @__PURE__ */ w("div", {
|
|
323
|
+
className: "space-y-1",
|
|
324
|
+
children: $.map((e) => /* @__PURE__ */ w(M, { s: e }, e.id))
|
|
325
|
+
})]
|
|
249
326
|
})
|
|
250
327
|
})]
|
|
251
328
|
})
|
|
@@ -253,6 +330,6 @@ function P() {
|
|
|
253
330
|
});
|
|
254
331
|
}
|
|
255
332
|
//#endregion
|
|
256
|
-
export {
|
|
333
|
+
export { N as ApplicationsProblemDetailsPagePage };
|
|
257
334
|
|
|
258
335
|
//# sourceMappingURL=ApplicationsProblemDetailsPagePage.js.map
|
|
@@ -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-dom';\nimport { Clock, Code2, FileText, Layers, Send, Tag, Trophy } 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 EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n} from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n useProblemQuery,\n useSubmissionsQuery,\n useSubmitSolution,\n type TecSubmission,\n} from '../api/technospamApi';\nimport { formatDateTime, humanizeEnum } from '../utils/format';\nimport { notify } from '../lib/toast';\n\nconst FALLBACK_LANGUAGES = ['javascript', 'python', 'typescript'];\n\nfunction SubmissionRow({ s }: { s: TecSubmission }) {\n return (\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 border-b border-border-seam py-3 last:border-0\">\n <StatusBadge status={s.verdict} />\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 );\n}\n\nexport function ApplicationsProblemDetailsPagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n const { currentUser } = useTechnoSpam();\n const learnerId = currentUser?.id;\n\n const { data, loading, error, refetch } = useProblemQuery(id);\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\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 const res = await submit({ variables: { input: { problemId: id, language, code } } });\n if (res.data?.tecSubmitSolution) {\n notify.success('Solution submitted', 'Your submission was recorded.');\n await Promise.all([refetch(), refetchSubs()]);\n }\n };\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.tags.map((t) => (\n <Badge key={t} variant=\"secondary\">\n <Tag size={10} className=\"mr-1\" />\n {t}\n </Badge>\n ))}\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} />\n <StatCard label=\"Languages\" value={languages.length} icon={Code2} />\n <StatCard label=\"Difficulty\" value={humanizeEnum(problem.difficulty)} icon={Layers} />\n <StatCard label=\"My submissions\" value={submissions.length} icon={FileText} />\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.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.starterCode ? (\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-2 text-base font-semibold text-text-primary\">Starter code</h2>\n <pre className=\"max-h-72 overflow-auto rounded-xl bg-bg-sunken p-4 font-mono text-xs leading-relaxed text-text-primary\">\n {problem.starterCode}\n </pre>\n </GlassCard>\n ) : null}\n\n <GlassCard className=\"p-5\">\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">Submit a solution</h2>\n <div className=\"space-y-3\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <span className=\"text-sm text-text-secondary sm:w-28\">Language</span>\n <Select value={language} onValueChange={setLanguage}>\n <SelectTrigger className=\"w-full sm:w-56\">\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 <Textarea\n value={code}\n onChange={(e) => setCode(e.target.value)}\n placeholder=\"Write your solution…\"\n className=\"min-h-[240px] font-mono text-xs leading-relaxed\"\n spellCheck={false}\n />\n <div className=\"flex justify-end\">\n <Button onClick={handleSubmit} disabled={submitting || !code.trim()}>\n <Send size={16} /> {submitting ? 'Submitting…' : 'Submit solution'}\n </Button>\n </div>\n </div>\n </GlassCard>\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>\n {submissions.map((s) => (\n <SubmissionRow key={s.id} s={s} />\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, type ReactNode } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Clock, Code2, FileText, Layers, Send, Tag, Trophy } 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 EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n} from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n useProblemQuery,\n useSubmissionsQuery,\n useSubmitSolution,\n type TecSubmission,\n} from '../api/technospamApi';\nimport { formatDateTime, humanizeEnum } from '../utils/format';\nimport { cn } from '../utils/cn';\nimport { notify } from '../lib/toast';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\nconst FALLBACK_LANGUAGES = ['javascript', 'python', 'typescript'];\n\n/**\n * A dark, editor-window styled code surface: mac-style traffic-light dots plus a\n * language label header over a scrollable monospace body so read-only code reads\n * like a real IDE panel rather than a flat block.\n */\nfunction CodePanel({\n label,\n children,\n bodyClassName,\n}: {\n label: string;\n children: ReactNode;\n bodyClassName?: string;\n}) {\n return (\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} /> {label}\n </span>\n </div>\n <pre\n className={cn(\n 'overflow-auto p-4 font-mono text-xs leading-relaxed text-text-primary',\n bodyClassName\n )}\n >\n {children}\n </pre>\n </div>\n );\n}\n\nfunction SubmissionRow({ s }: { s: TecSubmission }) {\n return (\n <div 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\">\n <StatusBadge status={s.verdict} />\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 );\n}\n\nexport function ApplicationsProblemDetailsPagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n const { currentUser, workspaceId } = useTechnoSpam();\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\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 notify.success('Solution submitted', 'Your submission was recorded.');\n await Promise.all([refetch(), refetchSubs()]);\n }\n };\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.tags.map((t) => (\n <Badge key={t} variant=\"secondary\">\n <Tag size={10} className=\"mr-1\" />\n {t}\n </Badge>\n ))}\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.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.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 ? 'Submitting…' : 'Submit solution'}\n </Button>\n </GlassCard>\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 key={s.id} s={s} />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|