@burdenoff/microfe-technospam 2026.1030.6 → 2026.1031.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.
- package/dist/technospam/api/technospamApi.d.ts.map +1 -1
- package/dist/technospam/api/technospamApi.js +254 -253
- package/dist/technospam/api/technospamApi.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/SubmissionViews.js +1 -1
- package/dist/technospam/components/SubmissionViews.js.map +1 -1
- package/dist/technospam/components/lesson/InteractiveWorkspace.d.ts.map +1 -1
- package/dist/technospam/components/lesson/InteractiveWorkspace.js +88 -81
- package/dist/technospam/components/lesson/InteractiveWorkspace.js.map +1 -1
- package/dist/technospam/lib/errorMessage.d.ts +7 -0
- package/dist/technospam/lib/errorMessage.d.ts.map +1 -0
- package/dist/technospam/lib/errorMessage.js +21 -0
- package/dist/technospam/lib/errorMessage.js.map +1 -0
- package/dist/technospam/lib/toast.js +1 -1
- package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js +194 -186
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
- package/dist/technospam/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js +5 -2
- package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/technospam/pages/ApplicationsPortfolioPagePage.js +1 -0
- package/dist/technospam/pages/ApplicationsPortfolioPagePage.js.map +1 -1
- package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +2 -1
- package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/ApplicationsProblemsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/ApplicationsProblemsPagePage.js +167 -162
- package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +2 -2
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesCoursesPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesCoursesPagePage.js +77 -77
- package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesLessonPlayerPagePage.js +2 -2
- package/dist/technospam/pages/CoursesLessonPlayerPagePage.js.map +1 -1
- package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +3 -3
- package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/HackathonsHackathonsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/HackathonsHackathonsPagePage.js +71 -71
- package/dist/technospam/pages/HackathonsHackathonsPagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipMentorProfilePagePage.d.ts.map +1 -1
- package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +6 -1
- package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipMentorsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/MentorshipMentorsPagePage.js +1 -0
- package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipSessionsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/MentorshipSessionsPagePage.js +97 -98
- package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
- package/package.json +9 -7
- package/dist/node_modules/sonner/dist/index.js +0 -600
- package/dist/node_modules/sonner/dist/index.js.map +0 -1
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
2
|
import { humanizeEnum as t } from "../../utils/format.js";
|
|
3
3
|
import { IoBlock as n } from "../SubmissionViews.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
4
|
+
import { userMessageFromError as r } from "../../lib/errorMessage.js";
|
|
5
|
+
import { useRunLessonCode as i } from "../../api/technospamApi.js";
|
|
6
|
+
import { useEffect as a, useRef as o, useState as s } from "react";
|
|
7
|
+
import { CheckCircle2 as c, Clock as l, Code2 as u, Loader2 as d, Play as f, RotateCcw as p, Terminal as m, XCircle as h } from "lucide-react";
|
|
8
|
+
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
9
|
+
import { Button as v, Textarea as y } from "@burdenoff/fe-libs/ui";
|
|
9
10
|
//#region src/technospam/components/lesson/InteractiveWorkspace.tsx
|
|
10
|
-
var
|
|
11
|
-
function
|
|
11
|
+
var b = 4e3, x = 4 * 6e4;
|
|
12
|
+
function S(e) {
|
|
12
13
|
return e.compileFailed ? {
|
|
13
14
|
tone: "error",
|
|
14
15
|
text: "Compile error"
|
|
@@ -32,160 +33,166 @@ function x(e) {
|
|
|
32
33
|
text: `Exited with code ${e.exitCode}`
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
|
-
function
|
|
36
|
-
let
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
function C({ lesson: C, onCompleted: w }) {
|
|
37
|
+
let T = C.starterCode ?? "", [E, D] = s(T), [O, k] = s(C.sampleInput ?? ""), [A, j] = s(null), [M, N] = s(!1), [P, F] = s(!1), [I, L] = s(null), R = o(null), z = o(null), [B] = i();
|
|
38
|
+
a(() => () => {
|
|
39
|
+
z.current && clearTimeout(z.current);
|
|
39
40
|
}, []);
|
|
40
|
-
let
|
|
41
|
-
|
|
41
|
+
let V = async () => {
|
|
42
|
+
F(!0), L(null);
|
|
42
43
|
try {
|
|
43
|
-
let e =
|
|
44
|
-
lessonId:
|
|
45
|
-
language:
|
|
46
|
-
code:
|
|
47
|
-
stdin:
|
|
48
|
-
} } })
|
|
49
|
-
if (!
|
|
50
|
-
|
|
44
|
+
let e = await B({ variables: { input: {
|
|
45
|
+
lessonId: C.id,
|
|
46
|
+
language: C.language ?? void 0,
|
|
47
|
+
code: E,
|
|
48
|
+
stdin: O
|
|
49
|
+
} } }), t = e.data?.tecRunLessonCode;
|
|
50
|
+
if (!t) {
|
|
51
|
+
N(!1), R.current = null, j(null), L(r(e.error, "Your code could not be run. Please try again."));
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
|
-
if (
|
|
54
|
-
if (
|
|
55
|
-
|
|
54
|
+
if (t.status === "WARMING") {
|
|
55
|
+
if (R.current ??= Date.now(), Date.now() - R.current < x) {
|
|
56
|
+
N(!0), z.current = setTimeout(() => void V(), b);
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
-
...
|
|
59
|
+
N(!1), R.current = null, j({
|
|
60
|
+
...t,
|
|
60
61
|
status: "UNAVAILABLE"
|
|
61
62
|
});
|
|
62
63
|
return;
|
|
63
64
|
}
|
|
64
|
-
|
|
65
|
+
N(!1), R.current = null, j(t), t.lessonCompleted && w();
|
|
65
66
|
} finally {
|
|
66
|
-
|
|
67
|
+
F(!1);
|
|
67
68
|
}
|
|
68
|
-
},
|
|
69
|
-
return /* @__PURE__ */
|
|
69
|
+
}, H = P || M, U = A && A.status === "COMPLETED" ? S(A) : null;
|
|
70
|
+
return /* @__PURE__ */ _("div", {
|
|
70
71
|
className: "space-y-4",
|
|
71
72
|
children: [
|
|
72
|
-
/* @__PURE__ */
|
|
73
|
+
/* @__PURE__ */ _("div", {
|
|
73
74
|
className: "overflow-hidden rounded-xl border border-border-strong bg-bg-sunken shadow-sm",
|
|
74
|
-
children: [/* @__PURE__ */
|
|
75
|
+
children: [/* @__PURE__ */ _("div", {
|
|
75
76
|
className: "flex items-center justify-between gap-2 border-b border-border-seam bg-bg-canvas/40 px-4 py-2",
|
|
76
|
-
children: [/* @__PURE__ */
|
|
77
|
+
children: [/* @__PURE__ */ _("div", {
|
|
77
78
|
className: "flex items-center gap-1.5",
|
|
78
79
|
children: [
|
|
79
|
-
/* @__PURE__ */
|
|
80
|
-
/* @__PURE__ */
|
|
81
|
-
/* @__PURE__ */
|
|
80
|
+
/* @__PURE__ */ g("span", { className: "h-2.5 w-2.5 rounded-full bg-status-error-border" }),
|
|
81
|
+
/* @__PURE__ */ g("span", { className: "h-2.5 w-2.5 rounded-full bg-status-warning-border" }),
|
|
82
|
+
/* @__PURE__ */ g("span", { className: "h-2.5 w-2.5 rounded-full bg-status-success-border" })
|
|
82
83
|
]
|
|
83
|
-
}), /* @__PURE__ */
|
|
84
|
-
className: "inline-flex items-center gap-1.5 font-mono text-xs text-text-
|
|
84
|
+
}), /* @__PURE__ */ _("span", {
|
|
85
|
+
className: "inline-flex items-center gap-1.5 font-mono text-xs text-text-secondary",
|
|
85
86
|
children: [
|
|
86
|
-
/* @__PURE__ */
|
|
87
|
+
/* @__PURE__ */ g(u, { size: 13 }),
|
|
87
88
|
" ",
|
|
88
|
-
|
|
89
|
+
C.language ? t(C.language) : "code"
|
|
89
90
|
]
|
|
90
91
|
})]
|
|
91
|
-
}), /* @__PURE__ */
|
|
92
|
+
}), /* @__PURE__ */ g(y, {
|
|
92
93
|
"aria-label": "Lesson code editor",
|
|
93
|
-
value:
|
|
94
|
-
onChange: (e) =>
|
|
94
|
+
value: E,
|
|
95
|
+
onChange: (e) => D(e.target.value),
|
|
95
96
|
placeholder: "Write your code…",
|
|
96
97
|
className: "min-h-[260px] resize-y rounded-none border-0 bg-transparent font-mono text-xs leading-relaxed focus-visible:ring-0 focus-visible:ring-offset-0",
|
|
97
98
|
spellCheck: !1
|
|
98
99
|
})]
|
|
99
100
|
}),
|
|
100
|
-
/* @__PURE__ */
|
|
101
|
-
htmlFor: `tec-lesson-stdin-${
|
|
101
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
102
|
+
htmlFor: `tec-lesson-stdin-${C.id}`,
|
|
102
103
|
className: "mb-1 block text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
103
104
|
children: "Input (stdin)"
|
|
104
|
-
}), /* @__PURE__ */
|
|
105
|
-
id: `tec-lesson-stdin-${
|
|
106
|
-
value:
|
|
107
|
-
onChange: (e) =>
|
|
105
|
+
}), /* @__PURE__ */ g(y, {
|
|
106
|
+
id: `tec-lesson-stdin-${C.id}`,
|
|
107
|
+
value: O,
|
|
108
|
+
onChange: (e) => k(e.target.value),
|
|
108
109
|
placeholder: "Optional input your program reads",
|
|
109
110
|
className: "min-h-[64px] font-mono text-xs",
|
|
110
111
|
spellCheck: !1
|
|
111
112
|
})] }),
|
|
112
|
-
/* @__PURE__ */
|
|
113
|
+
/* @__PURE__ */ _("div", {
|
|
113
114
|
className: "flex flex-wrap items-center gap-2",
|
|
114
115
|
children: [
|
|
115
|
-
/* @__PURE__ */
|
|
116
|
-
onClick: () => void
|
|
117
|
-
disabled:
|
|
118
|
-
children: [
|
|
116
|
+
/* @__PURE__ */ _(v, {
|
|
117
|
+
onClick: () => void V(),
|
|
118
|
+
disabled: H || !E.trim(),
|
|
119
|
+
children: [H ? /* @__PURE__ */ g(d, {
|
|
119
120
|
size: 16,
|
|
120
121
|
className: "animate-spin"
|
|
121
|
-
}) : /* @__PURE__ */
|
|
122
|
+
}) : /* @__PURE__ */ g(f, { size: 16 }), M ? "Starting sandbox…" : P ? "Running…" : "Run code"]
|
|
122
123
|
}),
|
|
123
|
-
/* @__PURE__ */
|
|
124
|
+
/* @__PURE__ */ _(v, {
|
|
124
125
|
variant: "outline",
|
|
125
126
|
onClick: () => {
|
|
126
|
-
|
|
127
|
+
D(T), j(null), L(null);
|
|
127
128
|
},
|
|
128
|
-
disabled:
|
|
129
|
-
children: [/* @__PURE__ */
|
|
129
|
+
disabled: H || E === T,
|
|
130
|
+
children: [/* @__PURE__ */ g(p, { size: 15 }), " Reset to starter"]
|
|
130
131
|
}),
|
|
131
|
-
/* @__PURE__ */
|
|
132
|
+
/* @__PURE__ */ g("span", {
|
|
132
133
|
className: "text-xs text-text-muted",
|
|
133
134
|
children: "Runs in an isolated sandbox."
|
|
134
135
|
})
|
|
135
136
|
]
|
|
136
137
|
}),
|
|
137
|
-
|
|
138
|
+
M ? /* @__PURE__ */ g("p", {
|
|
138
139
|
role: "status",
|
|
139
140
|
className: "rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary",
|
|
140
141
|
children: "The code sandbox is starting up. Your code will run automatically as soon as it is ready."
|
|
141
142
|
}) : null,
|
|
142
|
-
|
|
143
|
+
I ? /* @__PURE__ */ g("p", {
|
|
144
|
+
role: "alert",
|
|
145
|
+
"data-testid": "lesson-run-error",
|
|
146
|
+
className: "rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
147
|
+
children: I
|
|
148
|
+
}) : null,
|
|
149
|
+
A?.status === "UNAVAILABLE" ? /* @__PURE__ */ g("p", {
|
|
143
150
|
role: "alert",
|
|
144
151
|
className: "rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
145
152
|
children: "The code sandbox is not available right now. Please try again in a moment."
|
|
146
153
|
}) : null,
|
|
147
|
-
|
|
154
|
+
A && U ? /* @__PURE__ */ _("div", {
|
|
148
155
|
className: "space-y-3 rounded-xl border border-border-seam p-4",
|
|
149
156
|
"data-testid": "lesson-run-output",
|
|
150
157
|
children: [
|
|
151
|
-
/* @__PURE__ */
|
|
158
|
+
/* @__PURE__ */ _("div", {
|
|
152
159
|
className: "flex flex-wrap items-center gap-3",
|
|
153
160
|
children: [
|
|
154
|
-
/* @__PURE__ */
|
|
155
|
-
className: e("inline-flex items-center gap-1.5 text-sm font-medium",
|
|
156
|
-
children: [
|
|
161
|
+
/* @__PURE__ */ _("span", {
|
|
162
|
+
className: e("inline-flex items-center gap-1.5 text-sm font-medium", U.tone === "success" && "text-status-success-text", U.tone === "error" && "text-status-error-text", U.tone === "neutral" && "text-text-primary"),
|
|
163
|
+
children: [U.tone === "error" ? /* @__PURE__ */ g(h, { size: 16 }) : /* @__PURE__ */ g(c, { size: 16 }), U.text]
|
|
157
164
|
}),
|
|
158
|
-
/* @__PURE__ */
|
|
165
|
+
/* @__PURE__ */ _("span", {
|
|
159
166
|
className: "inline-flex items-center gap-1 text-xs text-text-muted",
|
|
160
167
|
children: [
|
|
161
|
-
/* @__PURE__ */
|
|
168
|
+
/* @__PURE__ */ g(l, { size: 12 }),
|
|
162
169
|
" ",
|
|
163
|
-
|
|
170
|
+
A.durationMs,
|
|
164
171
|
" ms"
|
|
165
172
|
]
|
|
166
173
|
}),
|
|
167
|
-
|
|
174
|
+
A.lessonCompleted ? /* @__PURE__ */ g("span", {
|
|
168
175
|
className: "rounded-full border border-status-success-border bg-status-success-bg-subtle px-2 py-0.5 text-xs font-medium text-status-success-text",
|
|
169
176
|
children: "Lesson completed"
|
|
170
177
|
}) : null
|
|
171
178
|
]
|
|
172
179
|
}),
|
|
173
|
-
/* @__PURE__ */
|
|
180
|
+
/* @__PURE__ */ _("div", {
|
|
174
181
|
className: "grid grid-cols-1 gap-3 md:grid-cols-2",
|
|
175
|
-
children: [/* @__PURE__ */
|
|
182
|
+
children: [/* @__PURE__ */ g(n, {
|
|
176
183
|
label: "Output",
|
|
177
|
-
value:
|
|
178
|
-
}),
|
|
184
|
+
value: A.stdout
|
|
185
|
+
}), C.expectedOutput ? /* @__PURE__ */ g(n, {
|
|
179
186
|
label: "Expected output",
|
|
180
|
-
value:
|
|
187
|
+
value: C.expectedOutput
|
|
181
188
|
}) : null]
|
|
182
189
|
}),
|
|
183
|
-
|
|
190
|
+
A.stderr ? /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ _("p", {
|
|
184
191
|
className: "mb-1 flex items-center gap-1 text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
185
|
-
children: [/* @__PURE__ */
|
|
186
|
-
}), /* @__PURE__ */
|
|
192
|
+
children: [/* @__PURE__ */ g(m, { size: 12 }), " Errors"]
|
|
193
|
+
}), /* @__PURE__ */ g("pre", {
|
|
187
194
|
className: "overflow-auto rounded-lg bg-status-error-bg-subtle px-3 py-2 font-mono text-xs text-status-error-text",
|
|
188
|
-
children:
|
|
195
|
+
children: A.stderr
|
|
189
196
|
})] }) : null
|
|
190
197
|
]
|
|
191
198
|
}) : null
|
|
@@ -193,6 +200,6 @@ function S({ lesson: S, onCompleted: C }) {
|
|
|
193
200
|
});
|
|
194
201
|
}
|
|
195
202
|
//#endregion
|
|
196
|
-
export {
|
|
203
|
+
export { C as InteractiveWorkspace };
|
|
197
204
|
|
|
198
205
|
//# sourceMappingURL=InteractiveWorkspace.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InteractiveWorkspace.js","names":[],"sources":["../../../../src/technospam/components/lesson/InteractiveWorkspace.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport {\n CheckCircle2,\n Clock,\n Code2,\n Loader2,\n Play,\n RotateCcw,\n Terminal,\n XCircle,\n} from 'lucide-react';\nimport { Button, Textarea } from '@burdenoff/fe-libs/ui';\nimport { IoBlock } from '../SubmissionViews';\nimport { humanizeEnum } from '../../utils/format';\nimport { cn } from '../../utils/cn';\nimport { useRunLessonCode, type TecLesson, type TecLessonCodeRun } from '../../api/technospamApi';\n\n/**\n * A cold code sandbox can take up to a few minutes to start. While the API\n * answers WARMING we call it again on this interval, giving up after the\n * budget below so the learner can retry by hand.\n */\nconst WARMING_RETRY_MS = 4_000;\nconst WARMING_BUDGET_MS = 4 * 60_000;\n\nfunction runSummary(run: TecLessonCodeRun): {\n tone: 'success' | 'error' | 'neutral';\n text: string;\n} {\n if (run.compileFailed) return { tone: 'error', text: 'Compile error' };\n if (run.timedOut) return { tone: 'error', text: 'Time limit exceeded' };\n if (run.memoryLimitExceeded) return { tone: 'error', text: 'Memory limit exceeded' };\n if (run.exitCode !== 0) return { tone: 'error', text: `Exited with code ${run.exitCode}` };\n if (run.matchedExpected === true)\n return { tone: 'success', text: 'Output matches the expected output' };\n if (run.matchedExpected === false) return { tone: 'error', text: 'Output does not match yet' };\n return { tone: 'neutral', text: 'Ran successfully' };\n}\n\n/**\n * Runnable workspace for an INTERACTIVE lesson (BOFF-4107). Mount it with\n * `key={lesson.id}` so moving to another lesson starts a fresh editor: an editor\n * pre-filled with the lesson's starter code, an optional stdin box, and the\n * run output. Code runs only in the platform sandbox via `tecRunLessonCode`;\n * when the lesson has an expected output, a matching run completes the lesson\n * server-side and `onCompleted` lets the page refresh progress.\n */\nexport function InteractiveWorkspace({\n lesson,\n onCompleted,\n}: {\n lesson: TecLesson;\n onCompleted: () => void;\n}) {\n const starter = lesson.starterCode ?? '';\n const [code, setCode] = useState(starter);\n const [stdin, setStdin] = useState(lesson.sampleInput ?? '');\n const [run, setRun] = useState<TecLessonCodeRun | null>(null);\n const [warming, setWarming] = useState(false);\n const [running, setRunning] = useState(false);\n const warmingSince = useRef<number | null>(null);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const [runLessonCode] = useRunLessonCode();\n\n // The page mounts one workspace per lesson (keyed by lesson id), so the\n // editor starts fresh on every lesson; only the pending retry needs cleanup.\n useEffect(\n () => () => {\n if (timer.current) clearTimeout(timer.current);\n },\n []\n );\n\n const execute = async (): Promise<void> => {\n setRunning(true);\n try {\n const res = await runLessonCode({\n variables: {\n input: {\n lessonId: lesson.id,\n language: lesson.language ?? undefined,\n code,\n stdin,\n },\n },\n });\n const result = res.data?.tecRunLessonCode;\n if (!result) {\n // The mutation failed (for example a spent sandbox quota); the error\n // toast explains why, so stop any warm-up polling and re-enable Run.\n setWarming(false);\n warmingSince.current = null;\n return;\n }\n if (result.status === 'WARMING') {\n warmingSince.current ??= Date.now();\n if (Date.now() - warmingSince.current < WARMING_BUDGET_MS) {\n setWarming(true);\n timer.current = setTimeout(() => void execute(), WARMING_RETRY_MS);\n return;\n }\n setWarming(false);\n warmingSince.current = null;\n setRun({ ...result, status: 'UNAVAILABLE' });\n return;\n }\n setWarming(false);\n warmingSince.current = null;\n setRun(result);\n if (result.lessonCompleted) onCompleted();\n } finally {\n setRunning(false);\n }\n };\n\n const busy = running || warming;\n const summary = run && run.status === 'COMPLETED' ? runSummary(run) : null;\n\n return (\n <div className=\"space-y-4\">\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} /> {lesson.language ? humanizeEnum(lesson.language) : 'code'}\n </span>\n </div>\n <Textarea\n aria-label=\"Lesson code editor\"\n value={code}\n onChange={(e) => setCode(e.target.value)}\n placeholder=\"Write your code…\"\n className=\"min-h-[260px] 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 <div>\n <label\n htmlFor={`tec-lesson-stdin-${lesson.id}`}\n className=\"mb-1 block text-xs font-medium uppercase tracking-wide text-text-muted\"\n >\n Input (stdin)\n </label>\n <Textarea\n id={`tec-lesson-stdin-${lesson.id}`}\n value={stdin}\n onChange={(e) => setStdin(e.target.value)}\n placeholder=\"Optional input your program reads\"\n className=\"min-h-[64px] font-mono text-xs\"\n spellCheck={false}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button onClick={() => void execute()} disabled={busy || !code.trim()}>\n {busy ? <Loader2 size={16} className=\"animate-spin\" /> : <Play size={16} />}\n {warming ? 'Starting sandbox…' : running ? 'Running…' : 'Run code'}\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => {\n setCode(starter);\n setRun(null);\n }}\n disabled={busy || code === starter}\n >\n <RotateCcw size={15} /> Reset to starter\n </Button>\n <span className=\"text-xs text-text-muted\">Runs in an isolated sandbox.</span>\n </div>\n\n {warming ? (\n <p\n role=\"status\"\n className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\"\n >\n The code sandbox is starting up. Your code will run automatically as soon as it is ready.\n </p>\n ) : null}\n\n {run?.status === 'UNAVAILABLE' ? (\n <p\n role=\"alert\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n The code sandbox is not available right now. Please try again in a moment.\n </p>\n ) : null}\n\n {run && summary ? (\n <div\n className=\"space-y-3 rounded-xl border border-border-seam p-4\"\n data-testid=\"lesson-run-output\"\n >\n <div className=\"flex flex-wrap items-center gap-3\">\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 text-sm font-medium',\n summary.tone === 'success' && 'text-status-success-text',\n summary.tone === 'error' && 'text-status-error-text',\n summary.tone === 'neutral' && 'text-text-primary'\n )}\n >\n {summary.tone === 'error' ? <XCircle size={16} /> : <CheckCircle2 size={16} />}\n {summary.text}\n </span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Clock size={12} /> {run.durationMs} ms\n </span>\n {run.lessonCompleted ? (\n <span className=\"rounded-full border border-status-success-border bg-status-success-bg-subtle px-2 py-0.5 text-xs font-medium text-status-success-text\">\n Lesson completed\n </span>\n ) : null}\n </div>\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n <IoBlock label=\"Output\" value={run.stdout} />\n {lesson.expectedOutput ? (\n <IoBlock label=\"Expected output\" value={lesson.expectedOutput} />\n ) : null}\n </div>\n {run.stderr ? (\n <div>\n <p className=\"mb-1 flex items-center gap-1 text-xs font-medium uppercase tracking-wide text-text-muted\">\n <Terminal size={12} /> Errors\n </p>\n <pre className=\"overflow-auto rounded-lg bg-status-error-bg-subtle px-3 py-2 font-mono text-xs text-status-error-text\">\n {run.stderr}\n </pre>\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InteractiveWorkspace.js","names":[],"sources":["../../../../src/technospam/components/lesson/InteractiveWorkspace.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport {\n CheckCircle2,\n Clock,\n Code2,\n Loader2,\n Play,\n RotateCcw,\n Terminal,\n XCircle,\n} from 'lucide-react';\nimport { Button, Textarea } from '@burdenoff/fe-libs/ui';\nimport { IoBlock } from '../SubmissionViews';\nimport { humanizeEnum } from '../../utils/format';\nimport { cn } from '../../utils/cn';\nimport { userMessageFromError } from '../../lib/errorMessage';\nimport { useRunLessonCode, type TecLesson, type TecLessonCodeRun } from '../../api/technospamApi';\n\n/**\n * A cold code sandbox can take up to a few minutes to start. While the API\n * answers WARMING we call it again on this interval, giving up after the\n * budget below so the learner can retry by hand.\n */\nconst WARMING_RETRY_MS = 4_000;\nconst WARMING_BUDGET_MS = 4 * 60_000;\n\nfunction runSummary(run: TecLessonCodeRun): {\n tone: 'success' | 'error' | 'neutral';\n text: string;\n} {\n if (run.compileFailed) return { tone: 'error', text: 'Compile error' };\n if (run.timedOut) return { tone: 'error', text: 'Time limit exceeded' };\n if (run.memoryLimitExceeded) return { tone: 'error', text: 'Memory limit exceeded' };\n if (run.exitCode !== 0) return { tone: 'error', text: `Exited with code ${run.exitCode}` };\n if (run.matchedExpected === true)\n return { tone: 'success', text: 'Output matches the expected output' };\n if (run.matchedExpected === false) return { tone: 'error', text: 'Output does not match yet' };\n return { tone: 'neutral', text: 'Ran successfully' };\n}\n\n/**\n * Runnable workspace for an INTERACTIVE lesson (BOFF-4107). Mount it with\n * `key={lesson.id}` so moving to another lesson starts a fresh editor: an editor\n * pre-filled with the lesson's starter code, an optional stdin box, and the\n * run output. Code runs only in the platform sandbox via `tecRunLessonCode`;\n * when the lesson has an expected output, a matching run completes the lesson\n * server-side and `onCompleted` lets the page refresh progress.\n */\nexport function InteractiveWorkspace({\n lesson,\n onCompleted,\n}: {\n lesson: TecLesson;\n onCompleted: () => void;\n}) {\n const starter = lesson.starterCode ?? '';\n const [code, setCode] = useState(starter);\n const [stdin, setStdin] = useState(lesson.sampleInput ?? '');\n const [run, setRun] = useState<TecLessonCodeRun | null>(null);\n const [warming, setWarming] = useState(false);\n const [running, setRunning] = useState(false);\n /** Why the last run was refused (for example a spent sandbox quota), shown inline. */\n const [runError, setRunError] = useState<string | null>(null);\n const warmingSince = useRef<number | null>(null);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const [runLessonCode] = useRunLessonCode();\n\n // The page mounts one workspace per lesson (keyed by lesson id), so the\n // editor starts fresh on every lesson; only the pending retry needs cleanup.\n useEffect(\n () => () => {\n if (timer.current) clearTimeout(timer.current);\n },\n []\n );\n\n const execute = async (): Promise<void> => {\n setRunning(true);\n setRunError(null);\n try {\n const res = await runLessonCode({\n variables: {\n input: {\n lessonId: lesson.id,\n language: lesson.language ?? undefined,\n code,\n stdin,\n },\n },\n });\n const result = res.data?.tecRunLessonCode;\n if (!result) {\n // The mutation failed (for example a spent sandbox quota). Stop any\n // warm-up polling, re-enable Run and say why next to the editor, where\n // the learner is looking, not only in a transient toast.\n setWarming(false);\n warmingSince.current = null;\n setRun(null);\n setRunError(\n userMessageFromError(res.error, 'Your code could not be run. Please try again.')\n );\n return;\n }\n if (result.status === 'WARMING') {\n warmingSince.current ??= Date.now();\n if (Date.now() - warmingSince.current < WARMING_BUDGET_MS) {\n setWarming(true);\n timer.current = setTimeout(() => void execute(), WARMING_RETRY_MS);\n return;\n }\n setWarming(false);\n warmingSince.current = null;\n setRun({ ...result, status: 'UNAVAILABLE' });\n return;\n }\n setWarming(false);\n warmingSince.current = null;\n setRun(result);\n if (result.lessonCompleted) onCompleted();\n } finally {\n setRunning(false);\n }\n };\n\n const busy = running || warming;\n const summary = run && run.status === 'COMPLETED' ? runSummary(run) : null;\n\n return (\n <div className=\"space-y-4\">\n <div className=\"overflow-hidden rounded-xl border border-border-strong bg-bg-sunken shadow-sm\">\n <div className=\"flex items-center justify-between gap-2 border-b border-border-seam bg-bg-canvas/40 px-4 py-2\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-error-border\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-warning-border\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-status-success-border\" />\n </div>\n <span className=\"inline-flex items-center gap-1.5 font-mono text-xs text-text-secondary\">\n <Code2 size={13} /> {lesson.language ? humanizeEnum(lesson.language) : 'code'}\n </span>\n </div>\n <Textarea\n aria-label=\"Lesson code editor\"\n value={code}\n onChange={(e) => setCode(e.target.value)}\n placeholder=\"Write your code…\"\n className=\"min-h-[260px] 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 <div>\n <label\n htmlFor={`tec-lesson-stdin-${lesson.id}`}\n className=\"mb-1 block text-xs font-medium uppercase tracking-wide text-text-muted\"\n >\n Input (stdin)\n </label>\n <Textarea\n id={`tec-lesson-stdin-${lesson.id}`}\n value={stdin}\n onChange={(e) => setStdin(e.target.value)}\n placeholder=\"Optional input your program reads\"\n className=\"min-h-[64px] font-mono text-xs\"\n spellCheck={false}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button onClick={() => void execute()} disabled={busy || !code.trim()}>\n {busy ? <Loader2 size={16} className=\"animate-spin\" /> : <Play size={16} />}\n {warming ? 'Starting sandbox…' : running ? 'Running…' : 'Run code'}\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => {\n setCode(starter);\n setRun(null);\n setRunError(null);\n }}\n disabled={busy || code === starter}\n >\n <RotateCcw size={15} /> Reset to starter\n </Button>\n <span className=\"text-xs text-text-muted\">Runs in an isolated sandbox.</span>\n </div>\n\n {warming ? (\n <p\n role=\"status\"\n className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\"\n >\n The code sandbox is starting up. Your code will run automatically as soon as it is ready.\n </p>\n ) : null}\n\n {runError ? (\n <p\n role=\"alert\"\n data-testid=\"lesson-run-error\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {runError}\n </p>\n ) : null}\n\n {run?.status === 'UNAVAILABLE' ? (\n <p\n role=\"alert\"\n className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n The code sandbox is not available right now. Please try again in a moment.\n </p>\n ) : null}\n\n {run && summary ? (\n <div\n className=\"space-y-3 rounded-xl border border-border-seam p-4\"\n data-testid=\"lesson-run-output\"\n >\n <div className=\"flex flex-wrap items-center gap-3\">\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 text-sm font-medium',\n summary.tone === 'success' && 'text-status-success-text',\n summary.tone === 'error' && 'text-status-error-text',\n summary.tone === 'neutral' && 'text-text-primary'\n )}\n >\n {summary.tone === 'error' ? <XCircle size={16} /> : <CheckCircle2 size={16} />}\n {summary.text}\n </span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Clock size={12} /> {run.durationMs} ms\n </span>\n {run.lessonCompleted ? (\n <span className=\"rounded-full border border-status-success-border bg-status-success-bg-subtle px-2 py-0.5 text-xs font-medium text-status-success-text\">\n Lesson completed\n </span>\n ) : null}\n </div>\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n <IoBlock label=\"Output\" value={run.stdout} />\n {lesson.expectedOutput ? (\n <IoBlock label=\"Expected output\" value={lesson.expectedOutput} />\n ) : null}\n </div>\n {run.stderr ? (\n <div>\n <p className=\"mb-1 flex items-center gap-1 text-xs font-medium uppercase tracking-wide text-text-muted\">\n <Terminal size={12} /> Errors\n </p>\n <pre className=\"overflow-auto rounded-lg bg-status-error-bg-subtle px-3 py-2 font-mono text-xs text-status-error-text\">\n {run.stderr}\n </pre>\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The message to show a person for a failed GraphQL operation: the service's
|
|
3
|
+
* `userMessage` when there is one, then the GraphQL error message, then the
|
|
4
|
+
* transport error's own message, then `fallback`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function userMessageFromError(error: unknown, fallback?: string): string;
|
|
7
|
+
//# sourceMappingURL=errorMessage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorMessage.d.ts","sourceRoot":"","sources":["../../../src/technospam/lib/errorMessage.ts"],"names":[],"mappings":"AAiCA;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAAsB,GAAG,MAAM,CAS3F"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CombinedGraphQLErrors as e, ServerError as t } from "@apollo/client";
|
|
2
|
+
//#region src/technospam/lib/errorMessage.ts
|
|
3
|
+
function n(e) {
|
|
4
|
+
if (!e) return null;
|
|
5
|
+
let t = e.extensions?.userMessage;
|
|
6
|
+
return typeof t == "string" && t.trim() ? t.trim() : typeof e.message == "string" && e.message.trim() ? e.message.trim() : null;
|
|
7
|
+
}
|
|
8
|
+
function r(e) {
|
|
9
|
+
try {
|
|
10
|
+
return n(JSON.parse(e)?.errors?.[0]);
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function i(i, a = "Please try again.") {
|
|
16
|
+
return e.is(i) ? n(i.errors[0]) ?? a : t.is(i) ? r(i.bodyText) ?? a : i instanceof Error && i.message.trim() ? i.message.trim() : a;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { i as userMessageFromError };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=errorMessage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorMessage.js","names":[],"sources":["../../../src/technospam/lib/errorMessage.ts"],"sourcesContent":["import { CombinedGraphQLErrors, ServerError } from '@apollo/client';\n\n/** The fields a platform GraphQL error carries that are worth showing a learner. */\ninterface GraphQLErrorShape {\n readonly message?: unknown;\n readonly extensions?: { readonly userMessage?: unknown } | null;\n}\n\nfunction messageOf(error: GraphQLErrorShape | undefined): string | null {\n if (!error) return null;\n const userMessage = error.extensions?.userMessage;\n if (typeof userMessage === 'string' && userMessage.trim()) return userMessage.trim();\n if (typeof error.message === 'string' && error.message.trim()) return error.message.trim();\n return null;\n}\n\n/**\n * Read the first GraphQL error out of a raw response body. The workspace\n * gateway answers a quota block (`QUOTA_EXHAUSTED` / `QUOTA_NOT_PROVISIONED`)\n * with HTTP 429 and a normal GraphQL error body; Apollo Client surfaces that as\n * a `ServerError` whose message is only \"Response not successful: Received\n * status code 429\", so the real reason has to come from the body.\n */\nfunction messageFromBody(bodyText: string): string | null {\n try {\n // A GraphQL response body; anything that is not one falls through to null.\n const body = JSON.parse(bodyText) as { errors?: readonly GraphQLErrorShape[] | null } | null;\n return messageOf(body?.errors?.[0]);\n } catch {\n return null;\n }\n}\n\n/**\n * The message to show a person for a failed GraphQL operation: the service's\n * `userMessage` when there is one, then the GraphQL error message, then the\n * transport error's own message, then `fallback`.\n */\nexport function userMessageFromError(error: unknown, fallback = 'Please try again.'): string {\n if (CombinedGraphQLErrors.is(error)) {\n return messageOf(error.errors[0]) ?? fallback;\n }\n if (ServerError.is(error)) {\n return messageFromBody(error.bodyText) ?? fallback;\n }\n if (error instanceof Error && error.message.trim()) return error.message.trim();\n return fallback;\n}\n"],"mappings":";;AAQA,SAAS,EAAU,GAAqD;AACtE,KAAI,CAAC,EAAO,QAAO;CACnB,IAAM,IAAc,EAAM,YAAY;AAGtC,QAFI,OAAO,KAAgB,YAAY,EAAY,MAAM,GAAS,EAAY,MAAM,GAChF,OAAO,EAAM,WAAY,YAAY,EAAM,QAAQ,MAAM,GAAS,EAAM,QAAQ,MAAM,GACnF;;AAUT,SAAS,EAAgB,GAAiC;AACxD,KAAI;AAGF,SAAO,EADM,KAAK,MAAM,EAAS,EACV,SAAS,GAAG;SAC7B;AACN,SAAO;;;AASX,SAAgB,EAAqB,GAAgB,IAAW,qBAA6B;AAQ3F,QAPI,EAAsB,GAAG,EAAM,GAC1B,EAAU,EAAM,OAAO,GAAG,IAAI,IAEnC,EAAY,GAAG,EAAM,GAChB,EAAgB,EAAM,SAAS,IAAI,IAExC,aAAiB,SAAS,EAAM,QAAQ,MAAM,GAAS,EAAM,QAAQ,MAAM,GACxE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminAdminDashboardPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/AdminAdminDashboardPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminAdminDashboardPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/AdminAdminDashboardPagePage.tsx"],"names":[],"mappings":"AA4nDA,wBAAgB,2BAA2B,gCA0D1C"}
|