diff --git a/Client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx b/Client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx
new file mode 100644
index 000000000..5484202d5
--- /dev/null
+++ b/Client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx
@@ -0,0 +1,106 @@
+// Components
+import { Stack, Typography } from "@mui/material";
+import ReorderRoundedIcon from "@mui/icons-material/ReorderRounded";
+import DeleteIcon from "@mui/icons-material/Delete";
+
+// Utils
+import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd";
+import { useTheme } from "@emotion/react";
+const MonitorListItem = ({
+ monitor,
+ innerRef,
+ draggableProps,
+ dragHandleProps,
+ onDelete,
+}) => {
+ const theme = useTheme();
+ return (
+
+
+ {monitor.name}
+ {
+ onDelete(monitor);
+ }}
+ />
+
+ );
+};
+
+const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
+ const onDelete = (monitorToDelete) => {
+ const newMonitors = selectedMonitors.filter(
+ (monitor) => monitor._id !== monitorToDelete._id
+ );
+ setSelectedMonitors(newMonitors);
+ };
+ const reorder = (list, startIndex, endIndex) => {
+ const result = Array.from(list);
+ const [removed] = result.splice(startIndex, 1);
+ result.splice(endIndex, 0, removed);
+
+ return result;
+ };
+
+ const onDragEnd = (result) => {
+ // dropped outside the list
+ if (!result.destination) {
+ return;
+ }
+
+ const reorderedMonitors = reorder(
+ selectedMonitors,
+ result.source.index,
+ result.destination.index
+ );
+
+ setSelectedMonitors(reorderedMonitors);
+ };
+
+ return (
+
+
+ {(provided, snapshot) => (
+
+ {selectedMonitors?.map((monitor, index) => (
+
+ {(provided, snapshot) => (
+
+ )}
+
+ ))}
+ {provided.placeholder}
+
+ )}
+
+
+ );
+};
+
+export default MonitorList;
diff --git a/Client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx b/Client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx
new file mode 100644
index 000000000..482c490fb
--- /dev/null
+++ b/Client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx
@@ -0,0 +1,14 @@
+import { Stack, Skeleton } from "@mui/material";
+
+const SkeletonLayout = () => {
+ return (
+
+
+
+ );
+};
+
+export default SkeletonLayout;
diff --git a/Client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx b/Client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
index 763863ec7..6dd08acb0 100644
--- a/Client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
+++ b/Client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
@@ -1,12 +1,115 @@
-import { Stack, Typography } from "@mui/material";
+// Components
+import { Stack, Typography, Button } from "@mui/material";
import { TabPanel } from "@mui/lab";
+import ConfigBox from "../../../../../Components/ConfigBox";
+import MonitorList from "../MonitorList";
+import Search from "../../../../../Components/Inputs/Search";
+import Checkbox from "../../../../../Components/Inputs/Checkbox";
+// Utils
+import { useState } from "react";
+import { useTheme } from "@emotion/react";
+const Content = ({
+ tabValue,
+ form,
+ monitors,
+ handleFormChange,
+ errors,
+ selectedMonitors,
+ setSelectedMonitors,
+}) => {
+ // Local state
+ const [search, setSearch] = useState("");
+
+ // Handlers
+ const handleMonitorsChange = (selectedMonitors) => {
+ handleFormChange({
+ target: { name: "monitors", value: selectedMonitors.map((monitor) => monitor._id) },
+ });
+ setSelectedMonitors(selectedMonitors);
+ };
+
+ // Utils
+ const theme = useTheme();
-const Content = ({ tabValue }) => {
return (
-
- Content
+
+
+
+ Status page servers
+
+ You can add any number of servers that you monitor to your status page. You
+ can also reorder them for the best viewing experience.
+
+
+
+
+ {/*
+ {" "}
+ Servers list{" "}
+
+ */}
+
+
+
+ {errors["monitors"]}
+
+
+
+ {" "}
+
+
+ Features
+ Show more details on the status page
+
+
+
+
+
+
+
);
};
diff --git a/Client/src/Pages/StatusPage/Create/Components/Tabs/Skeleton.jsx b/Client/src/Pages/StatusPage/Create/Components/Tabs/Skeleton.jsx
new file mode 100644
index 000000000..fb0cd6ca8
--- /dev/null
+++ b/Client/src/Pages/StatusPage/Create/Components/Tabs/Skeleton.jsx
@@ -0,0 +1,14 @@
+import { Stack, Skeleton } from "@mui/material";
+
+const SkeletonLayout = () => {
+ return (
+
+
+
+ );
+};
+
+export default SkeletonLayout;
diff --git a/Client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx b/Client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx
new file mode 100644
index 000000000..b81b70c7b
--- /dev/null
+++ b/Client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx
@@ -0,0 +1,59 @@
+import { TabContext, TabList } from "@mui/lab";
+import { Stack, Tab } from "@mui/material";
+import Settings from "./Settings";
+import Content from "./Content";
+const Tabs = ({
+ form,
+ errors,
+ monitors,
+ selectedMonitors,
+ setSelectedMonitors,
+ handleFormChange,
+ handleImageChange,
+ progress,
+ removeLogo,
+ tab,
+ setTab,
+ TAB_LIST,
+}) => {
+ return (
+
+ {
+ setTab(TAB_LIST.indexOf(tab));
+ }}
+ >
+ {TAB_LIST.map((tab, idx) => (
+
+ ))}
+
+ {tab === 0 ? (
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export default Tabs;
diff --git a/Client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx b/Client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx
new file mode 100644
index 000000000..eeb309b28
--- /dev/null
+++ b/Client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx
@@ -0,0 +1,28 @@
+import { useState } from "react";
+import { networkService } from "../../../../main";
+import { useSelector } from "react-redux";
+import { createToast } from "../../../../Utils/toastUtils";
+
+const useCreateStatusPage = () => {
+ const { authToken, user } = useSelector((state) => state.auth);
+
+ const [isLoading, setIsLoading] = useState(false);
+ const [networkError, setNetworkError] = useState(false);
+
+ const createStatusPage = async ({ form }) => {
+ try {
+ await networkService.createStatusPage({ authToken, user, form });
+ return true;
+ } catch (error) {
+ setNetworkError(true);
+ createToast({ body: error?.response?.data?.msg ?? error.message });
+ return false;
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return [createStatusPage, isLoading, networkError];
+};
+
+export { useCreateStatusPage };
diff --git a/Client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx b/Client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx
new file mode 100644
index 000000000..4813157d1
--- /dev/null
+++ b/Client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx
@@ -0,0 +1,35 @@
+import { useEffect, useState } from "react";
+import { networkService } from "../../../../main";
+import { useSelector } from "react-redux";
+import { createToast } from "../../../../Utils/toastUtils";
+
+const useMonitorsFetch = () => {
+ const { user, authToken } = useSelector((state) => state.auth);
+
+ const [monitors, setMonitors] = useState(undefined);
+ const [isLoading, setIsLoading] = useState(true);
+ const [networkError, setNetworkError] = useState(false);
+ useEffect(() => {
+ const fetchMonitors = async () => {
+ try {
+ const response = await networkService.getMonitorsByTeamId({
+ authToken: authToken,
+ teamId: user.teamId,
+ limit: null, // donot return any checks for the monitors
+ types: ["http"], // status page is available only for the uptime type
+ });
+ setMonitors(response.data.data.monitors);
+ } catch (error) {
+ setNetworkError(true);
+ createToast({ body: error.message });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchMonitors();
+ }, [authToken, user]);
+
+ return [monitors, isLoading, networkError];
+};
+
+export { useMonitorsFetch };
diff --git a/Client/src/Pages/StatusPage/PublicStatus/Hooks/useStatusPageFetch.jsx b/Client/src/Pages/StatusPage/PublicStatus/Hooks/useStatusPageFetch.jsx
index 5af297679..8bc7e4f11 100644
--- a/Client/src/Pages/StatusPage/PublicStatus/Hooks/useStatusPageFetch.jsx
+++ b/Client/src/Pages/StatusPage/PublicStatus/Hooks/useStatusPageFetch.jsx
@@ -1,9 +1,33 @@
import { useEffect, useState } from "react";
+import { networkService } from "../../../../main";
+import { useSelector } from "react-redux";
+import { createToast } from "../../../../Utils/toastUtils";
const useStatusPageFetch = () => {
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [statusPage, setStatusPage] = useState(undefined);
+ const { authToken } = useSelector((state) => state.auth);
+
+ useEffect(() => {
+ const fetchStatusPage = async () => {
+ try {
+ const res = await networkService.getStatusPage({ authToken });
+ setStatusPage(res.data.data);
+ } catch (error) {
+ // If there is a 404, status page is not found
+ if (error?.response?.status === 404) {
+ setStatusPage(undefined);
+ return;
+ }
+ createToast({ body: error.message });
+ setNetworkError(true);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchStatusPage();
+ }, [authToken]);
return [statusPage, isLoading, networkError];
};
diff --git a/Client/src/Pages/StatusPage/PublicStatus/index.jsx b/Client/src/Pages/StatusPage/PublicStatus/index.jsx
index f3503e4f5..6e8f6ffb2 100644
--- a/Client/src/Pages/StatusPage/PublicStatus/index.jsx
+++ b/Client/src/Pages/StatusPage/PublicStatus/index.jsx
@@ -12,7 +12,6 @@ const PublicStatus = () => {
const theme = useTheme();
const isAdmin = useIsAdmin();
const [statusPage, isLoading, networkError] = useStatusPageFetch();
-
if (networkError === true) {
return (
diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js
index 176c6d093..a1e0c0426 100644
--- a/Client/src/Utils/NetworkService.js
+++ b/Client/src/Utils/NetworkService.js
@@ -862,9 +862,9 @@ class NetworkService {
);
}
- async getStatusPageByUrl(config) {
- const { url, authToken } = config;
- return this.axiosInstance.get(`/status-page/${url}`, {
+ async getStatusPage(config) {
+ const { authToken } = config;
+ return this.axiosInstance.get(`/status-page`, {
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
@@ -873,11 +873,28 @@ class NetworkService {
}
async createStatusPage(config) {
- const { url, authToken, data } = config;
- return this.axiosInstance.post(`/status-page/${url}`, data, {
+ const { authToken, user, form } = config;
+ const fd = new FormData();
+ fd.append("isPublished", form.isPublished);
+ fd.append("companyName", form.companyName);
+ fd.append("url", form.url);
+ fd.append("timezone", form.timezone);
+ fd.append("color", form.color);
+ fd.append("showCharts", form.showCharts);
+ fd.append("showUptimePercentage", form.showUptimePercentage);
+ form.monitors.forEach((monitorId) => {
+ fd.append("monitors[]", monitorId);
+ });
+ if (form?.logo?.src && form?.logo?.src !== "") {
+ const imageResult = await axios.get(form.logo.src, {
+ responseType: "blob",
+ });
+ fd.append("logo", imageResult.data);
+ }
+
+ return this.axiosInstance.post(`/status-page`, fd, {
headers: {
Authorization: `Bearer ${authToken}`,
- "Content-Type": "application/json",
},
});
}