Compare commits
56 Commits
master
...
frontend-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0cf4623a4 | ||
|
|
12670240b3 | ||
|
|
444e825fde | ||
|
|
c2a3c70c9d | ||
|
|
56d8a10694 | ||
|
|
86b4ea79de | ||
|
|
8dcf55254e | ||
|
|
e52d3d89fd | ||
|
|
bb3ab6cba4 | ||
|
|
fc40952da3 | ||
|
|
1733f6ec01 | ||
|
|
5d8743fb29 | ||
|
|
70ffc552b5 | ||
|
|
3dbd6a76df | ||
|
|
cb3ad5c53a | ||
|
|
65462e7d80 | ||
|
|
7bfb5d16d0 | ||
|
|
c31c397c61 | ||
|
|
4fc41b0fe7 | ||
|
|
df9454bbe6 | ||
|
|
5e41382f24 | ||
|
|
fe44896856 | ||
|
|
ed58e02eae | ||
|
|
2337a7105f | ||
|
|
b9f2c5da43 | ||
|
|
1a0a6e3179 | ||
|
|
92208a0a7c | ||
|
|
45535ee055 | ||
|
|
e53c4997bb | ||
|
|
f311539e12 | ||
|
|
c1070930bf | ||
|
|
dad2887eed | ||
|
|
4f79099eca | ||
|
|
49f42a8857 | ||
|
|
2d6b401312 | ||
|
|
c602136798 | ||
|
|
9a48fe323b | ||
|
|
967c52bde9 | ||
|
|
3252b593fd | ||
|
|
c98b9a1408 | ||
|
|
b6c659ccb8 | ||
|
|
7efa279a29 | ||
|
|
f83f24af89 | ||
|
|
b8116485b0 | ||
|
|
762ff75fc4 | ||
|
|
095b156b95 | ||
|
|
033e0fd7c5 | ||
|
|
1ecdb0b6af | ||
|
|
18951cc4d0 | ||
|
|
4dd5ddcc8b | ||
|
|
6acbf64a71 | ||
|
|
aea12a6a37 | ||
|
|
3d778807f1 | ||
|
|
014a990c22 | ||
|
|
7c8051a440 | ||
|
|
8f8531a242 |
45
.env
45
.env
@@ -1,45 +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=''
|
||||
# Fallback in local style files
|
||||
PARAGON_THEME_URLS={}
|
||||
@@ -41,5 +41,3 @@ APP_ID=''
|
||||
MFE_CONFIG_API_URL=''
|
||||
ZENDESK_KEY=''
|
||||
ZENDESK_LOGO_URL=''
|
||||
# Fallback in local style files
|
||||
PARAGON_THEME_URLS={}
|
||||
|
||||
@@ -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'
|
||||
21
.env.test
21
.env.test
@@ -1,21 +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=''
|
||||
PARAGON_THEME_URLS={}
|
||||
@@ -1,6 +0,0 @@
|
||||
coverage/*
|
||||
dist/
|
||||
docs
|
||||
node_modules/
|
||||
__mocks__/
|
||||
__snapshots__/
|
||||
52
.eslintrc.js
52
.eslintrc.js
@@ -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
16
.gitignore
vendored
@@ -1,21 +1,15 @@
|
||||
.DS_Store
|
||||
.eslintcache
|
||||
.idea
|
||||
node_modules
|
||||
npm-debug.log
|
||||
coverage
|
||||
module.config.js
|
||||
.env.private
|
||||
|
||||
dist/
|
||||
/*.tgz
|
||||
|
||||
### i18n ###
|
||||
src/i18n/transifex_input.json
|
||||
temp/babel-plugin-react-intl
|
||||
|
||||
### pyenv ###
|
||||
.python-version
|
||||
|
||||
### Emacs ###
|
||||
### Editors ###
|
||||
.DS_Store
|
||||
*~
|
||||
/temp
|
||||
/.vscode
|
||||
src/i18n/messages
|
||||
15
.npmignore
15
.npmignore
@@ -1,11 +1,6 @@
|
||||
.eslintignore
|
||||
.eslintrc.json
|
||||
.gitignore
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
Makefile
|
||||
npm-debug.log
|
||||
|
||||
coverage
|
||||
__mocks__
|
||||
node_modules
|
||||
public
|
||||
*.test.js
|
||||
*.test.jsx
|
||||
*.test.ts
|
||||
*.test.tsx
|
||||
|
||||
2
CODEOWNERS
Normal file
2
CODEOWNERS
Normal file
@@ -0,0 +1,2 @@
|
||||
# The following users are the owners of all frontend-app-authn files
|
||||
* @openedx/2u-infinity
|
||||
13
Makefile
13
Makefile
@@ -13,6 +13,19 @@ precommit:
|
||||
requirements:
|
||||
npm ci
|
||||
|
||||
clean:
|
||||
rm -rf dist
|
||||
|
||||
build: clean
|
||||
tsc --project tsconfig.build.json
|
||||
tsc-alias -p tsconfig.build.json
|
||||
find src -type f -name '*.scss' -exec sh -c '\
|
||||
for f in "$$@"; do \
|
||||
d="dist/$${f#src/}"; \
|
||||
mkdir -p "$$(dirname "$$d")"; \
|
||||
cp "$$f" "$$d"; \
|
||||
done' sh {} +
|
||||
|
||||
i18n.extract:
|
||||
# Pulling display strings from .jsx files into .json files...
|
||||
rm -rf $(transifex_temp)
|
||||
|
||||
47
README.rst
47
README.rst
@@ -26,49 +26,14 @@ This is a micro-frontend application responsible for the login, registration and
|
||||
Getting Started
|
||||
***************
|
||||
|
||||
Prerequisites
|
||||
=============
|
||||
Installation
|
||||
============
|
||||
|
||||
`Tutor`_ is currently recommended as a development environment for your new MFE. Please refer to the `relevant tutor-mfe documentation`_ to get started using it.
|
||||
|
||||
.. _Tutor: https://github.com/overhangio/tutor
|
||||
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development
|
||||
|
||||
Cloning and Startup
|
||||
===================
|
||||
|
||||
1. Clone your new repo:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/edx/frontend-app-authn.git
|
||||
|
||||
2. Use the version of Node specified in the ``.nvmrc`` file.
|
||||
|
||||
The current version of the micro-frontend build scripts supports the version of Node found in ``.nvmrc``.
|
||||
Using other major versions of node *may* work, but this is unsupported. For
|
||||
convenience, this repository includes a ``.nvmrc`` file to help in setting the
|
||||
correct node version via `nvm <https://github.com/nvm-sh/nvm>`_.
|
||||
|
||||
3. Install npm dependencies:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cd frontend-app-authn && npm install
|
||||
|
||||
4. Update the application port to use for local development:
|
||||
|
||||
The default port is 1999. If this does not work for you, update the line
|
||||
``PORT=1999`` to your port in all ``.env.*`` files
|
||||
|
||||
5. Start the devserver. The app will be running at ``localhost:1999``, or whatever port you change it too.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
npm run dev
|
||||
|
||||
**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
|
||||
=================================
|
||||
|
||||
@@ -130,7 +95,7 @@ The authentication micro-frontend also requires the following additional variabl
|
||||
|
||||
* - ``MFE_CONFIG_API_URL``
|
||||
- Link of the API to get runtime mfe configuration variables from the site configuration or django settings.
|
||||
- ``/api/v1/mfe_config`` | ``''`` (empty strings are falsy)
|
||||
- ``/api/v1/mfe_config`` | ``''`` (empty strings are falsy)
|
||||
|
||||
* - ``APP_ID``
|
||||
- Name of MFE, this will be used by the API to get runtime configurations for the specific micro frontend. For a frontend repo `frontend-app-appName`, use `appName` as APP_ID.
|
||||
@@ -158,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.
|
||||
- ``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
|
||||
edX <https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development>`__.
|
||||
|
||||
@@ -240,4 +201,4 @@ Please see `LICENSE <https://github.com/openedx/frontend-app-authn/blob/master/L
|
||||
:target: https://github.com/openedx/edx-developer-docs/actions/workflows/ci.yml
|
||||
:alt: Continuous Integration
|
||||
.. |semantic-release| image:: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
|
||||
:target: https://github.com/semantic-release/semantic-release
|
||||
:target: https://github.com/semantic-release/semantic-release
|
||||
|
||||
5
app.d.ts
vendored
Normal file
5
app.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/// <reference types="@openedx/frontend-base" />
|
||||
|
||||
declare module 'site.config' {
|
||||
export default SiteConfig;
|
||||
}
|
||||
3
babel.config.js
Normal file
3
babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const { createConfig } = require('@openedx/frontend-base/tools');
|
||||
|
||||
module.exports = createConfig('babel');
|
||||
22
eslint.config.js
Normal file
22
eslint.config.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// @ts-check
|
||||
|
||||
const { createLintConfig } = require('@openedx/frontend-base/tools');
|
||||
|
||||
module.exports = createLintConfig(
|
||||
{
|
||||
files: [
|
||||
'src/**/*',
|
||||
'site.config.*',
|
||||
],
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
'coverage/*',
|
||||
'dist/*',
|
||||
'docs/*',
|
||||
'node_modules/*',
|
||||
'**/__mocks__/*',
|
||||
'**/__snapshots__/*',
|
||||
],
|
||||
},
|
||||
);
|
||||
@@ -1,14 +1,15 @@
|
||||
const { createConfig } = require('@openedx/frontend-build');
|
||||
const { createConfig } = require('@openedx/frontend-base/tools');
|
||||
|
||||
module.exports = createConfig('jest', {
|
||||
setupFiles: [
|
||||
module.exports = createConfig('test', {
|
||||
setupFilesAfterEnv: [
|
||||
'<rootDir>/src/setupTest.js',
|
||||
],
|
||||
coveragePathIgnorePatterns: [
|
||||
'src/setupTest.js',
|
||||
'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',
|
||||
},
|
||||
});
|
||||
|
||||
16397
package-lock.json
generated
16397
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
112
package.json
112
package.json
@@ -1,25 +1,40 @@
|
||||
{
|
||||
"name": "@edx/frontend-app-authn",
|
||||
"version": "0.1.0",
|
||||
"description": "Frontend application template",
|
||||
"name": "@openedx/frontend-app-authn",
|
||||
"version": "1.0.0-alpha.6",
|
||||
"description": "Frontend authentication",
|
||||
"engines": {
|
||||
"node": "^24.12"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/openedx/frontend-app-authn.git"
|
||||
},
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./app.scss": "./dist/app.scss"
|
||||
},
|
||||
"files": [
|
||||
"/dist"
|
||||
],
|
||||
"browserslist": [
|
||||
"extends @edx/browserslist-config"
|
||||
],
|
||||
"sideEffects": [
|
||||
"*.css",
|
||||
"*.scss"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "fedx-scripts webpack",
|
||||
"i18n_extract": "fedx-scripts formatjs extract",
|
||||
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
|
||||
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .",
|
||||
"snapshot": "fedx-scripts jest --updateSnapshot",
|
||||
"start": "fedx-scripts webpack-dev-server --progress",
|
||||
"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"
|
||||
"build": "make build",
|
||||
"clean": "make clean",
|
||||
"dev": "PORT=1999 PUBLIC_PATH=/authn openedx dev",
|
||||
"i18n_extract": "openedx formatjs extract",
|
||||
"lint": "openedx lint .",
|
||||
"lint:fix": "openedx lint --fix .",
|
||||
"prepack": "npm run build",
|
||||
"snapshot": "openedx test --updateSnapshot",
|
||||
"test": "openedx test --coverage --passWithNoTests"
|
||||
},
|
||||
"author": "edX",
|
||||
"author": "Open edX",
|
||||
"license": "AGPL-3.0",
|
||||
"homepage": "https://github.com/openedx/frontend-app-authn#readme",
|
||||
"publishConfig": {
|
||||
@@ -29,48 +44,41 @@
|
||||
"url": "https://github.com/openedx/frontend-app-authn/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||
"@edx/frontend-platform": "^8.3.1",
|
||||
"@edx/openedx-atlas": "^0.6.0",
|
||||
"@fortawesome/fontawesome-svg-core": "6.7.2",
|
||||
"@fortawesome/free-brands-svg-icons": "6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "6.7.2",
|
||||
"@fortawesome/react-fontawesome": "0.2.6",
|
||||
"@openedx/frontend-plugin-framework": "^1.7.0",
|
||||
"@openedx/paragon": "^23.4.2",
|
||||
"@optimizely/react-sdk": "^2.9.1",
|
||||
"@tanstack/react-query": "^5.90.19",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"algoliasearch": "^4.14.3",
|
||||
"algoliasearch-helper": "^3.26.0",
|
||||
"classnames": "2.5.1",
|
||||
"core-js": "3.43.0",
|
||||
"fastest-levenshtein": "1.0.16",
|
||||
"form-urlencoded": "6.1.6",
|
||||
"prop-types": "15.8.1",
|
||||
"query-string": "7.1.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-helmet": "6.1.0",
|
||||
"react-loading-skeleton": "3.5.0",
|
||||
"react-responsive": "8.2.0",
|
||||
"react-router": "6.30.3",
|
||||
"react-router-dom": "6.30.3",
|
||||
"react-zendesk": "^0.1.13",
|
||||
"regenerator-runtime": "0.14.1",
|
||||
"universal-cookie": "7.2.2"
|
||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.3",
|
||||
"@edx/openedx-atlas": "^0.7.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"classnames": "^2.5.1",
|
||||
"fastest-levenshtein": "^1.0.16",
|
||||
"form-urlencoded": "^6.1.5",
|
||||
"i18n-iso-countries": "^7.13.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^7.1.3",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-loading-skeleton": "^3.5.0",
|
||||
"react-responsive": "^8.2.0",
|
||||
"universal-cookie": "^8.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/browserslist-config": "^1.1.1",
|
||||
"@edx/typescript-config": "^1.1.0",
|
||||
"@openedx/frontend-build": "^14.6.2",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"babel-plugin-formatjs": "10.5.41",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"glob": "7.2.3",
|
||||
"history": "5.3.0",
|
||||
"jest": "30.3.0",
|
||||
"@edx/browserslist-config": "^1.5.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"babel-plugin-formatjs": "10.5.38",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"jest": "^29.7.0",
|
||||
"react-test-renderer": "^18.3.1",
|
||||
"ts-jest": "^29.4.0"
|
||||
"ts-jest": "^29.4.0",
|
||||
"tsc-alias": "^1.8.16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@openedx/frontend-base": "^1.0.0-alpha.14",
|
||||
"@openedx/paragon": "^23",
|
||||
"@tanstack/react-query": "^5",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-router": "^6",
|
||||
"react-router-dom": "^6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,9 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<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 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>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
19
site.config.dev.tsx
Normal file
19
site.config.dev.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
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,
|
||||
apps: [authnApp],
|
||||
};
|
||||
|
||||
export default siteConfig;
|
||||
52
site.config.test.tsx
Normal file
52
site.config.test.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { 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',
|
||||
|
||||
// Use 'test' instead of EnvironmentTypes.TEST to break a circular dependency
|
||||
// when mocking `@openedx/frontend-base` itself.
|
||||
environment: 'test' as SiteConfig['environment'],
|
||||
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;
|
||||
19
src/Main.tsx
Executable file
19
src/Main.tsx
Executable file
@@ -0,0 +1,19 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { CurrentAppProvider } from '@openedx/frontend-base';
|
||||
|
||||
import { appId } from './constants';
|
||||
import {
|
||||
registerIcons,
|
||||
} from './common-components';
|
||||
|
||||
import './sass/_style.scss';
|
||||
|
||||
registerIcons();
|
||||
|
||||
const Main = () => (
|
||||
<CurrentAppProvider appId={appId}>
|
||||
<Outlet />
|
||||
</CurrentAppProvider>
|
||||
);
|
||||
|
||||
export default Main;
|
||||
@@ -1,76 +0,0 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
EmbeddedRegistrationRoute, NotFoundPage, registerIcons, UnAuthOnlyRoute, Zendesk,
|
||||
} from './common-components';
|
||||
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 queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
mutations: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const MainApp = () => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AppProvider>
|
||||
<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 selectedPage={REGISTER_PAGE} /></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>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
export default MainApp;
|
||||
1
src/__mocks__/file.js
Normal file
1
src/__mocks__/file.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = 'FileMock';
|
||||
1
src/__mocks__/svg.js
Normal file
1
src/__mocks__/svg.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = 'SvgURL';
|
||||
2
src/app.scss
Executable file
2
src/app.scss
Executable file
@@ -0,0 +1,2 @@
|
||||
@use "@openedx/frontend-base/shell/app.scss";
|
||||
@use "./sass/style";
|
||||
43
src/app.ts
Normal file
43
src/app.ts
Normal 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;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { IntlProvider } from '@openedx/frontend-base';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import { DefaultLargeLayout, DefaultMediumLayout, DefaultSmallLayout } from './index';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Hyperlink, Image } from '@openedx/paragon';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -11,20 +10,20 @@ const LargeLayout = () => {
|
||||
return (
|
||||
<div className="w-50 d-flex">
|
||||
<div className="col-md-9 bg-primary-400">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
<Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo position-absolute" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<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
|
||||
className={classNames(
|
||||
'display-2 text-white mw-xs',
|
||||
{ 'ml-6': getConfig().SITE_NAME !== 'edX' },
|
||||
{ 'ml-6': getSiteConfig().siteName !== 'edX' },
|
||||
)}
|
||||
>
|
||||
{formatMessage(messages['start.learning'])}
|
||||
<div className="text-accent-a">
|
||||
{formatMessage(messages['with.site.name'], { siteName: getConfig().SITE_NAME })}
|
||||
{formatMessage(messages['with.site.name'], { siteName: getSiteConfig().siteName })}
|
||||
</div>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Hyperlink, Image } from '@openedx/paragon';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -13,22 +12,22 @@ const MediumLayout = () => {
|
||||
<div className="w-100 medium-screen-top-stripe" />
|
||||
<div className="w-100 p-0 mb-3 d-flex">
|
||||
<div className="col-md-10 bg-primary-400">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image alt={getConfig().SITE_NAME} className="logo" src={getConfig().LOGO_WHITE_URL} />
|
||||
<Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image alt={getSiteConfig().siteName} className="logo" src={useAppConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<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>
|
||||
<h1
|
||||
className={classNames(
|
||||
'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>
|
||||
{formatMessage(messages['start.learning'])}{' '}
|
||||
<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>
|
||||
</h1>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Hyperlink, Image } from '@openedx/paragon';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -12,11 +11,11 @@ const SmallLayout = () => {
|
||||
<span className="bg-primary-400 w-100">
|
||||
<div className="col-md-12 small-screen-top-stripe" />
|
||||
<div>
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
<Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo-small" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<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
|
||||
className={classNames(
|
||||
'text-white mt-3.5 mb-3.5',
|
||||
@@ -25,7 +24,7 @@ const SmallLayout = () => {
|
||||
<span>
|
||||
{formatMessage(messages['start.learning'])}{' '}
|
||||
<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>
|
||||
</h1>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
import { defineMessages } from '@openedx/frontend-base';
|
||||
|
||||
const messages = defineMessages({
|
||||
'start.learning': {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Hyperlink, Image } from '@openedx/paragon';
|
||||
|
||||
import messages from './messages';
|
||||
@@ -12,10 +9,10 @@ const ExtraSmallLayout = () => {
|
||||
return (
|
||||
<span
|
||||
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}>
|
||||
<Image className="company-logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
<Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="company-logo" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="ml-4.5 mr-1 pb-3.5 pt-3.5">
|
||||
<h1 className="banner__heading">
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Hyperlink, Image } from '@openedx/paragon';
|
||||
|
||||
import './index.scss';
|
||||
@@ -13,10 +10,10 @@ const LargeLayout = () => {
|
||||
return (
|
||||
<div
|
||||
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}>
|
||||
<Image className="company-logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
<Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="company-logo position-absolute" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<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">
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Hyperlink, Image } from '@openedx/paragon';
|
||||
|
||||
import './index.scss';
|
||||
@@ -13,10 +10,10 @@ const MediumLayout = () => {
|
||||
return (
|
||||
<div
|
||||
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}>
|
||||
<Image className="company-logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
<Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="company-logo" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="ml-5 pb-4 pt-4">
|
||||
<h1 className="display-2 banner__heading">
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Hyperlink, Image } from '@openedx/paragon';
|
||||
|
||||
import messages from './messages';
|
||||
@@ -12,10 +9,10 @@ const SmallLayout = () => {
|
||||
return (
|
||||
<span
|
||||
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}>
|
||||
<Image className="company-logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_WHITE_URL} />
|
||||
<Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="company-logo" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_WHITE_URL} />
|
||||
</Hyperlink>
|
||||
<div className="ml-5 mr-1 pb-3.5 pt-3.5">
|
||||
<h1 className="display-2">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
import { defineMessages } from '@openedx/frontend-base';
|
||||
|
||||
const messages = defineMessages({
|
||||
'your.career.turning.point': {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Hyperlink, Image } from '@openedx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
@@ -11,14 +10,14 @@ const LargeLayout = ({ fullName }) => {
|
||||
return (
|
||||
<div className="w-50 d-flex">
|
||||
<div className="col-md-10 bg-light-200 p-0">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo position-absolute" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
|
||||
<Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo position-absolute" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_URL} />
|
||||
</Hyperlink>
|
||||
<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>
|
||||
<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>
|
||||
<h2 className="complete-your-profile">
|
||||
{formatMessage(messages['complete.your.profile.1'])}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Hyperlink, Image } from '@openedx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
@@ -13,14 +12,14 @@ const MediumLayout = ({ fullName }) => {
|
||||
<div className="w-100 medium-screen-top-stripe" />
|
||||
<div className="w-100 p-0 mb-3 d-flex">
|
||||
<div className="col-md-10 bg-light-200">
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
|
||||
<Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_URL} />
|
||||
</Hyperlink>
|
||||
<div className="d-flex align-items-center justify-content-center mb-4 ml-5">
|
||||
<div className="medium-yellow-line mt-5 mr-n2" />
|
||||
<div>
|
||||
<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>
|
||||
<h2 className="display-1">
|
||||
{formatMessage(messages['complete.your.profile.1'])}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Hyperlink, Image } from '@openedx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
@@ -11,14 +10,14 @@ const SmallLayout = ({ fullName }) => {
|
||||
return (
|
||||
<div className="min-vw-100 bg-light-200">
|
||||
<div className="col-md-12 small-screen-top-stripe" />
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo-small" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
|
||||
<Hyperlink destination={useAppConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo-small" alt={getSiteConfig().siteName} src={useAppConfig().LOGO_URL} />
|
||||
</Hyperlink>
|
||||
<div className="d-flex align-items-center m-3.5">
|
||||
<div className="small-yellow-line mt-4.5" />
|
||||
<div>
|
||||
<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>
|
||||
<h2 className="h1">
|
||||
{formatMessage(messages['complete.your.profile.1'])}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
import { defineMessages } from '@openedx/frontend-base';
|
||||
|
||||
const messages = defineMessages({
|
||||
'welcome.to.platform': {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useAppConfig } from '@openedx/frontend-base';
|
||||
import { breakpoints } from '@openedx/paragon';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { AuthLargeLayout, AuthMediumLayout, AuthSmallLayout } from './components/welcome-page-layout';
|
||||
|
||||
const BaseContainer = ({ children, showWelcomeBanner, fullName }) => {
|
||||
const enableImageLayout = getConfig().ENABLE_IMAGE_LAYOUT;
|
||||
const enableImageLayout = useAppConfig().ENABLE_IMAGE_LAYOUT;
|
||||
|
||||
if (enableImageLayout) {
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { mergeConfig } from '@edx/frontend-platform';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { IntlProvider, mergeAppConfig } from '@openedx/frontend-base';
|
||||
import { render } from '@testing-library/react';
|
||||
import { Context as ResponsiveContext } from 'react-responsive';
|
||||
|
||||
import BaseContainer from '../index';
|
||||
import { appId } from '../../constants';
|
||||
|
||||
const LargeScreen = {
|
||||
wrappingComponent: ResponsiveContext.Provider,
|
||||
@@ -26,7 +26,7 @@ describe('Base component tests', () => {
|
||||
});
|
||||
|
||||
it('renders Image layout when ENABLE_IMAGE_LAYOUT configuration is enabled', () => {
|
||||
mergeConfig({
|
||||
mergeAppConfig(appId, {
|
||||
ENABLE_IMAGE_LAYOUT: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import {
|
||||
Button, Form,
|
||||
Icon,
|
||||
@@ -8,8 +7,8 @@ import {
|
||||
import { Login } from '@openedx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
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
|
||||
@@ -17,12 +16,12 @@ import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
|
||||
const EnterpriseSSO = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const tpaProvider = props.provider;
|
||||
const hideRegistrationLink = getConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false
|
||||
|| getConfig().SHOW_REGISTRATION_LINKS === false;
|
||||
const hideRegistrationLink = useAppConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false
|
||||
|| useAppConfig().SHOW_REGISTRATION_LINKS === false;
|
||||
|
||||
const handleSubmit = (e, url) => {
|
||||
e.preventDefault();
|
||||
window.location.href = getConfig().LMS_BASE_URL + url;
|
||||
window.location.href = getSiteConfig().lmsBaseUrl + url;
|
||||
};
|
||||
|
||||
const handleClick = (e) => {
|
||||
@@ -48,7 +47,7 @@ const EnterpriseSSO = (props) => {
|
||||
{tpaProvider.iconImage ? (
|
||||
<div aria-hidden="true">
|
||||
<img className="btn-tpa__image-icon" src={tpaProvider.iconImage} alt={`icon ${tpaProvider.name}`} />
|
||||
<span className="pl-2" aria-hidden="true">{ tpaProvider.name }</span>
|
||||
<span className="pl-2" aria-hidden="true">{tpaProvider.name}</span>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
@@ -60,7 +59,7 @@ const EnterpriseSSO = (props) => {
|
||||
<Icon className="h-75" src={Login} />
|
||||
)}
|
||||
</div>
|
||||
<span className="pl-2" aria-hidden="true">{ tpaProvider.name }</span>
|
||||
<span className="pl-2" aria-hidden="true">{tpaProvider.name}</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Button, Hyperlink, Icon } from '@openedx/paragon';
|
||||
import { Institution } from '@openedx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -29,7 +28,7 @@ export const RenderInstitutionButton = props => {
|
||||
* This component renders the page list of available institutions for login
|
||||
* */
|
||||
const InstitutionLogistration = props => {
|
||||
const lmsBaseUrl = getConfig().LMS_BASE_URL;
|
||||
const lmsBaseUrl = getSiteConfig().lmsBaseUrl;
|
||||
const { formatMessage } = useIntl();
|
||||
const {
|
||||
secondaryProviders,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { FormattedMessage } from '@openedx/frontend-base';
|
||||
|
||||
const NotFoundPage = () => (
|
||||
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@openedx/frontend-base';
|
||||
import {
|
||||
Form, Icon, IconButton, OverlayTrigger, Tooltip, useToggle,
|
||||
} from '@openedx/paragon';
|
||||
@@ -9,11 +9,11 @@ import {
|
||||
} from '@openedx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
import { LETTER_REGEX, NUMBER_REGEX } from '../data/constants';
|
||||
import { useRegisterContext } from '../register/components/RegisterContext';
|
||||
import { useFieldValidations } from '../register/data/apiHook';
|
||||
import { validatePasswordField } from '../register/data/utils';
|
||||
import messages from './messages';
|
||||
|
||||
const noopFn = () => {};
|
||||
|
||||
const PasswordField = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
@@ -21,20 +21,10 @@ const PasswordField = (props) => {
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
|
||||
const {
|
||||
setValidationsSuccess,
|
||||
setValidationsFailure,
|
||||
validationApiRateLimited,
|
||||
clearRegistrationBackendError,
|
||||
} = useRegisterContext();
|
||||
|
||||
const fieldValidationsMutation = useFieldValidations({
|
||||
onSuccess: (data) => {
|
||||
setValidationsSuccess(data);
|
||||
},
|
||||
onError: () => {
|
||||
setValidationsFailure();
|
||||
},
|
||||
});
|
||||
validationApiRateLimited = false,
|
||||
clearRegistrationBackendError = noopFn,
|
||||
validateField = noopFn,
|
||||
} = props;
|
||||
|
||||
const handleBlur = (e) => {
|
||||
const { name, value } = e.target;
|
||||
@@ -63,7 +53,7 @@ const PasswordField = (props) => {
|
||||
if (fieldError) {
|
||||
props.handleErrorChange('password', fieldError);
|
||||
} else if (!validationApiRateLimited) {
|
||||
fieldValidationsMutation.mutate({ password: passwordValue });
|
||||
validateField({ password: passwordValue });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -168,6 +158,9 @@ PasswordField.defaultProps = {
|
||||
showRequirements: true,
|
||||
showScreenReaderText: true,
|
||||
autoComplete: null,
|
||||
clearRegistrationBackendError: noopFn,
|
||||
validateField: noopFn,
|
||||
validationApiRateLimited: false,
|
||||
};
|
||||
|
||||
PasswordField.propTypes = {
|
||||
@@ -183,6 +176,9 @@ PasswordField.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
autoComplete: PropTypes.string,
|
||||
showScreenReaderText: PropTypes.bool,
|
||||
clearRegistrationBackendError: PropTypes.func,
|
||||
validateField: PropTypes.func,
|
||||
validationApiRateLimited: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default PasswordField;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useAppConfig, getSiteConfig } from '@openedx/frontend-base';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS, REDIRECT,
|
||||
AUTHN_PROGRESSIVE_PROFILING, REDIRECT,
|
||||
} from '../data/constants';
|
||||
import { setCookie } from '../data/utils';
|
||||
|
||||
@@ -15,20 +15,20 @@ const RedirectLogistration = (props) => {
|
||||
redirectToProgressiveProfilingPage,
|
||||
success,
|
||||
optionalFields,
|
||||
redirectToRecommendationsPage,
|
||||
educationLevel,
|
||||
userId,
|
||||
registrationEmbedded,
|
||||
host,
|
||||
} = props;
|
||||
let finalRedirectUrl = '';
|
||||
|
||||
if (success) {
|
||||
// If we're in a third party auth pipeline, we must complete the pipeline
|
||||
// once user has successfully logged in. Otherwise, redirect to the specified redirect url.
|
||||
// Note: For multiple enterprise use case, we need to make sure that user first visits the
|
||||
// enterprise selection page and then complete the auth workflow
|
||||
if (finishAuthUrl && !redirectUrl.includes(finishAuthUrl)) {
|
||||
finalRedirectUrl = getConfig().LMS_BASE_URL + finishAuthUrl;
|
||||
finalRedirectUrl = getSiteConfig().lmsBaseUrl + finishAuthUrl;
|
||||
} else {
|
||||
finalRedirectUrl = redirectUrl;
|
||||
}
|
||||
@@ -36,12 +36,12 @@ const RedirectLogistration = (props) => {
|
||||
// Redirect to Progressive Profiling after successful registration
|
||||
if (redirectToProgressiveProfilingPage) {
|
||||
// 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) {
|
||||
window.parent.postMessage({
|
||||
action: REDIRECT,
|
||||
redirectUrl: getConfig().POST_REGISTRATION_REDIRECT_URL,
|
||||
redirectUrl: useAppConfig().POST_REGISTRATION_REDIRECT_URL,
|
||||
}, host);
|
||||
return null;
|
||||
}
|
||||
@@ -59,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;
|
||||
}
|
||||
|
||||
@@ -89,7 +73,6 @@ RedirectLogistration.defaultProps = {
|
||||
redirectUrl: '',
|
||||
redirectToProgressiveProfilingPage: false,
|
||||
optionalFields: {},
|
||||
redirectToRecommendationsPage: false,
|
||||
userId: null,
|
||||
registrationEmbedded: false,
|
||||
host: '',
|
||||
@@ -103,7 +86,6 @@ RedirectLogistration.propTypes = {
|
||||
redirectUrl: PropTypes.string,
|
||||
redirectToProgressiveProfilingPage: PropTypes.bool,
|
||||
optionalFields: PropTypes.shape({}),
|
||||
redirectToRecommendationsPage: PropTypes.bool,
|
||||
userId: PropTypes.number,
|
||||
registrationEmbedded: PropTypes.bool,
|
||||
host: PropTypes.string,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Icon } from '@openedx/paragon';
|
||||
import { Login } from '@openedx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const SocialAuthProviders = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
@@ -16,7 +15,7 @@ const SocialAuthProviders = (props) => {
|
||||
e.preventDefault();
|
||||
|
||||
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) => (
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import {
|
||||
Hyperlink, Icon,
|
||||
} from '@openedx/paragon';
|
||||
@@ -8,10 +7,10 @@ import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
|
||||
import messages from './messages';
|
||||
import {
|
||||
ENTERPRISE_LOGIN_URL, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE,
|
||||
} from '../data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
import {
|
||||
RenderInstitutionButton,
|
||||
@@ -33,8 +32,8 @@ const ThirdPartyAuth = (props) => {
|
||||
} = props;
|
||||
const isInstitutionAuthActive = !!secondaryProviders.length && !currentProvider;
|
||||
const isSocialAuthActive = !!providers.length && !currentProvider;
|
||||
const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN;
|
||||
const enterpriseLoginURL = getConfig().LMS_BASE_URL + ENTERPRISE_LOGIN_URL;
|
||||
const isEnterpriseLoginDisabled = useAppConfig().DISABLE_ENTERPRISE_LOGIN;
|
||||
const enterpriseLoginURL = getSiteConfig().lmsBaseUrl + ENTERPRISE_LOGIN_URL;
|
||||
const isThirdPartyAuthActive = isSocialAuthActive || (isEnterpriseLoginDisabled && isInstitutionAuthActive);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Alert } from '@openedx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const ThirdPartyAuthAlert = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { currentProvider, referrer } = props;
|
||||
const platformName = getConfig().SITE_NAME;
|
||||
const platformName = getSiteConfig().siteName;
|
||||
let message;
|
||||
|
||||
if (referrer === LOGIN_PAGE) {
|
||||
@@ -28,7 +27,7 @@ const ThirdPartyAuthAlert = (props) => {
|
||||
{referrer === REGISTER_PAGE ? (
|
||||
<Alert.Heading>{formatMessage(messages['tpa.alert.heading'])}</Alert.Heading>
|
||||
) : null}
|
||||
<p>{ message }</p>
|
||||
<p>{message}</p>
|
||||
</Alert>
|
||||
{referrer === REGISTER_PAGE ? (
|
||||
<h4 className="mt-4 mb-4">{formatMessage(messages['registration.using.tpa.form.heading'])}</h4>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { fetchAuthenticatedUser, getAuthenticatedUser, getSiteConfig } from '@openedx/frontend-base';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
@@ -25,7 +24,7 @@ const UnAuthOnlyRoute = ({ children }) => {
|
||||
|
||||
if (isReady) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
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;
|
||||
@@ -1,6 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
import { ThirdPartyAuthProvider, useThirdPartyAuthContext } from './ThirdPartyAuthContext';
|
||||
|
||||
const TestComponent = () => {
|
||||
@@ -29,7 +28,7 @@ describe('ThirdPartyAuthContext', () => {
|
||||
</ThirdPartyAuthProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Test Child')).toBeInTheDocument();
|
||||
expect(screen.getByText('Test Child')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should provide all context values to children', () => {
|
||||
@@ -39,10 +38,10 @@ describe('ThirdPartyAuthContext', () => {
|
||||
</ThirdPartyAuthProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('FieldDescriptions Available')).toBeInTheDocument();
|
||||
expect(screen.getByText('OptionalFields Available')).toBeInTheDocument();
|
||||
expect(screen.getByText('AuthApiStatus Not Available')).toBeInTheDocument(); // Initially null
|
||||
expect(screen.getByText('AuthContext Available')).toBeInTheDocument();
|
||||
expect(screen.getByText('FieldDescriptions Available')).toBeTruthy();
|
||||
expect(screen.getByText('OptionalFields Available')).toBeTruthy();
|
||||
expect(screen.getByText('AuthApiStatus Not Available')).toBeTruthy(); // Initially null
|
||||
expect(screen.getByText('AuthContext Available')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render multiple children', () => {
|
||||
@@ -54,8 +53,8 @@ describe('ThirdPartyAuthContext', () => {
|
||||
</ThirdPartyAuthProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('First Child')).toBeInTheDocument();
|
||||
expect(screen.getByText('Second Child')).toBeInTheDocument();
|
||||
expect(screen.getByText('Third Child')).toBeInTheDocument();
|
||||
expect(screen.getByText('First Child')).toBeTruthy();
|
||||
expect(screen.getByText('Second Child')).toBeTruthy();
|
||||
expect(screen.getByText('Third Child')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,34 +5,34 @@ import {
|
||||
import { COMPLETE_STATE, FAILURE_STATE, PENDING_STATE } from '../../data/constants';
|
||||
|
||||
interface ThirdPartyAuthContextType {
|
||||
fieldDescriptions: any;
|
||||
fieldDescriptions: any,
|
||||
optionalFields: {
|
||||
fields: any;
|
||||
extended_profile: any[];
|
||||
};
|
||||
thirdPartyAuthApiStatus: string | null;
|
||||
fields: any,
|
||||
extended_profile: any[],
|
||||
},
|
||||
thirdPartyAuthApiStatus: string | null,
|
||||
thirdPartyAuthContext: {
|
||||
platformName: string | null;
|
||||
autoSubmitRegForm: boolean;
|
||||
currentProvider: string | null;
|
||||
finishAuthUrl: string | null;
|
||||
countryCode: string | null;
|
||||
providers: any[];
|
||||
secondaryProviders: any[];
|
||||
pipelineUserDetails: any | null;
|
||||
errorMessage: string | null;
|
||||
welcomePageRedirectUrl: string | null;
|
||||
};
|
||||
setThirdPartyAuthContextBegin: () => void;
|
||||
setThirdPartyAuthContextSuccess: (fieldDescData: any, optionalFieldsData: any, contextData: any) => void;
|
||||
setThirdPartyAuthContextFailure: () => void;
|
||||
clearThirdPartyAuthErrorMessage: () => void;
|
||||
platformName: string | null,
|
||||
autoSubmitRegForm: boolean,
|
||||
currentProvider: string | null,
|
||||
finishAuthUrl: string | null,
|
||||
countryCode: string | null,
|
||||
providers: any[],
|
||||
secondaryProviders: any[],
|
||||
pipelineUserDetails: any | null,
|
||||
errorMessage: string | null,
|
||||
welcomePageRedirectUrl: string | null,
|
||||
},
|
||||
setThirdPartyAuthContextBegin: () => void,
|
||||
setThirdPartyAuthContextSuccess: (fieldDescData: any, optionalFieldsData: any, contextData: any) => void,
|
||||
setThirdPartyAuthContextFailure: () => void,
|
||||
clearThirdPartyAuthErrorMessage: () => void,
|
||||
}
|
||||
|
||||
const ThirdPartyAuthContext = createContext<ThirdPartyAuthContextType | undefined>(undefined);
|
||||
|
||||
interface ThirdPartyAuthProviderProps {
|
||||
children: ReactNode;
|
||||
children: ReactNode,
|
||||
}
|
||||
|
||||
export const ThirdPartyAuthProvider: FC<ThirdPartyAuthProviderProps> = ({ children }) => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { getAuthenticatedHttpClient, getSiteConfig } from '@openedx/frontend-base';
|
||||
|
||||
const getThirdPartyAuthContext = async (urlParams : string) => {
|
||||
const getThirdPartyAuthContext = async (urlParams: string) => {
|
||||
const requestConfig = {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
params: urlParams,
|
||||
@@ -10,7 +9,7 @@ const getThirdPartyAuthContext = async (urlParams : string) => {
|
||||
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(
|
||||
`${getConfig().LMS_BASE_URL}/api/mfe_context`,
|
||||
`${getSiteConfig().lmsBaseUrl}/api/mfe_context`,
|
||||
requestConfig,
|
||||
);
|
||||
return {
|
||||
|
||||
@@ -6,10 +6,12 @@ import { ThirdPartyAuthQueryKeys } from './queryKeys';
|
||||
// Error constants
|
||||
export const THIRD_PARTY_AUTH_ERROR = 'third-party-auth-error';
|
||||
|
||||
const useThirdPartyAuthHook = (pageId, payload) => useQuery({
|
||||
queryKey: ThirdPartyAuthQueryKeys.byPage(pageId),
|
||||
const useThirdPartyAuthHook = (pageId, payload, { enabled = true } = {}) => useQuery({
|
||||
queryKey: ThirdPartyAuthQueryKeys.byPage(pageId, payload),
|
||||
queryFn: () => getThirdPartyAuthContext(payload),
|
||||
retry: false,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes — TPA context is effectively static per session
|
||||
enabled,
|
||||
});
|
||||
|
||||
export {
|
||||
|
||||
@@ -2,5 +2,5 @@ import { appId } from '../../constants';
|
||||
|
||||
export const ThirdPartyAuthQueryKeys = {
|
||||
all: [appId, 'ThirdPartyAuth'] as const,
|
||||
byPage: (pageId: string) => [appId, 'ThirdPartyAuth', pageId] as const,
|
||||
byPage: (pageId: string, payload?: unknown) => [appId, 'ThirdPartyAuth', pageId, payload] as const,
|
||||
};
|
||||
|
||||
@@ -9,4 +9,3 @@ export { default as InstitutionLogistration } from './InstitutionLogistration';
|
||||
export { RenderInstitutionButton } from './InstitutionLogistration';
|
||||
export { default as FormGroup } from './FormGroup';
|
||||
export { default as PasswordField } from './PasswordField';
|
||||
export { default as Zendesk } from './Zendesk';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
import { defineMessages } from '@openedx/frontend-base';
|
||||
|
||||
const messages = defineMessages({
|
||||
// institution login strings
|
||||
@@ -85,33 +85,23 @@ const messages = defineMessages({
|
||||
'login.third.party.auth.account.not.linked': {
|
||||
id: 'login.third.party.auth.account.not.linked',
|
||||
defaultMessage: 'You have successfully signed into {currentProvider}, but your {currentProvider} '
|
||||
+ 'account does not have a linked {platformName} account. To link your accounts, '
|
||||
+ 'sign in now using your {platformName} password.',
|
||||
+ 'account does not have a linked {platformName} account. To link your accounts, '
|
||||
+ 'sign in now using your {platformName} password.',
|
||||
description: 'Message that appears on login page if user has successfully authenticated with social '
|
||||
+ 'auth but no associated platform account exists',
|
||||
+ 'auth but no associated platform account exists',
|
||||
},
|
||||
'register.third.party.auth.account.not.linked': {
|
||||
id: 'register.third.party.auth.account.not.linked',
|
||||
defaultMessage: 'You\'ve successfully signed into {currentProvider}! We just need a little more information '
|
||||
+ 'before you start learning with {platformName}.',
|
||||
+ 'before you start learning with {platformName}.',
|
||||
description: 'Message that appears on register page if user has successfully authenticated with TPA '
|
||||
+ 'but no associated platform account exists',
|
||||
+ 'but no associated platform account exists',
|
||||
},
|
||||
'registration.using.tpa.form.heading': {
|
||||
id: 'registration.using.tpa.form.heading',
|
||||
defaultMessage: 'Finish creating your account',
|
||||
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': {
|
||||
id: 'registration.other.options.heading',
|
||||
defaultMessage: 'Or register with:',
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/* eslint-disable import/no-import-module-exports */
|
||||
/* 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 { act } from 'react-dom/test-utils';
|
||||
import {
|
||||
@@ -15,7 +14,7 @@ import EmbeddedRegistrationRoute from '../EmbeddedRegistrationRoute';
|
||||
const RRD = require('react-router-dom');
|
||||
// Just render plain div with its children
|
||||
// eslint-disable-next-line react/prop-types
|
||||
RRD.BrowserRouter = ({ children }) => <div>{ children }</div>;
|
||||
RRD.BrowserRouter = ({ children }) => <div>{children}</div>;
|
||||
module.exports = RRD;
|
||||
|
||||
const TestApp = () => (
|
||||
@@ -60,7 +59,7 @@ describe('EmbeddedRegistrationRoute', () => {
|
||||
it('should render embedded register page if host query param is available in the url (embedded)', async () => {
|
||||
delete 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',
|
||||
};
|
||||
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { IntlProvider } from '@openedx/frontend-base';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import { RegisterProvider } from '../../register/components/RegisterContext';
|
||||
import { useFieldValidations } from '../../register/data/apiHook';
|
||||
import FormGroup from '../FormGroup';
|
||||
import PasswordField from '../PasswordField';
|
||||
|
||||
// Mock the useFieldValidations hook
|
||||
jest.mock('../../register/data/apiHook', () => ({
|
||||
useFieldValidations: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('FormGroup', () => {
|
||||
const props = {
|
||||
@@ -42,51 +32,24 @@ describe('FormGroup', () => {
|
||||
|
||||
describe('PasswordField', () => {
|
||||
let props = {};
|
||||
let queryClient;
|
||||
let mockMutate;
|
||||
|
||||
const renderWrapper = (children) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
<RegisterProvider>
|
||||
{children}
|
||||
</RegisterProvider>
|
||||
</MemoryRouter>
|
||||
</IntlProvider>
|
||||
</QueryClientProvider>
|
||||
const wrapper = children => (
|
||||
<IntlProvider locale="en">
|
||||
{children}
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
mutations: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
mockMutate = jest.fn();
|
||||
useFieldValidations.mockReturnValue({
|
||||
mutate: mockMutate,
|
||||
isPending: false,
|
||||
});
|
||||
|
||||
props = {
|
||||
floatingLabel: 'Password',
|
||||
name: 'password',
|
||||
value: 'password123',
|
||||
handleFocus: jest.fn(),
|
||||
};
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should show/hide password on icon click', () => {
|
||||
const { getByLabelText } = render(renderWrapper(<PasswordField {...props} />));
|
||||
const { getByLabelText } = render(wrapper(<PasswordField {...props} />));
|
||||
const passwordInput = getByLabelText('Password');
|
||||
|
||||
const showPasswordButton = getByLabelText('Show password');
|
||||
@@ -99,7 +62,7 @@ describe('PasswordField', () => {
|
||||
});
|
||||
|
||||
it('should show password requirement tooltip on focus', async () => {
|
||||
const { getByLabelText } = render(renderWrapper(<PasswordField {...props} />));
|
||||
const { getByLabelText } = render(wrapper(<PasswordField {...props} />));
|
||||
const passwordInput = getByLabelText('Password');
|
||||
jest.useFakeTimers();
|
||||
await act(async () => {
|
||||
@@ -116,7 +79,7 @@ describe('PasswordField', () => {
|
||||
...props,
|
||||
value: '',
|
||||
};
|
||||
const { getByLabelText } = render(renderWrapper(<PasswordField {...props} />));
|
||||
const { getByLabelText } = render(wrapper(<PasswordField {...props} />));
|
||||
const passwordInput = getByLabelText('Password');
|
||||
jest.useFakeTimers();
|
||||
await act(async () => {
|
||||
@@ -139,7 +102,7 @@ describe('PasswordField', () => {
|
||||
});
|
||||
|
||||
it('should update password requirement checks', async () => {
|
||||
const { getByLabelText } = render(renderWrapper(<PasswordField {...props} />));
|
||||
const { getByLabelText } = render(wrapper(<PasswordField {...props} />));
|
||||
const passwordInput = getByLabelText('Password');
|
||||
jest.useFakeTimers();
|
||||
await act(async () => {
|
||||
@@ -162,7 +125,7 @@ describe('PasswordField', () => {
|
||||
});
|
||||
|
||||
it('should not run validations when blur is fired on password icon click', () => {
|
||||
const { container, getByLabelText } = render(renderWrapper(<PasswordField {...props} />));
|
||||
const { container, getByLabelText } = render(wrapper(<PasswordField {...props} />));
|
||||
const passwordInput = container.querySelector('input[name="password"]');
|
||||
|
||||
const passwordIcon = getByLabelText('Show password');
|
||||
@@ -183,7 +146,7 @@ describe('PasswordField', () => {
|
||||
...props,
|
||||
handleBlur: jest.fn(),
|
||||
};
|
||||
const { container } = render(renderWrapper(<PasswordField {...props} />));
|
||||
const { container } = render(wrapper(<PasswordField {...props} />));
|
||||
const passwordInput = container.querySelector('input[name="password"]');
|
||||
|
||||
fireEvent.blur(passwordInput, {
|
||||
@@ -201,7 +164,7 @@ describe('PasswordField', () => {
|
||||
...props,
|
||||
handleErrorChange: jest.fn(),
|
||||
};
|
||||
const { container } = render(renderWrapper(<PasswordField {...props} />));
|
||||
const { container } = render(wrapper(<PasswordField {...props} />));
|
||||
const passwordInput = container.querySelector('input[name="password"]');
|
||||
|
||||
fireEvent.blur(passwordInput, {
|
||||
@@ -224,7 +187,7 @@ describe('PasswordField', () => {
|
||||
handleErrorChange: jest.fn(),
|
||||
};
|
||||
|
||||
const { getByLabelText } = render(renderWrapper(<PasswordField {...props} />));
|
||||
const { getByLabelText } = render(wrapper(<PasswordField {...props} />));
|
||||
|
||||
const passwordIcon = getByLabelText('Show password');
|
||||
|
||||
@@ -244,7 +207,7 @@ describe('PasswordField', () => {
|
||||
handleErrorChange: jest.fn(),
|
||||
};
|
||||
|
||||
const { getByLabelText } = render(renderWrapper(<PasswordField {...props} />));
|
||||
const { getByLabelText } = render(wrapper(<PasswordField {...props} />));
|
||||
|
||||
const passwordIcon = getByLabelText('Show password');
|
||||
|
||||
@@ -263,11 +226,13 @@ describe('PasswordField', () => {
|
||||
});
|
||||
|
||||
it('should run backend validations when frontend validations pass on blur when rendered from register page', () => {
|
||||
const mockValidateField = jest.fn();
|
||||
props = {
|
||||
...props,
|
||||
handleErrorChange: jest.fn(),
|
||||
validateField: mockValidateField,
|
||||
};
|
||||
const { getByLabelText } = render(renderWrapper(<PasswordField {...props} />));
|
||||
const { getByLabelText } = render(wrapper(<PasswordField {...props} />));
|
||||
const passwordField = getByLabelText('Password');
|
||||
fireEvent.blur(passwordField, {
|
||||
target: {
|
||||
@@ -276,7 +241,7 @@ describe('PasswordField', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockMutate).toHaveBeenCalledWith({ password: 'password123' });
|
||||
expect(mockValidateField).toHaveBeenCalledWith({ password: 'password123' });
|
||||
});
|
||||
|
||||
it('should use password value from prop when password icon is focused out (blur due to icon)', () => {
|
||||
@@ -286,7 +251,7 @@ describe('PasswordField', () => {
|
||||
handleErrorChange: jest.fn(),
|
||||
handleBlur: jest.fn(),
|
||||
};
|
||||
const { getByLabelText } = render(renderWrapper(<PasswordField {...props} />));
|
||||
const { getByLabelText } = render(wrapper(<PasswordField {...props} />));
|
||||
|
||||
const passwordIcon = getByLabelText('Show password');
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { IntlProvider } from '@openedx/frontend-base';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import registerIcons from '../RegisterFaIcons';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { IntlProvider } from '@openedx/frontend-base';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import { PENDING_STATE, REGISTER_PAGE } from '../../data/constants';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable import/no-import-module-exports */
|
||||
/* eslint-disable react/function-component-definition */
|
||||
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
|
||||
import { fetchAuthenticatedUser, getAuthenticatedUser } from '@openedx/frontend-base';
|
||||
import { render } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import {
|
||||
@@ -10,7 +11,8 @@ import {
|
||||
import { UnAuthOnlyRoute } from '..';
|
||||
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(),
|
||||
fetchAuthenticatedUser: jest.fn(),
|
||||
}));
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,7 @@ exports[`ThirdPartyAuthAlert renders skeleton for pending third-party auth 1`] =
|
||||
className="alert-message-content"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,7 +33,7 @@ exports[`ThirdPartyAuthAlert should match login page third party auth alert mess
|
||||
className="alert-message-content"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,7 +59,7 @@ exports[`ThirdPartyAuthAlert should match register page third party auth alert m
|
||||
Almost done!
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
@@ -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,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,
|
||||
};
|
||||
@@ -5,7 +5,6 @@ export const REGISTER_EMBEDDED_PAGE = '/register-embedded';
|
||||
export const RESET_PAGE = '/reset';
|
||||
export const AUTHN_PROGRESSIVE_PROFILING = '/welcome';
|
||||
export const DEFAULT_REDIRECT_URL = '/dashboard';
|
||||
export const RECOMMENDATIONS = '/recommendations';
|
||||
export const PASSWORD_RESET_CONFIRM = '/password_reset_confirm/:token/';
|
||||
export const PAGE_NOT_FOUND = '/notfound';
|
||||
export const ENTERPRISE_LOGIN_URL = '/enterprise/login';
|
||||
@@ -29,9 +28,9 @@ export const EMBEDDED = 'embedded';
|
||||
export const LETTER_REGEX = /[a-zA-Z]/;
|
||||
export const NUMBER_REGEX = /\d/;
|
||||
export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*'
|
||||
+ '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"'
|
||||
+ ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+)(?:[A-Z0-9-]{2,63})'
|
||||
+ '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$';
|
||||
+ '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"'
|
||||
+ ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+)(?:[A-Z0-9-]{2,63})'
|
||||
+ '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$';
|
||||
|
||||
// Query string parameters that can be passed to LMS to manage
|
||||
// things like auto-enrollment upon login and registration.
|
||||
|
||||
59
src/data/countries.ts
Normal file
59
src/data/countries.ts
Normal 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 }));
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,7 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import Cookies from 'universal-cookie';
|
||||
|
||||
import { setCookie } from '../utils';
|
||||
|
||||
// Mock getConfig function
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock Cookies class
|
||||
jest.mock('universal-cookie');
|
||||
|
||||
@@ -17,9 +11,7 @@ describe('setCookie function', () => {
|
||||
});
|
||||
|
||||
it('should set a cookie with default options', () => {
|
||||
getConfig.mockReturnValue({ SESSION_COOKIE_DOMAIN: 'example.com' });
|
||||
|
||||
setCookie('testCookie', 'testValue');
|
||||
setCookie('testCookie', 'testValue', 'example.com');
|
||||
|
||||
expect(Cookies).toHaveBeenCalled();
|
||||
expect(Cookies).toHaveBeenCalledWith();
|
||||
@@ -30,10 +22,8 @@ describe('setCookie function', () => {
|
||||
});
|
||||
|
||||
it('should set a cookie with specified expiry', () => {
|
||||
getConfig.mockReturnValue({ SESSION_COOKIE_DOMAIN: 'example.com' });
|
||||
|
||||
const expiry = new Date('2023-12-31');
|
||||
setCookie('testCookie', 'testValue', expiry);
|
||||
setCookie('testCookie', 'testValue', 'example.com', expiry);
|
||||
|
||||
expect(Cookies).toHaveBeenCalled();
|
||||
expect(Cookies).toHaveBeenCalledWith();
|
||||
@@ -45,7 +35,7 @@ describe('setCookie function', () => {
|
||||
});
|
||||
|
||||
it('should not set a cookie if cookieName is undefined', () => {
|
||||
setCookie(undefined, 'testValue');
|
||||
setCookie(undefined, 'testValue', 'example.com');
|
||||
|
||||
expect(Cookies).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
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.
|
||||
const cookies = new Cookies();
|
||||
const options = { domain: getConfig().SESSION_COOKIE_DOMAIN, path: '/' };
|
||||
const options = { domain: cookieDomain, path: '/' };
|
||||
if (cookieExpiry) {
|
||||
options.expires = cookieExpiry;
|
||||
}
|
||||
|
||||
@@ -40,10 +40,8 @@ export const updatePathWithQueryParams = (path) => {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (queryParams.indexOf('track=pwreset') > -1) {
|
||||
queryParams = queryParams.replace(
|
||||
'?track=pwreset&', '?',
|
||||
).replace('?track=pwreset', '').replace('&track=pwreset', '').replace('?&', '?');
|
||||
if (queryParams.includes('track=pwreset')) {
|
||||
queryParams = queryParams.replace('?track=pwreset&', '?',).replace('?track=pwreset', '').replace('&track=pwreset', '').replace('?&', '?');
|
||||
}
|
||||
|
||||
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 params = {};
|
||||
Object.entries(urlParams).forEach(([key, value]) => {
|
||||
if (AUTH_PARAMS.indexOf(key) > -1) {
|
||||
if (AUTH_PARAMS.includes(key)) {
|
||||
params[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ import { breakpoints } from '@openedx/paragon';
|
||||
const useMobileResponsive = (breakpoint) => {
|
||||
const [isMobileWindow, setIsMobileWindow] = useState();
|
||||
const checkForMobile = () => {
|
||||
setIsMobileWindow(window.matchMedia(`(max-width: ${breakpoint || breakpoints.small.maxWidth}px)`).matches);
|
||||
setIsMobileWindow(window.matchMedia(`(max-width: ${breakpoint ?? breakpoints.small.maxWidth}px)`).matches);
|
||||
};
|
||||
useEffect(() => {
|
||||
checkForMobile();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getSiteConfig } from '@openedx/frontend-base';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
|
||||
import FieldRenderer from '../FieldRenderer';
|
||||
@@ -43,7 +43,7 @@ describe('FieldRendererTests', () => {
|
||||
name: 'yob-field',
|
||||
};
|
||||
|
||||
const { container } = render(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
|
||||
const { container } = render(<FieldRenderer fieldData={fieldData} onChangeHandler={() => { }} />);
|
||||
expect(container.innerHTML).toEqual('');
|
||||
});
|
||||
|
||||
@@ -84,7 +84,7 @@ describe('FieldRendererTests', () => {
|
||||
it('should render checkbox field', () => {
|
||||
const fieldData = {
|
||||
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',
|
||||
};
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('FieldRendererTests', () => {
|
||||
type: 'unknown',
|
||||
};
|
||||
|
||||
const { container } = render(<FieldRenderer fieldData={fieldData} onChangeHandler={() => {}} />);
|
||||
const { container } = render(<FieldRenderer fieldData={fieldData} onChangeHandler={() => { }} />);
|
||||
expect(container.innerHTML).toContain('');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { FormattedMessage, useAppConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Alert } from '@openedx/paragon';
|
||||
import { CheckCircle, Error } from '@openedx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import messages from './messages';
|
||||
import {
|
||||
COMPLETE_STATE, FORBIDDEN_STATE, FORM_SUBMISSION_ERROR, INTERNAL_SERVER_ERROR,
|
||||
} from '../data/constants';
|
||||
import { PASSWORD_RESET } from '../reset-password/data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const ForgotPasswordAlert = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
@@ -34,7 +33,7 @@ const ForgotPasswordAlert = (props) => {
|
||||
values={{
|
||||
email: <span className="data-hj-suppress">{email}</span>,
|
||||
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'])}
|
||||
</Alert.Link>
|
||||
),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
getSiteConfig, sendPageEvent, sendTrackEvent, useAppConfig, useIntl,
|
||||
} from '@openedx/frontend-base';
|
||||
import {
|
||||
Form,
|
||||
Hyperlink,
|
||||
@@ -24,11 +24,12 @@ import { LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
|
||||
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
|
||||
|
||||
const ForgotPasswordPage = () => {
|
||||
const platformName = getConfig().SITE_NAME;
|
||||
const platformName = getSiteConfig().siteName;
|
||||
const emailRegex = new RegExp(VALID_EMAIL_REGEX, 'i');
|
||||
const { formatMessage } = useIntl();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const appConfig = useAppConfig();
|
||||
const [email, setEmail] = useState('');
|
||||
const [bannerEmail, setBannerEmail] = useState('');
|
||||
const [formErrors, setFormErrors] = useState('');
|
||||
@@ -110,7 +111,7 @@ const ForgotPasswordPage = () => {
|
||||
<BaseContainer>
|
||||
<Helmet>
|
||||
<title>{formatMessage(messages['forgot.password.page.title'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
{ siteName: getSiteConfig().siteName })}
|
||||
</title>
|
||||
</Helmet>
|
||||
<div>
|
||||
@@ -151,12 +152,12 @@ const ForgotPasswordPage = () => {
|
||||
onClick={handleSubmit}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
/>
|
||||
{(getConfig().LOGIN_ISSUE_SUPPORT_LINK) && (
|
||||
{(appConfig.LOGIN_ISSUE_SUPPORT_LINK) && (
|
||||
<Hyperlink
|
||||
id="forgot-password"
|
||||
name="forgot-password"
|
||||
className="ml-4 font-weight-500 text-body"
|
||||
destination={getConfig().LOGIN_ISSUE_SUPPORT_LINK}
|
||||
destination={appConfig.LOGIN_ISSUE_SUPPORT_LINK}
|
||||
target="_blank"
|
||||
showLaunchIcon={false}
|
||||
>
|
||||
@@ -166,7 +167,7 @@ const ForgotPasswordPage = () => {
|
||||
<p className="mt-5.5 small text-gray-700">
|
||||
{formatMessage(messages['additional.help.text'], { platformName })}
|
||||
<span className="mx-1">
|
||||
<Hyperlink isInline destination={`mailto:${getConfig().INFO_EMAIL}`}>{getConfig().INFO_EMAIL}</Hyperlink>
|
||||
<Hyperlink isInline destination={`mailto:${appConfig.INFO_EMAIL}`}>{appConfig.INFO_EMAIL}</Hyperlink>
|
||||
</span>
|
||||
</p>
|
||||
</Form>
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { getAuthenticatedHttpClient, getSiteConfig } from '@openedx/frontend-base';
|
||||
import formurlencoded from 'form-urlencoded';
|
||||
|
||||
import { forgotPassword } from './api';
|
||||
|
||||
// Mock the platform dependencies
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
jest.mock('@openedx/frontend-base', () => ({
|
||||
getSiteConfig: jest.fn(),
|
||||
getAuthenticatedHttpClient: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('form-urlencoded', () => jest.fn());
|
||||
|
||||
const mockGetConfig = getConfig as jest.MockedFunction<typeof getConfig>;
|
||||
const mockGetSiteConfig = getSiteConfig as jest.MockedFunction<typeof getSiteConfig>;
|
||||
const mockGetAuthenticatedHttpClient = getAuthenticatedHttpClient as
|
||||
jest.MockedFunction<typeof getAuthenticatedHttpClient>;
|
||||
const mockFormurlencoded = formurlencoded as jest.MockedFunction<typeof formurlencoded>;
|
||||
@@ -26,19 +22,19 @@ describe('forgot-password api', () => {
|
||||
};
|
||||
|
||||
const mockConfig = {
|
||||
LMS_BASE_URL: 'http://localhost:18000',
|
||||
};
|
||||
lmsBaseUrl: 'http://localhost:18000',
|
||||
} as ReturnType<typeof getSiteConfig>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockGetConfig.mockReturnValue(mockConfig);
|
||||
mockGetSiteConfig.mockReturnValue(mockConfig);
|
||||
mockGetAuthenticatedHttpClient.mockReturnValue(mockHttpClient as any);
|
||||
mockFormurlencoded.mockImplementation((data) => `encoded=${JSON.stringify(data)}`);
|
||||
});
|
||||
|
||||
describe('forgotPassword', () => {
|
||||
const testEmail = 'test@example.com';
|
||||
const expectedUrl = `${mockConfig.LMS_BASE_URL}/account/password`;
|
||||
const expectedUrl = `${mockConfig.lmsBaseUrl}/account/password`;
|
||||
const expectedConfig = {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
isPublic: true,
|
||||
@@ -60,7 +56,7 @@ describe('forgot-password api', () => {
|
||||
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
||||
expectedUrl,
|
||||
`encoded=${JSON.stringify({ email: testEmail })}`,
|
||||
expectedConfig
|
||||
expectedConfig,
|
||||
);
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
@@ -71,7 +67,7 @@ describe('forgot-password api', () => {
|
||||
data: {
|
||||
message: 'Email is required',
|
||||
success: false,
|
||||
}
|
||||
},
|
||||
};
|
||||
mockHttpClient.post.mockResolvedValueOnce(mockResponse);
|
||||
|
||||
@@ -96,7 +92,7 @@ describe('forgot-password api', () => {
|
||||
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
||||
expectedUrl,
|
||||
expect.any(String),
|
||||
expectedConfig
|
||||
expectedConfig,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -110,7 +106,6 @@ describe('forgot-password api', () => {
|
||||
|
||||
it('should handle response with no data field', async () => {
|
||||
const mockResponse = {
|
||||
// No data field
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { getAuthenticatedHttpClient, getSiteConfig } from '@openedx/frontend-base';
|
||||
import formurlencoded from 'form-urlencoded';
|
||||
|
||||
const forgotPassword = async (email: string) => {
|
||||
@@ -10,7 +9,7 @@ const forgotPassword = async (email: string) => {
|
||||
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.post(
|
||||
`${getConfig().LMS_BASE_URL}/account/password`,
|
||||
`${getSiteConfig().lmsBaseUrl}/account/password`,
|
||||
formurlencoded({ email }),
|
||||
requestConfig,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { logError, logInfo } from '@edx/frontend-platform/logging';
|
||||
import { logError, logInfo } from '@openedx/frontend-base';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as api from './api';
|
||||
import { useForgotPassword } from './apiHook';
|
||||
|
||||
// Mock the logging functions
|
||||
jest.mock('@edx/frontend-platform/logging', () => ({
|
||||
jest.mock('@openedx/frontend-base', () => ({
|
||||
logError: jest.fn(),
|
||||
logInfo: jest.fn(),
|
||||
}));
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { logError, logInfo } from '@edx/frontend-platform/logging';
|
||||
import { logError, logInfo } from '@openedx/frontend-base';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { forgotPassword } from './api';
|
||||
|
||||
interface ForgotPasswordResult {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
success: boolean,
|
||||
message?: string,
|
||||
}
|
||||
|
||||
interface UseForgotPasswordOptions {
|
||||
onSuccess?: (data: ForgotPasswordResult, email: string) => void;
|
||||
onError?: (error: Error) => void;
|
||||
onSuccess?: (data: ForgotPasswordResult, email: string) => void,
|
||||
onError?: (error: Error) => void,
|
||||
}
|
||||
|
||||
interface ApiError {
|
||||
interface ApiError extends Error {
|
||||
response?: {
|
||||
status: number;
|
||||
data: Record<string, unknown>;
|
||||
};
|
||||
status: number,
|
||||
data: Record<string, unknown>,
|
||||
},
|
||||
}
|
||||
|
||||
const useForgotPassword = (options: UseForgotPasswordOptions = {}) => useMutation({
|
||||
@@ -31,13 +31,13 @@ const useForgotPassword = (options: UseForgotPasswordOptions = {}) => useMutatio
|
||||
},
|
||||
onError: (error: ApiError) => {
|
||||
// Handle different error types like the saga did
|
||||
if (error.response && error.response.status === 403) {
|
||||
if (error.response?.status === 403) {
|
||||
logInfo(error);
|
||||
} else {
|
||||
logError(error);
|
||||
}
|
||||
if (options.onError) {
|
||||
options.onError(error as Error);
|
||||
options.onError(error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
import { defineMessages } from '@openedx/frontend-base';
|
||||
|
||||
const messages = defineMessages({
|
||||
'forgot.password.page.title': {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { mergeConfig } from '@edx/frontend-platform';
|
||||
import { configure, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
CurrentAppProvider, IntlProvider, mergeAppConfig,
|
||||
} from '@openedx/frontend-base';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import {
|
||||
fireEvent, render, screen, waitFor,
|
||||
} from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import { appId } from '../../constants';
|
||||
import {
|
||||
FORBIDDEN_STATE, FORM_SUBMISSION_ERROR, INTERNAL_SERVER_ERROR, LOGIN_PAGE,
|
||||
} from '../../data/constants';
|
||||
@@ -16,11 +18,15 @@ import ForgotPasswordPage from '../ForgotPasswordPage';
|
||||
|
||||
const mockedNavigator = jest.fn();
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
jest.mock('@openedx/frontend-base', () => ({
|
||||
...jest.requireActual('@openedx/frontend-base'),
|
||||
sendPageEvent: 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.requireActual('react-router-dom')),
|
||||
useNavigate: () => mockedNavigator,
|
||||
@@ -31,7 +37,7 @@ jest.mock('../data/apiHook', () => ({
|
||||
}));
|
||||
|
||||
describe('ForgotPasswordPage', () => {
|
||||
mergeConfig({
|
||||
mergeAppConfig(appId, {
|
||||
LOGIN_ISSUE_SUPPORT_LINK: '',
|
||||
INFO_EMAIL: '',
|
||||
});
|
||||
@@ -65,7 +71,9 @@ describe('ForgotPasswordPage', () => {
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
{component}
|
||||
<CurrentAppProvider appId={appId}>
|
||||
{component}
|
||||
</CurrentAppProvider>
|
||||
</MemoryRouter>
|
||||
</IntlProvider>
|
||||
</QueryClientProvider>
|
||||
@@ -85,21 +93,6 @@ describe('ForgotPasswordPage', () => {
|
||||
},
|
||||
});
|
||||
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getAuthenticatedUser: jest.fn(() => ({
|
||||
userId: 3,
|
||||
username: 'test-user',
|
||||
})),
|
||||
}));
|
||||
configure({
|
||||
loggingService: { logError: jest.fn() },
|
||||
config: {
|
||||
ENVIRONMENT: 'production',
|
||||
LANGUAGE_PREFERENCE_COOKIE_NAME: 'yum',
|
||||
},
|
||||
messages: { 'es-419': {}, de: {}, 'en-us': {} },
|
||||
});
|
||||
|
||||
// Clear mock calls between tests
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
@@ -111,7 +104,7 @@ describe('ForgotPasswordPage', () => {
|
||||
});
|
||||
|
||||
it('should display need other help signing in button', () => {
|
||||
mergeConfig({
|
||||
mergeAppConfig(appId, {
|
||||
LOGIN_ISSUE_SUPPORT_LINK: '/support',
|
||||
});
|
||||
render(renderWrapper(<ForgotPasswordPage />));
|
||||
@@ -354,7 +347,9 @@ describe('ForgotPasswordAlert', () => {
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
<ForgotPasswordAlert {...props} />
|
||||
<CurrentAppProvider appId={appId}>
|
||||
<ForgotPasswordAlert {...props} />
|
||||
</CurrentAppProvider>
|
||||
</MemoryRouter>
|
||||
</IntlProvider>
|
||||
</QueryClientProvider>,
|
||||
|
||||
@@ -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: {},
|
||||
};
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
|
||||
import { 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,
|
||||
});
|
||||
@@ -1,75 +0,0 @@
|
||||
@use "@openedx/paragon/styles/css/core/custom-media-breakpoints" as paragonCustomMediaBreakpoints;
|
||||
@import "sass/style";
|
||||
|
||||
//commit color - Andal Learning Brand Colors - Override Paragon variables
|
||||
:root {
|
||||
--pgn-color-primary: #ff4f00;
|
||||
--pgn-color-primary-100: #ffe6cc;
|
||||
--pgn-color-primary-200: #ffcc99;
|
||||
--pgn-color-primary-300: #ffb366;
|
||||
--pgn-color-primary-400: #ff9933;
|
||||
--pgn-color-primary-500: #ff4f00;
|
||||
--pgn-color-primary-600: #cc3f00;
|
||||
--pgn-color-primary-700: #992f00;
|
||||
--pgn-color-primary-800: #661f00;
|
||||
--pgn-color-primary-900: #330f00;
|
||||
--pgn-color-action-primary: #ff4f00;
|
||||
--pgn-color-action-primary-hover: #cc3f00;
|
||||
--pgn-color-action-primary-focus: #992f00;
|
||||
--pgn-color-action-primary-active: #992f00;
|
||||
--pgn-color-primary-base: #ff4f00;
|
||||
}
|
||||
|
||||
// Override all button variants to use Andal orange
|
||||
.btn-primary {
|
||||
background-color: #ff4f00 !important;
|
||||
border-color: #ff4f00 !important;
|
||||
|
||||
&:hover {
|
||||
background-color: #cc3f00 !important;
|
||||
border-color: #cc3f00 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
color: #ff4f00 !important;
|
||||
border-color: #ff4f00 !important;
|
||||
|
||||
&:hover {
|
||||
background-color: #ff4f00 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-brand {
|
||||
background-color: #ff4f00 !important;
|
||||
border-color: #ff4f00 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
// Override link colors
|
||||
a {
|
||||
color: #ff4f00;
|
||||
|
||||
&:hover {
|
||||
color: #cc3f00;
|
||||
}
|
||||
}
|
||||
|
||||
// Override active states
|
||||
.active {
|
||||
background-color: #ff4f00 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
// Hide Register tab, only show Sign in
|
||||
.nav-tabs {
|
||||
.nav-link[href*="register"] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Change "with Andal LND" text color from blue to black
|
||||
.text-accent-a {
|
||||
color: #000000 !important;
|
||||
}
|
||||
3
src/index.ts
Normal file
3
src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as authnApp } from './app';
|
||||
export { default as authnRoutes } from './routes';
|
||||
export { default as authnMessages } from './i18n';
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { FormattedMessage, useAppConfig, useIntl } from '@openedx/frontend-base';
|
||||
import { Alert } from '@openedx/paragon';
|
||||
import { CheckCircle, Error } from '@openedx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
@@ -15,7 +14,7 @@ const AccountActivationMessage = ({ 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 = {
|
||||
[ACCOUNT_ACTIVATION_MESSAGE.SUCCESS]: CheckCircle,
|
||||
[ACCOUNT_ACTIVATION_MESSAGE.ERROR]: Error,
|
||||
@@ -35,7 +34,7 @@ const AccountActivationMessage = ({ messageType }) => {
|
||||
}
|
||||
case ACCOUNT_ACTIVATION_MESSAGE.ERROR: {
|
||||
const supportLink = (
|
||||
<Alert.Link href={getConfig().ACTIVATION_EMAIL_SUPPORT_LINK}>
|
||||
<Alert.Link href={useAppConfig().ACTIVATION_EMAIL_SUPPORT_LINK}>
|
||||
{formatMessage(messages['account.activation.support.link'])}
|
||||
</Alert.Link>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { getSiteConfig, useIntl } from '@openedx/frontend-base';
|
||||
import {
|
||||
ActionRow, ModalDialog, useToggle,
|
||||
} from '@openedx/paragon';
|
||||
@@ -9,10 +8,10 @@ import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
import messages from './messages';
|
||||
import { DEFAULT_REDIRECT_URL, RESET_PAGE } from '../data/constants';
|
||||
import { updatePathWithQueryParams } from '../data/utils';
|
||||
import useMobileResponsive from '../data/utils/useMobileResponsive';
|
||||
import messages from './messages';
|
||||
|
||||
const ChangePasswordPrompt = ({ variant, redirectUrl }) => {
|
||||
const isMobileView = useMobileResponsive();
|
||||
@@ -22,11 +21,11 @@ const ChangePasswordPrompt = ({ variant, redirectUrl }) => {
|
||||
if (variant === 'block') {
|
||||
setRedirectToResetPasswordPage(true);
|
||||
} 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, @typescript-eslint/no-unused-vars
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [isOpen, open, close] = useToggle(true, handlers);
|
||||
const { formatMessage } = useIntl();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthService } from '@edx/frontend-platform/auth';
|
||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
FormattedMessage, getAuthService, getSiteConfig, useIntl
|
||||
} from '@openedx/frontend-base';
|
||||
import { Alert, Hyperlink } from '@openedx/paragon';
|
||||
import { Error } from '@openedx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { windowScrollTo } from '../data/utils';
|
||||
import ChangePasswordPrompt from './ChangePasswordPrompt';
|
||||
import {
|
||||
ACCOUNT_LOCKED_OUT,
|
||||
@@ -23,7 +24,6 @@ import {
|
||||
TPA_AUTHENTICATION_FAILURE,
|
||||
} from './data/constants';
|
||||
import messages from './messages';
|
||||
import { windowScrollTo } from '../data/utils';
|
||||
|
||||
const LoginFailureMessage = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
@@ -75,6 +75,7 @@ const LoginFailureMessage = (props) => {
|
||||
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,
|
||||
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={{
|
||||
lineBreak: <br />,
|
||||
email: <strong className="data-hj-suppress">{context.email}</strong>,
|
||||
@@ -86,7 +87,7 @@ const LoginFailureMessage = (props) => {
|
||||
break;
|
||||
}
|
||||
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 = (
|
||||
<a href={url}>
|
||||
{formatMessage(messages['tpa.account.link'], { provider: context.provider })}
|
||||
@@ -161,6 +162,7 @@ const LoginFailureMessage = (props) => {
|
||||
<FormattedMessage
|
||||
id="login.incorrect.credentials.error.with.reset.link"
|
||||
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 }}
|
||||
/>
|
||||
</p>
|
||||
@@ -184,7 +186,7 @@ const LoginFailureMessage = (props) => {
|
||||
errorMessage = (
|
||||
<p>
|
||||
{formatMessage(messages['login.tpa.authentication.failure'], {
|
||||
platform_name: getConfig().SITE_NAME,
|
||||
platform_name: getSiteConfig().siteName,
|
||||
lineBreak: <br />,
|
||||
errorMessage: context.errorMessage,
|
||||
})}
|
||||
@@ -200,7 +202,7 @@ const LoginFailureMessage = (props) => {
|
||||
return (
|
||||
<Alert id="login-failure-alert" className="mb-5" variant="danger" icon={Error}>
|
||||
<Alert.Heading>{formatMessage(messages['login.failure.header.title'])}</Alert.Heading>
|
||||
{ errorMessage }
|
||||
{errorMessage}
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
getSiteConfig, sendPageEvent, sendTrackEvent, useIntl
|
||||
} from '@openedx/frontend-base';
|
||||
import { Form, StatefulButton } from '@openedx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Helmet } from 'react-helmet';
|
||||
@@ -100,11 +100,11 @@ const LoginPage = ({
|
||||
|
||||
useEffect(() => {
|
||||
sendPageEvent('login_and_registration', 'login');
|
||||
}, []);
|
||||
|
||||
// Fetch third-party auth context data
|
||||
useEffect(() => {
|
||||
setThirdPartyAuthContextBegin();
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Sync third-party auth context data
|
||||
useEffect(() => {
|
||||
if (isSuccess && data) {
|
||||
setThirdPartyAuthContextSuccess(
|
||||
data.fieldDescriptions,
|
||||
@@ -116,7 +116,7 @@ const LoginPage = ({
|
||||
setThirdPartyAuthContextFailure();
|
||||
}
|
||||
}, [tpaHint, queryParams, isSuccess, data, error,
|
||||
setThirdPartyAuthContextBegin, setThirdPartyAuthContextSuccess, setThirdPartyAuthContextFailure]);
|
||||
setThirdPartyAuthContextSuccess, setThirdPartyAuthContextFailure]);
|
||||
|
||||
useEffect(() => {
|
||||
if (thirdPartyErrorMessage) {
|
||||
@@ -210,7 +210,7 @@ const LoginPage = ({
|
||||
}
|
||||
|
||||
if (skipHintedLogin) {
|
||||
window.location.href = getConfig().LMS_BASE_URL + provider.loginUrl;
|
||||
window.location.href = getSiteConfig().lmsBaseUrl + provider.loginUrl;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -227,11 +227,10 @@ const LoginPage = ({
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{formatMessage(messages['login.page.title'], { siteName: getConfig().SITE_NAME })}</title>
|
||||
<title>{formatMessage(messages['login.page.title'], { siteName: getSiteConfig().siteName })}</title>
|
||||
</Helmet>
|
||||
<RedirectLogistration
|
||||
success={loginResult.success}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
import { LoginProvider, useLoginContext } from './LoginContext';
|
||||
|
||||
const TestComponent = () => {
|
||||
@@ -29,7 +28,7 @@ describe('LoginContext', () => {
|
||||
</LoginProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Test Child')).toBeInTheDocument();
|
||||
expect(screen.getByText('Test Child')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should provide all context values to children', () => {
|
||||
@@ -39,12 +38,12 @@ describe('LoginContext', () => {
|
||||
</LoginProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('FormFields Available')).toBeInTheDocument();
|
||||
expect(screen.getByText('EmailOrUsername Field Available')).toBeInTheDocument();
|
||||
expect(screen.getByText('Password Field Available')).toBeInTheDocument();
|
||||
expect(screen.getByText('Errors Available')).toBeInTheDocument();
|
||||
expect(screen.getByText('EmailOrUsername Error Available')).toBeInTheDocument();
|
||||
expect(screen.getByText('Password Error Available')).toBeInTheDocument();
|
||||
expect(screen.getByText('FormFields Available')).toBeTruthy();
|
||||
expect(screen.getByText('EmailOrUsername Field Available')).toBeTruthy();
|
||||
expect(screen.getByText('Password Field Available')).toBeTruthy();
|
||||
expect(screen.getByText('Errors Available')).toBeTruthy();
|
||||
expect(screen.getByText('EmailOrUsername Error Available')).toBeTruthy();
|
||||
expect(screen.getByText('Password Error Available')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render multiple children', () => {
|
||||
@@ -56,8 +55,8 @@ describe('LoginContext', () => {
|
||||
</LoginProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('First Child')).toBeInTheDocument();
|
||||
expect(screen.getByText('Second Child')).toBeInTheDocument();
|
||||
expect(screen.getByText('Third Child')).toBeInTheDocument();
|
||||
expect(screen.getByText('First Child')).toBeTruthy();
|
||||
expect(screen.getByText('Second Child')).toBeTruthy();
|
||||
expect(screen.getByText('Third Child')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import {
|
||||
createContext, FC, ReactNode, useContext, useMemo, useState,
|
||||
createContext, Dispatch, FC, ReactNode, SetStateAction, useContext, useMemo, useState,
|
||||
} from 'react';
|
||||
|
||||
export interface FormFields {
|
||||
emailOrUsername: string;
|
||||
password: string;
|
||||
emailOrUsername: string,
|
||||
password: string,
|
||||
}
|
||||
|
||||
export interface FormErrors {
|
||||
emailOrUsername: string;
|
||||
password: string;
|
||||
emailOrUsername: string,
|
||||
password: string,
|
||||
}
|
||||
|
||||
interface LoginContextType {
|
||||
formFields: FormFields;
|
||||
setFormFields: (fields: FormFields) => void;
|
||||
errors: FormErrors;
|
||||
setErrors: (errors: FormErrors) => void;
|
||||
formFields: FormFields,
|
||||
setFormFields: Dispatch<SetStateAction<FormFields>>,
|
||||
errors: FormErrors,
|
||||
setErrors: Dispatch<SetStateAction<FormErrors>>,
|
||||
}
|
||||
|
||||
const LoginContext = createContext<LoginContextType | undefined>(undefined);
|
||||
|
||||
interface LoginProviderProps {
|
||||
children: ReactNode;
|
||||
children: ReactNode,
|
||||
}
|
||||
|
||||
export const LoginProvider: FC<LoginProviderProps> = ({ children }) => {
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { camelCaseObject, getAuthenticatedHttpClient, getSiteConfig, getUrlByRouteRole } from '@openedx/frontend-base';
|
||||
import * as QueryString from 'query-string';
|
||||
|
||||
import { login } from './api';
|
||||
|
||||
// Mock the platform dependencies
|
||||
jest.mock('@edx/frontend-platform', () => ({
|
||||
getConfig: jest.fn(),
|
||||
camelCaseObject: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
jest.mock('@openedx/frontend-base', () => ({
|
||||
getSiteConfig: jest.fn(),
|
||||
getAuthenticatedHttpClient: jest.fn(),
|
||||
getUrlByRouteRole: jest.fn(),
|
||||
camelCaseObject: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('query-string', () => ({
|
||||
stringify: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockGetConfig = getConfig as jest.MockedFunction<typeof getConfig>;
|
||||
const mockGetSiteConfig = getSiteConfig as jest.MockedFunction<typeof getSiteConfig>;
|
||||
const mockCamelCaseObject = camelCaseObject as jest.MockedFunction<typeof camelCaseObject>;
|
||||
const mockGetAuthenticatedHttpClient = getAuthenticatedHttpClient as
|
||||
jest.MockedFunction<typeof getAuthenticatedHttpClient>;
|
||||
const mockGetUrlByRouteRole = getUrlByRouteRole as jest.MockedFunction<typeof getUrlByRouteRole>;
|
||||
const mockQueryStringify = QueryString.stringify as jest.MockedFunction<typeof QueryString.stringify>;
|
||||
|
||||
describe('login api', () => {
|
||||
@@ -30,13 +28,14 @@ describe('login api', () => {
|
||||
};
|
||||
|
||||
const mockConfig = {
|
||||
LMS_BASE_URL: 'http://localhost:18000',
|
||||
};
|
||||
lmsBaseUrl: 'http://localhost:18000',
|
||||
} as ReturnType<typeof getSiteConfig>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockGetConfig.mockReturnValue(mockConfig);
|
||||
mockGetSiteConfig.mockReturnValue(mockConfig);
|
||||
mockGetAuthenticatedHttpClient.mockReturnValue(mockHttpClient as any);
|
||||
mockGetUrlByRouteRole.mockReturnValue('/dashboard');
|
||||
mockCamelCaseObject.mockImplementation((obj) => obj);
|
||||
mockQueryStringify.mockImplementation((obj) => `stringified=${JSON.stringify(obj)}`);
|
||||
});
|
||||
@@ -46,7 +45,7 @@ describe('login api', () => {
|
||||
email_or_username: 'testuser@example.com',
|
||||
password: 'password123',
|
||||
};
|
||||
const expectedUrl = `${mockConfig.LMS_BASE_URL}/api/user/v2/account/login_session/`;
|
||||
const expectedUrl = `${mockConfig.lmsBaseUrl}/api/user/v2/account/login_session/`;
|
||||
const expectedConfig = {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
isPublic: true,
|
||||
@@ -151,7 +150,7 @@ describe('login api', () => {
|
||||
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
||||
expectedUrl,
|
||||
`stringified=${JSON.stringify(mockCredentials)}`,
|
||||
expectedConfig
|
||||
expectedConfig,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { camelCaseObject, getAuthenticatedHttpClient, getSiteConfig, getUrlByRouteRole } from '@openedx/frontend-base';
|
||||
import * as QueryString from 'query-string';
|
||||
|
||||
const login = async (creds) => {
|
||||
@@ -7,11 +6,12 @@ const login = async (creds) => {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
isPublic: true,
|
||||
};
|
||||
const url = `${getConfig().LMS_BASE_URL}/api/user/v2/account/login_session/`;
|
||||
const url = `${getSiteConfig().lmsBaseUrl}/api/user/v2/account/login_session/`;
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.post(url, QueryString.stringify(creds), requestConfig);
|
||||
const defaultRedirectUrl = getUrlByRouteRole('org.openedx.frontend.role.dashboard');
|
||||
return camelCaseObject({
|
||||
redirectUrl: data.redirect_url || `${getConfig().LMS_BASE_URL}/dashboard`,
|
||||
redirectUrl: data.redirect_url || defaultRedirectUrl,
|
||||
success: data.success || false,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { logError, logInfo } from '@edx/frontend-platform/logging';
|
||||
import { camelCaseObject } from '@edx/frontend-platform/utils';
|
||||
import { camelCaseObject, logError, logInfo } from '@openedx/frontend-base';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
|
||||
@@ -12,12 +11,9 @@ import {
|
||||
import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from './constants';
|
||||
|
||||
// Mock the dependencies
|
||||
jest.mock('@edx/frontend-platform/logging', () => ({
|
||||
jest.mock('@openedx/frontend-base', () => ({
|
||||
logError: jest.fn(),
|
||||
logInfo: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/utils', () => ({
|
||||
camelCaseObject: jest.fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { logError, logInfo } from '@edx/frontend-platform/logging';
|
||||
import { camelCaseObject } from '@edx/frontend-platform/utils';
|
||||
import { camelCaseObject, logError, logInfo } from '@openedx/frontend-base';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { login } from './api';
|
||||
@@ -7,17 +6,17 @@ import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from './constants';
|
||||
|
||||
// Type definitions
|
||||
interface LoginData {
|
||||
email_or_username: string;
|
||||
password: string;
|
||||
email_or_username: string,
|
||||
password: string,
|
||||
}
|
||||
|
||||
interface LoginResponse {
|
||||
redirectUrl?: string;
|
||||
redirectUrl?: string,
|
||||
}
|
||||
|
||||
interface UseLoginOptions {
|
||||
onSuccess?: (data: LoginResponse) => void;
|
||||
onError?: (error: unknown) => void;
|
||||
onSuccess?: (data: LoginResponse) => void,
|
||||
onError?: (error: unknown) => void,
|
||||
}
|
||||
|
||||
const useLogin = (options: UseLoginOptions = {}) => useMutation<LoginResponse, unknown, LoginData>({
|
||||
@@ -36,10 +35,10 @@ const useLogin = (options: UseLoginOptions = {}) => useMutation<LoginResponse, u
|
||||
count: 0,
|
||||
};
|
||||
if (error && typeof error === 'object' && 'response' in error && error.response) {
|
||||
const response = error.response as { status?: number; data?: unknown };
|
||||
const response = error.response as { status?: number, data?: unknown };
|
||||
const { status, data } = camelCaseObject(response);
|
||||
if (data && typeof data === 'object') {
|
||||
const errorData = data as { errorCode?: string; context?: { failureCount?: number } };
|
||||
const errorData = data as { errorCode?: string, context?: { failureCount?: number } };
|
||||
formattedError = {
|
||||
type: errorData.errorCode || FORBIDDEN_REQUEST,
|
||||
context: errorData.context || {},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user