fix(api-client): never-resolving promise on 401 redirect kills the wrong-UI flash
Some checks failed
CI / ruff (push) Successful in 17s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled

The b04b36a2 fix (loading=true initially) wasn't enough on its own:
once loadCard() got 401, apiClient cleared tokens, scheduled the
redirect, and threw. The caller's catch logged the error and the
finally block ran `loading = false` before the browser actually
navigated away — so Alpine re-rendered with loading=false + card=null
and the "Rejoignez notre programme" CTA flashed for a beat.

Fix: in apiClient's 3 401 paths, when redirectIfCustomerAreaUnauthorized
returns true (meaning a navigation was scheduled), return a
never-resolving promise instead of throwing. The caller's await never
returns, their .finally() never fires, the loading spinner stays up,
and the browser navigates cleanly with no intermediate render.

Other personas (admin/store/merchant) — where the helper returns false
because the path doesn't match /account/* — still get the existing
throw, preserving their current behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 22:35:12 +02:00
parent b04b36a232
commit 6564f13898

View File

@@ -155,7 +155,15 @@ class APIClient {
apiLog.debug('Error details:', data);
apiLog.info('Clearing authentication tokens');
this.clearTokens();
this.redirectIfCustomerAreaUnauthorized();
if (this.redirectIfCustomerAreaUnauthorized()) {
// Page is navigating away to /account/login. Return a
// never-resolving promise so the caller's await never
// returns and any `.finally(() => loading = false)`
// never fires — prevents a wrong-state UI flash
// between the redirect being scheduled and the browser
// actually navigating away.
return new Promise(() => {});
}
const errorMessage = data.message || data.detail || 'Unauthorized - please login again';
apiLog.error('Throwing authentication error:', errorMessage);
@@ -304,7 +312,9 @@ class APIClient {
if (response.status === 401) {
apiLog.warn('401 Unauthorized - Authentication failed');
this.clearTokens();
this.redirectIfCustomerAreaUnauthorized();
if (this.redirectIfCustomerAreaUnauthorized()) {
return new Promise(() => {});
}
throw new Error(data.message || data.detail || 'Unauthorized');
}
@@ -345,7 +355,9 @@ class APIClient {
if (response.status === 401) {
apiLog.warn('401 Unauthorized - Authentication failed');
this.clearTokens();
this.redirectIfCustomerAreaUnauthorized();
if (this.redirectIfCustomerAreaUnauthorized()) {
return new Promise(() => {});
}
throw new Error('Unauthorized');
}