feat: VAN-1056 - remove formik (#621)
This commit is contained in:
@@ -12,14 +12,16 @@ export const ENTERPRISE_LOGIN_URL = '/enterprise/login';
|
||||
export const SUPPORTED_ICON_CLASSES = ['apple', 'facebook', 'google', 'microsoft'];
|
||||
|
||||
// Error Codes
|
||||
export const FORM_SUBMISSION_ERROR = 'form-submission-error';
|
||||
export const INTERNAL_SERVER_ERROR = 'internal-server-error';
|
||||
export const API_RATELIMIT_ERROR = 'api-ratelimit-error';
|
||||
|
||||
// States
|
||||
// Common States
|
||||
export const DEFAULT_STATE = 'default';
|
||||
export const PENDING_STATE = 'pending';
|
||||
export const COMPLETE_STATE = 'complete';
|
||||
export const FAILURE_STATE = 'failure';
|
||||
export const FORBIDDEN_STATE = 'forbidden';
|
||||
|
||||
// Regex
|
||||
export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*'
|
||||
|
||||
@@ -74,7 +74,7 @@ const FormFieldRenderer = (props) => {
|
||||
}
|
||||
case 'text': {
|
||||
formField = (
|
||||
<Form.Group controlId={fieldData.name} isInvalid={isRequired && errorMessage}>
|
||||
<Form.Group controlId={fieldData.name} isInvalid={!!(isRequired && errorMessage)}>
|
||||
<Form.Control
|
||||
name={fieldData.name}
|
||||
value={value}
|
||||
|
||||
@@ -6,23 +6,24 @@ import { Alert } from '@edx/paragon';
|
||||
import { CheckCircle, Error } from '@edx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { INTERNAL_SERVER_ERROR } from '../data/constants';
|
||||
import {
|
||||
COMPLETE_STATE, FORBIDDEN_STATE, FORM_SUBMISSION_ERROR, INTERNAL_SERVER_ERROR,
|
||||
} from '../data/constants';
|
||||
import { PASSWORD_RESET } from '../reset-password/data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const ForgotPasswordAlert = (props) => {
|
||||
const { email, emailError, intl } = props;
|
||||
let message = '';
|
||||
let heading = intl.formatMessage(messages['forgot.password.error.alert.title']);
|
||||
let { status } = props;
|
||||
|
||||
if (emailError) {
|
||||
status = 'form-submission-error';
|
||||
status = FORM_SUBMISSION_ERROR;
|
||||
}
|
||||
|
||||
let message = '';
|
||||
let heading = intl.formatMessage(messages['forgot.password.error.alert.title']);
|
||||
const supportUrl = getConfig().PASSWORD_RESET_SUPPORT_LINK;
|
||||
switch (status) {
|
||||
case 'complete':
|
||||
case COMPLETE_STATE:
|
||||
heading = intl.formatMessage(messages['confirmation.message.title']);
|
||||
message = (
|
||||
<FormattedMessage
|
||||
@@ -34,14 +35,7 @@ const ForgotPasswordAlert = (props) => {
|
||||
values={{
|
||||
email: <span className="data-hj-suppress">{email}</span>,
|
||||
supportLink: (
|
||||
<Alert.Link
|
||||
className="alert-link"
|
||||
href={supportUrl}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
window.open(supportUrl, '_blank');
|
||||
}}
|
||||
>
|
||||
<Alert.Link href={getConfig().PASSWORD_RESET_SUPPORT_LINK} target="_blank">
|
||||
{intl.formatMessage(messages['confirmation.support.link'])}
|
||||
</Alert.Link>
|
||||
),
|
||||
@@ -52,11 +46,11 @@ const ForgotPasswordAlert = (props) => {
|
||||
case INTERNAL_SERVER_ERROR:
|
||||
message = intl.formatMessage(messages['internal.server.error']);
|
||||
break;
|
||||
case 'forbidden':
|
||||
case FORBIDDEN_STATE:
|
||||
heading = intl.formatMessage(messages['forgot.password.error.message.title']);
|
||||
message = intl.formatMessage(messages['forgot.password.request.in.progress.message']);
|
||||
break;
|
||||
case 'form-submission-error':
|
||||
case FORM_SUBMISSION_ERROR:
|
||||
message = intl.formatMessage(messages['extend.field.errors'], { emailError });
|
||||
break;
|
||||
case PASSWORD_RESET.INVALID_TOKEN:
|
||||
@@ -79,7 +73,7 @@ const ForgotPasswordAlert = (props) => {
|
||||
return (
|
||||
<Alert
|
||||
id="validation-errors"
|
||||
className="mb-5"
|
||||
className="mb-4"
|
||||
variant={`${status === 'complete' ? 'success' : 'danger'}`}
|
||||
icon={status === 'complete' ? CheckCircle : Error}
|
||||
>
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
Tabs,
|
||||
} from '@edx/paragon';
|
||||
import { ChevronLeft } from '@edx/paragon/icons';
|
||||
import { Formik } from 'formik';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
@@ -28,16 +27,15 @@ import ForgotPasswordAlert from './ForgotPasswordAlert';
|
||||
import messages from './messages';
|
||||
|
||||
const ForgotPasswordPage = (props) => {
|
||||
const platformName = getConfig().SITE_NAME;
|
||||
const emailRegex = new RegExp(VALID_EMAIL_REGEX, 'i');
|
||||
const {
|
||||
intl, status, submitState, emailValidationError,
|
||||
} = props;
|
||||
|
||||
const platformName = getConfig().SITE_NAME;
|
||||
const supportUrl = getConfig().LOGIN_ISSUE_SUPPORT_LINK;
|
||||
const regex = new RegExp(VALID_EMAIL_REGEX, 'i');
|
||||
|
||||
const [bannerEmail, setBannerEmail] = useState('');
|
||||
const [email, setEmail] = useState(props.email);
|
||||
const [bannerEmail, setBannerEmail] = useState('');
|
||||
const [formErrors, setFormErrors] = useState('');
|
||||
const [validationError, setValidationError] = useState(emailValidationError);
|
||||
const [key, setKey] = useState('');
|
||||
|
||||
@@ -48,33 +46,44 @@ const ForgotPasswordPage = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
setValidationError(emailValidationError);
|
||||
}, [emailValidationError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'complete') {
|
||||
setEmail('');
|
||||
} else {
|
||||
setEmail(props.email);
|
||||
}
|
||||
}, [emailValidationError, props.email, status]);
|
||||
}, [status]);
|
||||
|
||||
const getValidationMessage = (value) => {
|
||||
let error = '';
|
||||
|
||||
if (value === '') {
|
||||
error = intl.formatMessage(messages['forgot.password.empty.email.field.error']);
|
||||
} else if (!regex.test(value)) {
|
||||
} else if (!emailRegex.test(value)) {
|
||||
error = intl.formatMessage(messages['forgot.password.page.invalid.email.message']);
|
||||
}
|
||||
|
||||
return error;
|
||||
};
|
||||
|
||||
const onBlur = (value) => {
|
||||
const emailError = getValidationMessage(value);
|
||||
setBannerEmail(value);
|
||||
props.setForgotPasswordFormData({ email: value, emailValidationError: emailError });
|
||||
const handleBlur = () => {
|
||||
props.setForgotPasswordFormData({ email, emailValidationError: getValidationMessage(email) });
|
||||
};
|
||||
|
||||
const onFocus = () => {
|
||||
props.setForgotPasswordFormData({ emailValidationError: '' });
|
||||
const handleFocus = () => props.setForgotPasswordFormData({ emailValidationError: '' });
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
setBannerEmail(email);
|
||||
|
||||
const error = getValidationMessage(email);
|
||||
if (error) {
|
||||
setFormErrors(error);
|
||||
props.setForgotPasswordFormData({ email, emailValidationError: error });
|
||||
windowScrollTo({ left: 0, top: 0, behavior: 'smooth' });
|
||||
} else {
|
||||
props.forgotPassword(email);
|
||||
}
|
||||
};
|
||||
|
||||
const tabTitle = (
|
||||
@@ -86,6 +95,11 @@ const ForgotPasswordPage = (props) => {
|
||||
|
||||
return (
|
||||
<BaseComponent>
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages['forgot.password.page.title'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
</title>
|
||||
</Helmet>
|
||||
<div>
|
||||
<Tabs activeKey="" id="controlled-tab" onSelect={(k) => setKey(k)}>
|
||||
<Tab title={tabTitle} eventKey={LOGIN_PAGE} />
|
||||
@@ -94,86 +108,55 @@ const ForgotPasswordPage = (props) => {
|
||||
<Redirect to={updatePathWithQueryParams(key)} />
|
||||
)}
|
||||
<div id="main-content" className="main-content">
|
||||
<Formik
|
||||
initialValues={{ email }}
|
||||
enableReinitialize={props.email === ''}
|
||||
validateOnChange={false}
|
||||
validate={(values) => {
|
||||
const validationMessage = getValidationMessage(values.email);
|
||||
props.setForgotPasswordFormData({ emailValidationError: validationMessage });
|
||||
if (validationMessage !== '') {
|
||||
windowScrollTo({ left: 0, top: 0, behavior: 'smooth' });
|
||||
return { email: validationMessage };
|
||||
}
|
||||
|
||||
return {};
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
onBlur(values.email);
|
||||
props.forgotPassword(values.email);
|
||||
}}
|
||||
>
|
||||
{({
|
||||
errors, handleSubmit, setFieldValue, values,
|
||||
}) => (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages['forgot.password.page.title'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
</title>
|
||||
</Helmet>
|
||||
<Form id="forget-password-form" name="forget-password-form" className="mw-xs">
|
||||
<ForgotPasswordAlert email={bannerEmail} emailError={errors.email} status={status} />
|
||||
<h2 className="h4">
|
||||
{intl.formatMessage(messages['forgot.password.page.heading'])}
|
||||
</h2>
|
||||
<p className="mb-4">
|
||||
{intl.formatMessage(messages['forgot.password.page.instructions'])}
|
||||
</p>
|
||||
<FormGroup
|
||||
floatingLabel={intl.formatMessage(messages['forgot.password.page.email.field.label'])}
|
||||
name="email"
|
||||
errorMessage={validationError}
|
||||
value={values.email}
|
||||
handleBlur={() => onBlur(values.email)}
|
||||
handleChange={e => setFieldValue('email', e.target.value)}
|
||||
handleFocus={() => onFocus()}
|
||||
helpText={[intl.formatMessage(messages['forgot.password.email.help.text'], { platformName })]}
|
||||
/>
|
||||
<StatefulButton
|
||||
id="submit-forget-password"
|
||||
name="submit-forget-password"
|
||||
type="submit"
|
||||
variant="brand"
|
||||
className="login-button-width"
|
||||
state={submitState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['forgot.password.page.submit.button']),
|
||||
pending: '',
|
||||
}}
|
||||
onClick={handleSubmit}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
/>
|
||||
<Hyperlink
|
||||
id="forgot-password"
|
||||
name="forgot-password"
|
||||
className="ml-4 font-weight-500 text-body"
|
||||
destination={supportUrl}
|
||||
target="_blank"
|
||||
showLaunchIcon={false}
|
||||
>
|
||||
{intl.formatMessage(messages['need.help.sign.in.text'])}
|
||||
</Hyperlink>
|
||||
<p className="mt-5 small text-gray-700">
|
||||
{intl.formatMessage(messages['additional.help.text'], { platformName })}
|
||||
<span>
|
||||
<Hyperlink isInline destination={`mailto:${getConfig().INFO_EMAIL}`}>{getConfig().INFO_EMAIL}</Hyperlink>
|
||||
</span>
|
||||
</p>
|
||||
</Form>
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
<Form id="forget-password-form" name="forget-password-form" className="mw-xs">
|
||||
<ForgotPasswordAlert email={bannerEmail} emailError={formErrors} status={status} />
|
||||
<h2 className="h4">
|
||||
{intl.formatMessage(messages['forgot.password.page.heading'])}
|
||||
</h2>
|
||||
<p className="mb-4">
|
||||
{intl.formatMessage(messages['forgot.password.page.instructions'])}
|
||||
</p>
|
||||
<FormGroup
|
||||
floatingLabel={intl.formatMessage(messages['forgot.password.page.email.field.label'])}
|
||||
name="email"
|
||||
value={email}
|
||||
errorMessage={validationError}
|
||||
handleChange={(e) => setEmail(e.target.value)}
|
||||
handleBlur={handleBlur}
|
||||
handleFocus={handleFocus}
|
||||
helpText={[intl.formatMessage(messages['forgot.password.email.help.text'], { platformName })]}
|
||||
/>
|
||||
<StatefulButton
|
||||
id="submit-forget-password"
|
||||
name="submit-forget-password"
|
||||
type="submit"
|
||||
variant="brand"
|
||||
className="forgot-password-button-width"
|
||||
state={submitState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['forgot.password.page.submit.button']),
|
||||
pending: '',
|
||||
}}
|
||||
onClick={handleSubmit}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
/>
|
||||
<Hyperlink
|
||||
id="forgot-password"
|
||||
name="forgot-password"
|
||||
className="ml-4 font-weight-500 text-body"
|
||||
destination={getConfig().LOGIN_ISSUE_SUPPORT_LINK}
|
||||
target="_blank"
|
||||
showLaunchIcon={false}
|
||||
>
|
||||
{intl.formatMessage(messages['need.help.sign.in.text'])}
|
||||
</Hyperlink>
|
||||
<p className="mt-5.5 small text-gray-700">
|
||||
{intl.formatMessage(messages['additional.help.text'], { platformName })}
|
||||
<span>
|
||||
<Hyperlink isInline destination={`mailto:${getConfig().INFO_EMAIL}`}>{getConfig().INFO_EMAIL}</Hyperlink>
|
||||
</span>
|
||||
</p>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</BaseComponent>
|
||||
@@ -181,10 +164,10 @@ const ForgotPasswordPage = (props) => {
|
||||
};
|
||||
|
||||
ForgotPasswordPage.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
email: PropTypes.string,
|
||||
emailValidationError: PropTypes.string,
|
||||
forgotPassword: PropTypes.func.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
setForgotPasswordFormData: PropTypes.func.isRequired,
|
||||
status: PropTypes.string,
|
||||
submitState: PropTypes.string,
|
||||
|
||||
@@ -51,11 +51,6 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Enter your email',
|
||||
description: 'Error message that appears when user tries to submit empty email field',
|
||||
},
|
||||
'forgot.password.invalid.email.heading': {
|
||||
id: 'forgot.password.invalid.email',
|
||||
defaultMessage: 'An error occurred.',
|
||||
description: 'heading for invalid email',
|
||||
},
|
||||
'forgot.password.invalid.email.message': {
|
||||
id: 'forgot.password.invalid.email.message',
|
||||
defaultMessage: "The email address you've provided isn't formatted correctly.",
|
||||
|
||||
@@ -19,6 +19,11 @@ $apple-focus-black: $apple-black;
|
||||
|
||||
$elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
|
||||
|
||||
// Forgot Password Page
|
||||
.forgot-password-button-width {
|
||||
min-width: 6rem;
|
||||
}
|
||||
|
||||
.centered-align-spinner {
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
Reference in New Issue
Block a user