Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 179 additions & 25 deletions src/pages/Trivia.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,62 +28,216 @@ export default function Trivia() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [score, setScore] = useState(0);
const [showReview, setShowReview] = useState(false);

useEffect(() => { fetchQuestions(); }, [category]);
useEffect(() => {
fetchQuestions();
}, [category]);

async function fetchQuestions() {
try {
setLoading(true); setError(null); setScore(0);
const res = await fetch(`https://opentdb.com/api.php?amount=5&category=${category}&type=multiple`);
setLoading(true);
setError(null);
setScore(0);
setShowReview(false);

const res = await fetch(
`https://opentdb.com/api.php?amount=5&category=${category}&type=multiple`
);
if (!res.ok) throw new Error('Failed to fetch');
const json = await res.json();
const qs = json.results.map(q => ({

const qs = json.results.map((q) => ({
...q,
answers: shuffle([q.correct_answer, ...q.incorrect_answers]),
picked: null
picked: null,
}));
setQuestions(qs);
} catch (e) { setError(e); } finally { setLoading(false); }
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}

function shuffle(arr) { return arr.sort(() => Math.random() - 0.5); }
function shuffle(arr) {
return arr.sort(() => Math.random() - 0.5);
}

function pick(qIndex, answer) {
setQuestions(qs => qs.map((q,i) => i===qIndex ? { ...q, picked: answer } : q));
if (questions[qIndex].correct_answer === answer) setScore(s => s+1);
setQuestions((qs) =>
qs.map((q, i) => (i === qIndex ? { ...q, picked: answer } : q))
);
if (questions[qIndex].correct_answer === answer) {
setScore((s) => s + 1);
}
}

function decodeHtml(html) {
const txt = document.createElement('textarea');
txt.innerHTML = html;
return txt.value;
}

const answeredCount = questions.filter((q) => q.picked !== null).length;
const totalQuestions = questions.length;
const progressPercent =
totalQuestions > 0 ? (answeredCount / totalQuestions) * 100 : 0;

const allAnswered = answeredCount === totalQuestions && totalQuestions > 0;

return (
<div>
<div style={{ padding: '20px' }}>
<h2>Trivia Quiz</h2>
<label>Category:
<select value={category} onChange={e => setCategory(e.target.value)}>

{/* Category Selector */}
<label>
Category:{' '}
<select
value={category}
onChange={(e) => setCategory(e.target.value)}
disabled={showReview}
>
<option value="18">Science: Computers</option>
<option value="21">Sports</option>
<option value="23">History</option>
</select>
</label>

{/* Loading / Error */}
{loading && <Loading />}
<ErrorMessage error={error} />
<p>Score: {score}</p>

{/* Progress Bar */}
{totalQuestions > 0 && (
<div style={{ margin: '15px 0' }}>
<p>
Progress: {answeredCount} / {totalQuestions} answered
</p>
<div
style={{
height: '10px',
width: '100%',
background: '#ddd',
borderRadius: '8px',
overflow: 'hidden',
}}
>
<div
style={{
width: `${progressPercent}%`,
height: '100%',
background: '#4caf50',
transition: 'width 0.3s ease',
}}
></div>
</div>
</div>
)}

{/* Score + Review Button */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '10px',
marginBottom: '15px',
}}
>
<p style={{ margin: 0, fontWeight: 'bold' }}>Score: {score}</p>
<button
onClick={() => setShowReview(true)}
disabled={!allAnswered || showReview}
style={{
background: allAnswered ? '#007bff' : '#ccc',
color: '#fff',
padding: '6px 12px',
border: 'none',
borderRadius: '6px',
cursor: allAnswered && !showReview ? 'pointer' : 'not-allowed',
}}
>
Review Answers
</button>
</div>

{/* Quiz Cards */}
{questions.map((q, idx) => (
<Card key={idx} title={`Q${idx+1}: ${decodeHtml(q.question)}`}>
<Card key={idx} title={`Q${idx + 1}: ${decodeHtml(q.question)}`}>
<ul>
{q.answers.map(a => (
<li key={a}>
<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>
</li>
))}
{q.answers.map((a) => {
const isPicked = q.picked === a;
const isCorrect = a === q.correct_answer;
let btnClass = '';

if (showReview) {
btnClass = isCorrect
? 'correct'
: isPicked
? 'wrong'
: 'neutral';
} else if (isPicked) {
btnClass = isCorrect ? 'correct' : 'wrong';
}

return (
<li key={a}>
<button
disabled={!!q.picked || showReview}
onClick={() => pick(idx, a)}
style={{
margin: '5px',
padding: '8px 12px',
borderRadius: '6px',
cursor: q.picked || showReview ? 'default' : 'pointer',
border:
btnClass === 'correct'
? '2px solid green'
: btnClass === 'wrong'
? '2px solid red'
: '1px solid #ccc',
background:
btnClass === 'correct'
? '#c8e6c9'
: btnClass === 'wrong'
? '#ffcdd2'
: '#fff',
color: 'black', // Always black text
fontWeight: '500',
}}
>
{decodeHtml(a)}
</button>
</li>
);
})}
</ul>
</Card>
))}
{/* TODO: Add scoreboard persistence */}

{/* Review Section */}
{showReview && (
<div style={{ marginTop: '20px', textAlign: 'center' }}>
<h3>🎯 Quiz Complete!</h3>
<p>
Final Score: {score} / {totalQuestions}
</p>
<button
onClick={fetchQuestions}
style={{
background: '#007bff',
color: '#fff',
padding: '10px 16px',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
}}
>
Play Again
</button>
</div>
)}
</div>
);
}

function decodeHtml(html) {
const txt = document.createElement('textarea');
txt.innerHTML = html;
return txt.value;
}
Loading