Files
frontend-app-authn/src/reset-password/ResetPasswordPage.jsx
Muhammad Abdullah Waheed 0d603b5fa1 feat: added app name identifier in segment events (#1277)
* feat: added app name identifier in registration call

* feat: added utils for tracking events

* refactor: mapped login events

* refactor: mapped forgot password events

* refactor: mapped reset password events

* refactor: mapped register events

* fix: fixed unit tests

* refactor: mapped progressive prifiling events

* fix: fixed unit tests

* refactor: added app name in logistration events

* refactor: resolved PR reviews and fixed tests
2024-07-03 17:08:44 +05:00

235 lines
7.7 KiB
JavaScript

import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Form,
Icon,
Spinner,
StatefulButton,
Tab,
Tabs,
} from '@openedx/paragon';
import { ChevronLeft } from '@openedx/paragon/icons';
import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import { useNavigate, useParams } from 'react-router-dom';
import { resetPassword, validateToken } from './data/actions';
import {
FORM_SUBMISSION_ERROR, PASSWORD_RESET_ERROR, PASSWORD_VALIDATION_ERROR, SUCCESS, TOKEN_STATE,
} from './data/constants';
import { resetPasswordResultSelector } from './data/selectors';
import { validatePassword } from './data/service';
import messages from './messages';
import ResetPasswordFailure from './ResetPasswordFailure';
import BaseContainer from '../base-container';
import { PasswordField } from '../common-components';
import {
LETTER_REGEX, LOGIN_PAGE, NUMBER_REGEX, RESET_PAGE,
} from '../data/constants';
import { getAllPossibleQueryParams, updatePathWithQueryParams, windowScrollTo } from '../data/utils';
import { trackPasswordResetSuccess, trackResetPasswordPageViewed } from '../tracking/trackers/reset-password';
const ResetPasswordPage = (props) => {
const { formatMessage } = useIntl();
const newPasswordError = formatMessage(messages['password.validation.message']);
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [formErrors, setFormErrors] = useState({});
const [errorCode, setErrorCode] = useState(null);
const { token } = useParams();
const navigate = useNavigate();
useEffect(() => {
if (props.status === TOKEN_STATE.VALID) {
trackResetPasswordPageViewed();
}
if (props.status === SUCCESS) {
trackPasswordResetSuccess();
}
}, [props.status]);
useEffect(() => {
if (props.status !== TOKEN_STATE.PENDING && props.status !== PASSWORD_RESET_ERROR) {
setErrorCode(props.status);
}
if (props.status === PASSWORD_VALIDATION_ERROR) {
setFormErrors({ newPassword: newPasswordError });
}
}, [props.status, newPasswordError]);
const validatePasswordFromBackend = async (password) => {
let errorMessage = '';
try {
const payload = {
reset_password_page: true,
password,
};
errorMessage = await validatePassword(payload);
} catch (err) {
errorMessage = '';
}
setFormErrors({ ...formErrors, newPassword: errorMessage });
};
const validateInput = (name, value) => {
switch (name) {
case 'newPassword':
if (!value || !LETTER_REGEX.test(value) || !NUMBER_REGEX.test(value) || value.length < 8) {
formErrors.newPassword = formatMessage(messages['password.validation.message']);
} else {
validatePasswordFromBackend(value);
}
break;
case 'confirmPassword':
if (!value) {
formErrors.confirmPassword = formatMessage(messages['confirm.your.password']);
} else if (value !== newPassword) {
formErrors.confirmPassword = formatMessage(messages['passwords.do.not.match']);
} else {
formErrors.confirmPassword = '';
}
break;
default:
break;
}
setFormErrors({ ...formErrors });
return !Object.values(formErrors).some(x => (x !== ''));
};
const handleOnBlur = (event) => {
const { name, value } = event.target;
validateInput(name, value);
};
const handleConfirmPasswordChange = (e) => {
const { value } = e.target;
setConfirmPassword(value);
validateInput('confirmPassword', value);
};
const handleOnFocus = (e) => {
setFormErrors({ ...formErrors, [e.target.name]: '' });
};
const handleSubmit = (e) => {
e.preventDefault();
const isPasswordValid = validateInput('newPassword', newPassword);
const isPasswordConfirmed = validateInput('confirmPassword', confirmPassword);
if (isPasswordValid && isPasswordConfirmed) {
const formPayload = {
new_password1: newPassword,
new_password2: confirmPassword,
};
const params = getAllPossibleQueryParams();
props.resetPassword(formPayload, props.token, params);
} else {
setErrorCode(FORM_SUBMISSION_ERROR);
windowScrollTo({ left: 0, top: 0, behavior: 'smooth' });
}
};
const tabTitle = (
<div className="d-inline-flex flex-wrap align-items-center">
<Icon src={ChevronLeft} />
<span className="ml-2">{formatMessage(messages['sign.in'])}</span>
</div>
);
if (props.status === TOKEN_STATE.PENDING) {
if (token) {
props.validateToken(token);
return <Spinner animation="border" variant="primary" className="spinner--position-centered" />;
}
} else if (props.status === PASSWORD_RESET_ERROR) {
navigate(updatePathWithQueryParams(RESET_PAGE));
} else if (props.status === SUCCESS) {
navigate(updatePathWithQueryParams(LOGIN_PAGE));
} else {
return (
<BaseContainer>
<div>
<Helmet>
<title>
{formatMessage(messages['reset.password.page.title'], { siteName: getConfig().SITE_NAME })}
</title>
</Helmet>
<Tabs activeKey="" id="controlled-tab" onSelect={(key) => navigate(updatePathWithQueryParams(key))}>
<Tab title={tabTitle} eventKey={LOGIN_PAGE} />
</Tabs>
<div id="main-content" className="main-content">
<div className="mw-xs">
<ResetPasswordFailure errorCode={errorCode} errorMsg={props.errorMsg} />
<h4>{formatMessage(messages['reset.password'])}</h4>
<p className="mb-4">{formatMessage(messages['reset.password.page.instructions'])}</p>
<Form id="set-reset-password-form" name="set-reset-password-form">
<PasswordField
name="newPassword"
value={newPassword}
handleChange={(e) => setNewPassword(e.target.value)}
handleBlur={handleOnBlur}
handleFocus={handleOnFocus}
errorMessage={formErrors.newPassword}
floatingLabel={formatMessage(messages['new.password.label'])}
/>
<PasswordField
name="confirmPassword"
value={confirmPassword}
handleChange={handleConfirmPasswordChange}
handleFocus={handleOnFocus}
errorMessage={formErrors.confirmPassword}
showRequirements={false}
floatingLabel={formatMessage(messages['confirm.password.label'])}
/>
<StatefulButton
id="submit-new-password"
name="submit-new-password"
type="submit"
variant="brand"
className="reset-password--button"
state={props.status}
labels={{
default: formatMessage(messages['reset.password']),
pending: '',
}}
onClick={e => handleSubmit(e)}
onMouseDown={(e) => e.preventDefault()}
/>
</Form>
</div>
</div>
</div>
</BaseContainer>
);
}
return null;
};
ResetPasswordPage.defaultProps = {
status: null,
token: null,
errorMsg: null,
};
ResetPasswordPage.propTypes = {
resetPassword: PropTypes.func.isRequired,
validateToken: PropTypes.func.isRequired,
token: PropTypes.string,
status: PropTypes.string,
errorMsg: PropTypes.string,
};
export default connect(
resetPasswordResultSelector,
{
resetPassword,
validateToken,
},
)(ResetPasswordPage);