- Update validation

- Switch to active tab when there are errors on it upon submission
- Add createStatusPage function
- Updated JSDOC for functions
This commit is contained in:
Shemy Gan committed 2024-12-24 14:46:54 -05:00
1 parent c535a6f99b
commit d844078ed7
6 files changed
+128 -104

No files matched your search

@@ -3,16 +3,13 @@ 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 { publicPageGeneralSettingsValidation } from "../../../Validation/validation";
import { buildErrors } from "../../../Validation/error";
import Card from "./Card";
import update from "immutability-helper";
import Checkbox from "../../Inputs/Checkbox";
import { StatusFormContext } from "../../../Pages/Status/CreateStatusContext";
const ContentPanel = () => {
const theme = useTheme();
const { form, setForm, errors, setErrors } = useContext(StatusFormContext);
const { form, setForm, errors, setErrors } = useContext(StatusFormContext);
const [cards, setCards] = useState(form.monitors);
const moveCard = useCallback(
@@ -30,44 +27,31 @@ const ContentPanel = () => {
[cards]
);
const handleChange = (event) => {
event.preventDefault();
const { value, id } = event.target;
setForm((prev) => ({
...prev,
[id]: value,
}));
};
const handleCardChange = (event) => {
event.preventDefault();
const { value, id } = event.target;
let idx = cards.findIndex((a) => a.id == id);
let newCards;
if (idx >= 0) {
let x = JSON.parse(JSON.stringify(cards[idx]));
x.url = value;
setCards(update(cards, { $splice: [[idx, 1, x]] }));
} else setCards(update(cards, { $push: [{ id: id, url: value }] }));
}
newCards = update(cards, { $splice: [[idx, 1, x]] });
setCards(newCards);
} else {
newCards = update(cards, { $push: [{ id: id, url: value }] });
setCards(newCards);
}
setForm({ ...form, monitors: newCards });
};
const handleAddNew = () => {
setCards([...cards, { id: "" + Math.random() }]);
const newCards = [...cards, { id: "" + Math.random() }];
setCards(newCards);
setForm({ ...form, monitors: newCards });
};
const removeCard = (id) => {
setCards(cards.filter((c) => c?.id != id));
};
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 newCards = cards.filter((c) => c?.id != id);
setCards(newCards);
setForm({ ...form, monitors: newCards });
};
return (
@@ -101,7 +85,6 @@ const ContentPanel = () => {
className="status-contents-server-list"
sx={{
margin: theme.spacing(20),
//padding: theme.spacing(20),
border: "solid",
borderRadius: theme.shape.borderRadius,
borderColor: theme.palette.border.light,
@@ -113,48 +96,54 @@ const ContentPanel = () => {
},
}}
>
<Box>
<Stack
direction="row"
justifyContent="space-around"
<Stack
direction="row"
justifyContent="space-around"
>
<Typography
component="p"
alignSelf={"center"}
>
<Typography
component="p"
alignSelf={"center"}
>
{" "}
Servers list{" "}
</Typography>
<Button onClick={handleAddNew}>Add New</Button>
{" "}
Servers list{" "}
</Typography>
<Button onClick={handleAddNew}>Add New</Button>
</Stack>
{cards.length > 0 && (
<Stack
id="monitors"
direction="column"
gap={theme.spacing(2)}
>
{cards.map((card, idx) => (
<Card
key={idx}
index={idx}
id={card?.id ?? "" + Math.random()}
text={"" + idx}
moveCard={moveCard}
removeCard={removeCard}
value={card?.url ?? ""}
onChange={handleCardChange}
/>
))}
</Stack>
{cards.length > 0 && (
<Stack
direction="column"
alignItems="center"
gap={theme.spacing(6)}
sx={{
ml: theme.spacing(4),
mr: theme.spacing(8),
mb: theme.spacing(8),
}}
>
{cards.map((card, idx) => (
<Card
key={idx}
index={idx}
id={card?.id ?? "" + Math.random()}
text={"" + idx}
moveCard={moveCard}
removeCard={removeCard}
value={card?.url??""}
onChange = {handleCardChange}
/>
))}
</Stack>
)}
</Box>
)}
{errors["monitors"] && (
<Typography
component="span"
className="input-error"
color={theme.palette.error.main}
sx={{
opacity: 0.8,
}}
>
{errors["monitors"]}
</Typography>
)}
</Box>
</ConfigBox>
<ConfigBox>
<Box>
<Stack gap={theme.spacing(6)}>
@@ -162,7 +151,7 @@ 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(20)}}
>
<Checkbox
id="show-barcode"
@@ -178,7 +167,7 @@ const ContentPanel = () => {
onChange={handleChange}
onBlur={handleBlur}
/>
</Stack>
</Stack> */}
</ConfigBox>
</Stack>
</TabPanel>
@@ -64,7 +64,6 @@ const GeneralSettingsPanel = () => {
abortEarly: false,
}
);
setErrors((prev) => {
return buildErrors(prev, id, error);
});
@@ -186,7 +185,7 @@ const GeneralSettingsPanel = () => {
</Box>
<Stack gap={theme.spacing(6)}>
<Select
id="display-timezone"
id="timezone"
label="Display timezone"
value={form.timezone}
onChange={handleChange}
+23 -17
View File
@@ -1,5 +1,5 @@
import PropTypes from "prop-types";
import { useEffect, useState } from "react";
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";
@@ -21,6 +21,7 @@ import { networkService } from "../../../main";
const CreateStatus = ({ initForm }) => {
const theme = useTheme();
const mode = useSelector((state) => state.ui.mode);
const {authToken} = useSelector((state) => state.auth);
const {apiBaseUrl} = useSelector((state) => state.settings);
const [tabIdx, setTabIdx] = useState(0);
@@ -46,31 +47,36 @@ import { networkService } from "../../../main";
}
);
// switch to the respective tab when there is error on it
useEffect(() => {
const setActiveTabOnErrors = () => {
let newIdx = -1;
Object.keys(errors).map(id=>{
error_tab_maping.map((val, idx) => {
let anyMatch = val.some(vl=> vl==id )
if(anyMatch){
newIdx = idx;
Object.keys(errors).map((id) => {
if (newIdx !== -1) return;
error_tab_maping.map((val, idx) => {
let anyMatch = val.some((vl) => vl == id);
if (anyMatch) {
newIdx = idx;
return;
}
})
})
if (newIdx !== -1) setTabIdx(newIdx);
}, [errors]);
});
});
if (newIdx !== -1) setTabIdx(newIdx);
};
const handleTabChange = (_, newTab) => {
setTabIdx(tabList.indexOf(newTab));
};
const handleSubmit = async () => {
const handleSubmit = async (e) => {
e.preventDefault();
//validate rest of the form
delete form.logo;
//let localData = {...form, monitors: form.monitors.map(m=>m.url)}
let localData = {...form, monitors: form.monitors, theme: mode}
if (hasValidationErrors(form, publicPageGeneralSettingsValidation, setErrors)) {
if (hasValidationErrors(localData, publicPageGeneralSettingsValidation, setErrors)) {
setActiveTabOnErrors();
return;
}
}
// //validate image field, double check if it is required
// let error = validateField(
// { type: logo?.type ?? null, size: logo?.size ?? null },
@@ -78,8 +84,8 @@ import { networkService } from "../../../main";
// );
// if (error) return;
//form.logo = { ...logo, size: formatBytes(logo?.size) };
let config = { authToken: authToken, url: apiBaseUrl, data: form };
//form.logo = { ...logo, size: formatBytes(logo?.size) };
let config = { authToken: authToken, url: apiBaseUrl, data: localData };
try{
const res = await networkService.createStatusPage(config)
if (!res.status === 200) {
+10
View File
@@ -856,6 +856,16 @@ class NetworkService {
},
});
}
async createStatusPage(config) {
const { url, authToken, data } = config;
return this.axiosInstance.post(`/status-page/${url}`, data, {
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
});
}
}
export default NetworkService;
+22 -4
View File
@@ -1,6 +1,16 @@
/**
* Update errors if passed id matches the error.details[0].path, otherwise remove
* the error for the id
* @param {*} prev Previous errors *
* @param {*} id ID of the field whose error is to be either updated or removed
* @param {*} error the error object
* @returns the Update Errors with the specific field with id being either removed or updated
*/
const buildErrors = (prev, id, error) => {
const updatedErrors = { ...prev };
if (error) {
if (error && id == error.details[0].path) {
updatedErrors[id] = error.details[0].message ?? "Validation error";
} else {
delete updatedErrors[id];
@@ -32,7 +42,15 @@ const getTouchedFieldErrors = (validation, touchedErrors) => {
return newErrors;
};
/**
*
* @param {*} form The form object of the submitted form data
* @param {*} validation The Joi validation rules
* @param {*} setErrors The function used to set the local errors
* @returns true if there is no error or false if there is error after validating the form
* the error will be reset to {} if returns false; otherwise the errors object will be set with
* the new value
*/
const hasValidationErrors = (form, validation, setErrors) => {
const { error } = validation.validate(form, {
abortEarly: false,
@@ -66,8 +84,7 @@ const hasValidationErrors = (form, validation, setErrors) => {
newErrors["usage_temperature"] = null;
}
});
console.log("newErrors", newErrors);
if (Object.values(newErrors).some((v) => v)) {
setErrors(newErrors);
return true;
@@ -76,6 +93,7 @@ const hasValidationErrors = (form, validation, setErrors) => {
return false;
}
}
setErrors({});
return false;
};
export { buildErrors, hasValidationErrors, getTouchedFieldErrors };
+10 -8
View File
@@ -150,25 +150,27 @@ const logoImageValidation = joi.object({
});
const publicPageGeneralSettingsValidation = joi.object({
companyName: joi.string().trim().messages({ "string.empty": "Company name is required." }),
publish: joi.bool(),
companyName: joi
.string()
.trim()
.messages({ "string.empty": "Company name is required." }),
url: joi.string().trim().messages({ "string.empty": "URL is required." }),
timezone: joi.string().trim().messages({ "string.empty": "Timezone is required." }),
color: joi.string().trim().messages({ "string.empty": "Color is required." }),
publish: joi.bool(),
theme: joi.string(),
monitors: joi
.array()
.items(joi.string().pattern(/^[0-9a-fA-F]{24}$/))
.required()
.min(1)
.items({ id: joi.string(), url: joi.string().required() })
.messages({
"string.pattern.base": "Must be a valid monitor ID",
"array.base": "Monitors must be an array",
"array.empty": "At least one monitor is required",
"any.required": "Monitors are required",
}),
}
)
});
const settingsValidation = joi.object({
ttl: joi.number().required().messages({
"string.empty": "TTL is required",