chore: peek overview authorization (#2632)

* chore: peek overview authorization

* chore: comment access specifier validation
This commit is contained in:
Aaryan Khandelwal 2023-11-03 19:13:10 +05:30 committed by GitHub
parent d48f13416f
commit 992cf79031
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 270 additions and 195 deletions

View File

@ -1,7 +1,37 @@
import { BoldIcon, Heading1, CheckSquare, Heading2, Heading3, QuoteIcon, ImageIcon, TableIcon, ListIcon, ListOrderedIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon, CodeIcon } from "lucide-react"; import {
BoldIcon,
Heading1,
CheckSquare,
Heading2,
Heading3,
QuoteIcon,
ImageIcon,
TableIcon,
ListIcon,
ListOrderedIcon,
ItalicIcon,
UnderlineIcon,
StrikethroughIcon,
CodeIcon,
} from "lucide-react";
import { Editor } from "@tiptap/react"; import { Editor } from "@tiptap/react";
import { UploadImage } from "../../../types/upload-image"; import { UploadImage } from "../../../types/upload-image";
import { insertImageCommand, insertTableCommand, toggleBlockquote, toggleBold, toggleBulletList, toggleCode, toggleHeadingOne, toggleHeadingThree, toggleHeadingTwo, toggleItalic, toggleOrderedList, toggleStrike, toggleTaskList, toggleUnderline, } from "../../../lib/editor-commands"; import {
insertImageCommand,
insertTableCommand,
toggleBlockquote,
toggleBold,
toggleBulletList,
toggleCode,
toggleHeadingOne,
toggleHeadingThree,
toggleHeadingTwo,
toggleItalic,
toggleOrderedList,
toggleStrike,
toggleTaskList,
toggleUnderline,
} from "../../../lib/editor-commands";
export interface EditorMenuItem { export interface EditorMenuItem {
name: string; name: string;
@ -15,95 +45,101 @@ export const HeadingOneItem = (editor: Editor): EditorMenuItem => ({
isActive: () => editor.isActive("heading", { level: 1 }), isActive: () => editor.isActive("heading", { level: 1 }),
command: () => toggleHeadingOne(editor), command: () => toggleHeadingOne(editor),
icon: Heading1, icon: Heading1,
}) });
export const HeadingTwoItem = (editor: Editor): EditorMenuItem => ({ export const HeadingTwoItem = (editor: Editor): EditorMenuItem => ({
name: "H2", name: "H2",
isActive: () => editor.isActive("heading", { level: 2 }), isActive: () => editor.isActive("heading", { level: 2 }),
command: () => toggleHeadingTwo(editor), command: () => toggleHeadingTwo(editor),
icon: Heading2, icon: Heading2,
}) });
export const HeadingThreeItem = (editor: Editor): EditorMenuItem => ({ export const HeadingThreeItem = (editor: Editor): EditorMenuItem => ({
name: "H3", name: "H3",
isActive: () => editor.isActive("heading", { level: 3 }), isActive: () => editor.isActive("heading", { level: 3 }),
command: () => toggleHeadingThree(editor), command: () => toggleHeadingThree(editor),
icon: Heading3, icon: Heading3,
}) });
export const BoldItem = (editor: Editor): EditorMenuItem => ({ export const BoldItem = (editor: Editor): EditorMenuItem => ({
name: "bold", name: "bold",
isActive: () => editor?.isActive("bold"), isActive: () => editor?.isActive("bold"),
command: () => toggleBold(editor), command: () => toggleBold(editor),
icon: BoldIcon, icon: BoldIcon,
}) });
export const ItalicItem = (editor: Editor): EditorMenuItem => ({ export const ItalicItem = (editor: Editor): EditorMenuItem => ({
name: "italic", name: "italic",
isActive: () => editor?.isActive("italic"), isActive: () => editor?.isActive("italic"),
command: () => toggleItalic(editor), command: () => toggleItalic(editor),
icon: ItalicIcon, icon: ItalicIcon,
}) });
export const UnderLineItem = (editor: Editor): EditorMenuItem => ({ export const UnderLineItem = (editor: Editor): EditorMenuItem => ({
name: "underline", name: "underline",
isActive: () => editor?.isActive("underline"), isActive: () => editor?.isActive("underline"),
command: () => toggleUnderline(editor), command: () => toggleUnderline(editor),
icon: UnderlineIcon, icon: UnderlineIcon,
}) });
export const StrikeThroughItem = (editor: Editor): EditorMenuItem => ({ export const StrikeThroughItem = (editor: Editor): EditorMenuItem => ({
name: "strike", name: "strike",
isActive: () => editor?.isActive("strike"), isActive: () => editor?.isActive("strike"),
command: () => toggleStrike(editor), command: () => toggleStrike(editor),
icon: StrikethroughIcon, icon: StrikethroughIcon,
}) });
export const CodeItem = (editor: Editor): EditorMenuItem => ({ export const CodeItem = (editor: Editor): EditorMenuItem => ({
name: "code", name: "code",
isActive: () => editor?.isActive("code"), isActive: () => editor?.isActive("code"),
command: () => toggleCode(editor), command: () => toggleCode(editor),
icon: CodeIcon, icon: CodeIcon,
}) });
export const BulletListItem = (editor: Editor): EditorMenuItem => ({ export const BulletListItem = (editor: Editor): EditorMenuItem => ({
name: "bullet-list", name: "bullet-list",
isActive: () => editor?.isActive("bulletList"), isActive: () => editor?.isActive("bulletList"),
command: () => toggleBulletList(editor), command: () => toggleBulletList(editor),
icon: ListIcon, icon: ListIcon,
}) });
export const TodoListItem = (editor: Editor): EditorMenuItem => ({ export const TodoListItem = (editor: Editor): EditorMenuItem => ({
name: "To-do List", name: "To-do List",
isActive: () => editor.isActive("taskItem"), isActive: () => editor.isActive("taskItem"),
command: () => toggleTaskList(editor), command: () => toggleTaskList(editor),
icon: CheckSquare, icon: CheckSquare,
}) });
export const NumberedListItem = (editor: Editor): EditorMenuItem => ({ export const NumberedListItem = (editor: Editor): EditorMenuItem => ({
name: "ordered-list", name: "ordered-list",
isActive: () => editor?.isActive("orderedList"), isActive: () => editor?.isActive("orderedList"),
command: () => toggleOrderedList(editor), command: () => toggleOrderedList(editor),
icon: ListOrderedIcon icon: ListOrderedIcon,
}) });
export const QuoteItem = (editor: Editor): EditorMenuItem => ({ export const QuoteItem = (editor: Editor): EditorMenuItem => ({
name: "quote", name: "quote",
isActive: () => editor?.isActive("quote"), isActive: () => editor?.isActive("quote"),
command: () => toggleBlockquote(editor), command: () => toggleBlockquote(editor),
icon: QuoteIcon icon: QuoteIcon,
}) });
export const TableItem = (editor: Editor): EditorMenuItem => ({ export const TableItem = (editor: Editor): EditorMenuItem => ({
name: "quote", name: "table",
isActive: () => editor?.isActive("table"), isActive: () => editor?.isActive("table"),
command: () => insertTableCommand(editor), command: () => insertTableCommand(editor),
icon: TableIcon icon: TableIcon,
}) });
export const ImageItem = (editor: Editor, uploadFile: UploadImage, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void): EditorMenuItem => ({ export const ImageItem = (
editor: Editor,
uploadFile: UploadImage,
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void,
): EditorMenuItem => ({
name: "image", name: "image",
isActive: () => editor?.isActive("image"), isActive: () => editor?.isActive("image"),
command: () => insertImageCommand(editor, uploadFile, setIsSubmitting), command: () => insertImageCommand(editor, uploadFile, setIsSubmitting),
icon: ImageIcon, icon: ImageIcon,
}) });

View File

@ -72,12 +72,10 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
props.commentAccessSpecifier?.onAccessChange(accessKey); props.commentAccessSpecifier?.onAccessChange(accessKey);
}; };
console.log(complexItems);
return ( return (
<div className="flex items-stretch gap-1.5 w-full h-9"> <div className="flex items-stretch gap-1.5 w-full h-9 overflow-x-scroll">
{props.commentAccessSpecifier && ( {props.commentAccessSpecifier && (
<div className="flex-shrink-0 flex items-stretch gap-0.5 border border-custom-border-200 rounded p-1"> <div className="flex-shrink-0 flex items-stretch gap-0.5 border-[0.5px] border-custom-border-200 rounded p-1">
{props?.commentAccessSpecifier.commentAccess?.map((access) => ( {props?.commentAccessSpecifier.commentAccess?.map((access) => (
<Tooltip key={access.key} tooltipContent={access.label}> <Tooltip key={access.key} tooltipContent={access.label}>
<button <button
@ -102,12 +100,15 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
))} ))}
</div> </div>
)} )}
<div className="flex items-stretch justify-between gap-2 w-full border border-custom-border-200 bg-custom-background-90 rounded p-1"> <div className="flex items-stretch justify-between gap-2 w-full border-[0.5px] border-custom-border-200 bg-custom-background-90 rounded p-1">
<div className="flex items-stretch"> <div className="flex items-stretch">
<div className="flex items-stretch gap-0.5 pr-2.5 border-r border-custom-border-200"> <div className="flex items-stretch gap-0.5 pr-2.5 border-r border-custom-border-200">
{basicMarkItems.map((item, index) => ( {basicMarkItems.map((item, index) => (
<button <Tooltip
key={index} key={index}
tooltipContent={<span className="capitalize">{item.name}</span>}
>
<button
type="button" type="button"
onClick={item.command} onClick={item.command}
className={cn( className={cn(
@ -125,12 +126,16 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
strokeWidth={2.5} strokeWidth={2.5}
/> />
</button> </button>
</Tooltip>
))} ))}
</div> </div>
<div className="flex items-stretch gap-0.5 px-2.5 border-r border-custom-border-200"> <div className="flex items-stretch gap-0.5 px-2.5 border-r border-custom-border-200">
{listItems.map((item, index) => ( {listItems.map((item, index) => (
<button <Tooltip
key={index} key={index}
tooltipContent={<span className="capitalize">{item.name}</span>}
>
<button
type="button" type="button"
onClick={item.command} onClick={item.command}
className={cn( className={cn(
@ -148,12 +153,16 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
strokeWidth={2.5} strokeWidth={2.5}
/> />
</button> </button>
</Tooltip>
))} ))}
</div> </div>
<div className="flex items-stretch gap-0.5 px-2.5 border-r border-custom-border-200"> <div className="flex items-stretch gap-0.5 px-2.5 border-r border-custom-border-200">
{userActionItems.map((item, index) => ( {userActionItems.map((item, index) => (
<button <Tooltip
key={index} key={index}
tooltipContent={<span className="capitalize">{item.name}</span>}
>
<button
type="button" type="button"
onClick={item.command} onClick={item.command}
className={cn( className={cn(
@ -171,12 +180,16 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
strokeWidth={2.5} strokeWidth={2.5}
/> />
</button> </button>
</Tooltip>
))} ))}
</div> </div>
<div className="flex items-stretch gap-0.5 pl-2.5"> <div className="flex items-stretch gap-0.5 pl-2.5">
{complexItems.map((item, index) => ( {complexItems.map((item, index) => (
<button <Tooltip
key={index} key={index}
tooltipContent={<span className="capitalize">{item.name}</span>}
>
<button
type="button" type="button"
onClick={item.command} onClick={item.command}
className={cn( className={cn(
@ -194,10 +207,11 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
strokeWidth={2.5} strokeWidth={2.5}
/> />
</button> </button>
</Tooltip>
))} ))}
</div> </div>
</div> </div>
{props.submitButton} <div className="sticky right-1">{props.submitButton}</div>
</div> </div>
</div> </div>
); );

View File

@ -52,7 +52,7 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAc
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const editorSuggestions = useEditorSuggestions(workspaceSlug as string | undefined, projectId as string | undefined) const editorSuggestions = useEditorSuggestions(workspaceSlug as string | undefined, projectId as string | undefined);
const { const {
control, control,
@ -74,7 +74,6 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAc
<div> <div>
<form onSubmit={handleSubmit(handleAddComment)}> <form onSubmit={handleSubmit(handleAddComment)}>
<div> <div>
<div className="relative">
<Controller <Controller
name="access" name="access"
control={control} control={control}
@ -92,7 +91,11 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAc
customClassName="p-3 min-h-[100px] shadow-sm" customClassName="p-3 min-h-[100px] shadow-sm"
debouncedUpdatesEnabled={false} debouncedUpdatesEnabled={false}
onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)} onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)}
commentAccessSpecifier={{ accessValue, onAccessChange, showAccessSpecifier, commentAccess }} commentAccessSpecifier={
showAccessSpecifier
? { accessValue, onAccessChange, showAccessSpecifier, commentAccess }
: undefined
}
mentionSuggestions={editorSuggestions.mentionSuggestions} mentionSuggestions={editorSuggestions.mentionSuggestions}
mentionHighlights={editorSuggestions.mentionHighlights} mentionHighlights={editorSuggestions.mentionHighlights}
/> />
@ -100,7 +103,6 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAc
/> />
)} )}
/> />
</div>
<Button variant="neutral-primary" type="submit" disabled={isSubmitting || disabled}> <Button variant="neutral-primary" type="submit" disabled={isSubmitting || disabled}>
{isSubmitting ? "Adding..." : "Comment"} {isSubmitting ? "Adding..." : "Comment"}

View File

@ -96,6 +96,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
hideDropdownArrow hideDropdownArrow
onChange={handleAssignee} onChange={handleAssignee}
disabled={false} disabled={false}
multiple
/> />
)} )}

View File

@ -1,18 +1,17 @@
import React from "react"; import React from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
import { Globe2, Lock } from "lucide-react";
// services // services
import { FileService } from "services/file.service"; import { FileService } from "services/file.service";
// hooks
import useEditorSuggestions from "hooks/use-editor-suggestions";
// components // components
import { LiteTextEditorWithRef } from "@plane/lite-text-editor"; import { LiteTextEditorWithRef } from "@plane/lite-text-editor";
// ui // ui
import { Button, Tooltip } from "@plane/ui"; import { Button } from "@plane/ui";
import { Globe2, Lock } from "lucide-react";
// types // types
import type { IIssueComment } from "types"; import type { IIssueComment } from "types";
import useEditorSuggestions from "hooks/use-editor-suggestions";
const defaultValues: Partial<IIssueComment> = { const defaultValues: Partial<IIssueComment> = {
access: "INTERNAL", access: "INTERNAL",
@ -75,36 +74,7 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
return ( return (
<form onSubmit={handleSubmit(handleAddComment)}> <form onSubmit={handleSubmit(handleAddComment)}>
<div className="space-y-2"> <div className="space-y-2">
<div className="relative h-full"> <div className="h-full">
{showAccessSpecifier && (
<div className="absolute bottom-2 left-3 z-[1]">
<Controller
control={control}
name="access"
render={({ field: { onChange, value } }) => (
<div className="flex border border-custom-border-300 divide-x divide-custom-border-300 rounded overflow-hidden">
{commentAccess.map((access) => (
<Tooltip key={access.key} tooltipContent={access.label}>
<button
type="button"
onClick={() => onChange(access.key)}
className={`grid place-items-center p-1 hover:bg-custom-background-80 ${
value === access.key ? "bg-custom-background-80" : ""
}`}
>
<access.icon
className={`w-4 h-4 -mt-1 ${
value === access.key ? "!text-custom-text-100" : "!text-custom-text-400"
}`}
/>
</button>
</Tooltip>
))}
</div>
)}
/>
</div>
)}
<Controller <Controller
name="access" name="access"
control={control} control={control}
@ -124,7 +94,11 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
mentionSuggestions={editorSuggestions.mentionSuggestions} mentionSuggestions={editorSuggestions.mentionSuggestions}
mentionHighlights={editorSuggestions.mentionHighlights} mentionHighlights={editorSuggestions.mentionHighlights}
onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)} onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)}
commentAccessSpecifier={{ accessValue, onAccessChange, showAccessSpecifier, commentAccess }} commentAccessSpecifier={
showAccessSpecifier
? { accessValue, onAccessChange, showAccessSpecifier, commentAccess }
: undefined
}
submitButton={ submitButton={
<Button <Button
variant="primary" variant="primary"

View File

@ -14,6 +14,7 @@ interface IIssueComment {
issueCommentRemove: (commentId: string) => void; issueCommentRemove: (commentId: string) => void;
issueCommentReactionCreate: (commentId: string, reaction: string) => void; issueCommentReactionCreate: (commentId: string, reaction: string) => void;
issueCommentReactionRemove: (commentId: string, reaction: string) => void; issueCommentReactionRemove: (commentId: string, reaction: string) => void;
showCommentAccessSpecifier: boolean;
} }
export const IssueComment: FC<IIssueComment> = (props) => { export const IssueComment: FC<IIssueComment> = (props) => {
@ -28,6 +29,7 @@ export const IssueComment: FC<IIssueComment> = (props) => {
issueCommentRemove, issueCommentRemove,
issueCommentReactionCreate, issueCommentReactionCreate,
issueCommentReactionRemove, issueCommentReactionRemove,
showCommentAccessSpecifier,
} = props; } = props;
const handleAddComment = async (formData: any) => { const handleAddComment = async (formData: any) => {
@ -40,10 +42,7 @@ export const IssueComment: FC<IIssueComment> = (props) => {
<div className="font-medium text-lg">Activity</div> <div className="font-medium text-lg">Activity</div>
<div className="space-y-2"> <div className="space-y-2">
<IssueCommentEditor <IssueCommentEditor onSubmit={handleAddComment} showAccessSpecifier={showCommentAccessSpecifier} />
onSubmit={handleAddComment}
// showAccessSpecifier={projectDetails && projectDetails.is_deployed}
/>
<IssueActivityCard <IssueActivityCard
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}

View File

@ -32,13 +32,14 @@ import { IssueService } from "services/issue";
interface IPeekOverviewProperties { interface IPeekOverviewProperties {
issue: IIssue; issue: IIssue;
issueUpdate: (issue: Partial<IIssue>) => void; issueUpdate: (issue: Partial<IIssue>) => void;
user: any; disableUserActions: boolean;
} }
const issueService = new IssueService(); const issueService = new IssueService();
export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((props) => { export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((props) => {
const { issue, issueUpdate, user } = props; const { issue, issueUpdate, disableUserActions } = props;
// states
const [linkModal, setLinkModal] = useState(false); const [linkModal, setLinkModal] = useState(false);
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(null); const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(null);
@ -172,8 +173,6 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
const maxDate = issue.target_date ? new Date(issue.target_date) : null; const maxDate = issue.target_date ? new Date(issue.target_date) : null;
maxDate?.setDate(maxDate.getDate()); maxDate?.setDate(maxDate.getDate());
const isNotAllowed = user?.memberRole?.isGuest || user?.memberRole?.isViewer;
return ( return (
<> <>
<LinkModal <LinkModal
@ -196,7 +195,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<p>State</p> <p>State</p>
</div> </div>
<div> <div>
<SidebarStateSelect value={issue?.state || ""} onChange={handleState} disabled={isNotAllowed} /> <SidebarStateSelect value={issue?.state || ""} onChange={handleState} disabled={disableUserActions} />
</div> </div>
</div> </div>
@ -207,7 +206,11 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<p>Assignees</p> <p>Assignees</p>
</div> </div>
<div> <div>
<SidebarAssigneeSelect value={issue.assignees || []} onChange={handleAssignee} disabled={isNotAllowed} /> <SidebarAssigneeSelect
value={issue.assignees || []}
onChange={handleAssignee}
disabled={disableUserActions}
/>
</div> </div>
</div> </div>
@ -218,7 +221,11 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<p>Priority</p> <p>Priority</p>
</div> </div>
<div> <div>
<SidebarPrioritySelect value={issue.priority || ""} onChange={handlePriority} disabled={isNotAllowed} /> <SidebarPrioritySelect
value={issue.priority || ""}
onChange={handlePriority}
disabled={disableUserActions}
/>
</div> </div>
</div> </div>
@ -229,7 +236,11 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<p>Estimate</p> <p>Estimate</p>
</div> </div>
<div> <div>
<SidebarEstimateSelect value={issue.estimate_point} onChange={handleEstimate} disabled={isNotAllowed} /> <SidebarEstimateSelect
value={issue.estimate_point}
onChange={handleEstimate}
disabled={disableUserActions}
/>
</div> </div>
</div> </div>
@ -246,7 +257,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
onChange={handleStartDate} onChange={handleStartDate}
className="bg-custom-background-80 border-none !px-2.5 !py-0.5" className="bg-custom-background-80 border-none !px-2.5 !py-0.5"
maxDate={maxDate ?? undefined} maxDate={maxDate ?? undefined}
disabled={isNotAllowed} disabled={disableUserActions}
/> />
</div> </div>
</div> </div>
@ -264,7 +275,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
onChange={handleTargetDate} onChange={handleTargetDate}
className="bg-custom-background-80 border-none !px-2.5 !py-0.5" className="bg-custom-background-80 border-none !px-2.5 !py-0.5"
minDate={minDate ?? undefined} minDate={minDate ?? undefined}
disabled={isNotAllowed} disabled={disableUserActions}
/> />
</div> </div>
</div> </div>
@ -276,7 +287,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<p>Parent</p> <p>Parent</p>
</div> </div>
<div> <div>
<SidebarParentSelect onChange={handleParent} issueDetails={issue} disabled={isNotAllowed} /> <SidebarParentSelect onChange={handleParent} issueDetails={issue} disabled={disableUserActions} />
</div> </div>
</div> </div>
</div> </div>
@ -290,7 +301,11 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<p>Cycle</p> <p>Cycle</p>
</div> </div>
<div> <div>
<SidebarCycleSelect issueDetail={issue} handleCycleChange={addIssueToCycle} disabled={isNotAllowed} /> <SidebarCycleSelect
issueDetail={issue}
handleCycleChange={addIssueToCycle}
disabled={disableUserActions}
/>
</div> </div>
</div> </div>
@ -300,7 +315,11 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<p>Module</p> <p>Module</p>
</div> </div>
<div> <div>
<SidebarModuleSelect issueDetail={issue} handleModuleChange={addIssueToModule} disabled={isNotAllowed} /> <SidebarModuleSelect
issueDetail={issue}
handleModuleChange={addIssueToModule}
disabled={disableUserActions}
/>
</div> </div>
</div> </div>
<div className="flex items-start gap-2 w-full"> <div className="flex items-start gap-2 w-full">
@ -313,8 +332,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
issueDetails={issue} issueDetails={issue}
labelList={issue.labels} labelList={issue.labels}
submitChanges={handleLabels} submitChanges={handleLabels}
isNotAllowed={isNotAllowed} isNotAllowed={disableUserActions}
uneditable={isNotAllowed} uneditable={disableUserActions}
/> />
</div> </div>
</div> </div>
@ -330,11 +349,11 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<p>Links</p> <p>Links</p>
</div> </div>
<div> <div>
{!isNotAllowed && ( {!disableUserActions && (
<button <button
type="button" type="button"
className={`flex ${ className={`flex ${
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-90" disableUserActions ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-90"
} items-center gap-1 rounded-2xl border border-custom-border-100 px-2 py-0.5 text-xs hover:text-custom-text-200 text-custom-text-300`} } items-center gap-1 rounded-2xl border border-custom-border-100 px-2 py-0.5 text-xs hover:text-custom-text-200 text-custom-text-300`}
onClick={() => setLinkModal(true)} onClick={() => setLinkModal(true)}
disabled={false} disabled={false}

View File

@ -6,7 +6,6 @@ import { IssueView } from "./view";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
import { RootStore } from "store/root";
interface IIssuePeekOverview { interface IIssuePeekOverview {
workspaceSlug: string; workspaceSlug: string;
@ -19,7 +18,7 @@ interface IIssuePeekOverview {
export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => { export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
const { workspaceSlug, projectId, issueId, handleIssue, children } = props; const { workspaceSlug, projectId, issueId, handleIssue, children } = props;
const { issueDetail: issueDetailStore }: RootStore = useMobxStore(); const { issueDetail: issueDetailStore, project: projectStore, user: userStore } = useMobxStore();
const issueUpdate = (_data: Partial<IIssue>) => { const issueUpdate = (_data: Partial<IIssue>) => {
handleIssue(_data); handleIssue(_data);
@ -52,6 +51,8 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
const handleDeleteIssue = () => issueDetailStore.deleteIssue(workspaceSlug, projectId, issueId); const handleDeleteIssue = () => issueDetailStore.deleteIssue(workspaceSlug, projectId, issueId);
const userRole = userStore.currentProjectRole ?? 5;
return ( return (
<IssueView <IssueView
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
@ -68,6 +69,8 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
issueSubscriptionCreate={issueSubscriptionCreate} issueSubscriptionCreate={issueSubscriptionCreate}
issueSubscriptionRemove={issueSubscriptionRemove} issueSubscriptionRemove={issueSubscriptionRemove}
handleDeleteIssue={handleDeleteIssue} handleDeleteIssue={handleDeleteIssue}
disableUserActions={[5, 10].includes(userRole)}
showCommentAccessSpecifier={projectStore.currentProjectDetails?.is_deployed}
> >
{children} {children}
</IssueView> </IssueView>

View File

@ -1,8 +1,10 @@
import { FC, ReactNode, useState } from "react"; import { FC, ReactNode, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { MoveRight, MoveDiagonal, Bell, Link2, Trash2 } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import useSWR from "swr"; import useSWR from "swr";
import { MoveRight, MoveDiagonal, Bell, Link2, Trash2 } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { PeekOverviewIssueDetails } from "./issue-detail"; import { PeekOverviewIssueDetails } from "./issue-detail";
import { PeekOverviewProperties } from "./properties"; import { PeekOverviewProperties } from "./properties";
@ -13,7 +15,6 @@ import { DeleteIssueModal } from "../delete-issue-modal";
import { IIssue } from "types"; import { IIssue } from "types";
import { RootStore } from "store/root"; import { RootStore } from "store/root";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// helpers // helpers
import { copyUrlToClipboard } from "helpers/string.helper"; import { copyUrlToClipboard } from "helpers/string.helper";
@ -34,6 +35,8 @@ interface IIssueView {
issueSubscriptionRemove: () => void; issueSubscriptionRemove: () => void;
handleDeleteIssue: () => Promise<void>; handleDeleteIssue: () => Promise<void>;
children: ReactNode; children: ReactNode;
disableUserActions?: boolean;
showCommentAccessSpecifier?: boolean;
} }
type TPeekModes = "side-peek" | "modal" | "full-screen"; type TPeekModes = "side-peek" | "modal" | "full-screen";
@ -73,6 +76,8 @@ export const IssueView: FC<IIssueView> = observer((props) => {
issueSubscriptionRemove, issueSubscriptionRemove,
handleDeleteIssue, handleDeleteIssue,
children, children,
disableUserActions = false,
showCommentAccessSpecifier = false,
} = props; } = props;
const router = useRouter(); const router = useRouter();
@ -239,9 +244,11 @@ export const IssueView: FC<IIssueView> = observer((props) => {
<button onClick={handleCopyText}> <button onClick={handleCopyText}>
<Link2 className="h-4 w-4 text-custom-text-400 hover:text-custom-text-200 -rotate-45" /> <Link2 className="h-4 w-4 text-custom-text-400 hover:text-custom-text-200 -rotate-45" />
</button> </button>
{!disableUserActions && (
<button onClick={() => setDeleteIssueModal(true)}> <button onClick={() => setDeleteIssueModal(true)}>
<Trash2 className="h-4 w-4 text-custom-text-400 hover:text-custom-text-200" /> <Trash2 className="h-4 w-4 text-custom-text-400 hover:text-custom-text-200" />
</button> </button>
)}
</div> </div>
</div> </div>
@ -264,7 +271,11 @@ export const IssueView: FC<IIssueView> = observer((props) => {
issueReactionRemove={issueReactionRemove} issueReactionRemove={issueReactionRemove}
/> />
<PeekOverviewProperties issue={issue} issueUpdate={issueUpdate} user={user} /> <PeekOverviewProperties
issue={issue}
issueUpdate={issueUpdate}
disableUserActions={disableUserActions}
/>
<IssueComment <IssueComment
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
@ -277,6 +288,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
issueCommentRemove={issueCommentRemove} issueCommentRemove={issueCommentRemove}
issueCommentReactionCreate={issueCommentReactionCreate} issueCommentReactionCreate={issueCommentReactionCreate}
issueCommentReactionRemove={issueCommentReactionRemove} issueCommentReactionRemove={issueCommentReactionRemove}
showCommentAccessSpecifier={showCommentAccessSpecifier}
/> />
</div> </div>
) : ( ) : (
@ -305,10 +317,15 @@ export const IssueView: FC<IIssueView> = observer((props) => {
issueCommentRemove={issueCommentRemove} issueCommentRemove={issueCommentRemove}
issueCommentReactionCreate={issueCommentReactionCreate} issueCommentReactionCreate={issueCommentReactionCreate}
issueCommentReactionRemove={issueCommentReactionRemove} issueCommentReactionRemove={issueCommentReactionRemove}
showCommentAccessSpecifier={showCommentAccessSpecifier}
/> />
</div> </div>
<div className="flex-shrink-0 !w-[400px] h-full border-l border-custom-border-200 p-4 py-5"> <div className="flex-shrink-0 !w-[400px] h-full border-l border-custom-border-200 p-4 py-5">
<PeekOverviewProperties issue={issue} issueUpdate={issueUpdate} user={user} /> <PeekOverviewProperties
issue={issue}
issueUpdate={issueUpdate}
disableUserActions={disableUserActions}
/>
</div> </div>
</div> </div>
)} )}

View File

@ -1,4 +1,5 @@
import { useState, Fragment } from "react"; import { useState, Fragment } from "react";
import { useRouter } from "next/router";
import { Transition, Dialog } from "@headlessui/react"; import { Transition, Dialog } from "@headlessui/react";
// ui // ui
import { Button } from "@plane/ui"; import { Button } from "@plane/ui";
@ -17,10 +18,12 @@ type TJoinProjectModalProps = {
export const JoinProjectModal: React.FC<TJoinProjectModalProps> = (props) => { export const JoinProjectModal: React.FC<TJoinProjectModalProps> = (props) => {
const { handleClose, isOpen, project, workspaceSlug } = props; const { handleClose, isOpen, project, workspaceSlug } = props;
// store
const { project: projectStore } = useMobxStore();
// states // states
const [isJoiningLoading, setIsJoiningLoading] = useState(false); const [isJoiningLoading, setIsJoiningLoading] = useState(false);
// store
const { project: projectStore } = useMobxStore();
// router
const router = useRouter();
const handleJoin = () => { const handleJoin = () => {
setIsJoiningLoading(true); setIsJoiningLoading(true);
@ -29,6 +32,8 @@ export const JoinProjectModal: React.FC<TJoinProjectModalProps> = (props) => {
.joinProject(workspaceSlug, [project.id]) .joinProject(workspaceSlug, [project.id])
.then(() => { .then(() => {
setIsJoiningLoading(false); setIsJoiningLoading(false);
router.push(`/${workspaceSlug}/projects/${project.id}/issues`);
handleClose(); handleClose();
}) })
.catch(() => { .catch(() => {

View File

@ -17,11 +17,17 @@ export const UserAuthWrapper: FC<IUserAuthWrapper> = (props) => {
// router // router
const router = useRouter(); const router = useRouter();
// fetching user information // fetching user information
const { data: currentUser, error } = useSWR("CURRENT_USER_DETAILS", () => userStore.fetchCurrentUser()); const { data: currentUser, error } = useSWR("CURRENT_USER_DETAILS", () => userStore.fetchCurrentUser(), {
shouldRetryOnError: false,
});
// fetching user settings // fetching user settings
useSWR("CURRENT_USER_SETTINGS", () => userStore.fetchCurrentUserSettings()); useSWR("CURRENT_USER_SETTINGS", () => userStore.fetchCurrentUserSettings(), {
shouldRetryOnError: false,
});
// fetching all workspaces // fetching all workspaces
useSWR(`USER_WORKSPACES_LIST`, () => workspaceStore.fetchWorkspaces()); useSWR(`USER_WORKSPACES_LIST`, () => workspaceStore.fetchWorkspaces(), {
shouldRetryOnError: false,
});
if (!currentUser && !error) { if (!currentUser && !error) {
return ( return (

View File

@ -43,7 +43,6 @@ export interface IProject {
page_view: boolean; page_view: boolean;
project_lead: IUserLite | string | null; project_lead: IUserLite | string | null;
sort_order: number | null; sort_order: number | null;
slug: string;
total_cycles: number; total_cycles: number;
total_members: number; total_members: number;
total_modules: number; total_modules: number;