Skip to content

Add interactive quiz player feature in Output.jsx with answer selection and score calculation#553

Open
PunyaGowdagd66 wants to merge 3 commits intoAOSSIE-Org:mainfrom
PunyaGowdagd66:interactive-quiz-player
Open

Add interactive quiz player feature in Output.jsx with answer selection and score calculation#553
PunyaGowdagd66 wants to merge 3 commits intoAOSSIE-Org:mainfrom
PunyaGowdagd66:interactive-quiz-player

Conversation

@PunyaGowdagd66
Copy link
Copy Markdown

@PunyaGowdagd66 PunyaGowdagd66 commented Mar 11, 2026

Description

This PR introduces a prototype interactive quiz player in Output.jsx.

Features Added

Users can select answers for each question
Selected answers are stored using React state
Score calculation logic implemented
Submit Quiz button displays the final score
Prototype UI for interactive quiz player implemented.

This feature improves the EduAid quiz experience by allowing users to interact with generated questions and immediately see their performance.

Files Modified

eduaid_web/src/pages/Output.jsx

Notes

Backend integration may require further refinement depending on API responses.

Summary by CodeRabbit

  • New Features

    • Quiz options are now selectable (clickable) and a "Submit Quiz" button scores the quiz and displays results instantly.
  • Bug Fixes / Validation

    • Saving is blocked when both text input and Google Doc URL are empty; an alert is shown instead of proceeding.
  • UI Enhancements

    • Minor layout and option rendering tweaks to accommodate the new quiz actions.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

Adds client-side quiz state and scoring UI to Output.jsx and adds a guard in Text_Input.jsx to require either text input or a Google Doc URL before saving.

Changes

Cohort / File(s) Summary
Quiz State & UI
eduaid_web/src/pages/Output.jsx
Added userAnswers state and handleAnswerSelect(questionIndex, option) to record selections; added calculateScore() to compare answers and alert the score; options made clickable and a "Submit Quiz" button added; minor layout tweaks. (+39/-7)
Save Guard
eduaid_web/src/pages/Text_Input.jsx
Added validation in handleSaveToLocalStorage to require either text input or a Google Doc URL and show an alert if both are empty; prevents setting loading state when guard fails. (+7/-1)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped through lines with nimble paws,

I saved your picks and counted applause.
Click each choice, then tap submit—
A tiny score, a happy skit.
🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically summarizes the main change: adding an interactive quiz player feature with answer selection and score calculation in Output.jsx.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
eduaid_web/src/pages/Output.jsx (1)

250-262: ⚠️ Potential issue | 🔴 Critical

Invalid HTML: button nested inside another button.

The "Submit Quiz" button (lines 255-260) is nested inside the "Generate Google form" button (lines 250-262). This is invalid HTML—<button> elements cannot contain interactive content. This causes:

  1. Both onClick handlers fire when clicking "Submit Quiz" (event bubbling)
  2. Unpredictable browser behavior
  3. Accessibility violations

Move the "Submit Quiz" button outside and make it a sibling element.

🐛 Proposed fix
           <div className="flex flex-col sm:flex-row items-center justify-center gap-4 sm:gap-6 mx-4 sm:mx-auto pb-4 sm:pb-6">
             <button
               className="bg-[`#518E8E`] items-center flex gap-1 w-full sm:w-auto font-semibold text-white px-4 sm:px-6 py-3 sm:py-2 rounded-xl text-sm sm:text-base hover:bg-[`#3a6b6b`] transition-colors justify-center"
               onClick={generateGoogleForm}
             >
               Generate Google form
-              <button
-              className="bg-green-600 text-white px-6 py-2 rounded-xl"
-              onClick={calculateScore}
-              >
-              Submit Quiz
-              </button>
-
             </button>
+            
+            <button
+              className="bg-green-600 items-center flex gap-1 w-full sm:w-auto font-semibold text-white px-4 sm:px-6 py-3 sm:py-2 rounded-xl text-sm sm:text-base hover:bg-green-700 transition-colors justify-center"
+              onClick={calculateScore}
+            >
+              Submit Quiz
+            </button>
             
             <div className="relative w-full sm:w-auto">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Output.jsx` around lines 250 - 262, The "Submit Quiz"
<button> is incorrectly nested inside the "Generate Google form" <button>,
causing invalid HTML and mixed event handling; move the inner <button> that
calls calculateScore so it becomes a sibling (not a child) of the outer button
that calls generateGoogleForm, updating the JSX around the elements that use the
generateGoogleForm and calculateScore onClick handlers and preserving their
className/styling (e.g., the outer button with className "bg-[`#518E8E`] ...", and
the inner with "bg-green-600 ...") so both buttons render as separate elements
next to each other.
🧹 Nitpick comments (2)
eduaid_web/src/pages/Output.jsx (2)

35-40: Use functional state update to avoid stale closure issues.

When updating state based on previous state, prefer the functional update pattern to ensure you're working with the latest state value, especially if multiple rapid clicks occur.

♻️ Proposed fix
 const handleAnswerSelect = (questionIndex, option) => {
-  setUserAnswers({
-    ...userAnswers,
-    [questionIndex]: option
-    });
+  setUserAnswers((prev) => ({
+    ...prev,
+    [questionIndex]: option,
+  }));
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Output.jsx` around lines 35 - 40, The state update in
handleAnswerSelect uses the stale userAnswers object; change it to a functional
state update by calling setUserAnswers with an updater function that receives
the previous state and returns a new object merging prev and {[questionIndex]:
option} so updates are based on the latest state (reference: handleAnswerSelect,
setUserAnswers, userAnswers, questionIndex, option).

226-230: No visual feedback for selected answer.

Users cannot see which option they've selected. Consider adding a visual indicator (e.g., background color or border) for the selected option.

♻️ Example implementation
 <div
   key={idx}
-  className="mb-1 sm:mb-2 cursor-pointer"
+  className={`mb-1 sm:mb-2 cursor-pointer p-2 rounded ${
+    userAnswers[index] === option
+      ? "bg-[`#518E8E`] border border-white"
+      : "hover:bg-[`#ffffff1a`]"
+  }`}
   onClick={() => handleAnswerSelect(index, option)}
 >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Output.jsx` around lines 226 - 230, The option div
currently calls handleAnswerSelect(index, option) but provides no visual state;
add a selected state (e.g., selectedAnswers or selectedAnswer per question) and
update handleAnswerSelect to store the chosen option for that question, then
apply a conditional className on the div (using idx/index/option) to add a
visual indicator such as "bg-blue-100 border-blue-500" or a highlighted style
when the stored selection equals option; ensure the click handler still calls
handleAnswerSelect so UI and state stay in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@eduaid_web/src/pages/Output.jsx`:
- Around line 41-51: The options are being reshuffled on every render because
shuffleArray is called inline, so when setUserAnswers causes a re-render the
option order changes; fix this by computing shuffled options once when qaPairs
are loaded (inside the existing useEffect that builds combinedQaPairs) by
mapping each pair to include a shuffledOptions property using
shuffleArray(allOptions) and then call setQaPairs with those pairs, remove the
inline shuffleArray call from the render and use qaPair.shuffledOptions instead;
reference symbols: shuffleArray, qaPairs, setQaPairs, setUserAnswers, useEffect.
- Around line 44-48: The score loop treats all qaPairs as MCQs but Boolean
questions lack an answer property and are not rendered, causing them to always
be marked wrong; change the scoring logic in Output.jsx so you only score
rendered/answerable questions — e.g., build a filteredQuestions =
qaPairs.filter(q => q.question_type !== "Boolean" && q.answer !== undefined) (or
filter by presence of q.answer) and then iterate filteredQuestions to compare
against userAnswers, or if userAnswers is keyed by question id, match by q.id
instead of index; update the loop that references qaPairs, userAnswers and score
to use this filtered/mapped collection so Boolean questions are excluded from
scoring.

---

Outside diff comments:
In `@eduaid_web/src/pages/Output.jsx`:
- Around line 250-262: The "Submit Quiz" <button> is incorrectly nested inside
the "Generate Google form" <button>, causing invalid HTML and mixed event
handling; move the inner <button> that calls calculateScore so it becomes a
sibling (not a child) of the outer button that calls generateGoogleForm,
updating the JSX around the elements that use the generateGoogleForm and
calculateScore onClick handlers and preserving their className/styling (e.g.,
the outer button with className "bg-[`#518E8E`] ...", and the inner with
"bg-green-600 ...") so both buttons render as separate elements next to each
other.

---

Nitpick comments:
In `@eduaid_web/src/pages/Output.jsx`:
- Around line 35-40: The state update in handleAnswerSelect uses the stale
userAnswers object; change it to a functional state update by calling
setUserAnswers with an updater function that receives the previous state and
returns a new object merging prev and {[questionIndex]: option} so updates are
based on the latest state (reference: handleAnswerSelect, setUserAnswers,
userAnswers, questionIndex, option).
- Around line 226-230: The option div currently calls handleAnswerSelect(index,
option) but provides no visual state; add a selected state (e.g.,
selectedAnswers or selectedAnswer per question) and update handleAnswerSelect to
store the chosen option for that question, then apply a conditional className on
the div (using idx/index/option) to add a visual indicator such as "bg-blue-100
border-blue-500" or a highlighted style when the stored selection equals option;
ensure the click handler still calls handleAnswerSelect so UI and state stay in
sync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8a727393-8067-400e-b509-594c32f1495c

📥 Commits

Reviewing files that changed from the base of the PR and between fc3bf1a and 3b7427a.

⛔ Files ignored due to path filters (1)
  • eduaid_web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • eduaid_web/src/pages/Output.jsx

Comment on lines +41 to +51
const calculateScore = () => {
let score = 0;

qaPairs.forEach((q, i) => {
if (userAnswers[i] === q.answer) {
score++;
}
});

alert(`Your Score: ${score} / ${qaPairs.length}`);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Options reshuffle on every render, breaking selection UX.

The shuffleArray call at line 203 executes on every render. When a user selects an answer, setUserAnswers triggers a re-render, causing all options to reshuffle. This makes the quiz unusable—the selected option moves to a different position, and the visual feedback is lost.

Consider memoizing the shuffled options (e.g., compute once in useEffect and store in state alongside qaPairs).

🛠️ Suggested approach

Store shuffled options when qaPairs is loaded:

useEffect(() => {
  // ... existing qaPairs loading logic ...
  
  // After building combinedQaPairs, shuffle options once:
  const pairsWithShuffledOptions = combinedQaPairs.map((pair) => {
    if (pair.options && pair.options.length > 0) {
      const allOptions = [...pair.options, pair.answer];
      return { ...pair, shuffledOptions: shuffleArray(allOptions) };
    }
    return pair;
  });
  
  setQaPairs(pairsWithShuffledOptions);
}, []);

Then in the render, use qaPair.shuffledOptions instead of computing shuffledOptions inline.

Also applies to: 200-203

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Output.jsx` around lines 41 - 51, The options are being
reshuffled on every render because shuffleArray is called inline, so when
setUserAnswers causes a re-render the option order changes; fix this by
computing shuffled options once when qaPairs are loaded (inside the existing
useEffect that builds combinedQaPairs) by mapping each pair to include a
shuffledOptions property using shuffleArray(allOptions) and then call setQaPairs
with those pairs, remove the inline shuffleArray call from the render and use
qaPair.shuffledOptions instead; reference symbols: shuffleArray, qaPairs,
setQaPairs, setUserAnswers, useEffect.

Comment on lines +44 to +48
qaPairs.forEach((q, i) => {
if (userAnswers[i] === q.answer) {
score++;
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Score calculation ignores Boolean questions.

Boolean questions (loaded at lines 95-101) don't have an answer property, so q.answer is undefined. Additionally, the UI (lines 215-242) doesn't render selectable options for Boolean questions—they're skipped with the question_type !== "Boolean" check. This means:

  1. Boolean questions cannot be answered
  2. They always count as incorrect in the score

If the quiz feature is MCQ-only for now, consider filtering Boolean questions from the score calculation or documenting this limitation.

💡 Option: Exclude Boolean questions from scoring
 const calculateScore = () => {
   let score = 0;
+  const scorableQuestions = qaPairs.filter((q) => q.question_type !== "Boolean");

-  qaPairs.forEach((q, i) => {
+  scorableQuestions.forEach((q, i) => {
+    const originalIndex = qaPairs.indexOf(q);
-    if (userAnswers[i] === q.answer) {
+    if (userAnswers[originalIndex] === q.answer) {
       score++;
     }
   });

-  alert(`Your Score: ${score} / ${qaPairs.length}`);
+  alert(`Your Score: ${score} / ${scorableQuestions.length}`);
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
qaPairs.forEach((q, i) => {
if (userAnswers[i] === q.answer) {
score++;
}
});
const calculateScore = () => {
let score = 0;
const scorableQuestions = qaPairs.filter((q) => q.question_type !== "Boolean");
scorableQuestions.forEach((q, i) => {
const originalIndex = qaPairs.indexOf(q);
if (userAnswers[originalIndex] === q.answer) {
score++;
}
});
alert(`Your Score: ${score} / ${scorableQuestions.length}`);
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Output.jsx` around lines 44 - 48, The score loop treats
all qaPairs as MCQs but Boolean questions lack an answer property and are not
rendered, causing them to always be marked wrong; change the scoring logic in
Output.jsx so you only score rendered/answerable questions — e.g., build a
filteredQuestions = qaPairs.filter(q => q.question_type !== "Boolean" &&
q.answer !== undefined) (or filter by presence of q.answer) and then iterate
filteredQuestions to compare against userAnswers, or if userAnswers is keyed by
question id, match by q.id instead of index; update the loop that references
qaPairs, userAnswers and score to use this filtered/mapped collection so Boolean
questions are excluded from scoring.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@eduaid_web/src/pages/Text_Input.jsx`:
- Around line 51-54: The emptiness guard uses raw text and docUrl so
whitespace-only input passes; trim both values first (e.g., create
normalizedText = text?.trim() and normalizedDocUrl = docUrl?.trim()) and replace
the check with if (!normalizedText && !normalizedDocUrl); then use
normalizedText / normalizedDocUrl in the subsequent branches and API payloads
instead of the original text and docUrl to ensure whitespace-only input is
rejected and consistent values are sent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae1e6c6b-5ecd-4c92-bb64-91299324c98a

📥 Commits

Reviewing files that changed from the base of the PR and between 3b7427a and 013a7d3.

📒 Files selected for processing (1)
  • eduaid_web/src/pages/Text_Input.jsx

Comment on lines +51 to +54
if (!text && !docUrl) {
alert("Please upload a file or provide input before proceeding.");
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Trim both fields before this emptiness check.

if (!text && !docUrl) still lets whitespace-only input through, so " " in either field bypasses the guard and reaches the backend as if content were present.

✂️ Proposed fix
-    if (!text && !docUrl) {
+    const normalizedText = text.trim();
+    const normalizedDocUrl = docUrl.trim();
+
+    if (!normalizedText && !normalizedDocUrl) {
       alert("Please upload a file or provide input before proceeding.");
       return;
     }

Use normalizedText / normalizedDocUrl in the branches below as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Text_Input.jsx` around lines 51 - 54, The emptiness
guard uses raw text and docUrl so whitespace-only input passes; trim both values
first (e.g., create normalizedText = text?.trim() and normalizedDocUrl =
docUrl?.trim()) and replace the check with if (!normalizedText &&
!normalizedDocUrl); then use normalizedText / normalizedDocUrl in the subsequent
branches and API payloads instead of the original text and docUrl to ensure
whitespace-only input is rejected and consistent values are sent.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
eduaid_web/src/pages/Output.jsx (1)

200-203: ⚠️ Potential issue | 🟠 Major

De-duplicate the option list before rendering it.

The backend payload already carries answer separately from options, and for MCQs that answer text typically matches one of the provided options. Appending qaPair.answer again here can render the correct choice twice and bias the quiz.

Suggested fix
-                const combinedOptions = qaPair.options
-                  ? [...qaPair.options, qaPair.answer]
-                  : [qaPair.answer];
+                const combinedOptions = qaPair.options
+                  ? [...new Set([...qaPair.options, qaPair.answer])]
+                  : qaPair.answer
+                    ? [qaPair.answer]
+                    : [];

Also applies to: 225-230

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Output.jsx` around lines 200 - 203, The code appends
qaPair.answer to qaPair.options which can duplicate the correct choice; update
the logic that builds combinedOptions (used with shuffleArray) to first
concatenate qaPair.options and qaPair.answer but then remove duplicates (e.g.,
by filtering or using a Set) before calling shuffleArray; apply the same
de-duplication approach to the other occurrence that constructs options (the
block around the second combinedOptions usage).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@eduaid_web/src/pages/Output.jsx`:
- Around line 41-50: calculateScore currently uses qaPairs.length and counts
Short-type questions (which have no selectable options) as unanswered; modify
calculateScore to first derive the list of answerable items (e.g., filter
qaPairs by presence of selectable options or by type !== 'Short', using the same
property your rendering logic uses), then iterate that filtered array to compare
userAnswers (mapping indexes appropriately) against each item's answer and
increment score, and finally use filtered.length as the denominator in the alert
so only answerable questions affect the reported score.
- Around line 226-230: Replace the clickable <div> for each answer choice with a
real <button type="button"> so it is keyboard-focusable and accessible; keep the
existing onClick that calls handleAnswerSelect(index, option) but also add an
accessible selected state like aria-pressed={userAnswers[index] === option} (or
aria-selected if inside a role="listbox") and apply a conditional CSS class when
userAnswers[index] === option so the selection is visually indicated; ensure the
button uses the same key={idx} and preserves other props/styling.

---

Outside diff comments:
In `@eduaid_web/src/pages/Output.jsx`:
- Around line 200-203: The code appends qaPair.answer to qaPair.options which
can duplicate the correct choice; update the logic that builds combinedOptions
(used with shuffleArray) to first concatenate qaPair.options and qaPair.answer
but then remove duplicates (e.g., by filtering or using a Set) before calling
shuffleArray; apply the same de-duplication approach to the other occurrence
that constructs options (the block around the second combinedOptions usage).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3725a0cd-2805-40e6-ab6b-97df92126c61

📥 Commits

Reviewing files that changed from the base of the PR and between 013a7d3 and 43455ca.

📒 Files selected for processing (1)
  • eduaid_web/src/pages/Output.jsx

Comment on lines +41 to +50
const calculateScore = () => {
let score = 0;

qaPairs.forEach((q, i) => {
if (userAnswers[i] === q.answer) {
score++;
}
});

alert(`Your Score: ${score} / ${qaPairs.length}`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Only score questions that are actually answerable.

qaPairs in this component also includes Short items, but those never render selectable options. calculateScore() still counts them in qaPairs.length, so short-answer quizzes will always report unanswered items as wrong.

Suggested fix
 const calculateScore = () => {
-  let score = 0;
-
-  qaPairs.forEach((q, i) => {
-    if (userAnswers[i] === q.answer) {
-      score++;
-    }
-  });
-
-  alert(`Your Score: ${score} / ${qaPairs.length}`);
+  const scoreableCount = qaPairs.filter(
+    (q) => q.question_type === "MCQ" && Array.isArray(q.options) && q.answer != null
+  ).length;
+
+  const score = qaPairs.reduce((total, q, i) => {
+    const isScoreable =
+      q.question_type === "MCQ" && Array.isArray(q.options) && q.answer != null;
+
+    return total + (isScoreable && userAnswers[i] === q.answer ? 1 : 0);
+  }, 0);
+
+  alert(`Your Score: ${score} / ${scoreableCount}`);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Output.jsx` around lines 41 - 50, calculateScore
currently uses qaPairs.length and counts Short-type questions (which have no
selectable options) as unanswered; modify calculateScore to first derive the
list of answerable items (e.g., filter qaPairs by presence of selectable options
or by type !== 'Short', using the same property your rendering logic uses), then
iterate that filtered array to compare userAnswers (mapping indexes
appropriately) against each item's answer and increment score, and finally use
filtered.length as the denominator in the alert so only answerable questions
affect the reported score.

Comment on lines +226 to +230
<div
key={idx}
className="mb-1 sm:mb-2 cursor-pointer"
onClick={() => handleAnswerSelect(index, option)}
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the file exists and read the relevant section
if [ -f "eduaid_web/src/pages/Output.jsx" ]; then
  echo "=== Lines 226-230 of Output.jsx ==="
  sed -n '226,230p' eduaid_web/src/pages/Output.jsx
  echo ""
  echo "=== Context: Lines 215-240 ==="
  sed -n '215,240p' eduaid_web/src/pages/Output.jsx
else
  echo "File not found at eduaid_web/src/pages/Output.jsx"
  find . -name "Output.jsx" -type f 2>/dev/null | head -5
fi

Repository: AOSSIE-Org/EduAid

Length of output: 1736


🏁 Script executed:

# Let's find the handleAnswerSelect function and understand the full context
echo "=== Searching for handleAnswerSelect function ==="
rg -n "handleAnswerSelect" eduaid_web/src/pages/Output.jsx -A 5 -B 2

echo ""
echo "=== Searching for userAnswers state ==="
rg -n "userAnswers" eduaid_web/src/pages/Output.jsx -B 2 -A 2 | head -40

echo ""
echo "=== Checking for any keyboard event handlers in the file ==="
rg -n "onKeyDown|onKeyUp|onKeyPress" eduaid_web/src/pages/Output.jsx

Repository: AOSSIE-Org/EduAid

Length of output: 1323


🏁 Script executed:

# Check if there's any conditional styling based on userAnswers[index] === option
echo "=== Checking for conditional styling or aria attributes ==="
sed -n '220,245p' eduaid_web/src/pages/Output.jsx

echo ""
echo "=== Full render section of answer options ==="
sed -n '210,250p' eduaid_web/src/pages/Output.jsx

echo ""
echo "=== Searching for any visual feedback patterns related to selected answers ==="
rg -n "className.*ring|className.*selected|className.*active|aria-" eduaid_web/src/pages/Output.jsx | head -20

Repository: AOSSIE-Org/EduAid

Length of output: 3398


Use a real button for each answer choice.

A clickable div is not keyboard-focusable and exposes no semantic selected state, so keyboard and assistive-tech users cannot reliably complete the quiz. The quiz also provides no visual feedback that an answer choice was selected—the selected state is tracked in the userAnswers object (used only for scoring) but never displayed to users.

Suggested fix
-                          <div
+                          <button
                             key={idx}
-                            className="mb-1 sm:mb-2 cursor-pointer"
+                            type="button"
+                            aria-pressed={userAnswers[index] === option}
+                            className={`mb-1 sm:mb-2 block w-full text-left ${
+                              userAnswers[index] === option
+                                ? "ring-2 ring-[`#00CBE7`] rounded-md"
+                                : ""
+                            }`}
                             onClick={() => handleAnswerSelect(index, option)}
                           >
                                 <span className="text-[`#E4E4E4`] text-xs sm:text-sm">
                                   Option {idx + 1}:
                                 </span>{" "}
                                 <span className="text-[`#FFF4F4`] text-sm sm:text-base">
                                   {option}
                                 </span>
-                              </div>
+                              </button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Output.jsx` around lines 226 - 230, Replace the
clickable <div> for each answer choice with a real <button type="button"> so it
is keyboard-focusable and accessible; keep the existing onClick that calls
handleAnswerSelect(index, option) but also add an accessible selected state like
aria-pressed={userAnswers[index] === option} (or aria-selected if inside a
role="listbox") and apply a conditional CSS class when userAnswers[index] ===
option so the selection is visually indicated; ensure the button uses the same
key={idx} and preserves other props/styling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant