diff --git a/.gitignore b/.gitignore
index f19497acc..f0093a0e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -111,3 +111,4 @@ build/
.react-router/
AGENTS.md
temp/
+scripts/
diff --git a/apps/web/ce/components/navigations/top-navigation-root.tsx b/apps/web/ce/components/navigations/top-navigation-root.tsx
index 2607d3832..df2300686 100644
--- a/apps/web/ce/components/navigations/top-navigation-root.tsx
+++ b/apps/web/ce/components/navigations/top-navigation-root.tsx
@@ -1,17 +1,41 @@
// components
import { observer } from "mobx-react";
+import { useParams, usePathname } from "next/navigation";
import { cn } from "@plane/utils";
import { TopNavPowerK } from "@/components/navigation";
import { HelpMenuRoot } from "@/components/workspace/sidebar/help-section/root";
import { UserMenuRoot } from "@/components/workspace/sidebar/user-menu-root";
import { WorkspaceMenuRoot } from "@/components/workspace/sidebar/workspace-menu-root";
import { useAppRailPreferences } from "@/hooks/use-navigation-preferences";
+import { Tooltip } from "@plane/propel/tooltip";
+import { AppSidebarItem } from "@/components/sidebar/sidebar-item";
+import { InboxIcon } from "@plane/propel/icons";
+import useSWR from "swr";
+import { useWorkspaceNotifications } from "@/hooks/store/notifications";
export const TopNavigationRoot = observer(() => {
+ // router
+ const { workspaceSlug, projectId, workItem } = useParams();
+ const pathname = usePathname();
+
+ // store hooks
+ const { unreadNotificationsCount, getUnreadNotificationsCount } = useWorkspaceNotifications();
const { preferences } = useAppRailPreferences();
const showLabel = preferences.displayMode === "icon_with_label";
+ // Fetch notification count
+ useSWR(
+ workspaceSlug ? "WORKSPACE_UNREAD_NOTIFICATION_COUNT" : null,
+ workspaceSlug ? () => getUnreadNotificationsCount(workspaceSlug.toString()) : null
+ );
+
+ // Calculate notification count
+ const isMentionsEnabled = unreadNotificationsCount.mention_unread_notifications_count > 0;
+ const totalNotifications = isMentionsEnabled
+ ? unreadNotificationsCount.mention_unread_notifications_count
+ : unreadNotificationsCount.total_unread_notifications_count;
+
return (
{
{/* Additional Actions */}
+
+
+
+ {totalNotifications > 0 && (
+
+ )}
+
+ ),
+ isActive: pathname?.includes("/notifications/"),
+ }}
+ />
+
diff --git a/apps/web/core/components/navigation/project-actions-menu.tsx b/apps/web/core/components/navigation/project-actions-menu.tsx
index 12c960f6b..e947f9120 100644
--- a/apps/web/core/components/navigation/project-actions-menu.tsx
+++ b/apps/web/core/components/navigation/project-actions-menu.tsx
@@ -44,7 +44,7 @@ export const ProjectActionsMenu: FC
= ({
customButton={
setIsMenuActive(!isMenuActive)}
>
diff --git a/apps/web/core/components/navigation/project-header-button.tsx b/apps/web/core/components/navigation/project-header-button.tsx
new file mode 100644
index 000000000..eb0341540
--- /dev/null
+++ b/apps/web/core/components/navigation/project-header-button.tsx
@@ -0,0 +1,30 @@
+import type { TPartialProject } from "@/plane-web/types";
+// plane propel imports
+import { Logo } from "@plane/propel/emoji-icon-picker";
+import { ChevronDownIcon } from "@plane/propel/icons";
+import { Tooltip } from "@plane/propel/tooltip";
+
+type TProjectHeaderButtonProps = {
+ project: TPartialProject;
+};
+
+export function ProjectHeaderButton({ project }: TProjectHeaderButtonProps) {
+ return (
+
+
+
+ );
+}
diff --git a/apps/web/core/components/navigation/project-header.tsx b/apps/web/core/components/navigation/project-header.tsx
index cf2f6f634..a88fd1cad 100644
--- a/apps/web/core/components/navigation/project-header.tsx
+++ b/apps/web/core/components/navigation/project-header.tsx
@@ -1,20 +1,107 @@
-import type { FC } from "react";
-// plane imports
-import { Logo } from "@plane/propel/emoji-icon-picker";
-import type { TLogoProps } from "@plane/types";
+import { useCallback, useMemo } from "react";
+import { observer } from "mobx-react";
+// plane ui imports
+import type { ICustomSearchSelectOption } from "@plane/types";
+import { CustomSearchSelect } from "@plane/ui";
+// plane propel imports
+import { ProjectIcon } from "@plane/propel/icons";
+// hooks
+import { useAppRouter } from "@/hooks/use-app-router";
+import { useProject } from "@/hooks/store/use-project";
+import { useUserPermissions } from "@/hooks/store/user";
+import { useNavigationItems } from "@/plane-web/components/navigations";
+// local components
+import { SwitcherLabel } from "../common/switcher-label";
+import { ProjectHeaderButton } from "./project-header-button";
+// utils
+import { getTabUrl } from "./tab-navigation-utils";
+import { useTabPreferences } from "./use-tab-preferences";
-type ProjectHeaderProps = {
- project: {
- name: string;
- logo_props: TLogoProps;
- };
+type TProjectHeaderProps = {
+ workspaceSlug: string;
+ projectId: string;
};
-export const ProjectHeader: FC = ({ project }) => (
-
-);
+export const ProjectHeader = observer((props: TProjectHeaderProps) => {
+ const { workspaceSlug, projectId } = props;
+ // router
+ const router = useAppRouter();
+ // store hooks
+ const { joinedProjectIds, getPartialProjectById } = useProject();
+ const { allowPermissions } = useUserPermissions();
+
+ // Get current project details
+ const currentProjectDetails = getPartialProjectById(projectId);
+
+ // Get available navigation items for this project
+ const navigationItems = useNavigationItems({
+ workspaceSlug: workspaceSlug,
+ projectId,
+ project: currentProjectDetails,
+ allowPermissions,
+ });
+
+ // Get preferences from hook
+ const { tabPreferences } = useTabPreferences(workspaceSlug, projectId);
+
+ // Memoize available tab keys
+ const availableTabKeys = useMemo(() => navigationItems.map((item) => item.key), [navigationItems]);
+
+ // Memoize validated default tab key
+ const validatedDefaultTabKey = useMemo(
+ () =>
+ availableTabKeys.includes(tabPreferences.defaultTab)
+ ? tabPreferences.defaultTab
+ : availableTabKeys[0] || "work_items",
+ [availableTabKeys, tabPreferences.defaultTab]
+ );
+
+ // Memoize switcher options to prevent recalculation on every render
+ const switcherOptions = useMemo(
+ () =>
+ joinedProjectIds
+ .map((id): ICustomSearchSelectOption | null => {
+ const project = getPartialProjectById(id);
+ if (!project) return null;
+
+ return {
+ value: id,
+ query: project.name,
+ content: (
+
+ ),
+ };
+ })
+ .filter((option): option is ICustomSearchSelectOption => option !== null),
+ [joinedProjectIds, getPartialProjectById]
+ );
+
+ // Memoize onChange handler
+ const handleProjectChange = useCallback(
+ (value: string) => {
+ if (value !== currentProjectDetails?.id) {
+ router.push(getTabUrl(workspaceSlug, value, validatedDefaultTabKey));
+ }
+ },
+ [currentProjectDetails?.id, router, workspaceSlug, validatedDefaultTabKey]
+ );
+
+ // Early return if no project details
+ if (!currentProjectDetails) return null;
+
+ return (
+ : null}
+ className="h-full rounded"
+ customButtonClassName="group flex items-center gap-0.5 rounded hover:bg-custom-background-90 outline-none cursor-pointer h-full"
+ />
+ );
+});
diff --git a/apps/web/core/components/navigation/tab-navigation-root.tsx b/apps/web/core/components/navigation/tab-navigation-root.tsx
index a3285fd82..103c4dba8 100644
--- a/apps/web/core/components/navigation/tab-navigation-root.tsx
+++ b/apps/web/core/components/navigation/tab-navigation-root.tsx
@@ -169,7 +169,7 @@ export const TabNavigationRoot: FC = observer((props) =
{/* container for the tab navigation */}
-
+
,
+ icon:
,
isActive: isNeedHelpOpen,
}}
/>