From 470bf07608eda03cfaf422cf62df995f4665da00 Mon Sep 17 00:00:00 2001
From: Stijnvandenbroek <70574420+Stijnvandenbroek@users.noreply.github.com>
Date: Tue, 12 Aug 2025 14:39:53 +0200
Subject: [PATCH] chore: organise frontend
---
frontend/src/components/Home.js | 130 +++++++------
frontend/src/components/Quiz.js | 331 +++++++++++++++++---------------
2 files changed, 239 insertions(+), 222 deletions(-)
diff --git a/frontend/src/components/Home.js b/frontend/src/components/Home.js
index c0239cc..0ffb86c 100644
--- a/frontend/src/components/Home.js
+++ b/frontend/src/components/Home.js
@@ -2,41 +2,25 @@ import React, { useState, useEffect } from 'react';
import axios from 'axios';
const Home = ({ setSessionId, setQuizStarted }) => {
- const [files, setFiles] = useState([]);
- const [fileNames, setFileNames] = useState('');
- const [settingsOpen, setSettingsOpen] = useState(true);
- const [setting1, setSetting1] = useState(false);
- const [setting2, setSetting2] = useState(false);
+ const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
+
+ // File state
+ const [files, setFiles] = useState([]);
+ const [fileNames, setFileNames] = useState('');
const [isFileListVisible, setIsFileListVisible] = useState(false);
+
+ // Settings state
+ const [settingsOpen, setSettingsOpen] = useState(true);
+ const [setting1, setSetting1] = useState(false);
+ const [setting2, setSetting2] = useState(false);
const [quizSettings, setQuizSettings] = useState({
repeat_on_mistake: false,
shuffle_answers: false,
randomise_order: false,
question_count_multiplier: 1,
});
-
- // Backend API URL - use environment variable or default
- const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
-
- // Add useEffect to handle file list visibility
- useEffect(() => {
- if (fileNames) {
- // Slight delay to ensure smooth transition
- const timer = setTimeout(() => {
- setIsFileListVisible(true);
- }, 50);
- return () => clearTimeout(timer);
- } else {
- setIsFileListVisible(false);
- }
- }, [fileNames]);
-
- const handleFileChange = (e) => {
- const selectedFiles = Array.from(e.target.files);
- setFiles(selectedFiles);
- setFileNames(selectedFiles.map(file => file.name).join(', '));
- };
+ // API functions
const handleFileUpload = async () => {
if (files.length === 0) return alert("Please select files");
@@ -68,6 +52,13 @@ const Home = ({ setSessionId, setQuizStarted }) => {
}
};
+ // Event handlers
+ const handleFileChange = (e) => {
+ const selectedFiles = Array.from(e.target.files);
+ setFiles(selectedFiles);
+ setFileNames(selectedFiles.map(file => file.name).join(', '));
+ };
+
const toggleSettings = () => {
setSettingsOpen(!settingsOpen);
};
@@ -84,7 +75,27 @@ const Home = ({ setSessionId, setQuizStarted }) => {
setQuizSettings({ ...quizSettings, shuffle_answers: newSetting });
};
- // Main container style with flexbox to position elements side by side
+ const handleRandomizeOrderToggle = () => {
+ setQuizSettings({ ...quizSettings, randomise_order: !quizSettings.randomise_order });
+ };
+
+ const handleQuestionCountChange = (e) => {
+ setQuizSettings({ ...quizSettings, question_count_multiplier: parseInt(e.target.value) });
+ };
+
+ // Effects
+ useEffect(() => {
+ if (fileNames) {
+ const timer = setTimeout(() => {
+ setIsFileListVisible(true);
+ }, 50);
+ return () => clearTimeout(timer);
+ } else {
+ setIsFileListVisible(false);
+ }
+ }, [fileNames]);
+
+ // Styles
const containerStyle = {
display: 'flex',
justifyContent: 'center',
@@ -97,16 +108,14 @@ const Home = ({ setSessionId, setQuizStarted }) => {
boxSizing: 'border-box',
};
- // Left side container for quiz setup
const leftContainerStyle = {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
- marginRight: '30px', // Space between left and right containers
+ marginRight: '30px',
};
- // Right side container for file names with improved fade transition
const rightContainerStyle = {
backgroundColor: '#333',
padding: '20px',
@@ -123,31 +132,6 @@ const Home = ({ setSessionId, setQuizStarted }) => {
position: 'relative',
};
- const fileNameHeaderStyle = {
- fontWeight: 'bold',
- marginBottom: '10px',
- position: 'sticky',
- top: '0',
- backgroundColor: '#333',
- zIndex: 1,
- paddingBottom: '10px',
- };
-
- const fileNamesScrollContainerStyle = {
- overflowY: 'auto',
- flexGrow: 1,
- };
-
- const fileNamesContainerStyle = {
- backgroundColor: '#444',
- borderRadius: '5px',
- padding: '10px',
- marginTop: '10px',
- fontSize: '0.9em',
- color: 'white',
- wordBreak: 'break-all',
- };
-
const uploadContainerStyle = {
backgroundColor: '#333',
padding: '20px',
@@ -265,6 +249,31 @@ const Home = ({ setSessionId, setQuizStarted }) => {
marginLeft: '5px',
};
+ const fileNameHeaderStyle = {
+ fontWeight: 'bold',
+ marginBottom: '10px',
+ position: 'sticky',
+ top: '0',
+ backgroundColor: '#333',
+ zIndex: 1,
+ paddingBottom: '10px',
+ };
+
+ const fileNamesScrollContainerStyle = {
+ overflowY: 'auto',
+ flexGrow: 1,
+ };
+
+ const fileNamesContainerStyle = {
+ backgroundColor: '#444',
+ borderRadius: '5px',
+ padding: '10px',
+ marginTop: '10px',
+ fontSize: '0.9em',
+ color: 'white',
+ wordBreak: 'break-all',
+ };
+
return (
@@ -284,7 +293,6 @@ const Home = ({ setSessionId, setQuizStarted }) => {
- {/* Show/Hide Settings Button */}
{
{settingsOpen ? 'Hide Settings' : 'Show Settings'}
- {/* Settings Section */}
Repeat on Mistake
@@ -314,7 +321,7 @@ const Home = ({ setSessionId, setQuizStarted }) => {
- {/* Right side container for selected files with improved fade transition */}
{fileNames && (
Selected Files:
diff --git a/frontend/src/components/Quiz.js b/frontend/src/components/Quiz.js
index 831f98c..4b875e2 100644
--- a/frontend/src/components/Quiz.js
+++ b/frontend/src/components/Quiz.js
@@ -3,20 +3,184 @@ import axios from 'axios';
const Quiz = ({ sessionId, onGoHome, onRetry }) => {
const API_URL = process.env.REACT_APP_API_URL;
+
+ // Quiz state
const [questionData, setQuestionData] = useState(null);
- const [selectedAnswers, setSelectedAnswers] = useState([]);
- const [userAnswer, setUserAnswer] = useState('');
+ const [quizCompleted, setQuizCompleted] = useState(false);
const [totalQuestions, setTotalQuestions] = useState(0);
const [correctAnswerCount, setCorrectAnswersCount] = useState(0);
const [incorrectAnswersCount, setIncorrectAnswersCount] = useState(0);
+
+ // Answer state
+ const [selectedAnswers, setSelectedAnswers] = useState([]);
+ const [userAnswer, setUserAnswer] = useState('');
const [correctAnswers, setCorrectAnswers] = useState('');
- const [quizCompleted, setQuizCompleted] = useState(false);
- const [timer, setTimer] = useState(0);
- const [timerId, setTimerId] = useState(null);
+
+ // UI state
const [feedback, setFeedback] = useState('');
const [isFeedbackVisible, setIsFeedbackVisible] = useState(false);
const [fadeOut, setFadeOut] = useState(false);
const [showResults, setShowResults] = useState(false);
+
+ // Timer state
+ const [timer, setTimer] = useState(0);
+ const [timerId, setTimerId] = useState(null);
+
+ // API functions
+ const fetchQuestion = async () => {
+ try {
+ const response = await axios.get(`${API_URL}/next-question/?session_id=${sessionId}`);
+ console.log("Fetched Question Data:", response.data);
+
+ if (response.data.message === 'Quiz complete!') {
+ setFadeOut(true);
+ setTimeout(() => {
+ setQuizCompleted(true);
+ setQuestionData(null);
+ setTimeout(() => {
+ setShowResults(true);
+ }, 100);
+ }, 500);
+ } else {
+ setQuestionData(response.data);
+ setSelectedAnswers([]);
+ setUserAnswer('');
+ setFeedback('');
+ setIsFeedbackVisible(false);
+ }
+ } catch (error) {
+ alert('Error fetching question: ' + (error.response?.data?.error || error.message));
+ }
+ };
+
+ const fetchQuizStats = async () => {
+ try {
+ const response = await axios.get(`${API_URL}/quiz-stats/?session_id=${sessionId}`);
+ setTotalQuestions(response.data.total_questions);
+ setCorrectAnswersCount(response.data.correct_answers);
+ setIncorrectAnswersCount(response.data.incorrect_answers);
+ } catch (error) {
+ console.error('Error fetching quiz stats:', error);
+ }
+ };
+
+ const handleSubmit = async () => {
+ let submissionData;
+ if (questionData.options.length > 1) {
+ submissionData = {
+ session_id: sessionId,
+ selected_answers: selectedAnswers,
+ };
+ } else {
+ submissionData = {
+ session_id: sessionId,
+ selected_answers: [userAnswer.trim()],
+ };
+ }
+
+ try {
+ const response = await axios.post(`${API_URL}/submit-answer/`, submissionData);
+ const result = response.data.result;
+ setFeedback(result);
+ setIsFeedbackVisible(true);
+
+ if (result === 'Correct') {
+ fetchQuestion();
+ } else {
+ setCorrectAnswers(response.data.correct_answers.join(', '));
+ await moveQuestionToBottom();
+ }
+
+ fetchQuizStats();
+ } catch (error) {
+ alert('Error submitting answer: ' + (error.response?.data?.error || error.message));
+ }
+ };
+
+ const moveQuestionToBottom = async () => {
+ try {
+ const questionIndex = questionData.question_index;
+ await axios.post(`${API_URL}/move-question-to-bottom/`, {
+ session_id: sessionId,
+ question_index: questionIndex,
+ });
+ } catch (error) {
+ alert('Error moving question to bottom: ' + (error.response?.data?.error || error.message));
+ }
+ };
+
+ // Event handlers
+ const handleMultipleAnswerChange = (answerText) => {
+ setSelectedAnswers((prev) => {
+ if (prev.includes(answerText)) {
+ return prev.filter((answer) => answer !== answerText);
+ }
+ return [...prev, answerText];
+ });
+ };
+
+ const handleAnswerChange = (answerText) => {
+ setSelectedAnswers([answerText]);
+ };
+
+ const handleUserAnswerChange = (event) => {
+ setUserAnswer(event.target.value);
+ };
+
+ const handleContinueClick = () => {
+ setIsFeedbackVisible(false);
+ setFeedback('');
+ fetchQuestion();
+ };
+
+ const handleKeyPress = (event) => {
+ if (event.key === 'Enter') {
+ if (isFeedbackVisible) {
+ handleContinueClick();
+ } else {
+ const canSubmit = questionData.options.length > 1
+ ? selectedAnswers.length > 0
+ : userAnswer.trim() !== '';
+
+ if (canSubmit) {
+ handleSubmit();
+ }
+ }
+ }
+ };
+
+ // Utility functions
+ const formatTime = (totalSeconds) => {
+ const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0');
+ const minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0');
+ const seconds = String(totalSeconds % 60).padStart(2, '0');
+ return `${hours}:${minutes}:${seconds}`;
+ };
+
+ // Effects
+ useEffect(() => {
+ document.addEventListener('keypress', handleKeyPress);
+ return () => {
+ document.removeEventListener('keypress', handleKeyPress);
+ };
+ }, [isFeedbackVisible, selectedAnswers, userAnswer, questionData]);
+
+ useEffect(() => {
+ fetchQuestion();
+ fetchQuizStats();
+ const id = setInterval(() => setTimer((prev) => prev + 1), 1000);
+ setTimerId(id);
+
+ return () => {
+ clearInterval(id);
+ };
+ }, [sessionId]);
+
+ useEffect(() => {
+ if (quizCompleted) {
+ clearInterval(timerId);
+ }
+ }, [quizCompleted, timerId]);
// Styles
const pageStyle = {
@@ -118,160 +282,7 @@ const Quiz = ({ sessionId, onGoHome, onRetry }) => {
transition: 'opacity 0.5s ease-out, transform 0.5s ease-out',
};
- // Keyboard event handler
- const handleKeyPress = (event) => {
- if (event.key === 'Enter') {
- if (isFeedbackVisible) {
- handleContinueClick();
- } else {
- const canSubmit = questionData.options.length > 1
- ? selectedAnswers.length > 0
- : userAnswer.trim() !== '';
-
- if (canSubmit) {
- handleSubmit();
- }
- }
- }
- };
-
- // Event listeners
- useEffect(() => {
- document.addEventListener('keypress', handleKeyPress);
- return () => {
- document.removeEventListener('keypress', handleKeyPress);
- };
- }, [isFeedbackVisible, selectedAnswers, userAnswer, questionData]);
-
- const fetchQuestion = async () => {
- try {
- const response = await axios.get(`${API_URL}/next-question/?session_id=${sessionId}`);
- console.log("Fetched Question Data:", response.data);
-
- if (response.data.message === 'Quiz complete!') {
- setFadeOut(true);
- setTimeout(() => {
- setQuizCompleted(true);
- setQuestionData(null);
- setTimeout(() => {
- setShowResults(true);
- }, 100);
- }, 500);
- } else {
- setQuestionData(response.data);
- setSelectedAnswers([]);
- setUserAnswer('');
- setFeedback('');
- setIsFeedbackVisible(false);
- }
- } catch (error) {
- alert('Error fetching question: ' + (error.response?.data?.error || error.message));
- }
- };
-
- const fetchQuizStats = async () => {
- try {
- const response = await axios.get(`${API_URL}/quiz-stats/?session_id=${sessionId}`);
- setTotalQuestions(response.data.total_questions);
- setCorrectAnswersCount(response.data.correct_answers);
- setIncorrectAnswersCount(response.data.incorrect_answers);
- } catch (error) {
- console.error('Error fetching quiz stats:', error);
- }
- };
-
- const handleMultipleAnswerChange = (answerText) => {
- setSelectedAnswers((prev) => {
- if (prev.includes(answerText)) {
- return prev.filter((answer) => answer !== answerText);
- }
- return [...prev, answerText];
- });
- };
-
- const handleAnswerChange = (answerText) => {
- setSelectedAnswers([answerText]);
- };
-
- const handleUserAnswerChange = (event) => {
- setUserAnswer(event.target.value);
- };
-
- const handleContinueClick = () => {
- setIsFeedbackVisible(false);
- setFeedback('');
- fetchQuestion();
- };
-
- const handleSubmit = async () => {
- let submissionData;
- if (questionData.options.length > 1) {
- submissionData = {
- session_id: sessionId,
- selected_answers: selectedAnswers,
- };
- } else {
- submissionData = {
- session_id: sessionId,
- selected_answers: [userAnswer.trim()],
- };
- }
-
- try {
- const response = await axios.post(`${API_URL}/submit-answer/`, submissionData);
- const result = response.data.result;
- setFeedback(result);
- setIsFeedbackVisible(true);
-
- if (result === 'Correct') {
- fetchQuestion();
- } else {
- setCorrectAnswers(response.data.correct_answers.join(', '));
- await moveQuestionToBottom();
- }
-
- fetchQuizStats();
- } catch (error) {
- alert('Error submitting answer: ' + (error.response?.data?.error || error.message));
- }
- };
-
- const moveQuestionToBottom = async () => {
- try {
- const questionIndex = questionData.question_index;
- await axios.post(`${API_URL}/move-question-to-bottom/`, {
- session_id: sessionId,
- question_index: questionIndex,
- });
- } catch (error) {
- alert('Error moving question to bottom: ' + (error.response?.data?.error || error.message));
- }
- };
-
- useEffect(() => {
- fetchQuestion();
- fetchQuizStats();
- const id = setInterval(() => setTimer((prev) => prev + 1), 1000);
- setTimerId(id);
-
- return () => {
- clearInterval(id);
- };
- }, [sessionId]);
-
- useEffect(() => {
- if (quizCompleted) {
- clearInterval(timerId);
- }
- }, [quizCompleted, timerId]);
-
- const formatTime = (totalSeconds) => {
- const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0');
- const minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0');
- const seconds = String(totalSeconds % 60).padStart(2, '0');
- return `${hours}:${minutes}:${seconds}`;
- };
-
+ // Render quiz completion screen
if (quizCompleted) {
const totalAnswered = correctAnswerCount + (totalQuestions - correctAnswerCount);
const percentageCorrect = totalAnswered > 0 ? (correctAnswerCount / (correctAnswerCount + incorrectAnswersCount)) * 100 : 0;
@@ -306,8 +317,10 @@ const Quiz = ({ sessionId, onGoHome, onRetry }) => {
);
}
+ // Loading state
if (!questionData) return
Loading...
;
+ // Quiz question rendering
const hasMultipleOptions = questionData.options.length > 1;
const hasMultipleCorrectAnswers = questionData.multiple_choice;