From e7948eabf25eed0dd2fe291660e3bcfa1ac6b69e Mon Sep 17 00:00:00 2001 From: Akshita Goyal <36129505+gakshita@users.noreply.github.com> Date: Fri, 19 Jul 2024 15:28:48 +0530 Subject: [PATCH] [WEB-1956] fix: Keyboard shortcuts (#5134) * fix: shortcuts * fix: naming * fix: structure optimization --- .../command-palette/command-modal.tsx | 4 +- .../command-palette/command-palette.tsx | 25 ++++++++++- .../components/issues/issue-modal/form.tsx | 36 +++++++++------- web/core/store/user/index.ts | 41 +++++++++++++++++++ web/core/store/user/user-membership.store.ts | 3 ++ 5 files changed, 90 insertions(+), 19 deletions(-) diff --git a/web/core/components/command-palette/command-modal.tsx b/web/core/components/command-palette/command-modal.tsx index e24e64f3e..f0c779c55 100644 --- a/web/core/components/command-palette/command-modal.tsx +++ b/web/core/components/command-palette/command-modal.tsx @@ -46,7 +46,7 @@ export const CommandModal: React.FC = observer(() => { // hooks const { getProjectById, workspaceProjectIds } = useProject(); const { isMobile } = usePlatformOS(); - const { canPerformWorkspaceCreateActions } = useUser(); + const { canPerformAnyCreateAction } = useUser(); // states const [placeholder, setPlaceholder] = useState("Type a command or search..."); const [resultsCount, setResultsCount] = useState(0); @@ -286,7 +286,7 @@ export const CommandModal: React.FC = observer(() => { {workspaceSlug && workspaceProjectIds && workspaceProjectIds.length > 0 && - canPerformWorkspaceCreateActions && ( + canPerformAnyCreateAction && ( { diff --git a/web/core/components/command-palette/command-palette.tsx b/web/core/components/command-palette/command-palette.tsx index 70eef011f..ab9215d3e 100644 --- a/web/core/components/command-palette/command-palette.tsx +++ b/web/core/components/command-palette/command-palette.tsx @@ -41,7 +41,12 @@ export const CommandPalette: FC = observer(() => { const { toggleSidebar } = useAppTheme(); const { setTrackElement } = useEventTracker(); const { platform } = usePlatformOS(); - const { data: currentUser, canPerformProjectCreateActions, canPerformWorkspaceCreateActions } = useUser(); + const { + data: currentUser, + canPerformProjectCreateActions, + canPerformWorkspaceCreateActions, + canPerformAnyCreateAction, + } = useUser(); const { issues: { removeIssue }, } = useIssuesStore(); @@ -120,6 +125,18 @@ export const CommandPalette: FC = observer(() => { [canPerformWorkspaceCreateActions] ); + const performAnyProjectCreateActions = useCallback( + (showToast: boolean = true) => { + if (!canPerformAnyCreateAction && showToast) + setToast({ + type: TOAST_TYPE.ERROR, + title: "You don't have permission to perform this action.", + }); + return canPerformAnyCreateAction; + }, + [canPerformAnyCreateAction] + ); + const shortcutsList: { global: Record void }>; workspace: Record void }>; @@ -222,7 +239,10 @@ export const CommandPalette: FC = observer(() => { } } else if (!isAnyModalOpen) { setTrackElement("Shortcut key"); - if (Object.keys(shortcutsList.global).includes(keyPressed) && performWorkspaceCreateActions()) + if ( + Object.keys(shortcutsList.global).includes(keyPressed) && + ((!projectId && performAnyProjectCreateActions()) || performProjectCreateActions()) + ) shortcutsList.global[keyPressed].action(); // workspace authorized actions else if ( @@ -244,6 +264,7 @@ export const CommandPalette: FC = observer(() => { } }, [ + performAnyProjectCreateActions, performProjectCreateActions, performWorkspaceCreateActions, copyIssueUrlToClipboard, diff --git a/web/core/components/issues/issue-modal/form.tsx b/web/core/components/issues/issue-modal/form.tsx index b041cf3dd..8c6cd1b8c 100644 --- a/web/core/components/issues/issue-modal/form.tsx +++ b/web/core/components/issues/issue-modal/form.tsx @@ -32,7 +32,7 @@ import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper" import { getChangedIssuefields, getDescriptionPlaceholder } from "@/helpers/issue.helper"; import { shouldRenderProject } from "@/helpers/project.helper"; // hooks -import { useProjectEstimates, useInstance, useIssueDetail, useProject, useWorkspace } from "@/hooks/store"; +import { useProjectEstimates, useInstance, useIssueDetail, useProject, useWorkspace, useUser } from "@/hooks/store"; import useKeypress from "@/hooks/use-keypress"; import { useProjectIssueProperties } from "@/hooks/use-project-issue-properties"; // services @@ -121,6 +121,8 @@ export const IssueFormRoot: FC = observer((props) => { const workspaceStore = useWorkspace(); const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug?.toString())?.id as string; const { config } = useInstance(); + const { projectsWithCreatePermissions } = useUser(); + const { getProjectById } = useProject(); const { areEstimateEnabledByProjectId } = useProjectEstimates(); @@ -341,20 +343,24 @@ export const IssueFormRoot: FC = observer((props) => { rules={{ required: true, }} - render={({ field: { value, onChange } }) => ( -
- { - onChange(projectId); - handleFormChange(); - }} - buttonVariant="border-with-text" - renderCondition={(project) => shouldRenderProject(project)} - tabIndex={getTabIndex("project_id")} - /> -
- )} + render={({ field: { value, onChange } }) => + projectsWithCreatePermissions && projectsWithCreatePermissions[value!] ? ( +
+ { + onChange(projectId); + handleFormChange(); + }} + buttonVariant="border-with-text" + renderCondition={(project) => shouldRenderProject(project)} + tabIndex={getTabIndex("project_id")} + /> +
+ ) : ( + <> + ) + } /> )}

{data?.id ? "Update" : "Create"} Issue

diff --git a/web/core/store/user/index.ts b/web/core/store/user/index.ts index 4983c61d8..d21881171 100644 --- a/web/core/store/user/index.ts +++ b/web/core/store/user/index.ts @@ -44,6 +44,8 @@ export interface IUserStore { // computed canPerformProjectCreateActions: boolean; canPerformWorkspaceCreateActions: boolean; + canPerformAnyCreateAction: boolean; + projectsWithCreatePermissions: { [projectId: string]: number } | null; } export class UserStore implements IUserStore { @@ -91,6 +93,8 @@ export class UserStore implements IUserStore { // computed canPerformProjectCreateActions: computed, canPerformWorkspaceCreateActions: computed, + canPerformAnyCreateAction: computed, + projectsWithCreatePermissions: computed, }); } @@ -136,6 +140,26 @@ export class UserStore implements IUserStore { } }; + /** + * @description fetches the prjects with write permissions + * @returns {{[projectId: string]: number} || null} + */ + fetchProjectsWithCreatePermissions() { + const allWorkspaceRoles = + this.membership.workspaceProjectsRole && + this.membership.workspaceProjectsRole[this.membership.router.workspaceSlug || ""]; + return ( + (allWorkspaceRoles && + Object.keys(allWorkspaceRoles) + .filter((key) => allWorkspaceRoles[key] >= EUserProjectRoles.MEMBER) + .reduce( + (res: { [projectId: string]: number }, key: string) => ((res[key] = allWorkspaceRoles[key]), res), + {} + )) || + null + ); + } + /** * @description updates the current user * @param data @@ -229,6 +253,23 @@ export class UserStore implements IUserStore { this.store.resetOnSignOut(); }; + /** + * @description returns projects where user has permissions + * @returns {{[projectId: string]: number} || null} + */ + get projectsWithCreatePermissions() { + return this.fetchProjectsWithCreatePermissions(); + } + + /** + * @description returns true if user has permissions to write in any project + * @returns {boolean} + */ + get canPerformAnyCreateAction() { + const filteredProjects = this.fetchProjectsWithCreatePermissions(); + return filteredProjects ? Object.keys(filteredProjects).length > 0 : false; + } + /** * @description tells if user has project create actions permissions * @returns {boolean} diff --git a/web/core/store/user/user-membership.store.ts b/web/core/store/user/user-membership.store.ts index 86f8c442b..efe58eac0 100644 --- a/web/core/store/user/user-membership.store.ts +++ b/web/core/store/user/user-membership.store.ts @@ -11,6 +11,7 @@ import { ProjectMemberService } from "@/services/project"; import { UserService } from "@/services/user.service"; // plane web store import { CoreRootStore } from "../root.store"; +import { IRouterStore } from "../router.store"; export interface IUserMembershipStore { // observables @@ -47,6 +48,8 @@ export interface IUserMembershipStore { leaveWorkspace: (workspaceSlug: string) => Promise; joinProject: (workspaceSlug: string, projectIds: string[]) => Promise; leaveProject: (workspaceSlug: string, projectId: string) => Promise; + + router: IRouterStore; } export class UserMembershipStore implements IUserMembershipStore {