feat: add retake attempt functionality and update translations for retake button
Test / backend (push) Successful in 15s
Test / frontend (push) Successful in 27s
Release Pipeline / build-and-release (push) Successful in 5m24s

This commit is contained in:
LaurensBartelds committed 2026-04-27 20:22:17 +02:00
1 parent 028c732716
commit 95cf0db5a4
5 files changed
+24 -2

No files matched your search

+16
View File
@@ -289,6 +289,21 @@ function AppInner() {
}
};
const retakeAttempt = (entry: HistoryEntry) => {
const remoteId = (entry as HistoryEntry & { _remoteId?: number })._remoteId;
if (remoteId && user && isAuthenticated()) {
void resultApi.getResult(remoteId)
.then((data) => {
loadExam(data.exam as unknown as Exam, false, null, false);
})
.catch(() => {
loadExam(entry.exam, false, null, false);
});
} else {
loadExam(entry.exam, false, null, false);
}
};
const clearAllHistory = () => {
if (confirm(t('landing.clearHistoryConfirm'))) {
clearHistory();
@@ -464,6 +479,7 @@ function AppInner() {
onResume={resume}
onClearHistory={clearAllHistory}
onReviewAttempt={reviewAttempt}
onRetakeAttempt={retakeAttempt}
/>
}
/>
+5 -1
View File
@@ -11,6 +11,7 @@ interface Props {
onResume: () => void
onClearHistory: () => void
onReviewAttempt: (entry: HistoryEntry) => void
onRetakeAttempt?: (entry: HistoryEntry) => void
}
const SAMPLES = [
@@ -19,7 +20,7 @@ const SAMPLES = [
{ path: '/samples/mixed-demo.json', title: 'Mixed Demo — Every Question Type', meta: '4 questions · Untimed · Media', mark: '◇' },
]
export default function Landing({ onLoadExam, history, onResume, onClearHistory, onReviewAttempt }: Readonly<Props>) {
export default function Landing({ onLoadExam, history, onResume, onClearHistory, onReviewAttempt, onRetakeAttempt }: Readonly<Props>) {
const { t } = useTranslation()
const [err, setErr] = useState<string | null>(null)
const [dragOver, setDragOver] = useState(false)
@@ -483,6 +484,9 @@ Set a 20-minute time limit. Include explanations for each answer."
</div>
<div className="mono muted" style={{ fontSize: 12 }}>{formatTime(h.elapsedMs)}</div>
<div className={`score-pill ${passed ? 'pass' : 'fail'}`}>{pct}%</div>
{onRetakeAttempt && (
<button className="btn btn-ghost btn-sm" onClick={() => onRetakeAttempt(h)}>{t('landing.retake')}</button>
)}
<button className="btn btn-ghost btn-sm" onClick={() => onReviewAttempt(h)}>{t('landing.review')}</button>
</div>
)
+1
View File
@@ -39,6 +39,7 @@
"clearHistory": "Clear history",
"dateTimeFormat": "{{date}} · {{correct}}/{{graded}} correct",
"review": "Review",
"retake": "Retake",
"loadError": "Could not load: {{message}}",
"loadSampleError": "Could not load sample: {{message}}",
"clearHistoryConfirm": "Clear all past attempts?"
+1
View File
@@ -39,6 +39,7 @@
"clearHistory": "Wis geschiedenis",
"dateTimeFormat": "{{date}} · {{correct}}/{{graded}} correct",
"review": "Nakijken",
"retake": "Opnieuw maken",
"loadError": "Kon niet laden: {{message}}",
"loadSampleError": "Kon voorbeeld niet laden: {{message}}",
"clearHistoryConfirm": "Alle eerdere pogingen wissen?"
+1 -1
View File
@@ -424,7 +424,7 @@ select {
}
.history-row {
display: grid;
grid-template-columns: 1fr auto auto auto;
grid-template-columns: 1fr auto auto auto auto;
gap: 24px;
padding: 14px 0;
border-bottom: 1px solid var(--line);