// router.jsx — URL ↔ screen for Grant4Me (History API). // Paths live under /app/* so they never collide with /graphql, /healthz, /download. const G4M_ROUTER = (() => { const WIZARD = ["company", "reference", "upload", "run", "confirm", "result"]; function pathFromNav(nav) { if (!nav || !nav.screen) return "/app"; const s = nav.screen; const id = nav.runId ? encodeURIComponent(String(nav.runId)) : null; if (s === "login") return "/app/login"; if (s === "register") return "/app/register"; if (s === "dashboard") return "/app"; if (s === "team") return "/app/team"; if (s === "settings") return "/app/settings"; if (s === "pickProject") return "/app/new/select"; if (s === "company") return "/app/new"; if (s === "reference") { const q = nav.refStage === "review" ? "?stage=review" : ""; return "/app/new/project" + q; } if (s === "upload") return "/app/new/upload"; if (s === "run") return id ? `/app/runs/${id}` : "/app/new/run"; if (s === "confirm") return id ? `/app/runs/${id}/review` : "/app/new/review"; if (s === "result") return id ? `/app/runs/${id}/result` : "/app/new/result"; return "/app"; } function navFromLocation(loc) { const raw = (loc && loc.pathname) || "/"; let path = raw; // Legacy entry: / or /Grant4me.html → treat as app root (caller may replaceState). if (path === "/" || path === "/Grant4me.html" || path.endsWith("/Grant4me.html")) { return { screen: "dashboard", refStage: "intro" }; } // Strip trailing slash (except root) if (path.length > 1 && path.endsWith("/")) path = path.slice(0, -1); const params = new URLSearchParams((loc && loc.search) || ""); const stage = params.get("stage"); if (path === "/app/login") return { screen: "login", refStage: "intro" }; if (path === "/app/register") return { screen: "register", refStage: "intro" }; if (path === "/app" || path === "/app/") return { screen: "dashboard", refStage: "intro" }; if (path === "/app/team") return { screen: "team", refStage: "intro" }; if (path === "/app/settings") return { screen: "settings", refStage: "intro" }; if (path === "/app/new/select") return { screen: "pickProject", refStage: "intro" }; if (path === "/app/new") return { screen: "company", refStage: "intro" }; if (path === "/app/new/project") { return { screen: "reference", refStage: stage === "review" ? "review" : "intro" }; } if (path === "/app/new/upload") return { screen: "upload", refStage: "intro" }; if (path === "/app/new/run") return { screen: "run", refStage: "intro" }; if (path === "/app/new/review") return { screen: "confirm", refStage: "intro" }; if (path === "/app/new/result") return { screen: "result", refStage: "intro" }; let m = path.match(/^\/app\/runs\/([^/]+)\/review$/); if (m) return { screen: "confirm", runId: decodeURIComponent(m[1]), refStage: "intro" }; m = path.match(/^\/app\/runs\/([^/]+)\/result$/); if (m) return { screen: "result", runId: decodeURIComponent(m[1]), refStage: "intro" }; m = path.match(/^\/app\/runs\/([^/]+)$/); if (m) return { screen: "run", runId: decodeURIComponent(m[1]), refStage: "intro" }; return { screen: "dashboard", refStage: "intro" }; } function isWizardScreen(screen) { return WIZARD.indexOf(screen) >= 0; } /** Mid-flow wizard (not finished result): warn before abandoning. */ function shouldConfirmLeave(screen) { return isWizardScreen(screen) && screen !== "result"; } function samePath(a, b) { const norm = (p) => { if (!p) return "/"; const [path, qs] = String(p).split("?"); let x = path; if (x.length > 1 && x.endsWith("/")) x = x.slice(0, -1); return x + (qs ? "?" + qs : ""); }; return norm(a) === norm(b); } return { pathFromNav, navFromLocation, isWizardScreen, shouldConfirmLeave, samePath, WIZARD }; })(); Object.assign(window, { G4M_ROUTER });