diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 502e2eb..9489035 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -365,30 +365,6 @@ export default function App() { saveLayoutLocal(l); } if (isSupported(prefs.language)) setLanguage(prefs.language); - // Nudge admins when access requests are waiting (in lieu of a server-push bell). - const pending = meQuery.data?.pending_invites ?? 0; - if (meQuery.data?.role === "admin") { - // Keep a single, current nudge: drop any prior one (persisted copies reload as dismissed - // history, so they'd otherwise pile up one-per-reload) before re-issuing the live notice. - removeByMetaKind("access-requests"); - if (pending > 0) { - notify({ - level: "warning", - title: t("common.accessRequestsTitle"), - message: t("common.accessRequestsMessage", { count: pending }), - // Durable inbox link (survives reload, unlike a live action callback); the toast also - // gets a click via the action. - meta: { kind: "access-requests" }, - action: { - label: t("common.accessRequestsReview"), - onClick: () => { - focusAccessRequestsTab(); - setPage("users"); - }, - }, - }); - } - } // The demo account is shared: there are no subscriptions, so default it to the whole // library (the "my" feed would be empty). The communal-state warning is a permanent // banner (see DemoBanner below), not a toast that re-pops on every reload. @@ -398,6 +374,35 @@ export default function App() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [meQuery.data?.id]); + // Nudge admins when access requests are waiting (in lieu of a server-push bell). Keyed on the + // pending COUNT so it re-resolves the moment an approval/denial changes it — not only on a + // fresh load (which is why the notice used to linger until F5 after an approval). + useEffect(() => { + if (meQuery.data?.role !== "admin") return; + // Keep a single, current nudge: drop any prior one (persisted copies reload as dismissed + // history, so they'd otherwise pile up one-per-reload) before re-issuing the live notice. + removeByMetaKind("access-requests"); + const pending = meQuery.data.pending_invites ?? 0; + if (pending > 0) { + notify({ + level: "warning", + title: t("common.accessRequestsTitle"), + message: t("common.accessRequestsMessage", { count: pending }), + // Durable inbox link (survives reload, unlike a live action callback); the toast also + // gets a click via the action. + meta: { kind: "access-requests" }, + action: { + label: t("common.accessRequestsReview"), + onClick: () => { + focusAccessRequestsTab(); + setPage("users"); + }, + }, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [meQuery.data?.role, meQuery.data?.pending_invites]); + // Theme is part of the Settings draft: apply locally for preview, persist on Save. function setTheme(next: ThemePrefs) { setThemeState(next);