diff --git a/.env.development b/.env.development index 9fdccfc9..808b175d 100644 --- a/.env.development +++ b/.env.development @@ -23,11 +23,10 @@ AUTHN_MINIMAL_HEADER=true LOGIN_ISSUE_SUPPORT_LINK='/login-issue-support-url' TOS_AND_HONOR_CODE='http://localhost:18000/honor' PRIVACY_POLICY='http://localhost:18000/privacy' -REGISTRATION_OPTIONAL_FIELDS='gender,goals,level_of_education,year_of_birth' +REGISTRATION_OPTIONAL_FIELDS='' USER_SURVEY_COOKIE_NAME='openedx-user-survey-type' COOKIE_DOMAIN='localhost' WELCOME_PAGE_SUPPORT_LINK='http://localhost:1999/welcome' INFO_EMAIL='info@edx.org' DISABLE_ENTERPRISE_LOGIN='' -DEFAULT_DESIGN='redesign' REGISTER_CONVERSION_COOKIE_NAME='openedx-user-register-conversion' diff --git a/Makefile b/Makefile index 20eea5dc..08e40a08 100755 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ transifex_resource = frontend-app-authn transifex_langs = "ar,fr,es_419,zh_CN" transifex_utils = ./node_modules/.bin/transifex-utils.js -i18n = ./src/legacy/i18n +i18n = ./src/i18n transifex_input = $(i18n)/transifex_input.json tx_url1 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/translation/en/strings/ tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/source/ diff --git a/jest.config.js b/jest.config.js index 49f06480..35fcbc69 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,10 +6,7 @@ module.exports = createConfig('jest', { ], coveragePathIgnorePatterns: [ 'src/setupTest.js', - 'src/legacy/i18n', - 'src/redesign/i18n', + 'src/i18n', 'src/index.jsx', - 'src/legacy/index.jsx', - 'src/redesign/index.jsx', ], }); diff --git a/package-lock.json b/package-lock.json index 246b2b5a..2e011529 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3869,29 +3869,6 @@ } } }, - "@edx/frontend-component-header": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-2.2.5.tgz", - "integrity": "sha512-8ZfdQBgp5beLd3r+xIxpDQT4/eYggUrzkHe4iJk322lYqmjAEHio9jrLnHv7lW/B8ooH3qZNl+sJlT2UcWRxmg==", - "requires": { - "babel-polyfill": "6.26.0", - "react-responsive": "8.0.3", - "react-transition-group": "4.3.0" - }, - "dependencies": { - "react-responsive": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-8.0.3.tgz", - "integrity": "sha512-F9VXyLao7O8XHXbLjQbIr4+mC6Zr0RDTwNjd7ixTmYEAyKyNanBkLkFchNaMZgszoSK6PgSs/3m/QDWw33/gpg==", - "requires": { - "hyphenate-style-name": "^1.0.0", - "matchmediaquery": "^0.3.0", - "prop-types": "^15.6.1", - "shallow-equal": "^1.1.0" - } - } - } - }, "@edx/frontend-platform": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-1.8.4.tgz", @@ -7273,6 +7250,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, "requires": { "babel-runtime": "^6.26.0", "core-js": "^2.5.0", @@ -7282,12 +7260,14 @@ "core-js": { "version": "2.6.12", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "dev": true }, "regenerator-runtime": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true } } }, @@ -7355,6 +7335,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" @@ -7363,12 +7344,14 @@ "core-js": { "version": "2.6.12", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "dev": true }, "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true } } }, diff --git a/package.json b/package.json index 97b6b881..5f50502c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ ], "scripts": { "build": "fedx-scripts webpack", - "i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src/legacy --quiet > /dev/null", + "i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null", "is-es5": "es-check es5 ./dist/*.js", "lint": "fedx-scripts eslint --ext .js --ext .jsx .", "snapshot": "fedx-scripts jest --updateSnapshot", @@ -36,7 +36,6 @@ "dependencies": { "@edx/brand": "npm:@edx/brand-openedx@1.1.0", "@edx/frontend-component-cookie-policy-banner": "2.1.12", - "@edx/frontend-component-header": "2.2.5", "@edx/frontend-platform": "1.8.4", "@edx/paragon": "16.6.1", "@fortawesome/fontawesome-svg-core": "1.2.32", diff --git a/src/redesign/index.jsx b/src/MainApp.jsx similarity index 96% rename from src/redesign/index.jsx rename to src/MainApp.jsx index d7e72a59..08157953 100755 --- a/src/redesign/index.jsx +++ b/src/MainApp.jsx @@ -19,7 +19,7 @@ import './index.scss'; registerIcons(); -const RedesignApp = () => ( +const MainApp = () => ( @@ -40,4 +40,4 @@ const RedesignApp = () => ( ); -export default RedesignApp; +export default MainApp; diff --git a/src/redesign/_style.scss b/src/_style.scss similarity index 100% rename from src/redesign/_style.scss rename to src/_style.scss diff --git a/src/redesign/base-component/AuthExtraLargeLayout.jsx b/src/base-component/AuthExtraLargeLayout.jsx similarity index 100% rename from src/redesign/base-component/AuthExtraLargeLayout.jsx rename to src/base-component/AuthExtraLargeLayout.jsx diff --git a/src/redesign/base-component/AuthMediumLayout.jsx b/src/base-component/AuthMediumLayout.jsx similarity index 100% rename from src/redesign/base-component/AuthMediumLayout.jsx rename to src/base-component/AuthMediumLayout.jsx diff --git a/src/redesign/base-component/AuthSmallLayout.jsx b/src/base-component/AuthSmallLayout.jsx similarity index 100% rename from src/redesign/base-component/AuthSmallLayout.jsx rename to src/base-component/AuthSmallLayout.jsx diff --git a/src/redesign/base-component/BaseComponent.jsx b/src/base-component/BaseComponent.jsx similarity index 87% rename from src/redesign/base-component/BaseComponent.jsx rename to src/base-component/BaseComponent.jsx index a13c4fe1..2c829e82 100644 --- a/src/redesign/base-component/BaseComponent.jsx +++ b/src/base-component/BaseComponent.jsx @@ -1,9 +1,7 @@ -import React, { useEffect } from 'react'; - +import React from 'react'; import classNames from 'classnames'; import PropTypes from 'prop-types'; -import { history } from '@edx/frontend-platform'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { ExtraSmall, Small, Medium, Large, ExtraLarge, ExtraExtraLarge, @@ -18,18 +16,6 @@ import AuthMediumLayout from './AuthMediumLayout'; import AuthSmallLayout from './AuthSmallLayout'; const BaseComponent = ({ children }) => { - let { search } = history.location; - - if (search && search.indexOf('theme') < 0) { - search = `${search}&theme=redesign`; - } else { - search = '?theme=redesign'; - } - - useEffect(() => { - history.replace({ search }); - }, []); - const authenticatedUser = getAuthenticatedUser(); return ( diff --git a/src/redesign/base-component/LargeLayout.jsx b/src/base-component/LargeLayout.jsx similarity index 100% rename from src/redesign/base-component/LargeLayout.jsx rename to src/base-component/LargeLayout.jsx diff --git a/src/redesign/base-component/LargeLeftLayout.jsx b/src/base-component/LargeLeftLayout.jsx similarity index 100% rename from src/redesign/base-component/LargeLeftLayout.jsx rename to src/base-component/LargeLeftLayout.jsx diff --git a/src/redesign/base-component/MediumLayout.jsx b/src/base-component/MediumLayout.jsx similarity index 100% rename from src/redesign/base-component/MediumLayout.jsx rename to src/base-component/MediumLayout.jsx diff --git a/src/redesign/base-component/SmallLayout.jsx b/src/base-component/SmallLayout.jsx similarity index 100% rename from src/redesign/base-component/SmallLayout.jsx rename to src/base-component/SmallLayout.jsx diff --git a/src/redesign/base-component/index.jsx b/src/base-component/index.jsx similarity index 100% rename from src/redesign/base-component/index.jsx rename to src/base-component/index.jsx diff --git a/src/redesign/base-component/messages.jsx b/src/base-component/messages.jsx similarity index 100% rename from src/redesign/base-component/messages.jsx rename to src/base-component/messages.jsx diff --git a/src/redesign/base-component/tests/BaseComponent.test.jsx b/src/base-component/tests/BaseComponent.test.jsx similarity index 100% rename from src/redesign/base-component/tests/BaseComponent.test.jsx rename to src/base-component/tests/BaseComponent.test.jsx diff --git a/src/redesign/common-components/EnterpriseSSO.jsx b/src/common-components/EnterpriseSSO.jsx similarity index 100% rename from src/redesign/common-components/EnterpriseSSO.jsx rename to src/common-components/EnterpriseSSO.jsx diff --git a/src/redesign/common-components/FormGroup.jsx b/src/common-components/FormGroup.jsx similarity index 100% rename from src/redesign/common-components/FormGroup.jsx rename to src/common-components/FormGroup.jsx diff --git a/src/redesign/common-components/InstitutionLogistration.jsx b/src/common-components/InstitutionLogistration.jsx similarity index 100% rename from src/redesign/common-components/InstitutionLogistration.jsx rename to src/common-components/InstitutionLogistration.jsx diff --git a/src/redesign/common-components/Logistration.jsx b/src/common-components/Logistration.jsx similarity index 100% rename from src/redesign/common-components/Logistration.jsx rename to src/common-components/Logistration.jsx diff --git a/src/legacy/common-components/NotFoundPage.jsx b/src/common-components/NotFoundPage.jsx similarity index 100% rename from src/legacy/common-components/NotFoundPage.jsx rename to src/common-components/NotFoundPage.jsx diff --git a/src/redesign/common-components/PasswordField.jsx b/src/common-components/PasswordField.jsx similarity index 100% rename from src/redesign/common-components/PasswordField.jsx rename to src/common-components/PasswordField.jsx diff --git a/src/legacy/common-components/RedirectLogistration.jsx b/src/common-components/RedirectLogistration.jsx similarity index 100% rename from src/legacy/common-components/RedirectLogistration.jsx rename to src/common-components/RedirectLogistration.jsx diff --git a/src/legacy/common-components/RegisterFaIcons.jsx b/src/common-components/RegisterFaIcons.jsx similarity index 100% rename from src/legacy/common-components/RegisterFaIcons.jsx rename to src/common-components/RegisterFaIcons.jsx diff --git a/src/redesign/common-components/SocialAuthProviders.jsx b/src/common-components/SocialAuthProviders.jsx similarity index 100% rename from src/redesign/common-components/SocialAuthProviders.jsx rename to src/common-components/SocialAuthProviders.jsx diff --git a/src/legacy/common-components/SwitchContent.jsx b/src/common-components/SwitchContent.jsx similarity index 100% rename from src/legacy/common-components/SwitchContent.jsx rename to src/common-components/SwitchContent.jsx diff --git a/src/redesign/common-components/ThirdPartyAuthAlert.jsx b/src/common-components/ThirdPartyAuthAlert.jsx similarity index 100% rename from src/redesign/common-components/ThirdPartyAuthAlert.jsx rename to src/common-components/ThirdPartyAuthAlert.jsx diff --git a/src/legacy/common-components/UnAuthOnlyRoute.jsx b/src/common-components/UnAuthOnlyRoute.jsx similarity index 100% rename from src/legacy/common-components/UnAuthOnlyRoute.jsx rename to src/common-components/UnAuthOnlyRoute.jsx diff --git a/src/legacy/common-components/data/actions.js b/src/common-components/data/actions.js similarity index 100% rename from src/legacy/common-components/data/actions.js rename to src/common-components/data/actions.js diff --git a/src/legacy/common-components/data/reducers.js b/src/common-components/data/reducers.js similarity index 100% rename from src/legacy/common-components/data/reducers.js rename to src/common-components/data/reducers.js diff --git a/src/legacy/common-components/data/sagas.js b/src/common-components/data/sagas.js similarity index 100% rename from src/legacy/common-components/data/sagas.js rename to src/common-components/data/sagas.js diff --git a/src/legacy/common-components/data/selectors.js b/src/common-components/data/selectors.js similarity index 100% rename from src/legacy/common-components/data/selectors.js rename to src/common-components/data/selectors.js diff --git a/src/redesign/common-components/data/service.js b/src/common-components/data/service.js similarity index 100% rename from src/redesign/common-components/data/service.js rename to src/common-components/data/service.js diff --git a/src/legacy/common-components/data/tests/sagas.test.js b/src/common-components/data/tests/sagas.test.js similarity index 96% rename from src/legacy/common-components/data/tests/sagas.test.js rename to src/common-components/data/tests/sagas.test.js index ffbc33da..fe0feb8a 100644 --- a/src/legacy/common-components/data/tests/sagas.test.js +++ b/src/common-components/data/tests/sagas.test.js @@ -3,7 +3,7 @@ import { runSaga } from 'redux-saga'; import * as actions from '../actions'; import { fetchThirdPartyAuthContext } from '../sagas'; import * as api from '../service'; -import initializeMockLogging from '../../../../setupTest'; +import initializeMockLogging from '../../../setupTest'; const { loggingService } = initializeMockLogging(); diff --git a/src/redesign/common-components/index.jsx b/src/common-components/index.jsx similarity index 100% rename from src/redesign/common-components/index.jsx rename to src/common-components/index.jsx diff --git a/src/redesign/common-components/messages.jsx b/src/common-components/messages.jsx similarity index 100% rename from src/redesign/common-components/messages.jsx rename to src/common-components/messages.jsx diff --git a/src/redesign/common-components/tests/FormField.test.jsx b/src/common-components/tests/FormField.test.jsx similarity index 100% rename from src/redesign/common-components/tests/FormField.test.jsx rename to src/common-components/tests/FormField.test.jsx diff --git a/src/redesign/common-components/tests/Logistration.test.jsx b/src/common-components/tests/Logistration.test.jsx similarity index 100% rename from src/redesign/common-components/tests/Logistration.test.jsx rename to src/common-components/tests/Logistration.test.jsx diff --git a/src/legacy/common-components/tests/SocialAuthProviders.test.jsx b/src/common-components/tests/SocialAuthProviders.test.jsx similarity index 100% rename from src/legacy/common-components/tests/SocialAuthProviders.test.jsx rename to src/common-components/tests/SocialAuthProviders.test.jsx diff --git a/src/redesign/common-components/tests/ThirdPartyAuthAlert.test.jsx b/src/common-components/tests/ThirdPartyAuthAlert.test.jsx similarity index 100% rename from src/redesign/common-components/tests/ThirdPartyAuthAlert.test.jsx rename to src/common-components/tests/ThirdPartyAuthAlert.test.jsx diff --git a/src/legacy/common-components/tests/UnAuthOnlyRoute.test.jsx b/src/common-components/tests/UnAuthOnlyRoute.test.jsx similarity index 100% rename from src/legacy/common-components/tests/UnAuthOnlyRoute.test.jsx rename to src/common-components/tests/UnAuthOnlyRoute.test.jsx diff --git a/src/redesign/common-components/tests/__snapshots__/SocialAuthProviders.test.jsx.snap b/src/common-components/tests/__snapshots__/SocialAuthProviders.test.jsx.snap similarity index 100% rename from src/redesign/common-components/tests/__snapshots__/SocialAuthProviders.test.jsx.snap rename to src/common-components/tests/__snapshots__/SocialAuthProviders.test.jsx.snap diff --git a/src/redesign/common-components/tests/__snapshots__/ThirdPartyAuthAlert.test.jsx.snap b/src/common-components/tests/__snapshots__/ThirdPartyAuthAlert.test.jsx.snap similarity index 100% rename from src/redesign/common-components/tests/__snapshots__/ThirdPartyAuthAlert.test.jsx.snap rename to src/common-components/tests/__snapshots__/ThirdPartyAuthAlert.test.jsx.snap diff --git a/src/legacy/data/configureStore.js b/src/data/configureStore.js similarity index 100% rename from src/legacy/data/configureStore.js rename to src/data/configureStore.js diff --git a/src/redesign/data/constants.js b/src/data/constants.js similarity index 100% rename from src/redesign/data/constants.js rename to src/data/constants.js diff --git a/src/redesign/data/reducers.js b/src/data/reducers.js similarity index 100% rename from src/redesign/data/reducers.js rename to src/data/reducers.js diff --git a/src/redesign/data/sagas.js b/src/data/sagas.js similarity index 100% rename from src/redesign/data/sagas.js rename to src/data/sagas.js diff --git a/src/legacy/data/utils/cookies.js b/src/data/utils/cookies.js similarity index 100% rename from src/legacy/data/utils/cookies.js rename to src/data/utils/cookies.js diff --git a/src/redesign/data/utils/dataUtils.js b/src/data/utils/dataUtils.js similarity index 100% rename from src/redesign/data/utils/dataUtils.js rename to src/data/utils/dataUtils.js diff --git a/src/legacy/data/utils/dataUtils.test.js b/src/data/utils/dataUtils.test.js similarity index 100% rename from src/legacy/data/utils/dataUtils.test.js rename to src/data/utils/dataUtils.test.js diff --git a/src/legacy/data/utils/index.js b/src/data/utils/index.js similarity index 100% rename from src/legacy/data/utils/index.js rename to src/data/utils/index.js diff --git a/src/legacy/data/utils/reduxUtils.js b/src/data/utils/reduxUtils.js similarity index 100% rename from src/legacy/data/utils/reduxUtils.js rename to src/data/utils/reduxUtils.js diff --git a/src/legacy/data/utils/reduxUtils.test.js b/src/data/utils/reduxUtils.test.js similarity index 100% rename from src/legacy/data/utils/reduxUtils.test.js rename to src/data/utils/reduxUtils.test.js diff --git a/src/redesign/forgot-password/ForgotPasswordAlert.jsx b/src/forgot-password/ForgotPasswordAlert.jsx similarity index 100% rename from src/redesign/forgot-password/ForgotPasswordAlert.jsx rename to src/forgot-password/ForgotPasswordAlert.jsx diff --git a/src/redesign/forgot-password/ForgotPasswordPage.jsx b/src/forgot-password/ForgotPasswordPage.jsx similarity index 100% rename from src/redesign/forgot-password/ForgotPasswordPage.jsx rename to src/forgot-password/ForgotPasswordPage.jsx diff --git a/src/legacy/forgot-password/data/actions.js b/src/forgot-password/data/actions.js similarity index 100% rename from src/legacy/forgot-password/data/actions.js rename to src/forgot-password/data/actions.js diff --git a/src/redesign/forgot-password/data/reducers.js b/src/forgot-password/data/reducers.js similarity index 100% rename from src/redesign/forgot-password/data/reducers.js rename to src/forgot-password/data/reducers.js diff --git a/src/legacy/forgot-password/data/sagas.js b/src/forgot-password/data/sagas.js similarity index 100% rename from src/legacy/forgot-password/data/sagas.js rename to src/forgot-password/data/sagas.js diff --git a/src/legacy/forgot-password/data/selectors.js b/src/forgot-password/data/selectors.js similarity index 100% rename from src/legacy/forgot-password/data/selectors.js rename to src/forgot-password/data/selectors.js diff --git a/src/legacy/forgot-password/data/service.js b/src/forgot-password/data/service.js similarity index 100% rename from src/legacy/forgot-password/data/service.js rename to src/forgot-password/data/service.js diff --git a/src/legacy/forgot-password/data/tests/sagas.test.js b/src/forgot-password/data/tests/sagas.test.js similarity index 96% rename from src/legacy/forgot-password/data/tests/sagas.test.js rename to src/forgot-password/data/tests/sagas.test.js index 9e4de10e..d3e85654 100644 --- a/src/legacy/forgot-password/data/tests/sagas.test.js +++ b/src/forgot-password/data/tests/sagas.test.js @@ -3,7 +3,7 @@ import { runSaga } from 'redux-saga'; import * as actions from '../actions'; import { handleForgotPassword } from '../sagas'; import * as api from '../service'; -import initializeMockLogging from '../../../../setupTest'; +import initializeMockLogging from '../../../setupTest'; const { loggingService } = initializeMockLogging(); diff --git a/src/legacy/forgot-password/index.js b/src/forgot-password/index.js similarity index 100% rename from src/legacy/forgot-password/index.js rename to src/forgot-password/index.js diff --git a/src/redesign/forgot-password/messages.js b/src/forgot-password/messages.js similarity index 100% rename from src/redesign/forgot-password/messages.js rename to src/forgot-password/messages.js diff --git a/src/redesign/forgot-password/tests/ForgotPasswordPage.test.jsx b/src/forgot-password/tests/ForgotPasswordPage.test.jsx similarity index 100% rename from src/redesign/forgot-password/tests/ForgotPasswordPage.test.jsx rename to src/forgot-password/tests/ForgotPasswordPage.test.jsx diff --git a/src/legacy/i18n/index.jsx b/src/i18n/index.jsx similarity index 100% rename from src/legacy/i18n/index.jsx rename to src/i18n/index.jsx diff --git a/src/legacy/i18n/messages/ar.json b/src/i18n/messages/ar.json similarity index 100% rename from src/legacy/i18n/messages/ar.json rename to src/i18n/messages/ar.json diff --git a/src/legacy/i18n/messages/ca.json b/src/i18n/messages/ca.json similarity index 100% rename from src/legacy/i18n/messages/ca.json rename to src/i18n/messages/ca.json diff --git a/src/legacy/i18n/messages/es_419.json b/src/i18n/messages/es_419.json similarity index 100% rename from src/legacy/i18n/messages/es_419.json rename to src/i18n/messages/es_419.json diff --git a/src/legacy/i18n/messages/fr.json b/src/i18n/messages/fr.json similarity index 100% rename from src/legacy/i18n/messages/fr.json rename to src/i18n/messages/fr.json diff --git a/src/legacy/i18n/messages/he.json b/src/i18n/messages/he.json similarity index 100% rename from src/legacy/i18n/messages/he.json rename to src/i18n/messages/he.json diff --git a/src/legacy/i18n/messages/id.json b/src/i18n/messages/id.json similarity index 100% rename from src/legacy/i18n/messages/id.json rename to src/i18n/messages/id.json diff --git a/src/legacy/i18n/messages/ko_kr.json b/src/i18n/messages/ko_kr.json similarity index 100% rename from src/legacy/i18n/messages/ko_kr.json rename to src/i18n/messages/ko_kr.json diff --git a/src/legacy/i18n/messages/pl.json b/src/i18n/messages/pl.json similarity index 100% rename from src/legacy/i18n/messages/pl.json rename to src/i18n/messages/pl.json diff --git a/src/legacy/i18n/messages/pt_br.json b/src/i18n/messages/pt_br.json similarity index 100% rename from src/legacy/i18n/messages/pt_br.json rename to src/i18n/messages/pt_br.json diff --git a/src/legacy/i18n/messages/ru.json b/src/i18n/messages/ru.json similarity index 100% rename from src/legacy/i18n/messages/ru.json rename to src/i18n/messages/ru.json diff --git a/src/legacy/i18n/messages/th.json b/src/i18n/messages/th.json similarity index 100% rename from src/legacy/i18n/messages/th.json rename to src/i18n/messages/th.json diff --git a/src/legacy/i18n/messages/uk.json b/src/i18n/messages/uk.json similarity index 100% rename from src/legacy/i18n/messages/uk.json rename to src/i18n/messages/uk.json diff --git a/src/legacy/i18n/messages/zh_CN.json b/src/i18n/messages/zh_CN.json similarity index 100% rename from src/legacy/i18n/messages/zh_CN.json rename to src/i18n/messages/zh_CN.json diff --git a/src/index.jsx b/src/index.jsx index 6350e3e7..ee0f133f 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -8,30 +8,12 @@ import { ErrorPage } from '@edx/frontend-platform/react'; import React from 'react'; import ReactDOM from 'react-dom'; -import { messages as headerMessages } from '@edx/frontend-component-header'; - -const RedesignApp = React.lazy(() => import('./redesign/index.jsx')); -const LegacyApp = React.lazy(() => import('./legacy/index.jsx')); - -const redesignAppMessages = React.lazy(() => import('./redesign/i18n')); -const legacyAppMessages = React.lazy(() => import('./legacy/i18n')); - -const OLD_DESIGN = 'legacy'; -const NEW_DESIGN = 'redesign'; -const DEFAULT_DESIGN = process.env.DEFAULT_DESIGN || OLD_DESIGN; -const CHOSEN_DESIGN = localStorage.getItem('DESIGN_NAME') || DEFAULT_DESIGN; -const REGISTRATION_OPTIONAL_FIELDS = CHOSEN_DESIGN === DEFAULT_DESIGN ? process.env.REGISTRATION_OPTIONAL_FIELDS : ''; - -const AppSelector = () => ( - }> - {(CHOSEN_DESIGN === OLD_DESIGN) && } - {(CHOSEN_DESIGN === NEW_DESIGN) && } - -); +import MainApp from './MainApp'; +import messages from './i18n'; subscribe(APP_READY, () => { ReactDOM.render( - , + , document.getElementById('root'), ); }); @@ -49,20 +31,18 @@ initialize({ PASSWORD_RESET_SUPPORT_LINK: process.env.PASSWORD_RESET_SUPPORT_LINK || null, TOS_AND_HONOR_CODE: process.env.TOS_AND_HONOR_CODE || null, PRIVACY_POLICY: process.env.PRIVACY_POLICY || null, - REGISTRATION_OPTIONAL_FIELDS, + REGISTRATION_OPTIONAL_FIELDS: process.env.REGISTRATION_OPTIONAL_FIELDS || '', USER_SURVEY_COOKIE_NAME: process.env.USER_SURVEY_COOKIE_NAME || null, COOKIE_DOMAIN: process.env.COOKIE_DOMAIN, WELCOME_PAGE_SUPPORT_LINK: process.env.WELCOME_PAGE_SUPPORT_LINK || null, DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '', INFO_EMAIL: process.env.INFO_EMAIL || '', REGISTER_CONVERSION_COOKIE_NAME: process.env.REGISTER_CONVERSION_COOKIE_NAME || null, - DESIGN_NAME: CHOSEN_DESIGN, ENABLE_PROGRESSIVE_PROFILING: process.env.ENABLE_PROGRESSIVE_PROFILING || false, }); }, }, messages: [ - CHOSEN_DESIGN === DEFAULT_DESIGN ? legacyAppMessages : redesignAppMessages, - headerMessages, + messages, ], }); diff --git a/src/redesign/index.scss b/src/index.scss similarity index 82% rename from src/redesign/index.scss rename to src/index.scss index 3b10b284..a408de5b 100755 --- a/src/redesign/index.scss +++ b/src/index.scss @@ -3,8 +3,6 @@ @import "~@edx/paragon/scss/core/core"; @import "~@edx/brand/paragon/overrides"; -@import "~@edx/frontend-component-header/dist/index"; - @import '@edx/frontend-component-cookie-policy-banner/build/_cookie-policy-banner'; @import "./style"; diff --git a/src/legacy/_style.scss b/src/legacy/_style.scss deleted file mode 100644 index cf084862..00000000 --- a/src/legacy/_style.scss +++ /dev/null @@ -1,340 +0,0 @@ -// ---------------------------- -// #COLORS -// ---------------------------- -$font-blue: #126f9a; -$white: #FFFFFF; - -// social platforms -$facebook-blue: #1877F2; -$facebook-focus-blue: #29487d; -$google-blue: #4285f4; -$google-focus-blue: #287ae6; -$microsoft-black: #2f2f2f; -$microsoft-focus-black: #000; -$apple-black: #000000; -$apple-focus-black: $apple-black; - -.sr-only { - position: absolute; - left: -10000px; - top: auto; - width: 1px; - height: 1px; - overflow: hidden; -} - -.focus-out { - position: absolute; - padding-left: 17px; - opacity: 0.75; - z-index: 1; -} - -.alert-link { - font-weight: normal; - text-decoration: underline; - color: #0075b4 !important; - - &:hover { - color: #065683 !important; - } -} - -.authn-header { - border-bottom: 1px solid #e7e7e7; - height: 3.75rem; - position: relative; - z-index: 1000; -} - -.authn-header img { - height: 1.75rem; - margin-left: 2rem; - padding: 1rem 0; - display: block; - position: relative; - box-sizing: content-box; -} - -.form-control { - width: 500px; -} - -.btn-social { - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - - margin-bottom: 1rem; - font-size: 14px; - - background-color: $white; - border: 1px solid $font-blue; - width: 242px; - height: 36px; - color: $font-blue; - - .icon-image { - background-color: transparent; - max-height: 24px; - max-width: 24px; - } -} - -.btn-tpa { - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - padding-left: 20px; - - .icon-image { - background-color: transparent; - max-height: 24px; - max-width: 24px; - } -} - -.tpa-container { - display: flex; - flex-direction: row; - justify-content: flex-start; - - margin: 0 !important; -} - -.font-container { - background-color: $font-blue; - color: $white; - font-size: 11px; - - margin-left: -6px; - padding-top: 10px; - min-width: 30px; - height: 35px; -} - -.btn-oa2-facebook { - color: $white; - border-color: $facebook-blue; - background-color: $facebook-blue; - - &:hover, - &:focus { - background-color: $facebook-focus-blue; - border: 1px solid $facebook-focus-blue; - color: $white; - } -} - -.btn-oa2-google-oauth2 { - color: $white; - border-color: $google-blue; - background-color: $google-blue; - - .icon-image { - margin-left: 2px; - } - - &:hover, - &:focus { - background-color: $google-focus-blue; - border: 1px solid $google-focus-blue; - color: $white; - } -} - -.btn-oa2-apple-id { - color: $white; - border-color: $apple-black; - background-color: $apple-black; - font-size: 16px; - - .icon-image { - max-height: 1.8em; - max-width: 2.0em; - } - - &:hover, - &:focus { - background-color: $apple-focus-black; - border: 1px solid $apple-focus-black; - color: $white; - } -} - -.btn-oa2-azuread-oauth2 { - color: $white; - border-color: $microsoft-black; - background-color: $microsoft-black; - - &:hover, - &:focus { - background-color: $microsoft-focus-black; - border: 1px solid $microsoft-focus-black; - color: $white; - } -} - -.submit { - display: inherit; - margin: 0 auto; - margin-bottom: 2rem; -} - -.section-heading-line { - position: relative; - text-align: center; - - &:before { - content: ''; - position: absolute; - left: 0; - top: 50%; - width: 20%; - background-color: gray; - height: 1px; - } - - &:after { - content: ''; - position: absolute; - right: 0; - top: 50%; - width: 20%; - background-color: gray; - height: 1px; - } -} - -.field-link { - font-weight: normal; - display: block; - color: $primary; - margin-bottom: 5px; - margin-top: 5px; - border: none; - padding: 0; - background: transparent; - box-shadow: none; - text-transform: initial; - letter-spacing: normal; - text-decoration: none; - text-shadow: none; - - &:focus, - &:hover { - color: $primary; - } -} - -.login-help { - padding-left: 14px; -} - -.opt-inline-field { - display: inline-block; - width: 50%; - - .form-control { - width: 100%; - } -} - -.opt-year-field { - padding-left: 15px; -} - -.invalid-feedback { - color: $red; -} - -.full-vertical-height { - height: 100vh; -} - -.help-links { - margin-left: -5px; -} - -#honor-code p { - margin: 0; - padding: 0; -} - -#honor-code a span { - @extend .sr-only; -} - -.mw-420 { - max-width: 420px; -} - -.mw-500 { - max-width: 500px; -} - -.mw-32em { - max-width: 32em; -} - -.h-90 { - height: 90%; -} - -.mt-10 { - margin-top: 10px; -} - -.pt-10 { - padding-top: 10px; -} - -@media (min-width: 576px) { - .reset-password-container { - width: 420px; - max-width: 420px; - } -} - -@media (min-width: 1024px) { - .mw-500 { - width: 500px; - } -} - -@media (max-width: 600px) { - .btn-social { - width: 47%; - margin-bottom: 0.75rem; - } - - .tpa-container { - justify-content: center; - } - .form-control { - width: 100%; - } -} - -@media (max-width: 450px) { - .section-heading-line { - position: relative; - text-align: center; - - &:before, - &:after { - width: 10%; - } - } -} - -.custom-select-size { - background-size: 8px 10px; -} - -.x-small-label { - font-size: 0.75rem; - font-weight: 700; -} diff --git a/src/legacy/common-components/APIFailureMessage.jsx b/src/legacy/common-components/APIFailureMessage.jsx deleted file mode 100644 index 2febce1a..00000000 --- a/src/legacy/common-components/APIFailureMessage.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import { Alert } from '@edx/paragon'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import PropTypes from 'prop-types'; -import messages from './messages'; -import { API_RATELIMIT_ERROR, INTERNAL_SERVER_ERROR } from '../data/constants'; - -const APIFailureMessage = (props) => { - const { intl, header, errorCode } = props; - let errorMessage = null; - let id = null; - - switch (errorCode) { - case INTERNAL_SERVER_ERROR: - id = INTERNAL_SERVER_ERROR; - errorMessage = intl.formatMessage(messages['internal.server.error.message']); - break; - case API_RATELIMIT_ERROR: - id = API_RATELIMIT_ERROR; - errorMessage = intl.formatMessage(messages['server.ratelimit.error.message']); - break; - default: - break; - } - - return ( -
-
- - - {header} - -
    -
  • - {errorMessage} -
  • -
-
-
-
- ); -}; - -APIFailureMessage.propTypes = { - intl: intlShape.isRequired, - header: PropTypes.string.isRequired, - errorCode: PropTypes.string.isRequired, -}; - -export default injectIntl(APIFailureMessage); diff --git a/src/legacy/common-components/AuthnValidationFormGroup.jsx b/src/legacy/common-components/AuthnValidationFormGroup.jsx deleted file mode 100644 index d1201c62..00000000 --- a/src/legacy/common-components/AuthnValidationFormGroup.jsx +++ /dev/null @@ -1,166 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import { - Form, - Input, - ValidationFormGroup, -} from '@edx/paragon'; - -const AuthnCustomValidationFormGroup = (props) => { - const { - onBlur, onChange, onClick, onFocus, - } = props; - const [showHelpText, setShowHelpText] = useState(false); - const [showLabelText, setShowLabelText] = useState(false); - - // handler code that need to be invoked via input - const onClickHandler = (e, clickCb) => { - setShowHelpText(true); - setShowLabelText(true); - if (clickCb) { - clickCb(e); - } - }; - const onBlurHandler = (e, blurCb) => { - setShowHelpText(false); - setShowLabelText(false); - if (blurCb) { - blurCb(e); - } - }; - const onChangeHandler = (e, changeCb) => { - if (changeCb) { - changeCb(e); - } - }; - const onFocusHandler = (e, focusCb) => { - if (focusCb) { - focusCb(e); - } - }; - const onOptionalHandler = (e, clickCb) => { clickCb(e); }; - - const showLabel = () => { - let className; - if (props.optionalFieldCheckbox || (!showLabelText && (props.value !== '' || props.type === 'select'))) { - className = 'sr-only'; - } else if (showLabelText) { - className = 'pt-10 x-small-label'; - } else { - className = 'pt-10 focus-out'; - } - - return ( - {props.label} - ); - }; - const showOptional = () => { - const additionalField = props.optionalFieldCheckbox ? ( - - ) : ; - return additionalField; - }; - - const inputProps = { - name: props.name, - id: props.for, - type: props.type, - value: props.value, - className: props.inputFieldStyle, - 'aria-invalid': props.ariaInvalid, - autoComplete: 'on', - }; - inputProps.onChange = (e) => onChangeHandler(e, onChange); - inputProps.onClick = (e) => onClickHandler(e, onClick); - inputProps.onBlur = (e) => onBlurHandler(e, onBlur); - inputProps.onFocus = (e) => onFocusHandler(e, onFocus); - - if (props.type === 'select') { - inputProps.options = props.selectOptions; - inputProps.className = props.value === '' ? `${props.inputFieldStyle} text-muted` : props.inputFieldStyle; - } - if (props.type === 'checkbox') { - inputProps.checked = props.isChecked; - } - - const validationGroupProps = { - for: props.for, - }; - if (!props.optionalFieldCheckbox) { - validationGroupProps.invalid = props.invalid; - validationGroupProps.invalidMessage = props.invalidMessage; - validationGroupProps.helpText = showHelpText ? props.helpText : ''; - } else { - validationGroupProps.className = props.optionalFieldCheckbox ? 'custom-control pt-10 mb-0' : ''; - } - if (props.className) { - validationGroupProps.className = props.className; - } - - return ( - - {showLabel()} - - {showOptional()} - - ); -}; - -AuthnCustomValidationFormGroup.defaultProps = { - name: '', - for: '', - label: '', - optionalFieldCheckbox: false, - type: '', - value: '', - invalid: false, - ariaInvalid: false, - invalidMessage: '', - inputFieldStyle: '', - helpText: '', - className: '', - onClick: null, - onBlur: null, - onChange: null, - onFocus: null, - isChecked: false, - checkboxMessage: '', - selectOptions: null, -}; - -AuthnCustomValidationFormGroup.propTypes = { - name: PropTypes.string, - for: PropTypes.string, - label: PropTypes.string, - type: PropTypes.string, - value: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.bool, - ]), - invalid: PropTypes.bool, - ariaInvalid: PropTypes.bool, - invalidMessage: PropTypes.string, - helpText: PropTypes.string, - className: PropTypes.string, - inputFieldStyle: PropTypes.string, - isChecked: PropTypes.bool, - optionalFieldCheckbox: PropTypes.bool, - onClick: PropTypes.func, - onBlur: PropTypes.func, - onChange: PropTypes.func, - onFocus: PropTypes.func, - checkboxMessage: PropTypes.string, - selectOptions: PropTypes.arrayOf(PropTypes.shape({ - key: PropTypes.string, - value: PropTypes.string, - })), -}; - -export default AuthnCustomValidationFormGroup; diff --git a/src/legacy/common-components/ConfirmationAlert.jsx b/src/legacy/common-components/ConfirmationAlert.jsx deleted file mode 100644 index 5a202373..00000000 --- a/src/legacy/common-components/ConfirmationAlert.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { getConfig } from '@edx/frontend-platform'; -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { Alert } from '@edx/paragon'; - -import messages from './messages'; - -const ConfirmationAlert = (props) => { - const { email, intl } = props; - - return ( - - {intl.formatMessage(messages['forgot.password.confirmation.title'])} -

- {email} }} - /> -

-

{intl.formatMessage(messages['forgot.password.confirmation.info'])}

-

- - {intl.formatMessage(messages['forgot.password.confirmation.support.link'])} - - ), - }} - /> -

-
- ); -}; - -ConfirmationAlert.propTypes = { - email: PropTypes.string.isRequired, - intl: intlShape.isRequired, -}; - -export default injectIntl(ConfirmationAlert); diff --git a/src/legacy/common-components/EnterpriseSSO.jsx b/src/legacy/common-components/EnterpriseSSO.jsx deleted file mode 100644 index d195e4c2..00000000 --- a/src/legacy/common-components/EnterpriseSSO.jsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; - -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import PropTypes from 'prop-types'; -import { getConfig } from '@edx/frontend-platform'; -import { faSignInAlt } from '@fortawesome/free-solid-svg-icons'; - -import { - Form, Button, -} from '@edx/paragon'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; - -import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants'; -import messages from './messages'; - -const EnterpriseSSO = (props) => { - const { intl } = props; - const tpaProvider = props.provider; - - const handleSubmit = (e, url) => { - e.preventDefault(); - window.location.href = getConfig().LMS_BASE_URL + url; - }; - - const handleClick = (e) => { - e.preventDefault(); - window.location.href = LOGIN_PAGE; - }; - - if (tpaProvider) { - return ( -
-
-
-

Sign in

-
-

{intl.formatMessage(messages['enterprisetpa.title.heading'], { providerName: tpaProvider.name })}

- -
- - -
-
-
- ); - } - return
; -}; - -EnterpriseSSO.defaultProps = { - provider: { - id: '', - name: '', - iconClass: '', - iconImage: '', - loginUrl: '', - registerUrl: '', - }, -}; - -EnterpriseSSO.propTypes = { - provider: PropTypes.shape({ - id: PropTypes.string, - name: PropTypes.string, - iconClass: PropTypes.string, - iconImage: PropTypes.string, - loginUrl: PropTypes.string, - registerUrl: PropTypes.string, - }), - intl: intlShape.isRequired, -}; - -export default injectIntl(EnterpriseSSO); diff --git a/src/legacy/common-components/HeaderLayout.jsx b/src/legacy/common-components/HeaderLayout.jsx deleted file mode 100644 index a519b0da..00000000 --- a/src/legacy/common-components/HeaderLayout.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner'; -import PropTypes from 'prop-types'; -import { getLocale } from '@edx/frontend-platform/i18n'; - -import Header from '@edx/frontend-component-header'; - -const HeaderLayout = ({ children }) => ( -
- -
-
- {children} -
-
-); - -HeaderLayout.propTypes = { - children: PropTypes.node.isRequired, -}; - -export default HeaderLayout; diff --git a/src/legacy/common-components/InstitutionLogistration.jsx b/src/legacy/common-components/InstitutionLogistration.jsx deleted file mode 100644 index 4557ea7e..00000000 --- a/src/legacy/common-components/InstitutionLogistration.jsx +++ /dev/null @@ -1,101 +0,0 @@ -import React from 'react'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { getConfig } from '@edx/frontend-platform'; -import PropTypes from 'prop-types'; -import { Button, Hyperlink } from '@edx/paragon'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; -import messages from './messages'; - -export const RenderInstitutionButton = props => { - const { onSubmitHandler, secondaryProviders, buttonTitle } = props; - if (secondaryProviders !== undefined && secondaryProviders.length > 0) { - return ( - - ); - } - return <>; -}; - -const InstitutionLogistration = props => { - const lmsBaseUrl = getConfig().LMS_BASE_URL; - const { - intl, - onSubmitHandler, - secondaryProviders, - headingTitle, - buttonTitle, - } = props; - - return ( - <> -
-
-
- - - {buttonTitle} - -
-

- {headingTitle} -

-

- {intl.formatMessage(messages['institution.login.page.sub.heading'])} -

-
-
    - {secondaryProviders.map(provider => ( -
  • - {provider.name} -
  • - ))} -
-
-
-
- - ); -}; - -const LogistrationDefaultProps = { - secondaryProviders: [], - buttonTitle: '', -}; -const LogistrationProps = { - onSubmitHandler: PropTypes.func.isRequired, - secondaryProviders: PropTypes.arrayOf(PropTypes.shape({ - name: PropTypes.string.isRequried, - loginUrl: PropTypes.string.isRequired, - })), - buttonTitle: PropTypes.string, -}; - -RenderInstitutionButton.propTypes = { - ...LogistrationProps, -}; -RenderInstitutionButton.defaultProps = { - ...LogistrationDefaultProps, -}; - -InstitutionLogistration.propTypes = { - ...LogistrationProps, - intl: intlShape.isRequired, - headingTitle: PropTypes.string, -}; -InstitutionLogistration.defaultProps = { - ...LogistrationDefaultProps, - headingTitle: '', -}; - -export default injectIntl(InstitutionLogistration); diff --git a/src/legacy/common-components/SocialAuthProviders.jsx b/src/legacy/common-components/SocialAuthProviders.jsx deleted file mode 100644 index ad29ce51..00000000 --- a/src/legacy/common-components/SocialAuthProviders.jsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { getConfig } from '@edx/frontend-platform'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faSignInAlt } from '@fortawesome/free-solid-svg-icons'; - -import messages from './messages'; -import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants'; - -function SocialAuthProviders(props) { - const { intl, referrer, socialAuthProviders } = props; - - function handleSubmit(e) { - e.preventDefault(); - - const url = e.currentTarget.dataset.providerUrl; - window.location.href = getConfig().LMS_BASE_URL + url; - } - - const socialAuth = socialAuthProviders.map((provider, index) => ( - - )); - - return <>{socialAuth}; -} - -SocialAuthProviders.defaultProps = { - referrer: LOGIN_PAGE, - socialAuthProviders: [], -}; - -SocialAuthProviders.propTypes = { - intl: intlShape.isRequired, - referrer: PropTypes.string, - socialAuthProviders: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string, - name: PropTypes.string, - iconClass: PropTypes.string, - iconImage: PropTypes.string, - loginUrl: PropTypes.string, - registerUrl: PropTypes.string, - })), -}; - -export default injectIntl(SocialAuthProviders); diff --git a/src/legacy/common-components/ThirdPartyAuthAlert.jsx b/src/legacy/common-components/ThirdPartyAuthAlert.jsx deleted file mode 100644 index 0504a9f2..00000000 --- a/src/legacy/common-components/ThirdPartyAuthAlert.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { Alert } from '@edx/paragon'; -import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants'; - -const ThirdPartyAuthAlert = (props) => { - const { currentProvider, referrer, platformName } = props; - let message; - - if (referrer === LOGIN_PAGE) { - message = ( - - ); - } else { - message = ( - - ); - } - - return { message }; -}; - -ThirdPartyAuthAlert.defaultProps = { - referrer: LOGIN_PAGE, -}; - -ThirdPartyAuthAlert.propTypes = { - currentProvider: PropTypes.string.isRequired, - platformName: PropTypes.string.isRequired, - referrer: PropTypes.string, -}; - -export default ThirdPartyAuthAlert; diff --git a/src/legacy/common-components/data/service.js b/src/legacy/common-components/data/service.js deleted file mode 100644 index 566a6fb8..00000000 --- a/src/legacy/common-components/data/service.js +++ /dev/null @@ -1,23 +0,0 @@ -import { camelCaseObject, convertKeyNames, getConfig } from '@edx/frontend-platform'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; - -// eslint-disable-next-line import/prefer-default-export -export async function getThirdPartyAuthContext(urlParams) { - const requestConfig = { - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - params: urlParams, - isPublic: true, - }; - - const { data } = await getAuthenticatedHttpClient() - .get( - `${getConfig().LMS_BASE_URL}/api/third_party_auth_context`, - requestConfig, - ) - .catch((e) => { - throw (e); - }); - return { - thirdPartyAuthContext: camelCaseObject(convertKeyNames(data, { fullname: 'name' })), - }; -} diff --git a/src/legacy/common-components/index.jsx b/src/legacy/common-components/index.jsx deleted file mode 100644 index 20894220..00000000 --- a/src/legacy/common-components/index.jsx +++ /dev/null @@ -1,14 +0,0 @@ -export { default as HeaderLayout } from './HeaderLayout'; -export { default as RedirectLogistration } from './RedirectLogistration'; -export { default as registerIcons } from './RegisterFaIcons'; -export { default as UnAuthOnlyRoute } from './UnAuthOnlyRoute'; -export { default as NotFoundPage } from './NotFoundPage'; -export { default as SocialAuthProviders } from './SocialAuthProviders'; -export { default as ThirdPartyAuthAlert } from './ThirdPartyAuthAlert'; -export { default as InstitutionLogistration } from './InstitutionLogistration'; -export { RenderInstitutionButton } from './InstitutionLogistration'; -export { default as AuthnValidationFormGroup } from './AuthnValidationFormGroup'; -export { default as APIFailureMessage } from './APIFailureMessage'; -export { default as reducer } from './data/reducers'; -export { default as saga } from './data/sagas'; -export { storeName } from './data/selectors'; diff --git a/src/legacy/common-components/messages.jsx b/src/legacy/common-components/messages.jsx deleted file mode 100644 index d667457d..00000000 --- a/src/legacy/common-components/messages.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - 'institution.login.page.sub.heading': { - id: 'institution.login.page.sub.heading', - defaultMessage: 'Choose your institution from the list below:', - description: 'Heading of the institutions list', - }, - // Confirmation Alert Message - 'forgot.password.confirmation.title': { - id: 'forgot.password.confirmation.title', - defaultMessage: 'Check your email', - description: 'Forgot password confirmation message title', - }, - 'forgot.password.confirmation.support.link': { - id: 'forgot.password.confirmation.support.link', - defaultMessage: 'contact technical support', - description: 'Technical support link text', - }, - 'forgot.password.confirmation.info': { - id: 'forgot.password.confirmation.info', - defaultMessage: 'If you do not receive a password reset message after 1 minute, verify that you entered the correct ' - + 'email address, or check your spam folder.', - description: 'Part of message that appears after user requests password change', - }, - 'internal.server.error.message': { - id: 'internal.server.error.message', - defaultMessage: 'An error has occurred. Try refreshing the page, or check your internet connection.', - description: 'Error message that appears when server responds with 500 error code', - }, - 'server.ratelimit.error.message': { - id: 'server.ratelimit.error.message', - defaultMessage: 'An error has occurred because of too many requests. Please try again after some time.', - description: 'Error message that appears when server responds with 429 error code', - }, - // enterprise sso strings - 'enterprisetpa.title.heading': { - id: 'enterprisetpa.title.heading', - defaultMessage: 'Would you like to sign in using your {providerName} credentials?', - description: 'Header text used in enterprise third party authentication', - }, - 'enterprisetpa.sso.button.title': { - id: 'enterprisetpa.sso.button.title', - defaultMessage: 'Sign in using {providerName}', - description: 'Text for third party auth provider buttons', - }, - 'enterprisetpa.login.button.text': { - id: 'enterprisetpa.login.button.text', - defaultMessage: 'Show me other ways to sign in or register', - description: 'Button text for login', - }, - // social auth providers - 'sso.sign.in.with': { - id: 'sso.sign.in.with', - defaultMessage: 'Sign in with {providerName}', - description: 'Screen reader text that appears before social auth provider name', - }, - 'sso.create.account.using': { - id: 'sso.create.account.using', - defaultMessage: 'Create account using {providerName}', - description: 'Screen reader text that appears before social auth provider name', - }, -}); - -export default messages; diff --git a/src/legacy/common-components/tests/AuthnValidationFormGroup.test.jsx b/src/legacy/common-components/tests/AuthnValidationFormGroup.test.jsx deleted file mode 100644 index 6a2eeebb..00000000 --- a/src/legacy/common-components/tests/AuthnValidationFormGroup.test.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; - -import AuthnCustomValidationFormGroup from '../AuthnValidationFormGroup'; - -describe('AuthnCustomValidationFormGroup', () => { - let props = { - label: 'Email Label', - for: 'email', - name: 'email', - type: 'email', - value: '', - helpText: 'Email field help text', - }; - - it('should show label in place of placeholder when field is empty', () => { - const validationFormGroup = mount(); - expect(validationFormGroup.find('label').prop('className')).toEqual('pgn__form-label pt-10 focus-out'); - }); - - it('should show label on top of field when field is focused in', () => { - const validationFormGroup = mount(); - - validationFormGroup.find('input').simulate('focus'); - expect(validationFormGroup.find('label').prop('className')).toEqual('pgn__form-label pt-10 focus-out'); - }); - - it('should keep label hidden for checkbox field', () => { - props = { - ...props, - type: 'checkbox', - optionalFieldCheckbox: true, - }; - const validationFormGroup = mount(); - expect(validationFormGroup.find('label').prop('className')).toEqual('pgn__form-label sr-only'); - }); - - it('should keep label hidden when input field is not empty', () => { - props = { - ...props, - value: 'test', - }; - const validationFormGroup = mount(); - expect(validationFormGroup.find('label').prop('className')).toEqual('pgn__form-label sr-only'); - }); -}); diff --git a/src/legacy/common-components/tests/ConfirmationAlert.test.jsx b/src/legacy/common-components/tests/ConfirmationAlert.test.jsx deleted file mode 100644 index b80f1dbb..00000000 --- a/src/legacy/common-components/tests/ConfirmationAlert.test.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; - -import { mergeConfig } from '@edx/frontend-platform'; -import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n'; - -import ConfirmationAlert from '../ConfirmationAlert'; - -const IntlConfirmationAlertMessage = injectIntl(ConfirmationAlert); - -describe('ConfirmationAlert', () => { - const supportLink = 'https://support.test.com/What-if-I-did-not-receive-a-password-reset-message'; - mergeConfig({ - PASSWORD_RESET_SUPPORT_LINK: supportLink, - }); - - it('should match default confirmation message', () => { - const confirmationAlertMessage = mount( - - - , - ); - - const expectedMessage = 'Check your email' - + 'You entered test@example.com. If this email address is associated with your edX account, ' - + 'we will send a message with password recovery instructions to this email address.' - + 'If you do not receive a password reset message after 1 minute, verify that you entered ' - + 'the correct email address, or check your spam folder.' - + 'If you need further assistance, contact technical support.'; - - expect(confirmationAlertMessage.find('#confirmation-alert').first().text()).toEqual(expectedMessage); - expect(confirmationAlertMessage.find('#confirmation-alert').find('a').props().href).toEqual(supportLink); - }); -}); diff --git a/src/legacy/common-components/tests/ThirdPartyAuthAlert.test.jsx b/src/legacy/common-components/tests/ThirdPartyAuthAlert.test.jsx deleted file mode 100644 index 548bb513..00000000 --- a/src/legacy/common-components/tests/ThirdPartyAuthAlert.test.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import renderer from 'react-test-renderer'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; - -import ThirdPartyAuthAlert from '../ThirdPartyAuthAlert'; - -describe('ThirdPartyAuthAlert', () => { - let props = {}; - - beforeEach(() => { - props = { - currentProvider: 'Google', - platformName: 'edX', - }; - }); - - it('should match login page third party auth alert message snapshot', () => { - const tree = renderer.create( - - - , - ).toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should match register page third party auth alert message snapshot', () => { - props = { - ...props, - referrer: 'register', - }; - - const tree = renderer.create( - - - , - ).toJSON(); - expect(tree).toMatchSnapshot(); - }); -}); diff --git a/src/legacy/common-components/tests/__snapshots__/SocialAuthProviders.test.jsx.snap b/src/legacy/common-components/tests/__snapshots__/SocialAuthProviders.test.jsx.snap deleted file mode 100644 index 972437cf..00000000 --- a/src/legacy/common-components/tests/__snapshots__/SocialAuthProviders.test.jsx.snap +++ /dev/null @@ -1,156 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SocialAuthProviders should match social auth provider with default icon snapshot 1`] = ` - -`; - -exports[`SocialAuthProviders should match social auth provider with iconClass snapshot 1`] = ` - -`; - -exports[`SocialAuthProviders should match social auth provider with iconImage snapshot 1`] = ` -Array [ - , - , -] -`; diff --git a/src/legacy/common-components/tests/__snapshots__/ThirdPartyAuthAlert.test.jsx.snap b/src/legacy/common-components/tests/__snapshots__/ThirdPartyAuthAlert.test.jsx.snap deleted file mode 100644 index 680d25e9..00000000 --- a/src/legacy/common-components/tests/__snapshots__/ThirdPartyAuthAlert.test.jsx.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ThirdPartyAuthAlert should match login page third party auth alert message snapshot 1`] = ` - -`; - -exports[`ThirdPartyAuthAlert should match register page third party auth alert message snapshot 1`] = ` - -`; diff --git a/src/legacy/data/constants.js b/src/legacy/data/constants.js deleted file mode 100644 index 45f2bfb0..00000000 --- a/src/legacy/data/constants.js +++ /dev/null @@ -1,31 +0,0 @@ -// URL Paths -export const LOGIN_PAGE = '/login'; -export const REGISTER_PAGE = '/register'; -export const RESET_PAGE = '/reset'; -export const WELCOME_PAGE = '/welcome'; -export const DEFAULT_REDIRECT_URL = '/dashboard'; -export const PASSWORD_RESET_CONFIRM = '/password_reset_confirm/:token/'; -export const PAGE_NOT_FOUND = '/notfound'; -export const ENTERPRISE_LOGIN_URL = '/enterprise/login'; - -// Constants -export const SUPPORTED_ICON_CLASSES = ['apple', 'facebook', 'google', 'microsoft']; - -// Error Codes -export const INTERNAL_SERVER_ERROR = 'internal-server-error'; -export const API_RATELIMIT_ERROR = 'api-ratelimit-error'; - -// States -export const DEFAULT_STATE = 'default'; -export const PENDING_STATE = 'pending'; -export const COMPLETE_STATE = 'complete'; - -// Regex -export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*' - + '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"' - + ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+)(?:[A-Z0-9-]{2,63})' - + '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$'; - -// Query string parameters that can be passed to LMS to manage -// things like auto-enrollment upon login and registration. -export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow', 'next']; diff --git a/src/legacy/data/reducers.js b/src/legacy/data/reducers.js deleted file mode 100755 index 4442848e..00000000 --- a/src/legacy/data/reducers.js +++ /dev/null @@ -1,31 +0,0 @@ -import { combineReducers } from 'redux'; - -import { - reducer as loginReducer, - storeName as loginStoreName, -} from '../login'; -import { - reducer as registerReducer, - storeName as registerStoreName, -} from '../register'; -import { - reducer as commonComponentsReducer, - storeName as commonComponentsStoreName, -} from '../common-components'; -import { - reducer as forgotPasswordReducer, - storeName as forgotPasswordStoreName, -} from '../forgot-password'; -import { - reducer as resetPasswordReducer, - storeName as resetPasswordStoreName, -} from '../reset-password'; - -const createRootReducer = () => combineReducers({ - [loginStoreName]: loginReducer, - [registerStoreName]: registerReducer, - [commonComponentsStoreName]: commonComponentsReducer, - [forgotPasswordStoreName]: forgotPasswordReducer, - [resetPasswordStoreName]: resetPasswordReducer, -}); -export default createRootReducer; diff --git a/src/legacy/data/sagas.js b/src/legacy/data/sagas.js deleted file mode 100644 index 30ad4799..00000000 --- a/src/legacy/data/sagas.js +++ /dev/null @@ -1,17 +0,0 @@ -import { all } from 'redux-saga/effects'; - -import { saga as registrationSaga } from '../register'; -import { saga as loginSaga } from '../login'; -import { saga as commonComponentsSaga } from '../common-components'; -import { saga as forgotPasswordSaga } from '../forgot-password'; -import { saga as resetPasswordSaga } from '../reset-password'; - -export default function* rootSaga() { - yield all([ - loginSaga(), - registrationSaga(), - commonComponentsSaga(), - forgotPasswordSaga(), - resetPasswordSaga(), - ]); -} diff --git a/src/legacy/data/utils/dataUtils.js b/src/legacy/data/utils/dataUtils.js deleted file mode 100644 index b61a4d20..00000000 --- a/src/legacy/data/utils/dataUtils.js +++ /dev/null @@ -1,80 +0,0 @@ -// Utility functions - -import * as QueryString from 'query-string'; -import { AUTH_PARAMS } from '../constants'; - -export default function processLink(link) { - let matches; - link.replace(/(.*?)([^<]+)<\/a>(.*)/g, function () { // eslint-disable-line func-names - matches = Array.prototype.slice.call(arguments, 1, 5); // eslint-disable-line prefer-rest-params - }); - return matches; -} - -export const getTpaProvider = (tpaHintProvider, primaryProviders, secondaryProviders) => { - let tpaProvider = null; - let skipHintedLogin = false; - [...primaryProviders, ...secondaryProviders].forEach((provider) => { - if (provider.id === tpaHintProvider) { - tpaProvider = provider; - if (provider.skipHintedLogin) { - skipHintedLogin = true; - } - } - }); - return { provider: tpaProvider, skipHintedLogin }; -}; - -export const getTpaHint = () => { - const params = QueryString.parse(window.location.search); - let tpaHint = null; - tpaHint = params.tpa_hint; - if (!tpaHint) { - const { next } = params; - if (next) { - const index = next.indexOf('tpa_hint='); - if (index !== -1) { - tpaHint = next.substring(index + 'tpa_hint='.length, next.length); - } - } - } - return tpaHint; -}; - -export const updatePathWithQueryParams = (path) => { - const queryParams = window.location.search; - - if (!queryParams) { - return path; - } - - return `${path}${queryParams}`; -}; - -export const getAllPossibleQueryParam = () => { - const urlParams = QueryString.parse(window.location.search); - const params = {}; - Object.entries(urlParams).forEach(([key, value]) => { - if (AUTH_PARAMS.indexOf(key) > -1) { - params[key] = value; - } - }); - - return params; -}; - -export const getActivationStatus = () => { - const params = QueryString.parse(window.location.search); - - return params.account_activation_status; -}; - -export const isScrollBehaviorSupported = () => 'scrollBehavior' in document.documentElement.style; - -export const windowScrollTo = (options) => { - if (isScrollBehaviorSupported()) { - return window.scrollTo(options); - } - - return window.scrollTo(options.top, options.left); -}; diff --git a/src/legacy/forgot-password/ForgotPasswordPage.jsx b/src/legacy/forgot-password/ForgotPasswordPage.jsx deleted file mode 100644 index 7d8d2c5b..00000000 --- a/src/legacy/forgot-password/ForgotPasswordPage.jsx +++ /dev/null @@ -1,158 +0,0 @@ -import React, { useState } from 'react'; - -import { Formik } from 'formik'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { Helmet } from 'react-helmet'; -import { Redirect } from 'react-router-dom'; - -import { getConfig } from '@edx/frontend-platform'; -import { sendPageEvent } from '@edx/frontend-platform/analytics'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { - Alert, - Form, - StatefulButton, -} from '@edx/paragon'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faSpinner } from '@fortawesome/free-solid-svg-icons'; - -import { forgotPassword } from './data/actions'; -import { forgotPasswordResultSelector } from './data/selectors'; -import RequestInProgressAlert from './RequestInProgressAlert'; - -import messages from './messages'; -import { - AuthnValidationFormGroup, -} from '../common-components'; -import APIFailureMessage from '../common-components/APIFailureMessage'; -import { INTERNAL_SERVER_ERROR, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants'; -import LoginHelpLinks from '../login/LoginHelpLinks'; -import { updatePathWithQueryParams, windowScrollTo } from '../data/utils'; - -const ForgotPasswordPage = (props) => { - const { intl, status } = props; - - const platformName = getConfig().SITE_NAME; - const regex = new RegExp(VALID_EMAIL_REGEX, 'i'); - const [validationError, setValidationError] = useState(''); - - const getErrorMessage = (errors) => { - const header = intl.formatMessage(messages['forgot.password.request.server.error']); - if (errors.email) { - return ( - - {header} -
  • {errors.email}
-
- ); - } - if (status === INTERNAL_SERVER_ERROR) { - return ; - } - return status === 'forbidden' ? : null; - }; - - const getValidationMessage = (email) => { - let error = ''; - - if (email === '') { - error = intl.formatMessage(messages['forgot.password.empty.email.field.error']); - } else if (!regex.test(email)) { - error = intl.formatMessage(messages['forgot.password.page.invalid.email.message']); - } - - setValidationError(error); - return error; - }; - - sendPageEvent('login_and_registration', 'reset'); - - return ( - { - const validationMessage = getValidationMessage(values.email); - - if (validationMessage !== '') { - windowScrollTo({ left: 0, top: 0, behavior: 'smooth' }); - return { email: validationMessage }; - } - - return {}; - }} - onSubmit={(values) => { props.forgotPassword(values.email); }} - > - {({ - errors, handleSubmit, setFieldValue, values, - }) => ( - <> - - {intl.formatMessage(messages['forgot.password.page.title'], - { siteName: getConfig().SITE_NAME })} - - - {status === 'complete' ? : null} -
-
-
- { getErrorMessage(errors) } -

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

-

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

- getValidationMessage(values.email)} - onChange={e => setFieldValue('email', e.target.value)} - helpText={intl.formatMessage(messages['forgot.password.email.help.text'], { platformName })} - className="mb-0 w-100" - inputFieldStyle="border-gray-600" - /> - - }} - onClick={handleSubmit} - onMouseDown={(e) => e.preventDefault()} - /> - -
-
- - )} -
- ); -}; - -ForgotPasswordPage.propTypes = { - intl: intlShape.isRequired, - forgotPassword: PropTypes.func.isRequired, - status: PropTypes.string, -}; - -ForgotPasswordPage.defaultProps = { - status: null, -}; - -export default connect( - forgotPasswordResultSelector, - { - forgotPassword, - }, -)(injectIntl(ForgotPasswordPage)); diff --git a/src/legacy/forgot-password/RequestInProgressAlert.jsx b/src/legacy/forgot-password/RequestInProgressAlert.jsx deleted file mode 100644 index 7a69cd45..00000000 --- a/src/legacy/forgot-password/RequestInProgressAlert.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { Alert } from '@edx/paragon'; - -import messages from './messages'; - -const RequestInProgressAlert = (props) => { - const { intl } = props; - - return ( - - {intl.formatMessage(messages['forgot.password.error.message.title'])} -
    -
  • {intl.formatMessage(messages['forgot.password.request.in.progress.message'])}
  • -
-
- ); -}; - -RequestInProgressAlert.propTypes = { - intl: intlShape.isRequired, -}; - -export default injectIntl(RequestInProgressAlert); diff --git a/src/legacy/forgot-password/data/reducers.js b/src/legacy/forgot-password/data/reducers.js deleted file mode 100644 index 3dfa9b43..00000000 --- a/src/legacy/forgot-password/data/reducers.js +++ /dev/null @@ -1,35 +0,0 @@ -import { FORGOT_PASSWORD } from './actions'; -import { INTERNAL_SERVER_ERROR } from '../../data/constants'; - -export const defaultState = { - status: null, -}; - -const reducer = (state = defaultState, action = null) => { - if (action !== null) { - switch (action.type) { - case FORGOT_PASSWORD.BEGIN: - return { - status: 'pending', - }; - case FORGOT_PASSWORD.SUCCESS: - return { - ...action.payload, - status: 'complete', - }; - case FORGOT_PASSWORD.FORBIDDEN: - return { - status: 'forbidden', - }; - case FORGOT_PASSWORD.FAILURE: - return { - status: INTERNAL_SERVER_ERROR, - }; - default: - return state; - } - } - return state; -}; - -export default reducer; diff --git a/src/legacy/forgot-password/messages.js b/src/legacy/forgot-password/messages.js deleted file mode 100644 index 8c33acc5..00000000 --- a/src/legacy/forgot-password/messages.js +++ /dev/null @@ -1,70 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - 'forgot.password.page.title': { - id: 'forgot.password.page.title', - defaultMessage: 'Forgot Password | {siteName}', - description: 'forgot password page title', - }, - 'forgot.password.page.heading': { - id: 'forgot.password.page.heading', - defaultMessage: 'Password assistance', - description: 'The page heading for the forgot password page.', - }, - 'forgot.password.page.instructions': { - id: 'forgot.password.page.instructions', - defaultMessage: 'Please enter your log-in or recovery email address below and we will send you an email with instructions.', - description: 'Instructions message for forgot password page.', - }, - 'forgot.password.page.invalid.email.message': { - id: 'forgot.password.page.invalid.email.message', - defaultMessage: "The email address you've provided isn't formatted correctly.", - description: 'Invalid email address message for the forgot password page.', - }, - 'forgot.password.page.email.field.label': { - id: 'forgot.password.page.email.field.label', - defaultMessage: 'Email', - description: 'Email field label for the forgot password page.', - }, - 'forgot.password.page.submit.button': { - id: 'forgot.password.page.submit.button', - defaultMessage: 'Recover my password', - description: 'Submit button text for the forgot password page.', - }, - 'forgot.password.request.server.error': { - id: 'forgot.password.request.server.error', - defaultMessage: 'We couldn’t send the password recovery email.', - description: 'Failed to send password recovery email.', - }, - 'forgot.password.error.message.title': { - id: 'forgot.password.error.message.title', - defaultMessage: 'An error occurred.', - description: 'Title for message that appears when error occurs for password assistance page', - }, - 'forgot.password.request.in.progress.message': { - id: 'forgot.password.request.in.progress.message', - defaultMessage: 'Your previous request is in progress, please try again in a few moments.', - description: 'Message displayed when previous password reset request is still in progress.', - }, - 'forgot.password.empty.email.field.error': { - id: 'forgot.password.empty.email.field.error', - defaultMessage: 'Please 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.", - description: 'message for invalid email', - }, - 'forgot.password.email.help.text': { - id: 'forgot.password.email.help.text', - defaultMessage: 'The email address you used to register with {platformName}', - description: 'text help for the email', - }, -}); -export default messages; diff --git a/src/legacy/forgot-password/tests/ForgotPasswordPage.test.jsx b/src/legacy/forgot-password/tests/ForgotPasswordPage.test.jsx deleted file mode 100644 index 5b83b0fb..00000000 --- a/src/legacy/forgot-password/tests/ForgotPasswordPage.test.jsx +++ /dev/null @@ -1,160 +0,0 @@ -import React from 'react'; -import { act } from 'react-dom/test-utils'; -import { Provider } from 'react-redux'; -import { Router } from 'react-router-dom'; -import renderer from 'react-test-renderer'; -import { mount } from 'enzyme'; -import configureStore from 'redux-mock-store'; -import { createMemoryHistory } from 'history'; -import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; -import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner'; -import * as analytics from '@edx/frontend-platform/analytics'; - -import ForgotPasswordPage from '../ForgotPasswordPage'; -import { INTERNAL_SERVER_ERROR } from '../../data/constants'; - -jest.mock('@edx/frontend-platform/analytics'); - -analytics.sendPageEvent = jest.fn(); - -const IntlForgotPasswordPage = injectIntl(ForgotPasswordPage); -const mockStore = configureStore(); -const history = createMemoryHistory(); - -describe('ForgotPasswordPage', () => { - let props = {}; - let store = {}; - - const reduxWrapper = children => ( - - {children} - - ); - - beforeEach(() => { - store = mockStore(); - props = { - forgotPassword: jest.fn(), - status: null, - }; - }); - - it('should match default section snapshot', () => { - const tree = renderer.create(reduxWrapper()) - .toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should match forbidden section snapshot', () => { - props = { - ...props, - status: 'forbidden', - }; - const tree = renderer.create(reduxWrapper()) - .toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should match pending section snapshot', () => { - props = { - ...props, - status: 'pending', - }; - const tree = renderer.create(reduxWrapper()) - .toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should match success section snapshot', () => { - props = { - ...props, - status: 'complete', - }; - renderer.create( - reduxWrapper( - - - , - ), - ); - expect(history.location.pathname).toEqual('/login'); - }); - - it('should display need other help signing in button', () => { - const wrapper = mount(reduxWrapper()); - expect(wrapper.find('button.field-link').first().text()).toEqual('Need other help signing in?'); - }); - - it('should display email validation error message', async () => { - const validationMessage = "We couldn’t send the password recovery email.The email address you've provided isn't formatted correctly."; - const wrapper = mount(reduxWrapper()); - - wrapper.find('input#forgot-password-input').simulate( - 'change', { target: { value: 'invalid-email', name: 'email' } }, - ); - await act(async () => { await wrapper.find('button.btn-primary').simulate('click'); }); - wrapper.update(); - - expect(wrapper.find('.alert-danger').text()).toEqual(validationMessage); - }); - - it('should show alert on server error', () => { - props = { - ...props, - status: INTERNAL_SERVER_ERROR, - }; - const expectedMessage = 'We couldn’t send the password recovery email.' - + 'An error has occurred. Try refreshing the page, or check your internet connection.'; - const wrapper = mount(reduxWrapper()); - - expect(wrapper.find('#internal-server-error').first().text()).toEqual(expectedMessage); - }); - - it('should display empty email validation message', async () => { - const validationMessage = 'We couldn’t send the password recovery email.Please enter your email.'; - const forgotPasswordPage = mount(reduxWrapper()); - - await act(async () => { await forgotPasswordPage.find('button.btn-primary').simulate('click'); }); - - forgotPasswordPage.update(); - expect(forgotPasswordPage.find('.alert-danger').text()).toEqual(validationMessage); - }); - - it('should display request in progress error message', () => { - const rateLimitMessage = 'An error occurred.Your previous request is in progress, please try again in a few moments.'; - store = mockStore({ - forgotPassword: { status: 'forbidden' }, - }); - - const forgotPasswordPage = mount(reduxWrapper()); - expect(forgotPasswordPage.find('.alert-danger').text()).toEqual(rateLimitMessage); - }); - - it('should not display any error message on change event', () => { - const forgotPasswordPage = mount(reduxWrapper()); - - const emailInput = forgotPasswordPage.find('input#forgot-password-input'); - emailInput.simulate('change', { target: { value: 'invalid-email', name: 'email' } }); - forgotPasswordPage.update(); - - expect(forgotPasswordPage.find('#email-invalid-feedback').exists()).toEqual(false); - }); - - it('should display error message on blur event', async () => { - const validationMessage = 'Please enter your email.'; - const forgotPasswordPage = mount(reduxWrapper()); - const emailInput = forgotPasswordPage.find('input#forgot-password-input'); - - await act(async () => { - await emailInput.simulate('blur', { target: { value: '', name: 'email' } }); - }); - - forgotPasswordPage.update(); - expect(forgotPasswordPage.find('#forgot-password-input-invalid-feedback').text()).toEqual(validationMessage); - }); - - it('check cookie rendered', () => { - const forgotPage = mount(reduxWrapper()); - expect(forgotPage.find()).toBeTruthy(); - }); -}); diff --git a/src/legacy/forgot-password/tests/__snapshots__/ForgotPasswordPage.test.jsx.snap b/src/legacy/forgot-password/tests/__snapshots__/ForgotPasswordPage.test.jsx.snap deleted file mode 100644 index 677bf27c..00000000 --- a/src/legacy/forgot-password/tests/__snapshots__/ForgotPasswordPage.test.jsx.snap +++ /dev/null @@ -1,375 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ForgotPasswordPage should match default section snapshot 1`] = ` -
-
-
-

- Password assistance -

-

- Please enter your log-in or recovery email address below and we will send you an email with instructions. -

-
- - - -
- -
-
-
- - -
-
-`; - -exports[`ForgotPasswordPage should match forbidden section snapshot 1`] = ` -
-
-
-
-
-
-
- An error occurred. -
-
    -
  • - Your previous request is in progress, please try again in a few moments. -
  • -
-
-
-
-

- Password assistance -

-

- Please enter your log-in or recovery email address below and we will send you an email with instructions. -

-
- - - -
- -
-
-
- - -
-
-`; - -exports[`ForgotPasswordPage should match pending section snapshot 1`] = ` -
-
-
-

- Password assistance -

-

- Please enter your log-in or recovery email address below and we will send you an email with instructions. -

-
- - - -
- -
-
-
- - -
-
-`; diff --git a/src/legacy/i18n/transifex_input.json b/src/legacy/i18n/transifex_input.json deleted file mode 100644 index a2d67e72..00000000 --- a/src/legacy/i18n/transifex_input.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "forgot.password.confirmation.message": "You entered {strongEmail}. If this email address is associated with your\n edX account, we will send a message with password recovery instructions to this email address.", - "forgot.password.technical.support.help.message": "If you need further assistance, {technicalSupportLink}.", - "institution.login.page.sub.heading": "Choose your institution from the list below:", - "forgot.password.confirmation.title": "Check your email", - "forgot.password.confirmation.support.link": "contact technical support", - "forgot.password.confirmation.info": "If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder.", - "internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.", - "server.ratelimit.error.message": "An error has occurred because of too many requests. Please try again after some time.", - "enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?", - "enterprisetpa.sso.button.title": "Sign in using {providerName}", - "enterprisetpa.login.button.text": "Show me other ways to sign in or register", - "sso.sign.in.with": "Sign in with {providerName}", - "sso.create.account.using": "Create account using {providerName}", - "error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.", - "login.third.party.auth.account.not.linked.message": "You have successfully signed into {currentProvider}, but your {currentProvider} account does not have a linked {platformName} account. To link your accounts, sign in now using your {platformName} password.", - "register.third.party.auth.account.not.linked.message": "You've successfully signed into {currentProvider}. We just need a little more information before you start learning with {platformName}.", - "forgot.password.page.title": "Forgot Password | {siteName}", - "forgot.password.page.heading": "Password assistance", - "forgot.password.page.instructions": "Please enter your log-in or recovery email address below and we will send you an email with instructions.", - "forgot.password.page.invalid.email.message": "The email address you've provided isn't formatted correctly.", - "forgot.password.page.email.field.label": "Email", - "forgot.password.page.submit.button": "Recover my password", - "forgot.password.request.server.error": "We couldn’t send the password recovery email.", - "forgot.password.error.message.title": "An error occurred.", - "forgot.password.request.in.progress.message": "Your previous request is in progress, please try again in a few moments.", - "forgot.password.empty.email.field.error": "Please enter your email.", - "forgot.password.invalid.email": "An error occurred.", - "forgot.password.invalid.email.message": "The email address you've provided isn't formatted correctly.", - "forgot.password.email.help.text": "The email address you used to register with {platformName}", - "account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.", - "non.compliant.password.error": "{passwordComplaintRequirements} {lineBreak}Your current password does not meet the new security\n requirements. We just sent a password-reset message to the email address associated with this account.\n Thank you for helping us keep your data safe.", - "login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.", - "login.reset.password.message.with.link": "If you've forgotten your password, click {resetLink} to reset.", - "login.locked.reset.password.message.with.link": "To be on the safe side, you can reset your password {resetLink} before you try again.", - "login.page.title": "Login | {siteName}", - "sign.in.button": "Sign in", - "need.help.signing.in.collapsible.menu": "Need help signing in?", - "forgot.password.link": "Forgot my password", - "other.sign.in.issues": "Other sign in issues", - "need.other.help.signing.in.collapsible.menu": "Need other help signing in?", - "institution.login.button": "Use my university info", - "institution.login.page.title": "Sign in with institution/campus credentials", - "institution.login.page.back.button": "Back to sign in", - "create.an.account": "Create an account", - "or.sign.in.with": "or sign in with", - "non.compliant.password.title": "We recently changed our password requirements", - "first.time.here": "First time here?", - "email.label": "Email", - "email.help.message": "The email address you used to register with edX.", - "enterprise.login.link.text": "Sign in with your company or school", - "email.format.validation.message": "The email address you've provided isn't formatted correctly.", - "email.format.validation.less.chars.message": "Email must have at least 3 characters.", - "email.validation.message": "Please enter your email.", - "password.validation.message": "Please enter your password.", - "password.label": "Password (required)", - "register.link": "Create an account", - "sign.in.heading": "Sign in", - "account.activation.success.message.title": "Success! You have activated your account.", - "account.activation.success.message": "You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.", - "account.already.activated.message": "This account has already been activated.", - "account.activation.error.message.title": "Your account could not be activated", - "account.activation.support.link": "contact support", - "login.rate.limit.reached.message": "Too many failed login attempts. Try again later.", - "login.failure.header.title": "We couldn't sign you in.", - "contact.support.link": "contact {platformName} support", - "login.failed.link.text": "here", - "login.incorrect.credentials.error": "Email or password is incorrect.", - "login.failed.attempt.error": "You have {remainingAttempts} more sign in attempts before your account is temporarily locked.", - "login.locked.out.error.message": "To protect your account, it’s been temporarily locked. Try again in {lockedOutPeriod} minutes.", - "register.page.title": "Register | {siteName}", - "create.account.button": "Create account", - "already.have.an.edx.account": "Already have an edX account?", - "sign.in.hyperlink": "Sign in.", - "create.an.account.using": "or create an account using", - "create.a.new.account": "Create a new account", - "register.institution.login.button": "Use my institution/campus credentials", - "register.institution.login.page.title": "Register with institution/campus credentials", - "register.page.email.label": "Email (required)", - "register.rate.limit.reached.message": "Too many failed registration attempts. Try again later.", - "email.ratelimit.less.chars.validation.message": "Email must have 3 characters.", - "email.ratelimit.incorrect.format.validation.message": "The email address you provided isn't formatted correctly.", - "email.ratelimit.password.validation.message": "Your password must contain at least 8 characters", - "register.page.password.validation.message": "Please enter your password.", - "fullname.label": "Full name (required)", - "fullname.validation.message": "Please enter your full name.", - "username.label": "Public username (required)", - "username.validation.message": "Please enter your public username.", - "username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-).", - "username.character.validation.message": "Your password must contain at least 1 letter.", - "username.number.validation.message": "Your password must contain at least 1 number.", - "username.ratelimit.less.chars.message": "Public username must have atleast 2 characters.", - "country.validation.message": "Select your country or region of residence.", - "support.education.research": "Support education research by providing additional information. (Optional)", - "registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.", - "registration.request.failure.header": "We couldn't create your account.", - "helptext.name": "This name will be used by any certificates that you earn.", - "helptext.username": "The name that will identify you in your courses. It cannot be changed later.", - "helptext.password": "Your password must contain at least 8 characters, including 1 letter & 1 number.", - "helptext.email": "This is what you will use to login.", - "terms.of.service.and.honor.code": "Terms of Service and Honor Code", - "privacy.policy": "Privacy Policy", - "registration.year.of.birth.label": "Year of birth (optional)", - "registration.country.label": "Country or region of residence (required)", - "registration.field.gender.options.label": "Gender (optional)", - "registration.goals.label": "Tell us why you're interested in edX (optional)", - "registration.field.gender.options.f": "Female", - "registration.field.gender.options.m": "Male", - "registration.field.gender.options.o": "Other/Prefer not to say", - "registration.field.education.levels.label": "Highest level of education completed (optional)", - "registration.field.education.levels.p": "Doctorate", - "registration.field.education.levels.m": "Master's or professional degree", - "registration.field.education.levels.b": "Bachelor's degree", - "registration.field.education.levels.a": "Associate's degree", - "registration.field.education.levels.hs": "Secondary/high school", - "registration.field.education.levels.jhs": "Junior secondary/junior high/middle school", - "registration.field.education.levels.el": "Elementary/primary school", - "registration.field.education.levels.none": "No formal education", - "registration.field.education.levels.other": "Other education", - "register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.", - "reset.password.request.invalid.token.description.message": "This password reset link is invalid. It may have been used already.\n To reset your password, go to the {loginPasswordLink} page and select {forgotPassword}", - "reset.password.page.title": "Reset Password | {siteName}", - "reset.password.page.heading": "Reset your password", - "reset.password.page.instructions": "Enter and confirm your new password.", - "reset.password.page.invalid.match.message": "Passwords do not match.", - "forgot.password.page.new.field.label": "New password", - "forgot.password.page.confirm.field.label": "Confirm password", - "reset.password.page.submit.button": "Reset my password", - "reset.password.request.success.header.message": "Password reset complete.", - "forgot.password.confirmation.sign.in.link": "sign in", - "reset.password.request.forgot.password.text": "Forgot password", - "reset.password.request.invalid.token.header": "Invalid password reset link", - "reset.password.empty.new.password.field.error": "Please enter your new password.", - "forgot.password.empty.new.password.error.heading": "We couldn't reset your password.", - "reset.password.request.server.error": "Failed to reset password", - "reset.password.token.validation.sever.error": "Token validation failure", - "reset.server.ratelimit.error": "Too many requests.", - "reset.password.confirmation.support.link": "Sign in to your account.", - "reset.password.request.success.header.description.message": "Your password has been reset. {loginPasswordLink}", - "optional.fields.page.title": "Optional Fields | {siteName}", - "optional.fields.page.heading": "Support education research by providing additional information.", - "welcome.to.edx": "Welcome to edX, {username}!", - "optional.fields.information.link": "Learn more about how we use this information.", - "optional.fields.submit.button": "Submit", - "optional.fields.skip.button": "Skip for now" -} \ No newline at end of file diff --git a/src/legacy/index.jsx b/src/legacy/index.jsx deleted file mode 100755 index eb48a68c..00000000 --- a/src/legacy/index.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { Redirect, Route, Switch } from 'react-router-dom'; - -import { AppProvider } from '@edx/frontend-platform/react'; - -import configureStore from './data/configureStore'; -import { RegistrationPage } from './register'; -import { LoginPage } from './login'; -import { - LOGIN_PAGE, PAGE_NOT_FOUND, REGISTER_PAGE, RESET_PAGE, PASSWORD_RESET_CONFIRM, WELCOME_PAGE, -} from './data/constants'; -import ForgotPasswordPage from './forgot-password'; -import { - HeaderLayout, UnAuthOnlyRoute, registerIcons, NotFoundPage, -} from './common-components'; -import ResetPasswordPage from './reset-password'; -import WelcomePage from './welcome'; -import './index.scss'; - -registerIcons(); - -const LegacyApp = () => ( - - - - - - - - - - - - - - - - - - -); - -export default LegacyApp; diff --git a/src/legacy/index.scss b/src/legacy/index.scss deleted file mode 100755 index 3b10b284..00000000 --- a/src/legacy/index.scss +++ /dev/null @@ -1,10 +0,0 @@ -@import "~@edx/brand/paragon/fonts"; -@import "~@edx/brand/paragon/variables"; -@import "~@edx/paragon/scss/core/core"; -@import "~@edx/brand/paragon/overrides"; - -@import "~@edx/frontend-component-header/dist/index"; - -@import '@edx/frontend-component-cookie-policy-banner/build/_cookie-policy-banner'; - -@import "./style"; diff --git a/src/legacy/login/AccountActivationMessage.jsx b/src/legacy/login/AccountActivationMessage.jsx deleted file mode 100644 index c86cd5db..00000000 --- a/src/legacy/login/AccountActivationMessage.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; - -import { getConfig } from '@edx/frontend-platform'; -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { Alert } from '@edx/paragon'; -import PropTypes from 'prop-types'; - -import { ACCOUNT_ACTIVATION_MESSAGE } from './data/constants'; -import messages from './messages'; - -const AccountActivationMessage = (props) => { - const { intl, messageType } = props; - const variant = messageType === ACCOUNT_ACTIVATION_MESSAGE.ERROR ? 'danger' : messageType; - - let activationMessage; - let heading; - - switch (messageType) { - case ACCOUNT_ACTIVATION_MESSAGE.SUCCESS: { - heading = intl.formatMessage(messages['account.activation.success.message.title']); - activationMessage = intl.formatMessage(messages['account.activation.success.message']); - break; - } - case ACCOUNT_ACTIVATION_MESSAGE.INFO: { - activationMessage = intl.formatMessage(messages['account.already.activated.message']); - break; - } - case ACCOUNT_ACTIVATION_MESSAGE.ERROR: { - const supportLink = ( - - {intl.formatMessage(messages['account.activation.support.link'])} - - ); - - heading = intl.formatMessage(messages['account.activation.error.message.title']); - activationMessage = ( - - ); - break; - } - default: - break; - } - - return activationMessage ? ( - - {heading && {heading}} - {activationMessage} - - ) : null; -}; - -AccountActivationMessage.propTypes = { - messageType: PropTypes.string.isRequired, - intl: intlShape.isRequired, -}; - -export default injectIntl(AccountActivationMessage); diff --git a/src/legacy/login/LoginFailure.jsx b/src/legacy/login/LoginFailure.jsx deleted file mode 100644 index 60efa5f7..00000000 --- a/src/legacy/login/LoginFailure.jsx +++ /dev/null @@ -1,195 +0,0 @@ -import React from 'react'; - -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { Alert } from '@edx/paragon'; -import PropTypes from 'prop-types'; - -import processLink from '../data/utils'; -import { - ACCOUNT_LOCKED_OUT, - FAILED_LOGIN_ATTEMPT, - FORBIDDEN_REQUEST, - INACTIVE_USER, - INCORRECT_EMAIL_PASSWORD, - INTERNAL_SERVER_ERROR, - INVALID_FORM, - NON_COMPLIANT_PASSWORD_EXCEPTION, -} from './data/constants'; -import messages from './messages'; - -const LoginFailureMessage = (props) => { - const { intl } = props; - const { context, errorCode, value } = props.loginError; - let errorList; - let link; - - switch (errorCode) { - case NON_COMPLIANT_PASSWORD_EXCEPTION: { - errorList = ( -
  • - {intl.formatMessage(messages['non.compliant.password.title'])}, - lineBreak:
    , - }} - /> -
  • - ); - break; - } - case FORBIDDEN_REQUEST: - errorList = ( -
  • - {intl.formatMessage(messages['login.rate.limit.reached.message'])} -
  • - ); - break; - case INACTIVE_USER: { - const contextSupportLink = typeof context.supportLink === 'string' ? context.supportLink : ''; - const supportLink = ( - - {intl.formatMessage(messages['contact.support.link'], { platformName: context.platformName })} - - ); - errorList = ( -
  • - , - email: {props.loginError.email}, - supportLink, - }} - /> -
  • - ); - break; - } - case INTERNAL_SERVER_ERROR: - errorList = ( -
  • - {intl.formatMessage(messages['internal.server.error.message'])} -
  • - ); - break; - case INVALID_FORM: - errorList = ( - <> - {context.email &&
  • {context.email}
  • } - {context.password &&
  • {context.password}
  • } - - ); - break; - case FAILED_LOGIN_ATTEMPT: { - const resetLink = ( - - {intl.formatMessage(messages['login.failed.link.text'])} - - ); - errorList = ( - <> -
  • - {intl.formatMessage(messages['login.incorrect.credentials.error'])} -
  • -
  • - {intl.formatMessage(messages['login.failed.attempt.error'], { remainingAttempts: context.remainingAttempts })} -
  • -
  • - -
  • - - ); - break; - } - case ACCOUNT_LOCKED_OUT: { - const resetLink = ( - - {intl.formatMessage(messages['login.failed.link.text'])} - - ); - errorList = ( - <> -
  • - {intl.formatMessage(messages['login.locked.out.error.message'], { lockedOutPeriod: context.lockedOutPeriod })} -
  • -
  • - -
  • - - ); - break; - } - case INCORRECT_EMAIL_PASSWORD: - errorList = ( -
  • - {intl.formatMessage(messages['login.incorrect.credentials.error'])} -
  • - ); - break; - default: - // TODO: use errorCode instead of processing error messages on frontend - errorList = value.trim().split('\n'); - errorList = errorList.map((error) => { - let matches; - if (error.includes('a href')) { - matches = processLink(error); - const [beforeLink, href, linkText, afterLink] = matches; - link = href; - if (href.indexOf('/dashboard?tpa_hint') === 0) { - link = `/login?next=${href}`; - } - return ( -
  • - {beforeLink} - {linkText} - {afterLink} -
  • - ); - } - return
  • {error}
  • ; - }); - } - - return ( - - {intl.formatMessage(messages['login.failure.header.title'])} -
      {errorList}
    -
    - ); -}; - -LoginFailureMessage.defaultProps = { - loginError: { - errorCode: null, - value: '', - }, -}; - -LoginFailureMessage.propTypes = { - loginError: PropTypes.shape({ - context: PropTypes.object, - email: PropTypes.string, - errorCode: PropTypes.string, - value: PropTypes.string, - }), - intl: intlShape.isRequired, -}; - -export default injectIntl(LoginFailureMessage); diff --git a/src/legacy/login/LoginHelpLinks.jsx b/src/legacy/login/LoginHelpLinks.jsx deleted file mode 100644 index bba14ed5..00000000 --- a/src/legacy/login/LoginHelpLinks.jsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { getConfig } from '@edx/frontend-platform'; -import { Hyperlink } from '@edx/paragon'; - -import SwitchContent from '../common-components/SwitchContent'; -import { - LOGIN_PAGE, - REGISTER_PAGE, - RESET_PAGE, -} from '../data/constants'; -import messages from './messages'; -import { updatePathWithQueryParams } from '../data/utils'; - -const LoginHelpLinks = (props) => { - const { intl, page } = props; - const [showLoginHelp, setShowLoginHelpValue] = useState(false); - - const toggleLoginHelp = (e) => { - e.preventDefault(); - setShowLoginHelpValue(!showLoginHelp); - }; - - const handleForgotPasswordLinkClickEvent = () => { - sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' }); - }; - - const forgotPasswordLink = () => ( - - {intl.formatMessage(messages['forgot.password.link'])} - - ); - - const signUpLink = () => ( - - {intl.formatMessage(messages['register.link'])} - - ); - - const loginIssueSupportURL = (config) => (config.LOGIN_ISSUE_SUPPORT_LINK - ? ( - - {intl.formatMessage(messages['other.sign.in.issues'])} - - ) - : null); - - const getHelpButtonMessage = () => { - let mid = 'need.other.help.signing.in.collapsible.menu'; - if (page === LOGIN_PAGE) { - mid = 'need.help.signing.in.collapsible.menu'; - } - - return intl.formatMessage(messages[mid]); - }; - - const renderLoginHelp = () => ( -
    - { page === LOGIN_PAGE ? forgotPasswordLink() : signUpLink() } - { loginIssueSupportURL(getConfig()) } -
    - ); - - return ( - <> - - , - }} - /> - - ); -}; - -LoginHelpLinks.propTypes = { - intl: intlShape.isRequired, - page: PropTypes.string.isRequired, -}; - -export default injectIntl(LoginHelpLinks); diff --git a/src/legacy/login/LoginPage.jsx b/src/legacy/login/LoginPage.jsx deleted file mode 100644 index c296d7a5..00000000 --- a/src/legacy/login/LoginPage.jsx +++ /dev/null @@ -1,384 +0,0 @@ -import React from 'react'; -import Skeleton from 'react-loading-skeleton'; -import { Helmet } from 'react-helmet'; - -import { getConfig } from '@edx/frontend-platform'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; -import { - Form, Hyperlink, StatefulButton, -} from '@edx/paragon'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faSpinner } from '@fortawesome/free-solid-svg-icons'; - -import AccountActivationMessage from './AccountActivationMessage'; -import ConfirmationAlert from '../common-components/ConfirmationAlert'; -import { loginRequest, loginRequestFailure } from './data/actions'; -import { INVALID_FORM } from './data/constants'; -import { getThirdPartyAuthContext } from '../common-components/data/actions'; -import { loginErrorSelector, loginRequestSelector } from './data/selectors'; -import { thirdPartyAuthContextSelector } from '../common-components/data/selectors'; -import LoginHelpLinks from './LoginHelpLinks'; -import LoginFailureMessage from './LoginFailure'; -import EnterpriseSSO from '../common-components/EnterpriseSSO'; -import messages from './messages'; -import { - RedirectLogistration, SocialAuthProviders, ThirdPartyAuthAlert, RenderInstitutionButton, - InstitutionLogistration, AuthnValidationFormGroup, -} from '../common-components'; -import { - DEFAULT_STATE, LOGIN_PAGE, REGISTER_PAGE, ENTERPRISE_LOGIN_URL, PENDING_STATE, VALID_EMAIL_REGEX, -} from '../data/constants'; -import { forgotPasswordResultSelector } from '../forgot-password'; -import { - getTpaHint, - getTpaProvider, - windowScrollTo, - setSurveyCookie, - getActivationStatus, - getAllPossibleQueryParam, - updatePathWithQueryParams, -} from '../data/utils'; - -class LoginPage extends React.Component { - constructor(props, context) { - super(props, context); - - sendPageEvent('login_and_registration', 'login'); - this.state = { - password: '', - email: '', - errors: { - email: '', - password: '', - }, - institutionLogin: false, - isSubmitted: false, - }; - this.queryParams = getAllPossibleQueryParam(); - this.tpaHint = getTpaHint(); - } - - componentDidMount() { - const payload = { ...this.queryParams }; - - if (this.tpaHint) { - payload.tpa_hint = this.tpaHint; - } - this.props.getThirdPartyAuthContext(payload); - } - - getEnterPriseLoginURL() { - return getConfig().LMS_BASE_URL + ENTERPRISE_LOGIN_URL; - } - - handleInstitutionLogin = () => { - sendTrackEvent('edx.bi.institution_login_form.toggled', { category: 'user-engagement' }); - sendPageEvent('login_and_registration', 'institution_login'); - this.setState(prevState => ({ institutionLogin: !prevState.institutionLogin })); - } - - handleSubmit = (e) => { - e.preventDefault(); - this.setState({ isSubmitted: true }); - - const { email, password } = this.state; - const emailValidationError = this.validateEmail(email); - const passwordValidationError = this.validatePassword(password); - - if (emailValidationError !== '' || passwordValidationError !== '') { - this.props.loginRequestFailure({ - errorCode: INVALID_FORM, - context: { email: emailValidationError, password: passwordValidationError }, - }); - return; - } - - const payload = { - email, password, ...this.queryParams, - }; - this.props.loginRequest(payload); - } - - validateEmail(email) { - const { errors } = this.state; - const regex = new RegExp(VALID_EMAIL_REGEX, 'i'); - - if (email === '') { - errors.email = this.props.intl.formatMessage(messages['email.validation.message']); - } else if (email.length < 3) { - errors.email = this.props.intl.formatMessage(messages['email.format.validation.less.chars.message']); - } else if (!regex.test(email)) { - errors.email = this.props.intl.formatMessage(messages['email.format.validation.message']); - } else { - errors.email = ''; - } - this.setState({ errors }); - return errors.email; - } - - validatePassword(password) { - const { errors } = this.state; - errors.password = password.length > 0 ? '' : this.props.intl.formatMessage(messages['password.validation.message']); - - this.setState({ errors }); - return errors.password; - } - - handleCreateAccountLinkClickEvent() { - sendTrackEvent('edx.bi.register_form.toggled', { category: 'user-engagement' }); - } - - renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl) { - let thirdPartyComponent = null; - if ((providers.length || secondaryProviders.length) && !currentProvider) { - thirdPartyComponent = ( - <> - -
    - -
    - - ); - } else if (thirdPartyAuthApiStatus === PENDING_STATE) { - thirdPartyComponent = ; - } return thirdPartyComponent; - } - - renderForm( - currentProvider, - providers, - secondaryProviders, - thirdPartyAuthContext, - thirdPartyAuthApiStatus, - submitState, - intl, - ) { - const { email, errors, password } = this.state; - const activationMsgType = getActivationStatus(); - if (this.state.institutionLogin) { - return ( - - ); - } - - if (this.props.loginResult.success) { - setSurveyCookie('login'); - - // Fire optimizely events - window.optimizely = window.optimizely || []; - window.optimizely.push({ - type: 'event', - eventName: 'authn-login-coversion', - }); - } - - return ( - <> - - {intl.formatMessage(messages['login.page.title'], - { siteName: getConfig().SITE_NAME })} - - - -
    -
    -
    - {thirdPartyAuthContext.currentProvider - && ( - - )} - {this.props.loginError ? : null} - {submitState === DEFAULT_STATE && this.state.isSubmitted ? windowScrollTo({ left: 0, top: 0, behavior: 'smooth' }) : null} - {activationMsgType && } - {this.props.forgotPassword.status === 'complete' && !this.props.loginError ? ( - - ) : null} -

    - {intl.formatMessage(messages['first.time.here'])} - - {intl.formatMessage(messages['create.an.account'])}. - -

    -
    -

    - {intl.formatMessage(messages['sign.in.heading'])} -

    -
    - this.setState({ email: e.target.value, isSubmitted: false })} - inputFieldStyle="border-gray-600" - /> - this.setState({ password: e.target.value, isSubmitted: false })} - inputFieldStyle="border-gray-600" - /> - - - {intl.formatMessage(messages['enterprise.login.link.text'])} - - }} - onClick={this.handleSubmit} - onMouseDown={(e) => e.preventDefault()} - /> - - {(providers.length || secondaryProviders.length || thirdPartyAuthApiStatus === PENDING_STATE) - && !currentProvider ? ( -
    -
    - {intl.formatMessage(messages['or.sign.in.with'])} -
    - ) : null} - {this.renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl)} -
    -
    -
    - - ); - } - - render() { - const { - intl, submitState, thirdPartyAuthContext, thirdPartyAuthApiStatus, - } = this.props; - const { currentProvider, providers, secondaryProviders } = this.props.thirdPartyAuthContext; - - if (this.tpaHint) { - if (thirdPartyAuthApiStatus === PENDING_STATE) { - return ; - } - const { provider, skipHintedLogin } = getTpaProvider(this.tpaHint, providers, secondaryProviders); - if (skipHintedLogin) { - window.location.href = getConfig().LMS_BASE_URL + provider.loginUrl; - return null; - } - return provider ? () : this.renderForm( - currentProvider, - providers, - secondaryProviders, - thirdPartyAuthContext, - thirdPartyAuthApiStatus, - submitState, - intl, - ); - } - return this.renderForm( - currentProvider, - providers, - secondaryProviders, - thirdPartyAuthContext, - thirdPartyAuthApiStatus, - submitState, - intl, - ); - } -} - -LoginPage.defaultProps = { - forgotPassword: null, - loginResult: null, - loginError: null, - submitState: DEFAULT_STATE, - thirdPartyAuthApiStatus: 'pending', - thirdPartyAuthContext: { - currentProvider: null, - finishAuthUrl: null, - providers: [], - secondaryProviders: [], - }, -}; - -LoginPage.propTypes = { - forgotPassword: PropTypes.shape({ - email: PropTypes.string, - status: PropTypes.string, - }), - getThirdPartyAuthContext: PropTypes.func.isRequired, - intl: intlShape.isRequired, - loginError: PropTypes.objectOf(PropTypes.any), - loginRequest: PropTypes.func.isRequired, - loginRequestFailure: PropTypes.func.isRequired, - loginResult: PropTypes.shape({ - redirectUrl: PropTypes.string, - success: PropTypes.bool, - }), - submitState: PropTypes.string, - thirdPartyAuthApiStatus: PropTypes.string, - thirdPartyAuthContext: PropTypes.shape({ - currentProvider: PropTypes.string, - platformName: PropTypes.string, - providers: PropTypes.array, - secondaryProviders: PropTypes.array, - finishAuthUrl: PropTypes.string, - }), -}; - -const mapStateToProps = state => { - const forgotPassword = forgotPasswordResultSelector(state); - const loginResult = loginRequestSelector(state); - const thirdPartyAuthContext = thirdPartyAuthContextSelector(state); - const loginError = loginErrorSelector(state); - return { - submitState: state.login.submitState, - thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus, - forgotPassword, - loginError, - loginResult, - thirdPartyAuthContext, - }; -}; - -export default connect( - mapStateToProps, - { - getThirdPartyAuthContext, - loginRequest, - loginRequestFailure, - }, -)(injectIntl(LoginPage)); diff --git a/src/legacy/login/data/actions.js b/src/legacy/login/data/actions.js deleted file mode 100644 index 7d70a324..00000000 --- a/src/legacy/login/data/actions.js +++ /dev/null @@ -1,23 +0,0 @@ -import { AsyncActionType } from '../../data/utils'; - -export const LOGIN_REQUEST = new AsyncActionType('LOGIN', 'REQUEST'); - -// Login -export const loginRequest = creds => ({ - type: LOGIN_REQUEST.BASE, - payload: { creds }, -}); - -export const loginRequestBegin = () => ({ - type: LOGIN_REQUEST.BEGIN, -}); - -export const loginRequestSuccess = (redirectUrl, success) => ({ - type: LOGIN_REQUEST.SUCCESS, - payload: { redirectUrl, success }, -}); - -export const loginRequestFailure = (loginError) => ({ - type: LOGIN_REQUEST.FAILURE, - payload: { loginError }, -}); diff --git a/src/legacy/login/data/reducers.js b/src/legacy/login/data/reducers.js deleted file mode 100644 index 04d38b96..00000000 --- a/src/legacy/login/data/reducers.js +++ /dev/null @@ -1,33 +0,0 @@ -import { LOGIN_REQUEST } from './actions'; - -import { DEFAULT_STATE, PENDING_STATE } from '../../data/constants'; - -export const defaultState = { - loginError: null, - loginResult: {}, -}; - -const reducer = (state = defaultState, action) => { - switch (action.type) { - case LOGIN_REQUEST.BEGIN: - return { - ...state, - submitState: PENDING_STATE, - }; - case LOGIN_REQUEST.SUCCESS: - return { - ...state, - loginResult: action.payload, - }; - case LOGIN_REQUEST.FAILURE: - return { - ...state, - loginError: action.payload.loginError, - submitState: DEFAULT_STATE, - }; - default: - return state; - } -}; - -export default reducer; diff --git a/src/legacy/login/data/service.js b/src/legacy/login/data/service.js deleted file mode 100644 index 24923d3c..00000000 --- a/src/legacy/login/data/service.js +++ /dev/null @@ -1,26 +0,0 @@ -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { getConfig } from '@edx/frontend-platform'; -import querystring from 'querystring'; - -// eslint-disable-next-line import/prefer-default-export -export async function loginRequest(creds) { - const requestConfig = { - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - isPublic: true, - }; - - const { data } = await getAuthenticatedHttpClient() - .post( - `${getConfig().LMS_BASE_URL}/user_api/v1/account/login_session/`, - querystring.stringify(creds), - requestConfig, - ) - .catch((e) => { - throw (e); - }); - - return { - redirectUrl: data.redirect_url || `${getConfig().LMS_BASE_URL}/dashboard`, - success: data.success || false, - }; -} diff --git a/src/legacy/login/messages.jsx b/src/legacy/login/messages.jsx deleted file mode 100644 index d2eb4dfa..00000000 --- a/src/legacy/login/messages.jsx +++ /dev/null @@ -1,192 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - 'login.page.title': { - id: 'login.page.title', - defaultMessage: 'Login | {siteName}', - description: 'login page title', - }, - 'sign.in.button': { - id: 'sign.in.button', - defaultMessage: 'Sign in', - description: 'Button label that appears on login page', - }, - 'need.help.signing.in.collapsible.menu': { - id: 'need.help.signing.in.collapsible.menu', - defaultMessage: 'Need help signing in?', - description: 'A button for collapsible need help signing in menu on login page', - }, - 'forgot.password.link': { - id: 'forgot.password.link', - defaultMessage: 'Forgot my password', - description: 'Forgot password link', - }, - 'other.sign.in.issues': { - id: 'other.sign.in.issues', - defaultMessage: 'Other sign in issues', - description: 'A link that redirects to sign-in issues help', - }, - 'need.other.help.signing.in.collapsible.menu': { - id: 'need.other.help.signing.in.collapsible.menu', - defaultMessage: 'Need other help signing in?', - description: 'A button for collapsible need other help signing in menu on forgot password page', - }, - 'institution.login.button': { - id: 'institution.login.button', - defaultMessage: 'Use my university info', - description: 'shows institutions list', - }, - 'institution.login.page.title': { - id: 'institution.login.page.title', - defaultMessage: 'Sign in with institution/campus credentials', - description: 'Heading of institution page', - }, - 'institution.login.page.sub.heading': { - id: 'institution.login.page.sub.heading', - defaultMessage: 'Choose your institution from the list below:', - description: 'Heading of the institutions list', - }, - 'institution.login.page.back.button': { - id: 'institution.login.page.back.button', - defaultMessage: 'Back to sign in', - description: 'return to login page', - }, - 'create.an.account': { - id: 'create.an.account', - defaultMessage: 'Create an account', - description: 'Message on button to return to register page', - }, - 'or.sign.in.with': { - id: 'or.sign.in.with', - defaultMessage: 'or sign in with', - description: 'gives hint about other sign in options', - }, - 'non.compliant.password.title': { - id: 'non.compliant.password.title', - defaultMessage: 'We recently changed our password requirements', - description: 'A title that appears in bold before error message for non-compliant password', - }, - 'first.time.here': { - id: 'first.time.here', - defaultMessage: 'First time here?', - description: 'A question that appears before sign up link', - }, - 'email.label': { - id: 'email.label', - defaultMessage: 'Email', - description: 'Label that appears above email field', - }, - 'email.help.message': { - id: 'email.help.message', - defaultMessage: 'The email address you used to register with edX.', - description: 'Message that appears below email field on login page', - }, - 'enterprise.login.link.text': { - id: 'enterprise.login.link.text', - defaultMessage: 'Sign in with your company or school', - description: 'Company or school login link text.', - }, - 'email.format.validation.message': { - id: 'email.format.validation.message', - defaultMessage: 'The email address you\'ve provided isn\'t formatted correctly.', - description: 'Validation message that appears when email address format is incorrect', - }, - 'email.format.validation.less.chars.message': { - id: 'email.format.validation.less.chars.message', - defaultMessage: 'Email must have at least 3 characters.', - description: 'Validation message that appears when email address is less than 3 characters', - }, - 'email.validation.message': { - id: 'email.validation.message', - defaultMessage: 'Please enter your email.', - description: 'Validation message that appears when email is empty', - }, - 'password.validation.message': { - id: 'password.validation.message', - defaultMessage: 'Please enter your password.', - description: 'Validation message that appears when password is empty', - }, - 'password.label': { - id: 'password.label', - defaultMessage: 'Password', - description: 'Text that appears above password field or as a placeholder', - }, - 'register.link': { - id: 'register.link', - defaultMessage: 'Create an account', - description: 'Register page link', - }, - 'sign.in.heading': { - id: 'sign.in.heading', - defaultMessage: 'Sign in', - description: 'Sign in text', - }, - // Account Activation Strings - 'account.activation.success.message.title': { - id: 'account.activation.success.message.title', - defaultMessage: 'Success! You have activated your account.', - description: 'Account Activation success message title', - }, - 'account.activation.success.message': { - id: 'account.activation.success.message', - defaultMessage: 'You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.', - description: 'Message show to learners when their account has been activated successfully', - }, - 'account.already.activated.message': { - id: 'account.already.activated.message', - defaultMessage: 'This account has already been activated.', - description: 'Message shown when learner account has already been activated', - }, - 'account.activation.error.message.title': { - id: 'account.activation.error.message.title', - defaultMessage: 'Your account could not be activated', - description: 'Account Activation error message title', - }, - 'account.activation.support.link': { - id: 'account.activation.support.link', - defaultMessage: 'contact support', - description: 'Link text used in account activation error message to go to learner help center', - }, - 'internal.server.error.message': { - id: 'internal.server.error.message', - defaultMessage: 'An error has occurred. Try refreshing the page, or check your internet connection.', - description: 'Error message that appears when server responds with 500 error code', - }, - 'login.rate.limit.reached.message': { - id: 'login.rate.limit.reached.message', - defaultMessage: 'Too many failed login attempts. Try again later.', - description: 'Error message that appears when an anonymous user has made too many failed login attempts', - }, - 'login.failure.header.title': { - id: 'login.failure.header.title', - defaultMessage: 'We couldn\'t sign you in.', - description: 'Login failure header message.', - }, - 'contact.support.link': { - id: 'contact.support.link', - defaultMessage: 'contact {platformName} support', - description: 'Link text used in inactive user error message to go to learner help center', - }, - 'login.failed.link.text': { - id: 'login.failed.link.text', - defaultMessage: 'here', - description: 'Link text used in failed login attempt user error message to reset password', - }, - 'login.incorrect.credentials.error': { - id: 'login.incorrect.credentials.error', - defaultMessage: 'Email or password is incorrect.', - description: 'Error message for incorrect email or password', - }, - 'login.failed.attempt.error': { - id: 'login.failed.attempt.error', - defaultMessage: 'You have {remainingAttempts} more sign in attempts before your account is temporarily locked.', - description: 'Failed login attempts error message', - }, - 'login.locked.out.error.message': { - id: 'login.locked.out.error.message', - defaultMessage: 'To protect your account, it’s been temporarily locked. Try again in {lockedOutPeriod} minutes.', - description: 'Account locked out user message', - }, -}); - -export default messages; diff --git a/src/legacy/login/tests/LoginFailure.test.jsx b/src/legacy/login/tests/LoginFailure.test.jsx deleted file mode 100644 index fc406320..00000000 --- a/src/legacy/login/tests/LoginFailure.test.jsx +++ /dev/null @@ -1,155 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; - -import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n'; - -import LoginFailureMessage from '../LoginFailure'; -import { - FORBIDDEN_REQUEST, - INACTIVE_USER, - INTERNAL_SERVER_ERROR, - INVALID_FORM, - NON_COMPLIANT_PASSWORD_EXCEPTION, -} from '../data/constants'; - -const IntlLoginFailureMessage = injectIntl(LoginFailureMessage); - -describe('LoginFailureMessage', () => { - let props = {}; - - it('should match non compliant password error message', () => { - props = { - loginError: { - errorCode: NON_COMPLIANT_PASSWORD_EXCEPTION, - }, - }; - - const loginFailureMessage = mount( - - - , - ); - - const expectedMessage = 'We couldn\'t sign you in.We recently changed our password requirements ' - + 'Your current password does not meet the new security requirements. We just sent a ' - + 'password-reset message to the email address associated with this account. ' - + 'Thank you for helping us keep your data safe.'; - - expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage); - }); - - it('should match inactive user error message', () => { - props = { - loginError: { - email: 'text@example.com', - errorCode: INACTIVE_USER, - context: { - platformName: 'openedX', - supportLink: 'https://support.edx.org/', - }, - }, - }; - - const loginFailureMessage = mount( - - - , - ); - - const expectedMessage = 'We couldn\'t sign you in.In order to sign in, you need to activate your account. ' - + 'We just sent an activation link to text@example.com. If you do not receive an email, ' - + 'check your spam folders or contact openedX support.'; - - expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage); - expect(loginFailureMessage.find('#login-failure-alert').find('a').props().href).toEqual('https://support.edx.org/'); - }); - - it('should match rate limit error message', () => { - props = { - loginError: { - errorCode: FORBIDDEN_REQUEST, - }, - }; - - const loginFailureMessage = mount( - - - , - ); - - const expectedMessage = 'We couldn\'t sign you in.Too many failed login attempts. Try again later.'; - expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage); - }); - - it('should match internal server error message', () => { - props = { - loginError: { - errorCode: INTERNAL_SERVER_ERROR, - }, - }; - - const loginFailureMessage = mount( - - - , - ); - - const expectedMessage = 'We couldn\'t sign you in.An error has occurred. Try refreshing the page, or check your internet connection.'; - expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage); - }); - - it('should match invalid form error message', () => { - props = { - loginError: { - errorCode: INVALID_FORM, - context: { email: 'Please enter your email.', password: 'Please enter your password.' }, - }, - }; - - const loginFailureMessage = mount( - - - , - ); - - const expectedMessage = 'We couldn\'t sign you in.Please enter your email.Please enter your password.'; - expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage); - }); - - it('should match direct render of error message', () => { - const errorMessage = 'Email or password is incorrect.'; - props = { - loginError: { - value: errorMessage, - }, - }; - - const loginFailureMessage = mount( - - - , - ); - - const expectedMessage = 'We couldn\'t sign you in.'.concat(errorMessage); - expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage); - }); - - it('should match error message containing link snapshot', () => { - props = { - loginError: { - value: 'To be on the safe side, you can reset your password
    here before you try again.\n', - }, - }; - - const loginFailureMessage = mount( - - - , - ); - - const expectedMessage = 'We couldn\'t sign you in.To be on the safe side, you can reset your password here before you try again.'; - - expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage); - expect(loginFailureMessage.find('#login-failure-alert').find('a').props().href).toEqual('/reset'); - }); -}); diff --git a/src/legacy/login/tests/LoginHelpLinks.test.jsx b/src/legacy/login/tests/LoginHelpLinks.test.jsx deleted file mode 100644 index e975aa1c..00000000 --- a/src/legacy/login/tests/LoginHelpLinks.test.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import * as analytics from '@edx/frontend-platform/analytics'; -import { mount } from 'enzyme'; - -import LoginHelpLinks from '../LoginHelpLinks'; -import { LOGIN_PAGE } from '../../data/constants'; - -const otherSignInIssues = 'https://login-issue-support-url.com'; - -jest.mock('@edx/frontend-platform', () => ({ - getConfig: jest.fn().mockReturnValue({ LOGIN_ISSUE_SUPPORT_LINK: otherSignInIssues }), -})); - -jest.mock('@edx/frontend-platform/analytics'); -analytics.sendTrackEvent = jest.fn(); - -describe('LoginHelpLinks', () => { - let props = {}; - - const reduxWrapper = children => ( - - {children} - - ); - - it('renders help links on button click', () => { - props = { - ...props, - page: LOGIN_PAGE, - }; - const loginHelpLinks = mount(reduxWrapper()); - - expect(loginHelpLinks.find('.login-help').length).toBe(0); - loginHelpLinks.find('button').first().simulate('click'); - expect(loginHelpLinks.find('.login-help').length).toBe(1); - }); - - it('should display login page help links', () => { - props = { - ...props, - page: LOGIN_PAGE, - }; - - const wrapper = mount(reduxWrapper()); - wrapper.find('button').first().simulate('click'); - - const loginHelpLinks = wrapper.find('a'); - - expect(loginHelpLinks.at(0).prop('href')).toEqual('/reset'); - expect(loginHelpLinks.at(1).prop('href')).toEqual(otherSignInIssues); - }); - - it('should display forget password page help links', () => { - props = { - ...props, - page: 'forget-password', - }; - - const wrapper = mount(reduxWrapper()); - wrapper.find('button').first().simulate('click'); - - const loginHelpLinks = wrapper.find('a'); - - expect(loginHelpLinks.at(0).prop('href')).toEqual('/register'); - expect(loginHelpLinks.at(1).prop('href')).toEqual(otherSignInIssues); - }); -}); diff --git a/src/legacy/login/tests/LoginPage.test.jsx b/src/legacy/login/tests/LoginPage.test.jsx deleted file mode 100644 index 18c48c8c..00000000 --- a/src/legacy/login/tests/LoginPage.test.jsx +++ /dev/null @@ -1,523 +0,0 @@ -import React from 'react'; -import { Provider } from 'react-redux'; -import renderer from 'react-test-renderer'; -import { mount } from 'enzyme'; -import configureStore from 'redux-mock-store'; - -import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner'; -import { getConfig, mergeConfig } from '@edx/frontend-platform'; -import * as analytics from '@edx/frontend-platform/analytics'; -import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; - -import LoginFailureMessage from '../LoginFailure'; -import LoginPage from '../LoginPage'; -import { loginRequest, loginRequestFailure } from '../data/actions'; -import { RenderInstitutionButton } from '../../common-components'; -import { COMPLETE_STATE, PENDING_STATE } from '../../data/constants'; - -jest.mock('@edx/frontend-platform/analytics'); - -analytics.sendTrackEvent = jest.fn(); -analytics.sendPageEvent = jest.fn(); - -const IntlLoginFailureMessage = injectIntl(LoginFailureMessage); -const IntlLoginPage = injectIntl(LoginPage); -const mockStore = configureStore(); - -describe('LoginPage', () => { - mergeConfig({ - USER_SURVEY_COOKIE_NAME: process.env.USER_SURVEY_COOKIE_NAME, - }); - - const initialState = { - forgotPassword: { status: null }, - login: { - loginResult: { success: false, redirectUrl: '' }, - }, - commonComponents: { - thirdPartyAuthApiStatus: null, - thirdPartyAuthContext: { - currentProvider: null, - finishAuthUrl: null, - providers: [], - secondaryProviders: [], - }, - }, - }; - - let props = {}; - let store = {}; - - const secondaryProviders = { - id: 'saml-test', - name: 'Test University', - loginUrl: '/dummy-auth', - registerUrl: '/dummy_auth', - skipHintedLogin: false, - }; - - const appleProvider = { - id: 'oa2-apple-id', - name: 'Apple', - iconClass: null, - iconImage: 'https://edx.devstack.lms/logo.png', - loginUrl: '/auth/login/apple-id/?auth_entry=login&next=/dashboard', - }; - - const reduxWrapper = children => ( - - {children} - - ); - - beforeEach(() => { - store = mockStore(initialState); - props = { - loginRequest: jest.fn(), - }; - }); - - it('should match default section snapshot', () => { - const tree = renderer.create(reduxWrapper()) - .toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should match pending button state snapshot', () => { - store = mockStore({ - ...initialState, - login: { - ...initialState.login, - submitState: PENDING_STATE, - }, - }); - - const tree = renderer.create(reduxWrapper()) - .toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should match forget password alert message snapshot', () => { - store = mockStore({ - ...initialState, - forgotPassword: { status: 'complete', email: 'test@example.com' }, - }); - - const tree = renderer.create(reduxWrapper()).toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should match TPA provider snapshot', () => { - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - providers: [appleProvider], - }, - }, - }); - - const tree = renderer.create(reduxWrapper()).toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should show error message', () => { - store = mockStore({ - ...initialState, - login: { - ...initialState.login, - loginError: { value: 'Email or password is incorrect.' }, - }, - }); - - const tree = renderer.create(reduxWrapper()).toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should show account activation message', () => { - delete window.location; - window.location = { href: getConfig().BASE_URL.concat('/login'), search: '?account_activation_status=info' }; - - const expectedMessage = 'This account has already been activated.'; - - const loginPage = mount(reduxWrapper()); - expect(loginPage.find('#account-activation-message').find('div').first().text()).toEqual(expectedMessage); - }); - - it('should display login help button', () => { - const root = mount(reduxWrapper()); - expect(root.find('button.field-link').first().text()).toEqual('Need help signing in?'); - }); - - it('updates the error state for empty email input on form submission', () => { - const errorState = { email: 'Please enter your email.', password: '' }; - store.dispatch = jest.fn(store.dispatch); - - const loginPage = (mount(reduxWrapper())).find('LoginPage'); - - loginPage.find('input#password').simulate('change', { target: { value: 'test', name: 'password' } }); - loginPage.find('button.btn-brand').simulate('click'); - - expect(loginPage.state('errors')).toEqual(errorState); - expect(store.dispatch).toHaveBeenCalledWith( - loginRequestFailure({ errorCode: 'invalid-form', context: errorState }), - ); - }); - - it('updates the error state for invalid email; less than 3 characters on form submission', () => { - const errorState = { email: 'Email must have at least 3 characters.', password: '' }; - store.dispatch = jest.fn(store.dispatch); - - const loginPage = (mount(reduxWrapper())).find('LoginPage'); - - loginPage.find('input#password').simulate('change', { target: { value: 'test', name: 'password' } }); - loginPage.find('input#email').simulate('change', { target: { value: 'te', name: 'email' } }); - loginPage.find('button.btn-brand').simulate('click'); - - expect(loginPage.state('errors')).toEqual(errorState); - expect(store.dispatch).toHaveBeenCalledWith( - loginRequestFailure({ errorCode: 'invalid-form', context: errorState }), - ); - }); - - it('updates the error state for invalid email format validation on form submission', () => { - const errorState = { email: 'The email address you\'ve provided isn\'t formatted correctly.', password: '' }; - store.dispatch = jest.fn(store.dispatch); - - const loginPage = (mount(reduxWrapper())).find('LoginPage'); - - loginPage.find('input#password').simulate('change', { target: { value: 'test', name: 'password' } }); - loginPage.find('input#email').simulate('change', { target: { value: 'test@', name: 'email' } }); - loginPage.find('button.btn-brand').simulate('click'); - - expect(loginPage.state('errors')).toEqual(errorState); - }); - - it('updates the error state for invalid password', () => { - const errorState = { email: '', password: 'Please enter your password.' }; - store.dispatch = jest.fn(store.dispatch); - - const loginPage = (mount(reduxWrapper())).find('LoginPage'); - - loginPage.find('input#email').simulate('change', { target: { value: 'test@example.com', name: 'email' } }); - loginPage.find('button.btn-brand').simulate('click'); - - expect(loginPage.state('errors')).toEqual(errorState); - expect(store.dispatch).toHaveBeenCalledWith( - loginRequestFailure({ errorCode: 'invalid-form', context: errorState }), - ); - }); - - it('submits login request for valid email and password values', () => { - store.dispatch = jest.fn(store.dispatch); - const loginPage = (mount(reduxWrapper())).find('LoginPage'); - loginPage.find('input#email').simulate('change', { target: { value: 'test@example.com' } }); - loginPage.find('input#password').simulate('change', { target: { value: 'password' } }); - loginPage.find('button.btn-brand').simulate('click'); - - expect(store.dispatch).toHaveBeenCalledWith( - loginRequest({ email: 'test@example.com', password: 'password' }), - ); - }); - - it('should match url after redirection', () => { - const dasboardUrl = 'http://test.com/testing-dashboard/'; - store = mockStore({ - ...initialState, - login: { - ...initialState.login, - loginResult: { - success: true, - redirectUrl: dasboardUrl, - }, - }, - }); - delete window.location; - window.location = { href: getConfig().BASE_URL }; - renderer.create(reduxWrapper()); - expect(window.location.href).toBe(dasboardUrl); - }); - - it('should match url after TPA redirection', () => { - const authCompleteUrl = '/auth/complete/google-oauth2/'; - store = mockStore({ - ...initialState, - login: { - ...initialState.login, - loginResult: { - success: true, - redirectUrl: '', - }, - }, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - finishAuthUrl: authCompleteUrl, - }, - }, - }); - - delete window.location; - window.location = { href: getConfig().BASE_URL }; - renderer.create(reduxWrapper()); - expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl); - }); - - it('should redirect to enterprise selection page', () => { - const authCompleteUrl = '/auth/complete/google-oauth2/'; - const enterpriseSelectionPage = 'http://localhost:18000/enterprise/select/active/?success_url='.concat(authCompleteUrl); - store = mockStore({ - ...initialState, - login: { - ...initialState.login, - loginResult: { - success: true, - redirectUrl: enterpriseSelectionPage, - }, - }, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - finishAuthUrl: authCompleteUrl, - }, - }, - }); - - delete window.location; - window.location = { href: getConfig().BASE_URL }; - renderer.create(reduxWrapper()); - expect(window.location.href).toBe(enterpriseSelectionPage); - }); - - it('should redirect to social auth provider url', () => { - const loginUrl = '/auth/login/apple-id/?auth_entry=login&next=/dashboard'; - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - providers: [{ - ...appleProvider, - loginUrl, - }], - }, - }, - }); - - delete window.location; - window.location = { href: getConfig().BASE_URL }; - - const loginPage = mount(reduxWrapper()); - - loginPage.find('button#oa2-apple-id').simulate('click'); - expect(window.location.href).toBe(getConfig().LMS_BASE_URL + loginUrl); - }); - - it('should match third party auth alert', () => { - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - currentProvider: 'Apple', - platformName: 'edX', - }, - }, - }); - - const expectedMessage = 'You have successfully signed into Apple, but your Apple account does not have a ' - + 'linked edX account. To link your accounts, sign in now using your edX password.'; - - const loginPage = mount(reduxWrapper()); - expect(loginPage.find('#tpa-alert').find('span').text()).toEqual(expectedMessage); - }); - - it('should display institution login button', () => { - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - secondaryProviders: [secondaryProviders], - }, - }, - }); - const root = mount(reduxWrapper()); - expect(root.text().includes('Use my university info')).toBe(true); - }); - - it('should not display institution login button', () => { - const root = mount(reduxWrapper()); - expect(root.text().includes('Use my university info')).toBe(false); - }); - - it('should display institution login page', () => { - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - secondaryProviders: [secondaryProviders], - }, - }, - }); - const loginPage = mount(reduxWrapper()); - loginPage.find(RenderInstitutionButton).simulate('click', { institutionLogin: true }); - expect(loginPage.text().includes('Test University')).toBe(true); - }); - - it('send tracking event when create account link is clicked', () => { - const loginPage = mount(reduxWrapper()); - - loginPage.find('a[href*="/register"]').simulate('click'); - loginPage.update(); - expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.register_form.toggled', { category: 'user-engagement' }); - }); - - it('send page event when login page is rendered', () => { - mount(reduxWrapper()); - expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login'); - }); - - it('send tracking and page events when institutional button is clicked', () => { - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - secondaryProviders: [secondaryProviders], - }, - }, - }); - const loginPage = mount(reduxWrapper()); - loginPage.find(RenderInstitutionButton).simulate('click', { institutionLogin: true }); - expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' }); - expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login'); - }); - - it('check cookie rendered', () => { - const loginPage = mount(reduxWrapper()); - expect(loginPage.find()).toBeTruthy(); - }); - - it('form only be scrollable on submission', () => { - const loginPage = mount(reduxWrapper()); - - loginPage.find('input#password').simulate('change', { target: { value: 'test@example.com', name: 'password' } }); - loginPage.find('button.btn-brand').simulate('click'); - - expect(loginPage.find()).toBeTruthy(); - expect(loginPage.find('LoginPage').state('isSubmitted')).toEqual(true); - }); - - it('should render tpa button for tpa_hint id in primary provider', () => { - const expectedMessage = `Sign in using ${appleProvider.name}`; - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - providers: [appleProvider], - }, - thirdPartyAuthApiStatus: COMPLETE_STATE, - }, - }); - - delete window.location; - window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?next=/dashboard&tpa_hint=${appleProvider.id}` }; - appleProvider.iconImage = null; - - const loginPage = mount(reduxWrapper()); - expect(loginPage.find(`button#${appleProvider.id}`).find('span').text()).toEqual(expectedMessage); - }); - - it('should render regular tpa button for invalid tpa_hint value', () => { - const expectedMessage = `${appleProvider.name}`; - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - providers: [appleProvider], - }, - thirdPartyAuthApiStatus: COMPLETE_STATE, - }, - }); - - delete window.location; - window.location = { href: getConfig().BASE_URL.concat('/login'), search: '?next=/dashboard&tpa_hint=invalid' }; - appleProvider.iconImage = null; - - const loginPage = mount(reduxWrapper()); - expect(loginPage.find(`button#${appleProvider.id}`).find('span#provider-name').text()).toEqual(expectedMessage); - }); - - it('should render tpa button for tpa_hint id in secondary provider', () => { - const expectedMessage = `Sign in using ${secondaryProviders.name}`; - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - secondaryProviders: [secondaryProviders], - }, - thirdPartyAuthApiStatus: COMPLETE_STATE, - }, - }); - delete window.location; - window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` }; - secondaryProviders.iconImage = null; - - const loginPage = mount(reduxWrapper()); - expect(loginPage.find(`button#${secondaryProviders.id}`).find('span').text()).toEqual(expectedMessage); - }); - - it('should redirect to idp page if skipHinetedLogin is true', () => { - secondaryProviders.skipHintedLogin = true; - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - secondaryProviders: [secondaryProviders], - }, - thirdPartyAuthApiStatus: COMPLETE_STATE, - }, - }); - - delete window.location; - window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` }; - secondaryProviders.iconImage = null; - - mount(reduxWrapper()); - expect(window.location.href).toEqual(getConfig().LMS_BASE_URL + secondaryProviders.loginUrl); - }); - - it('should set login survey cookie', () => { - store = mockStore({ - ...initialState, - login: { - ...initialState.login, - loginResult: { - success: true, - }, - }, - }); - - renderer.create(reduxWrapper()); - expect(document.cookie).toMatch(`${getConfig().USER_SURVEY_COOKIE_NAME}=login`); - }); -}); diff --git a/src/legacy/login/tests/__snapshots__/LoginPage.test.jsx.snap b/src/legacy/login/tests/__snapshots__/LoginPage.test.jsx.snap deleted file mode 100644 index f751f33c..00000000 --- a/src/legacy/login/tests/__snapshots__/LoginPage.test.jsx.snap +++ /dev/null @@ -1,926 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LoginPage should match TPA provider snapshot 1`] = ` -
    -
    -
    -

    - First time here? - - Create an account - . - -

    -
    -

    - Sign in -

    -
    -
    - - - -
    -
    - - - -
    - -
    -
    -
    - - Sign in with your company or school - - - -
    -
    - or sign in with -
    -
    - -
    -
    -
    -
    -`; - -exports[`LoginPage should match default section snapshot 1`] = ` -
    -
    -
    -

    - First time here? - - Create an account - . - -

    -
    -

    - Sign in -

    -
    -
    - - - -
    -
    - - - -
    - -
    -
    -
    - - Sign in with your company or school - - - -
    -
    -
    -`; - -exports[`LoginPage should match forget password alert message snapshot 1`] = ` -
    -
    -
    - -

    - First time here? - - Create an account - . - -

    -
    -

    - Sign in -

    -
    -
    - - - -
    -
    - - - -
    - -
    -
    -
    - - Sign in with your company or school - - - -
    -
    -
    -`; - -exports[`LoginPage should match pending button state snapshot 1`] = ` -
    -
    -
    -

    - First time here? - - Create an account - . - -

    -
    -

    - Sign in -

    -
    -
    - - - -
    -
    - - - -
    - -
    -
    -
    - - Sign in with your company or school - - - -
    -
    -
    -`; - -exports[`LoginPage should show error message 1`] = ` -
    -
    -
    - -

    - First time here? - - Create an account - . - -

    -
    -

    - Sign in -

    -
    -
    - - - -
    -
    - - - -
    - -
    -
    -
    - - Sign in with your company or school - - - -
    -
    -
    -`; diff --git a/src/legacy/register/OptionalFields.jsx b/src/legacy/register/OptionalFields.jsx deleted file mode 100644 index be5c6006..00000000 --- a/src/legacy/register/OptionalFields.jsx +++ /dev/null @@ -1,93 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; - -import { EDUCATION_LEVELS, GENDER_OPTIONS, YEAR_OF_BIRTH_OPTIONS } from './data/constants'; -import messages from './messages'; - -import { AuthnValidationFormGroup } from '../common-components'; - -const OptionalFields = (props) => { - const { intl, onChangeHandler, values } = props; - - const getOptions = () => ({ - yearOfBirthOptions: [{ - value: '', - label: intl.formatMessage(messages['registration.year.of.birth.label']), - }].concat(YEAR_OF_BIRTH_OPTIONS), - educationLevelOptions: EDUCATION_LEVELS.map(key => ({ - value: key, - label: intl.formatMessage(messages[`registration.field.education.levels.${key || 'label'}`]), - })), - genderOptions: GENDER_OPTIONS.map(key => ({ - value: key, - label: intl.formatMessage(messages[`registration.field.gender.options.${key || 'label'}`]), - })), - }); - - return ( - <> - onChangeHandler('gender', e.target.value)} - selectOptions={getOptions().genderOptions} - inputFieldStyle="border-gray-600" - /> - onChangeHandler('yearOfBirth', e.target.value)} - selectOptions={getOptions().yearOfBirthOptions} - inputFieldStyle="border-gray-600" - /> - onChangeHandler('levelOfEducation', e.target.value)} - selectOptions={getOptions().educationLevelOptions} - inputFieldStyle="border-gray-600" - /> - onChangeHandler('goals', e.target.value)} - inputFieldStyle="border-gray-600" - /> - - ); -}; - -OptionalFields.propTypes = { - intl: intlShape.isRequired, - onChangeHandler: PropTypes.func.isRequired, - values: PropTypes.shape({ - gender: PropTypes.string, - goals: PropTypes.string, - levelOfEducation: PropTypes.string, - yearOfBirth: PropTypes.string, - }).isRequired, -}; - -export default injectIntl(OptionalFields); diff --git a/src/legacy/register/RegistrationFailure.jsx b/src/legacy/register/RegistrationFailure.jsx deleted file mode 100644 index 45edb90e..00000000 --- a/src/legacy/register/RegistrationFailure.jsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useEffect } from 'react'; -import PropTypes from 'prop-types'; - -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { Alert } from '@edx/paragon'; - -import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from './data/constants'; -import messages from './messages'; -import { DEFAULT_STATE, PENDING_STATE } from '../data/constants'; -import { windowScrollTo } from '../data/utils'; - -const RegistrationFailureMessage = (props) => { - const errorMessage = props.errors; - const { errorCode } = props.errors; - const userErrors = []; - - useEffect(() => { - if (props.isSubmitted && props.submitButtonState !== PENDING_STATE) { - windowScrollTo({ left: 0, top: 0, behavior: 'smooth' }); - } - }); - - let serverError; - switch (errorCode) { - case INTERNAL_SERVER_ERROR: - serverError = ( -
  • - {props.intl.formatMessage(messages['registration.request.server.error'])} -
  • - ); - userErrors.push(serverError); - break; - case FORBIDDEN_REQUEST: - userErrors.push( - ( -
  • - {props.intl.formatMessage(messages['register.rate.limit.reached.message'])} -
  • - ), - ); - break; - default: - Object.keys(errorMessage).forEach((key) => { - if (key !== 'error_code') { - const errors = errorMessage[key]; - const suppressionClass = ['email', 'username'].includes(key) ? 'data-hj-suppress' : ''; - const errorList = errors.map((error) => ( - (error.user_message) ? ( -
  • - {error.user_message} -
  • - ) : null - )); - userErrors.push(errorList); - } - }); - } - - return ( - !userErrors.length ? null : ( - - {props.intl.formatMessage(messages['registration.request.failure.header'])} -
      {userErrors}
    -
    - ) - ); -}; - -RegistrationFailureMessage.defaultProps = { - errors: '', - submitButtonState: DEFAULT_STATE, - isSubmitted: false, -}; - -RegistrationFailureMessage.propTypes = { - errors: PropTypes.shape({ - email: PropTypes.array, - username: PropTypes.array, - errorCode: PropTypes.string, - }), - submitButtonState: PropTypes.string, - isSubmitted: PropTypes.bool, - intl: intlShape.isRequired, -}; - -export default injectIntl(RegistrationFailureMessage); diff --git a/src/legacy/register/RegistrationPage.jsx b/src/legacy/register/RegistrationPage.jsx deleted file mode 100644 index 677c004f..00000000 --- a/src/legacy/register/RegistrationPage.jsx +++ /dev/null @@ -1,793 +0,0 @@ -import React from 'react'; - -import camelCase from 'lodash.camelcase'; -import { connect } from 'react-redux'; -import Skeleton from 'react-loading-skeleton'; -import { Helmet } from 'react-helmet'; -import PropTypes from 'prop-types'; - -import { getConfig } from '@edx/frontend-platform'; -import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; -import { - injectIntl, intlShape, getCountryList, getLocale, FormattedMessage, -} from '@edx/frontend-platform/i18n'; -import { faSpinner } from '@fortawesome/free-solid-svg-icons'; -import { Form, Hyperlink, StatefulButton } from '@edx/paragon'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; - -import { registerNewUser, fetchRealtimeValidations } from './data/actions'; -import { registrationRequestSelector } from './data/selectors'; -import messages from './messages'; -import OptionalFields from './OptionalFields'; -import RegistrationFailure from './RegistrationFailure'; - -import { - RedirectLogistration, SocialAuthProviders, ThirdPartyAuthAlert, RenderInstitutionButton, - InstitutionLogistration, AuthnValidationFormGroup, -} from '../common-components'; -import { getThirdPartyAuthContext } from '../common-components/data/actions'; -import { thirdPartyAuthContextSelector } from '../common-components/data/selectors'; -import EnterpriseSSO from '../common-components/EnterpriseSSO'; -import { - DEFAULT_STATE, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE, VALID_EMAIL_REGEX, -} from '../data/constants'; -import { - getTpaProvider, getTpaHint, updatePathWithQueryParams, getAllPossibleQueryParam, setSurveyCookie, setCookie, -} from '../data/utils'; - -class RegistrationPage extends React.Component { - constructor(props, context) { - super(props, context); - - sendPageEvent('login_and_registration', 'register'); - this.intl = props.intl; - this.queryParams = getAllPossibleQueryParam(); - this.tpaHint = getTpaHint(); - - this.state = { - email: '', - name: '', - username: '', - password: '', - country: '', - gender: '', - yearOfBirth: '', - goals: '', - levelOfEducation: '', - enableOptionalField: false, - validationAlertMessages: { - name: [{ user_message: '' }], - username: [{ user_message: '' }], - email: [{ user_message: '' }], - password: [{ user_message: '' }], - country: [{ user_message: '' }], - }, - errors: { - email: '', - name: '', - username: '', - password: '', - country: '', - }, - institutionLogin: false, - formValid: false, - startTime: Date.now(), - updateFieldErrors: false, - updateAlertErrors: false, - registrationErrorsUpdated: false, - optimizelyExperimentName: '', - totalRegistrationTime: 0, - }; - } - - componentDidMount() { - const payload = { ...this.queryParams }; - - if (this.tpaHint) { - payload.tpa_hint = this.tpaHint; - } - this.props.getThirdPartyAuthContext(payload); - this.getExperiments(); - } - - shouldComponentUpdate(nextProps) { - if (nextProps.statusCode !== 403 && this.props.validations !== nextProps.validations) { - const { errors } = this.state; - const { fieldName } = this.state; - const errorMsg = nextProps.validations.validation_decisions[fieldName]; - errors[fieldName] = errorMsg; - this.setState({ - errors, - }); - return false; - } - - if (this.props.thirdPartyAuthContext.pipelineUserDetails !== nextProps.thirdPartyAuthContext.pipelineUserDetails) { - this.setState({ - ...nextProps.thirdPartyAuthContext.pipelineUserDetails, - }); - return false; - } - - if (this.props.registrationError !== nextProps.registrationError) { - this.setState({ - formValid: false, - registrationErrorsUpdated: true, - }); - return false; - } - - if (this.state.registrationErrorsUpdated && this.props.registrationError === nextProps.registrationError) { - this.setState({ - formValid: false, - registrationErrorsUpdated: false, - }); - return false; - } - - return true; - } - - getExperiments = () => { - const { optimizelyExperimentName } = window; - - if (optimizelyExperimentName) { - this.setState({ optimizelyExperimentName }); - } - }; - - getCountryOptions = () => { - const { intl } = this.props; - return [{ - value: '', - label: intl.formatMessage(messages['registration.country.label']), - }].concat(getCountryList(getLocale()).map(({ code, name }) => ({ value: code, label: name }))); - } - - getOptionalFields() { - const values = {}; - const optionalFields = getConfig().REGISTRATION_OPTIONAL_FIELDS.split(','); - optionalFields.forEach((key) => { - values[camelCase(key)] = this.state[camelCase(key)]; - }); - - return ( - { this.setState({ [fieldName]: value }); }} - /> - ); - } - - handleInstitutionLogin = () => { - this.setState(prevState => ({ institutionLogin: !prevState.institutionLogin })); - } - - handleSubmit = (e) => { - e.preventDefault(); - const { startTime } = this.state; - const totalRegistrationTime = (Date.now() - startTime) / 1000; - let payload = { - name: this.state.name, - username: this.state.username, - email: this.state.email, - country: this.state.country, - honor_code: true, - }; - - if (this.props.thirdPartyAuthContext.currentProvider) { - payload.social_auth_provider = this.props.thirdPartyAuthContext.currentProvider; - } else { - payload.password = this.state.password; - } - - const postParams = getAllPossibleQueryParam(); - payload = { ...payload, ...postParams }; - - let finalValidation = this.state.formValid; - if (!this.state.formValid) { - Object.keys(payload).forEach(key => { - finalValidation = this.validateInput(key, payload[key], payload); - }); - } - // Since optional fields are not validated we can add it to payload after required fields - // have been validated. This will save us unwanted calls to validateInput() - const optionalFields = getConfig().REGISTRATION_OPTIONAL_FIELDS.split(','); - optionalFields.forEach((key) => { - const stateKey = camelCase(key); - if (this.state[stateKey]) { - payload[key] = this.state[stateKey]; - } - }); - if (finalValidation) { - payload.totalRegistrationTime = totalRegistrationTime; - this.setState({ - totalRegistrationTime, - }, () => { - this.props.registerNewUser(payload); - }); - } - } - - checkNoFieldErrors(validations) { - const keyValidList = Object.entries(validations).map(([key]) => !validations[key]); - return keyValidList.every((current) => current === true); - } - - checkNoAlertErrors(validations) { - const keyValidList = Object.entries(validations).map(([key]) => { - const validation = validations[key][0]; - return !validation.user_message; - }); - return keyValidList.every((current) => current === true); - } - - handleOnBlur(e) { - const payload = { - email: this.state.email, - username: this.state.username, - password: this.state.password, - name: this.state.name, - honor_code: true, - country: this.state.country, - }; - const { name, value } = e.target; - this.setState({ - updateFieldErrors: false, - updateAlertErrors: false, - fieldName: e.target.name, - }, () => { - this.validateInput(name, value, payload, false); - }); - } - - handleOnChange(e) { - if (!(e.target.name === 'username' && e.target.value.length > 30)) { - this.setState({ - [e.target.name]: e.target.value, - updateFieldErrors: false, - updateAlertErrors: false, - }); - } - } - - handleOnFocus(e) { - const { errors } = this.state; - errors[e.target.name] = ''; - this.setState({ errors }); - } - - handleOnOptional(e) { - const optionalEnable = this.state.enableOptionalField; - const targetValue = e.target.id === 'additionalFields' ? !optionalEnable : e.target.checked; - this.setState({ - enableOptionalField: targetValue, - updateAlertErrors: false, - updateFieldErrors: false, - }); - sendTrackEvent('edx.bi.user.register.optional_fields_selected', {}); - } - - handleLoginLinkClickEvent() { - sendTrackEvent('edx.bi.login_form.toggled', { category: 'user-engagement' }); - } - - validateInput(inputName, value, payload, updateAlertMessage = true) { - const { errors } = this.state; - const { intl, statusCode } = this.props; - const emailRegex = new RegExp(VALID_EMAIL_REGEX, 'i'); - - let { - formValid, - updateFieldErrors, - updateAlertErrors, - } = this.state; - switch (inputName) { - case 'email': - if (value.length < 1) { - errors.email = intl.formatMessage(messages['email.validation.message']); - } else if (value.length <= 2) { - errors.email = intl.formatMessage(messages['email.ratelimit.less.chars.validation.message']); - } else if (!emailRegex.test(value)) { - errors.email = intl.formatMessage(messages['email.ratelimit.incorrect.format.validation.message']); - } else if (payload && statusCode !== 403) { - this.props.fetchRealtimeValidations(payload); - } else { - errors.email = ''; - } - break; - case 'name': - if (value.length < 1) { - errors.name = intl.formatMessage(messages['fullname.validation.message']); - } else { - errors.name = ''; - } - break; - case 'username': - if (value.length < 1) { - errors.username = intl.formatMessage(messages['username.validation.message']); - } else if (value.length <= 1 || value.length > 30) { - errors.username = intl.formatMessage(messages['username.ratelimit.less.chars.message']); - } else if (!value.match(/^[a-zA-Z0-9_-]*$/i)) { - errors.username = intl.formatMessage(messages['username.format.validation.message']); - } else if (payload && statusCode !== 403) { - this.props.fetchRealtimeValidations(payload); - } else { - errors.username = ''; - } - break; - case 'password': - if (value.length < 1) { - errors.password = intl.formatMessage(messages['register.page.password.validation.message']); - } else if (value.length < 8) { - errors.password = intl.formatMessage(messages['email.ratelimit.password.validation.message']); - } else if (!value.match(/.*[0-9].*/i)) { - errors.password = intl.formatMessage(messages['username.number.validation.message']); - } else if (!value.match(/.*[a-zA-Z].*/i)) { - errors.password = intl.formatMessage(messages['username.character.validation.message']); - } else if (payload && statusCode !== 403) { - this.props.fetchRealtimeValidations(payload); - } else { - errors.password = ''; - } - break; - case 'country': - if (!value) { - errors.country = intl.formatMessage(messages['country.validation.message']); - } else { - errors.country = ''; - } - break; - default: - break; - } - - if (updateAlertMessage) { - updateFieldErrors = true; - updateAlertErrors = true; - formValid = this.checkNoFieldErrors(errors); - } - this.setState({ - formValid, - updateFieldErrors, - updateAlertErrors, - errors, - }); - return formValid; - } - - updateFieldErrors(registrationError) { - const { - errors, - } = this.state; - Object.entries(registrationError).map(([key]) => { - if (registrationError[key]) { - errors[key] = registrationError[key][0].user_message; - } - return errors; - }); - } - - updateValidationAlertMessages() { - const { - errors, - validationAlertMessages, - } = this.state; - Object.entries(errors).map(([key, value]) => { - if (validationAlertMessages[key]) { - validationAlertMessages[key][0].user_message = value; - } - return validationAlertMessages; - }); - } - - renderErrors() { - let errorsObject = null; - let { registrationErrorsUpdated } = this.state; - const { - updateAlertErrors, - updateFieldErrors, - validationAlertMessages, - } = this.state; - const { registrationError, submitState } = this.props; - if (registrationError && registrationErrorsUpdated) { - if (updateFieldErrors && submitState !== PENDING_STATE) { - this.updateFieldErrors(registrationError); - } - registrationErrorsUpdated = false; - errorsObject = registrationError; - } else { - if (updateAlertErrors && submitState !== PENDING_STATE) { - this.updateValidationAlertMessages(); - } - errorsObject = !this.checkNoAlertErrors(validationAlertMessages) ? validationAlertMessages : {}; - } - return ( - - ); - } - - renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl) { - let thirdPartyComponent = null; - if ((providers.length || secondaryProviders.length) && !currentProvider) { - thirdPartyComponent = ( - <> - -
    - -
    - - ); - } else if (thirdPartyAuthApiStatus === PENDING_STATE) { - thirdPartyComponent = ; - } - return thirdPartyComponent; - } - - renderForm(currentProvider, - providers, - secondaryProviders, - thirdPartyAuthApiStatus, - finishAuthUrl, - submitState, - intl) { - if (this.state.institutionLogin) { - return ( - - ); - } - - if (this.props.registrationResult.success) { - setSurveyCookie('register'); - setCookie(getConfig().REGISTER_CONVERSION_COOKIE_NAME, true); - - // Fire optimizely events - window.optimizely = window.optimizely || []; - window.optimizely.push({ - type: 'event', - eventName: 'authn-register-conversion', - tags: { - value: this.state.totalRegistrationTime, - }, - }); - - if (this.state.optimizelyExperimentName !== 'progressiveProfilingConcept1') { - window.optimizely.push({ - type: 'event', - eventName: 'van_504_conversion_rate', - }); - ['yearOfBirth', 'gender', 'levelOfEducation'].forEach(fieldName => { - if (this.state[fieldName]) { - window.optimizely.push({ - type: 'event', - eventName: `van_504_${fieldName}`, - }); - } - }); - } - } - - return ( - <> - - {intl.formatMessage(messages['register.page.title'], - { siteName: getConfig().SITE_NAME })} - - - -
    -
    -
    - {this.renderErrors()} - {currentProvider && ( - - )} -

    - {intl.formatMessage(messages['already.have.an.edx.account'])} - - {intl.formatMessage(messages['sign.in.hyperlink'])} - -

    -
    -

    {intl.formatMessage(messages['create.a.new.account'])}

    -
    - this.handleOnBlur(e)} - onChange={(e) => this.handleOnChange(e)} - onFocus={(e) => this.handleOnFocus(e)} - helpText={intl.formatMessage(messages['helptext.name'])} - inputFieldStyle="border-gray-600" - /> - this.handleOnBlur(e)} - onChange={(e) => this.handleOnChange(e)} - onFocus={(e) => this.handleOnFocus(e)} - helpText={intl.formatMessage(messages['helptext.username'])} - inputFieldStyle="border-gray-600" - /> - this.handleOnBlur(e)} - onChange={(e) => this.handleOnChange(e)} - onFocus={(e) => this.handleOnFocus(e)} - helpText={intl.formatMessage(messages['helptext.email'])} - inputFieldStyle="border-gray-600" - /> - {!currentProvider && ( - this.handleOnBlur(e)} - onChange={(e) => this.handleOnChange(e)} - onFocus={(e) => this.handleOnFocus(e)} - helpText={intl.formatMessage(messages['helptext.password'])} - inputFieldStyle="border-gray-600" - /> - )} - this.handleOnBlur(e)} - onChange={(e) => this.handleOnChange(e)} - onFocus={(e) => this.handleOnFocus(e)} - selectOptions={this.getCountryOptions()} - inputFieldStyle="border-gray-600 " - /> -
    - - {intl.formatMessage(messages['terms.of.service.and.honor.code'])} - - ), - privacyPolicy: ( - - {intl.formatMessage(messages['privacy.policy'])} - - ), - }} - /> -
    - {getConfig().REGISTRATION_OPTIONAL_FIELDS && localStorage.getItem('DESIGN_NAME') !== 'redesign' ? ( - this.handleOnOptional(e)} - onBlur={null} - onChange={(e) => this.handleOnOptional(e)} - optionalFieldCheckbox - isChecked={this.state.enableOptionalField} - checkboxMessage={intl.formatMessage(messages['support.education.research'])} - /> - ) : null} - { this.state.enableOptionalField ? this.getOptionalFields() : null } - }} - onClick={this.handleSubmit} - onMouseDown={(e) => e.preventDefault()} - /> - {(providers.length || secondaryProviders.length || thirdPartyAuthApiStatus === PENDING_STATE) - && !currentProvider ? ( -
    -
    - - {intl.formatMessage(messages['create.an.account.using'])} - -
    - ) : null} - {this.renderThirdPartyAuth(providers, - secondaryProviders, - currentProvider, - thirdPartyAuthApiStatus, - intl)} - -
    -
    -
    - - ); - } - - render() { - const { intl, submitState, thirdPartyAuthApiStatus } = this.props; - const { - currentProvider, finishAuthUrl, providers, secondaryProviders, - } = this.props.thirdPartyAuthContext; - - if (this.tpaHint) { - if (thirdPartyAuthApiStatus === PENDING_STATE) { - return ; - } - const { provider, skipHintedLogin } = getTpaProvider(this.tpaHint, providers, secondaryProviders); - if (skipHintedLogin) { - window.location.href = getConfig().LMS_BASE_URL + provider.registerUrl; - return null; - } - return provider ? () - : this.renderForm( - currentProvider, - providers, - secondaryProviders, - thirdPartyAuthApiStatus, - finishAuthUrl, - submitState, - intl, - ); - } - return this.renderForm( - currentProvider, - providers, - secondaryProviders, - thirdPartyAuthApiStatus, - finishAuthUrl, - submitState, - intl, - ); - } -} - -RegistrationPage.defaultProps = { - registrationResult: null, - registerNewUser: null, - registrationError: null, - submitState: DEFAULT_STATE, - thirdPartyAuthApiStatus: 'pending', - thirdPartyAuthContext: { - currentProvider: null, - finishAuthUrl: null, - providers: [], - secondaryProviders: [], - pipelineUserDetails: null, - }, - validations: null, - statusCode: null, -}; - -RegistrationPage.propTypes = { - intl: intlShape.isRequired, - getThirdPartyAuthContext: PropTypes.func.isRequired, - registerNewUser: PropTypes.func, - registrationResult: PropTypes.shape({ - redirectUrl: PropTypes.string, - success: PropTypes.bool, - }), - registrationError: PropTypes.shape({ - email: PropTypes.array, - username: PropTypes.array, - country: PropTypes.array, - password: PropTypes.array, - name: PropTypes.array, - }), - submitState: PropTypes.string, - thirdPartyAuthApiStatus: PropTypes.string, - thirdPartyAuthContext: PropTypes.shape({ - currentProvider: PropTypes.string, - platformName: PropTypes.string, - providers: PropTypes.array, - secondaryProviders: PropTypes.array, - finishAuthUrl: PropTypes.string, - pipelineUserDetails: PropTypes.shape({ - email: PropTypes.string, - fullname: PropTypes.string, - firstName: PropTypes.string, - lastName: PropTypes.string, - username: PropTypes.string, - }), - }), - fetchRealtimeValidations: PropTypes.func.isRequired, - validations: PropTypes.shape({ - validation_decisions: PropTypes.shape({ - country: PropTypes.string, - email: PropTypes.string, - name: PropTypes.string, - password: PropTypes.string, - username: PropTypes.string, - }), - }), - statusCode: PropTypes.number, -}; - -const mapStateToProps = state => { - const registrationResult = registrationRequestSelector(state); - const thirdPartyAuthContext = thirdPartyAuthContextSelector(state); - return { - registrationError: state.register.registrationError, - submitState: state.register.submitState, - thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus, - registrationResult, - thirdPartyAuthContext, - validations: state.register.validations, - statusCode: state.register.statusCode, - }; -}; - -export default connect( - mapStateToProps, - { - getThirdPartyAuthContext, - fetchRealtimeValidations, - registerNewUser, - }, -)(injectIntl(RegistrationPage)); diff --git a/src/legacy/register/data/actions.js b/src/legacy/register/data/actions.js deleted file mode 100644 index 71556a43..00000000 --- a/src/legacy/register/data/actions.js +++ /dev/null @@ -1,45 +0,0 @@ -import { AsyncActionType } from '../../data/utils'; - -export const REGISTER_NEW_USER = new AsyncActionType('REGISTRATION', 'REGISTER_NEW_USER'); -export const REGISTER_FORM_VALIDATIONS = new AsyncActionType('REGISTRATION', 'GET_FORM_VALIDATIONS'); - -// Register -export const registerNewUser = registrationInfo => ({ - type: REGISTER_NEW_USER.BASE, - payload: { registrationInfo }, -}); - -export const registerNewUserBegin = () => ({ - type: REGISTER_NEW_USER.BEGIN, -}); - -export const registerNewUserSuccess = (redirectUrl, success) => ({ - type: REGISTER_NEW_USER.SUCCESS, - payload: { redirectUrl, success }, - -}); - -export const registerNewUserFailure = (error) => ({ - type: REGISTER_NEW_USER.FAILURE, - payload: { error }, -}); - -// Realtime Field validations -export const fetchRealtimeValidations = (formPayload) => ({ - type: REGISTER_FORM_VALIDATIONS.BASE, - payload: { formPayload }, -}); - -export const fetchRealtimeValidationsBegin = () => ({ - type: REGISTER_FORM_VALIDATIONS.BEGIN, -}); - -export const fetchRealtimeValidationsSuccess = (validations) => ({ - type: REGISTER_FORM_VALIDATIONS.SUCCESS, - payload: { validations }, -}); - -export const fetchRealtimeValidationsFailure = (error, statusCode) => ({ - type: REGISTER_FORM_VALIDATIONS.FAILURE, - payload: { error, statusCode }, -}); diff --git a/src/legacy/register/data/constants.js b/src/legacy/register/data/constants.js deleted file mode 100644 index f6d923c7..00000000 --- a/src/legacy/register/data/constants.js +++ /dev/null @@ -1,30 +0,0 @@ -// Registration Error Codes -export const INTERNAL_SERVER_ERROR = 'internal-server-error'; -export const FORBIDDEN_REQUEST = 'forbidden-request'; - -export const YEAR_OF_BIRTH_OPTIONS = (() => { - const currentYear = new Date().getFullYear(); - const years = []; - let startYear = currentYear - 120; - while (startYear < currentYear) { - startYear += 1; - - years.push({ value: startYear.toString(), label: startYear }); - } - return years.reverse(); -})(); - -export const EDUCATION_LEVELS = [ - '', - 'p', - 'm', - 'b', - 'a', - 'hs', - 'jhs', - 'el', - 'none', - 'other', -]; - -export const GENDER_OPTIONS = ['', 'f', 'm', 'o']; diff --git a/src/legacy/register/data/reducers.js b/src/legacy/register/data/reducers.js deleted file mode 100644 index 42d926be..00000000 --- a/src/legacy/register/data/reducers.js +++ /dev/null @@ -1,51 +0,0 @@ -import { REGISTER_NEW_USER, REGISTER_FORM_VALIDATIONS } from './actions'; - -import { DEFAULT_STATE, PENDING_STATE } from '../../data/constants'; - -export const defaultState = { - registrationError: null, - registrationResult: {}, - formData: null, - validations: null, - statusCode: null, -}; - -const reducer = (state = defaultState, action) => { - switch (action.type) { - case REGISTER_NEW_USER.BEGIN: - return { - ...state, - submitState: PENDING_STATE, - }; - case REGISTER_NEW_USER.SUCCESS: - return { - ...state, - registrationResult: action.payload, - }; - case REGISTER_NEW_USER.FAILURE: - return { - ...state, - registrationError: action.payload.error, - submitState: DEFAULT_STATE, - }; - case REGISTER_FORM_VALIDATIONS.BEGIN: - return { - ...state, - }; - case REGISTER_FORM_VALIDATIONS.SUCCESS: - return { - ...state, - validations: action.payload.validations, - }; - case REGISTER_FORM_VALIDATIONS.FAILURE: - return { - ...state, - validations: action.payload.error, - statusCode: action.payload.statusCode, - }; - default: - return state; - } -}; - -export default reducer; diff --git a/src/legacy/register/data/sagas.js b/src/legacy/register/data/sagas.js deleted file mode 100644 index 271e4c3a..00000000 --- a/src/legacy/register/data/sagas.js +++ /dev/null @@ -1,69 +0,0 @@ -import { call, put, takeEvery } from 'redux-saga/effects'; - -import { camelCaseObject } from '@edx/frontend-platform'; -import { logError, logInfo } from '@edx/frontend-platform/logging'; - -// Actions -import { - REGISTER_NEW_USER, - registerNewUserBegin, - registerNewUserFailure, - registerNewUserSuccess, - REGISTER_FORM_VALIDATIONS, - fetchRealtimeValidationsBegin, - fetchRealtimeValidationsSuccess, - fetchRealtimeValidationsFailure, -} from './actions'; -import { INTERNAL_SERVER_ERROR } from './constants'; - -// Services -import { getFieldsValidations, registerRequest } from './service'; - -export function* handleNewUserRegistration(action) { - try { - yield put(registerNewUserBegin()); - - const { redirectUrl, success } = yield call(registerRequest, action.payload.registrationInfo); - - yield put(registerNewUserSuccess( - redirectUrl, - success, - )); - } catch (e) { - const statusCodes = [400, 409]; - if (e.response && statusCodes.includes(e.response.status)) { - yield put(registerNewUserFailure(e.response.data)); - logInfo(e); - } else if (e.response.status === 403) { - yield put(registerNewUserFailure(camelCaseObject(e.response.data))); - logInfo(e); - } else { - yield put(registerNewUserFailure({ errorCode: INTERNAL_SERVER_ERROR })); - logError(e); - } - } -} - -export function* fetchRealtimeValidations(action) { - try { - yield put(fetchRealtimeValidationsBegin()); - const { fieldValidations } = yield call(getFieldsValidations, action.payload.formPayload); - - yield put(fetchRealtimeValidationsSuccess( - fieldValidations, - )); - } catch (e) { - const statusCodes = [403]; - if (e.response && statusCodes.includes(e.response.status)) { - yield put(fetchRealtimeValidationsFailure(e.response.data, e.response.status)); - logInfo(e); - } else { - logError(e); - } - } -} - -export default function* saga() { - yield takeEvery(REGISTER_NEW_USER.BASE, handleNewUserRegistration); - yield takeEvery(REGISTER_FORM_VALIDATIONS.BASE, fetchRealtimeValidations); -} diff --git a/src/legacy/register/data/selectors.js b/src/legacy/register/data/selectors.js deleted file mode 100644 index 1935831b..00000000 --- a/src/legacy/register/data/selectors.js +++ /dev/null @@ -1,10 +0,0 @@ -import { createSelector } from 'reselect'; - -export const storeName = 'register'; - -export const registerSelector = state => ({ ...state[storeName] }); - -export const registrationRequestSelector = createSelector( - registerSelector, - register => register.registrationResult, -); diff --git a/src/legacy/register/data/service.js b/src/legacy/register/data/service.js deleted file mode 100644 index 2e638c59..00000000 --- a/src/legacy/register/data/service.js +++ /dev/null @@ -1,45 +0,0 @@ -import { getConfig } from '@edx/frontend-platform'; -import { getHttpClient, getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import querystring from 'querystring'; - -export async function registerRequest(registrationInformation) { - const requestConfig = { - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - isPublic: true, - }; - - const { data } = await getAuthenticatedHttpClient() - .post( - `${getConfig().LMS_BASE_URL}/user_api/v2/account/registration/`, - querystring.stringify(registrationInformation), - requestConfig, - ) - .catch((e) => { - throw (e); - }); - - return { - redirectUrl: data.redirect_url || `${getConfig().LMS_BASE_URL}/dashboard`, - success: data.success || false, - }; -} - -export async function getFieldsValidations(formPayload) { - const requestConfig = { - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - }; - - const { data } = await getHttpClient() - .post( - `${getConfig().LMS_BASE_URL}/api/user/v1/validation/registration`, - querystring.stringify(formPayload), - requestConfig, - ) - .catch((e) => { - throw (e); - }); - - return { - fieldValidations: data, - }; -} diff --git a/src/legacy/register/data/tests/sagas.test.js b/src/legacy/register/data/tests/sagas.test.js deleted file mode 100644 index 0680950f..00000000 --- a/src/legacy/register/data/tests/sagas.test.js +++ /dev/null @@ -1,234 +0,0 @@ -import { runSaga } from 'redux-saga'; - -import { camelCaseObject } from '@edx/frontend-platform'; -import * as actions from '../actions'; -import { - fetchRealtimeValidations, - handleNewUserRegistration, -} from '../sagas'; -import * as api from '../service'; -import initializeMockLogging from '../../../../setupTest'; -import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from '../constants'; - -const { loggingService } = initializeMockLogging(); - -describe('fetchRealtimeValidations', () => { - const params = { - payload: { - formData: { - email: 'test@test.com', - username: '', - password: 'test-password', - name: 'test-name', - honor_code: true, - country: 'test-country', - }, - }, - }; - - beforeEach(() => { - loggingService.logInfo.mockReset(); - }); - - const data = { - validation_decisions: { - username: 'Username must be between 2 and 30 characters long.', - }, - }; - - it('should call service and dispatch success action', async () => { - const getFieldsValidations = jest.spyOn(api, 'getFieldsValidations') - .mockImplementation(() => Promise.resolve({ fieldValidations: data })); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - fetchRealtimeValidations, - params, - ); - - expect(getFieldsValidations).toHaveBeenCalledTimes(1); - expect(dispatched).toEqual([ - actions.fetchRealtimeValidationsBegin(), - actions.fetchRealtimeValidationsSuccess(data), - ]); - getFieldsValidations.mockClear(); - }); - - it('should call service and dispatch error action', async () => { - const validationRatelimitResponse = { - response: { - status: 403, - data: { - detail: 'You do not have permission to perform this action.', - }, - }, - }; - const getFieldsValidations = jest.spyOn(api, 'getFieldsValidations') - .mockImplementation(() => Promise.reject(validationRatelimitResponse)); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - fetchRealtimeValidations, - params, - ); - - expect(getFieldsValidations).toHaveBeenCalledTimes(1); - expect(loggingService.logInfo).toHaveBeenCalled(); - expect(dispatched).toEqual([ - actions.fetchRealtimeValidationsBegin(), - actions.fetchRealtimeValidationsFailure( - validationRatelimitResponse.response.data, - validationRatelimitResponse.response.status, - ), - ]); - getFieldsValidations.mockClear(); - }); - - it('should call logError on 500 server error', async () => { - const validationRatelimitResponse = { - response: { - status: 500, - data: {}, - }, - }; - const getFieldsValidations = jest.spyOn(api, 'getFieldsValidations') - .mockImplementation(() => Promise.reject(validationRatelimitResponse)); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - fetchRealtimeValidations, - params, - ); - - expect(getFieldsValidations).toHaveBeenCalledTimes(1); - expect(loggingService.logError).toHaveBeenCalled(); - getFieldsValidations.mockClear(); - }); -}); - -describe('handleNewUserRegistration', () => { - const params = { - payload: { - formData: { - email: 'test@test.com', - username: 'test-username', - password: 'test-password', - name: 'test-name', - honor_code: true, - country: 'test-country', - }, - }, - }; - - beforeEach(() => { - loggingService.logError.mockReset(); - loggingService.logInfo.mockReset(); - }); - - it('should call service and dispatch success action', async () => { - const data = { redirectUrl: '/dashboard', success: true }; - const registerRequest = jest.spyOn(api, 'registerRequest') - .mockImplementation(() => Promise.resolve(data)); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - handleNewUserRegistration, - params, - ); - - expect(registerRequest).toHaveBeenCalledTimes(1); - expect(dispatched).toEqual([ - actions.registerNewUserBegin(), - actions.registerNewUserSuccess(data.redirectUrl, data.success), - ]); - registerRequest.mockClear(); - }); - - it('should handle 500 error code', async () => { - const registerErrorResponse = { - response: { - status: 500, - data: { - errorCode: INTERNAL_SERVER_ERROR, - }, - }, - }; - - const registerRequest = jest.spyOn(api, 'registerRequest').mockImplementation(() => Promise.reject(registerErrorResponse)); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - handleNewUserRegistration, - params, - ); - - expect(loggingService.logError).toHaveBeenCalled(); - expect(dispatched).toEqual([ - actions.registerNewUserBegin(), - actions.registerNewUserFailure(camelCaseObject(registerErrorResponse.response.data)), - ]); - registerRequest.mockClear(); - }); - - it('should call service and dispatch error action', async () => { - const registerErrorResponse = { - response: { - status: 400, - data: { - error: 'something went wrong', - }, - }, - }; - const registerRequest = jest.spyOn(api, 'registerRequest') - .mockImplementation(() => Promise.reject(registerErrorResponse)); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - handleNewUserRegistration, - params, - ); - - expect(registerRequest).toHaveBeenCalledTimes(1); - expect(loggingService.logInfo).toHaveBeenCalled(); - expect(dispatched).toEqual([ - actions.registerNewUserBegin(), - actions.registerNewUserFailure(registerErrorResponse.response.data), - ]); - registerRequest.mockClear(); - }); - - it('should handle rate limit error code', async () => { - const registerErrorResponse = { - response: { - status: 403, - data: { - errorCode: FORBIDDEN_REQUEST, - }, - }, - }; - - const registerRequest = jest.spyOn(api, 'registerRequest') - .mockImplementation(() => Promise.reject(registerErrorResponse)); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - handleNewUserRegistration, - params, - ); - - expect(registerRequest).toHaveBeenCalledTimes(1); - expect(loggingService.logInfo).toHaveBeenCalled(); - expect(dispatched).toEqual([ - actions.registerNewUserBegin(), - actions.registerNewUserFailure(registerErrorResponse.response.data), - ]); - registerRequest.mockClear(); - }); -}); diff --git a/src/legacy/register/messages.jsx b/src/legacy/register/messages.jsx deleted file mode 100644 index 25156129..00000000 --- a/src/legacy/register/messages.jsx +++ /dev/null @@ -1,268 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - 'register.page.title': { - id: 'register.page.title', - defaultMessage: 'Register | {siteName}', - description: 'register page title', - }, - 'create.account.button': { - id: 'create.account.button', - defaultMessage: 'Create account', - description: 'Button label that appears on register page', - }, - 'already.have.an.edx.account': { - id: 'already.have.an.edx.account', - defaultMessage: 'Already have an edX account?', - description: 'A message on registration page asking the user if he already has an edX account', - }, - 'sign.in.hyperlink': { - id: 'sign.in.hyperlink', - defaultMessage: 'Sign in.', - description: 'Text for the hyperlink that takes user to login page', - }, - 'create.an.account.using': { - id: 'create.an.account.using', - defaultMessage: 'or create an account using', - description: 'A message that after optional form fields checkbox', - }, - 'create.a.new.account': { - id: 'create.a.new.account', - defaultMessage: 'Create a new account', - description: 'Text that appears before social auth buttons and before the registration form', - }, - 'register.institution.login.button': { - id: 'register.institution.login.button', - defaultMessage: 'Use my institution/campus credentials', - description: 'shows institutions list', - }, - 'register.institution.login.page.title': { - id: 'register.institution.login.page.title', - defaultMessage: 'Register with institution/campus credentials', - description: 'Heading of institution page', - }, - 'create.an.account': { - id: 'create.an.account', - defaultMessage: 'Create an account', - description: 'Message on button to return to register page', - }, - 'register.page.email.label': { - id: 'register.page.email.label', - defaultMessage: 'Email (required)', - description: 'Label that appears above email field on register page', - }, - 'register.rate.limit.reached.message': { - id: 'register.rate.limit.reached.message', - defaultMessage: 'Too many failed registration attempts. Try again later.', - description: 'Error message that appears when an anonymous user has made too many failed registration attempts', - }, - 'email.validation.message': { - id: 'email.validation.message', - defaultMessage: 'Please enter your email.', - description: 'Validation message that appears when email address is empty', - }, - 'email.ratelimit.less.chars.validation.message': { - id: 'email.ratelimit.less.chars.validation.message', - defaultMessage: 'Email must have 3 characters.', - description: 'Validation message that appears when email address is less than 3 characters', - }, - 'email.ratelimit.incorrect.format.validation.message': { - id: 'email.ratelimit.incorrect.format.validation.message', - defaultMessage: 'The email address you provided isn\'t formatted correctly.', - description: 'Validation message that appears when email address is not formatted correctly with no backend validations available.', - }, - 'email.ratelimit.password.validation.message': { - id: 'email.ratelimit.password.validation.message', - defaultMessage: 'Your password must contain at least 8 characters', - description: 'Validation message that appears when password is not formatted correctly with no backend validations available.', - }, - 'password.label': { - id: 'password.label', - defaultMessage: 'Password (required)', - description: 'Label that appears above password field', - }, - 'register.page.password.validation.message': { - id: 'register.page.password.validation.message', - defaultMessage: 'Please enter your password.', - description: 'Validation message that appears when password is non compliant with edX requirement', - }, - 'fullname.label': { - id: 'fullname.label', - defaultMessage: 'Full name (required)', - description: 'Label that appears above fullname field', - }, - 'fullname.validation.message': { - id: 'fullname.validation.message', - defaultMessage: 'Please enter your full name.', - description: 'Validation message that appears when fullname is empty', - }, - 'username.label': { - id: 'username.label', - defaultMessage: 'Public username (required)', - description: 'Label that appears above username field', - }, - 'username.validation.message': { - id: 'username.validation.message', - defaultMessage: 'Please enter your public username.', - description: 'Validation message that appears when username is invalid', - }, - 'username.format.validation.message': { - id: 'username.format.validation.message', - defaultMessage: 'Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-).', - description: 'Validation message that appears when username format is invalid', - }, - 'username.character.validation.message': { - id: 'username.character.validation.message', - defaultMessage: 'Your password must contain at least 1 letter.', - description: 'Validation message that appears when password does not contain letter', - }, - 'username.number.validation.message': { - id: 'username.number.validation.message', - defaultMessage: 'Your password must contain at least 1 number.', - description: 'Validation message that appears when password does not contain number', - }, - 'username.ratelimit.less.chars.message': { - id: 'username.ratelimit.less.chars.message', - defaultMessage: 'Public username must have atleast 2 characters.', - description: 'Validation message that appears when username is less than 2 characters and with no backend validations available.', - }, - 'country.validation.message': { - id: 'country.validation.message', - defaultMessage: 'Select your country or region of residence.', - description: 'Validation message that appears when country is not selected', - }, - 'support.education.research': { - id: 'support.education.research', - defaultMessage: 'Support education research by providing additional information. (Optional)', - description: 'Text for a checkbox to ask user for if they are willing to provide extra information for education research', - }, - 'registration.request.server.error': { - id: 'registration.request.server.error', - defaultMessage: 'An error has occurred. Try refreshing the page, or check your internet connection.', - description: 'error message on server error.', - }, - 'registration.request.failure.header': { - id: 'registration.request.failure.header', - defaultMessage: 'We couldn\'t create your account.', - description: 'error message when registration failure.', - }, - 'helptext.name': { - id: 'helptext.name', - defaultMessage: 'This name will be used by any certificates that you earn.', - description: 'help text for name field on registration field', - }, - 'helptext.username': { - id: 'helptext.username', - defaultMessage: 'The name that will identify you in your courses. It cannot be changed later.', - description: 'helptext for username field on registration page', - }, - 'helptext.password': { - id: 'helptext.password', - defaultMessage: 'Your password must contain at least 8 characters, including 1 letter & 1 number.', - description: 'help text for password field on registration page', - }, - 'helptext.email': { - id: 'helptext.email', - defaultMessage: 'This is what you will use to login.', - description: 'help text for email field on registration page', - }, - // Terms of Service and Honor Code - 'terms.of.service.and.honor.code': { - id: 'terms.of.service.and.honor.code', - defaultMessage: 'Terms of Service and Honor Code', - description: 'Text for the hyperlink that redirects user to terms of service and honor code', - }, - 'privacy.policy': { - id: 'privacy.policy', - defaultMessage: 'Privacy Policy', - description: 'Text for the hyperlink that redirects user to privacy policy', - }, - // Registration Fields - 'registration.year.of.birth.label': { - id: 'registration.year.of.birth.label', - defaultMessage: 'Year of birth (optional)', - description: 'Placeholder for the year of birth options dropdown', - }, - 'registration.country.label': { - id: 'registration.country.label', - defaultMessage: 'Country or region of residence (required)', - description: 'Placeholder for the country options dropdown.', - }, - 'registration.field.gender.options.label': { - id: 'registration.field.gender.options.label', - defaultMessage: 'Gender (optional)', - description: 'Placeholder for the gender options dropdown', - }, - 'registration.goals.label': { - id: 'registration.goals.label', - defaultMessage: 'Tell us why you\'re interested in edX (optional)', - description: 'Placeholder for the goals options dropdown', - }, - 'registration.field.gender.options.f': { - id: 'registration.field.gender.options.f', - defaultMessage: 'Female', - description: 'The label for the female gender option.', - }, - 'registration.field.gender.options.m': { - id: 'registration.field.gender.options.m', - defaultMessage: 'Male', - description: 'The label for the male gender option.', - }, - 'registration.field.gender.options.o': { - id: 'registration.field.gender.options.o', - defaultMessage: 'Other/Prefer not to say', - description: 'The label for catch-all gender option.', - }, - 'registration.field.education.levels.label': { - id: 'registration.field.education.levels.label', - defaultMessage: 'Highest level of education completed (optional)', - description: 'Placeholder for the education levels dropdown.', - }, - 'registration.field.education.levels.p': { - id: 'registration.field.education.levels.p', - defaultMessage: 'Doctorate', - description: 'Selected by the user if their highest level of education is a doctorate degree.', - }, - 'registration.field.education.levels.m': { - id: 'registration.field.education.levels.m', - defaultMessage: "Master's or professional degree", - description: "Selected by the user if their highest level of education is a master's or professional degree from a college or university.", - }, - 'registration.field.education.levels.b': { - id: 'registration.field.education.levels.b', - defaultMessage: "Bachelor's degree", - description: "Selected by the user if their highest level of education is a four year college or university bachelor's degree.", - }, - 'registration.field.education.levels.a': { - id: 'registration.field.education.levels.a', - defaultMessage: "Associate's degree", - description: "Selected by the user if their highest level of education is an associate's degree. 1-2 years of college or university.", - }, - 'registration.field.education.levels.hs': { - id: 'registration.field.education.levels.hs', - defaultMessage: 'Secondary/high school', - description: 'Selected by the user if their highest level of education is secondary or high school. 9-12 years of education.', - }, - 'registration.field.education.levels.jhs': { - id: 'registration.field.education.levels.jhs', - defaultMessage: 'Junior secondary/junior high/middle school', - description: 'Selected by the user if their highest level of education is junior or middle school. 6-8 years of education.', - }, - 'registration.field.education.levels.el': { - id: 'registration.field.education.levels.el', - defaultMessage: 'Elementary/primary school', - description: 'Selected by the user if their highest level of education is elementary or primary school. 1-5 years of education.', - }, - 'registration.field.education.levels.none': { - id: 'registration.field.education.levels.none', - defaultMessage: 'No formal education', - description: 'Selected by the user to describe their education.', - }, - 'registration.field.education.levels.other': { - id: 'registration.field.education.levels.other', - defaultMessage: 'Other education', - description: 'Selected by the user if they have a type of education not described by the other choices.', - }, -}); - -export default messages; diff --git a/src/legacy/register/tests/RegistrationPage.test.jsx b/src/legacy/register/tests/RegistrationPage.test.jsx deleted file mode 100644 index 5875f67d..00000000 --- a/src/legacy/register/tests/RegistrationPage.test.jsx +++ /dev/null @@ -1,781 +0,0 @@ -import React from 'react'; -import { Provider } from 'react-redux'; -import renderer from 'react-test-renderer'; -import { mount } from 'enzyme'; -import configureStore from 'redux-mock-store'; -import { getConfig, mergeConfig } from '@edx/frontend-platform'; -import { IntlProvider, injectIntl, configure } from '@edx/frontend-platform/i18n'; -import * as analytics from '@edx/frontend-platform/analytics'; -import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner'; - -import RegistrationPage from '../RegistrationPage'; -import { RenderInstitutionButton } from '../../common-components'; -import RegistrationFailureMessage from '../RegistrationFailure'; -import { COMPLETE_STATE, PENDING_STATE } from '../../data/constants'; -import { fetchRealtimeValidations, registerNewUser } from '../data/actions'; -import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from '../data/constants'; - -jest.mock('@edx/frontend-platform/analytics'); - -analytics.sendTrackEvent = jest.fn(); -analytics.sendPageEvent = jest.fn(); - -const IntlRegistrationPage = injectIntl(RegistrationPage); -const IntlRegistrationFailure = injectIntl(RegistrationFailureMessage); -const mockStore = configureStore(); - -describe('RegistrationPageTests', () => { - mergeConfig({ - PRIVACY_POLICY: 'http://privacy-policy.com', - REGISTRATION_OPTIONAL_FIELDS: 'gender,goals,level_of_education,year_of_birth', - TOS_AND_HONOR_CODE: 'http://tos-and-honot-code.com', - USER_SURVEY_COOKIE_NAME: process.env.USER_SURVEY_COOKIE_NAME, - REGISTER_CONVERSION_COOKIE_NAME: process.env.REGISTER_CONVERSION_COOKIE_NAME, - }); - - const initialState = { - register: { - registrationResult: { success: false, redirectUrl: '' }, - registrationError: null, - }, - commonComponents: { - thirdPartyAuthApiStatus: null, - thirdPartyAuthContext: { - platformName: 'openedX', - currentProvider: null, - finishAuthUrl: null, - providers: [], - secondaryProviders: [], - pipelineUserDetails: null, - }, - }, - }; - - let props = {}; - let store = {}; - - const appleProvider = { - id: 'oa2-apple-id', - name: 'Apple', - iconClass: null, - iconImage: 'https://edx.devstack.lms/logo.png', - loginUrl: '/auth/login/apple-id/?auth_entry=login&next=/dashboard', - }; - - const secondaryProviders = { - id: 'saml-test', - name: 'Test University', - loginUrl: '/dummy-auth', - registerUrl: '/dummy_auth', - skipHintedLogin: false, - }; - - const emptyFieldValidation = { - name: 'Please enter your full name.', - username: 'Please enter your public username.', - email: 'Please enter your email.', - password: 'Please enter your password.', - country: 'Select your country or region of residence.', - }; - - const reduxWrapper = children => ( - - {children} - - ); - - const submitForm = (payload, submitOptionalFields = true, isThirdPartyAuth = false) => { - const registerPage = mount(reduxWrapper()); - - registerPage.find('input#name').simulate('change', { target: { value: payload.name, name: 'name' } }); - registerPage.find('input#username').simulate('change', { target: { value: payload.username, name: 'username' } }); - registerPage.find('input#email').simulate('change', { target: { value: payload.email, name: 'email' } }); - registerPage.find('select#country').simulate('change', { target: { value: payload.country, name: 'country' } }); - - if (!isThirdPartyAuth) { - registerPage.find('input#password').simulate('change', { target: { value: payload.password, name: 'password' } }); - } - - // Send optional field - if (submitOptionalFields) { - registerPage.find('input#optional').simulate('change', { target: { checked: true } }); - registerPage.find('select#gender').simulate('change', { target: { value: payload.gender || null, name: 'gender' } }); - registerPage.find('select#yearOfBirth').simulate('change', { target: { values: payload.yearOfBirth || null, name: 'yearOfBirth' } }); - registerPage.find('select#levelOfEducation').simulate('change', { target: { values: payload.levelOfEducation || null, name: 'levelOfEducation' } }); - registerPage.find('textarea#goals').simulate('change', { target: { value: payload.goals || '', name: 'goals' } }); - } - - registerPage.find('button.btn-brand').simulate('click'); - }; - - beforeEach(() => { - store = mockStore(initialState); - configure({ - loggingService: { logError: jest.fn() }, - config: { - ENVIRONMENT: 'production', - LANGUAGE_PREFERENCE_COOKIE_NAME: 'yum', - }, - messages: { 'es-419': {}, de: {}, 'en-us': {} }, - }); - props = { - registrationResult: jest.fn(), - }; - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should toggle optional fields state on checkbox click', () => { - const registrationPage = mount(reduxWrapper()); - - registrationPage.find('input#optional').simulate('change', { target: { checked: true } }); - expect(registrationPage.find('RegistrationPage').state('enableOptionalField')).toEqual(true); - }); - - it('should toggle optional fields state on text click', () => { - const registrationPage = mount(reduxWrapper()); - - registrationPage.find('#additionalFields').simulate('click'); - expect(registrationPage.find('RegistrationPage').state('enableOptionalField')).toEqual(true); - }); - - it('send tracking event on optional checkbox enabled', () => { - const registrationPage = mount(reduxWrapper()); - - registrationPage.find('input#optional').simulate('change', { target: { checked: true } }); - registrationPage.update(); - expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.register.optional_fields_selected', {}); - }); - - it('send tracking event when login link is clicked', () => { - const registrationPage = mount(reduxWrapper()); - - registrationPage.find('a[href*="/login"]').simulate('click'); - registrationPage.update(); - expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.login_form.toggled', { category: 'user-engagement' }); - }); - - it('send page event when register page is rendered', () => { - mount(reduxWrapper()); - expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register'); - }); - - it('should show optional fields section on optional check enabled', () => { - const registrationPage = mount(reduxWrapper()); - registrationPage.find('input#optional').simulate('change', { target: { checked: true } }); - registrationPage.update(); - - expect(registrationPage.find('textarea#goals').length).toEqual(1); - expect(registrationPage.find('select#levelOfEducation').length).toEqual(1); - expect(registrationPage.find('select#yearOfBirth').length).toEqual(1); - expect(registrationPage.find('select#gender').length).toEqual(1); - }); - - it('should not show optional field check if process env has empty optional fields list', () => { - mergeConfig({ - REGISTRATION_OPTIONAL_FIELDS: '', - }); - let registrationPage = mount(reduxWrapper()); - expect(registrationPage.find('input#optional').length).toEqual(0); - - mergeConfig({ - REGISTRATION_OPTIONAL_FIELDS: 'gender,goals,level_of_education,year_of_birth', - }); - - registrationPage = mount(reduxWrapper()); - expect(registrationPage.find('input#optional').length).toEqual(1); - }); - - it('should dispatch fetchRealtimeValidations on Blur after frontend validations ', () => { - const formPayload = { - email: '', - name: '', - username: 'test', - password: '', - country: '', - honor_code: true, - }; - store.dispatch = jest.fn(store.dispatch); - - const registrationPage = mount(reduxWrapper()); - IntlRegistrationPage.prototype.componentDidMount = jest.fn(); - registrationPage.find('input#username').simulate('change', { target: { value: 'test', name: 'username' } }); - registrationPage.find('input#username').simulate('blur'); - expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations(formPayload)); - - registrationPage.find('input#email').simulate('change', { target: { value: 'test@test.com', name: 'email' } }); - registrationPage.find('input#email').simulate('blur'); - formPayload.email = 'test@test.com'; - expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations(formPayload)); - - registrationPage.find('input#password').simulate('change', { target: { value: 'random123', name: 'password' } }); - registrationPage.find('input#password').simulate('blur'); - formPayload.password = 'random123'; - - expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations(formPayload)); - }); - - it('should call validations function on Blur', () => { - const registrationPage = mount(reduxWrapper()); - registrationPage.find('input#username').simulate('blur', { target: { value: '', name: 'username' } }); - registrationPage.find('input#name').simulate('blur', { target: { value: '', name: 'name' } }); - registrationPage.find('input#email').simulate('blur', { target: { value: '', name: 'email' } }); - registrationPage.find('input#password').simulate('blur', { target: { value: '', name: 'password' } }); - registrationPage.find('select#country').simulate('blur', { target: { value: '', name: 'country' } }); - expect(registrationPage.find('RegistrationPage').state('errors')).toEqual(emptyFieldValidation); - }); - - it('validate password validations', () => { - const errors = { - email: '', - name: '', - username: '', - password: 'Your password must contain at least 8 characters', - country: '', - }; - - const registrationPage = mount(reduxWrapper()); - registrationPage.find('input#password').simulate('blur', { target: { value: 'pas', name: 'password' } }); - expect(registrationPage.find('RegistrationPage').state('errors')).toEqual(errors); - - errors.password = 'Your password must contain at least 1 number.'; - registrationPage.find('input#password').simulate('blur', { target: { value: 'passwordd', name: 'password' } }); - expect(registrationPage.find('RegistrationPage').state('errors')).toEqual(errors); - - errors.password = 'Your password must contain at least 1 letter.'; - registrationPage.find('input#password').simulate('blur', { target: { value: '123456789', name: 'password' } }); - expect(registrationPage.find('RegistrationPage').state('errors')).toEqual(errors); - }); - - it('tests shouldComponentUpdate change validations and formValid state', () => { - store = mockStore({ - ...initialState, - register: { - ...initialState.register, - updateFieldErrors: false, - }, - }); - const nextProps = { - validations: { - validation_decisions: { - username: 'Username must be between 2 and 30 characters long.', - }, - }, - registrationError: { - username: [{ username: 'Username must be between 2 and 30 characters long.' }], - }, - }; - - const root = mount(reduxWrapper()); - // calling this to update the state - root.find('input#username').simulate('blur', { target: { value: '', name: 'username' } }); - const shouldUpdate = root.find('RegistrationPage').instance().shouldComponentUpdate(nextProps); - expect(root.find('RegistrationPage').state('formValid')).not.toEqual(true); - expect(shouldUpdate).toBe(false); - }); - - it('should not dispatch registerNewUser on empty form Submission', () => { - const formPayload = { - email: '', - username: '', - password: '', - name: '', - honor_code: true, - country: '', - }; - store.dispatch = jest.fn(store.dispatch); - - const registrationPage = mount(reduxWrapper()); - registrationPage.find('button.btn-brand').simulate('click'); - expect(store.dispatch).not.toHaveBeenCalledWith(registerNewUser(formPayload)); - }); - - it('should show error messages for required fields on empty form submission', () => { - const registrationPage = mount(reduxWrapper()); - registrationPage.find('button.btn-brand').simulate('click'); - - expect(registrationPage.find('#name-invalid-feedback').text()).toEqual(emptyFieldValidation.name); - expect(registrationPage.find('#username-invalid-feedback').text()).toEqual(emptyFieldValidation.username); - expect(registrationPage.find('#email-invalid-feedback').text()).toEqual(emptyFieldValidation.email); - expect(registrationPage.find('#password-invalid-feedback').text()).toEqual(emptyFieldValidation.password); - expect(registrationPage.find('#country-invalid-feedback').text()).toEqual(emptyFieldValidation.country); - - let alertBanner = 'We couldn\'t create your account.'; - Object.keys(emptyFieldValidation).forEach(key => { - alertBanner += emptyFieldValidation[key]; - }); - - expect(registrationPage.find('#validation-errors').first().text()).toEqual(alertBanner); - }); - - it('should clear field related error messages on input field Focus', () => { - const errors = { - email: '', - name: '', - username: '', - password: '', - country: '', - }; - const registrationPage = mount(reduxWrapper()); - registrationPage.find('button.btn-brand').simulate('click'); - - expect(registrationPage.find('#name-invalid-feedback').text()).toEqual(emptyFieldValidation.name); - registrationPage.find('input#name').simulate('focus'); - expect(registrationPage.find('#username-invalid-feedback').text()).toEqual(emptyFieldValidation.username); - registrationPage.find('input#username').simulate('focus'); - expect(registrationPage.find('#email-invalid-feedback').text()).toEqual(emptyFieldValidation.email); - registrationPage.find('input#email').simulate('focus'); - expect(registrationPage.find('#password-invalid-feedback').text()).toEqual(emptyFieldValidation.password); - registrationPage.find('input#password').simulate('focus'); - registrationPage.find('select#country').simulate('focus', { target: { value: 'US', name: 'country' } }); - expect(registrationPage.find('RegistrationPage').state('errors')).toEqual(errors); - }); - - it('should show error message on alert and below the fields in case of 409', () => { - const errors = { - email: 'It looks like test@gmail.com belongs to an existing account. Try again with a different email address.', - username: 'It looks like test belongs to an existing account. Try again with a different username.', - }; - store = mockStore({ - ...initialState, - register: { - ...initialState.register, - registrationError: { - email: [{ user_message: errors.email }], - username: [{ user_message: errors.username }], - }, - }, - }); - - const nextProps = { - validations: null, - thirdPartyAuthContext: { - pipelineUserDetails: null, - }, - registrationError: { - username: [{ username: errors.username }], - }, - }; - - const registrationPage = mount(reduxWrapper()); - registrationPage.find('button.btn-brand').simulate('click'); - registrationPage.find('RegistrationPage').instance().shouldComponentUpdate(nextProps); - expect(registrationPage.find('#email-invalid-feedback').text()).toEqual(errors.email); - expect(registrationPage.find('#username-invalid-feedback').text()).toEqual(errors.username); - expect(registrationPage.find('#validation-errors').first().text()).toEqual( - 'We couldn\'t create your account.'.concat(errors.email + errors.username), - ); - }); - - it('should submit form for valid input', () => { - jest.spyOn(global.Date, 'now').mockImplementation(() => 0); - - const formPayload = { - name: 'John Doe', - username: 'john_doe', - email: 'john.doe@example.com', - password: 'password1', - country: 'Pakistan', - gender: 'm', - honor_code: true, - totalRegistrationTime: 0, - }; - - store.dispatch = jest.fn(store.dispatch); - submitForm(formPayload); - expect(store.dispatch).toHaveBeenCalledWith(registerNewUser(formPayload)); - }); - - it('should submit form with no password when current provider is present', () => { - jest.spyOn(global.Date, 'now').mockImplementation(() => 0); - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - currentProvider: appleProvider.name, - }, - }, - }); - - const formPayload = { - name: 'John Doe', - username: 'john_doe', - email: 'john.doe@example.com', - country: 'Pakistan', - honor_code: true, - social_auth_provider: appleProvider.name, - totalRegistrationTime: 0, - }; - - store.dispatch = jest.fn(store.dispatch); - submitForm(formPayload, false, true); - expect(store.dispatch).toHaveBeenCalledWith(registerNewUser(formPayload)); - }); - - it('should display validationAlertMessages in case of invalid form submission', () => { - const alertMessages = { - name: [{ user_message: 'Please enter your full name.' }], - username: [{ user_message: 'Please enter your public username.' }], - email: [{ user_message: 'Please enter your email.' }], - password: [{ user_message: 'Please enter your password.' }], - country: [{ user_message: 'Select your country or region of residence.' }], - }; - store.dispatch = jest.fn(store.dispatch); - - const registrationPage = mount(reduxWrapper()); - registrationPage.find('button.btn-brand').simulate('click'); - expect(registrationPage.find('RegistrationPage').state('validationAlertMessages')).toEqual(alertMessages); - }); - - it('should not update validationAlertMessages on blur event', () => { - const alertMessages = { - name: [{ user_message: 'Please enter your full name.' }], - username: [{ user_message: 'Please enter your public username.' }], - email: [{ user_message: 'Please enter your email.' }], - password: [{ user_message: 'Please enter your password.' }], - country: [{ user_message: 'Select your country or region of residence.' }], - }; - store.dispatch = jest.fn(store.dispatch); - - const registrationPage = mount(reduxWrapper()); - registrationPage.find('button.btn-brand').simulate('click'); - expect(registrationPage.find('RegistrationPage').state('validationAlertMessages')).toEqual(alertMessages); - - registrationPage.find('input#password').simulate('blur', { target: { value: 'test12345', name: 'password' } }); - registrationPage.find('input#email').simulate('blur', { target: { value: 'test@test.com', name: 'email' } }); - registrationPage.find('input#name').simulate('blur', { target: { value: 'test', name: 'name' } }); - - expect(registrationPage.find('RegistrationPage').state('validationAlertMessages')).toEqual(alertMessages); - }); - - it('should match default section snapshot', () => { - const tree = renderer.create(reduxWrapper()); - expect(tree.toJSON()).toMatchSnapshot(); - }); - - it('should match registration api rate limit error message', () => { - props = { - errors: { - errorCode: FORBIDDEN_REQUEST, - }, - }; - - const registrationPage = mount(reduxWrapper()); - expect(registrationPage.find('div.alert-heading').length).toEqual(1); - const expectedMessage = 'We couldn\'t create your account.Too many failed registration attempts. Try again later.'; - expect(registrationPage.find('div.alert').first().text()).toEqual(expectedMessage); - }); - - it('should match internal server error message', () => { - props = { - errors: { - errorCode: INTERNAL_SERVER_ERROR, - }, - }; - - const registrationPage = mount(reduxWrapper()); - expect(registrationPage.find('div.alert-heading').length).toEqual(1); - const expectedMessage = 'We couldn\'t create your account.An error has occurred. Try refreshing the page, or check your internet connection.'; - expect(registrationPage.find('div.alert').first().text()).toEqual(expectedMessage); - }); - - it('should match pending button state snapshot', () => { - store = mockStore({ - ...initialState, - register: { - ...initialState.register, - submitState: PENDING_STATE, - }, - }); - - const tree = renderer.create(reduxWrapper()); - expect(tree.toJSON()).toMatchSnapshot(); - }); - - it('should match TPA provider snapshot', () => { - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - providers: [appleProvider], - }, - }, - }); - - const tree = renderer.create(reduxWrapper()).toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should display no password field when current provider is present', () => { - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - currentProvider: appleProvider.name, - }, - }, - }); - - const registrationPage = mount(reduxWrapper()); - expect(registrationPage.find('input#password').length).toEqual(0); - }); - - it('tests shouldComponentUpdate with pipeline user data', () => { - const nextProps = { - validations: null, - thirdPartyAuthContext: { - pipelineUserDetails: { - name: 'test', - email: 'test@example.com', - username: 'test-username', - }, - }, - }; - - const root = mount(reduxWrapper()); - - const shouldUpdate = root.find('RegistrationPage').instance().shouldComponentUpdate(nextProps); - expect(shouldUpdate).toBe(false); - }); - - it('should match url after redirection', () => { - const dasboardUrl = 'http://test.com/testing-dashboard/'; - - store = mockStore({ - ...initialState, - register: { - ...initialState.register, - registrationResult: { - success: true, - redirectUrl: dasboardUrl, - }, - }, - }); - delete window.location; - window.location = { href: getConfig().BASE_URL }; - renderer.create(reduxWrapper()); - expect(window.location.href).toBe(dasboardUrl); - }); - - it('should set registration survey cookie', () => { - store = mockStore({ - ...initialState, - register: { - ...initialState.register, - registrationResult: { - success: true, - }, - }, - }); - - renderer.create(reduxWrapper()); - expect(document.cookie).toMatch(`${getConfig().USER_SURVEY_COOKIE_NAME}=register`); - expect(document.cookie).toMatch(`${getConfig().REGISTER_CONVERSION_COOKIE_NAME}=true`); - }); - - it('should display institution register button', () => { - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - secondaryProviders: [secondaryProviders], - }, - }, - }); - delete window.location; - window.location = { href: getConfig().BASE_URL }; - const root = mount(reduxWrapper()); - expect(root.text().includes('Use my institution/campus credentials')).toBe(true); - }); - - it('should not display institution register button', () => { - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - secondaryProviders: [secondaryProviders], - }, - }, - }); - delete window.location; - window.location = { href: getConfig().BASE_URL }; - const root = mount(reduxWrapper()); - root.find(RenderInstitutionButton).simulate('click', { institutionLogin: true }); - expect(root.text().includes('Test University')).toBe(true); - }); - - it('should match url after TPA redirection', () => { - const authCompleteUrl = '/auth/complete/google-oauth2/'; - store = mockStore({ - ...initialState, - register: { - ...initialState.register, - registrationResult: { - success: true, - redirectUrl: '', - }, - }, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - finishAuthUrl: authCompleteUrl, - }, - }, - }); - - delete window.location; - window.location = { href: getConfig().BASE_URL }; - - renderer.create(reduxWrapper()); - expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl); - }); - - it('should redirect to social auth provider url', () => { - const registerUrl = '/auth/login/apple-id/?auth_entry=register&next=/dashboard'; - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - providers: [{ - ...appleProvider, - registerUrl, - }], - }, - }, - }); - - delete window.location; - window.location = { href: getConfig().BASE_URL }; - - const loginPage = mount(reduxWrapper()); - - loginPage.find('button#oa2-apple-id').simulate('click'); - expect(window.location.href).toBe(getConfig().LMS_BASE_URL + registerUrl); - }); - - it('should match third party auth alert', () => { - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - currentProvider: 'Apple', - }, - }, - }); - - const expectedMessage = 'You\'ve successfully signed into Apple. We just need a little more information before ' - + 'you start learning with openedX.'; - - const registerPage = mount(reduxWrapper()); - expect(registerPage.find('#tpa-alert').find('span').text()).toEqual(expectedMessage); - }); - - it('check cookie rendered', () => { - const registerPage = mount(reduxWrapper()); - expect(registerPage.find()).toBeTruthy(); - }); - - it('should render tpa button for tpa_hint id in primary provider', () => { - const expectedMessage = `Sign in using ${appleProvider.name}`; - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - providers: [appleProvider], - }, - thirdPartyAuthApiStatus: COMPLETE_STATE, - }, - }); - - delete window.location; - window.location = { href: getConfig().BASE_URL.concat('/register'), search: `?next=/dashboard&tpa_hint=${appleProvider.id}` }; - appleProvider.iconImage = null; - - const registerPage = mount(reduxWrapper()); - expect(registerPage.find(`button#${appleProvider.id}`).find('span').text()).toEqual(expectedMessage); - }); - - it('should render regular tpa button for invalid tpa_hint value', () => { - const expectedMessage = `${appleProvider.name}`; - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - providers: [appleProvider], - }, - thirdPartyAuthApiStatus: COMPLETE_STATE, - }, - }); - - delete window.location; - window.location = { href: getConfig().BASE_URL.concat('/register'), search: '?next=/dashboard&tpa_hint=invalid' }; - appleProvider.iconImage = null; - - const registerPage = mount(reduxWrapper()); - expect(registerPage.find(`button#${appleProvider.id}`).find('span#provider-name').text()).toEqual(expectedMessage); - }); - - it('should render tpa button for tpa_hint id in secondary provider', () => { - const expectedMessage = `Sign in using ${secondaryProviders.name}`; - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - secondaryProviders: [secondaryProviders], - }, - thirdPartyAuthApiStatus: COMPLETE_STATE, - }, - }); - delete window.location; - window.location = { href: getConfig().BASE_URL.concat('/register'), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` }; - secondaryProviders.iconImage = null; - - const registerPage = mount(reduxWrapper()); - expect(registerPage.find(`button#${secondaryProviders.id}`).find('span').text()).toEqual(expectedMessage); - }); - - it('should redirect to idp page if skipHinetedLogin is true', () => { - secondaryProviders.skipHintedLogin = true; - store = mockStore({ - ...initialState, - commonComponents: { - ...initialState.commonComponents, - thirdPartyAuthContext: { - ...initialState.commonComponents.thirdPartyAuthContext, - secondaryProviders: [secondaryProviders], - }, - thirdPartyAuthApiStatus: COMPLETE_STATE, - }, - }); - - delete window.location; - window.location = { href: getConfig().BASE_URL.concat('/register'), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` }; - secondaryProviders.iconImage = null; - - mount(reduxWrapper()); - expect(window.location.href).toEqual(getConfig().LMS_BASE_URL + secondaryProviders.registerUrl); - }); -}); diff --git a/src/legacy/register/tests/__snapshots__/RegistrationPage.test.jsx.snap b/src/legacy/register/tests/__snapshots__/RegistrationPage.test.jsx.snap deleted file mode 100644 index 7e2d5b39..00000000 --- a/src/legacy/register/tests/__snapshots__/RegistrationPage.test.jsx.snap +++ /dev/null @@ -1,4783 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RegistrationPageTests should match TPA provider snapshot 1`] = ` -
    -
    -
    -

    - Already have an edX account? - - Sign in. - -

    -
    -

    - Create a new account -

    -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - - - Select your country or region of residence. - -
    -
    -

    - By creating an account, you agree to the - - Terms of Service and Honor Code - - - - - - - - - - and you acknowledge that edX and each Member process your personal data in accordance with the - - Privacy Policy - - - - - - - - - - . -

    -
    -
    - - - -
    - -
    -
    - - or create an account using - -
    -
    - -
    -
    -
    -
    -
    -`; - -exports[`RegistrationPageTests should match default section snapshot 1`] = ` -
    -
    -
    -

    - Already have an edX account? - - Sign in. - -

    -
    -

    - Create a new account -

    -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - - - Select your country or region of residence. - -
    -
    -

    - By creating an account, you agree to the - - Terms of Service and Honor Code - - - - - - - - - - and you acknowledge that edX and each Member process your personal data in accordance with the - - Privacy Policy - - - - - - - - - - . -

    -
    -
    - - - -
    - -
    -
    -
    -
    -`; - -exports[`RegistrationPageTests should match pending button state snapshot 1`] = ` -
    -
    -
    -

    - Already have an edX account? - - Sign in. - -

    -
    -

    - Create a new account -

    -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - - - Select your country or region of residence. - -
    -
    -

    - By creating an account, you agree to the - - Terms of Service and Honor Code - - - - - - - - - - and you acknowledge that edX and each Member process your personal data in accordance with the - - Privacy Policy - - - - - - - - - - . -

    -
    -
    - - - -
    - -
    -
    -
    -
    -`; diff --git a/src/legacy/reset-password/InvalidToken.jsx b/src/legacy/reset-password/InvalidToken.jsx deleted file mode 100644 index fe927e4d..00000000 --- a/src/legacy/reset-password/InvalidToken.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import { Helmet } from 'react-helmet'; -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { getConfig } from '@edx/frontend-platform'; -import { Alert } from '@edx/paragon'; - -import messages from './messages'; -import { LOGIN_PAGE } from '../data/constants'; - -const InvalidTokenMessage = props => { - const { intl } = props; - - const loginPasswordLink = ( - - {intl.formatMessage(messages['forgot.password.confirmation.sign.in.link'])} - - ); - - return ( - <> - - {intl.formatMessage(messages['reset.password.page.title'], - { siteName: getConfig().SITE_NAME })} - - -
    -
    - - {intl.formatMessage(messages['reset.password.request.invalid.token.header'])} - {intl.formatMessage(messages['reset.password.request.forgot.password.text'])} , - loginPasswordLink, - }} - /> - -
    -
    - - ); -}; - -InvalidTokenMessage.propTypes = { - intl: intlShape.isRequired, -}; - -export default injectIntl(InvalidTokenMessage); diff --git a/src/legacy/reset-password/ResetPasswordPage.jsx b/src/legacy/reset-password/ResetPasswordPage.jsx deleted file mode 100644 index 923fe528..00000000 --- a/src/legacy/reset-password/ResetPasswordPage.jsx +++ /dev/null @@ -1,225 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; -import { Helmet } from 'react-helmet'; -import { connect } from 'react-redux'; -import { - Alert, Form, StatefulButton, -} from '@edx/paragon'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { getQueryParameters, getConfig } from '@edx/frontend-platform'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faSpinner } from '@fortawesome/free-solid-svg-icons'; - -import messages from './messages'; -import { resetPassword, validateToken } from './data/actions'; -import { resetPasswordResultSelector } from './data/selectors'; -import { validatePassword } from './data/service'; -import InvalidTokenMessage from './InvalidToken'; -import ResetSuccessMessage from './ResetSuccess'; -import { - AuthnValidationFormGroup, - APIFailureMessage, -} from '../common-components'; -import Spinner from './Spinner'; -import { API_RATELIMIT_ERROR, INTERNAL_SERVER_ERROR } from '../data/constants'; -import { windowScrollTo } from '../data/utils'; - -const ResetPasswordPage = (props) => { - const { intl } = props; - const params = getQueryParameters(); - - const [newPasswordInput, setNewPasswordValue] = useState(''); - const [confirmPasswordInput, setConfirmPasswordValue] = useState(''); - const [passwordValid, setPasswordValidValue] = useState(true); - const [passwordMatch, setPasswordMatchValue] = useState(true); - const [validationMessage, setvalidationMessage] = useState(''); - const [bannerErrorMessage, setbannerErrorMessage] = useState(''); - - useEffect(() => { - if (props.status === 'failure' - && props.errors !== INTERNAL_SERVER_ERROR - && props.errors !== API_RATELIMIT_ERROR) { - setbannerErrorMessage(props.errors); - setvalidationMessage(props.errors); - setPasswordValidValue(false); - } - }, [props.status]); - - const validatePasswordFromBackend = async (newPassword) => { - let errorMessage; - try { - errorMessage = await validatePassword(newPassword); - } catch (err) { - errorMessage = ''; - } - setPasswordValidValue(!errorMessage); - setvalidationMessage(errorMessage); - }; - - const handleNewPasswordChange = (e) => { - const newPassword = e.target.value; - setNewPasswordValue(newPassword); - }; - - const handleNewPasswordOnBlur = (e) => { - const newPassword = e.target.value; - setNewPasswordValue(newPassword); - - if (newPassword === '') { - setPasswordValidValue(false); - setvalidationMessage(intl.formatMessage(messages['reset.password.empty.new.password.field.error'])); - } else { - validatePasswordFromBackend(newPassword); - } - }; - - const handleConfirmPasswordChange = (e) => { - const confirmPassword = e.target.value; - setConfirmPasswordValue(confirmPassword); - setPasswordMatchValue(confirmPassword === newPasswordInput); - }; - - const handleSubmit = (e) => { - e.preventDefault(); - - windowScrollTo({ left: 0, top: 0, behavior: 'smooth' }); - if (newPasswordInput === '') { - setPasswordValidValue(false); - setvalidationMessage(intl.formatMessage(messages['reset.password.empty.new.password.field.error'])); - setbannerErrorMessage(intl.formatMessage(messages['reset.password.empty.new.password.field.error'])); - return; - } - if (newPasswordInput !== confirmPasswordInput) { - setPasswordMatchValue(false); - return; - } - - const formPayload = { - new_password1: newPasswordInput, - new_password2: confirmPasswordInput, - }; - props.resetPassword(formPayload, props.token, params); - }; - - if (props.status === 'token-pending') { - const { token } = props.match.params; - if (token) { - props.validateToken(token); - return ; - } - } else if (props.status === 'invalid' && props.errors === INTERNAL_SERVER_ERROR) { - return ( - - ); - } else if (props.status === 'invalid' && props.errors === API_RATELIMIT_ERROR) { - return ( - - ); - } else if (props.status === 'invalid') { - return ; - } else if (props.status === 'success') { - return ; - } else { - return ( - <> - - {intl.formatMessage(messages['reset.password.page.title'], - { siteName: getConfig().SITE_NAME })} - - - {props.status === 'failure' && props.errors === INTERNAL_SERVER_ERROR ? ( - - ) : null} - {props.status === 'failure' && props.errors === API_RATELIMIT_ERROR ? ( - - ) : null} -
    -
    - {bannerErrorMessage ? ( - - {intl.formatMessage(messages['forgot.password.empty.new.password.error.heading'])} -
    • {bannerErrorMessage}
    -
    - ) : null} -
    -

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

    -

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

    - handleNewPasswordChange(e)} - onBlur={e => handleNewPasswordOnBlur(e)} - className="w-100" - inputFieldStyle="border-gray-600" - /> - handleConfirmPasswordChange(e)} - className="w-100" - inputFieldStyle="border-gray-600" - /> - }} - onClick={e => handleSubmit(e)} - onMouseDown={(e) => e.preventDefault()} - /> - -
    -
    - - ); - } - return null; -}; - -ResetPasswordPage.defaultProps = { - status: null, - token: null, - match: null, - errors: null, -}; - -ResetPasswordPage.propTypes = { - intl: intlShape.isRequired, - resetPassword: PropTypes.func.isRequired, - validateToken: PropTypes.func.isRequired, - token: PropTypes.string, - match: PropTypes.shape({ - params: PropTypes.shape({ - token: PropTypes.string, - }), - }), - status: PropTypes.string, - errors: PropTypes.string, -}; - -export default connect( - resetPasswordResultSelector, - { - resetPassword, - validateToken, - }, -)(injectIntl(ResetPasswordPage)); diff --git a/src/legacy/reset-password/ResetSuccess.jsx b/src/legacy/reset-password/ResetSuccess.jsx deleted file mode 100644 index 37a298ea..00000000 --- a/src/legacy/reset-password/ResetSuccess.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { Helmet } from 'react-helmet'; - -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { getConfig } from '@edx/frontend-platform'; -import { Alert } from '@edx/paragon'; - -import messages from './messages'; - -const ResetSuccessMessage = (props) => { - const { intl } = props; - - const loginPasswordLink = ( - - - - ); - - return ( - <> - - {intl.formatMessage(messages['reset.password.page.title'], - { siteName: getConfig().SITE_NAME })} - - -
    -
    -
    - - - {intl.formatMessage(messages['reset.password.request.success.header.message'])} - - - -
    -
    -
    - - ); -}; - -ResetSuccessMessage.propTypes = { - intl: intlShape.isRequired, -}; - -export default injectIntl(ResetSuccessMessage); diff --git a/src/legacy/reset-password/Spinner.jsx b/src/legacy/reset-password/Spinner.jsx deleted file mode 100644 index f02bff8e..00000000 --- a/src/legacy/reset-password/Spinner.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { Spinner as ParagonSpinner } from '@edx/paragon'; - -const Spinner = () => ( -
    -
    - -
    -
    -); - -export default Spinner; diff --git a/src/legacy/reset-password/data/actions.js b/src/legacy/reset-password/data/actions.js deleted file mode 100644 index 7e6b637b..00000000 --- a/src/legacy/reset-password/data/actions.js +++ /dev/null @@ -1,44 +0,0 @@ -import { AsyncActionType } from '../../data/utils'; - -export const RESET_PASSWORD = new AsyncActionType('RESET', 'PASSWORD'); -export const VALIDATE_TOKEN = new AsyncActionType('VALIDATE', 'TOKEN'); - -// Validate confirmation token -export const validateToken = (token) => ({ - type: VALIDATE_TOKEN.BASE, - payload: { token }, -}); - -export const validateTokenBegin = () => ({ - type: VALIDATE_TOKEN.BEGIN, -}); - -export const validateTokenSuccess = (tokenStatus, token) => ({ - type: VALIDATE_TOKEN.SUCCESS, - payload: { tokenStatus, token }, -}); - -export const validateTokenFailure = errors => ({ - type: VALIDATE_TOKEN.FAILURE, - payload: { errors }, -}); - -// Reset Password -export const resetPassword = (formPayload, token, params) => ({ - type: RESET_PASSWORD.BASE, - payload: { formPayload, token, params }, -}); - -export const resetPasswordBegin = () => ({ - type: RESET_PASSWORD.BEGIN, -}); - -export const resetPasswordSuccess = data => ({ - type: RESET_PASSWORD.SUCCESS, - payload: { data }, -}); - -export const resetPasswordFailure = errors => ({ - type: RESET_PASSWORD.FAILURE, - payload: { errors }, -}); diff --git a/src/legacy/reset-password/data/reducers.js b/src/legacy/reset-password/data/reducers.js deleted file mode 100644 index f7ce1898..00000000 --- a/src/legacy/reset-password/data/reducers.js +++ /dev/null @@ -1,44 +0,0 @@ -import { RESET_PASSWORD, VALIDATE_TOKEN } from './actions'; - -export const defaultState = { - status: 'token-pending', - token: null, - errors: null, -}; - -const reducer = (state = defaultState, action = null) => { - switch (action.type) { - case VALIDATE_TOKEN.SUCCESS: - return { - ...state, - status: 'valid', - token: action.payload.token, - }; - case VALIDATE_TOKEN.FAILURE: - return { - ...state, - status: 'invalid', - errors: action.payload.errors, - }; - case RESET_PASSWORD.BEGIN: - return { - ...state, - status: 'pending', - }; - case RESET_PASSWORD.SUCCESS: - return { - ...state, - status: 'success', - }; - case RESET_PASSWORD.FAILURE: - return { - ...state, - status: 'failure', - errors: action.payload.errors, - }; - default: - return state; - } -}; - -export default reducer; diff --git a/src/legacy/reset-password/data/sagas.js b/src/legacy/reset-password/data/sagas.js deleted file mode 100644 index ef16270c..00000000 --- a/src/legacy/reset-password/data/sagas.js +++ /dev/null @@ -1,69 +0,0 @@ -import { call, put, takeEvery } from 'redux-saga/effects'; -import { logError, logInfo } from '@edx/frontend-platform/logging'; - -// Actions -import { - VALIDATE_TOKEN, - validateTokenBegin, - validateTokenSuccess, - validateTokenFailure, - RESET_PASSWORD, - resetPasswordBegin, - resetPasswordSuccess, - resetPasswordFailure, -} from './actions'; - -import { validateToken, resetPassword } from './service'; -import { INTERNAL_SERVER_ERROR, API_RATELIMIT_ERROR } from '../../data/constants'; - -// Services -export function* handleValidateToken(action) { - try { - yield put(validateTokenBegin()); - const data = yield call(validateToken, action.payload.token); - const isValid = data.is_valid; - if (isValid) { - yield put(validateTokenSuccess(isValid, action.payload.token)); - } else { - yield put(validateTokenFailure(isValid)); - } - } catch (err) { - const statusCodes = [429]; - if (err.response && statusCodes.includes(err.response.status)) { - yield put(validateTokenFailure(API_RATELIMIT_ERROR)); - logInfo(err); - } else { - yield put(validateTokenFailure(INTERNAL_SERVER_ERROR)); - logError(err); - } - } -} - -export function* handleResetPassword(action) { - try { - yield put(resetPasswordBegin()); - const data = yield call(resetPassword, action.payload.formPayload, action.payload.token, action.payload.params); - const resetStatus = data.reset_status; - const resetErrors = data.err_msg; - - if (resetStatus) { - yield put(resetPasswordSuccess(resetStatus)); - } else { - yield put(resetPasswordFailure(resetErrors)); - } - } catch (err) { - const statusCodes = [429]; - if (err.response && statusCodes.includes(err.response.status)) { - yield put(resetPasswordFailure(API_RATELIMIT_ERROR)); - logInfo(err); - } else { - yield put(resetPasswordFailure(INTERNAL_SERVER_ERROR)); - logError(err); - } - } -} - -export default function* saga() { - yield takeEvery(RESET_PASSWORD.BASE, handleResetPassword); - yield takeEvery(VALIDATE_TOKEN.BASE, handleValidateToken); -} diff --git a/src/legacy/reset-password/data/tests/sagas.test.js b/src/legacy/reset-password/data/tests/sagas.test.js deleted file mode 100644 index 1183d44b..00000000 --- a/src/legacy/reset-password/data/tests/sagas.test.js +++ /dev/null @@ -1,167 +0,0 @@ -import { runSaga } from 'redux-saga'; - -import { - resetPasswordBegin, - resetPasswordSuccess, - resetPasswordFailure, - validateTokenBegin, - validateTokenFailure, -} from '../actions'; -import { handleResetPassword, handleValidateToken } from '../sagas'; -import * as api from '../service'; -import initializeMockLogging from '../../../../setupTest'; -import { API_RATELIMIT_ERROR, INTERNAL_SERVER_ERROR } from '../../../data/constants'; - -const { loggingService } = initializeMockLogging(); - -describe('handleResetPassword', () => { - const params = { - payload: { - formPayload: { - new_password1: 'new_password1', - new_password2: 'new_password1', - }, - token: 'token', - params: {}, - }, - }; - - const responseData = { - reset_status: true, - err_msg: '', - }; - - beforeEach(() => { - loggingService.logError.mockReset(); - loggingService.logInfo.mockReset(); - }); - - it('should call service and dispatch success action', async () => { - const resetPassword = jest.spyOn(api, 'resetPassword') - .mockImplementation(() => Promise.resolve(responseData)); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - handleResetPassword, - params, - ); - - expect(resetPassword).toHaveBeenCalledTimes(1); - expect(dispatched).toEqual([resetPasswordBegin(), resetPasswordSuccess(true)]); - resetPassword.mockClear(); - }); - - it('should call service and dispatch internal server error action', async () => { - const errorResponse = { - response: { - status: 500, - data: { - errorCode: INTERNAL_SERVER_ERROR, - }, - }, - }; - const resetPassword = jest.spyOn(api, 'resetPassword') - .mockImplementation(() => Promise.reject(errorResponse)); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - handleResetPassword, - params, - ); - - expect(loggingService.logError).toHaveBeenCalled(); - expect(resetPassword).toHaveBeenCalledTimes(1); - expect(dispatched).toEqual([resetPasswordBegin(), resetPasswordFailure(INTERNAL_SERVER_ERROR)]); - resetPassword.mockClear(); - }); - - it('should call service and dispatch ratelimit error', async () => { - const errorResponse = { - response: { - status: 429, - data: { - errorCode: API_RATELIMIT_ERROR, - }, - }, - }; - const resetPassword = jest.spyOn(api, 'resetPassword') - .mockImplementation(() => Promise.reject(errorResponse)); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - handleResetPassword, - params, - ); - - expect(loggingService.logInfo).toHaveBeenCalled(); - expect(resetPassword).toHaveBeenCalledTimes(1); - expect(dispatched).toEqual([resetPasswordBegin(), resetPasswordFailure(API_RATELIMIT_ERROR)]); - resetPassword.mockClear(); - }); -}); - -describe('handleValidateToken', () => { - const params = { - payload: { - token: 'token', - params: {}, - }, - }; - - beforeEach(() => { - loggingService.logError.mockReset(); - loggingService.logInfo.mockReset(); - }); - - it('check internal server error on api failure', async () => { - const errorResponse = { - response: { - status: 500, - data: { - errorCode: INTERNAL_SERVER_ERROR, - }, - }, - }; - const validateToken = jest.spyOn(api, 'validateToken') - .mockImplementation(() => Promise.reject(errorResponse)); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - handleValidateToken, - params, - ); - - expect(validateToken).toHaveBeenCalledTimes(1); - expect(dispatched).toEqual([validateTokenBegin(), validateTokenFailure(INTERNAL_SERVER_ERROR)]); - validateToken.mockClear(); - }); - - it('should call service and dispatch ratelimit error', async () => { - const errorResponse = { - response: { - status: 429, - data: { - errorCode: API_RATELIMIT_ERROR, - }, - }, - }; - const validateToken = jest.spyOn(api, 'validateToken') - .mockImplementation(() => Promise.reject(errorResponse)); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - handleValidateToken, - params, - ); - - expect(loggingService.logInfo).toHaveBeenCalled(); - expect(validateToken).toHaveBeenCalledTimes(1); - expect(dispatched).toEqual([validateTokenBegin(), validateTokenFailure(API_RATELIMIT_ERROR)]); - validateToken.mockClear(); - }); -}); diff --git a/src/legacy/reset-password/messages.js b/src/legacy/reset-password/messages.js deleted file mode 100644 index b68d82d6..00000000 --- a/src/legacy/reset-password/messages.js +++ /dev/null @@ -1,86 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - 'reset.password.page.title': { - id: 'reset.password.page.title', - defaultMessage: 'Reset Password | {siteName}', - description: 'page title', - }, - 'reset.password.page.heading': { - id: 'reset.password.page.heading', - defaultMessage: 'Reset your password', - description: 'The page heading for the Reset password page.', - }, - 'reset.password.page.instructions': { - id: 'reset.password.page.instructions', - defaultMessage: 'Enter and confirm your new password.', - description: 'Instructions message for reset password page.', - }, - 'reset.password.page.invalid.match.message': { - id: 'reset.password.page.invalid.match.message', - defaultMessage: 'Passwords do not match.', - description: 'Password format error.', - }, - 'reset.password.page.new.field.label': { - id: 'forgot.password.page.new.field.label', - defaultMessage: 'New password', - description: 'New password field label for the reset password page.', - }, - 'reset.password.page.confirm.field.label': { - id: 'forgot.password.page.confirm.field.label', - defaultMessage: 'Confirm password', - description: 'Confirm password field label for the reset password page.', - }, - 'reset.password.page.submit.button': { - id: 'reset.password.page.submit.button', - defaultMessage: 'Reset my password', - description: 'Submit button text for the reset password page.', - }, - 'reset.password.request.success.header.message': { - id: 'reset.password.request.success.header.message', - defaultMessage: 'Password reset complete.', - description: 'header message when reset is successful.', - }, - 'forgot.password.confirmation.sign.in.link': { - id: 'forgot.password.confirmation.sign.in.link', - defaultMessage: 'sign in', - description: 'link text used in message to refer to sign in page', - }, - 'reset.password.request.forgot.password.text': { - id: 'reset.password.request.forgot.password.text', - defaultMessage: 'Forgot password', - description: 'Forgot password text', - }, - 'reset.password.request.invalid.token.header': { - id: 'reset.password.request.invalid.token.header', - defaultMessage: 'Invalid password reset link', - description: 'Invalid password reset link help text heading', - }, - 'reset.password.empty.new.password.field.error': { - id: 'reset.password.empty.new.password.field.error', - defaultMessage: 'Please enter your new password.', - description: 'Error message that appears when user tries to submit form with empty New Password field', - }, - 'forgot.password.empty.new.password.error.heading': { - id: 'forgot.password.empty.new.password.error.heading', - defaultMessage: 'We couldn\'t reset your password.', - description: 'Heading that appears above error message when user submits empty form.', - }, - 'reset.password.request.server.error': { - id: 'reset.password.request.server.error', - defaultMessage: 'Failed to reset password', - description: 'Failed to reset password error message heading.', - }, - 'reset.password.token.validation.sever.error': { - id: 'reset.password.token.validation.sever.error', - defaultMessage: 'Token validation failure', - description: 'Failed to validate reset password token error message.', - }, - 'reset.server.ratelimit.error': { - id: 'reset.server.ratelimit.error', - defaultMessage: 'Too many requests.', - description: 'Too many request at server end point', - }, -}); - -export default messages; diff --git a/src/legacy/reset-password/tests/ResetPasswordPage.test.jsx b/src/legacy/reset-password/tests/ResetPasswordPage.test.jsx deleted file mode 100644 index 25427cb1..00000000 --- a/src/legacy/reset-password/tests/ResetPasswordPage.test.jsx +++ /dev/null @@ -1,291 +0,0 @@ -import React from 'react'; -import { Provider } from 'react-redux'; -import { act } from 'react-dom/test-utils'; -import renderer from 'react-test-renderer'; -import configureStore from 'redux-mock-store'; -import { mount } from 'enzyme'; -import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; -import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner'; -import * as auth from '@edx/frontend-platform/auth'; -import { resetPassword } from '../data/actions'; -import { APIFailureMessage } from '../../common-components'; - -import ResetPasswordPage from '../ResetPasswordPage'; -import { API_RATELIMIT_ERROR, INTERNAL_SERVER_ERROR } from '../../data/constants'; - -jest.mock('@edx/frontend-platform/auth'); - -const IntlResetPasswordPage = injectIntl(ResetPasswordPage); -const mockStore = configureStore(); - -describe('ResetPasswordPage', () => { - let props = {}; - let store = {}; - - const emptyFieldError = 'Please enter your new password.'; - const validationMessage = 'This password is too short. It must contain at least 8 characters. This password must contain at least 1 number.'; - - const reduxWrapper = children => ( - - {children} - - ); - - const submitForm = async (password) => { - const resetPasswordPage = mount(reduxWrapper()); - await act(async () => { - resetPasswordPage.find('input#reset-password-input').simulate('blur', { target: { value: password } }); - }); - resetPasswordPage.find('input#confirm-password-input').simulate('change', { target: { value: password } }); - resetPasswordPage.find('button.btn-primary').simulate('click'); - - return resetPasswordPage; - }; - - beforeEach(() => { - store = mockStore(); - props = { - resetPassword: jest.fn(), - status: null, - token_status: 'pending', - token: null, - errors: null, - match: { - params: {}, - }, - }; - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should match reset password default section snapshot', () => { - props = { - ...props, - token: 'token', - token_status: 'valid', - }; - const tree = renderer.create(reduxWrapper()) - .toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('show spinner component during token validation', () => { - props = { - ...props, - token_status: 'pending', - match: { - params: { - token: 'test-token', - }, - }, - }; - const tree = renderer.create(reduxWrapper()) - .toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should match invalid token message section snapshot', () => { - props = { - ...props, - token_status: 'invalid', - }; - const tree = renderer.create(reduxWrapper()) - .toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should match pending reset message section snapshot', () => { - props = { - ...props, - token_status: 'valid', - status: 'pending', - }; - const tree = renderer.create(reduxWrapper()) - .toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should match successful reset message section snapshot', () => { - props = { - ...props, - token_status: 'valid', - status: 'success', - }; - const tree = renderer.create(reduxWrapper()) - .toJSON(); - expect(tree).toMatchSnapshot(); - }); - - it('should display invalid password message', async () => { - props = { - ...props, - token_status: 'valid', - }; - - auth.getHttpClient = jest.fn(() => ({ - post: async () => ({ - data: { - validation_decisions: { - password: validationMessage, - }, - }, - catch: () => {}, - }), - })); - - const resetPasswordPage = mount(reduxWrapper()); - - // Focus out of empty field - await act(async () => { - await resetPasswordPage.find('input#reset-password-input').simulate('blur'); - }); - resetPasswordPage.update(); - expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(emptyFieldError); - - // Enter non-compliant password - await act(async () => { - await resetPasswordPage.find('input#reset-password-input').simulate('blur', { target: { value: 'invalid' } }); - }); - expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(validationMessage); - }); - - it('should display error message on empty form submission', () => { - const bannerMessage = 'We couldn\'t reset your password.'.concat(emptyFieldError); - props = { - ...props, - token_status: 'valid', - token: 'token', - }; - - const resetPasswordPage = mount(reduxWrapper()); - resetPasswordPage.find('button.btn-primary').simulate('click'); - - resetPasswordPage.update(); - expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(emptyFieldError); - expect(resetPasswordPage.find('#validation-errors').first().text()).toEqual(bannerMessage); - }); - - it('with valid inputs resetPassword action is dispatch', async () => { - const newPassword = 'test-password1'; - props = { - ...props, - token_status: 'valid', - token: 'token', - }; - - auth.getHttpClient = jest.fn(() => ({ - post: async () => ({ - data: {}, - catch: () => {}, - }), - })); - - const formPayload = { - new_password1: newPassword, - new_password2: newPassword, - }; - - store.dispatch = jest.fn(store.dispatch); - - const resetPasswordPage = await submitForm(newPassword); - expect(store.dispatch).toHaveBeenCalledWith(resetPassword(formPayload, props.token, {})); - resetPasswordPage.unmount(); - }); - - it('should dispatch resetPassword action if validations have reached rate limit', async () => { - const password = 'test-password'; - - auth.getHttpClient = jest.fn(() => ({ - post: async () => { - throw new Error('error'); - }, - })); - store.dispatch = jest.fn(store.dispatch); - - props = { - ...props, - token_status: 'valid', - token: 'token', - }; - - const resetPasswordPage = await submitForm(password); - expect(store.dispatch).toHaveBeenCalledWith( - resetPassword({ new_password1: password, new_password2: password }, props.token, {}), - ); - - resetPasswordPage.unmount(); - }); - - it('should not update the banner message on focus out', async () => { - const bannerMessage = 'We couldn\'t reset your password.'.concat(validationMessage); - props = { - ...props, - token_status: 'valid', - token: 'token', - errors: validationMessage, - status: 'failure', - }; - - const resetPasswordPage = mount(reduxWrapper()); - expect(resetPasswordPage.find('#validation-errors').first().text()).toEqual(bannerMessage); - - await act(async () => { - await resetPasswordPage.find('input#reset-password-input').simulate('blur', { target: { value: '' } }); - }); - // On blur event, the banner message remains same - expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(emptyFieldError); - expect(resetPasswordPage.find('#validation-errors').first().text()).toEqual(bannerMessage); - }); - - it('check cookie rendered', () => { - const resetPasswordPage = mount(reduxWrapper()); - expect(resetPasswordPage.find()).toBeTruthy(); - }); - - it('should display error banner on server error', () => { - const bannerMessage = 'Failed to reset passwordAn error has occurred. Try refreshing the page, or check your internet connection.'; - props = { - ...props, - status: 'failure', - errors: INTERNAL_SERVER_ERROR, - }; - - const resetPasswordPage = mount(reduxWrapper()); - resetPasswordPage.find('button.btn-primary').simulate('click'); - - resetPasswordPage.update(); - expect(resetPasswordPage.find('#internal-server-error').first().text()).toEqual(bannerMessage); - }); - - it('check api failure banner rendered', () => { - props = { - ...props, - status: 'invalid', - errors: INTERNAL_SERVER_ERROR, - }; - const resetPasswordPage = mount(reduxWrapper()); - expect(resetPasswordPage.find()).toBeTruthy(); - }); - - it('check failure banner rendered on validate token api ratelimit', () => { - props = { - ...props, - status: 'invalid', - errors: API_RATELIMIT_ERROR, - }; - const resetPasswordPage = mount(reduxWrapper()); - expect(resetPasswordPage.find()).toBeTruthy(); - }); - - it('check failure banner rendered on reset password api ratelimit', () => { - props = { - ...props, - status: 'failure', - errors: API_RATELIMIT_ERROR, - }; - const resetPasswordPage = mount(reduxWrapper()); - expect(resetPasswordPage.find()).toBeTruthy(); - }); -}); diff --git a/src/legacy/reset-password/tests/__snapshots__/ResetPasswordPage.test.jsx.snap b/src/legacy/reset-password/tests/__snapshots__/ResetPasswordPage.test.jsx.snap deleted file mode 100644 index 5acfeae8..00000000 --- a/src/legacy/reset-password/tests/__snapshots__/ResetPasswordPage.test.jsx.snap +++ /dev/null @@ -1,480 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ResetPasswordPage should match invalid token message section snapshot 1`] = ` -
    -
    -
    -

    - Reset your password -

    -

    - Enter and confirm your new password. -

    -
    - - - -
    -
    - - - - - Passwords do not match. - -
    - -
    -
    -
    -`; - -exports[`ResetPasswordPage should match pending reset message section snapshot 1`] = ` -
    -
    -
    -

    - Reset your password -

    -

    - Enter and confirm your new password. -

    -
    - - - -
    -
    - - - - - Passwords do not match. - -
    - -
    -
    -
    -`; - -exports[`ResetPasswordPage should match reset password default section snapshot 1`] = ` -
    -
    -
    -

    - Reset your password -

    -

    - Enter and confirm your new password. -

    -
    - - - -
    -
    - - - - - Passwords do not match. - -
    - -
    -
    -
    -`; - -exports[`ResetPasswordPage should match successful reset message section snapshot 1`] = ` -
    -
    -
    -
    -
    -
    -
    - Password reset complete. -
    - - Your password has been reset. - - - Sign in to your account. - - - -
    -
    -
    -
    -
    -
    -`; - -exports[`ResetPasswordPage show spinner component during token validation 1`] = ` -
    -
    -
    -

    - Reset your password -

    -

    - Enter and confirm your new password. -

    -
    - - - -
    -
    - - - - - Passwords do not match. - -
    - -
    -
    -
    -`; diff --git a/src/legacy/welcome/WelcomePage.jsx b/src/legacy/welcome/WelcomePage.jsx deleted file mode 100644 index 3cd125e7..00000000 --- a/src/legacy/welcome/WelcomePage.jsx +++ /dev/null @@ -1,205 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import { Helmet } from 'react-helmet'; - -import { getConfig } from '@edx/frontend-platform'; -import { sendPageEvent } from '@edx/frontend-platform/analytics'; -import { - ensureAuthenticatedUser, hydrateAuthenticatedUser, getAuthenticatedUser, -} from '@edx/frontend-platform/auth'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { - Form, - StatefulButton, - Hyperlink, -} from '@edx/paragon'; - -import messages from './messages'; - -import { EDUCATION_LEVELS, GENDER_OPTIONS, YEAR_OF_BIRTH_OPTIONS } from '../register/data/constants'; -import { AuthnValidationFormGroup } from '../common-components'; -import { DEFAULT_REDIRECT_URL } from '../data/constants'; - -const WelcomePage = (props) => { - const { intl } = props; - const [registrationResult, setRegistrationResult] = useState({}); - const [values, setValues] = useState({}); - const [ready, setReady] = useState(false); - const DASHBOARD_URL = getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL); - - useEffect(() => { - ensureAuthenticatedUser(DASHBOARD_URL).then(() => { - hydrateAuthenticatedUser().then(() => { - setReady(true); - }); - }); - - if (props.location.state && props.location.state.registrationResult) { - setRegistrationResult(props.location.state.registrationResult); - sendPageEvent('login_and_registration', 'welcome'); - } - }, []); - - const authenticatedUser = getAuthenticatedUser(); - - if (!props.location.state || !props.location.state.registrationResult) { - global.location.assign(DASHBOARD_URL); - return null; - } - - if (!ready) { - return null; - } - - const getOptions = () => ({ - yearOfBirthOptions: [{ - value: '', - label: intl.formatMessage(messages['registration.year.of.birth.label']), - }].concat(YEAR_OF_BIRTH_OPTIONS), - educationLevelOptions: EDUCATION_LEVELS.map(key => ({ - value: key, - label: intl.formatMessage(messages[`registration.field.education.levels.${key || 'label'}`]), - })), - genderOptions: GENDER_OPTIONS.map(key => ({ - value: key, - label: intl.formatMessage(messages[`registration.field.gender.options.${key || 'label'}`]), - })), - }); - - const fireOptimizelyEvent = () => { - window.optimizely = window.optimizely || []; - window.optimizely.push({ - type: 'event', - eventName: 'van_504_conversion_rate', - }); - }; - - const handleSubmit = (e) => { - e.preventDefault(); - fireOptimizelyEvent(); - window.optimizely = window.optimizely || []; - ['yearOfBirth', 'gender', 'levelOfEducation'].forEach(fieldName => { - if (values[fieldName]) { - window.optimizely.push({ - type: 'event', - eventName: `van_504_${fieldName}`, - }); - } - }); - if (registrationResult.success) { - window.location.href = registrationResult.redirectUrl; - } - return null; - }; - - const handleSkip = (e) => { - e.preventDefault(); - fireOptimizelyEvent(); - window.location.href = registrationResult.redirectUrl; - return null; - }; - - const onChangeHandler = (e) => { - setValues({ ...values, [e.target.name]: e.target.value }); - }; - - return ( - <> - - {intl.formatMessage(messages['optional.fields.page.title'], - { siteName: getConfig().SITE_NAME })} - - -
    -
    -
    -

    - { intl.formatMessage(messages['welcome.to.edx'], { username: authenticatedUser.username }) } -

    -
    -

    {intl.formatMessage(messages['optional.fields.page.heading'])}

    - onChangeHandler(e)} - selectOptions={getOptions().educationLevelOptions} - inputFieldStyle="border-gray-600" - /> - onChangeHandler(e)} - selectOptions={getOptions().yearOfBirthOptions} - inputFieldStyle="border-gray-600" - /> - onChangeHandler(e)} - selectOptions={getOptions().genderOptions} - inputFieldStyle="border-gray-600" - /> -

    - - {intl.formatMessage(messages['optional.fields.information.link'])} - -

    -
    - e.preventDefault()} - /> - e.preventDefault()} - /> -
    - -
    -
    - - ); -}; - -WelcomePage.propTypes = { - intl: intlShape.isRequired, - location: PropTypes.shape({ - state: PropTypes.object, - }), -}; - -WelcomePage.defaultProps = { - location: { state: {} }, -}; - -export default (injectIntl(WelcomePage)); diff --git a/src/legacy/welcome/index.js b/src/legacy/welcome/index.js deleted file mode 100644 index b031301e..00000000 --- a/src/legacy/welcome/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './WelcomePage'; diff --git a/src/legacy/welcome/messages.jsx b/src/legacy/welcome/messages.jsx deleted file mode 100644 index d3244f2a..00000000 --- a/src/legacy/welcome/messages.jsx +++ /dev/null @@ -1,110 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - 'optional.fields.page.title': { - id: 'optional.fields.page.title', - defaultMessage: 'Optional Fields | {siteName}', - description: 'optional fields page title', - }, - 'optional.fields.page.heading': { - id: 'optional.fields.page.heading', - defaultMessage: 'Support education research by providing additional information.', - description: 'The page heading for the optional fields page.', - }, - 'welcome.to.edx': { - id: 'welcome.to.edx', - defaultMessage: 'Welcome to edX, {username}!', - description: 'Welcome message on the optional fields page.', - }, - 'registration.field.gender.options.label': { - id: 'registration.field.gender.options.label', - defaultMessage: 'Gender (optional)', - description: 'Placeholder for the gender options dropdown', - }, - 'registration.field.gender.options.f': { - id: 'registration.field.gender.options.f', - defaultMessage: 'Female', - description: 'The label for the female gender option.', - }, - 'registration.field.gender.options.m': { - id: 'registration.field.gender.options.m', - defaultMessage: 'Male', - description: 'The label for the male gender option.', - }, - 'registration.field.gender.options.o': { - id: 'registration.field.gender.options.o', - defaultMessage: 'Other/Prefer not to say', - description: 'The label for catch-all gender option.', - }, - 'registration.field.education.levels.label': { - id: 'registration.field.education.levels.label', - defaultMessage: 'Highest level of education completed (optional)', - description: 'Placeholder for the education levels dropdown.', - }, - 'registration.field.education.levels.p': { - id: 'registration.field.education.levels.p', - defaultMessage: 'Doctorate', - description: 'Selected by the user if their highest level of education is a doctorate degree.', - }, - 'registration.field.education.levels.m': { - id: 'registration.field.education.levels.m', - defaultMessage: "Master's or professional degree", - description: "Selected by the user if their highest level of education is a master's or professional degree from a college or university.", - }, - 'registration.field.education.levels.b': { - id: 'registration.field.education.levels.b', - defaultMessage: "Bachelor's degree", - description: "Selected by the user if their highest level of education is a four year college or university bachelor's degree.", - }, - 'registration.field.education.levels.a': { - id: 'registration.field.education.levels.a', - defaultMessage: "Associate's degree", - description: "Selected by the user if their highest level of education is an associate's degree. 1-2 years of college or university.", - }, - 'registration.field.education.levels.hs': { - id: 'registration.field.education.levels.hs', - defaultMessage: 'Secondary/high school', - description: 'Selected by the user if their highest level of education is secondary or high school. 9-12 years of education.', - }, - 'registration.field.education.levels.jhs': { - id: 'registration.field.education.levels.jhs', - defaultMessage: 'Junior secondary/junior high/middle school', - description: 'Selected by the user if their highest level of education is junior or middle school. 6-8 years of education.', - }, - 'registration.field.education.levels.el': { - id: 'registration.field.education.levels.el', - defaultMessage: 'Elementary/primary school', - description: 'Selected by the user if their highest level of education is elementary or primary school. 1-5 years of education.', - }, - 'registration.field.education.levels.none': { - id: 'registration.field.education.levels.none', - defaultMessage: 'No formal education', - description: 'Selected by the user to describe their education.', - }, - 'registration.field.education.levels.other': { - id: 'registration.field.education.levels.other', - defaultMessage: 'Other education', - description: 'Selected by the user if they have a type of education not described by the other choices.', - }, - 'registration.year.of.birth.label': { - id: 'registration.year.of.birth.label', - defaultMessage: 'Year of birth (optional)', - description: 'Placeholder for the year of birth options dropdown', - }, - 'optional.fields.information.link': { - id: 'optional.fields.information.link', - defaultMessage: 'Learn more about how we use this information.', - description: 'Optional fields page information link', - }, - 'optional.fields.submit.button': { - id: 'optional.fields.submit.button', - defaultMessage: 'Submit', - description: 'Submit button text', - }, - 'optional.fields.skip.button': { - id: 'optional.fields.skip.button', - defaultMessage: 'Skip for now', - description: 'Skip button text', - }, -}); -export default messages; diff --git a/src/redesign/login/AccountActivationMessage.jsx b/src/login/AccountActivationMessage.jsx similarity index 100% rename from src/redesign/login/AccountActivationMessage.jsx rename to src/login/AccountActivationMessage.jsx diff --git a/src/redesign/login/LoginFailure.jsx b/src/login/LoginFailure.jsx similarity index 100% rename from src/redesign/login/LoginFailure.jsx rename to src/login/LoginFailure.jsx diff --git a/src/redesign/login/LoginPage.jsx b/src/login/LoginPage.jsx similarity index 100% rename from src/redesign/login/LoginPage.jsx rename to src/login/LoginPage.jsx diff --git a/src/redesign/login/data/actions.js b/src/login/data/actions.js similarity index 100% rename from src/redesign/login/data/actions.js rename to src/login/data/actions.js diff --git a/src/legacy/login/data/constants.js b/src/login/data/constants.js similarity index 100% rename from src/legacy/login/data/constants.js rename to src/login/data/constants.js diff --git a/src/redesign/login/data/reducers.js b/src/login/data/reducers.js similarity index 100% rename from src/redesign/login/data/reducers.js rename to src/login/data/reducers.js diff --git a/src/legacy/login/data/sagas.js b/src/login/data/sagas.js similarity index 100% rename from src/legacy/login/data/sagas.js rename to src/login/data/sagas.js diff --git a/src/legacy/login/data/selectors.js b/src/login/data/selectors.js similarity index 100% rename from src/legacy/login/data/selectors.js rename to src/login/data/selectors.js diff --git a/src/redesign/login/data/service.js b/src/login/data/service.js similarity index 100% rename from src/redesign/login/data/service.js rename to src/login/data/service.js diff --git a/src/legacy/login/data/tests/sagas.test.js b/src/login/data/tests/sagas.test.js similarity index 98% rename from src/legacy/login/data/tests/sagas.test.js rename to src/login/data/tests/sagas.test.js index a7f40180..377caf27 100644 --- a/src/legacy/login/data/tests/sagas.test.js +++ b/src/login/data/tests/sagas.test.js @@ -6,7 +6,7 @@ import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from '../constants'; import * as actions from '../actions'; import { handleLoginRequest } from '../sagas'; import * as api from '../service'; -import initializeMockLogging from '../../../../setupTest'; +import initializeMockLogging from '../../../setupTest'; const { loggingService } = initializeMockLogging(); diff --git a/src/legacy/login/index.js b/src/login/index.js similarity index 100% rename from src/legacy/login/index.js rename to src/login/index.js diff --git a/src/redesign/login/messages.jsx b/src/login/messages.jsx similarity index 100% rename from src/redesign/login/messages.jsx rename to src/login/messages.jsx diff --git a/src/legacy/login/tests/AccountActivationMessage.test.jsx b/src/login/tests/AccountActivationMessage.test.jsx similarity index 100% rename from src/legacy/login/tests/AccountActivationMessage.test.jsx rename to src/login/tests/AccountActivationMessage.test.jsx diff --git a/src/redesign/login/tests/LoginFailure.test.jsx b/src/login/tests/LoginFailure.test.jsx similarity index 100% rename from src/redesign/login/tests/LoginFailure.test.jsx rename to src/login/tests/LoginFailure.test.jsx diff --git a/src/redesign/login/tests/LoginPage.test.jsx b/src/login/tests/LoginPage.test.jsx similarity index 100% rename from src/redesign/login/tests/LoginPage.test.jsx rename to src/login/tests/LoginPage.test.jsx diff --git a/src/redesign/common-components/NotFoundPage.jsx b/src/redesign/common-components/NotFoundPage.jsx deleted file mode 100644 index 5386f9d1..00000000 --- a/src/redesign/common-components/NotFoundPage.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import { FormattedMessage } from '@edx/frontend-platform/i18n'; - -export default function NotFoundPage() { - return ( -
    -

    - -

    -
    - ); -} diff --git a/src/redesign/common-components/RedirectLogistration.jsx b/src/redesign/common-components/RedirectLogistration.jsx deleted file mode 100644 index 824d8872..00000000 --- a/src/redesign/common-components/RedirectLogistration.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; - -import PropTypes from 'prop-types'; -import { Redirect } from 'react-router-dom'; - -import { getConfig } from '@edx/frontend-platform'; - -import { WELCOME_PAGE } from '../data/constants'; -import { setCookie } from '../data/utils'; - -function RedirectLogistration(props) { - const { - finishAuthUrl, redirectUrl, redirectToWelcomePage, success, - } = props; - let finalRedirectUrl = ''; - - if (success) { - // If we're in a third party auth pipeline, we must complete the pipeline - // once user has successfully logged in. Otherwise, redirect to the specified redirect url. - // Note: For multiple enterprise use case, we need to make sure that user first visits the - // enterprise selection page and then complete the auth workflow - if (finishAuthUrl && !redirectUrl.includes(finishAuthUrl)) { - finalRedirectUrl = getConfig().LMS_BASE_URL + finishAuthUrl; - } else { - finalRedirectUrl = redirectUrl; - } - - if (redirectToWelcomePage) { - setCookie('van-504-returning-user', true); - // use this component to redirect WelcomePage after successful registration - // return ; - const registrationResult = { redirectUrl: finalRedirectUrl, success }; - return ; - } - - window.location.href = finalRedirectUrl; - } - return <>; -} - -RedirectLogistration.defaultProps = { - finishAuthUrl: null, - success: false, - redirectUrl: '', - redirectToWelcomePage: false, -}; - -RedirectLogistration.propTypes = { - finishAuthUrl: PropTypes.string, - success: PropTypes.bool, - redirectUrl: PropTypes.string, - redirectToWelcomePage: PropTypes.bool, -}; - -export default RedirectLogistration; diff --git a/src/redesign/common-components/RegisterFaIcons.jsx b/src/redesign/common-components/RegisterFaIcons.jsx deleted file mode 100644 index 870bbe6b..00000000 --- a/src/redesign/common-components/RegisterFaIcons.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { - faApple, faFacebook, faGoogle, faMicrosoft, -} from '@fortawesome/free-brands-svg-icons'; - -export default function registerIcons() { - library.add(faApple, faFacebook, faGoogle, faMicrosoft); -} diff --git a/src/redesign/common-components/SwitchContent.jsx b/src/redesign/common-components/SwitchContent.jsx deleted file mode 100644 index a4c13c74..00000000 --- a/src/redesign/common-components/SwitchContent.jsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { TransitionReplace } from '@edx/paragon'; - -const onChildExit = (htmlNode) => { - // If the leaving child has focus, take control and redirect it - if (htmlNode.contains(document.activeElement)) { - // Get the newly entering sibling. - // It's the previousSibling, but not for any explicit reason. So checking for both. - const enteringChild = htmlNode.previousSibling || htmlNode.nextSibling; - - // There's no replacement, do nothing. - if (!enteringChild) return; // eslint-disable-line curly - - // Get all the focusable elements in the entering child and focus the first one - const focusableElements = enteringChild.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); - if (focusableElements.length) { - focusableElements[0].focus(); - } - } -}; - -function SwitchContent({ expression, cases, className }) { - const getContent = (caseKey) => { - if (cases[caseKey]) { - if (typeof cases[caseKey] === 'string') { - return getContent(cases[caseKey]); - } - return React.cloneElement(cases[caseKey], { key: caseKey }); - } else if (cases.default) { // eslint-disable-line no-else-return - if (typeof cases.default === 'string') { - return getContent(cases.default); - } - React.cloneElement(cases.default, { key: 'default' }); - } - - return null; - }; - - return ( - - {getContent(expression)} - - ); -} - -SwitchContent.propTypes = { - expression: PropTypes.string, - cases: PropTypes.objectOf(PropTypes.node).isRequired, - className: PropTypes.string, -}; - -SwitchContent.defaultProps = { - expression: null, - className: null, -}; - -export default SwitchContent; diff --git a/src/redesign/common-components/UnAuthOnlyRoute.jsx b/src/redesign/common-components/UnAuthOnlyRoute.jsx deleted file mode 100644 index f136f4f3..00000000 --- a/src/redesign/common-components/UnAuthOnlyRoute.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { Route } from 'react-router-dom'; -import { AppContext } from '@edx/frontend-platform/react'; - -import { DEFAULT_REDIRECT_URL } from '../data/constants'; - -/** - * This wrapper redirects the requester to our default redirect url if they are - * already authenticated. - */ -const UnAuthOnlyRoute = (props) => { - const { authenticatedUser, config } = React.useContext(AppContext); - - if (authenticatedUser) { - global.location.href = config.LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL); - return null; - } - - return ; -}; - -export default UnAuthOnlyRoute; diff --git a/src/redesign/common-components/data/actions.js b/src/redesign/common-components/data/actions.js deleted file mode 100644 index 28a34982..00000000 --- a/src/redesign/common-components/data/actions.js +++ /dev/null @@ -1,22 +0,0 @@ -import { AsyncActionType } from '../../data/utils'; - -export const THIRD_PARTY_AUTH_CONTEXT = new AsyncActionType('THIRD_PARTY_AUTH', 'GET_THIRD_PARTY_AUTH_CONTEXT'); - -// Third party auth context -export const getThirdPartyAuthContext = (urlParams) => ({ - type: THIRD_PARTY_AUTH_CONTEXT.BASE, - payload: { urlParams }, -}); - -export const getThirdPartyAuthContextBegin = () => ({ - type: THIRD_PARTY_AUTH_CONTEXT.BEGIN, -}); - -export const getThirdPartyAuthContextSuccess = (thirdPartyAuthContext) => ({ - type: THIRD_PARTY_AUTH_CONTEXT.SUCCESS, - payload: { thirdPartyAuthContext }, -}); - -export const getThirdPartyAuthContextFailure = () => ({ - type: THIRD_PARTY_AUTH_CONTEXT.FAILURE, -}); diff --git a/src/redesign/common-components/data/reducers.js b/src/redesign/common-components/data/reducers.js deleted file mode 100644 index 6a944bd2..00000000 --- a/src/redesign/common-components/data/reducers.js +++ /dev/null @@ -1,32 +0,0 @@ -import { THIRD_PARTY_AUTH_CONTEXT } from './actions'; - -import { PENDING_STATE, COMPLETE_STATE } from '../../data/constants'; - -export const defaultState = { - thirdPartyAuthApiStatus: null, -}; - -const reducer = (state = defaultState, action) => { - switch (action.type) { - case THIRD_PARTY_AUTH_CONTEXT.BEGIN: - return { - ...state, - thirdPartyAuthApiStatus: PENDING_STATE, - }; - case THIRD_PARTY_AUTH_CONTEXT.SUCCESS: - return { - ...state, - thirdPartyAuthContext: action.payload.thirdPartyAuthContext, - thirdPartyAuthApiStatus: COMPLETE_STATE, - }; - case THIRD_PARTY_AUTH_CONTEXT.FAILURE: - return { - ...state, - thirdPartyAuthApiStatus: COMPLETE_STATE, - }; - default: - return state; - } -}; - -export default reducer; diff --git a/src/redesign/common-components/data/sagas.js b/src/redesign/common-components/data/sagas.js deleted file mode 100644 index d3ac7f03..00000000 --- a/src/redesign/common-components/data/sagas.js +++ /dev/null @@ -1,34 +0,0 @@ -import { call, put, takeEvery } from 'redux-saga/effects'; - -import { logError } from '@edx/frontend-platform/logging'; - -// Actions -import { - THIRD_PARTY_AUTH_CONTEXT, - getThirdPartyAuthContextBegin, - getThirdPartyAuthContextSuccess, - getThirdPartyAuthContextFailure, -} from './actions'; - -// Services -import { - getThirdPartyAuthContext, -} from './service'; - -export function* fetchThirdPartyAuthContext(action) { - try { - yield put(getThirdPartyAuthContextBegin()); - const { thirdPartyAuthContext } = yield call(getThirdPartyAuthContext, action.payload.urlParams); - - yield put(getThirdPartyAuthContextSuccess( - thirdPartyAuthContext, - )); - } catch (e) { - yield put(getThirdPartyAuthContextFailure()); - logError(e); - } -} - -export default function* saga() { - yield takeEvery(THIRD_PARTY_AUTH_CONTEXT.BASE, fetchThirdPartyAuthContext); -} diff --git a/src/redesign/common-components/data/selectors.js b/src/redesign/common-components/data/selectors.js deleted file mode 100644 index f5bf2173..00000000 --- a/src/redesign/common-components/data/selectors.js +++ /dev/null @@ -1,10 +0,0 @@ -import { createSelector } from 'reselect'; - -export const storeName = 'commonComponents'; - -export const commonComponentsSelector = state => ({ ...state[storeName] }); - -export const thirdPartyAuthContextSelector = createSelector( - commonComponentsSelector, - commonComponents => commonComponents.thirdPartyAuthContext, -); diff --git a/src/redesign/common-components/data/tests/sagas.test.js b/src/redesign/common-components/data/tests/sagas.test.js deleted file mode 100644 index ffbc33da..00000000 --- a/src/redesign/common-components/data/tests/sagas.test.js +++ /dev/null @@ -1,65 +0,0 @@ -import { runSaga } from 'redux-saga'; - -import * as actions from '../actions'; -import { fetchThirdPartyAuthContext } from '../sagas'; -import * as api from '../service'; -import initializeMockLogging from '../../../../setupTest'; - -const { loggingService } = initializeMockLogging(); - -describe('fetchThirdPartyAuthContext', () => { - const params = { - payload: { urlParams: {} }, - }; - - const data = { - currentProvider: null, - providers: [], - secondaryProviders: [], - finishAuthUrl: null, - pipelineUserDetails: {}, - }; - - beforeEach(() => { - loggingService.logError.mockReset(); - }); - - it('should call service and dispatch success action', async () => { - const getThirdPartyAuthContext = jest.spyOn(api, 'getThirdPartyAuthContext') - .mockImplementation(() => Promise.resolve({ thirdPartyAuthContext: data })); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - fetchThirdPartyAuthContext, - params, - ); - - expect(getThirdPartyAuthContext).toHaveBeenCalledTimes(1); - expect(dispatched).toEqual([ - actions.getThirdPartyAuthContextBegin(), - actions.getThirdPartyAuthContextSuccess(data), - ]); - getThirdPartyAuthContext.mockClear(); - }); - - it('should call service and dispatch error action', async () => { - const getThirdPartyAuthContext = jest.spyOn(api, 'getThirdPartyAuthContext') - .mockImplementation(() => Promise.reject(new Error('something went wrong'))); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - fetchThirdPartyAuthContext, - params, - ); - - expect(getThirdPartyAuthContext).toHaveBeenCalledTimes(1); - expect(loggingService.logError).toHaveBeenCalled(); - expect(dispatched).toEqual([ - actions.getThirdPartyAuthContextBegin(), - actions.getThirdPartyAuthContextFailure(), - ]); - getThirdPartyAuthContext.mockClear(); - }); -}); diff --git a/src/redesign/common-components/tests/SocialAuthProviders.test.jsx b/src/redesign/common-components/tests/SocialAuthProviders.test.jsx deleted file mode 100644 index 22aaf712..00000000 --- a/src/redesign/common-components/tests/SocialAuthProviders.test.jsx +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import renderer from 'react-test-renderer'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; - -import SocialAuthProviders from '../SocialAuthProviders'; -import registerIcons from '../RegisterFaIcons'; - -registerIcons(); - -describe('SocialAuthProviders', () => { - let props = {}; - - const appleProvider = { - id: 'oa2-apple-id', - name: 'Apple', - iconClass: null, - iconImage: 'https://edx.devstack.lms/logo.png', - loginUrl: '/auth/login/apple-id/?auth_entry=login&next=/dashboard', - }; - - const facebookProvider = { - id: 'oa2-facebook', - name: 'Facebook', - iconClass: null, - iconImage: 'https://edx.devstack.lms/facebook-logo.png', - loginUrl: '/auth/login/facebook/?auth_entry=login&next=/dashboard', - }; - - it('should match social auth provider with iconImage snapshot', () => { - props = { socialAuthProviders: [appleProvider, facebookProvider] }; - - const tree = renderer.create( - - - , - ).toJSON(); - - expect(tree).toMatchSnapshot(); - }); - - it('should match social auth provider with iconClass snapshot', () => { - props = { - socialAuthProviders: [{ - ...appleProvider, - iconClass: 'google', - iconImage: null, - }], - }; - - const tree = renderer.create( - - - , - ).toJSON(); - - expect(tree).toMatchSnapshot(); - }); - - it('should match social auth provider with default icon snapshot', () => { - props = { - socialAuthProviders: [{ - ...appleProvider, - iconClass: 'default', - iconImage: null, - }], - }; - - const tree = renderer.create( - - - , - ).toJSON(); - - expect(tree).toMatchSnapshot(); - }); -}); diff --git a/src/redesign/common-components/tests/UnAuthOnlyRoute.test.jsx b/src/redesign/common-components/tests/UnAuthOnlyRoute.test.jsx deleted file mode 100644 index 3b0ad0cb..00000000 --- a/src/redesign/common-components/tests/UnAuthOnlyRoute.test.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import { BrowserRouter as Router, MemoryRouter, Switch } from 'react-router-dom'; - -import { getConfig } from '@edx/frontend-platform'; - -import { UnAuthOnlyRoute } from '..'; -import { DEFAULT_REDIRECT_URL, LOGIN_PAGE } from '../../data/constants'; - -const RRD = require('react-router-dom'); -// Just render plain div with its children -// eslint-disable-next-line react/prop-types -RRD.BrowserRouter = ({ children }) =>
    { children }
    ; -module.exports = RRD; - -const TestApp = () => ( - -
    - - (Login Page)} /> - -
    -
    -); - -describe('UnAuthOnlyRoute', () => { - const routerWrapper = () => ( - - - - ); - - it('should redirect to dashboard if already logged in', () => { - const dashboardURL = getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL); - delete window.location; - window.location = { href: '' }; - const user = { - username: 'gonzo', - other: 'data', - }; - const mockUseContext = jest.fn().mockImplementation(() => ({ - authenticatedUser: user, - config: getConfig(), - })); - - React.useContext = mockUseContext; - mount(routerWrapper()); - - expect(window.location.href).toBe(dashboardURL); - }); - - it('should render test login components', () => { - const mockUseContext = jest.fn().mockImplementation(() => ({ - authenticatedUser: null, - config: {}, - })); - - React.useContext = mockUseContext; - const wrapper = mount(routerWrapper()); - - expect(wrapper.find('span').text()).toBe('Login Page'); - }); -}); diff --git a/src/redesign/data/configureStore.js b/src/redesign/data/configureStore.js deleted file mode 100644 index 16a19631..00000000 --- a/src/redesign/data/configureStore.js +++ /dev/null @@ -1,33 +0,0 @@ -import { getConfig } from '@edx/frontend-platform'; -import { applyMiddleware, createStore, compose } from 'redux'; -import thunkMiddleware from 'redux-thunk'; -import { composeWithDevTools } from 'redux-devtools-extension'; -import { createLogger } from 'redux-logger'; -import createSagaMiddleware from 'redux-saga'; - -import createRootReducer from './reducers'; -import rootSaga from './sagas'; - -const sagaMiddleware = createSagaMiddleware(); - -function composeMiddleware() { - if (getConfig().ENVIRONMENT === 'development') { - const loggerMiddleware = createLogger({ - collapsed: true, - }); - return composeWithDevTools(applyMiddleware(thunkMiddleware, sagaMiddleware, loggerMiddleware)); - } - - return compose(applyMiddleware(thunkMiddleware, sagaMiddleware)); -} - -export default function configureStore(initialState = {}) { - const store = createStore( - createRootReducer(), - initialState, - composeMiddleware(), - ); - sagaMiddleware.run(rootSaga); - - return store; -} diff --git a/src/redesign/data/utils/cookies.js b/src/redesign/data/utils/cookies.js deleted file mode 100644 index 369913c6..00000000 --- a/src/redesign/data/utils/cookies.js +++ /dev/null @@ -1,21 +0,0 @@ -import Cookies from 'universal-cookie'; -import { getConfig } from '@edx/frontend-platform'; - -export function setCookie(cookieName, cookieValue, cookieExpiry) { - const cookies = new Cookies(); - const options = { domain: getConfig().COOKIE_DOMAIN, path: '/' }; - if (cookieExpiry) { - options.expires = cookieExpiry; - } - cookies.set(cookieName, cookieValue, options); -} - -export default function setSurveyCookie(surveyType) { - const cookieName = getConfig().USER_SURVEY_COOKIE_NAME; - if (cookieName) { - const signupTimestamp = (new Date()).getTime(); - // set expiry to exactly 24 hours from now - const cookieExpiry = new Date(signupTimestamp + 1 * 864e5); - setCookie(cookieName, surveyType, cookieExpiry); - } -} diff --git a/src/redesign/data/utils/dataUtils.test.js b/src/redesign/data/utils/dataUtils.test.js deleted file mode 100644 index b2dd3c58..00000000 --- a/src/redesign/data/utils/dataUtils.test.js +++ /dev/null @@ -1,32 +0,0 @@ -import { LOGIN_PAGE } from '../constants'; -import processLink, { updatePathWithQueryParams } from './dataUtils'; - -describe('processLink', () => { - it('should use the provided processLink function to', () => { - const expectedHref = 'http://test.server.com/'; - const expectedText = 'test link'; - const link = `${expectedText}`; - - const matches = processLink(link); - - expect(matches[1]).toEqual(expectedHref); - expect(matches[2]).toEqual(expectedText); - }); -}); - -describe('updatePathWithQueryParams', () => { - it('should append query params into the path', () => { - const params = '?course_id=testCourseId'; - const expectedPath = `${LOGIN_PAGE}${params}`; - - Object.defineProperty(window, 'location', { - value: { - href: 'http://localhost/', - search: params, - }, - }); - const updatedPath = updatePathWithQueryParams(LOGIN_PAGE); - - expect(updatedPath).toEqual(expectedPath); - }); -}); diff --git a/src/redesign/data/utils/index.js b/src/redesign/data/utils/index.js deleted file mode 100644 index d452dc5d..00000000 --- a/src/redesign/data/utils/index.js +++ /dev/null @@ -1,11 +0,0 @@ -export { - default, - getTpaProvider, - getTpaHint, - updatePathWithQueryParams, - getAllPossibleQueryParam, - getActivationStatus, - windowScrollTo, -} from './dataUtils'; -export { default as AsyncActionType } from './reduxUtils'; -export { default as setSurveyCookie, setCookie } from './cookies'; diff --git a/src/redesign/data/utils/reduxUtils.js b/src/redesign/data/utils/reduxUtils.js deleted file mode 100644 index 45b0d762..00000000 --- a/src/redesign/data/utils/reduxUtils.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Helper class to save time when writing out action types for asynchronous methods. Also helps - * ensure that actions are namespaced. - */ -export default class AsyncActionType { - constructor(topic, name) { - this.topic = topic; - this.name = name; - } - - get BASE() { - return `${this.topic}__${this.name}`; - } - - get BEGIN() { - return `${this.topic}__${this.name}__BEGIN`; - } - - get SUCCESS() { - return `${this.topic}__${this.name}__SUCCESS`; - } - - get FAILURE() { - return `${this.topic}__${this.name}__FAILURE`; - } - - get RESET() { - return `${this.topic}__${this.name}__RESET`; - } - - get FORBIDDEN() { - return `${this.topic}__${this.name}__FORBIDDEN`; - } -} diff --git a/src/redesign/data/utils/reduxUtils.test.js b/src/redesign/data/utils/reduxUtils.test.js deleted file mode 100644 index 3e5a3d80..00000000 --- a/src/redesign/data/utils/reduxUtils.test.js +++ /dev/null @@ -1,14 +0,0 @@ -import AsyncActionType from './reduxUtils'; - -describe('AsyncActionType', () => { - it('should return well formatted action strings', () => { - const actionType = new AsyncActionType('HOUSE_CATS', 'START_THE_RACE'); - - expect(actionType.BASE).toBe('HOUSE_CATS__START_THE_RACE'); - expect(actionType.BEGIN).toBe('HOUSE_CATS__START_THE_RACE__BEGIN'); - expect(actionType.SUCCESS).toBe('HOUSE_CATS__START_THE_RACE__SUCCESS'); - expect(actionType.FAILURE).toBe('HOUSE_CATS__START_THE_RACE__FAILURE'); - expect(actionType.RESET).toBe('HOUSE_CATS__START_THE_RACE__RESET'); - expect(actionType.FORBIDDEN).toBe('HOUSE_CATS__START_THE_RACE__FORBIDDEN'); - }); -}); diff --git a/src/redesign/forgot-password/data/actions.js b/src/redesign/forgot-password/data/actions.js deleted file mode 100644 index dcdd871c..00000000 --- a/src/redesign/forgot-password/data/actions.js +++ /dev/null @@ -1,26 +0,0 @@ -import { AsyncActionType } from '../../data/utils'; - -export const FORGOT_PASSWORD = new AsyncActionType('FORGOT', 'PASSWORD'); - -// Forgot Password -export const forgotPassword = email => ({ - type: FORGOT_PASSWORD.BASE, - payload: { email }, -}); - -export const forgotPasswordBegin = () => ({ - type: FORGOT_PASSWORD.BEGIN, -}); - -export const forgotPasswordSuccess = email => ({ - type: FORGOT_PASSWORD.SUCCESS, - payload: { email }, -}); - -export const forgotPasswordForbidden = () => ({ - type: FORGOT_PASSWORD.FORBIDDEN, -}); - -export const forgotPasswordServerError = () => ({ - type: FORGOT_PASSWORD.FAILURE, -}); diff --git a/src/redesign/forgot-password/data/sagas.js b/src/redesign/forgot-password/data/sagas.js deleted file mode 100644 index 439f6bf0..00000000 --- a/src/redesign/forgot-password/data/sagas.js +++ /dev/null @@ -1,36 +0,0 @@ -import { logError, logInfo } from '@edx/frontend-platform/logging'; -import { call, put, takeEvery } from 'redux-saga/effects'; - -// Actions -import { - FORGOT_PASSWORD, - forgotPasswordBegin, - forgotPasswordSuccess, - forgotPasswordForbidden, - forgotPasswordServerError, -} from './actions'; - -import { forgotPassword } from './service'; - -// Services -export function* handleForgotPassword(action) { - try { - yield put(forgotPasswordBegin()); - - yield call(forgotPassword, action.payload.email); - - yield put(forgotPasswordSuccess(action.payload.email)); - } catch (e) { - if (e.response && e.response.status === 403) { - yield put(forgotPasswordForbidden()); - logInfo(e); - } else { - yield put(forgotPasswordServerError()); - logError(e); - } - } -} - -export default function* saga() { - yield takeEvery(FORGOT_PASSWORD.BASE, handleForgotPassword); -} diff --git a/src/redesign/forgot-password/data/selectors.js b/src/redesign/forgot-password/data/selectors.js deleted file mode 100644 index dbb3f10e..00000000 --- a/src/redesign/forgot-password/data/selectors.js +++ /dev/null @@ -1,10 +0,0 @@ -import { createSelector } from 'reselect'; - -export const storeName = 'forgotPassword'; - -export const forgotPasswordSelector = state => ({ ...state[storeName] }); - -export const forgotPasswordResultSelector = createSelector( - forgotPasswordSelector, - forgotPassword => forgotPassword, -); diff --git a/src/redesign/forgot-password/data/service.js b/src/redesign/forgot-password/data/service.js deleted file mode 100644 index 25020c56..00000000 --- a/src/redesign/forgot-password/data/service.js +++ /dev/null @@ -1,23 +0,0 @@ -import { getConfig } from '@edx/frontend-platform'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import formurlencoded from 'form-urlencoded'; - -// eslint-disable-next-line import/prefer-default-export -export async function forgotPassword(email) { - const requestConfig = { - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - isPublic: true, - }; - - const { data } = await getAuthenticatedHttpClient() - .post( - `${getConfig().LMS_BASE_URL}/account/password`, - formurlencoded({ email }), - requestConfig, - ) - .catch((e) => { - throw (e); - }); - - return data; -} diff --git a/src/redesign/forgot-password/data/tests/sagas.test.js b/src/redesign/forgot-password/data/tests/sagas.test.js deleted file mode 100644 index 9e4de10e..00000000 --- a/src/redesign/forgot-password/data/tests/sagas.test.js +++ /dev/null @@ -1,67 +0,0 @@ -import { runSaga } from 'redux-saga'; - -import * as actions from '../actions'; -import { handleForgotPassword } from '../sagas'; -import * as api from '../service'; -import initializeMockLogging from '../../../../setupTest'; - -const { loggingService } = initializeMockLogging(); - -describe('handleForgotPassword', () => { - const params = { - payload: { - formData: { - email: 'test@test.com', - }, - }, - }; - - beforeEach(() => { - loggingService.logError.mockReset(); - loggingService.logInfo.mockReset(); - }); - - it('should handle 500 error code', async () => { - const passwordErrorResponse = { response: { status: 500 } }; - - const forgotPasswordRequest = jest.spyOn(api, 'forgotPassword').mockImplementation( - () => Promise.reject(passwordErrorResponse), - ); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - handleForgotPassword, - params, - ); - - expect(loggingService.logError).toHaveBeenCalled(); - expect(dispatched).toEqual([ - actions.forgotPasswordBegin(), - actions.forgotPasswordServerError(), - ]); - forgotPasswordRequest.mockClear(); - }); - - it('should handle rate limit error', async () => { - const forbiddenErrorResponse = { response: { status: 403 } }; - - const forbiddenPasswordRequest = jest.spyOn(api, 'forgotPassword').mockImplementation( - () => Promise.reject(forbiddenErrorResponse), - ); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - handleForgotPassword, - params, - ); - - expect(loggingService.logInfo).toHaveBeenCalled(); - expect(dispatched).toEqual([ - actions.forgotPasswordBegin(), - actions.forgotPasswordForbidden(null), - ]); - forbiddenPasswordRequest.mockClear(); - }); -}); diff --git a/src/redesign/forgot-password/index.js b/src/redesign/forgot-password/index.js deleted file mode 100644 index 2d30ff06..00000000 --- a/src/redesign/forgot-password/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export { default } from './ForgotPasswordPage'; -export { default as reducer } from './data/reducers'; -export { FORGOT_PASSWORD } from './data/actions'; -export { default as saga } from './data/sagas'; -export { storeName, forgotPasswordResultSelector } from './data/selectors'; diff --git a/src/redesign/i18n/index.jsx b/src/redesign/i18n/index.jsx deleted file mode 100644 index 57b2ee41..00000000 --- a/src/redesign/i18n/index.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import arMessages from './messages/ar.json'; -import caMessages from './messages/ca.json'; -// no need to import en messages-- they are in the defaultMessage field -import es419Messages from './messages/es_419.json'; -import frMessages from './messages/fr.json'; -import zhcnMessages from './messages/zh_CN.json'; -import heMessages from './messages/he.json'; -import idMessages from './messages/id.json'; -import kokrMessages from './messages/ko_kr.json'; -import plMessages from './messages/pl.json'; -import ptbrMessages from './messages/pt_br.json'; -import ruMessages from './messages/ru.json'; -import thMessages from './messages/th.json'; -import ukMessages from './messages/uk.json'; - -const messages = { - ar: arMessages, - es: es419Messages, // Prospectus uses es language code for spanish, added `es` option and pointed to es-419 strings. - 'es-419': es419Messages, - fr: frMessages, - 'zh-cn': zhcnMessages, - ca: caMessages, - he: heMessages, - id: idMessages, - 'ko-kr': kokrMessages, - pl: plMessages, - 'pt-br': ptbrMessages, - ru: ruMessages, - th: thMessages, - uk: ukMessages, -}; - -export default messages; diff --git a/src/redesign/i18n/messages/ar.json b/src/redesign/i18n/messages/ar.json deleted file mode 100644 index 804d7611..00000000 --- a/src/redesign/i18n/messages/ar.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "forgot.password.confirmation.message": "You entered {strongEmail}. If this email address is associated with your\n edX account, we will send a message with password recovery instructions to this email address.", - "forgot.password.technical.support.help.message": "If you need further assistance, {technicalSupportLink}.", - "institution.login.page.sub.heading": "Choose your institution from the list below:", - "forgot.password.confirmation.title": "Check your email", - "forgot.password.confirmation.support.link": "contact technical support", - "forgot.password.confirmation.info": "If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder.", - "internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.", - "server.ratelimit.error.message": "An error has occurred because of too many requests. Please try again after some time.", - "enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?", - "enterprisetpa.sso.button.title": "Sign in using {providerName}", - "enterprisetpa.login.button.text": "Show me other ways to sign in or register", - "sso.sign.in.with": "Sign in with {providerName}", - "sso.create.account.using": "Create account using {providerName}", - "error.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في نص الرابط. الرجاء التحقق من الرابط والمحاولة مجددا.", - "login.third.party.auth.account.not.linked.message": "لقد نجحت في تسجيل الدخول إلى {currentProvider}، ولكن حساب {currentProvider} لا يحتوي على حساب {platformName} مرتبط. لربط حساباتك، قم بتسجيل الدخول الآن باستخدام كلمة مرور {platformName}.", - "register.third.party.auth.account.not.linked.message": "لقد نجحت في تسجيل الدخول إلى {currentProvider}. نحتاج إلى القليل من المعلومات قبل بدء التعلّم مع {platformName}.", - "forgot.password.page.title": "Forgot Password | {siteName}", - "forgot.password.page.heading": "Password assistance", - "forgot.password.page.instructions": "Please enter your log-in or recovery email address below and we will send you an email with instructions.", - "forgot.password.page.invalid.email.message": "The email address you've provided isn't formatted correctly.", - "forgot.password.page.email.field.label": "Email", - "forgot.password.page.submit.button": "Recover my password", - "forgot.password.request.server.error": "We couldn’t send the password recovery email.", - "forgot.password.error.message.title": "An error occurred.", - "forgot.password.request.in.progress.message": "Your previous request is in progress, please try again in a few moments.", - "forgot.password.empty.email.field.error": "Please enter your email.", - "forgot.password.invalid.email": "An error occurred.", - "forgot.password.invalid.email.message": "The email address you've provided isn't formatted correctly.", - "forgot.password.email.help.text": "The email address you used to register with {platformName}", - "account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.", - "non.compliant.password.error": "{passwordComplaintRequirements} {lineBreak}Your current password does not meet the new security\n requirements. We just sent a password-reset message to the email address associated with this account.\n Thank you for helping us keep your data safe.", - "login.inactive.user.error": "أنت بحاجة لتنشيط حسابك من أجل تسجيل الدخول {lineBreak}\n{lineBreak}لقد أرسلنا للتو رابط التفعيل إلى البريد الإلكتروني {email}. تحقق من مجلدات الرسائل غير المرغوب فيها أو {supportLink} إذا لم تستلم بريدًا إلكترونيًا.", - "login.reset.password.message.with.link": "If you've forgotten your password, click {resetLink} to reset.", - "login.locked.reset.password.message.with.link": "To be on the safe side, you can reset your password {resetLink} before you try again.", - "login.page.title": "Login | {siteName}", - "sign.in.button": "Sign in", - "need.help.signing.in.collapsible.menu": "Need help signing in?", - "forgot.password.link": "Forgot my password", - "other.sign.in.issues": "Other sign in issues", - "need.other.help.signing.in.collapsible.menu": "Need other help signing in?", - "institution.login.button": "Use my university info", - "institution.login.page.title": "Sign in with institution/campus credentials", - "institution.login.page.back.button": "Back to sign in", - "create.an.account": "Create an account", - "or.sign.in.with": "or sign in with", - "non.compliant.password.title": "We recently changed our password requirements", - "first.time.here": "First time here?", - "email.label": "Email", - "email.help.message": "The email address you used to register with edX.", - "enterprise.login.link.text": "Sign in with your company or school", - "email.format.validation.message": "The email address you've provided isn't formatted correctly.", - "email.format.validation.less.chars.message": "Email must have at least 3 characters.", - "email.validation.message": "Please enter your email.", - "password.validation.message": "Please enter your password.", - "password.label": "Password (required)", - "register.link": "Create an account", - "sign.in.heading": "Sign in", - "account.activation.success.message.title": "Success! You have activated your account.", - "account.activation.success.message": "You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.", - "account.already.activated.message": "This account has already been activated.", - "account.activation.error.message.title": "Your account could not be activated", - "account.activation.support.link": "contact support", - "login.rate.limit.reached.message": "Too many failed login attempts. Try again later.", - "login.failure.header.title": "We couldn't sign you in.", - "contact.support.link": "contact {platformName} support", - "login.failed.link.text": "here", - "login.incorrect.credentials.error": "Email or password is incorrect.", - "login.failed.attempt.error": "You have {remainingAttempts} more sign in attempts before your account is temporarily locked.", - "login.locked.out.error.message": "To protect your account, it’s been temporarily locked. Try again in {lockedOutPeriod} minutes.", - "register.page.title": "Register | {siteName}", - "create.account.button": "Create account", - "already.have.an.edx.account": "Already have an edX account?", - "sign.in.hyperlink": "Sign in.", - "create.an.account.using": "or create an account using", - "create.a.new.account": "Create a new account", - "register.institution.login.button": "Use my institution/campus credentials", - "register.institution.login.page.title": "Register with institution/campus credentials", - "register.page.email.label": "Email (required)", - "register.rate.limit.reached.message": "Too many failed registration attempts. Try again later.", - "email.ratelimit.less.chars.validation.message": "Email must have 3 characters.", - "email.ratelimit.incorrect.format.validation.message": "The email address you provided isn't formatted correctly.", - "email.ratelimit.password.validation.message": "Your password must contain at least 8 characters", - "register.page.password.validation.message": "Please enter your password.", - "fullname.label": "Full name (required)", - "fullname.validation.message": "Please enter your full name.", - "username.label": "Public username (required)", - "username.validation.message": "Please enter your public username.", - "username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-).", - "username.character.validation.message": "Your password must contain at least 1 letter.", - "username.number.validation.message": "Your password must contain at least 1 number.", - "username.ratelimit.less.chars.message": "Public username must have atleast 2 characters.", - "country.validation.message": "Select your country or region of residence.", - "support.education.research": "Support education research by providing additional information. (Optional)", - "registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.", - "registration.request.failure.header": "We couldn't create your account.", - "helptext.name": "This name will be used by any certificates that you earn.", - "helptext.username": "The name that will identify you in your courses. It cannot be changed later.", - "helptext.password": "Your password must contain at least 8 characters, including 1 letter & 1 number.", - "helptext.email": "This is what you will use to login.", - "terms.of.service.and.honor.code": "Terms of Service and Honor Code", - "privacy.policy": "Privacy Policy", - "registration.year.of.birth.label": "Year of birth (optional)", - "registration.country.label": "Country or region of residence (required)", - "registration.field.gender.options.label": "Gender (optional)", - "registration.goals.label": "Tell us why you're interested in edX (optional)", - "registration.field.gender.options.f": "Female", - "registration.field.gender.options.m": "Male", - "registration.field.gender.options.o": "Other/Prefer not to say", - "registration.field.education.levels.label": "Highest level of education completed (optional)", - "registration.field.education.levels.p": "Doctorate", - "registration.field.education.levels.m": "Master's or professional degree", - "registration.field.education.levels.b": "Bachelor's degree", - "registration.field.education.levels.a": "Associate's degree", - "registration.field.education.levels.hs": "Secondary/high school", - "registration.field.education.levels.jhs": "Junior secondary/junior high/middle school", - "registration.field.education.levels.el": "Elementary/primary school", - "registration.field.education.levels.none": "No formal education", - "registration.field.education.levels.other": "Other education", - "register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.", - "reset.password.request.invalid.token.description.message": "This password reset link is invalid. It may have been used already.\n To reset your password, go to the {loginPasswordLink} page and select {forgotPassword}", - "reset.password.page.title": "Reset Password | {siteName}", - "reset.password.page.heading": "Reset your password", - "reset.password.page.instructions": "Enter and confirm your new password.", - "reset.password.page.invalid.match.message": "Passwords do not match.", - "forgot.password.page.new.field.label": "New password", - "forgot.password.page.confirm.field.label": "Confirm password", - "reset.password.page.submit.button": "Reset my password", - "reset.password.request.success.header.message": "Password reset complete.", - "forgot.password.confirmation.sign.in.link": "sign in", - "reset.password.request.forgot.password.text": "Forgot password", - "reset.password.request.invalid.token.header": "Invalid password reset link", - "reset.password.empty.new.password.field.error": "Please enter your new password.", - "forgot.password.empty.new.password.error.heading": "We couldn't reset your password.", - "reset.password.request.server.error": "Failed to reset password", - "reset.password.token.validation.sever.error": "Token validation failure", - "reset.server.ratelimit.error": "Too many requests.", - "reset.password.confirmation.support.link": "Sign in to your account.", - "reset.password.request.success.header.description.message": "Your password has been reset. {loginPasswordLink}", - "optional.fields.page.title": "Optional Fields | {siteName}", - "optional.fields.page.heading": "Support education research by providing additional information.", - "welcome.to.edx": "Welcome to edX, {username}!", - "optional.fields.information.link": "Learn more about how we use this information.", - "optional.fields.submit.button": "Submit", - "optional.fields.skip.button": "Skip for now" -} \ No newline at end of file diff --git a/src/redesign/i18n/messages/ca.json b/src/redesign/i18n/messages/ca.json deleted file mode 100644 index 9e26dfee..00000000 --- a/src/redesign/i18n/messages/ca.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/src/redesign/i18n/messages/es_419.json b/src/redesign/i18n/messages/es_419.json deleted file mode 100644 index d92ad83d..00000000 --- a/src/redesign/i18n/messages/es_419.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "forgot.password.confirmation.message": "Introduciste {strongEmail}. Si esta dirección de correo electrónico está asociada con tu\n cuenta de edX, enviaremos un mensaje con instrucciones para recuperar tu contraseña a ese correo.", - "forgot.password.technical.support.help.message": "Si necesitas ayuda adicional, {technicalSupportLink}.", - "institution.login.page.sub.heading": "Elige tu institución en la siguiente lista:", - "forgot.password.confirmation.title": "Verifica tu correo electrónico", - "forgot.password.confirmation.support.link": "contacta con el equipo de soporte técnico", - "forgot.password.confirmation.info": "Si no recibes un mensaje de recuperación de tu contraseña en un minuto, verifica que introduciste la dirección de correo electrónico correcta, o verifica tu carpeta de correo no deseado.", - "internal.server.error.message": "Se ha producido un error. Intenta actualizar la página o comprueba tu conexión a Internet.", - "server.ratelimit.error.message": "Se ha producido un error debido a demasiadas solicitudes. Por favor, inténtalo de nuevo después de algún tiempo.", - "enterprisetpa.title.heading": "¿Deseas iniciar sesión con tus credenciales de {providerName}?", - "enterprisetpa.sso.button.title": "Inicio de sesión con {providerName}", - "enterprisetpa.login.button.text": "Mostrar otras formas de iniciar sesión o de registrarme", - "sso.sign.in.with": "Inicio de sesión con {providerName}", - "sso.create.account.using": "Crear una cuenta con {providerName}", - "error.notfound.message": "La página que estas buscando no está disponible o hay un error en la URL. Por favor, verifica la URL y vuelve a intentarlo.", - "login.third.party.auth.account.not.linked.message": "Te has registrado correctamente en {currentProvider}, pero tu cuenta de {currentProvider} no tiene una cuenta de {platformName} asociada. Para asociar tus cuentas, inicia sesión ahora usando tu contraseña de {platformName}.", - "register.third.party.auth.account.not.linked.message": "Has iniciado sesión correctamente en {currentProvider}. Solo necesitamos un poco más de información para que puedas empezar a aprender con {platformName}.", - "forgot.password.page.title": "Olvidé la contraseña | {siteName}", - "forgot.password.page.heading": "Ayuda con la contraseña", - "forgot.password.page.instructions": "Por favor introduce tu dirección de correo electrónico de inicio de sesión o de recuperación a continuación y te enviaremos un correo electrónico con instrucciones.", - "forgot.password.page.invalid.email.message": "La dirección de correo que has introducido no está en el formato correcto.", - "forgot.password.page.email.field.label": "Correo electrónico", - "forgot.password.page.submit.button": "Recuperar mi contraseña", - "forgot.password.request.server.error": "No hemos podido enviar el correo electrónico de recuperación de la contraseña.", - "forgot.password.error.message.title": "Ha ocurrido un error.", - "forgot.password.request.in.progress.message": "Su solicitud anterior está en progreso, por favor inténtalo de nuevo en unos minutos.", - "forgot.password.empty.email.field.error": "Por favor, introduce tu correo electrónico.", - "forgot.password.invalid.email": "Ha ocurrido un error.", - "forgot.password.invalid.email.message": "La dirección de correo que has ingresado no está en el formato correcto.", - "forgot.password.email.help.text": "El correo electrónico que utilizaste para registrarte en {platformName}", - "account.activation.error.message": "Algo no funcionó correctamente, por favor {supportLink} para resolver este problema.", - "non.compliant.password.error": "{passwordComplaintRequirements} {lineBreak}Tu contraseña actual no cumple con los nuevos requisitos\n de seguridad. Acabamos de enviar un mensaje de actualización de contraseña al correo electrónico asociado con esta cuenta.\n Gracias por ayudarnos a mantener tus datos seguros.", - "login.inactive.user.error": "Para iniciar sesión, debes activar tu cuenta..{lineBreak}\n {lineBreak} Acabamos de enviar un enlace de activación a {email}. Si no recibes un correo electrónico,\n revisa tus carpetas de spam o {supportLink}.", - "login.reset.password.message.with.link": "Si has olvidado tu contraseña, haz clic en {resetLink} para restablecerla.", - "login.locked.reset.password.message.with.link": "Para estar seguro, puedes restablecer tu contraseña {resetLink} antes de volver a intentarlo.", - "login.page.title": "Login | {siteName}", - "sign.in.button": "Iniciar sesión", - "need.help.signing.in.collapsible.menu": "¿Necesitas ayuda para iniciar sesión?", - "forgot.password.link": "Olvidé mi contraseña", - "other.sign.in.issues": "Otros problemas de inicio de sesión ", - "need.other.help.signing.in.collapsible.menu": "¿Necesitas más ayuda para iniciar sesión?", - "institution.login.button": "Usar información de mi universidad", - "institution.login.page.title": "Iniciar sesión con las credenciales de la institución/campus", - "institution.login.page.back.button": "Volver al inicio", - "create.an.account": "Crear una cuenta", - "or.sign.in.with": "o inicie sesión con", - "non.compliant.password.title": "Recientemente hemos cambiado los requisitos de las contraseñas", - "first.time.here": "Primera vez aquí?", - "email.label": "Correo electrónico", - "email.help.message": "La dirección de correo electrónico que usaste para registrarte en edX.", - "enterprise.login.link.text": "Iniciar sesión con tu compañía o universidad", - "email.format.validation.message": "La dirección de correo que has ingresado no está en el formato correcto.", - "email.format.validation.less.chars.message": "El correo electrónico debe tener al menos 3 caracteres.", - "email.validation.message": "Por favor, introduce tu correo electrónico.", - "password.validation.message": "Por favor, introduzca tu contraseña.", - "password.label": "Contraseña (obligatorio)", - "register.link": "Crear una cuenta", - "sign.in.heading": "Iniciar sesión", - "account.activation.success.message.title": "Ha sido un éxito. Has activado tu cuenta.", - "account.activation.success.message": "Ahora recibirás por correo electrónico actualizaciones y alertas relacionadas con los cursos en los que estás inscrito. Inicia sesión para continuar.", - "account.already.activated.message": "La cuenta ya ha sido activada.", - "account.activation.error.message.title": "Tu cuenta no ha podido ser activada", - "account.activation.support.link": "contacta al equipo de soporte de edX", - "login.rate.limit.reached.message": "Demasiados intentos fallidos de inicio de sesión. Inténtelo de nuevo más tarde.", - "login.failure.header.title": "No se ha podido iniciar tu sesión.", - "contact.support.link": "entrar en contacto con el soporte de {platformName}", - "login.failed.link.text": "aquí", - "login.incorrect.credentials.error": "Correo electrónico o contraseña incorrectos.", - "login.failed.attempt.error": "Tienes {remainAttempts} más intentos de inicio de sesión antes de que tu cuenta se bloquee temporalmente.", - "login.locked.out.error.message": "Para proteger tu cuenta, se ha bloqueado temporalmente. Inténtalo de nuevo en {lockedOutPeriod} minutos.", - "register.page.title": "Register | {siteName}", - "create.account.button": "Crear cuenta", - "already.have.an.edx.account": "¿Ya tienes una cuenta en edX?", - "sign.in.hyperlink": "Iniciar sesión ", - "create.an.account.using": "o crea una cuenta con", - "create.a.new.account": "Crear una nueva cuenta", - "register.institution.login.button": "Usar mis credenciales de la institución o el Campus", - "register.institution.login.page.title": "Registro con credenciales de la institución/campus", - "register.page.email.label": "Correo electrónico (obligatorio)", - "register.rate.limit.reached.message": "Demasiados intentos de registro fallidos. Vuelva a intentarlo más tarde.", - "email.ratelimit.less.chars.validation.message": "El correo electrónico debe tener 3 caracteres.", - "email.ratelimit.incorrect.format.validation.message": "La dirección de correo electrónico que has proporcionado no tiene el formato correcto.", - "email.ratelimit.password.validation.message": "Tu contraseña debe contener al menos 8 caracteres", - "register.page.password.validation.message": "Por favor, introduzca tu contraseña.", - "fullname.label": "Nombre completo (obligatorio) ", - "fullname.validation.message": "Por favor, introduce tu nombre completo.", - "username.label": "Nombre de usuario público (obligatorio)", - "username.validation.message": "Por favor, introduce tu nombre de usuario público.", - "username.format.validation.message": "Los nombres de usuario únicamente pueden contener las letras (A-Z, a-z), números (0-9), guión bajo (_) y guiones (-).", - "username.character.validation.message": "Tu contraseña debe contener al menos una letra.", - "username.number.validation.message": "Tu contraseña debe contener al menos un número.", - "username.ratelimit.less.chars.message": "El nombre de usuario público debe tener al menos 2 caracteres.", - "country.validation.message": "Selecciona tu país o región de residencia. ", - "support.education.research": "Apoya la investigación sobre educación proporcionando información adicional. (Opcional)", - "registration.request.server.error": "Se ha producido un error. Intenta actualizar la página o comprueba tu conexión a Internet.", - "registration.request.failure.header": "No pudimos crear tu cuenta.", - "helptext.name": "Este nombre será utilizado por los certificados que obtengas.", - "helptext.username": "El nombre bajo el cual te presentarás en tus cursos no puede ser cambiado después.", - "helptext.password": "Tu contraseña debe contener al menos 8 caracteres, incluyendo 1 letra y 1 número.", - "helptext.email": "Esto es lo que usará para iniciar sesión.", - "terms.of.service.and.honor.code": "Condiciones de servicio y código de honor", - "privacy.policy": "Política de privacidad ", - "registration.year.of.birth.label": "Año de nacimiento (opcional)", - "registration.country.label": "País o región de residencia (obligatorio)", - "registration.field.gender.options.label": "Género (opcional)", - "registration.goals.label": "Díganos por qué estás interesado en edX (opcional)", - "registration.field.gender.options.f": "Femenino ", - "registration.field.gender.options.m": "Masculino", - "registration.field.gender.options.o": "Otro/Prefiero no decir", - "registration.field.education.levels.label": "Nivel más alto de educación completado (opcional)", - "registration.field.education.levels.p": "Doctorado", - "registration.field.education.levels.m": "Maestría o magíster", - "registration.field.education.levels.b": "Pregrado o Licenciatura", - "registration.field.education.levels.a": "Grado técnico - tecnológico", - "registration.field.education.levels.hs": "Enseñanza secundaria", - "registration.field.education.levels.jhs": "Formación media", - "registration.field.education.levels.el": "Enseñanza primaria", - "registration.field.education.levels.none": "Ninguna educación formal", - "registration.field.education.levels.other": "Otra educación", - "register.page.terms.of.service.and.honor.code": "Al crear una cuenta, aceptas el {tosAndHonorCode} y reconoces que {platformName} y cada\n Miembro procesa tus datos personales de acuerdo con la {privacyPolicy}.", - "reset.password.request.invalid.token.description.message": "Este enlace para restablecer la contraseña no es válido. Es posible que ya haya sido utilizado.\n Para restablecer tu contraseña, ve a la página {loginPasswordLink} y selecciona {forgotPassword}", - "reset.password.page.title": "Restablecer contraseña | {siteName}", - "reset.password.page.heading": "Restablece tu contraseña", - "reset.password.page.instructions": "Ingresa y confirma tu nueva contraseña.", - "reset.password.page.invalid.match.message": "Las contraseñas no son iguales.", - "forgot.password.page.new.field.label": "Nueva contraseña", - "forgot.password.page.confirm.field.label": "Confirmar contraseña", - "reset.password.page.submit.button": "Restablecer mi contraseña", - "reset.password.request.success.header.message": "Restablecimiento de la contraseña completado.", - "forgot.password.confirmation.sign.in.link": "iniciar sesión", - "reset.password.request.forgot.password.text": "Olvidé mi contraseña", - "reset.password.request.invalid.token.header": "Enlace de restablecimiento de contraseña inválido", - "reset.password.empty.new.password.field.error": "Por favor, introduce tu nueva contraseña.", - "forgot.password.empty.new.password.error.heading": "No hemos podido restablecer tu contraseña.", - "reset.password.request.server.error": "No se ha podido restablecer la contraseña", - "reset.password.token.validation.sever.error": "Fallo de validación del token", - "reset.server.ratelimit.error": "Demasiadas solicitudes.", - "reset.password.confirmation.support.link": "Inicia sesión en tu cuenta.", - "reset.password.request.success.header.description.message": "Su contraseña ha sido restablecida. {loginPasswordLink}", - "optional.fields.page.title": "Optional Fields | {siteName}", - "optional.fields.page.heading": "Support education research by providing additional information.", - "welcome.to.edx": "Welcome to edX, {username}!", - "optional.fields.information.link": "Learn more about how we use this information.", - "optional.fields.submit.button": "Submit", - "optional.fields.skip.button": "Skip for now" -} \ No newline at end of file diff --git a/src/redesign/i18n/messages/fr.json b/src/redesign/i18n/messages/fr.json deleted file mode 100644 index a2d67e72..00000000 --- a/src/redesign/i18n/messages/fr.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "forgot.password.confirmation.message": "You entered {strongEmail}. If this email address is associated with your\n edX account, we will send a message with password recovery instructions to this email address.", - "forgot.password.technical.support.help.message": "If you need further assistance, {technicalSupportLink}.", - "institution.login.page.sub.heading": "Choose your institution from the list below:", - "forgot.password.confirmation.title": "Check your email", - "forgot.password.confirmation.support.link": "contact technical support", - "forgot.password.confirmation.info": "If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder.", - "internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.", - "server.ratelimit.error.message": "An error has occurred because of too many requests. Please try again after some time.", - "enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?", - "enterprisetpa.sso.button.title": "Sign in using {providerName}", - "enterprisetpa.login.button.text": "Show me other ways to sign in or register", - "sso.sign.in.with": "Sign in with {providerName}", - "sso.create.account.using": "Create account using {providerName}", - "error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.", - "login.third.party.auth.account.not.linked.message": "You have successfully signed into {currentProvider}, but your {currentProvider} account does not have a linked {platformName} account. To link your accounts, sign in now using your {platformName} password.", - "register.third.party.auth.account.not.linked.message": "You've successfully signed into {currentProvider}. We just need a little more information before you start learning with {platformName}.", - "forgot.password.page.title": "Forgot Password | {siteName}", - "forgot.password.page.heading": "Password assistance", - "forgot.password.page.instructions": "Please enter your log-in or recovery email address below and we will send you an email with instructions.", - "forgot.password.page.invalid.email.message": "The email address you've provided isn't formatted correctly.", - "forgot.password.page.email.field.label": "Email", - "forgot.password.page.submit.button": "Recover my password", - "forgot.password.request.server.error": "We couldn’t send the password recovery email.", - "forgot.password.error.message.title": "An error occurred.", - "forgot.password.request.in.progress.message": "Your previous request is in progress, please try again in a few moments.", - "forgot.password.empty.email.field.error": "Please enter your email.", - "forgot.password.invalid.email": "An error occurred.", - "forgot.password.invalid.email.message": "The email address you've provided isn't formatted correctly.", - "forgot.password.email.help.text": "The email address you used to register with {platformName}", - "account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.", - "non.compliant.password.error": "{passwordComplaintRequirements} {lineBreak}Your current password does not meet the new security\n requirements. We just sent a password-reset message to the email address associated with this account.\n Thank you for helping us keep your data safe.", - "login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.", - "login.reset.password.message.with.link": "If you've forgotten your password, click {resetLink} to reset.", - "login.locked.reset.password.message.with.link": "To be on the safe side, you can reset your password {resetLink} before you try again.", - "login.page.title": "Login | {siteName}", - "sign.in.button": "Sign in", - "need.help.signing.in.collapsible.menu": "Need help signing in?", - "forgot.password.link": "Forgot my password", - "other.sign.in.issues": "Other sign in issues", - "need.other.help.signing.in.collapsible.menu": "Need other help signing in?", - "institution.login.button": "Use my university info", - "institution.login.page.title": "Sign in with institution/campus credentials", - "institution.login.page.back.button": "Back to sign in", - "create.an.account": "Create an account", - "or.sign.in.with": "or sign in with", - "non.compliant.password.title": "We recently changed our password requirements", - "first.time.here": "First time here?", - "email.label": "Email", - "email.help.message": "The email address you used to register with edX.", - "enterprise.login.link.text": "Sign in with your company or school", - "email.format.validation.message": "The email address you've provided isn't formatted correctly.", - "email.format.validation.less.chars.message": "Email must have at least 3 characters.", - "email.validation.message": "Please enter your email.", - "password.validation.message": "Please enter your password.", - "password.label": "Password (required)", - "register.link": "Create an account", - "sign.in.heading": "Sign in", - "account.activation.success.message.title": "Success! You have activated your account.", - "account.activation.success.message": "You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.", - "account.already.activated.message": "This account has already been activated.", - "account.activation.error.message.title": "Your account could not be activated", - "account.activation.support.link": "contact support", - "login.rate.limit.reached.message": "Too many failed login attempts. Try again later.", - "login.failure.header.title": "We couldn't sign you in.", - "contact.support.link": "contact {platformName} support", - "login.failed.link.text": "here", - "login.incorrect.credentials.error": "Email or password is incorrect.", - "login.failed.attempt.error": "You have {remainingAttempts} more sign in attempts before your account is temporarily locked.", - "login.locked.out.error.message": "To protect your account, it’s been temporarily locked. Try again in {lockedOutPeriod} minutes.", - "register.page.title": "Register | {siteName}", - "create.account.button": "Create account", - "already.have.an.edx.account": "Already have an edX account?", - "sign.in.hyperlink": "Sign in.", - "create.an.account.using": "or create an account using", - "create.a.new.account": "Create a new account", - "register.institution.login.button": "Use my institution/campus credentials", - "register.institution.login.page.title": "Register with institution/campus credentials", - "register.page.email.label": "Email (required)", - "register.rate.limit.reached.message": "Too many failed registration attempts. Try again later.", - "email.ratelimit.less.chars.validation.message": "Email must have 3 characters.", - "email.ratelimit.incorrect.format.validation.message": "The email address you provided isn't formatted correctly.", - "email.ratelimit.password.validation.message": "Your password must contain at least 8 characters", - "register.page.password.validation.message": "Please enter your password.", - "fullname.label": "Full name (required)", - "fullname.validation.message": "Please enter your full name.", - "username.label": "Public username (required)", - "username.validation.message": "Please enter your public username.", - "username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-).", - "username.character.validation.message": "Your password must contain at least 1 letter.", - "username.number.validation.message": "Your password must contain at least 1 number.", - "username.ratelimit.less.chars.message": "Public username must have atleast 2 characters.", - "country.validation.message": "Select your country or region of residence.", - "support.education.research": "Support education research by providing additional information. (Optional)", - "registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.", - "registration.request.failure.header": "We couldn't create your account.", - "helptext.name": "This name will be used by any certificates that you earn.", - "helptext.username": "The name that will identify you in your courses. It cannot be changed later.", - "helptext.password": "Your password must contain at least 8 characters, including 1 letter & 1 number.", - "helptext.email": "This is what you will use to login.", - "terms.of.service.and.honor.code": "Terms of Service and Honor Code", - "privacy.policy": "Privacy Policy", - "registration.year.of.birth.label": "Year of birth (optional)", - "registration.country.label": "Country or region of residence (required)", - "registration.field.gender.options.label": "Gender (optional)", - "registration.goals.label": "Tell us why you're interested in edX (optional)", - "registration.field.gender.options.f": "Female", - "registration.field.gender.options.m": "Male", - "registration.field.gender.options.o": "Other/Prefer not to say", - "registration.field.education.levels.label": "Highest level of education completed (optional)", - "registration.field.education.levels.p": "Doctorate", - "registration.field.education.levels.m": "Master's or professional degree", - "registration.field.education.levels.b": "Bachelor's degree", - "registration.field.education.levels.a": "Associate's degree", - "registration.field.education.levels.hs": "Secondary/high school", - "registration.field.education.levels.jhs": "Junior secondary/junior high/middle school", - "registration.field.education.levels.el": "Elementary/primary school", - "registration.field.education.levels.none": "No formal education", - "registration.field.education.levels.other": "Other education", - "register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.", - "reset.password.request.invalid.token.description.message": "This password reset link is invalid. It may have been used already.\n To reset your password, go to the {loginPasswordLink} page and select {forgotPassword}", - "reset.password.page.title": "Reset Password | {siteName}", - "reset.password.page.heading": "Reset your password", - "reset.password.page.instructions": "Enter and confirm your new password.", - "reset.password.page.invalid.match.message": "Passwords do not match.", - "forgot.password.page.new.field.label": "New password", - "forgot.password.page.confirm.field.label": "Confirm password", - "reset.password.page.submit.button": "Reset my password", - "reset.password.request.success.header.message": "Password reset complete.", - "forgot.password.confirmation.sign.in.link": "sign in", - "reset.password.request.forgot.password.text": "Forgot password", - "reset.password.request.invalid.token.header": "Invalid password reset link", - "reset.password.empty.new.password.field.error": "Please enter your new password.", - "forgot.password.empty.new.password.error.heading": "We couldn't reset your password.", - "reset.password.request.server.error": "Failed to reset password", - "reset.password.token.validation.sever.error": "Token validation failure", - "reset.server.ratelimit.error": "Too many requests.", - "reset.password.confirmation.support.link": "Sign in to your account.", - "reset.password.request.success.header.description.message": "Your password has been reset. {loginPasswordLink}", - "optional.fields.page.title": "Optional Fields | {siteName}", - "optional.fields.page.heading": "Support education research by providing additional information.", - "welcome.to.edx": "Welcome to edX, {username}!", - "optional.fields.information.link": "Learn more about how we use this information.", - "optional.fields.submit.button": "Submit", - "optional.fields.skip.button": "Skip for now" -} \ No newline at end of file diff --git a/src/redesign/i18n/messages/he.json b/src/redesign/i18n/messages/he.json deleted file mode 100644 index 9e26dfee..00000000 --- a/src/redesign/i18n/messages/he.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/src/redesign/i18n/messages/id.json b/src/redesign/i18n/messages/id.json deleted file mode 100644 index 9e26dfee..00000000 --- a/src/redesign/i18n/messages/id.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/src/redesign/i18n/messages/ko_kr.json b/src/redesign/i18n/messages/ko_kr.json deleted file mode 100644 index 9e26dfee..00000000 --- a/src/redesign/i18n/messages/ko_kr.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/src/redesign/i18n/messages/pl.json b/src/redesign/i18n/messages/pl.json deleted file mode 100644 index 9e26dfee..00000000 --- a/src/redesign/i18n/messages/pl.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/src/redesign/i18n/messages/pt_br.json b/src/redesign/i18n/messages/pt_br.json deleted file mode 100644 index 9e26dfee..00000000 --- a/src/redesign/i18n/messages/pt_br.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/src/redesign/i18n/messages/ru.json b/src/redesign/i18n/messages/ru.json deleted file mode 100644 index 9e26dfee..00000000 --- a/src/redesign/i18n/messages/ru.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/src/redesign/i18n/messages/th.json b/src/redesign/i18n/messages/th.json deleted file mode 100644 index 9e26dfee..00000000 --- a/src/redesign/i18n/messages/th.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/src/redesign/i18n/messages/uk.json b/src/redesign/i18n/messages/uk.json deleted file mode 100644 index 9e26dfee..00000000 --- a/src/redesign/i18n/messages/uk.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/src/redesign/i18n/messages/zh_CN.json b/src/redesign/i18n/messages/zh_CN.json deleted file mode 100644 index a2d67e72..00000000 --- a/src/redesign/i18n/messages/zh_CN.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "forgot.password.confirmation.message": "You entered {strongEmail}. If this email address is associated with your\n edX account, we will send a message with password recovery instructions to this email address.", - "forgot.password.technical.support.help.message": "If you need further assistance, {technicalSupportLink}.", - "institution.login.page.sub.heading": "Choose your institution from the list below:", - "forgot.password.confirmation.title": "Check your email", - "forgot.password.confirmation.support.link": "contact technical support", - "forgot.password.confirmation.info": "If you do not receive a password reset message after 1 minute, verify that you entered the correct email address, or check your spam folder.", - "internal.server.error.message": "An error has occurred. Try refreshing the page, or check your internet connection.", - "server.ratelimit.error.message": "An error has occurred because of too many requests. Please try again after some time.", - "enterprisetpa.title.heading": "Would you like to sign in using your {providerName} credentials?", - "enterprisetpa.sso.button.title": "Sign in using {providerName}", - "enterprisetpa.login.button.text": "Show me other ways to sign in or register", - "sso.sign.in.with": "Sign in with {providerName}", - "sso.create.account.using": "Create account using {providerName}", - "error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.", - "login.third.party.auth.account.not.linked.message": "You have successfully signed into {currentProvider}, but your {currentProvider} account does not have a linked {platformName} account. To link your accounts, sign in now using your {platformName} password.", - "register.third.party.auth.account.not.linked.message": "You've successfully signed into {currentProvider}. We just need a little more information before you start learning with {platformName}.", - "forgot.password.page.title": "Forgot Password | {siteName}", - "forgot.password.page.heading": "Password assistance", - "forgot.password.page.instructions": "Please enter your log-in or recovery email address below and we will send you an email with instructions.", - "forgot.password.page.invalid.email.message": "The email address you've provided isn't formatted correctly.", - "forgot.password.page.email.field.label": "Email", - "forgot.password.page.submit.button": "Recover my password", - "forgot.password.request.server.error": "We couldn’t send the password recovery email.", - "forgot.password.error.message.title": "An error occurred.", - "forgot.password.request.in.progress.message": "Your previous request is in progress, please try again in a few moments.", - "forgot.password.empty.email.field.error": "Please enter your email.", - "forgot.password.invalid.email": "An error occurred.", - "forgot.password.invalid.email.message": "The email address you've provided isn't formatted correctly.", - "forgot.password.email.help.text": "The email address you used to register with {platformName}", - "account.activation.error.message": "Something went wrong, please {supportLink} to resolve this issue.", - "non.compliant.password.error": "{passwordComplaintRequirements} {lineBreak}Your current password does not meet the new security\n requirements. We just sent a password-reset message to the email address associated with this account.\n Thank you for helping us keep your data safe.", - "login.inactive.user.error": "In order to sign in, you need to activate your account.{lineBreak}\n {lineBreak}We just sent an activation link to {email}. If you do not receive an email,\n check your spam folders or {supportLink}.", - "login.reset.password.message.with.link": "If you've forgotten your password, click {resetLink} to reset.", - "login.locked.reset.password.message.with.link": "To be on the safe side, you can reset your password {resetLink} before you try again.", - "login.page.title": "Login | {siteName}", - "sign.in.button": "Sign in", - "need.help.signing.in.collapsible.menu": "Need help signing in?", - "forgot.password.link": "Forgot my password", - "other.sign.in.issues": "Other sign in issues", - "need.other.help.signing.in.collapsible.menu": "Need other help signing in?", - "institution.login.button": "Use my university info", - "institution.login.page.title": "Sign in with institution/campus credentials", - "institution.login.page.back.button": "Back to sign in", - "create.an.account": "Create an account", - "or.sign.in.with": "or sign in with", - "non.compliant.password.title": "We recently changed our password requirements", - "first.time.here": "First time here?", - "email.label": "Email", - "email.help.message": "The email address you used to register with edX.", - "enterprise.login.link.text": "Sign in with your company or school", - "email.format.validation.message": "The email address you've provided isn't formatted correctly.", - "email.format.validation.less.chars.message": "Email must have at least 3 characters.", - "email.validation.message": "Please enter your email.", - "password.validation.message": "Please enter your password.", - "password.label": "Password (required)", - "register.link": "Create an account", - "sign.in.heading": "Sign in", - "account.activation.success.message.title": "Success! You have activated your account.", - "account.activation.success.message": "You will now receive email updates and alerts from us related to the courses you are enrolled in. Sign in to continue.", - "account.already.activated.message": "This account has already been activated.", - "account.activation.error.message.title": "Your account could not be activated", - "account.activation.support.link": "contact support", - "login.rate.limit.reached.message": "Too many failed login attempts. Try again later.", - "login.failure.header.title": "We couldn't sign you in.", - "contact.support.link": "contact {platformName} support", - "login.failed.link.text": "here", - "login.incorrect.credentials.error": "Email or password is incorrect.", - "login.failed.attempt.error": "You have {remainingAttempts} more sign in attempts before your account is temporarily locked.", - "login.locked.out.error.message": "To protect your account, it’s been temporarily locked. Try again in {lockedOutPeriod} minutes.", - "register.page.title": "Register | {siteName}", - "create.account.button": "Create account", - "already.have.an.edx.account": "Already have an edX account?", - "sign.in.hyperlink": "Sign in.", - "create.an.account.using": "or create an account using", - "create.a.new.account": "Create a new account", - "register.institution.login.button": "Use my institution/campus credentials", - "register.institution.login.page.title": "Register with institution/campus credentials", - "register.page.email.label": "Email (required)", - "register.rate.limit.reached.message": "Too many failed registration attempts. Try again later.", - "email.ratelimit.less.chars.validation.message": "Email must have 3 characters.", - "email.ratelimit.incorrect.format.validation.message": "The email address you provided isn't formatted correctly.", - "email.ratelimit.password.validation.message": "Your password must contain at least 8 characters", - "register.page.password.validation.message": "Please enter your password.", - "fullname.label": "Full name (required)", - "fullname.validation.message": "Please enter your full name.", - "username.label": "Public username (required)", - "username.validation.message": "Please enter your public username.", - "username.format.validation.message": "Usernames can only contain letters (A-Z, a-z), numerals (0-9), underscores (_), and hyphens (-).", - "username.character.validation.message": "Your password must contain at least 1 letter.", - "username.number.validation.message": "Your password must contain at least 1 number.", - "username.ratelimit.less.chars.message": "Public username must have atleast 2 characters.", - "country.validation.message": "Select your country or region of residence.", - "support.education.research": "Support education research by providing additional information. (Optional)", - "registration.request.server.error": "An error has occurred. Try refreshing the page, or check your internet connection.", - "registration.request.failure.header": "We couldn't create your account.", - "helptext.name": "This name will be used by any certificates that you earn.", - "helptext.username": "The name that will identify you in your courses. It cannot be changed later.", - "helptext.password": "Your password must contain at least 8 characters, including 1 letter & 1 number.", - "helptext.email": "This is what you will use to login.", - "terms.of.service.and.honor.code": "Terms of Service and Honor Code", - "privacy.policy": "Privacy Policy", - "registration.year.of.birth.label": "Year of birth (optional)", - "registration.country.label": "Country or region of residence (required)", - "registration.field.gender.options.label": "Gender (optional)", - "registration.goals.label": "Tell us why you're interested in edX (optional)", - "registration.field.gender.options.f": "Female", - "registration.field.gender.options.m": "Male", - "registration.field.gender.options.o": "Other/Prefer not to say", - "registration.field.education.levels.label": "Highest level of education completed (optional)", - "registration.field.education.levels.p": "Doctorate", - "registration.field.education.levels.m": "Master's or professional degree", - "registration.field.education.levels.b": "Bachelor's degree", - "registration.field.education.levels.a": "Associate's degree", - "registration.field.education.levels.hs": "Secondary/high school", - "registration.field.education.levels.jhs": "Junior secondary/junior high/middle school", - "registration.field.education.levels.el": "Elementary/primary school", - "registration.field.education.levels.none": "No formal education", - "registration.field.education.levels.other": "Other education", - "register.page.terms.of.service.and.honor.code": "By creating an account, you agree to the {tosAndHonorCode} and you acknowledge that {platformName} and each\n Member process your personal data in accordance with the {privacyPolicy}.", - "reset.password.request.invalid.token.description.message": "This password reset link is invalid. It may have been used already.\n To reset your password, go to the {loginPasswordLink} page and select {forgotPassword}", - "reset.password.page.title": "Reset Password | {siteName}", - "reset.password.page.heading": "Reset your password", - "reset.password.page.instructions": "Enter and confirm your new password.", - "reset.password.page.invalid.match.message": "Passwords do not match.", - "forgot.password.page.new.field.label": "New password", - "forgot.password.page.confirm.field.label": "Confirm password", - "reset.password.page.submit.button": "Reset my password", - "reset.password.request.success.header.message": "Password reset complete.", - "forgot.password.confirmation.sign.in.link": "sign in", - "reset.password.request.forgot.password.text": "Forgot password", - "reset.password.request.invalid.token.header": "Invalid password reset link", - "reset.password.empty.new.password.field.error": "Please enter your new password.", - "forgot.password.empty.new.password.error.heading": "We couldn't reset your password.", - "reset.password.request.server.error": "Failed to reset password", - "reset.password.token.validation.sever.error": "Token validation failure", - "reset.server.ratelimit.error": "Too many requests.", - "reset.password.confirmation.support.link": "Sign in to your account.", - "reset.password.request.success.header.description.message": "Your password has been reset. {loginPasswordLink}", - "optional.fields.page.title": "Optional Fields | {siteName}", - "optional.fields.page.heading": "Support education research by providing additional information.", - "welcome.to.edx": "Welcome to edX, {username}!", - "optional.fields.information.link": "Learn more about how we use this information.", - "optional.fields.submit.button": "Submit", - "optional.fields.skip.button": "Skip for now" -} \ No newline at end of file diff --git a/src/redesign/login/data/constants.js b/src/redesign/login/data/constants.js deleted file mode 100644 index a213714c..00000000 --- a/src/redesign/login/data/constants.js +++ /dev/null @@ -1,16 +0,0 @@ -// Login Error Codes -export const INACTIVE_USER = 'inactive-user'; -export const INTERNAL_SERVER_ERROR = 'internal-server-error'; -export const INVALID_FORM = 'invalid-form'; -export const NON_COMPLIANT_PASSWORD_EXCEPTION = 'NonCompliantPasswordException'; -export const FORBIDDEN_REQUEST = 'forbidden-request'; -export const FAILED_LOGIN_ATTEMPT = 'failed-login-attempt'; -export const ACCOUNT_LOCKED_OUT = 'account-locked-out'; -export const INCORRECT_EMAIL_PASSWORD = 'incorrect-email-or-password'; - -// Account Activation Message -export const ACCOUNT_ACTIVATION_MESSAGE = { - INFO: 'info', - SUCCESS: 'success', - ERROR: 'error', -}; diff --git a/src/redesign/login/data/sagas.js b/src/redesign/login/data/sagas.js deleted file mode 100644 index 825ed738..00000000 --- a/src/redesign/login/data/sagas.js +++ /dev/null @@ -1,50 +0,0 @@ -import { call, put, takeEvery } from 'redux-saga/effects'; - -import { camelCaseObject } from '@edx/frontend-platform'; -import { logError, logInfo } from '@edx/frontend-platform/logging'; -import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from './constants'; - -// Actions -import { - LOGIN_REQUEST, - loginRequestBegin, - loginRequestFailure, - loginRequestSuccess, -} from './actions'; - -// Services -import { - loginRequest, -} from './service'; - -export function* handleLoginRequest(action) { - try { - yield put(loginRequestBegin()); - - const { redirectUrl, success } = yield call(loginRequest, action.payload.creds); - - yield put(loginRequestSuccess( - redirectUrl, - success, - )); - } catch (e) { - const statusCodes = [400]; - if (e.response) { - const { status } = e.response; - if (statusCodes.includes(status)) { - yield put(loginRequestFailure(camelCaseObject(e.response.data))); - logInfo(e); - } else if (status === 403) { - yield put(loginRequestFailure({ errorCode: FORBIDDEN_REQUEST })); - logInfo(e); - } else { - yield put(loginRequestFailure({ errorCode: INTERNAL_SERVER_ERROR })); - logError(e); - } - } - } -} - -export default function* saga() { - yield takeEvery(LOGIN_REQUEST.BASE, handleLoginRequest); -} diff --git a/src/redesign/login/data/selectors.js b/src/redesign/login/data/selectors.js deleted file mode 100644 index a409a5ee..00000000 --- a/src/redesign/login/data/selectors.js +++ /dev/null @@ -1,15 +0,0 @@ -import { createSelector } from 'reselect'; - -export const storeName = 'login'; - -export const loginSelector = state => ({ ...state[storeName] }); - -export const loginRequestSelector = createSelector( - loginSelector, - login => login.loginResult, -); - -export const loginErrorSelector = createSelector( - loginSelector, - login => login.loginError, -); diff --git a/src/redesign/login/data/tests/sagas.test.js b/src/redesign/login/data/tests/sagas.test.js deleted file mode 100644 index a7f40180..00000000 --- a/src/redesign/login/data/tests/sagas.test.js +++ /dev/null @@ -1,111 +0,0 @@ -import { runSaga } from 'redux-saga'; - -import { camelCaseObject } from '@edx/frontend-platform'; - -import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from '../constants'; -import * as actions from '../actions'; -import { handleLoginRequest } from '../sagas'; -import * as api from '../service'; -import initializeMockLogging from '../../../../setupTest'; - -const { loggingService } = initializeMockLogging(); - -describe('handleLoginRequest', () => { - const params = { - payload: { - formData: { - email: 'test@test.com', - password: 'test-password', - }, - }, - }; - - const testErrorResponse = async (loginErrorResponse, expectedLogFunc, expectedDispatchers) => { - const loginRequest = jest.spyOn(api, 'loginRequest').mockImplementation(() => Promise.reject(loginErrorResponse)); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - handleLoginRequest, - params, - ); - - expect(loginRequest).toHaveBeenCalledTimes(1); - expect(expectedLogFunc).toHaveBeenCalled(); - expect(dispatched).toEqual(expectedDispatchers); - loginRequest.mockClear(); - }; - - beforeEach(() => { - loggingService.logError.mockReset(); - loggingService.logInfo.mockReset(); - }); - - it('should call service and dispatch success action', async () => { - const data = { redirectUrl: '/dashboard', success: true }; - const loginRequest = jest.spyOn(api, 'loginRequest') - .mockImplementation(() => Promise.resolve(data)); - - const dispatched = []; - await runSaga( - { dispatch: (action) => dispatched.push(action) }, - handleLoginRequest, - params, - ); - - expect(loginRequest).toHaveBeenCalledTimes(1); - expect(dispatched).toEqual([ - actions.loginRequestBegin(), - actions.loginRequestSuccess(data.redirectUrl, data.success), - ]); - loginRequest.mockClear(); - }); - - it('should call service and dispatch error action', async () => { - const loginErrorResponse = { - response: { - status: 400, - data: { - login_error: 'something went wrong', - }, - }, - }; - - await testErrorResponse(loginErrorResponse, loggingService.logInfo, [ - actions.loginRequestBegin(), - actions.loginRequestFailure(camelCaseObject(loginErrorResponse.response.data)), - ]); - }); - - it('should handle rate limit error code', async () => { - const loginErrorResponse = { - response: { - status: 403, - data: { - errorCode: FORBIDDEN_REQUEST, - }, - }, - }; - - await testErrorResponse(loginErrorResponse, loggingService.logInfo, [ - actions.loginRequestBegin(), - actions.loginRequestFailure(loginErrorResponse.response.data), - ]); - }); - - it('should handle 500 error code', async () => { - const loginErrorResponse = { - response: { - status: 500, - data: { - errorCode: INTERNAL_SERVER_ERROR, - }, - }, - }; - - await testErrorResponse(loginErrorResponse, loggingService.logError, [ - actions.loginRequestBegin(), - actions.loginRequestFailure(loginErrorResponse.response.data), - ]); - }); -}); diff --git a/src/redesign/login/index.js b/src/redesign/login/index.js deleted file mode 100644 index 769161d2..00000000 --- a/src/redesign/login/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export { default as LoginPage } from './LoginPage'; -export { default as reducer } from './data/reducers'; -export { default as saga } from './data/sagas'; -export { storeName } from './data/selectors'; diff --git a/src/redesign/login/tests/AccountActivationMessage.test.jsx b/src/redesign/login/tests/AccountActivationMessage.test.jsx deleted file mode 100644 index 4c20ba13..00000000 --- a/src/redesign/login/tests/AccountActivationMessage.test.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; - -import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n'; - -import AccountActivationMessage from '../AccountActivationMessage'; -import { ACCOUNT_ACTIVATION_MESSAGE } from '../data/constants'; - -const IntlAccountActivationMessage = injectIntl(AccountActivationMessage); - -describe('AccountActivationMessage', () => { - it('should match account already activated message', () => { - const accountActivationMessage = mount( - - - , - ); - - const expectedMessage = 'This account has already been activated.'; - expect(accountActivationMessage.find('#account-activation-message').find('div').first().text()).toEqual(expectedMessage); - }); - - it('should match account activated success message', () => { - const accountActivationMessage = mount( - - - , - ); - - const expectedMessage = 'Success! You have activated your account.' - + 'You will now receive email updates and alerts from us related to ' - + 'the courses you are enrolled in. Sign in to continue.'; - expect(accountActivationMessage.find('#account-activation-message').first().text()).toEqual(expectedMessage); - }); - - it('should match account activation error message', () => { - const accountActivationMessage = mount( - - - , - ); - - const expectedMessage = 'Your account could not be activated' - + 'Something went wrong, please contact support to resolve this issue.'; - expect(accountActivationMessage.find('#account-activation-message').first().text()).toEqual(expectedMessage); - }); - - it('should not display anything for invalid message type', () => { - const accountActivationMessage = mount( - - - , - ); - - expect(accountActivationMessage).toEqual({}); - }); -}); diff --git a/src/redesign/register/index.js b/src/redesign/register/index.js deleted file mode 100644 index 496c0f7b..00000000 --- a/src/redesign/register/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export { default as RegistrationPage } from './RegistrationPage'; -export { default as reducer } from './data/reducers'; -export { default as saga } from './data/sagas'; -export { storeName } from './data/selectors'; diff --git a/src/redesign/reset-password/data/selectors.js b/src/redesign/reset-password/data/selectors.js deleted file mode 100644 index a280d6f9..00000000 --- a/src/redesign/reset-password/data/selectors.js +++ /dev/null @@ -1,10 +0,0 @@ -import { createSelector } from 'reselect'; - -export const storeName = 'resetPassword'; - -export const resetPasswordSelector = state => ({ ...state[storeName] }); - -export const resetPasswordResultSelector = createSelector( - resetPasswordSelector, - resetPassword => resetPassword, -); diff --git a/src/redesign/reset-password/data/service.js b/src/redesign/reset-password/data/service.js deleted file mode 100644 index 8a4f234e..00000000 --- a/src/redesign/reset-password/data/service.js +++ /dev/null @@ -1,64 +0,0 @@ -import { getConfig } from '@edx/frontend-platform'; -import { getHttpClient } from '@edx/frontend-platform/auth'; -import formurlencoded from 'form-urlencoded'; - -// eslint-disable-next-line import/prefer-default-export -export async function validateToken(token) { - const requestConfig = { - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - }; - - const { data } = await getHttpClient() - .post( - `${getConfig().LMS_BASE_URL}/user_api/v1/account/password_reset/token/validate/`, - formurlencoded({ token }), - requestConfig, - ) - .catch((e) => { - throw (e); - }); - return data; -} - -// eslint-disable-next-line import/prefer-default-export -export async function resetPassword(payload, token, queryParams) { - const requestConfig = { - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - }; - const url = new URL(`${getConfig().LMS_BASE_URL}/password/reset/${token}/`); - - if (queryParams.is_account_recovery) { - url.searchParams.append('is_account_recovery', true); - } - - const { data } = await getHttpClient() - .post(url.href, formurlencoded(payload), requestConfig) - .catch((e) => { - throw (e); - }); - return data; -} - -export async function validatePassword(password) { - const requestConfig = { - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - }; - const { data } = await getHttpClient() - .post( - `${getConfig().LMS_BASE_URL}/api/user/v1/validation/registration`, - formurlencoded({ password }), - requestConfig, - ) - .catch((e) => { - throw (e); - }); - - let errorMessage = ''; - // Be careful about grabbing this message, since we could have received an HTTP error or the - // endpoint didn't give us what we expect. We only care if we get a clear error message. - if (data.validation_decisions && data.validation_decisions.password) { - errorMessage = data.validation_decisions.password; - } - - return errorMessage; -} diff --git a/src/redesign/reset-password/index.js b/src/redesign/reset-password/index.js deleted file mode 100644 index 2116170a..00000000 --- a/src/redesign/reset-password/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export { default } from './ResetPasswordPage'; -export { default as reducer } from './data/reducers'; -export { RESET_PASSWORD } from './data/actions'; -export { default as saga } from './data/sagas'; -export { storeName } from './data/selectors'; diff --git a/src/redesign/register/CountryDropdown.jsx b/src/register/CountryDropdown.jsx similarity index 100% rename from src/redesign/register/CountryDropdown.jsx rename to src/register/CountryDropdown.jsx diff --git a/src/redesign/register/OptionalFields.jsx b/src/register/OptionalFields.jsx similarity index 100% rename from src/redesign/register/OptionalFields.jsx rename to src/register/OptionalFields.jsx diff --git a/src/redesign/register/RegistrationFailure.jsx b/src/register/RegistrationFailure.jsx similarity index 100% rename from src/redesign/register/RegistrationFailure.jsx rename to src/register/RegistrationFailure.jsx diff --git a/src/redesign/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx similarity index 99% rename from src/redesign/register/RegistrationPage.jsx rename to src/register/RegistrationPage.jsx index 2bdcc368..c704fff6 100644 --- a/src/redesign/register/RegistrationPage.jsx +++ b/src/register/RegistrationPage.jsx @@ -610,7 +610,7 @@ class RegistrationPage extends React.Component { }} />
    - {getConfig().REGISTRATION_OPTIONAL_FIELDS && getConfig().DESIGN_NAME !== 'redesign' ? ( + {getConfig().REGISTRATION_OPTIONAL_FIELDS ? (