@@ -28,62 +28,216 @@ export default function Trivia() {
2828 const [ loading , setLoading ] = useState ( false ) ;
2929 const [ error , setError ] = useState ( null ) ;
3030 const [ score , setScore ] = useState ( 0 ) ;
31+ const [ showReview , setShowReview ] = useState ( false ) ;
3132
32- useEffect ( ( ) => { fetchQuestions ( ) ; } , [ category ] ) ;
33+ useEffect ( ( ) => {
34+ fetchQuestions ( ) ;
35+ } , [ category ] ) ;
3336
3437 async function fetchQuestions ( ) {
3538 try {
36- setLoading ( true ) ; setError ( null ) ; setScore ( 0 ) ;
37- const res = await fetch ( `https://opentdb.com/api.php?amount=5&category=${ category } &type=multiple` ) ;
39+ setLoading ( true ) ;
40+ setError ( null ) ;
41+ setScore ( 0 ) ;
42+ setShowReview ( false ) ;
43+
44+ const res = await fetch (
45+ `https://opentdb.com/api.php?amount=5&category=${ category } &type=multiple`
46+ ) ;
3847 if ( ! res . ok ) throw new Error ( 'Failed to fetch' ) ;
3948 const json = await res . json ( ) ;
40- const qs = json . results . map ( q => ( {
49+
50+ const qs = json . results . map ( ( q ) => ( {
4151 ...q ,
4252 answers : shuffle ( [ q . correct_answer , ...q . incorrect_answers ] ) ,
43- picked : null
53+ picked : null ,
4454 } ) ) ;
4555 setQuestions ( qs ) ;
46- } catch ( e ) { setError ( e ) ; } finally { setLoading ( false ) ; }
56+ } catch ( e ) {
57+ setError ( e ) ;
58+ } finally {
59+ setLoading ( false ) ;
60+ }
4761 }
4862
49- function shuffle ( arr ) { return arr . sort ( ( ) => Math . random ( ) - 0.5 ) ; }
63+ function shuffle ( arr ) {
64+ return arr . sort ( ( ) => Math . random ( ) - 0.5 ) ;
65+ }
5066
5167 function pick ( qIndex , answer ) {
52- setQuestions ( qs => qs . map ( ( q , i ) => i === qIndex ? { ...q , picked : answer } : q ) ) ;
53- if ( questions [ qIndex ] . correct_answer === answer ) setScore ( s => s + 1 ) ;
68+ setQuestions ( ( qs ) =>
69+ qs . map ( ( q , i ) => ( i === qIndex ? { ...q , picked : answer } : q ) )
70+ ) ;
71+ if ( questions [ qIndex ] . correct_answer === answer ) {
72+ setScore ( ( s ) => s + 1 ) ;
73+ }
74+ }
75+
76+ function decodeHtml ( html ) {
77+ const txt = document . createElement ( 'textarea' ) ;
78+ txt . innerHTML = html ;
79+ return txt . value ;
5480 }
5581
82+ const answeredCount = questions . filter ( ( q ) => q . picked !== null ) . length ;
83+ const totalQuestions = questions . length ;
84+ const progressPercent =
85+ totalQuestions > 0 ? ( answeredCount / totalQuestions ) * 100 : 0 ;
86+
87+ const allAnswered = answeredCount === totalQuestions && totalQuestions > 0 ;
88+
5689 return (
57- < div >
90+ < div style = { { padding : '20px' } } >
5891 < h2 > Trivia Quiz</ h2 >
59- < label > Category:
60- < select value = { category } onChange = { e => setCategory ( e . target . value ) } >
92+
93+ { /* Category Selector */ }
94+ < label >
95+ Category:{ ' ' }
96+ < select
97+ value = { category }
98+ onChange = { ( e ) => setCategory ( e . target . value ) }
99+ disabled = { showReview }
100+ >
61101 < option value = "18" > Science: Computers</ option >
62102 < option value = "21" > Sports</ option >
63103 < option value = "23" > History</ option >
64104 </ select >
65105 </ label >
106+
107+ { /* Loading / Error */ }
66108 { loading && < Loading /> }
67109 < ErrorMessage error = { error } />
68- < p > Score: { score } </ p >
110+
111+ { /* Progress Bar */ }
112+ { totalQuestions > 0 && (
113+ < div style = { { margin : '15px 0' } } >
114+ < p >
115+ Progress: { answeredCount } / { totalQuestions } answered
116+ </ p >
117+ < div
118+ style = { {
119+ height : '10px' ,
120+ width : '100%' ,
121+ background : '#ddd' ,
122+ borderRadius : '8px' ,
123+ overflow : 'hidden' ,
124+ } }
125+ >
126+ < div
127+ style = { {
128+ width : `${ progressPercent } %` ,
129+ height : '100%' ,
130+ background : '#4caf50' ,
131+ transition : 'width 0.3s ease' ,
132+ } }
133+ > </ div >
134+ </ div >
135+ </ div >
136+ ) }
137+
138+ { /* Score + Review Button */ }
139+ < div
140+ style = { {
141+ display : 'flex' ,
142+ alignItems : 'center' ,
143+ gap : '10px' ,
144+ marginBottom : '15px' ,
145+ } }
146+ >
147+ < p style = { { margin : 0 , fontWeight : 'bold' } } > Score: { score } </ p >
148+ < button
149+ onClick = { ( ) => setShowReview ( true ) }
150+ disabled = { ! allAnswered || showReview }
151+ style = { {
152+ background : allAnswered ? '#007bff' : '#ccc' ,
153+ color : '#fff' ,
154+ padding : '6px 12px' ,
155+ border : 'none' ,
156+ borderRadius : '6px' ,
157+ cursor : allAnswered && ! showReview ? 'pointer' : 'not-allowed' ,
158+ } }
159+ >
160+ Review Answers
161+ </ button >
162+ </ div >
163+
164+ { /* Quiz Cards */ }
69165 { questions . map ( ( q , idx ) => (
70- < Card key = { idx } title = { `Q${ idx + 1 } : ${ decodeHtml ( q . question ) } ` } >
166+ < Card key = { idx } title = { `Q${ idx + 1 } : ${ decodeHtml ( q . question ) } ` } >
71167 < ul >
72- { q . answers . map ( a => (
73- < li key = { a } >
74- < button disabled = { q . picked } className = { a === q . correct_answer ? ( q . picked && a === q . picked ? 'correct' : '' ) : ( q . picked === a ? 'wrong' :'' ) } onClick = { ( ) => pick ( idx , a ) } > { decodeHtml ( a ) } </ button >
75- </ li >
76- ) ) }
168+ { q . answers . map ( ( a ) => {
169+ const isPicked = q . picked === a ;
170+ const isCorrect = a === q . correct_answer ;
171+ let btnClass = '' ;
172+
173+ if ( showReview ) {
174+ btnClass = isCorrect
175+ ? 'correct'
176+ : isPicked
177+ ? 'wrong'
178+ : 'neutral' ;
179+ } else if ( isPicked ) {
180+ btnClass = isCorrect ? 'correct' : 'wrong' ;
181+ }
182+
183+ return (
184+ < li key = { a } >
185+ < button
186+ disabled = { ! ! q . picked || showReview }
187+ onClick = { ( ) => pick ( idx , a ) }
188+ style = { {
189+ margin : '5px' ,
190+ padding : '8px 12px' ,
191+ borderRadius : '6px' ,
192+ cursor : q . picked || showReview ? 'default' : 'pointer' ,
193+ border :
194+ btnClass === 'correct'
195+ ? '2px solid green'
196+ : btnClass === 'wrong'
197+ ? '2px solid red'
198+ : '1px solid #ccc' ,
199+ background :
200+ btnClass === 'correct'
201+ ? '#c8e6c9'
202+ : btnClass === 'wrong'
203+ ? '#ffcdd2'
204+ : '#fff' ,
205+ color : 'black' , // Always black text
206+ fontWeight : '500' ,
207+ } }
208+ >
209+ { decodeHtml ( a ) }
210+ </ button >
211+ </ li >
212+ ) ;
213+ } ) }
77214 </ ul >
78215 </ Card >
79216 ) ) }
80- { /* TODO: Add scoreboard persistence */ }
217+
218+ { /* Review Section */ }
219+ { showReview && (
220+ < div style = { { marginTop : '20px' , textAlign : 'center' } } >
221+ < h3 > 🎯 Quiz Complete!</ h3 >
222+ < p >
223+ Final Score: { score } / { totalQuestions }
224+ </ p >
225+ < button
226+ onClick = { fetchQuestions }
227+ style = { {
228+ background : '#007bff' ,
229+ color : '#fff' ,
230+ padding : '10px 16px' ,
231+ border : 'none' ,
232+ borderRadius : '8px' ,
233+ cursor : 'pointer' ,
234+ } }
235+ >
236+ Play Again
237+ </ button >
238+ </ div >
239+ ) }
81240 </ div >
82241 ) ;
83242}
84243
85- function decodeHtml ( html ) {
86- const txt = document . createElement ( 'textarea' ) ;
87- txt . innerHTML = html ;
88- return txt . value ;
89- }
0 commit comments