- Add log and general settins tab validation

- WIP logo uploading in general settings tab
This commit is contained in:
Shemy Gan committed 2024-12-06 10:05:35 -05:00
1 parent 8506291586
commit df36f873ae
3 files changed
+194 -32

No files matched your search

+20 -5
View File
@@ -14,8 +14,9 @@ import { checkImage } from "../../../Utils/fileUtils";
* @returns {JSX.Element} The rendered component.
*/
const ImageField = ({ id, src, loading, onChange }) => {
const ImageField = ({ id, src, loading, onChange, error }) => {
const theme = useTheme();
const error_border_style = error? {borderColor: theme.palette.error.main}: {}
const [isDragging, setIsDragging] = useState(false);
const handleDragEnter = () => {
@@ -28,7 +29,7 @@ const ImageField = ({ id, src, loading, onChange }) => {
return (
<>
{!checkImage(src) || loading ? (
<>
<>
<Box
className="image-field-wrapper"
mt={theme.spacing(8)}
@@ -46,6 +47,7 @@ const ImageField = ({ id, src, loading, onChange }) => {
borderColor: theme.palette.primary.main,
backgroundColor: "hsl(215, 87%, 51%, 0.05)",
},
...error_border_style
}}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
@@ -62,7 +64,7 @@ const ImageField = ({ id, src, loading, onChange }) => {
cursor: "pointer",
maxWidth: "500px",
minHeight: "175px",
zIndex: 1
zIndex: 1,
},
"& fieldset": {
padding: 0,
@@ -79,7 +81,7 @@ const ImageField = ({ id, src, loading, onChange }) => {
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
zIndex:0,
zIndex: 0,
width: "100%",
}}
>
@@ -114,7 +116,7 @@ const ImageField = ({ id, src, loading, onChange }) => {
>
(maximum size: 3MB)
</Typography>
</Stack>
</Stack>
</Box>
<Typography
component="p"
@@ -123,6 +125,19 @@ const ImageField = ({ id, src, loading, onChange }) => {
>
Supported formats: JPG, PNG
</Typography>
{error && (
<Typography
component="span"
className="input-error"
color={theme.palette.error.main}
mt={theme.spacing(2)}
sx={{
opacity: 0.8,
}}
>
{error}
</Typography>
)}
</>
) : (
<Stack
@@ -1,5 +1,5 @@
import { useState, useRef } from "react";
import { Box, Stack, Typography } from "@mui/material";
import { Button, Box, Stack, Typography } from "@mui/material";
import { ConfigBox } from "../../../Pages/Settings/styled";
import Checkbox from "../../Inputs/Checkbox";
import { useTheme } from "@emotion/react";
@@ -10,41 +10,115 @@ import ImageField from "../../Inputs/Image";
import { setMode, setTimezone } from "../../../Features/UI/uiSlice";
import timezones from "../../../Utils/timezones.json";
import Select from "../../Inputs/Select"
import { logoImageValidation, publicPageGeneralSettingsValidation } from "../../../Validation/validation"
import { buildErrors } from "../../../Validation/error";
import { formatBytes } from "../../../Utils/fileUtils";
import ProgressUpload from "../../ProgressBars";
import ImageIcon from "@mui/icons-material/Image";
const GeneralSettingsPanel = () => {
const theme = useTheme();
const dispatch = useDispatch();
const [errors, setErrors] = useState({});
const [file, setFile] = useState();
const [logo, setLogo] = useState();
const [progress, setProgress] = useState({ value: 0, isLoading: false });
const intervalRef = useRef(null);
// Local state for form data, errors, and file handling
const [localData, setLocalData] = useState({
companyName: "",
url: "",
timezone: "America/Toronto",
color: "#4169E1",
color: "#4169E1",
theme: "light",
//which fields matching below?
publish: false,
file: null
logo: null,
});
const handleChange = (event, appendedId) => {
// Clears specific error from errors state
const clearError = (err) => {
setErrors((prev) => {
const updatedErrors = { ...prev };
if (updatedErrors[err]) delete updatedErrors[err];
return updatedErrors;
});
};
const removeLogo = () => {
errors["logo"] && clearError("logo");
setLogo({});
// interrupt interval if image upload is canceled prior to completing the process
clearInterval(intervalRef.current);
setProgress({ value: 0, isLoading: false });
};
// Updates profile image displayed on UI
const handleUpdateLogo = () => {
setProgress({ value: 0, isLoading: false });
setLocalData((prev) => ({
...prev,
logo: logo.src,
}));
};
const handleChange = (event) => {
event.preventDefault();
const { value, id } = event.target;
let name = appendedId ?? idMap[id] ?? id;
setLocalData((prev) => ({
...prev,
[name]: value,
}));
}
...prev,
[id]: value,
}));
};
const handleSubmit = () => {};
const handleBlur = () => {};
const handleBlur = (event) => {
event.preventDefault();
const { value, id } = event.target;
const { error } = publicPageGeneralSettingsValidation.validate(
{ [id]: value },
{
abortEarly: false,
}
);
const handlePicture = (event) => {
setErrors((prev) => {
return buildErrors(prev, id, error);
});
};
const validateField = (toValidate, schema, name = "logo") => {
const { error } = schema.validate(toValidate, { abortEarly: false });
setErrors((prev) => {
let prevErrors = { ...prev };
if (error) prevErrors[name] = error?.details[0].message;
else delete prevErrors[name];
return prevErrors;
});
if (error) return true;
};
const handleLogo = (event) => {
const pic = event.target.files[0];
let error = validateField({ type: pic?.type, size: pic?.size }, logoImageValidation);
if (error) return;
setProgress((prev) => ({ ...prev, isLoading: true }));
setLogo({
src: URL.createObjectURL(pic),
name: pic.name,
size: formatBytes(pic.size),
});
intervalRef.current = setInterval(() => {
const buffer = 12;
setProgress((prev) => {
if (prev.value + buffer >= 100) {
clearInterval(intervalRef.current);
return { value: 100, isLoading: false };
}
return { ...prev, value: prev.value + buffer };
});
}, 120);
};
return (
@@ -79,7 +153,7 @@ const GeneralSettingsPanel = () => {
id="published-to-public"
label={`Published and visible to the public`}
isChecked={localData.publish}
value={localData.publish}
value={"localData.publish"}
onChange={handleChange}
onBlur={handleBlur}
/>
@@ -97,19 +171,24 @@ const GeneralSettingsPanel = () => {
</Box>
<Stack gap={theme.spacing(6)}>
<Field
id="company-name"
id="companyName"
type="text"
label="Company name"
value={localData.companyName}
onChange={handleChange}
onBlur={handleBlur}
error={errors["companyName"]}
/>
<Field
id="suburl"
id="url"
type="url"
label="suburl"
label="SubURL"
placeholder={""}
value={localData.url}
prefix={"http://uptimegenie.com/"}
onChange={handleChange}
onBlur={handleBlur}
error={errors["url"]}
/>
</Stack>
</ConfigBox>
@@ -128,29 +207,71 @@ const GeneralSettingsPanel = () => {
id="display-timezone"
label="Display timezone"
value={localData.timezone}
onChange={(e) => {
dispatch(setTimezone({ timezone: e.target.value }));
}}
onChange={handleChange}
onBlur={handleBlur}
items={timezones}
error={errors["display-timezone"]}
/>
<ImageField
id="update-logo-picture"
src={localData.src}
loading={true}
onChange={handlePicture}
id="logo"
src={localData.logo?.src??logo?.src}
loading={progress.isLoading && progress.value !== 100}
onChange={handleLogo}
/>
{progress.isLoading || progress.value !== 0 || errors["logo"] ? (
<ProgressUpload
icon={<ImageIcon />}
label={logo?.name}
size={logo?.size}
progress={progress.value}
onClick={removeLogo}
error={errors["logo"]}
/>
) : (
""
)}
<Stack
direction="row"
mt={theme.spacing(10)}
gap={theme.spacing(5)}
justifyContent="flex-end"
>
<Button
variant="text"
color="info"
onClick={removeLogo}
>
Remove
</Button>
<Button
variant="contained"
color="primary"
onClick={handleUpdateLogo}
disabled={
(Object.keys(errors).length !== 0 && errors?.logo) ||
progress.value !== 100
? true
: false
}
>
Update
</Button>
</Stack>
<Field
id="color"
label="Color"
value={localData.color}
error={errors["color"]}
onChange={handleChange}
onBlur={handleBlur}
/>
<Select
id="theme"
label="Theme"
value={localData.theme}
onChange={(e) => {
dispatch(setMode(e.target.value));
}}
onChange={handleChange}
onBlur={handleBlur}
items={[
{ _id: "light", name: "Light" },
{ _id: "dark", name: "Dark" },
+26
View File
@@ -120,6 +120,30 @@ const imageValidation = joi.object({
}),
});
const logoImageValidation = joi.object({
type: joi.string().valid("image/jpeg", "image/png").messages({
"any.only": "Invalid file format.",
"string.empty": "File type required.",
}),
size: joi
.number()
.max(800*800)
.messages({
"number.base": "File size must be a number.",
"number.max": "File size must be less than 640000 pixels.",
"number.empty": "File size required.",
}),
});
const publicPageGeneralSettingsValidation = joi.object({
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." }),
theme: joi.string().trim().messages({ "string.empty": "Theme is required." })
}
)
const settingsValidation = joi.object({
ttl: joi.number().required().messages({
"string.empty": "TTL is required",
@@ -220,4 +244,6 @@ export {
maintenanceWindowValidation,
advancedSettingsValidation,
infrastructureMonitorValidation,
publicPageGeneralSettingsValidation,
logoImageValidation
};