diff --git a/web/app/[workspaceSlug]/(projects)/sidebar.tsx b/web/app/[workspaceSlug]/(projects)/sidebar.tsx index 9ca29af3e..5199928d3 100644 --- a/web/app/[workspaceSlug]/(projects)/sidebar.tsx +++ b/web/app/[workspaceSlug]/(projects)/sidebar.tsx @@ -1,18 +1,23 @@ import { FC, useRef } from "react"; import { observer } from "mobx-react"; // components -import { ProjectSidebarList } from "@/components/project"; import { - WorkspaceHelpSection, - WorkspaceSidebarDropdown, - WorkspaceSidebarMenu, - WorkspaceSidebarQuickAction, + SidebarDropdown, + SidebarHelpSection, + SidebarProjectsList, + SidebarQuickActions, + SidebarUserMenu, + SidebarWorkspaceMenu, } from "@/components/workspace"; +// helpers +import { cn } from "@/helpers/common.helper"; // hooks import { useAppTheme } from "@/hooks/store"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; +// plane web components +import { SidebarAppSwitcher } from "@/plane-web/components/sidebar"; -export interface IAppSidebar { } +export interface IAppSidebar {} export const AppSidebar: FC = observer(() => { // store hooks @@ -30,19 +35,42 @@ export const AppSidebar: FC = observer(() => { return (
-
- - - - - +
+ +
+ + +
+ +
+ +
+ +
); diff --git a/web/ce/components/sidebar/app-switcher.tsx b/web/ce/components/sidebar/app-switcher.tsx index 53492e6b6..1344211b0 100644 --- a/web/ce/components/sidebar/app-switcher.tsx +++ b/web/ce/components/sidebar/app-switcher.tsx @@ -1 +1 @@ -export const AppSwitcher = () => null; +export const SidebarAppSwitcher = () => null; diff --git a/web/core/components/notifications/notification-popover.tsx b/web/core/components/notifications/notification-popover.tsx index 4aee91086..ce4c6efd8 100644 --- a/web/core/components/notifications/notification-popover.tsx +++ b/web/core/components/notifications/notification-popover.tsx @@ -4,17 +4,18 @@ import React, { Fragment } from "react"; import { observer } from "mobx-react"; import { Bell } from "lucide-react"; import { Popover, Transition } from "@headlessui/react"; -// hooks -// icons -// components +// ui import { Tooltip } from "@plane/ui"; +// components import { EmptyState } from "@/components/empty-state"; import { SnoozeNotificationModal, NotificationCard, NotificationHeader } from "@/components/notifications"; import { NotificationsLoader } from "@/components/ui"; // constants import { EmptyStateType } from "@/constants/empty-state"; // helpers +import { cn } from "@/helpers/common.helper"; import { getNumberCount } from "@/helpers/string.helper"; +// hooks import { useAppTheme } from "@/hooks/store"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; import { usePlatformOS } from "@/hooks/use-platform-os"; @@ -93,18 +94,23 @@ export const NotificationPopover = observer(() => { isMobile={isMobile} > - - {!disabled && workspaceDraftIssue && ( - <> -
- - - -
-
- -
-
- - )} -
- )} - - -
- - ); -}); diff --git a/web/core/components/workspace/sidebar-dropdown.tsx b/web/core/components/workspace/sidebar/dropdown.tsx similarity index 66% rename from web/core/components/workspace/sidebar-dropdown.tsx rename to web/core/components/workspace/sidebar/dropdown.tsx index a75607b21..3d4995784 100644 --- a/web/core/components/workspace/sidebar-dropdown.tsx +++ b/web/core/components/workspace/sidebar/dropdown.tsx @@ -13,12 +13,10 @@ import { Menu, Transition } from "@headlessui/react"; import { IWorkspace } from "@plane/types"; // plane ui import { Avatar, Loader, TOAST_TYPE, setToast } from "@plane/ui"; -import { GOD_MODE_URL } from "@/helpers/common.helper"; +import { GOD_MODE_URL, cn } from "@/helpers/common.helper"; // hooks import { useAppTheme, useUser, useUserProfile, useWorkspace } from "@/hooks/store"; -// plane web components -import { AppSwitcher } from "@/plane-web/components/sidebar"; -import { WorkspaceLogo } from "./logo"; +import { WorkspaceLogo } from "../logo"; // Static Data const userLinks = (workspaceSlug: string) => [ @@ -35,6 +33,7 @@ const userLinks = (workspaceSlug: string) => [ icon: Settings, }, ]; + const profileLinks = (workspaceSlug: string, userId: string) => [ { name: "My activity", @@ -47,7 +46,8 @@ const profileLinks = (workspaceSlug: string, userId: string) => [ link: "/profile", }, ]; -export const WorkspaceSidebarDropdown = observer(() => { + +export const SidebarDropdown = observer(() => { // router params const { workspaceSlug } = useParams(); // store hooks @@ -77,10 +77,12 @@ export const WorkspaceSidebarDropdown = observer(() => { }, ], }); + const handleWorkspaceNavigation = (workspace: IWorkspace) => updateUserProfile({ last_workspace_id: workspace?.id, }); + const handleSignOut = async () => { await signOut().catch(() => setToast({ @@ -90,6 +92,7 @@ export const WorkspaceSidebarDropdown = observer(() => { }) ); }; + const handleItemClick = () => { if (window.innerWidth < 768) { toggleSidebar(); @@ -98,32 +101,41 @@ export const WorkspaceSidebarDropdown = observer(() => { const workspacesList = Object.values(workspaces ?? {}); // TODO: fix workspaces list scroll return ( -
- +
+ {({ open }) => ( <> - -
-
- - {!sidebarCollapsed && ( -

- {activeWorkspace?.name ? activeWorkspace.name : "Loading..."} -

- )} -
+ +
+ {!sidebarCollapsed && ( -
+ {!sidebarCollapsed && ( +
{ leaveTo="transform opacity-0 scale-95" > -
+
{currentUser?.email}
- {workspacesList ? ( -
- {workspacesList.length > 0 && - workspacesList.map((workspace) => ( - { - handleWorkspaceNavigation(workspace); - handleItemClick(); - }} - className="w-full" +
+ {workspacesList.map((workspace) => ( + { + handleWorkspaceNavigation(workspace); + handleItemClick(); + }} + className="w-full" + > + - -
- - {workspace?.logo && workspace.logo !== "" ? ( - Workspace Logo - ) : ( - workspace?.name?.charAt(0) ?? "..." - )} - -
- {workspace.name} -
-
- {workspace.id === activeWorkspace?.id && ( - - - - )} -
- - ))} +
+ + {workspace?.logo && workspace.logo !== "" ? ( + Workspace Logo + ) : ( + workspace?.name?.charAt(0) ?? "..." + )} + +
+ {workspace.name} +
+
+ {workspace.id === activeWorkspace?.id && ( + + + + )} +
+ + ))}
) : (
@@ -200,7 +210,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
)}
-
+
{ className="flex w-full items-center gap-2 rounded px-2 py-1 text-sm font-medium text-red-600 hover:bg-custom-sidebar-background-80" onClick={handleSignOut} > - + Sign out
@@ -299,7 +309,7 @@ export const WorkspaceSidebarDropdown = observer(() => { className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80" onClick={handleSignOut} > - + Sign out
diff --git a/web/core/components/workspace/help-section.tsx b/web/core/components/workspace/sidebar/help-section.tsx similarity index 98% rename from web/core/components/workspace/help-section.tsx rename to web/core/components/workspace/sidebar/help-section.tsx index a8dbf8b79..a14b2b92b 100644 --- a/web/core/components/workspace/help-section.tsx +++ b/web/core/components/workspace/sidebar/help-section.tsx @@ -39,7 +39,7 @@ export interface WorkspaceHelpSectionProps { setSidebarActive?: React.Dispatch>; } -export const WorkspaceHelpSection: React.FC = observer(() => { +export const SidebarHelpSection: React.FC = observer(() => { // store hooks const { sidebarCollapsed, toggleSidebar } = useAppTheme(); const { toggleShortcutModal } = useCommandPalette(); @@ -63,7 +63,7 @@ export const WorkspaceHelpSection: React.FC = observe <>
[ }, ]; -export const ProjectSidebarListItem: React.FC = observer((props) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars +export const SidebarProjectsListItem: React.FC = observer((props) => { const { projectId, handleCopyText, disableDrag, disableDrop, isLastChild, handleOnProjectDrop, projectListType } = props; // store hooks - const { sidebarCollapsed: isSideBarCollapsed, toggleSidebar } = useAppTheme(); + const { sidebarCollapsed: isSidebarCollapsed, toggleSidebar } = useAppTheme(); const { setTrackElement } = useEventTracker(); const { addProjectToFavorites, removeProjectFromFavorites, getProjectById } = useProject(); const { isMobile } = usePlatformOS(); @@ -175,10 +170,6 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { setLeaveProjectModal(true); }; - const handleLeaveProjectModalClose = () => { - setLeaveProjectModal(false); - }; - const handleProjectClick = () => { if (window.innerWidth < 768) { toggleSidebar(); @@ -194,7 +185,7 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { return combine( draggable({ element, - canDrag: () => !disableDrag && !isSideBarCollapsed, + canDrag: () => !disableDrag && !isSidebarCollapsed, dragHandle: dragHandleElement ?? undefined, getInitialData: () => ({ id: projectId, dragInstanceId: "PROJECTS" }), onDragStart: () => { @@ -282,20 +273,22 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { return ( <> setPublishModal(false)} /> - + setLeaveProjectModal(false)} /> {({ open }) => (
@@ -309,148 +302,153 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { )} - - -
-
- -
- {!isSideBarCollapsed &&

{project.name}

} + {isSidebarCollapsed ? ( + +
+
- {!isSideBarCollapsed && ( - - )}
- - - {!isSideBarCollapsed && ( - setIsMenuActive(!isMenuActive)} + ) : ( + <> + + - -
- } - className={cn("hidden flex-shrink-0 mr-1 group-hover:block", { - "!block": isMenuActive, - })} - buttonClassName="!text-custom-sidebar-text-400" - placement="bottom-start" - > - {!project.is_favorite && ( - - - - Add to favorites - - - )} - {project.is_favorite && ( - - - - Remove from favorites - - - )} - {/* publish project settings */} - {isAdmin && ( - setPublishModal(true)}> -
-
- -
-
{project.anchor ? "Publish settings" : "Publish"}
-
-
- )} - - -
- - Draft issues +
+
+ {!isSidebarCollapsed && ( +

{project.name}

+ )} - - - - - Copy link - - - - {!isViewerOrGuest && ( + + + + + setIsMenuActive(!isMenuActive)} + > + + + } + className={cn( + "opacity-0 pointer-events-none flex-shrink-0 mr-1 group-hover/project-item:opacity-100 group-hover/project-item:pointer-events-auto", + { + "opacity-100 pointer-events-auto": isMenuActive, + } + )} + customButtonClassName="grid place-items-center" + placement="bottom-start" + > + {!project.is_favorite && ( + + + + Add to favorites + + + )} + {project.is_favorite && ( + + + + Remove from favorites + + + )} + {/* publish project settings */} + {isAdmin && ( + setPublishModal(true)}> +
+
+ +
+
{project.anchor ? "Publish settings" : "Publish"}
+
+
+ )} - +
- - Archives + + Draft issues
- )} - - -
- - Settings -
- -
- {/* leave project */} - {isViewerOrGuest && ( - -
- - Leave project -
+ + + + Copy link + - )} -
+ + {!isViewerOrGuest && ( + + +
+ + Archives +
+ +
+ )} + + +
+ + Settings +
+ +
+ {/* leave project */} + {isViewerOrGuest && ( + +
+ + Leave project +
+
+ )} + + )}
@@ -462,8 +460,8 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { leaveFrom="transform scale-100 opacity-100" leaveTo="transform scale-95 opacity-0" > - - {navigation(workspaceSlug as string, project?.id).map((item) => { + + {navigation(workspaceSlug?.toString(), project?.id).map((item) => { if ( (item.name === "Cycles" && !project.cycle_view) || (item.name === "Modules" && !project.module_view) || @@ -475,26 +473,27 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { return ( - - +
-
- - {!isSideBarCollapsed && item.name} -
- - + + {!isSidebarCollapsed && {item.name}} +
+
); })} diff --git a/web/core/components/project/sidebar-list.tsx b/web/core/components/workspace/sidebar/projects-list.tsx similarity index 55% rename from web/core/components/project/sidebar-list.tsx rename to web/core/components/workspace/sidebar/projects-list.tsx index 98cde31c9..9057adeb8 100644 --- a/web/core/components/project/sidebar-list.tsx +++ b/web/core/components/workspace/sidebar/projects-list.tsx @@ -5,14 +5,15 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; -import { ChevronDown, ChevronRight, Plus } from "lucide-react"; +import { ChevronRight, Plus } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; // types import { IProject } from "@plane/types"; // ui -import { TOAST_TYPE, setToast } from "@plane/ui"; +import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; // components -import { CreateProjectModal, ProjectSidebarListItem } from "@/components/project"; +import { CreateProjectModal } from "@/components/project"; +import { SidebarProjectsListItem } from "@/components/workspace"; // constants import { EUserWorkspaceRoles } from "@/constants/workspace"; // helpers @@ -22,7 +23,7 @@ import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store"; -export const ProjectSidebarList: FC = observer(() => { +export const SidebarProjectsList: FC = observer(() => { // get local storage data for isFavoriteProjectsListOpen and isAllProjectsListOpen const isFavProjectsListOpenInLocalStorage = localStorage.getItem("isFavoriteProjectsListOpen"); const isAllProjectsListOpenInLocalStorage = localStorage.getItem("isAllProjectsListOpen"); @@ -51,7 +52,7 @@ export const ProjectSidebarList: FC = observer(() => { } = useProject(); // router params const { workspaceSlug } = useParams(); - + // auth const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; const handleCopyText = (projectId: string) => { @@ -141,126 +142,102 @@ export const ProjectSidebarList: FC = observer(() => { } }; + const projectSections: { + key: "all" | "favorite"; + type: "FAVORITES" | "JOINED"; + title: string; + shortTitle: string; + projects: string[]; + isOpen: boolean; + }[] = [ + { + key: "favorite", + type: "FAVORITES", + title: "Favorites", + shortTitle: "FP", + projects: favoriteProjects, + isOpen: isFavoriteProjectsListOpen, + }, + { + key: "all", + type: "JOINED", + title: "My projects", + shortTitle: "MP", + projects: joinedProjects, + isOpen: isAllProjectsListOpen, + }, + ]; + return ( <> {workspaceSlug && ( { - setIsProjectModalOpen(false); - }} + onClose={() => setIsProjectModalOpen(false)} setToFavorite={isFavoriteProjectCreate} workspaceSlug={workspaceSlug.toString()} /> )}
-
- {favoriteProjects && favoriteProjects.length > 0 && ( - + {projectSections.map((section) => { + if (!section.projects || section.projects.length === 0) return; + + return ( + <> - {!isCollapsed && ( -
- toggleListDisclosure(!isFavoriteProjectsListOpen, "favorite")} - > - Favorites - {isFavoriteProjectsListOpen ? ( - - ) : ( - - )} - - {isAuthorizedUser && ( - - )} -
- )} - - {isFavoriteProjectsListOpen && ( - - {favoriteProjects.map((projectId, index) => ( - handleCopyText(projectId)} - projectListType="FAVORITES" - disableDrag - disableDrop - isLastChild={index === favoriteProjects.length - 1} - /> - ))} - +
- - - )} -
-
- {joinedProjects && joinedProjects.length > 0 && ( - - <> - {!isCollapsed && ( -
- toggleListDisclosure(!isAllProjectsListOpen, "all")} - > - Your projects - {isAllProjectsListOpen ? ( - - ) : ( - - )} - - {isAuthorizedUser && ( + > + toggleListDisclosure(!section.isOpen, section.key)} + > + + {isCollapsed ? section.shortTitle : section.title} + + {!isCollapsed && ( + + )} + + {!isCollapsed && isAuthorizedUser && ( + - )} -
- )} + + )} +
{ leaveFrom="transform scale-100 opacity-100" leaveTo="transform scale-95 opacity-0" > - {isAllProjectsListOpen && ( - - {joinedProjects.map((projectId, index) => ( - + {section.projects.map((projectId, index) => ( + handleCopyText(projectId)} - isLastChild={index === joinedProjects.length - 1} + projectListType={section.type} + disableDrag={section.key === "favorite"} + disableDrop={section.key === "favorite"} + isLastChild={index === section.projects.length - 1} handleOnProjectDrop={handleOnProjectDrop} /> ))} @@ -285,10 +264,9 @@ export const ProjectSidebarList: FC = observer(() => {
- )} -
- - {isAuthorizedUser && joinedProjects && joinedProjects.length === 0 && ( + ); + })} + {isAuthorizedUser && joinedProjects?.length === 0 && ( )} diff --git a/web/core/components/workspace/sidebar/quick-actions.tsx b/web/core/components/workspace/sidebar/quick-actions.tsx new file mode 100644 index 000000000..1f63c4798 --- /dev/null +++ b/web/core/components/workspace/sidebar/quick-actions.tsx @@ -0,0 +1,151 @@ +import { useRef, useState } from "react"; +import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; +import { ChevronUp, PenSquare, Search } from "lucide-react"; +// types +import { TIssue } from "@plane/types"; +// components +import { CreateUpdateIssueModal } from "@/components/issues"; +// constants +import { EIssuesStoreType } from "@/constants/issue"; +import { EUserWorkspaceRoles } from "@/constants/workspace"; +// helpers +import { cn } from "@/helpers/common.helper"; +// hooks +import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store"; +import useLocalStorage from "@/hooks/use-local-storage"; + +export const SidebarQuickActions = observer(() => { + // states + const [isDraftIssueModalOpen, setIsDraftIssueModalOpen] = useState(false); + const [isDraftButtonOpen, setIsDraftButtonOpen] = useState(false); + // refs + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const timeoutRef = useRef(); + // router + const { workspaceSlug: routerWorkspaceSlug } = useParams(); + const workspaceSlug = routerWorkspaceSlug?.toString(); + // store hooks + const { toggleCreateIssueModal, toggleCommandPaletteModal } = useCommandPalette(); + const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme(); + const { setTrackElement } = useEventTracker(); + const { joinedProjectIds } = useProject(); + const { + membership: { currentWorkspaceRole }, + } = useUser(); + // local storage + const { storedValue, setValue } = useLocalStorage>>("draftedIssue", {}); + // derived values + const disabled = joinedProjectIds.length === 0; + const workspaceDraftIssue = workspaceSlug ? storedValue?.[workspaceSlug] ?? undefined : undefined; + // auth + const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; + + const handleMouseEnter = () => { + // if enter before time out clear the timeout + timeoutRef?.current && clearTimeout(timeoutRef.current); + setIsDraftButtonOpen(true); + }; + + const handleMouseLeave = () => { + timeoutRef.current = setTimeout(() => { + setIsDraftButtonOpen(false); + }, 300); + }; + + const removeWorkspaceDraftIssue = () => { + const draftIssues = storedValue ?? {}; + if (workspaceSlug && draftIssues[workspaceSlug]) delete draftIssues[workspaceSlug]; + setValue(draftIssues); + return Promise.resolve(); + }; + + return ( + <> + setIsDraftIssueModalOpen(false)} + data={workspaceDraftIssue ?? {}} + onSubmit={() => removeWorkspaceDraftIssue()} + isDraft + /> +
+ {isAuthorizedUser && ( +
+ + {!disabled && workspaceDraftIssue && ( + <> + {!isSidebarCollapsed && ( + + )} + {isDraftButtonOpen && ( +
+
+ +
+
+ )} + + )} +
+ )} + +
+ + ); +}); diff --git a/web/core/components/workspace/sidebar-menu.tsx b/web/core/components/workspace/sidebar/user-menu.tsx similarity index 50% rename from web/core/components/workspace/sidebar-menu.tsx rename to web/core/components/workspace/sidebar/user-menu.tsx index 0a5db18cc..2a5ccfb3d 100644 --- a/web/core/components/workspace/sidebar-menu.tsx +++ b/web/core/components/workspace/sidebar/user-menu.tsx @@ -4,22 +4,21 @@ import React from "react"; import { observer } from "mobx-react"; import Link from "next/link"; import { useParams, usePathname } from "next/navigation"; -import { Crown } from "lucide-react"; // ui import { Tooltip } from "@plane/ui"; // components import { NotificationPopover } from "@/components/notifications"; // constants -import { SIDEBAR_MENU_ITEMS } from "@/constants/dashboard"; +import { SIDEBAR_USER_MENU_ITEMS } from "@/constants/dashboard"; import { SIDEBAR_CLICKED } from "@/constants/event-tracker"; import { EUserWorkspaceRoles } from "@/constants/workspace"; -// helper +// helpers import { cn } from "@/helpers/common.helper"; // hooks import { useAppTheme, useEventTracker, useUser } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; -export const WorkspaceSidebarMenu = observer(() => { +export const SidebarUserMenu = observer(() => { // store hooks const { toggleSidebar, sidebarCollapsed } = useAppTheme(); const { captureEvent } = useEventTracker(); @@ -44,40 +43,38 @@ export const WorkspaceSidebarMenu = observer(() => { }; return ( -
- {SIDEBAR_MENU_ITEMS.map( +
+ {SIDEBAR_USER_MENU_ITEMS.map( (link) => workspaceMemberInfo >= link.access && ( handleLinkClick(link.key)}> - - -
+ +
+ "text-custom-primary-100 bg-custom-primary-100/10 hover:bg-custom-primary-100/10": link.highlight( + pathname, + `/${workspaceSlug}` + ), + "p-0 size-8 aspect-square justify-center mx-auto": sidebarCollapsed, } - {!sidebarCollapsed &&

{link.label}

} - {!sidebarCollapsed && link.key === "active-cycles" && ( - - )} -
-
- + )} + > + {} + {!sidebarCollapsed &&

{link.label}

} +
+
) )} diff --git a/web/core/components/workspace/sidebar/workspace-menu.tsx b/web/core/components/workspace/sidebar/workspace-menu.tsx new file mode 100644 index 000000000..c5ef23bf8 --- /dev/null +++ b/web/core/components/workspace/sidebar/workspace-menu.tsx @@ -0,0 +1,141 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { observer } from "mobx-react"; +import Link from "next/link"; +import { useParams, usePathname } from "next/navigation"; +import { ChevronRight, Crown, Settings } from "lucide-react"; +import { Disclosure, Transition } from "@headlessui/react"; +// ui +import { Tooltip } from "@plane/ui"; +// constants +import { SIDEBAR_WORKSPACE_MENU_ITEMS } from "@/constants/dashboard"; +import { SIDEBAR_CLICKED } from "@/constants/event-tracker"; +import { EUserWorkspaceRoles } from "@/constants/workspace"; +// helpers +import { cn } from "@/helpers/common.helper"; +// hooks +import { useAppTheme, useEventTracker, useUser } from "@/hooks/store"; +import { usePlatformOS } from "@/hooks/use-platform-os"; + +export const SidebarWorkspaceMenu = observer(() => { + // states + const [isWorkspaceMenuOpen, setIsWorkspaceMenuOpen] = useState(true); + // store hooks + const { toggleSidebar, sidebarCollapsed } = useAppTheme(); + const { captureEvent } = useEventTracker(); + const { isMobile } = usePlatformOS(); + const { + membership: { currentWorkspaceRole }, + } = useUser(); + // router params + const { workspaceSlug } = useParams(); + // pathname + const pathname = usePathname(); + // computed + const workspaceMemberInfo = currentWorkspaceRole || EUserWorkspaceRoles.GUEST; + + const handleLinkClick = (itemKey: string) => { + if (window.innerWidth < 768) { + toggleSidebar(); + } + captureEvent(SIDEBAR_CLICKED, { + destination: itemKey, + }); + }; + + useEffect(() => { + if (sidebarCollapsed) setIsWorkspaceMenuOpen(true); + }, [sidebarCollapsed]); + + return ( + + {!sidebarCollapsed && ( +
+ setIsWorkspaceMenuOpen((prev) => !prev)} + > + Workspace + + + + + + + +
+ )} + + {isWorkspaceMenuOpen && ( + + {SIDEBAR_WORKSPACE_MENU_ITEMS.map( + (link) => + workspaceMemberInfo >= link.access && ( + handleLinkClick(link.key)} + className="block" + > + +
+ { + + } + {!sidebarCollapsed &&

{link.label}

} + {!sidebarCollapsed && link.key === "active-cycles" && ( + + )} +
+
+ + ) + )} +
+ )} +
+
+ ); +}); diff --git a/web/core/constants/dashboard.ts b/web/core/constants/dashboard.ts index 024d87f8c..e3e38ddf9 100644 --- a/web/core/constants/dashboard.ts +++ b/web/core/constants/dashboard.ts @@ -251,7 +251,49 @@ export const CREATED_ISSUES_EMPTY_STATES = { }, }; -export const SIDEBAR_MENU_ITEMS: { +export const SIDEBAR_WORKSPACE_MENU_ITEMS: { + key: string; + label: string; + href: string; + access: EUserWorkspaceRoles; + highlight: (pathname: string, baseUrl: string) => boolean; + Icon: React.FC; +}[] = [ + { + key: "all-issues", + label: "All Issues", + href: `/workspace-views/all-issues`, + access: EUserWorkspaceRoles.GUEST, + highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/workspace-views/`), + Icon: CheckCircle, + }, + { + key: "projects", + label: "Projects", + href: `/projects`, + access: EUserWorkspaceRoles.GUEST, + highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/projects/`, + Icon: Briefcase, + }, + { + key: "active-cycles", + label: "Active Cycles", + href: `/active-cycles`, + access: EUserWorkspaceRoles.GUEST, + highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/active-cycles/`, + Icon: ContrastIcon, + }, + { + key: "analytics", + label: "Analytics", + href: `/analytics`, + access: EUserWorkspaceRoles.MEMBER, + highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/analytics/`), + Icon: BarChart2, + }, +]; + +export const SIDEBAR_USER_MENU_ITEMS: { key: string; label: string; href: string; @@ -267,36 +309,4 @@ export const SIDEBAR_MENU_ITEMS: { highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/`, Icon: Home, }, - { - key: "analytics", - label: "Analytics", - href: `/analytics`, - access: EUserWorkspaceRoles.MEMBER, - highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/analytics/`), - Icon: BarChart2, - }, - { - key: "projects", - label: "Projects", - href: `/projects`, - access: EUserWorkspaceRoles.GUEST, - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/projects/`, - Icon: Briefcase, - }, - { - key: "all-issues", - label: "All Issues", - href: `/workspace-views/all-issues`, - access: EUserWorkspaceRoles.GUEST, - highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/workspace-views/`), - Icon: CheckCircle, - }, - { - key: "active-cycles", - label: "Active Cycles", - href: `/active-cycles`, - access: EUserWorkspaceRoles.GUEST, - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/active-cycles/`, - Icon: ContrastIcon, - }, ];