feat: add notes functionality to exam state and update UI for note-taking

This commit is contained in:
LaurensBartelds committed 2026-04-22 22:48:24 +02:00
1 parent d0b1ac0145
commit c4c1e1a45a
5 files changed
+90 -31

No files matched your search

+28 -19
View File
@@ -14,6 +14,11 @@ import ResetPassword from './components/ResetPassword'
const DEFAULT_TWEAKS: Tweaks = { theme: 'light', fontSize: 'medium', feedbackMode: 'auto' }
const withNotes = (state: ExamState): ExamState => ({
...state,
notes: state.notes ?? '',
})
function AppInner() {
const { user, loading: authLoading, logout } = useAuth()
const [screen, setScreen] = useState<AppScreen>('landing')
@@ -27,8 +32,8 @@ function AppInner() {
// Check URL for verify/reset-password routes on mount
useEffect(() => {
const path = window.location.pathname
const search = window.location.search
const path = globalThis.location.pathname
const search = globalThis.location.search
if (path === '/verify' || search.includes('token=') && path === '/verify') {
setScreen('verify')
} else if (path === '/reset-password') {
@@ -56,7 +61,7 @@ function AppInner() {
scorePoints: r.score_points,
passingScore: r.passing_score,
exam: { title: r.title, questions: [] } as unknown as Exam,
state: { currentIdx: 0, answers: {}, flags: {}, startedAt: 0, feedbackMode: 'end' as const } as ExamState,
state: { currentIdx: 0, answers: {}, flags: {}, notes: '', startedAt: 0, feedbackMode: 'end' as const } as ExamState,
_remoteId: r.id,
}))
setHistory(apiHistory)
@@ -81,16 +86,25 @@ function AppInner() {
const updateTweak = <K extends keyof Tweaks>(key: K, value: Tweaks[K]) =>
setTweaks((t) => ({ ...t, [key]: value }))
const setExamStateStrict: React.Dispatch<React.SetStateAction<ExamState>> = (nextState) => {
setExamState((prev) => {
if (!prev) return prev
return typeof nextState === 'function'
? (nextState as (prevState: ExamState) => ExamState)(prev)
: nextState
})
}
const loadExam = (data: Exam) => {
const fm = tweaks.feedbackMode === 'auto' ? (data.feedbackMode ?? 'end') : tweaks.feedbackMode
setExam(data)
setExamState({ currentIdx: 0, answers: {}, flags: {}, revealed: {}, startedAt: Date.now(), feedbackMode: fm })
setExamState({ currentIdx: 0, answers: {}, flags: {}, notes: '', revealed: {}, startedAt: Date.now(), feedbackMode: fm })
setScreen('exam')
}
const resume = () => {
const saved = loadState()
if (saved?.exam && saved?.state) { setExam(saved.exam); setExamState(saved.state); setScreen('exam') }
if (saved?.exam && saved?.state) { setExam(saved.exam); setExamState(withNotes(saved.state)); setScreen('exam') }
}
const submit = useCallback(() => {
@@ -124,6 +138,7 @@ function AppInner() {
gradedCount: entry.gradedCount,
passingScore: entry.passingScore,
elapsedMs: entry.elapsedMs,
notes: entry.state.notes,
state: entry.state,
exam: entry.exam,
}),
@@ -155,15 +170,15 @@ function AppInner() {
}>(`/api/results/${remoteId}`)
.then((data) => {
setExam(data.exam)
setExamState(data.state)
setExamState(withNotes(data.state))
setScreen('review')
})
.catch(() => {
// Fallback to local data
setExam(entry.exam); setExamState(entry.state); setScreen('review')
setExam(entry.exam); setExamState(withNotes(entry.state)); setScreen('review')
})
} else {
setExam(entry.exam); setExamState(entry.state); setScreen('review')
setExam(entry.exam); setExamState(withNotes(entry.state)); setScreen('review')
}
}
@@ -174,14 +189,8 @@ function AppInner() {
}
}
const handleVerifyDone = () => {
window.history.replaceState({}, '', '/')
setScreen('landing')
setAuthModalOpen(true)
}
const handleResetDone = () => {
window.history.replaceState({}, '', '/')
const handleAuthFlowDone = () => {
globalThis.history.replaceState({}, '', '/')
setScreen('landing')
setAuthModalOpen(true)
}
@@ -195,7 +204,7 @@ function AppInner() {
<span className="brand-name">BrainWave</span>
</div>
</header>
<EmailVerify onDone={handleVerifyDone} />
<EmailVerify onDone={handleAuthFlowDone} />
</div>
)
}
@@ -209,7 +218,7 @@ function AppInner() {
<span className="brand-name">BrainWave</span>
</div>
</header>
<ResetPassword onDone={handleResetDone} />
<ResetPassword onDone={handleAuthFlowDone} />
</div>
)
}
@@ -274,7 +283,7 @@ function AppInner() {
<Landing onLoadExam={loadExam} history={history} onResume={resume} onClearHistory={clearAllHistory} onReviewAttempt={reviewAttempt} />
)}
{screen === 'exam' && exam && examState && (
<ExamRunner exam={exam} state={examState} setState={setExamState as React.Dispatch<React.SetStateAction<ExamState>>} onSubmit={submit} onExit={exit} />
<ExamRunner exam={exam} state={examState} setState={setExamStateStrict} onSubmit={submit} onExit={exit} />
)}
{(screen === 'results' || screen === 'review') && exam && examState && (
<Results exam={exam} state={examState} onRestart={restart} onExit={exit} reviewOnly={screen === 'review'} />