- 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:
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}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user