307 lines
8.3 KiB
React
307 lines
8.3 KiB
React
// Components
|
|
import { Stack, Typography, Button, Box } from "@mui/material";
|
|
import ConfigBox from "../../../Components/ConfigBox";
|
|
import Checkbox from "../../../Components/Inputs/Checkbox";
|
|
import TextInput from "../../../Components/Inputs/TextInput";
|
|
import VisuallyHiddenInput from "./Components/VisuallyHiddenInput";
|
|
import Image from "../../../Components/Image";
|
|
import LogoPlaceholder from "../../../assets/Images/logo_placeholder.svg";
|
|
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
|
import Search from "../../../Components/Inputs/Search";
|
|
import MonitorList from "../../StatusPage/Create/Components/MonitorList";
|
|
// Utils
|
|
import { useTheme } from "@emotion/react";
|
|
import { useState, useEffect } from "react";
|
|
import { useParams } from "react-router-dom";
|
|
import { useDUStatusPageFetch } from "./Hooks/useDUStatusPageFetch";
|
|
import { useCreateStatusPage } from "../../StatusPage/Create/Hooks/useCreateStatusPage";
|
|
import { statusPageValidation } from "../../../Validation/validation";
|
|
import { buildErrors } from "../../../Validation/error";
|
|
import { createToast } from "../../../Utils/toastUtils";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { useMonitorsFetch } from "../../StatusPage/Create/Hooks/useMonitorsFetch";
|
|
|
|
const CreateStatus = () => {
|
|
const theme = useTheme();
|
|
const { monitorId, url } = useParams();
|
|
const navigate = useNavigate();
|
|
const isCreate = typeof url === "undefined";
|
|
|
|
const [createStatusPage, isLoading, networkError] = useCreateStatusPage(isCreate);
|
|
|
|
const [statusPage, statusPageIsLoading, statusPageNetworkError] = useDUStatusPageFetch(
|
|
isCreate,
|
|
url
|
|
);
|
|
|
|
const [monitors, monitorsIsLoading, monitorsNetworkError] = useMonitorsFetch();
|
|
|
|
const BREADCRUMBS = [
|
|
{ name: "distributed uptime", path: "/distributed-uptime" },
|
|
{ name: "details", path: `/distributed-uptime/${monitorId}` },
|
|
{ name: isCreate ? "create status page" : "edit status page", path: `` },
|
|
];
|
|
// Local state
|
|
const [form, setForm] = useState({
|
|
type: "distributed",
|
|
isPublished: false,
|
|
url: url ?? Math.floor(Math.random() * 1000000).toFixed(0),
|
|
logo: undefined,
|
|
companyName: "",
|
|
monitors: [monitorId],
|
|
});
|
|
const [errors, setErrors] = useState({});
|
|
const [search, setSearch] = useState("");
|
|
const [selectedMonitors, setSelectedMonitors] = useState([]);
|
|
|
|
const handleFormChange = (e) => {
|
|
const { name, value, checked, type } = e.target;
|
|
|
|
// Check for errors
|
|
const { error } = statusPageValidation.validate(
|
|
{ [name]: value },
|
|
{ abortEarly: false }
|
|
);
|
|
|
|
setErrors((prev) => buildErrors(prev, name, error));
|
|
|
|
if (type === "checkbox") {
|
|
setForm({ ...form, [name]: checked });
|
|
return;
|
|
}
|
|
setForm({ ...form, [name]: value });
|
|
};
|
|
|
|
const handleMonitorsChange = (selectedMonitors) => {
|
|
handleFormChange({
|
|
target: {
|
|
name: "subMonitors",
|
|
value: selectedMonitors.map((monitor) => monitor._id),
|
|
},
|
|
});
|
|
setSelectedMonitors(selectedMonitors);
|
|
};
|
|
|
|
const handleImageUpload = (e) => {
|
|
const img = e.target?.files?.[0];
|
|
setForm((prev) => ({
|
|
...prev,
|
|
logo: img,
|
|
}));
|
|
};
|
|
const handleSubmit = async () => {
|
|
let logoToSubmit = undefined;
|
|
|
|
// Handle image
|
|
if (typeof form.logo !== "undefined" && typeof form.logo.src === "undefined") {
|
|
logoToSubmit = {
|
|
src: URL.createObjectURL(form.logo),
|
|
name: form.logo.name,
|
|
type: form.logo.type,
|
|
size: form.logo.size,
|
|
};
|
|
} else if (typeof form.logo !== "undefined") {
|
|
logoToSubmit = form.logo;
|
|
}
|
|
const formToSubmit = { ...form };
|
|
if (typeof logoToSubmit !== "undefined") {
|
|
formToSubmit.logo = logoToSubmit;
|
|
}
|
|
// Validate
|
|
const { error } = statusPageValidation.validate(formToSubmit, { abortEarly: false });
|
|
if (typeof error === "undefined") {
|
|
const success = await createStatusPage({ form: formToSubmit });
|
|
if (success) {
|
|
const verb = isCreate ? "created" : "updated";
|
|
createToast({ body: `Status page ${verb} successfully` });
|
|
navigate(`/status/distributed/${form.url}`);
|
|
}
|
|
return;
|
|
}
|
|
const newErrors = {};
|
|
error?.details?.forEach((err) => {
|
|
newErrors[err.path[0]] = err.message;
|
|
});
|
|
setErrors((prev) => ({ ...prev, ...newErrors }));
|
|
};
|
|
|
|
// If we are configuring, populate fields
|
|
useEffect(() => {
|
|
if (isCreate) return;
|
|
if (typeof statusPage === "undefined") {
|
|
return;
|
|
}
|
|
|
|
let newLogo = undefined;
|
|
if (statusPage.logo) {
|
|
newLogo = {
|
|
src: `data:${statusPage.logo.contentType};base64,${statusPage.logo.data}`,
|
|
name: "logo",
|
|
type: statusPage.logo.contentType,
|
|
size: null,
|
|
};
|
|
}
|
|
|
|
setForm((prev) => {
|
|
return {
|
|
...prev,
|
|
companyName: statusPage?.companyName,
|
|
isPublished: statusPage?.isPublished,
|
|
timezone: statusPage?.timezone,
|
|
monitors: statusPage?.monitors,
|
|
subMonitors: statusPage?.subMonitors.map((monitor) => monitor._id),
|
|
color: statusPage?.color,
|
|
logo: newLogo,
|
|
};
|
|
});
|
|
setSelectedMonitors(statusPage?.subMonitors);
|
|
}, [isCreate, statusPage]);
|
|
|
|
const imgSrc = form?.logo?.src
|
|
? form.logo.src
|
|
: form.logo
|
|
? URL.createObjectURL(form.logo)
|
|
: undefined;
|
|
|
|
return (
|
|
<Stack gap={theme.spacing(10)}>
|
|
<Breadcrumbs list={BREADCRUMBS} />
|
|
<Typography variant="h1">
|
|
<Typography
|
|
component="span"
|
|
fontSize="inherit"
|
|
>
|
|
Create your{" "}
|
|
</Typography>
|
|
<Typography
|
|
component="span"
|
|
variant="h2"
|
|
fontSize="inherit"
|
|
fontWeight="inherit"
|
|
>
|
|
status page
|
|
</Typography>
|
|
</Typography>
|
|
<ConfigBox>
|
|
<Stack>
|
|
<Typography component="h2">Access</Typography>
|
|
<Typography component="p">
|
|
If your status page is ready, you can mark it as published.
|
|
</Typography>
|
|
</Stack>
|
|
<Stack gap={theme.spacing(18)}>
|
|
<Checkbox
|
|
id="publish"
|
|
name="isPublished"
|
|
label={`Published and visible to the public`}
|
|
isChecked={form.isPublished}
|
|
onChange={handleFormChange}
|
|
/>
|
|
</Stack>
|
|
</ConfigBox>
|
|
<ConfigBox>
|
|
<Stack gap={theme.spacing(6)}>
|
|
<Typography component="h2">Basic Information</Typography>
|
|
<Typography component="p">
|
|
Define company name and the subdomain that your status page points to.
|
|
</Typography>
|
|
</Stack>
|
|
<Stack gap={theme.spacing(18)}>
|
|
<TextInput
|
|
id="companyName"
|
|
name="companyName"
|
|
type="text"
|
|
label="Company name"
|
|
placeholder="Company name"
|
|
value={form.companyName}
|
|
onChange={handleFormChange}
|
|
helperText={errors["companyName"]}
|
|
error={errors["companyName"] ? true : false}
|
|
/>
|
|
<TextInput
|
|
id="url"
|
|
name="url"
|
|
type="url"
|
|
label="Your status page address"
|
|
disabled={!isCreate}
|
|
value={form.url}
|
|
onChange={handleFormChange}
|
|
helperText={errors["url"]}
|
|
error={errors["url"] ? true : false}
|
|
/>
|
|
</Stack>
|
|
</ConfigBox>
|
|
<ConfigBox>
|
|
<Stack gap={theme.spacing(6)}>
|
|
<Typography component="h2">Logo</Typography>
|
|
<Typography component="p">Upload a logo for your status page </Typography>
|
|
</Stack>
|
|
<Stack
|
|
gap={theme.spacing(18)}
|
|
alignItems="center"
|
|
>
|
|
<Image
|
|
src={imgSrc}
|
|
alt="Logo"
|
|
minWidth={"300px"}
|
|
minHeight={"100px"}
|
|
maxWidth={"300px"}
|
|
maxHeight={"300px"}
|
|
placeholder={LogoPlaceholder}
|
|
/>
|
|
<Box>
|
|
<Button
|
|
component="label"
|
|
role={undefined}
|
|
variant="contained"
|
|
color="accent"
|
|
tabIndex={-1}
|
|
>
|
|
Upload logo
|
|
<VisuallyHiddenInput onChange={handleImageUpload} />
|
|
</Button>
|
|
</Box>
|
|
</Stack>
|
|
</ConfigBox>
|
|
<ConfigBox>
|
|
<Stack>
|
|
<Typography component="h2">Standard Monitors</Typography>
|
|
<Typography component="p">
|
|
Attach standard monitors to your status page.
|
|
</Typography>
|
|
</Stack>
|
|
<Stack gap={theme.spacing(18)}>
|
|
<Search
|
|
options={monitors ?? []}
|
|
multiple={true}
|
|
filteredBy="name"
|
|
value={selectedMonitors}
|
|
inputValue={search}
|
|
handleInputChange={setSearch}
|
|
handleChange={handleMonitorsChange}
|
|
/>
|
|
<MonitorList
|
|
monitors={monitors}
|
|
selectedMonitors={selectedMonitors}
|
|
setSelectedMonitors={handleMonitorsChange}
|
|
/>
|
|
</Stack>
|
|
</ConfigBox>
|
|
<Stack
|
|
direction="row"
|
|
justifyContent="flex-end"
|
|
>
|
|
<Button
|
|
variant="contained"
|
|
color="accent"
|
|
onClick={handleSubmit}
|
|
>
|
|
Save
|
|
</Button>
|
|
</Stack>
|
|
</Stack>
|
|
);
|
|
};
|
|
|
|
export default CreateStatus;
|