-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
525 implement ability to change password #527
Changes from 3 commits
75db25c
66db9ca
3cadd2a
0a9e3c6
394baa6
8ea4c06
8e60a30
6176201
2a2f783
c59ddbd
7b44ebe
101b586
327dc01
1cf6d66
588a1cb
cbcb783
50a6a7b
7750f44
029340d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import axios from 'axios'; | ||
import { PropTypes } from 'prop-types'; | ||
import { toast } from 'react-toastify'; | ||
import { useState, useEffect } from 'react'; | ||
import { useContext } from 'react'; | ||
import { DirtyFormContext } from '../../../context/DirtyFormContext'; | ||
import PasswordField from './FormFields/PasswordField'; | ||
import Loader from '../../loader/Loader'; | ||
import css from './ChangePassword.module.css'; | ||
import { PASSWORD_PATTERN } from '../../../constants/constants'; | ||
|
||
|
||
export default function ChangePassword(props) { | ||
const [formData, setFormData] = useState({ currentPassword: '', newPassword: '', reNewPassword: '' }); | ||
const [passwordsDontMatchError, setPasswordsDontMatchError] = useState(null); | ||
const [invalidPasswordError, setInvalidPasswordError] = useState(null); | ||
const { setFormIsDirty } = useContext(DirtyFormContext); | ||
|
||
useEffect(() => { | ||
props.currentFormNameHandler(props.curForm); | ||
}, [props]); | ||
|
||
useEffect(() => { | ||
setFormIsDirty(Object.keys(formData).some((field) => formData[field] !== '')); | ||
}, [formData] | ||
); | ||
|
||
const handleSubmit = (event) => { | ||
event.preventDefault(); | ||
if (!invalidPasswordError && !passwordsDontMatchError) { | ||
axios.post(`${process.env.REACT_APP_BASE_API_URL}/api/auth/users/set_password/`, { | ||
current_password: formData.currentPassword, | ||
new_password: formData.newPassword, | ||
re_new_password: formData.reNewPassword | ||
}) | ||
.then(() => toast.success('Пароль успішно змінено')) | ||
.catch(() => toast.error('Виникла помилка. Можливо, вказано невірний поточний пароль')); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you! Initially, I was outputting the error status code, but the team advised against it as it's not user-friendly. I'll consider better ways to inform the user. |
||
setFormData({ currentPassword: '', newPassword: '', reNewPassword: '' }); | ||
} | ||
}; | ||
|
||
const handleInputChange = (e) => { | ||
e.preventDefault(); | ||
const updatedFormData = { ...formData, [e.target.name]: e.target.value }; | ||
setFormData(updatedFormData); | ||
(updatedFormData.newPassword !== '' && | ||
!PASSWORD_PATTERN.test(updatedFormData.newPassword)) ? | ||
setInvalidPasswordError('Пароль не відповідає вимогам') : | ||
setInvalidPasswordError(null); | ||
(updatedFormData.reNewPassword !== '' && | ||
updatedFormData.newPassword !== updatedFormData.reNewPassword) ? | ||
setPasswordsDontMatchError('Паролі не співпадають') : | ||
setPasswordsDontMatchError(null); | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function updates state multiple times which can lead to performance issues or unexpected renders. It would be better to consolidate state updates into a single function call if possible There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
return ( | ||
<div className={css['form__container']}> | ||
{props.user | ||
? | ||
<form id="ChangePassword" onSubmit={handleSubmit}> | ||
<PasswordField | ||
name="currentPassword" | ||
label="Поточний пароль" | ||
updateHandler={handleInputChange} | ||
value={formData.currentPassword} | ||
requredField={true} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There seems to be a typo in the |
||
/> | ||
<PasswordField | ||
name="newPassword" | ||
label="Новий пароль" | ||
updateHandler={handleInputChange} | ||
value={formData.newPassword} | ||
requredField={true} | ||
error={invalidPasswordError} | ||
/> | ||
<PasswordField | ||
name="reNewPassword" | ||
label="Повторіть новий пароль" | ||
updateHandler={handleInputChange} | ||
value={formData.reNewPassword} | ||
requredField={true} | ||
error={passwordsDontMatchError} | ||
/> | ||
</form> | ||
: <Loader /> | ||
} | ||
</div> | ||
); | ||
} | ||
|
||
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 | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.form__container { | ||
word-wrap: break-word; | ||
width: 530px; | ||
margin-left: 10px; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
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'; | ||
|
||
const PasswordField = (props) => { | ||
const [showPassword, setShowPassword] = useState(false); | ||
|
||
const togglePassword = () => { | ||
setShowPassword(!showPassword); | ||
}; | ||
|
||
return ( | ||
<div className={css['password-field__item']}> | ||
<div className={css['password-field__label-wrapper']}> | ||
{props.requredField && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, you're right! Initially, I was reusing the existing HalfFormField component in the project where this typo was present (the component was taking prop 'requredField' instead of 'requiredField'), and then I forgot to correct the improper prop passing when I wrote my PasswordField. |
||
<span> | ||
* | ||
</span>} | ||
<label | ||
htmlFor={props.inputId} | ||
> | ||
{props.label} | ||
</label> | ||
</div> | ||
<div className={css['password-field__password']}> | ||
<div className={css['password-field__password__wrapper']}> | ||
<input | ||
id={props.inputId} | ||
name={props.name} | ||
type={showPassword ? 'text' : 'password'} | ||
value={props.value} | ||
onChange={props.updateHandler} | ||
placeholder={props.label} | ||
required={props.requredField} | ||
/> | ||
</div> | ||
<span | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with this point. I added a span there because it's done that way in our LoginPage component. |
||
className={css['password-visibility']} | ||
onClick={togglePassword} | ||
> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you, will be done. |
||
{!showPassword ? <EyeInvisible /> : <EyeVisible />} | ||
</span> | ||
</div> | ||
{(props.requredField || props.error) && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message section will display even if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you, I'll consider it. |
||
<div className={css['error-message']}> | ||
{props.error} | ||
</div> | ||
} | ||
</div> | ||
); | ||
}; | ||
|
||
PasswordField.propTypes = { | ||
name: PropTypes.string.isRequired, | ||
label: PropTypes.string.isRequired, | ||
updateHandler: PropTypes.func.isRequired, | ||
value: PropTypes.string, | ||
inputId: PropTypes.string, | ||
requredField: PropTypes.bool, | ||
error: PropTypes.string | ||
}; | ||
|
||
export default PasswordField; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useEffect
has[props]
as a dependency, which could lead to unnecessary re-executions ifprops
object itself changes but not its values. Consider destructuringprops
outsideuseEffect
and using specific properties in the dependency arraye.g.