forked from Tadrisino/MathApp_frontend
Session, navigation, payment and resilience fixes found in a behaviour review
- http: a request whose 401 arrived after another request's refresh had finished refreshed again; it now resends with the new token. A refresh finishing after logout no longer restores tokens. - logout sends the refresh token current at send time (after a 401 -> refresh it had sent the rotated-out one and left the new session alive on the server for 30 days). - Sign-out/sign-in in another browser tab is followed (storage event). - Navigator removes its history entries when the main app unmounts (Back did nothing after logout). - Login: +98 / 0098 / 9xxxxxxxxx phone input (autofill, typing, paste) normalised; incomplete OTP is not sent (each wrong guess burns one of five). - Quiz resumes at the same question after hearts refill instead of restarting; the no-hearts screen re-checks while due (phone clock ahead of the server left it stuck at zero). - Payment return: a pending order is re-checked for about a minute instead of shown as failed; the pay button is reset when the page comes back from bfcache. - Tutor: retry keeps the quiz question id; a network error no longer forgets the conversation; daily quota detected by code ai_daily_quota. - Root error boundary and vite:preloadError reload instead of a white screen; deploy.sh keeps hashed assets of earlier builds for 30 days so open apps can still load their chunks. - Sheet: labelled dialog, focus moves in and back, Escape closes only the top sheet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -223,6 +223,25 @@ export function QuizScreen({ lessonId, nodeId }: { lessonId?: number; nodeId?: s
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* قلب برگشت: آزمون از همان سوال ادامه مییابد. قبلاً کلِ آزمون از نو بار میشد و سوال و «درستها» صفر میشدند؛
|
||||
* دانشآموزی که وسطِ جزیره قلبش تمام شده بود بعد از ساعتها انتظار باید سوالهای جوابداده را دوباره میزد.
|
||||
*/
|
||||
function resume() {
|
||||
if (!questions.length) {
|
||||
setReload((n) => n + 1);
|
||||
return;
|
||||
}
|
||||
api.hearts().then(
|
||||
(h) => {
|
||||
setHearts(h);
|
||||
if (!h.isUnlimited && h.currentHearts <= 0) setPhase({ kind: 'noHearts', nextRefillAt: h.nextRefillAt, fullRefillAt: h.fullRefillAt });
|
||||
else setPhase({ kind: 'playing' });
|
||||
},
|
||||
(err: unknown) => toast(friendlyError(err, 'وضعیت قلبها معلوم نشد، دوباره بزن'), 'error'),
|
||||
);
|
||||
}
|
||||
|
||||
const askWhy = () =>
|
||||
nav.push({
|
||||
name: 'ask',
|
||||
@@ -245,7 +264,7 @@ export function QuizScreen({ lessonId, nodeId }: { lessonId?: number; nodeId?: s
|
||||
);
|
||||
}
|
||||
if (phase.kind === 'noHearts') {
|
||||
return <NoHeartsScreen nextRefillAt={phase.nextRefillAt} fullRefillAt={phase.fullRefillAt} onRefilled={() => setReload((n) => n + 1)} />;
|
||||
return <NoHeartsScreen nextRefillAt={phase.nextRefillAt} fullRefillAt={phase.fullRefillAt} onRefilled={resume} />;
|
||||
}
|
||||
if (phase.kind === 'result') return <Result result={phase.result} total={questions.length} correct={correctCount} lessonId={lessonId} nodeId={ref?.node.id ?? nodeId} color={color} streakUp={streakUp} />;
|
||||
if (phase.kind === 'finishing' || phase.kind === 'finishError') {
|
||||
|
||||
Reference in New Issue
Block a user