diff --git a/package-lock.json b/package-lock.json index 459aef53..0fe84797 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,6 @@ "extract-react-intl-messages": "4.1.1", "fastest-levenshtein": "1.0.12", "form-urlencoded": "4.2.1", - "formik": "2.2.9", "lodash.camelcase": "4.3.0", "lodash.snakecase": "4.1.1", "prop-types": "15.8.1", @@ -13270,42 +13269,6 @@ "url": "https://ko-fi.com/tunnckoCore/commissions" } }, - "node_modules/formik": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.9.tgz", - "integrity": "sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==", - "funding": [ - { - "type": "individual", - "url": "https://opencollective.com/formik" - } - ], - "dependencies": { - "deepmerge": "^2.1.1", - "hoist-non-react-statics": "^3.3.0", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "react-fast-compare": "^2.0.1", - "tiny-warning": "^1.0.2", - "tslib": "^1.10.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/formik/node_modules/deepmerge": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", - "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/formik/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -19753,11 +19716,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -22993,11 +22951,6 @@ "integrity": "sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==", "dev": true }, - "node_modules/react-fast-compare": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", - "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" - }, "node_modules/react-focus-lock": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.8.1.tgz", @@ -38345,32 +38298,6 @@ "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", "dev": true }, - "formik": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.9.tgz", - "integrity": "sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==", - "requires": { - "deepmerge": "^2.1.1", - "hoist-non-react-statics": "^3.3.0", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "react-fast-compare": "^2.0.1", - "tiny-warning": "^1.0.2", - "tslib": "^1.10.0" - }, - "dependencies": { - "deepmerge": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", - "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -43244,11 +43171,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -45635,11 +45557,6 @@ "integrity": "sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==", "dev": true }, - "react-fast-compare": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", - "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" - }, "react-focus-lock": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.8.1.tgz", diff --git a/package.json b/package.json index eb73f2be..53aa84f3 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "extract-react-intl-messages": "4.1.1", "fastest-levenshtein": "1.0.12", "form-urlencoded": "4.2.1", - "formik": "2.2.9", "lodash.camelcase": "4.3.0", "lodash.snakecase": "4.1.1", "prop-types": "15.8.1", diff --git a/src/data/constants.js b/src/data/constants.js index 48a73a83..66a79619 100644 --- a/src/data/constants.js +++ b/src/data/constants.js @@ -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]+)*' diff --git a/src/field-renderer/FieldRenderer.jsx b/src/field-renderer/FieldRenderer.jsx index 2afa69d4..fc1d8ef8 100644 --- a/src/field-renderer/FieldRenderer.jsx +++ b/src/field-renderer/FieldRenderer.jsx @@ -74,7 +74,7 @@ const FormFieldRenderer = (props) => { } case 'text': { formField = ( - + { 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 = ( { values={{ email: {email}, supportLink: ( - { - e.preventDefault(); - window.open(supportUrl, '_blank'); - }} - > + {intl.formatMessage(messages['confirmation.support.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 ( diff --git a/src/forgot-password/ForgotPasswordPage.jsx b/src/forgot-password/ForgotPasswordPage.jsx index 86a25205..616433dc 100644 --- a/src/forgot-password/ForgotPasswordPage.jsx +++ b/src/forgot-password/ForgotPasswordPage.jsx @@ -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 ( + + {intl.formatMessage(messages['forgot.password.page.title'], + { siteName: getConfig().SITE_NAME })} + +
setKey(k)}> @@ -94,86 +108,55 @@ const ForgotPasswordPage = (props) => { )}
- { - 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, - }) => ( - <> - - {intl.formatMessage(messages['forgot.password.page.title'], - { siteName: getConfig().SITE_NAME })} - - -
- -

- {intl.formatMessage(messages['forgot.password.page.heading'])} -

-

- {intl.formatMessage(messages['forgot.password.page.instructions'])} -

- onBlur(values.email)} - handleChange={e => setFieldValue('email', e.target.value)} - handleFocus={() => onFocus()} - helpText={[intl.formatMessage(messages['forgot.password.email.help.text'], { platformName })]} - /> - e.preventDefault()} - /> - - {intl.formatMessage(messages['need.help.sign.in.text'])} - -

- {intl.formatMessage(messages['additional.help.text'], { platformName })} - - {getConfig().INFO_EMAIL} - -

- - - )} -
+
+ +

+ {intl.formatMessage(messages['forgot.password.page.heading'])} +

+

+ {intl.formatMessage(messages['forgot.password.page.instructions'])} +

+ setEmail(e.target.value)} + handleBlur={handleBlur} + handleFocus={handleFocus} + helpText={[intl.formatMessage(messages['forgot.password.email.help.text'], { platformName })]} + /> + e.preventDefault()} + /> + + {intl.formatMessage(messages['need.help.sign.in.text'])} + +

+ {intl.formatMessage(messages['additional.help.text'], { platformName })} + + {getConfig().INFO_EMAIL} + +

+
@@ -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, diff --git a/src/forgot-password/messages.js b/src/forgot-password/messages.js index 460720b1..d65475b5 100644 --- a/src/forgot-password/messages.js +++ b/src/forgot-password/messages.js @@ -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.", diff --git a/src/sass/_style.scss b/src/sass/_style.scss index 2672be6e..d2de3396 100644 --- a/src/sass/_style.scss +++ b/src/sass/_style.scss @@ -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;