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 = () => { logo {/* Heading */} -

- - Edu - - - Aid - -

+

Edu

+

Aid

{/* Subtitle */}
@@ -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 */} - -
- logo -
- - Edu - - - Aid - -
-
- +
- {/* Title and Shuffle Button */} -
-
- Generated Questions + +
+ logo +
+ 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 -
-