From 2b488aed4812925c15364b6e133f6b242311140c Mon Sep 17 00:00:00 2001 From: Mahek Gupta Date: Thu, 26 Feb 2026 20:02:33 +0530 Subject: [PATCH 1/9] feat: improve landing page with how-it-works section and strong CTA --- eduaid_web/src/pages/Home.jsx | 124 ++++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 28 deletions(-) diff --git a/eduaid_web/src/pages/Home.jsx b/eduaid_web/src/pages/Home.jsx index e00a0d23..f882191f 100644 --- a/eduaid_web/src/pages/Home.jsx +++ b/eduaid_web/src/pages/Home.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; import "../index.css"; -import logo_trans from "../assets/aossie_logo_transparent.png" +import logo_trans from "../assets/aossie_logo_transparent.png"; import starsImg from "../assets/stars.png"; import arrow from "../assets/arrow.png"; import gitStar from "../assets/gitStar.png"; @@ -12,7 +12,9 @@ const Home = () => { const [error, setError] = useState(""); async function fetchGitHubStars() { - const response = await fetch("https://api.github.com/repos/AOSSIE-Org/EduAid"); + const response = await fetch( + "https://api.github.com/repos/AOSSIE-Org/EduAid" + ); if (!response.ok) throw new Error("Failed to fetch stars"); const data = await response.json(); return data.stargazers_count; @@ -27,7 +29,11 @@ const Home = () => { const storedStars = localStorage.getItem("stars"); const storedTime = localStorage.getItem("fetchTime"); - if (storedStars && storedTime && !isMoreThanOneDayOld(parseInt(storedTime))) { + if ( + storedStars && + storedTime && + !isMoreThanOneDayOld(parseInt(storedTime)) + ) { setStars(parseInt(storedStars)); } else { fetchGitHubStars() @@ -41,10 +47,11 @@ const Home = () => { }, []); return ( -
+
-
- logo +
+ {/* Logo */} + logo {/* Heading */}

@@ -56,54 +63,115 @@ const Home = () => {

- {/* Subtitle */} -
-

A tool that can auto-generate short quizzes

-
-

based on user input

- stars -
+ {/* Improved Subtitle */} +
+

+ Turn your notes into interactive quizzes in seconds. +

+

+ Upload your PDF or DOC file and start practicing instantly. +

- {/* Features */} -
+ {/* Feature Highlights */} +
{[ - "Doc/Audio Input", - "In-depth questions gen", - "Dynamic Google Form Integration", + "PDF / DOC Upload", + "AI-powered Quiz Generation", + "Instant Practice Mode", ].map((feature, i) => (
-
{feature}
+
+ {feature} +
))}
- {/* Buttons */} -
+ {/* Primary Buttons */} +
- + -
+ {/* How It Works Section */} +
+

+ How It Works +

+ +
+ {[ + "Upload your PDF or DOC file", + "AI generates quiz questions instantly", + "Start practicing and test your knowledge", + ].map((step, i) => ( +
+

+ Step {i + 1} +

+

{step}

+
+ ))} +
+
+ + {/* Why EduAid Section */} +
+

+ Why EduAid? +

+ +
+ {[ + "Saves hours of manual question creation", + "Encourages active recall learning", + "Instant feedback improves retention", + "Perfect for exams and revision", + ].map((point, i) => ( +
+

{point}

+
+ ))} +
+
+ + {/* Final CTA */} +
+ + + +
+ {/* GitHub Stars */}
GitHub Star @@ -125,4 +193,4 @@ const Home = () => { ); }; -export default Home; +export default Home; \ No newline at end of file From ce79c02efff534f222824f99857e646766ed915f Mon Sep 17 00:00:00 2001 From: Mahek Gupta Date: Thu, 26 Feb 2026 20:14:56 +0530 Subject: [PATCH 2/9] feat: improve landing page with how-it-works section and strong CTA --- eduaid_web/src/pages/Home.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eduaid_web/src/pages/Home.jsx b/eduaid_web/src/pages/Home.jsx index f882191f..2781bf0e 100644 --- a/eduaid_web/src/pages/Home.jsx +++ b/eduaid_web/src/pages/Home.jsx @@ -47,7 +47,7 @@ const Home = () => { }, []); return ( -
+
{/* Logo */} From 39efd8651bf1f3e3ce07cfaadca77ef71823023a Mon Sep 17 00:00:00 2001 From: Mahek Gupta Date: Thu, 26 Feb 2026 20:32:33 +0530 Subject: [PATCH 3/9] fix: address CodeRabbit review (safe parsing + remove nested buttons) --- eduaid_web/src/pages/Home.jsx | 60 ++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/eduaid_web/src/pages/Home.jsx b/eduaid_web/src/pages/Home.jsx index 2781bf0e..60bafec3 100644 --- a/eduaid_web/src/pages/Home.jsx +++ b/eduaid_web/src/pages/Home.jsx @@ -29,17 +29,20 @@ const Home = () => { const storedStars = localStorage.getItem("stars"); const storedTime = localStorage.getItem("fetchTime"); + const cachedStars = Number(storedStars); + const cachedTime = Number(storedTime); + if ( - storedStars && - storedTime && - !isMoreThanOneDayOld(parseInt(storedTime)) + Number.isFinite(cachedStars) && + Number.isFinite(cachedTime) && + !isMoreThanOneDayOld(cachedTime) ) { - setStars(parseInt(storedStars)); + setStars(cachedStars); } else { fetchGitHubStars() .then((starCount) => { setStars(starCount); - localStorage.setItem("stars", starCount); + localStorage.setItem("stars", String(starCount)); localStorage.setItem("fetchTime", Date.now().toString()); }) .catch(() => setError("Failed to fetch stars")); @@ -50,8 +53,9 @@ const Home = () => {
+ {/* Logo */} - logo + EduAid Logo {/* Heading */}

@@ -63,11 +67,9 @@ const Home = () => {

- {/* Improved Subtitle */} + {/* Subtitle */}
-

- Turn your notes into interactive quizzes in seconds. -

+

Turn your notes into interactive quizzes in seconds.

Upload your PDF or DOC file and start practicing instantly.

@@ -94,22 +96,26 @@ const Home = () => { {/* Primary Buttons */}
- - + + + Upload & Generate Quiz + - - + + View Previous Work + +
- {/* How It Works Section */} + {/* How It Works */}

How It Works @@ -134,7 +140,7 @@ const Home = () => {

- {/* Why EduAid Section */} + {/* Why EduAid */}

Why EduAid? @@ -159,10 +165,11 @@ const Home = () => { {/* Final CTA */}
- - + + Turn Your Notes Into a Quiz Now
@@ -187,6 +194,7 @@ const Home = () => {

+
From 1449774207947bbc6cb6484d6c3c110597354ecb Mon Sep 17 00:00:00 2001 From: Mahek Gupta Date: Thu, 26 Feb 2026 20:42:36 +0530 Subject: [PATCH 4/9] fix: harden cache guard and validate GitHub API response per CodeRabbit review --- eduaid_web/src/pages/Home.jsx | 49 ++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/eduaid_web/src/pages/Home.jsx b/eduaid_web/src/pages/Home.jsx index 60bafec3..f581f7f4 100644 --- a/eduaid_web/src/pages/Home.jsx +++ b/eduaid_web/src/pages/Home.jsx @@ -15,8 +15,18 @@ const Home = () => { const response = await fetch( "https://api.github.com/repos/AOSSIE-Org/EduAid" ); - if (!response.ok) throw new Error("Failed to fetch stars"); + const data = await response.json(); + + if ( + !response.ok || + !data || + typeof data.stargazers_count !== "number" + ) { + console.error("Invalid GitHub API response:", data); + throw new Error("Invalid GitHub response"); + } + return data.stargazers_count; } @@ -29,24 +39,27 @@ const Home = () => { const storedStars = localStorage.getItem("stars"); const storedTime = localStorage.getItem("fetchTime"); - const cachedStars = Number(storedStars); - const cachedTime = Number(storedTime); - - if ( - Number.isFinite(cachedStars) && - Number.isFinite(cachedTime) && - !isMoreThanOneDayOld(cachedTime) - ) { - setStars(cachedStars); - } else { - fetchGitHubStars() - .then((starCount) => { - setStars(starCount); - localStorage.setItem("stars", String(starCount)); - localStorage.setItem("fetchTime", Date.now().toString()); - }) - .catch(() => setError("Failed to fetch stars")); + if (storedStars !== null && storedTime !== null) { + const cachedStars = Number(storedStars); + const cachedTime = Number(storedTime); + + if ( + Number.isFinite(cachedStars) && + Number.isFinite(cachedTime) && + !isMoreThanOneDayOld(cachedTime) + ) { + setStars(cachedStars); + return; + } } + + fetchGitHubStars() + .then((starCount) => { + setStars(starCount); + localStorage.setItem("stars", String(starCount)); + localStorage.setItem("fetchTime", Date.now().toString()); + }) + .catch(() => setError("Failed to fetch stars")); }, []); return ( From a4ea480c1852a38659e92442c748ee60062ac6e0 Mon Sep 17 00:00:00 2001 From: Mahek Gupta Date: Thu, 26 Feb 2026 21:16:39 +0530 Subject: [PATCH 5/9] Fix: Prevent infinite loading when backend is unreachable by adding request timeout and proper error handling (Closes #469) --- eduaid_desktop/main.js | 76 ++++------ eduaid_web/src/pages/Text_Input.jsx | 210 ++++++++++------------------ 2 files changed, 98 insertions(+), 188 deletions(-) diff --git a/eduaid_desktop/main.js b/eduaid_desktop/main.js index b0346138..3d433d09 100644 --- a/eduaid_desktop/main.js +++ b/eduaid_desktop/main.js @@ -1,28 +1,22 @@ const { app, BrowserWindow, Menu, shell, dialog, ipcMain } = require('electron'); const path = require('path'); -const fs = require('fs'); const config = require('./config'); const https = require('https'); const http = require('http'); -// Keep a global reference of the window object let mainWindow; function createWindow() { - // Create the browser window using configuration mainWindow = new BrowserWindow({ ...config.windowOptions, icon: config.window.icon, webPreferences: config.window.webPreferences, - show: false, // Don't show until ready + show: false, titleBarStyle: config.window.titleBarStyle }); - // Determine which URL to load based on environment if (!app.isPackaged) { - // In development, load from the dev server mainWindow.loadURL(config.webUrl); - // Open DevTools in development if (config.devTools) { mainWindow.webContents.openDevTools(); } @@ -30,36 +24,29 @@ function createWindow() { mainWindow.loadFile(config.webPath); } - // Show window when ready to prevent visual flash mainWindow.once('ready-to-show', () => { mainWindow.show(); - - // Focus on the window if (config.isDevelopment) { mainWindow.focus(); } }); - // Handle window closed mainWindow.on('closed', () => { mainWindow = null; }); - // Handle external links mainWindow.webContents.setWindowOpenHandler(({ url }) => { shell.openExternal(url); return { action: 'deny' }; }); - // Prevent navigation to external URLs mainWindow.webContents.on('will-navigate', (event, navigationUrl) => { const parsedUrl = new URL(navigationUrl); - - // Check if the URL is in our allowed origins - const isAllowed = config.security.allowedOrigins.some(origin => + + const isAllowed = config.security.allowedOrigins.some(origin => parsedUrl.origin === origin || navigationUrl.startsWith(origin) ); - + if (!isAllowed) { event.preventDefault(); shell.openExternal(navigationUrl); @@ -67,7 +54,6 @@ function createWindow() { }); } -// Create application menu function createMenu() { const template = [ { @@ -86,9 +72,7 @@ function createMenu() { { label: 'Quit', accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q', - click: () => { - app.quit(); - } + click: () => app.quit() } ] }, @@ -149,7 +133,6 @@ function createMenu() { } ]; - // macOS specific menu adjustments if (process.platform === 'darwin') { template.unshift({ label: app.getName(), @@ -165,28 +148,17 @@ function createMenu() { { role: 'quit' } ] }); - - // Window menu - template[4].submenu = [ - { role: 'close' }, - { role: 'minimize' }, - { role: 'zoom' }, - { type: 'separator' }, - { role: 'front' } - ]; } const menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); } -// App event handlers app.whenReady().then(() => { createWindow(); createMenu(); app.on('activate', () => { - // On macOS, re-create window when dock icon is clicked if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } @@ -194,13 +166,11 @@ app.whenReady().then(() => { }); app.on('window-all-closed', () => { - // On macOS, keep app running even when all windows are closed if (process.platform !== 'darwin') { app.quit(); } }); -// Security: Prevent new window creation app.on('web-contents-created', (event, contents) => { contents.on('new-window', (event, navigationUrl) => { event.preventDefault(); @@ -208,10 +178,8 @@ app.on('web-contents-created', (event, contents) => { }); }); -// Handle certificate errors app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { if (url.startsWith('http://localhost:')) { - // Allow localhost in development event.preventDefault(); callback(true); } else { @@ -219,10 +187,14 @@ app.on('certificate-error', (event, webContents, url, error, certificate, callba } }); -// API request handler for the renderer process -ipcMain.handle('api-request', async (event, { endpoint, options }) => { + +// ================================ +// πŸ”₯ API REQUEST HANDLER (FIXED) +// ================================ +ipcMain.handle('api-request', async (event, { endpoint, options = {} }) => { return new Promise((resolve, reject) => { const apiUrl = new URL(endpoint, config.apiUrl); + const requestOptions = { method: options.method || 'GET', headers: { @@ -232,32 +204,40 @@ ipcMain.handle('api-request', async (event, { endpoint, options }) => { }; const protocol = apiUrl.protocol === 'https:' ? https : http; - + const req = protocol.request(apiUrl, requestOptions, (res) => { let data = ''; - + res.on('data', (chunk) => { data += chunk; }); - + res.on('end', () => { try { - const jsonData = JSON.parse(data); + const parsed = data ? JSON.parse(data) : null; + resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode, - data: jsonData + data: parsed }); - } catch (error) { + } catch (err) { resolve({ - ok: res.statusCode >= 200 && res.statusCode < 300, + ok: false, status: res.statusCode, - data: data + data: null, + error: 'Invalid JSON response' }); } }); }); + // πŸ”₯ Timeout after 15 seconds + req.setTimeout(15000, () => { + req.destroy(); + reject(new Error('Request timed out after 15 seconds')); + }); + req.on('error', (error) => { reject(error); }); @@ -268,4 +248,4 @@ ipcMain.handle('api-request', async (event, { endpoint, options }) => { req.end(); }); -}); +}); \ No newline at end of file diff --git a/eduaid_web/src/pages/Text_Input.jsx b/eduaid_web/src/pages/Text_Input.jsx index e341d331..bbda88eb 100644 --- a/eduaid_web/src/pages/Text_Input.jsx +++ b/eduaid_web/src/pages/Text_Input.jsx @@ -1,69 +1,74 @@ import React, { useState, useRef } from "react"; import "../index.css"; -import logo_trans from "../assets/aossie_logo_transparent.png" +import logo_trans from "../assets/aossie_logo_transparent.png"; import stars from "../assets/stars.png"; import cloud from "../assets/cloud.png"; import { FaClipboard } from "react-icons/fa"; import Switch from "react-switch"; -import { Link,useNavigate } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import apiClient from "../utils/apiClient"; const Text_Input = () => { const navigate = useNavigate(); + const [text, setText] = useState(""); const [difficulty, setDifficulty] = useState("Easy Difficulty"); const [numQuestions, setNumQuestions] = useState(10); const [loading, setLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const fileInputRef = useRef(null); - const [fileContent, setFileContent] = useState(""); const [docUrl, setDocUrl] = useState(""); const [isToggleOn, setIsToggleOn] = useState(0); const toggleSwitch = () => { - setIsToggleOn((isToggleOn + 1) % 2); + setIsToggleOn((prev) => (prev + 1) % 2); }; const handleFileUpload = async (event) => { const file = event.target.files[0]; - if (file) { - const formData = new FormData(); - formData.append("file", file); + if (!file) return; - try { - const data = await apiClient.postFormData("/upload", formData); - setText(data.content || data.error); - } catch (error) { - console.error("Error uploading file:", error); - setText("Error uploading file"); - } + const formData = new FormData(); + formData.append("file", file); + + try { + const data = await apiClient.postFormData("/upload", formData); + setText(data.content || data.error); + } catch (error) { + console.error("Error uploading file:", error); + setErrorMessage("File upload failed."); } }; const handleClick = (event) => { - event.preventDefault(); // Prevent default behavior - event.stopPropagation(); // Stop event propagation - - // Open file input dialog + event.preventDefault(); + event.stopPropagation(); fileInputRef.current.click(); }; const handleSaveToLocalStorage = async () => { setLoading(true); + setErrorMessage(""); + + try { + if (docUrl) { + const data = await apiClient.post("/get_content", { + document_url: docUrl, + }); - // Check if a Google Doc URL is provided - if (docUrl) { - try { - const data = await apiClient.post("/get_content", { document_url: docUrl }); setDocUrl(""); - setText(data || "Error in retrieving"); - } catch (error) { - console.error("Error:", error); - setText("Error retrieving Google Doc content"); - } finally { + setText(data || "Error retrieving content"); setLoading(false); + return; } - } else if (text) { - // Proceed with existing functionality for local storage + + if (!text) { + setErrorMessage("Please enter text before proceeding."); + setLoading(false); + return; + } + localStorage.setItem("textContent", text); localStorage.setItem("difficulty", difficulty); localStorage.setItem("numQuestions", numQuestions); @@ -73,45 +78,38 @@ const Text_Input = () => { difficulty, localStorage.getItem("selectedQuestionType") ); + } catch (error) { + console.error("Error:", error); + setErrorMessage("Backend unavailable or request timed out."); + setLoading(false); } }; - const handleDifficultyChange = (e) => { - setDifficulty(e.target.value); - }; - - const incrementQuestions = () => { - setNumQuestions((prev) => prev + 1); - }; - - const decrementQuestions = () => { - setNumQuestions((prev) => (prev > 0 ? prev - 1 : 0)); - }; - const getEndpoint = (difficulty, questionType) => { if (difficulty !== "Easy Difficulty") { - if (questionType === "get_shortq") { - return "get_shortq_hard"; - } else if (questionType === "get_mcq") { - return "get_mcq_hard"; - } + if (questionType === "get_shortq") return "get_shortq_hard"; + if (questionType === "get_mcq") return "get_mcq_hard"; } return questionType; }; const sendToBackend = async (data, difficulty, questionType) => { - const endpoint = getEndpoint(difficulty, questionType); try { + const endpoint = getEndpoint(difficulty, questionType); + const requestData = { input_text: data, max_questions: numQuestions, use_mediawiki: isToggleOn, }; - const responseData = await apiClient.post(`/${endpoint}`, requestData); + const responseData = await apiClient.post( + `/${endpoint}`, + requestData + ); + localStorage.setItem("qaPairs", JSON.stringify(responseData)); - // Save quiz details to local storage const quizDetails = { difficulty, numQuestions, @@ -121,22 +119,29 @@ const Text_Input = () => { let last5Quizzes = JSON.parse(localStorage.getItem("last5Quizzes")) || []; + last5Quizzes.push(quizDetails); if (last5Quizzes.length > 5) { - last5Quizzes.shift(); // Keep only the last 5 quizzes + last5Quizzes.shift(); } - localStorage.setItem("last5Quizzes", JSON.stringify(last5Quizzes)); + + localStorage.setItem( + "last5Quizzes", + JSON.stringify(last5Quizzes) + ); navigate("/output"); } catch (error) { - console.error("Error:", error); + console.error("Backend error:", error); + setErrorMessage("Backend unavailable or request timed out."); } finally { - setLoading(false); + setLoading(false); // πŸ”₯ CRITICAL FIX } }; return (
+ {loading && (
@@ -144,6 +149,7 @@ const Text_Input = () => { )}
+ {/* Header */}
@@ -155,96 +161,21 @@ const Text_Input = () => {
- {/* Headline */} -
-
Enter the Content
-
- to Generate{" "} - Questionaries - stars -
-
- - {/* Textarea */} -
- -