Quiz: selecting an option only highlights it blue; the answer is submitted by a «بررسی» button and green/red is shown after, then «ادامه»

This commit is contained in:
2026-10-08 18:04:02 +03:30
parent d0a2822658
commit 6930c83e07

View File

@@ -101,9 +101,15 @@ export function QuizScreen({ lessonId, nodeId }: { lessonId?: number; nodeId?: s
}; };
}, [lessonId, nodeId, nodeMode, reload]); }, [lessonId, nodeId, nodeMode, reload]);
async function choose(optionId: string) { /** انتخاب فقط گزینه را آبی می‌کند؛ تا «بررسی» نزده، چیزی ثبت یا رو نمی‌شود و می‌شود عوضش کرد. */
if (!question || selected !== null || submitting) return; function choose(optionId: string) {
if (!question || answer || submitting) return;
setSelected(optionId); setSelected(optionId);
}
async function check() {
if (!question || selected === null || answer || submitting) return;
const optionId = selected;
setSubmitting(true); setSubmitting(true);
let key = answerKeys.current.get(question.id); let key = answerKeys.current.get(question.id);
if (!key) { if (!key) {
@@ -121,7 +127,7 @@ export function QuizScreen({ lessonId, nodeId }: { lessonId?: number; nodeId?: s
sounds.wrong(); sounds.wrong();
} }
} catch (err) { } catch (err) {
setSelected(null); // گزینه انتخاب‌شده می‌ماند تا کاربر دوباره «بررسی» بزند.
if (err instanceof ApiError && err.code === 'no_hearts') { if (err instanceof ApiError && err.code === 'no_hearts') {
setPhase({ setPhase({
kind: 'noHearts', kind: 'noHearts',
@@ -239,7 +245,7 @@ export function QuizScreen({ lessonId, nodeId }: { lessonId?: number; nodeId?: s
const mood: SeyarMood = !answer ? 'idle' : answer.correct ? 'excited' : outOfHearts ? 'depressed' : 'sad'; const mood: SeyarMood = !answer ? 'idle' : answer.correct ? 'excited' : outOfHearts ? 'depressed' : 'sad';
const bubble = !answer const bubble = !answer
? 'جوابِ درست رو انتخاب کن!' ? selected === null ? 'جوابِ درست رو انتخاب کن!' : 'مطمئنی؟ «بررسی» رو بزن.'
: answer.correct : answer.correct
? 'آفرین! درست بود' ? 'آفرین! درست بود'
: outOfHearts : outOfHearts
@@ -248,7 +254,7 @@ export function QuizScreen({ lessonId, nodeId }: { lessonId?: number; nodeId?: s
const shortOptions = question.options.every((o) => o.value.length <= 14); const shortOptions = question.options.every((o) => o.value.length <= 14);
const optionClass = (id: string) => { const optionClass = (id: string) => {
if (!answer) return selected === id ? 'border-coral bg-[#fff4f2] text-ink' : 'border-line bg-white text-ink'; if (!answer) return selected === id ? 'border-[#84D8FF] bg-[#DDF4FF] text-[#1581BA]' : 'border-line bg-white text-ink';
if (id === answer.correctOptionId) return 'border-green-400 bg-[#e5fdf0] text-green-700'; if (id === answer.correctOptionId) return 'border-green-400 bg-[#e5fdf0] text-green-700';
if (id === selected) return 'border-red-300 bg-[#fff0f0] text-red-500'; if (id === selected) return 'border-red-300 bg-[#fff0f0] text-red-500';
return 'border-line bg-white text-faint'; return 'border-line bg-white text-faint';
@@ -312,9 +318,10 @@ export function QuizScreen({ lessonId, nodeId }: { lessonId?: number; nodeId?: s
<motion.button <motion.button
key={option.id} key={option.id}
type="button" type="button"
onClick={() => void choose(option.id)} onClick={() => choose(option.id)}
disabled={selected !== null && !answer} disabled={submitting || answer !== null}
whileTap={{ scale: selected === null ? 0.95 : 1 }} aria-pressed={selected === option.id}
whileTap={{ scale: answer ? 1 : 0.95 }}
className={`flex min-h-14 items-center justify-center gap-1.5 rounded-2xl border-2 border-b-4 px-3 py-3 font-black transition-colors ${shortOptions ? 'text-xl' : 'text-base'} ${optionClass(option.id)}`} className={`flex min-h-14 items-center justify-center gap-1.5 rounded-2xl border-2 border-b-4 px-3 py-3 font-black transition-colors ${shortOptions ? 'text-xl' : 'text-base'} ${optionClass(option.id)}`}
> >
<span><MathText text={option.value} /></span> <span><MathText text={option.value} /></span>
@@ -324,17 +331,24 @@ export function QuizScreen({ lessonId, nodeId }: { lessonId?: number; nodeId?: s
))} ))}
</div> </div>
{answer && ( </div>
<motion.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} className="mt-1 flex flex-col gap-2.5">
<div className="flex flex-shrink-0 flex-col gap-2.5 border-t border-line bg-white px-4 pb-[max(env(safe-area-inset-bottom),1rem)] pt-3">
{answer ? (
<>
{!answer.correct && ( {!answer.correct && (
<button type="button" onClick={askWhy} className="flex items-center justify-center gap-2 rounded-2xl bg-[#F5F1FF] py-3 text-sm font-bold text-[#7857E0]"> <button type="button" onClick={askWhy} className="flex items-center justify-center gap-2 rounded-2xl bg-[#F5F1FF] py-3 text-sm font-bold text-[#7857E0]">
<Brain size={16} /> از پی بپرس چرا غلط شد <Brain size={16} /> از پی بپرس چرا غلط شد
</button> </button>
)} )}
<PrimaryButton color={answer.correct ? '#5FBF8F' : color} onClick={() => void next()}> <PrimaryButton color={answer.correct ? '#5FBF8F' : color} onClick={() => void next()}>
{isLast ? 'پایانِ تمرین' : 'سوالِ بعدی'} <ChevronLeft size={18} /> {isLast ? 'پایانِ تمرین' : 'ادامه'} <ChevronLeft size={18} />
</PrimaryButton> </PrimaryButton>
</motion.div> </>
) : (
<PrimaryButton color={color} disabled={selected === null} loading={submitting} onClick={() => void check()}>
بررسی
</PrimaryButton>
)} )}
</div> </div>