From b92d2b9232508c4319884fcf7161052b241240aa Mon Sep 17 00:00:00 2001
From: Daniel
Date: Wed, 1 Apr 2026 02:44:00 +0200
Subject: [PATCH] Quiz UX: remove popups, hide skipped questions warning
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- safeNavigate: removed window.confirm() entirely — navigating between
question numbers never loses answers (they're in React state), so the
popup was wrong and annoying
- handleSubmit: replaced window.confirm() with a 3s toast pill at the
bottom of the screen (dark, slides up, fades away automatically)
- ModeSelectScreen: removed skipped-questions warning block — internal
extraction detail, not useful to show users
- Added fadeInUp CSS keyframe for the toast animation
Co-Authored-By: Claude Sonnet 4.6 (1M context)
---
frontend/src/index.css | 5 ++++
frontend/src/pages/QuizPage.jsx | 45 ++++++++++++++++-----------------
2 files changed, 27 insertions(+), 23 deletions(-)
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 1494493..13c9c43 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -265,6 +265,11 @@ body {
.spinner { display: inline-block; width: 20px; height: 20px; border: 3px solid var(--border); border-top-color: var(--primary); border-radius: 50%; animation: spin 0.6s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
+@keyframes fadeInUp {
+ from { opacity: 0; transform: translateX(-50%) translateY(8px); }
+ to { opacity: 1; transform: translateX(-50%) translateY(0); }
+}
+
/* ── Section items ──────────────────────────────────────────── */
.section-item { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; border: 1px solid var(--border); border-radius: 8px; margin-bottom: 8px; background: var(--card-bg); }
diff --git a/frontend/src/pages/QuizPage.jsx b/frontend/src/pages/QuizPage.jsx
index e97c713..48290e3 100644
--- a/frontend/src/pages/QuizPage.jsx
+++ b/frontend/src/pages/QuizPage.jsx
@@ -85,18 +85,6 @@ function ModeSelectScreen({ quiz, voices, onStart }) {
{quiz.time_limit_minutes ? ` · ${quiz.time_limit_minutes} min limit` : ''}
- {skipped.length > 0 && (
-
-
- ⚠️ {skipped.length} question{skipped.length > 1 ? 's' : ''} skipped (no correct answer found):
-
-
- {skipped.map((q, i) => (
- - {q}…
- ))}
-
-
- )}
Choose how to take this quiz:
@@ -147,9 +135,17 @@ export default function QuizPage() {
const [attemptId, setAttemptId] = useState(null)
const [timeLeft, setTimeLeft] = useState(null)
const [totalTime, setTotalTime] = useState(null)
+ const [toast, setToast] = useState('')
const timerRef = useRef(null)
+ const toastRef = useRef(null)
const hasStarted = useRef(false)
+ const showToast = (msg) => {
+ setToast(msg)
+ clearTimeout(toastRef.current)
+ toastRef.current = setTimeout(() => setToast(''), 3000)
+ }
+
useEffect(() => {
const load = async () => {
try {
@@ -198,21 +194,13 @@ useEffect(() => {
const setAnswer = (questionId, value) => setAnswers(prev => ({ ...prev, [questionId]: value }))
- // Warn before navigating away mid-quiz
- const safeNavigate = (targetIdx) => {
- if (Object.keys(answers).length > 0 && attemptId) {
- const confirmed = window.confirm(
- 'You have answered some questions.\n\n• OK — go back (progress saved so far)\n• Cancel — stay on current question'
- )
- if (!confirmed) return
- }
- setCurrentIdx(targetIdx)
- }
+ const safeNavigate = (targetIdx) => setCurrentIdx(targetIdx)
const handleSubmit = useCallback(async (autoSubmit = false) => {
if (!attemptId || submitting) return
if (!autoSubmit && Object.keys(answers).length === 0) {
- if (!window.confirm('You haven\'t answered any questions. Submit anyway?')) return
+ showToast('No answers selected — submitting with 0 answered.')
+ await new Promise(r => setTimeout(r, 1200))
}
clearInterval(timerRef.current)
setSubmitting(true)
@@ -251,6 +239,17 @@ useEffect(() => {
return (
+ {toast && (
+
+ {toast}
+
+ )}