[WEB-2043] fix: delete action validation and toast alert (#5254)

* dev: canPerformProjectAdminActions helper function added

* chore: deleteInboxIssue action updated

* dev: bulk delete modal validation updated

* chore: issue, intake, cycle and module delete action toast updated

* chore: code refactor
This commit is contained in:
Anmol Singh Bhatia
2024-07-29 19:08:18 +05:30
committed by GitHub
parent ba9d9fd5eb
commit 35e58e9ec7
8 changed files with 107 additions and 18 deletions

View File

@@ -46,6 +46,7 @@ export const CommandPalette: FC = observer(() => {
canPerformProjectCreateActions,
canPerformWorkspaceCreateActions,
canPerformAnyCreateAction,
canPerformProjectAdminActions,
} = useUser();
const {
issues: { removeIssue },
@@ -113,6 +114,19 @@ export const CommandPalette: FC = observer(() => {
[canPerformProjectCreateActions]
);
const performProjectBulkDeleteActions = useCallback(
(showToast: boolean = true) => {
if (!canPerformProjectAdminActions && showToast)
setToast({
type: TOAST_TYPE.ERROR,
title: "You don't have permission to perform this action.",
});
return canPerformProjectAdminActions;
},
[canPerformProjectAdminActions]
);
const performWorkspaceCreateActions = useCallback(
(showToast: boolean = true) => {
if (!canPerformWorkspaceCreateActions && showToast)
@@ -210,6 +224,7 @@ export const CommandPalette: FC = observer(() => {
const keyPressed = key.toLowerCase();
const cmdClicked = ctrlKey || metaKey;
const shiftClicked = shiftKey;
const deleteKey = keyPressed === "backspace" || keyPressed === "delete";
if (cmdClicked && keyPressed === "k" && !isAnyModalOpen) {
e.preventDefault();
@@ -229,7 +244,11 @@ export const CommandPalette: FC = observer(() => {
toggleShortcutModal(true);
}
if (cmdClicked) {
if (deleteKey) {
if (performProjectBulkDeleteActions()) {
shortcutsList.project.delete.action();
}
} else if (cmdClicked) {
if (keyPressed === "c" && ((platform === "MacOS" && ctrlKey) || altKey)) {
e.preventDefault();
copyIssueUrlToClipboard();
@@ -266,6 +285,7 @@ export const CommandPalette: FC = observer(() => {
[
performAnyProjectCreateActions,
performProjectCreateActions,
performProjectBulkDeleteActions,
performWorkspaceCreateActions,
copyIssueUrlToClipboard,
isAnyModalOpen,

View File

@@ -9,6 +9,7 @@ import { ICycle } from "@plane/types";
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
// constants
import { CYCLE_DELETED } from "@/constants/event-tracker";
import { PROJECT_ERROR_MESSAGES } from "@/constants/project";
// hooks
import { useEventTracker, useCycle } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
@@ -51,16 +52,24 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
payload: { ...cycle, state: "SUCCESS" },
});
})
.catch(() => {
.catch((errors) => {
const isPermissionError = errors?.error === "Only admin or owner can delete the cycle";
const currentError = isPermissionError
? PROJECT_ERROR_MESSAGES.permissionError
: PROJECT_ERROR_MESSAGES.cycleDeleteError;
setToast({
title: currentError.title,
type: TOAST_TYPE.ERROR,
message: currentError.message,
});
captureCycleEvent({
eventName: CYCLE_DELETED,
payload: { ...cycle, state: "FAILED" },
});
});
})
.finally(() => handleClose());
if (cycleId || peekCycle) router.push(`/${workspaceSlug}/projects/${projectId}/cycles`);
handleClose();
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,

View File

@@ -3,7 +3,9 @@ import { observer } from "mobx-react";
// types
import type { TIssue } from "@plane/types";
// ui
import { AlertModalCore } from "@plane/ui";
import { AlertModalCore, setToast, TOAST_TYPE } from "@plane/ui";
// constants
import { PROJECT_ERROR_MESSAGES } from "@/constants/project";
// hooks
import { useProject } from "@/hooks/store";
@@ -29,7 +31,26 @@ export const DeleteInboxIssueModal: React.FC<Props> = observer(({ isOpen, onClos
const handleDelete = async () => {
setIsDeleting(true);
await onSubmit().finally(() => handleClose());
await onSubmit()
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: `Issue deleted successfully`,
});
})
.catch((errors) => {
const isPermissionError = errors?.error === "Only admin or creator can delete the issue";
const currentError = isPermissionError
? PROJECT_ERROR_MESSAGES.permissionError
: PROJECT_ERROR_MESSAGES.issueDeleteError;
setToast({
title: currentError.title,
type: TOAST_TYPE.ERROR,
message: currentError.message,
});
})
.finally(() => handleClose());
};
return (

View File

@@ -5,6 +5,8 @@ import { useEffect, useState } from "react";
import { TIssue } from "@plane/types";
// ui
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
// constants
import { PROJECT_ERROR_MESSAGES } from "@/constants/project";
// hooks
import { useIssues, useProject } from "@/hooks/store";
@@ -52,14 +54,18 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
});
onClose();
})
.catch(() => {
.catch((errors) => {
const isPermissionError = errors?.error === "Only admin or creator can delete the issue";
const currentError = isPermissionError
? PROJECT_ERROR_MESSAGES.permissionError
: PROJECT_ERROR_MESSAGES.issueDeleteError;
setToast({
title: "Error",
title: currentError.title,
type: TOAST_TYPE.ERROR,
message: "Failed to delete issue",
message: currentError.message,
});
})
.finally(() => setIsDeleting(false));
.finally(() => onClose());
};
return (

View File

@@ -9,6 +9,7 @@ import type { IModule } from "@plane/types";
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
// constants
import { MODULE_DELETED } from "@/constants/event-tracker";
import { PROJECT_ERROR_MESSAGES } from "@/constants/project";
// hooks
import { useEventTracker, useModule } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
@@ -54,20 +55,22 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
payload: { ...data, state: "SUCCESS" },
});
})
.catch(() => {
.catch((errors) => {
const isPermissionError = errors?.error === "Only admin or creator can delete the module";
const currentError = isPermissionError
? PROJECT_ERROR_MESSAGES.permissionError
: PROJECT_ERROR_MESSAGES.moduleDeleteError;
setToast({
title: currentError.title,
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Module could not be deleted. Please try again.",
message: currentError.message,
});
captureModuleEvent({
eventName: MODULE_DELETED,
payload: { ...data, state: "FAILED" },
});
})
.finally(() => {
setIsDeleteLoading(false);
});
.finally(() => handleClose());
};
return (

View File

@@ -168,3 +168,22 @@ export const PROJECT_DISPLAY_FILTER_OPTIONS: {
label: "Archived",
},
];
export const PROJECT_ERROR_MESSAGES = {
permissionError: {
title: "You don't have permission to perform this action.",
message: undefined,
},
cycleDeleteError: {
title: "Error",
message: "Failed to delete project",
},
moduleDeleteError: {
title: "Error",
message: "Failed to delete module",
},
issueDeleteError: {
title: "Error",
message: "Failed to delete issue",
},
};

View File

@@ -455,11 +455,12 @@ export class ProjectInboxStore implements IProjectInboxStore {
);
});
await this.inboxIssueService.destroy(workspaceSlug, projectId, inboxIssueId);
} catch {
} catch (error) {
console.error("Error removing the intake issue");
set(this.inboxIssues, [inboxIssueId], currentIssue);
set(this, ["inboxIssuePaginationInfo", "total_results"], (this.inboxIssuePaginationInfo?.total_results || 0) + 1);
set(this, ["inboxIssueIds"], [...this.inboxIssueIds, inboxIssueId]);
throw error;
}
};
}

View File

@@ -43,6 +43,7 @@ export interface IUserStore {
signOut: () => Promise<void>;
// computed
canPerformProjectCreateActions: boolean;
canPerformProjectAdminActions: boolean;
canPerformWorkspaceCreateActions: boolean;
canPerformAnyCreateAction: boolean;
projectsWithCreatePermissions: { [projectId: string]: number } | null;
@@ -92,6 +93,7 @@ export class UserStore implements IUserStore {
signOut: action,
// computed
canPerformProjectCreateActions: computed,
canPerformProjectAdminActions: computed,
canPerformWorkspaceCreateActions: computed,
canPerformAnyCreateAction: computed,
projectsWithCreatePermissions: computed,
@@ -278,6 +280,14 @@ export class UserStore implements IUserStore {
return !!this.membership.currentProjectRole && this.membership.currentProjectRole >= EUserProjectRoles.MEMBER;
}
/**
* @description tells if user has project admin actions permissions
* @returns {boolean}
*/
get canPerformProjectAdminActions() {
return !!this.membership.currentProjectRole && this.membership.currentProjectRole === EUserProjectRoles.ADMIN;
}
/**
* @description tells if user has workspace create actions permissions
* @returns {boolean}