diff --git a/Client/package-lock.json b/Client/package-lock.json index a5bd33cf9..c3ed59f91 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -15,8 +15,8 @@ "@mui/lab": "6.0.0-beta.20", "@mui/material": "6.2.1", "@mui/x-charts": "^7.5.1", - "@mui/x-data-grid": "7.23.2", - "@mui/x-date-pickers": "7.23.2", + "@mui/x-data-grid": "7.23.3", + "@mui/x-date-pickers": "7.23.3", "@reduxjs/toolkit": "2.5.0", "axios": "^1.7.4", "dayjs": "1.11.13", @@ -1451,9 +1451,9 @@ } }, "node_modules/@mui/x-data-grid": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.23.2.tgz", - "integrity": "sha512-Xhm4Bh+WiU5MSLFIZ8mE1egHoS0xPEVlwvjYvairPIkxNPubB7f+gtLkuAJ2+m+BYbgO7yDte+Pp7dfjkU+vOA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.23.3.tgz", + "integrity": "sha512-EiM5kut6N/0o0iEYx8A7M3fJqknAa1kcPvGhlX3hH50ERLDeuJaqoKzvRYLBbYKWydHIc+0hHIFcK5oQTXLenw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", @@ -1488,9 +1488,9 @@ } }, "node_modules/@mui/x-date-pickers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.23.2.tgz", - "integrity": "sha512-Kt9VsEnShaBKiaastTYku66UIWptgc9UMA16d0G/0TkfIsvZrAD3iacQR6HHAXWspaFshdfsRmW2JAoFhzKZsg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.23.3.tgz", + "integrity": "sha512-bjTYX/QzD5ZhVZNNnastMUS3j2Hy4p4IXmJgPJ0vKvQBvUdfEO+ZF42r3PJNNde0FVT1MmTzkmdTlz0JZ6ukdw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", @@ -2390,9 +2390,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.17.tgz", - "integrity": "sha512-opAQ5no6LqJNo9TqnxBKsgnkIYHozW9KSTlFVoSUJYh1Fl/sswkEoqIugRSm7tbh6pABtYjGAjW+GOS23j8qbw==", + "version": "18.3.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", + "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -4939,16 +4939,15 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -5455,9 +5454,9 @@ } }, "node_modules/react-router": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz", - "integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==", + "version": "6.28.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.1.tgz", + "integrity": "sha512-2omQTA3rkMljmrvvo6WtewGdVh45SpL9hGiCI9uUrwGGfNFDIvGK4gYJsKlJoNVi6AQZcopSCballL+QGOm7fA==", "license": "MIT", "dependencies": { "@remix-run/router": "1.21.0" @@ -5470,13 +5469,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz", - "integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==", + "version": "6.28.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.1.tgz", + "integrity": "sha512-YraE27C/RdjcZwl5UCqF/ffXnZDxpJdk9Q6jw38SZHjXs7NNdpViq2l2c7fO7+4uWaEfcwfGCv3RSg4e1By/fQ==", "license": "MIT", "dependencies": { "@remix-run/router": "1.21.0", - "react-router": "6.28.0" + "react-router": "6.28.1" }, "engines": { "node": ">=14.0.0" diff --git a/Client/package.json b/Client/package.json index e5f111c30..adf3b9d34 100644 --- a/Client/package.json +++ b/Client/package.json @@ -18,8 +18,8 @@ "@mui/lab": "6.0.0-beta.20", "@mui/material": "6.2.1", "@mui/x-charts": "^7.5.1", - "@mui/x-data-grid": "7.23.2", - "@mui/x-date-pickers": "7.23.2", + "@mui/x-data-grid": "7.23.3", + "@mui/x-date-pickers": "7.23.3", "@reduxjs/toolkit": "2.5.0", "axios": "^1.7.4", "dayjs": "1.11.13", diff --git a/Client/src/Components/BasicTable/index.jsx b/Client/src/Components/BasicTable/index.jsx index e519a079f..cfc3030b0 100644 --- a/Client/src/Components/BasicTable/index.jsx +++ b/Client/src/Components/BasicTable/index.jsx @@ -115,7 +115,7 @@ TablePaginationActions.propTypes = { * @param {boolean} [props.paginated=false] - Flag to enable pagination. * @param {boolean} [props.reversed=false] - Flag to enable reverse order. * @param {number} props.rowsPerPage- Number of rows per page (table). - * + * @param {string} props.emptyMessage - Message to display when there is no data. * @example * const data = { * cols: [ @@ -149,7 +149,7 @@ TablePaginationActions.propTypes = { * */ -const BasicTable = ({ data, paginated, reversed, table }) => { +const BasicTable = ({ data, paginated, reversed, table, emptyMessage = "No data" }) => { const DEFAULT_ROWS_PER_PAGE = 5; const theme = useTheme(); const dispatch = useDispatch(); @@ -216,7 +216,9 @@ const BasicTable = ({ data, paginated, reversed, table }) => { sx={{ cursor: row.handleClick ? "pointer" : "default", "&:hover": { - backgroundColor: theme.palette.background.accent, + filter: "brightness(.75)", + opacity: 0.75, + transition: "filter 0.3s ease, opacity 0.3s ease", }, }} key={row.id} @@ -228,6 +230,16 @@ const BasicTable = ({ data, paginated, reversed, table }) => { ); })} + {displayData.length === 0 && ( + + + {emptyMessage} + + + )} @@ -317,6 +329,7 @@ BasicTable.propTypes = { reversed: PropTypes.bool, rowPerPage: PropTypes.number, table: PropTypes.string, + emptyMessage: PropTypes.string, }; export default BasicTable; diff --git a/Client/src/Pages/Uptime/styled.jsx b/Client/src/Components/ConfigBox/index.jsx similarity index 81% rename from Client/src/Pages/Uptime/styled.jsx rename to Client/src/Components/ConfigBox/index.jsx index f159d44f2..7650dd47f 100644 --- a/Client/src/Pages/Uptime/styled.jsx +++ b/Client/src/Components/ConfigBox/index.jsx @@ -1,12 +1,15 @@ import { Stack, styled } from "@mui/material"; -export const ConfigBox = styled(Stack)(({ theme }) => ({ +const ConfigBox = styled(Stack)(({ theme }) => ({ + display: "flex", flexDirection: "row", + justifyContent: "space-between", + gap: theme.spacing(20), + backgroundColor: theme.palette.background.main, border: 1, borderStyle: "solid", borderColor: theme.palette.border.light, borderRadius: theme.spacing(2), - backgroundColor: theme.palette.background.main, "& > *": { paddingTop: theme.spacing(12), paddingBottom: theme.spacing(18), @@ -24,15 +27,12 @@ export const ConfigBox = styled(Stack)(({ theme }) => ({ paddingRight: theme.spacing(20), paddingLeft: theme.spacing(18), }, - "& h2": { + "& h1, & h2": { color: theme.palette.text.secondary, - fontSize: 15, - fontWeight: 600, - }, - "& h3, & p": { - color: theme.palette.text.tertiary, }, "& p": { - fontSize: 13, + color: theme.palette.text.tertiary, }, })); + +export default ConfigBox; diff --git a/Client/src/Components/IconBox/index.jsx b/Client/src/Components/IconBox/index.jsx new file mode 100644 index 000000000..8000fbd64 --- /dev/null +++ b/Client/src/Components/IconBox/index.jsx @@ -0,0 +1,77 @@ +import { Box, styled } from "@mui/material"; +import PropTypes from "prop-types"; + +/** + * IconBox - A styled box component for rendering icons with consistent sizing and styling + * + * @component + * @param {Object} [props] - Configuration options for the IconBox + * @param {number} [props.height=34] - Height of the icon box + * @param {number} [props.width=34] - Width of the icon box + * @param {number} [props.minWidth=34] - Minimum width of the icon box + * @param {number} [props.borderRadius=4] - Border radius of the icon box + * @param {number} [props.svgWidth=20] - Width of the SVG icon + * @param {number} [props.svgHeight=20] - Height of the SVG icon + * + * @example + * // Basic usage + * + * + * + * + * @example + * // Customized usage + * + * + * + * + * @returns {React.ReactElement} A styled box containing an icon + */ +const IconBox = styled(Box)( + ({ + theme, + height = 34, + width = 34, + minWidth = 34, + borderRadius = 4, + svgWidth = 20, + svgHeight = 20, + }) => ({ + height: height, + minWidth: minWidth, + width: width, + position: "relative", + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.dark, + borderRadius: borderRadius, + backgroundColor: theme.palette.background.accent, + "& svg": { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: svgWidth, + height: svgHeight, + "& path": { + stroke: theme.palette.text.tertiary, + }, + }, + }) +); + +IconBox.propTypes = { + height: PropTypes.number, + width: PropTypes.number, + minWidth: PropTypes.number, + borderRadius: PropTypes.number, + svgWidth: PropTypes.number, + svgHeight: PropTypes.number, +}; + +export default IconBox; diff --git a/Client/src/Components/Inputs/Radio/index.jsx b/Client/src/Components/Inputs/Radio/index.jsx index 7a700e2b7..6291485f0 100644 --- a/Client/src/Components/Inputs/Radio/index.jsx +++ b/Client/src/Components/Inputs/Radio/index.jsx @@ -42,6 +42,9 @@ const Radio = (props) => { width: 16, height: 16, boxShadow: `inset 0 0 0 1px ${theme.palette.secondary.main}`, + "&:not(.Mui-checked)": { + boxShadow: `inset 0 0 0 1px ${theme.palette.text.primary}70`, // Use theme text color for the outline + }, mt: theme.spacing(0.5), }} /> diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx index c944955b9..9a1995726 100644 --- a/Client/src/Components/Inputs/Search/index.jsx +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -40,6 +40,7 @@ const SearchAdornment = () => { //TODO keep search state inside of component const Search = ({ + label, id, options, filteredBy, @@ -76,6 +77,14 @@ const Search = ({ getOptionLabel={(option) => option[filteredBy]} renderInput={(params) => ( + + {label} + { }; ProtectedRoute.propTypes = { - children: PropTypes.elementType.isRequired, + children: PropTypes.element.isRequired, }; export default ProtectedRoute; diff --git a/Client/src/Components/Sidebar/index.jsx b/Client/src/Components/Sidebar/index.jsx index a573b3bb0..4636c1739 100644 --- a/Client/src/Components/Sidebar/index.jsx +++ b/Client/src/Components/Sidebar/index.jsx @@ -21,6 +21,7 @@ import { useDispatch, useSelector } from "react-redux"; import { clearAuthState } from "../../Features/Auth/authSlice"; import { toggleSidebar } from "../../Features/UI/uiSlice"; import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice"; +import ThemeSwitch from "../ThemeSwitch"; import Avatar from "../Avatar"; import LockSvg from "../../assets/icons/lock.svg?react"; import UserSvg from "../../assets/icons/user.svg?react"; @@ -43,6 +44,7 @@ import ChangeLog from "../../assets/icons/changeLog.svg?react"; import Docs from "../../assets/icons/docs.svg?react"; import Folder from "../../assets/icons/folder.svg?react"; import StatusPages from "../../assets/icons/status-pages.svg?react"; +import ChatBubbleOutlineRoundedIcon from "@mui/icons-material/ChatBubbleOutlineRounded"; import "./index.css"; @@ -64,20 +66,31 @@ const menu = [ { name: "Team", path: "account/team", icon: }, ], }, + { + name: "Settings", + icon: , + path: "settings", + }, { name: "Other", icon: , nested: [ - { name: "Settings", path: "settings", icon: }, { name: "Support", path: "support", icon: }, + { + name: "Discussions", + path: "discussions", + icon: , + }, { name: "Docs", path: "docs", icon: }, { name: "Changelog", path: "changelog", icon: }, ], }, ]; +/* TODO this could be a key in nested Path would be the link */ const URL_MAP = { - support: "https://github.com/bluewave-labs/bluewave-uptime/issues", + support: "https://discord.com/invite/NAb6H3UTjK", + discussions: "https://github.com/bluewave-labs/checkmate/discussions", docs: "https://bluewavelabs.gitbook.io/checkmate", changelog: "https://github.com/bluewave-labs/bluewave-uptime/releases", }; @@ -546,28 +559,35 @@ function Sidebar() { {authState.user?.role} - - openPopup(event, "logout")} + + - - - + openPopup(event, "logout")} + > + + + + )} + * ) + * + * @returns {React.ReactElement} A styled box containing the statistic + */ + +const StatBox = ({ heading, subHeading, sx }) => { + const theme = useTheme(); + return ( + + {heading} + {subHeading} + + ); +}; + +StatBox.propTypes = { + heading: PropTypes.string.isRequired, + subHeading: PropTypes.node.isRequired, + sx: PropTypes.object, +}; + +export default StatBox; diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx index 209dbeaed..bbc6c4305 100644 --- a/Client/src/Components/TabPanels/Account/TeamPanel.jsx +++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx @@ -64,7 +64,7 @@ const TeamPanel = () => { }; fetchTeam(); - }, [user]); + }, [authToken]); useEffect(() => { let team = members; @@ -81,8 +81,6 @@ const TeamPanel = () => { { id: 1, name: "NAME" }, { id: 2, name: "EMAIL" }, { id: 3, name: "ROLE" }, - // FEATURE STILL TO BE IMPLEMENTED - // { id: 4, name: "ACTION" }, ], rows: team?.map((member, idx) => { const roles = member.role.map((role) => roleMap[role]).join(","); @@ -104,45 +102,19 @@ const TeamPanel = () => { }, { id: idx + 1, data: member.email }, { - // TODO - Add select dropdown id: idx + 2, data: roles, }, - // FEATURE STILL TO BE IMPLEMENTED - // { - // // TODO - Add delete onClick - // id: idx + 3, - // data: ( - // - // - // - // ), - // }, ], }; }), }; setTableData(data); - }, [members, filter]); + }, [filter, members, roleMap, theme]); useEffect(() => { setIsDisabled(Object.keys(errors).length !== 0 || toInvite.email === ""); }, [errors, toInvite.email]); - - // RENAME ORGANIZATION - // const toggleEdit = () => { - // setOrgStates((prev) => ({ ...prev, isEdit: !prev.isEdit })); - // }; - // const handleRename = () => {}; - - // INVITE MEMBER const [isOpen, setIsOpen] = useState(false); const handleChange = (event) => { @@ -226,58 +198,11 @@ const TeamPanel = () => { }, }} > - {/* FEATURE STILL TO BE IMPLEMENTED */} - {/* - - Organization name - - - - setOrgStates((prev) => ({ - ...prev, - name: event.target.value, - })) - } - disabled={!orgStates.isEdit} - sx={{ - color: theme.palette.otherColors.bluishGray, - "& .Mui-disabled": { - WebkitTextFillColor: "initial !important", - }, - "& .Mui-disabled fieldset": { - borderColor: "transparent !important", - }, - }} - inputProps={{ - sx: { textAlign: "end", padding: theme.spacing(4) }, - }} - /> - - - - - By continuing, you agree to our{" "} - { - window.open( - "https://bluewavelabs.ca/terms-of-service-open-source", - "_blank", - "noreferrer" - ); - }} - sx={{ - "&:hover": { - color: theme.palette.text.tertiary, - }, - }} - > - Terms of Service - {" "} - and{" "} - { - window.open( - "https://bluewavelabs.ca/privacy-policy-open-source", - "_blank", - "noreferrer" - ); - }} - sx={{ - "&:hover": { - color: theme.palette.text.tertiary, - }, - }} - > - Privacy Policy. - - - - - - ); -}; - -LandingPage.propTypes = { - onContinue: PropTypes.func.isRequired, -}; - -/** - * Renders the first step of the login process. - * - * @param {Object} props - * @param {Object} props.form - Form state object. - * @param {Object} props.errors - Object containing form validation errors. - * @param {Function} props.onSubmit - Callback function to handle form submission. - * @param {Function} props.onChange - Callback function to handle form input changes. - * @param {Function} props.onBack - Callback function to handle "Back" button click. - * @returns {JSX.Element} - */ -const StepOne = ({ form, errors, onSubmit, onChange, onBack }) => { - const theme = useTheme(); - const inputRef = useRef(null); - - useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, []); - - return ( - <> - - - Log In - Enter your email address - - - (e.target.value = e.target.value.toLowerCase())} - onChange={onChange} - error={errors.email ? true : false} - helperText={errors.email} - ref={inputRef} - /> - - - - - - - - ); -}; - -StepOne.propTypes = { - form: PropTypes.object.isRequired, - errors: PropTypes.object.isRequired, - onSubmit: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, - onBack: PropTypes.func.isRequired, -}; - -/** - * Renders the second step of the login process, including a password input field. - * - * @param {Object} props - * @param {Object} props.form - Form state object. - * @param {Object} props.errors - Object containing form validation errors. - * @param {Function} props.onSubmit - Callback function to handle form submission. - * @param {Function} props.onChange - Callback function to handle form input changes. - * @param {Function} props.onBack - Callback function to handle "Back" button click. - * @returns {JSX.Element} - */ -const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => { - const theme = useTheme(); - const navigate = useNavigate(); - const inputRef = useRef(null); - const authState = useSelector((state) => state.auth); - - useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, []); - - const handleNavigate = () => { - if (form.email !== "" && !errors.email) { - sessionStorage.setItem("email", form.email); - } - navigate("/forgot-password"); - }; - - return ( - <> - - - Log In - Enter your password - - - } - /> - - - - Continue - - - - - - Forgot password? - - - Reset password - - - - - ); -}; - -StepTwo.propTypes = { - form: PropTypes.object.isRequired, - errors: PropTypes.object.isRequired, - onSubmit: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, - onBack: PropTypes.func.isRequired, -}; - -const Login = () => { - const dispatch = useDispatch(); - const navigate = useNavigate(); - const theme = useTheme(); - - const authState = useSelector((state) => state.auth); - const { authToken } = authState; - - const idMap = { - "login-email-input": "email", - "login-password-input": "password", - }; - - const [form, setForm] = useState({ - email: DEMO !== undefined ? "uptimedemo@demo.com" : "", - password: DEMO !== undefined ? "Demouser1!" : "", - }); - const [errors, setErrors] = useState({}); - const [step, setStep] = useState(0); - - useEffect(() => { - if (authToken) { - navigate("/uptime"); - return; - } - networkService - .doesSuperAdminExist() - .then((response) => { - if (response.data.data === false) { - navigate("/register"); - } - }) - .catch((error) => { - logger.error(error); - }); - }, [authToken, navigate]); - - const handleChange = (event) => { - const { value, id } = event.target; - const name = idMap[id]; - setForm((prev) => ({ - ...prev, - [name]: value, - })); - - const { error } = credentials.validate({ [name]: value }, { abortEarly: false }); - - setErrors((prev) => { - const prevErrors = { ...prev }; - if (error) prevErrors[name] = error.details[0].message; - else delete prevErrors[name]; - return prevErrors; - }); - }; - - const handleSubmit = async (event) => { - event.preventDefault(); - - if (step === 1) { - const { error } = credentials.validate( - { email: form.email }, - { abortEarly: false } - ); - if (error) { - setErrors((prev) => ({ ...prev, email: error.details[0].message })); - createToast({ body: error.details[0].message }); - } else { - setStep(2); - } - } else if (step === 2) { - const { error } = credentials.validate(form, { abortEarly: false }); - - if (error) { - // validation errors - const newErrors = {}; - error.details.forEach((err) => { - newErrors[err.path[0]] = err.message; - }); - setErrors(newErrors); - createToast({ - body: - error.details && error.details.length > 0 - ? error.details[0].message - : "Error validating data.", - }); - } else { - const action = await dispatch(login(form)); - if (action.payload.success) { - navigate("/uptime"); - createToast({ - body: "Welcome back! You're successfully logged in.", - }); - } else { - if (action.payload) { - if (action.payload.msg === "Incorrect password") - setErrors({ - password: "The password you provided does not match our records", - }); - // dispatch errors - createToast({ - body: action.payload.msg, - }); - } else { - // unknown errors - createToast({ - body: "Unknown error.", - }); - } - } - } - } - }; - - return ( - - - - - - - BlueWave Uptime - - .MuiStack-root": { - border: 1, - borderRadius: theme.spacing(5), - borderColor: theme.palette.border.light, - backgroundColor: theme.palette.background.main, - padding: { - xs: theme.spacing(12), - sm: theme.spacing(20), - }, - }, - }} - > - {step === 0 ? ( - setStep(1)} /> - ) : step === 1 ? ( - setStep(0)} - /> - ) : ( - step === 2 && ( - setStep(1)} - /> - ) - )} - - - ); -}; - -export default Login; diff --git a/Client/src/Pages/Auth/Login/Components/EmailStep.jsx b/Client/src/Pages/Auth/Login/Components/EmailStep.jsx index f78193186..ac796e627 100644 --- a/Client/src/Pages/Auth/Login/Components/EmailStep.jsx +++ b/Client/src/Pages/Auth/Login/Components/EmailStep.jsx @@ -1,4 +1,4 @@ -import { useRef, useEffect } from "react"; +import { useRef, useEffect } from "react"; import { Box, Button, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import TextInput from "../../../../Components/Inputs/TextInput"; @@ -30,6 +30,7 @@ const EmailStep = ({ form, errors, onSubmit, onChange }) => { Log In @@ -92,4 +93,4 @@ EmailStep.propTypes = { onChange: PropTypes.func.isRequired, }; -export default EmailStep; \ No newline at end of file +export default EmailStep; diff --git a/Client/src/Pages/Auth/Login/Components/ForgotPasswordLabel.jsx b/Client/src/Pages/Auth/Login/Components/ForgotPasswordLabel.jsx new file mode 100644 index 000000000..0a5fa8153 --- /dev/null +++ b/Client/src/Pages/Auth/Login/Components/ForgotPasswordLabel.jsx @@ -0,0 +1,43 @@ +import { Box, Typography, useTheme } from "@mui/material"; +import PropTypes from "prop-types"; +import { useNavigate } from "react-router"; + +const ForgotPasswordLabel = ({ email, errorEmail }) => { + const theme = useTheme(); + const navigate = useNavigate(); + + const handleNavigate = () => { + if (email !== "" && !errorEmail) { + sessionStorage.setItem("email", email); + } + navigate("/forgot-password"); + }; + + return ( + + + Forgot password? + + + Reset password + + + ); +}; + +ForgotPasswordLabel.proptype = { + email: PropTypes.string.isRequired, + emailError: PropTypes.string.isRequired, +}; + +export default ForgotPasswordLabel; diff --git a/Client/src/Pages/Auth/Login/Components/PasswordStep.jsx b/Client/src/Pages/Auth/Login/Components/PasswordStep.jsx index 34df4959d..b7384ba98 100644 --- a/Client/src/Pages/Auth/Login/Components/PasswordStep.jsx +++ b/Client/src/Pages/Auth/Login/Components/PasswordStep.jsx @@ -1,5 +1,4 @@ import { useRef, useEffect } from "react"; -import { useNavigate } from "react-router-dom"; import { Box, Button, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import LoadingButton from "@mui/lab/LoadingButton"; @@ -22,7 +21,6 @@ import PropTypes from "prop-types"; */ const PasswordStep = ({ form, errors, onSubmit, onChange, onBack }) => { const theme = useTheme(); - const navigate = useNavigate(); const inputRef = useRef(null); const authState = useSelector((state) => state.auth); @@ -32,13 +30,6 @@ const PasswordStep = ({ form, errors, onSubmit, onChange, onBack }) => { } }, []); - const handleNavigate = () => { - if (form.email !== "" && !errors.email) { - sessionStorage.setItem("email", form.email); - } - navigate("/forgot-password"); - }; - return ( <> { - - - Forgot password? - - - Reset password - - ); @@ -156,4 +121,4 @@ PasswordStep.propTypes = { onBack: PropTypes.func.isRequired, }; -export default PasswordStep \ No newline at end of file +export default PasswordStep; diff --git a/Client/src/Pages/Auth/Login/Login.jsx b/Client/src/Pages/Auth/Login/Login.jsx index 1b3a420ea..890c87eb7 100644 --- a/Client/src/Pages/Auth/Login/Login.jsx +++ b/Client/src/Pages/Auth/Login/Login.jsx @@ -11,8 +11,10 @@ import Background from "../../../assets/Images/background-grid.svg?react"; import Logo from "../../../assets/icons/bwu-icon.svg?react"; import { logger } from "../../../Utils/Logger"; import "../index.css"; -import EmailStep from "./Components/EmailStep"; -import PasswordStep from "./Components/PasswordStep"; +import EmailStep from "./Components/EmailStep"; +import PasswordStep from "./Components/PasswordStep"; +import ThemeSwitch from "../../../Components/ThemeSwitch"; +import ForgotPasswordLabel from "./Components/ForgotPasswordLabel"; const DEMO = import.meta.env.VITE_APP_DEMO; @@ -42,7 +44,7 @@ const Login = () => { useEffect(() => { if (authToken) { - navigate("/monitors"); + navigate("/uptime"); return; } networkService @@ -108,7 +110,7 @@ const Login = () => { } else { const action = await dispatch(login(form)); if (action.payload.success) { - navigate("/monitors"); + navigate("/uptime"); createToast({ body: "Welcome back! You're successfully logged in.", }); @@ -174,6 +176,7 @@ const Login = () => { px={{ xs: theme.spacing(12), lg: theme.spacing(20) }} pb={theme.spacing(20)} mx="auto" + rowGap={theme.spacing(8)} sx={{ "& > .MuiStack-root": { border: 1, @@ -205,6 +208,13 @@ const Login = () => { /> ) )} + + + + ); diff --git a/Client/src/Pages/Auth/NewPasswordConfirmed.jsx b/Client/src/Pages/Auth/NewPasswordConfirmed.jsx index c380425c9..2c4ef0106 100644 --- a/Client/src/Pages/Auth/NewPasswordConfirmed.jsx +++ b/Client/src/Pages/Auth/NewPasswordConfirmed.jsx @@ -4,10 +4,10 @@ import { useNavigate } from "react-router"; import { useDispatch } from "react-redux"; import { clearAuthState } from "../../Features/Auth/authSlice"; import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice"; -import { IconBox } from "./styled"; import Background from "../../assets/Images/background-grid.svg?react"; import ConfirmIcon from "../../assets/icons/check-outlined.svg?react"; import Logo from "../../assets/icons/bwu-icon.svg?react"; +import IconBox from "../../Components/IconBox"; import "./index.css"; const NewPasswordConfirmed = () => { @@ -80,9 +80,22 @@ const NewPasswordConfirmed = () => { textAlign="center" > - - - + + + + + Password reset Your password has been successfully reset. Click below to log in magically. @@ -91,7 +104,7 @@ const NewPasswordConfirmed = () => {