@schoolio/player 1.2.0 → 1.2.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/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # @schoolio/player
2
2
 
3
- A React component for loading and playing quizzes from Quiz Engine.
3
+ React components for loading quizzes and displaying attempt results from Quiz Engine.
4
+
5
+ ## Components
6
+
7
+ - **QuizPlayer** - Interactive quiz-taking component
8
+ - **AttemptViewer** - Displays quiz attempt results with detailed breakdown
4
9
 
5
10
  ## Installation
6
11
 
@@ -138,6 +143,90 @@ await client.updateAttempt(attempt.id, {
138
143
  });
139
144
  ```
140
145
 
146
+ ---
147
+
148
+ ## AttemptViewer
149
+
150
+ The `AttemptViewer` component displays quiz attempt results with a summary and detailed question-by-question breakdown.
151
+
152
+ ### Usage
153
+
154
+ ```tsx
155
+ import { AttemptViewer } from '@schoolio/player';
156
+
157
+ function MyResultsPage() {
158
+ return (
159
+ <AttemptViewer
160
+ attemptId={123}
161
+ apiBaseUrl="https://your-quiz-engine-url.com"
162
+ />
163
+ );
164
+ }
165
+ ```
166
+
167
+ ### Props
168
+
169
+ | Prop | Type | Required | Description |
170
+ |------|------|----------|-------------|
171
+ | `attemptId` | `number` | Yes | The ID of the quiz attempt to display |
172
+ | `apiBaseUrl` | `string` | Yes | Base URL of your Quiz Engine API |
173
+ | `title` | `string` | No | Optional title (reserved for future use) |
174
+
175
+ ### API Endpoint Required
176
+
177
+ The component expects this endpoint:
178
+
179
+ ```
180
+ GET {apiBaseUrl}/api/quiz-attempts/{attemptId}/results
181
+ ```
182
+
183
+ **Response format:**
184
+
185
+ ```json
186
+ {
187
+ "attempt": {
188
+ "id": 123,
189
+ "quizId": 1,
190
+ "score": 80,
191
+ "totalQuestions": 10,
192
+ "correctAnswers": 8,
193
+ "timeTaken": 300,
194
+ "completedAt": "2025-01-02T10:00:00Z",
195
+ "answers": { "1": "selected_answer", "2": "another_answer" }
196
+ },
197
+ "questions": [
198
+ {
199
+ "id": 1,
200
+ "questionText": "What is 2+2?",
201
+ "questionType": "multiple_choice",
202
+ "options": ["3", "4", "5"],
203
+ "correctAnswer": "4",
204
+ "explanation": "Basic addition"
205
+ }
206
+ ]
207
+ }
208
+ ```
209
+
210
+ ### Styling
211
+
212
+ The component uses 100% width to fill its parent container. Wrap it in a sized container to control dimensions:
213
+
214
+ ```tsx
215
+ <div style={{ maxWidth: '800px', margin: '0 auto' }}>
216
+ <AttemptViewer attemptId={123} apiBaseUrl="https://api.example.com" />
217
+ </div>
218
+ ```
219
+
220
+ ### Features
221
+
222
+ - **Summary cards** showing score, questions count, and time taken
223
+ - **Detailed breakdown** of each question with correct/incorrect indicators
224
+ - **Explanations** displayed for each question when available
225
+ - **Purple theme** (#6721b0) matching the QuizPlayer design
226
+ - **Responsive layout** that adapts to container width
227
+
228
+ ---
229
+
141
230
  ## License
142
231
 
143
232
  MIT
package/dist/index.js CHANGED
@@ -1,9 +1,37 @@
1
- 'use strict';
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
2
19
 
3
- var react = require('react');
4
- var jsxRuntime = require('react/jsx-runtime');
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AttemptViewer: () => AttemptViewer,
24
+ QuizApiClient: () => QuizApiClient,
25
+ QuizPlayer: () => QuizPlayer,
26
+ calculateScore: () => calculateScore,
27
+ checkAnswer: () => checkAnswer,
28
+ createAnswerDetail: () => createAnswerDetail,
29
+ formatTime: () => formatTime
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
5
32
 
6
33
  // src/QuizPlayer.tsx
34
+ var import_react = require("react");
7
35
 
8
36
  // src/api.ts
9
37
  var QuizApiClient = class {
@@ -143,6 +171,9 @@ function formatTime(seconds) {
143
171
  const secs = seconds % 60;
144
172
  return `${mins}:${secs.toString().padStart(2, "0")}`;
145
173
  }
174
+
175
+ // src/QuizPlayer.tsx
176
+ var import_jsx_runtime = require("react/jsx-runtime");
146
177
  var defaultStyles = {
147
178
  container: {
148
179
  fontFamily: "system-ui, -apple-system, sans-serif",
@@ -203,6 +234,14 @@ var defaultStyles = {
203
234
  borderColor: "#6721b0",
204
235
  backgroundColor: "#f3e8ff"
205
236
  },
237
+ optionCorrect: {
238
+ borderColor: "#22c55e",
239
+ backgroundColor: "#f0fdf4"
240
+ },
241
+ optionIncorrect: {
242
+ borderColor: "#ef4444",
243
+ backgroundColor: "#fef2f2"
244
+ },
206
245
  input: {
207
246
  width: "100%",
208
247
  padding: "12px 16px",
@@ -268,7 +307,7 @@ var defaultStyles = {
268
307
  }
269
308
  };
270
309
  function Spinner({ size = 16, color = "#ffffff" }) {
271
- return /* @__PURE__ */ jsxRuntime.jsx(
310
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
272
311
  "span",
273
312
  {
274
313
  style: {
@@ -280,7 +319,7 @@ function Spinner({ size = 16, color = "#ffffff" }) {
280
319
  borderRadius: "50%",
281
320
  animation: "spin 0.8s linear infinite"
282
321
  },
283
- children: /* @__PURE__ */ jsxRuntime.jsx("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
322
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
284
323
  }
285
324
  );
286
325
  }
@@ -298,25 +337,25 @@ function QuizPlayer({
298
337
  onProgress,
299
338
  className
300
339
  }) {
301
- const [quiz, setQuiz] = react.useState(null);
302
- const [attempt, setAttempt] = react.useState(null);
303
- const [currentQuestionIndex, setCurrentQuestionIndex] = react.useState(0);
304
- const [answers, setAnswers] = react.useState(/* @__PURE__ */ new Map());
305
- const [answersDetail, setAnswersDetail] = react.useState([]);
306
- const [isSubmitting, setIsSubmitting] = react.useState(false);
307
- const [isNavigating, setIsNavigating] = react.useState(false);
308
- const [isCompleted, setIsCompleted] = react.useState(false);
309
- const [result, setResult] = react.useState(null);
310
- const [error, setError] = react.useState(null);
311
- const [isLoading, setIsLoading] = react.useState(true);
312
- const [elapsedSeconds, setElapsedSeconds] = react.useState(0);
313
- const apiClient = react.useRef(null);
314
- const timerRef = react.useRef(null);
315
- const startTimeRef = react.useRef(Date.now());
316
- react.useEffect(() => {
340
+ const [quiz, setQuiz] = (0, import_react.useState)(null);
341
+ const [attempt, setAttempt] = (0, import_react.useState)(null);
342
+ const [currentQuestionIndex, setCurrentQuestionIndex] = (0, import_react.useState)(0);
343
+ const [answers, setAnswers] = (0, import_react.useState)(/* @__PURE__ */ new Map());
344
+ const [answersDetail, setAnswersDetail] = (0, import_react.useState)([]);
345
+ const [isSubmitting, setIsSubmitting] = (0, import_react.useState)(false);
346
+ const [isNavigating, setIsNavigating] = (0, import_react.useState)(false);
347
+ const [isCompleted, setIsCompleted] = (0, import_react.useState)(false);
348
+ const [result, setResult] = (0, import_react.useState)(null);
349
+ const [error, setError] = (0, import_react.useState)(null);
350
+ const [isLoading, setIsLoading] = (0, import_react.useState)(true);
351
+ const [elapsedSeconds, setElapsedSeconds] = (0, import_react.useState)(0);
352
+ const apiClient = (0, import_react.useRef)(null);
353
+ const timerRef = (0, import_react.useRef)(null);
354
+ const startTimeRef = (0, import_react.useRef)(Date.now());
355
+ (0, import_react.useEffect)(() => {
317
356
  apiClient.current = new QuizApiClient({ baseUrl: apiBaseUrl, authToken });
318
357
  }, [apiBaseUrl, authToken]);
319
- react.useEffect(() => {
358
+ (0, import_react.useEffect)(() => {
320
359
  async function initialize() {
321
360
  if (!apiClient.current) return;
322
361
  try {
@@ -363,7 +402,7 @@ function QuizPlayer({
363
402
  }
364
403
  initialize();
365
404
  }, [quizId, lessonId, assignLessonId, courseId, childId, parentId, onError]);
366
- react.useEffect(() => {
405
+ (0, import_react.useEffect)(() => {
367
406
  if (!isLoading && !isCompleted && !error) {
368
407
  startTimeRef.current = Date.now();
369
408
  timerRef.current = setInterval(() => {
@@ -376,7 +415,7 @@ function QuizPlayer({
376
415
  }
377
416
  };
378
417
  }, [isLoading, isCompleted, error]);
379
- react.useEffect(() => {
418
+ (0, import_react.useEffect)(() => {
380
419
  if (quiz && onProgress) {
381
420
  onProgress({
382
421
  currentQuestion: currentQuestionIndex + 1,
@@ -386,11 +425,11 @@ function QuizPlayer({
386
425
  }
387
426
  }, [currentQuestionIndex, answers.size, quiz, onProgress]);
388
427
  const currentQuestion = quiz?.questions[currentQuestionIndex];
389
- const handleAnswerChange = react.useCallback((value) => {
428
+ const handleAnswerChange = (0, import_react.useCallback)((value) => {
390
429
  if (!currentQuestion) return;
391
430
  setAnswers((prev) => new Map(prev).set(currentQuestion.id, value));
392
431
  }, [currentQuestion]);
393
- const handleNext = react.useCallback(async () => {
432
+ const handleNext = (0, import_react.useCallback)(async () => {
394
433
  if (!quiz || !attempt || !currentQuestion || !apiClient.current) return;
395
434
  const selectedAnswer2 = answers.get(currentQuestion.id);
396
435
  if (selectedAnswer2 === void 0) return;
@@ -417,12 +456,12 @@ function QuizPlayer({
417
456
  setCurrentQuestionIndex((prev) => prev + 1);
418
457
  }
419
458
  }, [quiz, attempt, currentQuestion, answers, answersDetail, currentQuestionIndex]);
420
- const handlePrevious = react.useCallback(() => {
459
+ const handlePrevious = (0, import_react.useCallback)(() => {
421
460
  if (currentQuestionIndex > 0) {
422
461
  setCurrentQuestionIndex((prev) => prev - 1);
423
462
  }
424
463
  }, [currentQuestionIndex]);
425
- const handleSubmit = react.useCallback(async () => {
464
+ const handleSubmit = (0, import_react.useCallback)(async () => {
426
465
  if (!quiz || !attempt || !apiClient.current) return;
427
466
  setIsSubmitting(true);
428
467
  try {
@@ -469,55 +508,55 @@ function QuizPlayer({
469
508
  }
470
509
  }, [quiz, attempt, currentQuestion, answers, answersDetail, onComplete, onError]);
471
510
  if (isLoading) {
472
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.loading, children: "Loading quiz..." }) });
511
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.loading, children: "Loading quiz..." }) });
473
512
  }
474
513
  if (error) {
475
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.error, children: /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
514
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.error, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
476
515
  "Error: ",
477
516
  error
478
517
  ] }) }) });
479
518
  }
480
519
  if (isCompleted && result) {
481
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.results, children: [
482
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.resultScore, children: [
520
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.results, children: [
521
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.resultScore, children: [
483
522
  result.score,
484
523
  "%"
485
524
  ] }),
486
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.resultLabel, children: [
525
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.resultLabel, children: [
487
526
  result.correctAnswers,
488
527
  " of ",
489
528
  result.totalQuestions,
490
529
  " correct"
491
530
  ] }),
492
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...defaultStyles.resultLabel, marginTop: "8px" }, children: [
531
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...defaultStyles.resultLabel, marginTop: "8px" }, children: [
493
532
  "Time: ",
494
533
  formatTime(result.timeSpentSeconds)
495
534
  ] })
496
535
  ] }) });
497
536
  }
498
537
  if (!quiz || !currentQuestion) {
499
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.error, children: "No quiz data available" }) });
538
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.error, children: "No quiz data available" }) });
500
539
  }
501
540
  const selectedAnswer = answers.get(currentQuestion.id);
502
541
  const isLastQuestion = currentQuestionIndex === quiz.questions.length - 1;
503
542
  const progressPercent = (currentQuestionIndex + 1) / quiz.questions.length * 100;
504
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: defaultStyles.container, children: [
505
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.header, children: [
506
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
507
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.title, children: quiz.title }),
508
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.timer, children: formatTime(elapsedSeconds) })
543
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, style: defaultStyles.container, children: [
544
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.header, children: [
545
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
546
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.title, children: quiz.title }),
547
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.timer, children: formatTime(elapsedSeconds) })
509
548
  ] }),
510
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.progress, children: [
549
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.progress, children: [
511
550
  "Question ",
512
551
  currentQuestionIndex + 1,
513
552
  " of ",
514
553
  quiz.questions.length
515
554
  ] }),
516
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
555
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
517
556
  ] }),
518
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.question, children: [
519
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.questionText, children: currentQuestion.question }),
520
- (currentQuestion.type === "single" || currentQuestion.type === "true-false") && /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => /* @__PURE__ */ jsxRuntime.jsx(
557
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.question, children: [
558
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.questionText, children: currentQuestion.question }),
559
+ (currentQuestion.type === "single" || currentQuestion.type === "true-false") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
521
560
  "div",
522
561
  {
523
562
  style: {
@@ -529,9 +568,9 @@ function QuizPlayer({
529
568
  },
530
569
  idx
531
570
  )) }),
532
- currentQuestion.type === "multiple" && /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
571
+ currentQuestion.type === "multiple" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
533
572
  const selected = Array.isArray(selectedAnswer) && selectedAnswer.includes(option);
534
- return /* @__PURE__ */ jsxRuntime.jsx(
573
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
535
574
  "div",
536
575
  {
537
576
  style: {
@@ -551,7 +590,7 @@ function QuizPlayer({
551
590
  idx
552
591
  );
553
592
  }) }),
554
- (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ jsxRuntime.jsx(
593
+ (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
555
594
  "textarea",
556
595
  {
557
596
  style: { ...defaultStyles.input, minHeight: currentQuestion.type === "essay" ? "150px" : "60px" },
@@ -560,7 +599,7 @@ function QuizPlayer({
560
599
  placeholder: "Type your answer here..."
561
600
  }
562
601
  ),
563
- currentQuestion.type === "fill" && /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles.options, children: currentQuestion.blanks?.map((_, idx) => /* @__PURE__ */ jsxRuntime.jsx(
602
+ currentQuestion.type === "fill" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.options, children: currentQuestion.blanks?.map((_, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
564
603
  "input",
565
604
  {
566
605
  style: defaultStyles.input,
@@ -575,8 +614,8 @@ function QuizPlayer({
575
614
  idx
576
615
  )) })
577
616
  ] }),
578
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.buttons, children: [
579
- /* @__PURE__ */ jsxRuntime.jsx(
617
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.buttons, children: [
618
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
580
619
  "button",
581
620
  {
582
621
  style: {
@@ -588,7 +627,7 @@ function QuizPlayer({
588
627
  children: "Previous"
589
628
  }
590
629
  ),
591
- isLastQuestion ? /* @__PURE__ */ jsxRuntime.jsx(
630
+ isLastQuestion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
592
631
  "button",
593
632
  {
594
633
  style: {
@@ -597,9 +636,9 @@ function QuizPlayer({
597
636
  },
598
637
  onClick: handleSubmit,
599
638
  disabled: isSubmitting || selectedAnswer === void 0,
600
- children: isSubmitting ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
639
+ children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
601
640
  }
602
- ) : /* @__PURE__ */ jsxRuntime.jsx(
641
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
603
642
  "button",
604
643
  {
605
644
  style: {
@@ -608,12 +647,16 @@ function QuizPlayer({
608
647
  },
609
648
  onClick: handleNext,
610
649
  disabled: isNavigating || selectedAnswer === void 0,
611
- children: isNavigating ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 16, color: "#9ca3af" }) : "Next"
650
+ children: isNavigating ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Next"
612
651
  }
613
652
  )
614
653
  ] })
615
654
  ] });
616
655
  }
656
+
657
+ // src/AttemptViewer.tsx
658
+ var import_react2 = require("react");
659
+ var import_jsx_runtime2 = require("react/jsx-runtime");
617
660
  var defaultStyles2 = {
618
661
  container: {
619
662
  fontFamily: "system-ui, -apple-system, sans-serif",
@@ -628,6 +671,12 @@ var defaultStyles2 = {
628
671
  borderBottom: "1px solid #e5e7eb",
629
672
  paddingBottom: "20px"
630
673
  },
674
+ title: {
675
+ fontSize: "24px",
676
+ fontWeight: "600",
677
+ marginBottom: "16px",
678
+ color: "#111827"
679
+ },
631
680
  summaryGrid: {
632
681
  display: "grid",
633
682
  gridTemplateColumns: "repeat(auto-fit, minmax(120px, 1fr))",
@@ -787,11 +836,11 @@ function AttemptViewer({
787
836
  showExplanations = true,
788
837
  title
789
838
  }) {
790
- const [attempt, setAttempt] = react.useState(null);
791
- const [loading, setLoading] = react.useState(true);
792
- const [error, setError] = react.useState(null);
793
- react.useEffect(() => {
794
- new QuizApiClient({
839
+ const [attempt, setAttempt] = (0, import_react2.useState)(null);
840
+ const [loading, setLoading] = (0, import_react2.useState)(true);
841
+ const [error, setError] = (0, import_react2.useState)(null);
842
+ (0, import_react2.useEffect)(() => {
843
+ const apiClient = new QuizApiClient({
795
844
  baseUrl: apiBaseUrl,
796
845
  authToken
797
846
  });
@@ -823,49 +872,49 @@ function AttemptViewer({
823
872
  window.location.reload();
824
873
  };
825
874
  if (loading) {
826
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.container, className, children: [
827
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: spinnerKeyframes }),
828
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.loading, children: [
829
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.spinner }),
830
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { marginTop: "16px", color: "#6b7280" }, children: "Loading attempt..." })
875
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.container, className, children: [
876
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: spinnerKeyframes }),
877
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.loading, children: [
878
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.spinner }),
879
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { marginTop: "16px", color: "#6b7280" }, children: "Loading attempt..." })
831
880
  ] })
832
881
  ] });
833
882
  }
834
883
  if (error || !attempt) {
835
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.container, className, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.error, children: [
836
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "18px", fontWeight: "500" }, children: "Failed to load attempt" }),
837
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { marginTop: "8px", color: "#6b7280" }, children: error }),
838
- /* @__PURE__ */ jsxRuntime.jsx("button", { style: defaultStyles2.retryButton, onClick: handleRetry, children: "Try Again" })
884
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.container, className, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.error, children: [
885
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: "18px", fontWeight: "500" }, children: "Failed to load attempt" }),
886
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { marginTop: "8px", color: "#6b7280" }, children: error }),
887
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: defaultStyles2.retryButton, onClick: handleRetry, children: "Try Again" })
839
888
  ] }) });
840
889
  }
841
890
  const scorePercentage = attempt.score ?? 0;
842
891
  const correctCount = attempt.correctAnswers ?? 0;
843
892
  const totalQuestions = attempt.totalQuestions;
844
893
  const timeSpent = attempt.timeSpentSeconds ?? 0;
845
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.container, className, children: [
846
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: spinnerKeyframes }),
847
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.header, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.summaryGrid, children: [
848
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.summaryCard, children: [
849
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.summaryValue, children: [
894
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.container, className, children: [
895
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: spinnerKeyframes }),
896
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.header, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryGrid, children: [
897
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
898
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
850
899
  scorePercentage,
851
900
  "%"
852
901
  ] }),
853
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.summaryLabel, children: "Score" })
902
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Score" })
854
903
  ] }),
855
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.summaryCard, children: [
856
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.summaryValue, children: [
904
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
905
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
857
906
  correctCount,
858
907
  "/",
859
908
  totalQuestions
860
909
  ] }),
861
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.summaryLabel, children: "Correct" })
910
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Correct" })
862
911
  ] }),
863
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.summaryCard, children: [
864
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.summaryValue, children: formatTime(timeSpent) }),
865
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.summaryLabel, children: "Time" })
912
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
913
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryValue, children: formatTime(timeSpent) }),
914
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Time" })
866
915
  ] })
867
916
  ] }) }),
868
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.questionsList, children: attempt.answers.map((answer, index) => /* @__PURE__ */ jsxRuntime.jsxs(
917
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.questionsList, children: attempt.answers.map((answer, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
869
918
  "div",
870
919
  {
871
920
  style: {
@@ -873,12 +922,12 @@ function AttemptViewer({
873
922
  ...answer.isCorrect ? defaultStyles2.questionCardCorrect : defaultStyles2.questionCardIncorrect
874
923
  },
875
924
  children: [
876
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.questionHeader, children: [
877
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: defaultStyles2.questionNumber, children: [
925
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.questionHeader, children: [
926
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: defaultStyles2.questionNumber, children: [
878
927
  "Question ",
879
928
  index + 1
880
929
  ] }),
881
- /* @__PURE__ */ jsxRuntime.jsx(
930
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
882
931
  "span",
883
932
  {
884
933
  style: {
@@ -889,23 +938,23 @@ function AttemptViewer({
889
938
  }
890
939
  )
891
940
  ] }),
892
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: defaultStyles2.questionText, children: answer.questionText }),
893
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.answerSection, children: [
894
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: defaultStyles2.answerLabel, children: "Your answer:" }),
895
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: defaultStyles2.studentAnswer, children: formatAnswer(answer.selectedAnswer) })
941
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.questionText, children: answer.questionText }),
942
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.answerSection, children: [
943
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.answerLabel, children: "Your answer:" }),
944
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.studentAnswer, children: formatAnswer(answer.selectedAnswer) })
896
945
  ] }),
897
- !answer.isCorrect && answer.correctAnswer && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.answerSection, children: [
898
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: defaultStyles2.answerLabel, children: "Correct answer:" }),
899
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: defaultStyles2.correctAnswer, children: formatAnswer(answer.correctAnswer) })
946
+ !answer.isCorrect && answer.correctAnswer && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.answerSection, children: [
947
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.answerLabel, children: "Correct answer:" }),
948
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.correctAnswer, children: formatAnswer(answer.correctAnswer) })
900
949
  ] }),
901
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.points, children: [
950
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.points, children: [
902
951
  answer.pointsEarned,
903
952
  " / ",
904
953
  answer.points,
905
954
  " points"
906
955
  ] }),
907
- showExplanations && answer.explanation && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles2.explanation, children: [
908
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Explanation:" }),
956
+ showExplanations && answer.explanation && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.explanation, children: [
957
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "Explanation:" }),
909
958
  " ",
910
959
  answer.explanation
911
960
  ] })
@@ -915,13 +964,14 @@ function AttemptViewer({
915
964
  )) })
916
965
  ] });
917
966
  }
918
-
919
- exports.AttemptViewer = AttemptViewer;
920
- exports.QuizApiClient = QuizApiClient;
921
- exports.QuizPlayer = QuizPlayer;
922
- exports.calculateScore = calculateScore;
923
- exports.checkAnswer = checkAnswer;
924
- exports.createAnswerDetail = createAnswerDetail;
925
- exports.formatTime = formatTime;
926
- //# sourceMappingURL=index.js.map
967
+ // Annotate the CommonJS export names for ESM import in node:
968
+ 0 && (module.exports = {
969
+ AttemptViewer,
970
+ QuizApiClient,
971
+ QuizPlayer,
972
+ calculateScore,
973
+ checkAnswer,
974
+ createAnswerDetail,
975
+ formatTime
976
+ });
927
977
  //# sourceMappingURL=index.js.map