diff --git a/eduaid_web/.dockerignore b/eduaid_web/.dockerignore
new file mode 100644
index 00000000..2eac0dc0
--- /dev/null
+++ b/eduaid_web/.dockerignore
@@ -0,0 +1,3 @@
+node_modules
+.git
+.env
\ No newline at end of file
diff --git a/eduaid_web/Dockerfile b/eduaid_web/Dockerfile
new file mode 100644
index 00000000..e211d89e
--- /dev/null
+++ b/eduaid_web/Dockerfile
@@ -0,0 +1,12 @@
+FROM node:18-alpine
+
+WORKDIR /app
+
+COPY package*.json ./
+RUN npm install
+
+COPY . .
+
+EXPOSE 3000
+
+CMD ["npm", "start"]
\ No newline at end of file
diff --git a/eduaid_web/src/index.css b/eduaid_web/src/index.css
index 31dc491c..9ddb4bda 100644
--- a/eduaid_web/src/index.css
+++ b/eduaid_web/src/index.css
@@ -2,6 +2,10 @@
@tailwind components;
@tailwind utilities;
+/* =========================
+ Base Styles
+ ========================= */
+
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
@@ -16,10 +20,38 @@ code {
monospace;
}
+/* =========================
+ Border Gradient
+ ========================= */
+
.border-gradient {
- border-width: 2px;
+ border-width: 2px;
border-style: solid;
border-image: linear-gradient(to right, #ff005c, #7600f2, #00cbe7);
border-image-slice: 1;
border-radius: 2rem;
+}
+
+/* =========================
+ Gradient Text Styling
+ ========================= */
+
+/* Shared Base Class */
+.gradient-text-base {
+ background: var(--gradient);
+ -webkit-background-clip: text;
+ background-clip: text;
+ -webkit-text-fill-color: transparent;
+ display: inline-block;
+ transform: translateZ(0);
+}
+
+/* Main Gradient */
+.gradient-text {
+ --gradient: linear-gradient(to right, #FF005C, #7600F2);
+}
+
+/* Secondary Gradient */
+.gradient-text-2 {
+ --gradient: linear-gradient(to right, #7600F2, #00CBE7);
}
\ No newline at end of file
diff --git a/eduaid_web/src/pages/Home.jsx b/eduaid_web/src/pages/Home.jsx
index e00a0d23..fc05dc23 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";
@@ -47,14 +47,8 @@ const Home = () => {
{/* Heading */}
-
@@ -125,4 +119,4 @@ const Home = () => {
);
};
-export default Home;
+export default Home;
\ No newline at end of file
diff --git a/eduaid_web/src/pages/Output.jsx b/eduaid_web/src/pages/Output.jsx
index 7add8fe1..fcf36483 100644
--- a/eduaid_web/src/pages/Output.jsx
+++ b/eduaid_web/src/pages/Output.jsx
@@ -7,420 +7,267 @@ import { FiShuffle, FiEdit2, FiCheck, FiX } from "react-icons/fi";
const Output = () => {
const [qaPairs, setQaPairs] = useState([]);
- const [questionType, setQuestionType] = useState(
- localStorage.getItem("selectedQuestionType")
- );
- const [pdfMode, setPdfMode] = useState("questions");
+ const [questionType] = useState(localStorage.getItem("selectedQuestionType"));
+
const [editingIndex, setEditingIndex] = useState(null);
const [editedQuestion, setEditedQuestion] = useState("");
const [editedAnswer, setEditedAnswer] = useState("");
const [editedOptions, setEditedOptions] = useState([]);
+ // Quiz State
+ const [quizMode, setQuizMode] = useState(false);
+ const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
+ const [selectedAnswers, setSelectedAnswers] = useState({});
+ const [showFeedback, setShowFeedback] = useState(false);
+ const [quizCompleted, setQuizCompleted] = useState(false);
+
+ // ===================== LOAD FROM STORAGE =====================
useEffect(() => {
- const handleClickOutside = (event) => {
- const dropdown = document.getElementById('pdfDropdown');
- if (dropdown && !dropdown.contains(event.target) &&
- !event.target.closest('button')) {
- dropdown.classList.add('hidden');
- }
- };
+ const data = JSON.parse(localStorage.getItem("qaPairs")) || {};
+ const combined = [];
+
+ if (data.output_mcq?.questions) {
+ data.output_mcq.questions.forEach((q) => {
+ combined.push({
+ question: q.question_statement,
+ options: q.options || [],
+ answer: q.answer,
+ question_type: "MCQ",
+ });
+ });
+ }
- document.addEventListener('mousedown', handleClickOutside);
- return () => document.removeEventListener('mousedown', handleClickOutside);
-}, []);
+ setQaPairs(combined);
+ }, []);
+ // ===================== SHUFFLE =====================
function shuffleArray(array) {
- const shuffledArray = [...array];
- for (let i = shuffledArray.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];
- }
- return shuffledArray;
+ return [...array].sort(() => Math.random() - 0.5);
}
const shuffledOptionsMap = useMemo(() => {
- return qaPairs.map((qaPair) => {
- const combinedOptions = qaPair.options
- ? [...qaPair.options, qaPair.answer]
- : [qaPair.answer];
- return shuffleArray(combinedOptions);
- });
+ return qaPairs.map((qaPair) =>
+ shuffleArray([...(qaPair.options || []), qaPair.answer])
+ );
}, [qaPairs]);
const handleShuffleQuestions = () => {
- if (editingIndex !== null) {
- handleCancelEdit();
- }
- const shuffled = shuffleArray(qaPairs);
- setQaPairs(shuffled);
+ setQaPairs(shuffleArray(qaPairs));
};
+ // ===================== EDIT =====================
const handleEditQuestion = (index) => {
setEditingIndex(index);
setEditedQuestion(qaPairs[index].question);
- setEditedAnswer(qaPairs[index].answer || "");
+ setEditedAnswer(qaPairs[index].answer);
setEditedOptions(qaPairs[index].options || []);
};
const handleSaveQuestion = (index) => {
- const updatedQaPairs = [...qaPairs];
- updatedQaPairs[index] = {
- ...updatedQaPairs[index],
+ const updated = [...qaPairs];
+ updated[index] = {
+ ...updated[index],
question: editedQuestion,
answer: editedAnswer,
options: editedOptions,
};
- setQaPairs(updatedQaPairs);
+ setQaPairs(updated);
setEditingIndex(null);
- setEditedQuestion("");
- setEditedAnswer("");
- setEditedOptions([]);
};
const handleCancelEdit = () => {
setEditingIndex(null);
- setEditedQuestion("");
- setEditedAnswer("");
- setEditedOptions([]);
};
- const handleOptionChange = (optionIndex, value) => {
- const updatedOptions = [...editedOptions];
- updatedOptions[optionIndex] = value;
- setEditedOptions(updatedOptions);
- };
+ // ===================== QUIZ RENDER =====================
+ const renderQuizMode = () => {
+ if (!qaPairs.length) {
+ return
No questions.
;
+ }
- useEffect(() => {
- const qaPairsFromStorage =
- JSON.parse(localStorage.getItem("qaPairs")) || {};
- if (qaPairsFromStorage) {
- const combinedQaPairs = [];
-
- if (qaPairsFromStorage["output_boolq"]) {
- qaPairsFromStorage["output_boolq"]["Boolean_Questions"].forEach(
- (question, index) => {
- combinedQaPairs.push({
- question,
- question_type: "Boolean",
- context: qaPairsFromStorage["output_boolq"]["Text"],
- });
- }
- );
- }
-
- if (qaPairsFromStorage["output_mcq"]) {
- qaPairsFromStorage["output_mcq"]["questions"].forEach((qaPair) => {
- combinedQaPairs.push({
- question: qaPair.question_statement,
- question_type: "MCQ",
- options: qaPair.options,
- answer: qaPair.answer,
- context: qaPair.context,
- });
- });
- }
-
- if (qaPairsFromStorage["output_mcq"] || questionType === "get_mcq") {
- qaPairsFromStorage["output"].forEach((qaPair) => {
- combinedQaPairs.push({
- question: qaPair.question_statement,
- question_type: "MCQ",
- options: qaPair.options,
- answer: qaPair.answer,
- context: qaPair.context,
- });
- });
- }
-
- if (questionType == "get_boolq") {
- qaPairsFromStorage["output"].forEach((qaPair) => {
- combinedQaPairs.push({
- question: qaPair,
- question_type: "Boolean",
- });
- });
- } else if (qaPairsFromStorage["output"] && questionType !== "get_mcq") {
- qaPairsFromStorage["output"].forEach((qaPair) => {
- combinedQaPairs.push({
- question:
- qaPair.question || qaPair.question_statement || qaPair.Question,
- options: qaPair.options,
- answer: qaPair.answer || qaPair.Answer,
- context: qaPair.context,
- question_type: "Short",
- });
- });
- }
+ const current = qaPairs[currentQuestionIndex];
- setQaPairs(combinedQaPairs);
- }
- }, []);
+ if (quizCompleted) {
+ const score = qaPairs.filter(
+ (q, i) => selectedAnswers[i] === q.answer
+ ).length;
- const generateGoogleForm = async () => {
- try {
- const result = await apiClient.post("/generate_gform", {
- qa_pairs: qaPairs,
- question_type: questionType,
- });
- const formUrl = result.form_link;
- window.open(formUrl, "_blank");
- } catch (error) {
- console.error("Failed to generate Google Form:", error);
+ return (
+
+
Quiz Completed 🎉
+
+ Score: {score} / {qaPairs.length}
+
+
+
+ );
}
- };
- const loadLogoAsBytes = async () => {
- try {
- const response = await fetch(logoPNG);
- const arrayBuffer = await response.arrayBuffer();
- return new Uint8Array(arrayBuffer);
- } catch (error) {
- console.error('Error loading logo:', error);
- return null;
- }
- };
+ return (
+
+
+ Question {currentQuestionIndex + 1} of {qaPairs.length}
+
- const generatePDF = async (mode) => {
- const logoBytes = await loadLogoAsBytes();
- const worker = new Worker(new URL("../workers/pdfWorker.js", import.meta.url), { type: "module" });
+
+ {current.question}
+
- worker.postMessage({ qaPairs, mode, logoBytes });
+ {current.options?.map((option, idx) => (
+
+ ))}
- worker.onmessage = (e) => {
- const blob = new Blob([e.data], { type: 'application/pdf' });
- const link = document.createElement('a');
- link.href = URL.createObjectURL(blob);
- link.download = "generated_questions.pdf";
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
+ {showFeedback && (
+
+ {selectedAnswers[currentQuestionIndex] === current.answer ? (
+ Correct ✅
+ ) : (
+
+ Incorrect ❌ (Correct: {current.answer})
+
+ )}
+
+ )}
- document.getElementById('pdfDropdown').classList.add('hidden');
- worker.terminate();
- };
+
+
- worker.onerror = (err) => {
- console.error("PDF generation failed in worker:", err);
- worker.terminate();
- };
+ {currentQuestionIndex === qaPairs.length - 1 ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+ );
};
+ // ===================== MAIN RETURN =====================
return (
-
-
- {/* Header - Responsive logo and title */}
-
-
-

-
-
- Edu
-
-
- Aid
-
-
-
-
+
- {/* Title and Shuffle Button */}
-
-
- Generated Questions
+
+
+

+
+ EduAid
+
+
+
+
+
+ Generated Questions
+
+
+
-
-
- {/* Questions Container - Responsive padding and margins */}
-
- {qaPairs &&
- qaPairs.map((qaPair, index) => {
- const shuffledOptions = shuffledOptionsMap[index];
- const isEditing = editingIndex === index;
-
- return (
-
-
-
- Question {index + 1}
-
- {!isEditing ? (
-
- ) : (
-
-
-
-
- )}
-
-
- {!isEditing ? (
- <>
-
- {qaPair.question}
-
- {qaPair.question_type !== "Boolean" && (
- <>
-
- Answer
-
-
- {qaPair.answer}
-
- {qaPair.options && qaPair.options.length > 0 && (
-
- {shuffledOptions.map((option, idx) => (
-
-
- Option {idx + 1}:
- {" "}
-
- {option}
-
-
- ))}
-
- )}
- >
- )}
- >
- ) : (
- <>
-
- Edit Question
-
-
- );
- })}
-
- {/* Action Buttons - Responsive layout */}
-
-
-
-
-
+
+
+
+
+ {quizMode ? (
+ renderQuizMode()
+ ) : (
+ qaPairs.map((qaPair, index) => (
-
-
-
+
+ Question {index + 1}
+
+
+
+
{qaPair.question}
+
+
+ Answer: {qaPair.answer}
+
-
-
+ ))
+ )}
+
);
};
-
-export default Output;
+export default Output;
\ No newline at end of file