diff --git a/Client/src/App.jsx b/Client/src/App.jsx index 03ecf303b..4a05c1de3 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -6,6 +6,7 @@ import Register from "./Pages/Register"; import HomeLayout from "./Layouts/HomeLayout"; import Demo from "./Pages/Demo/Demo"; import PlayGround from "./Pages/PlayGround/PlayGround"; +import Account from "./Pages/Account"; import Monitors from "./Pages/Monitors"; import CreateNewMonitor from "./Pages/CreateNewMonitor"; import Incidents from "./Pages/Incidents"; @@ -53,6 +54,18 @@ function App() { path="settings" element={} /> + } + /> + } + /> + } + /> } /> diff --git a/Client/src/Components/Checkbox/Checkbox.css b/Client/src/Components/Checkbox/Checkbox.css index 27c6310cc..e78fff6da 100644 --- a/Client/src/Components/Checkbox/Checkbox.css +++ b/Client/src/Components/Checkbox/Checkbox.css @@ -33,5 +33,4 @@ font-size: var(--env-var-font-size-medium); color: var(--env-var-color-5); font-weight: 500; - cursor: pointer; } diff --git a/Client/src/Components/Checkbox/CustomizableCheckbox.jsx b/Client/src/Components/Checkbox/CustomizableCheckbox.jsx index 807562f26..e39ea134b 100644 --- a/Client/src/Components/Checkbox/CustomizableCheckbox.jsx +++ b/Client/src/Components/Checkbox/CustomizableCheckbox.jsx @@ -10,9 +10,9 @@ const CustomizableCheckbox = (props) => { onChange={props.handleChange} className="custom-checkbox" type="checkbox" - id="checkbox" + id={props.id} /> - + ); }; diff --git a/Client/src/Components/NavBar/index.jsx b/Client/src/Components/NavBar/index.jsx index c3ef60091..1e28f0aff 100644 --- a/Client/src/Components/NavBar/index.jsx +++ b/Client/src/Components/NavBar/index.jsx @@ -66,10 +66,10 @@ function NavBar() { setAnchorElUser(null); switch (setting) { case "Profile": - console.log("Profile"); + navigate("/account/profile"); break; case "Team": - console.log("Team"); + navigate("/account/team"); break; case "Invite Colleagues": console.log("Invite Colleagues"); diff --git a/Client/src/Components/RadioButton/index.jsx b/Client/src/Components/RadioButton/index.jsx index e9c874bee..40f3fc230 100644 --- a/Client/src/Components/RadioButton/index.jsx +++ b/Client/src/Components/RadioButton/index.jsx @@ -11,6 +11,7 @@ * /> * * @param {Object} props - The component props. + * @param {string} props.id - The id of the radio button. * @param {string} props.title - The title of the radio button. * @param {string} [props.desc] - The description of the radio button. * @param {string} [props.size="small"] - The size of the radio button. @@ -28,7 +29,7 @@ function RadioButton(props) { } + control={} onChange={props.onChange} label={
diff --git a/Client/src/Components/TabPanels/ProfileSettings/PasswordPanel.jsx b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx similarity index 99% rename from Client/src/Components/TabPanels/ProfileSettings/PasswordPanel.jsx rename to Client/src/Components/TabPanels/Account/PasswordPanel.jsx index 56eb9a12a..31664e2a2 100644 --- a/Client/src/Components/TabPanels/ProfileSettings/PasswordPanel.jsx +++ b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx @@ -87,7 +87,7 @@ const PasswordPanel = () => { }; return ( - +
{ }; return ( - +
- First Name + First name @@ -178,7 +178,7 @@ const ProfilePanel = () => {
- Last Name + Last name @@ -232,18 +232,17 @@ const ProfilePanel = () => {
- Your Photo + Your photo This photo will be displayed in your profile page. - {/* TODO - Update placeholder values with redux data */} { return count; }; return ( - +
{ +const FlexibileTextField = ({ + id, + title, + type, + placeholder, + value, + onChange, +}) => { return (
{title}
{ + try { + const { authToken, monitor } = data; + + const res = await axios.post(`${BASE_URL}/monitors`, monitor, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + return thunkApi.rejectWithValue(error.message); + } + } +); + export const getMonitors = createAsyncThunk( "monitors/getMonitors", async (token, thunkApi) => { @@ -99,6 +122,25 @@ const monitorsSlice = createSlice({ state.msg = action.payload ? action.payload.msg : "Getting montiors failed"; + }) + + // ***************************************************** + // Create Monitor + // ***************************************************** + .addCase(createMonitor.pending, (state) => { + state.isLoading = true; + }) + .addCase(createMonitor.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(createMonitor.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to create monitor"; }); }, }); diff --git a/Client/src/Pages/Account/index.css b/Client/src/Pages/Account/index.css new file mode 100644 index 000000000..212292d9b --- /dev/null +++ b/Client/src/Pages/Account/index.css @@ -0,0 +1,200 @@ +.account h1.MuiTypography-root, +.account p.MuiTypography-root, +.account input, +.account button, +.account td, +.account .MuiSelect-select { + font-size: var(--env-var-font-size-medium); +} +.account h1.MuiTypography-root, +.account p.MuiTypography-root, +.account input, +.account .MuiSelect-select { + color: var(--env-var-color-2); +} +.account h1.MuiTypography-root { + font-weight: 600; +} +.account .MuiStack-root:not(.table-stack) { + flex: 1; + -webkit-flex: 1; + -ms-flex: 1; +} +.account .MuiTabPanel-root { + padding: 0; + margin-top: 50px; +} +.account .MuiDivider-root:not(.short-divider) { + margin: 50px 0; +} +.account .MuiDivider-root, +.account .MuiOutlinedInput-root fieldset, +.account .announcement-tile { + border-color: var(--env-var-color-29); +} +.account [class$="-form"] { + width: inherit; +} +.account [class$="__wrapper"], +.account [class$="__wrapper compact"] { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; +} +.edit-profile-form__wrapper .MuiStack-root:not(.row-stack), +.edit-password-form__wrapper .MuiStack-root:not(.row-stack), +.edit-organization-form__wrapper .MuiStack-root:not(.row-stack) { + flex-direction: column; + gap: 8px; + margin-right: 10px; +} +.edit-profile-form__wrapper p.MuiTypography-root, +.delete-profile-form__wrapper p.MuiTypography-root { + opacity: 0.6; +} +#modal-delete-account, +#modal-edit-org-name, +#modal-invite-member { + font-size: var(--env-var-font-size-large); +} +.edit-profile-form__wrapper:not(:first-of-type), +.edit-password-form__wrapper:not(:first-of-type), +.edit-team-form__wrapper:not(:first-of-type) { + margin-top: var(--env-var-spacing-4); +} +.edit-team-form__wrapper.compact { + margin-top: var(--env-var-spacing-3); + justify-content: flex-start; + align-items: center; +} +.edit-profile-form__wrapper input, +.edit-password-form__wrapper input, +#edit-organization-name, +#input-team-member { + padding: 10px; +} +.edit-password-form__wrapper > .announcement-tile { + width: 100%; +} +.edit-password-form__wrapper + > .announcement-tile + .announcement-content-subject { + margin-bottom: 2px; +} +#edit-new-password + div > button > svg, +#edit-confirm-password + div > button > svg, +#edit-current-password + div > button > svg { + color: var(--env-var-color-29); +} +.edit-password-form__wrapper .announcement-tile { + margin: 0; +} +.edit-password-form__wrapper .announcement-tile .announcement-close { + margin-left: auto; +} +.MuiBox-root > .announcement-tile { + border-color: var(--env-var-color-27); + background-color: var(--lighter-env-var-color-27); +} +.MuiBox-root > .announcement-tile .announcement-content-body { + color: var(--env-var-color-28); +} +.MuiBox-root > .announcement-tile .announcement-close svg, +.edit-password-form__wrapper > .announcement-tile .announcement-close svg { + display: none; +} +#org-name-flex-container .Mui-disabled { + color: var(--env-var-color-2) !important; +} + +.edit-team-form__wrapper td { + padding: 10px 0; + padding-top: 20px; + vertical-align: text-top; +} +.edit-team-form__wrapper th { + padding: var(--env-var-spacing-1) 0; +} +.edit-team-form__wrapper th:first-of-type { + padding-left: 20px; +} +.edit-team-form__wrapper td { + color: var(--env-var-color-25); +} + +#select-actions, +#select-role { + padding: 5px 0; + padding-left: 15px; + padding-right: 50px; + line-height: 1.75; +} +.MuiInputBase-root:has(#select-actions) fieldset, +.MuiInputBase-root:has(#select-role) fieldset { + border-color: #a1a7b1; + margin: -1px; + border-width: 1px; +} +.MuiInputBase-root:has(#select-actions):hover fieldset, +.MuiInputBase-root:has(#select-role):hover fieldset { + border-color: var(--env-var-color-2); +} + +.MuiStack-root:has(.MuiBox-root > .members-query) .MuiBox-root { + cursor: pointer; +} +.members-query { + display: inline-flex; + justify-content: center; + align-items: center; + margin-left: 6px; + padding: 2px; + font-size: 12px; + font-weight: 500; + color: var(--env-var-color-3); + background-color: #e0e9fd; + min-width: 22px; + min-height: 22px; + border-radius: 50%; +} + +/* for testing, will remove later */ +@media only screen and (max-width: 1600px) { + .edit-profile-form__wrapper .MuiStack-root:not(.row-stack), + .edit-password-form__wrapper .MuiStack-root:not(.row-stack), + .edit-organization-form__wrapper .MuiStack-root:not(.row-stack) { + margin-right: 30px; + } +} + +.account .MuiStack-root:has(p.MuiTypography-root.input-error) { + position: relative; +} +.account .text-field-icon { + position: absolute; + top: 50%; + transform: translateY(-50%); + width: 18px !important; + height: 18px !important; + opacity: 0.8; +} +.account p.MuiTypography-root.input-error { + color: var(--env-var-color-24); + opacity: 0.8; + position: absolute; + top: calc(100% + 5px); +} +.account .email-text-field, +.account .password-text-field { + margin: 0; +} + +.edit-profile-form__wrapper .MuiFormControl-root, +.edit-password-form__wrapper .MuiFormControl-root { + width: 100%; +} +.edit-profile-form__wrapper .MuiStack-root:has(.MuiFormControl-root), +.edit-password-form__wrapper .MuiStack-root:has(.MuiFormControl-root) { + margin: 0; +} \ No newline at end of file diff --git a/Client/src/Pages/Account/index.jsx b/Client/src/Pages/Account/index.jsx new file mode 100644 index 000000000..1a24bb36e --- /dev/null +++ b/Client/src/Pages/Account/index.jsx @@ -0,0 +1,84 @@ +import PropTypes from "prop-types"; +import { useState } from "react"; +import { Box, Tab, useTheme } from "@mui/material"; +import TabContext from "@mui/lab/TabContext"; +import TabList from "@mui/lab/TabList"; +import "./index.css"; +import ProfilePanel from "../../Components/TabPanels/Account/ProfilePanel"; +import PasswordPanel from "../../Components/TabPanels/Account/PasswordPanel"; +import TeamPanel from "../../Components/TabPanels/Account/TeamPanel"; +import { useNavigate } from "react-router"; + +/** + * Account component renders a settings page with tabs for Profile, Password, and Team settings. + * @param {string} [props.open] - Specifies the initially open tab: 'profile', 'password', or 'team'. + * @returns {JSX.Element} + */ + +const tabList = ["Profile", "Password", "Team"]; + +const Account = ({ open = "profile" }) => { + const theme = useTheme(); + const navigate = useNavigate(); + const tab = open; + const handleTabChange = (event, newTab) => { + navigate(`/account/${newTab}`); + }; + + return ( + + + + + {tabList.map((label, index) => ( + + ))} + + + + + + + + ); +}; + +Account.propTypes = { + open: PropTypes.oneOf(["profile", "password", "team"]), +}; + +export default Account; diff --git a/Client/src/Pages/CreateNewMonitor/index.jsx b/Client/src/Pages/CreateNewMonitor/index.jsx index d4f795b47..19dd1386a 100644 --- a/Client/src/Pages/CreateNewMonitor/index.jsx +++ b/Client/src/Pages/CreateNewMonitor/index.jsx @@ -3,122 +3,124 @@ import ConfigBox from "../../Components/ConfigBox"; import FlexibileTextField from "../../Components/TextFields/Flexibile/FlexibileTextField"; import React, { useState } from "react"; import RadioButton from "../../Components/RadioButton"; -import Dropdown from "../../Components/Dropdown"; import CustomizableCheckBox from "../../Components/Checkbox/CustomizableCheckbox"; import Button from "../../Components/Button"; +import { MenuItem, Select, Typography } from "@mui/material"; +import { useSelector, useDispatch } from "react-redux"; +import { createMonitorValidation } from "../../Validation/validation"; +import { createMonitor } from "../../Features/Monitors/monitorsSlice"; +import { useNavigate } from "react-router-dom"; const CreateNewMonitor = () => { - const portOptions = [ - { label: "Port 1", value: "Port1" }, - { label: "Port 2", value: "Port2" }, - // Add more Ports as needed - ]; + const MS_PER_MINUTE = 60000; + const { user, authToken } = useSelector((state) => state.auth); + const dispatch = useDispatch(); + const navigate = useNavigate(); - const frequencyOptions = [ - { label: "1 minute", value: "1" }, - { label: "5 minutes", value: "5" }, - // Add more Ports as needed - ]; + const [errors, setErrors] = useState({}); - const [incidentEmail, setIncidentEmail] = useState(""); + //General Settings Form + const [generalSettings, setGeneralSettings] = useState({ url: "", name: "" }); + //Checks Form + const [checks, setChecks] = useState({ + type: "", + // port: "" + }); + //Incidents Form + // const [notifications, setNotifications] = useState({ + // viaSms: false, + // viaEmail: false, + // viaOther: false, + // email: "", + // }); + //Advanced Settings Form + const [advancedSettings, setAdvancedSettings] = useState({ + frequency: 1, + // retries: "", + // codes: "", + // redirects: "", + }); + //Proxy Settings Form + // const [proxy, setProxy] = useState({ + // enabled: false, + // protocol: "", + // address: "", + // proxy_port: "", + // }); - const [notifyViaSMS, setNotifyViaSMS] = useState(false); - const [notifyViaEmail, setNotifyViaEmail] = useState(false); - const [notifyViaCurrentEmail, setNotifyViaCurrentEmail] = useState(false); + const handleChange = (event, id, setState, checkbox) => { + const { value } = event.target; + setState((prev) => ({ + ...prev, + [id]: checkbox ? true : value, + })); - const [maxRetries, setMaxRetries] = useState(0); - const [acceptCode, setAcceptCode] = useState(0); - const [maxRedirects, setMaxRedirects] = useState(0); + const validation = createMonitorValidation.validate( + { [id]: value }, + { abortEarly: false } + ); - const [proxy, setProxy] = useState(false); + setErrors((prev) => { + const updatedErrors = { ...prev }; - const [proxyAddress, setProxyAddress] = useState(""); - const [proxyPort, setProxyPort] = useState(""); + if (validation.error) { + updatedErrors[id] = validation.error.details[0].message; + } else { + delete updatedErrors[id]; + } + return updatedErrors; + }); + }; + // const handleCheck = (id, setState) => { + // setState((prev) => ({ + // ...prev, + // [id]: !prev[id], + // })); + // }; - const [selectedMonitorType, setSelectedMonitorType] = useState(""); + const handleCreateNewMonitor = async (event) => { + event.preventDefault(); + //obj to submit + let monitor = { + ...generalSettings, + ...checks, + }; - const [urlToMonitor, setUrlToMonitor] = useState(""); - const [friendlyName, setFriendlyName] = useState(""); + const { error } = createMonitorValidation.validate(monitor, { + abortEarly: false, + }); - const [port, setPort] = useState("Select a port to check"); - const [frequncy, setFrequency] = useState("1 minute"); - - const handlePortOptionChange = (newValue) => { - setPort(newValue); + if (error) { + const newErrors = {}; + error.details.forEach((err) => { + newErrors[err.path[0]] = err.message; + }); + setErrors(newErrors); + } else { + monitor = { + ...monitor, + description: monitor.name, + userId: user._id, + // ...advancedSettings, // TODO frequency should be interval, then we can use spread + interval: advancedSettings.frequency * MS_PER_MINUTE, + }; + try { + const action = await dispatch(createMonitor({ authToken, monitor })); + if (action.meta.requestStatus === "fulfilled") { + navigate("/monitors"); + } + } catch (error) { + alert(error); + } + } }; - const handleFrequencyOptionChange = (newValue) => { - setPort(newValue); - }; - - const handleUrlToMonitor = (event) => { - setUrlToMonitor(event.target.value); - console.log(urlToMonitor); - }; - - const handleFriendlyName = (event) => { - setFriendlyName(event.target.value); - console.log(friendlyName); - }; - - const handleIncidentEmail = (event) => { - setIncidentEmail(event.target.value); - console.log(incidentEmail); - }; - - const handleNotifyViaSMS = (event) => { - setNotifyViaSMS(event.target.checked); - }; - - const handleNotifyViaEmail = (event) => { - setNotifyViaEmail(event.target.checked); - }; - - const handleNotifyViaCurrentEmail = (event) => { - setNotifyViaCurrentEmail(event.target.checked); - }; - - const handleSetMaxRetries = (event) => { - setMaxRetries(event.target.value); - console.log(maxRetries); - }; - - const handleSetAcceptCode = (event) => { - setAcceptCode(event.target.value); - console.log(acceptCode); - }; - - const handleSetMaxRedirects = (event) => { - setMaxRedirects(event.target.value); - console.log(maxRedirects); - }; - - const handleEnableProxy = (event) => { - setProxy(event.target.value); - console.log(proxy); - }; - - const handleproxyAddress = (event) => { - setProxyAddress(event.target.value); - console.log(proxyAddress); - }; - - const handleproxyPort = (event) => { - setProxyPort(event.target.value); - console.log(proxyPort); - }; - - const handleMonitorTypeChange = (value) => { - setSelectedMonitorType(value); - console.log(value); - }; - - const handleCreateNewMonitor = () => { - console.log("Created"); - }; + //select values + // const ports = ["Port 1", "Port 2", "Port 3"]; + const frequencies = [1, 2, 3, 4, 5]; return ( -
+
Create new monitor
{ rightLayout={
+ handleChange(event, "url", setGeneralSettings) + } /> + {errors["url"] ? ( + + {errors["url"]} + + ) : ( + "" + )}
+ handleChange(event, "name", setGeneralSettings) + } /> + {errors["name"] ? ( + + {errors["name"]} + + ) : ( + "" + )}
} /> @@ -163,42 +187,62 @@ const CreateNewMonitor = () => { rightLayout={
handleMonitorTypeChange("http")} + checked={checks.type === "http"} + onChange={(event) => handleChange(event, "type", setChecks)} />
handleMonitorTypeChange("ping")} + checked={checks.type === "ping"} + onChange={(event) => handleChange(event, "type", setChecks)} />
- handleMonitorTypeChange("port")} + checked={checks.type === "port"} + onChange={(event) => handleChange(event, "type", setChecks)} />
- -
+ +
*/} + {errors["type"] ? ( + + {errors["type"]} + + ) : ( + "" + )}
} /> -
+ {/* TODO */} + {/*
{
handleCheck("viaSms", setNotifications)} />
handleCheck("viaEmail", setNotifications)} />
handleCheck("viaOther", setNotifications)} />
+ handleChange(event, "email", setNotifications) + } />
} - /> + /> */}
{
Check frequency
- + handleChange(event, "frequency", setAdvancedSettings) + } + > + {frequencies.map((freq) => ( + + {freq} {freq === 1 ? "minute" : "minutes"} + + ))} + +
+ {/* TODO */} + {/* + handleChange(event, "retries", setAdvancedSettings) + } />
-
- Maximum retries before the service is marked as down -
+ handleChange(event, "codes", setAdvancedSettings) + } />
-
Accepted status codes
-
-
Maximum redirects
- + value={advancedSettings.redirects} + onChange={(event) => + handleChange(event, "redirects", setAdvancedSettings) + } + /> */}
} /> -
+ {/* TODO */} + {/*
{ rightLayout={
handleCheck("enabled", setProxy)} />
-
Proxy protocol
- + handleChange(event, "protocol", setProxy)} + />
-
Proxy address
- + handleChange(event, "address", setProxy)} + />
-
Proxy port
- + handleChange(event, "proxy_port", setProxy)} + />
} - /> + /> */}
@@ -326,10 +405,10 @@ const CreateNewMonitor = () => { level="primary" label="Create new monitor" sx={{ width: "210px", fontSize: "var(--env-var-font-size-medium)" }} - onClick={() => handleCreateNewMonitor()} + onClick={handleCreateNewMonitor} />
-
+ ); }; diff --git a/Client/src/Pages/Login/index.css b/Client/src/Pages/Login/index.css index 68c1df5e2..41020cf67 100644 --- a/Client/src/Pages/Login/index.css +++ b/Client/src/Pages/Login/index.css @@ -45,6 +45,12 @@ font-size: var(--env-var-font-size-medium); } +#login-password-input { + width: 308px; + height: 11px; + font-size: var(--env-var-font-size-medium); +} + .login-form-password-options { display: flex; align-items: center; diff --git a/Client/src/Pages/Monitors/index.jsx b/Client/src/Pages/Monitors/index.jsx index 00df4ee24..5f7b4b9af 100644 --- a/Client/src/Pages/Monitors/index.jsx +++ b/Client/src/Pages/Monitors/index.jsx @@ -16,8 +16,7 @@ const Monitors = () => { return (
- {/* */} - +
); }; diff --git a/Client/src/Pages/Settings/index.css b/Client/src/Pages/Settings/index.css index 0769c9117..e69de29bb 100644 --- a/Client/src/Pages/Settings/index.css +++ b/Client/src/Pages/Settings/index.css @@ -1,204 +0,0 @@ -.settings h1.MuiTypography-root, -.settings p.MuiTypography-root, -.settings input, -.settings button, -.settings td, -.settings .MuiSelect-select { - font-size: var(--env-var-font-size-medium); -} -.settings h1.MuiTypography-root, -.settings p.MuiTypography-root, -.settings input, -.settings .MuiSelect-select { - color: var(--env-var-color-2); -} -.settings h1.MuiTypography-root { - font-weight: 600; -} -.settings .MuiStack-root:not(.table-stack) { - flex: 1; - -webkit-flex: 1; - -ms-flex: 1; -} -.settings .MuiTabPanel-root { - padding: 0; - margin-top: 50px; -} -.settings .MuiDivider-root:not(.short-divider) { - margin: 50px 0; -} -.settings .MuiDivider-root, -.settings .MuiOutlinedInput-root fieldset, -.announcement-tile { - border-color: var(--env-var-color-29); -} -.settings [class$="-form"] { - width: inherit; -} -.settings [class$="__wrapper"], -.settings [class$="__wrapper compact"] { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: flex-start; -} -.edit-profile-form__wrapper .MuiStack-root:not(.row-stack), -.edit-password-form__wrapper .MuiStack-root:not(.row-stack), -.edit-organization-form__wrapper .MuiStack-root:not(.row-stack) { - flex-direction: column; - gap: 8px; - margin-right: 10px; -} -.edit-profile-form__wrapper p.MuiTypography-root, -.delete-profile-form__wrapper p.MuiTypography-root { - opacity: 0.6; -} -#modal-delete-account, -#modal-edit-org-name, -#modal-invite-member { - font-size: var(--env-var-font-size-large); -} -.edit-profile-form__wrapper:not(:first-of-type), -.edit-password-form__wrapper:not(:first-of-type), -.edit-team-form__wrapper:not(:first-of-type) { - margin-top: var(--env-var-spacing-4); -} -.edit-team-form__wrapper.compact { - margin-top: var(--env-var-spacing-3); - justify-content: flex-start; - align-items: center; -} -.edit-profile-form__wrapper input, -.edit-password-form__wrapper input, -#edit-organization-name, -#input-team-member { - padding: 10px; -} -.edit-password-form__wrapper > .announcement-tile { - width: 100%; -} -.edit-password-form__wrapper - > .announcement-tile - .announcement-content-subject { - margin-bottom: 2px; -} -#edit-new-password + div > button > svg, -#edit-confirm-password + div > button > svg, -#edit-current-password + div > button > svg { - color: var(--env-var-color-29); -} -.edit-password-form__wrapper .announcement-tile { - margin: 0; -} -.edit-password-form__wrapper .announcement-tile .announcement-close { - margin-left: auto; -} -.MuiBox-root > .announcement-tile { - border-color: var(--env-var-color-27); - background-color: var(--lighter-env-var-color-27); -} -.MuiBox-root > .announcement-tile .announcement-content-body { - color: var(--env-var-color-28); -} -.MuiBox-root > .announcement-tile .announcement-close svg, -.edit-password-form__wrapper > .announcement-tile .announcement-close svg { - display: none; -} -#org-name-flex-container .Mui-disabled { - color: var(--env-var-color-2) !important; -} - -.edit-team-form__wrapper td { - padding: 10px 0; - padding-top: 20px; - vertical-align: text-top; -} -.edit-team-form__wrapper th { - padding: var(--env-var-spacing-1) 0; -} -.edit-team-form__wrapper th:first-of-type { - padding-left: 20px; -} -.edit-team-form__wrapper td { - color: var(--env-var-color-25); -} - -#select-actions, -#select-role { - padding: 5px 0; - padding-left: 15px; - padding-right: 50px; - line-height: 1.75; -} -.MuiInputBase-root:has(#select-actions) fieldset, -.MuiInputBase-root:has(#select-role) fieldset { - border-color: #a1a7b1; - margin: -1px; - border-width: 1px; -} -.MuiInputBase-root:has(#select-actions):hover fieldset, -.MuiInputBase-root:has(#select-role):hover fieldset { - border-color: var(--env-var-color-2); -} - -.MuiStack-root:has(.MuiBox-root > .members-query) .MuiBox-root { - cursor: pointer; -} -.members-query { - display: inline-flex; - justify-content: center; - align-items: center; - margin-left: 6px; - padding: 2px; - font-size: 12px; - font-weight: 500; - color: var(--env-var-color-3); - background-color: #e0e9fd; - min-width: 22px; - min-height: 22px; - border-radius: 50%; -} - -/* for testing, will remove later */ -@media only screen and (max-width: 1600px) { - .settings { - max-width: calc(90vw - 250px) !important; - } - .edit-profile-form__wrapper .MuiStack-root:not(.row-stack), - .edit-password-form__wrapper .MuiStack-root:not(.row-stack), - .edit-organization-form__wrapper .MuiStack-root:not(.row-stack) { - margin-right: 30px; - } -} - -.settings .MuiStack-root:has(p.MuiTypography-root.input-error) { - position: relative; - margin-bottom: 8px; -} -.settings .text-field-icon { - position: absolute; - top: 50%; - transform: translateY(-50%); - width: 18px !important; - height: 18px !important; - opacity: 0.8; -} -.settings p.MuiTypography-root.input-error { - color: var(--env-var-color-24); - opacity: 0.8; - position: absolute; - top: calc(100% + 5px); -} -.settings .email-text-field, -.settings .password-text-field { - margin: 0; -} - -.edit-profile-form__wrapper .MuiFormControl-root, -.edit-password-form__wrapper .MuiFormControl-root { - width: 100%; -} -.edit-profile-form__wrapper .MuiStack-root:has(.MuiFormControl-root), -.edit-password-form__wrapper .MuiStack-root:has(.MuiFormControl-root) { - margin: 0; -} \ No newline at end of file diff --git a/Client/src/Pages/Settings/index.jsx b/Client/src/Pages/Settings/index.jsx index 3dd47859b..3ca261a80 100644 --- a/Client/src/Pages/Settings/index.jsx +++ b/Client/src/Pages/Settings/index.jsx @@ -1,86 +1,7 @@ -import PropTypes from "prop-types"; -import { useState } from "react"; -import { Box, Tab, useTheme } from "@mui/material"; -import TabContext from "@mui/lab/TabContext"; -import TabList from "@mui/lab/TabList"; -import "./index.css"; -import ProfilePanel from "../../Components/TabPanels/ProfileSettings/ProfilePanel"; -import PasswordPanel from "../../Components/TabPanels/ProfileSettings/PasswordPanel"; -import TeamPanel from "../../Components/TabPanels/ProfileSettings/TeamPanel"; - -/** - * Settings component renders a settings page with tabs for Profile, Password, and Team settings. - * - * @returns {JSX.Element} - */ - -const tabList = ["Profile", "Password", "Team"]; +import React from "react"; const Settings = () => { - const theme = useTheme(); - //(tab) 0 - Profile - //(tab) 1 - Password - //(tab) 2 - Team - const [tab, setTab] = useState("0"); - const handleTabChange = (event, newTab) => { - setTab(newTab); - }; - - return ( - - - - - {tabList.map((label, index) => ( - - ))} - - - - - - - - ); -}; - -Settings.propTypes = { - // No props are being passed to this component, hence no specific PropTypes are defined. + return
Settings
; }; export default Settings; diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js index c2249fc93..802965870 100644 --- a/Client/src/Validation/validation.js +++ b/Client/src/Validation/validation.js @@ -82,43 +82,41 @@ const editProfileValidation = joi.object({ }), }); +const passwordSchema = joi + .string() + .trim() + .min(8) + .messages({ + "string.empty": "*Password is required.", + "string.min": "*Password must be at least 8 characters long.", + }) + .custom((value, helpers) => { + if (!/[A-Z]/.test(value)) { + return helpers.message( + "*Password must contain at least one uppercase letter." + ); + } + if (!/[a-z]/.test(value)) { + return helpers.message( + "*Password must contain at least one lowercase letter." + ); + } + if (!/\d/.test(value)) { + return helpers.message("*Password must contain at least one number."); + } + if (!/[!@#$%^&*]/.test(value)) { + return helpers.message( + "*Password must contain at least one special character." + ); + } + + return value; + }); + const editPasswordValidation = joi.object({ // TBD - validation for current password ? - password : joi - .string().trim() - .messages({ - "string.empty": "*Current password is required.", - }), - newpassword: joi - .string() - .trim() - .min(8) - .messages({ - "string.empty": "*Password is required.", - "string.min": "*Password must be at least 8 characters long.", - }) - .custom((value, helpers) => { - if (!/[A-Z]/.test(value)) { - return helpers.message( - "*Password must contain at least one uppercase letter." - ); - } - if (!/[a-z]/.test(value)) { - return helpers.message( - "*Password must contain at least one lowercase letter." - ); - } - if (!/\d/.test(value)) { - return helpers.message("*Password must contain at least one number."); - } - if (!/[!@#$%^&*]/.test(value)) { - return helpers.message( - "*Password must contain at least one special character." - ); - } - - return value; - }), + password: passwordSchema, + newpassword: passwordSchema, confirm: joi .string() .trim() @@ -126,15 +124,22 @@ const editPasswordValidation = joi.object({ "string.empty": "*Password confirmation is required.", }) .custom((value, helpers) => { - const { password } = helpers.prefs.context; - if (value !== password) { + const { newpassword } = helpers.prefs.context; + if (value !== newpassword) { return helpers.message("*Passwords do not match."); } return value; }), }); +const createMonitorValidation = joi.object({ + url: joi.string().trim().messages({ "string.empty": "*Url is required." }), + name: joi.string().trim().messages({ "string.empty": "*Name is required." }), + type: joi.string().trim().messages({ "string.empty": "*Type is required." }), +}); + export { + createMonitorValidation, registerValidation, loginValidation, recoveryValidation, diff --git a/README.md b/README.md index 897daf520..84874ea40 100644 --- a/README.md +++ b/README.md @@ -819,6 +819,18 @@ curl --request GET \ > | -------------- | ----- | > | Method | POST | +##### Body + +> | Name | Type | Notes | Accepted Values | +> | ----------- | --------- | ---------------------- | ---------------------- | +> | userId | `string` | UserId of current user | | +> | name | `string` | Monitor name | | +> | description | `string` | Monitor Description | | +> | type | `string` | Valid email address | `"ping"`|`"http"` | +> | url | `string` | URL of service or IP | | +> | isActive | `boolean` | | | +> | interval | `number` | In ms | | + ###### Response Payload > | Type | Notes | @@ -832,12 +844,15 @@ curl --request POST \ --url http://localhost:5000/api/v1/monitors \ --header \ --header 'Content-Type: application/json' \ - --data '{"userId": "6645079aae0b439371913972", - "name": "Inserting a new Monitor", - "description": "Description", - "url": "https://monitor0.com", + --header 'User-Agent: insomnia/2023.5.8' \ + --data '{ + "userId": "66675891cb17336d84c25d9f", + "name": "Ping Google", + "description": "Google", + "type": "ping", + "url": "8.8.8.8", "isActive": true, - "interval": 60000}' + "interval": 5000}' ``` ##### Sample Response