From 9a9d3c89b988146feb8819f0f88d2b1163c8a255 Mon Sep 17 00:00:00 2001 From: le king fu Date: Tue, 14 Apr 2026 08:20:20 -0400 Subject: [PATCH] feat: dismissable banner with session-storage memory (#81) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a close button and session-scoped dismissal flag so the banner can be acknowledged for the current run but reappears on the next app launch if the fallback is still active — matches the #81 acceptance criterion. - sessionStorage key survives page navigation within the run, is cleared on app restart. - Graceful on storage quota errors. - New `common.close` i18n key (FR: "Fermer", EN: "Close") used as the aria-label of the close button. --- .../settings/TokenStoreFallbackBanner.tsx | 36 +++++++++++++++++-- src/i18n/locales/en.json | 3 +- src/i18n/locales/fr.json | 3 +- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/components/settings/TokenStoreFallbackBanner.tsx b/src/components/settings/TokenStoreFallbackBanner.tsx index 26db4b3..5091539 100644 --- a/src/components/settings/TokenStoreFallbackBanner.tsx +++ b/src/components/settings/TokenStoreFallbackBanner.tsx @@ -1,11 +1,23 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { ShieldAlert } from "lucide-react"; +import { ShieldAlert, X } from "lucide-react"; import { getTokenStoreMode, TokenStoreMode } from "../../services/authService"; +// Per-session dismissal flag. Kept in sessionStorage so the banner +// returns on the next app launch if the fallback condition still +// holds — this matches the acceptance criteria from issue #81. +const DISMISS_KEY = "tokenStoreFallbackBannerDismissed"; + export default function TokenStoreFallbackBanner() { const { t } = useTranslation(); const [mode, setMode] = useState(null); + const [dismissed, setDismissed] = useState(() => { + try { + return sessionStorage.getItem(DISMISS_KEY) === "1"; + } catch { + return false; + } + }); useEffect(() => { let cancelled = false; @@ -21,12 +33,22 @@ export default function TokenStoreFallbackBanner() { }; }, []); - if (mode !== "file") return null; + if (mode !== "file" || dismissed) return null; + + const dismiss = () => { + try { + sessionStorage.setItem(DISMISS_KEY, "1"); + } catch { + // Ignore storage errors — the banner will simply hide for the + // remainder of this render cycle via state. + } + setDismissed(true); + }; return (
-
+

{t("account.tokenStore.fallback.title")}

@@ -34,6 +56,14 @@ export default function TokenStoreFallbackBanner() { {t("account.tokenStore.fallback.description")}

+
); } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index fca5cd5..c6fad2c 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -847,7 +847,8 @@ "language": "Language", "total": "Total", "darkMode": "Dark mode", - "lightMode": "Light mode" + "lightMode": "Light mode", + "close": "Close" }, "license": { "title": "License", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 5a0669e..7ae66b4 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -847,7 +847,8 @@ "language": "Langue", "total": "Total", "darkMode": "Mode sombre", - "lightMode": "Mode clair" + "lightMode": "Mode clair", + "close": "Fermer" }, "license": { "title": "Licence",