Merge pull request #176 from bluewave-labs/feat/profile-settings-refactor
Refactor Profile Settings elements with their respective components, #172
This commit is contained in:
7 files changed
+413
-138
No files matched your search
@@ -8,13 +8,14 @@ import PropTypes from "prop-types";
|
||||
* @param {string} props.firstName - The users first name
|
||||
* @param {string} props.lastName - The users last name
|
||||
* @param {boolean} props.small - Specifies if avatar should be large
|
||||
* @param {Object} [props.sx] - Additional styles to apply to the button.
|
||||
* @returns {JSX.Element}
|
||||
* @example
|
||||
* // Render a red label
|
||||
* <Avatar src="assets/img" first="Alex" last="Holliday" small />
|
||||
*/
|
||||
|
||||
const Avatar = ({ src, firstName, lastName, small }) => {
|
||||
const Avatar = ({ src, firstName, lastName, small, sx }) => {
|
||||
const borderColor = "#F0F2F4";
|
||||
const smallStyle = small ? { width: 32, height: 32 } : {};
|
||||
return (
|
||||
@@ -25,6 +26,7 @@ const Avatar = ({ src, firstName, lastName, small }) => {
|
||||
display: "inline-flex",
|
||||
border: `0.2rem solid ${borderColor}`,
|
||||
...smallStyle,
|
||||
...sx
|
||||
}}
|
||||
>
|
||||
{src === undefined ? `${firstName[0]}${lastName[0]}` : null}
|
||||
@@ -37,6 +39,7 @@ Avatar.propTypes = {
|
||||
firstName: PropTypes.string.isRequired,
|
||||
lastName: PropTypes.string.isRequired,
|
||||
small: PropTypes.bool,
|
||||
sx: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Avatar;
|
||||
@@ -2,21 +2,12 @@ import TabPanel from "@mui/lab/TabPanel";
|
||||
import React, { useState } from "react";
|
||||
import AnnouncementsDualButtonWithIcon from "../../Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
FormControl,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
OutlinedInput,
|
||||
Stack,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||
import Visibility from "@mui/icons-material/Visibility";
|
||||
import { Box, Divider, Stack, Typography } from "@mui/material";
|
||||
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
||||
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
|
||||
import ButtonSpinner from "../../ButtonSpinner";
|
||||
import PasswordTextField from "../../TextFields/Password/PasswordTextField";
|
||||
import { editPasswordValidation } from "../../../Validation/validation";
|
||||
|
||||
/**
|
||||
* PasswordPanel component manages the form for editing password.
|
||||
@@ -31,20 +22,85 @@ const PasswordPanel = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
//TODO - implement save password function
|
||||
const handleSavePassword = () => {
|
||||
//for testing, will tweak when I implement redux slice
|
||||
const idToName = {
|
||||
"edit-current-password": "password",
|
||||
//TBD - form field naming
|
||||
"edit-new-password": "newpassword",
|
||||
"edit-confirm-password": "confirm",
|
||||
};
|
||||
const [localData, setLocalData] = useState({
|
||||
password: "",
|
||||
newpassword: "",
|
||||
confirm: "",
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const handleChange = (event) => {
|
||||
const { value, id } = event.target;
|
||||
const name = idToName[id];
|
||||
setLocalData((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
|
||||
const validation = editPasswordValidation.validate(
|
||||
{ [name]: value },
|
||||
{ abortEarly: false, context: { newpassword: localData.newpassword } }
|
||||
);
|
||||
|
||||
setErrors((prev) => {
|
||||
const updatedErrors = { ...prev };
|
||||
|
||||
if (validation.error) {
|
||||
updatedErrors[name] = validation.error.details[0].message;
|
||||
} else {
|
||||
delete updatedErrors[name];
|
||||
}
|
||||
return updatedErrors;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
setIsLoading(true);
|
||||
|
||||
const { error } = editPasswordValidation.validate(localData, {
|
||||
abortEarly: false,
|
||||
context: { newpassword: localData.newpassword },
|
||||
});
|
||||
|
||||
if (error) {
|
||||
const newErrors = {};
|
||||
error.details.forEach((err) => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
setErrors(newErrors);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
//TODO - submit logic
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
setIsOpen(false);
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<TabPanel value="1">
|
||||
<form className="edit-password-form" noValidate spellCheck="false">
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="edit-password-form"
|
||||
noValidate
|
||||
spellCheck="false"
|
||||
>
|
||||
<div className="edit-password-form__wrapper">
|
||||
<AnnouncementsDualButtonWithIcon
|
||||
icon={<InfoOutlinedIcon style={{ fill: theme.palette.secondary.main }} />}
|
||||
icon={
|
||||
<InfoOutlinedIcon
|
||||
style={{ fill: theme.palette.secondary.main }}
|
||||
/>
|
||||
}
|
||||
subject="SSO login"
|
||||
body="Since you logged in via SSO, you cannot reset or modify your password."
|
||||
/>
|
||||
@@ -52,64 +108,54 @@ const PasswordPanel = () => {
|
||||
<div className="edit-password-form__wrapper">
|
||||
<Stack>
|
||||
<Typography variant="h4" component="h1">
|
||||
Current Password
|
||||
Current password
|
||||
</Typography>
|
||||
</Stack>
|
||||
<FormControl sx={{ flex: 1 }}>
|
||||
<OutlinedInput
|
||||
<Stack>
|
||||
<PasswordTextField
|
||||
id="edit-current-password"
|
||||
value="RandomPasswordLol"
|
||||
type={showPassword ? "text" : "password"}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={() => setShowPassword((show) => !show)}
|
||||
sx={{
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{!showPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
></OutlinedInput>
|
||||
</FormControl>
|
||||
label={null}
|
||||
placeholder="Enter your current password"
|
||||
autoComplete="current-password"
|
||||
visibility={showPassword}
|
||||
setVisibility={setShowPassword}
|
||||
onChange={handleChange}
|
||||
error={errors[idToName["edit-current-password"]] ? true : false}
|
||||
/>
|
||||
{errors[idToName["edit-current-password"]] ? (
|
||||
<Typography variant="h5" component="p" className="input-error">
|
||||
{errors[idToName["edit-current-password"]]}
|
||||
</Typography>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
<div className="edit-password-form__wrapper">
|
||||
<Stack>
|
||||
<Typography variant="h4" component="h1">
|
||||
Password
|
||||
New password
|
||||
</Typography>
|
||||
</Stack>
|
||||
<FormControl sx={{ flex: 1 }}>
|
||||
<OutlinedInput
|
||||
id="edit-password"
|
||||
value="RandomPasswordLol"
|
||||
type={showPassword ? "text" : "password"}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={() => setShowPassword((show) => !show)}
|
||||
sx={{
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{!showPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
></OutlinedInput>
|
||||
</FormControl>
|
||||
<Stack>
|
||||
<PasswordTextField
|
||||
id="edit-new-password"
|
||||
label={null}
|
||||
placeholder="Enter your new password"
|
||||
autoComplete="new-password"
|
||||
visibility={showPassword}
|
||||
setVisibility={setShowPassword}
|
||||
onChange={handleChange}
|
||||
error={errors[idToName["edit-new-password"]] ? true : false}
|
||||
/>
|
||||
{errors[idToName["edit-new-password"]] ? (
|
||||
<Typography variant="h5" component="p" className="input-error">
|
||||
{errors[idToName["edit-new-password"]]}
|
||||
</Typography>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
<div className="edit-password-form__wrapper">
|
||||
<Stack>
|
||||
@@ -117,6 +163,28 @@ const PasswordPanel = () => {
|
||||
Confirm new password
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<PasswordTextField
|
||||
id="edit-confirm-password"
|
||||
label={null}
|
||||
placeholder="Reenter your new password"
|
||||
autoComplete="new-password"
|
||||
visibility={showPassword}
|
||||
setVisibility={setShowPassword}
|
||||
onChange={handleChange}
|
||||
error={errors[idToName["edit-confirm-password"]] ? true : false}
|
||||
/>
|
||||
{errors[idToName["edit-confirm-password"]] ? (
|
||||
<Typography variant="h5" component="p" className="input-error">
|
||||
{errors[idToName["edit-confirm-password"]]}
|
||||
</Typography>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
<div className="edit-password-form__wrapper">
|
||||
<Stack></Stack>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<AnnouncementsDualButtonWithIcon
|
||||
icon={<WarningAmberOutlinedIcon style={{ fill: "#f79009" }} />}
|
||||
@@ -130,9 +198,10 @@ const PasswordPanel = () => {
|
||||
<ButtonSpinner
|
||||
level="primary"
|
||||
label="Save"
|
||||
onClick={handleSavePassword}
|
||||
onClick={handleSubmit}
|
||||
isLoading={isLoading}
|
||||
loadingText="Saving..."
|
||||
disabled={Object.keys(errors).length !== 0 && true}
|
||||
sx={{
|
||||
paddingX: "40px",
|
||||
}}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useState } from "react";
|
||||
import TabPanel from "@mui/lab/TabPanel";
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Divider,
|
||||
Modal,
|
||||
Stack,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { Box, Divider, Modal, Stack, Typography } from "@mui/material";
|
||||
import ButtonSpinner from "../../ButtonSpinner";
|
||||
import Button from "../../Button";
|
||||
import EmailTextField from "../../TextFields/Email/EmailTextField";
|
||||
import StringTextField from "../../TextFields/Text/TextField";
|
||||
import Avatar from "../../Avatar";
|
||||
import { editProfileValidation } from "../../../Validation/validation";
|
||||
|
||||
/**
|
||||
* ProfilePanel component displays a form for editing user profile information
|
||||
@@ -26,6 +22,45 @@ const ProfilePanel = () => {
|
||||
//TODO - use redux loading state
|
||||
//!! - currently all loading buttons are tied to the same state
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
//for testing, will tweak when I implement redux slice
|
||||
const idToName = {
|
||||
"edit-first-name": "firstname",
|
||||
"edit-last-name": "lastname",
|
||||
"edit-email": "email",
|
||||
};
|
||||
const [localData, setLocalData] = useState({
|
||||
firstname: "",
|
||||
lastname: "",
|
||||
email: "",
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const handleChange = (event) => {
|
||||
const { value, id } = event.target;
|
||||
const name = idToName[id];
|
||||
setLocalData((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
|
||||
const validation = editProfileValidation.validate(
|
||||
{ [name]: value },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
|
||||
setErrors((prev) => {
|
||||
const updatedErrors = { ...prev };
|
||||
|
||||
if (validation.error) {
|
||||
updatedErrors[name] = validation.error.details[0].message;
|
||||
} else {
|
||||
delete updatedErrors[name];
|
||||
}
|
||||
return updatedErrors;
|
||||
});
|
||||
};
|
||||
|
||||
//TODO - implement delete profile picture function
|
||||
const handleDeletePicture = () => {
|
||||
setIsLoading(true);
|
||||
@@ -62,14 +97,23 @@ const ProfilePanel = () => {
|
||||
First Name
|
||||
</Typography>
|
||||
</Stack>
|
||||
{/* TODO - use existing textfield components */}
|
||||
<TextField
|
||||
id="edit-first-name"
|
||||
placeholder="Enter your first name"
|
||||
sx={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
<Stack>
|
||||
<StringTextField
|
||||
id="edit-first-name"
|
||||
label={null}
|
||||
placeholder="Enter your first name"
|
||||
autoComplete="given-name"
|
||||
onChange={handleChange}
|
||||
error={errors[idToName["edit-first-name"]] ? true : false}
|
||||
/>
|
||||
{errors[idToName["edit-first-name"]] ? (
|
||||
<Typography variant="h5" component="p" className="input-error">
|
||||
{errors[idToName["edit-first-name"]]}
|
||||
</Typography>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
<div className="edit-profile-form__wrapper">
|
||||
<Stack>
|
||||
@@ -77,14 +121,23 @@ const ProfilePanel = () => {
|
||||
Last Name
|
||||
</Typography>
|
||||
</Stack>
|
||||
{/* TODO - use existing textfield components */}
|
||||
<TextField
|
||||
id="edit-last-name"
|
||||
placeholder="Enter your last name"
|
||||
sx={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
<Stack>
|
||||
<StringTextField
|
||||
id="edit-last-name"
|
||||
label={null}
|
||||
placeholder="Enter your last name"
|
||||
autoComplete="family-name"
|
||||
onChange={handleChange}
|
||||
error={errors[idToName["edit-last-name"]] ? true : false}
|
||||
/>
|
||||
{errors[idToName["edit-last-name"]] ? (
|
||||
<Typography variant="h5" component="p" className="input-error">
|
||||
{errors[idToName["edit-last-name"]]}
|
||||
</Typography>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
<div className="edit-profile-form__wrapper">
|
||||
<Stack>
|
||||
@@ -95,14 +148,23 @@ const ProfilePanel = () => {
|
||||
After updating, you'll receive a confirmation email.
|
||||
</Typography>
|
||||
</Stack>
|
||||
{/* TODO - use existing textfield components */}
|
||||
<TextField
|
||||
id="edit-email"
|
||||
placeholder="Enter your email"
|
||||
sx={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
<Stack>
|
||||
<EmailTextField
|
||||
id="edit-email"
|
||||
label={null}
|
||||
placeholder="Enter your email"
|
||||
autoComplete="email"
|
||||
onChange={handleChange}
|
||||
error={errors[idToName["edit-email"]] ? true : false}
|
||||
/>
|
||||
{errors[idToName["edit-email"]] ? (
|
||||
<Typography variant="h5" component="p" className="input-error">
|
||||
{errors[idToName["edit-email"]]}
|
||||
</Typography>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
<div className="edit-profile-form__wrapper">
|
||||
<Stack>
|
||||
@@ -114,12 +176,17 @@ const ProfilePanel = () => {
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack className="row-stack" direction="row" alignItems="center">
|
||||
{/* TODO - Use Avatar component instead of @mui */}
|
||||
{/* TODO - Update placeholder values with redux data */}
|
||||
<Avatar
|
||||
alt="Remy Sharp"
|
||||
src="/static/images/avatar/2.jpg"
|
||||
className="icon-button-avatar"
|
||||
style={{ width: "64px", height: "64px" }}
|
||||
firstName="Jackie"
|
||||
lastName="Dawn"
|
||||
sx={{
|
||||
width: "64px",
|
||||
height: "64px",
|
||||
border: "none",
|
||||
mr: "8px",
|
||||
}}
|
||||
/>
|
||||
<ButtonSpinner
|
||||
level="tertiary"
|
||||
@@ -152,6 +219,7 @@ const ProfilePanel = () => {
|
||||
onClick={handleSaveProfile}
|
||||
isLoading={isLoading}
|
||||
loadingText="Saving..."
|
||||
disabled={Object.keys(errors).length !== 0 && true}
|
||||
sx={{
|
||||
paddingX: "40px",
|
||||
}}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import "./passwordTextField.css";
|
||||
import React, { useState } from "react";
|
||||
import { InputAdornment, TextField } from "@mui/material";
|
||||
import { IconButton, InputAdornment, TextField } from "@mui/material";
|
||||
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
|
||||
import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
|
||||
import { useTheme } from "@mui/material";
|
||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||
import Visibility from "@mui/icons-material/Visibility";
|
||||
|
||||
/**
|
||||
* @component
|
||||
@@ -16,6 +18,8 @@ import { useTheme } from "@mui/material";
|
||||
* @param {"error" | "help"} [props.icon] - The type of icon to display (error or help) (optional)
|
||||
* @param {string} props.helperText - The helper text displayed below the text field (optional)
|
||||
* @param {boolean} props.error - A flag indicating if the text field has an error (required)
|
||||
* @param {boolean} props.visibility - A flag indicating if the password visibility is toggled (optional)
|
||||
* @param {function} props.setVisibility - Function to toggle password visibility (optional)
|
||||
* @returns {JSX.Element} - Renders the password text field component with dynamic icon display
|
||||
*/
|
||||
const PasswordTextField = ({
|
||||
@@ -28,6 +32,8 @@ const PasswordTextField = ({
|
||||
icon,
|
||||
helperText,
|
||||
error,
|
||||
visibility,
|
||||
setVisibility,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -48,7 +54,7 @@ const PasswordTextField = ({
|
||||
<div className="password-text-field">
|
||||
<TextField
|
||||
onChange={onChange}
|
||||
type="password"
|
||||
type={visibility ? "text" : "password"}
|
||||
autoComplete={autoComplete}
|
||||
error={error}
|
||||
className="password-text-field-input"
|
||||
@@ -58,26 +64,43 @@ const PasswordTextField = ({
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<div className="icon-holder">
|
||||
{error && showIcon && (
|
||||
<ErrorOutlineIcon
|
||||
className="text-field-icon"
|
||||
style={{ fill: "red", width: "16px", height: "16px" }}
|
||||
/>
|
||||
)}
|
||||
{error && !showIcon && (
|
||||
<ErrorOutlineIcon
|
||||
className="text-field-icon"
|
||||
style={{ fill: "red", width: "16px", height: "16px" }}
|
||||
/>
|
||||
)}
|
||||
{!error && showIcon && (
|
||||
<HelpOutlineIcon
|
||||
className="text-field-icon"
|
||||
style={{ width: "16px", height: "16px" }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{visibility !== undefined ? (
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={() => setVisibility((show) => !show)}
|
||||
tabIndex={-1}
|
||||
sx={{
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{!visibility ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
) : (
|
||||
<div className="icon-holder">
|
||||
{error && showIcon && (
|
||||
<ErrorOutlineIcon
|
||||
className="text-field-icon"
|
||||
style={{ fill: "red", width: "16px", height: "16px" }}
|
||||
/>
|
||||
)}
|
||||
{error && !showIcon && (
|
||||
<ErrorOutlineIcon
|
||||
className="text-field-icon"
|
||||
style={{ fill: "red", width: "16px", height: "16px" }}
|
||||
/>
|
||||
)}
|
||||
{!error && showIcon && (
|
||||
<HelpOutlineIcon
|
||||
className="text-field-icon"
|
||||
style={{ width: "16px", height: "16px" }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -64,8 +64,9 @@
|
||||
margin-top: var(--env-var-spacing-4);
|
||||
}
|
||||
.edit-team-form__wrapper.compact {
|
||||
margin-top: 30px;
|
||||
color: var(--env-var-color-29);
|
||||
margin-top: var(--env-var-spacing-3);
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
.edit-profile-form__wrapper input,
|
||||
.edit-password-form__wrapper input,
|
||||
@@ -81,7 +82,8 @@
|
||||
.announcement-content-subject {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
#edit-password + div > button > svg,
|
||||
#edit-new-password + div > button > svg,
|
||||
#edit-confirm-password + div > button > svg,
|
||||
#edit-current-password + div > button > svg {
|
||||
color: var(--env-var-color-29);
|
||||
}
|
||||
@@ -95,10 +97,12 @@
|
||||
border-color: var(--env-var-color-27);
|
||||
background-color: var(--lighter-env-var-color-27);
|
||||
}
|
||||
.MuiBox-root > .announcement-tile .announcement-content-body,
|
||||
.MuiBox-root > .announcement-tile .announcement-close svg {
|
||||
.MuiBox-root > .announcement-tile .announcement-content-body {
|
||||
color: var(--env-var-color-28);
|
||||
fill: var(--env-var-color-28) !important;
|
||||
}
|
||||
.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;
|
||||
@@ -116,8 +120,7 @@
|
||||
padding-left: 20px;
|
||||
}
|
||||
.edit-team-form__wrapper td {
|
||||
font-size: (--env-var-font-size-medium);
|
||||
color: var(--env-var-color-29);
|
||||
color: var(--env-var-color-25);
|
||||
}
|
||||
|
||||
#select-actions,
|
||||
@@ -155,3 +158,39 @@
|
||||
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 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;
|
||||
}
|
||||
@@ -63,9 +63,82 @@ const newPasswordValidation = joi.object({
|
||||
}),
|
||||
});
|
||||
|
||||
const editProfileValidation = joi.object({
|
||||
firstname: joi.string().trim().pattern(new RegExp("^[A-Za-z]+$")).messages({
|
||||
"string.empty": "*First name is required.",
|
||||
"string.pattern.base": "*First name must contain only letters.",
|
||||
}),
|
||||
lastname: joi.string().trim().pattern(new RegExp("^[A-Za-z]+$")).messages({
|
||||
"string.empty": "*Last name is required.",
|
||||
"string.pattern.base": "*Last name must contain only letters.",
|
||||
}),
|
||||
email: joi
|
||||
.string()
|
||||
.trim()
|
||||
.email({ tlds: { allow: false } })
|
||||
.messages({
|
||||
"string.empty": "*Email is required.",
|
||||
"string.email": "*Invalid email address.",
|
||||
}),
|
||||
});
|
||||
|
||||
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;
|
||||
}),
|
||||
confirm: joi
|
||||
.string()
|
||||
.trim()
|
||||
.messages({
|
||||
"string.empty": "*Password confirmation is required.",
|
||||
})
|
||||
.custom((value, helpers) => {
|
||||
const { password } = helpers.prefs.context;
|
||||
if (value !== password) {
|
||||
return helpers.message("*Passwords do not match.");
|
||||
}
|
||||
return value;
|
||||
}),
|
||||
});
|
||||
|
||||
export {
|
||||
registerValidation,
|
||||
loginValidation,
|
||||
recoveryValidation,
|
||||
newPasswordValidation,
|
||||
editPasswordValidation,
|
||||
editProfileValidation,
|
||||
};
|
||||
@@ -3,9 +3,9 @@
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
/* color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
background-color: #242424; */
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
@@ -45,7 +45,7 @@
|
||||
--env-var-color-27: #fecf60;
|
||||
--lighter-env-var-color-27: rgba(254, 207, 96, 0.1);
|
||||
--env-var-color-28: #f79009;
|
||||
--env-var-color-29: #667085;
|
||||
--env-var-color-29: #D0D5DD;
|
||||
|
||||
--env-var-radius-1: 4px;
|
||||
--env-var-radius-2: 8px;
|
||||
|
||||
Reference in new issue
Block a user