diff --git a/web/components/headers/project-view-issues.tsx b/web/components/headers/project-view-issues.tsx
index aeb91538c..827997f7b 100644
--- a/web/components/headers/project-view-issues.tsx
+++ b/web/components/headers/project-view-issues.tsx
@@ -173,7 +173,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
handleDisplayPropertiesUpdate={handleDisplayProperties}
/>
- {
+ {canUserCreateIssue && (
- }
+ )}
);
diff --git a/web/components/headers/project-views.tsx b/web/components/headers/project-views.tsx
index 964110967..36c278e82 100644
--- a/web/components/headers/project-views.tsx
+++ b/web/components/headers/project-views.tsx
@@ -7,15 +7,24 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui";
// helpers
import { renderEmoji } from "helpers/emoji.helper";
+// constants
+import { EUserWorkspaceRoles } from "constants/workspace";
export const ProjectViewsHeader: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
- const { project: projectStore, commandPalette } = useMobxStore();
+ const {
+ project: projectStore,
+ commandPalette,
+ user: { currentProjectRole },
+ } = useMobxStore();
const { currentProjectDetails } = projectStore;
+ const canUserCreateIssue =
+ currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole);
+
return (
<>
@@ -50,18 +59,20 @@ export const ProjectViewsHeader: React.FC = observer(() => {
-
-
-
}
- onClick={() => commandPalette.toggleCreateViewModal(true)}
- >
- Create View
-
+ {canUserCreateIssue && (
+
+
+ }
+ onClick={() => commandPalette.toggleCreateViewModal(true)}
+ >
+ Create View
+
+
-
+ )}
>
);
diff --git a/web/components/issues/sidebar-select/assignee.tsx b/web/components/issues/sidebar-select/assignee.tsx
index 497ab6589..34e3bc06a 100644
--- a/web/components/issues/sidebar-select/assignee.tsx
+++ b/web/components/issues/sidebar-select/assignee.tsx
@@ -60,7 +60,9 @@ export const SidebarAssigneeSelect: React.FC = ({ value, onChange, disabl
) : (
diff --git a/web/components/issues/sidebar.tsx b/web/components/issues/sidebar.tsx
index f002b6dda..4cb4d74a1 100644
--- a/web/components/issues/sidebar.tsx
+++ b/web/components/issues/sidebar.tsx
@@ -572,7 +572,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => {
labelList={issueDetail?.labels ?? []}
submitChanges={submitChanges}
isNotAllowed={!isAllowed}
- uneditable={uneditable ?? false}
+ uneditable={uneditable || !isAllowed}
/>
diff --git a/web/components/views/view-list-item.tsx b/web/components/views/view-list-item.tsx
index 95ca50562..6299c4cdb 100644
--- a/web/components/views/view-list-item.tsx
+++ b/web/components/views/view-list-item.tsx
@@ -2,17 +2,22 @@ import React, { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
-import { PencilIcon, StarIcon, TrashIcon } from "lucide-react";
+import { LinkIcon, PencilIcon, StarIcon, TrashIcon } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
+// hooks
+import useToast from "hooks/use-toast";
// components
import { CreateUpdateProjectViewModal, DeleteProjectViewModal } from "components/views";
// ui
import { CustomMenu, PhotoFilterIcon } from "@plane/ui";
// helpers
import { calculateTotalFilters } from "helpers/filter.helper";
+import { copyUrlToClipboard } from "helpers/string.helper";
// types
import { IProjectView } from "types";
+// constants
+import { EUserWorkspaceRoles } from "constants/workspace";
type Props = {
view: IProjectView;
@@ -27,7 +32,12 @@ export const ProjectViewListItem: React.FC = observer((props) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
- const { projectViews: projectViewsStore } = useMobxStore();
+ const { setToastAlert } = useToast();
+
+ const {
+ projectViews: projectViewsStore,
+ user: { currentProjectRole },
+ } = useMobxStore();
const handleAddToFavorites = () => {
if (!workspaceSlug || !projectId) return;
@@ -41,8 +51,22 @@ export const ProjectViewListItem: React.FC = observer((props) => {
projectViewsStore.removeViewFromFavorites(workspaceSlug.toString(), projectId.toString(), view.id);
};
+ const handleCopyText = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/views/${view.id}`).then(() => {
+ setToastAlert({
+ type: "success",
+ title: "Link Copied!",
+ message: "View link copied to clipboard.",
+ });
+ });
+ };
+
const totalFilters = calculateTotalFilters(view.query_data ?? {});
+ const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
+
return (
<>
{workspaceSlug && projectId && view && (
@@ -73,55 +97,66 @@ export const ProjectViewListItem: React.FC = observer((props) => {
{totalFilters} {totalFilters === 1 ? "filter" : "filters"}
+ {isEditingAllowed &&
+ (view.is_favorite ? (
+
+ ) : (
+
+ ))}
- {view.is_favorite ? (
-
- ) : (
-
- )}
- {
- e.preventDefault();
- e.stopPropagation();
- setCreateUpdateViewModal(true);
- }}
- >
+ {isEditingAllowed && (
+ <>
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ setCreateUpdateViewModal(true);
+ }}
+ >
+
+
+ Edit View
+
+
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ setDeleteViewModal(true);
+ }}
+ >
+
+
+ Delete View
+
+
+ >
+ )}
+
-
- Edit View
-
-
- {
- e.preventDefault();
- e.stopPropagation();
- setDeleteViewModal(true);
- }}
- >
-
-
- Delete View
+
+ Copy view link