Skip to content

Commit 46742e5

Browse files
Merge pull request #51 from Arghyadeep09/feature/Add-Progress-Bar-and-Review-mode
feat: enhance Trivia component with review functionality and progress tracking
2 parents 70123ee + 4fe8111 commit 46742e5

File tree

1 file changed

+179
-25
lines changed

1 file changed

+179
-25
lines changed

src/pages/Trivia.jsx

Lines changed: 179 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)