- Remove Color picker OOTB label, add custom label

- Add label for logo
- Replace react beautiful dnd with hello-pangea to fix the  "defaultProps will not be supported " console warning due to react beatuful dnd deprecation
- Add all the checkboxes and respective validation
- Remove subdomain from UI display
- Pass Handlers as context for both tabs to ease code scalability
- All Status page fields are now ready for submission
This commit is contained in:
Shemy Gan
2025-01-17 14:34:29 -05:00
parent 642c5f0479
commit 045a9cff3a
11 changed files with 243 additions and 257 deletions

110
Client/package-lock.json generated
View File

@@ -11,6 +11,7 @@
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fontsource/roboto": "^5.0.13",
"@hello-pangea/dnd": "^17.0.0",
"@mui/icons-material": "6.3.1",
"@mui/lab": "6.0.0-beta.22",
"@mui/material": "6.3.1",
@@ -25,7 +26,6 @@
"jwt-decode": "^4.0.0",
"mui-color-input": "^5.0.1",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0",
"react-redux": "9.2.0",
"react-router": "^6.23.0",
@@ -995,6 +995,24 @@
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@hello-pangea/dnd": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-17.0.0.tgz",
"integrity": "sha512-LDDPOix/5N0j5QZxubiW9T0M0+1PR0rTDWeZF5pu1Tz91UQnuVK4qQ/EjY83Qm2QeX0eM8qDXANfDh3VVqtR4Q==",
"dependencies": {
"@babel/runtime": "^7.25.6",
"css-box-model": "^1.2.1",
"memoize-one": "^6.0.0",
"raf-schd": "^4.0.3",
"react-redux": "^9.1.2",
"redux": "^5.0.1",
"use-memo-one": "^1.1.3"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@@ -2370,15 +2388,6 @@
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"license": "MIT"
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz",
"integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==",
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
@@ -2411,25 +2420,6 @@
"@types/react": "^18.0.0"
}
},
"node_modules/@types/react-redux": {
"version": "7.1.34",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.34.tgz",
"integrity": "sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==",
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"node_modules/@types/react-redux/node_modules/redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"dependencies": {
"@babel/runtime": "^7.9.2"
}
},
"node_modules/@types/react-transition-group": {
"version": "4.4.12",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
@@ -4978,9 +4968,9 @@
}
},
"node_modules/memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
},
"node_modules/mime-db": {
"version": "1.52.0",
@@ -5477,62 +5467,6 @@
"node": ">=0.10.0"
}
},
"node_modules/react-beautiful-dnd": {
"version": "13.1.1",
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz",
"integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==",
"deprecated": "react-beautiful-dnd is now deprecated. Context and options: https://github.com/atlassian/react-beautiful-dnd/issues/2672",
"dependencies": {
"@babel/runtime": "^7.9.2",
"css-box-model": "^1.2.0",
"memoize-one": "^5.1.1",
"raf-schd": "^4.0.2",
"react-redux": "^7.2.0",
"redux": "^4.0.4",
"use-memo-one": "^1.1.1"
},
"peerDependencies": {
"react": "^16.8.5 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-beautiful-dnd/node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"node_modules/react-beautiful-dnd/node_modules/react-redux": {
"version": "7.2.9",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
"integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==",
"dependencies": {
"@babel/runtime": "^7.15.4",
"@types/react-redux": "^7.1.20",
"hoist-non-react-statics": "^3.3.2",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-is": "^17.0.2"
},
"peerDependencies": {
"react": "^16.8.3 || ^17 || ^18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/react-beautiful-dnd/node_modules/redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"dependencies": {
"@babel/runtime": "^7.9.2"
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",

View File

@@ -14,6 +14,7 @@
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fontsource/roboto": "^5.0.13",
"@hello-pangea/dnd": "^17.0.0",
"@mui/icons-material": "6.3.1",
"@mui/lab": "6.0.0-beta.22",
"@mui/material": "6.3.1",
@@ -28,7 +29,6 @@
"jwt-decode": "^4.0.0",
"mui-color-input": "^5.0.1",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0",
"react-redux": "9.2.0",
"react-router": "^6.23.0",

View File

@@ -115,7 +115,7 @@ Checkbox.propTypes = {
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
size: PropTypes.oneOf(["small", "medium", "large"]),
isChecked: PropTypes.bool.isRequired,
value: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
onChange: PropTypes.func,
isDisabled: PropTypes.bool,
alignSelf: PropTypes.bool,

View File

@@ -6,7 +6,6 @@ import { MuiColorInput } from "mui-color-input";
/**
*
* @param {*} id The ID of the component
* @param {*} label The Label of the component
* @param {*} value The color value of the component
* @param {*} error The error of the component
* @param {*} onChange The Change handler function
@@ -15,7 +14,6 @@ import { MuiColorInput } from "mui-color-input";
* Example usage:
* <ColorPicker
* id="color"
* label="Color"
* value={form.color}
* error={errors["color"]}
* onChange={handleColorChange}
@@ -23,7 +21,7 @@ import { MuiColorInput } from "mui-color-input";
* >
* </ColorPicker>
*/
const ColorPicker = ({ id, label, value, error, onChange, onBlur }) => {
const ColorPicker = ({ id, value, error, onChange, onBlur }) => {
const theme = useTheme();
return (
<Stack gap={theme.spacing(4)}>
@@ -31,7 +29,6 @@ const ColorPicker = ({ id, label, value, error, onChange, onBlur }) => {
format="hex"
value={value}
id={id}
label={label}
onChange={onChange}
onBlur={onBlur}
/>
@@ -54,7 +51,6 @@ const ColorPicker = ({ id, label, value, error, onChange, onBlur }) => {
ColorPicker.propTypes = {
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
value: PropTypes.string,
error: PropTypes.string,
onChange: PropTypes.func.isRequired,

View File

@@ -1,30 +1,37 @@
import { useState, useContext, useEffect } from "react";
import { Button, Box, Stack, Typography } from "@mui/material";
import ConfigBox from "../../../Components/ConfigBox";
import { useTheme } from "@emotion/react";
import TabPanel from "@mui/lab/TabPanel";
import ConfigBox from "../../../Components/ConfigBox";
import { StatusFormContext } from "../../../Pages/Status/CreateStatusContext";
import { useSelector } from "react-redux";
import { logger } from "../../../Utils/Logger"
import { logger } from "../../../Utils/Logger";
import { createToast } from "../../../Utils/toastUtils";
import { networkService } from "../../../main";
import ServersList from "./ServersList";
import Checkbox from "../../Inputs/Checkbox";
/**
* Content Panel is used to compose the second part of the status page
* for the servers/monitors to watch for in its public page presence and some
* for the servers/monitors to watch for in its public page presence and some
* other server related configurations etc
*
*
*/
const ContentPanel = () => {
const theme = useTheme();
const { form, setForm, errors, setErrors } = useContext(StatusFormContext);
const {
form,
setForm,
errors,
setErrors,
handleBlur,
handelCheckboxChange,
} = useContext(StatusFormContext);
const [cards, setCards] = useState([]);
const {user, authToken } = useSelector((state) => state.auth);
const { user, authToken } = useSelector((state) => state.auth);
const [monitors, setMonitors] = useState([]);
useEffect(() => {
const fetchMonitors = async () => {
try {
@@ -34,10 +41,10 @@ const ContentPanel = () => {
limit: null, // donot return any checks for the monitors
types: ["http"], // status page is available only for the uptime type
});
if(response.data.data.monitors.length==0){
setErrors({monitors: "Please config monitors to setup status page"})
if (response.data.data.monitors.length == 0) {
setErrors({ monitors: "Please config monitors to setup status page" });
}
const fullMonitors = response.data.data.monitors ;
const fullMonitors = response.data.data.monitors;
setMonitors(fullMonitors);
if (form.monitors.length > 0) {
const initiCards = form.monitors.map((mid, idx) => ({
@@ -52,12 +59,12 @@ const ContentPanel = () => {
createToast({ body: "Failed to fetch monitors data" });
logger.error("Failed to fetch monitors", error);
}
};
};
fetchMonitors();
}, [user, authToken]);
const handleAddNew = () => {
if (cards.length === monitors.length) return;
const newCards = [...cards, { id: "" + Math.random(),val:{} }];
const newCards = [...cards, { id: "" + Math.random(), val: {} }];
setCards(newCards);
};
const removeCard = (id) => {
@@ -96,10 +103,10 @@ const ContentPanel = () => {
</Typography>
</Stack>
</Box>
<Box
<Stack
className="status-contents-server-list"
sx={{
margin: theme.spacing(20),
margin: theme.spacing(6),
border: "solid",
borderRadius: theme.shape.borderRadius,
borderColor: theme.palette.border.light,
@@ -129,22 +136,16 @@ const ContentPanel = () => {
Add new
</Button>
</Stack>
{cards.length > 0 && (
<Stack
id="monitors"
direction="column"
gap={theme.spacing(2)}
>
<ServersList
monitors={monitors}
cards={cards}
setCards={setCards}
form={form}
setForm={setForm}
removeItem={removeCard}
/>
</Stack>
)}
<ServersList
monitors={monitors}
cards={cards}
setCards={setCards}
form={form}
setForm={setForm}
removeItem={removeCard}
/>
{errors["monitors"] && (
<Typography
component="span"
@@ -157,7 +158,7 @@ const ContentPanel = () => {
{errors["monitors"]}
</Typography>
)}
</Box>
</Stack>
</ConfigBox>
<ConfigBox>
@@ -167,23 +168,22 @@ const ContentPanel = () => {
<Typography component="p">Show more details on the status page</Typography>
</Stack>
</Box>
{/* <Stack sx={{margin: theme.spacing(20)}}
>
<Stack sx={{ margin: theme.spacing(6) }}>
<Checkbox
id="show-barcode"
id="showBarcode"
label={`Show Barcode`}
isChecked={form.showBarcode}
onChange={handleChange}
onChange={handelCheckboxChange}
onBlur={handleBlur}
/>
<Checkbox
id="show-uptime-percentage"
id="showUptimePercentage"
label={`Show Uptime Percentage`}
isChecked={form.showUptimePercentage}
onChange={handleChange}
onChange={handelCheckboxChange}
onBlur={handleBlur}
/>
</Stack> */}
/>
</Stack>
</ConfigBox>
</Stack>
</TabPanel>

View File

@@ -1,38 +1,34 @@
import { useState, useRef, useContext } from "react";
import { Box, Button, Stack, Typography } from "@mui/material";
import ConfigBox from "../../../Components/ConfigBox";
import { useTheme } from "@emotion/react";
import TabPanel from "@mui/lab/TabPanel";
import ImageIcon from "@mui/icons-material/Image";
import ConfigBox from "../../../Components/ConfigBox";
import TextInput from "../../Inputs/TextInput";
import ImageField from "../../Inputs/Image";
import timezones from "../../../Utils/timezones.json";
import Select from "../../Inputs/Select";
import {
logoImageValidation,
publicPageGeneralSettingsValidation,
} from "../../../Validation/validation";
import { buildErrors } from "../../../Validation/error";
import { logoImageValidation } from "../../../Validation/validation";
import { formatBytes } from "../../../Utils/fileUtils";
import ProgressUpload from "../../ProgressBars";
import ImageIcon from "@mui/icons-material/Image";
import { HttpAdornment } from "../../Inputs/TextInput/Adornments";
import { StatusFormContext } from "../../../Pages/Status/CreateStatusContext";
import ColorPicker from "../../Inputs/ColorPicker";
import Checkbox from "../../Inputs/Checkbox";
/**
* General settings panel is ued to compose part of the public static page
* for general informations like company name, subdomain url, logo and color etc
*/
const GeneralSettingsPanel = () => {
const theme = useTheme();
const { form, setForm, errors, setErrors } = useContext(StatusFormContext);
const theme = useTheme();
const { form, setForm, errors, setErrors, handleBlur, handleChange, handelCheckboxChange } =
useContext(StatusFormContext);
const [logo, setLogo] = useState(form.logo);
const [progress, setProgress] = useState({ value: 0, isLoading: false });
const intervalRef = useRef(null);
const SUBDOAMIN_PREFIX =
import.meta.env.VITE_STATUS_PAGE_SUBDOMAIN_PREFIX ?? "http://localhost/";
const STATUS_PAGE = import.meta.env.VITE_STATU_PAGE_URL?? "status-page";
const STATUS_PAGE = import.meta.env.VITE_STATU_PAGE_URL ?? "status-page";
// Clears specific error from errors state
const clearError = (err) => {
@@ -59,29 +55,6 @@ const GeneralSettingsPanel = () => {
...prev,
color: newValue,
}));
}
const handleChange = (event) => {
event.preventDefault();
const { value, id, name } = event.target;
setForm((prev) => ({
...prev,
[id ?? name]: value,
}));
};
const handleBlur = (event) => {
event.preventDefault();
const { value, id } = event.target;
const { error } = publicPageGeneralSettingsValidation.validate(
{ [id]: value },
{
abortEarly: false,
}
);
setErrors((prev) => {
return buildErrors(prev, id, error);
});
};
const validateField = (toValidate, schema, name = "logo") => {
@@ -105,7 +78,7 @@ const GeneralSettingsPanel = () => {
name: pic.name,
type: pic.type,
size: pic.size,
}
};
setProgress((prev) => ({ ...prev, isLoading: true }));
setLogo(newLogo);
setForm({ ...form, logo: newLogo });
@@ -141,13 +114,14 @@ const GeneralSettingsPanel = () => {
</Stack>
</Box>
<Stack gap={theme.spacing(6)}>
{/* <Checkbox
id="published-to-public"
<Checkbox
id="publish"
label={`Published and visible to the public`}
isChecked={form.publish}
onChange={handleChange}
value={form.publish}
onChange={handelCheckboxChange}
onBlur={handleBlur}
/> */}
/>
</Stack>
</ConfigBox>
@@ -176,13 +150,7 @@ const GeneralSettingsPanel = () => {
type="url"
label="Your status page address"
disabled
value={form.url ?? STATUS_PAGE}
startAdornment={
<HttpAdornment
prefix={SUBDOAMIN_PREFIX}
https={false}
/>
}
value={form.url ?? "/" + STATUS_PAGE}
onChange={handleChange}
onBlur={handleBlur}
helperText={errors["url"]}
@@ -210,46 +178,67 @@ const GeneralSettingsPanel = () => {
items={timezones}
error={errors["display-timezone"]}
/>
<ImageField
id="logo"
src={form.logo?.src ?? logo?.src}
loading={progress.isLoading && progress.value !== 100}
onChange={handleLogo}
isRound={false}
/>
{progress.isLoading || progress.value !== 0 || errors["logo"] ? (
<ProgressUpload
icon={<ImageIcon />}
label={logo?.name}
size={formatBytes(logo?.size)}
progress={progress.value}
onClick={removeLogo}
error={errors["logo"]}
/>
) : logo && logo.type ? (
<Button
variant="contained"
color="secondary"
onClick={removeLogo}
sx={{
width: "100%",
maxWidth: "200px",
alignSelf: "center",
}}
<Stack direction={"column"}>
<Typography
component="h3"
color={theme.palette.text.secondary}
fontWeight={500}
fontSize={13}
sx={{ mb: theme.spacing(-2) }}
>
Remove Logo
</Button>
) : (
""
)}
<ColorPicker
id="color"
label="Color"
value={form.color}
error={errors["color"]}
onChange={handleColorChange}
onBlur={handleBlur}
></ColorPicker>
Logo
</Typography>
<ImageField
id="logo"
src={form.logo?.src ?? logo?.src}
loading={progress.isLoading && progress.value !== 100}
onChange={handleLogo}
isRound={false}
/>
{progress.isLoading || progress.value !== 0 || errors["logo"] ? (
<ProgressUpload
icon={<ImageIcon />}
label={logo?.name}
size={formatBytes(logo?.size)}
progress={progress.value}
onClick={removeLogo}
error={errors["logo"]}
/>
) : logo && logo.type ? (
<Button
variant="contained"
color="secondary"
onClick={removeLogo}
sx={{
width: "100%",
maxWidth: "200px",
alignSelf: "center",
}}
>
Remove Logo
</Button>
) : (
""
)}
</Stack>
<Stack direction={"column"}>
<Typography
component="h3"
color={theme.palette.text.secondary}
fontWeight={500}
fontSize={13}
>
Color
</Typography>
<ColorPicker
id="color"
value={form.color}
error={errors["color"]}
onChange={handleColorChange}
onBlur={handleBlur}
></ColorPicker>
</Stack>
</Stack>
</ConfigBox>
</Stack>

View File

@@ -3,12 +3,13 @@ import Search from "../../../../Inputs/Search";
import {useState} from "react"
import React from "react";
import { Stack } from "@mui/material";
import PropTypes from "prop-types";
/**
*
* @param {*} id The Id of the Server component
* @param {*} monitors The server monitors options
* @param {*} value the option label of the server/monitor, namely the monitor name field
* @param {*} value - Current input value for the Autocomplete
* @param {*} removeItem The function used to remove a single server
* @param {*} onChange The Change handler function to handle when the server value is changed
* used to update the server(monitor) lists*
@@ -49,9 +50,9 @@ const Server = ({ id, value, monitors, onChange, removeItem, dragHandleProps })
Server.propTypes = {
id: PropTypes.string.isRequired,
monitors: PropTypes.array.isRequired,
value: PropTypes.string.isRequired,
removeItem: PropTypes.function.isRequired,
onChange: PropTypes.function.isRequired,
value: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
removeItem: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
dragHandleProps: PropTypes.object.isRequired,
};

View File

@@ -1,6 +1,8 @@
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd";
import Server from "./Server";
import update from "immutability-helper";
import PropTypes from "prop-types";
import { Stack, useTheme } from "@mui/material";
/**
*
@@ -16,7 +18,8 @@ import update from "immutability-helper";
const ServersList = ({ monitors, cards, setCards, form, setForm, removeItem }) => {
const grid = 8;
const theme = useTheme()
const grid = parseInt(theme.spacing(4));
const handleCardChange = (event, val) => {
let newCards;
@@ -63,11 +66,11 @@ const ServersList = ({ monitors, cards, setCards, form, setForm, removeItem }) =
const getItemStyle = (isDragging, draggableStyle) => ({
// some basic styles to make the items look a bit nicer
userSelect: "none",
padding: grid * 2,
padding: grid,
margin: `0 0 ${grid}px 0`,
// change background colour if dragging
background: isDragging ? "lightgreen" : "lightgrey",
background: isDragging ? "#D0D5DD" : "#F8F9F8",
// styles we need to apply on draggables
...draggableStyle,
@@ -76,17 +79,17 @@ const ServersList = ({ monitors, cards, setCards, form, setForm, removeItem }) =
const getListStyle = (isDraggingOver) => ({
background: isDraggingOver ? "lightblue" : "white",
padding: grid,
width: 550,
});
return (
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div
<Stack
{...provided.droppableProps}
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
sx={{...getListStyle(snapshot.isDraggingOver)}}
>
{cards.map((item, index) => (
<Draggable
@@ -95,13 +98,13 @@ const ServersList = ({ monitors, cards, setCards, form, setForm, removeItem }) =
index={index}
>
{(provided, snapshot) => (
<div
<Stack
ref={provided.innerRef}
{...provided.draggableProps}
style={getItemStyle(
sx={{...getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
)}}
>
<Server
key={index}
@@ -112,12 +115,12 @@ const ServersList = ({ monitors, cards, setCards, form, setForm, removeItem }) =
removeItem={removeItem}
dragHandleProps={provided.dragHandleProps}
/>
</div>
</Stack>
)}
</Draggable>
))}
{provided.placeholder}
</div>
</Stack>
)}
</Droppable>
</DragDropContext>
@@ -127,10 +130,10 @@ const ServersList = ({ monitors, cards, setCards, form, setForm, removeItem }) =
ServersList.propTypes = {
monitors: PropTypes.array.isRequired,
cards: PropTypes.array.isRequired,
setCards: PropTypes.function.isRequired,
setCards: PropTypes.func.isRequired,
form: PropTypes.object.isRequired,
setForm: PropTypes.function.isRequired,
removeItem: PropTypes.function.isRequired
setForm: PropTypes.func.isRequired,
removeItem: PropTypes.func.isRequired
};
export default ServersList;

View File

@@ -3,15 +3,17 @@ import { useState } from "react";
import { Box, Tab, useTheme, Stack, Button } from "@mui/material";
import TabContext from "@mui/lab/TabContext";
import TabList from "@mui/lab/TabList";
import { useSelector } from "react-redux";
import GeneralSettingsPanel from "../../../Components/TabPanels/Status/GeneralSettingsPanel";
import ContentPanel from "../../../Components/TabPanels/Status/ContentPanel";
import { publicPageGeneralSettingsValidation } from "../../../Validation/validation";
import { publicPageSettingsValidation } from "../../../Validation/validation";
import { hasValidationErrors } from "../../../Validation/error";
import { StatusFormProvider } from "../CreateStatusContext";
import { formatBytes } from "../../../Utils/fileUtils";
import { useSelector } from "react-redux";
import { createToast } from "../../../Utils/toastUtils";
import { networkService } from "../../../main";
import { buildErrors } from "../../../Validation/error";
/**
* CreateStatus page renders a page with tabs for general settings and contents.
@@ -31,21 +33,21 @@ import { networkService } from "../../../main";
];
const tabList = ["General settings", "Contents"];
const hasInitForm = initForm && Object.keys(initForm).length > 0;
const STATUS_PAGE = import.meta.env.VITE_STATU_PAGE_URL?? "status-page";
const STATUS_PAGE = import.meta.env.VITE_STATU_PAGE_URL?? "status-page";
const [form, setForm] = useState(
hasInitForm
? initForm
: {
companyName: "",
url: STATUS_PAGE,
url: "/"+STATUS_PAGE,
timezone: "America/Toronto",
color: "#4169E1",
//which fields matching below?
//publish: false,
publish: false,
logo: null,
monitors: [],
// showUptimePercentage: false,
// showBarcode: false,
showUptimePercentage: false,
showBarcode: false,
}
);
const setActiveTabOnErrors = () => {
@@ -77,16 +79,15 @@ import { networkService } from "../../../main";
: form.monitors,
theme: mode,
logo: { type: form.logo?.type ?? "", size: form.logo?.size ?? "" },
};
delete localData.logo
};
if (
hasValidationErrors(localData, publicPageGeneralSettingsValidation, setErrors)
hasValidationErrors(localData, publicPageSettingsValidation, setErrors)
) {
setActiveTabOnErrors();
return;
}
//localData.logo = form.logo
localData.logo = form.logo
localData.url = STATUS_PAGE
let config = { authToken: authToken, url: STATUS_PAGE, data: localData };
try {
@@ -100,6 +101,39 @@ import { networkService } from "../../../main";
}
};
const handleChange = (event) => {
event.preventDefault();
const { value, id, name } = event.target;
setForm((prev) => ({
...prev,
[id ?? name]: value
}));
};
const handelCheckboxChange = (e) => {
const { id } = e.target;
setForm((prev) => {
return ({
...prev,
[id ]: !prev[id]
})});
}
const handleBlur = (event) => {
event.preventDefault();
const { value, id } = event.target;
const { error } = publicPageSettingsValidation.validate(
{ [id]: value },
{
abortEarly: false,
}
);
setErrors((prev) => {
return buildErrors(prev, id, error);
});
};
return (
<Stack
className="status"
@@ -151,6 +185,9 @@ import { networkService } from "../../../main";
setForm={setForm}
errors={errors}
setErrors={setErrors}
handleBlur ={handleBlur}
handleChange = {handleChange}
handelCheckboxChange = {handelCheckboxChange}
>
{tabIdx == 0 ? <GeneralSettingsPanel /> : <ContentPanel />}
</StatusFormProvider>

View File

@@ -5,11 +5,33 @@ const StatusFormContext = createContext({
setForm: () => {},
errors: {},
setErrors: () => {},
handleBlur: () => {},
handleChange: () => {},
handelCheckboxChange: () =>{}
});
const StatusFormProvider = ({ form, setForm, errors, setErrors, children }) => {
const StatusFormProvider = ({
form,
setForm,
errors,
setErrors,
handleBlur,
handleChange,
handelCheckboxChange,
children,
}) => {
return (
<StatusFormContext.Provider value={{ form, setForm, errors, setErrors }}>
<StatusFormContext.Provider
value={{
form,
setForm,
errors,
setErrors,
handleBlur,
handleChange,
handelCheckboxChange,
}}
>
{children}
</StatusFormContext.Provider>
);

View File

@@ -189,7 +189,7 @@ const logoImageValidation = joi.object({
}),
});
const publicPageGeneralSettingsValidation = joi.object({
const publicPageSettingsValidation = joi.object({
publish: joi.bool(),
companyName: joi
.string()
@@ -205,7 +205,11 @@ const publicPageGeneralSettingsValidation = joi.object({
"array.empty": "At least one monitor is required",
"any.required": "Monitors are required",
}),
logo: logoImageValidation
logo: logoImageValidation,
showUptimePercentage: joi.boolean(),
showBarcode: joi.boolean(),
showBarcode: joi.boolean()
});
const settingsValidation = joi.object({
ttl: joi.number().required().messages({
@@ -321,6 +325,6 @@ export {
maintenanceWindowValidation,
advancedSettingsValidation,
infrastructureMonitorValidation,
publicPageGeneralSettingsValidation,
publicPageSettingsValidation,
logoImageValidation
};