diff --git a/FrontEnd/src/components/ProfilePage/FormComponents/ChangePassword.js b/FrontEnd/src/components/ProfilePage/FormComponents/ChangePassword.js new file mode 100644 index 000000000..8c7c99590 --- /dev/null +++ b/FrontEnd/src/components/ProfilePage/FormComponents/ChangePassword.js @@ -0,0 +1,125 @@ +import axios from 'axios'; +import { PropTypes } from 'prop-types'; +import { toast } from 'react-toastify'; +import { useEffect } from 'react'; +import { useContext } from 'react'; +import { useForm } from 'react-hook-form'; +import { DirtyFormContext } from '../../../context/DirtyFormContext'; +import PasswordField from './FormFields/PasswordField'; +import Loader from '../../loader/Loader'; +import css from './ChangePassword.module.css'; + + +export default function ChangePassword(props) { + const { setFormIsDirty } = useContext(DirtyFormContext); + const { currentFormNameHandler, curForm } = props; + const { + register, + handleSubmit, + getValues, + watch, + reset, + formState: { errors, isDirty }, + } = useForm({ + mode: 'all', + defaultValues: { + currentPassword: '', + newPassword: '', + reNewPassword: '' + } + }); + + useEffect(() => { + currentFormNameHandler(curForm); + }, [currentFormNameHandler, curForm]); + + useEffect(() => { + setFormIsDirty(isDirty); + }, [isDirty, setFormIsDirty] + ); + + const handleFormSubmit = () => { + axios.post(`${process.env.REACT_APP_BASE_API_URL}/api/auth/users/set_password/`, { + current_password: getValues('currentPassword'), + new_password: getValues('newPassword'), + re_new_password: getValues('reNewPassword') + }) + .then(() => toast.success('Пароль успішно змінено')) + .catch(() => toast.error('Виникла помилка. Можливо, вказано невірний поточний пароль')); + reset(); + }; + + return ( +
+ {props.user + ? +
+ + + + + : + } +
+ ); +} + +ChangePassword.propTypes = { + user: PropTypes.shape({ + id: PropTypes.number.isRequired, + email: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + surname: PropTypes.string.isRequired, + profile_id: PropTypes.number.isRequired, + is_staff: PropTypes.bool.isRequired + }).isRequired, + currentFormNameHandler: PropTypes.func.isRequired, + curForm: PropTypes.string.isRequired +}; + + diff --git a/FrontEnd/src/components/ProfilePage/FormComponents/ChangePassword.module.css b/FrontEnd/src/components/ProfilePage/FormComponents/ChangePassword.module.css new file mode 100644 index 000000000..776533af1 --- /dev/null +++ b/FrontEnd/src/components/ProfilePage/FormComponents/ChangePassword.module.css @@ -0,0 +1,5 @@ +.form__container { + word-wrap: break-word; + width: 530px; + margin-left: 10px; +} \ No newline at end of file diff --git a/FrontEnd/src/components/ProfilePage/FormComponents/FormFields/PasswordField.js b/FrontEnd/src/components/ProfilePage/FormComponents/FormFields/PasswordField.js new file mode 100644 index 000000000..e772d4f07 --- /dev/null +++ b/FrontEnd/src/components/ProfilePage/FormComponents/FormFields/PasswordField.js @@ -0,0 +1,102 @@ +import { useState } from 'react'; +import { PropTypes } from 'prop-types'; +import EyeInvisible from '../../../authorization/EyeInvisible'; +import EyeVisible from '../../../authorization/EyeVisible'; +import css from './PasswordField.module.css'; +import { PASSWORD_PATTERN } from '../../../../constants/constants'; + +const PasswordField = (props) => { + + const errorMessages = { + invalidPassword: 'Пароль не відповідає вимогам', + passwordsDontMatch: 'Паролі не співпадають' + }; + + const [showPassword, setShowPassword] = useState(false); + + const togglePassword = () => { + setShowPassword(!showPassword); + }; + + const { + register, + name, + error, + showError, + watch, + label, + inputId, + checkValid, + checkMatch + } = props; + + return ( +
+
+ + * + + +
+
+
+ { + return value === watch(checkMatch.checkWith) || + value === '' || + errorMessages.passwordsDontMatch; + } : + null + })} + /> +
+ + {!showPassword ? : } + +
+ {(error[name] && showError) ? +
+ {error[name].message} +
+ : + null + } +
+ ); +}; + +PasswordField.propTypes = { + name: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + register: PropTypes.func.isRequired, + watch: PropTypes.func.isRequired, + inputId: PropTypes.string.isRequired, + showError: PropTypes.bool.isRequired, + error: PropTypes.object, + checkValid: PropTypes.bool.isRequired, + checkMatch: PropTypes.shape({ + isCheck: PropTypes.bool.isRequired, + checkWith: PropTypes.string + }) +}; + +export default PasswordField; \ No newline at end of file diff --git a/FrontEnd/src/components/ProfilePage/FormComponents/FormFields/PasswordField.module.css b/FrontEnd/src/components/ProfilePage/FormComponents/FormFields/PasswordField.module.css new file mode 100644 index 000000000..0011f4eb6 --- /dev/null +++ b/FrontEnd/src/components/ProfilePage/FormComponents/FormFields/PasswordField.module.css @@ -0,0 +1,83 @@ +.password-field__item { + display: flex; + width: 257px; + height: 89px; + flex-direction: column; + align-items: flex-start; +} + +.password-field__label-wrapper { + padding-bottom: 9px; +} + +.password-field__label-wrapper span { + padding-right: 5px; + color: #FF4D4F; +} + +.password-field__item input { + border: none; +} +.password-field__item input:focus { + border: none; + outline: none; +} + +.password-field__item input::placeholder { + font-style: normal; + font-family: "Inter", sans-serif; + font-size: 14px; + font-weight: 400; + line-height: 22px; + letter-spacing: -0.01em; + text-align: left; + color: #00000040; +} +.password-field__password { + display: flex; + padding: 4px 12px; + align-items: center; + gap: 4px; + align-self: stretch; + border-radius: 2px; + border: 1px solid #d9d9d9; + background: #fff; +} + +.password-field__password:focus { + border-radius: 2px; + border: 1px solid #1f9a7c; + background: #fff; + outline: none; +} + +.password-field__password:focus-within { + border-radius: 2px; + border: 1px solid #1f9a7c; + background: #fff; + } + +.password-field__password__wrapper { + display: flex; +} + +.password-visibility { + cursor: pointer; + margin-left: auto; +} + +.error-message { + display: flex; + padding: 1px 0px; + align-items: flex-start; + gap: 10px; + align-self: stretch; + flex: 1 0 0; + color: var(--red-red-100, #F34444); + font-family: Roboto; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 22px; +} + diff --git a/FrontEnd/src/components/ProfilePage/ProfilePageComponents/Description.js b/FrontEnd/src/components/ProfilePage/ProfilePageComponents/Description.js index b14ee13b5..ba2fe0f8f 100644 --- a/FrontEnd/src/components/ProfilePage/ProfilePageComponents/Description.js +++ b/FrontEnd/src/components/ProfilePage/ProfilePageComponents/Description.js @@ -9,6 +9,7 @@ const DESCRIPTIONS = { 'AdditionalInfo': 'Інформація про компанію', 'StartupInfo': 'Інформація про стартап', 'Delete': 'Видалення профілю', + 'ChangePassword': 'Зміна паролю користувача' }; const Description = (props) => { diff --git a/FrontEnd/src/components/ProfilePage/ProfilePageComponents/ProfileContent.js b/FrontEnd/src/components/ProfilePage/ProfilePageComponents/ProfileContent.js index 25b60c6dc..4e5f6631a 100644 --- a/FrontEnd/src/components/ProfilePage/ProfilePageComponents/ProfileContent.js +++ b/FrontEnd/src/components/ProfilePage/ProfilePageComponents/ProfileContent.js @@ -13,6 +13,7 @@ import UserInfo from '../FormComponents/UserInfo'; import ProfileFormButton from '../UI/ProfileFormButton/ProfileFormButton'; import MyModal from '../UI/MyModal/MyModal'; import WarnUnsavedDataModal from '../FormComponents/WarnUnsavedDataModal'; +import ChangePassword from '../FormComponents/ChangePassword'; import css from './ProfileContent.module.css'; @@ -53,7 +54,8 @@ const FORM_NAMES = [ 'ProductServiceInfo', 'AdditionalInfo', 'StartupInfo', - 'Delete' + 'Delete', + 'ChangePassword' ]; const ProfileContent = (props) => { @@ -143,6 +145,11 @@ const ProfileContent = (props) => { ); })}
+ + Змінити пароль + @@ -191,6 +198,12 @@ const ProfileContent = (props) => { profile={props.profile} currentFormNameHandler={props.currentFormNameHandler} curForm={FORM_NAMES[5]} />} /> + } />