diff --git a/apiserver/plane/app/serializers/workspace.py b/apiserver/plane/app/serializers/workspace.py
index 68e12608517..3467fff9960 100644
--- a/apiserver/plane/app/serializers/workspace.py
+++ b/apiserver/plane/app/serializers/workspace.py
@@ -32,10 +32,10 @@
class WorkSpaceSerializer(DynamicBaseSerializer):
- owner = UserLiteSerializer(read_only=True)
total_members = serializers.IntegerField(read_only=True)
total_issues = serializers.IntegerField(read_only=True)
logo_url = serializers.CharField(read_only=True)
+ role = serializers.IntegerField(read_only=True)
def validate_slug(self, value):
# Check if the slug is restricted
diff --git a/apiserver/plane/app/views/workspace/base.py b/apiserver/plane/app/views/workspace/base.py
index 058f7702abd..4d81eb1607d 100644
--- a/apiserver/plane/app/views/workspace/base.py
+++ b/apiserver/plane/app/views/workspace/base.py
@@ -7,9 +7,11 @@
from dateutil.relativedelta import relativedelta
from django.db import IntegrityError
from django.db.models import Count, F, Func, OuterRef, Prefetch, Q
+
from django.db.models.fields import DateField
from django.db.models.functions import Cast, ExtractDay, ExtractWeek
+
# Django imports
from django.http import HttpResponse
from django.utils import timezone
@@ -173,6 +175,11 @@ def get(self, request):
.values("count")
)
+ role = (
+ WorkspaceMember.objects.filter(workspace=OuterRef("id"), member=request.user, is_active=True)
+ .values("role")
+ )
+
workspace = (
Workspace.objects.prefetch_related(
Prefetch(
@@ -184,17 +191,19 @@ def get(self, request):
)
.select_related("owner")
.annotate(total_members=member_count)
- .annotate(total_issues=issue_count)
+ .annotate(total_issues=issue_count, role=role)
.filter(
workspace_member__member=request.user, workspace_member__is_active=True
)
.distinct()
)
+
workspaces = WorkSpaceSerializer(
self.filter_queryset(workspace),
fields=fields if fields else None,
many=True,
).data
+
return Response(workspaces, status=status.HTTP_200_OK)
diff --git a/packages/types/src/workspace.d.ts b/packages/types/src/workspace.d.ts
index 021ea173e35..b68cddd7d01 100644
--- a/packages/types/src/workspace.d.ts
+++ b/packages/types/src/workspace.d.ts
@@ -23,6 +23,8 @@ export interface IWorkspace {
organization_size: string;
total_issues: number;
total_projects?: number;
+ current_plan?: string;
+ role: number;
}
export interface IWorkspaceLite {
diff --git a/web/ce/components/common/subscription-pill.tsx b/web/ce/components/common/subscription-pill.tsx
new file mode 100644
index 00000000000..c557ce204b5
--- /dev/null
+++ b/web/ce/components/common/subscription-pill.tsx
@@ -0,0 +1,7 @@
+import { IWorkspace } from "@plane/types";
+
+type TProps = {
+ workspace: IWorkspace;
+};
+
+export const SubscriptionPill = (props: TProps) => <>>;
diff --git a/web/core/components/workspace/sidebar/dropdown-item.tsx b/web/core/components/workspace/sidebar/dropdown-item.tsx
new file mode 100644
index 00000000000..b2519783135
--- /dev/null
+++ b/web/core/components/workspace/sidebar/dropdown-item.tsx
@@ -0,0 +1,106 @@
+import Link from "next/link";
+import { useParams } from "next/navigation";
+import { Check, Settings, UserPlus } from "lucide-react";
+import { Menu } from "@headlessui/react";
+import { EUserPermissions } from "@plane/constants";
+import { useTranslation } from "@plane/i18n";
+import { IWorkspace } from "@plane/types";
+import { cn, getFileURL } from "@plane/utils";
+import { getUserRole } from "@/helpers/user.helper";
+import { SubscriptionPill } from "@/plane-web/components/common/subscription-pill";
+
+type TProps = {
+ workspace: IWorkspace;
+ activeWorkspace: IWorkspace | null;
+ handleItemClick: () => void;
+ handleWorkspaceNavigation: (workspace: IWorkspace) => void;
+};
+const SidebarDropdownItem = (props: TProps) => {
+ const { workspace, activeWorkspace, handleItemClick, handleWorkspaceNavigation } = props;
+
+ // router params
+ const { workspaceSlug } = useParams();
+ const { t } = useTranslation();
+
+ return (
+ {
+ handleWorkspaceNavigation(workspace);
+ handleItemClick();
+ }}
+ className="w-full"
+ id={workspace.id}
+ >
+
+
+
+
+ {workspace?.logo_url && workspace.logo_url !== "" ? (
+
+ ) : (
+ (workspace?.name?.[0] ?? "...")
+ )}
+
+
+
+ {workspace.name}
+
+
+
{getUserRole(workspace.role)?.toLowerCase() || "guest"}
+
+
{t("member", { count: workspace.total_members || 0 })}
+
+
+
+ {workspace.id === activeWorkspace?.id ? (
+
+
+
+ ) : (
+
+ )}
+
+ {workspace.id === activeWorkspace?.id && (
+
+ {workspace?.role > EUserPermissions.GUEST && (
+
+
+ {t("settings")}
+
+ )}
+
+
+ {t("invite")}
+
+
+ )}
+
+
+ );
+};
+
+export default SidebarDropdownItem;
diff --git a/web/core/components/workspace/sidebar/dropdown.tsx b/web/core/components/workspace/sidebar/dropdown.tsx
index 00993352820..8d131594840 100644
--- a/web/core/components/workspace/sidebar/dropdown.tsx
+++ b/web/core/components/workspace/sidebar/dropdown.tsx
@@ -1,16 +1,14 @@
"use client";
-import { Fragment, Ref, useState, useMemo } from "react";
+import { Fragment, Ref, useState } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
-import { useParams } from "next/navigation";
import { usePopper } from "react-popper";
// icons
-import { Check, ChevronDown, LogOut, Mails, PlusSquare, Settings } from "lucide-react";
+import { ChevronDown, CirclePlus, LogOut, Mails, Settings } from "lucide-react";
// ui
import { Menu, Transition } from "@headlessui/react";
// types
-import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { IWorkspace } from "@plane/types";
// plane ui
@@ -19,36 +17,16 @@ import { GOD_MODE_URL, cn } from "@/helpers/common.helper";
// helpers
import { getFileURL } from "@/helpers/file.helper";
// hooks
-import { useAppTheme, useUser, useUserPermissions, useUserProfile, useWorkspace } from "@/hooks/store";
-// plane web constants
+import { useAppTheme, useUser, useUserProfile, useWorkspace } from "@/hooks/store";
// plane web helpers
import { getIsWorkspaceCreationDisabled } from "@/plane-web/helpers/instance.helper";
// components
import { WorkspaceLogo } from "../logo";
+import SidebarDropdownItem from "./dropdown-item";
export const SidebarDropdown = observer(() => {
const { t } = useTranslation();
- const userLinks = useMemo(
- () => (workspaceSlug: string) => [
- {
- key: "workspace_invites",
- name: t("workspace_invites"),
- href: "/invitations",
- icon: Mails,
- access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
- },
- {
- key: "settings",
- name: t("workspace_settings.label"),
- href: `/${workspaceSlug}/settings`,
- icon: Settings,
- access: [EUserPermissions.ADMIN],
- },
- ],
- [t]
- );
- // router params
- const { workspaceSlug } = useParams();
+
// store hooks
const { sidebarCollapsed, toggleSidebar } = useAppTheme();
const { data: currentUser } = useUser();
@@ -58,8 +36,6 @@ export const SidebarDropdown = observer(() => {
signOut,
} = useUser();
const { updateUserProfile } = useUserProfile();
- const { allowPermissions } = useUserPermissions();
- // derived values
const isWorkspaceCreationEnabled = getIsWorkspaceCreationDisabled() === false;
const isUserInstanceAdmin = false;
@@ -150,57 +126,26 @@ export const SidebarDropdown = observer(() => {
>
-
-
+
+
{currentUser?.email}
-
+
{workspacesList ? (
-
- {workspacesList.map((workspace) => (
-
+ {(activeWorkspace
+ ? [
+ activeWorkspace,
+ ...workspacesList.filter((workspace) => workspace.id !== activeWorkspace?.id),
+ ]
+ : workspacesList
+ ).map((workspace) => (
+
{
- handleWorkspaceNavigation(workspace);
- handleItemClick();
- }}
- className="w-full"
- >
-
-
-
- {workspace?.logo_url && workspace.logo_url !== "" ? (
-
- ) : (
- (workspace?.name?.[0] ?? "...")
- )}
-
-
- {workspace.name}
-
-
- {workspace.id === activeWorkspace?.id && (
-
-
-
- )}
-
-
+ workspace={workspace}
+ activeWorkspace={activeWorkspace}
+ handleItemClick={handleItemClick}
+ handleWorkspaceNavigation={handleWorkspaceNavigation}
+ />
))}
) : (
@@ -219,43 +164,33 @@ export const SidebarDropdown = observer(() => {
as="div"
className="flex items-center gap-2 rounded px-2 py-1 text-sm font-medium text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
>
-
+
{t("create_workspace")}
)}
- {userLinks(workspaceSlug?.toString() ?? "").map(
- (link, index) =>
- allowPermissions(link.access, EUserPermissionsLevel.WORKSPACE) && (
-
{
- if (index > 0) handleItemClick();
- }}
- >
-
-
- {link.name}
-
-
- )
- )}
-
-
-
-
- {t("sign_out")}
-
+
+
+
+
+ {t("workspace_invites")}
+
+
+
+
+
+
+ {t("sign_out")}
+
+
diff --git a/web/ee/components/common/subscription-pill.tsx b/web/ee/components/common/subscription-pill.tsx
new file mode 100644
index 00000000000..38bcdebd5ad
--- /dev/null
+++ b/web/ee/components/common/subscription-pill.tsx
@@ -0,0 +1 @@
+export * from "ce/components/common/subscription-pill";