From 2796754e5fc476213b50239cc4275736bb744516 Mon Sep 17 00:00:00 2001
From: rahulramesha <71900764+rahulramesha@users.noreply.github.com>
Date: Wed, 29 Nov 2023 19:58:27 +0530
Subject: [PATCH] fix: v3 issues for the layouts (#2941)

* fix drag n drop exception error

* fix peek overlay close buttons

* fix project empty state view

* fix cycle and module empty state view

* add ai options to inbox issue creation

* fix inbox filters for viewers

* fix inbox filters for viewers for project

* disable editing permission for members and viewers

* define accurate types for drag and drop
---
 .../inbox/modals/create-issue-modal.tsx       | 144 +++++++++++--
 .../issue-layouts/kanban/base-kanban-root.tsx |  18 +-
 .../issues/issue-layouts/kanban/block.tsx     |   6 +-
 .../issue-layouts/kanban/roots/cycle-root.tsx |   5 +-
 .../kanban/roots/module-root.tsx              |  27 +--
 .../kanban/roots/profile-issues-root.tsx      |   2 +-
 .../kanban/roots/project-root.tsx             |   5 +-
 .../kanban/roots/project-view-root.tsx        |   5 +-
 .../issue-layouts/list/base-list-root.tsx     |   6 +-
 .../list/roots/profile-issues-root.tsx        |   2 +-
 .../issue-layouts/roots/cycle-layout-root.tsx |   2 +-
 .../roots/module-layout-root.tsx              |   2 +-
 .../roots/project-layout-root.tsx             |   2 +-
 .../spreadsheet/base-spreadsheet-root.tsx     |   5 +-
 web/store/inbox/inbox_filters.store.ts        |   5 +-
 .../issues/base-issue-kanban-helper.store.ts  | 194 ++++++++++--------
 web/store/issues/profile/issue.store.ts       |   7 +-
 17 files changed, 290 insertions(+), 147 deletions(-)

diff --git a/web/components/inbox/modals/create-issue-modal.tsx b/web/components/inbox/modals/create-issue-modal.tsx
index 3a0746da6..0ab6b3d85 100644
--- a/web/components/inbox/modals/create-issue-modal.tsx
+++ b/web/components/inbox/modals/create-issue-modal.tsx
@@ -16,6 +16,10 @@ import { Button, Input, ToggleSwitch } from "@plane/ui";
 // types
 import { IIssue } from "types";
 import useEditorSuggestions from "hooks/use-editor-suggestions";
+import { GptAssistantModal } from "components/core";
+import { Sparkle } from "lucide-react";
+import useToast from "hooks/use-toast";
+import { AIService } from "services/ai.service";
 
 type Props = {
   isOpen: boolean;
@@ -31,6 +35,7 @@ const defaultValues: Partial<IIssue> = {
 };
 
 // services
+const aiService = new AIService();
 const fileService = new FileService();
 
 export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
@@ -38,21 +43,35 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
 
   // states
   const [createMore, setCreateMore] = useState(false);
+  const [gptAssistantModal, setGptAssistantModal] = useState(false);
+  const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
 
   const editorRef = useRef<any>(null);
 
+  const { setToastAlert } = useToast();
   const editorSuggestion = useEditorSuggestions();
 
   const router = useRouter();
-  const { workspaceSlug, projectId, inboxId } = router.query;
+  const { workspaceSlug, projectId, inboxId } = router.query as {
+    workspaceSlug: string;
+    projectId: string;
+    inboxId: string;
+  };
 
-  const { inboxIssueDetails: inboxIssueDetailsStore, trackEvent: { postHogEventTracker } } = useMobxStore();
+  const {
+    inboxIssueDetails: inboxIssueDetailsStore,
+    trackEvent: { postHogEventTracker },
+    appConfig: { envConfig },
+  } = useMobxStore();
 
   const {
     control,
     formState: { errors, isSubmitting },
     handleSubmit,
     reset,
+    watch,
+    getValues,
+    setValue,
   } = useForm({ defaultValues });
 
   const handleClose = () => {
@@ -60,6 +79,8 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
     reset(defaultValues);
   };
 
+  const issueName = watch("name");
+
   const handleFormSubmit = async (formData: Partial<IIssue>) => {
     if (!workspaceSlug || !projectId || !inboxId) return;
 
@@ -70,24 +91,66 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
           router.push(`/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}?inboxIssueId=${res.issue_inbox[0].id}`);
           handleClose();
         } else reset(defaultValues);
-        postHogEventTracker(
-          "ISSUE_CREATE",
-          {
-            ...res,
-            state: "SUCCESS"
-          }
-        );
-      }).catch((error) => {
+        postHogEventTracker("ISSUE_CREATE", {
+          ...res,
+          state: "SUCCESS",
+        });
+      })
+      .catch((error) => {
         console.log(error);
-        postHogEventTracker(
-          "ISSUE_CREATE",
-          {
-            state: "FAILED"
-          }
-        );
+        postHogEventTracker("ISSUE_CREATE", {
+          state: "FAILED",
+        });
       });
   };
 
+  const handleAiAssistance = async (response: string) => {
+    if (!workspaceSlug || !projectId) return;
+
+    setValue("description", {});
+    setValue("description_html", `${watch("description_html")}<p>${response}</p>`);
+    editorRef.current?.setEditorValue(`${watch("description_html")}`);
+  };
+
+  const handleAutoGenerateDescription = async () => {
+    if (!workspaceSlug || !projectId || !issueName) return;
+
+    setIAmFeelingLucky(true);
+
+    aiService
+      .createGptTask(workspaceSlug as string, projectId as string, {
+        prompt: issueName,
+        task: "Generate a proper description for this issue.",
+      })
+      .then((res) => {
+        if (res.response === "")
+          setToastAlert({
+            type: "error",
+            title: "Error!",
+            message:
+              "Issue title isn't informative enough to generate the description. Please try with a different title.",
+          });
+        else handleAiAssistance(res.response_html);
+      })
+      .catch((err) => {
+        const error = err?.data?.error;
+
+        if (err.status === 429)
+          setToastAlert({
+            type: "error",
+            title: "Error!",
+            message: error || "You have reached the maximum number of requests of 50 requests per month per user.",
+          });
+        else
+          setToastAlert({
+            type: "error",
+            title: "Error!",
+            message: error || "Some error occurred. Please try again.",
+          });
+      })
+      .finally(() => setIAmFeelingLucky(false));
+  };
+
   return (
     <Transition.Root show={isOpen} as={React.Fragment}>
       <Dialog as="div" className="relative z-20" onClose={onClose}>
@@ -146,7 +209,35 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
                             )}
                           />
                         </div>
-                        <div>
+                        <div className="relative">
+                          <div className="flex justify-end">
+                            {issueName && issueName !== "" && (
+                              <button
+                                type="button"
+                                className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 ${
+                                  iAmFeelingLucky ? "cursor-wait" : ""
+                                }`}
+                                onClick={handleAutoGenerateDescription}
+                                disabled={iAmFeelingLucky}
+                              >
+                                {iAmFeelingLucky ? (
+                                  "Generating response..."
+                                ) : (
+                                  <>
+                                    <Sparkle className="h-4 w-4" />I{"'"}m feeling lucky
+                                  </>
+                                )}
+                              </button>
+                            )}
+                            <button
+                              type="button"
+                              className="flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90"
+                              onClick={() => setGptAssistantModal((prevData) => !prevData)}
+                            >
+                              <Sparkle className="h-4 w-4" />
+                              AI
+                            </button>
+                          </div>
                           <Controller
                             name="description_html"
                             control={control}
@@ -168,6 +259,23 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
                               />
                             )}
                           />
+                          {envConfig?.has_openai_configured && (
+                            <GptAssistantModal
+                              isOpen={gptAssistantModal}
+                              handleClose={() => {
+                                setGptAssistantModal(false);
+                                // this is done so that the title do not reset after gpt popover closed
+                                reset(getValues());
+                              }}
+                              inset="top-2 left-0"
+                              content=""
+                              htmlContent={watch("description_html")}
+                              onResponse={(response) => {
+                                handleAiAssistance(response);
+                              }}
+                              projectId={projectId}
+                            />
+                          )}
                         </div>
 
                         <div className="flex flex-wrap items-center gap-2">
@@ -188,7 +296,7 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
                       onClick={() => setCreateMore((prevData) => !prevData)}
                     >
                       <span className="text-xs">Create more</span>
-                      <ToggleSwitch value={createMore} onChange={() => { }} size="md" />
+                      <ToggleSwitch value={createMore} onChange={() => {}} size="md" />
                     </div>
                     <div className="flex items-center gap-2">
                       <Button variant="neutral-primary" size="sm" onClick={() => handleClose()}>
diff --git a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx
index 2b4c12aef..aa3ba503e 100644
--- a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx
+++ b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx
@@ -1,6 +1,6 @@
 import { FC, useCallback, useState } from "react";
+import { DragDropContext, DropResult, Droppable } from "@hello-pangea/dnd";
 import { useRouter } from "next/router";
-import { DragDropContext, Droppable } from "@hello-pangea/dnd";
 import { observer } from "mobx-react-lite";
 // mobx store
 import { useMobxStore } from "lib/mobx/store-provider";
@@ -89,8 +89,12 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
     projectLabel: { projectLabels },
     projectMember: { projectMembers },
     projectState: projectStateStore,
+    user: userStore,
   } = useMobxStore();
 
+  const { currentProjectRole } = userStore;
+  const isEditingAllowed = [15, 20].includes(currentProjectRole || 0);
+
   const issues = issueStore?.getIssues || {};
   const issueIds = issueStore?.getIssuesIds || [];
 
@@ -114,7 +118,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
     setIsDragStarted(true);
   };
 
-  const onDragEnd = (result: any) => {
+  const onDragEnd = (result: DropResult) => {
     setIsDragStarted(false);
 
     if (!result) return;
@@ -159,7 +163,11 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
 
       <div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
         <DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
-          <div className={`fixed left-1/2 -translate-x-1/2 z-40 w-72 top-3 flex items-center justify-center mx-3`}>
+          <div
+            className={`fixed left-1/2 -translate-x-1/2 ${
+              isDragStarted ? "z-40" : ""
+            } w-72 top-3 flex items-center justify-center mx-3`}
+          >
             <Droppable droppableId="issue-trash-box" isDropDisabled={!isDragStarted}>
               {(provided, snapshot) => (
                 <div
@@ -216,7 +224,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
               quickAddCallback={issueStore?.quickAddIssue}
               viewId={viewId}
               disableIssueCreation={!enableIssueCreation}
-              isReadOnly={!enableInlineEditing}
+              isReadOnly={!enableInlineEditing || !isEditingAllowed}
               currentStore={currentStore}
               addIssuesToView={addIssuesToView}
             />
@@ -257,7 +265,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
               isDragStarted={isDragStarted}
               disableIssueCreation={true}
               enableQuickIssueCreate={enableQuickAdd}
-              isReadOnly={!enableInlineEditing}
+              isReadOnly={!enableInlineEditing || !isEditingAllowed}
               currentStore={currentStore}
               addIssuesToView={(issues) => {
                 console.log("kanban existingIds", issues);
diff --git a/web/components/issues/issue-layouts/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx
index 571982426..316d88144 100644
--- a/web/components/issues/issue-layouts/kanban/block.tsx
+++ b/web/components/issues/issue-layouts/kanban/block.tsx
@@ -50,9 +50,13 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
     });
   };
 
+  let draggableId = issue.id;
+  if (columnId) draggableId = `${draggableId}__${columnId}`;
+  if (sub_group_id) draggableId = `${draggableId}__${sub_group_id}`;
+
   return (
     <>
-      <Draggable draggableId={issue.id} index={index}>
+      <Draggable draggableId={draggableId} index={index}>
         {(provided, snapshot) => (
           <div
             className="group/kanban-block relative p-1.5 hover:cursor-default"
diff --git a/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx b/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx
index 7fead980c..6bc0db584 100644
--- a/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx
+++ b/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx
@@ -11,6 +11,7 @@ import { EIssueActions } from "../../types";
 // components
 import { BaseKanBanRoot } from "../base-kanban-root";
 import { EProjectStore } from "store/command-palette.store";
+import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
 
 export interface ICycleKanBanLayout {}
 
@@ -51,8 +52,8 @@ export const CycleKanBanLayout: React.FC = observer(() => {
     destination: any,
     subGroupBy: string | null,
     groupBy: string | null,
-    issues: IIssue[],
-    issueWithIds: any
+    issues: IIssueResponse | undefined,
+    issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
   ) => {
     if (kanBanHelperStore.handleDragDrop)
       kanBanHelperStore.handleDragDrop(
diff --git a/web/components/issues/issue-layouts/kanban/roots/module-root.tsx b/web/components/issues/issue-layouts/kanban/roots/module-root.tsx
index 257378e78..20130269c 100644
--- a/web/components/issues/issue-layouts/kanban/roots/module-root.tsx
+++ b/web/components/issues/issue-layouts/kanban/roots/module-root.tsx
@@ -11,6 +11,7 @@ import { IIssue } from "types";
 import { EIssueActions } from "../../types";
 import { BaseKanBanRoot } from "../base-kanban-root";
 import { EProjectStore } from "store/command-palette.store";
+import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
 
 export interface IModuleKanBanLayout {}
 
@@ -30,28 +31,6 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
     kanBanHelpers: kanBanHelperStore,
   } = useMobxStore();
 
-  // const handleIssues = useCallback(
-  //   (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => {
-  //     if (!workspaceSlug || !moduleId) return;
-
-  //     if (action === "update") {
-  //       moduleIssueStore.updateIssueStructure(group_by, sub_group_by, issue);
-  //       issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
-  //     }
-  //     if (action === "delete") moduleIssueStore.deleteIssue(group_by, sub_group_by, issue);
-  //     if (action === "remove" && issue.bridge_id) {
-  //       moduleIssueStore.deleteIssue(group_by, null, issue);
-  //       moduleIssueStore.removeIssueFromModule(
-  //         workspaceSlug.toString(),
-  //         issue.project,
-  //         moduleId.toString(),
-  //         issue.bridge_id
-  //       );
-  //     }
-  //   },
-  //   [moduleIssueStore, issueDetailStore, moduleId, workspaceSlug]
-  // );
-
   const issueActions = {
     [EIssueActions.UPDATE]: async (issue: IIssue) => {
       if (!workspaceSlug || !moduleId) return;
@@ -73,8 +52,8 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
     destination: any,
     subGroupBy: string | null,
     groupBy: string | null,
-    issues: IIssue[],
-    issueWithIds: any
+    issues: IIssueResponse | undefined,
+    issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
   ) => {
     if (kanBanHelperStore.handleDragDrop)
       kanBanHelperStore.handleDragDrop(
diff --git a/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx b/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx
index 6b8c9c2d0..affbab2d8 100644
--- a/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx
+++ b/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx
@@ -30,7 +30,7 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
     [EIssueActions.DELETE]: async (issue: IIssue) => {
       if (!workspaceSlug || !userId) return;
 
-      await profileIssuesStore.removeIssue(workspaceSlug, userId, issue.project, issue.id);
+      await profileIssuesStore.removeIssue(workspaceSlug, issue.project, issue.id, userId);
     },
   };
 
diff --git a/web/components/issues/issue-layouts/kanban/roots/project-root.tsx b/web/components/issues/issue-layouts/kanban/roots/project-root.tsx
index bc085b37c..aa17eedaa 100644
--- a/web/components/issues/issue-layouts/kanban/roots/project-root.tsx
+++ b/web/components/issues/issue-layouts/kanban/roots/project-root.tsx
@@ -10,6 +10,7 @@ import { IIssue } from "types";
 import { EIssueActions } from "../../types";
 import { BaseKanBanRoot } from "../base-kanban-root";
 import { EProjectStore } from "store/command-palette.store";
+import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
 
 export interface IKanBanLayout {}
 
@@ -42,8 +43,8 @@ export const KanBanLayout: React.FC = observer(() => {
     destination: any,
     subGroupBy: string | null,
     groupBy: string | null,
-    issues: IIssue[],
-    issueWithIds: any
+    issues: IIssueResponse | undefined,
+    issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
   ) => {
     if (kanBanHelperStore.handleDragDrop)
       kanBanHelperStore.handleDragDrop(
diff --git a/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx b/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx
index 3ad8b65cf..1315d284a 100644
--- a/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx
+++ b/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx
@@ -10,6 +10,7 @@ import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
 // components
 import { BaseKanBanRoot } from "../base-kanban-root";
 import { EProjectStore } from "store/command-palette.store";
+import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
 
 export interface IViewKanBanLayout {}
 
@@ -42,8 +43,8 @@ export const ProjectViewKanBanLayout: React.FC = observer(() => {
     destination: any,
     subGroupBy: string | null,
     groupBy: string | null,
-    issues: IIssue[],
-    issueWithIds: any
+    issues: IIssueResponse | undefined,
+    issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
   ) => {
     if (kanBanHelperStore.handleDragDrop)
       kanBanHelperStore.handleDragDrop(
diff --git a/web/components/issues/issue-layouts/list/base-list-root.tsx b/web/components/issues/issue-layouts/list/base-list-root.tsx
index 4cde57e7a..0b20a0ffe 100644
--- a/web/components/issues/issue-layouts/list/base-list-root.tsx
+++ b/web/components/issues/issue-layouts/list/base-list-root.tsx
@@ -79,8 +79,12 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
     projectMember: { projectMembers },
     projectState: projectStateStore,
     projectLabel: { projectLabels },
+    user: userStore,
   } = useMobxStore();
 
+  const { currentProjectRole } = userStore;
+  const isEditingAllowed = [15, 20].includes(currentProjectRole || 0);
+
   const issueIds = issueStore?.getIssuesIds || [];
   const issues = issueStore?.getIssues;
 
@@ -142,7 +146,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
             viewId={viewId}
             quickAddCallback={issueStore?.quickAddIssue}
             enableIssueQuickAdd={!!enableQuickAdd}
-            isReadonly={!enableInlineEditing}
+            isReadonly={!enableInlineEditing || !isEditingAllowed}
             disableIssueCreation={!enableIssueCreation}
             currentStore={currentStore}
             addIssuesToView={addIssuesToView}
diff --git a/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx b/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx
index 0a1e9ff6f..933fef331 100644
--- a/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx
+++ b/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx
@@ -30,7 +30,7 @@ export const ProfileIssuesListLayout: FC = observer(() => {
     [EIssueActions.DELETE]: async (group_by: string | null, issue: IIssue) => {
       if (!workspaceSlug || !userId) return;
 
-      await profileIssuesStore.removeIssue(workspaceSlug, userId, issue.project, issue.id);
+      await profileIssuesStore.removeIssue(workspaceSlug, issue.project, issue.id, userId);
     },
   };
 
diff --git a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx
index 76fcb0a95..39d4c01e4 100644
--- a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx
+++ b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx
@@ -68,7 +68,7 @@ export const CycleLayoutRoot: React.FC = observer(() => {
           </div>
         ) : (
           <>
-            {Object.keys(getIssues ?? {}).length == 0 ? (
+            {Object.keys(getIssues ?? {}).length == 0 && !loader ? (
               <CycleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} cycleId={cycleId} />
             ) : (
               <div className="h-full w-full overflow-auto">
diff --git a/web/components/issues/issue-layouts/roots/module-layout-root.tsx b/web/components/issues/issue-layouts/roots/module-layout-root.tsx
index 54fe84309..4d5db8dfb 100644
--- a/web/components/issues/issue-layouts/roots/module-layout-root.tsx
+++ b/web/components/issues/issue-layouts/roots/module-layout-root.tsx
@@ -53,7 +53,7 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
         </div>
       ) : (
         <>
-          {Object.keys(getIssues ?? {}).length == 0 ? (
+          {Object.keys(getIssues ?? {}).length == 0 && !loader ? (
             <ModuleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} />
           ) : (
             <div className="h-full w-full overflow-auto">
diff --git a/web/components/issues/issue-layouts/roots/project-layout-root.tsx b/web/components/issues/issue-layouts/roots/project-layout-root.tsx
index c19b45fb7..704e69db0 100644
--- a/web/components/issues/issue-layouts/roots/project-layout-root.tsx
+++ b/web/components/issues/issue-layouts/roots/project-layout-root.tsx
@@ -53,7 +53,7 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
         </div>
       ) : (
         <>
-          {Object.keys(getIssues ?? {}).length == 0 ? (
+          {Object.keys(getIssues ?? {}).length == 0 && !loader ? (
             <ProjectEmptyState />
           ) : (
             <div className="w-full h-full relative overflow-auto">
diff --git a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx
index 36d2e05a9..db0aa37e1 100644
--- a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx
+++ b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx
@@ -48,7 +48,8 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
     user: userStore,
   } = useMobxStore();
 
-  const user = userStore.currentUser;
+  const { currentProjectRole } = userStore;
+  const isEditingAllowed = [15, 20].includes(currentProjectRole || 0);
 
   const issuesResponse = issueStore.getIssues;
   const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues;
@@ -103,7 +104,7 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
       labels={projectLabels || undefined}
       states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined}
       handleIssues={handleIssues}
-      disableUserActions={false}
+      disableUserActions={!isEditingAllowed}
       quickAddCallback={issueStore.quickAddIssue}
       viewId={viewId}
       enableQuickCreateIssue
diff --git a/web/store/inbox/inbox_filters.store.ts b/web/store/inbox/inbox_filters.store.ts
index 8a7c7ff37..c5758ad9d 100644
--- a/web/store/inbox/inbox_filters.store.ts
+++ b/web/store/inbox/inbox_filters.store.ts
@@ -132,7 +132,10 @@ export class InboxFiltersStore implements IInboxFiltersStore {
         };
       });
 
-      await this.inboxService.patchInbox(workspaceSlug, projectId, inboxId, { view_props: newViewProps });
+      const userRole = this.rootStore.user?.projectMemberInfo?.[projectId]?.role || 0;
+      if (userRole > 10) {
+        await this.inboxService.patchInbox(workspaceSlug, projectId, inboxId, { view_props: newViewProps });
+      }
     } catch (error) {
       runInAction(() => {
         this.error = error;
diff --git a/web/store/issues/base-issue-kanban-helper.store.ts b/web/store/issues/base-issue-kanban-helper.store.ts
index 6ab32bbc5..62b25fe22 100644
--- a/web/store/issues/base-issue-kanban-helper.store.ts
+++ b/web/store/issues/base-issue-kanban-helper.store.ts
@@ -1,15 +1,30 @@
+import { DraggableLocation } from "@hello-pangea/dnd";
+import { IProjectIssuesStore } from "./project-issues/project/issue.store";
+import { IModuleIssuesStore } from "./project-issues/module/issue.store";
+import { ICycleIssuesStore } from "./project-issues/cycle/issue.store";
+import { IViewIssuesStore } from "./project-issues/project-view/issue.store";
+import { IProjectDraftIssuesStore } from "./project-issues/draft/issue.store";
+import { IProfileIssuesStore } from "./profile/issue.store";
+import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "./types";
+
 export interface IKanBanHelpers {
   // actions
   handleDragDrop: (
-    source: any,
-    destination: any,
+    source: DraggableLocation | null,
+    destination: DraggableLocation | null,
     workspaceSlug: string,
-    projectId: string,
-    store: any,
+    projectId: string, // projectId for all views or user id in profile issues
+    store:
+      | IProjectIssuesStore
+      | IModuleIssuesStore
+      | ICycleIssuesStore
+      | IViewIssuesStore
+      | IProjectDraftIssuesStore
+      | IProfileIssuesStore,
     subGroupBy: string | null,
     groupBy: string | null,
-    issues: any,
-    issueWithIds: any,
+    issues: IIssueResponse | undefined,
+    issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined,
     viewId?: string | null
   ) => void;
 }
@@ -53,108 +68,125 @@ export class KanBanHelpers implements IKanBanHelpers {
   };
 
   handleDragDrop = async (
-    source: any,
-    destination: any,
+    source: DraggableLocation | null,
+    destination: DraggableLocation | null,
     workspaceSlug: string,
     projectId: string, // projectId for all views or user id in profile issues
-    store: any,
+    store:
+      | IProjectIssuesStore
+      | IModuleIssuesStore
+      | ICycleIssuesStore
+      | IViewIssuesStore
+      | IProjectDraftIssuesStore
+      | IProfileIssuesStore,
     subGroupBy: string | null,
     groupBy: string | null,
-    issues: any,
-    issueWithIds: any,
+    issues: IIssueResponse | undefined,
+    issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined,
     viewId: string | null = null // it can be moduleId, cycleId
   ) => {
-    if (issues && issueWithIds) {
-      let updateIssue: any = {};
+    if (!issues || !issueWithIds || !source || !destination) return;
 
-      const sourceColumnId = (source?.droppableId && source?.droppableId.split("__")) || null;
-      const destinationColumnId = (destination?.droppableId && destination?.droppableId.split("__")) || null;
+    let updateIssue: any = {};
 
-      const sourceGroupByColumnId = sourceColumnId[0] || null;
-      const destinationGroupByColumnId = destinationColumnId[0] || null;
+    const sourceColumnId = (source?.droppableId && source?.droppableId.split("__")) || null;
+    const destinationColumnId = (destination?.droppableId && destination?.droppableId.split("__")) || null;
 
-      const sourceSubGroupByColumnId = sourceColumnId[1] || null;
-      const destinationSubGroupByColumnId = destinationColumnId[1] || null;
+    if (!sourceColumnId || !destinationColumnId) return;
 
-      if (!workspaceSlug || !projectId || !groupBy || !sourceGroupByColumnId || !destinationGroupByColumnId) return;
+    const sourceGroupByColumnId = sourceColumnId[0] || null;
+    const destinationGroupByColumnId = destinationColumnId[0] || null;
 
-      if (destinationGroupByColumnId === "issue-trash-box") {
-        const sourceIssues = subGroupBy
-          ? issueWithIds[sourceSubGroupByColumnId][sourceGroupByColumnId]
-          : issueWithIds[sourceGroupByColumnId];
+    const sourceSubGroupByColumnId = sourceColumnId[1] || null;
+    const destinationSubGroupByColumnId = destinationColumnId[1] || null;
 
-        const [removed] = sourceIssues.splice(source.index, 1);
+    if (
+      !workspaceSlug ||
+      !projectId ||
+      !groupBy ||
+      !sourceGroupByColumnId ||
+      !destinationGroupByColumnId ||
+      !sourceSubGroupByColumnId ||
+      !sourceGroupByColumnId
+    )
+      return;
 
-        console.log("removed", removed);
+    if (destinationGroupByColumnId === "issue-trash-box") {
+      const sourceIssues: string[] = subGroupBy
+        ? (issueWithIds as ISubGroupedIssues)[sourceSubGroupByColumnId][sourceGroupByColumnId]
+        : (issueWithIds as IGroupedIssues)[sourceGroupByColumnId];
 
-        if (removed) {
-          if (viewId) store?.removeIssue(workspaceSlug, projectId, removed, viewId);
-          else store?.removeIssue(workspaceSlug, projectId, removed);
-        }
-      } else {
-        const sourceIssues = subGroupBy
-          ? issueWithIds[sourceSubGroupByColumnId][sourceGroupByColumnId]
-          : issueWithIds[sourceGroupByColumnId];
-        const destinationIssues = subGroupBy
-          ? issueWithIds[sourceSubGroupByColumnId][destinationGroupByColumnId]
-          : issueWithIds[destinationGroupByColumnId];
+      const [removed] = sourceIssues.splice(source.index, 1);
 
-        const [removed] = sourceIssues.splice(source.index, 1);
-        const removedIssueDetail = issues[removed];
+      console.log("removed", removed);
 
-        if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) {
-          updateIssue = {
-            id: removedIssueDetail?.id,
-          };
+      if (removed) {
+        if (viewId) store?.removeIssue(workspaceSlug, projectId, removed, viewId);
+        else store?.removeIssue(workspaceSlug, projectId, removed);
+      }
+    } else {
+      const sourceIssues = subGroupBy
+        ? (issueWithIds as ISubGroupedIssues)[sourceSubGroupByColumnId][sourceGroupByColumnId]
+        : (issueWithIds as IGroupedIssues)[sourceGroupByColumnId];
+      const destinationIssues = subGroupBy
+        ? (issueWithIds as ISubGroupedIssues)[sourceSubGroupByColumnId][destinationGroupByColumnId]
+        : (issueWithIds as IGroupedIssues)[destinationGroupByColumnId];
 
-          // for both horizontal and vertical dnd
-          updateIssue = {
-            ...updateIssue,
-            ...this.handleSortOrder(destinationIssues, destination.index, issues),
-          };
+      const [removed] = sourceIssues.splice(source.index, 1);
+      const removedIssueDetail = issues[removed];
 
-          if (sourceSubGroupByColumnId === destinationSubGroupByColumnId) {
-            if (sourceGroupByColumnId != destinationGroupByColumnId) {
-              if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId };
-              if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId };
-            }
-          } else {
-            if (subGroupBy === "state")
-              updateIssue = {
-                ...updateIssue,
-                state: destinationSubGroupByColumnId,
-                priority: destinationGroupByColumnId,
-              };
-            if (subGroupBy === "priority")
-              updateIssue = {
-                ...updateIssue,
-                state: destinationGroupByColumnId,
-                priority: destinationSubGroupByColumnId,
-              };
-          }
-        } else {
-          updateIssue = {
-            id: removedIssueDetail?.id,
-          };
+      if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) {
+        updateIssue = {
+          id: removedIssueDetail?.id,
+        };
 
-          // for both horizontal and vertical dnd
-          updateIssue = {
-            ...updateIssue,
-            ...this.handleSortOrder(destinationIssues, destination.index, issues),
-          };
+        // for both horizontal and vertical dnd
+        updateIssue = {
+          ...updateIssue,
+          ...this.handleSortOrder(destinationIssues, destination.index, issues),
+        };
 
-          // for horizontal dnd
-          if (sourceColumnId != destinationColumnId) {
+        if (sourceSubGroupByColumnId === destinationSubGroupByColumnId) {
+          if (sourceGroupByColumnId != destinationGroupByColumnId) {
             if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId };
             if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId };
           }
+        } else {
+          if (subGroupBy === "state")
+            updateIssue = {
+              ...updateIssue,
+              state: destinationSubGroupByColumnId,
+              priority: destinationGroupByColumnId,
+            };
+          if (subGroupBy === "priority")
+            updateIssue = {
+              ...updateIssue,
+              state: destinationGroupByColumnId,
+              priority: destinationSubGroupByColumnId,
+            };
         }
+      } else {
+        updateIssue = {
+          id: removedIssueDetail?.id,
+        };
 
-        if (updateIssue && updateIssue?.id) {
-          if (viewId) store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue, viewId);
-          else store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue);
+        // for both horizontal and vertical dnd
+        updateIssue = {
+          ...updateIssue,
+          ...this.handleSortOrder(destinationIssues, destination.index, issues),
+        };
+
+        // for horizontal dnd
+        if (sourceColumnId != destinationColumnId) {
+          if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId };
+          if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId };
         }
       }
+
+      if (updateIssue && updateIssue?.id) {
+        if (viewId) store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue, viewId);
+        else store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue);
+      }
     }
   };
 }
diff --git a/web/store/issues/profile/issue.store.ts b/web/store/issues/profile/issue.store.ts
index 727f7f623..96f3976e0 100644
--- a/web/store/issues/profile/issue.store.ts
+++ b/web/store/issues/profile/issue.store.ts
@@ -40,9 +40,9 @@ export interface IProfileIssuesStore {
   ) => Promise<IIssue | undefined>;
   removeIssue: (
     workspaceSlug: string,
-    userId: string,
     projectId: string,
-    issueId: string
+    issueId: string,
+    userId?: string
   ) => Promise<IIssue | undefined>;
   quickAddIssue: (workspaceSlug: string, userId: string, data: IIssue) => Promise<IIssue | undefined>;
   viewFlags: ViewFlags;
@@ -275,7 +275,8 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues
     }
   };
 
-  removeIssue = async (workspaceSlug: string, userId: string, projectId: string, issueId: string) => {
+  removeIssue = async (workspaceSlug: string, projectId: string, issueId: string, userId?: string) => {
+    if (!userId) return;
     try {
       let _issues = { ...this.issues };
       if (!_issues) _issues = {};