feat(ui): add reusable BetaBadge and use it for Projects sidebar item (#33449)

* fix(ui): remove New badge from Projects sidebar item

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* feat(ui): add reusable BetaBadge and use it for Projects sidebar item

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* feat(ui): rename disableShowNewBadge flag to disableShowBadges and make BetaBadge respect it

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* style(ui): use blue for BetaBadge to match existing New badge

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* revert(ui): keep disableShowNewBadge localStorage key to preserve existing user opt-outs

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore: remove non-functional md artifacts from PR

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: ryan <ryan@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-07-15 16:04:29 -07:00 • committed by GitHub
parent c6d49a85b2
commit ff4a40f017
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 91 additions and 2 deletions

View file

@ -0,0 +1,70 @@
import { render, screen } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import BetaBadge from "./BetaBadge";
// Mock the hook directly
vi.mock("@/app/(dashboard)/hooks/useDisableShowNewBadge", () => ({
useDisableShowNewBadge: vi.fn(),
}));
import { useDisableShowNewBadge } from "@/app/(dashboard)/hooks/useDisableShowNewBadge";
const mockUseDisableShowNewBadge = vi.mocked(useDisableShowNewBadge);
describe("BetaBadge", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("should render the badge when disableShowNewBadge is false", () => {
mockUseDisableShowNewBadge.mockReturnValue(false);
render(<BetaBadge>Test Content</BetaBadge>);
expect(screen.getByText("Beta")).toBeInTheDocument();
expect(screen.getByText("Test Content")).toBeInTheDocument();
});
it("should render the badge when disableShowNewBadge is not set", () => {
mockUseDisableShowNewBadge.mockReturnValue(false);
render(<BetaBadge />);
expect(screen.getByText("Beta")).toBeInTheDocument();
});
it("should render only children when disableShowNewBadge is true", () => {
mockUseDisableShowNewBadge.mockReturnValue(true);
render(<BetaBadge>Test Content</BetaBadge>);
expect(screen.queryByText("Beta")).not.toBeInTheDocument();
expect(screen.getByText("Test Content")).toBeInTheDocument();
});
it("should render nothing when disableShowNewBadge is true and no children", () => {
mockUseDisableShowNewBadge.mockReturnValue(true);
const { container } = render(<BetaBadge />);
expect(container.firstChild).toBeNull();
});
it("should render badge with dot instead of text when dot prop is true", () => {
mockUseDisableShowNewBadge.mockReturnValue(false);
render(<BetaBadge dot={true}>Test Content</BetaBadge>);
expect(screen.queryByText("Beta")).not.toBeInTheDocument();
expect(screen.getByText("Test Content")).toBeInTheDocument();
});
it("should render badge with 'Beta' text when dot prop is not provided (defaults to false)", () => {
mockUseDisableShowNewBadge.mockReturnValue(false);
render(<BetaBadge>Test Content</BetaBadge>);
expect(screen.getByText("Beta")).toBeInTheDocument();
expect(screen.getByText("Test Content")).toBeInTheDocument();
});
});

View file

@ -0,0 +1,18 @@
import { Badge } from "antd";
import { useDisableShowNewBadge } from "@/app/(dashboard)/hooks/useDisableShowNewBadge";
export default function BetaBadge({ children, dot = false }: { children?: React.ReactNode; dot?: boolean }) {
const disableShowNewBadge = useDisableShowNewBadge();
if (disableShowNewBadge) {
return children ? <>{children}</> : null;
}
return children ? (
<Badge color="blue" count={dot ? undefined : "Beta"} dot={dot}>
{children}
</Badge>
) : (
<Badge color="blue" count={dot ? undefined : "Beta"} dot={dot} />
);
}

View file

@ -72,6 +72,7 @@ import {
rolesAllowedToViewWriteScopedPages,
rolesWithWriteAccess,
} from "../utils/roles";
import BetaBadge from "./BetaBadge";
import NewBadge from "./common_components/NewBadge";
import type { Organization } from "./networking";
import SidebarAccountMenu from "./SidebarAccountMenu/SidebarAccountMenu";
@ -211,7 +212,7 @@ const menuGroups: MenuGroup[] = [
page: "projects",
label: (
<span className="flex items-center gap-2">
Projects <NewBadge />
Projects <BetaBadge />
</span>
),
icon: <Folder {...ICON} />,

View file

@ -252,7 +252,7 @@ describe("Navbar", () => {
expect(screen.queryByRole("button", { name: /^notifications$/i })).not.toBeInTheDocument();
});
it("should handle hide new features toggle", async () => {
it("should handle hide new feature indicators toggle", async () => {
const user = userEvent.setup();
// Initially disabled