chore: language support phase 2 (#6323)
* fix: adding langauge support for sidebar items * fix: worksapce sidebar item refactor * chore: code cleanup --------- Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
parent
732963b591
commit
a6216beb7e
21 changed files with 431 additions and 356 deletions
1
packages/constants/src/event.ts
Normal file
1
packages/constants/src/event.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const SIDEBAR_CLICKED = "Sidenav clicked";
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
export * from "./ai";
|
export * from "./ai";
|
||||||
export * from "./auth";
|
export * from "./auth";
|
||||||
export * from "./endpoints";
|
export * from "./endpoints";
|
||||||
|
export * from "./event";
|
||||||
export * from "./file";
|
export * from "./file";
|
||||||
export * from "./instance";
|
export * from "./instance";
|
||||||
export * from "./issue";
|
export * from "./issue";
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,20 @@ export type TUserStatus = {
|
||||||
status: EUserStatus | undefined;
|
status: EUserStatus | undefined;
|
||||||
message?: string;
|
message?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum EUserPermissionsLevel {
|
||||||
|
WORKSPACE = "WORKSPACE",
|
||||||
|
PROJECT = "PROJECT",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EUserWorkspaceRoles {
|
||||||
|
ADMIN = 20,
|
||||||
|
MEMBER = 15,
|
||||||
|
GUEST = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EUserProjectRoles {
|
||||||
|
ADMIN = 20,
|
||||||
|
MEMBER = 15,
|
||||||
|
GUEST = 5,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
export * from "./use-local-storage";
|
export * from "./use-local-storage";
|
||||||
export * from "./use-outside-click-detector";
|
export * from "./use-outside-click-detector";
|
||||||
|
export * from "./use-platform-os";
|
||||||
|
|
|
||||||
34
packages/hooks/src/use-platform-os.tsx
Normal file
34
packages/hooks/src/use-platform-os.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
export const usePlatformOS = () => {
|
||||||
|
const [platformData, setPlatformData] = useState({
|
||||||
|
isMobile: false,
|
||||||
|
platform: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const detectPlatform = () => {
|
||||||
|
const userAgent = window.navigator.userAgent;
|
||||||
|
const isMobile = /iPhone|iPad|iPod|Android/i.test(userAgent);
|
||||||
|
let platform = "";
|
||||||
|
|
||||||
|
if (!isMobile) {
|
||||||
|
if (userAgent.indexOf("Win") !== -1) {
|
||||||
|
platform = "Windows";
|
||||||
|
} else if (userAgent.indexOf("Mac") !== -1) {
|
||||||
|
platform = "MacOS";
|
||||||
|
} else if (userAgent.indexOf("Linux") !== -1) {
|
||||||
|
platform = "Linux";
|
||||||
|
} else {
|
||||||
|
platform = "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setPlatformData({ isMobile, platform });
|
||||||
|
};
|
||||||
|
|
||||||
|
detectPlatform();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return platformData;
|
||||||
|
};
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
"activity": "Activity",
|
"activity": "Activity",
|
||||||
"appearance": "Appearance",
|
"appearance": "Appearance",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
|
"inbox": "Inbox",
|
||||||
"workspaces": "Workspaces",
|
"workspaces": "Workspaces",
|
||||||
"create_workspace": "Create workspace",
|
"create_workspace": "Create workspace",
|
||||||
"invitations": "Invitations",
|
"invitations": "Invitations",
|
||||||
|
|
|
||||||
|
|
@ -314,5 +314,6 @@
|
||||||
"change_parent_issue": "Cambiar problema padre",
|
"change_parent_issue": "Cambiar problema padre",
|
||||||
"remove_parent_issue": "Eliminar problema padre",
|
"remove_parent_issue": "Eliminar problema padre",
|
||||||
"add_parent": "Agregar padre",
|
"add_parent": "Agregar padre",
|
||||||
"loading_members": "Cargando miembros..."
|
"loading_members": "Cargando miembros...",
|
||||||
|
"inbox": "bandeja de entrada"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -314,5 +314,6 @@
|
||||||
"change_parent_issue": "Modifier le problème parent",
|
"change_parent_issue": "Modifier le problème parent",
|
||||||
"remove_parent_issue": "Supprimer le problème parent",
|
"remove_parent_issue": "Supprimer le problème parent",
|
||||||
"add_parent": "Ajouter un parent",
|
"add_parent": "Ajouter un parent",
|
||||||
"loading_members": "Chargement des membres..."
|
"loading_members": "Chargement des membres...",
|
||||||
|
"inbox": "boîte de réception"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -314,5 +314,6 @@
|
||||||
"change_parent_issue": "親問題を変更",
|
"change_parent_issue": "親問題を変更",
|
||||||
"remove_parent_issue": "親問題を削除",
|
"remove_parent_issue": "親問題を削除",
|
||||||
"add_parent": "親問題を追加",
|
"add_parent": "親問題を追加",
|
||||||
"loading_members": "メンバーを読み込んでいます..."
|
"loading_members": "メンバーを読み込んでいます...",
|
||||||
|
"inbox": "受信箱"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
// icons
|
|
||||||
import { Briefcase, Home, Inbox, Layers, PenSquare, BarChart2 } from "lucide-react";
|
|
||||||
// ui
|
|
||||||
import { UserActivityIcon, ContrastIcon } from "@plane/ui";
|
|
||||||
import { Props } from "@/components/icons/types";
|
|
||||||
// constants
|
|
||||||
import { TLinkOptions } from "@/constants/dashboard";
|
|
||||||
// plane web constants
|
|
||||||
import { EUserPermissions } from "@/plane-web/constants/user-permissions";
|
|
||||||
// plane web types
|
|
||||||
import { TSidebarUserMenuItemKeys, TSidebarWorkspaceMenuItemKeys } from "@/plane-web/types/dashboard";
|
|
||||||
|
|
||||||
export type TSidebarMenuItems<T extends TSidebarUserMenuItemKeys | TSidebarWorkspaceMenuItemKeys> = {
|
|
||||||
value: T;
|
|
||||||
label: string;
|
|
||||||
key: string;
|
|
||||||
href: string;
|
|
||||||
access: EUserPermissions[];
|
|
||||||
highlight: (pathname: string, baseUrl: string, options?: TLinkOptions) => boolean;
|
|
||||||
Icon: React.FC<Props>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TSidebarUserMenuItems = TSidebarMenuItems<TSidebarUserMenuItemKeys>;
|
|
||||||
|
|
||||||
export const SIDEBAR_USER_MENU_ITEMS: TSidebarUserMenuItems[] = [
|
|
||||||
{
|
|
||||||
value: "home",
|
|
||||||
label: "Home",
|
|
||||||
key: "home",
|
|
||||||
href: ``,
|
|
||||||
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
|
|
||||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/`,
|
|
||||||
Icon: Home,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "your-work",
|
|
||||||
label: "Your work",
|
|
||||||
key: "your_work",
|
|
||||||
href: "/profile",
|
|
||||||
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
|
||||||
highlight: (pathname: string, baseUrl: string, options?: TLinkOptions) =>
|
|
||||||
options?.userId ? pathname.includes(`${baseUrl}/profile/${options?.userId}`) : false,
|
|
||||||
Icon: UserActivityIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "notifications",
|
|
||||||
label: "Inbox",
|
|
||||||
key: "notifications",
|
|
||||||
href: `/notifications`,
|
|
||||||
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
|
|
||||||
highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/notifications/`),
|
|
||||||
Icon: Inbox,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "drafts",
|
|
||||||
label: "Drafts",
|
|
||||||
key: "drafts",
|
|
||||||
href: `/drafts`,
|
|
||||||
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
|
||||||
highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/drafts/`),
|
|
||||||
Icon: PenSquare,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export type TSidebarWorkspaceMenuItems = TSidebarMenuItems<TSidebarWorkspaceMenuItemKeys>;
|
|
||||||
|
|
||||||
export const SIDEBAR_WORKSPACE_MENU: Partial<Record<TSidebarWorkspaceMenuItemKeys, TSidebarWorkspaceMenuItems>> = {
|
|
||||||
projects: {
|
|
||||||
value: "projects",
|
|
||||||
key: "projects",
|
|
||||||
label: "Projects",
|
|
||||||
href: `/projects`,
|
|
||||||
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
|
|
||||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/projects/`,
|
|
||||||
Icon: Briefcase,
|
|
||||||
},
|
|
||||||
"all-issues": {
|
|
||||||
value: "all-issues",
|
|
||||||
key: "views",
|
|
||||||
label: "Views",
|
|
||||||
href: `/workspace-views/all-issues`,
|
|
||||||
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
|
|
||||||
highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/workspace-views/`),
|
|
||||||
Icon: Layers,
|
|
||||||
},
|
|
||||||
"active-cycles": {
|
|
||||||
value: "active-cycles",
|
|
||||||
key: "active_cycles",
|
|
||||||
label: "Cycles",
|
|
||||||
href: `/active-cycles`,
|
|
||||||
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
|
||||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/active-cycles/`,
|
|
||||||
Icon: ContrastIcon,
|
|
||||||
},
|
|
||||||
analytics: {
|
|
||||||
value: "analytics",
|
|
||||||
key: "analytics",
|
|
||||||
label: "Analytics",
|
|
||||||
href: `/analytics`,
|
|
||||||
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
|
||||||
highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/analytics/`),
|
|
||||||
Icon: BarChart2,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SIDEBAR_WORKSPACE_MENU_ITEMS: TSidebarWorkspaceMenuItems[] = [
|
|
||||||
SIDEBAR_WORKSPACE_MENU?.projects,
|
|
||||||
SIDEBAR_WORKSPACE_MENU?.["all-issues"],
|
|
||||||
SIDEBAR_WORKSPACE_MENU?.["active-cycles"],
|
|
||||||
SIDEBAR_WORKSPACE_MENU?.analytics,
|
|
||||||
].filter((item): item is TSidebarWorkspaceMenuItems => item !== undefined);
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
// plane web types
|
|
||||||
import { TSidebarUserMenuItemKeys, TSidebarWorkspaceMenuItemKeys } from "@/plane-web/types/dashboard";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
export const isUserFeatureEnabled = (featureKey: TSidebarUserMenuItemKeys) => true;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
export const isWorkspaceFeatureEnabled = (featureKey: TSidebarWorkspaceMenuItemKeys, workspaceSlug: string) => true;
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export type TSidebarUserMenuItemKeys = "home" | "your-work" | "notifications" | "drafts";
|
|
||||||
|
|
||||||
export type TSidebarWorkspaceMenuItemKeys = "projects" | "all-issues" | "active-cycles" | "analytics";
|
|
||||||
|
|
@ -6,4 +6,7 @@ export * from "./projects-list";
|
||||||
export * from "./project-navigation";
|
export * from "./project-navigation";
|
||||||
export * from "./quick-actions";
|
export * from "./quick-actions";
|
||||||
export * from "./user-menu";
|
export * from "./user-menu";
|
||||||
|
export * from "./user-menu-item";
|
||||||
export * from "./workspace-menu";
|
export * from "./workspace-menu";
|
||||||
|
export * from "./workspace-menu-item";
|
||||||
|
export * from "./workspace-menu-header";
|
||||||
|
|
|
||||||
84
web/core/components/workspace/sidebar/user-menu-item.tsx
Normal file
84
web/core/components/workspace/sidebar/user-menu-item.tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useParams, usePathname } from "next/navigation";
|
||||||
|
// plane imports
|
||||||
|
import { EUserPermissionsLevel, SIDEBAR_CLICKED, EUserWorkspaceRoles } from "@plane/constants";
|
||||||
|
import { usePlatformOS } from "@plane/hooks";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
import { Tooltip } from "@plane/ui";
|
||||||
|
// components
|
||||||
|
import { SidebarNavItem } from "@/components/sidebar";
|
||||||
|
import { NotificationAppSidebarOption } from "@/components/workspace-notifications";
|
||||||
|
// hooks
|
||||||
|
import { useAppTheme, useEventTracker, useUserPermissions } from "@/hooks/store";
|
||||||
|
|
||||||
|
export interface SidebarUserMenuItemProps {
|
||||||
|
item: {
|
||||||
|
key: string;
|
||||||
|
href: string;
|
||||||
|
access: EUserWorkspaceRoles[];
|
||||||
|
labelTranslationKey: string;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
Icon: any;
|
||||||
|
};
|
||||||
|
draftIssueCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SidebarUserMenuItem: FC<SidebarUserMenuItemProps> = observer((props) => {
|
||||||
|
const { item, draftIssueCount } = props;
|
||||||
|
// nextjs hooks
|
||||||
|
const { workspaceSlug } = useParams();
|
||||||
|
const pathname = usePathname();
|
||||||
|
// package hooks
|
||||||
|
const { t } = useTranslation();
|
||||||
|
// store hooks
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
const { toggleSidebar, sidebarCollapsed } = useAppTheme();
|
||||||
|
const { isMobile } = usePlatformOS();
|
||||||
|
|
||||||
|
const isActive = pathname === item.href;
|
||||||
|
|
||||||
|
if (item.key === "drafts" && draftIssueCount === 0) return null;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
if (!allowPermissions(item.access as any, EUserPermissionsLevel.WORKSPACE, workspaceSlug.toString())) return null;
|
||||||
|
|
||||||
|
const handleLinkClick = (itemKey: string) => {
|
||||||
|
if (window.innerWidth < 768) {
|
||||||
|
toggleSidebar();
|
||||||
|
}
|
||||||
|
captureEvent(SIDEBAR_CLICKED, {
|
||||||
|
destination: itemKey,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
tooltipContent={t(item.labelTranslationKey)}
|
||||||
|
position="right"
|
||||||
|
className="ml-2"
|
||||||
|
disabled={!sidebarCollapsed}
|
||||||
|
isMobile={isMobile}
|
||||||
|
>
|
||||||
|
<Link href={item.href} onClick={() => handleLinkClick(item.key)}>
|
||||||
|
<SidebarNavItem
|
||||||
|
className={`${sidebarCollapsed ? "p-0 size-8 aspect-square justify-center mx-auto" : ""}`}
|
||||||
|
isActive={isActive}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-1.5 py-[1px]">
|
||||||
|
<item.Icon className="size-4 flex-shrink-0" />
|
||||||
|
{!sidebarCollapsed && <p className="text-sm leading-5 font-medium">{t(item.labelTranslationKey)}</p>}
|
||||||
|
</div>
|
||||||
|
{item.key === "notifications" && (
|
||||||
|
<NotificationAppSidebarOption
|
||||||
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
|
isSidebarCollapsed={sidebarCollapsed ?? false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SidebarNavItem>
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -2,58 +2,54 @@
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import Link from "next/link";
|
import { useParams } from "next/navigation";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { Home, Inbox, PenSquare } from "lucide-react";
|
||||||
import { useTranslation } from "@plane/i18n";
|
// plane imports
|
||||||
|
import { EUserWorkspaceRoles } from "@plane/constants";
|
||||||
|
import { UserActivityIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { Tooltip } from "@plane/ui";
|
import { SidebarUserMenuItem } from "@/components/workspace/sidebar";
|
||||||
import { SidebarNavItem } from "@/components/sidebar";
|
|
||||||
import { NotificationAppSidebarOption } from "@/components/workspace-notifications";
|
|
||||||
// constants
|
|
||||||
import { SIDEBAR_CLICKED } from "@/constants/event-tracker";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useAppTheme, useEventTracker, useUser, useUserPermissions } from "@/hooks/store";
|
import { useAppTheme, useUserPermissions, useUser } from "@/hooks/store";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
|
||||||
// plane web constants
|
|
||||||
import { SIDEBAR_USER_MENU_ITEMS } from "@/plane-web/constants/dashboard";
|
|
||||||
import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
// plane web helpers
|
|
||||||
import { isUserFeatureEnabled } from "@/plane-web/helpers/dashboard.helper";
|
|
||||||
|
|
||||||
export const SidebarUserMenu = observer(() => {
|
export const SidebarUserMenu = observer(() => {
|
||||||
const { t } = useTranslation();
|
|
||||||
// store hooks
|
|
||||||
const { toggleSidebar, sidebarCollapsed } = useAppTheme();
|
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
const { isMobile } = usePlatformOS();
|
|
||||||
const { data: currentUser } = useUser();
|
|
||||||
const { allowPermissions, workspaceUserInfo } = useUserPermissions();
|
|
||||||
// router params
|
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
// pathname
|
const { sidebarCollapsed } = useAppTheme();
|
||||||
const pathname = usePathname();
|
const { workspaceUserInfo } = useUserPermissions();
|
||||||
// computed
|
const { data: currentUser } = useUser();
|
||||||
|
|
||||||
const getHref = (link: any) =>
|
const SIDEBAR_USER_MENU_ITEMS = [
|
||||||
`/${workspaceSlug}${link.href}${link.key === "your-work" ? `/${currentUser?.id}` : ""}`;
|
{
|
||||||
|
key: "home",
|
||||||
const handleLinkClick = (itemKey: string) => {
|
labelTranslationKey: "home",
|
||||||
if (window.innerWidth < 768) {
|
href: `/${workspaceSlug.toString()}/`,
|
||||||
toggleSidebar();
|
access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER, EUserWorkspaceRoles.GUEST],
|
||||||
}
|
Icon: Home,
|
||||||
captureEvent(SIDEBAR_CLICKED, {
|
},
|
||||||
destination: itemKey,
|
{
|
||||||
});
|
key: "your-work",
|
||||||
};
|
labelTranslationKey: "your_work",
|
||||||
|
href: `/${workspaceSlug.toString()}/profile/${currentUser?.id}/`,
|
||||||
const notificationIndicatorElement = (
|
access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER],
|
||||||
<NotificationAppSidebarOption
|
Icon: UserActivityIcon,
|
||||||
workspaceSlug={workspaceSlug.toString()}
|
},
|
||||||
isSidebarCollapsed={sidebarCollapsed ?? false}
|
{
|
||||||
/>
|
key: "notifications",
|
||||||
);
|
labelTranslationKey: "inbox",
|
||||||
|
href: `/${workspaceSlug.toString()}/notifications/`,
|
||||||
|
access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER, EUserWorkspaceRoles.GUEST],
|
||||||
|
Icon: Inbox,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "drafts",
|
||||||
|
labelTranslationKey: "drafts",
|
||||||
|
href: `/${workspaceSlug.toString()}/drafts/`,
|
||||||
|
access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER],
|
||||||
|
Icon: PenSquare,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const draftIssueCount = workspaceUserInfo[workspaceSlug.toString()]?.draft_issue_count;
|
const draftIssueCount = workspaceUserInfo[workspaceSlug.toString()]?.draft_issue_count;
|
||||||
|
|
||||||
|
|
@ -63,35 +59,9 @@ export const SidebarUserMenu = observer(() => {
|
||||||
"space-y-0": sidebarCollapsed,
|
"space-y-0": sidebarCollapsed,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{SIDEBAR_USER_MENU_ITEMS.map((link) => {
|
{SIDEBAR_USER_MENU_ITEMS.map((item) => (
|
||||||
if (link.value === "drafts" && draftIssueCount === 0) return null;
|
<SidebarUserMenuItem key={item.key} item={item} draftIssueCount={draftIssueCount} />
|
||||||
if (!isUserFeatureEnabled(link.value)) return null;
|
))}
|
||||||
return (
|
|
||||||
allowPermissions(link.access, EUserPermissionsLevel.WORKSPACE, workspaceSlug.toString()) && (
|
|
||||||
<Tooltip
|
|
||||||
key={link.value}
|
|
||||||
tooltipContent={t(link.key)}
|
|
||||||
position="right"
|
|
||||||
className="ml-2"
|
|
||||||
disabled={!sidebarCollapsed}
|
|
||||||
isMobile={isMobile}
|
|
||||||
>
|
|
||||||
<Link key={link.value} href={getHref(link)} onClick={() => handleLinkClick(link.value)}>
|
|
||||||
<SidebarNavItem
|
|
||||||
className={`${sidebarCollapsed ? "p-0 size-8 aspect-square justify-center mx-auto" : ""}`}
|
|
||||||
isActive={link.highlight(pathname, `/${workspaceSlug}`, { userId: currentUser?.id })}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-1.5 py-[1px]">
|
|
||||||
<link.Icon className="size-4 flex-shrink-0" />
|
|
||||||
{!sidebarCollapsed && <p className="text-sm leading-5 font-medium">{t(link.key)}</p>}
|
|
||||||
</div>
|
|
||||||
{link.value === "notifications" && notificationIndicatorElement}
|
|
||||||
</SidebarNavItem>
|
|
||||||
</Link>
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
116
web/core/components/workspace/sidebar/workspace-menu-header.tsx
Normal file
116
web/core/components/workspace/sidebar/workspace-menu-header.tsx
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { FC, useState, useRef } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
import { MoreHorizontal, ArchiveIcon, ChevronRight, Settings } from "lucide-react";
|
||||||
|
import { Disclosure } from "@headlessui/react";
|
||||||
|
// plane imports
|
||||||
|
import { EUserWorkspaceRoles, EUserPermissionsLevel } from "@plane/constants";
|
||||||
|
import { useOutsideClickDetector } from "@plane/hooks";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
import { CustomMenu } from "@plane/ui";
|
||||||
|
import { cn } from "@plane/utils";
|
||||||
|
// store hooks
|
||||||
|
import { useAppTheme, useUserPermissions } from "@/hooks/store";
|
||||||
|
|
||||||
|
export type SidebarWorkspaceMenuHeaderProps = {
|
||||||
|
isWorkspaceMenuOpen: boolean;
|
||||||
|
toggleWorkspaceMenu: (value: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SidebarWorkspaceMenuHeader: FC<SidebarWorkspaceMenuHeaderProps> = observer((props) => {
|
||||||
|
const { isWorkspaceMenuOpen, toggleWorkspaceMenu } = props;
|
||||||
|
// state
|
||||||
|
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||||
|
// refs
|
||||||
|
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
// hooks
|
||||||
|
const { workspaceSlug } = useParams();
|
||||||
|
const { sidebarCollapsed } = useAppTheme();
|
||||||
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));
|
||||||
|
|
||||||
|
// TODO: fix types
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const isAdmin = allowPermissions([EUserWorkspaceRoles.ADMIN] as any, EUserPermissionsLevel.WORKSPACE);
|
||||||
|
|
||||||
|
if (sidebarCollapsed) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex px-2 bg-custom-sidebar-background-100 group/workspace-button hover:bg-custom-sidebar-background-90 rounded",
|
||||||
|
{
|
||||||
|
"mt-2.5": !sidebarCollapsed,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Disclosure.Button
|
||||||
|
as="button"
|
||||||
|
className="flex-1 sticky top-0 z-10 w-full py-1.5 flex items-center justify-between gap-1 text-custom-sidebar-text-400 text-xs font-semibold"
|
||||||
|
onClick={() => toggleWorkspaceMenu(!isWorkspaceMenuOpen)}
|
||||||
|
>
|
||||||
|
<span>{t("workspace").toUpperCase()}</span>
|
||||||
|
</Disclosure.Button>
|
||||||
|
<CustomMenu
|
||||||
|
customButton={
|
||||||
|
<span
|
||||||
|
ref={actionSectionRef}
|
||||||
|
className="grid place-items-center p-0.5 text-custom-sidebar-text-400 hover:bg-custom-sidebar-background-80 rounded my-auto"
|
||||||
|
onClick={() => {
|
||||||
|
setIsMenuActive(!isMenuActive);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MoreHorizontal className="size-4" />
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
className={cn(
|
||||||
|
"h-full flex items-center opacity-0 z-20 pointer-events-none flex-shrink-0 group-hover/workspace-button:opacity-100 group-hover/workspace-button:pointer-events-auto my-auto",
|
||||||
|
{
|
||||||
|
"opacity-100 pointer-events-auto": isMenuActive,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
customButtonClassName="grid place-items-center"
|
||||||
|
placement="bottom-start"
|
||||||
|
>
|
||||||
|
<CustomMenu.MenuItem>
|
||||||
|
<Link href={`/${workspaceSlug}/projects/archives`}>
|
||||||
|
<div className="flex items-center justify-start gap-2">
|
||||||
|
<ArchiveIcon className="h-3.5 w-3.5 stroke-[1.5]" />
|
||||||
|
<span>{t("archives")}</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
|
||||||
|
{isAdmin && (
|
||||||
|
<CustomMenu.MenuItem>
|
||||||
|
<Link href={`/${workspaceSlug}/settings`}>
|
||||||
|
<div className="flex items-center justify-start gap-2">
|
||||||
|
<Settings className="h-3.5 w-3.5 stroke-[1.5]" />
|
||||||
|
<span>{t("settings")}</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
)}
|
||||||
|
</CustomMenu>
|
||||||
|
<Disclosure.Button
|
||||||
|
as="button"
|
||||||
|
className="sticky top-0 z-10 group/workspace-button px-0.5 py-1.5 flex items-center justify-between gap-1 text-custom-sidebar-text-400 hover:bg-custom-sidebar-background-90 rounded text-xs font-semibold"
|
||||||
|
onClick={() => toggleWorkspaceMenu(!isWorkspaceMenuOpen)}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
<span className="flex-shrink-0 opacity-0 pointer-events-none group-hover/workspace-button:opacity-100 group-hover/workspace-button:pointer-events-auto rounded hover:bg-custom-sidebar-background-80">
|
||||||
|
<ChevronRight
|
||||||
|
className={cn("size-4 flex-shrink-0 text-custom-sidebar-text-400 transition-transform", {
|
||||||
|
"rotate-90": isWorkspaceMenuOpen,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Disclosure.Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useParams, usePathname } from "next/navigation";
|
||||||
|
// plane imports
|
||||||
|
import { EUserWorkspaceRoles, EUserPermissionsLevel } from "@plane/constants";
|
||||||
|
import { usePlatformOS } from "@plane/hooks";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
import { Tooltip } from "@plane/ui";
|
||||||
|
import { cn } from "@plane/utils";
|
||||||
|
// components
|
||||||
|
import { SidebarNavItem } from "@/components/sidebar";
|
||||||
|
// hooks
|
||||||
|
import { useAppTheme, useUserPermissions } from "@/hooks/store";
|
||||||
|
// plane web imports
|
||||||
|
import { UpgradeBadge } from "@/plane-web/components/workspace";
|
||||||
|
|
||||||
|
export type SidebarWorkspaceMenuItemProps = {
|
||||||
|
item: {
|
||||||
|
labelTranslationKey: string;
|
||||||
|
key: string;
|
||||||
|
href: string;
|
||||||
|
Icon: any;
|
||||||
|
access: EUserWorkspaceRoles[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SidebarWorkspaceMenuItem: FC<SidebarWorkspaceMenuItemProps> = observer((props) => {
|
||||||
|
const { item } = props;
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
// nextjs hooks
|
||||||
|
const pathname = usePathname();
|
||||||
|
const { workspaceSlug } = useParams();
|
||||||
|
const { allowPermissions } = useUserPermissions();
|
||||||
|
// store hooks
|
||||||
|
const { toggleSidebar, sidebarCollapsed } = useAppTheme();
|
||||||
|
const { isMobile } = usePlatformOS();
|
||||||
|
|
||||||
|
const handleLinkClick = () => {
|
||||||
|
if (window.innerWidth < 768) {
|
||||||
|
toggleSidebar();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!allowPermissions(item.access as any, EUserPermissionsLevel.WORKSPACE, workspaceSlug.toString())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isActive = item.href === pathname;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
tooltipContent={t(item.labelTranslationKey)}
|
||||||
|
position="right"
|
||||||
|
className="ml-2"
|
||||||
|
disabled={!sidebarCollapsed}
|
||||||
|
isMobile={isMobile}
|
||||||
|
>
|
||||||
|
<Link href={item.href} onClick={() => handleLinkClick()}>
|
||||||
|
<SidebarNavItem
|
||||||
|
className={`${sidebarCollapsed ? "p-0 size-8 aspect-square justify-center mx-auto" : ""}`}
|
||||||
|
isActive={isActive}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-1.5 py-[1px]">
|
||||||
|
<item.Icon
|
||||||
|
className={cn("size-4", {
|
||||||
|
"rotate-180": item.key === "active-cycles",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{!sidebarCollapsed && <p className="text-sm leading-5 font-medium">{t(item.labelTranslationKey)}</p>}
|
||||||
|
</div>
|
||||||
|
{!sidebarCollapsed && item.key === "active-cycles" && (
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<UpgradeBadge />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</SidebarNavItem>
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -1,150 +1,69 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import Link from "next/link";
|
import { useParams } from "next/navigation";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { BarChart2, Briefcase, Layers } from "lucide-react";
|
||||||
import { ArchiveIcon, ChevronRight, MoreHorizontal, Settings } from "lucide-react";
|
|
||||||
import { Disclosure, Transition } from "@headlessui/react";
|
import { Disclosure, Transition } from "@headlessui/react";
|
||||||
import { useOutsideClickDetector } from "@plane/hooks";
|
|
||||||
import { useTranslation } from "@plane/i18n";
|
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu, Tooltip } from "@plane/ui";
|
import { EUserWorkspaceRoles } from "@plane/constants";
|
||||||
|
import { ContrastIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { SidebarNavItem } from "@/components/sidebar";
|
import { SidebarWorkspaceMenuHeader, SidebarWorkspaceMenuItem } from "@/components/workspace/sidebar";
|
||||||
// constants
|
|
||||||
import { SIDEBAR_CLICKED } from "@/constants/event-tracker";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useAppTheme, useEventTracker, useUserPermissions } from "@/hooks/store";
|
import { useAppTheme } from "@/hooks/store";
|
||||||
import useLocalStorage from "@/hooks/use-local-storage";
|
import useLocalStorage from "@/hooks/use-local-storage";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
|
||||||
// plane web components
|
|
||||||
import { UpgradeBadge } from "@/plane-web/components/workspace";
|
|
||||||
// plane web constants
|
|
||||||
import { SIDEBAR_WORKSPACE_MENU_ITEMS } from "@/plane-web/constants/dashboard";
|
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
||||||
// plane web hooks
|
|
||||||
import { isWorkspaceFeatureEnabled } from "@/plane-web/helpers/dashboard.helper";
|
|
||||||
|
|
||||||
export const SidebarWorkspaceMenu = observer(() => {
|
export const SidebarWorkspaceMenu = observer(() => {
|
||||||
// state
|
|
||||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
|
||||||
// refs
|
|
||||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
// router params
|
// router params
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
// pathname
|
|
||||||
const pathname = usePathname();
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const { t } = useTranslation();
|
const { sidebarCollapsed } = useAppTheme();
|
||||||
const { toggleSidebar, sidebarCollapsed } = useAppTheme();
|
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
const { isMobile } = usePlatformOS();
|
|
||||||
const { allowPermissions } = useUserPermissions();
|
|
||||||
// local storage
|
// local storage
|
||||||
const { setValue: toggleWorkspaceMenu, storedValue } = useLocalStorage<boolean>("is_workspace_menu_open", true);
|
const { setValue: toggleWorkspaceMenu, storedValue } = useLocalStorage<boolean>("is_workspace_menu_open", true);
|
||||||
// derived values
|
// derived values
|
||||||
const isWorkspaceMenuOpen = !!storedValue;
|
const isWorkspaceMenuOpen = !!storedValue;
|
||||||
const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
|
||||||
|
|
||||||
const handleLinkClick = (itemKey: string) => {
|
|
||||||
if (window.innerWidth < 768) {
|
|
||||||
toggleSidebar();
|
|
||||||
}
|
|
||||||
captureEvent(SIDEBAR_CLICKED, {
|
|
||||||
destination: itemKey,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sidebarCollapsed) toggleWorkspaceMenu(true);
|
if (sidebarCollapsed) toggleWorkspaceMenu(true);
|
||||||
}, [sidebarCollapsed, toggleWorkspaceMenu]);
|
}, [sidebarCollapsed, toggleWorkspaceMenu]);
|
||||||
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));
|
|
||||||
|
|
||||||
const indicatorElement = (
|
const SIDEBAR_WORKSPACE_MENU_ITEMS = [
|
||||||
<div className="flex-shrink-0">
|
{
|
||||||
<UpgradeBadge />
|
key: "projects",
|
||||||
</div>
|
labelTranslationKey: "projects",
|
||||||
);
|
href: `/${workspaceSlug}/projects/`,
|
||||||
|
access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER, EUserWorkspaceRoles.GUEST],
|
||||||
|
Icon: Briefcase,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "views",
|
||||||
|
labelTranslationKey: "views",
|
||||||
|
href: `/${workspaceSlug}/workspace-views/all-issues/`,
|
||||||
|
access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER, EUserWorkspaceRoles.GUEST],
|
||||||
|
Icon: Layers,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "active-cycles",
|
||||||
|
labelTranslationKey: "cycles",
|
||||||
|
href: `/${workspaceSlug}/active-cycles/`,
|
||||||
|
access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER],
|
||||||
|
Icon: ContrastIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "analytics",
|
||||||
|
labelTranslationKey: "analytics",
|
||||||
|
href: `/${workspaceSlug}/analytics/`,
|
||||||
|
access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER],
|
||||||
|
Icon: BarChart2,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Disclosure as="div" defaultOpen>
|
<Disclosure as="div" defaultOpen>
|
||||||
{!sidebarCollapsed && (
|
<SidebarWorkspaceMenuHeader isWorkspaceMenuOpen={isWorkspaceMenuOpen} toggleWorkspaceMenu={toggleWorkspaceMenu} />
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex px-2 bg-custom-sidebar-background-100 group/workspace-button hover:bg-custom-sidebar-background-90 rounded",
|
|
||||||
{
|
|
||||||
"mt-2.5": !sidebarCollapsed,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
<Disclosure.Button
|
|
||||||
as="button"
|
|
||||||
className="flex-1 sticky top-0 z-10 w-full py-1.5 flex items-center justify-between gap-1 text-custom-sidebar-text-400 text-xs font-semibold"
|
|
||||||
onClick={() => toggleWorkspaceMenu(!isWorkspaceMenuOpen)}
|
|
||||||
>
|
|
||||||
<span>{t("workspace").toUpperCase()}</span>
|
|
||||||
</Disclosure.Button>
|
|
||||||
<CustomMenu
|
|
||||||
customButton={
|
|
||||||
<span
|
|
||||||
ref={actionSectionRef}
|
|
||||||
className="grid place-items-center p-0.5 text-custom-sidebar-text-400 hover:bg-custom-sidebar-background-80 rounded my-auto"
|
|
||||||
onClick={() => {
|
|
||||||
setIsMenuActive(!isMenuActive);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MoreHorizontal className="size-4" />
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
className={cn(
|
|
||||||
"h-full flex items-center opacity-0 z-20 pointer-events-none flex-shrink-0 group-hover/workspace-button:opacity-100 group-hover/workspace-button:pointer-events-auto my-auto",
|
|
||||||
{
|
|
||||||
"opacity-100 pointer-events-auto": isMenuActive,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
customButtonClassName="grid place-items-center"
|
|
||||||
placement="bottom-start"
|
|
||||||
>
|
|
||||||
<CustomMenu.MenuItem>
|
|
||||||
<Link href={`/${workspaceSlug}/projects/archives`}>
|
|
||||||
<div className="flex items-center justify-start gap-2">
|
|
||||||
<ArchiveIcon className="h-3.5 w-3.5 stroke-[1.5]" />
|
|
||||||
<span>{t("archives")}</span>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
|
|
||||||
{isAdmin && (
|
|
||||||
<CustomMenu.MenuItem>
|
|
||||||
<Link href={`/${workspaceSlug}/settings`}>
|
|
||||||
<div className="flex items-center justify-start gap-2">
|
|
||||||
<Settings className="h-3.5 w-3.5 stroke-[1.5]" />
|
|
||||||
<span>{t("settings")}</span>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
)}
|
|
||||||
</CustomMenu>
|
|
||||||
<Disclosure.Button
|
|
||||||
as="button"
|
|
||||||
className="sticky top-0 z-10 group/workspace-button px-0.5 py-1.5 flex items-center justify-between gap-1 text-custom-sidebar-text-400 hover:bg-custom-sidebar-background-90 rounded text-xs font-semibold"
|
|
||||||
onClick={() => toggleWorkspaceMenu(!isWorkspaceMenuOpen)}
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
<span className="flex-shrink-0 opacity-0 pointer-events-none group-hover/workspace-button:opacity-100 group-hover/workspace-button:pointer-events-auto rounded hover:bg-custom-sidebar-background-80">
|
|
||||||
<ChevronRight
|
|
||||||
className={cn("size-4 flex-shrink-0 text-custom-sidebar-text-400 transition-transform", {
|
|
||||||
"rotate-90": isWorkspaceMenuOpen,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</Disclosure.Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Transition
|
<Transition
|
||||||
show={isWorkspaceMenuOpen}
|
show={isWorkspaceMenuOpen}
|
||||||
enter="transition duration-100 ease-out"
|
enter="transition duration-100 ease-out"
|
||||||
|
|
@ -162,39 +81,9 @@ export const SidebarWorkspaceMenu = observer(() => {
|
||||||
})}
|
})}
|
||||||
static
|
static
|
||||||
>
|
>
|
||||||
{SIDEBAR_WORKSPACE_MENU_ITEMS.map((link) => {
|
{SIDEBAR_WORKSPACE_MENU_ITEMS.map((item) => (
|
||||||
if (!isWorkspaceFeatureEnabled(link.value, workspaceSlug.toString())) return null;
|
<SidebarWorkspaceMenuItem key={item.key} item={item} />
|
||||||
return (
|
))}
|
||||||
allowPermissions(link.access, EUserPermissionsLevel.WORKSPACE, workspaceSlug.toString()) && (
|
|
||||||
<Tooltip
|
|
||||||
key={link.value}
|
|
||||||
tooltipContent={t(link.key)}
|
|
||||||
position="right"
|
|
||||||
className="ml-2"
|
|
||||||
disabled={!sidebarCollapsed}
|
|
||||||
isMobile={isMobile}
|
|
||||||
>
|
|
||||||
<Link href={`/${workspaceSlug}${link.href}`} onClick={() => handleLinkClick(link.value)}>
|
|
||||||
<SidebarNavItem
|
|
||||||
key={link.value}
|
|
||||||
className={`${sidebarCollapsed ? "p-0 size-8 aspect-square justify-center mx-auto" : ""}`}
|
|
||||||
isActive={link.highlight(pathname, `/${workspaceSlug}`)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-1.5 py-[1px]">
|
|
||||||
<link.Icon
|
|
||||||
className={cn("size-4", {
|
|
||||||
"rotate-180": link.value === "active-cycles",
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
{!sidebarCollapsed && <p className="text-sm leading-5 font-medium">{t(link.key)}</p>}
|
|
||||||
</div>
|
|
||||||
{!sidebarCollapsed && link.value === "active-cycles" && indicatorElement}
|
|
||||||
</SidebarNavItem>
|
|
||||||
</Link>
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
)}
|
)}
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from "ce/constants/dashboard";
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from "ce/helpers/dashboard.helper";
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from "ce/types/dashboard";
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue