Compare commits

...

17 Commits

Author SHA1 Message Date
Adolfo R. Brandes
c98b9a1408 1.0.0-alpha.2 2025-06-29 13:11:02 +02:00
Adolfo R. Brandes
b6c659ccb8 feat: redirect to route role on successful login
Now we have a way to discover app URLs dynamically; do so for the
(default) redirect on login.
2025-06-29 13:09:17 +02:00
Adolfo R. Brandes
7efa279a29 1.0.0-alpha.1 2025-06-28 22:16:19 +02:00
Adolfo R. Brandes
f83f24af89 chore: bump frontend-base 2025-06-28 22:16:07 +02:00
Adolfo R. Brandes
b8116485b0 v1.0.0-alpha.0 2025-06-28 19:31:31 +02:00
Adolfo R. Brandes
762ff75fc4 Merge pull request #1515 from arbrandes/frontend-base-fixes
feat: Prepare for publication to NPM
2025-06-28 14:28:54 -03:00
Adolfo R. Brandes
095b156b95 chore: bump frontend-base 2025-06-28 19:27:26 +02:00
Adolfo R. Brandes
033e0fd7c5 feat: handle authentication roles 2025-06-28 19:18:34 +02:00
Adolfo R. Brandes
1ecdb0b6af chore: gitignore npm pack artifacts 2025-06-26 23:05:50 -03:00
Adolfo R. Brandes
18951cc4d0 chore: clean up npmignore 2025-06-26 23:05:50 -03:00
Adolfo R. Brandes
4dd5ddcc8b refactor: use appId from constants 2025-06-26 23:05:50 -03:00
Adolfo R. Brandes
6acbf64a71 chore: prepare for publication
Update package.json for publication as a "buildless" library.

(Also upgrade openedx-atlas.)
2025-06-26 19:52:56 -03:00
Adolfo R. Brandes
aea12a6a37 fix: remove catch-all route
Apps should avoid having a catch-all route, instead relying on the shell
to provide it.
2025-06-26 19:46:12 -03:00
Adolfo R. Brandes
3d778807f1 fix: update index.html
The only purpose of the included index.html is to serve the dev
environment, so modify it to make that clear.
2025-06-26 19:28:02 -03:00
Adolfo R. Brandes
014a990c22 docs: remove reference to devstack 2025-06-26 19:27:40 -03:00
Adolfo R. Brandes
7c8051a440 test: fix a couple of tests 2025-06-26 19:27:23 -03:00
Adolfo R. Brandes
8f8531a242 refactor: migrate to frontend-base
BREAKING CHANGE: refactors the MFE for frontend-base.
2025-06-24 15:30:07 -03:00
180 changed files with 6071 additions and 11818 deletions

43
.env
View File

@@ -1,43 +0,0 @@
NODE_ENV='production'
ACCESS_TOKEN_COOKIE_NAME=null
BASE_URL=null
CREDENTIALS_BASE_URL=null
CSRF_TOKEN_API_PATH=null
ECOMMERCE_BASE_URL=null
LANGUAGE_PREFERENCE_COOKIE_NAME=null
LMS_BASE_URL=null
LOGIN_URL=null
LOGOUT_URL=null
MARKETING_SITE_BASE_URL=null
ORDER_HISTORY_URL=null
REFRESH_ACCESS_TOKEN_ENDPOINT=null
SEGMENT_KEY=''
SITE_NAME=null
INFO_EMAIL=''
# ***** Cookies *****
USER_RETENTION_COOKIE_NAME=null
# ***** Links *****
LOGIN_ISSUE_SUPPORT_LINK=''
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK=null
POST_REGISTRATION_REDIRECT_URL=''
SEARCH_CATALOG_URL=''
# ***** Features flags *****
DISABLE_ENTERPRISE_LOGIN=''
ENABLE_AUTO_GENERATED_USERNAME=''
ENABLE_DYNAMIC_REGISTRATION_FIELDS=''
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN=''
ENABLE_POST_REGISTRATION_RECOMMENDATIONS=''
MARKETING_EMAILS_OPT_IN=''
SHOW_CONFIGURABLE_EDX_FIELDS=''
ENABLE_IMAGE_LAYOUT=''
# ***** Zendesk related keys *****
ZENDESK_KEY=''
ZENDESK_LOGO_URL=''
# ***** Base Container Images *****
BANNER_IMAGE_LARGE=''
BANNER_IMAGE_MEDIUM=''
BANNER_IMAGE_SMALL=''
BANNER_IMAGE_EXTRA_SMALL=''
# ***** Miscellaneous *****
APP_ID=''
MFE_CONFIG_API_URL=''

View File

@@ -1,4 +0,0 @@
# Copy these to the .env.private to enable edX specific functionality on local system
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN='true'
MARKETING_EMAILS_OPT_IN='true'
SHOW_CONFIGURABLE_EDX_FIELDS='true'

View File

@@ -1,20 +0,0 @@
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
BASE_URL='http://localhost:1995'
CREDENTIALS_BASE_URL='http://localhost:18150'
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
ECOMMERCE_BASE_URL='http://localhost:18130'
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
LMS_BASE_URL='http://localhost:18000'
LOGIN_URL='http://localhost:18000/login'
LOGOUT_URL='http://localhost:18000/logout'
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
MARKETING_SITE_BASE_URL='http://localhost:18000'
ORDER_HISTORY_URL='http://localhost:1996/orders'
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
SEGMENT_KEY=''
SITE_NAME='Your Platform Name Here'
APP_ID=''
MFE_CONFIG_API_URL=''

View File

@@ -1,6 +0,0 @@
coverage/*
dist/
docs
node_modules/
__mocks__/
__snapshots__/

View File

@@ -1,52 +0,0 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const { createConfig } = require('@openedx/frontend-build');
module.exports = createConfig('eslint', {
rules: {
// Temporarily update the 'indent', 'template-curly-spacing' and
// 'no-multiple-empty-lines' rules since they are causing eslint
// to fail for no apparent reason since upgrading
// @openedx/frontend-build from v3 to v5:
// - TypeError: Cannot read property 'range' of null
indent: [
'error',
2,
{ ignoredNodes: ['TemplateLiteral', 'SwitchCase'] },
],
'template-curly-spacing': 'off',
'jsx-a11y/label-has-associated-control': ['error', {
labelComponents: [],
labelAttributes: [],
controlComponents: [],
assert: 'htmlFor',
depth: 25,
}],
'sort-imports': ['error', { ignoreCase: true, ignoreDeclarationSort: true }],
'import/order': [
'error',
{
groups: [
'builtin',
'external',
'internal',
['sibling', 'parent'],
'index',
],
pathGroups: [
{
pattern: '@(react|react-dom|react-redux)',
group: 'external',
position: 'before',
},
],
pathGroupsExcludedImportTypes: ['react'],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
},
],
'function-paren-newline': 'off',
},
});

16
.gitignore vendored
View File

@@ -1,21 +1,15 @@
.DS_Store
.eslintcache
.idea
node_modules node_modules
npm-debug.log npm-debug.log
coverage coverage
module.config.js module.config.js
.env.private
dist/ dist/
/*.tgz
### i18n ###
src/i18n/transifex_input.json src/i18n/transifex_input.json
temp/babel-plugin-react-intl
### pyenv ### ### Editors ###
.python-version .DS_Store
### Emacs ###
*~ *~
/temp /temp
/.vscode /.vscode
src/i18n/messages

View File

@@ -1,11 +1,6 @@
.eslintignore __mocks__
.eslintrc.json
.gitignore
docker-compose.yml
Dockerfile
Makefile
npm-debug.log
coverage
node_modules node_modules
public *.test.js
*.test.jsx
*.test.ts
*.test.tsx

View File

@@ -34,26 +34,6 @@ Installation
.. _Tutor: https://github.com/overhangio/tutor .. _Tutor: https://github.com/overhangio/tutor
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development .. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development
Devstack (Deprecated) instructions
==================================
1. Install Devstack using the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ instructions.
2. Start up LMS, if it's not already started.
4. Within this project (frontend-app-authn), install requirements and start the development server:
.. code-block::
npm install
npm start # The server will run on port 1999
5. Once the dev server is up, visit http://localhost:1999 to access the MFE
.. image:: ./docs/images/frontend-app-authn-localhost-preview.png
**Note:** Follow `Enable social auth locally <docs/how_tos/enable_social_auth.rst>`_ for enabling Social Sign-on Buttons (SSO) locally
Environment Variables/Setup Notes Environment Variables/Setup Notes
================================= =================================
@@ -143,10 +123,6 @@ Furthermore, there are several edX-specific environment variables that enable in
- Enables support for opting in marketing emails that helps us getting user consent for sending marketing emails. - Enables support for opting in marketing emails that helps us getting user consent for sending marketing emails.
- ``true`` | ``''`` (empty strings are falsy) - ``true`` | ``''`` (empty strings are falsy)
* - ``SHOW_CONFIGURABLE_EDX_FIELDS``
- For edX, country and honor code fields are required by default. This flag enables edX specific required fields.
- ``true`` | ``''`` (empty strings are falsy)
For more information see the document: `Micro-frontend applications in Open For more information see the document: `Micro-frontend applications in Open
edX <https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development>`__. edX <https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development>`__.

5
app.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
/// <reference types="@openedx/frontend-base" />
declare module 'site.config' {
export default SiteConfig;
}

3
babel.config.js Normal file
View File

@@ -0,0 +1,3 @@
const { createConfig } = require('@openedx/frontend-base/config');
module.exports = createConfig('babel');

22
eslint.config.js Normal file
View File

@@ -0,0 +1,22 @@
// @ts-check
const { createLintConfig } = require('@openedx/frontend-base/config');
module.exports = createLintConfig(
{
files: [
'src/**/*',
'site.config.*',
],
},
{
ignores: [
'coverage/*',
'dist/*',
'docs/*',
'node_modules/*',
'**/__mocks__/*',
'**/__snapshots__/*',
],
},
);

View File

@@ -1,14 +1,15 @@
const { createConfig } = require('@openedx/frontend-build'); const { createConfig } = require('@openedx/frontend-base/config');
module.exports = createConfig('jest', { module.exports = createConfig('test', {
setupFiles: [ setupFilesAfterEnv: [
'<rootDir>/src/setupTest.js', '<rootDir>/src/setupTest.js',
], ],
coveragePathIgnorePatterns: [ coveragePathIgnorePatterns: [
'src/setupTest.js', 'src/setupTest.js',
'src/i18n', 'src/i18n',
'src/index.jsx',
'MainApp.jsx',
], ],
testEnvironment: 'jsdom', moduleNameMapper: {
'\\.svg$': '<rootDir>/src/__mocks__/svg.js',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/__mocks__/file.js',
},
}); });

12635
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,31 @@
{ {
"name": "@edx/frontend-app-authn", "name": "@openedx/frontend-app-authn",
"version": "0.1.0", "version": "1.0.0-alpha.2",
"description": "Frontend application template", "description": "Frontend authentication",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/openedx/frontend-app-authn.git" "url": "git+https://github.com/openedx/frontend-app-authn.git"
}, },
"main": "src/index.ts",
"files": [
"/src"
],
"browserslist": [ "browserslist": [
"extends @edx/browserslist-config" "extends @edx/browserslist-config"
], ],
"sideEffects": [
"*.css",
"*.scss"
],
"scripts": { "scripts": {
"build": "fedx-scripts webpack", "dev": "PORT=1999 PUBLIC_PATH=/authn openedx dev",
"i18n_extract": "fedx-scripts formatjs extract", "i18n_extract": "openedx formatjs extract",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .", "lint": "openedx lint .",
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .", "lint:fix": "openedx lint --fix .",
"snapshot": "fedx-scripts jest --updateSnapshot", "snapshot": "openedx test --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress", "test": "openedx test --coverage --passWithNoTests"
"dev": "PUBLIC_PATH=/authn/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
"test": "fedx-scripts jest --coverage --passWithNoTests"
}, },
"author": "edX", "author": "Open edX",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"homepage": "https://github.com/openedx/frontend-app-authn#readme", "homepage": "https://github.com/openedx/frontend-app-authn#readme",
"publishConfig": { "publishConfig": {
@@ -29,52 +35,45 @@
"url": "https://github.com/openedx/frontend-app-authn/issues" "url": "https://github.com/openedx/frontend-app-authn/issues"
}, },
"dependencies": { "dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2", "@edx/brand": "npm:@openedx/brand-openedx@^1.2.3",
"@edx/frontend-platform": "^8.3.1", "@edx/openedx-atlas": "^0.7.0",
"@edx/openedx-atlas": "^0.6.0", "@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/fontawesome-svg-core": "6.7.2", "@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "6.7.2", "@fortawesome/react-fontawesome": "^0.2.2",
"@fortawesome/react-fontawesome": "0.2.2", "@redux-devtools/extension": "^3.3.0",
"@openedx/paragon": "^22.16.0", "classnames": "^2.5.1",
"@optimizely/react-sdk": "^2.9.1", "fastest-levenshtein": "^1.0.16",
"@redux-devtools/extension": "3.3.0", "form-urlencoded": "^6.1.5",
"@testing-library/react": "^16.2.0", "i18n-iso-countries": "^7.13.0",
"algoliasearch": "^4.14.3", "prop-types": "^15.8.1",
"algoliasearch-helper": "^3.14.0", "query-string": "^7.1.3",
"classnames": "2.5.1", "react-helmet": "^6.1.0",
"core-js": "3.43.0", "react-loading-skeleton": "^3.5.0",
"fastest-levenshtein": "1.0.16", "react-responsive": "^8.2.0",
"form-urlencoded": "6.1.5", "redux-logger": "^3.0.6",
"prop-types": "15.8.1", "redux-mock-store": "^1.5.5",
"query-string": "7.1.3", "redux-saga": "^1.3.0",
"react": "^18.3.1", "redux-thunk": "^2.4.2",
"react-dom": "^18.3.1", "reselect": "^5.1.1",
"react-helmet": "6.1.0", "universal-cookie": "^8.0.1"
"react-loading-skeleton": "3.5.0",
"react-redux": "7.2.9",
"react-responsive": "8.2.0",
"react-router": "6.30.1",
"react-router-dom": "6.30.1",
"react-zendesk": "^0.1.13",
"redux": "4.2.1",
"redux-logger": "3.0.6",
"redux-mock-store": "1.5.5",
"redux-saga": "1.3.0",
"redux-thunk": "2.4.2",
"regenerator-runtime": "0.14.1",
"reselect": "5.1.1",
"universal-cookie": "7.2.2"
}, },
"devDependencies": { "devDependencies": {
"@edx/browserslist-config": "^1.1.1", "@edx/browserslist-config": "^1.5.0",
"@edx/reactifex": "1.1.0", "@testing-library/react": "^16.3.0",
"@openedx/frontend-build": "^14.4.2",
"babel-plugin-formatjs": "10.5.38", "babel-plugin-formatjs": "10.5.38",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",
"glob": "7.2.3", "jest": "^29.7.0",
"history": "5.3.0",
"jest": "30.0.0",
"react-test-renderer": "^18.3.1" "react-test-renderer": "^18.3.1"
},
"peerDependencies": {
"@openedx/frontend-base": "^1.0.0-alpha.2",
"@openedx/paragon": "^22",
"react": "^18",
"react-dom": "^18",
"react-redux": "^8",
"react-router": "^6",
"react-router-dom": "^6",
"redux": "^4"
} }
} }

View File

@@ -1,24 +1,9 @@
<!doctype html> <!doctype html>
<html lang="en-us"> <html lang="en-us">
<head> <head>
<title><%= (process.env.SITE_NAME && process.env.SITE_NAME != 'null') ? 'Authentication | ' + process.env.SITE_NAME : 'Authentication' %></title> <title>Authentication Development Site></title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.4.4/iframeResizer.contentWindow.min.js"
integrity="sha512-IWwZFBvHzN41wNI6etRLLuLrDDj/6AwJcPt7cmKJAzluYTIHHQ1PF8wh0rSy05jxEvvjflVvH2MxeV6riyEEXg=="
crossorigin="anonymous"
referrerpolicy="no-referrer">
</script>
<% if (process.env.OPTIMIZELY_URL) { %>
<script
src="<%= process.env.OPTIMIZELY_URL %>"
></script>
<% } else if (process.env.OPTIMIZELY_PROJECT_ID) { %>
<script
src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"
></script>
<% } %>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

20
site.config.dev.tsx Normal file
View File

@@ -0,0 +1,20 @@
import { EnvironmentTypes, SiteConfig } from '@openedx/frontend-base';
import { authnApp } from './src';
import './src/app.scss';
const siteConfig: SiteConfig = {
siteId: 'authn-dev',
siteName: 'Authn Dev',
baseUrl: 'http://apps.local.openedx.io:8080',
lmsBaseUrl: 'http://local.openedx.io:8000',
loginUrl: 'http://local.openedx.io:8000/login',
logoutUrl: 'http://local.openedx.io:8000/logout',
environment: EnvironmentTypes.DEVELOPMENT,
basename: '/authn',
apps: [authnApp],
};
export default siteConfig;

50
site.config.test.tsx Normal file
View File

@@ -0,0 +1,50 @@
import { EnvironmentTypes, SiteConfig } from '@openedx/frontend-base';
import { appId } from './src/constants';
const siteConfig: SiteConfig = {
siteId: 'test-site',
siteName: 'Test Site',
baseUrl: 'http://localhost:1996',
lmsBaseUrl: 'http://localhost:8000',
loginUrl: 'http://localhost:8000/login',
logoutUrl: 'http://localhost:8000/logout',
environment: EnvironmentTypes.TEST,
apps: [{
appId,
config: {
ACTIVATION_EMAIL_SUPPORT_LINK: null,
ALLOW_PUBLIC_ACCOUNT_CREATION: false,
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: null,
BANNER_IMAGE_EXTRA_SMALL: '',
BANNER_IMAGE_LARGE: '',
BANNER_IMAGE_MEDIUM: '',
BANNER_IMAGE_SMALL: '',
DISABLE_ENTERPRISE_LOGIN: true,
ENABLE_AUTO_GENERATED_USERNAME: false,
ENABLE_DYNAMIC_REGISTRATION_FIELDS: false,
ENABLE_IMAGE_LAYOUT: false,
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: false,
FAVICON_URL: 'https://edx-cdn.org/v3/default/favicon.ico',
INFO_EMAIL: '',
LOGIN_ISSUE_SUPPORT_LINK: null,
LOGO_TRADEMARK_URL: 'https://edx-cdn.org/v3/default/logo-trademark.svg',
LOGO_URL: 'https://edx-cdn.org/v3/default/logo.svg',
LOGO_WHITE_URL: 'https://edx-cdn.org/v3/default/logo-white.svg',
MARKETING_EMAILS_OPT_IN: '',
MARKETING_SITE_BASE_URL: 'http://localhost:18000',
PASSWORD_RESET_SUPPORT_LINK: null,
POST_REGISTRATION_REDIRECT_URL: '',
PRIVACY_POLICY: null,
SEARCH_CATALOG_URL: null,
SESSION_COOKIE_DOMAIN: 'local.openedx.io',
SHOW_REGISTRATION_LINKS: false,
TOS_AND_HONOR_CODE: null,
TOS_LINK: null,
USER_RETENTION_COOKIE_NAME: '',
},
}],
};
export default siteConfig;

23
src/Main.tsx Executable file
View File

@@ -0,0 +1,23 @@
import { Provider as ReduxProvider } from 'react-redux';
import { Outlet } from 'react-router-dom';
import { CurrentAppProvider } from '@openedx/frontend-base';
import { appId } from './constants';
import {
registerIcons,
} from './common-components';
import configureStore from './data/configureStore';
import './sass/_style.scss';
registerIcons();
const Main = () => (
<CurrentAppProvider appId={appId}>
<ReduxProvider store={configureStore()}>
<Outlet />
</ReduxProvider>
</CurrentAppProvider>
);
export default Main;

View File

@@ -1,63 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { AppProvider } from '@edx/frontend-platform/react';
import { Helmet } from 'react-helmet';
import { Navigate, Route, Routes } from 'react-router-dom';
import {
EmbeddedRegistrationRoute, NotFoundPage, registerIcons, UnAuthOnlyRoute, Zendesk,
} from './common-components';
import configureStore from './data/configureStore';
import {
AUTHN_PROGRESSIVE_PROFILING,
LOGIN_PAGE,
PAGE_NOT_FOUND,
PASSWORD_RESET_CONFIRM,
RECOMMENDATIONS,
REGISTER_EMBEDDED_PAGE,
REGISTER_PAGE,
RESET_PAGE,
} from './data/constants';
import { updatePathWithQueryParams } from './data/utils';
import { ForgotPasswordPage } from './forgot-password';
import Logistration from './logistration/Logistration';
import { ProgressiveProfiling } from './progressive-profiling';
import { RecommendationsPage } from './recommendations';
import { RegistrationPage } from './register';
import { ResetPasswordPage } from './reset-password';
import './index.scss';
registerIcons();
const MainApp = () => (
<AppProvider store={configureStore()}>
<Helmet>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
{getConfig().ZENDESK_KEY && <Zendesk />}
<Routes>
<Route path="/" element={<Navigate replace to={updatePathWithQueryParams(REGISTER_PAGE)} />} />
<Route
path={REGISTER_EMBEDDED_PAGE}
element={<EmbeddedRegistrationRoute><RegistrationPage /></EmbeddedRegistrationRoute>}
/>
<Route
path={LOGIN_PAGE}
element={
<UnAuthOnlyRoute><Logistration selectedPage={LOGIN_PAGE} /></UnAuthOnlyRoute>
}
/>
<Route path={REGISTER_PAGE} element={<UnAuthOnlyRoute><Logistration /></UnAuthOnlyRoute>} />
<Route path={RESET_PAGE} element={<UnAuthOnlyRoute><ForgotPasswordPage /></UnAuthOnlyRoute>} />
<Route path={PASSWORD_RESET_CONFIRM} element={<ResetPasswordPage />} />
<Route path={AUTHN_PROGRESSIVE_PROFILING} element={<ProgressiveProfiling />} />
<Route path={RECOMMENDATIONS} element={<RecommendationsPage />} />
<Route path={PAGE_NOT_FOUND} element={<NotFoundPage />} />
<Route path="*" element={<Navigate replace to={PAGE_NOT_FOUND} />} />
</Routes>
</AppProvider>
);
export default MainApp;

1
src/__mocks__/file.js Normal file
View File

@@ -0,0 +1 @@
module.exports = 'FileMock';

1
src/__mocks__/svg.js Normal file
View File

@@ -0,0 +1 @@
module.exports = 'SvgURL';

6
src/app.scss Executable file
View File

@@ -0,0 +1,6 @@
@use "@edx/brand/paragon/fonts";
@use "@edx/brand/paragon/variables";
@use "@openedx/paragon/scss/core/core";
@use "@edx/brand/paragon/overrides";
@use "sass/style";

43
src/app.ts Normal file
View File

@@ -0,0 +1,43 @@
import { App } from '@openedx/frontend-base';
import { appId } from './constants';
import routes from './routes';
import messages from './i18n';
const app: App = {
appId,
routes,
messages,
config: {
ACTIVATION_EMAIL_SUPPORT_LINK: null,
ALLOW_PUBLIC_ACCOUNT_CREATION: true,
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: null,
BANNER_IMAGE_EXTRA_SMALL: '',
BANNER_IMAGE_LARGE: '',
BANNER_IMAGE_MEDIUM: '',
BANNER_IMAGE_SMALL: '',
DISABLE_ENTERPRISE_LOGIN: true,
ENABLE_AUTO_GENERATED_USERNAME: false,
ENABLE_DYNAMIC_REGISTRATION_FIELDS: false,
ENABLE_IMAGE_LAYOUT: false,
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: false,
FAVICON_URL: 'https://edx-cdn.org/v3/default/favicon.ico',
INFO_EMAIL: '',
LOGIN_ISSUE_SUPPORT_LINK: null,
LOGO_TRADEMARK_URL: 'https://edx-cdn.org/v3/default/logo-trademark.svg',
LOGO_URL: 'https://edx-cdn.org/v3/default/logo.svg',
LOGO_WHITE_URL: 'https://edx-cdn.org/v3/default/logo-white.svg',
MARKETING_EMAILS_OPT_IN: '',
MARKETING_SITE_BASE_URL: 'http://local.openedx.io',
PASSWORD_RESET_SUPPORT_LINK: null,
POST_REGISTRATION_REDIRECT_URL: '',
PRIVACY_POLICY: null,
SEARCH_CATALOG_URL: null,
SESSION_COOKIE_DOMAIN: 'local.openedx.io',
SHOW_REGISTRATION_LINKS: true,
TOS_AND_HONOR_CODE: null,
TOS_LINK: null,
USER_RETENTION_COOKIE_NAME: '',
},
};
export default app;

View File

@@ -1,6 +1,4 @@
import React from 'react'; import { IntlProvider } from '@openedx/frontend-base';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { DefaultLargeLayout, DefaultMediumLayout, DefaultSmallLayout } from './index'; import { DefaultLargeLayout, DefaultMediumLayout, DefaultSmallLayout } from './index';

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@openedx/paragon'; import { Hyperlink, Image } from '@openedx/paragon';
import classNames from 'classnames'; import classNames from 'classnames';
@@ -13,20 +10,20 @@ const LargeLayout = () => {
return ( return (
<div className="w-50 d-flex"> <div className="w-50 d-flex">
<div className="col-md-9 bg-primary-400"> <div className="col-md-9 bg-primary-400">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}> <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} /> <Image className="logo position-absolute" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_WHITE_URL} />
</Hyperlink> </Hyperlink>
<div className="min-vh-100 d-flex align-items-center"> <div className="min-vh-100 d-flex align-items-center">
<div className={classNames({ 'large-yellow-line mr-n4.5': getConfig().SITE_NAME === 'edX' })} /> <div className={classNames({ 'large-yellow-line mr-n4.5': getSiteConfig().siteName === 'edX' })} />
<h1 <h1
className={classNames( className={classNames(
'display-2 text-white mw-xs', 'display-2 text-white mw-xs',
{ 'ml-6': getConfig().SITE_NAME !== 'edX' }, { 'ml-6': getSiteConfig().siteName !== 'edX' },
)} )}
> >
{formatMessage(messages['start.learning'])} {formatMessage(messages['start.learning'])}
<div className="text-accent-a"> <div className="text-accent-a">
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })} {formatMessage(messages['with.site.name'], { siteName: getSiteConfig().siteName })}
</div> </div>
</h1> </h1>
</div> </div>

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@openedx/paragon'; import { Hyperlink, Image } from '@openedx/paragon';
import classNames from 'classnames'; import classNames from 'classnames';
@@ -15,22 +12,22 @@ const MediumLayout = () => {
<div className="w-100 medium-screen-top-stripe" /> <div className="w-100 medium-screen-top-stripe" />
<div className="w-100 p-0 mb-3 d-flex"> <div className="w-100 p-0 mb-3 d-flex">
<div className="col-md-10 bg-primary-400"> <div className="col-md-10 bg-primary-400">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}> <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
<Image alt={getConfig().SITE_NAME} className="logo" src={getConfig().LOGO_WHITE_URL} /> <Image alt={getSiteConfig().siteName} className="logo" src={useAppConfig().LOGO_WHITE_URL} />
</Hyperlink> </Hyperlink>
<div className="d-flex align-items-center justify-content-center mb-4 "> <div className="d-flex align-items-center justify-content-center mb-4 ">
<div className={classNames({ 'mt-1 medium-yellow-line': getConfig().SITE_NAME === 'edX' })} /> <div className={classNames({ 'mt-1 medium-yellow-line': getSiteConfig().siteName === 'edX' })} />
<div> <div>
<h1 <h1
className={classNames( className={classNames(
'display-1 text-white mt-5 mb-5 mr-2 main-heading', 'display-1 text-white mt-5 mb-5 mr-2 main-heading',
{ 'ml-4.5': getConfig().SITE_NAME !== 'edX' }, { 'ml-4.5': getSiteConfig().siteName !== 'edX' },
)} )}
> >
<span> <span>
{formatMessage(messages['start.learning'])}{' '} {formatMessage(messages['start.learning'])}{' '}
<span className="text-accent-a d-inline-block"> <span className="text-accent-a d-inline-block">
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })} {formatMessage(messages['with.site.name'], { siteName: getSiteConfig().siteName })}
</span> </span>
</span> </span>
</h1> </h1>

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@openedx/paragon'; import { Hyperlink, Image } from '@openedx/paragon';
import classNames from 'classnames'; import classNames from 'classnames';
@@ -14,11 +11,11 @@ const SmallLayout = () => {
<span className="bg-primary-400 w-100"> <span className="bg-primary-400 w-100">
<div className="col-md-12 small-screen-top-stripe" /> <div className="col-md-12 small-screen-top-stripe" />
<div> <div>
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}> <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} /> <Image className="logo-small" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_WHITE_URL} />
</Hyperlink> </Hyperlink>
<div className="d-flex align-items-center m-3.5"> <div className="d-flex align-items-center m-3.5">
<div className={classNames({ 'small-yellow-line mr-n2.5': getConfig().SITE_NAME === 'edX' })} /> <div className={classNames({ 'small-yellow-line mr-n2.5': getSiteConfig().siteName === 'edX' })} />
<h1 <h1
className={classNames( className={classNames(
'text-white mt-3.5 mb-3.5', 'text-white mt-3.5 mb-3.5',
@@ -27,7 +24,7 @@ const SmallLayout = () => {
<span> <span>
{formatMessage(messages['start.learning'])}{' '} {formatMessage(messages['start.learning'])}{' '}
<span className="text-accent-a d-inline-block"> <span className="text-accent-a d-inline-block">
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })} {formatMessage(messages['with.site.name'], { siteName: getSiteConfig().siteName })}
</span> </span>
</span> </span>
</h1> </h1>

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-platform/i18n'; import { defineMessages } from '@openedx/frontend-base';
const messages = defineMessages({ const messages = defineMessages({
'start.learning': { 'start.learning': {

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@openedx/paragon'; import { Hyperlink, Image } from '@openedx/paragon';
import messages from './messages'; import messages from './messages';
@@ -12,10 +9,10 @@ const ExtraSmallLayout = () => {
return ( return (
<span <span
className="w-100 bg-primary-500 banner__image extra-small-layout" className="w-100 bg-primary-500 banner__image extra-small-layout"
style={{ backgroundImage: `url(${getConfig().BANNER_IMAGE_EXTRA_SMALL})` }} style={{ backgroundImage: `url(${useAppConfig().BANNER_IMAGE_EXTRA_SMALL})` }}
> >
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}> <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
<Image className="company-logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} /> <Image className="company-logo" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_WHITE_URL} />
</Hyperlink> </Hyperlink>
<div className="ml-4.5 mr-1 pb-3.5 pt-3.5"> <div className="ml-4.5 mr-1 pb-3.5 pt-3.5">
<h1 className="banner__heading"> <h1 className="banner__heading">

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@openedx/paragon'; import { Hyperlink, Image } from '@openedx/paragon';
import './index.scss'; import './index.scss';
@@ -13,10 +10,10 @@ const LargeLayout = () => {
return ( return (
<div <div
className="w-50 bg-primary-500 banner__image large-layout" className="w-50 bg-primary-500 banner__image large-layout"
style={{ backgroundImage: `url(${getConfig().BANNER_IMAGE_LARGE})` }} style={{ backgroundImage: `url(${useAppConfig().BANNER_IMAGE_LARGE})` }}
> >
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}> <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
<Image className="company-logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} /> <Image className="company-logo position-absolute" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_WHITE_URL} />
</Hyperlink> </Hyperlink>
<div className="min-vh-100 p-5 d-flex align-items-end"> <div className="min-vh-100 p-5 d-flex align-items-end">
<h1 className="display-2 mw-sm mb-3 d-flex flex-column flex-shrink-0 justify-content-center"> <h1 className="display-2 mw-sm mb-3 d-flex flex-column flex-shrink-0 justify-content-center">

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@openedx/paragon'; import { Hyperlink, Image } from '@openedx/paragon';
import './index.scss'; import './index.scss';
@@ -13,10 +10,10 @@ const MediumLayout = () => {
return ( return (
<div <div
className="w-100 mb-3 bg-primary-500 banner__image medium-layout" className="w-100 mb-3 bg-primary-500 banner__image medium-layout"
style={{ backgroundImage: `url(${getConfig().BANNER_IMAGE_MEDIUM})` }} style={{ backgroundImage: `url(${useAppConfig().BANNER_IMAGE_MEDIUM})` }}
> >
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}> <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
<Image className="company-logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} /> <Image className="company-logo" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_WHITE_URL} />
</Hyperlink> </Hyperlink>
<div className="ml-5 pb-4 pt-4"> <div className="ml-5 pb-4 pt-4">
<h1 className="display-2 banner__heading"> <h1 className="display-2 banner__heading">

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@openedx/paragon'; import { Hyperlink, Image } from '@openedx/paragon';
import messages from './messages'; import messages from './messages';
@@ -12,10 +9,10 @@ const SmallLayout = () => {
return ( return (
<span <span
className="w-100 bg-primary-500 banner__image small-layout" className="w-100 bg-primary-500 banner__image small-layout"
style={{ backgroundImage: `url(${getConfig().BANNER_IMAGE_SMALL})` }} style={{ backgroundImage: `url(${useAppConfig().BANNER_IMAGE_SMALL})` }}
> >
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}> <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
<Image className="company-logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} /> <Image className="company-logo" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_WHITE_URL} />
</Hyperlink> </Hyperlink>
<div className="ml-5 mr-1 pb-3.5 pt-3.5"> <div className="ml-5 mr-1 pb-3.5 pt-3.5">
<h1 className="display-2"> <h1 className="display-2">

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-platform/i18n'; import { defineMessages } from '@openedx/frontend-base';
const messages = defineMessages({ const messages = defineMessages({
'your.career.turning.point': { 'your.career.turning.point': {

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@openedx/paragon'; import { Hyperlink, Image } from '@openedx/paragon';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -13,14 +10,14 @@ const LargeLayout = ({ fullName }) => {
return ( return (
<div className="w-50 d-flex"> <div className="w-50 d-flex">
<div className="col-md-10 bg-light-200 p-0"> <div className="col-md-10 bg-light-200 p-0">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}> <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} /> <Image className="logo position-absolute" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_URL} />
</Hyperlink> </Hyperlink>
<div className="min-vh-100 d-flex align-items-center"> <div className="min-vh-100 d-flex align-items-center">
<div className="large-screen-left-container mr-n4.5 large-yellow-line mt-5" /> <div className="large-screen-left-container mr-n4.5 large-yellow-line mt-5" />
<div> <div>
<h1 className="welcome-to-platform data-hj-suppress"> <h1 className="welcome-to-platform data-hj-suppress">
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, fullName })} {formatMessage(messages['welcome.to.platform'], { siteName: getSiteConfig().siteName, fullName })}
</h1> </h1>
<h2 className="complete-your-profile"> <h2 className="complete-your-profile">
{formatMessage(messages['complete.your.profile.1'])} {formatMessage(messages['complete.your.profile.1'])}

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@openedx/paragon'; import { Hyperlink, Image } from '@openedx/paragon';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -15,14 +12,14 @@ const MediumLayout = ({ fullName }) => {
<div className="w-100 medium-screen-top-stripe" /> <div className="w-100 medium-screen-top-stripe" />
<div className="w-100 p-0 mb-3 d-flex"> <div className="w-100 p-0 mb-3 d-flex">
<div className="col-md-10 bg-light-200"> <div className="col-md-10 bg-light-200">
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}> <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} /> <Image className="logo" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_URL} />
</Hyperlink> </Hyperlink>
<div className="d-flex align-items-center justify-content-center mb-4 ml-5"> <div className="d-flex align-items-center justify-content-center mb-4 ml-5">
<div className="medium-yellow-line mt-5 mr-n2" /> <div className="medium-yellow-line mt-5 mr-n2" />
<div> <div>
<h1 className="h3 data-hj-suppress mw-320"> <h1 className="h3 data-hj-suppress mw-320">
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, fullName })} {formatMessage(messages['welcome.to.platform'], { siteName: getSiteConfig().siteName, fullName })}
</h1> </h1>
<h2 className="display-1"> <h2 className="display-1">
{formatMessage(messages['complete.your.profile.1'])} {formatMessage(messages['complete.your.profile.1'])}

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Image } from '@openedx/paragon'; import { Hyperlink, Image } from '@openedx/paragon';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -13,14 +10,14 @@ const SmallLayout = ({ fullName }) => {
return ( return (
<div className="min-vw-100 bg-light-200"> <div className="min-vw-100 bg-light-200">
<div className="col-md-12 small-screen-top-stripe" /> <div className="col-md-12 small-screen-top-stripe" />
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}> <Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} /> <Image className="logo-small" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_URL} />
</Hyperlink> </Hyperlink>
<div className="d-flex align-items-center m-3.5"> <div className="d-flex align-items-center m-3.5">
<div className="small-yellow-line mt-4.5" /> <div className="small-yellow-line mt-4.5" />
<div> <div>
<h1 className="h5 data-hj-suppress"> <h1 className="h5 data-hj-suppress">
{formatMessage(messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, fullName })} {formatMessage(messages['welcome.to.platform'], { siteName: getSiteConfig().siteName, fullName })}
</h1> </h1>
<h2 className="h1"> <h2 className="h1">
{formatMessage(messages['complete.your.profile.1'])} {formatMessage(messages['complete.your.profile.1'])}

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-platform/i18n'; import { defineMessages } from '@openedx/frontend-base';
const messages = defineMessages({ const messages = defineMessages({
'welcome.to.platform': { 'welcome.to.platform': {

View File

@@ -1,6 +1,4 @@
import React from 'react'; import { useAppConfig } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { breakpoints } from '@openedx/paragon'; import { breakpoints } from '@openedx/paragon';
import classNames from 'classnames'; import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -13,7 +11,7 @@ import {
import { AuthLargeLayout, AuthMediumLayout, AuthSmallLayout } from './components/welcome-page-layout'; import { AuthLargeLayout, AuthMediumLayout, AuthSmallLayout } from './components/welcome-page-layout';
const BaseContainer = ({ children, showWelcomeBanner, fullName }) => { const BaseContainer = ({ children, showWelcomeBanner, fullName }) => {
const enableImageLayout = getConfig().ENABLE_IMAGE_LAYOUT; const enableImageLayout = useAppConfig().ENABLE_IMAGE_LAYOUT;
if (enableImageLayout) { if (enableImageLayout) {
return ( return (

View File

@@ -1,11 +1,9 @@
import React from 'react'; import { IntlProvider, mergeAppConfig } from '@openedx/frontend-base';
import { mergeConfig } from '@edx/frontend-platform';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { Context as ResponsiveContext } from 'react-responsive'; import { Context as ResponsiveContext } from 'react-responsive';
import BaseContainer from '../index'; import BaseContainer from '../index';
import { appId } from '../../constants';
const LargeScreen = { const LargeScreen = {
wrappingComponent: ResponsiveContext.Provider, wrappingComponent: ResponsiveContext.Provider,
@@ -28,7 +26,7 @@ describe('Base component tests', () => {
}); });
it('renders Image layout when ENABLE_IMAGE_LAYOUT configuration is enabled', () => { it('renders Image layout when ENABLE_IMAGE_LAYOUT configuration is enabled', () => {
mergeConfig({ mergeAppConfig(appId, {
ENABLE_IMAGE_LAYOUT: true, ENABLE_IMAGE_LAYOUT: true,
}); });

View File

@@ -1,5 +1,3 @@
import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Navigate } from 'react-router-dom'; import { Navigate } from 'react-router-dom';

View File

@@ -1,8 +1,5 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
import { import {
Button, Form, Button, Form,
Icon, Icon,
@@ -10,8 +7,8 @@ import {
import { Login } from '@openedx/paragon/icons'; import { Login } from '@openedx/paragon/icons';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import messages from './messages';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants'; import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
import messages from './messages';
/** /**
* This component renders the Single sign-on (SSO) button only for the tpa provider passed * This component renders the Single sign-on (SSO) button only for the tpa provider passed
@@ -19,12 +16,12 @@ import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
const EnterpriseSSO = (props) => { const EnterpriseSSO = (props) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const tpaProvider = props.provider; const tpaProvider = props.provider;
const hideRegistrationLink = getConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false const hideRegistrationLink = useAppConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false
|| getConfig().SHOW_REGISTRATION_LINKS === false; || useAppConfig().SHOW_REGISTRATION_LINKS === false;
const handleSubmit = (e, url) => { const handleSubmit = (e, url) => {
e.preventDefault(); e.preventDefault();
window.location.href = getConfig().LMS_BASE_URL + url; window.location.href = getSiteConfig().lmsBaseUrl + url;
}; };
const handleClick = (e) => { const handleClick = (e) => {

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react'; import { useState } from 'react';
import { import {
Form, TransitionReplace, Form, TransitionReplace,

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { getSiteConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Button, Hyperlink, Icon } from '@openedx/paragon'; import { Button, Hyperlink, Icon } from '@openedx/paragon';
import { Institution } from '@openedx/paragon/icons'; import { Institution } from '@openedx/paragon/icons';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -31,7 +28,7 @@ export const RenderInstitutionButton = props => {
* This component renders the page list of available institutions for login * This component renders the page list of available institutions for login
* */ * */
const InstitutionLogistration = props => { const InstitutionLogistration = props => {
const lmsBaseUrl = getConfig().LMS_BASE_URL; const lmsBaseUrl = getSiteConfig().lmsBaseUrl;
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { const {
secondaryProviders, secondaryProviders,

View File

@@ -1,6 +1,4 @@
import React from 'react'; import { FormattedMessage } from '@openedx/frontend-base';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
const NotFoundPage = () => ( const NotFoundPage = () => (
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center"> <div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react'; import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useIntl } from '@edx/frontend-platform/i18n'; import { useIntl } from '@openedx/frontend-base';
import { import {
Form, Icon, IconButton, OverlayTrigger, Tooltip, useToggle, Form, Icon, IconButton, OverlayTrigger, Tooltip, useToggle,
} from '@openedx/paragon'; } from '@openedx/paragon';
@@ -10,10 +10,10 @@ import {
} from '@openedx/paragon/icons'; } from '@openedx/paragon/icons';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import messages from './messages';
import { LETTER_REGEX, NUMBER_REGEX } from '../data/constants'; import { LETTER_REGEX, NUMBER_REGEX } from '../data/constants';
import { clearRegistrationBackendError, fetchRealtimeValidations } from '../register/data/actions'; import { clearRegistrationBackendError, fetchRealtimeValidations } from '../register/data/actions';
import { validatePasswordField } from '../register/data/utils'; import { validatePasswordField } from '../register/data/utils';
import messages from './messages';
const PasswordField = (props) => { const PasswordField = (props) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();

View File

@@ -1,9 +1,9 @@
import { getConfig } from '@edx/frontend-platform'; import { useAppConfig, getSiteConfig } from '@openedx/frontend-base';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Navigate } from 'react-router-dom'; import { Navigate } from 'react-router-dom';
import { import {
AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS, REDIRECT, AUTHN_PROGRESSIVE_PROFILING, REDIRECT,
} from '../data/constants'; } from '../data/constants';
import { setCookie } from '../data/utils'; import { setCookie } from '../data/utils';
@@ -15,7 +15,6 @@ const RedirectLogistration = (props) => {
redirectToProgressiveProfilingPage, redirectToProgressiveProfilingPage,
success, success,
optionalFields, optionalFields,
redirectToRecommendationsPage,
educationLevel, educationLevel,
userId, userId,
registrationEmbedded, registrationEmbedded,
@@ -29,7 +28,7 @@ const RedirectLogistration = (props) => {
// Note: For multiple enterprise use case, we need to make sure that user first visits the // 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 // enterprise selection page and then complete the auth workflow
if (finishAuthUrl && !redirectUrl.includes(finishAuthUrl)) { if (finishAuthUrl && !redirectUrl.includes(finishAuthUrl)) {
finalRedirectUrl = getConfig().LMS_BASE_URL + finishAuthUrl; finalRedirectUrl = getSiteConfig().lmsBaseUrl + finishAuthUrl;
} else { } else {
finalRedirectUrl = redirectUrl; finalRedirectUrl = redirectUrl;
} }
@@ -37,12 +36,12 @@ const RedirectLogistration = (props) => {
// Redirect to Progressive Profiling after successful registration // Redirect to Progressive Profiling after successful registration
if (redirectToProgressiveProfilingPage) { if (redirectToProgressiveProfilingPage) {
// TODO: Do we still need this cookie? // TODO: Do we still need this cookie?
setCookie('van-504-returning-user', true); setCookie('van-504-returning-user', true, useAppConfig().SESSION_COOKIE_DOMAIN);
if (registrationEmbedded) { if (registrationEmbedded) {
window.parent.postMessage({ window.parent.postMessage({
action: REDIRECT, action: REDIRECT,
redirectUrl: getConfig().POST_REGISTRATION_REDIRECT_URL, redirectUrl: useAppConfig().POST_REGISTRATION_REDIRECT_URL,
}, host); }, host);
return null; return null;
} }
@@ -60,22 +59,6 @@ const RedirectLogistration = (props) => {
); );
} }
// Redirect to Recommendation page
if (redirectToRecommendationsPage) {
const registrationResult = { redirectUrl: finalRedirectUrl, success };
return (
<Navigate
to={RECOMMENDATIONS}
state={{
registrationResult,
educationLevel,
userId,
}}
replace
/>
);
}
window.location.href = finalRedirectUrl; window.location.href = finalRedirectUrl;
} }
@@ -90,7 +73,6 @@ RedirectLogistration.defaultProps = {
redirectUrl: '', redirectUrl: '',
redirectToProgressiveProfilingPage: false, redirectToProgressiveProfilingPage: false,
optionalFields: {}, optionalFields: {},
redirectToRecommendationsPage: false,
userId: null, userId: null,
registrationEmbedded: false, registrationEmbedded: false,
host: '', host: '',
@@ -104,7 +86,6 @@ RedirectLogistration.propTypes = {
redirectUrl: PropTypes.string, redirectUrl: PropTypes.string,
redirectToProgressiveProfilingPage: PropTypes.bool, redirectToProgressiveProfilingPage: PropTypes.bool,
optionalFields: PropTypes.shape({}), optionalFields: PropTypes.shape({}),
redirectToRecommendationsPage: PropTypes.bool,
userId: PropTypes.number, userId: PropTypes.number,
registrationEmbedded: PropTypes.bool, registrationEmbedded: PropTypes.bool,
host: PropTypes.string, host: PropTypes.string,

View File

@@ -1,14 +1,11 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { getSiteConfig, useIntl } from '@openedx/frontend-base';
import { Icon } from '@openedx/paragon'; import { Icon } from '@openedx/paragon';
import { Login } from '@openedx/paragon/icons'; import { Login } from '@openedx/paragon/icons';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import messages from './messages';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants'; import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
import messages from './messages';
const SocialAuthProviders = (props) => { const SocialAuthProviders = (props) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@@ -18,7 +15,7 @@ const SocialAuthProviders = (props) => {
e.preventDefault(); e.preventDefault();
const url = e.currentTarget.dataset.providerUrl; const url = e.currentTarget.dataset.providerUrl;
window.location.href = getConfig().LMS_BASE_URL + url; window.location.href = getSiteConfig().lmsBaseUrl + url;
} }
const socialAuth = socialAuthProviders.map((provider, index) => ( const socialAuth = socialAuthProviders.map((provider, index) => (

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { import {
Hyperlink, Icon, Hyperlink, Icon,
} from '@openedx/paragon'; } from '@openedx/paragon';
@@ -10,10 +7,10 @@ import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Skeleton from 'react-loading-skeleton'; import Skeleton from 'react-loading-skeleton';
import messages from './messages';
import { import {
ENTERPRISE_LOGIN_URL, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE, ENTERPRISE_LOGIN_URL, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE,
} from '../data/constants'; } from '../data/constants';
import messages from './messages';
import { import {
RenderInstitutionButton, RenderInstitutionButton,
@@ -35,8 +32,8 @@ const ThirdPartyAuth = (props) => {
} = props; } = props;
const isInstitutionAuthActive = !!secondaryProviders.length && !currentProvider; const isInstitutionAuthActive = !!secondaryProviders.length && !currentProvider;
const isSocialAuthActive = !!providers.length && !currentProvider; const isSocialAuthActive = !!providers.length && !currentProvider;
const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN; const isEnterpriseLoginDisabled = useAppConfig().DISABLE_ENTERPRISE_LOGIN;
const enterpriseLoginURL = getConfig().LMS_BASE_URL + ENTERPRISE_LOGIN_URL; const enterpriseLoginURL = getSiteConfig().lmsBaseUrl + ENTERPRISE_LOGIN_URL;
const isThirdPartyAuthActive = isSocialAuthActive || (isEnterpriseLoginDisabled && isInstitutionAuthActive); const isThirdPartyAuthActive = isSocialAuthActive || (isEnterpriseLoginDisabled && isInstitutionAuthActive);
return ( return (

View File

@@ -1,17 +1,14 @@
import React from 'react'; import { getSiteConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Alert } from '@openedx/paragon'; import { Alert } from '@openedx/paragon';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import messages from './messages';
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants'; import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
import messages from './messages';
const ThirdPartyAuthAlert = (props) => { const ThirdPartyAuthAlert = (props) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { currentProvider, referrer } = props; const { currentProvider, referrer } = props;
const platformName = getConfig().SITE_NAME; const platformName = getSiteConfig().siteName;
let message; let message;
if (referrer === LOGIN_PAGE) { if (referrer === LOGIN_PAGE) {

View File

@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { getConfig } from '@edx/frontend-platform'; import { fetchAuthenticatedUser, getAuthenticatedUser, getSiteConfig } from '@openedx/frontend-base';
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
@@ -25,7 +24,7 @@ const UnAuthOnlyRoute = ({ children }) => {
if (isReady) { if (isReady) {
if (authUser && authUser.username) { if (authUser && authUser.username) {
global.location.href = getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL); global.location.href = getSiteConfig().lmsBaseUrl.concat(DEFAULT_REDIRECT_URL);
return null; return null;
} }

View File

@@ -1,61 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import Zendesk from 'react-zendesk';
import messages from './messages';
import { REGISTER_EMBEDDED_PAGE } from '../data/constants';
const ZendeskHelp = () => {
const { formatMessage } = useIntl();
const setting = {
cookies: true,
webWidget: {
contactOptions: {
enabled: false,
},
chat: {
suppress: false,
departments: {
enabled: ['account settings', 'billing and payments', 'certificates', 'deadlines', 'errors and technical issues', 'other', 'proctoring'],
},
},
contactForm: {
ticketForms: [
{
id: 360003368814,
subject: false,
fields: [{ id: 'description', prefill: { '*': '' } }],
},
],
selectTicketForm: {
'*': formatMessage(messages.selectTicketForm),
},
attachments: true,
},
helpCenter: {
originalArticleButton: true,
},
answerBot: {
suppress: false,
contactOnlyAfterQuery: true,
title: { '*': formatMessage(messages.supportTitle) },
avatar: {
url: getConfig().ZENDESK_LOGO_URL,
name: { '*': formatMessage(messages.supportTitle) },
},
},
},
};
if (window.location.pathname === REGISTER_EMBEDDED_PAGE) {
return null;
}
return (
<Zendesk defer zendeskKey={getConfig().ZENDESK_KEY} {...setting} />
);
};
export default ZendeskHelp;

View File

@@ -1,6 +1,7 @@
import { logError } from '@edx/frontend-platform/logging'; import { logError } from '@openedx/frontend-base';
import { call, put, takeEvery } from 'redux-saga/effects'; import { call, put, takeEvery } from 'redux-saga/effects';
import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions';
import { import {
getThirdPartyAuthContextBegin, getThirdPartyAuthContextBegin,
getThirdPartyAuthContextFailure, getThirdPartyAuthContextFailure,
@@ -10,7 +11,6 @@ import {
import { import {
getThirdPartyAuthContext, getThirdPartyAuthContext,
} from './service'; } from './service';
import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions';
export function* fetchThirdPartyAuthContext(action) { export function* fetchThirdPartyAuthContext(action) {
try { try {

View File

@@ -1,7 +1,5 @@
import { getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient, getSiteConfig } from '@openedx/frontend-base';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
// eslint-disable-next-line import/prefer-default-export
export async function getThirdPartyAuthContext(urlParams) { export async function getThirdPartyAuthContext(urlParams) {
const requestConfig = { const requestConfig = {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
@@ -11,7 +9,7 @@ export async function getThirdPartyAuthContext(urlParams) {
const { data } = await getAuthenticatedHttpClient() const { data } = await getAuthenticatedHttpClient()
.get( .get(
`${getConfig().LMS_BASE_URL}/api/mfe_context`, `${getSiteConfig().lmsBaseUrl}/api/mfe_context`,
requestConfig, requestConfig,
) )
.catch((e) => { .catch((e) => {

View File

@@ -1,12 +1,12 @@
import { runSaga } from 'redux-saga'; import { runSaga } from 'redux-saga';
import { setCountryFromThirdPartyAuthContext } from '../../../register/data/actions'; import { setCountryFromThirdPartyAuthContext } from '../../../register/data/actions';
import initializeMockLogging from '../../../setupTest'; import { initializeMockServices } from '../../../setupTest';
import * as actions from '../actions'; import * as actions from '../actions';
import { fetchThirdPartyAuthContext } from '../sagas'; import { fetchThirdPartyAuthContext } from '../sagas';
import * as api from '../service'; import * as api from '../service';
const { loggingService } = initializeMockLogging(); const { loggingService } = initializeMockServices();
describe('fetchThirdPartyAuthContext', () => { describe('fetchThirdPartyAuthContext', () => {
const params = { const params = {

View File

@@ -12,4 +12,3 @@ export { default as saga } from './data/sagas';
export { storeName } from './data/selectors'; export { storeName } from './data/selectors';
export { default as FormGroup } from './FormGroup'; export { default as FormGroup } from './FormGroup';
export { default as PasswordField } from './PasswordField'; export { default as PasswordField } from './PasswordField';
export { default as Zendesk } from './Zendesk';

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-platform/i18n'; import { defineMessages } from '@openedx/frontend-base';
const messages = defineMessages({ const messages = defineMessages({
// institution login strings // institution login strings
@@ -102,16 +102,6 @@ const messages = defineMessages({
defaultMessage: 'Finish creating your account', defaultMessage: 'Finish creating your account',
description: 'Heading that appears above form when user is trying to create account using social auth', description: 'Heading that appears above form when user is trying to create account using social auth',
}, },
supportTitle: {
id: 'zendesk.supportTitle',
description: 'Title for the support button',
defaultMessage: 'edX Support',
},
selectTicketForm: {
id: 'zendesk.selectTicketForm',
description: 'Select ticket form',
defaultMessage: 'Please choose your request type:',
},
'registration.other.options.heading': { 'registration.other.options.heading': {
id: 'registration.other.options.heading', id: 'registration.other.options.heading',
defaultMessage: 'Or register with:', defaultMessage: 'Or register with:',

View File

@@ -1,8 +1,7 @@
/* eslint-disable import/no-import-module-exports */ /* eslint-disable import/no-import-module-exports */
/* eslint-disable react/function-component-definition */ /* eslint-disable react/function-component-definition */
import React from 'react';
import { getConfig } from '@edx/frontend-platform'; import { getSiteConfig } from '@openedx/frontend-base';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { import {
@@ -60,7 +59,7 @@ describe('EmbeddedRegistrationRoute', () => {
it('should render embedded register page if host query param is available in the url (embedded)', async () => { it('should render embedded register page if host query param is available in the url (embedded)', async () => {
delete window.location; delete window.location;
window.location = { window.location = {
href: getConfig().BASE_URL.concat(REGISTER_EMBEDDED_PAGE), href: getSiteConfig().baseUrl.concat(REGISTER_EMBEDDED_PAGE),
search: '?host=http://localhost/host-websit', search: '?host=http://localhost/host-websit',
}; };

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n'; import { injectIntl, IntlProvider } from '@openedx/frontend-base';
import { fireEvent, render } from '@testing-library/react'; import { fireEvent, render } from '@testing-library/react';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';

View File

@@ -1,6 +1,4 @@
import React from 'react'; import { IntlProvider } from '@openedx/frontend-base';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import registerIcons from '../RegisterFaIcons'; import registerIcons from '../RegisterFaIcons';

View File

@@ -1,6 +1,4 @@
import React from 'react'; import { IntlProvider } from '@openedx/frontend-base';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import { REGISTER_PAGE } from '../../data/constants'; import { REGISTER_PAGE } from '../../data/constants';

View File

@@ -1,8 +1,7 @@
/* eslint-disable import/no-import-module-exports */ /* eslint-disable import/no-import-module-exports */
/* eslint-disable react/function-component-definition */ /* eslint-disable react/function-component-definition */
import React from 'react';
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { fetchAuthenticatedUser, getAuthenticatedUser } from '@openedx/frontend-base';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { import {
@@ -12,7 +11,8 @@ import {
import { UnAuthOnlyRoute } from '..'; import { UnAuthOnlyRoute } from '..';
import { REGISTER_PAGE } from '../../data/constants'; import { REGISTER_PAGE } from '../../data/constants';
jest.mock('@edx/frontend-platform/auth', () => ({ jest.mock('@openedx/frontend-base', () => ({
...jest.requireActual('@openedx/frontend-base'),
getAuthenticatedUser: jest.fn(), getAuthenticatedUser: jest.fn(),
fetchAuthenticatedUser: jest.fn(), fetchAuthenticatedUser: jest.fn(),
})); }));

View File

@@ -1,17 +0,0 @@
import { IntlProvider } from '@edx/frontend-platform/i18n';
import renderer from 'react-test-renderer';
import Zendesk from '../Zendesk';
jest.mock('react-zendesk', () => 'Zendesk');
describe('Zendesk Help', () => {
it('should match login page third party auth alert message snapshot', () => {
const tree = renderer.create(
<IntlProvider locale="en">
<Zendesk />
</IntlProvider>,
).toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@@ -13,7 +13,7 @@ exports[`ThirdPartyAuthAlert should match login page third party auth alert mess
className="alert-message-content" className="alert-message-content"
> >
<p> <p>
You have successfully signed into Google, but your Google account does not have a linked Your Platform Name Here account. To link your accounts, sign in now using your Your Platform Name Here password. You have successfully signed into Google, but your Google account does not have a linked Test Site account. To link your accounts, sign in now using your Test Site password.
</p> </p>
</div> </div>
</div> </div>
@@ -39,7 +39,7 @@ exports[`ThirdPartyAuthAlert should match register page third party auth alert m
Almost done! Almost done!
</div> </div>
<p> <p>
You've successfully signed into Google! We just need a little more information before you start learning with Your Platform Name Here. You've successfully signed into Google! We just need a little more information before you start learning with Test Site.
</p> </p>
</div> </div>
</div> </div>

View File

@@ -1,65 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Zendesk Help should match login page third party auth alert message snapshot 1`] = `
<Zendesk
cookies={true}
defer={true}
webWidget={
{
"answerBot": {
"avatar": {
"name": {
"*": "edX Support",
},
"url": undefined,
},
"contactOnlyAfterQuery": true,
"suppress": false,
"title": {
"*": "edX Support",
},
},
"chat": {
"departments": {
"enabled": [
"account settings",
"billing and payments",
"certificates",
"deadlines",
"errors and technical issues",
"other",
"proctoring",
],
},
"suppress": false,
},
"contactForm": {
"attachments": true,
"selectTicketForm": {
"*": "Please choose your request type:",
},
"ticketForms": [
{
"fields": [
{
"id": "description",
"prefill": {
"*": "",
},
},
],
"id": 360003368814,
"subject": false,
},
],
},
"contactOptions": {
"enabled": false,
},
"helpCenter": {
"originalArticleButton": true,
},
}
}
/>
`;

View File

@@ -1,40 +0,0 @@
const configuration = {
// Cookies related configs
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN,
USER_RETENTION_COOKIE_NAME: process.env.USER_RETENTION_COOKIE_NAME || '',
// Features
DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '',
ENABLE_AUTO_GENERATED_USERNAME: process.env.ENABLE_AUTO_GENERATED_USERNAME || false,
ENABLE_DYNAMIC_REGISTRATION_FIELDS: process.env.ENABLE_DYNAMIC_REGISTRATION_FIELDS || false,
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: process.env.ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN || false,
ENABLE_POST_REGISTRATION_RECOMMENDATIONS: process.env.ENABLE_POST_REGISTRATION_RECOMMENDATIONS || false,
MARKETING_EMAILS_OPT_IN: process.env.MARKETING_EMAILS_OPT_IN || '',
SHOW_CONFIGURABLE_EDX_FIELDS: process.env.SHOW_CONFIGURABLE_EDX_FIELDS || false,
SHOW_REGISTRATION_LINKS: process.env.SHOW_REGISTRATION_LINKS !== 'false',
ENABLE_IMAGE_LAYOUT: process.env.ENABLE_IMAGE_LAYOUT || false,
// Links
ACTIVATION_EMAIL_SUPPORT_LINK: process.env.ACTIVATION_EMAIL_SUPPORT_LINK || null,
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: process.env.AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK || null,
LOGIN_ISSUE_SUPPORT_LINK: process.env.LOGIN_ISSUE_SUPPORT_LINK || null,
PASSWORD_RESET_SUPPORT_LINK: process.env.PASSWORD_RESET_SUPPORT_LINK || null,
POST_REGISTRATION_REDIRECT_URL: process.env.POST_REGISTRATION_REDIRECT_URL || '',
PRIVACY_POLICY: process.env.PRIVACY_POLICY || null,
SEARCH_CATALOG_URL: process.env.SEARCH_CATALOG_URL || null,
TOS_AND_HONOR_CODE: process.env.TOS_AND_HONOR_CODE || null,
TOS_LINK: process.env.TOS_LINK || null,
// Base container images
BANNER_IMAGE_LARGE: process.env.BANNER_IMAGE_LARGE || '',
BANNER_IMAGE_MEDIUM: process.env.BANNER_IMAGE_MEDIUM || '',
BANNER_IMAGE_SMALL: process.env.BANNER_IMAGE_SMALL || '',
BANNER_IMAGE_EXTRA_SMALL: process.env.BANNER_IMAGE_EXTRA_SMALL || '',
// Recommendation constants
GENERAL_RECOMMENDATIONS: process.env.GENERAL_RECOMMENDATIONS || '[]',
// Miscellaneous
INFO_EMAIL: process.env.INFO_EMAIL || '',
ZENDESK_KEY: process.env.ZENDESK_KEY,
ZENDESK_LOGO_URL: process.env.ZENDESK_LOGO_URL,
ALGOLIA_APP_ID: process.env.ALGOLIA_APP_ID || '',
ALGOLIA_SEARCH_API_KEY: process.env.ALGOLIA_SEARCH_API_KEY || '',
};
export default configuration;

1
src/constants.ts Normal file
View File

@@ -0,0 +1 @@
export const appId = 'org.openedx.frontend.app.authn';

View File

@@ -1,20 +0,0 @@
import { getConfig } from '@edx/frontend-platform';
import algoliasearch from 'algoliasearch';
// initialize Algolia workers
const initializeSearchClient = () => algoliasearch(
getConfig().ALGOLIA_APP_ID,
getConfig().ALGOLIA_SEARCH_API_KEY,
);
const getLocationRestrictionFilter = (userCountry) => {
if (userCountry) {
return `NOT blocked_in:"${userCountry}" AND (allowed_in:"null" OR allowed_in:"${userCountry}")`;
}
return '';
};
export {
initializeSearchClient,
getLocationRestrictionFilter,
};

View File

@@ -1,4 +1,4 @@
import { getConfig } from '@edx/frontend-platform'; import { getSiteConfig } from '@openedx/frontend-base';
import { composeWithDevTools } from '@redux-devtools/extension'; import { composeWithDevTools } from '@redux-devtools/extension';
import { applyMiddleware, compose, createStore } from 'redux'; import { applyMiddleware, compose, createStore } from 'redux';
import { createLogger } from 'redux-logger'; import { createLogger } from 'redux-logger';
@@ -11,7 +11,7 @@ import rootSaga from './sagas';
const sagaMiddleware = createSagaMiddleware(); const sagaMiddleware = createSagaMiddleware();
function composeMiddleware() { function composeMiddleware() {
if (getConfig().ENVIRONMENT === 'development') { if (getSiteConfig().environment === 'development') {
const loggerMiddleware = createLogger({ const loggerMiddleware = createLogger({
collapsed: true, collapsed: true,
}); });

View File

@@ -5,7 +5,6 @@ export const REGISTER_EMBEDDED_PAGE = '/register-embedded';
export const RESET_PAGE = '/reset'; export const RESET_PAGE = '/reset';
export const AUTHN_PROGRESSIVE_PROFILING = '/welcome'; export const AUTHN_PROGRESSIVE_PROFILING = '/welcome';
export const DEFAULT_REDIRECT_URL = '/dashboard'; export const DEFAULT_REDIRECT_URL = '/dashboard';
export const RECOMMENDATIONS = '/recommendations';
export const PASSWORD_RESET_CONFIRM = '/password_reset_confirm/:token/'; export const PASSWORD_RESET_CONFIRM = '/password_reset_confirm/:token/';
export const PAGE_NOT_FOUND = '/notfound'; export const PAGE_NOT_FOUND = '/notfound';
export const ENTERPRISE_LOGIN_URL = '/enterprise/login'; export const ENTERPRISE_LOGIN_URL = '/enterprise/login';

59
src/data/countries.ts Normal file
View File

@@ -0,0 +1,59 @@
import { getPrimaryLanguageSubtag } from '@openedx/frontend-base';
import COUNTRIES, { langs as countryLangs } from 'i18n-iso-countries';
import arLocale from 'i18n-iso-countries/langs/ar.json';
import caLocale from 'i18n-iso-countries/langs/ca.json';
import enLocale from 'i18n-iso-countries/langs/en.json';
import esLocale from 'i18n-iso-countries/langs/es.json';
import frLocale from 'i18n-iso-countries/langs/fr.json';
import heLocale from 'i18n-iso-countries/langs/he.json';
import idLocale from 'i18n-iso-countries/langs/id.json';
import koLocale from 'i18n-iso-countries/langs/ko.json';
import plLocale from 'i18n-iso-countries/langs/pl.json';
import ptLocale from 'i18n-iso-countries/langs/pt.json';
import ruLocale from 'i18n-iso-countries/langs/ru.json';
import ukLocale from 'i18n-iso-countries/langs/uk.json';
import zhLocale from 'i18n-iso-countries/langs/zh.json';
COUNTRIES.registerLocale(arLocale);
COUNTRIES.registerLocale(enLocale);
COUNTRIES.registerLocale(esLocale);
COUNTRIES.registerLocale(frLocale);
COUNTRIES.registerLocale(zhLocale);
COUNTRIES.registerLocale(caLocale);
COUNTRIES.registerLocale(heLocale);
COUNTRIES.registerLocale(idLocale);
COUNTRIES.registerLocale(koLocale);
COUNTRIES.registerLocale(plLocale);
COUNTRIES.registerLocale(ptLocale);
COUNTRIES.registerLocale(ruLocale);
COUNTRIES.registerLocale(ukLocale);
/**
* Provides a lookup table of country IDs to country names for the current locale.
*
* @memberof module:I18n
*/
export function getCountryMessages(locale) {
const primaryLanguageSubtag = getPrimaryLanguageSubtag(locale);
const languageCode = countryLangs().includes(primaryLanguageSubtag) ? primaryLanguageSubtag : 'en';
return COUNTRIES.getNames(languageCode);
}
/**
* Provides a list of countries represented as objects of the following shape:
*
* {
* key, // The ID of the country
* name // The localized name of the country
* }
*
* TODO: ARCH-878: The list should be sorted alphabetically in the current locale.
* This is useful for populating dropdowns.
*
* @memberof module:I18n
*/
export function getCountryList(locale) {
const countryMessages = getCountryMessages(locale);
return Object.entries(countryMessages).map(([code, name]) => ({ code, name }));
}

View File

@@ -1,17 +0,0 @@
import {
createInstance,
} from '@optimizely/react-sdk';
const OPTIMIZELY_SDK_KEY = process.env.OPTIMIZELY_FULL_STACK_SDK_KEY;
const getOptimizelyInstance = () => {
if (OPTIMIZELY_SDK_KEY) {
return createInstance({
sdkKey: OPTIMIZELY_SDK_KEY,
});
}
return null;
};
export default getOptimizelyInstance();

View File

@@ -1,16 +0,0 @@
import { getLocationRestrictionFilter } from '../algolia';
describe('algoliaUtilsTests', () => {
it('test getLocationRestrictionFilter returns filter if country is passed', () => {
const countryCode = 'PK';
const filter = getLocationRestrictionFilter(countryCode);
const expectedFilter = `NOT blocked_in:"${countryCode}" AND (allowed_in:"null" OR allowed_in:"${countryCode}")`;
expect(filter).toEqual(expectedFilter);
});
it('test getLocationRestrictionFilter returns empty string if country is not passed', () => {
const countryCode = '';
const filter = getLocationRestrictionFilter(countryCode);
const expectedFilter = '';
expect(filter).toEqual(expectedFilter);
});
});

View File

@@ -1,13 +1,7 @@
import { getConfig } from '@edx/frontend-platform';
import Cookies from 'universal-cookie'; import Cookies from 'universal-cookie';
import { setCookie } from '../utils'; import { setCookie } from '../utils';
// Mock getConfig function
jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(),
}));
// Mock Cookies class // Mock Cookies class
jest.mock('universal-cookie'); jest.mock('universal-cookie');
@@ -17,9 +11,7 @@ describe('setCookie function', () => {
}); });
it('should set a cookie with default options', () => { it('should set a cookie with default options', () => {
getConfig.mockReturnValue({ SESSION_COOKIE_DOMAIN: 'example.com' }); setCookie('testCookie', 'testValue', 'example.com');
setCookie('testCookie', 'testValue');
expect(Cookies).toHaveBeenCalled(); expect(Cookies).toHaveBeenCalled();
expect(Cookies).toHaveBeenCalledWith(); expect(Cookies).toHaveBeenCalledWith();
@@ -30,10 +22,8 @@ describe('setCookie function', () => {
}); });
it('should set a cookie with specified expiry', () => { it('should set a cookie with specified expiry', () => {
getConfig.mockReturnValue({ SESSION_COOKIE_DOMAIN: 'example.com' });
const expiry = new Date('2023-12-31'); const expiry = new Date('2023-12-31');
setCookie('testCookie', 'testValue', expiry); setCookie('testCookie', 'testValue', 'example.com', expiry);
expect(Cookies).toHaveBeenCalled(); expect(Cookies).toHaveBeenCalled();
expect(Cookies).toHaveBeenCalledWith(); expect(Cookies).toHaveBeenCalledWith();
@@ -45,7 +35,7 @@ describe('setCookie function', () => {
}); });
it('should not set a cookie if cookieName is undefined', () => { it('should not set a cookie if cookieName is undefined', () => {
setCookie(undefined, 'testValue'); setCookie(undefined, 'testValue', 'example.com');
expect(Cookies).not.toHaveBeenCalled(); expect(Cookies).not.toHaveBeenCalled();
}); });

View File

@@ -1,10 +1,9 @@
import { getConfig } from '@edx/frontend-platform';
import Cookies from 'universal-cookie'; import Cookies from 'universal-cookie';
export default function setCookie(cookieName, cookieValue, cookieExpiry) { export default function setCookie(cookieName, cookieValue, cookieDomain, cookieExpiry) {
if (cookieName) { // To avoid setting getting exception when setting cookie with undefined names. if (cookieName) { // To avoid setting getting exception when setting cookie with undefined names.
const cookies = new Cookies(); const cookies = new Cookies();
const options = { domain: getConfig().SESSION_COOKIE_DOMAIN, path: '/' }; const options = { domain: cookieDomain, path: '/' };
if (cookieExpiry) { if (cookieExpiry) {
options.expires = cookieExpiry; options.expires = cookieExpiry;
} }

View File

@@ -40,10 +40,8 @@ export const updatePathWithQueryParams = (path) => {
return path; return path;
} }
if (queryParams.indexOf('track=pwreset') > -1) { if (queryParams.includes('track=pwreset')) {
queryParams = queryParams.replace( queryParams = queryParams.replace('?track=pwreset&', '?',).replace('?track=pwreset', '').replace('&track=pwreset', '').replace('?&', '?');
'?track=pwreset&', '?',
).replace('?track=pwreset', '').replace('&track=pwreset', '').replace('?&', '?');
} }
return `${path}${queryParams}`; return `${path}${queryParams}`;
@@ -53,7 +51,7 @@ export const getAllPossibleQueryParams = (locationURl = null) => {
const urlParams = locationURl ? QueryString.parseUrl(locationURl).query : QueryString.parse(window.location.search); const urlParams = locationURl ? QueryString.parseUrl(locationURl).query : QueryString.parse(window.location.search);
const params = {}; const params = {};
Object.entries(urlParams).forEach(([key, value]) => { Object.entries(urlParams).forEach(([key, value]) => {
if (AUTH_PARAMS.indexOf(key) > -1) { if (AUTH_PARAMS.includes(key)) {
params[key] = value; params[key] = value;
} }
}); });

View File

@@ -1,5 +1,3 @@
import React from 'react';
import { Form, Icon } from '@openedx/paragon'; import { Form, Icon } from '@openedx/paragon';
import { ExpandMore } from '@openedx/paragon/icons'; import { ExpandMore } from '@openedx/paragon/icons';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';

View File

@@ -1,6 +1,4 @@
import React from 'react'; import { getSiteConfig } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { fireEvent, render } from '@testing-library/react'; import { fireEvent, render } from '@testing-library/react';
import FieldRenderer from '../FieldRenderer'; import FieldRenderer from '../FieldRenderer';
@@ -86,7 +84,7 @@ describe('FieldRendererTests', () => {
it('should render checkbox field', () => { it('should render checkbox field', () => {
const fieldData = { const fieldData = {
type: 'checkbox', type: 'checkbox',
label: `I agree that ${getConfig().SITE_NAME} may send me marketing messages.`, label: `I agree that ${getSiteConfig().siteName} may send me marketing messages.`,
name: 'marketing-emails-opt-in-field', name: 'marketing-emails-opt-in-field',
}; };

View File

@@ -1,16 +1,13 @@
import React from 'react'; import { FormattedMessage, useAppConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { Alert } from '@openedx/paragon'; import { Alert } from '@openedx/paragon';
import { CheckCircle, Error } from '@openedx/paragon/icons'; import { CheckCircle, Error } from '@openedx/paragon/icons';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import messages from './messages';
import { import {
COMPLETE_STATE, FORBIDDEN_STATE, FORM_SUBMISSION_ERROR, INTERNAL_SERVER_ERROR, COMPLETE_STATE, FORBIDDEN_STATE, FORM_SUBMISSION_ERROR, INTERNAL_SERVER_ERROR,
} from '../data/constants'; } from '../data/constants';
import { PASSWORD_RESET } from '../reset-password/data/constants'; import { PASSWORD_RESET } from '../reset-password/data/constants';
import messages from './messages';
const ForgotPasswordAlert = (props) => { const ForgotPasswordAlert = (props) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@@ -36,7 +33,7 @@ const ForgotPasswordAlert = (props) => {
values={{ values={{
email: <span className="data-hj-suppress">{email}</span>, email: <span className="data-hj-suppress">{email}</span>,
supportLink: ( supportLink: (
<Alert.Link href={getConfig().PASSWORD_RESET_SUPPORT_LINK} target="_blank"> <Alert.Link href={useAppConfig().PASSWORD_RESET_SUPPORT_LINK} target="_blank">
{formatMessage(messages['confirmation.support.link'])} {formatMessage(messages['confirmation.support.link'])}
</Alert.Link> </Alert.Link>
), ),

View File

@@ -1,9 +1,10 @@
import React, { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getConfig } from '@edx/frontend-platform'; import {
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; useAppConfig,
import { useIntl } from '@edx/frontend-platform/i18n'; getSiteConfig, sendPageEvent, sendTrackEvent, useIntl
} from '@openedx/frontend-base';
import { import {
Form, Form,
Hyperlink, Hyperlink,
@@ -17,17 +18,17 @@ import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { forgotPassword, setForgotPasswordFormData } from './data/actions';
import { forgotPasswordResultSelector } from './data/selectors';
import ForgotPasswordAlert from './ForgotPasswordAlert';
import messages from './messages';
import BaseContainer from '../base-container'; import BaseContainer from '../base-container';
import { FormGroup } from '../common-components'; import { FormGroup } from '../common-components';
import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants'; import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils'; import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
import { forgotPassword, setForgotPasswordFormData } from './data/actions';
import { forgotPasswordResultSelector } from './data/selectors';
import ForgotPasswordAlert from './ForgotPasswordAlert';
import messages from './messages';
const ForgotPasswordPage = (props) => { const ForgotPasswordPage = (props) => {
const platformName = getConfig().SITE_NAME; const platformName = getSiteConfig().siteName;
const emailRegex = new RegExp(VALID_EMAIL_REGEX, 'i'); const emailRegex = new RegExp(VALID_EMAIL_REGEX, 'i');
const { const {
status, submitState, emailValidationError, status, submitState, emailValidationError,
@@ -97,8 +98,11 @@ const ForgotPasswordPage = (props) => {
return ( return (
<BaseContainer> <BaseContainer>
<Helmet> <Helmet>
<title>{formatMessage(messages['forgot.password.page.title'], <title>
{ siteName: getConfig().SITE_NAME })} {formatMessage(
messages['forgot.password.page.title'],
{ siteName: getSiteConfig().siteName }
)}
</title> </title>
</Helmet> </Helmet>
<div> <div>
@@ -139,12 +143,12 @@ const ForgotPasswordPage = (props) => {
onClick={handleSubmit} onClick={handleSubmit}
onMouseDown={(e) => e.preventDefault()} onMouseDown={(e) => e.preventDefault()}
/> />
{(getConfig().LOGIN_ISSUE_SUPPORT_LINK) && ( {(useAppConfig().LOGIN_ISSUE_SUPPORT_LINK) && (
<Hyperlink <Hyperlink
id="forgot-password" id="forgot-password"
name="forgot-password" name="forgot-password"
className="ml-4 font-weight-500 text-body" className="ml-4 font-weight-500 text-body"
destination={getConfig().LOGIN_ISSUE_SUPPORT_LINK} destination={useAppConfig().LOGIN_ISSUE_SUPPORT_LINK}
target="_blank" target="_blank"
showLaunchIcon={false} showLaunchIcon={false}
> >
@@ -154,7 +158,7 @@ const ForgotPasswordPage = (props) => {
<p className="mt-5.5 small text-gray-700"> <p className="mt-5.5 small text-gray-700">
{formatMessage(messages['additional.help.text'], { platformName })} {formatMessage(messages['additional.help.text'], { platformName })}
<span> <span>
<Hyperlink isInline destination={`mailto:${getConfig().INFO_EMAIL}`}>{getConfig().INFO_EMAIL}</Hyperlink> <Hyperlink isInline destination={`mailto:${useAppConfig().INFO_EMAIL}`}>{useAppConfig().INFO_EMAIL}</Hyperlink>
</span> </span>
</p> </p>
</Form> </Form>

View File

@@ -1,4 +1,4 @@
import { logError, logInfo } from '@edx/frontend-platform/logging'; import { logError, logInfo } from '@openedx/frontend-base';
import { call, put, takeEvery } from 'redux-saga/effects'; import { call, put, takeEvery } from 'redux-saga/effects';
// Actions // Actions

View File

@@ -1,8 +1,6 @@
import { getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient, getSiteConfig } from '@openedx/frontend-base';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import formurlencoded from 'form-urlencoded'; import formurlencoded from 'form-urlencoded';
// eslint-disable-next-line import/prefer-default-export
export async function forgotPassword(email) { export async function forgotPassword(email) {
const requestConfig = { const requestConfig = {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
@@ -11,7 +9,7 @@ export async function forgotPassword(email) {
const { data } = await getAuthenticatedHttpClient() const { data } = await getAuthenticatedHttpClient()
.post( .post(
`${getConfig().LMS_BASE_URL}/account/password`, `${getSiteConfig().lmsBaseUrl}/account/password`,
formurlencoded({ email }), formurlencoded({ email }),
requestConfig, requestConfig,
) )

View File

@@ -1,11 +1,11 @@
import { runSaga } from 'redux-saga'; import { runSaga } from 'redux-saga';
import initializeMockLogging from '../../../setupTest'; import { initializeMockServices } from '../../../setupTest';
import * as actions from '../actions'; import * as actions from '../actions';
import { handleForgotPassword } from '../sagas'; import { handleForgotPassword } from '../sagas';
import * as api from '../service'; import * as api from '../service';
const { loggingService } = initializeMockLogging(); const { loggingService } = initializeMockServices();
describe('handleForgotPassword', () => { describe('handleForgotPassword', () => {
const params = { const params = {

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-platform/i18n'; import { defineMessages } from '@openedx/frontend-base';
const messages = defineMessages({ const messages = defineMessages({
'forgot.password.page.title': { 'forgot.password.page.title': {

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { mergeConfig } from '@edx/frontend-platform'; import {
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n'; configureI18n, injectIntl, IntlProvider, mergeAppConfig
} from '@openedx/frontend-base';
import { import {
fireEvent, render, screen, fireEvent, render, screen,
} from '@testing-library/react'; } from '@testing-library/react';
@@ -11,16 +11,22 @@ import configureStore from 'redux-mock-store';
import { INTERNAL_SERVER_ERROR, LOGIN_PAGE } from '../../data/constants'; import { INTERNAL_SERVER_ERROR, LOGIN_PAGE } from '../../data/constants';
import { PASSWORD_RESET } from '../../reset-password/data/constants'; import { PASSWORD_RESET } from '../../reset-password/data/constants';
import { appId } from '../../constants';
import { setForgotPasswordFormData } from '../data/actions'; import { setForgotPasswordFormData } from '../data/actions';
import ForgotPasswordPage from '../ForgotPasswordPage'; import ForgotPasswordPage from '../ForgotPasswordPage';
const mockedNavigator = jest.fn(); const mockedNavigator = jest.fn();
jest.mock('@edx/frontend-platform/analytics', () => ({ jest.mock('@openedx/frontend-base', () => ({
...jest.requireActual('@openedx/frontend-base'),
sendPageEvent: jest.fn(), sendPageEvent: jest.fn(),
sendTrackEvent: jest.fn(), sendTrackEvent: jest.fn(),
getAuthenticatedUser: jest.fn(() => ({
userId: 3,
username: 'test-user',
})),
})); }));
jest.mock('@edx/frontend-platform/auth');
jest.mock('react-router-dom', () => ({ jest.mock('react-router-dom', () => ({
...(jest.requireActual('react-router-dom')), ...(jest.requireActual('react-router-dom')),
useNavigate: () => mockedNavigator, useNavigate: () => mockedNavigator,
@@ -36,7 +42,7 @@ const initialState = {
}; };
describe('ForgotPasswordPage', () => { describe('ForgotPasswordPage', () => {
mergeConfig({ mergeAppConfig(appId, {
LOGIN_ISSUE_SUPPORT_LINK: '', LOGIN_ISSUE_SUPPORT_LINK: '',
INFO_EMAIL: '', INFO_EMAIL: '',
}); });
@@ -54,18 +60,8 @@ describe('ForgotPasswordPage', () => {
beforeEach(() => { beforeEach(() => {
store = mockStore(initialState); store = mockStore(initialState);
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedUser: jest.fn(() => ({ configureI18n({
userId: 3,
username: 'test-user',
})),
}));
configure({
loggingService: { logError: jest.fn() },
config: {
ENVIRONMENT: 'production',
LANGUAGE_PREFERENCE_COOKIE_NAME: 'yum',
},
messages: { 'es-419': {}, de: {}, 'en-us': {} }, messages: { 'es-419': {}, de: {}, 'en-us': {} },
}); });
props = { props = {
@@ -84,7 +80,7 @@ describe('ForgotPasswordPage', () => {
}); });
it('should display need other help signing in button', () => { it('should display need other help signing in button', () => {
mergeConfig({ mergeAppConfig(appId, {
LOGIN_ISSUE_SUPPORT_LINK: '/support', LOGIN_ISSUE_SUPPORT_LINK: '/support',
}); });
render(reduxWrapper(<IntlForgotPasswordPage {...props} />)); render(reduxWrapper(<IntlForgotPasswordPage {...props} />));

View File

@@ -1 +1,25 @@
export default []; // Placeholder be overridden by `make pull_translations`
export default {
ar: {},
'zh-hk': {},
'zh-cn': {},
uk: {},
'tr-tr': {},
th: {},
te: {},
ru: {},
'pt-pt': {},
'pt-br': {},
'it-it': {},
id: {},
hi: {},
he: {},
'fr-ca': {},
fa: {},
'es-es': {},
'es-419': {},
el: {},
'de-de': {},
da: {},
bo: {},
};

View File

@@ -1,43 +0,0 @@
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import React, { StrictMode } from 'react';
import {
APP_INIT_ERROR, APP_READY, initialize, mergeConfig, subscribe,
} from '@edx/frontend-platform';
import { ErrorPage } from '@edx/frontend-platform/react';
import { createRoot } from 'react-dom/client';
import configuration from './config';
import messages from './i18n';
import MainApp from './MainApp';
subscribe(APP_READY, () => {
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<MainApp />
</StrictMode>,
);
});
subscribe(APP_INIT_ERROR, (error) => {
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<ErrorPage message={error.message} />
</StrictMode>,
);
});
initialize({
handlers: {
config: () => {
mergeConfig(configuration);
},
},
messages,
});

View File

@@ -1,6 +0,0 @@
@import "~@edx/brand/paragon/fonts";
@import "~@edx/brand/paragon/variables";
@import "~@openedx/paragon/scss/core/core";
@import "~@edx/brand/paragon/overrides";
@import "sass/style";

3
src/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export { default as authnApp } from './app';
export { default as authnRoutes } from './routes';
export { default as authnMessages } from './i18n';

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { FormattedMessage, useAppConfig, useIntl } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { Alert } from '@openedx/paragon'; import { Alert } from '@openedx/paragon';
import { CheckCircle, Error } from '@openedx/paragon/icons'; import { CheckCircle, Error } from '@openedx/paragon/icons';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -17,7 +14,7 @@ const AccountActivationMessage = ({ messageType }) => {
} }
const variant = messageType === ACCOUNT_ACTIVATION_MESSAGE.ERROR ? 'danger' : messageType; const variant = messageType === ACCOUNT_ACTIVATION_MESSAGE.ERROR ? 'danger' : messageType;
const activationOrConfirmation = getConfig().MARKETING_EMAILS_OPT_IN ? 'confirmation' : 'activation'; const activationOrConfirmation = useAppConfig().MARKETING_EMAILS_OPT_IN ? 'confirmation' : 'activation';
const iconMapping = { const iconMapping = {
[ACCOUNT_ACTIVATION_MESSAGE.SUCCESS]: CheckCircle, [ACCOUNT_ACTIVATION_MESSAGE.SUCCESS]: CheckCircle,
[ACCOUNT_ACTIVATION_MESSAGE.ERROR]: Error, [ACCOUNT_ACTIVATION_MESSAGE.ERROR]: Error,
@@ -37,7 +34,7 @@ const AccountActivationMessage = ({ messageType }) => {
} }
case ACCOUNT_ACTIVATION_MESSAGE.ERROR: { case ACCOUNT_ACTIVATION_MESSAGE.ERROR: {
const supportLink = ( const supportLink = (
<Alert.Link href={getConfig().ACTIVATION_EMAIL_SUPPORT_LINK}> <Alert.Link href={useAppConfig().ACTIVATION_EMAIL_SUPPORT_LINK}>
{formatMessage(messages['account.activation.support.link'])} {formatMessage(messages['account.activation.support.link'])}
</Alert.Link> </Alert.Link>
); );

View File

@@ -1,7 +1,6 @@
import React, { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { getConfig } from '@edx/frontend-platform'; import { getSiteConfig, useIntl } from '@openedx/frontend-base';
import { useIntl } from '@edx/frontend-platform/i18n';
import { import {
ActionRow, ModalDialog, useToggle, ActionRow, ModalDialog, useToggle,
} from '@openedx/paragon'; } from '@openedx/paragon';
@@ -9,10 +8,10 @@ import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import messages from './messages';
import { DEFAULT_REDIRECT_URL, RESET_PAGE } from '../data/constants'; import { DEFAULT_REDIRECT_URL, RESET_PAGE } from '../data/constants';
import { updatePathWithQueryParams } from '../data/utils'; import { updatePathWithQueryParams } from '../data/utils';
import useMobileResponsive from '../data/utils/useMobileResponsive'; import useMobileResponsive from '../data/utils/useMobileResponsive';
import messages from './messages';
const ChangePasswordPrompt = ({ variant, redirectUrl }) => { const ChangePasswordPrompt = ({ variant, redirectUrl }) => {
const isMobileView = useMobileResponsive(); const isMobileView = useMobileResponsive();
@@ -22,11 +21,11 @@ const ChangePasswordPrompt = ({ variant, redirectUrl }) => {
if (variant === 'block') { if (variant === 'block') {
setRedirectToResetPasswordPage(true); setRedirectToResetPasswordPage(true);
} else { } else {
window.location.href = redirectUrl || getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL); window.location.href = redirectUrl || getSiteConfig().lmsBaseUrl.concat(DEFAULT_REDIRECT_URL);
} }
}, },
}; };
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [isOpen, open, close] = useToggle(true, handlers); const [isOpen, open, close] = useToggle(true, handlers);
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const navigate = useNavigate(); const navigate = useNavigate();

View File

@@ -1,12 +1,13 @@
import React, { useEffect } from 'react'; import { useEffect } from 'react';
import { getConfig } from '@edx/frontend-platform'; import {
import { getAuthService } from '@edx/frontend-platform/auth'; FormattedMessage, getAuthService, getSiteConfig, useIntl
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; } from '@openedx/frontend-base';
import { Alert, Hyperlink } from '@openedx/paragon'; import { Alert, Hyperlink } from '@openedx/paragon';
import { Error } from '@openedx/paragon/icons'; import { Error } from '@openedx/paragon/icons';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { windowScrollTo } from '../data/utils';
import ChangePasswordPrompt from './ChangePasswordPrompt'; import ChangePasswordPrompt from './ChangePasswordPrompt';
import { import {
ACCOUNT_LOCKED_OUT, ACCOUNT_LOCKED_OUT,
@@ -23,7 +24,6 @@ import {
TPA_AUTHENTICATION_FAILURE, TPA_AUTHENTICATION_FAILURE,
} from './data/constants'; } from './data/constants';
import messages from './messages'; import messages from './messages';
import { windowScrollTo } from '../data/utils';
const LoginFailureMessage = (props) => { const LoginFailureMessage = (props) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@@ -75,6 +75,7 @@ const LoginFailureMessage = (props) => {
defaultMessage="In order to sign in, you need to activate your account.{lineBreak} defaultMessage="In order to sign in, you need to activate your account.{lineBreak}
{lineBreak}We just sent an activation link to {email}. If you do not receive an email, {lineBreak}We just sent an activation link to {email}. If you do not receive an email,
check your spam folders or {supportLink}." check your spam folders or {supportLink}."
description="An error message shown to users when they sign in if they have not yet activated their account. It attempts to explain to them how to activate their account by looking for an email from the system."
values={{ values={{
lineBreak: <br />, lineBreak: <br />,
email: <strong className="data-hj-suppress">{context.email}</strong>, email: <strong className="data-hj-suppress">{context.email}</strong>,
@@ -86,7 +87,7 @@ const LoginFailureMessage = (props) => {
break; break;
} }
case ALLOWED_DOMAIN_LOGIN_ERROR: { case ALLOWED_DOMAIN_LOGIN_ERROR: {
const url = `${getConfig().LMS_BASE_URL}/dashboard/?tpa_hint=${context.tpaHint}`; const url = `${getSiteConfig().lmsBaseUrl}/dashboard/?tpa_hint=${context.tpaHint}`;
const tpaLink = ( const tpaLink = (
<a href={url}> <a href={url}>
{formatMessage(messages['tpa.account.link'], { provider: context.provider })} {formatMessage(messages['tpa.account.link'], { provider: context.provider })}
@@ -161,6 +162,7 @@ const LoginFailureMessage = (props) => {
<FormattedMessage <FormattedMessage
id="login.incorrect.credentials.error.with.reset.link" id="login.incorrect.credentials.error.with.reset.link"
defaultMessage="The username, email, or password you entered is incorrect. Please try again or {resetLink}." defaultMessage="The username, email, or password you entered is incorrect. Please try again or {resetLink}."
description="An error message shown to users if some part of their login information was incorrect."
values={{ resetLink }} values={{ resetLink }}
/> />
</p> </p>
@@ -184,7 +186,7 @@ const LoginFailureMessage = (props) => {
errorMessage = ( errorMessage = (
<p> <p>
{formatMessage(messages['login.tpa.authentication.failure'], { {formatMessage(messages['login.tpa.authentication.failure'], {
platform_name: getConfig().SITE_NAME, platform_name: getSiteConfig().siteName,
lineBreak: <br />, lineBreak: <br />,
errorMessage: context.errorMessage, errorMessage: context.errorMessage,
})} })}

View File

@@ -1,9 +1,9 @@
import React, { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getConfig } from '@edx/frontend-platform'; import {
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; getSiteConfig, injectIntl, sendPageEvent, sendTrackEvent, useIntl
import { injectIntl, useIntl } from '@edx/frontend-platform/i18n'; } from '@openedx/frontend-base';
import { import {
Form, StatefulButton, Form, StatefulButton,
} from '@openedx/paragon'; } from '@openedx/paragon';
@@ -12,15 +12,6 @@ import { Helmet } from 'react-helmet';
import Skeleton from 'react-loading-skeleton'; import Skeleton from 'react-loading-skeleton';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import AccountActivationMessage from './AccountActivationMessage';
import {
backupLoginFormBegin,
dismissPasswordResetBanner,
loginRequest,
} from './data/actions';
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
import LoginFailureMessage from './LoginFailure';
import messages from './messages';
import { import {
FormGroup, FormGroup,
InstitutionLogistration, InstitutionLogistration,
@@ -43,6 +34,15 @@ import {
updatePathWithQueryParams, updatePathWithQueryParams,
} from '../data/utils'; } from '../data/utils';
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess'; import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';
import AccountActivationMessage from './AccountActivationMessage';
import {
backupLoginFormBegin,
dismissPasswordResetBanner,
loginRequest,
} from './data/actions';
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
import LoginFailureMessage from './LoginFailure';
import messages from './messages';
const LoginPage = (props) => { const LoginPage = (props) => {
const { const {
@@ -182,7 +182,7 @@ const LoginPage = (props) => {
} }
if (skipHintedLogin) { if (skipHintedLogin) {
window.location.href = getConfig().LMS_BASE_URL + provider.loginUrl; window.location.href = getSiteConfig().lmsBaseUrl + provider.loginUrl;
return null; return null;
} }
@@ -202,7 +202,7 @@ const LoginPage = (props) => {
return ( return (
<> <>
<Helmet> <Helmet>
<title>{formatMessage(messages['login.page.title'], { siteName: getConfig().SITE_NAME })}</title> <title>{formatMessage(messages['login.page.title'], { siteName: getSiteConfig().siteName })}</title>
</Helmet> </Helmet>
<RedirectLogistration <RedirectLogistration
success={loginResult.success} success={loginResult.success}

View File

@@ -1,5 +1,4 @@
import { camelCaseObject } from '@edx/frontend-platform'; import { camelCaseObject, logError, logInfo } from '@openedx/frontend-base';
import { logError, logInfo } from '@edx/frontend-platform/logging';
import { call, put, takeEvery } from 'redux-saga/effects'; import { call, put, takeEvery } from 'redux-saga/effects';
import { import {

View File

@@ -1,8 +1,6 @@
import { getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient, getSiteConfig, getUrlByRouteRole } from '@openedx/frontend-base';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import * as QueryString from 'query-string'; import * as QueryString from 'query-string';
// eslint-disable-next-line import/prefer-default-export
export async function loginRequest(creds) { export async function loginRequest(creds) {
const requestConfig = { const requestConfig = {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
@@ -11,7 +9,7 @@ export async function loginRequest(creds) {
const { data } = await getAuthenticatedHttpClient() const { data } = await getAuthenticatedHttpClient()
.post( .post(
`${getConfig().LMS_BASE_URL}/api/user/v2/account/login_session/`, `${getSiteConfig().lmsBaseUrl}/api/user/v2/account/login_session/`,
QueryString.stringify(creds), QueryString.stringify(creds),
requestConfig, requestConfig,
) )
@@ -19,8 +17,11 @@ export async function loginRequest(creds) {
throw (e); throw (e);
}); });
const defaultRedirectUrl = getUrlByRouteRole('org.openedx.frontend.role.dashboard');
const redirectUrl = data.redirect_url ?? defaultRedirectUrl;
return { return {
redirectUrl: data.redirect_url || `${getConfig().LMS_BASE_URL}/dashboard`, redirectUrl,
success: data.success || false, success: data.success ?? false,
}; };
} }

View File

@@ -1,4 +1,4 @@
import { getConfig } from '@edx/frontend-platform'; import { getSiteConfig } from '@openedx/frontend-base';
import { DEFAULT_REDIRECT_URL, DEFAULT_STATE, PENDING_STATE } from '../../../data/constants'; import { DEFAULT_REDIRECT_URL, DEFAULT_STATE, PENDING_STATE } from '../../../data/constants';
import { RESET_PASSWORD } from '../../../reset-password'; import { RESET_PASSWORD } from '../../../reset-password';
@@ -109,7 +109,7 @@ describe('login reducer', () => {
it('should set redirect url on login success action', () => { it('should set redirect url on login success action', () => {
const payload = { const payload = {
redirectUrl: `${getConfig().BASE_URL}${DEFAULT_REDIRECT_URL}`, redirectUrl: `${getSiteConfig().baseUrl}${DEFAULT_REDIRECT_URL}`,
success: true, success: true,
}; };
const action = { const action = {

View File

@@ -1,13 +1,13 @@
import { camelCaseObject } from '@edx/frontend-platform'; import { camelCaseObject } from '@openedx/frontend-base';
import { runSaga } from 'redux-saga'; import { runSaga } from 'redux-saga';
import initializeMockLogging from '../../../setupTest'; import { initializeMockServices } from '../../../setupTest';
import * as actions from '../actions'; import * as actions from '../actions';
import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from '../constants'; import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from '../constants';
import { handleLoginRequest } from '../sagas'; import { handleLoginRequest } from '../sagas';
import * as api from '../service'; import * as api from '../service';
const { loggingService } = initializeMockLogging(); const { loggingService } = initializeMockServices();
describe('handleLoginRequest', () => { describe('handleLoginRequest', () => {
const params = { const params = {

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-platform/i18n'; import { defineMessages } from '@openedx/frontend-base';
const messages = defineMessages({ const messages = defineMessages({
'login.page.title': { 'login.page.title': {

View File

@@ -1,29 +1,33 @@
import React from 'react'; import { CurrentAppProvider, injectIntl, IntlProvider, mergeAppConfig } from '@openedx/frontend-base';
import { mergeConfig } from '@edx/frontend-platform';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { import {
render, screen, render, screen,
} from '@testing-library/react'; } from '@testing-library/react';
import AccountActivationMessage from '../AccountActivationMessage';
import { appId } from '../../constants';
import { ACCOUNT_ACTIVATION_MESSAGE } from '../data/constants'; import { ACCOUNT_ACTIVATION_MESSAGE } from '../data/constants';
import AccountActivationMessage from '../AccountActivationMessage';
const IntlAccountActivationMessage = injectIntl(AccountActivationMessage); const IntlAccountActivationMessage = injectIntl(AccountActivationMessage);
const providerWrapper = children => (
<IntlProvider locale="en">
<CurrentAppProvider appId={appId}>
{children}
</CurrentAppProvider>
</IntlProvider>
);
describe('AccountActivationMessage', () => { describe('AccountActivationMessage', () => {
beforeEach(() => { beforeEach(() => {
mergeConfig({ mergeAppConfig({
MARKETING_EMAILS_OPT_IN: '', MARKETING_EMAILS_OPT_IN: '',
}); });
}); });
it('should match account already activated message', () => { it('should match account already activated message', () => {
render( render(providerWrapper(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.INFO} /> <IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.INFO} />
</IntlProvider>, ));
);
const expectedMessage = 'This account has already been activated.'; const expectedMessage = 'This account has already been activated.';
@@ -34,11 +38,9 @@ describe('AccountActivationMessage', () => {
}); });
it('should match account activated success message', () => { it('should match account activated success message', () => {
render( render(providerWrapper(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.SUCCESS} /> <IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.SUCCESS} />
</IntlProvider>, ));
);
const expectedMessage = 'Success! You have activated your account.' const expectedMessage = 'Success! You have activated your account.'
+ 'You will now receive email updates and alerts from us related to ' + 'You will now receive email updates and alerts from us related to '
@@ -51,11 +53,9 @@ describe('AccountActivationMessage', () => {
}); });
it('should match account activation error message', () => { it('should match account activation error message', () => {
render( render(providerWrapper(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.ERROR} /> <IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.ERROR} />
</IntlProvider>, ));
);
const expectedMessage = 'Your account could not be activated' const expectedMessage = 'Your account could not be activated'
+ 'Something went wrong, please contact support to resolve this issue.'; + 'Something went wrong, please contact support to resolve this issue.';
@@ -67,11 +67,9 @@ describe('AccountActivationMessage', () => {
}); });
it('should not display anything for invalid message type', () => { it('should not display anything for invalid message type', () => {
const { container } = render( const { container } = render(providerWrapper(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType="invalid-message" /> <IntlAccountActivationMessage messageType="invalid-message" />
</IntlProvider>, ));
);
const accountActivationMessage = container.querySelectorAll('#account-activation-message'); const accountActivationMessage = container.querySelectorAll('#account-activation-message');
expect(accountActivationMessage[0]).toBe(undefined); expect(accountActivationMessage[0]).toBe(undefined);
@@ -80,19 +78,17 @@ describe('AccountActivationMessage', () => {
describe('EmailConfirmationMessage', () => { describe('EmailConfirmationMessage', () => {
beforeEach(() => { beforeEach(() => {
mergeConfig({ mergeAppConfig({
MARKETING_EMAILS_OPT_IN: 'true', MARKETING_EMAILS_OPT_IN: 'true',
}); });
}); });
it('should match email already confirmed message', () => { it('should match email already confirmed message', () => {
render( render(providerWrapper(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.INFO} /> <IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.INFO} />
</IntlProvider>, ));
);
const expectedMessage = 'This email has already been confirmed.'; const expectedMessage = 'This account has already been activated.';
expect(screen.getByText( expect(screen.getByText(
'', '',
@@ -101,12 +97,10 @@ describe('EmailConfirmationMessage', () => {
}); });
it('should match email confirmation success message', () => { it('should match email confirmation success message', () => {
render( render(providerWrapper(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.SUCCESS} /> <IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.SUCCESS} />
</IntlProvider>, ));
); 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.';
const expectedMessage = 'Success! You have confirmed your email.Sign in to continue.';
expect(screen.getByText( expect(screen.getByText(
'', '',
@@ -115,12 +109,10 @@ describe('EmailConfirmationMessage', () => {
}); });
it('should match email confirmation error message', () => { it('should match email confirmation error message', () => {
render( render(providerWrapper(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.ERROR} /> <IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.ERROR} />
</IntlProvider>, ));
); const expectedMessage = 'Your account could not be activated'
const expectedMessage = 'Your email could not be confirmed'
+ 'Something went wrong, please contact support to resolve this issue.'; + 'Something went wrong, please contact support to resolve this issue.';
expect(screen.getByText( expect(screen.getByText(
'', '',

View File

@@ -1,7 +1,4 @@
import React from 'react'; import { getSiteConfig, injectIntl, IntlProvider } from '@openedx/frontend-base';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { import {
fireEvent, render, screen, fireEvent, render, screen,
} from '@testing-library/react'; } from '@testing-library/react';
@@ -32,14 +29,14 @@ describe('ChangePasswordPromptTests', () => {
}); });
it('[nudge modal] should redirect to next url when user clicks close button', () => { it('[nudge modal] should redirect to next url when user clicks close button', () => {
const dashboardUrl = getConfig().BASE_URL.concat('/dashboard'); const dashboardUrl = getSiteConfig().baseUrl.concat('/dashboard');
props = { props = {
variant: 'nudge', variant: 'nudge',
redirectUrl: dashboardUrl, redirectUrl: dashboardUrl,
}; };
delete window.location; delete window.location;
window.location = { href: getConfig().BASE_URL }; window.location = { href: getSiteConfig().baseUrl };
render( render(
<IntlProvider locale="en"> <IntlProvider locale="en">

View File

@@ -1,11 +1,10 @@
import React from 'react'; import { CurrentAppProvider, injectIntl, IntlProvider } from '@openedx/frontend-base';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { import {
render, screen, render, screen,
} from '@testing-library/react'; } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import { appId } from '../../constants';
import { import {
ACCOUNT_LOCKED_OUT, ACCOUNT_LOCKED_OUT,
ALLOWED_DOMAIN_LOGIN_ERROR, ALLOWED_DOMAIN_LOGIN_ERROR,
@@ -22,11 +21,19 @@ import {
} from '../data/constants'; } from '../data/constants';
import LoginFailureMessage from '../LoginFailure'; import LoginFailureMessage from '../LoginFailure';
jest.mock('@edx/frontend-platform/auth', () => ({ jest.mock('@openedx/frontend-base', () => ({
...jest.requireActual('@openedx/frontend-base'),
getAuthService: jest.fn(), getAuthService: jest.fn(),
})); }));
const IntlLoginFailureMessage = injectIntl(LoginFailureMessage); const IntlLoginFailureMessage = injectIntl(LoginFailureMessage);
const providerWrapper = children => (
<IntlProvider locale="en">
<CurrentAppProvider appId={appId}>
{children}
</CurrentAppProvider>
</IntlProvider>
);
describe('LoginFailureMessage', () => { describe('LoginFailureMessage', () => {
let props = {}; let props = {};
@@ -46,11 +53,7 @@ describe('LoginFailureMessage', () => {
errorCount: 0, errorCount: 0,
}; };
render( render(providerWrapper(<IntlLoginFailureMessage {...props} />));
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
const expectedMessage = 'We couldn\'t sign you in.We recently changed our password requirements' 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 ' + 'Your current password does not meet the new security requirements. We just sent a '
@@ -74,11 +77,7 @@ describe('LoginFailureMessage', () => {
errorCount: 0, errorCount: 0,
}; };
render( render(providerWrapper(<IntlLoginFailureMessage {...props} />));
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
const expectedMessage = 'We couldn\'t sign you in.In order to sign in, you need to activate your account. ' 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, ' + 'We just sent an activation link to text@example.com. If you do not receive an email, '
@@ -104,11 +103,7 @@ describe('LoginFailureMessage', () => {
errorCount: 0, errorCount: 0,
}; };
render( render(providerWrapper(<IntlLoginFailureMessage {...props} />));
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
const expectedMessage = 'We couldn\'t sign you in.The username, email or password you entered is incorrect. ' const expectedMessage = 'We couldn\'t sign you in.The username, email or password you entered is incorrect. '
+ 'You have 3 more sign in attempts before your account is temporarily locked.If you\'ve forgotten your password, click here to reset it.'; + 'You have 3 more sign in attempts before your account is temporarily locked.If you\'ve forgotten your password, click here to reset it.';
@@ -130,11 +125,7 @@ describe('LoginFailureMessage', () => {
errorCount: 0, errorCount: 0,
}; };
render( render(providerWrapper(<IntlLoginFailureMessage {...props} />));
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
const expectedMessage = 'We couldn\'t sign you in.The username, email, or password you entered is incorrect. Please try again.'; const expectedMessage = 'We couldn\'t sign you in.The username, email, or password you entered is incorrect. Please try again.';
@@ -150,11 +141,7 @@ describe('LoginFailureMessage', () => {
errorCount: 0, errorCount: 0,
}; };
render( render(providerWrapper(<IntlLoginFailureMessage {...props} />));
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
const expectedMessage = 'We couldn\'t sign you in.To protect your account, it\'s been temporarily locked. Try again in 30 minutes.To be on the safe side, you can reset your password before trying again.'; const expectedMessage = 'We couldn\'t sign you in.To protect your account, it\'s been temporarily locked. Try again in 30 minutes.To be on the safe side, you can reset your password before trying again.';
expect(screen.getByText( expect(screen.getByText(
@@ -174,11 +161,7 @@ describe('LoginFailureMessage', () => {
errorCount: 0, errorCount: 0,
}; };
render( render(providerWrapper(<IntlLoginFailureMessage {...props} />));
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
const expectedMessage = 'We couldn\'t sign you in.The username, email, or password you entered is incorrect. Please try again or reset your password.'; const expectedMessage = 'We couldn\'t sign you in.The username, email, or password you entered is incorrect. Please try again or reset your password.';
@@ -194,11 +177,7 @@ describe('LoginFailureMessage', () => {
errorCount: 0, errorCount: 0,
}; };
render( render(providerWrapper(<IntlLoginFailureMessage {...props} />));
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
const expectedMessage = 'We couldn\'t sign you in.Too many failed login attempts. Try again later.'; const expectedMessage = 'We couldn\'t sign you in.Too many failed login attempts. Try again later.';
@@ -214,11 +193,7 @@ describe('LoginFailureMessage', () => {
errorCount: 0, errorCount: 0,
}; };
render( render(providerWrapper(<IntlLoginFailureMessage {...props} />));
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
const expectedMessage = 'We couldn\'t sign you in.An error has occurred. Try refreshing the page, or check your internet connection.'; const expectedMessage = 'We couldn\'t sign you in.An error has occurred. Try refreshing the page, or check your internet connection.';
@@ -234,11 +209,7 @@ describe('LoginFailureMessage', () => {
errorCount: 0, errorCount: 0,
}; };
render( render(providerWrapper(<IntlLoginFailureMessage {...props} />));
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
const expectedMessage = 'We couldn\'t sign you in.Please fill in the fields below.'; const expectedMessage = 'We couldn\'t sign you in.Please fill in the fields below.';
expect(screen.getByText( expect(screen.getByText(
@@ -253,11 +224,7 @@ describe('LoginFailureMessage', () => {
errorCount: 0, errorCount: 0,
}; };
render( render(providerWrapper(<IntlLoginFailureMessage {...props} />));
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
const expectedMessage = 'We couldn\'t sign you in.An error has occurred. Try refreshing the page, or check your internet connection.'; const expectedMessage = 'We couldn\'t sign you in.An error has occurred. Try refreshing the page, or check your internet connection.';
expect(screen.getByText( expect(screen.getByText(
@@ -273,11 +240,7 @@ describe('LoginFailureMessage', () => {
context: { errorMessage: 'An error occurred' }, context: { errorMessage: 'An error occurred' },
}; };
render( render(providerWrapper(<IntlLoginFailureMessage {...props} />));
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
const expectedMessageSubstring = 'We are sorry, you are not authorized to access'; const expectedMessageSubstring = 'We are sorry, you are not authorized to access';
@@ -298,13 +261,7 @@ describe('LoginFailureMessage', () => {
errorCount: 0, errorCount: 0,
}; };
render( render(providerWrapper(<MemoryRouter><IntlLoginFailureMessage {...props} /></MemoryRouter>));
<IntlProvider locale="en">
<MemoryRouter>
<IntlLoginFailureMessage {...props} />
</MemoryRouter>
</IntlProvider>,
);
const message = 'Our system detected that your password is vulnerable. ' const message = 'Our system detected that your password is vulnerable. '
+ 'We recommend you change it so that your account stays secure.'; + 'We recommend you change it so that your account stays secure.';
@@ -324,13 +281,7 @@ describe('LoginFailureMessage', () => {
errorCount: 0, errorCount: 0,
}; };
render( render(providerWrapper(<MemoryRouter><IntlLoginFailureMessage {...props} /></MemoryRouter>));
<IntlProvider locale="en">
<MemoryRouter>
<IntlLoginFailureMessage {...props} />
</MemoryRouter>
</IntlProvider>,
);
expect(screen.getByText( expect(screen.getByText(
'Password change required', 'Password change required',
@@ -357,14 +308,10 @@ describe('LoginFailureMessage', () => {
errorCount: 0, errorCount: 0,
}; };
render( render(providerWrapper(<IntlLoginFailureMessage {...props} />));
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
const errorMessage = "We couldn't sign you in.As test.com user, You must login with your test.com Google account."; const errorMessage = "We couldn't sign you in.As test.com user, You must login with your test.com Google account.";
const url = 'http://localhost:18000/dashboard/?tpa_hint=google-auth2'; const url = 'http://localhost:8000/dashboard/?tpa_hint=google-auth2';
expect(screen.getByText( expect(screen.getByText(
'', '',

Some files were not shown because too many files have changed in this diff Show More