Skip to content

Refactor(eduaid_web): Clean up QA parsing and utilities, reduce duplication#640

Open
ashi2004 wants to merge 1 commit into
AOSSIE-Org:mainfrom
ashi2004:refactor/qa-utils-cleanup-clean
Open

Refactor(eduaid_web): Clean up QA parsing and utilities, reduce duplication#640
ashi2004 wants to merge 1 commit into
AOSSIE-Org:mainfrom
ashi2004:refactor/qa-utils-cleanup-clean

Conversation

@ashi2004
Copy link
Copy Markdown

@ashi2004 ashi2004 commented Mar 29, 2026

Fixes #639

Summary:

This PR cleans up repeated logic across the project by moving common functionality into shared utility files. It mainly focuses on QA parsing, shuffling, API handling, and some small accessibility improvements.

What’s changed:

  • Moved common QA parsing and shuffle logic into a shared utility
  • Reused the same logic across pages like Text_Input and Output
  • Simplified API usage to make it more consistent
  • Added some basic accessibility improvements (buttons, attributes, etc.)
  • Reduced duplicate code across multiple components

Why this matters:

  • Makes the codebase easier to manage and understand
  • Reduces repetition and chances of bugs
  • Keeps things more consistent across the app

Files involved:

  • src/pages/Text_Input.jsx
  • src/pages/Output.jsx
  • src/pages/Home.jsx
  • src/pages/Question_Type.jsx
  • src/utils/quizUtils.js
  • src/utils/githubStars.js

Notes:

  • No major behavior changes expected
  • Existing functionality should work as before

Summary by CodeRabbit

  • New Features

    • Added GitHub repository star count caching with automatic daily refresh.
    • Added helper message for question type selection step.
    • Added file type filtering for document uploads (.pdf, .mp3, .wav, .txt).
  • Bug Fixes

    • Fixed loading state when no input is provided.
  • Refactor

    • Extracted quiz utility functions to a shared module for better code organization and reusability.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

This PR extracts repeated QA parsing, shuffling, and API handling logic from individual page components into centralized utility modules (quizUtils.js, githubStars.js), reducing code duplication and improving maintainability. Accessibility attributes are added to form elements across multiple pages.

Changes

Cohort / File(s) Summary
Utility Module Creation
src/utils/githubStars.js, src/utils/quizUtils.js
New utility modules for shared functionality: getCachedGithubStars() manages GitHub API caching with localStorage; quizUtils.js exports shuffleArray(), getEndpointForQuestionType(), saveQuizHistory(), and parseQaPairs() for quiz operations and data normalization.
Component Refactoring
src/pages/Home.jsx, src/pages/Output.jsx, src/pages/Text_Input.jsx
Replaced inline utility logic with imported functions; Home now uses getCachedGithubStars() for star fetching; Output and Text_Input use shuffleArray and other quiz utilities; Text_Input adds accessibility labels and file input constraints.
Accessibility & UI Improvements
src/pages/Question_Type.jsx, src/pages/Text_Input.jsx
Added aria-pressed, aria-disabled, aria-hidden, aria-label attributes; converted clipboard icon to semantic span; added helper messages; set explicit type="button" on buttons; restricted file input to .pdf,.mp3,.wav,.txt.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #383: Modified Output.jsx shuffling logic; this PR extracts that shuffleArray() implementation into a shared utility module, consolidating the approach across components.

Poem

🐰 Utilities bundled, duplicates shed,
Shared logic springs forth from code spread,
Shuffle and parse in one place reside,
GitHub stars cached with localStorage pride,
Accessibility flourishes—clarity's thread! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective of the PR: refactoring code to extract shared QA parsing and utilities while reducing duplication across components.
Linked Issues check ✅ Passed All objectives from issue #639 are met: shared utilities created (quizUtils.js, githubStars.js), logic extracted and reused across pages (Text_Input, Output, Home), API usage standardized, and accessibility improvements added.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #639 objectives. No extraneous modifications beyond the stated scope of refactoring utilities and reducing duplication.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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: 3

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/Question_Type.jsx (1)

38-76: ⚠️ Potential issue | 🔴 Critical

Critical: Broken JSX syntax will prevent compilation.

The JSX structure is completely malformed. The <button> opening tag (lines 38-39), aria-pressed attribute (line 45), and </button> closing tag (line 76) are scattered in wrong locations instead of properly wrapping each option element.

Static analysis confirms:

  • Line 40: "expected ... but instead found }"
  • Line 41: "expected > but instead found <"
  • Lines 38-40: "Expected corresponding JSX closing tag for 'button'"

The intent appears to be converting each clickable <div> option into a semantic <button>, but the edits were applied incorrectly. The button tags and attributes need to be inside the .map() loop, replacing the <div> wrapper.

🐛 Proposed fix to correct the JSX structure

Remove the misplaced lines 38-39, 45, and 76, then properly convert each option <div> to a <button>:

-              <button
-                type="button"
         {/* Title */}
         <div className="text-center">
           <h2 className="text-white text-3xl sm:text-4xl font-extrabold">
             What's on your Mind?
           </h2>
-                aria-pressed={selectedOption === option.id}
         {/* Options */}
         <div className="flex flex-col items-center mt-8 gap-4 w-full">
           {[
             { id: "get_shortq", label: "Short-Answer Type Questions" },
             { id: "get_mcq", label: "Multiple Choice Questions" },
             { id: "get_boolq", label: "True/False Questions" },
             { id: "get_problems", label: "All Questions" },
           ].map((option) => (
-            <div
+            <button
+              type="button"
               key={option.id}
               onClick={() => handleOptionClick(option.id)}
               className={`w-full max-w-xl flex items-center gap-6 px-6 py-5 rounded-xl cursor-pointer bg-[`#202838`] bg-opacity-50 hover:bg-opacity-70 transition-all duration-200 ${
                 selectedOption === option.id ? "ring-2 ring-[`#00CBE7`]" : ""
               }`}
-              role="button"
-              tabIndex={0}
-              onKeyDown={(e) => {
-                if (e.key === "Enter") handleOptionClick(option.id);
-              }}
+              aria-pressed={selectedOption === option.id}
             >
               <div
                 className={`w-10 h-10 rounded-full flex-shrink-0 ${
                   selectedOption === option.id
                     ? "bg-gradient-to-b from-[`#405EED`] to-[`#01CBE7`]"
                     : "bg-[`#999C9D`]"
                 }`}
               ></div>
               <div className="text-white text-xl sm:text-2xl font-medium">
                 {option.label}
               </div>
-            </button>
+            </button>
           ))}

Note: When using a semantic <button>, role="button", tabIndex={0}, and onKeyDown handlers are no longer needed—the button provides these behaviors natively.

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

In `@eduaid_web/src/pages/Question_Type.jsx` around lines 38 - 76, The JSX is
broken because a stray opening <button> and attributes were inserted outside the
.map() loop and a closing </button> ended up after the options; fix by removing
the misplaced opening tag/attributes (the lines that start the button before the
Title and the stray aria-pressed and final </button>) and instead convert the
option wrapper <div key={option.id} ...> inside the .map() into a semantic
<button key={option.id} onClick={() => handleOptionClick(option.id)}
aria-pressed={selectedOption === option.id} className={...}> (drop
role="button", tabIndex, and onKeyDown since <button> is native). Keep the inner
color circle and label markup, and preserve the selectedOption conditional class
logic and onClick handler in the Button so compilation and accessibility are
correct in Question_Type.jsx.
🧹 Nitpick comments (2)
eduaid_web/src/pages/Question_Type.jsx (1)

83-89: Potential race condition: localStorage.setItem may not complete before navigation.

The onClick handler calls handleSaveToLocalStorage() synchronously, but <Link> navigation may proceed immediately. While localStorage.setItem is synchronous in practice, this pattern can be fragile if the handler ever becomes async or if additional logic is added.

Consider using useNavigate for programmatic navigation after the save completes:

♻️ Suggested refactor for explicit navigation control
+import { Link, useNavigate } from "react-router-dom";
 
 const Question_Type = () => {
   const [selectedOption, setSelectedOption] = useState(null);
+  const navigate = useNavigate();
 
-  const handleSaveToLocalStorage = () => {
+  const handleSaveAndNavigate = () => {
     if (selectedOption) {
       localStorage.setItem("selectedQuestionType", selectedOption);
+      navigate("/input");
     }
   };

Then replace the <Link> with a button:

-            <Link
-              to="/input"
-              onClick={handleSaveToLocalStorage}
-              className="inline-block rounded-2xl ..."
-            >
+            <button
+              type="button"
+              onClick={handleSaveAndNavigate}
+              className="rounded-2xl ..."
+            >
               Fire Up 🚀
-            </Link>
+            </button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Question_Type.jsx` around lines 83 - 89, The Link's
onClick may navigate before storage logic completes; change to programmatic
navigation by importing useNavigate and call navigate('/input') only after
handleSaveToLocalStorage finishes (or after await if you make it async). Replace
the <Link> with a button that calls an async wrapper (e.g., saveThenNavigate)
which invokes handleSaveToLocalStorage and then calls navigate('/input');
reference the existing handleSaveToLocalStorage function and useNavigate hook to
locate where to add the wrapper and swap the Link component.
eduaid_web/src/utils/quizUtils.js (1)

32-33: Harden history parsing against malformed localStorage JSON.

Line 32 can throw if last5Quizzes contains invalid JSON, which breaks the post-success flow even after questions are generated.

Resilient parsing fallback
-  const last5Quizzes = JSON.parse(localStorage.getItem("last5Quizzes") || "[]");
+  let last5Quizzes = [];
+  try {
+    last5Quizzes = JSON.parse(localStorage.getItem("last5Quizzes") || "[]");
+    if (!Array.isArray(last5Quizzes)) last5Quizzes = [];
+  } catch {
+    last5Quizzes = [];
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/utils/quizUtils.js` around lines 32 - 33, The JSON.parse of
localStorage.getItem("last5Quizzes") can throw on malformed data; wrap the parse
in a try/catch (or use a safeParse helper) to fall back to an empty array when
parsing fails, then push quizDetails into that safe array (variable
last5Quizzes) and continue to update localStorage; reference the existing
last5Quizzes variable and quizDetails so you only change the retrieval/parse
logic and preserve subsequent push/set operations.
🤖 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`:
- Line 168: The helper text shown to users is out of sync with the file input's
accepted types: the input element (the <input ... ref={fileInputRef}
onChange={handleFileUpload} accept=".pdf,.mp3,.wav,.txt" />) accepts .wav and
.txt but the UI copy still says only “PDF, MP3 supported”; update the visible
helper/caption text where fileInputRef/handleFileUpload are referenced so it
lists all accepted types (PDF, MP3, WAV, TXT) or otherwise matches the accept
attribute, and ensure any accessible hints (aria-describedby or labels) are
updated as well.

In `@eduaid_web/src/utils/githubStars.js`:
- Around line 1-2: The constants STARS_KEY and FETCH_TIME_KEY are too generic
and collide with the extension; rename them to namespaced keys (e.g., STARS_KEY
-> "eduaid_web:stars" and FETCH_TIME_KEY -> "eduaid_web:fetchTime") by updating
the constants in githubStars.js and ensuring every usage of STARS_KEY and
FETCH_TIME_KEY (reads/writes/removals) in this module or imports that reference
these symbols uses the new names so localStorage entries for the web app are
isolated from the extension.

In `@eduaid_web/src/utils/quizUtils.js`:
- Around line 69-98: The current logic pushes the same qaPairsFromStorage.output
twice (once as MCQ when output_mcq exists or questionType === "get_mcq", and
again as Short), causing duplicates in combinedQaPairs; change the branching so
output_mcq and output are treated as distinct sources: if
qaPairsFromStorage.output_mcq exists iterate that array for MCQs; only treat
qaPairsFromStorage.output as MCQ when questionType === "get_mcq" AND no
output_mcq is present; ensure the final else-if that pushes Short entries
explicitly excludes cases where questionType === "get_mcq" or output_mcq is
present so the same array is not parsed twice into combinedQaPairs.

---

Outside diff comments:
In `@eduaid_web/src/pages/Question_Type.jsx`:
- Around line 38-76: The JSX is broken because a stray opening <button> and
attributes were inserted outside the .map() loop and a closing </button> ended
up after the options; fix by removing the misplaced opening tag/attributes (the
lines that start the button before the Title and the stray aria-pressed and
final </button>) and instead convert the option wrapper <div key={option.id}
...> inside the .map() into a semantic <button key={option.id} onClick={() =>
handleOptionClick(option.id)} aria-pressed={selectedOption === option.id}
className={...}> (drop role="button", tabIndex, and onKeyDown since <button> is
native). Keep the inner color circle and label markup, and preserve the
selectedOption conditional class logic and onClick handler in the Button so
compilation and accessibility are correct in Question_Type.jsx.

---

Nitpick comments:
In `@eduaid_web/src/pages/Question_Type.jsx`:
- Around line 83-89: The Link's onClick may navigate before storage logic
completes; change to programmatic navigation by importing useNavigate and call
navigate('/input') only after handleSaveToLocalStorage finishes (or after await
if you make it async). Replace the <Link> with a button that calls an async
wrapper (e.g., saveThenNavigate) which invokes handleSaveToLocalStorage and then
calls navigate('/input'); reference the existing handleSaveToLocalStorage
function and useNavigate hook to locate where to add the wrapper and swap the
Link component.

In `@eduaid_web/src/utils/quizUtils.js`:
- Around line 32-33: The JSON.parse of localStorage.getItem("last5Quizzes") can
throw on malformed data; wrap the parse in a try/catch (or use a safeParse
helper) to fall back to an empty array when parsing fails, then push quizDetails
into that safe array (variable last5Quizzes) and continue to update
localStorage; reference the existing last5Quizzes variable and quizDetails so
you only change the retrieval/parse logic and preserve subsequent push/set
operations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 08fa0d55-388c-4e83-ad44-672fadc8fbb3

📥 Commits

Reviewing files that changed from the base of the PR and between 2038116 and 93e573e.

📒 Files selected for processing (6)
  • eduaid_web/src/pages/Home.jsx
  • eduaid_web/src/pages/Output.jsx
  • eduaid_web/src/pages/Question_Type.jsx
  • eduaid_web/src/pages/Text_Input.jsx
  • eduaid_web/src/utils/githubStars.js
  • eduaid_web/src/utils/quizUtils.js

Comment thread eduaid_web/src/pages/Text_Input.jsx
Comment thread eduaid_web/src/utils/githubStars.js
Comment thread eduaid_web/src/utils/quizUtils.js
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.

[ENHANCEMENT]: Clean up QA parsing and utility functions in eduaid_web for better performance

1 participant