@@ -12,7 +12,10 @@ import {
1212 LinearProgress ,
1313 Grid2 as Grid ,
1414 Alert ,
15+ IconButton ,
1516} from '@mui/material' ;
17+
18+ import CloseIcon from '@mui/icons-material/Close' ;
1619import MainLayout from './QuizLayout' ;
1720import Mascot1 from '../Mascot1.svg' ;
1821import Mascot2 from '../Mascot2.svg' ;
@@ -29,6 +32,7 @@ function QuizPage() {
2932 const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
3033 const timerRef = useRef ( null ) ;
3134 const questionStartTimeRef = useRef ( null ) ;
35+ const hasSubmittedRef = useRef ( false ) ;
3236
3337 useEffect ( ( ) => {
3438 if ( ! quizID || ! username ) {
@@ -52,41 +56,77 @@ function QuizPage() {
5256 } ) ;
5357 } , [ quizID , username , navigate ] ) ;
5458
55- const handleSubmit = useCallback ( ( ) => {
56- setIsSubmitting ( true ) ;
57- const submissionData = {
58- Username : username ,
59- QuizID : quizID ,
60- Answers : answers ,
61- } ;
62- if ( email ) {
63- submissionData . Email = email ;
64- }
59+ const handleSubmit = useCallback (
60+ ( timerExceeded = false ) => {
61+ if ( hasSubmittedRef . current ) return ;
62+ hasSubmittedRef . current = true ;
6563
66- fetch ( `${ process . env . REACT_APP_API_ENDPOINT } /submitquiz` , {
67- method : 'POST' ,
68- headers : { 'Content-Type' : 'application/json' } ,
69- body : JSON . stringify ( submissionData ) ,
70- } )
71- . then ( ( res ) => res . json ( ) )
72- . then ( ( data ) => {
73- navigate ( '/result' , {
74- state : { submissionID : data . SubmissionID , quizID } ,
75- } ) ;
64+ setIsSubmitting ( true ) ;
65+ const submissionData = {
66+ Username : username ,
67+ QuizID : quizID ,
68+ Answers : answers ,
69+ } ;
70+ if ( email ) {
71+ submissionData . Email = email ;
72+ }
73+ if ( timerExceeded ) {
74+ submissionData . TimerExceeded = true ;
75+ }
76+
77+ fetch ( `${ process . env . REACT_APP_API_ENDPOINT } /submitquiz` , {
78+ method : 'POST' ,
79+ headers : { 'Content-Type' : 'application/json' } ,
80+ body : JSON . stringify ( submissionData ) ,
7681 } )
77- . catch ( ( err ) => {
78- console . error ( 'Error submitting quiz:' , err ) ;
79- alert ( 'Failed to submit quiz. Please try again.' ) ;
80- setIsSubmitting ( false ) ;
81- } ) ;
82- } , [ username , quizID , answers , email , navigate ] ) ;
82+ . then ( ( res ) => res . json ( ) )
83+ . then ( ( data ) => {
84+ navigate ( '/result' , {
85+ state : { submissionID : data . SubmissionID , quizID } ,
86+ } ) ;
87+ } )
88+ . catch ( ( err ) => {
89+ console . error ( 'Error submitting quiz:' , err ) ;
90+ alert ( 'Failed to submit quiz. Please try again.' ) ;
91+ setIsSubmitting ( false ) ;
92+ hasSubmittedRef . current = false ;
93+ } ) ;
94+ } ,
95+ [ username , quizID , answers , email , navigate ]
96+ ) ;
8397
8498 const moveToNextQuestion = useCallback ( ( ) => {
8599 if ( quizData && currentQuestionIndex < quizData . Questions . length - 1 ) {
86100 setCurrentQuestionIndex ( ( prevIndex ) => prevIndex + 1 ) ;
87101 }
88102 } , [ quizData , currentQuestionIndex ] ) ;
89103
104+ const handleSkip = ( ) => {
105+ const timeTaken =
106+ quizData && quizData . EnableTimer ? quizData . TimerSeconds - timeLeft : 0 ;
107+
108+ setAnswers ( ( prevAnswers ) => ( {
109+ ...prevAnswers ,
110+ [ currentQuestionIndex ] : {
111+ Answer : '' ,
112+ TimeTaken : timeTaken ,
113+ Skipped : true ,
114+ } ,
115+ } ) ) ;
116+
117+ if ( quizData && quizData . EnableTimer && timerRef . current ) {
118+ clearInterval ( timerRef . current ) ;
119+ }
120+
121+ if ( currentQuestionIndex < quizData . Questions . length - 1 ) {
122+ moveToNextQuestion ( ) ;
123+ } else {
124+ if ( ! hasSubmittedRef . current ) {
125+ handleSubmit ( ) ;
126+ }
127+ }
128+ } ;
129+
90130 useEffect ( ( ) => {
91131 if ( quizData && quizData . EnableTimer ) {
92132 if ( timerRef . current ) {
@@ -109,6 +149,9 @@ function QuizPage() {
109149 if ( currentQuestionIndex < quizData . Questions . length - 1 ) {
110150 moveToNextQuestion ( ) ;
111151 } else {
152+ if ( ! hasSubmittedRef . current ) {
153+ handleSubmit ( true ) ;
154+ }
112155 }
113156 } ;
114157
@@ -125,7 +168,7 @@ function QuizPage() {
125168
126169 return ( ) => clearInterval ( timerRef . current ) ;
127170 }
128- } , [ currentQuestionIndex , quizData , moveToNextQuestion ] ) ;
171+ } , [ currentQuestionIndex , quizData , moveToNextQuestion , handleSubmit ] ) ;
129172
130173 const handleOptionChange = ( e ) => {
131174 const selectedOption = e . target . value ;
@@ -140,11 +183,16 @@ function QuizPage() {
140183 } ,
141184 } ) ) ;
142185
186+ if ( quizData && quizData . EnableTimer && timerRef . current ) {
187+ clearInterval ( timerRef . current ) ;
188+ }
189+
143190 if ( currentQuestionIndex < quizData . Questions . length - 1 ) {
144- if ( quizData && quizData . EnableTimer && timerRef . current ) {
145- clearInterval ( timerRef . current ) ;
146- }
147191 moveToNextQuestion ( ) ;
192+ } else {
193+ if ( ! hasSubmittedRef . current ) {
194+ handleSubmit ( ) ;
195+ }
148196 }
149197 } ;
150198
@@ -168,7 +216,19 @@ function QuizPage() {
168216
169217 return (
170218 < MainLayout >
171- < Container maxWidth = "sm" className = "main-quiz-container" >
219+ < Container maxWidth = "sm" className = "main-quiz-container" sx = { { position : 'relative' } } >
220+ { /* Skip Button at Top Right */ }
221+ { ! isSubmitting && (
222+ < IconButton
223+ onClick = { handleSkip }
224+ disabled = { isSubmitting || isTimeUp }
225+ sx = { { position : 'absolute' , top : 8 , right : 8 } }
226+ aria-label = "Skip"
227+ >
228+ < CloseIcon />
229+ </ IconButton >
230+ ) }
231+
172232 < Typography variant = "h6" gutterBottom align = "left" width = { '100%' } >
173233 Question { currentQuestionIndex + 1 } / { quizData . Questions . length }
174234 </ Typography >
@@ -227,32 +287,27 @@ function QuizPage() {
227287 ) ) }
228288 </ RadioGroup >
229289
230- { isSubmitting && (
290+ { ! isSubmitting && currentQuestionIndex === quizData . Questions . length - 1 && (
231291 < Box sx = { { textAlign : 'center' , marginTop : 4 } } >
232- < CircularProgress />
233- < Typography variant = "h6" sx = { { marginTop : 2 } } >
234- Submitting your answers...
235- </ Typography >
236- </ Box >
237- ) }
238-
239- { ! isSubmitting &&
240- quizData &&
241- currentQuestionIndex === quizData . Questions . length - 1 && (
242292 < Button
243293 variant = "contained"
244294 color = "primary"
245295 onClick = { handleSubmit }
246- disabled = {
247- Object . keys ( answers ) . length !== quizData . Questions . length ||
248- isSubmitting ||
249- isTimeUp
250- }
251- sx = { { marginTop : 4 } }
296+ disabled = { isSubmitting || isTimeUp }
252297 >
253298 Submit Quiz
254299 </ Button >
255- ) }
300+ </ Box >
301+ ) }
302+
303+ { isSubmitting && (
304+ < Box sx = { { textAlign : 'center' , marginTop : 4 } } >
305+ < CircularProgress />
306+ < Typography variant = "h6" sx = { { marginTop : 2 } } >
307+ Submitting your answers...
308+ </ Typography >
309+ </ Box >
310+ ) }
256311 </ Container >
257312 < div className = "character-container" >
258313 { currentQuestionIndex % 3 === 0 && (
0 commit comments