mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-06 08:16:43 +00:00
Merge pull request #20095 from BerriAI/litellm_user_dropdown_v2
[Refactor] UI - Navbar: User Dropdown
This commit is contained in:
commit
267993b388
4 changed files with 487 additions and 147 deletions
|
|
@ -0,0 +1,289 @@
|
|||
import userEvent from "@testing-library/user-event";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { renderWithProviders, screen, waitFor } from "../../../../tests/test-utils";
|
||||
import UserDropdown from "./UserDropdown";
|
||||
|
||||
let mockUseAuthorizedImpl = () => ({
|
||||
userId: "test-user-id",
|
||||
userEmail: "test@example.com",
|
||||
userRole: "Admin",
|
||||
premiumUser: false,
|
||||
});
|
||||
|
||||
let mockUseDisableShowPromptsImpl = () => false;
|
||||
|
||||
let mockGetLocalStorageItemImpl = (key: string): string | null => {
|
||||
if (key === "disableShowNewBadge") return null;
|
||||
if (key === "disableShowPrompts") return null;
|
||||
return null;
|
||||
};
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorizedImpl(),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/useDisableShowPrompts", () => ({
|
||||
useDisableShowPrompts: () => mockUseDisableShowPromptsImpl(),
|
||||
}));
|
||||
|
||||
vi.mock("@/utils/localStorageUtils", () => ({
|
||||
LOCAL_STORAGE_EVENT: "local-storage-change",
|
||||
getLocalStorageItem: (key: string) => mockGetLocalStorageItemImpl(key),
|
||||
setLocalStorageItem: vi.fn(),
|
||||
removeLocalStorageItem: vi.fn(),
|
||||
emitLocalStorageChange: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("UserDropdown", () => {
|
||||
const mockOnLogout = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockUseAuthorizedImpl = () => ({
|
||||
userId: "test-user-id",
|
||||
userEmail: "test@example.com",
|
||||
userRole: "Admin",
|
||||
premiumUser: false,
|
||||
});
|
||||
mockUseDisableShowPromptsImpl = () => false;
|
||||
mockGetLocalStorageItemImpl = (key: string): string | null => {
|
||||
if (key === "disableShowNewBadge") return null;
|
||||
if (key === "disableShowPrompts") return null;
|
||||
return null;
|
||||
};
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
expect(screen.getByRole("button")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display user button with User text", () => {
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
expect(screen.getByText("User")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show user email when dropdown is opened", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("test@example.com")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show user ID when dropdown is opened", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("test-user-id")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show user role when dropdown is opened", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Admin")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display Standard badge for non-premium users", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Standard")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display Premium badge for premium users", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockUseAuthorizedImpl = () => ({
|
||||
userId: "test-user-id",
|
||||
userEmail: "test@example.com",
|
||||
userRole: "Admin",
|
||||
premiumUser: true,
|
||||
});
|
||||
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Premium")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should call onLogout when logout is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("test@example.com")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await user.click(screen.getByText("Logout"));
|
||||
|
||||
expect(mockOnLogout).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should toggle hide new feature indicators switch", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("test@example.com")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const toggle = screen.getByLabelText("Toggle hide new feature indicators");
|
||||
expect(toggle).not.toBeChecked();
|
||||
|
||||
await user.click(toggle);
|
||||
|
||||
const localStorageUtils = vi.mocked(await import("@/utils/localStorageUtils"));
|
||||
expect(localStorageUtils.setLocalStorageItem).toHaveBeenCalledWith("disableShowNewBadge", "true");
|
||||
expect(localStorageUtils.emitLocalStorageChange).toHaveBeenCalledWith("disableShowNewBadge");
|
||||
});
|
||||
|
||||
it("should toggle hide new feature indicators switch off", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockGetLocalStorageItemImpl = (key: string): string | null => {
|
||||
if (key === "disableShowNewBadge") return "true";
|
||||
return null;
|
||||
};
|
||||
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("test@example.com")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const toggle = screen.getByLabelText("Toggle hide new feature indicators");
|
||||
expect(toggle).toBeChecked();
|
||||
|
||||
await user.click(toggle);
|
||||
|
||||
const localStorageUtils = vi.mocked(await import("@/utils/localStorageUtils"));
|
||||
expect(localStorageUtils.removeLocalStorageItem).toHaveBeenCalledWith("disableShowNewBadge");
|
||||
expect(localStorageUtils.emitLocalStorageChange).toHaveBeenCalledWith("disableShowNewBadge");
|
||||
});
|
||||
|
||||
it("should toggle hide all prompts switch", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("test@example.com")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const toggle = screen.getByLabelText("Toggle hide all prompts");
|
||||
expect(toggle).not.toBeChecked();
|
||||
|
||||
await user.click(toggle);
|
||||
|
||||
const localStorageUtils = vi.mocked(await import("@/utils/localStorageUtils"));
|
||||
expect(localStorageUtils.setLocalStorageItem).toHaveBeenCalledWith("disableShowPrompts", "true");
|
||||
expect(localStorageUtils.emitLocalStorageChange).toHaveBeenCalledWith("disableShowPrompts");
|
||||
});
|
||||
|
||||
it("should toggle hide all prompts switch off", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockUseDisableShowPromptsImpl = () => true;
|
||||
mockGetLocalStorageItemImpl = (key: string): string | null => {
|
||||
if (key === "disableShowPrompts") return "true";
|
||||
return null;
|
||||
};
|
||||
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("test@example.com")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const toggle = screen.getByLabelText("Toggle hide all prompts");
|
||||
expect(toggle).toBeChecked();
|
||||
|
||||
await user.click(toggle);
|
||||
|
||||
const localStorageUtils = vi.mocked(await import("@/utils/localStorageUtils"));
|
||||
expect(localStorageUtils.removeLocalStorageItem).toHaveBeenCalledWith("disableShowPrompts");
|
||||
expect(localStorageUtils.emitLocalStorageChange).toHaveBeenCalledWith("disableShowPrompts");
|
||||
});
|
||||
|
||||
it("should display dash when user email is not available", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockUseAuthorizedImpl = () => ({
|
||||
userId: "test-user-id",
|
||||
userEmail: null as any,
|
||||
userRole: "Admin",
|
||||
premiumUser: false,
|
||||
});
|
||||
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("-")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display dash when user ID is not available", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockUseAuthorizedImpl = () => ({
|
||||
userId: null as any,
|
||||
userEmail: "test@example.com",
|
||||
userRole: "Admin",
|
||||
premiumUser: false,
|
||||
});
|
||||
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
const dashElements = screen.getAllByText("-");
|
||||
expect(dashElements.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("should initialize hide new feature indicators from localStorage", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockGetLocalStorageItemImpl = (key: string): string | null => {
|
||||
if (key === "disableShowNewBadge") return "true";
|
||||
return null;
|
||||
};
|
||||
|
||||
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("test@example.com")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const toggle = screen.getByLabelText("Toggle hide new feature indicators");
|
||||
expect(toggle).toBeChecked();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import { useDisableShowPrompts } from "@/app/(dashboard)/hooks/useDisableShowPrompts";
|
||||
import {
|
||||
emitLocalStorageChange,
|
||||
getLocalStorageItem,
|
||||
removeLocalStorageItem,
|
||||
setLocalStorageItem,
|
||||
} from "@/utils/localStorageUtils";
|
||||
import {
|
||||
CrownOutlined,
|
||||
DownOutlined,
|
||||
LogoutOutlined,
|
||||
MailOutlined,
|
||||
SafetyOutlined,
|
||||
UserOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import type { MenuProps } from "antd";
|
||||
import { Button, Divider, Dropdown, Space, Switch, Tag, Tooltip, Typography } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface UserDropdownProps {
|
||||
onLogout: () => void;
|
||||
}
|
||||
|
||||
const UserDropdown: React.FC<UserDropdownProps> = ({ onLogout }) => {
|
||||
const { userId, userEmail, userRole, premiumUser } = useAuthorized();
|
||||
const disableShowPrompts = useDisableShowPrompts();
|
||||
const [disableShowNewBadge, setDisableShowNewBadge] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const storedValue = getLocalStorageItem("disableShowNewBadge");
|
||||
setDisableShowNewBadge(storedValue === "true");
|
||||
}, []);
|
||||
|
||||
const userItems: MenuProps["items"] = [
|
||||
{
|
||||
key: "logout",
|
||||
label: (
|
||||
<Space>
|
||||
<LogoutOutlined />
|
||||
Logout
|
||||
</Space>
|
||||
),
|
||||
onClick: onLogout,
|
||||
},
|
||||
];
|
||||
|
||||
const renderUserInfoSection = () => (
|
||||
<Space direction="vertical" size="small" style={{ width: "100%", padding: "12px" }}>
|
||||
<Space style={{ width: "100%", justifyContent: "space-between" }}>
|
||||
<Space>
|
||||
<MailOutlined />
|
||||
<Text type="secondary">{userEmail || "-"}</Text>
|
||||
</Space>
|
||||
{premiumUser ? (
|
||||
<Tag
|
||||
icon={<CrownOutlined />}
|
||||
color="gold"
|
||||
>
|
||||
Premium
|
||||
</Tag>
|
||||
) : (
|
||||
<Tooltip title="Upgrade to Premium for advanced features" placement="left">
|
||||
<Tag
|
||||
icon={<CrownOutlined />}
|
||||
>
|
||||
Standard
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
<Divider style={{ margin: "8px 0" }} />
|
||||
<Space style={{ width: "100%", justifyContent: "space-between" }}>
|
||||
<Space>
|
||||
<UserOutlined />
|
||||
<Text type="secondary">User ID</Text>
|
||||
</Space>
|
||||
<Text
|
||||
copyable
|
||||
ellipsis
|
||||
style={{ maxWidth: "150px" }}
|
||||
title={userId || "-"}
|
||||
>
|
||||
{userId || "-"}
|
||||
</Text>
|
||||
</Space>
|
||||
<Space style={{ width: "100%", justifyContent: "space-between" }}>
|
||||
<Space>
|
||||
<SafetyOutlined />
|
||||
<Text type="secondary">Role</Text>
|
||||
</Space>
|
||||
<Text>{userRole}</Text>
|
||||
</Space>
|
||||
<Divider style={{ margin: "8px 0" }} />
|
||||
<Space style={{ width: "100%", justifyContent: "space-between" }}>
|
||||
<Text type="secondary">Hide New Feature Indicators</Text>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={disableShowNewBadge}
|
||||
onChange={(checked) => {
|
||||
setDisableShowNewBadge(checked);
|
||||
if (checked) {
|
||||
setLocalStorageItem("disableShowNewBadge", "true");
|
||||
emitLocalStorageChange("disableShowNewBadge");
|
||||
} else {
|
||||
removeLocalStorageItem("disableShowNewBadge");
|
||||
emitLocalStorageChange("disableShowNewBadge");
|
||||
}
|
||||
}}
|
||||
aria-label="Toggle hide new feature indicators"
|
||||
/>
|
||||
</Space>
|
||||
<Space style={{ width: "100%", justifyContent: "space-between" }}>
|
||||
<Text type="secondary">Hide All Prompts</Text>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={disableShowPrompts}
|
||||
onChange={(checked) => {
|
||||
if (checked) {
|
||||
setLocalStorageItem("disableShowPrompts", "true");
|
||||
emitLocalStorageChange("disableShowPrompts");
|
||||
} else {
|
||||
removeLocalStorageItem("disableShowPrompts");
|
||||
emitLocalStorageChange("disableShowPrompts");
|
||||
}
|
||||
}}
|
||||
aria-label="Toggle hide all prompts"
|
||||
/>
|
||||
</Space>
|
||||
</Space>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
menu={{ items: userItems }}
|
||||
popupRender={(menu) => (
|
||||
<div
|
||||
className="bg-white rounded-lg shadow-lg"
|
||||
>
|
||||
{renderUserInfoSection()}
|
||||
<Divider style={{ margin: 0 }} />
|
||||
{React.cloneElement(menu as React.ReactElement, {
|
||||
style: { boxShadow: "none" },
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<Button type="text" >
|
||||
<Space>
|
||||
<UserOutlined />
|
||||
<Text>User</Text>
|
||||
<DownOutlined />
|
||||
</Space>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserDropdown;
|
||||
|
|
@ -15,8 +15,14 @@ vi.mock("@/utils/proxyUtils", () => ({
|
|||
// Create mock functions that can be controlled in tests
|
||||
let mockUseThemeImpl = () => ({ logoUrl: null as string | null });
|
||||
let mockUseHealthReadinessImpl = () => ({ data: null as any });
|
||||
let mockGetLocalStorageItemImpl = () => null as string | null;
|
||||
let mockGetLocalStorageItemImpl = (key: string) => null as string | null;
|
||||
let mockUseDisableShowPromptsImpl = () => false;
|
||||
let mockUseAuthorizedImpl = () => ({
|
||||
userId: "test-user",
|
||||
userEmail: "test@example.com",
|
||||
userRole: "Admin",
|
||||
premiumUser: false,
|
||||
});
|
||||
|
||||
vi.mock("@/contexts/ThemeContext", () => ({
|
||||
useTheme: () => mockUseThemeImpl(),
|
||||
|
|
@ -30,9 +36,13 @@ vi.mock("@/app/(dashboard)/hooks/useDisableShowPrompts", () => ({
|
|||
useDisableShowPrompts: () => mockUseDisableShowPromptsImpl(),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorizedImpl(),
|
||||
}));
|
||||
|
||||
vi.mock("@/utils/localStorageUtils", () => ({
|
||||
LOCAL_STORAGE_EVENT: "local-storage-change",
|
||||
getLocalStorageItem: () => mockGetLocalStorageItemImpl(),
|
||||
getLocalStorageItem: (key: string) => mockGetLocalStorageItemImpl(key),
|
||||
setLocalStorageItem: vi.fn(),
|
||||
removeLocalStorageItem: vi.fn(),
|
||||
emitLocalStorageChange: vi.fn(),
|
||||
|
|
@ -123,14 +133,27 @@ describe("Navbar", () => {
|
|||
|
||||
it("should show premium user badge when premiumUser is true", async () => {
|
||||
const user = userEvent.setup();
|
||||
const premiumProps = { ...defaultProps, premiumUser: true };
|
||||
renderWithProviders(<Navbar {...premiumProps} />);
|
||||
mockUseAuthorizedImpl = () => ({
|
||||
userId: "test-user",
|
||||
userEmail: "test@example.com",
|
||||
userRole: "Admin",
|
||||
premiumUser: true,
|
||||
});
|
||||
renderWithProviders(<Navbar {...defaultProps} />);
|
||||
|
||||
await user.click(screen.getByText("User"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Premium")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Reset mock
|
||||
mockUseAuthorizedImpl = () => ({
|
||||
userId: "test-user",
|
||||
userEmail: "test@example.com",
|
||||
userRole: "Admin",
|
||||
premiumUser: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("should show version badge when health data contains version", () => {
|
||||
|
|
@ -167,7 +190,10 @@ describe("Navbar", () => {
|
|||
const user = userEvent.setup();
|
||||
|
||||
// Initially disabled
|
||||
mockGetLocalStorageItemImpl = () => "false";
|
||||
mockGetLocalStorageItemImpl = (key: string) => {
|
||||
if (key === "disableShowNewBadge") return "false";
|
||||
return null;
|
||||
};
|
||||
|
||||
renderWithProviders(<Navbar {...defaultProps} />);
|
||||
|
||||
|
|
@ -186,6 +212,9 @@ describe("Navbar", () => {
|
|||
const localStorageUtils = vi.mocked(await import("@/utils/localStorageUtils"));
|
||||
expect(localStorageUtils.setLocalStorageItem).toHaveBeenCalledWith("disableShowNewBadge", "true");
|
||||
expect(localStorageUtils.emitLocalStorageChange).toHaveBeenCalledWith("disableShowNewBadge");
|
||||
|
||||
// Reset mock
|
||||
mockGetLocalStorageItemImpl = (key: string) => null;
|
||||
});
|
||||
|
||||
it("should handle logout functionality", async () => {
|
||||
|
|
|
|||
|
|
@ -1,32 +1,20 @@
|
|||
import { useHealthReadiness } from "@/app/(dashboard)/hooks/healthReadiness/useHealthReadiness";
|
||||
import { useDisableShowPrompts } from "@/app/(dashboard)/hooks/useDisableShowPrompts";
|
||||
import { getProxyBaseUrl } from "@/components/networking";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { clearTokenCookies } from "@/utils/cookieUtils";
|
||||
import {
|
||||
emitLocalStorageChange,
|
||||
getLocalStorageItem,
|
||||
removeLocalStorageItem,
|
||||
setLocalStorageItem,
|
||||
} from "@/utils/localStorageUtils";
|
||||
import { fetchProxySettings } from "@/utils/proxyUtils";
|
||||
import {
|
||||
CrownOutlined,
|
||||
GithubOutlined,
|
||||
LogoutOutlined,
|
||||
MailOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
MoonOutlined,
|
||||
SafetyOutlined,
|
||||
SlackOutlined,
|
||||
SunOutlined,
|
||||
UserOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import type { MenuProps } from "antd";
|
||||
import { Button, Dropdown, Switch, Tag, Tooltip } from "antd";
|
||||
import { Button, Switch, Tag } from "antd";
|
||||
import Link from "next/link";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import UserDropdown from "./Navbar/UserDropdown/UserDropdown";
|
||||
|
||||
interface NavbarProps {
|
||||
userID: string | null;
|
||||
|
|
@ -59,8 +47,6 @@ const Navbar: React.FC<NavbarProps> = ({
|
|||
}) => {
|
||||
const baseUrl = getProxyBaseUrl();
|
||||
const [logoutUrl, setLogoutUrl] = useState("");
|
||||
const [disableShowNewBadge, setDisableShowNewBadge] = useState(false);
|
||||
const disableShowPrompts = useDisableShowPrompts();
|
||||
const { logoUrl } = useTheme();
|
||||
const { data: healthData } = useHealthReadiness();
|
||||
const version = healthData?.litellm_version;
|
||||
|
|
@ -82,11 +68,6 @@ const Navbar: React.FC<NavbarProps> = ({
|
|||
initializeProxySettings();
|
||||
}, [accessToken]);
|
||||
|
||||
useEffect(() => {
|
||||
const storedValue = getLocalStorageItem("disableShowNewBadge");
|
||||
setDisableShowNewBadge(storedValue === "true");
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLogoutUrl(proxySettings?.PROXY_LOGOUT_URL || "");
|
||||
}, [proxySettings]);
|
||||
|
|
@ -96,105 +77,6 @@ const Navbar: React.FC<NavbarProps> = ({
|
|||
window.location.href = logoutUrl;
|
||||
};
|
||||
|
||||
const userItems: MenuProps["items"] = [
|
||||
{
|
||||
key: "user-info",
|
||||
// Prevent dropdown from closing when interacting with the toggle
|
||||
onClick: (info) => info.domEvent?.stopPropagation(),
|
||||
label: (
|
||||
<div className="px-3 py-3 border-b border-gray-100">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center">
|
||||
<UserOutlined className="mr-2 text-gray-700" />
|
||||
<span className="text-sm font-semibold text-gray-900">{userID}</span>
|
||||
</div>
|
||||
{premiumUser ? (
|
||||
<Tooltip title="Premium User" placement="left">
|
||||
<div className="flex items-center bg-gradient-to-r from-amber-500 to-yellow-500 text-white px-2 py-0.5 rounded-full cursor-help">
|
||||
<CrownOutlined className="mr-1 text-xs" />
|
||||
<span className="text-xs font-medium">Premium</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip title="Upgrade to Premium for advanced features" placement="left">
|
||||
<div className="flex items-center bg-gray-100 text-gray-500 px-2 py-0.5 rounded-full cursor-help">
|
||||
<CrownOutlined className="mr-1 text-xs" />
|
||||
<span className="text-xs font-medium">Standard</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center text-sm">
|
||||
<SafetyOutlined className="mr-2 text-gray-400 text-xs" />
|
||||
<span className="text-gray-500 text-xs">Role</span>
|
||||
<span className="ml-auto text-gray-700 font-medium">{userRole}</span>
|
||||
</div>
|
||||
<div className="flex items-center text-sm">
|
||||
<MailOutlined className="mr-2 text-gray-400 text-xs" />
|
||||
<span className="text-gray-500 text-xs">Email</span>
|
||||
<span className="ml-auto text-gray-700 font-medium truncate max-w-[150px]" title={userEmail || "Unknown"}>
|
||||
{userEmail || "Unknown"}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center text-sm pt-2 mt-2 border-t border-gray-100"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span className="text-gray-500 text-xs">Hide New Feature Indicators</span>
|
||||
<Switch
|
||||
className="ml-auto"
|
||||
size="small"
|
||||
checked={disableShowNewBadge}
|
||||
onChange={(checked) => {
|
||||
setDisableShowNewBadge(checked);
|
||||
if (checked) {
|
||||
setLocalStorageItem("disableShowNewBadge", "true");
|
||||
emitLocalStorageChange("disableShowNewBadge");
|
||||
} else {
|
||||
removeLocalStorageItem("disableShowNewBadge");
|
||||
emitLocalStorageChange("disableShowNewBadge");
|
||||
}
|
||||
}}
|
||||
aria-label="Toggle hide new feature indicators"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center text-sm pt-2 mt-2 border-t border-gray-100"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span className="text-gray-500 text-xs">Hide All Prompts</span>
|
||||
<Switch
|
||||
className="ml-auto"
|
||||
size="small"
|
||||
checked={disableShowPrompts}
|
||||
onChange={(checked) => {
|
||||
if (checked) {
|
||||
setLocalStorageItem("disableShowPrompts", "true");
|
||||
emitLocalStorageChange("disableShowPrompts");
|
||||
} else {
|
||||
removeLocalStorageItem("disableShowPrompts");
|
||||
emitLocalStorageChange("disableShowPrompts");
|
||||
}
|
||||
}}
|
||||
aria-label="Toggle hide all prompts"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "logout",
|
||||
label: (
|
||||
<div className="flex items-center py-2 px-3 hover:bg-gray-50 rounded-md mx-1 my-1" onClick={handleLogout}>
|
||||
<LogoutOutlined className="mr-3 text-gray-600" />
|
||||
<span className="text-gray-800">Logout</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<nav className="bg-white border-b border-gray-200 sticky top-0 z-10">
|
||||
<div className="w-full">
|
||||
|
|
@ -284,28 +166,7 @@ const Navbar: React.FC<NavbarProps> = ({
|
|||
</a>
|
||||
|
||||
{!isPublicPage && (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: userItems,
|
||||
className: "min-w-[200px]",
|
||||
style: {
|
||||
padding: "8px",
|
||||
marginTop: "8px",
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0 4px 24px rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
}}
|
||||
overlayStyle={{
|
||||
minWidth: "200px",
|
||||
}}
|
||||
>
|
||||
<button className="inline-flex items-center text-sm text-gray-600 hover:text-gray-900 transition-colors">
|
||||
User
|
||||
<svg className="ml-1 w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
</Dropdown>
|
||||
<UserDropdown onLogout={handleLogout} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue