refactor create status

This commit is contained in:
Alex Holliday committed 2025-02-03 15:12:00 -08:00
1 parent 7a3b6a679f
commit acc692ea97
10 files changed
+410 -11

No files matched your search

@@ -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 (
<Stack
direction={"row"}
{...draggableProps}
{...dragHandleProps}
ref={innerRef}
gap={theme.spacing(4)}
margin={theme.spacing(4)}
padding={theme.spacing(4)}
borderRadius={theme.shape.borderRadius}
alignItems={"center"}
justifyContent={"start"}
border={`1px solid ${theme.palette.primary.lowContrast}`}
>
<ReorderRoundedIcon />
<Typography>{monitor.name}</Typography>
<DeleteIcon
sx={{ marginLeft: "auto" }}
onClick={() => {
onDelete(monitor);
}}
/>
</Stack>
);
};
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 (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<Stack
{...provided.droppableProps}
ref={provided.innerRef}
>
{selectedMonitors?.map((monitor, index) => (
<Draggable
key={monitor._id}
draggableId={monitor._id}
index={index}
>
{(provided, snapshot) => (
<MonitorListItem
monitor={monitor}
innerRef={provided.innerRef}
draggableProps={provided.draggableProps}
dragHandleProps={provided.dragHandleProps}
onDelete={onDelete}
/>
)}
</Draggable>
))}
{provided.placeholder}
</Stack>
)}
</Droppable>
</DragDropContext>
);
};
export default MonitorList;
@@ -0,0 +1,14 @@
import { Stack, Skeleton } from "@mui/material";
const SkeletonLayout = () => {
return (
<Stack>
<Skeleton
variant="rectangular"
height={"90vh"}
/>
</Stack>
);
};
export default SkeletonLayout;
@@ -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 (
<TabPanel value={tabValue}>
<Stack>
<Typography>Content</Typography>
<Stack gap={theme.spacing(10)}>
<ConfigBox>
<Stack gap={theme.spacing(6)}>
<Typography component="h2">Status page servers</Typography>
<Typography component="p">
You can add any number of servers that you monitor to your status page. You
can also reorder them for the best viewing experience.
</Typography>
</Stack>
<Stack>
<Stack
direction="row"
justifyContent="space-between"
>
{/* <Typography
component="p"
alignSelf={"center"}
>
{" "}
Servers list{" "}
</Typography>
<Button
variant="contained"
color="accent"
>
Add new
</Button> */}
<Search
options={monitors}
multiple={true}
filteredBy="name"
value={selectedMonitors}
inputValue={search}
handleInputChange={setSearch}
handleChange={handleMonitorsChange}
/>
</Stack>
<Typography
component="span"
className="input-error"
color={theme.palette.error.main}
sx={{
opacity: 0.8,
}}
>
{errors["monitors"]}
</Typography>
<MonitorList
selectedMonitors={selectedMonitors}
setSelectedMonitors={handleMonitorsChange}
/>
</Stack>
</ConfigBox>{" "}
<ConfigBox>
<Stack gap={theme.spacing(6)}>
<Typography component="h2">Features</Typography>
<Typography component="p">Show more details on the status page</Typography>
</Stack>
<Stack sx={{ margin: theme.spacing(6) }}>
<Checkbox
id="showCharts"
name="showCharts"
label={`Show charts`}
isChecked={form.showCharts}
onChange={handleFormChange}
/>
<Checkbox
id="showUptimePercentage"
name="showUptimePercentage"
label={`Show uptime percentage`}
isChecked={form.showUptimePercentage}
onChange={handleFormChange}
/>
</Stack>
</ConfigBox>
</Stack>
<Stack gap={theme.spacing(6)}></Stack>
</TabPanel>
);
};
@@ -0,0 +1,14 @@
import { Stack, Skeleton } from "@mui/material";
const SkeletonLayout = () => {
return (
<Stack>
<Skeleton
variant="rectangular"
height={"80vh"}
/>
</Stack>
);
};
export default SkeletonLayout;
@@ -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 (
<TabContext value={TAB_LIST[tab]}>
<TabList
onChange={(_, tab) => {
setTab(TAB_LIST.indexOf(tab));
}}
>
{TAB_LIST.map((tab, idx) => (
<Tab
key={Math.random()}
label={TAB_LIST[idx]}
value={TAB_LIST[idx]}
/>
))}
</TabList>
{tab === 0 ? (
<Settings
tabValue={TAB_LIST[0]}
form={form}
handleFormChange={handleFormChange}
handleImageChange={handleImageChange}
progress={progress}
removeLogo={removeLogo}
errors={errors}
/>
) : (
<Content
tabValue={TAB_LIST[1]}
form={form}
monitors={monitors}
handleFormChange={handleFormChange}
errors={errors}
selectedMonitors={selectedMonitors}
setSelectedMonitors={setSelectedMonitors}
/>
)}
</TabContext>
);
};
export default Tabs;
@@ -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 };
@@ -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 };
@@ -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];
};
@@ -12,7 +12,6 @@ const PublicStatus = () => {
const theme = useTheme();
const isAdmin = useIsAdmin();
const [statusPage, isLoading, networkError] = useStatusPageFetch();
if (networkError === true) {
return (
<GenericFallback>
+23 -6
View File
@@ -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",
},
});
}