diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8bee085..d74470c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -106,8 +106,13 @@ export default function App() { } function setPage(next: Page) { + if (next === page) return; setPageState(next); localStorage.setItem(PAGE_KEY, next); + // Push an in-app history entry so the browser/mouse Back button steps through pages + // instead of leaving the app (e.g. back to the OAuth redirect). The URL stays clean — + // the page rides in history.state, not the query string (filters never go in the URL). + window.history.pushState({ ...window.history.state, sfPage: next }, ""); } function setSidebarLayout(next: SidebarLayout) { @@ -129,6 +134,23 @@ 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. + useEffect(() => { + window.history.replaceState( + { ...window.history.state, sfPage: page }, + "" + ); + function onPop(e: PopStateEvent) { + const p = (e.state?.sfPage as Page) ?? "feed"; + setPageState(p); + localStorage.setItem(PAGE_KEY, p); + } + window.addEventListener("popstate", onPop); + return () => window.removeEventListener("popstate", onPop); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + const meQuery = useQuery({ queryKey: ["me"], queryFn: api.me }); // First-login onboarding: prompt the user to connect YouTube (and resume the flow after