Files
Checkmate/Client/src/Components/TabPanels/Status/GeneralSettingsPanel.jsx
T

233 lines
6.3 KiB
React

import { useState, useRef, useContext } from "react";
import { Button, Box, Stack, Typography } from "@mui/material";
import { ConfigBox } from "../../../Pages/Settings/styled";
import Checkbox from "../../Inputs/Checkbox";
import { useTheme } from "@emotion/react";
import TabPanel from "@mui/lab/TabPanel";
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 { formatBytes } from "../../../Utils/fileUtils";
import ProgressUpload from "../../ProgressBars";
import ImageIcon from "@mui/icons-material/Image";
import { HttpAdornment } from "../../Inputs/TextInput/Adornments";
import { hasValidationErrors } from "../../../Validation/error";
import { StatusFormContext } from "../../../Pages/Status/TabContext";
const GeneralSettingsPanel = ({errors, setErrors}) => {
const theme = useTheme();
const [logo, setLogo] = useState();
const { form, setForm } = useContext(StatusFormContext);
const [progress, setProgress] = useState({ value: 0, isLoading: false });
const intervalRef = useRef(null);
// 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({});
setForm((prev) => ({
...prev,
logo: logo?.src,
}));
// interrupt interval if image upload is canceled prior to completing the process
clearInterval(intervalRef.current);
setProgress({ value: 0, isLoading: false });
};
const handleChange = (event) => {
event.preventDefault();
const { value, id } = event.target;
setForm((prev) => ({
...prev,
[id]: 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") => {
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,
type: pic.type,
size: 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 (
<TabPanel
value="General Settings"
sx={{
"& h1, & p, & input": {
color: theme.palette.text.tertiary,
},
}}
>
<Stack
component="form"
className="status-general-settings-form"
noValidate
spellCheck="false"
gap={theme.spacing(12)}
mt={theme.spacing(6)}
>
<ConfigBox>
<Box>
<Stack gap={theme.spacing(6)}>
<Typography component="h2">Access</Typography>
<Typography component="p">
If your status page is ready, you can mark it as published.
</Typography>
</Stack>
</Box>
<Stack gap={theme.spacing(6)}>
<Checkbox
id="published-to-public"
label={`Published and visible to the public`}
isChecked={form.publish}
onChange={handleChange}
onBlur={handleBlur}
/>
</Stack>
</ConfigBox>
<ConfigBox>
<Box>
<Stack gap={theme.spacing(6)}>
<Typography component="h2">Basic Information</Typography>
<Typography component="p">
Define company name and the subdomain that your status page points to.
</Typography>
</Stack>
</Box>
<Stack gap={theme.spacing(18)}>
<TextInput
id="companyName"
type="text"
label="Company name"
value={form.companyName}
onChange={handleChange}
onBlur={handleBlur}
helperText={errors["companyName"]}
error={errors["companyName"] ? true : false}
/>
<TextInput
id="url"
type="url"
label="SubURL"
value={form.url}
startAdornment={<HttpAdornment prefix = {"http://uptimegenie.com/"} https={false} />}
onChange={handleChange}
onBlur={handleBlur}
helperText={errors["url"]}
error={errors["url"] ? true : false}
/>
</Stack>
</ConfigBox>
<ConfigBox>
<Box>
<Stack gap={theme.spacing(6)}>
<Typography component="h2">Appearance</Typography>
<Typography component="p">
Define the default look and feel of your public status page.
</Typography>
</Stack>
</Box>
<Stack gap={theme.spacing(6)}>
<Select
id="display-timezone"
label="Display timezone"
value={form.timezone}
onChange={handleChange}
onBlur={handleBlur}
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"]}
/>
) : (
""
)}
<TextInput
id="color"
label="Color"
value={form.color}
error={errors["color"]}
onChange={handleChange}
onBlur={handleBlur}
/>
</Stack>
</ConfigBox>
</Stack>
</TabPanel>
);
};
export default GeneralSettingsPanel;