diff --git a/Client/src/Pages/DistributedUptime/CreateStatus/Components/VisuallyHiddenInput/index.jsx b/Client/src/Pages/DistributedUptime/CreateStatus/Components/VisuallyHiddenInput/index.jsx new file mode 100644 index 000000000..fd002d2a4 --- /dev/null +++ b/Client/src/Pages/DistributedUptime/CreateStatus/Components/VisuallyHiddenInput/index.jsx @@ -0,0 +1,22 @@ +const VisuallyHiddenInput = ({ onChange }) => { + return ( + + ); +}; + +export default VisuallyHiddenInput; diff --git a/Client/src/Pages/DistributedUptime/CreateStatus/index.jsx b/Client/src/Pages/DistributedUptime/CreateStatus/index.jsx new file mode 100644 index 000000000..d244bfe38 --- /dev/null +++ b/Client/src/Pages/DistributedUptime/CreateStatus/index.jsx @@ -0,0 +1,217 @@ +// 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"; +// Utils +import { useTheme } from "@emotion/react"; +import { useState } from "react"; +import { useParams } from "react-router-dom"; +import { useCreateStatusPage } from "../../StatusPage/Create/Hooks/useCreateStatusPage"; +import { useLocation } from "react-router-dom"; +import { statusPageValidation } from "../../../Validation/validation"; +import { buildErrors } from "../../../Validation/error"; +import { createToast } from "../../../Utils/toastUtils"; +import { useNavigate } from "react-router-dom"; + +const CreateStatus = () => { + const theme = useTheme(); + const { monitorId } = useParams(); + const navigate = useNavigate(); + const location = useLocation(); + const isCreate = location.pathname.startsWith("/distributed-uptime/status/create"); + const [createStatusPage, isLoading, networkError] = useCreateStatusPage(isCreate); + const BREADCRUMBS = [ + { name: "distributed uptime", path: "/distributed-uptime" }, + { name: "details", path: `/distributed-uptime/${monitorId}` }, + { name: "create status page", path: `` }, + ]; + // Local state + const [form, setForm] = useState({ + isPublished: false, + url: Math.floor(Math.random() * 1000000).toFixed(0), + logo: undefined, + companyName: "", + monitors: [monitorId], + }); + const [errors, setErrors] = 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 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") { + logoToSubmit = { + src: URL.createObjectURL(form.logo), + name: form.logo.name, + type: form.logo.type, + size: form.logo.size, + }; + } + 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) { + createToast({ body: "Status page created successfully" }); + navigate(`/distributed-uptime/status/${form.url}`); + } + return; + } + const newErrors = {}; + error?.details?.forEach((err) => { + newErrors[err.path[0]] = err.message; + }); + setErrors((prev) => ({ ...prev, ...newErrors })); + }; + + return ( + + + + + Create your{" "} + + + status page + + + + + Access + + If your status page is ready, you can mark it as published. + + + + + + + + + Basic Information + + Define company name and the subdomain that your status page points to. + + + + + + + + + + Logo + Upload a logo for your status page + + + Logo + + + + + + + + + + ); +}; + +export default CreateStatus; diff --git a/Client/src/Pages/DistributedUptime/Details/index.jsx b/Client/src/Pages/DistributedUptime/Details/index.jsx index ab39ec2ec..a8fc0c309 100644 --- a/Client/src/Pages/DistributedUptime/Details/index.jsx +++ b/Client/src/Pages/DistributedUptime/Details/index.jsx @@ -10,21 +10,23 @@ import StatBoxes from "./Components/StatBoxes"; import MonitorHeader from "./Components/MonitorHeader"; import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; import GenericFallback from "../../../Components/GenericFallback"; +import MonitorCreateHeader from "../../../Components/MonitorCreateHeader"; //Utils import { useTheme } from "@mui/material/styles"; import { useState } from "react"; import { useParams } from "react-router-dom"; -import useSubscribeToDetails from "./Hooks/useSubscribeToDetails"; +import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useSubscribeToDetails } from "./Hooks/useSubscribeToDetails"; const DistributedUptimeDetails = () => { const { monitorId } = useParams(); - // Local State const [dateRange, setDateRange] = useState("day"); // Utils const theme = useTheme(); + const isAdmin = useIsAdmin(); const [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger] = useSubscribeToDetails({ monitorId, dateRange }); // Constants @@ -65,6 +67,11 @@ const DistributedUptimeDetails = () => { gap={theme.spacing(10)} > + { }, }, onRowClick: (row) => { - navigate(`/distributed-uptime/${row.id}`); + navigate(`/distributed-uptime/${row._id}`); }, }} /> diff --git a/Client/src/Pages/DistributedUptime/Status/Hooks/useStatusPageFetchByUrl.jsx b/Client/src/Pages/DistributedUptime/Status/Hooks/useStatusPageFetchByUrl.jsx new file mode 100644 index 000000000..973e157c7 --- /dev/null +++ b/Client/src/Pages/DistributedUptime/Status/Hooks/useStatusPageFetchByUrl.jsx @@ -0,0 +1,35 @@ +import { useState, useEffect } from "react"; +import { networkService } from "../../../../main"; +import { createToast } from "../../../../Utils/toastUtils"; +import { useSelector } from "react-redux"; + +const useStatusPageFetchByUrl = ({ url }) => { + const [isLoading, setIsLoading] = useState(true); + const [networkError, setNetworkError] = useState(false); + const [statusPage, setStatusPage] = useState(undefined); + const [monitorId, setMonitorId] = useState(undefined); + const { authToken } = useSelector((state) => state.auth); + useEffect(() => { + const fetchStatusPageByUrl = async () => { + try { + const response = await networkService.getStatusPageByUrl({ authToken, url }); + if (!response?.data?.data) return; + const statusPage = response.data.data; + setStatusPage(statusPage); + setMonitorId(statusPage?.monitors[0]); + } catch (error) { + setNetworkError(true); + createToast({ + body: error.message, + }); + } finally { + setIsLoading(false); + } + }; + fetchStatusPageByUrl(); + }, [authToken, url]); + + return [isLoading, networkError, statusPage, monitorId]; +}; + +export { useStatusPageFetchByUrl }; diff --git a/Client/src/Pages/DistributedUptime/Status/index.jsx b/Client/src/Pages/DistributedUptime/Status/index.jsx new file mode 100644 index 000000000..c2cee4c12 --- /dev/null +++ b/Client/src/Pages/DistributedUptime/Status/index.jsx @@ -0,0 +1,135 @@ +//Components +import DistributedUptimeMap from "../Details/Components/DistributedUptimeMap"; +import Breadcrumbs from "../../../Components/Breadcrumbs"; +import { Stack, Typography } from "@mui/material"; +import DeviceTicker from "../Details/Components/DeviceTicker"; +import DistributedUptimeResponseChart from "../Details/Components/DistributedUptimeResponseChart"; +import NextExpectedCheck from "../Details/Components/NextExpectedCheck"; +import Footer from "../Details/Components/Footer"; +import StatBoxes from "../Details/Components/StatBoxes"; +import ControlsHeader from "../../StatusPage/Status/Components/ControlsHeader"; +import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; +import GenericFallback from "../../../Components/GenericFallback"; +import Dialog from "../../../Components/Dialog"; +//Utils +import { useTheme } from "@mui/material/styles"; +import { useState } from "react"; +import { useParams } from "react-router-dom"; +import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useSubscribeToDetails } from "../Details/Hooks/useSubscribeToDetails"; +import { useStatusPageFetchByUrl } from "./Hooks/useStatusPageFetchByUrl"; +import { useStatusPageDelete } from "../../StatusPage/Status/Hooks/useStatusPageDelete"; +import { useNavigate } from "react-router-dom"; +const DistributedUptimeStatus = () => { + const { url } = useParams(); + // Local State + const [dateRange, setDateRange] = useState("day"); + const [isDeleteOpen, setIsDeleteOpen] = useState(false); + // Utils + const theme = useTheme(); + const navigate = useNavigate(); + const [statusPageIsLoading, statusPageNetworkError, statusPage, monitorId] = + useStatusPageFetchByUrl({ + url, + }); + + const [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger] = + useSubscribeToDetails({ monitorId, dateRange }); + + const [deleteStatusPage, isDeleting] = useStatusPageDelete(() => { + navigate("/distributed-uptime"); + }, url); + // Constants + const BREADCRUMBS = [ + { name: "Distributed Uptime", path: "/distributed-uptime" }, + { name: "status", path: `` }, + ]; + + if (networkError) { + return ( + + + Network error + + Please check your connection + + ); + } + + if (typeof monitor === "undefined" || monitor.totalChecks === 0) { + return ( + + + + There is no check history for this monitor yet. + + + ); + } + + return ( + + + + + + + + + + + +