diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8ac7595..502e2eb 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -234,8 +234,8 @@ export default function App() { }, []); // eslint-disable-line react-hooks/exhaustive-deps // In-app Back/Forward: stamp the initial entry with the current page, then sync `page` - // from history.state on popstate. Runs after the strip-params effect above (which resets - // history.state to null), so the initial stamp isn't clobbered. + // from history.state on popstate. (stripUrlParams preserves history.state, so this stamp + // survives a later query-string strip.) useEffect(() => { window.history.replaceState( { ...window.history.state, sfPage: page }, @@ -288,6 +288,13 @@ export default function App() { refetchInterval: (query) => (query.state.data ? 60_000 : false), }); + // Once signed in, drop any leftover query string from the pre-login flow (e.g. + // ?access=requested, ?verify, ?reset). The logged-in app reads nothing from the URL, so the + // address bar stays clean. Gated on auth so the logged-out Welcome page still reads its params. + useEffect(() => { + if (meQuery.data) stripUrlParams(); + }, [meQuery.data?.id]); // eslint-disable-line react-hooks/exhaustive-deps + // Any request that 401s means the session ended server-side. If we were signed in (cached // `me` exists), reload to the login page at once (option 2 — also covers any click that hits // the API). Guarded on cached `me` so the public login page's own /api/me 401 can't loop. diff --git a/frontend/src/lib/urlState.ts b/frontend/src/lib/urlState.ts index 85c0282..7311d3a 100644 --- a/frontend/src/lib/urlState.ts +++ b/frontend/src/lib/urlState.ts @@ -115,10 +115,11 @@ export function shareUrl(f: FeedFilters, page: Page = "feed"): string { return `${window.location.origin}${window.location.pathname}${qs ? `?${qs}` : ""}`; } -/** Strip any filter/page query params from the address bar (after hydrating from a share - * link), leaving a clean URL without touching history. */ +/** Strip the query string from the address bar (after hydrating from a share/auth link), + * leaving a clean URL. Preserves the current history.state (e.g. the in-app `sfPage` stamp) + * so it's safe to call at any time, not just before that stamp is written. */ export function stripUrlParams(): void { if (window.location.search) { - window.history.replaceState(null, "", window.location.pathname); + window.history.replaceState(window.history.state, "", window.location.pathname); } }