- Add GetStatusPageByUrl to networkservice

- Make static status page and actual status config page toggleable
- Remove the sidebar nested menu item entry for status page tabs
- Pass context to status config page which contains general settings and contents tabs
- Status config page is now one submission and one validation
This commit is contained in:
Shemy Gan committed 2024-12-16 20:18:05 -05:00
1 parent 5045eafb8e
commit 219bcd4361
7 files changed
+120 -108

No files matched your search

+8 -1
View File
@@ -151,7 +151,14 @@ function App() {
/>
}
/>
<Route
path="status/create"
element={
<ProtectedRoute
Component={CreateStatus}
/>
}
/>
<Route
path="integrations"
element={<ProtectedRoute Component={Integrations} />}
@@ -1,4 +1,4 @@
import { useState, useCallback } from "react";
import { useState, useCallback, useContext } from "react";
import { Button, Box, Stack, Typography } from "@mui/material";
import { ConfigBox } from "../../../Pages/Settings/styled";
import { useTheme } from "@emotion/react";
@@ -8,20 +8,13 @@ import { buildErrors } from "../../../Validation/error";
import Card from "./Card";
import update from "immutability-helper";
import Checkbox from "../../Inputs/Checkbox";
import { StatusFormContext } from "../../../Pages/Status/TabContext";
const ContentPanel = ({errors, setErrors}) => {
const ContentPanel = () => {
const theme = useTheme();
const { form, setForm, errors, setErrors } = useContext(StatusFormContext);
const [cards, setCards] = useState(form.monitors);
// Clears specific error from errors state
const clearError = (err) => {
setErrors((prev) => {
const updatedErrors = { ...prev };
if (updatedErrors[err]) delete updatedErrors[err];
return updatedErrors;
});
};
const moveCard = useCallback(
(dragIndex, hoverIndex) => {
const dragCard = cards[dragIndex];
@@ -1,5 +1,5 @@
import { useState, useRef, useContext } from "react";
import { Button, Box, Stack, Typography } from "@mui/material";
import { Box, Stack, Typography } from "@mui/material";
import { ConfigBox } from "../../../Pages/Settings/styled";
import Checkbox from "../../Inputs/Checkbox";
import { useTheme } from "@emotion/react";
@@ -17,13 +17,12 @@ import { formatBytes } from "../../../Utils/fileUtils";
import ProgressUpload from "../../ProgressBars";
import ImageIcon from "@mui/icons-material/Image";
import { HttpAdornment } from "../../Inputs/TextInput/Adornments";
import { hasValidationErrors } from "../../../Validation/error";
import { StatusFormContext } from "../../../Pages/Status/TabContext";
const GeneralSettingsPanel = ({errors, setErrors}) => {
const GeneralSettingsPanel = () => {
const theme = useTheme();
const [logo, setLogo] = useState();
const { form, setForm } = useContext(StatusFormContext);
const { form, setForm, errors, setErrors } = useContext(StatusFormContext);
const [progress, setProgress] = useState({ value: 0, isLoading: false });
const intervalRef = useRef(null);
+25 -35
View File
@@ -4,37 +4,21 @@ import { Box, Tab, useTheme, Stack, Button } from "@mui/material";
import TabContext from "@mui/lab/TabContext";
import TabList from "@mui/lab/TabList";
import GeneralSettingsPanel from "../../../Components/TabPanels/Status/GeneralSettingsPanel";
import {
capitalizeFirstLetter,
} from "../../../Utils/stringUtils";
import { capitalizeFirstLetter } from "../../../Utils/stringUtils";
import ContentPanel from "../../../Components/TabPanels/Status/ContentPanel";
import {
publicPageGeneralSettingsValidation,
} from "../../../Validation/validation";
import { publicPageGeneralSettingsValidation } from "../../../Validation/validation";
import { hasValidationErrors } from "../../../Validation/error";
import { StatusFormProvider } from "../TabContext"
import { StatusFormProvider } from "../TabContext";
/**
* CreateStatus page renders a page with tabs for general settings and contents.
* @param {string} [props.open] - Specifies the initially open tab: 'general settings' or 'content'.
* @returns {JSX.Element}
*/
const CreateStatus = ({ open = "general-settings" }) => {
const CreateStatus = ({ open = "general-settings", initForm }) => {
const theme = useTheme();
let tabList = ["General Settings", "Contents"];
const [errors, setErrors] = useState({});
const tab = open
.split("-")
.map((a) => capitalizeFirstLetter(a))
.join(" ");
const [tabIdx, setTabIdx] = useState(tabList.indexOf(tab));
const handleTabChange = (event, newTab) => {
setTabIdx(tabList.indexOf(newTab));
};
const [form, setForm] = useState({
companyName: "",
url: "",
@@ -45,13 +29,23 @@ const CreateStatus = ({ open = "general-settings" }) => {
logo: null,
monitors: [],
showUptimePercentage: false,
showBarcode: false
showBarcode: false,
});
const tab = open
.split("-")
.map((a) => capitalizeFirstLetter(a))
.join(" ");
const [tabIdx, setTabIdx] = useState(tabList.indexOf(tab));
const handleTabChange = (event, newTab) => {
setTabIdx(tabList.indexOf(newTab));
};
const handleSubmit = () => {
//validate rest of the form
delete form.logo;
if (hasValidationErrors(form, publicPageGeneralSettingsValidation, setErrors)) {
if (hasValidationErrors(form, publicPageGeneralSettingsValidation, setErrors)) {
return;
}
// //validate image field, double check if it is required
@@ -59,9 +53,9 @@ const CreateStatus = ({ open = "general-settings" }) => {
// { type: logo?.type ?? null, size: logo?.size ?? null },
// logoImageValidation
// );
if (error) return;
// if (error) return;
form.logo = { ...logo, size: formatBytes(logo?.size) };
};
};
useEffect(() => {
setTabIdx(tabList.indexOf(tab));
@@ -112,17 +106,13 @@ const CreateStatus = ({ open = "general-settings" }) => {
))}
</TabList>
</Box>
<StatusFormProvider>
{tabIdx == 0 ? (
<GeneralSettingsPanel
errors={errors}
setErrors={setErrors}/>
) : (
<ContentPanel
ss errors={errors}
setErrors={setErrors}
/>
)}
<StatusFormProvider
form={initForm && Object.keys(initForm) > 0 ? initForm : form}
setForm={setForm}
errors={errors}
setErrors={setErrors}
>
{tabIdx == 0 ? <GeneralSettingsPanel /> : <ContentPanel />}
</StatusFormProvider>
</TabContext>
<Stack
+8 -19
View File
@@ -1,29 +1,18 @@
import { createContext, useState, React } from 'react';
import { createContext, useState, React } from "react";
const StatusFormContext = createContext({
form: {},
setForm: ()=>{},
form: {},
setForm: () => {},
errors: {},
setErrors: () => {},
});
const StatusFormProvider = ({ children }) => {
const [form, setForm] = useState({
companyName: "",
url: "",
timezone: "America/Toronto",
color: "#4169E1",
//which fields matching below?
publish: false,
logo: null,
monitors: [],
showUptimePercentage: false,
showBarcode: false,
});
const StatusFormProvider = ({ form, setForm, errors, setErrors, children }) => {
return (
<StatusFormContext.Provider value={{ form, setForm}}>
<StatusFormContext.Provider value={{ form, setForm, errors, setErrors }}>
{children}
</StatusFormContext.Provider>
);
};
export { StatusFormContext, StatusFormProvider };
export { StatusFormContext, StatusFormProvider };
+60 -38
View File
@@ -2,50 +2,72 @@ import { useTheme } from "@emotion/react";
import Fallback from "../../Components/Fallback";
import { Box, Button, Stack } from "@mui/material";
import { useNavigate } from "react-router-dom";
import CreateStatus from "./CreateStatus";
import { useEffect } from "react";
import { networkService } from "../../main";
import { useSelector } from "react-redux";
const Status = () => {
const theme = useTheme();
const navigate = useNavigate();
const navigate = useNavigate();
const {authToken} = useSelector((state) => state.auth);
const {apiBaseUrl} = useSelector((state) => state.settings);
let initForm = {}
useEffect(() => {
const getStatusPage = async () => {
let config = { authToken: authToken, url: apiBaseUrl };
initForm = await networkService.getStatusPageByUrl(config);
};
getStatusPage();
}, []);
return (
<Box
className="status"
sx={{
':has(> [class*="fallback__"])': {
position: "relative",
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
borderStyle: "dashed",
backgroundColor: theme.palette.background.main,
overflow: "hidden",
},
}}
>
<Fallback
title="status page"
checks={[
"Share your uptime publicly",
"Keep your users informed about incidents",
"Build trust with your customers",
]}
/>
<Stack
alignItems="center"
mt={theme.spacing(10)}
>
<Button
variant="contained"
color="primary"
onClick={() => {
navigate("/status/create");
<>
{Object.keys(initForm).length===0? (
<Box
className="status"
sx={{
':has(> [class*="fallback__"])': {
position: "relative",
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
borderStyle: "dashed",
backgroundColor: theme.palette.background.main,
overflow: "hidden",
},
}}
sx={{ fontWeight: 500 }}
>
Let's create your status page
</Button>
</Stack>
</Box>
);
<Fallback
title="status page"
checks={[
"Share your uptime publicly",
"Keep your users informed about incidents",
"Build trust with your customers",
]}
/>
<Stack
alignItems="center"
mt={theme.spacing(10)}
>
<Button
variant="contained"
color="primary"
onClick={() => {
navigate("/status/create");
}}
sx={{ fontWeight: 500 }}
>
Let's create your status page
</Button>
</Stack>
</Box>
) : (
<CreateStatus initForm={initForm}/>
)}
</>
);
};
export default Status;
+12
View File
@@ -846,6 +846,18 @@ class NetworkService {
"https://api.github.com/repos/bluewave-labs/bluewave-uptime/releases/latest"
);
}
async getStatusPageByUrl(config){
const params = new URLSearchParams();
params.append("url", encodeURIComponent(config.url));
return this.axiosInstance.get(`/status-page?${params.toString()}`, {
headers: {
Authorization: `Bearer ${config.authToken}`,
"Content-Type": "application/json",
},
});
}
}
export default NetworkService;