feat(ui): add Mutelist menu item under Configuration (#8444)

Co-authored-by: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com>
Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com>
This commit is contained in:
authored and GitHub committed 2025-08-08 09:09:37 +02:00
1 parent 94e60f7329
commit df4bf18b97
15 files changed
+302 -131

No files matched your search

+172 -79
View File
@@ -1,5 +1,6 @@
"use client";
import { Tooltip } from "@nextui-org/react";
import { DropdownMenuArrow } from "@radix-ui/react-dropdown-menu";
import { ChevronDown } from "lucide-react";
import Link from "next/link";
@@ -19,12 +20,6 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu/dropdown-menu";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip/tooltip";
import { cn } from "@/lib/utils";
import { CollapseMenuButtonProps } from "@/types";
@@ -94,90 +89,188 @@ export const CollapseMenuButton = ({
</CollapsibleTrigger>
<CollapsibleContent className="overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
{submenus.map(
({ href, label, active, icon: SubIcon, target }, index) => (
<Button
key={index}
variant={
(active === undefined && pathname === href) || active
? "secondary"
: "ghost"
}
className="ml-4 h-8 w-full justify-start"
asChild
>
<Link href={href} target={target} className="flex items-center">
<div className="mr-4 h-full border-l border-default-200"></div>
<span className="mr-2">
<SubIcon size={16} />
</span>
<p
className={cn(
"max-w-[170px] truncate",
isOpen
? "translate-x-0 opacity-100"
: "-translate-x-96 opacity-0",
)}
(
{ href, label, active, icon: SubIcon, target, disabled, onClick },
index,
) => {
const isActive =
(active === undefined && pathname === href) || active;
if (disabled && label === "Mutelist") {
return (
<Tooltip
key={index}
content="The mutelist will be enabled after adding a provider"
className="text-xs"
placement="right"
>
{label}
</p>
</Link>
</Button>
),
<div className="w-full">
<Button
variant={isActive ? "secondary" : "ghost"}
className={cn(
"ml-4 h-8 w-full justify-start",
"cursor-not-allowed opacity-50",
)}
disabled={true}
>
<div className="flex items-center">
<div className="mr-4 h-full border-l border-default-200"></div>
<span className="mr-2">
<SubIcon size={16} />
</span>
<p
className={cn(
"max-w-[170px] truncate",
isOpen
? "translate-x-0 opacity-100"
: "-translate-x-96 opacity-0",
)}
>
{label}
</p>
</div>
</Button>
</div>
</Tooltip>
);
}
return (
<Button
key={index}
variant={isActive ? "secondary" : "ghost"}
className={cn(
"ml-4 h-8 w-full justify-start",
disabled && "cursor-not-allowed opacity-50",
)}
asChild={!disabled}
disabled={disabled}
>
<Link
href={href}
target={target}
className="flex items-center"
onClick={onClick}
>
<div className="mr-4 h-full border-l border-default-200"></div>
<span className="mr-2">
<SubIcon size={16} />
</span>
<p
className={cn(
"max-w-[170px] truncate",
isOpen
? "translate-x-0 opacity-100"
: "-translate-x-96 opacity-0",
)}
>
{label}
</p>
</Link>
</Button>
);
},
)}
</CollapsibleContent>
</Collapsible>
) : (
<DropdownMenu>
<TooltipProvider disableHoverableContent>
<Tooltip delayDuration={100}>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<Button
variant={isSubmenuActive ? "secondary" : "ghost"}
className="mb-1 h-10 w-full justify-start"
>
<div className="flex w-full items-center justify-between">
<div className="flex items-center">
<span className={cn(isOpen === false ? "" : "mr-4")}>
<Icon size={18} />
</span>
<p
className={cn(
"max-w-[200px] truncate",
isOpen === false ? "opacity-0" : "opacity-100",
)}
>
{label}
</p>
</div>
</div>
</Button>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent side="right" align="start" alignOffset={2}>
{label}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<Tooltip
content={label}
placement="right"
delay={100}
className="text-xs"
>
<DropdownMenuTrigger asChild>
<Button
variant={isSubmenuActive ? "secondary" : "ghost"}
className="mb-1 h-10 w-full justify-start"
>
<div className="flex w-full items-center justify-between">
<div className="flex items-center">
<span className={cn(isOpen === false ? "" : "mr-4")}>
<Icon size={18} />
</span>
<p
className={cn(
"max-w-[200px] truncate",
isOpen === false ? "opacity-0" : "opacity-100",
)}
>
{label}
</p>
</div>
</div>
</Button>
</DropdownMenuTrigger>
</Tooltip>
<DropdownMenuContent side="right" sideOffset={25} align="start">
<DropdownMenuLabel className="max-w-[190px] truncate">
{label}
</DropdownMenuLabel>
<DropdownMenuSeparator />
{submenus.map(({ href, label, active, icon: SubIcon }, index) => (
<DropdownMenuItem key={index} asChild>
<Link
className={`flex cursor-pointer items-center gap-2 ${
((active === undefined && pathname === href) || active) &&
"bg-secondary"
}`}
href={href}
>
<SubIcon size={16} />
<p className="max-w-[180px] truncate">{label}</p>
</Link>
</DropdownMenuItem>
))}
{submenus.map(
(
{ href, label, active, icon: SubIcon, disabled, onClick },
index,
) => {
const isActive =
(active === undefined && pathname === href) || active;
if (disabled && label === "Mutelist") {
return (
<Tooltip
key={index}
content="The mutelist will be enabled after adding a provider"
className="text-xs"
>
<div className="w-full">
<DropdownMenuItem
disabled={true}
className={cn(
"cursor-not-allowed opacity-50",
isActive && "bg-default-100 dark:bg-prowler-blue-400",
)}
>
<div className="flex items-center gap-2">
<SubIcon size={16} />
<p className="max-w-[180px] truncate">{label}</p>
</div>
</DropdownMenuItem>
</div>
</Tooltip>
);
}
return (
<DropdownMenuItem
key={index}
asChild={!disabled}
disabled={disabled}
className={cn(
disabled && "cursor-not-allowed opacity-50",
isActive && "bg-default-100 dark:bg-prowler-blue-400",
)}
>
{disabled ? (
<div className="flex items-center gap-2">
<SubIcon size={16} />
<p className="max-w-[180px] truncate">{label}</p>
</div>
) : (
<Link
className="flex cursor-pointer items-center gap-2"
href={href}
onClick={onClick}
>
<SubIcon size={16} />
<p className="max-w-[180px] truncate">{label}</p>
</Link>
)}
</DropdownMenuItem>
);
},
)}
<DropdownMenuArrow className="fill-border" />
</DropdownMenuContent>
</DropdownMenu>
+15 -7
View File
@@ -17,6 +17,9 @@ import {
import { useAuth } from "@/hooks";
import { getMenuList } from "@/lib/menu-list";
import { cn } from "@/lib/utils";
import { useUIStore } from "@/store/ui/store";
import { GroupProps } from "@/types";
import { RolePermissionAttributes } from "@/types/users";
import { Button } from "../button/button";
import { CustomButton } from "../custom/custom-button";
@@ -24,7 +27,7 @@ import { ScrollArea } from "../scroll-area/scroll-area";
interface MenuHideRule {
label: string;
condition: (permissions: any) => boolean;
condition: (permissions: RolePermissionAttributes) => boolean;
}
// Configuration for hiding menu items based on permissions
@@ -48,16 +51,16 @@ const MENU_HIDE_RULES: MenuHideRule[] = [
// },
];
const hideMenuItems = (menuGroups: any[], labelsToHide: string[]) => {
const hideMenuItems = (menuGroups: GroupProps[], labelsToHide: string[]) => {
return menuGroups.map((group) => ({
...group,
menus: group.menus
.filter((menu: any) => !labelsToHide.includes(menu.label))
.map((menu: any) => ({
.filter((menu) => !labelsToHide.includes(menu.label))
.map((menu) => ({
...menu,
submenus:
menu.submenus?.filter(
(submenu: any) => !labelsToHide.includes(submenu.label),
(submenu) => !labelsToHide.includes(submenu.label),
) || [],
})),
}));
@@ -66,7 +69,12 @@ const hideMenuItems = (menuGroups: any[], labelsToHide: string[]) => {
export const Menu = ({ isOpen }: { isOpen: boolean }) => {
const pathname = usePathname();
const { permissions } = useAuth();
const menuList = getMenuList(pathname);
const { hasProviders, openMutelistModal } = useUIStore();
const menuList = getMenuList({
pathname,
hasProviders,
openMutelistModal,
});
const labelsToHide = MENU_HIDE_RULES.filter((rule) =>
rule.condition(permissions),
@@ -122,7 +130,7 @@ export const Menu = ({ isOpen }: { isOpen: boolean }) => {
) : (
<p className="pb-2"></p>
)}
{menus.map((menu: any, index: number) => {
{menus.map((menu, index) => {
const {
href,
label,