Public Access
feat: add notes functionality to exam state and update UI for note-taking
This commit is contained in:
1 parent
d0b1ac0145
commit
c4c1e1a45a
5 files changed
+90
-31
No files matched your search
+28
-19
@@ -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'} />
|
||||
|
||||
Reference in new issue
Block a user