add notificaion pages
This commit is contained in:
1 parent
46b2b4c3ab
commit
5b21906894
2 files changed
+445
No files matched your search
@@ -0,0 +1,324 @@
|
||||
// Components
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import Button from "@mui/material/Button";
|
||||
import ConfigBox from "../../../Components/ConfigBox";
|
||||
import Box from "@mui/material/Box";
|
||||
import Select from "../../../Components/Inputs/Select";
|
||||
import TextInput from "../../../Components/Inputs/TextInput";
|
||||
|
||||
// Utils
|
||||
import { useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useCreateNotification } from "../../../Hooks/useNotifications";
|
||||
import {
|
||||
notificationEmailValidation,
|
||||
notificationWebhookValidation,
|
||||
notificationPagerDutyValidation,
|
||||
} from "../../../Validation/validation";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// Setup
|
||||
|
||||
const NOTIFICATION_TYPES = [
|
||||
{ _id: 1, name: "E-mail", value: "email" },
|
||||
{ _id: 2, name: "Slack", value: "webhook" },
|
||||
{ _id: 3, name: "PagerDuty", value: "pager_duty" },
|
||||
];
|
||||
|
||||
const CreateNotifications = () => {
|
||||
const theme = useTheme();
|
||||
const [createNotification, isLoading, error] = useCreateNotification();
|
||||
const BREADCRUMBS = [
|
||||
{ name: "notifications", path: "/notifications" },
|
||||
{ name: "create", path: "/notifications/create" },
|
||||
];
|
||||
|
||||
// Redux state
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
|
||||
// local state
|
||||
const [notification, setNotification] = useState({
|
||||
userId: user._id,
|
||||
teamId: user.teamId,
|
||||
notificationName: "",
|
||||
address: "",
|
||||
type: NOTIFICATION_TYPES[0]._id,
|
||||
config: {
|
||||
webhookUrl: "",
|
||||
platform: "",
|
||||
routingKey: "",
|
||||
},
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
const { t } = useTranslation();
|
||||
|
||||
// handlers
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const form = {
|
||||
...notification,
|
||||
type: NOTIFICATION_TYPES.find((type) => type._id === notification.type).value,
|
||||
};
|
||||
|
||||
if (notification.type === 2) {
|
||||
form.type = "webhook";
|
||||
}
|
||||
|
||||
let error = null;
|
||||
|
||||
if (form.type === "email") {
|
||||
error = notificationEmailValidation.validate(
|
||||
{ notificationName: form.notificationName, address: form.address },
|
||||
{ abortEarly: false }
|
||||
).error;
|
||||
} else if (form.type === "webhook") {
|
||||
form.config = {
|
||||
platform: form.config.platform,
|
||||
webhookUrl: form.config.webhookUrl,
|
||||
};
|
||||
error = notificationWebhookValidation.validate(
|
||||
{ notificationName: form.notificationName, config: form.config },
|
||||
{ abortEarly: false }
|
||||
).error;
|
||||
} else if (form.type === "pager_duty") {
|
||||
form.config = {
|
||||
platform: form.config.platform,
|
||||
routingKey: form.config.routingKey,
|
||||
};
|
||||
error = notificationPagerDutyValidation.validate(
|
||||
{ notificationName: form.notificationName, config: form.config },
|
||||
{ abortEarly: false }
|
||||
).error;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
const newErrors = {};
|
||||
error.details.forEach((err) => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
createToast({ body: "Please check the form for errors." });
|
||||
setErrors(newErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
createNotification(form);
|
||||
};
|
||||
|
||||
const onChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
|
||||
const newNotification = { ...notification, [name]: value };
|
||||
|
||||
// Handle config/platform initialization if type is webhook
|
||||
|
||||
if (newNotification.type === 1) {
|
||||
newNotification.config = null;
|
||||
} else if (newNotification.type === 2) {
|
||||
newNotification.address = "";
|
||||
newNotification.config = newNotification.config || {};
|
||||
if (name === "config") {
|
||||
newNotification.config = value;
|
||||
}
|
||||
newNotification.config.platform = "slack";
|
||||
} else if (newNotification.type === 3) {
|
||||
newNotification.config = newNotification.config || {};
|
||||
if (name === "config") {
|
||||
newNotification.config = value;
|
||||
}
|
||||
newNotification.config.platform = "pager_duty";
|
||||
}
|
||||
|
||||
// Field-level validation
|
||||
let fieldError;
|
||||
|
||||
if (name === "notificationName") {
|
||||
const { error } = notificationEmailValidation.extract(name).validate(value);
|
||||
fieldError = error?.message;
|
||||
}
|
||||
|
||||
if (newNotification.type === 1 && name === "address") {
|
||||
const { error } = notificationEmailValidation.extract(name).validate(value);
|
||||
fieldError = error?.message;
|
||||
}
|
||||
|
||||
if (newNotification.type === 2 && name === "config") {
|
||||
// Validate only webhookUrl inside config
|
||||
const { error } = notificationWebhookValidation.extract("config").validate(value);
|
||||
fieldError = error?.message;
|
||||
}
|
||||
|
||||
// Set field-level error
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
[name]: fieldError,
|
||||
}));
|
||||
|
||||
setNotification(newNotification);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<Typography variant="h1">{t("createNotifications.title")}</Typography>
|
||||
<Stack
|
||||
component="form"
|
||||
onSubmit={onSubmit}
|
||||
noValidate
|
||||
gap={theme.spacing(12)}
|
||||
mt={theme.spacing(6)}
|
||||
>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">
|
||||
{t("createNotifications.nameSettings.title")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("createNotifications.nameSettings.description")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<TextInput
|
||||
label={t("createNotifications.nameSettings.nameLabel")}
|
||||
name="notificationName"
|
||||
placeholder={t("createNotifications.nameSettings.namePlaceholder")}
|
||||
value={notification.notificationName}
|
||||
onChange={onChange}
|
||||
error={Boolean(errors.notificationName)}
|
||||
helperText={errors["notificationName"]}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">
|
||||
{t("createNotifications.typeSettings.title")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("createNotifications.typeSettings.description")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<Select
|
||||
items={NOTIFICATION_TYPES}
|
||||
label="Type"
|
||||
name="type"
|
||||
value={notification.type}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
{notification.type === 1 && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">
|
||||
{t("createNotifications.emailSettings.title")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("createNotifications.emailSettings.description")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<TextInput
|
||||
label={t("createNotifications.emailSettings.emailLabel")}
|
||||
name="address"
|
||||
placeholder={t("createNotifications.emailSettings.emailPlaceholder")}
|
||||
value={notification.address}
|
||||
onChange={onChange}
|
||||
error={Boolean(errors.address)}
|
||||
helperText={errors["address"]}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
)}
|
||||
{notification.type === 2 && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">
|
||||
{t("createNotifications.slackSettings.title")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("createNotifications.slackSettings.description")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<TextInput
|
||||
label={t("createNotifications.slackSettings.webhookLabel")}
|
||||
value={notification.config.webhookUrl}
|
||||
error={Boolean(errors.config)}
|
||||
helperText={errors["config"]}
|
||||
onChange={(e) => {
|
||||
const updatedConfig = {
|
||||
...notification.config,
|
||||
webhookUrl: e.target.value,
|
||||
};
|
||||
|
||||
onChange({
|
||||
target: {
|
||||
name: "config",
|
||||
value: updatedConfig,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
)}
|
||||
{notification.type === 3 && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">
|
||||
{t("createNotifications.pagerdutySettings.title")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("createNotifications.pagerdutySettings.description")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<TextInput
|
||||
label={t("createNotifications.pagerdutySettings.integrationKeyLabel")}
|
||||
placeholder={t(
|
||||
"createNotifications.pagerdutySettings.integrationKeyPlaceholder"
|
||||
)}
|
||||
value={notification.config.routingKey}
|
||||
error={Boolean(errors.config)}
|
||||
helperText={errors["config"]}
|
||||
onChange={(e) => {
|
||||
const updatedConfig = {
|
||||
...notification.config,
|
||||
routingKey: e.target.value,
|
||||
};
|
||||
|
||||
onChange({
|
||||
target: {
|
||||
name: "config",
|
||||
value: updatedConfig,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
)}
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="accent"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateNotifications;
|
||||
@@ -0,0 +1,121 @@
|
||||
// Components
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Breadcrumbs from "../../Components/Breadcrumbs";
|
||||
import Button from "@mui/material/Button";
|
||||
import DataTable from "../../Components/Table";
|
||||
import Fallback from "../../Components/Fallback";
|
||||
// Utils
|
||||
import { useIsAdmin } from "../../Hooks/useIsAdmin";
|
||||
import { useState } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
useGetNotificationsByTeamId,
|
||||
useDeleteNotification,
|
||||
} from "../../Hooks/useNotifications";
|
||||
import { useTranslation } from "react-i18next";
|
||||
// Setup
|
||||
|
||||
const Notifications = () => {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const BREADCRUMBS = [{ name: "notifications", path: "/notifications" }];
|
||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||
const isAdmin = useIsAdmin();
|
||||
const [notifications, isLoading, error] = useGetNotificationsByTeamId(updateTrigger);
|
||||
const [deleteNotification, isDeleting, deleteError] = useDeleteNotification();
|
||||
const { t } = useTranslation();
|
||||
// Handlers
|
||||
const triggerUpdate = () => {
|
||||
setUpdateTrigger(!updateTrigger);
|
||||
};
|
||||
|
||||
const onDelete = (id) => {
|
||||
deleteNotification(id, triggerUpdate);
|
||||
};
|
||||
|
||||
const headers = [
|
||||
{
|
||||
id: "name",
|
||||
content: "Name",
|
||||
render: (row) => {
|
||||
return row.notificationName;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "type",
|
||||
content: "Type",
|
||||
render: (row) => {
|
||||
return row.type;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "target",
|
||||
content: "Target",
|
||||
render: (row) => {
|
||||
return row.address || row.config?.webhookUrl || row.config?.routingKey;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "platform",
|
||||
content: "Platform",
|
||||
render: (row) => {
|
||||
return row?.config?.platform || row.type;
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: "actions",
|
||||
content: "Actions",
|
||||
render: (row) => {
|
||||
return (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => onDelete(row._id)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (notifications?.length === 0) {
|
||||
return (
|
||||
<Fallback
|
||||
vowelStart={false}
|
||||
title={t("notifications.fallback.title")}
|
||||
checks={[t("notifications.fallback.checks")]}
|
||||
link="/notifications/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => navigate("/notifications/create")}
|
||||
>
|
||||
{t("notifications.createButton")}
|
||||
</Button>
|
||||
</Stack>
|
||||
<Typography variant="h1">{t("notifications.createTitle")}</Typography>
|
||||
<DataTable
|
||||
headers={headers}
|
||||
data={notifications}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notifications;
|
||||
Reference in new issue
Block a user