diff --git a/src/Components/NotificationIntegrationModal/NotificationIntegrationModal.jsx b/src/Components/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx
similarity index 78%
rename from src/Components/NotificationIntegrationModal/NotificationIntegrationModal.jsx
rename to src/Components/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx
index 6608b7c65..85b5232f3 100644
--- a/src/Components/NotificationIntegrationModal/NotificationIntegrationModal.jsx
+++ b/src/Components/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx
@@ -1,6 +1,5 @@
import { useState, useMemo } from "react";
import { useTranslation } from "react-i18next";
-import { toast } from 'react-toastify';
import {
Dialog,
@@ -10,11 +9,13 @@ import {
Typography,
Box,
Tabs,
- Tab
+ Tab,
+ CircularProgress
} from "@mui/material";
import { useTheme } from "@emotion/react";
import TabPanel from "./TabPanel";
import TabComponent from "./TabComponent";
+import useNotifications from "../Hooks/useNotification";
const NotificationIntegrationModal = ({
open,
@@ -28,6 +29,8 @@ const NotificationIntegrationModal = ({
const theme = useTheme();
const [tabValue, setTabValue] = useState(0);
+ const { loading, sendTestNotification } = useNotifications();
+
// Define notification types
const DEFAULT_NOTIFICATION_TYPES = [
{
@@ -144,74 +147,18 @@ const NotificationIntegrationModal = ({
return `${typeId}${fieldId.charAt(0).toUpperCase() + fieldId.slice(1)}`;
};
- // Prepare request payload based on notification type
- let payload = { platform: type };
+ // Prepare config object based on notification type
+ const config = {};
- switch(type) {
- case 'slack':
- payload.webhookUrl = integrations[getFieldKey('slack', 'webhook')];
- if (!payload.webhookUrl) {
- toast.error('Please enter a Slack webhook URL first.');
- return;
- }
- break;
-
- case 'discord':
- payload.webhookUrl = integrations[getFieldKey('discord', 'webhook')];
- if (!payload.webhookUrl) {
- toast.error('Please enter a Discord webhook URL first.');
- return;
- }
- break;
-
- case 'telegram':
- payload.botToken = integrations[getFieldKey('telegram', 'token')];
- payload.chatId = integrations[getFieldKey('telegram', 'chatId')];
- if (!payload.botToken || !payload.chatId) {
- toast.error('Please enter both Telegram bot token and chat ID.');
- return;
- }
- break;
-
- case 'webhook':
- payload.webhookUrl = integrations[getFieldKey('webhook', 'url')];
- payload.platform = 'slack'; // Use slack as platform for webhooks
- if (!payload.webhookUrl) {
- toast.error('Please enter a webhook URL first.');
- return;
- }
- break;
-
- default:
- toast.error('This notification type cannot be tested.');
- return;
- }
+ // Add each field value to the config object
+ notificationType.fields.forEach(field => {
+ const fieldKey = getFieldKey(type, field.id);
+ config[field.id] = integrations[fieldKey];
+ });
- try {
- const apiUrl = `${import.meta.env.VITE_APP_API_BASE_URL || 'http://localhost:5000'}/api/v1/notifications/test-webhook`;
-
-
- const response = await fetch(apiUrl, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(payload),
- });
-
- const data = await response.json();
-
- if (response.ok && data.success) {
- toast.success('Test notification sent successfully!');
- } else {
- throw new Error(data.msg || 'Failed to send test notification');
- }
- } catch (error) {
- toast.error(`Failed to send test notification: ${error.message}`);
- }
+ await sendTestNotification(type, config);
};
-
const handleSave = () => {
//notifications array for selected integrations
const notifications = [...(monitor?.notifications || [])];
@@ -318,6 +265,7 @@ const NotificationIntegrationModal = ({
handleIntegrationChange={handleIntegrationChange}
handleInputChange={handleInputChange}
handleTestNotification={handleTestNotification}
+ isLoading={loading}
/>
))}
@@ -335,13 +283,14 @@ const NotificationIntegrationModal = ({
variant="contained"
color="accent"
onClick={handleSave}
+ disabled={loading}
sx={{
width: 'auto',
minWidth: theme.spacing(60),
px: theme.spacing(8)
}}
>
- {t('common.save', 'Save')}
+ {loading ? : t('common.save', 'Save')}
diff --git a/src/Components/NotificationIntegrationModal/TabComponent.jsx b/src/Components/NotificationIntegrationModal/Components/TabComponent.jsx
similarity index 86%
rename from src/Components/NotificationIntegrationModal/TabComponent.jsx
rename to src/Components/NotificationIntegrationModal/Components/TabComponent.jsx
index 89216a8c1..df13ca6f6 100644
--- a/src/Components/NotificationIntegrationModal/TabComponent.jsx
+++ b/src/Components/NotificationIntegrationModal/Components/TabComponent.jsx
@@ -2,19 +2,21 @@ import React from "react";
import {
Typography,
Box,
- Button
+ Button,
+ CircularProgress
} from "@mui/material";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
-import TextInput from "../../../src/Components/Inputs/TextInput";
-import Checkbox from "../../../src/Components/Inputs/Checkbox";
+import TextInput from "../../../Components/Inputs/TextInput";
+import Checkbox from "../../../Components/Inputs/Checkbox";
const TabComponent = ({
type,
integrations,
handleIntegrationChange,
handleInputChange,
- handleTestNotification
+ handleTestNotification,
+ isLoading
}) => {
const theme = useTheme();
const { t } = useTranslation();
@@ -55,6 +57,7 @@ const TabComponent = ({
label={t('notifications.enableNotifications', { platform: type.label })}
isChecked={integrations[type.id]}
onChange={(e) => handleIntegrationChange(type.id, e.target.checked)}
+ disabled={isLoading}
/>
@@ -77,7 +80,7 @@ const TabComponent = ({
placeholder={field.placeholder}
value={integrations[fieldKey]}
onChange={(e) => handleInputChange(fieldKey, e.target.value)}
- disabled={!integrations[type.id]}
+ disabled={!integrations[type.id] || isLoading}
/>
);
@@ -88,8 +91,11 @@ const TabComponent = ({
variant="text"
color="info"
onClick={() => handleTestNotification(type.id)}
- disabled={!integrations[type.id] || !areAllFieldsFilled()}
+ disabled={!integrations[type.id] || !areAllFieldsFilled() || isLoading}
>
+ {isLoading ? (
+
+ ) : null}
{t('notifications.testNotification')}
diff --git a/src/Components/NotificationIntegrationModal/TabPanel.jsx b/src/Components/NotificationIntegrationModal/Components/TabPanel.jsx
similarity index 100%
rename from src/Components/NotificationIntegrationModal/TabPanel.jsx
rename to src/Components/NotificationIntegrationModal/Components/TabPanel.jsx
diff --git a/src/Components/NotificationIntegrationModal/Hooks/useNotification.js b/src/Components/NotificationIntegrationModal/Hooks/useNotification.js
new file mode 100644
index 000000000..03ef99163
--- /dev/null
+++ b/src/Components/NotificationIntegrationModal/Hooks/useNotification.js
@@ -0,0 +1,100 @@
+import { useState } from 'react';
+import { toast } from 'react-toastify';
+import { useTranslation } from "react-i18next";
+import { networkService } from '../../../Utils/NetworkService';
+
+/**
+ * Custom hook for notification-related operations
+ */
+const useNotifications = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ 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(null);
+
+ // Validation based on notification type
+ let payload = { platform: type };
+ let isValid = true;
+ let errorMessage = '';
+
+ switch(type) {
+ case 'slack':
+ payload.webhookUrl = config.webhook;
+ if (!payload.webhookUrl) {
+ isValid = false;
+ errorMessage = t('notifications.slack.webhookRequired', 'Please enter a Slack webhook URL first.');
+ }
+ break;
+
+ case 'discord':
+ payload.webhookUrl = config.webhook;
+ if (!payload.webhookUrl) {
+ isValid = false;
+ errorMessage = t('notifications.discord.webhookRequired', 'Please enter a Discord webhook URL first.');
+ }
+ break;
+
+ case 'telegram':
+ payload.botToken = config.token;
+ payload.chatId = config.chatId;
+ if (!payload.botToken || !payload.chatId) {
+ isValid = false;
+ errorMessage = t('notifications.telegram.fieldsRequired', 'Please enter both Telegram bot token and chat ID.');
+ }
+ break;
+
+ case 'webhook':
+ payload.webhookUrl = config.url;
+ payload.platform = 'slack'; // Use slack as platform for webhooks
+ if (!payload.webhookUrl) {
+ isValid = false;
+ errorMessage = t('notifications.webhook.urlRequired', 'Please enter a webhook URL first.');
+ }
+ break;
+
+ default:
+ isValid = false;
+ errorMessage = t('notifications.unsupportedType', 'This notification type cannot be tested.');
+ }
+
+ // If validation fails, show error and return
+ if (!isValid) {
+ toast.error(errorMessage);
+ setLoading(false);
+ return;
+ }
+
+ try {
+ // Use your existing NetworkService to make the API call
+ const response = await networkService.axiosInstance.post('/notifications/test-webhook', payload);
+
+ if (response.data.success) {
+ toast.success(t('notifications.testSuccess', 'Test notification sent successfully!'));
+ } else {
+ throw new Error(response.data.msg || t('notifications.testFailed', 'Failed to send test notification'));
+ }
+ } catch (error) {
+ const errorMsg = error.response?.data?.msg || error.message || t('notifications.networkError', 'Network error occurred');
+ toast.error(`${t('notifications.testFailed', 'Failed to send test notification')}: ${errorMsg}`);
+ setError(errorMsg);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return {
+ loading,
+ error,
+ sendTestNotification
+ };
+};
+
+export default useNotifications;
\ No newline at end of file
diff --git a/src/Pages/Uptime/Create/index.jsx b/src/Pages/Uptime/Create/index.jsx
index 3b295f586..05b42766d 100644
--- a/src/Pages/Uptime/Create/index.jsx
+++ b/src/Pages/Uptime/Create/index.jsx
@@ -23,7 +23,7 @@ import Radio from "../../../Components/Inputs/Radio";
import Checkbox from "../../../Components/Inputs/Checkbox";
import Select from "../../../Components/Inputs/Select";
import ConfigBox from "../../../Components/ConfigBox";
-import NotificationIntegrationModal from "../../../Components/NotificationIntegrationModal/NotificationIntegrationModal";
+import NotificationIntegrationModal from "../../../Components/NotificationIntegrationModal/Components/NotificationIntegrationModal";
const CreateMonitor = () => {
const MS_PER_MINUTE = 60000;
const SELECT_VALUES = [
@@ -413,7 +413,7 @@ const CreateMonitor = () => {
onChange={(event) => handleNotifications(event, "email")}
/>
- {/*
+
- */}
+