@@ -8,45 +8,52 @@ import Quiz from "./quiz.js";
88
99class App {
1010 constructor ( ) {
11- const numQuestions = 10 ;
1211 // Initialise app elements as JS objects.
13- this . quiz = new Quiz ( numQuestions ) ;
14- this . controller = new Controller ( this . quiz ) ;
12+ this . quiz = null ; // created on demand per quiz run
13+ this . controller = new Controller ( ) ;
1514 this . ui = new UI ( ) ;
15+ this . quizHistory = this . loadQuizHistory ( ) ;
16+ this . currentTopic = "" ;
17+ this . currentDifficulty = "" ;
18+ this . currentModel = "" ;
19+
20+ // Render any existing history on load
21+ this . ui . renderHistory ( this . quizHistory ) ;
1622
1723 // Initialize mobile menu toggle
1824 this . initMobileMenu ( ) ;
1925
2026 // Load supported models dynamically
2127 this . loadSupportedModels ( ) ;
2228
23- // Initialise button event listeners.
24- // The arrow function implicitly binds the method to the current instance of this class.
25- document
26- . querySelector ( "#fetchQuizButton" )
27- . addEventListener ( "click" , ( ) => this . fetchQuizData ( ) ) ;
28- document
29- . querySelector ( "#fetchQuizButton" )
30- . addEventListener ( "click" , ( ) => this . fetchAIImage ( ) ) ;
31- // Answer buttons aren't visible when the page first loads
32- document
33- . querySelector ( "#option-A" )
34- . addEventListener ( "click" , ( ) => this . checkAnswer ( "A" ) ) ;
35- document
36- . querySelector ( "#option-B" )
37- . addEventListener ( "click" , ( ) => this . checkAnswer ( "B" ) ) ;
38- document
39- . querySelector ( "#option-C" )
40- . addEventListener ( "click" , ( ) => this . checkAnswer ( "C" ) ) ;
41-
42- // If Enter key is pressed then simulate button press
43- document
44- . getElementById ( "quizTopic" )
45- . addEventListener ( "keydown" , function ( event ) {
46- // Check if the pressed key was the Enter key
47- if ( event . key === "Enter" ) {
48- event . preventDefault ( ) ; // Prevent any default action
49- document . querySelector ( "#fetchQuizButton" ) . click ( ) ; // Simulate a click on the button
29+ // Initialise button event listeners
30+ this . bindButtonEvents ( ) ;
31+ this . bindEnterKeyToQuizButton ( ) ;
32+ }
33+
34+ /**
35+ * Bind event listeners to quiz control buttons.
36+ * @private
37+ */
38+ bindButtonEvents ( ) {
39+ this . ui . elements . fetchButton . addEventListener ( "click" , ( ) => this . fetchQuizData ( ) ) ;
40+ this . ui . elements . fetchButton . addEventListener ( "click" , ( ) => this . fetchAIImage ( ) ) ;
41+ this . ui . elements . buttonA . addEventListener ( "click" , ( ) => this . checkAnswer ( "A" ) ) ;
42+ this . ui . elements . buttonB . addEventListener ( "click" , ( ) => this . checkAnswer ( "B" ) ) ;
43+ this . ui . elements . buttonC . addEventListener ( "click" , ( ) => this . checkAnswer ( "C" ) ) ;
44+ this . ui . elements . nextQuestionButton . addEventListener ( "click" , ( ) => this . nextQuestion ( ) ) ;
45+ this . ui . elements . newQuizButton . addEventListener ( "click" , ( ) => location . reload ( ) ) ;
46+ }
47+
48+ /**
49+ * Bind Enter key on quiz topic input to trigger quiz generation.
50+ * @private
51+ */
52+ bindEnterKeyToQuizButton ( ) {
53+ this . ui . elements . topicInput . addEventListener ( "keydown" , ( event ) => {
54+ if ( event . key === "Enter" ) {
55+ event . preventDefault ( ) ;
56+ this . ui . elements . fetchButton . click ( ) ;
5057 }
5158 } ) ;
5259 }
@@ -55,8 +62,8 @@ class App {
5562 * Initialize mobile menu toggle functionality
5663 */
5764 initMobileMenu ( ) {
58- const navbarToggle = document . getElementById ( 'navbar-toggle' ) ;
59- const navbarLinks = document . getElementById ( 'navbar-links' ) ;
65+ const navbarToggle = this . ui . elements . navbarToggle ;
66+ const navbarLinks = this . ui . elements . navbarLinks ;
6067
6168 if ( navbarToggle && navbarLinks ) {
6269 navbarToggle . addEventListener ( 'click' , ( ) => {
@@ -89,6 +96,17 @@ class App {
8996 const topic = this . ui . getTopic ( ) ;
9097 const difficulty = this . ui . getDifficulty ( ) ;
9198 const model = this . ui . getModel ( ) ;
99+ const numQuestions = this . ui . getNumQuestions ( ) ;
100+
101+ // Persist current quiz metadata for history logging
102+ this . currentTopic = topic ;
103+ this . currentDifficulty = difficulty ;
104+ this . currentModel = model ;
105+
106+ // Create fresh quiz state with requested number of questions
107+ this . quiz = new Quiz ( numQuestions ) ;
108+ this . controller . quiz = this . quiz ;
109+ this . controller . numQuestions = numQuestions ;
92110
93111 // Check if topic is empty or contains only whitespace
94112 if ( ! topic . trim ( ) ) {
@@ -128,7 +146,7 @@ class App {
128146
129147 async fetchAIImage ( ) {
130148 // Use the topic as a prompt to image
131- const prompt = document . getElementById ( "quizTopic" ) . value ;
149+ const prompt = this . ui . elements . topicInput . value ;
132150
133151 // Check if prompt is empty or contains only whitespace
134152 if ( ! prompt . trim ( ) ) {
@@ -159,12 +177,39 @@ class App {
159177 const question = this . quiz . getCurrentQuestion ( ) ;
160178 // Update UI elements
161179 this . ui . displayCurrentQuestion ( question ) ;
180+ this . ui . updateProgress ( this . quiz . currentIndex + 1 , this . quiz . numQuestions , this . quiz . score ) ;
162181 }
163182
164183 // Calls quiz check answer method
165184 // and displays the next question
185+ /**
186+ * Handles answer selection, renders inline feedback, and advances quiz flow.
187+ * @param {"A"|"B"|"C" } answer - Selected option key.
188+ */
166189 checkAnswer ( answer ) {
167- this . quiz . checkAnswer ( answer ) ;
190+ const result = this . quiz . checkAnswer ( answer ) ;
191+
192+ if ( ! result ) {
193+ return ;
194+ }
195+
196+ if ( result . isFinished ) {
197+ this . saveQuizResult ( result ) ;
198+ this . ui . showFinalScore ( result ) ;
199+ this . ui . updateProgress ( this . quiz . numQuestions , this . quiz . numQuestions , this . quiz . score ) ;
200+ return ;
201+ }
202+
203+ this . ui . hideAnswerButtons ( ) ;
204+ this . ui . showAnswerFeedback ( result ) ;
205+ this . ui . showNextQuestionButton ( ) ;
206+ }
207+
208+ nextQuestion ( ) {
209+ this . ui . hideFeedback ( ) ;
210+ this . ui . hideNextQuestionButton ( ) ;
211+ this . ui . hideNewQuizButton ( ) ;
212+ this . ui . showAnswerButtons ( ) ;
168213 this . showQuestion ( ) ;
169214 }
170215
@@ -191,6 +236,58 @@ class App {
191236 // The dropdown will keep its existing hardcoded options if this fails
192237 }
193238 }
239+
240+ /**
241+ * Retrieve quiz history from localStorage.
242+ * @returns {Array } Stored quiz attempts.
243+ */
244+ loadQuizHistory ( ) {
245+ try {
246+ const raw = localStorage . getItem ( "gptQuizHistory" ) ;
247+ return raw ? JSON . parse ( raw ) : [ ] ;
248+ } catch ( error ) {
249+ console . warn ( "Failed to load quiz history from localStorage" , error ) ;
250+ return [ ] ;
251+ }
252+ }
253+
254+ /**
255+ * Append the current quiz result to localStorage history.
256+ * @param {Object } result - Final quiz result payload.
257+ */
258+ saveQuizResult ( result ) {
259+ const entry = {
260+ topic : this . currentTopic ,
261+ difficulty : this . currentDifficulty ,
262+ model : this . currentModel ,
263+ score : result . score ,
264+ totalQuestions : result . totalQuestions ,
265+ finishedAt : new Date ( ) . toISOString ( ) ,
266+ } ;
267+
268+ try {
269+ const history = this . loadQuizHistory ( ) ;
270+ history . push ( entry ) ;
271+ localStorage . setItem ( "gptQuizHistory" , JSON . stringify ( history ) ) ;
272+ this . quizHistory = history ;
273+ this . ui . renderHistory ( history ) ;
274+ } catch ( error ) {
275+ console . warn ( "Failed to save quiz history to localStorage" , error ) ;
276+ }
277+ }
278+
279+ /**
280+ * Clear quiz history from localStorage and UI.
281+ */
282+ clearQuizHistory ( ) {
283+ try {
284+ localStorage . removeItem ( "gptQuizHistory" ) ;
285+ this . quizHistory = [ ] ;
286+ this . ui . renderHistory ( [ ] ) ;
287+ } catch ( error ) {
288+ console . warn ( "Failed to clear quiz history" , error ) ;
289+ }
290+ }
194291}
195292
196293const app = new App ( ) ;
0 commit comments