diff --git a/packages/frontend/src/components/selection-provider.tsx b/packages/frontend/src/components/selection-provider.tsx index b3acf7d..915e61c 100644 --- a/packages/frontend/src/components/selection-provider.tsx +++ b/packages/frontend/src/components/selection-provider.tsx @@ -1,6 +1,6 @@ import type { IssueResponse, OrganisationResponse, ProjectResponse } from "@sprint/shared"; import type { ReactNode } from "react"; -import { createContext, useCallback, useContext, useMemo, useState } from "react"; +import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; type SelectionContextValue = { selectedOrganisationId: number | null; @@ -29,6 +29,12 @@ const readStoredId = (key: string) => { return Number.isNaN(parsed) ? null : parsed; }; +const readStoredString = (key: string) => { + const value = localStorage.getItem(key); + if (!value) return null; + return value.trim() || null; +}; + const updateUrlParams = (updates: { orgSlug?: string | null; projectKey?: string | null; @@ -87,7 +93,14 @@ export function SelectionProvider({ children }: { children: ReactNode }) { setSelectedIssueId(null); if (id != null) localStorage.setItem("selectedOrganisationId", `${id}`); else localStorage.removeItem("selectedOrganisationId"); + if (organisation) { + localStorage.setItem("selectedOrganisationSlug", organisation.Organisation.slug.toLowerCase()); + } else { + localStorage.removeItem("selectedOrganisationSlug"); + } localStorage.removeItem("selectedProjectId"); + localStorage.removeItem("selectedProjectKey"); + localStorage.removeItem("selectedIssueNumber"); if (!options?.skipUrlUpdate) { updateUrlParams({ orgSlug: organisation?.Organisation.slug.toLowerCase() ?? null, @@ -105,6 +118,12 @@ export function SelectionProvider({ children }: { children: ReactNode }) { setSelectedIssueId(null); if (id != null) localStorage.setItem("selectedProjectId", `${id}`); else localStorage.removeItem("selectedProjectId"); + if (project) { + localStorage.setItem("selectedProjectKey", project.Project.key.toLowerCase()); + } else { + localStorage.removeItem("selectedProjectKey"); + } + localStorage.removeItem("selectedIssueNumber"); if (!options?.skipUrlUpdate) { updateUrlParams({ projectKey: project?.Project.key.toLowerCase() ?? null, @@ -116,11 +135,45 @@ export function SelectionProvider({ children }: { children: ReactNode }) { const selectIssue = useCallback((issue: IssueResponse | null, options?: SelectionOptions) => { const id = issue?.Issue.id ?? null; setSelectedIssueId(id); + if (issue) { + localStorage.setItem("selectedIssueNumber", `${issue.Issue.number}`); + } else { + localStorage.removeItem("selectedIssueNumber"); + } if (!options?.skipUrlUpdate) { updateUrlParams({ issueNumber: issue?.Issue.number ?? null }); } }, []); + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const allowIssue = window.location.pathname.startsWith("/issues"); + const updates: { + orgSlug?: string | null; + projectKey?: string | null; + issueNumber?: number | null; + } = {}; + + if (!params.get("o")) { + const storedOrgSlug = readStoredString("selectedOrganisationSlug"); + if (storedOrgSlug) updates.orgSlug = storedOrgSlug; + } + + if (!params.get("p")) { + const storedProjectKey = readStoredString("selectedProjectKey"); + if (storedProjectKey) updates.projectKey = storedProjectKey; + } + + if (allowIssue && !params.get("i")) { + const storedIssueNumber = readStoredId("selectedIssueNumber"); + if (storedIssueNumber != null) updates.issueNumber = storedIssueNumber; + } + + if (Object.keys(updates).length > 0) { + updateUrlParams(updates); + } + }, []); + const value = useMemo( () => ({ selectedOrganisationId, diff --git a/packages/frontend/src/components/top-bar.tsx b/packages/frontend/src/components/top-bar.tsx index 3eba9d8..380752a 100644 --- a/packages/frontend/src/components/top-bar.tsx +++ b/packages/frontend/src/components/top-bar.tsx @@ -27,7 +27,7 @@ import { useOrganisations } from "@/lib/query/hooks"; export default function TopBar({ showIssueForm = true }: { showIssueForm?: boolean }) { const { user } = useAuthenticatedSession(); - const { selectedOrganisationId, selectedProjectId } = useSelection(); + const { selectedOrganisationId, selectedProjectId, selectIssue } = useSelection(); const { data: organisationsData = [] } = useOrganisations(); const location = useLocation(); const navigate = useNavigate(); @@ -56,7 +56,29 @@ export default function TopBar({ showIssueForm = true }: { showIssueForm?: boole {selectedOrganisationId && } {selectedOrganisationId && ( - navigate(`/${value}`)}> + { + const orgSlug = localStorage.getItem("selectedOrganisationSlug")?.trim() ?? ""; + const projectKey = localStorage.getItem("selectedProjectKey")?.trim() ?? ""; + const issueNumber = localStorage.getItem("selectedIssueNumber")?.trim() ?? ""; + const params = new URLSearchParams(); + if (orgSlug) params.set("o", orgSlug.toLowerCase()); + if (projectKey) params.set("p", projectKey.toLowerCase()); + + if (value === "issues" && issueNumber) { + params.set("i", issueNumber); + } + + if (value === "timeline") { + localStorage.removeItem("selectedIssueNumber"); + selectIssue(null, { skipUrlUpdate: true }); + } + + const search = params.toString(); + navigate(`/${value}${search ? `?${search}` : ""}`); + }} + > Issues Timeline diff --git a/packages/frontend/src/lib/utils.ts b/packages/frontend/src/lib/utils.ts index dc28343..e67b6a3 100644 --- a/packages/frontend/src/lib/utils.ts +++ b/packages/frontend/src/lib/utils.ts @@ -21,7 +21,10 @@ export function clearAuth(): void { sessionStorage.removeItem("csrfToken"); localStorage.removeItem("user"); localStorage.removeItem("selectedOrganisationId"); + localStorage.removeItem("selectedOrganisationSlug"); localStorage.removeItem("selectedProjectId"); + localStorage.removeItem("selectedProjectKey"); + localStorage.removeItem("selectedIssueNumber"); } export function capitalise(str: string) {