From 6564f13898e75a6954bfe9d44ceea8f7b89519ee Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Fri, 29 May 2026 22:35:12 +0200 Subject: [PATCH] fix(api-client): never-resolving promise on 401 redirect kills the wrong-UI flash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- static/shared/js/api-client.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/static/shared/js/api-client.js b/static/shared/js/api-client.js index 5ff71ac6..ec412246 100644 --- a/static/shared/js/api-client.js +++ b/static/shared/js/api-client.js @@ -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'); }