From 1e1fc33b6a075c1bc2f13d0b3d9f6f1e42c7e147 Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Thu, 24 Sep 2026 12:04:58 -0400 Subject: [PATCH] fix(session): dismiss expiry warning toast when signed out or extended The one-shot heads-up toast used duration:Infinity with no id, so it lingered after auto sign-out (when the banner unmounts) and its Extend button 403'd on the expired token. Give the toast a stable id and dismiss it when the banner leaves the warn window or unmounts, matching the MissionReminderToasts pattern. --- .../frontend/session/SessionExpiryBanner.tsx | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/components/frontend/session/SessionExpiryBanner.tsx b/src/components/frontend/session/SessionExpiryBanner.tsx index 3ff6e12..89682e4 100644 --- a/src/components/frontend/session/SessionExpiryBanner.tsx +++ b/src/components/frontend/session/SessionExpiryBanner.tsx @@ -16,6 +16,13 @@ import { } from "@/lib/session/countdown"; import { SESSION_EXTENDED_EVENT } from "@/hooks/useGameTick"; +/** + * Stable id for the one-shot "session expiring" heads-up toast. A fixed id lets + * us dismiss it deterministically (e.g. on sign-out) instead of relying on the + * opaque id returned by `toast.info`. Mirrors MissionReminderToasts. + */ +const SESSION_EXPIRY_TOAST_ID = "session-expiry-warning"; + interface SessionExpiryBannerProps { /** Absolute expiry (ms since epoch), server-computed from the JWT `exp`. */ sessionExp: number | null; @@ -70,6 +77,7 @@ export function SessionExpiryBanner({ toastFiredRef.current = true; const minutes = Math.round(warnMs / 60_000); toast.info(`Your session expires in about ${minutes} min.`, { + id: SESSION_EXPIRY_TOAST_ID, action: { label: "Extend", onClick: () => void extendSession(), @@ -83,6 +91,20 @@ export function SessionExpiryBanner({ if (phase === "warn") fireToast(); }, [phase, warnMs, fireToast]); + // The heads-up toast is only relevant while we're in the warn window. Dismiss it + // when the user extends (phase leaves "warn") or dismisses the banner, and again + // on unmount (sign-out swaps the shell for the landing page) — otherwise the + // duration:Infinity toast lingers for a signed-out user and its Extend button 403s. + useEffect(() => { + if (!visible) toast.dismiss(SESSION_EXPIRY_TOAST_ID); + }, [visible]); + + useEffect(() => { + return () => { + toast.dismiss(SESSION_EXPIRY_TOAST_ID); + }; + }, []); + const extendSession = useCallback(async () => { setExtending(true); setError(null);