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