@@ -38,6 +38,9 @@ function scoreQuiz(questions: QuizQuestion[], answers: Answers, passingScore: nu
3838 return { score, passed : score >= passingScore , feedback }
3939}
4040
41+ const optionBase =
42+ 'w-full text-left rounded-[6px] border px-4 py-3 text-[14px] transition-colors disabled:cursor-default'
43+
4144/**
4245 * Interactive quiz component with per-question feedback and retry support.
4346 * Scoring is performed entirely client-side.
@@ -46,11 +49,12 @@ export function LessonQuiz({ lessonId, quizConfig, onPass }: LessonQuizProps) {
4649 const [ answers , setAnswers ] = useState < Answers > ( { } )
4750 const [ result , setResult ] = useState < QuizResult | null > ( null )
4851
49- const handleAnswer = ( qi : number , value : number | number [ ] | boolean ) => {
50- setAnswers ( ( prev ) => ( { ...prev , [ qi ] : value } ) )
52+ const handleAnswer = ( qi : number , value : number | boolean ) => {
53+ if ( ! result ) setAnswers ( ( prev ) => ( { ...prev , [ qi ] : value } ) )
5154 }
5255
5356 const handleMultiSelect = ( qi : number , oi : number ) => {
57+ if ( result ) return
5458 setAnswers ( ( prev ) => {
5559 const current = ( prev [ qi ] as number [ ] | undefined ) ?? [ ]
5660 const next = current . includes ( oi ) ? current . filter ( ( i ) => i !== oi ) : [ ...current , oi ]
@@ -61,9 +65,9 @@ export function LessonQuiz({ lessonId, quizConfig, onPass }: LessonQuizProps) {
6165 const allAnswered = quizConfig . questions . every ( ( _ , i ) => answers [ i ] !== undefined )
6266
6367 const handleSubmit = ( ) => {
64- const result = scoreQuiz ( quizConfig . questions , answers , quizConfig . passingScore )
65- setResult ( result )
66- if ( result . passed ) {
68+ const scored = scoreQuiz ( quizConfig . questions , answers , quizConfig . passingScore )
69+ setResult ( scored )
70+ if ( scored . passed ) {
6771 markLessonComplete ( lessonId )
6872 onPass ?.( )
6973 }
@@ -89,10 +93,13 @@ export function LessonQuiz({ lessonId, quizConfig, onPass }: LessonQuizProps) {
8993 { q . type === 'multiple_choice' && (
9094 < div className = 'space-y-2' >
9195 { q . options . map ( ( opt , oi ) => (
92- < label
96+ < button
9397 key = { oi }
98+ type = 'button'
99+ onClick = { ( ) => handleAnswer ( qi , oi ) }
100+ disabled = { Boolean ( result ) }
94101 className = { cn (
95- 'flex cursor-pointer items-center gap-3 rounded-[6px] border px-4 py-3 text-[14px] transition-colors' ,
102+ optionBase ,
96103 answers [ qi ] === oi
97104 ? 'border-[#ECECEC]/40 bg-[#ECECEC]/5 text-[#ECECEC]'
98105 : 'border-[#2A2A2A] text-[#999] hover:border-[#3A3A3A] hover:bg-[#272727]' ,
@@ -105,16 +112,8 @@ export function LessonQuiz({ lessonId, quizConfig, onPass }: LessonQuizProps) {
105112 'border-[#f44336]/40 bg-[#f44336]/5 text-[#f44336]'
106113 ) }
107114 >
108- < input
109- type = 'radio'
110- name = { `q-${ qi } ` }
111- checked = { answers [ qi ] === oi }
112- onChange = { ( ) => handleAnswer ( qi , oi ) }
113- disabled = { Boolean ( result ) }
114- className = 'sr-only'
115- />
116115 { opt }
117- </ label >
116+ </ button >
118117 ) ) }
119118 </ div >
120119 ) }
@@ -124,10 +123,13 @@ export function LessonQuiz({ lessonId, quizConfig, onPass }: LessonQuizProps) {
124123 { ( [ 'True' , 'False' ] as const ) . map ( ( label ) => {
125124 const val = label === 'True'
126125 return (
127- < label
126+ < button
128127 key = { label }
128+ type = 'button'
129+ onClick = { ( ) => handleAnswer ( qi , val ) }
130+ disabled = { Boolean ( result ) }
129131 className = { cn (
130- 'flex flex -1 cursor-pointer items-center justify-center rounded-[6px] border px-4 py-3 text-[14px] transition-colors' ,
132+ 'flex-1 rounded-[6px] border px-4 py-3 text-[14px] transition-colors disabled:cursor-default ' ,
131133 answers [ qi ] === val
132134 ? 'border-[#ECECEC]/40 bg-[#ECECEC]/5 text-[#ECECEC]'
133135 : 'border-[#2A2A2A] text-[#999] hover:border-[#3A3A3A] hover:bg-[#272727]' ,
@@ -140,16 +142,8 @@ export function LessonQuiz({ lessonId, quizConfig, onPass }: LessonQuizProps) {
140142 'border-[#f44336]/40 bg-[#f44336]/5 text-[#f44336]'
141143 ) }
142144 >
143- < input
144- type = 'radio'
145- name = { `q-${ qi } ` }
146- checked = { answers [ qi ] === val }
147- onChange = { ( ) => handleAnswer ( qi , val ) }
148- disabled = { Boolean ( result ) }
149- className = 'sr-only'
150- />
151145 { label }
152- </ label >
146+ </ button >
153147 )
154148 } ) }
155149 </ div >
@@ -160,10 +154,13 @@ export function LessonQuiz({ lessonId, quizConfig, onPass }: LessonQuizProps) {
160154 { q . options . map ( ( opt , oi ) => {
161155 const selected = ( ( answers [ qi ] as number [ ] ) ?? [ ] ) . includes ( oi )
162156 return (
163- < label
157+ < button
164158 key = { oi }
159+ type = 'button'
160+ onClick = { ( ) => handleMultiSelect ( qi , oi ) }
161+ disabled = { Boolean ( result ) }
165162 className = { cn (
166- 'flex cursor-pointer items-center gap-3 rounded-[6px] border px-4 py-3 text-[14px] transition-colors' ,
163+ optionBase ,
167164 selected
168165 ? 'border-[#ECECEC]/40 bg-[#ECECEC]/5 text-[#ECECEC]'
169166 : 'border-[#2A2A2A] text-[#999] hover:border-[#3A3A3A] hover:bg-[#272727]' ,
@@ -176,15 +173,8 @@ export function LessonQuiz({ lessonId, quizConfig, onPass }: LessonQuizProps) {
176173 'border-[#f44336]/40 bg-[#f44336]/5 text-[#f44336]'
177174 ) }
178175 >
179- < input
180- type = 'checkbox'
181- checked = { selected }
182- onChange = { ( ) => handleMultiSelect ( qi , oi ) }
183- disabled = { Boolean ( result ) }
184- className = 'h-3.5 w-3.5 accent-[#ECECEC]'
185- />
186176 { opt }
187- </ label >
177+ </ button >
188178 )
189179 } ) }
190180 </ div >
0 commit comments