@@ -35,6 +35,7 @@ export function ProjectKeysTable({
keys,
totalCount,
isLoading,
+ isError = false,
pagination,
onPaginationChange,
}: ProjectKeysTableProps) {
@@ -49,8 +50,9 @@ export function ProjectKeysTable({
pagination={pagination}
onPaginationChange={onPaginationChange}
rowCount={totalCount}
- pageSizeOptions={PAGE_SIZE_OPTIONS}
+ pageSizeOptions={PROJECT_KEYS_PAGE_SIZE_OPTIONS}
isLoading={isLoading}
+ isError={isError}
loadingMessage="Loading keys…"
noDataMessage={
}
size="compact"
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsPage.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsPage.test.tsx
index 66d7413f500..309da01b295 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsPage.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsPage.test.tsx
@@ -190,22 +190,48 @@ describe("ProjectsPage", () => {
it("should reset to the first page when the search text changes", async () => {
const user = userEvent.setup();
+ const onUrlUpdate = vi.fn<(event: UrlUpdateEvent) => void>();
const manyProjects = Array.from({ length: 12 }, (_, i) => ({
...mockProjects[0],
project_id: `proj-${i + 1}`,
project_alias: `Project ${String(i + 1).padStart(2, "0")}`,
}));
mockUseProjects.mockReturnValue({ data: manyProjects, isLoading: false });
- renderWithProviders(
);
+ renderWithProviders(
, { onUrlUpdate });
await user.click(screen.getByTestId("pagination-next"));
expect(screen.getByTestId("pagination-page")).toHaveTextContent("Page 2 of 2");
+ await waitFor(() => expect(onUrlUpdate.mock.calls.at(-1)?.[0].searchParams.get("page")).toBe("2"));
fireEvent.change(screen.getByPlaceholderText(/search projects/i), { target: { value: "Project 01" } });
await waitFor(() => {
expect(screen.getByText("Project 01")).toBeInTheDocument();
expect(screen.getByTestId("pagination-page")).toHaveTextContent("Page 1 of 1");
});
+ await waitFor(() => expect(onUrlUpdate.mock.calls.at(-1)?.[0].queryString).toBe("?project_search=Project+01"));
+ expect(onUrlUpdate).toHaveBeenCalledTimes(2);
+ });
+
+ it("should restore the search box and filtered list from a ?project_search= deep link", () => {
+ mockUseProjects.mockReturnValue({ data: mockProjects, isLoading: false });
+ renderWithProviders(
, { searchParams: "?project_search=Beta" });
+
+ expect(screen.getByPlaceholderText(/search projects/i)).toHaveValue("Beta");
+ expect(screen.getByText("Beta Project")).toBeInTheDocument();
+ expect(screen.queryByText("Alpha Project")).not.toBeInTheDocument();
+ });
+
+ it("should remove ?project_search= when the search is cleared", async () => {
+ const user = userEvent.setup();
+ const onUrlUpdate = vi.fn<(event: UrlUpdateEvent) => void>();
+ mockUseProjects.mockReturnValue({ data: mockProjects, isLoading: false });
+ renderWithProviders(
, { searchParams: "?project_search=Beta", onUrlUpdate });
+
+ await user.click(screen.getByRole("button", { name: /clear search/i }));
+
+ await waitFor(() => expect(onUrlUpdate).toHaveBeenLastCalledWith(expect.objectContaining({ queryString: "" })));
+ expect(screen.getByPlaceholderText(/search projects/i)).toHaveValue("");
+ expect(screen.getByText("Alpha Project")).toBeInTheDocument();
});
it("should open the detail view directly from a ?project= deep link", () => {
@@ -250,6 +276,24 @@ describe("ProjectsPage", () => {
expect(screen.getByText("Alpha Project")).toBeInTheDocument();
});
+ it("should drop the project's key table state but keep the list's search and page when the detail view is closed", async () => {
+ const user = userEvent.setup();
+ const onUrlUpdate = vi.fn<(event: UrlUpdateEvent) => void>();
+ mockUseProjects.mockReturnValue({ data: mockProjects, isLoading: false });
+ renderWithProviders(
, {
+ searchParams:
+ "?page=2&project_search=Project&project=proj-1&keys_page=3&keys_page_size=10&keys_search=prod&keys_sort_by=spend&keys_sort_order=asc",
+ onUrlUpdate,
+ });
+
+ await user.click(screen.getByRole("button", { name: /back to projects/i }));
+
+ await waitFor(() => expect(onUrlUpdate).toHaveBeenCalledTimes(1));
+ const [update] = onUrlUpdate.mock.calls[0];
+ expect(update.queryString).toBe("?page=2&project_search=Project");
+ expect(update.options.history).toBe("replace");
+ });
+
it("should resolve team alias from the teams list in the Team column", () => {
mockUseTeams.mockReturnValue({
data: [{ team_id: "team-1", team_alias: "Engineering", models: [] }],
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsPage.tsx b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsPage.tsx
index 4aba2bb627d..2d3c1acf75e 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsPage.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsPage.tsx
@@ -9,6 +9,7 @@ import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "
import { CreateProjectModal } from "./ProjectModals/CreateProjectModal";
import { ProjectDetail } from "./ProjectDetailsPage";
import { ProjectsTable } from "./ProjectsTable";
+import { useClearProjectKeysTableState, useProjectsTableState } from "./useProjectsUrlState";
export function ProjectsPage() {
const { data: projects, isLoading } = useProjects();
@@ -18,8 +19,9 @@ export function ProjectsPage() {
"project",
parseAsString.withOptions({ history: "push" }),
);
+ const clearProjectKeysTableState = useClearProjectKeysTableState();
+ const { search: searchText, setSearch: setSearchText } = useProjectsTableState();
const [isCreateModalVisible, setIsCreateModalVisible] = useState(false);
- const [searchText, setSearchText] = useState("");
const teamAliasMap = useMemo(() => {
const map = new Map
();
@@ -44,13 +46,13 @@ export function ProjectsPage() {
});
}, [projects, searchText, teamAliasMap]);
+ const closeProject = () => {
+ void setSelectedProjectId(null, { history: "replace" });
+ clearProjectKeysTableState();
+ };
+
if (selectedProjectId) {
- return (
- void setSelectedProjectId(null, { history: "replace" })}
- />
- );
+ return ;
}
return (
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsTable.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsTable.test.tsx
index a1b59f6035c..aaecf98d4ab 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsTable.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsTable.test.tsx
@@ -83,6 +83,7 @@ describe("ProjectsTable pagination URL state", () => {
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled());
const [update] = onUrlUpdate.mock.calls[0];
expect(update.searchParams.get("page")).toBe("2");
+ expect(update.searchParams.has("page_size")).toBe(false);
expect(update.options.history).toBe("push");
expect(firstDataRow().getByText("Project 11")).toBeInTheDocument();
});
@@ -147,6 +148,7 @@ describe("ProjectsTable pagination URL state", () => {
const lastUpdate = onUrlUpdate.mock.calls.at(-1)?.[0];
expect(lastUpdate.searchParams.get("page")).toBeNull();
expect(lastUpdate.searchParams.get("page_size")).toBe("25");
+ expect(lastUpdate.options.history).toBe("push");
});
it("should apply both params from a ?page=2&page_size=25 deep link so the restored view matches", () => {
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsTable.tsx b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsTable.tsx
index 74242f3ed45..1d63958faed 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsTable.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsTable.tsx
@@ -2,13 +2,13 @@
import { SortingState } from "@tanstack/react-table";
import { FolderKanban } from "lucide-react";
-import { parseAsInteger, useQueryStates } from "nuqs";
import { useMemo, useState } from "react";
import { ProjectResponse } from "@/app/(dashboard)/hooks/projects/useProjects";
import { DataTable, DataTablePagination } from "@/components/shared/DataTable";
import { getProjectsTableColumns } from "./ProjectsTableColumns";
+import { PROJECTS_DEFAULT_PAGE_SIZE, useProjectsTableState } from "./useProjectsUrlState";
interface ProjectsTableProps {
projects: ProjectResponse[];
@@ -19,8 +19,7 @@ interface ProjectsTableProps {
isTeamsLoading: boolean;
}
-const DEFAULT_PAGE_SIZE = 10;
-const PAGE_SIZE_OPTIONS = [DEFAULT_PAGE_SIZE, 25, 50];
+const PAGE_SIZE_OPTIONS = [PROJECTS_DEFAULT_PAGE_SIZE, 25, 50];
function EmptyState({ isFiltered }: { isFiltered: boolean }) {
return (
@@ -47,11 +46,8 @@ export function ProjectsTable({
isTeamsLoading,
}: ProjectsTableProps) {
const [sorting, setSorting] = useState([]);
- const [{ page, page_size }, setPagination] = useQueryStates(
- { page: parseAsInteger.withDefault(1), page_size: parseAsInteger.withDefault(DEFAULT_PAGE_SIZE) },
- { history: "push" },
- );
- const pageSize = PAGE_SIZE_OPTIONS.includes(page_size) ? page_size : DEFAULT_PAGE_SIZE;
+ const { pagination, onPaginationChange } = useProjectsTableState();
+ const pageSize = PAGE_SIZE_OPTIONS.includes(pagination.pageSize) ? pagination.pageSize : PROJECTS_DEFAULT_PAGE_SIZE;
const columns = useMemo(() => {
const deps = { onProjectClick, teamAliasMap, isTeamsLoading };
@@ -59,7 +55,7 @@ export function ProjectsTable({
}, [onProjectClick, teamAliasMap, isTeamsLoading]);
const pageCount = Math.max(Math.ceil(projects.length / pageSize), 1);
- const pageIndex = page >= 1 && page <= pageCount ? page - 1 : 0;
+ const pageIndex = pagination.pageIndex < pageCount ? pagination.pageIndex : 0;
return (
void setPagination({ page: nextPageIndex + 1 })}
- onPageSizeChange={(nextPageSize) => void setPagination({ page_size: nextPageSize, page: null })}
+ onPageChange={(nextPageIndex) => onPaginationChange({ pageIndex: nextPageIndex, pageSize })}
+ onPageSizeChange={(nextPageSize) => onPaginationChange({ pageIndex: 0, pageSize: nextPageSize })}
pageSizeOptions={PAGE_SIZE_OPTIONS}
isLoading={isLoading}
/>
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/useProjectsUrlState.ts b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/useProjectsUrlState.ts
new file mode 100644
index 00000000000..57c1a8fd167
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/useProjectsUrlState.ts
@@ -0,0 +1,79 @@
+import { functionalUpdate, type OnChangeFn, type PaginationState } from "@tanstack/react-table";
+import { useUrlTableState, type UrlTableState, type UrlTableStateOptions } from "@/components/shared/DataTable";
+import { parseAsInteger, useQueryStates } from "nuqs";
+import { useCallback, useMemo } from "react";
+
+export const PROJECTS_DEFAULT_PAGE_SIZE = 10;
+export const PROJECT_KEYS_DEFAULT_PAGE_SIZE = 5;
+export const PROJECT_KEYS_PAGE_SIZE_OPTIONS = [PROJECT_KEYS_DEFAULT_PAGE_SIZE, 10, 25];
+
+const PROJECTS_TABLE_STATE_OPTIONS: UrlTableStateOptions = {
+ sortFields: [],
+ defaultSort: { id: "created_at", desc: true },
+ defaultPageSize: PROJECTS_DEFAULT_PAGE_SIZE,
+ filterColumns: [],
+ urlKeys: { search: "project_search" },
+};
+
+const PROJECTS_PAGE_PARAMS = {
+ page: parseAsInteger.withDefault(1),
+ page_size: parseAsInteger.withDefault(PROJECTS_DEFAULT_PAGE_SIZE),
+};
+
+const PROJECT_KEYS_TABLE_STATE_OPTIONS: UrlTableStateOptions = {
+ sortFields: [],
+ defaultSort: { id: "created_at", desc: true },
+ defaultPageSize: PROJECT_KEYS_DEFAULT_PAGE_SIZE,
+ maxPageSize: Math.max(...PROJECT_KEYS_PAGE_SIZE_OPTIONS),
+ filterColumns: [],
+ keyPrefix: "keys_",
+};
+
+export function useProjectsTableState(): UrlTableState {
+ const tableState = useUrlTableState(PROJECTS_TABLE_STATE_OPTIONS);
+ const [, setPageParams] = useQueryStates(PROJECTS_PAGE_PARAMS, { history: "push" });
+ const { pagination } = tableState;
+
+ const onPaginationChange = useCallback>(
+ (updaterOrValue) => {
+ const next = functionalUpdate(updaterOrValue, pagination);
+ void setPageParams({ page: next.pageIndex + 1, page_size: next.pageSize });
+ },
+ [pagination, setPageParams],
+ );
+
+ return useMemo(() => ({ ...tableState, onPaginationChange }), [tableState, onPaginationChange]);
+}
+
+export function useProjectKeysTableState(): UrlTableState {
+ const tableState = useUrlTableState(PROJECT_KEYS_TABLE_STATE_OPTIONS);
+ const { pagination: urlPagination, onPaginationChange: writePagination } = tableState;
+ const pageSize = PROJECT_KEYS_PAGE_SIZE_OPTIONS.includes(urlPagination.pageSize)
+ ? urlPagination.pageSize
+ : PROJECT_KEYS_DEFAULT_PAGE_SIZE;
+
+ const pagination = useMemo(
+ () => ({ pageIndex: urlPagination.pageIndex, pageSize }),
+ [urlPagination.pageIndex, pageSize],
+ );
+
+ const onPaginationChange = useCallback>(
+ (updaterOrValue) => writePagination(functionalUpdate(updaterOrValue, pagination)),
+ [pagination, writePagination],
+ );
+
+ return useMemo(
+ () => ({ ...tableState, pagination, onPaginationChange }),
+ [tableState, pagination, onPaginationChange],
+ );
+}
+
+export function useClearProjectKeysTableState(): () => void {
+ const { setSearch, onSortingChange, onColumnFiltersChange, onPaginationChange } = useProjectKeysTableState();
+ return useCallback(() => {
+ setSearch("");
+ onSortingChange([]);
+ onColumnFiltersChange([]);
+ onPaginationChange({ pageIndex: 0, pageSize: PROJECT_KEYS_DEFAULT_PAGE_SIZE });
+ }, [setSearch, onSortingChange, onColumnFiltersChange, onPaginationChange]);
+}
diff --git a/ui/litellm-dashboard/src/components/organization/organizationTabs.ts b/ui/litellm-dashboard/src/components/organization/organizationTabs.ts
new file mode 100644
index 00000000000..db0a2692356
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/organization/organizationTabs.ts
@@ -0,0 +1,3 @@
+export const ORGANIZATION_TABS = ["overview", "members", "settings"] as const;
+export type OrganizationTab = (typeof ORGANIZATION_TABS)[number];
+export const ORGANIZATION_TAB_URL_KEY = "org_tab";
diff --git a/ui/litellm-dashboard/src/components/organization/organization_view.test.tsx b/ui/litellm-dashboard/src/components/organization/organization_view.test.tsx
index 799cd1adffe..d609b657717 100644
--- a/ui/litellm-dashboard/src/components/organization/organization_view.test.tsx
+++ b/ui/litellm-dashboard/src/components/organization/organization_view.test.tsx
@@ -1,8 +1,10 @@
import React from "react";
-import { fireEvent, screen, waitFor, within } from "@testing-library/react";
+import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import { vi, test, expect, beforeEach } from "vitest";
-import { renderWithProviders } from "../../../tests/test-utils";
+import { QueryClientProvider } from "@tanstack/react-query";
+import { NuqsTestingAdapter, type OnUrlUpdateFunction } from "nuqs/adapters/testing";
+import { vi, test, expect, beforeEach, describe, type Mock } from "vitest";
+import { renderWithProviders, testQueryClient } from "../../../tests/test-utils";
import OrganizationInfoView from "./organization_view";
import { useOrganization } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
@@ -115,7 +117,6 @@ test("renders organization view after loading data", async () => {
is_org_admin={false}
is_proxy_admin={false}
userModels={[]}
- editOrg={false}
/>,
);
@@ -135,7 +136,6 @@ test("should display empty state when organization has no members", async () =>
is_org_admin={false}
is_proxy_admin={false}
userModels={[]}
- editOrg={false}
/>,
);
@@ -165,7 +165,6 @@ test("should display team aliases when teams are available", async () => {
is_org_admin={false}
is_proxy_admin={false}
userModels={[]}
- editOrg={false}
/>,
);
@@ -199,7 +198,6 @@ test("should display team ID as fallback when alias is not found", async () => {
is_org_admin={false}
is_proxy_admin={false}
userModels={[]}
- editOrg={false}
/>,
);
@@ -223,7 +221,6 @@ test("links each team badge to that team's detail page", async () => {
is_org_admin={false}
is_proxy_admin={false}
userModels={[]}
- editOrg={false}
/>,
);
@@ -250,7 +247,6 @@ test("model badges stay non-clickable", async () => {
is_org_admin={false}
is_proxy_admin={false}
userModels={[]}
- editOrg={false}
/>,
);
@@ -272,7 +268,6 @@ test("should keep unsaved settings edits when switching tabs and back", async ()
is_org_admin={false}
is_proxy_admin={true}
userModels={[]}
- editOrg={false}
/>,
);
@@ -308,7 +303,6 @@ test("renders a tpm/rpm limit of 0 as 0 in the overview and settings tabs, never
is_org_admin={false}
is_proxy_admin={true}
userModels={[]}
- editOrg={false}
/>,
);
@@ -323,3 +317,99 @@ test("renders a tpm/rpm limit of 0 as 0 in the overview and settings tabs, never
expect(screen.queryByText("TPM: Unlimited")).not.toBeInTheDocument();
expect(screen.queryByText("RPM: Unlimited")).not.toBeInTheDocument();
});
+
+const renderOrgView = (props: { is_proxy_admin?: boolean } = {}) => (
+ {}}
+ accessToken="test-token"
+ is_org_admin={false}
+ is_proxy_admin={props.is_proxy_admin ?? false}
+ userModels={[]}
+ />
+);
+
+const lastSearchParams = (onUrlUpdate: Mock) => onUrlUpdate.mock.calls.at(-1)?.[0].searchParams;
+
+describe("organization detail tab in the URL (?org_tab=)", () => {
+ beforeEach(() => {
+ mockUseOrganization.mockReturnValue({ data: mockOrg, isLoading: false } as unknown as ReturnType<
+ typeof useOrganization
+ >);
+ });
+
+ test("opens on the tab named by ?org_tab=", () => {
+ renderWithProviders(renderOrgView(), { searchParams: "?org=org_123&org_tab=members" });
+
+ expect(screen.getByRole("tab", { name: "Members" })).toHaveAttribute("aria-selected", "true");
+ expect(screen.getByText("No members found")).toBeInTheDocument();
+ });
+
+ test("the settings deep link used by the list's Edit action opens the Settings tab", () => {
+ renderWithProviders(renderOrgView({ is_proxy_admin: true }), { searchParams: "?org=org_123&org_tab=settings" });
+
+ expect(screen.getByRole("tab", { name: "Settings" })).toHaveAttribute("aria-selected", "true");
+ expect(screen.getByRole("button", { name: /Edit Settings/i })).toBeInTheDocument();
+ });
+
+ test("opens on Overview when the URL names no tab", () => {
+ renderWithProviders(renderOrgView(), { searchParams: "?org=org_123" });
+
+ expect(screen.getByRole("tab", { name: "Overview" })).toHaveAttribute("aria-selected", "true");
+ });
+
+ test("writes the selected tab to ?org_tab= and drops it again for Overview", async () => {
+ const user = userEvent.setup();
+ const onUrlUpdate = vi.fn();
+ renderWithProviders(renderOrgView(), { searchParams: "?org=org_123", onUrlUpdate });
+
+ await user.click(screen.getByRole("tab", { name: "Settings" }));
+
+ await waitFor(() => expect(lastSearchParams(onUrlUpdate)?.get("org_tab")).toBe("settings"));
+ expect(lastSearchParams(onUrlUpdate)?.get("org")).toBe("org_123");
+ expect(screen.getByRole("tab", { name: "Settings" })).toHaveAttribute("aria-selected", "true");
+
+ await user.click(screen.getByRole("tab", { name: "Overview" }));
+
+ await waitFor(() => expect(lastSearchParams(onUrlUpdate)?.has("org_tab")).toBe(false));
+ expect(lastSearchParams(onUrlUpdate)?.get("org")).toBe("org_123");
+ });
+
+ test("falls back to Overview for an unknown ?org_tab= and removes it from the URL", async () => {
+ const onUrlUpdate = vi.fn();
+ render(renderOrgView(), {
+ wrapper: ({ children }) => (
+
+ {children}
+
+ ),
+ });
+
+ expect(screen.getByRole("tab", { name: "Overview" })).toHaveAttribute("aria-selected", "true");
+ await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled());
+ expect(lastSearchParams(onUrlUpdate)?.has("org_tab")).toBe(false);
+ expect(lastSearchParams(onUrlUpdate)?.get("org")).toBe("org_123");
+ });
+
+ test("follows back and forward navigation between tabs while the detail view stays open", () => {
+ const atUrl = (searchParams: string) => (
+
+ {renderOrgView()}
+
+ );
+ const { rerender } = render(atUrl("?org=org_123&org_tab=members"));
+ expect(screen.getByRole("tab", { name: "Members" })).toHaveAttribute("aria-selected", "true");
+
+ rerender(atUrl("?org=org_123"));
+ expect(screen.getByRole("tab", { name: "Overview" })).toHaveAttribute("aria-selected", "true");
+
+ rerender(atUrl("?org=org_123&org_tab=members"));
+ expect(screen.getByRole("tab", { name: "Members" })).toHaveAttribute("aria-selected", "true");
+ expect(screen.getByText("No members found")).toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/organization/organization_view.tsx b/ui/litellm-dashboard/src/components/organization/organization_view.tsx
index 096e736b493..c800d12ad62 100644
--- a/ui/litellm-dashboard/src/components/organization/organization_view.tsx
+++ b/ui/litellm-dashboard/src/components/organization/organization_view.tsx
@@ -1,6 +1,7 @@
import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
import { organizationKeys, useOrganization } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
import { useQueryClient } from "@tanstack/react-query";
+import { useUrlTab } from "@/hooks/useUrlTab";
import { useVisitedTabs } from "@/hooks/useVisitedTabs";
import { MoneyCell } from "@/components/shared/table_cells";
import CopyButton from "@/components/shared/CopyButton";
@@ -25,6 +26,7 @@ import {
import ObjectPermissionsView from "../object_permissions_view";
import MemberModal from "../team/EditMembership";
import { OrgSettingsForm } from "./org-settings/OrgSettingsForm";
+import { ORGANIZATION_TAB_URL_KEY, ORGANIZATION_TABS, type OrganizationTab } from "./organizationTabs";
interface OrganizationInfoProps {
organizationId: string;
@@ -33,7 +35,6 @@ interface OrganizationInfoProps {
is_org_admin: boolean;
is_proxy_admin: boolean;
userModels: string[];
- editOrg: boolean;
}
const OrganizationInfoView: React.FC = ({
@@ -43,7 +44,6 @@ const OrganizationInfoView: React.FC = ({
is_org_admin,
is_proxy_admin,
userModels,
- editOrg,
}) => {
const queryClient = useQueryClient();
const { data: orgData, isLoading: loading } = useOrganization(organizationId);
@@ -53,10 +53,16 @@ const OrganizationInfoView: React.FC = ({
const [selectedEditMember, setSelectedEditMember] = useState(null);
const canEditOrg = is_org_admin || is_proxy_admin;
const { data: teams } = useTeams();
- const { onTabChange, hasVisited } = useVisitedTabs(editOrg ? "settings" : "overview");
+ const [tab, setTab] = useUrlTab(ORGANIZATION_TABS, "overview", ORGANIZATION_TAB_URL_KEY);
+ const { onTabChange, hasVisited } = useVisitedTabs(tab);
const teamAliasMap = useMemo(() => createTeamAliasMap(teams), [teams]);
+ const handleTabChange = (value: OrganizationTab) => {
+ setTab(value);
+ onTabChange(value);
+ };
+
const handleMemberAdd = async (values: any) => {
try {
if (accessToken == null) {
@@ -158,7 +164,7 @@ const OrganizationInfoView: React.FC = ({
-