feat(rbac): implemented granular blocking of actions based on permissions on org level ui

This commit is contained in:
Akhil Mohan
2023-09-08 21:21:43 +05:30
parent 34fb7be1c4
commit aac3168c80
41 changed files with 1992 additions and 1652 deletions
@@ -3,17 +3,9 @@ import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import {
Button,
FormControl,
Input,
Modal,
ModalContent
} from "@app/components/v2";
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
import { useOrganization } from "@app/context";
import {
useAddIncidentContact
} from "@app/hooks/api";
import { useAddIncidentContact } from "@app/hooks/api";
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -24,97 +16,90 @@ const addContactFormSchema = yup.object({
type TAddContactForm = yup.InferType<typeof addContactFormSchema>;
type Props = {
popUp: UsePopUpState<["addContact"]>;
handlePopUpClose: (popUpName: keyof UsePopUpState<["addContact"]>) => void;
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addContact"]>, state?: boolean) => void;
popUp: UsePopUpState<["addContact"]>;
handlePopUpClose: (popUpName: keyof UsePopUpState<["addContact"]>) => void;
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addContact"]>, state?: boolean) => void;
};
export const AddOrgIncidentContactModal = ({
popUp,
handlePopUpClose,
handlePopUpToggle
popUp,
handlePopUpClose,
handlePopUpToggle
}: Props) => {
const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization();
const { data: serverDetails } = useFetchServerStatus()
const {
control,
handleSubmit,
reset
} = useForm<TAddContactForm>({ resolver: yupResolver(addContactFormSchema) });
const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization();
const { data: serverDetails } = useFetchServerStatus();
const { control, handleSubmit, reset } = useForm<TAddContactForm>({
resolver: yupResolver(addContactFormSchema)
});
const { mutateAsync, isLoading } = useAddIncidentContact();
const onFormSubmit = async ({ email }: TAddContactForm) => {
try {
if (!currentOrg?._id) return;
await mutateAsync({
orgId: currentOrg._id,
email
});
const { mutateAsync, isLoading } = useAddIncidentContact();
createNotification({
text: "Successfully added incident contact",
type: "success"
});
if (serverDetails?.emailConfigured){
handlePopUpClose("addContact");
}
const onFormSubmit = async ({ email }: TAddContactForm) => {
try {
if (!currentOrg?._id) return;
reset();
} catch (err) {
console.error(err);
createNotification({
text: "Failed to add incident contact",
type: "error"
});
}
await mutateAsync({
orgId: currentOrg._id,
email
});
createNotification({
text: "Successfully added incident contact",
type: "success"
});
if (serverDetails?.emailConfigured) {
handlePopUpClose("addContact");
}
reset();
} catch (err) {
console.error(err);
createNotification({
text: "Failed to add incident contact",
type: "error"
});
}
};
return (
<Modal
isOpen={popUp?.addContact?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("addContact", isOpen);
reset();
}}
>
<ModalContent
title="Add an Incident Contact"
subTitle="This contact will be notified in the unlikely event of a severe incident."
return (
<Modal
isOpen={popUp?.addContact?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("addContact", isOpen);
reset();
}}
>
<ModalContent
title="Add an Incident Contact"
subTitle="This contact will be notified in the unlikely event of a severe incident."
>
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
defaultValue=""
name="email"
render={({ field, fieldState: { error } }) => (
<FormControl label="Email" isError={Boolean(error)} errorText={error?.message}>
<Input {...field} />
</FormControl>
)}
/>
<div className="mt-8 flex items-center space-x-4">
<Button size="sm" type="submit" isLoading={isLoading} isDisabled={isLoading}>
Add Incident Contact
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpClose("addContact")}
>
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
defaultValue=""
name="email"
render={({ field, fieldState: { error } }) => (
<FormControl label="Email" isError={Boolean(error)} errorText={error?.message}>
<Input {...field} />
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
size="sm"
type="submit"
isLoading={isLoading}
isDisabled={isLoading}
>
Add Incident Contact
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpClose("addContact")}
>
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
}
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
};
@@ -2,42 +2,53 @@ import { useTranslation } from "react-i18next";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Button
} from "@app/components/v2";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button } from "@app/components/v2";
import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context";
import { withPermission } from "@app/hoc";
import { usePopUp } from "@app/hooks";
import { AddOrgIncidentContactModal } from "./AddOrgIncidentContactModal";
import { OrgIncidentContactsTable } from "./OrgIncidentContactsTable";
export const OrgIncidentContactsSection = () => {
export const OrgIncidentContactsSection = withPermission(
() => {
const { t } = useTranslation();
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"addContact"
"addContact"
] as const);
return (
<div className="p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
<div className="flex justify-between mb-4">
<p className="min-w-max text-xl font-semibold">
{t("section.incident.incident-contacts")}
</p>
<Button
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("addContact")}
>
Add contact
</Button>
</div>
<OrgIncidentContactsTable />
<AddOrgIncidentContactModal
popUp={popUp}
handlePopUpClose={handlePopUpClose}
handlePopUpToggle={handlePopUpToggle}
/>
<div className="p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
<div className="flex justify-between mb-4">
<p className="min-w-max text-xl font-semibold">
{t("section.incident.incident-contacts")}
</p>
<OrgPermissionCan
I={OrgGeneralPermissionActions.Create}
a={OrgPermissionSubjects.IncidentAccount}
>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
isDisabled={!isAllowed}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("addContact")}
>
Add contact
</Button>
)}
</OrgPermissionCan>
</div>
<OrgIncidentContactsTable />
<AddOrgIncidentContactModal
popUp={popUp}
handlePopUpClose={handlePopUpClose}
handlePopUpToggle={handlePopUpToggle}
/>
</div>
);
}
},
{ action: OrgGeneralPermissionActions.Read, subject: OrgPermissionSubjects.IncidentAccount }
);
@@ -3,6 +3,7 @@ import { faContactBook, faMagnifyingGlass, faTrash } from "@fortawesome/free-sol
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { OrgPermissionCan } from "@app/components/permissions";
import {
DeleteActionModal,
EmptyState,
@@ -17,7 +18,7 @@ import {
THead,
Tr
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useDeleteIncidentContact, useGetOrgIncidentContact } from "@app/hooks/api";
@@ -83,13 +84,21 @@ export const OrgIncidentContactsTable = () => {
<Tr key={email}>
<Td className="w-full">{email}</Td>
<Td className="mr-4">
<IconButton
ariaLabel="delete"
colorSchema="danger"
onClick={() => handlePopUpOpen("removeContact", { email })}
<OrgPermissionCan
I={OrgGeneralPermissionActions.Delete}
an={OrgPermissionSubjects.IncidentAccount}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
{(isAllowed) => (
<IconButton
ariaLabel="delete"
colorSchema="danger"
onClick={() => handlePopUpOpen("removeContact", { email })}
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
)}
</OrgPermissionCan>
</Td>
</Tr>
))}