mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-21 00:48:45 -05:00
remove unused
This commit is contained in:
-419
@@ -1,419 +0,0 @@
|
||||
import { useState, useMemo, useEffect, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Typography,
|
||||
Box,
|
||||
Tabs,
|
||||
Tab,
|
||||
Stack,
|
||||
} from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import TabPanel from "./TabPanel.jsx";
|
||||
import TabComponent from "./TabComponent.jsx";
|
||||
import useNotifications from "../Hooks/useNotification.js";
|
||||
|
||||
// Define constants for notification types to avoid magic values
|
||||
const NOTIFICATION_TYPES = {
|
||||
SLACK: "slack",
|
||||
DISCORD: "discord",
|
||||
TELEGRAM: "telegram",
|
||||
WEBHOOK: "webhook",
|
||||
};
|
||||
|
||||
// Define constants for field IDs
|
||||
const FIELD_IDS = {
|
||||
WEBHOOK: "webhook",
|
||||
TOKEN: "token",
|
||||
CHAT_ID: "chatId",
|
||||
URL: "url",
|
||||
};
|
||||
|
||||
const NotificationIntegrationModal = ({
|
||||
open,
|
||||
onClose,
|
||||
monitor,
|
||||
setMonitor,
|
||||
// Optional prop to configure available notification types
|
||||
notificationTypes = null,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const [tabValue, setTabValue] = useState(0);
|
||||
|
||||
const [loading, _, sendTestNotification] = useNotifications();
|
||||
|
||||
// Helper to get the field state key with error handling
|
||||
const getFieldKey = useCallback(
|
||||
(typeId, fieldId) => {
|
||||
if (typeof typeId !== "string" || typeId === "") {
|
||||
throw new Error(t("errorInvalidTypeId"));
|
||||
}
|
||||
|
||||
if (typeof fieldId !== "string" || fieldId === "") {
|
||||
throw new Error(t("errorInvalidFieldId"));
|
||||
}
|
||||
|
||||
return `${typeId}${fieldId.charAt(0).toUpperCase() + fieldId.slice(1)}`;
|
||||
},
|
||||
[t]
|
||||
);
|
||||
|
||||
// Define notification types
|
||||
const DEFAULT_NOTIFICATION_TYPES = [
|
||||
{
|
||||
id: NOTIFICATION_TYPES.SLACK,
|
||||
label: t("notifications.slack.label"),
|
||||
description: t("notifications.slack.description"),
|
||||
fields: [
|
||||
{
|
||||
id: FIELD_IDS.WEBHOOK,
|
||||
label: t("notifications.slack.webhookLabel"),
|
||||
placeholder: t("notifications.slack.webhookPlaceholder"),
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: NOTIFICATION_TYPES.DISCORD,
|
||||
label: t("notifications.discord.label"),
|
||||
description: t("notifications.discord.description"),
|
||||
fields: [
|
||||
{
|
||||
id: FIELD_IDS.WEBHOOK,
|
||||
label: t("notifications.discord.webhookLabel"),
|
||||
placeholder: t("notifications.discord.webhookPlaceholder"),
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: NOTIFICATION_TYPES.TELEGRAM,
|
||||
label: t("notifications.telegram.label"),
|
||||
description: t("notifications.telegram.description"),
|
||||
fields: [
|
||||
{
|
||||
id: FIELD_IDS.TOKEN,
|
||||
label: t("notifications.telegram.tokenLabel"),
|
||||
placeholder: t("notifications.telegram.tokenPlaceholder"),
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
id: FIELD_IDS.CHAT_ID,
|
||||
label: t("notifications.telegram.chatIdLabel"),
|
||||
placeholder: t("notifications.telegram.chatIdPlaceholder"),
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: NOTIFICATION_TYPES.WEBHOOK,
|
||||
label: t("notifications.webhook.label"),
|
||||
description: t("notifications.webhook.description"),
|
||||
fields: [
|
||||
{
|
||||
id: FIELD_IDS.URL,
|
||||
label: t("notifications.webhook.urlLabel"),
|
||||
placeholder: t("notifications.webhook.urlPlaceholder"),
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Use provided notification types or default to our translated ones
|
||||
const activeNotificationTypes = notificationTypes || DEFAULT_NOTIFICATION_TYPES;
|
||||
|
||||
// Memoized function to initialize integrations state
|
||||
const initialIntegrationsState = useMemo(() => {
|
||||
const state = {};
|
||||
|
||||
activeNotificationTypes.forEach((type) => {
|
||||
// Add enabled flag for each notification type
|
||||
state[type.id] = false;
|
||||
|
||||
// Add state for each field in the notification type
|
||||
type.fields.forEach((field) => {
|
||||
const fieldKey = getFieldKey(type.id, field.id);
|
||||
state[fieldKey] = "";
|
||||
});
|
||||
});
|
||||
|
||||
return state;
|
||||
}, [activeNotificationTypes, getFieldKey]); // Only recompute when these dependencies change
|
||||
|
||||
const [integrations, setIntegrations] = useState(initialIntegrationsState);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
const extractNotificationValues = () => {
|
||||
const values = {};
|
||||
|
||||
if (!monitor?.notifications || !Array.isArray(monitor.notifications)) {
|
||||
return values;
|
||||
}
|
||||
|
||||
monitor.notifications.forEach((notification) => {
|
||||
// Handle notification based on its structure
|
||||
if (notification.type === "webhook" && notification.platform) {
|
||||
if (typeof notification.config === "undefined") return;
|
||||
const platform = notification.platform;
|
||||
values[platform] = true; // Set platform as enabled
|
||||
|
||||
// Extract configuration based on platform
|
||||
switch (platform) {
|
||||
case NOTIFICATION_TYPES.SLACK:
|
||||
case NOTIFICATION_TYPES.DISCORD:
|
||||
if (notification.config.webhookUrl) {
|
||||
values[getFieldKey(platform, FIELD_IDS.WEBHOOK)] =
|
||||
notification.config.webhookUrl;
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_TYPES.TELEGRAM:
|
||||
if (notification.config.botToken) {
|
||||
values[getFieldKey(platform, FIELD_IDS.TOKEN)] =
|
||||
notification.config.botToken;
|
||||
}
|
||||
if (notification.config.chatId) {
|
||||
values[getFieldKey(platform, FIELD_IDS.CHAT_ID)] =
|
||||
notification.config.chatId;
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_TYPES.WEBHOOK:
|
||||
if (notification.config.webhookUrl) {
|
||||
values[getFieldKey(platform, FIELD_IDS.URL)] =
|
||||
notification.config.webhookUrl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return values;
|
||||
};
|
||||
|
||||
const extractedValues = extractNotificationValues();
|
||||
setIntegrations((prev) => ({
|
||||
...prev,
|
||||
...extractedValues,
|
||||
}));
|
||||
}
|
||||
}, [open, monitor, getFieldKey]);
|
||||
|
||||
const handleChangeTab = (event, newValue) => {
|
||||
setTabValue(newValue);
|
||||
};
|
||||
|
||||
const handleIntegrationChange = (type, checked) => {
|
||||
setIntegrations((prev) => ({
|
||||
...prev,
|
||||
[type]: checked,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleInputChange = (type, value) => {
|
||||
setIntegrations((prev) => ({
|
||||
...prev,
|
||||
[type]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleTestNotification = async (type) => {
|
||||
// Get the notification type details
|
||||
const notificationType = activeNotificationTypes.find((t) => t.id === type);
|
||||
|
||||
if (typeof notificationType === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare config object based on notification type
|
||||
const config = {};
|
||||
|
||||
// Add each field value to the config object
|
||||
notificationType.fields.forEach((field) => {
|
||||
const fieldKey = getFieldKey(type, field.id);
|
||||
config[field.id] = integrations[fieldKey];
|
||||
});
|
||||
|
||||
await sendTestNotification(type, config);
|
||||
};
|
||||
|
||||
// In NotificationIntegrationModal.jsx, update the handleSave function:
|
||||
|
||||
const handleSave = () => {
|
||||
// Get existing notifications
|
||||
const notifications = [...(monitor?.notifications || [])];
|
||||
|
||||
// Get all notification types IDs
|
||||
const existingTypes = activeNotificationTypes.map((type) => type.id);
|
||||
|
||||
// Filter out notifications that are configurable in this modal
|
||||
const filteredNotifications = notifications.filter((notification) => {
|
||||
if (notification.platform) {
|
||||
return !existingTypes.includes(notification.platform);
|
||||
}
|
||||
|
||||
return !existingTypes.includes(notification.type);
|
||||
});
|
||||
|
||||
// Add each enabled notification with its configured fields
|
||||
activeNotificationTypes.forEach((type) => {
|
||||
if (integrations[type.id]) {
|
||||
let notificationObject = {
|
||||
type: "webhook",
|
||||
platform: type.id, // Set platform to identify the specific service
|
||||
config: {},
|
||||
};
|
||||
|
||||
// Configure based on notification type
|
||||
switch (type.id) {
|
||||
case "slack":
|
||||
case "discord":
|
||||
notificationObject.config.webhookUrl =
|
||||
integrations[getFieldKey(type.id, "webhook")];
|
||||
break;
|
||||
case "telegram":
|
||||
notificationObject.config.botToken =
|
||||
integrations[getFieldKey(type.id, "token")];
|
||||
notificationObject.config.chatId =
|
||||
integrations[getFieldKey(type.id, "chatId")];
|
||||
break;
|
||||
case "webhook":
|
||||
notificationObject.config.webhookUrl =
|
||||
integrations[getFieldKey(type.id, "url")];
|
||||
break;
|
||||
}
|
||||
|
||||
filteredNotifications.push(notificationObject);
|
||||
}
|
||||
});
|
||||
|
||||
// Update monitor with new notifications
|
||||
setMonitor((prev) => ({
|
||||
...prev,
|
||||
notifications: filteredNotifications,
|
||||
}));
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
fullWidth
|
||||
maxWidth="md"
|
||||
sx={{
|
||||
"& .MuiDialog-paper": {
|
||||
width: `calc(80% - ${theme.spacing(40)})`,
|
||||
maxWidth: `${theme.breakpoints.values.md - 70}px`,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogContent>
|
||||
<Stack
|
||||
direction="row"
|
||||
sx={{
|
||||
height: `calc(30vh - ${theme.spacing(20)})`,
|
||||
}}
|
||||
>
|
||||
{/* Left sidebar with tabs */}
|
||||
<Box
|
||||
sx={{
|
||||
borderRight: 1,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
width: "30%",
|
||||
maxWidth: theme.spacing(120),
|
||||
pr: theme.spacing(10),
|
||||
}}
|
||||
>
|
||||
<Typography variant="h2">
|
||||
{t("notifications.addOrEditNotifications")}
|
||||
</Typography>
|
||||
|
||||
<Tabs
|
||||
orientation="vertical"
|
||||
variant="scrollable"
|
||||
value={tabValue}
|
||||
onChange={handleChangeTab}
|
||||
aria-label="Notification tabs"
|
||||
>
|
||||
{activeNotificationTypes.map((type) => (
|
||||
<Tab
|
||||
key={type.id}
|
||||
label={type.label}
|
||||
orientation="vertical"
|
||||
disableRipple
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
{/* Right side content */}
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
pl: theme.spacing(7.5),
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{activeNotificationTypes.map((type, index) => (
|
||||
<TabPanel
|
||||
key={type.id}
|
||||
value={tabValue}
|
||||
index={index}
|
||||
>
|
||||
<TabComponent
|
||||
type={type}
|
||||
integrations={integrations}
|
||||
handleIntegrationChange={handleIntegrationChange}
|
||||
handleInputChange={handleInputChange}
|
||||
handleTestNotification={handleTestNotification}
|
||||
isLoading={loading}
|
||||
/>
|
||||
</TabPanel>
|
||||
))}
|
||||
</Box>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions
|
||||
sx={{
|
||||
p: theme.spacing(4),
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
mb: theme.spacing(5),
|
||||
mr: theme.spacing(5),
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={handleSave}
|
||||
loading={loading}
|
||||
sx={{
|
||||
width: "auto",
|
||||
minWidth: theme.spacing(60),
|
||||
px: theme.spacing(8),
|
||||
}}
|
||||
>
|
||||
{t("commonSave")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
NotificationIntegrationModal.propTypes = {
|
||||
open: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
monitor: PropTypes.object.isRequired,
|
||||
setMonitor: PropTypes.func.isRequired,
|
||||
notificationTypes: PropTypes.array,
|
||||
};
|
||||
|
||||
export default NotificationIntegrationModal;
|
||||
@@ -1,115 +0,0 @@
|
||||
import React from "react";
|
||||
import { Typography, Box, Button, CircularProgress } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import TextInput from "../../Inputs/TextInput/index.jsx";
|
||||
import Checkbox from "../../Inputs/Checkbox/index.jsx";
|
||||
|
||||
const TabComponent = ({
|
||||
type,
|
||||
integrations,
|
||||
handleIntegrationChange,
|
||||
handleInputChange,
|
||||
handleTestNotification,
|
||||
isLoading,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Helper to get the field state key (e.g., slackWebhook, telegramToken)
|
||||
const getFieldKey = (typeId, fieldId) => {
|
||||
return `${typeId}${fieldId.charAt(0).toUpperCase() + fieldId.slice(1)}`;
|
||||
};
|
||||
|
||||
// Check if all fields have values to enable test button
|
||||
const areAllFieldsFilled = () => {
|
||||
return type.fields.every((field) => {
|
||||
const fieldKey = getFieldKey(type.id, field.id);
|
||||
return integrations[fieldKey];
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
component="h4"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
}}
|
||||
>
|
||||
{type.label}
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
mt: theme.spacing(0.5),
|
||||
mb: theme.spacing(1.5),
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
}}
|
||||
>
|
||||
{type.description}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ pl: theme.spacing(1.5) }}>
|
||||
<Checkbox
|
||||
id={`enable-${type.id}`}
|
||||
label={t("notifications.enableNotifications", { platform: type.label })}
|
||||
isChecked={integrations[type.id]}
|
||||
onChange={(e) => handleIntegrationChange(type.id, e.target.checked)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{type.fields.map((field) => {
|
||||
const fieldKey = getFieldKey(type.id, field.id);
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={field.id}
|
||||
sx={{ mt: theme.spacing(1) }}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
mb: theme.spacing(2),
|
||||
fontWeight: "bold",
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
}}
|
||||
>
|
||||
{field.label}
|
||||
</Typography>
|
||||
|
||||
<TextInput
|
||||
id={`${type.id}-${field.id}`}
|
||||
type={field.type}
|
||||
placeholder={field.placeholder}
|
||||
value={integrations[fieldKey]}
|
||||
onChange={(e) => handleInputChange(fieldKey, e.target.value)}
|
||||
disabled={!integrations[type.id] || isLoading}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
<Box sx={{ mt: theme.spacing(1) }}>
|
||||
<Button
|
||||
variant="text"
|
||||
color="info"
|
||||
onClick={() => handleTestNotification(type.id)}
|
||||
disabled={!integrations[type.id] || !areAllFieldsFilled() || isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<CircularProgress
|
||||
size={theme.spacing(8)}
|
||||
sx={{ mr: theme.spacing(1), color: theme.palette.accent.main }}
|
||||
/>
|
||||
) : null}
|
||||
{t("notifications.testNotification")}
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabComponent;
|
||||
@@ -1,41 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Box } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
/**
|
||||
* TabPanel component that displays content for the selected tab.
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props - The component props.
|
||||
* @param {React.ReactNode} props.children - The content to be displayed when this tab panel is selected.
|
||||
* @param {number} props.value - The currently selected tab value.
|
||||
* @param {number} props.index - The index of this specific tab panel.
|
||||
* @param {Object} props.other - Any additional props to be spread to the root element.
|
||||
* @returns {React.ReactElement|null} The rendered tab panel or null if not selected.
|
||||
*/
|
||||
function TabPanel({ children, value, index, ...other }) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`notification-tabpanel-${index}`}
|
||||
aria-labelledby={`notification-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && <Box sx={{ pt: theme.spacing(3) }}>{children}</Box>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
TabPanel.propTypes = {
|
||||
children: PropTypes.node,
|
||||
|
||||
index: PropTypes.number.isRequired,
|
||||
|
||||
value: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export default TabPanel;
|
||||
@@ -1,129 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { networkService } from "../../../../Utils/NetworkService.js";
|
||||
import { createToast } from "../../../../Utils/toastUtils.jsx";
|
||||
|
||||
// Define constants for notification types to avoid magic values
|
||||
const NOTIFICATION_TYPES = {
|
||||
SLACK: "slack",
|
||||
DISCORD: "discord",
|
||||
TELEGRAM: "telegram",
|
||||
WEBHOOK: "webhook",
|
||||
};
|
||||
|
||||
// Define constants for field IDs
|
||||
const FIELD_IDS = {
|
||||
WEBHOOK: "webhook",
|
||||
TOKEN: "token",
|
||||
CHAT_ID: "chatId",
|
||||
URL: "url",
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom hook for notification-related operations
|
||||
*/
|
||||
const useNotifications = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(undefined);
|
||||
const { t } = useTranslation();
|
||||
|
||||
/**
|
||||
* Send a test notification
|
||||
* @param {string} type - The notification type (slack, discord, telegram, webhook)
|
||||
* @param {object} config - Configuration object with necessary params
|
||||
*/
|
||||
const sendTestNotification = async (type, config) => {
|
||||
setLoading(true);
|
||||
setError(undefined);
|
||||
|
||||
// Validation based on notification type
|
||||
let payload = { platform: type };
|
||||
let isValid = true;
|
||||
let errorMessage = "";
|
||||
|
||||
switch (type) {
|
||||
case NOTIFICATION_TYPES.SLACK:
|
||||
payload.webhookUrl = config.webhook;
|
||||
if (typeof payload.webhookUrl === "undefined" || payload.webhookUrl === "") {
|
||||
isValid = false;
|
||||
errorMessage = t("notifications.slack.webhookRequired");
|
||||
}
|
||||
break;
|
||||
|
||||
case NOTIFICATION_TYPES.DISCORD:
|
||||
payload.webhookUrl = config.webhook;
|
||||
if (typeof payload.webhookUrl === "undefined" || payload.webhookUrl === "") {
|
||||
isValid = false;
|
||||
errorMessage = t("notifications.discord.webhookRequired");
|
||||
}
|
||||
break;
|
||||
|
||||
case NOTIFICATION_TYPES.TELEGRAM:
|
||||
payload.botToken = config.token;
|
||||
payload.chatId = config.chatId;
|
||||
if (
|
||||
typeof payload.botToken === "undefined" ||
|
||||
payload.botToken === "" ||
|
||||
typeof payload.chatId === "undefined" ||
|
||||
payload.chatId === ""
|
||||
) {
|
||||
isValid = false;
|
||||
errorMessage = t("notifications.telegram.fieldsRequired");
|
||||
}
|
||||
break;
|
||||
|
||||
case NOTIFICATION_TYPES.WEBHOOK:
|
||||
payload.webhookUrl = config.url;
|
||||
payload.platform = NOTIFICATION_TYPES.SLACK;
|
||||
if (typeof payload.webhookUrl === "undefined" || payload.webhookUrl === "") {
|
||||
isValid = false;
|
||||
errorMessage = t("notifications.webhook.urlRequired");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
isValid = false;
|
||||
errorMessage = t("notifications.unsupportedType");
|
||||
}
|
||||
|
||||
// If validation fails, show error and return
|
||||
if (isValid === false) {
|
||||
createToast({
|
||||
body: errorMessage,
|
||||
variant: "error",
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await networkService.testNotification({
|
||||
platform: type,
|
||||
payload: payload,
|
||||
});
|
||||
|
||||
if (response.data.success === true) {
|
||||
createToast({
|
||||
body: t("notifications.testSuccess"),
|
||||
variant: "info",
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.data.msg || t("notifications.testFailed"));
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg =
|
||||
error.response?.data?.msg || error.message || t("notifications.networkError");
|
||||
createToast({
|
||||
body: `${t("notifications.testFailed")}: ${errorMsg}`,
|
||||
variant: "error",
|
||||
});
|
||||
setError(errorMsg);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return [loading, error, sendTestNotification];
|
||||
};
|
||||
|
||||
export default useNotifications;
|
||||
@@ -1,36 +1,7 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { createToast } from "../Utils/toastUtils.jsx";
|
||||
import { networkService } from "../main.jsx";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { NOTIFICATION_TYPES } from "../Pages/Notifications/utils.js";
|
||||
|
||||
const useCreateNotification = () => {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const createNotification = async (notification) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await networkService.createNotification({ notification });
|
||||
createToast({
|
||||
body: t("notifications.create.success"),
|
||||
});
|
||||
navigate("/notifications");
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
createToast({
|
||||
body: t("notifications.create.failed"),
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return [createNotification, isLoading, error];
|
||||
};
|
||||
|
||||
const useGetNotificationsByTeamId = (updateTrigger) => {
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
@@ -60,119 +31,6 @@ const useGetNotificationsByTeamId = (updateTrigger) => {
|
||||
return [notifications, isLoading, error];
|
||||
};
|
||||
|
||||
const useDeleteNotification = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const deleteNotification = async (id, callback) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await networkService.deleteNotificationById({ id });
|
||||
createToast({
|
||||
body: t("notifications.delete.success"),
|
||||
});
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
createToast({
|
||||
body: t("notifications.delete.failed"),
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return [deleteNotification, isLoading, error];
|
||||
};
|
||||
|
||||
const useGetNotificationById = (id, setNotification) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const getNotificationById = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await networkService.getNotificationById({ id });
|
||||
|
||||
const notification = response?.data?.data ?? null;
|
||||
|
||||
const notificationData = {
|
||||
address: notification?.address,
|
||||
notificationName: notification?.notificationName,
|
||||
type: NOTIFICATION_TYPES.find((type) => type.value === notification?.type)?.id,
|
||||
};
|
||||
|
||||
setNotification(notificationData);
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [id, setNotification]);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
getNotificationById();
|
||||
}
|
||||
}, [getNotificationById, id]);
|
||||
|
||||
return [isLoading, error];
|
||||
};
|
||||
|
||||
const useEditNotification = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const editNotification = async (id, notification) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await networkService.editNotification({ id, notification });
|
||||
createToast({
|
||||
body: t("notifications.edit.success"),
|
||||
});
|
||||
navigate(`/notifications`);
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
createToast({
|
||||
body: t("notifications.edit.failed"),
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return [editNotification, isLoading, error];
|
||||
};
|
||||
|
||||
const useTestNotification = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const testNotification = async (notification) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await networkService.testNotification({ notification });
|
||||
createToast({
|
||||
body: t("notifications.test.success"),
|
||||
});
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
createToast({
|
||||
body: error?.response?.data?.msg || t("notifications.test.failed"),
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return [testNotification, isLoading, error];
|
||||
};
|
||||
|
||||
const useTestAllNotifications = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(undefined);
|
||||
@@ -197,12 +55,4 @@ const useTestAllNotifications = () => {
|
||||
return [testAllNotifications, isLoading, error];
|
||||
};
|
||||
|
||||
export {
|
||||
useCreateNotification,
|
||||
useGetNotificationsByTeamId,
|
||||
useDeleteNotification,
|
||||
useGetNotificationById,
|
||||
useEditNotification,
|
||||
useTestNotification,
|
||||
useTestAllNotifications,
|
||||
};
|
||||
export { useGetNotificationsByTeamId, useTestAllNotifications };
|
||||
|
||||
@@ -1,320 +0,0 @@
|
||||
// Components
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
|
||||
import Button from "@mui/material/Button";
|
||||
import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
|
||||
import Box from "@mui/material/Box";
|
||||
import Select from "@/Components/v1/Inputs/Select/index.jsx";
|
||||
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
|
||||
import Dialog from "@/Components/v1/Dialog/index.jsx";
|
||||
|
||||
// Utils
|
||||
import { useState } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import {
|
||||
useCreateNotification,
|
||||
useGetNotificationById,
|
||||
useEditNotification,
|
||||
useTestNotification,
|
||||
useDeleteNotification,
|
||||
} from "../../../Hooks/useNotifications.js";
|
||||
import { notificationValidation } from "../../../Validation/validation.js";
|
||||
import { createToast } from "../../../Utils/toastUtils.jsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import {
|
||||
NOTIFICATION_TYPES,
|
||||
TITLE_MAP,
|
||||
DESCRIPTION_MAP,
|
||||
LABEL_MAP,
|
||||
PLACEHOLDER_MAP,
|
||||
} from "../utils.js";
|
||||
|
||||
// Setup
|
||||
|
||||
const CreateNotifications = () => {
|
||||
const { notificationId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const [createNotification, isCreating] = useCreateNotification();
|
||||
const [editNotification, isEditing] = useEditNotification();
|
||||
const [testNotification, isTesting] = useTestNotification();
|
||||
const [deleteNotification, isDeleting] = useDeleteNotification();
|
||||
|
||||
const BREADCRUMBS = [
|
||||
{ name: "notifications", path: "/notifications" },
|
||||
{
|
||||
name: notificationId ? "edit" : "create",
|
||||
path: notificationId ? `/notifications/${notificationId}` : "/notifications/create",
|
||||
},
|
||||
];
|
||||
|
||||
// Redux state
|
||||
|
||||
// local state
|
||||
const [notification, setNotification] = useState({
|
||||
notificationName: "",
|
||||
address: "",
|
||||
type: NOTIFICATION_TYPES[0].id,
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [notificationIsLoading] = useGetNotificationById(notificationId, setNotification);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const getNotificationTypeValue = (typeId) => {
|
||||
return NOTIFICATION_TYPES.find((type) => type.id === typeId)?.value || "email";
|
||||
};
|
||||
|
||||
const extractError = (error, field) =>
|
||||
error?.details.find((d) => d.path.includes(field))?.message;
|
||||
|
||||
// handlers
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const form = {
|
||||
...notification,
|
||||
type: getNotificationTypeValue(notification.type),
|
||||
};
|
||||
|
||||
let error = null;
|
||||
|
||||
error = notificationValidation.validate(form, { abortEarly: false }).error;
|
||||
|
||||
if (error) {
|
||||
const newErrors = {};
|
||||
error.details.forEach((err) => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
createToast({ body: Object.values(newErrors)[0] });
|
||||
setErrors(newErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
if (notificationId) {
|
||||
editNotification(notificationId, form);
|
||||
} else {
|
||||
createNotification(form);
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
let rawNotification = { ...notification, [name]: value };
|
||||
let newNotification = {
|
||||
...rawNotification,
|
||||
type: getNotificationTypeValue(rawNotification.type),
|
||||
};
|
||||
|
||||
const { error } = notificationValidation.validate(newNotification, {
|
||||
abortEarly: false,
|
||||
});
|
||||
let validationError = { ...errors };
|
||||
|
||||
if (name === "type") {
|
||||
validationError["type"] = extractError(error, "type");
|
||||
validationError["address"] = extractError(error, "address");
|
||||
} else {
|
||||
validationError[name] = extractError(error, name);
|
||||
}
|
||||
|
||||
setNotification(rawNotification);
|
||||
setErrors(validationError);
|
||||
};
|
||||
|
||||
const onTestNotification = () => {
|
||||
const form = {
|
||||
...notification,
|
||||
type: getNotificationTypeValue(notification.type),
|
||||
};
|
||||
|
||||
let error = null;
|
||||
|
||||
error = notificationValidation.validate(form, { abortEarly: false }).error;
|
||||
|
||||
if (error) {
|
||||
const newErrors = {};
|
||||
error.details.forEach((err) => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
createToast({ body: Object.values(newErrors)[0] });
|
||||
setErrors(newErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
testNotification(form);
|
||||
};
|
||||
|
||||
const onDelete = () => {
|
||||
if (notificationId) {
|
||||
deleteNotification(notificationId, () => navigate("/notifications"));
|
||||
}
|
||||
};
|
||||
|
||||
const type = getNotificationTypeValue(notification.type);
|
||||
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"
|
||||
variant="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"
|
||||
variant="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>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{t(TITLE_MAP[type])}
|
||||
</Typography>
|
||||
<Typography component="p">{t(DESCRIPTION_MAP[type])}</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
{type === "matrix" ? (
|
||||
<>
|
||||
<TextInput
|
||||
label={t("createNotifications.matrixSettings.homeserverLabel")}
|
||||
name="homeserverUrl"
|
||||
placeholder={t(
|
||||
"createNotifications.matrixSettings.homeserverPlaceholder"
|
||||
)}
|
||||
value={notification.homeserverUrl || ""}
|
||||
onChange={onChange}
|
||||
error={Boolean(errors.homeserverUrl)}
|
||||
helperText={errors["homeserverUrl"]}
|
||||
/>
|
||||
<TextInput
|
||||
label={t("createNotifications.matrixSettings.roomIdLabel")}
|
||||
name="roomId"
|
||||
placeholder={t("createNotifications.matrixSettings.roomIdPlaceholder")}
|
||||
value={notification.roomId || ""}
|
||||
onChange={onChange}
|
||||
error={Boolean(errors.roomId)}
|
||||
helperText={errors["roomId"]}
|
||||
/>
|
||||
<TextInput
|
||||
label={t("createNotifications.matrixSettings.accessTokenLabel")}
|
||||
name="accessToken"
|
||||
type="password"
|
||||
placeholder={t(
|
||||
"createNotifications.matrixSettings.accessTokenPlaceholder"
|
||||
)}
|
||||
value={notification.accessToken || ""}
|
||||
onChange={onChange}
|
||||
error={Boolean(errors.accessToken)}
|
||||
helperText={errors["accessToken"]}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<TextInput
|
||||
label={t(LABEL_MAP[type])}
|
||||
name="address"
|
||||
placeholder={t(PLACEHOLDER_MAP[type])}
|
||||
value={notification.address}
|
||||
onChange={onChange}
|
||||
error={Boolean(errors.address)}
|
||||
helperText={errors["address"]}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</ConfigBox>{" "}
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
spacing={theme.spacing(2)}
|
||||
>
|
||||
<Button
|
||||
loading={isTesting}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={onTestNotification}
|
||||
>
|
||||
{t("createNotifications.testNotification")}
|
||||
</Button>
|
||||
{notificationId && (
|
||||
<Button
|
||||
loading={isDeleting}
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
{t("delete")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
loading={isCreating || isEditing || notificationIsLoading}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="accent"
|
||||
>
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
onCancel={() => setIsOpen(false)}
|
||||
title={t("createNotifications.dialogDeleteTitle")}
|
||||
confirmationButtonLabel={t("createNotifications.dialogDeleteConfirm")}
|
||||
onConfirm={onDelete}
|
||||
isLoading={isDeleting}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateNotifications;
|
||||
@@ -1,44 +0,0 @@
|
||||
export const NOTIFICATION_TYPES = [
|
||||
{ id: 1, name: "E-mail", value: "email" },
|
||||
{ id: 2, name: "Slack", value: "slack" },
|
||||
{ id: 3, name: "PagerDuty", value: "pager_duty" },
|
||||
{ id: 4, name: "Webhook", value: "webhook" },
|
||||
{ id: 5, name: "Discord", value: "discord" },
|
||||
{ id: 6, name: "Matrix", value: "matrix" },
|
||||
];
|
||||
|
||||
export const TITLE_MAP = {
|
||||
email: "createNotifications.emailSettings.title",
|
||||
slack: "createNotifications.slackSettings.title",
|
||||
pager_duty: "createNotifications.pagerdutySettings.title",
|
||||
webhook: "createNotifications.webhookSettings.title",
|
||||
discord: "createNotifications.discordSettings.title",
|
||||
matrix: "createNotifications.matrixSettings.title",
|
||||
};
|
||||
|
||||
export const DESCRIPTION_MAP = {
|
||||
email: "createNotifications.emailSettings.description",
|
||||
slack: "createNotifications.slackSettings.description",
|
||||
pager_duty: "createNotifications.pagerdutySettings.description",
|
||||
webhook: "createNotifications.webhookSettings.description",
|
||||
discord: "createNotifications.discordSettings.description",
|
||||
matrix: "createNotifications.matrixSettings.description",
|
||||
};
|
||||
|
||||
export const LABEL_MAP = {
|
||||
email: "createNotifications.emailSettings.emailLabel",
|
||||
slack: "createNotifications.slackSettings.webhookLabel",
|
||||
pager_duty: "createNotifications.pagerdutySettings.integrationKeyLabel",
|
||||
webhook: "createNotifications.webhookSettings.webhookLabel",
|
||||
discord: "createNotifications.discordSettings.webhookLabel",
|
||||
matrix: "createNotifications.matrixSettings.homeserverLabel",
|
||||
};
|
||||
|
||||
export const PLACEHOLDER_MAP = {
|
||||
email: "createNotifications.emailSettings.emailPlaceholder",
|
||||
slack: "createNotifications.slackSettings.webhookPlaceholder",
|
||||
pager_duty: "createNotifications.pagerdutySettings.integrationKeyPlaceholder",
|
||||
webhook: "createNotifications.webhookSettings.webhookPlaceholder",
|
||||
discord: "createNotifications.discordSettings.webhookPlaceholder",
|
||||
matrix: "createNotifications.matrixSettings.homeserverPlaceholder",
|
||||
};
|
||||
Reference in New Issue
Block a user