11'use client' ;
22
3- import { useState , useEffect } from 'react' ;
3+ import { useState , useEffect , useRef } from 'react' ;
44import Link from 'next/link' ;
55import { allQuestions } from '@/lib/questionsData' ;
6- import { getResponse , saveResponse , deleteResponse } from '@/lib/responseStorage' ;
6+ import { saveResponse , deleteResponse } from '@/lib/responseStorage' ;
7+ import { saveEvaluation } from '@/lib/evaluationStorage' ;
78
89interface Question {
910 id : string ;
@@ -12,22 +13,44 @@ interface Question {
1213 subcategory ?: string ;
1314}
1415
16+ const TIMER_DURATION = 300 ; // 5 minutes in seconds
17+ const QUESTIONS_PER_ROUND = 10 ;
18+
1519export default function PracticePage ( ) {
1620 const [ questions ] = useState < Question [ ] > ( allQuestions ) ;
1721 const [ currentQuestion , setCurrentQuestion ] = useState < Question | null > ( null ) ;
18- const [ timer , setTimer ] = useState ( 0 ) ;
22+ const [ timer , setTimer ] = useState ( TIMER_DURATION ) ;
1923 const [ isActive , setIsActive ] = useState ( false ) ;
2024 const [ usedQuestions , setUsedQuestions ] = useState < Set < string > > ( new Set ( ) ) ;
2125 const [ response , setResponse ] = useState ( '' ) ;
26+ const [ questionsInRound , setQuestionsInRound ] = useState ( 0 ) ;
27+ const [ extraTime , setExtraTime ] = useState ( 0 ) ;
28+ const [ showEvaluation , setShowEvaluation ] = useState ( false ) ;
29+ const [ evaluation , setEvaluation ] = useState ( {
30+ confidence : 3 ,
31+ effectiveness : 3 ,
32+ knowledge : 3 ,
33+ } ) ;
34+
35+ const skipQuestionRef = useRef < ( ( ) => void ) | null > ( null ) ;
2236
2337 useEffect ( ( ) => {
2438 let interval : NodeJS . Timeout | null = null ;
2539
26- if ( isActive ) {
40+ if ( isActive && timer > 0 ) {
2741 interval = setInterval ( ( ) => {
28- setTimer ( ( seconds ) => seconds + 1 ) ;
42+ setTimer ( ( seconds ) => {
43+ if ( seconds <= 1 ) {
44+ // Time's up, auto-skip to next question
45+ if ( skipQuestionRef . current ) {
46+ skipQuestionRef . current ( ) ;
47+ }
48+ return TIMER_DURATION ;
49+ }
50+ return seconds - 1 ;
51+ } ) ;
2952 } , 1000 ) ;
30- } else if ( ! isActive && timer !== 0 ) {
53+ } else if ( ! isActive && timer !== TIMER_DURATION ) {
3154 if ( interval ) clearInterval ( interval ) ;
3255 }
3356
@@ -45,6 +68,13 @@ export default function PracticePage() {
4568 const getRandomQuestion = ( ) => {
4669 if ( questions . length === 0 ) return ;
4770
71+ // Check if we've completed a round
72+ if ( questionsInRound >= QUESTIONS_PER_ROUND ) {
73+ setShowEvaluation ( true ) ;
74+ setIsActive ( false ) ;
75+ return ;
76+ }
77+
4878 // Save current response before switching questions (only if not empty)
4979 if ( currentQuestion && response . trim ( ) ) {
5080 saveResponse ( currentQuestion . id , response ) ;
@@ -74,8 +104,21 @@ export default function PracticePage() {
74104 setResponse ( '' ) ; // Clear the text box for new question
75105 }
76106
77- setTimer ( 0 ) ;
107+ // Add any remaining time to extra time pool, then reset timer
108+ if ( timer > 0 && questionsInRound > 0 ) {
109+ setExtraTime ( prev => prev + timer ) ;
110+ }
111+
112+ // Use extra time if available
113+ let newTimer = TIMER_DURATION ;
114+ if ( extraTime > 0 ) {
115+ newTimer = TIMER_DURATION + extraTime ;
116+ setExtraTime ( 0 ) ;
117+ }
118+
119+ setTimer ( newTimer ) ;
78120 setIsActive ( true ) ;
121+ setQuestionsInRound ( prev => prev + 1 ) ;
79122 } ;
80123
81124 const skipQuestion = ( ) => {
@@ -86,6 +129,11 @@ export default function PracticePage() {
86129 getRandomQuestion ( ) ;
87130 } ;
88131
132+ // Update ref whenever skipQuestion changes
133+ useEffect ( ( ) => {
134+ skipQuestionRef . current = skipQuestion ;
135+ } ) ;
136+
89137 const handleSaveResponse = ( ) => {
90138 if ( currentQuestion ) {
91139 saveResponse ( currentQuestion . id , response ) ;
@@ -99,6 +147,25 @@ export default function PracticePage() {
99147 }
100148 } ;
101149
150+ const handleSubmitEvaluation = ( ) => {
151+ saveEvaluation ( evaluation ) ;
152+ // Reset for new round
153+ setShowEvaluation ( false ) ;
154+ setQuestionsInRound ( 0 ) ;
155+ setExtraTime ( 0 ) ;
156+ setUsedQuestions ( new Set ( ) ) ;
157+ setCurrentQuestion ( null ) ;
158+ setTimer ( TIMER_DURATION ) ;
159+ setIsActive ( false ) ;
160+ } ;
161+
162+ const handleStartNewRound = ( ) => {
163+ setQuestionsInRound ( 0 ) ;
164+ setExtraTime ( 0 ) ;
165+ setUsedQuestions ( new Set ( ) ) ;
166+ getRandomQuestion ( ) ;
167+ } ;
168+
102169 return (
103170 < div className = "min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800" >
104171 < div className = "container mx-auto px-4 py-8 max-w-4xl" >
@@ -115,13 +182,97 @@ export default function PracticePage() {
115182 Practice Mode
116183 </ h1 >
117184
118- { ! currentQuestion ? (
185+ { showEvaluation ? (
186+ < div className = "bg-white dark:bg-slate-800 rounded-xl shadow-lg p-8 space-y-6" >
187+ < h2 className = "text-2xl font-bold text-slate-900 dark:text-slate-100 text-center" >
188+ Round Complete! 🎉
189+ </ h2 >
190+ < p className = "text-center text-slate-600 dark:text-slate-300" >
191+ You've completed { QUESTIONS_PER_ROUND } questions. Please evaluate yourself:
192+ </ p >
193+
194+ < div className = "space-y-6" >
195+ < div >
196+ < label className = "block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2" >
197+ Confidence (1-5)
198+ </ label >
199+ < div className = "flex gap-2 justify-center" >
200+ { [ 1 , 2 , 3 , 4 , 5 ] . map ( ( val ) => (
201+ < button
202+ key = { val }
203+ onClick = { ( ) => setEvaluation ( prev => ( { ...prev , confidence : val } ) ) }
204+ className = { `w-12 h-12 rounded-lg font-semibold transition-all ${
205+ evaluation . confidence === val
206+ ? 'bg-blue-600 text-white scale-110'
207+ : 'bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600'
208+ } `}
209+ >
210+ { val }
211+ </ button >
212+ ) ) }
213+ </ div >
214+ </ div >
215+
216+ < div >
217+ < label className = "block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2" >
218+ Effectiveness (1-5)
219+ </ label >
220+ < div className = "flex gap-2 justify-center" >
221+ { [ 1 , 2 , 3 , 4 , 5 ] . map ( ( val ) => (
222+ < button
223+ key = { val }
224+ onClick = { ( ) => setEvaluation ( prev => ( { ...prev , effectiveness : val } ) ) }
225+ className = { `w-12 h-12 rounded-lg font-semibold transition-all ${
226+ evaluation . effectiveness === val
227+ ? 'bg-green-600 text-white scale-110'
228+ : 'bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600'
229+ } `}
230+ >
231+ { val }
232+ </ button >
233+ ) ) }
234+ </ div >
235+ </ div >
236+
237+ < div >
238+ < label className = "block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2" >
239+ Knowledge (1-5)
240+ </ label >
241+ < div className = "flex gap-2 justify-center" >
242+ { [ 1 , 2 , 3 , 4 , 5 ] . map ( ( val ) => (
243+ < button
244+ key = { val }
245+ onClick = { ( ) => setEvaluation ( prev => ( { ...prev , knowledge : val } ) ) }
246+ className = { `w-12 h-12 rounded-lg font-semibold transition-all ${
247+ evaluation . knowledge === val
248+ ? 'bg-purple-600 text-white scale-110'
249+ : 'bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600'
250+ } `}
251+ >
252+ { val }
253+ </ button >
254+ ) ) }
255+ </ div >
256+ </ div >
257+ </ div >
258+
259+ < button
260+ onClick = { handleSubmitEvaluation }
261+ className = "w-full px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg shadow-lg hover:shadow-xl transition-all duration-300"
262+ >
263+ Submit Evaluation & Start New Round
264+ </ button >
265+ </ div >
266+ ) : ! currentQuestion ? (
119267 < div className = "text-center space-y-8" >
120268 < p className = "text-xl text-slate-600 dark:text-slate-300" >
121- Test your knowledge with random questions. A timer will start when you begin.
269+ Test your knowledge with random questions. Each round has { QUESTIONS_PER_ROUND } questions with 5 minutes per question.
270+ </ p >
271+ < p className = "text-md text-slate-500 dark:text-slate-400" >
272+ Finish questions early? Your extra time rolls over to the next question!
122273 </ p >
123274 < button
124- onClick = { getRandomQuestion }
275+ onClick = { handleStartNewRound }
125276 disabled = { questions . length === 0 }
126277 className = "px-8 py-4 bg-green-600 hover:bg-green-700 text-white font-semibold rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 text-lg disabled:opacity-50 disabled:cursor-not-allowed"
127278 >
@@ -141,14 +292,24 @@ export default function PracticePage() {
141292 { currentQuestion . category }
142293 </ span >
143294 ) }
295+ < div className = "mt-2 text-sm font-medium text-slate-600 dark:text-slate-400" >
296+ Question { questionsInRound } / { QUESTIONS_PER_ROUND }
297+ </ div >
144298 </ div >
145299 < div className = "text-right" >
146- < div className = "text-4xl font-bold text-slate-900 dark:text-slate-100 font-mono" >
300+ < div className = { `text-4xl font-bold font-mono ${
301+ timer <= 60 ? 'text-red-600 dark:text-red-400' : 'text-slate-900 dark:text-slate-100'
302+ } `} >
147303 { formatTime ( timer ) }
148304 </ div >
149305 < div className = "text-sm text-slate-500 dark:text-slate-400 mt-1" >
150- Time elapsed
306+ Time remaining
151307 </ div >
308+ { extraTime > 0 && (
309+ < div className = "text-xs text-green-600 dark:text-green-400 mt-1" >
310+ +{ formatTime ( extraTime ) } extra time available
311+ </ div >
312+ ) }
152313 </ div >
153314 </ div >
154315
@@ -192,9 +353,15 @@ export default function PracticePage() {
192353 < div className = "flex gap-4 justify-center" >
193354 < button
194355 onClick = { skipQuestion }
195- className = "px-6 py-3 bg-slate-200 dark:bg-slate-700 hover:bg-slate-300 dark:hover:bg-slate-600 text-slate-900 dark:text-slate-100 font-semibold rounded-lg transition-colors"
356+ className = "px-4 py-2 bg-slate-200 dark:bg-slate-700 hover:bg-slate-300 dark:hover:bg-slate-600 text-slate-900 dark:text-slate-100 text-sm font-medium rounded-lg transition-colors"
357+ >
358+ Skip
359+ </ button >
360+ < button
361+ onClick = { skipQuestion }
362+ className = "px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg transition-colors shadow-lg hover:shadow-xl"
196363 >
197- Skip Question →
364+ Next Question →
198365 </ button >
199366 </ div >
200367
0 commit comments