From 1a4a26dbd78521a2780e88df20b107090cccf23a Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Fri, 3 Jan 2025 21:00:08 +0530 Subject: [PATCH 1/3] fix: adding langauge support for sidebar items --- packages/constants/src/event.ts | 1 + packages/constants/src/index.ts | 1 + packages/constants/src/user.ts | 17 +++ packages/hooks/src/index.ts | 1 + packages/hooks/src/use-platform-os.tsx | 34 +++++ web/ce/constants/dashboard.ts | 40 ------ .../components/workspace/sidebar/index.ts | 1 + .../workspace/sidebar/user-menu-item.tsx | 82 +++++++++++++ .../workspace/sidebar/user-menu.tsx | 116 +++++++----------- 9 files changed, 180 insertions(+), 113 deletions(-) create mode 100644 packages/constants/src/event.ts create mode 100644 packages/hooks/src/use-platform-os.tsx create mode 100644 web/core/components/workspace/sidebar/user-menu-item.tsx diff --git a/packages/constants/src/event.ts b/packages/constants/src/event.ts new file mode 100644 index 00000000000..5e7e22004d5 --- /dev/null +++ b/packages/constants/src/event.ts @@ -0,0 +1 @@ +export const SIDEBAR_CLICKED = "Sidenav clicked"; diff --git a/packages/constants/src/index.ts b/packages/constants/src/index.ts index 95a4f978435..eeab193bb39 100644 --- a/packages/constants/src/index.ts +++ b/packages/constants/src/index.ts @@ -1,6 +1,7 @@ export * from "./ai"; export * from "./auth"; export * from "./endpoints"; +export * from "./event"; export * from "./file"; export * from "./instance"; export * from "./issue"; diff --git a/packages/constants/src/user.ts b/packages/constants/src/user.ts index 5c6a89a17f6..f10801807c5 100644 --- a/packages/constants/src/user.ts +++ b/packages/constants/src/user.ts @@ -19,3 +19,20 @@ export type TUserStatus = { status: EUserStatus | undefined; 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, +} diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index c07642907fe..11c33cfa48d 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -1,2 +1,3 @@ export * from "./use-local-storage"; export * from "./use-outside-click-detector"; +export * from "./use-platform-os"; diff --git a/packages/hooks/src/use-platform-os.tsx b/packages/hooks/src/use-platform-os.tsx new file mode 100644 index 00000000000..3f62e1499bd --- /dev/null +++ b/packages/hooks/src/use-platform-os.tsx @@ -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; +}; diff --git a/web/ce/constants/dashboard.ts b/web/ce/constants/dashboard.ts index e2567d5804a..f9f1074d77f 100644 --- a/web/ce/constants/dashboard.ts +++ b/web/ce/constants/dashboard.ts @@ -24,46 +24,6 @@ export type TSidebarMenuItems; -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; export const SIDEBAR_WORKSPACE_MENU: Partial> = { diff --git a/web/core/components/workspace/sidebar/index.ts b/web/core/components/workspace/sidebar/index.ts index 564e3177157..fadc112c35b 100644 --- a/web/core/components/workspace/sidebar/index.ts +++ b/web/core/components/workspace/sidebar/index.ts @@ -6,4 +6,5 @@ export * from "./projects-list"; export * from "./project-navigation"; export * from "./quick-actions"; export * from "./user-menu"; +export * from "./user-menu-item"; export * from "./workspace-menu"; diff --git a/web/core/components/workspace/sidebar/user-menu-item.tsx b/web/core/components/workspace/sidebar/user-menu-item.tsx new file mode 100644 index 00000000000..9674bd900bc --- /dev/null +++ b/web/core/components/workspace/sidebar/user-menu-item.tsx @@ -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 { 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 = 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(); + + 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 ( + + handleLinkClick(item.key)}> + +
+ + {!sidebarCollapsed &&

{t(item.key)}

} +
+ {item.key === "notifications" && ( + + )} +
+ +
+ ); +}); diff --git a/web/core/components/workspace/sidebar/user-menu.tsx b/web/core/components/workspace/sidebar/user-menu.tsx index 1a28301955b..dcfec3139e9 100644 --- a/web/core/components/workspace/sidebar/user-menu.tsx +++ b/web/core/components/workspace/sidebar/user-menu.tsx @@ -2,58 +2,54 @@ import React from "react"; import { observer } from "mobx-react"; -import Link from "next/link"; -import { useParams, usePathname } from "next/navigation"; -import { useTranslation } from "@plane/i18n"; +import { useParams } from "next/navigation"; +import { Home, Inbox, PenSquare } from "lucide-react"; +// plane imports +import { EUserWorkspaceRoles } from "@plane/constants"; +import { UserActivityIcon } from "@plane/ui"; // components -import { Tooltip } from "@plane/ui"; -import { SidebarNavItem } from "@/components/sidebar"; -import { NotificationAppSidebarOption } from "@/components/workspace-notifications"; -// constants -import { SIDEBAR_CLICKED } from "@/constants/event-tracker"; +import { SidebarUserMenuItem } from "@/components/workspace/sidebar"; // helpers import { cn } from "@/helpers/common.helper"; // hooks -import { useAppTheme, useEventTracker, useUser, useUserPermissions } 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"; +import { useAppTheme, useUserPermissions, useUser } from "@/hooks/store"; 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(); - // pathname - const pathname = usePathname(); - // computed - - const getHref = (link: any) => - `/${workspaceSlug}${link.href}${link.key === "your-work" ? `/${currentUser?.id}` : ""}`; - - const handleLinkClick = (itemKey: string) => { - if (window.innerWidth < 768) { - toggleSidebar(); - } - captureEvent(SIDEBAR_CLICKED, { - destination: itemKey, - }); - }; + const { sidebarCollapsed } = useAppTheme(); + const { workspaceUserInfo } = useUserPermissions(); + const { data: currentUser } = useUser(); - const notificationIndicatorElement = ( - - ); + const SIDEBAR_USER_MENU_ITEMS = [ + { + key: "home", + labelTranslationKey: "home", + href: `/${workspaceSlug.toString()}`, + access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER, EUserWorkspaceRoles.GUEST], + Icon: Home, + }, + { + key: "your-work", + labelTranslationKey: "your_work", + href: `/${workspaceSlug.toString()}/profile/${currentUser?.id}`, + access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER], + Icon: UserActivityIcon, + }, + { + 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; @@ -63,35 +59,9 @@ export const SidebarUserMenu = observer(() => { "space-y-0": sidebarCollapsed, })} > - {SIDEBAR_USER_MENU_ITEMS.map((link) => { - if (link.value === "drafts" && draftIssueCount === 0) return null; - if (!isUserFeatureEnabled(link.value)) return null; - return ( - allowPermissions(link.access, EUserPermissionsLevel.WORKSPACE, workspaceSlug.toString()) && ( - - handleLinkClick(link.value)}> - -
- - {!sidebarCollapsed &&

{t(link.key)}

} -
- {link.value === "notifications" && notificationIndicatorElement} -
- -
- ) - ); - })} + {SIDEBAR_USER_MENU_ITEMS.map((item) => ( + + ))} ); }); From 57487bc99e24c807eec63ca4f2b3426080a48d3a Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Mon, 6 Jan 2025 14:12:17 +0530 Subject: [PATCH 2/3] fix: worksapce sidebar item refactor --- .../i18n/src/locales/en/translations.json | 1 + .../i18n/src/locales/es/translations.json | 3 +- .../i18n/src/locales/fr/translations.json | 3 +- .../i18n/src/locales/ja/translations.json | 3 +- web/ce/constants/dashboard.ts | 46 ----- .../components/workspace/sidebar/index.ts | 2 + .../workspace/sidebar/user-menu-item.tsx | 6 +- .../workspace/sidebar/user-menu.tsx | 8 +- .../sidebar/workspace-menu-header.tsx | 116 +++++++++++ .../workspace/sidebar/workspace-menu-item.tsx | 82 ++++++++ .../workspace/sidebar/workspace-menu.tsx | 195 ++++-------------- 11 files changed, 257 insertions(+), 208 deletions(-) create mode 100644 web/core/components/workspace/sidebar/workspace-menu-header.tsx create mode 100644 web/core/components/workspace/sidebar/workspace-menu-item.tsx diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json index d67f34a2df9..774c607f2f0 100644 --- a/packages/i18n/src/locales/en/translations.json +++ b/packages/i18n/src/locales/en/translations.json @@ -43,6 +43,7 @@ "activity": "Activity", "appearance": "Appearance", "notifications": "Notifications", + "inbox": "Inbox", "workspaces": "Workspaces", "create_workspace": "Create workspace", "invitations": "Invitations", diff --git a/packages/i18n/src/locales/es/translations.json b/packages/i18n/src/locales/es/translations.json index 6cbd8c3a8cb..35fa67c1ec6 100644 --- a/packages/i18n/src/locales/es/translations.json +++ b/packages/i18n/src/locales/es/translations.json @@ -314,5 +314,6 @@ "change_parent_issue": "Cambiar problema padre", "remove_parent_issue": "Eliminar problema padre", "add_parent": "Agregar padre", - "loading_members": "Cargando miembros..." + "loading_members": "Cargando miembros...", + "inbox": "bandeja de entrada" } diff --git a/packages/i18n/src/locales/fr/translations.json b/packages/i18n/src/locales/fr/translations.json index f9b59be3d3c..c41312e6e02 100644 --- a/packages/i18n/src/locales/fr/translations.json +++ b/packages/i18n/src/locales/fr/translations.json @@ -314,5 +314,6 @@ "change_parent_issue": "Modifier le problème parent", "remove_parent_issue": "Supprimer le problème parent", "add_parent": "Ajouter un parent", - "loading_members": "Chargement des membres..." + "loading_members": "Chargement des membres...", + "inbox": "boîte de réception" } diff --git a/packages/i18n/src/locales/ja/translations.json b/packages/i18n/src/locales/ja/translations.json index a6a929b735e..71b7a17622b 100644 --- a/packages/i18n/src/locales/ja/translations.json +++ b/packages/i18n/src/locales/ja/translations.json @@ -314,5 +314,6 @@ "change_parent_issue": "親問題を変更", "remove_parent_issue": "親問題を削除", "add_parent": "親問題を追加", - "loading_members": "メンバーを読み込んでいます..." + "loading_members": "メンバーを読み込んでいます...", + "inbox": "受信箱" } diff --git a/web/ce/constants/dashboard.ts b/web/ce/constants/dashboard.ts index f9f1074d77f..4e00b1cd4ba 100644 --- a/web/ce/constants/dashboard.ts +++ b/web/ce/constants/dashboard.ts @@ -25,49 +25,3 @@ export type TSidebarMenuItems; export type TSidebarWorkspaceMenuItems = TSidebarMenuItems; - -export const SIDEBAR_WORKSPACE_MENU: Partial> = { - 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); diff --git a/web/core/components/workspace/sidebar/index.ts b/web/core/components/workspace/sidebar/index.ts index fadc112c35b..0a1f2a920c7 100644 --- a/web/core/components/workspace/sidebar/index.ts +++ b/web/core/components/workspace/sidebar/index.ts @@ -8,3 +8,5 @@ export * from "./quick-actions"; export * from "./user-menu"; export * from "./user-menu-item"; export * from "./workspace-menu"; +export * from "./workspace-menu-item"; +export * from "./workspace-menu-header"; diff --git a/web/core/components/workspace/sidebar/user-menu-item.tsx b/web/core/components/workspace/sidebar/user-menu-item.tsx index 9674bd900bc..f8f0f120a3a 100644 --- a/web/core/components/workspace/sidebar/user-menu-item.tsx +++ b/web/core/components/workspace/sidebar/user-menu-item.tsx @@ -38,6 +38,8 @@ export const SidebarUserMenuItem: FC = observer((props 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 @@ -63,11 +65,11 @@ export const SidebarUserMenuItem: FC = observer((props handleLinkClick(item.key)}>
- {!sidebarCollapsed &&

{t(item.key)}

} + {!sidebarCollapsed &&

{t(item.labelTranslationKey)}

}
{item.key === "notifications" && ( { { key: "home", labelTranslationKey: "home", - href: `/${workspaceSlug.toString()}`, + href: `/${workspaceSlug.toString()}/`, access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER, EUserWorkspaceRoles.GUEST], Icon: Home, }, { key: "your-work", labelTranslationKey: "your_work", - href: `/${workspaceSlug.toString()}/profile/${currentUser?.id}`, + href: `/${workspaceSlug.toString()}/profile/${currentUser?.id}/`, access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER], Icon: UserActivityIcon, }, { key: "notifications", labelTranslationKey: "inbox", - href: `/${workspaceSlug.toString()}/notifications`, + href: `/${workspaceSlug.toString()}/notifications/`, access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER, EUserWorkspaceRoles.GUEST], Icon: Inbox, }, { key: "drafts", labelTranslationKey: "drafts", - href: `/${workspaceSlug.toString()}/drafts`, + href: `/${workspaceSlug.toString()}/drafts/`, access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER], Icon: PenSquare, }, diff --git a/web/core/components/workspace/sidebar/workspace-menu-header.tsx b/web/core/components/workspace/sidebar/workspace-menu-header.tsx new file mode 100644 index 00000000000..c0755f763c1 --- /dev/null +++ b/web/core/components/workspace/sidebar/workspace-menu-header.tsx @@ -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 = observer((props) => { + const { isWorkspaceMenuOpen, toggleWorkspaceMenu } = props; + // state + const [isMenuActive, setIsMenuActive] = useState(false); + // refs + const actionSectionRef = useRef(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 ( +
+ toggleWorkspaceMenu(!isWorkspaceMenuOpen)} + > + {t("workspace").toUpperCase()} + + { + setIsMenuActive(!isMenuActive); + }} + > + + + } + 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" + > + + +
+ + {t("archives")} +
+ +
+ + {isAdmin && ( + + +
+ + {t("settings")} +
+ +
+ )} +
+ toggleWorkspaceMenu(!isWorkspaceMenuOpen)} + > + {" "} + + + + +
+ ); +}); diff --git a/web/core/components/workspace/sidebar/workspace-menu-item.tsx b/web/core/components/workspace/sidebar/workspace-menu-item.tsx new file mode 100644 index 00000000000..0fd72761406 --- /dev/null +++ b/web/core/components/workspace/sidebar/workspace-menu-item.tsx @@ -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 = 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 ( + + handleLinkClick()}> + +
+ + {!sidebarCollapsed &&

{t(item.labelTranslationKey)}

} +
+ {!sidebarCollapsed && item.key === "active-cycles" && ( +
+ +
+ )} +
+ +
+ ); +}); diff --git a/web/core/components/workspace/sidebar/workspace-menu.tsx b/web/core/components/workspace/sidebar/workspace-menu.tsx index be26ce8f8fb..b24e7abe26e 100644 --- a/web/core/components/workspace/sidebar/workspace-menu.tsx +++ b/web/core/components/workspace/sidebar/workspace-menu.tsx @@ -1,150 +1,69 @@ "use client"; -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect } from "react"; import { observer } from "mobx-react"; -import Link from "next/link"; -import { useParams, usePathname } from "next/navigation"; -import { ArchiveIcon, ChevronRight, MoreHorizontal, Settings } from "lucide-react"; +import { useParams } from "next/navigation"; +import { BarChart2, Briefcase, Layers } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; -import { useOutsideClickDetector } from "@plane/hooks"; -import { useTranslation } from "@plane/i18n"; // ui -import { CustomMenu, Tooltip } from "@plane/ui"; +import { EUserWorkspaceRoles } from "@plane/constants"; +import { ContrastIcon } from "@plane/ui"; // components -import { SidebarNavItem } from "@/components/sidebar"; -// constants -import { SIDEBAR_CLICKED } from "@/constants/event-tracker"; +import { SidebarWorkspaceMenuHeader, SidebarWorkspaceMenuItem } from "@/components/workspace/sidebar"; // helpers import { cn } from "@/helpers/common.helper"; // hooks -import { useAppTheme, useEventTracker, useUserPermissions } from "@/hooks/store"; +import { useAppTheme } from "@/hooks/store"; 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(() => { - // state - const [isMenuActive, setIsMenuActive] = useState(false); - // refs - const actionSectionRef = useRef(null); // router params const { workspaceSlug } = useParams(); - // pathname - const pathname = usePathname(); // store hooks - const { t } = useTranslation(); - const { toggleSidebar, sidebarCollapsed } = useAppTheme(); - const { captureEvent } = useEventTracker(); - const { isMobile } = usePlatformOS(); - const { allowPermissions } = useUserPermissions(); + const { sidebarCollapsed } = useAppTheme(); // local storage const { setValue: toggleWorkspaceMenu, storedValue } = useLocalStorage("is_workspace_menu_open", true); // derived values 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(() => { if (sidebarCollapsed) toggleWorkspaceMenu(true); }, [sidebarCollapsed, toggleWorkspaceMenu]); - useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false)); - const indicatorElement = ( -
- -
- ); + const SIDEBAR_WORKSPACE_MENU_ITEMS = [ + { + key: "projects", + 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 ( - {!sidebarCollapsed && ( -
- {" "} - toggleWorkspaceMenu(!isWorkspaceMenuOpen)} - > - {t("workspace").toUpperCase()} - - { - setIsMenuActive(!isMenuActive); - }} - > - - - } - 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" - > - - -
- - {t("archives")} -
- -
- - {isAdmin && ( - - -
- - {t("settings")} -
- -
- )} -
- toggleWorkspaceMenu(!isWorkspaceMenuOpen)} - > - {" "} - - - - -
- )} + { })} static > - {SIDEBAR_WORKSPACE_MENU_ITEMS.map((link) => { - if (!isWorkspaceFeatureEnabled(link.value, workspaceSlug.toString())) return null; - return ( - allowPermissions(link.access, EUserPermissionsLevel.WORKSPACE, workspaceSlug.toString()) && ( - - handleLinkClick(link.value)}> - -
- - {!sidebarCollapsed &&

{t(link.key)}

} -
- {!sidebarCollapsed && link.value === "active-cycles" && indicatorElement} -
- -
- ) - ); - })} + {SIDEBAR_WORKSPACE_MENU_ITEMS.map((item) => ( + + ))} )}
From 25f7780660dcf1ddffeaed8e541124c7d346de07 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Mon, 6 Jan 2025 14:59:58 +0530 Subject: [PATCH 3/3] chore: code cleanup --- web/ce/constants/dashboard.ts | 27 --------------------------- web/ce/helpers/dashboard.helper.ts | 8 -------- web/ce/types/dashboard.ts | 3 --- web/ee/constants/dashboard.ts | 1 - web/ee/helpers/dashboard.helper.ts | 1 - web/ee/types/dashboard.ts | 1 - 6 files changed, 41 deletions(-) delete mode 100644 web/ce/constants/dashboard.ts delete mode 100644 web/ce/helpers/dashboard.helper.ts delete mode 100644 web/ce/types/dashboard.ts delete mode 100644 web/ee/constants/dashboard.ts delete mode 100644 web/ee/helpers/dashboard.helper.ts delete mode 100644 web/ee/types/dashboard.ts diff --git a/web/ce/constants/dashboard.ts b/web/ce/constants/dashboard.ts deleted file mode 100644 index 4e00b1cd4ba..00000000000 --- a/web/ce/constants/dashboard.ts +++ /dev/null @@ -1,27 +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 = { - value: T; - label: string; - key: string; - href: string; - access: EUserPermissions[]; - highlight: (pathname: string, baseUrl: string, options?: TLinkOptions) => boolean; - Icon: React.FC; -}; - -export type TSidebarUserMenuItems = TSidebarMenuItems; - -export type TSidebarWorkspaceMenuItems = TSidebarMenuItems; diff --git a/web/ce/helpers/dashboard.helper.ts b/web/ce/helpers/dashboard.helper.ts deleted file mode 100644 index c96c818a1f0..00000000000 --- a/web/ce/helpers/dashboard.helper.ts +++ /dev/null @@ -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; diff --git a/web/ce/types/dashboard.ts b/web/ce/types/dashboard.ts deleted file mode 100644 index de35f60c6a7..00000000000 --- a/web/ce/types/dashboard.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type TSidebarUserMenuItemKeys = "home" | "your-work" | "notifications" | "drafts"; - -export type TSidebarWorkspaceMenuItemKeys = "projects" | "all-issues" | "active-cycles" | "analytics"; diff --git a/web/ee/constants/dashboard.ts b/web/ee/constants/dashboard.ts deleted file mode 100644 index 8612fbe013f..00000000000 --- a/web/ee/constants/dashboard.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "ce/constants/dashboard"; diff --git a/web/ee/helpers/dashboard.helper.ts b/web/ee/helpers/dashboard.helper.ts deleted file mode 100644 index 7f853699f49..00000000000 --- a/web/ee/helpers/dashboard.helper.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "ce/helpers/dashboard.helper"; diff --git a/web/ee/types/dashboard.ts b/web/ee/types/dashboard.ts deleted file mode 100644 index 359c46578ca..00000000000 --- a/web/ee/types/dashboard.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "ce/types/dashboard";