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:
Lakhan Baheti 2023-12-17 00:38:05 +05:30 committed by GitHub
parent 31fdaf2659
commit e1793dda74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 67 additions and 36 deletions

View File

@ -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)}

View File

@ -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>
))} ))}
</> </>

View File

@ -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);

View File

@ -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"

View File

@ -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}

View File

@ -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>

View File

@ -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);