@schoolio/player 1.2.2 → 1.4.0

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/index.js CHANGED
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  AttemptViewer: () => AttemptViewer,
24
24
  QuizApiClient: () => QuizApiClient,
25
25
  QuizPlayer: () => QuizPlayer,
26
+ TextToSpeech: () => TextToSpeech,
26
27
  calculateScore: () => calculateScore,
27
28
  checkAnswer: () => checkAnswer,
28
29
  createAnswerDetail: () => createAnswerDetail,
@@ -31,7 +32,7 @@ __export(index_exports, {
31
32
  module.exports = __toCommonJS(index_exports);
32
33
 
33
34
  // src/QuizPlayer.tsx
34
- var import_react = require("react");
35
+ var import_react3 = require("react");
35
36
 
36
37
  // src/api.ts
37
38
  var QuizApiClient = class {
@@ -83,6 +84,35 @@ var QuizApiClient = class {
83
84
  `/api/external/quiz-attempts?${queryParams.toString()}`
84
85
  );
85
86
  }
87
+ async skipQuestion(payload) {
88
+ return this.request(
89
+ "POST",
90
+ "/api/external/skip-question",
91
+ payload
92
+ );
93
+ }
94
+ async reportQuestion(payload) {
95
+ return this.request(
96
+ "POST",
97
+ "/api/external/report-question",
98
+ payload
99
+ );
100
+ }
101
+ async getStarterPrompts() {
102
+ return this.request("GET", "/api/external/question-chat/prompts");
103
+ }
104
+ async getOrCreateChatSession(params) {
105
+ return this.request("POST", "/api/external/question-chat/session", params);
106
+ }
107
+ async sendChatMessage(params) {
108
+ return this.request("POST", "/api/external/question-chat/message", params);
109
+ }
110
+ async getChatHistory(questionId, childId) {
111
+ return this.request(
112
+ "GET",
113
+ `/api/external/question-chat/${questionId}/${childId}`
114
+ );
115
+ }
86
116
  };
87
117
 
88
118
  // src/utils.ts
@@ -172,16 +202,605 @@ function formatTime(seconds) {
172
202
  return `${mins}:${secs.toString().padStart(2, "0")}`;
173
203
  }
174
204
 
175
- // src/QuizPlayer.tsx
205
+ // src/TextToSpeech.tsx
206
+ var import_react = require("react");
176
207
  var import_jsx_runtime = require("react/jsx-runtime");
208
+ var styles = {
209
+ container: {
210
+ display: "flex",
211
+ alignItems: "flex-start",
212
+ gap: "8px"
213
+ },
214
+ button: {
215
+ display: "inline-flex",
216
+ alignItems: "center",
217
+ justifyContent: "center",
218
+ border: "none",
219
+ borderRadius: "6px",
220
+ cursor: "pointer",
221
+ transition: "all 0.2s ease",
222
+ flexShrink: 0
223
+ },
224
+ buttonSm: {
225
+ width: "28px",
226
+ height: "28px",
227
+ padding: "4px",
228
+ backgroundColor: "transparent"
229
+ },
230
+ buttonMd: {
231
+ width: "32px",
232
+ height: "32px",
233
+ padding: "6px",
234
+ backgroundColor: "rgba(103, 33, 176, 0.1)"
235
+ },
236
+ buttonPlaying: {
237
+ backgroundColor: "rgba(103, 33, 176, 0.2)"
238
+ },
239
+ buttonDisabled: {
240
+ opacity: 0.4,
241
+ cursor: "not-allowed"
242
+ },
243
+ icon: {
244
+ width: "16px",
245
+ height: "16px",
246
+ color: "#6721b0"
247
+ },
248
+ textContainer: {
249
+ flex: 1
250
+ },
251
+ highlightedWord: {
252
+ backgroundColor: "rgba(103, 33, 176, 0.25)",
253
+ borderRadius: "3px",
254
+ padding: "0 2px",
255
+ transition: "background-color 0.15s ease",
256
+ fontWeight: 500
257
+ }
258
+ };
259
+ function VolumeIcon() {
260
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
261
+ "svg",
262
+ {
263
+ style: styles.icon,
264
+ viewBox: "0 0 24 24",
265
+ fill: "none",
266
+ stroke: "currentColor",
267
+ strokeWidth: "2",
268
+ strokeLinecap: "round",
269
+ strokeLinejoin: "round",
270
+ children: [
271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
272
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
273
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
274
+ ]
275
+ }
276
+ );
277
+ }
278
+ function StopIcon() {
279
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
280
+ "svg",
281
+ {
282
+ style: styles.icon,
283
+ viewBox: "0 0 24 24",
284
+ fill: "none",
285
+ stroke: "currentColor",
286
+ strokeWidth: "2",
287
+ strokeLinecap: "round",
288
+ strokeLinejoin: "round",
289
+ children: [
290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
291
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "9", width: "6", height: "6" })
292
+ ]
293
+ }
294
+ );
295
+ }
296
+ function TextToSpeech({ text, inline = false, size = "sm" }) {
297
+ const [isPlaying, setIsPlaying] = (0, import_react.useState)(false);
298
+ const [currentWordIndex, setCurrentWordIndex] = (0, import_react.useState)(-1);
299
+ const [isSupported, setIsSupported] = (0, import_react.useState)(true);
300
+ const [bestVoice, setBestVoice] = (0, import_react.useState)(null);
301
+ const utteranceRef = (0, import_react.useRef)(null);
302
+ const wordsRef = (0, import_react.useRef)([]);
303
+ (0, import_react.useEffect)(() => {
304
+ wordsRef.current = text.split(/\s+/).filter((word) => word.length > 0);
305
+ }, [text]);
306
+ (0, import_react.useEffect)(() => {
307
+ if (typeof window === "undefined" || !("speechSynthesis" in window)) {
308
+ setIsSupported(false);
309
+ }
310
+ }, []);
311
+ (0, import_react.useEffect)(() => {
312
+ if (!isSupported || typeof window === "undefined") return;
313
+ const loadVoices = () => {
314
+ const voices = window.speechSynthesis.getVoices();
315
+ if (voices.length === 0) return;
316
+ const premiumVoices = [
317
+ "samantha",
318
+ "victoria",
319
+ "karen",
320
+ "serena",
321
+ "jenny",
322
+ "aria",
323
+ "emma",
324
+ "moira",
325
+ "fiona",
326
+ "alice"
327
+ ];
328
+ const englishVoices = voices.filter(
329
+ (voice) => voice.lang.startsWith("en-")
330
+ );
331
+ const scoredVoices = englishVoices.map((voice) => {
332
+ let score = 0;
333
+ const nameLower = voice.name.toLowerCase();
334
+ premiumVoices.forEach((premiumName, index) => {
335
+ if (nameLower.includes(premiumName)) {
336
+ score += (premiumVoices.length - index) * 10;
337
+ }
338
+ });
339
+ if (voice.localService) score += 8;
340
+ if (!nameLower.includes("male")) score += 6;
341
+ if (voice.lang === "en-US") score += 3;
342
+ if (nameLower.includes("google us english") || nameLower.includes("microsoft david")) {
343
+ score -= 20;
344
+ }
345
+ return { voice, score };
346
+ });
347
+ scoredVoices.sort((a, b) => b.score - a.score);
348
+ if (scoredVoices.length > 0) {
349
+ setBestVoice(scoredVoices[0].voice);
350
+ }
351
+ };
352
+ loadVoices();
353
+ if (window.speechSynthesis.onvoiceschanged !== void 0) {
354
+ window.speechSynthesis.onvoiceschanged = loadVoices;
355
+ }
356
+ return () => {
357
+ if (window.speechSynthesis.onvoiceschanged !== void 0) {
358
+ window.speechSynthesis.onvoiceschanged = null;
359
+ }
360
+ };
361
+ }, [isSupported]);
362
+ const handlePlay = () => {
363
+ if (!isSupported || typeof window === "undefined") return;
364
+ try {
365
+ if (isPlaying) {
366
+ window.speechSynthesis.cancel();
367
+ setIsPlaying(false);
368
+ setCurrentWordIndex(-1);
369
+ return;
370
+ }
371
+ const utterance = new SpeechSynthesisUtterance(text);
372
+ utteranceRef.current = utterance;
373
+ if (bestVoice) {
374
+ utterance.voice = bestVoice;
375
+ }
376
+ utterance.rate = 1;
377
+ utterance.pitch = 1;
378
+ utterance.volume = 1;
379
+ let wordIndex = 0;
380
+ utterance.onboundary = (event) => {
381
+ if (event.name === "word") {
382
+ setCurrentWordIndex(wordIndex);
383
+ wordIndex++;
384
+ }
385
+ };
386
+ utterance.onstart = () => {
387
+ setIsPlaying(true);
388
+ setCurrentWordIndex(0);
389
+ };
390
+ utterance.onend = () => {
391
+ setIsPlaying(false);
392
+ setCurrentWordIndex(-1);
393
+ };
394
+ utterance.onerror = (event) => {
395
+ console.error("Speech error:", event);
396
+ setIsPlaying(false);
397
+ setCurrentWordIndex(-1);
398
+ };
399
+ window.speechSynthesis.speak(utterance);
400
+ } catch (error) {
401
+ console.error("TTS error:", error);
402
+ setIsPlaying(false);
403
+ }
404
+ };
405
+ (0, import_react.useEffect)(() => {
406
+ return () => {
407
+ if (typeof window !== "undefined" && "speechSynthesis" in window) {
408
+ try {
409
+ window.speechSynthesis.cancel();
410
+ } catch (error) {
411
+ }
412
+ }
413
+ };
414
+ }, []);
415
+ const buttonStyle = {
416
+ ...styles.button,
417
+ ...size === "sm" ? styles.buttonSm : styles.buttonMd,
418
+ ...isPlaying ? styles.buttonPlaying : {},
419
+ ...!isSupported ? styles.buttonDisabled : {}
420
+ };
421
+ if (inline) {
422
+ const words = text.split(/\s+/);
423
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.container, children: [
424
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
425
+ "button",
426
+ {
427
+ style: buttonStyle,
428
+ onClick: handlePlay,
429
+ disabled: !isSupported,
430
+ "aria-label": isPlaying ? "Stop reading" : "Read aloud",
431
+ title: isPlaying ? "Stop" : "Read aloud",
432
+ "data-testid": "button-tts",
433
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StopIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VolumeIcon, {})
434
+ }
435
+ ),
436
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.textContainer, children: words.map((word, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
437
+ "span",
438
+ {
439
+ style: index === currentWordIndex ? styles.highlightedWord : void 0,
440
+ children: [
441
+ word,
442
+ index < words.length - 1 ? " " : ""
443
+ ]
444
+ },
445
+ index
446
+ )) })
447
+ ] });
448
+ }
449
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
450
+ "button",
451
+ {
452
+ style: buttonStyle,
453
+ onClick: handlePlay,
454
+ disabled: !isSupported,
455
+ "aria-label": isPlaying ? "Stop reading" : "Read aloud",
456
+ title: isPlaying ? "Stop" : "Read aloud",
457
+ "data-testid": "button-tts",
458
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StopIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VolumeIcon, {})
459
+ }
460
+ );
461
+ }
462
+
463
+ // src/QuestionChatPanel.tsx
464
+ var import_react2 = require("react");
465
+ var import_jsx_runtime2 = require("react/jsx-runtime");
466
+ var panelStyles = {
467
+ container: {
468
+ display: "flex",
469
+ flexDirection: "column",
470
+ height: "100%",
471
+ backgroundColor: "#f8fafc",
472
+ borderRadius: "12px",
473
+ border: "1px solid #e2e8f0",
474
+ overflow: "hidden"
475
+ },
476
+ header: {
477
+ padding: "12px 16px",
478
+ backgroundColor: "#6721b0",
479
+ color: "#ffffff",
480
+ fontWeight: "600",
481
+ fontSize: "14px",
482
+ display: "flex",
483
+ alignItems: "center",
484
+ gap: "8px"
485
+ },
486
+ messagesContainer: {
487
+ flex: 1,
488
+ overflowY: "auto",
489
+ padding: "12px",
490
+ display: "flex",
491
+ flexDirection: "column",
492
+ gap: "8px"
493
+ },
494
+ starterPrompts: {
495
+ display: "flex",
496
+ flexDirection: "column",
497
+ gap: "8px",
498
+ padding: "16px"
499
+ },
500
+ starterButton: {
501
+ padding: "10px 14px",
502
+ borderRadius: "20px",
503
+ border: "1px solid #e2e8f0",
504
+ backgroundColor: "#ffffff",
505
+ color: "#374151",
506
+ fontSize: "13px",
507
+ cursor: "pointer",
508
+ textAlign: "left",
509
+ transition: "all 0.2s ease"
510
+ },
511
+ starterButtonHover: {
512
+ backgroundColor: "#f3e8ff",
513
+ borderColor: "#6721b0"
514
+ },
515
+ messageRow: {
516
+ display: "flex"
517
+ },
518
+ userMessage: {
519
+ maxWidth: "85%",
520
+ marginLeft: "auto",
521
+ padding: "10px 14px",
522
+ borderRadius: "16px 16px 4px 16px",
523
+ backgroundColor: "#6721b0",
524
+ color: "#ffffff",
525
+ fontSize: "14px",
526
+ lineHeight: 1.4
527
+ },
528
+ assistantMessage: {
529
+ maxWidth: "85%",
530
+ marginRight: "auto",
531
+ padding: "10px 14px",
532
+ borderRadius: "16px 16px 16px 4px",
533
+ backgroundColor: "#ffffff",
534
+ color: "#1f2937",
535
+ fontSize: "14px",
536
+ lineHeight: 1.4,
537
+ border: "1px solid #e2e8f0"
538
+ },
539
+ inputContainer: {
540
+ padding: "12px",
541
+ borderTop: "1px solid #e2e8f0",
542
+ backgroundColor: "#ffffff",
543
+ display: "flex",
544
+ gap: "8px"
545
+ },
546
+ input: {
547
+ flex: 1,
548
+ padding: "10px 14px",
549
+ borderRadius: "20px",
550
+ border: "1px solid #e2e8f0",
551
+ fontSize: "14px",
552
+ outline: "none"
553
+ },
554
+ sendButton: {
555
+ width: "40px",
556
+ height: "40px",
557
+ borderRadius: "50%",
558
+ border: "none",
559
+ backgroundColor: "#6721b0",
560
+ color: "#ffffff",
561
+ cursor: "pointer",
562
+ display: "flex",
563
+ alignItems: "center",
564
+ justifyContent: "center",
565
+ fontSize: "16px",
566
+ transition: "all 0.2s ease"
567
+ },
568
+ sendButtonDisabled: {
569
+ backgroundColor: "#d1d5db",
570
+ cursor: "not-allowed"
571
+ },
572
+ loadingDots: {
573
+ display: "flex",
574
+ alignItems: "center",
575
+ gap: "4px",
576
+ padding: "10px 14px",
577
+ maxWidth: "85%",
578
+ marginRight: "auto"
579
+ },
580
+ dot: {
581
+ width: "8px",
582
+ height: "8px",
583
+ backgroundColor: "#9ca3af",
584
+ borderRadius: "50%",
585
+ animation: "bounce 1.4s ease-in-out infinite"
586
+ },
587
+ emptyState: {
588
+ flex: 1,
589
+ display: "flex",
590
+ flexDirection: "column",
591
+ alignItems: "center",
592
+ justifyContent: "center",
593
+ padding: "20px",
594
+ color: "#6b7280",
595
+ textAlign: "center"
596
+ },
597
+ helperIcon: {
598
+ width: "48px",
599
+ height: "48px",
600
+ marginBottom: "12px",
601
+ fontSize: "32px"
602
+ }
603
+ };
604
+ var STARTER_PROMPTS = [
605
+ { id: "dont_understand", label: "I don't understand this question", message: "I don't understand this question. Can you help me?" },
606
+ { id: "word_help", label: "I don't understand a word", message: "I don't understand a word in this question. Can you help?" },
607
+ { id: "explain_again", label: "Explain this again", message: "Can you explain this question to me again in a different way?" }
608
+ ];
609
+ function QuestionChatPanel({
610
+ apiClient,
611
+ question,
612
+ quizId,
613
+ childId,
614
+ parentId,
615
+ lessonId,
616
+ courseId
617
+ }) {
618
+ const [messages, setMessages] = (0, import_react2.useState)([]);
619
+ const [inputValue, setInputValue] = (0, import_react2.useState)("");
620
+ const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
621
+ const [chatId, setChatId] = (0, import_react2.useState)(null);
622
+ const [hoveredButton, setHoveredButton] = (0, import_react2.useState)(null);
623
+ const messagesContainerRef = (0, import_react2.useRef)(null);
624
+ const messagesEndRef = (0, import_react2.useRef)(null);
625
+ const scrollToBottom = (0, import_react2.useCallback)(() => {
626
+ if (messagesContainerRef.current) {
627
+ messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
628
+ }
629
+ }, []);
630
+ (0, import_react2.useEffect)(() => {
631
+ scrollToBottom();
632
+ }, [messages, scrollToBottom]);
633
+ (0, import_react2.useEffect)(() => {
634
+ setMessages([]);
635
+ setChatId(null);
636
+ setInputValue("");
637
+ const loadHistory = async () => {
638
+ try {
639
+ const history = await apiClient.getChatHistory(question.id, childId);
640
+ if (history.chatId && history.messages.length > 0) {
641
+ setChatId(history.chatId);
642
+ setMessages(history.messages);
643
+ }
644
+ } catch (err) {
645
+ console.error("Failed to load chat history:", err);
646
+ }
647
+ };
648
+ loadHistory();
649
+ }, [question.id, childId, apiClient]);
650
+ const initializeChat = async () => {
651
+ if (chatId) return chatId;
652
+ try {
653
+ const session = await apiClient.getOrCreateChatSession({
654
+ questionId: question.id,
655
+ questionContent: question,
656
+ quizId,
657
+ childId,
658
+ parentId,
659
+ lessonId,
660
+ courseId
661
+ });
662
+ setChatId(session.chatId);
663
+ return session.chatId;
664
+ } catch (err) {
665
+ console.error("Failed to create chat session:", err);
666
+ return null;
667
+ }
668
+ };
669
+ const sendMessage = async (messageText) => {
670
+ if (!messageText.trim() || isLoading) return;
671
+ setIsLoading(true);
672
+ const userMsg = {
673
+ role: "user",
674
+ content: messageText,
675
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
676
+ };
677
+ setMessages((prev) => [...prev, userMsg]);
678
+ setInputValue("");
679
+ try {
680
+ const currentChatId = await initializeChat();
681
+ if (!currentChatId) {
682
+ throw new Error("Failed to initialize chat");
683
+ }
684
+ const response = await apiClient.sendChatMessage({
685
+ chatId: currentChatId,
686
+ message: messageText,
687
+ questionContext: question,
688
+ childId
689
+ });
690
+ setMessages((prev) => [...prev, response.assistantMessage]);
691
+ } catch (err) {
692
+ console.error("Failed to send message:", err);
693
+ const errorMsg = {
694
+ role: "assistant",
695
+ content: "Sorry, I'm having trouble right now. Please try again!",
696
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
697
+ };
698
+ setMessages((prev) => [...prev, errorMsg]);
699
+ } finally {
700
+ setIsLoading(false);
701
+ }
702
+ };
703
+ const handleKeyPress = (e) => {
704
+ if (e.key === "Enter" && !e.shiftKey) {
705
+ e.preventDefault();
706
+ sendMessage(inputValue);
707
+ }
708
+ };
709
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyles.container, children: [
710
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `
711
+ @keyframes bounce {
712
+ 0%, 60%, 100% { transform: translateY(0); }
713
+ 30% { transform: translateY(-4px); }
714
+ }
715
+ ` }),
716
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: panelStyles.header, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Need Help?" }) }),
717
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: messagesContainerRef, style: panelStyles.messagesContainer, children: messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyles.emptyState, children: [
718
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: panelStyles.helperIcon, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", stroke: "#6721b0", strokeWidth: "2", children: [
719
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
720
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }),
721
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
722
+ ] }) }),
723
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "14px", fontWeight: "500", marginBottom: "8px" }, children: "Hi! I'm your question helper" }),
724
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "13px", color: "#9ca3af" }, children: "Ask me if you need help understanding this question" }),
725
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...panelStyles.starterPrompts, marginTop: "16px" }, children: STARTER_PROMPTS.map((prompt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
726
+ "button",
727
+ {
728
+ style: {
729
+ ...panelStyles.starterButton,
730
+ ...hoveredButton === prompt.id ? panelStyles.starterButtonHover : {}
731
+ },
732
+ onMouseEnter: () => setHoveredButton(prompt.id),
733
+ onMouseLeave: () => setHoveredButton(null),
734
+ onClick: () => sendMessage(prompt.message),
735
+ disabled: isLoading,
736
+ "data-testid": `button-chat-starter-${prompt.id}`,
737
+ children: prompt.label
738
+ },
739
+ prompt.id
740
+ )) })
741
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
742
+ messages.map((msg, idx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
743
+ "div",
744
+ {
745
+ style: {
746
+ ...panelStyles.messageRow,
747
+ justifyContent: msg.role === "user" ? "flex-end" : "flex-start"
748
+ },
749
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: msg.role === "user" ? panelStyles.userMessage : panelStyles.assistantMessage, children: msg.content })
750
+ },
751
+ idx
752
+ )),
753
+ isLoading && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyles.loadingDots, children: [
754
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...panelStyles.dot, animationDelay: "0s" } }),
755
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...panelStyles.dot, animationDelay: "0.2s" } }),
756
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...panelStyles.dot, animationDelay: "0.4s" } })
757
+ ] }),
758
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: messagesEndRef })
759
+ ] }) }),
760
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyles.inputContainer, children: [
761
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
762
+ "input",
763
+ {
764
+ type: "text",
765
+ value: inputValue,
766
+ onChange: (e) => setInputValue(e.target.value),
767
+ onKeyPress: handleKeyPress,
768
+ placeholder: "Ask about this question...",
769
+ style: panelStyles.input,
770
+ disabled: isLoading,
771
+ "data-testid": "input-chat-message"
772
+ }
773
+ ),
774
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
775
+ "button",
776
+ {
777
+ onClick: () => sendMessage(inputValue),
778
+ disabled: isLoading || !inputValue.trim(),
779
+ style: {
780
+ ...panelStyles.sendButton,
781
+ ...isLoading || !inputValue.trim() ? panelStyles.sendButtonDisabled : {}
782
+ },
783
+ "data-testid": "button-send-chat",
784
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
785
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
786
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
787
+ ] })
788
+ }
789
+ )
790
+ ] })
791
+ ] });
792
+ }
793
+
794
+ // src/QuizPlayer.tsx
795
+ var import_jsx_runtime3 = require("react/jsx-runtime");
177
796
  var defaultStyles = {
178
797
  container: {
179
798
  fontFamily: "system-ui, -apple-system, sans-serif",
180
- maxWidth: "800px",
181
- margin: "0 auto",
799
+ width: "100%",
182
800
  padding: "20px",
183
801
  backgroundColor: "#ffffff",
184
- borderRadius: "12px"
802
+ borderRadius: "12px",
803
+ boxSizing: "border-box"
185
804
  },
186
805
  header: {
187
806
  marginBottom: "20px",
@@ -211,7 +830,8 @@ var defaultStyles = {
211
830
  transition: "width 0.3s ease"
212
831
  },
213
832
  question: {
214
- marginBottom: "24px"
833
+ marginBottom: "24px",
834
+ minHeight: "280px"
215
835
  },
216
836
  questionText: {
217
837
  fontSize: "18px",
@@ -228,7 +848,12 @@ var defaultStyles = {
228
848
  border: "2px solid #e5e7eb",
229
849
  borderRadius: "8px",
230
850
  cursor: "pointer",
231
- transition: "all 0.2s ease"
851
+ transition: "all 0.2s ease",
852
+ outline: "none",
853
+ boxShadow: "none",
854
+ backgroundColor: "#ffffff",
855
+ WebkitTapHighlightColor: "transparent",
856
+ userSelect: "none"
232
857
  },
233
858
  optionSelected: {
234
859
  borderColor: "#6721b0",
@@ -247,7 +872,10 @@ var defaultStyles = {
247
872
  padding: "12px 16px",
248
873
  border: "2px solid #e5e7eb",
249
874
  borderRadius: "8px",
250
- fontSize: "16px"
875
+ fontSize: "16px",
876
+ outline: "none",
877
+ boxSizing: "border-box",
878
+ backgroundColor: "#ffffff"
251
879
  },
252
880
  buttons: {
253
881
  display: "flex",
@@ -276,24 +904,103 @@ var defaultStyles = {
276
904
  color: "#9ca3af",
277
905
  cursor: "not-allowed"
278
906
  },
907
+ buttonAddMore: {
908
+ padding: "12px 24px",
909
+ borderRadius: "8px",
910
+ fontSize: "16px",
911
+ fontWeight: "500",
912
+ cursor: "pointer",
913
+ border: "2px solid #6721b0",
914
+ backgroundColor: "transparent",
915
+ color: "#6721b0",
916
+ transition: "all 0.2s ease",
917
+ display: "flex",
918
+ alignItems: "center",
919
+ justifyContent: "center",
920
+ gap: "8px"
921
+ },
922
+ buttonAddMoreDisabled: {
923
+ border: "2px solid #e5e7eb",
924
+ color: "#9ca3af",
925
+ cursor: "not-allowed"
926
+ },
927
+ buttonsColumn: {
928
+ display: "flex",
929
+ flexDirection: "column",
930
+ gap: "12px",
931
+ marginTop: "24px"
932
+ },
933
+ mainLayout: {
934
+ display: "flex",
935
+ gap: "24px"
936
+ },
937
+ quizContent: {
938
+ flex: 1,
939
+ minWidth: 0
940
+ },
941
+ chatPanel: {
942
+ width: "320px",
943
+ flexShrink: 0,
944
+ height: "460px"
945
+ },
279
946
  timer: {
280
947
  fontSize: "14px",
281
948
  color: "#6b7280",
282
949
  marginLeft: "16px"
283
950
  },
284
951
  results: {
952
+ display: "flex",
953
+ flexDirection: "column",
954
+ alignItems: "center",
955
+ justifyContent: "center",
956
+ padding: "48px 24px",
285
957
  textAlign: "center",
286
- padding: "40px 20px"
958
+ minHeight: "400px",
959
+ position: "relative",
960
+ overflow: "hidden"
287
961
  },
288
- resultScore: {
289
- fontSize: "48px",
290
- fontWeight: "700",
291
- color: "#6721b0",
292
- marginBottom: "8px"
962
+ resultsBackground: {
963
+ position: "absolute",
964
+ inset: "0",
965
+ borderRadius: "16px",
966
+ zIndex: 0
967
+ },
968
+ resultsContent: {
969
+ position: "relative",
970
+ zIndex: 1,
971
+ display: "flex",
972
+ flexDirection: "column",
973
+ alignItems: "center"
293
974
  },
294
- resultLabel: {
975
+ resultDetails: {
295
976
  fontSize: "18px",
296
- color: "#6b7280"
977
+ color: "#6b7280",
978
+ marginBottom: "4px"
979
+ },
980
+ resultStars: {
981
+ display: "flex",
982
+ justifyContent: "center",
983
+ gap: "8px",
984
+ marginBottom: "20px"
985
+ },
986
+ resultStar: {
987
+ fontSize: "32px",
988
+ animation: "starPop 0.5s ease-out forwards",
989
+ opacity: 0
990
+ },
991
+ confettiContainer: {
992
+ position: "absolute",
993
+ inset: "0",
994
+ pointerEvents: "none",
995
+ overflow: "hidden",
996
+ zIndex: 0
997
+ },
998
+ confettiPiece: {
999
+ position: "absolute",
1000
+ width: "10px",
1001
+ height: "10px",
1002
+ top: "-10px",
1003
+ animation: "confettiFall 3s ease-out forwards"
297
1004
  },
298
1005
  loading: {
299
1006
  textAlign: "center",
@@ -304,10 +1011,84 @@ var defaultStyles = {
304
1011
  textAlign: "center",
305
1012
  padding: "40px",
306
1013
  color: "#ef4444"
1014
+ },
1015
+ intro: {
1016
+ display: "flex",
1017
+ flexDirection: "column",
1018
+ alignItems: "center",
1019
+ justifyContent: "center",
1020
+ padding: "48px 24px",
1021
+ textAlign: "center",
1022
+ background: "linear-gradient(135deg, #f3e8ff 0%, #e0e7ff 50%, #fce7f3 100%)",
1023
+ borderRadius: "16px",
1024
+ minHeight: "320px"
1025
+ },
1026
+ introTitle: {
1027
+ fontSize: "28px",
1028
+ fontWeight: "700",
1029
+ color: "#4c1d95",
1030
+ marginBottom: "12px"
1031
+ },
1032
+ introSubtitle: {
1033
+ fontSize: "16px",
1034
+ color: "#6b7280",
1035
+ marginBottom: "8px"
1036
+ },
1037
+ introQuestionCount: {
1038
+ fontSize: "14px",
1039
+ color: "#8b5cf6",
1040
+ marginBottom: "32px",
1041
+ fontWeight: "500"
1042
+ },
1043
+ startButton: {
1044
+ padding: "16px 48px",
1045
+ fontSize: "18px",
1046
+ fontWeight: "600",
1047
+ backgroundColor: "#7c3aed",
1048
+ color: "#ffffff",
1049
+ border: "none",
1050
+ borderRadius: "12px",
1051
+ cursor: "pointer",
1052
+ transition: "all 0.2s ease",
1053
+ boxShadow: "0 4px 14px rgba(124, 58, 237, 0.4)"
1054
+ },
1055
+ feedback: {
1056
+ marginTop: "16px",
1057
+ padding: "16px",
1058
+ borderRadius: "8px",
1059
+ backgroundColor: "#f9fafb",
1060
+ border: "1px solid #e5e7eb"
1061
+ },
1062
+ feedbackCorrect: {
1063
+ backgroundColor: "#f0fdf4",
1064
+ borderColor: "#22c55e"
1065
+ },
1066
+ feedbackIncorrect: {
1067
+ backgroundColor: "#fef2f2",
1068
+ borderColor: "#ef4444"
1069
+ },
1070
+ feedbackTitle: {
1071
+ fontSize: "16px",
1072
+ fontWeight: "600",
1073
+ marginBottom: "8px",
1074
+ display: "flex",
1075
+ alignItems: "center",
1076
+ gap: "8px"
1077
+ },
1078
+ feedbackTitleCorrect: {
1079
+ color: "#16a34a"
1080
+ },
1081
+ feedbackTitleIncorrect: {
1082
+ color: "#dc2626"
1083
+ },
1084
+ feedbackExplanation: {
1085
+ fontSize: "14px",
1086
+ color: "#4b5563",
1087
+ lineHeight: "1.5"
307
1088
  }
308
1089
  };
309
1090
  function Spinner({ size = 16, color = "#ffffff" }) {
310
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1091
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
311
1092
  "span",
312
1093
  {
313
1094
  style: {
@@ -319,7 +1100,7 @@ function Spinner({ size = 16, color = "#ffffff" }) {
319
1100
  borderRadius: "50%",
320
1101
  animation: "spin 0.8s linear infinite"
321
1102
  },
322
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
1103
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
323
1104
  }
324
1105
  );
325
1106
  }
@@ -335,27 +1116,43 @@ function QuizPlayer({
335
1116
  onComplete,
336
1117
  onError,
337
1118
  onProgress,
338
- className
1119
+ onGenerateMoreQuestions,
1120
+ className,
1121
+ forceNewAttempt = true
339
1122
  }) {
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)(() => {
1123
+ const [quiz, setQuiz] = (0, import_react3.useState)(null);
1124
+ const [attempt, setAttempt] = (0, import_react3.useState)(null);
1125
+ const [currentQuestionIndex, setCurrentQuestionIndex] = (0, import_react3.useState)(0);
1126
+ const [answers, setAnswers] = (0, import_react3.useState)(/* @__PURE__ */ new Map());
1127
+ const [answersDetail, setAnswersDetail] = (0, import_react3.useState)([]);
1128
+ const [isSubmitting, setIsSubmitting] = (0, import_react3.useState)(false);
1129
+ const [isNavigating, setIsNavigating] = (0, import_react3.useState)(false);
1130
+ const [isCompleted, setIsCompleted] = (0, import_react3.useState)(false);
1131
+ const [result, setResult] = (0, import_react3.useState)(null);
1132
+ const [error, setError] = (0, import_react3.useState)(null);
1133
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(true);
1134
+ const [elapsedSeconds, setElapsedSeconds] = (0, import_react3.useState)(0);
1135
+ const [showIntro, setShowIntro] = (0, import_react3.useState)(true);
1136
+ const [timerStarted, setTimerStarted] = (0, import_react3.useState)(false);
1137
+ const [showFeedback, setShowFeedback] = (0, import_react3.useState)(false);
1138
+ const [currentAnswerDetail, setCurrentAnswerDetail] = (0, import_react3.useState)(null);
1139
+ const [extraQuestions, setExtraQuestions] = (0, import_react3.useState)([]);
1140
+ const [isGeneratingExtra, setIsGeneratingExtra] = (0, import_react3.useState)(false);
1141
+ const [showSkipModal, setShowSkipModal] = (0, import_react3.useState)(false);
1142
+ const [skippedQuestionIds, setSkippedQuestionIds] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
1143
+ const [isSkipping, setIsSkipping] = (0, import_react3.useState)(false);
1144
+ const [skipComment, setSkipComment] = (0, import_react3.useState)("");
1145
+ const [selectedSkipReason, setSelectedSkipReason] = (0, import_react3.useState)(null);
1146
+ const [showReportModal, setShowReportModal] = (0, import_react3.useState)(false);
1147
+ const [isReporting, setIsReporting] = (0, import_react3.useState)(false);
1148
+ const [reportComment, setReportComment] = (0, import_react3.useState)("");
1149
+ const apiClient = (0, import_react3.useRef)(null);
1150
+ const timerRef = (0, import_react3.useRef)(null);
1151
+ const startTimeRef = (0, import_react3.useRef)(0);
1152
+ (0, import_react3.useEffect)(() => {
356
1153
  apiClient.current = new QuizApiClient({ baseUrl: apiBaseUrl, authToken });
357
1154
  }, [apiBaseUrl, authToken]);
358
- (0, import_react.useEffect)(() => {
1155
+ (0, import_react3.useEffect)(() => {
359
1156
  async function initialize() {
360
1157
  if (!apiClient.current) return;
361
1158
  try {
@@ -369,10 +1166,11 @@ function QuizPlayer({
369
1166
  assignLessonId,
370
1167
  courseId,
371
1168
  childId,
372
- parentId
1169
+ parentId,
1170
+ forceNew: forceNewAttempt
373
1171
  });
374
1172
  setAttempt(attemptData);
375
- if (attemptData.answers && attemptData.answers.length > 0) {
1173
+ if (!forceNewAttempt && attemptData.answers && attemptData.answers.length > 0) {
376
1174
  setAnswersDetail(attemptData.answers);
377
1175
  const answersMap = /* @__PURE__ */ new Map();
378
1176
  attemptData.answers.forEach((a) => {
@@ -401,9 +1199,9 @@ function QuizPlayer({
401
1199
  }
402
1200
  }
403
1201
  initialize();
404
- }, [quizId, lessonId, assignLessonId, courseId, childId, parentId, onError]);
405
- (0, import_react.useEffect)(() => {
406
- if (!isLoading && !isCompleted && !error) {
1202
+ }, [quizId, lessonId, assignLessonId, courseId, childId, parentId, forceNewAttempt, onError]);
1203
+ (0, import_react3.useEffect)(() => {
1204
+ if (timerStarted && !isCompleted && !error) {
407
1205
  startTimeRef.current = Date.now();
408
1206
  timerRef.current = setInterval(() => {
409
1207
  setElapsedSeconds(Math.floor((Date.now() - startTimeRef.current) / 1e3));
@@ -414,27 +1212,39 @@ function QuizPlayer({
414
1212
  clearInterval(timerRef.current);
415
1213
  }
416
1214
  };
417
- }, [isLoading, isCompleted, error]);
418
- (0, import_react.useEffect)(() => {
1215
+ }, [timerStarted, isCompleted, error]);
1216
+ const handleStart = (0, import_react3.useCallback)(() => {
1217
+ setShowIntro(false);
1218
+ setTimerStarted(true);
1219
+ }, []);
1220
+ (0, import_react3.useEffect)(() => {
1221
+ setShowFeedback(false);
1222
+ setCurrentAnswerDetail(null);
1223
+ }, [currentQuestionIndex]);
1224
+ const allQuestions = quiz ? [...quiz.questions, ...extraQuestions].filter((q) => !skippedQuestionIds.has(q.id)) : [];
1225
+ const totalQuestions = allQuestions.length;
1226
+ const maxQuestions = 50;
1227
+ (0, import_react3.useEffect)(() => {
419
1228
  if (quiz && onProgress) {
420
1229
  onProgress({
421
1230
  currentQuestion: currentQuestionIndex + 1,
422
- totalQuestions: quiz.questions.length,
1231
+ totalQuestions,
423
1232
  answeredQuestions: answers.size
424
1233
  });
425
1234
  }
426
- }, [currentQuestionIndex, answers.size, quiz, onProgress]);
427
- const currentQuestion = quiz?.questions[currentQuestionIndex];
428
- const handleAnswerChange = (0, import_react.useCallback)((value) => {
1235
+ }, [currentQuestionIndex, answers.size, quiz, onProgress, totalQuestions]);
1236
+ const currentQuestion = allQuestions[currentQuestionIndex];
1237
+ const handleAnswerChange = (0, import_react3.useCallback)((value) => {
429
1238
  if (!currentQuestion) return;
430
1239
  setAnswers((prev) => new Map(prev).set(currentQuestion.id, value));
431
1240
  }, [currentQuestion]);
432
- const handleNext = (0, import_react.useCallback)(async () => {
1241
+ const handleCheckAnswer = (0, import_react3.useCallback)(async () => {
433
1242
  if (!quiz || !attempt || !currentQuestion || !apiClient.current) return;
434
1243
  const selectedAnswer2 = answers.get(currentQuestion.id);
435
1244
  if (selectedAnswer2 === void 0) return;
436
1245
  setIsNavigating(true);
437
1246
  const answerDetail = createAnswerDetail(currentQuestion, selectedAnswer2);
1247
+ setCurrentAnswerDetail(answerDetail);
438
1248
  const newAnswersDetail = [...answersDetail];
439
1249
  const existingIdx = newAnswersDetail.findIndex((a) => a.questionId === currentQuestion.id);
440
1250
  if (existingIdx >= 0) {
@@ -452,16 +1262,40 @@ function QuizPlayer({
452
1262
  } finally {
453
1263
  setIsNavigating(false);
454
1264
  }
455
- if (currentQuestionIndex < quiz.questions.length - 1) {
1265
+ setShowFeedback(true);
1266
+ }, [quiz, attempt, currentQuestion, answers, answersDetail]);
1267
+ const handleContinue = (0, import_react3.useCallback)(() => {
1268
+ if (!quiz) return;
1269
+ setShowFeedback(false);
1270
+ setCurrentAnswerDetail(null);
1271
+ if (currentQuestionIndex < totalQuestions - 1) {
456
1272
  setCurrentQuestionIndex((prev) => prev + 1);
457
1273
  }
458
- }, [quiz, attempt, currentQuestion, answers, answersDetail, currentQuestionIndex]);
459
- const handlePrevious = (0, import_react.useCallback)(() => {
460
- if (currentQuestionIndex > 0) {
461
- setCurrentQuestionIndex((prev) => prev - 1);
1274
+ }, [quiz, currentQuestionIndex, totalQuestions]);
1275
+ const handleAddMoreQuestions = (0, import_react3.useCallback)(async () => {
1276
+ if (!attempt || !onGenerateMoreQuestions || isGeneratingExtra) return;
1277
+ if (totalQuestions >= maxQuestions) return;
1278
+ setIsGeneratingExtra(true);
1279
+ try {
1280
+ const result2 = await onGenerateMoreQuestions(attempt.id, totalQuestions);
1281
+ if (result2.extraQuestions && result2.extraQuestions.length > 0) {
1282
+ const slotsAvailable = maxQuestions - totalQuestions;
1283
+ const questionsToAppend = result2.extraQuestions.slice(0, slotsAvailable);
1284
+ if (questionsToAppend.length > 0) {
1285
+ setExtraQuestions((prev) => [...prev, ...questionsToAppend]);
1286
+ setCurrentQuestionIndex(totalQuestions);
1287
+ setShowFeedback(false);
1288
+ setCurrentAnswerDetail(null);
1289
+ }
1290
+ }
1291
+ } catch (err) {
1292
+ console.error("Failed to generate extra questions:", err);
1293
+ onError?.(err instanceof Error ? err : new Error("Failed to generate extra questions"));
1294
+ } finally {
1295
+ setIsGeneratingExtra(false);
462
1296
  }
463
- }, [currentQuestionIndex]);
464
- const handleSubmit = (0, import_react.useCallback)(async () => {
1297
+ }, [attempt, onGenerateMoreQuestions, isGeneratingExtra, totalQuestions, maxQuestions, onError]);
1298
+ const handleSubmit = (0, import_react3.useCallback)(async () => {
465
1299
  if (!quiz || !attempt || !apiClient.current) return;
466
1300
  setIsSubmitting(true);
467
1301
  try {
@@ -477,12 +1311,13 @@ function QuizPlayer({
477
1311
  }
478
1312
  }
479
1313
  const scoreData = calculateScore(finalAnswersDetail);
480
- const timeSpent = Math.floor((Date.now() - startTimeRef.current) / 1e3);
1314
+ const timeSpent = timerStarted && startTimeRef.current > 0 ? Math.floor((Date.now() - startTimeRef.current) / 1e3) : elapsedSeconds;
481
1315
  const updatedAttempt = await apiClient.current.updateAttempt(attempt.id, {
482
1316
  answers: finalAnswersDetail,
483
1317
  status: "completed",
484
1318
  score: scoreData.score,
485
1319
  correctAnswers: scoreData.correctAnswers,
1320
+ totalQuestions,
486
1321
  timeSpentSeconds: timeSpent
487
1322
  });
488
1323
  setIsCompleted(true);
@@ -490,7 +1325,7 @@ function QuizPlayer({
490
1325
  attemptId: updatedAttempt.id,
491
1326
  score: scoreData.score,
492
1327
  correctAnswers: scoreData.correctAnswers,
493
- totalQuestions: quiz.questions.length,
1328
+ totalQuestions,
494
1329
  answers: finalAnswersDetail,
495
1330
  timeSpentSeconds: timeSpent
496
1331
  };
@@ -506,157 +1341,956 @@ function QuizPlayer({
506
1341
  } finally {
507
1342
  setIsSubmitting(false);
508
1343
  }
509
- }, [quiz, attempt, currentQuestion, answers, answersDetail, onComplete, onError]);
1344
+ }, [quiz, attempt, currentQuestion, answers, answersDetail, onComplete, onError, totalQuestions, timerStarted, elapsedSeconds]);
1345
+ const isExtraQuestion = currentQuestion && extraQuestions.some((q) => q.id === currentQuestion.id);
1346
+ const handleSkipQuestion = (0, import_react3.useCallback)(async (reason, comment) => {
1347
+ if (!currentQuestion || !apiClient.current || !attempt) return;
1348
+ setIsSkipping(true);
1349
+ try {
1350
+ await apiClient.current.skipQuestion({
1351
+ questionId: currentQuestion.id,
1352
+ questionContent: currentQuestion,
1353
+ skipReason: reason,
1354
+ skipComment: comment?.trim() || void 0,
1355
+ quizId: quiz?.id,
1356
+ attemptId: attempt.id,
1357
+ childId,
1358
+ parentId,
1359
+ lessonId,
1360
+ courseId,
1361
+ assignLessonId
1362
+ });
1363
+ const newSkippedIds = new Set(skippedQuestionIds).add(currentQuestion.id);
1364
+ setSkippedQuestionIds(newSkippedIds);
1365
+ const newExtraQuestions = extraQuestions.filter((q) => q.id !== currentQuestion.id);
1366
+ setExtraQuestions(newExtraQuestions);
1367
+ const newAllQuestions = quiz ? [...quiz.questions, ...newExtraQuestions].filter((q) => !newSkippedIds.has(q.id)) : [];
1368
+ const newTotalQuestions = newAllQuestions.length;
1369
+ if (newTotalQuestions === 0) {
1370
+ setIsCompleted(true);
1371
+ const quizResult = {
1372
+ attemptId: attempt.id,
1373
+ score: 0,
1374
+ correctAnswers: 0,
1375
+ totalQuestions: 0,
1376
+ answers: [],
1377
+ timeSpentSeconds: elapsedSeconds
1378
+ };
1379
+ setResult(quizResult);
1380
+ onComplete?.(quizResult);
1381
+ } else if (currentQuestionIndex >= newTotalQuestions) {
1382
+ setCurrentQuestionIndex(newTotalQuestions - 1);
1383
+ }
1384
+ setShowSkipModal(false);
1385
+ setSkipComment("");
1386
+ setSelectedSkipReason(null);
1387
+ } catch (err) {
1388
+ console.error("Failed to skip question:", err);
1389
+ } finally {
1390
+ setIsSkipping(false);
1391
+ }
1392
+ }, [currentQuestion, apiClient, attempt, quiz, childId, parentId, lessonId, courseId, assignLessonId, skippedQuestionIds, extraQuestions, currentQuestionIndex, elapsedSeconds, onComplete]);
1393
+ const handleReportQuestion = (0, import_react3.useCallback)(async (comment) => {
1394
+ if (!currentQuestion || !apiClient.current || !attempt || !comment.trim()) return;
1395
+ setIsReporting(true);
1396
+ try {
1397
+ await apiClient.current.reportQuestion({
1398
+ questionId: currentQuestion.id,
1399
+ questionContent: currentQuestion,
1400
+ reportComment: comment.trim(),
1401
+ quizId: quiz?.id,
1402
+ attemptId: attempt.id,
1403
+ childId,
1404
+ parentId,
1405
+ lessonId,
1406
+ courseId,
1407
+ assignLessonId
1408
+ });
1409
+ setShowReportModal(false);
1410
+ setReportComment("");
1411
+ } catch (err) {
1412
+ console.error("Failed to report question:", err);
1413
+ } finally {
1414
+ setIsReporting(false);
1415
+ }
1416
+ }, [currentQuestion, apiClient, attempt, quiz, childId, parentId, lessonId, courseId, assignLessonId]);
510
1417
  if (isLoading) {
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..." }) });
1418
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.loading, children: "Loading quiz..." }) });
512
1419
  }
513
1420
  if (error) {
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: [
1421
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.error, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { children: [
515
1422
  "Error: ",
516
1423
  error
517
1424
  ] }) }) });
518
1425
  }
519
1426
  if (isCompleted && result) {
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: [
522
- result.score,
523
- "%"
524
- ] }),
525
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.resultLabel, children: [
526
- result.correctAnswers,
527
- " of ",
528
- result.totalQuestions,
529
- " correct"
530
- ] }),
531
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...defaultStyles.resultLabel, marginTop: "8px" }, children: [
532
- "Time: ",
533
- formatTime(result.timeSpentSeconds)
1427
+ const percentage = result.totalQuestions > 0 ? Math.round(result.correctAnswers / result.totalQuestions * 100) : 0;
1428
+ const getScoreTheme = (pct) => {
1429
+ if (pct >= 80) {
1430
+ return {
1431
+ color: "#22c55e",
1432
+ bgGradient: "linear-gradient(135deg, #dcfce7 0%, #bbf7d0 50%, #86efac 100%)",
1433
+ badge: "Quiz Champion!",
1434
+ badgeColor: "#fbbf24",
1435
+ badgeBg: "linear-gradient(135deg, #fef3c7 0%, #fde68a 100%)",
1436
+ mascotMood: "celebrating",
1437
+ stars: 3
1438
+ };
1439
+ } else if (pct >= 60) {
1440
+ return {
1441
+ color: "#f59e0b",
1442
+ bgGradient: "linear-gradient(135deg, #fef3c7 0%, #fde68a 50%, #fcd34d 100%)",
1443
+ badge: "Rising Star!",
1444
+ badgeColor: "#f59e0b",
1445
+ badgeBg: "linear-gradient(135deg, #fed7aa 0%, #fdba74 100%)",
1446
+ mascotMood: "happy",
1447
+ stars: 2
1448
+ };
1449
+ } else if (pct >= 40) {
1450
+ return {
1451
+ color: "#3b82f6",
1452
+ bgGradient: "linear-gradient(135deg, #dbeafe 0%, #bfdbfe 50%, #93c5fd 100%)",
1453
+ badge: "Great Learner!",
1454
+ badgeColor: "#3b82f6",
1455
+ badgeBg: "linear-gradient(135deg, #bfdbfe 0%, #93c5fd 100%)",
1456
+ mascotMood: "encouraging",
1457
+ stars: 1
1458
+ };
1459
+ } else {
1460
+ return {
1461
+ color: "#8b5cf6",
1462
+ bgGradient: "linear-gradient(135deg, #f3e8ff 0%, #e9d5ff 50%, #d8b4fe 100%)",
1463
+ badge: "Keep Growing!",
1464
+ badgeColor: "#8b5cf6",
1465
+ badgeBg: "linear-gradient(135deg, #e9d5ff 0%, #d8b4fe 100%)",
1466
+ mascotMood: "supportive",
1467
+ stars: 0
1468
+ };
1469
+ }
1470
+ };
1471
+ const theme = getScoreTheme(percentage);
1472
+ const confettiColors = ["#f43f5e", "#ec4899", "#8b5cf6", "#3b82f6", "#22c55e", "#f59e0b", "#ef4444"];
1473
+ const confettiPieces = Array.from({ length: 50 }, (_, i) => ({
1474
+ id: i,
1475
+ left: `${Math.random() * 100}%`,
1476
+ delay: `${Math.random() * 2}s`,
1477
+ duration: `${2 + Math.random() * 2}s`,
1478
+ color: confettiColors[Math.floor(Math.random() * confettiColors.length)],
1479
+ rotation: Math.random() * 360,
1480
+ size: 6 + Math.random() * 8
1481
+ }));
1482
+ const StarIcon = ({ filled, delay }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1483
+ "svg",
1484
+ {
1485
+ width: "36",
1486
+ height: "36",
1487
+ viewBox: "0 0 24 24",
1488
+ style: {
1489
+ animation: "starPop 0.5s ease-out forwards",
1490
+ animationDelay: `${delay}s`,
1491
+ opacity: 0
1492
+ },
1493
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1494
+ "path",
1495
+ {
1496
+ d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z",
1497
+ fill: filled ? "#fbbf24" : "#e5e7eb",
1498
+ stroke: filled ? "#f59e0b" : "#d1d5db",
1499
+ strokeWidth: "1"
1500
+ }
1501
+ )
1502
+ }
1503
+ );
1504
+ const MascotOwl = ({ mood }) => {
1505
+ const getEyeExpression = () => {
1506
+ switch (mood) {
1507
+ case "celebrating":
1508
+ return { leftEye: ">", rightEye: "<", pupilY: 42 };
1509
+ // Squinting happy
1510
+ case "happy":
1511
+ return { leftEye: null, rightEye: null, pupilY: 42 };
1512
+ // Normal happy
1513
+ case "encouraging":
1514
+ return { leftEye: null, rightEye: null, pupilY: 44 };
1515
+ // Looking down warmly
1516
+ default:
1517
+ return { leftEye: null, rightEye: null, pupilY: 42 };
1518
+ }
1519
+ };
1520
+ const eyeExpr = getEyeExpression();
1521
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1522
+ "svg",
1523
+ {
1524
+ width: "120",
1525
+ height: "120",
1526
+ viewBox: "0 0 100 100",
1527
+ style: {
1528
+ animation: mood === "celebrating" ? "bounce 0.6s ease-in-out infinite" : "gentleBob 2s ease-in-out infinite"
1529
+ },
1530
+ children: [
1531
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "50", cy: "60", rx: "35", ry: "30", fill: "#8b5cf6" }),
1532
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "50", cy: "65", rx: "25", ry: "20", fill: "#c4b5fd" }),
1533
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "50", cy: "35", r: "28", fill: "#a78bfa" }),
1534
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "28,15 35,30 22,28", fill: "#7c3aed" }),
1535
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "72,15 65,30 78,28", fill: "#7c3aed" }),
1536
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "38", cy: "38", rx: "10", ry: "12", fill: "white" }),
1537
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "62", cy: "38", rx: "10", ry: "12", fill: "white" }),
1538
+ eyeExpr.leftEye ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: "38", y: "44", textAnchor: "middle", fontSize: "16", fill: "#1f2937", children: eyeExpr.leftEye }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "38", cy: eyeExpr.pupilY, r: "5", fill: "#1f2937" }),
1539
+ eyeExpr.rightEye ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: "62", y: "44", textAnchor: "middle", fontSize: "16", fill: "#1f2937", children: eyeExpr.rightEye }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "62", cy: eyeExpr.pupilY, r: "5", fill: "#1f2937" }),
1540
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "50,45 45,52 55,52", fill: "#fbbf24" }),
1541
+ (mood === "celebrating" || mood === "happy") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1542
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "28", cy: "45", rx: "5", ry: "3", fill: "#fda4af", opacity: "0.6" }),
1543
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "72", cy: "45", rx: "5", ry: "3", fill: "#fda4af", opacity: "0.6" })
1544
+ ] }),
1545
+ mood === "celebrating" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1546
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "18", cy: "55", rx: "8", ry: "15", fill: "#7c3aed", transform: "rotate(-30 18 55)" }),
1547
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "82", cy: "55", rx: "8", ry: "15", fill: "#7c3aed", transform: "rotate(30 82 55)" })
1548
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1549
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "20", cy: "60", rx: "8", ry: "15", fill: "#7c3aed" }),
1550
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "80", cy: "60", rx: "8", ry: "15", fill: "#7c3aed" })
1551
+ ] }),
1552
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "40", cy: "88", rx: "8", ry: "4", fill: "#fbbf24" }),
1553
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "60", cy: "88", rx: "8", ry: "4", fill: "#fbbf24" })
1554
+ ]
1555
+ }
1556
+ );
1557
+ };
1558
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, style: defaultStyles.container, children: [
1559
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: `
1560
+ @keyframes confettiFall {
1561
+ 0% {
1562
+ transform: translateY(-10px) rotate(0deg);
1563
+ opacity: 1;
1564
+ }
1565
+ 100% {
1566
+ transform: translateY(500px) rotate(720deg);
1567
+ opacity: 0;
1568
+ }
1569
+ }
1570
+ @keyframes starPop {
1571
+ 0% {
1572
+ transform: scale(0);
1573
+ opacity: 0;
1574
+ }
1575
+ 50% {
1576
+ transform: scale(1.3);
1577
+ }
1578
+ 100% {
1579
+ transform: scale(1);
1580
+ opacity: 1;
1581
+ }
1582
+ }
1583
+ @keyframes bounce {
1584
+ 0%, 100% {
1585
+ transform: translateY(0);
1586
+ }
1587
+ 50% {
1588
+ transform: translateY(-10px);
1589
+ }
1590
+ }
1591
+ @keyframes gentleBob {
1592
+ 0%, 100% {
1593
+ transform: translateY(0);
1594
+ }
1595
+ 50% {
1596
+ transform: translateY(-5px);
1597
+ }
1598
+ }
1599
+ @keyframes badgePop {
1600
+ 0% {
1601
+ transform: scale(0) rotate(-10deg);
1602
+ opacity: 0;
1603
+ }
1604
+ 60% {
1605
+ transform: scale(1.1) rotate(5deg);
1606
+ }
1607
+ 100% {
1608
+ transform: scale(1) rotate(0deg);
1609
+ opacity: 1;
1610
+ }
1611
+ }
1612
+ @keyframes scoreSlideIn {
1613
+ 0% {
1614
+ transform: translateY(20px);
1615
+ opacity: 0;
1616
+ }
1617
+ 100% {
1618
+ transform: translateY(0);
1619
+ opacity: 1;
1620
+ }
1621
+ }
1622
+ ` }),
1623
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.results, children: [
1624
+ percentage >= 60 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.confettiContainer, children: confettiPieces.map((piece) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1625
+ "div",
1626
+ {
1627
+ style: {
1628
+ ...defaultStyles.confettiPiece,
1629
+ left: piece.left,
1630
+ width: `${piece.size}px`,
1631
+ height: `${piece.size}px`,
1632
+ backgroundColor: piece.color,
1633
+ borderRadius: Math.random() > 0.5 ? "50%" : "2px",
1634
+ animationDelay: piece.delay,
1635
+ animationDuration: piece.duration,
1636
+ transform: `rotate(${piece.rotation}deg)`
1637
+ }
1638
+ },
1639
+ piece.id
1640
+ )) }),
1641
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { ...defaultStyles.resultsBackground, background: theme.bgGradient } }),
1642
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.resultsContent, children: [
1643
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.resultStars, children: [
1644
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StarIcon, { filled: theme.stars >= 1, delay: 0.3 }),
1645
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StarIcon, { filled: theme.stars >= 2, delay: 0.5 }),
1646
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StarIcon, { filled: theme.stars >= 3, delay: 0.7 })
1647
+ ] }),
1648
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { marginBottom: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MascotOwl, { mood: theme.mascotMood }) }),
1649
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1650
+ "div",
1651
+ {
1652
+ style: {
1653
+ background: theme.badgeBg,
1654
+ padding: "12px 28px",
1655
+ borderRadius: "50px",
1656
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
1657
+ marginBottom: "20px",
1658
+ animation: "badgePop 0.6s ease-out 0.2s forwards",
1659
+ opacity: 0,
1660
+ border: `3px solid ${theme.badgeColor}`
1661
+ },
1662
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1663
+ "span",
1664
+ {
1665
+ style: {
1666
+ fontSize: "22px",
1667
+ fontWeight: "700",
1668
+ color: "#1f2937",
1669
+ textShadow: "0 1px 2px rgba(255,255,255,0.5)"
1670
+ },
1671
+ children: theme.badge
1672
+ }
1673
+ )
1674
+ }
1675
+ ),
1676
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1677
+ "div",
1678
+ {
1679
+ style: {
1680
+ animation: "scoreSlideIn 0.5s ease-out 0.4s forwards",
1681
+ opacity: 0
1682
+ },
1683
+ children: [
1684
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1685
+ "div",
1686
+ {
1687
+ style: {
1688
+ fontSize: "48px",
1689
+ fontWeight: "800",
1690
+ color: theme.color,
1691
+ lineHeight: "1",
1692
+ marginBottom: "4px"
1693
+ },
1694
+ children: [
1695
+ result.correctAnswers,
1696
+ " of ",
1697
+ result.totalQuestions
1698
+ ]
1699
+ }
1700
+ ),
1701
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1702
+ "div",
1703
+ {
1704
+ style: {
1705
+ fontSize: "20px",
1706
+ fontWeight: "600",
1707
+ color: "#6b7280",
1708
+ marginBottom: "12px"
1709
+ },
1710
+ children: "correct answers"
1711
+ }
1712
+ )
1713
+ ]
1714
+ }
1715
+ ),
1716
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { ...defaultStyles.resultDetails, marginTop: "8px" }, children: [
1717
+ "Time: ",
1718
+ formatTime(result.timeSpentSeconds)
1719
+ ] })
1720
+ ] })
534
1721
  ] })
1722
+ ] });
1723
+ }
1724
+ if (quiz && showIntro) {
1725
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.intro, children: [
1726
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.introTitle, children: quiz.title }),
1727
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.introSubtitle, children: "Ready to test your knowledge?" }),
1728
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.introQuestionCount, children: [
1729
+ quiz.questions.length,
1730
+ " question",
1731
+ quiz.questions.length !== 1 ? "s" : "",
1732
+ " to answer"
1733
+ ] }),
1734
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1735
+ "button",
1736
+ {
1737
+ style: defaultStyles.startButton,
1738
+ onClick: handleStart,
1739
+ onMouseOver: (e) => {
1740
+ e.currentTarget.style.transform = "translateY(-2px)";
1741
+ e.currentTarget.style.boxShadow = "0 6px 20px rgba(124, 58, 237, 0.5)";
1742
+ },
1743
+ onMouseOut: (e) => {
1744
+ e.currentTarget.style.transform = "translateY(0)";
1745
+ e.currentTarget.style.boxShadow = "0 4px 14px rgba(124, 58, 237, 0.4)";
1746
+ },
1747
+ "data-testid": "button-start-quiz",
1748
+ children: "Let's Start!"
1749
+ }
1750
+ )
535
1751
  ] }) });
536
1752
  }
537
1753
  if (!quiz || !currentQuestion) {
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" }) });
1754
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.error, children: "No quiz data available" }) });
539
1755
  }
540
1756
  const selectedAnswer = answers.get(currentQuestion.id);
541
- const isLastQuestion = currentQuestionIndex === quiz.questions.length - 1;
542
- const progressPercent = (currentQuestionIndex + 1) / quiz.questions.length * 100;
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) })
548
- ] }),
549
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.progress, children: [
1757
+ const isLastQuestion = currentQuestionIndex === totalQuestions - 1;
1758
+ const progressPercent = (currentQuestionIndex + 1) / totalQuestions * 100;
1759
+ const remainingSlots = maxQuestions - totalQuestions;
1760
+ const questionsToAdd = Math.min(5, remainingSlots);
1761
+ const canAddMore = onGenerateMoreQuestions && remainingSlots > 0;
1762
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, style: defaultStyles.container, children: [
1763
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.header, children: [
1764
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.title, children: quiz.title }),
1765
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.progress, children: [
550
1766
  "Question ",
551
1767
  currentQuestionIndex + 1,
552
1768
  " of ",
553
- quiz.questions.length
1769
+ totalQuestions
554
1770
  ] }),
555
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
1771
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
556
1772
  ] }),
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)(
560
- "div",
561
- {
562
- style: {
563
- ...defaultStyles.option,
564
- ...selectedAnswer === option ? defaultStyles.optionSelected : {}
565
- },
566
- onClick: () => handleAnswerChange(option),
567
- children: option
568
- },
569
- idx
570
- )) }),
571
- currentQuestion.type === "multiple" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
572
- const selected = Array.isArray(selectedAnswer) && selectedAnswer.includes(option);
573
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
574
- "div",
575
- {
576
- style: {
577
- ...defaultStyles.option,
578
- ...selected ? defaultStyles.optionSelected : {}
579
- },
580
- onClick: () => {
581
- const current = Array.isArray(selectedAnswer) ? selectedAnswer : [];
582
- if (selected) {
583
- handleAnswerChange(current.filter((o) => o !== option));
584
- } else {
585
- handleAnswerChange([...current, option]);
1773
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.mainLayout, children: [
1774
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.quizContent, children: [
1775
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { ...defaultStyles.question, position: "relative", paddingBottom: "40px" }, children: [
1776
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.questionText, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TextToSpeech, { text: currentQuestion.question, inline: true, size: "md" }) }),
1777
+ isExtraQuestion && !showFeedback && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1778
+ "button",
1779
+ {
1780
+ onClick: () => setShowSkipModal(true),
1781
+ title: "Skip question",
1782
+ style: {
1783
+ position: "absolute",
1784
+ bottom: "8px",
1785
+ left: "0",
1786
+ background: "transparent",
1787
+ border: "none",
1788
+ cursor: "pointer",
1789
+ padding: "6px 10px",
1790
+ borderRadius: "6px",
1791
+ color: "#9ca3af",
1792
+ display: "flex",
1793
+ alignItems: "center",
1794
+ justifyContent: "center",
1795
+ gap: "4px",
1796
+ fontSize: "12px",
1797
+ opacity: 0.6,
1798
+ transition: "opacity 0.2s, color 0.2s"
1799
+ },
1800
+ onMouseEnter: (e) => {
1801
+ e.currentTarget.style.opacity = "1";
1802
+ e.currentTarget.style.color = "#6b7280";
1803
+ },
1804
+ onMouseLeave: (e) => {
1805
+ e.currentTarget.style.opacity = "0.6";
1806
+ e.currentTarget.style.color = "#9ca3af";
1807
+ },
1808
+ "data-testid": "button-skip-question",
1809
+ children: [
1810
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1811
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "5 4 15 12 5 20 5 4" }),
1812
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "19", y1: "5", x2: "19", y2: "19" })
1813
+ ] }),
1814
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Skip" })
1815
+ ]
1816
+ }
1817
+ ),
1818
+ !isExtraQuestion && !showFeedback && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1819
+ "button",
1820
+ {
1821
+ onClick: () => setShowReportModal(true),
1822
+ title: "Report an issue with this question",
1823
+ style: {
1824
+ position: "absolute",
1825
+ bottom: "8px",
1826
+ left: "0",
1827
+ background: "transparent",
1828
+ border: "none",
1829
+ cursor: "pointer",
1830
+ padding: "6px 10px",
1831
+ borderRadius: "6px",
1832
+ color: "#9ca3af",
1833
+ display: "flex",
1834
+ alignItems: "center",
1835
+ justifyContent: "center",
1836
+ gap: "4px",
1837
+ fontSize: "12px",
1838
+ opacity: 0.6,
1839
+ transition: "opacity 0.2s, color 0.2s"
1840
+ },
1841
+ onMouseEnter: (e) => {
1842
+ e.currentTarget.style.opacity = "1";
1843
+ e.currentTarget.style.color = "#ef4444";
1844
+ },
1845
+ onMouseLeave: (e) => {
1846
+ e.currentTarget.style.opacity = "0.6";
1847
+ e.currentTarget.style.color = "#9ca3af";
1848
+ },
1849
+ "data-testid": "button-report-question",
1850
+ children: [
1851
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1852
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z" }),
1853
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "4", y1: "22", x2: "4", y2: "15" })
1854
+ ] }),
1855
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Report" })
1856
+ ]
1857
+ }
1858
+ ),
1859
+ (currentQuestion.type === "single" || currentQuestion.type === "true-false") && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
1860
+ const isSelected = selectedAnswer === option;
1861
+ const isCorrectOption = currentQuestion.correctAnswer === option;
1862
+ let optionStyle = { ...defaultStyles.option };
1863
+ if (showFeedback) {
1864
+ if (isCorrectOption) {
1865
+ optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
1866
+ } else if (isSelected && !isCorrectOption) {
1867
+ optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
586
1868
  }
1869
+ } else if (isSelected) {
1870
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
1871
+ }
1872
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1873
+ "div",
1874
+ {
1875
+ style: {
1876
+ ...optionStyle,
1877
+ cursor: showFeedback ? "default" : "pointer",
1878
+ display: "flex",
1879
+ alignItems: "center",
1880
+ gap: "8px"
1881
+ },
1882
+ onClick: () => !showFeedback && handleAnswerChange(option),
1883
+ children: [
1884
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TextToSpeech, { text: option, size: "sm" }) }),
1885
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1 }, children: option })
1886
+ ]
1887
+ },
1888
+ idx
1889
+ );
1890
+ }) }),
1891
+ currentQuestion.type === "multiple" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
1892
+ const selected = Array.isArray(selectedAnswer) && selectedAnswer.includes(option);
1893
+ const correctAnswers = Array.isArray(currentQuestion.correctAnswer) ? currentQuestion.correctAnswer : currentQuestion.correctAnswer ? [currentQuestion.correctAnswer] : [];
1894
+ const isCorrectOption = correctAnswers.includes(option);
1895
+ let optionStyle = { ...defaultStyles.option };
1896
+ if (showFeedback) {
1897
+ if (isCorrectOption) {
1898
+ optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
1899
+ } else if (selected && !isCorrectOption) {
1900
+ optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
1901
+ }
1902
+ } else if (selected) {
1903
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
1904
+ }
1905
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1906
+ "div",
1907
+ {
1908
+ style: {
1909
+ ...optionStyle,
1910
+ cursor: showFeedback ? "default" : "pointer",
1911
+ display: "flex",
1912
+ alignItems: "center",
1913
+ gap: "8px"
1914
+ },
1915
+ onClick: () => {
1916
+ if (showFeedback) return;
1917
+ const current = Array.isArray(selectedAnswer) ? selectedAnswer : [];
1918
+ if (selected) {
1919
+ handleAnswerChange(current.filter((o) => o !== option));
1920
+ } else {
1921
+ handleAnswerChange([...current, option]);
1922
+ }
1923
+ },
1924
+ children: [
1925
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TextToSpeech, { text: option, size: "sm" }) }),
1926
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1 }, children: option })
1927
+ ]
1928
+ },
1929
+ idx
1930
+ );
1931
+ }) }),
1932
+ (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1933
+ "textarea",
1934
+ {
1935
+ style: { ...defaultStyles.input, minHeight: currentQuestion.type === "essay" ? "150px" : "60px" },
1936
+ value: selectedAnswer || "",
1937
+ onChange: (e) => handleAnswerChange(e.target.value),
1938
+ placeholder: "Type your answer here...",
1939
+ disabled: showFeedback
1940
+ }
1941
+ ),
1942
+ currentQuestion.type === "fill" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.options, children: currentQuestion.blanks?.map((_, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1943
+ "input",
1944
+ {
1945
+ style: defaultStyles.input,
1946
+ value: (Array.isArray(selectedAnswer) ? selectedAnswer[idx] : "") || "",
1947
+ onChange: (e) => {
1948
+ const current = Array.isArray(selectedAnswer) ? [...selectedAnswer] : [];
1949
+ current[idx] = e.target.value;
1950
+ handleAnswerChange(current);
1951
+ },
1952
+ placeholder: `Blank ${idx + 1}`,
1953
+ disabled: showFeedback
587
1954
  },
588
- children: option
589
- },
590
- idx
591
- );
592
- }) }),
593
- (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
594
- "textarea",
595
- {
596
- style: { ...defaultStyles.input, minHeight: currentQuestion.type === "essay" ? "150px" : "60px" },
597
- value: selectedAnswer || "",
598
- onChange: (e) => handleAnswerChange(e.target.value),
599
- placeholder: "Type your answer here..."
600
- }
601
- ),
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)(
603
- "input",
604
- {
605
- style: defaultStyles.input,
606
- value: (Array.isArray(selectedAnswer) ? selectedAnswer[idx] : "") || "",
607
- onChange: (e) => {
608
- const current = Array.isArray(selectedAnswer) ? [...selectedAnswer] : [];
609
- current[idx] = e.target.value;
610
- handleAnswerChange(current);
611
- },
612
- placeholder: `Blank ${idx + 1}`
613
- },
614
- idx
615
- )) })
616
- ] }),
617
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.buttons, children: [
618
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
619
- "button",
620
- {
621
- style: {
622
- ...defaultStyles.button,
623
- ...currentQuestionIndex > 0 ? defaultStyles.buttonSecondary : defaultStyles.buttonDisabled
624
- },
625
- onClick: handlePrevious,
626
- disabled: currentQuestionIndex === 0,
627
- children: "Previous"
628
- }
629
- ),
630
- isLastQuestion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
631
- "button",
632
- {
633
- style: {
634
- ...defaultStyles.button,
635
- ...isSubmitting ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
636
- },
637
- onClick: handleSubmit,
638
- disabled: isSubmitting || selectedAnswer === void 0,
639
- children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
640
- }
641
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
642
- "button",
1955
+ idx
1956
+ )) }),
1957
+ showFeedback && currentAnswerDetail && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
1958
+ ...defaultStyles.feedback,
1959
+ ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackCorrect : defaultStyles.feedbackIncorrect
1960
+ }, children: [
1961
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
1962
+ ...defaultStyles.feedbackTitle,
1963
+ ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackTitleCorrect : defaultStyles.feedbackTitleIncorrect
1964
+ }, children: currentAnswerDetail.isCorrect ? "\u2713 Correct!" : "\u2717 Incorrect" }),
1965
+ currentQuestion.explanation && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.feedbackExplanation, children: currentQuestion.explanation })
1966
+ ] })
1967
+ ] }),
1968
+ showSkipModal && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
1969
+ position: "fixed",
1970
+ top: 0,
1971
+ left: 0,
1972
+ right: 0,
1973
+ bottom: 0,
1974
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
1975
+ display: "flex",
1976
+ alignItems: "center",
1977
+ justifyContent: "center",
1978
+ zIndex: 1e3
1979
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
1980
+ backgroundColor: "#ffffff",
1981
+ borderRadius: "12px",
1982
+ padding: "24px",
1983
+ maxWidth: "400px",
1984
+ width: "90%",
1985
+ boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
1986
+ }, children: [
1987
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Skip Question" }),
1988
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "Help us improve by telling us why you're skipping this question." }),
1989
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "8px", marginBottom: "16px" }, children: [
1990
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1991
+ "button",
1992
+ {
1993
+ onClick: () => setSelectedSkipReason("question_issue"),
1994
+ disabled: isSkipping,
1995
+ style: {
1996
+ padding: "12px 16px",
1997
+ borderRadius: "8px",
1998
+ border: selectedSkipReason === "question_issue" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
1999
+ backgroundColor: selectedSkipReason === "question_issue" ? "#f5f3ff" : "#f9fafb",
2000
+ cursor: isSkipping ? "not-allowed" : "pointer",
2001
+ fontSize: "14px",
2002
+ fontWeight: "500",
2003
+ color: "#374151",
2004
+ textAlign: "left",
2005
+ opacity: isSkipping ? 0.6 : 1
2006
+ },
2007
+ "data-testid": "button-skip-reason-issue",
2008
+ children: "Question has an issue"
2009
+ }
2010
+ ),
2011
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2012
+ "button",
2013
+ {
2014
+ onClick: () => setSelectedSkipReason("dont_know"),
2015
+ disabled: isSkipping,
2016
+ style: {
2017
+ padding: "12px 16px",
2018
+ borderRadius: "8px",
2019
+ border: selectedSkipReason === "dont_know" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
2020
+ backgroundColor: selectedSkipReason === "dont_know" ? "#f5f3ff" : "#f9fafb",
2021
+ cursor: isSkipping ? "not-allowed" : "pointer",
2022
+ fontSize: "14px",
2023
+ fontWeight: "500",
2024
+ color: "#374151",
2025
+ textAlign: "left",
2026
+ opacity: isSkipping ? 0.6 : 1
2027
+ },
2028
+ "data-testid": "button-skip-reason-dont-know",
2029
+ children: "I don't know the answer"
2030
+ }
2031
+ )
2032
+ ] }),
2033
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "16px" }, children: [
2034
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { style: { display: "block", fontSize: "13px", fontWeight: "500", color: "#374151", marginBottom: "6px" }, children: "Additional details (optional)" }),
2035
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2036
+ "textarea",
2037
+ {
2038
+ value: skipComment,
2039
+ onChange: (e) => setSkipComment(e.target.value.slice(0, 200)),
2040
+ placeholder: "Tell us more about the issue...",
2041
+ disabled: isSkipping,
2042
+ style: {
2043
+ width: "100%",
2044
+ minHeight: "80px",
2045
+ padding: "10px 12px",
2046
+ borderRadius: "8px",
2047
+ border: "1px solid #e5e7eb",
2048
+ fontSize: "14px",
2049
+ resize: "vertical",
2050
+ fontFamily: "inherit",
2051
+ boxSizing: "border-box"
2052
+ },
2053
+ "data-testid": "input-skip-comment"
2054
+ }
2055
+ ),
2056
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2057
+ skipComment.length,
2058
+ "/200"
2059
+ ] })
2060
+ ] }),
2061
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: "10px" }, children: [
2062
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2063
+ "button",
2064
+ {
2065
+ onClick: () => {
2066
+ setShowSkipModal(false);
2067
+ setSkipComment("");
2068
+ setSelectedSkipReason(null);
2069
+ },
2070
+ style: {
2071
+ flex: 1,
2072
+ padding: "10px 16px",
2073
+ borderRadius: "8px",
2074
+ border: "1px solid #e5e7eb",
2075
+ backgroundColor: "#ffffff",
2076
+ cursor: "pointer",
2077
+ fontSize: "14px",
2078
+ fontWeight: "500",
2079
+ color: "#6b7280"
2080
+ },
2081
+ "data-testid": "button-skip-cancel",
2082
+ children: "Cancel"
2083
+ }
2084
+ ),
2085
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2086
+ "button",
2087
+ {
2088
+ onClick: () => selectedSkipReason && handleSkipQuestion(selectedSkipReason, skipComment),
2089
+ disabled: isSkipping || !selectedSkipReason,
2090
+ style: {
2091
+ flex: 1,
2092
+ padding: "10px 16px",
2093
+ borderRadius: "8px",
2094
+ border: "none",
2095
+ backgroundColor: selectedSkipReason ? "#8b5cf6" : "#d1d5db",
2096
+ cursor: isSkipping || !selectedSkipReason ? "not-allowed" : "pointer",
2097
+ fontSize: "14px",
2098
+ fontWeight: "500",
2099
+ color: "#ffffff",
2100
+ opacity: isSkipping ? 0.6 : 1
2101
+ },
2102
+ "data-testid": "button-skip-submit",
2103
+ children: isSkipping ? "Skipping..." : "Skip Question"
2104
+ }
2105
+ )
2106
+ ] })
2107
+ ] }) }),
2108
+ showReportModal && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
2109
+ position: "fixed",
2110
+ top: 0,
2111
+ left: 0,
2112
+ right: 0,
2113
+ bottom: 0,
2114
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
2115
+ display: "flex",
2116
+ alignItems: "center",
2117
+ justifyContent: "center",
2118
+ zIndex: 1e3
2119
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
2120
+ backgroundColor: "#ffffff",
2121
+ borderRadius: "12px",
2122
+ padding: "24px",
2123
+ maxWidth: "400px",
2124
+ width: "90%",
2125
+ boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
2126
+ }, children: [
2127
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Report Question" }),
2128
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "What's wrong with this question?" }),
2129
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "16px" }, children: [
2130
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2131
+ "textarea",
2132
+ {
2133
+ value: reportComment,
2134
+ onChange: (e) => setReportComment(e.target.value.slice(0, 300)),
2135
+ placeholder: "Describe the issue with this question...",
2136
+ disabled: isReporting,
2137
+ style: {
2138
+ width: "100%",
2139
+ minHeight: "120px",
2140
+ padding: "10px 12px",
2141
+ borderRadius: "8px",
2142
+ border: "1px solid #e5e7eb",
2143
+ fontSize: "14px",
2144
+ resize: "vertical",
2145
+ fontFamily: "inherit",
2146
+ boxSizing: "border-box"
2147
+ },
2148
+ "data-testid": "input-report-comment"
2149
+ }
2150
+ ),
2151
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2152
+ reportComment.length,
2153
+ "/300"
2154
+ ] })
2155
+ ] }),
2156
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: "10px" }, children: [
2157
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2158
+ "button",
2159
+ {
2160
+ onClick: () => {
2161
+ setShowReportModal(false);
2162
+ setReportComment("");
2163
+ },
2164
+ style: {
2165
+ flex: 1,
2166
+ padding: "10px 16px",
2167
+ borderRadius: "8px",
2168
+ border: "1px solid #e5e7eb",
2169
+ backgroundColor: "#ffffff",
2170
+ cursor: "pointer",
2171
+ fontSize: "14px",
2172
+ fontWeight: "500",
2173
+ color: "#6b7280"
2174
+ },
2175
+ "data-testid": "button-report-cancel",
2176
+ children: "Cancel"
2177
+ }
2178
+ ),
2179
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2180
+ "button",
2181
+ {
2182
+ onClick: () => handleReportQuestion(reportComment),
2183
+ disabled: isReporting || !reportComment.trim(),
2184
+ style: {
2185
+ flex: 1,
2186
+ padding: "10px 16px",
2187
+ borderRadius: "8px",
2188
+ border: "none",
2189
+ backgroundColor: reportComment.trim() ? "#ef4444" : "#d1d5db",
2190
+ cursor: isReporting || !reportComment.trim() ? "not-allowed" : "pointer",
2191
+ fontSize: "14px",
2192
+ fontWeight: "500",
2193
+ color: "#ffffff",
2194
+ opacity: isReporting ? 0.6 : 1
2195
+ },
2196
+ "data-testid": "button-report-submit",
2197
+ children: isReporting ? "Reporting..." : "Report"
2198
+ }
2199
+ )
2200
+ ] })
2201
+ ] }) }),
2202
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.buttonsColumn, children: [
2203
+ showFeedback && isLastQuestion && canAddMore && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2204
+ "button",
2205
+ {
2206
+ style: {
2207
+ ...defaultStyles.buttonAddMore,
2208
+ ...isGeneratingExtra ? defaultStyles.buttonAddMoreDisabled : {}
2209
+ },
2210
+ onClick: handleAddMoreQuestions,
2211
+ disabled: isGeneratingExtra,
2212
+ "data-testid": "button-add-more-questions",
2213
+ children: isGeneratingExtra ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2214
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: 16, color: "#9ca3af" }),
2215
+ "Generating Questions..."
2216
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2217
+ "+ Add ",
2218
+ questionsToAdd,
2219
+ " More Question",
2220
+ questionsToAdd !== 1 ? "s" : ""
2221
+ ] })
2222
+ }
2223
+ ),
2224
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { ...defaultStyles.buttons, justifyContent: "flex-end" }, children: showFeedback ? (
2225
+ // After viewing feedback
2226
+ isLastQuestion ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2227
+ "button",
2228
+ {
2229
+ style: {
2230
+ ...defaultStyles.button,
2231
+ ...isSubmitting || isGeneratingExtra ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2232
+ },
2233
+ onClick: handleSubmit,
2234
+ disabled: isSubmitting || isGeneratingExtra,
2235
+ "data-testid": "button-submit-quiz",
2236
+ children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
2237
+ }
2238
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2239
+ "button",
2240
+ {
2241
+ style: {
2242
+ ...defaultStyles.button,
2243
+ ...defaultStyles.buttonPrimary
2244
+ },
2245
+ onClick: handleContinue,
2246
+ "data-testid": "button-continue",
2247
+ children: "Continue"
2248
+ }
2249
+ )
2250
+ ) : (
2251
+ // Before checking answer
2252
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2253
+ "button",
2254
+ {
2255
+ style: {
2256
+ ...defaultStyles.button,
2257
+ ...isNavigating || selectedAnswer === void 0 ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2258
+ },
2259
+ onClick: handleCheckAnswer,
2260
+ disabled: isNavigating || selectedAnswer === void 0,
2261
+ "data-testid": "button-check-answer",
2262
+ children: isNavigating ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Check Answer"
2263
+ }
2264
+ )
2265
+ ) })
2266
+ ] })
2267
+ ] }),
2268
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.chatPanel, children: apiClient.current && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2269
+ QuestionChatPanel,
643
2270
  {
644
- style: {
645
- ...defaultStyles.button,
646
- ...isNavigating || selectedAnswer === void 0 ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2271
+ apiClient: apiClient.current,
2272
+ question: {
2273
+ id: currentQuestion.id,
2274
+ question: currentQuestion.question,
2275
+ type: currentQuestion.type,
2276
+ options: currentQuestion.options,
2277
+ correctAnswer: currentQuestion.correctAnswer,
2278
+ explanation: currentQuestion.explanation
647
2279
  },
648
- onClick: handleNext,
649
- disabled: isNavigating || selectedAnswer === void 0,
650
- children: isNavigating ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Next"
2280
+ quizId: quiz.id,
2281
+ childId,
2282
+ parentId,
2283
+ lessonId,
2284
+ courseId
651
2285
  }
652
- )
2286
+ ) })
653
2287
  ] })
654
2288
  ] });
655
2289
  }
656
2290
 
657
2291
  // src/AttemptViewer.tsx
658
- var import_react2 = require("react");
659
- var import_jsx_runtime2 = require("react/jsx-runtime");
2292
+ var import_react4 = require("react");
2293
+ var import_jsx_runtime4 = require("react/jsx-runtime");
660
2294
  var defaultStyles2 = {
661
2295
  container: {
662
2296
  fontFamily: "system-ui, -apple-system, sans-serif",
@@ -836,10 +2470,10 @@ function AttemptViewer({
836
2470
  showExplanations = true,
837
2471
  title
838
2472
  }) {
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)(() => {
2473
+ const [attempt, setAttempt] = (0, import_react4.useState)(null);
2474
+ const [loading, setLoading] = (0, import_react4.useState)(true);
2475
+ const [error, setError] = (0, import_react4.useState)(null);
2476
+ (0, import_react4.useEffect)(() => {
843
2477
  const apiClient = new QuizApiClient({
844
2478
  baseUrl: apiBaseUrl,
845
2479
  authToken
@@ -872,49 +2506,49 @@ function AttemptViewer({
872
2506
  window.location.reload();
873
2507
  };
874
2508
  if (loading) {
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..." })
2509
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.container, className, children: [
2510
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: spinnerKeyframes }),
2511
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.loading, children: [
2512
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.spinner }),
2513
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { marginTop: "16px", color: "#6b7280" }, children: "Loading attempt..." })
880
2514
  ] })
881
2515
  ] });
882
2516
  }
883
2517
  if (error || !attempt) {
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" })
2518
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.container, className, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.error, children: [
2519
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { fontSize: "18px", fontWeight: "500" }, children: "Failed to load attempt" }),
2520
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { marginTop: "8px", color: "#6b7280" }, children: error }),
2521
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { style: defaultStyles2.retryButton, onClick: handleRetry, children: "Try Again" })
888
2522
  ] }) });
889
2523
  }
890
2524
  const scorePercentage = attempt.score ?? 0;
891
2525
  const correctCount = attempt.correctAnswers ?? 0;
892
2526
  const totalQuestions = attempt.totalQuestions;
893
2527
  const timeSpent = attempt.timeSpentSeconds ?? 0;
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: [
2528
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.container, className, children: [
2529
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: spinnerKeyframes }),
2530
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.header, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryGrid, children: [
2531
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
2532
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
899
2533
  scorePercentage,
900
2534
  "%"
901
2535
  ] }),
902
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Score" })
2536
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Score" })
903
2537
  ] }),
904
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
905
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
2538
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
2539
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
906
2540
  correctCount,
907
2541
  "/",
908
2542
  totalQuestions
909
2543
  ] }),
910
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Correct" })
2544
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Correct" })
911
2545
  ] }),
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" })
2546
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
2547
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.summaryValue, children: formatTime(timeSpent) }),
2548
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Time" })
915
2549
  ] })
916
2550
  ] }) }),
917
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.questionsList, children: attempt.answers.map((answer, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2551
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.questionsList, children: attempt.answers.map((answer, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
918
2552
  "div",
919
2553
  {
920
2554
  style: {
@@ -922,12 +2556,12 @@ function AttemptViewer({
922
2556
  ...answer.isCorrect ? defaultStyles2.questionCardCorrect : defaultStyles2.questionCardIncorrect
923
2557
  },
924
2558
  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: [
2559
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.questionHeader, children: [
2560
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: defaultStyles2.questionNumber, children: [
927
2561
  "Question ",
928
2562
  index + 1
929
2563
  ] }),
930
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2564
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
931
2565
  "span",
932
2566
  {
933
2567
  style: {
@@ -938,23 +2572,23 @@ function AttemptViewer({
938
2572
  }
939
2573
  )
940
2574
  ] }),
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) })
2575
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.questionText, children: answer.questionText }),
2576
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.answerSection, children: [
2577
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.answerLabel, children: "Your answer:" }),
2578
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.studentAnswer, children: formatAnswer(answer.selectedAnswer) })
945
2579
  ] }),
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) })
2580
+ !answer.isCorrect && answer.correctAnswer && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.answerSection, children: [
2581
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.answerLabel, children: "Correct answer:" }),
2582
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.correctAnswer, children: formatAnswer(answer.correctAnswer) })
949
2583
  ] }),
950
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.points, children: [
2584
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.points, children: [
951
2585
  answer.pointsEarned,
952
2586
  " / ",
953
2587
  answer.points,
954
2588
  " points"
955
2589
  ] }),
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:" }),
2590
+ showExplanations && answer.explanation && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.explanation, children: [
2591
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: "Explanation:" }),
958
2592
  " ",
959
2593
  answer.explanation
960
2594
  ] })
@@ -969,6 +2603,7 @@ function AttemptViewer({
969
2603
  AttemptViewer,
970
2604
  QuizApiClient,
971
2605
  QuizPlayer,
2606
+ TextToSpeech,
972
2607
  calculateScore,
973
2608
  checkAnswer,
974
2609
  createAnswerDetail,