diff --git a/Client/src/App.jsx b/Client/src/App.jsx index 62b6bd9ff..82c8203a8 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -41,7 +41,6 @@ import { logger } from "./Utils/Logger"; // Import the logger import { networkService } from "./main"; import { Infrastructure } from "./Pages/Infrastructure"; import InfrastructureDetails from "./Pages/Infrastructure/Details"; -import Test from "./Pages/test"; function App() { const AdminCheckedRegister = withAdminCheck(Register); const MonitorsWithAdminProp = withAdminProp(Monitors); @@ -90,11 +89,6 @@ function App() { path="/" element={} > - } - /> - const theme = useTheme(); const colors = { success: theme.palette.success.main, - error: theme.palette.error.contrastText, + error: theme.palette.error.main, info: theme.palette.info.border, }; - return ( { - const theme = useTheme(); - - const [isVisible, setVisible] = useState(false); - - return ( - - ), - endAdornment: type === "password" && ( - - setVisible((show) => !show)} - sx={{ - color: theme.palette.border.dark, - padding: theme.spacing(1), - "&:focus-visible": { - outline: `2px solid ${theme.palette.primary.main}`, - outlineOffset: `2px`, - }, - "& .MuiTouchRipple-root": { - pointerEvents: "none", - display: "none", - }, - }} - > - {!isVisible ? : } - - - ), - }} - /> - {error && ( - - )} - - ); - } -); - -Field.displayName = "Field"; - -Field.propTypes = { - type: PropTypes.oneOf(["text", "password", "url", "email", "description", "number"]), - id: PropTypes.string.isRequired, - name: PropTypes.string, - label: PropTypes.string, - https: PropTypes.bool, - isRequired: PropTypes.bool, - isOptional: PropTypes.bool, - optionalLabel: PropTypes.string, - autoComplete: PropTypes.string, - placeholder: PropTypes.string, - value: PropTypes.string.isRequired, - onChange: PropTypes.func, - onBlur: PropTypes.func, - onInput: PropTypes.func, - error: PropTypes.string, - disabled: PropTypes.bool, - hidden: PropTypes.bool, - className: PropTypes.string, - hideErrorText: PropTypes.bool, -}; - -export default Field; diff --git a/Client/src/Components/Inputs/TextInput/Adornments/index.jsx b/Client/src/Components/Inputs/TextInput/Adornments/index.jsx index 0709b5425..686db183b 100644 --- a/Client/src/Components/Inputs/TextInput/Adornments/index.jsx +++ b/Client/src/Components/Inputs/TextInput/Adornments/index.jsx @@ -29,6 +29,10 @@ export const HttpAdornment = ({ https }) => { ); }; +HttpAdornment.propTypes = { + https: PropTypes.bool.isRequired, +}; + export const PasswordEndAdornment = ({ fieldType, setFieldType }) => { const theme = useTheme(); return ( @@ -55,6 +59,7 @@ export const PasswordEndAdornment = ({ fieldType, setFieldType }) => { ); }; -HttpAdornment.propTypes = { - https: PropTypes.bool.isRequired, +PasswordEndAdornment.propTypes = { + fieldType: PropTypes.string, + setFieldType: PropTypes.func, }; diff --git a/Client/src/Components/Inputs/TextInput/index.jsx b/Client/src/Components/Inputs/TextInput/index.jsx index d97d1ebac..26f051c06 100644 --- a/Client/src/Components/Inputs/TextInput/index.jsx +++ b/Client/src/Components/Inputs/TextInput/index.jsx @@ -6,6 +6,11 @@ import PropTypes from "prop-types"; const getSx = (theme, type, maxWidth) => { const sx = { maxWidth: maxWidth, + "& .MuiFormHelperText-root": { + position: "absolute", + bottom: `-${theme.spacing(24)}`, + minHeight: theme.spacing(24), + }, }; if (type === "url") { @@ -56,6 +61,8 @@ Optional.propTypes = { const TextInput = forwardRef( ( { + id, + name, type, value, placeholder, @@ -63,19 +70,34 @@ const TextInput = forwardRef( isOptional, optionalLabel, onChange, + onBlur, error = false, helperText = null, startAdornment = null, endAdornment = null, label = null, maxWidth = "100%", + flex, + marginTop, + marginRight, + marginBottom, + marginLeft, + disabled = false, + hidden = false, }, ref ) => { const [fieldType, setFieldType] = useState(type); const theme = useTheme(); return ( - + } ); @@ -113,18 +139,28 @@ TextInput.displayName = "TextInput"; TextInput.propTypes = { type: PropTypes.string, + id: PropTypes.string.isRequired, + name: PropTypes.string, value: PropTypes.string, placeholder: PropTypes.string, isRequired: PropTypes.bool, isOptional: PropTypes.bool, optionalLabel: PropTypes.string, onChange: PropTypes.func, + onBlur: PropTypes.func, error: PropTypes.bool, helperText: PropTypes.string, startAdornment: PropTypes.node, endAdornment: PropTypes.node, label: PropTypes.string, maxWidth: PropTypes.string, + flex: PropTypes.number, + marginTop: PropTypes.string, + marginRight: PropTypes.string, + marginBottom: PropTypes.string, + marginLeft: PropTypes.string, + disabled: PropTypes.bool, + hidden: PropTypes.bool, }; export default TextInput; diff --git a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx index 0ea313d6c..ae322156c 100644 --- a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx +++ b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx @@ -3,7 +3,8 @@ import { useState } from "react"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography } from "@mui/material"; import LoadingButton from "@mui/lab/LoadingButton"; -import Field from "../../Inputs/Field"; +import { PasswordEndAdornment } from "../../Inputs/TextInput/Adornments"; +import TextInput from "../../Inputs/TextInput"; import { credentials } from "../../../Validation/validation"; import Alert from "../../Alert"; import { update } from "../../../Features/Auth/authSlice"; @@ -113,7 +114,7 @@ const PasswordPanel = () => { maxWidth={"80ch"} marginInline={"auto"} > - { > Current password - } + flex={1} /> { New password - } + flex={1} /> { Confirm new password - } + flex={1} /> {Object.keys(errors).length > 0 && ( diff --git a/Client/src/Components/TabPanels/Account/ProfilePanel.jsx b/Client/src/Components/TabPanels/Account/ProfilePanel.jsx index 0875891f5..9c00f6c02 100644 --- a/Client/src/Components/TabPanels/Account/ProfilePanel.jsx +++ b/Client/src/Components/TabPanels/Account/ProfilePanel.jsx @@ -3,7 +3,7 @@ import { useRef, useState } from "react"; import TabPanel from "@mui/lab/TabPanel"; import { Box, Button, Divider, Stack, Typography } from "@mui/material"; import Avatar from "../../Avatar"; -import Field from "../../Inputs/Field"; +import TextInput from "../../Inputs/TextInput"; import ImageField from "../../Inputs/Image"; import { credentials, imageValidation } from "../../../Validation/validation"; import { useDispatch, useSelector } from "react-redux"; @@ -229,26 +229,30 @@ const ProfilePanel = () => { First name - Last name - @@ -261,14 +265,14 @@ const ProfilePanel = () => { This is your current email address — it cannot be changed. - logger.warn("disabled")} - // error={errors[idToName["edit-email"]]} disabled={true} + flex={1} /> diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx index 7d9171384..74fae01c6 100644 --- a/Client/src/Components/TabPanels/Account/TeamPanel.jsx +++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx @@ -2,7 +2,7 @@ import { useTheme } from "@emotion/react"; import TabPanel from "@mui/lab/TabPanel"; import { Button, ButtonGroup, Stack, Typography } from "@mui/material"; import { useEffect, useState } from "react"; -import Field from "../../Inputs/Field"; +import TextInput from "../../Inputs/TextInput"; import { credentials } from "../../../Validation/validation"; import { networkService } from "../../../main"; import { createToast } from "../../../Utils/toastUtils"; @@ -338,13 +338,15 @@ const TeamPanel = () => { onClose={closeInviteModal} theme={theme} > - { - { value={localSettings.systemEmailHost} onChange={handleChange} onBlur={handleBlur} - error={errors.systemEmailHost} + error={errors.systemEmailHost ? true : false} + helperText={errors.systemEmailHost} /> - { value={localSettings.systemEmailPort?.toString()} onChange={handleChange} onBlur={handleBlur} - error={errors.systemEmailPort} + error={errors.systemEmailPort ? true : false} + helperText={errors.systemEmailPort} /> - - { value={localSettings.systemEmailPassword} onChange={handleChange} onBlur={handleBlur} - error={errors.systemEmailPassword} + error={errors.systemEmailPassword ? true : false} + helperText={errors.systemEmailPassword} /> @@ -242,7 +248,7 @@ const AdvancedSettings = ({ isAdmin }) => { direction="row" gap={theme.spacing(10)} > - { value={localSettings.jwtTTLNum.toString()} onChange={handleChange} onBlur={handleBlur} - error={errors.jwtTTLNum} + error={errors.jwtTTLNum ? true : false} + helperText={errors.jwtTTLNum} /> { - { handleFormChange("name", event.target.value); }} - error={errors["name"]} + error={errors["name"] ? true : false} + helperText={errors["name"]} /> diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx index 22cc8618e..ba23c5634 100644 --- a/Client/src/Pages/Monitors/Configure/index.jsx +++ b/Client/src/Pages/Monitors/Configure/index.jsx @@ -14,7 +14,8 @@ import { getUptimeMonitorsByTeamId, deleteUptimeMonitor, } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; -import Field from "../../../Components/Inputs/Field"; +import TextInput from "../../../Components/Inputs/TextInput"; +import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; import PauseIcon from "../../../assets/icons/pause-icon.svg?react"; import ResumeIcon from "../../../assets/icons/resume-icon.svg?react"; import Select from "../../../Components/Inputs/Select"; @@ -343,17 +344,17 @@ const Configure = () => { - } id="monitor-url" label="URL to monitor" placeholder="google.com" value={parsedUrl?.host || monitor?.url || ""} disabled={true} - error={errors["url"]} /> - { placeholder="Google" value={monitor?.name || ""} onChange={handleChange} - error={errors["name"]} + error={errors["name"] ? true : false} + helperText={errors["name"]} /> @@ -405,7 +407,7 @@ const Configure = () => { (notification) => notification.type === "emails" ) ? ( - { - } label={monitorTypeMaps[monitor.type].label || "URL to monitor"} https={https} placeholder={monitorTypeMaps[monitor.type].placeholder || ""} value={monitor.url} onChange={handleChange} - error={errors["url"]} + error={errors["url"] ? true : false} + helperText={errors["url"]} /> - { placeholder={monitorTypeMaps[monitor.type].namePlaceholder || ""} value={monitor.name} onChange={handleChange} - error={errors["name"]} + error={errors["name"] ? true : false} + helperText={errors["name"]} /> @@ -381,7 +385,7 @@ const CreateMonitor = () => { (notification) => notification.type === "emails" ) ? ( - { }, }} > - - { isOptional={true} value={monitor?.name || ""} onChange={handleChange} - error={errors.name} + error={errors.name ? true : false} + helperText={errors.name} /> @@ -383,7 +385,7 @@ const PageSpeedConfigure = () => { (notification) => notification.type === "emails" ) ? ( - { - } placeholder="google.com" value={monitor.url} onChange={handleChange} - error={errors["url"]} + error={errors["url"] ? true : false} + helperText={errors["url"]} /> - { placeholder="Google" value={monitor.name} onChange={handleChange} - error={errors["name"]} + error={errors["name"] ? true : false} + helperText={errors["name"]} /> @@ -315,7 +318,7 @@ const CreatePageSpeed = () => { (notification) => notification.type === "emails" ) ? ( - { - Clear all stats. This is irreversible. diff --git a/Client/src/Pages/test.jsx b/Client/src/Pages/test.jsx deleted file mode 100644 index 8f5bb464e..000000000 --- a/Client/src/Pages/test.jsx +++ /dev/null @@ -1,192 +0,0 @@ -import { Stack, Typography } from "@mui/material"; -import Field from "../Components/Inputs/Field"; -import TextInput from "../Components/Inputs/TextInput"; -import { useState, useEffect, useRef } from "react"; -import { HttpAdornment } from "../Components/Inputs/TextInput/Adornments"; -import { PasswordEndAdornment } from "../Components/Inputs/TextInput/Adornments"; -const Test = () => { - const [originalValue, setOriginalValue] = useState(""); - const [originalError, setOriginalError] = useState(""); - - const [newValue, setNewValue] = useState(""); - const [newError, setNewError] = useState(""); - - const [thresholdValue, setThresholdValue] = useState(20); - const [thresholdError, setThresholdError] = useState(""); - - const [thresholdValue2, setThresholdValue2] = useState(20); - const [thresholdError2, setThresholdError2] = useState(""); - - const inputRef = useRef(null); - - useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, []); - - const checkError = (value) => { - if (value !== "clear") { - return "This is an error"; - } - return ""; - }; - - const checkThresholdError = (value) => { - if (value !== 99) { - return "This is a threshold error"; - } - return ""; - }; - const checkThresholdError2 = (value) => { - if (value !== 99) { - return "This is a threshold error 2"; - } - return ""; - }; - - const handleOriginalValue = (e) => { - setOriginalError(checkError(e.target.value)); - setOriginalValue(e.target.value); - }; - - const handleNewValue = (e) => { - setNewError(checkError(e.target.value)); - setNewValue(e.target.value); - }; - - const handleThresholdValue = (e) => { - const parsedVal = parseInt(e.target.value); - setThresholdError(checkThresholdError(parsedVal)); - setThresholdValue(parsedVal); - }; - - const handleThresholdValue2 = (e) => { - const parsedVal = parseInt(e.target.value); - setThresholdError2(checkThresholdError2(parsedVal)); - setThresholdValue2(parsedVal); - }; - - return ( - - - This is a test page for the TextInput component. It is a rationalized Input - component. - - Type anything for an error. - Typing "clear" will clear the error for text based input - Typing "99" will clear the error for threshold based input - - - - - } - onChange={handleNewValue} - error={newError !== ""} - helperText={newError} - /> - - - } - onChange={handleNewValue} - error={newError !== ""} - helperText={newError} - ref={inputRef} - /> - - - - - - Short field for threshold. Easily show/hide error text - - - - {thresholdError} {thresholdError2} - - - ); -}; - -export default Test;