diff --git a/apps/web/src/portal/CustomerPortal.tsx b/apps/web/src/portal/CustomerPortal.tsx index aa877f2..d725a2d 100644 --- a/apps/web/src/portal/CustomerPortal.tsx +++ b/apps/web/src/portal/CustomerPortal.tsx @@ -39,6 +39,9 @@ export function CustomerPortal() { const [sessionExtended, setSessionExtended] = useState(false); const [clientName, setClientName] = useState(""); const [initComplete, setInitComplete] = useState(false); + // Track whether we've attempted to fetch a session — used to prevent premature redirect + // when a session fetch is in-flight (E2E mocks resolve synchronously, batched with setInitComplete) + const [sessionAttempted, setSessionAttempted] = useState(false); const { branding } = useBranding(); const [searchParams, setSearchParams] = useSearchParams(); @@ -60,6 +63,7 @@ export function CustomerPortal() { .then((s) => { if (s && s.status === "active") { setSession(s); + setSessionAttempted(true); fetch(`/api/portal/me`, { headers: { "X-Impersonation-Session-Id": s.id } }) .then(r => r.ok ? r.json() : null) .then(data => { if (data?.name) setClientName(data.name); }) @@ -68,6 +72,7 @@ export function CustomerPortal() { setSearchParams({}, { replace: true }); }) .catch(() => { + setSessionAttempted(true); setSearchParams({}, { replace: true }); }) .finally(() => setInitComplete(true)); @@ -89,10 +94,11 @@ export function CustomerPortal() { .then((s) => { if (s && s.id) { setSession(s); + setSessionAttempted(true); setClientName(devUser.name); } }) - .catch(() => {}) + .catch(() => { setSessionAttempted(true); }) .finally(() => setInitComplete(true)); } else { // No valid session: staff dev users and unauthenticated users fall through here @@ -175,8 +181,11 @@ export function CustomerPortal() { const avatarInitials = (clientName.split(" ")[0] || "G").charAt(0).toUpperCase(); // After init completes, redirect unauthenticated users to /login and staff to /admin - // The portal chrome must NEVER be visible to users without a valid client session - if (initComplete && !session) { + // The portal chrome must NEVER be visible to users without a valid client session. + // Only redirect if we have NOT attempted a session fetch yet — if a fetch is in-flight + // (E2E mock resolves synchronously, batched with setInitComplete), sessionAttempted + // is still false so we don't redirect prematurely. + if (initComplete && !session && !sessionAttempted) { const devUser = getDevUser(); if (devUser && devUser.type === "staff") { return ; @@ -251,7 +260,7 @@ export function CustomerPortal() {
{branding.logoBase64 && branding.logoMimeType ? (