mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: role restriction to issue detail & peek-overview (#3146)
* chore: disabled interaction with title & description in peekview & issue detail * chore: restriction to attachments delete for lower roles * chore: kanban & calendar block drag restriction for lower roles * fix: module/issue sidebar links access for lower roles * fix: issue detail role validation * fix: user role validation condition
This commit is contained in:
parent
31fdaf2659
commit
e1793dda74
@ -50,8 +50,8 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, handleEdit
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isNotAllowed && (
|
|
||||||
<div className="z-[1] flex flex-shrink-0 items-center gap-2">
|
<div className="z-[1] flex flex-shrink-0 items-center gap-2">
|
||||||
|
{!isNotAllowed && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center justify-center p-1 hover:bg-custom-background-80"
|
className="flex items-center justify-center p-1 hover:bg-custom-background-80"
|
||||||
@ -63,6 +63,7 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, handleEdit
|
|||||||
>
|
>
|
||||||
<Pencil className="h-3 w-3 stroke-[1.5] text-custom-text-200" />
|
<Pencil className="h-3 w-3 stroke-[1.5] text-custom-text-200" />
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
<a
|
<a
|
||||||
href={link.url}
|
href={link.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -71,6 +72,7 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, handleEdit
|
|||||||
>
|
>
|
||||||
<ExternalLinkIcon className="h-3 w-3 stroke-[1.5] text-custom-text-200" />
|
<ExternalLinkIcon className="h-3 w-3 stroke-[1.5] text-custom-text-200" />
|
||||||
</a>
|
</a>
|
||||||
|
{!isNotAllowed && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center justify-center p-1 hover:bg-custom-background-80"
|
className="flex items-center justify-center p-1 hover:bg-custom-background-80"
|
||||||
@ -82,9 +84,9 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, handleEdit
|
|||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className="px-5">
|
<div className="px-5">
|
||||||
<p className="mt-0.5 stroke-[1.5] text-xs text-custom-text-300">
|
<p className="mt-0.5 stroke-[1.5] text-xs text-custom-text-300">
|
||||||
Added {timeAgo(link.created_at)}
|
Added {timeAgo(link.created_at)}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
@ -24,7 +24,14 @@ import { IIssueAttachment } from "types";
|
|||||||
const issueAttachmentService = new IssueAttachmentService();
|
const issueAttachmentService = new IssueAttachmentService();
|
||||||
const projectMemberService = new ProjectMemberService();
|
const projectMemberService = new ProjectMemberService();
|
||||||
|
|
||||||
export const IssueAttachments = () => {
|
type Props = {
|
||||||
|
editable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IssueAttachments: React.FC<Props> = (props) => {
|
||||||
|
const { editable } = props;
|
||||||
|
|
||||||
|
// states
|
||||||
const [deleteAttachment, setDeleteAttachment] = useState<IIssueAttachment | null>(null);
|
const [deleteAttachment, setDeleteAttachment] = useState<IIssueAttachment | null>(null);
|
||||||
const [attachmentDeleteModal, setAttachmentDeleteModal] = useState<boolean>(false);
|
const [attachmentDeleteModal, setAttachmentDeleteModal] = useState<boolean>(false);
|
||||||
|
|
||||||
@ -86,6 +93,7 @@ export const IssueAttachments = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
{editable && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDeleteAttachment(file);
|
setDeleteAttachment(file);
|
||||||
@ -94,6 +102,7 @@ export const IssueAttachments = () => {
|
|||||||
>
|
>
|
||||||
<X className="h-4 w-4 text-custom-text-200 hover:text-custom-text-100" />
|
<X className="h-4 w-4 text-custom-text-200 hover:text-custom-text-100" />
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -135,7 +135,9 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
|||||||
debouncedFormSave();
|
debouncedFormSave();
|
||||||
}}
|
}}
|
||||||
required
|
required
|
||||||
className="min-h-min block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-2 text-2xl font-medium outline-none ring-0 focus:ring-1 focus:ring-custom-primary"
|
className={`min-h-min block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-2 text-2xl font-medium outline-none ring-0 focus:ring-1 focus:ring-custom-primary ${
|
||||||
|
!isAllowed ? "hover:cursor-not-allowed" : ""
|
||||||
|
}`}
|
||||||
hasError={Boolean(errors?.description)}
|
hasError={Boolean(errors?.description)}
|
||||||
role="textbox"
|
role="textbox"
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
@ -170,7 +172,9 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
|||||||
setShouldShowAlert={setShowAlert}
|
setShouldShowAlert={setShowAlert}
|
||||||
setIsSubmitting={setIsSubmitting}
|
setIsSubmitting={setIsSubmitting}
|
||||||
dragDropEnabled
|
dragDropEnabled
|
||||||
customClassName={isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200"}
|
customClassName={
|
||||||
|
isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200 pointer-events-none"
|
||||||
|
}
|
||||||
noBorder={!isAllowed}
|
noBorder={!isAllowed}
|
||||||
onChange={(description: Object, description_html: string) => {
|
onChange={(description: Object, description_html: string) => {
|
||||||
setShowAlert(true);
|
setShowAlert(true);
|
||||||
|
@ -10,6 +10,9 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
|||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
import { IIssueResponse } from "store/issues/types";
|
import { IIssueResponse } from "store/issues/types";
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// constants
|
||||||
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issues: IIssueResponse | undefined;
|
issues: IIssueResponse | undefined;
|
||||||
@ -26,6 +29,11 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
|||||||
// states
|
// states
|
||||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||||
|
|
||||||
|
// mobx store
|
||||||
|
const {
|
||||||
|
user: { currentProjectRole },
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
const menuActionRef = useRef<HTMLDivElement | null>(null);
|
const menuActionRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const handleIssuePeekOverview = (issue: IIssue) => {
|
const handleIssuePeekOverview = (issue: IIssue) => {
|
||||||
@ -51,6 +59,8 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isEditable = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{issueIdList?.slice(0, showAllIssues ? issueIdList.length : 4).map((issueId, index) => {
|
{issueIdList?.slice(0, showAllIssues ? issueIdList.length : 4).map((issueId, index) => {
|
||||||
@ -58,7 +68,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const issue = issues?.[issueId];
|
const issue = issues?.[issueId];
|
||||||
return (
|
return (
|
||||||
<Draggable key={issue.id} draggableId={issue.id} index={index}>
|
<Draggable key={issue.id} draggableId={issue.id} index={index} isDragDisabled={!isEditable}>
|
||||||
{(provided, snapshot) => (
|
{(provided, snapshot) => (
|
||||||
<div
|
<div
|
||||||
className="relative cursor-pointer p-1 px-2"
|
className="relative cursor-pointer p-1 px-2"
|
||||||
|
@ -121,10 +121,10 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Draggable draggableId={draggableId} index={index}>
|
<Draggable draggableId={draggableId} index={index} isDragDisabled={!canEditIssueProperties}>
|
||||||
{(provided, snapshot) => (
|
{(provided, snapshot) => (
|
||||||
<div
|
<div
|
||||||
className="group/kanban-block relative p-1.5 hover:cursor-default"
|
className="group/kanban-block relative p-1.5"
|
||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
{...provided.dragHandleProps}
|
{...provided.dragHandleProps}
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
|
@ -41,7 +41,7 @@ const issueService = new IssueService();
|
|||||||
const issueCommentService = new IssueCommentService();
|
const issueCommentService = new IssueCommentService();
|
||||||
|
|
||||||
export const IssueMainContent: React.FC<Props> = observer((props) => {
|
export const IssueMainContent: React.FC<Props> = observer((props) => {
|
||||||
const { issueDetails, submitChanges, uneditable = false } = props;
|
const { issueDetails, submitChanges, uneditable } = props;
|
||||||
// states
|
// states
|
||||||
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
|
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
|
||||||
// router
|
// router
|
||||||
@ -152,7 +152,9 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
const isAllowed =
|
||||||
|
(!!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER) ||
|
||||||
|
(uneditable !== undefined && !uneditable);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -232,7 +234,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
|
|||||||
workspaceSlug={workspaceSlug as string}
|
workspaceSlug={workspaceSlug as string}
|
||||||
issue={issueDetails}
|
issue={issueDetails}
|
||||||
handleFormSubmit={submitChanges}
|
handleFormSubmit={submitChanges}
|
||||||
isAllowed={isAllowed || !uneditable}
|
isAllowed={isAllowed}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{workspaceSlug && projectId && (
|
{workspaceSlug && projectId && (
|
||||||
@ -250,8 +252,8 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
|
|||||||
<div className="flex flex-col gap-3 py-3">
|
<div className="flex flex-col gap-3 py-3">
|
||||||
<h3 className="text-lg">Attachments</h3>
|
<h3 className="text-lg">Attachments</h3>
|
||||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||||
<IssueAttachmentUpload disabled={uneditable} />
|
<IssueAttachmentUpload disabled={!isAllowed} />
|
||||||
<IssueAttachments />
|
<IssueAttachments editable={isAllowed} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-5 pt-3">
|
<div className="space-y-5 pt-3">
|
||||||
@ -264,7 +266,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
|
|||||||
/>
|
/>
|
||||||
<AddComment
|
<AddComment
|
||||||
onSubmit={handleAddComment}
|
onSubmit={handleAddComment}
|
||||||
disabled={uneditable}
|
disabled={!isAllowed}
|
||||||
showAccessSpecifier={projectDetails && projectDetails.is_deployed}
|
showAccessSpecifier={projectDetails && projectDetails.is_deployed}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -153,10 +153,12 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
|
|||||||
debouncedFormSave();
|
debouncedFormSave();
|
||||||
}}
|
}}
|
||||||
required={true}
|
required={true}
|
||||||
className="min-h-10 block w-full resize-none overflow-hidden rounded border-none bg-transparent !p-0 text-xl outline-none ring-0 focus:!px-3 focus:!py-2 focus:ring-1 focus:ring-custom-primary"
|
className={`min-h-10 block w-full resize-none overflow-hidden rounded border-none bg-transparent !p-0 text-xl outline-none ring-0 focus:!px-3 focus:!py-2 focus:ring-1 focus:ring-custom-primary ${
|
||||||
|
!isAllowed ? "hover:cursor-not-allowed" : ""
|
||||||
|
}`}
|
||||||
hasError={Boolean(errors?.description)}
|
hasError={Boolean(errors?.description)}
|
||||||
role="textbox"
|
role="textbox"
|
||||||
disabled={!true}
|
disabled={!isAllowed}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -188,7 +190,9 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
|
|||||||
setShouldShowAlert={setShowAlert}
|
setShouldShowAlert={setShowAlert}
|
||||||
setIsSubmitting={setIsSubmitting}
|
setIsSubmitting={setIsSubmitting}
|
||||||
dragDropEnabled
|
dragDropEnabled
|
||||||
customClassName={isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200"}
|
customClassName={
|
||||||
|
isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200 pointer-events-none"
|
||||||
|
}
|
||||||
noBorder={!isAllowed}
|
noBorder={!isAllowed}
|
||||||
onChange={(description: Object, description_html: string) => {
|
onChange={(description: Object, description_html: string) => {
|
||||||
setShowAlert(true);
|
setShowAlert(true);
|
||||||
|
Loading…
Reference in New Issue
Block a user