Compare commits

..

2 Commits

Author SHA1 Message Date
Attiya Ishaque
25909563a4 fix: TPA data not auto-populated (#1156) 2024-02-14 16:18:10 +05:00
Attiya Ishaque
94e823663e feat: split full name into first name and last name (#1131) 2024-02-14 16:18:10 +05:00
221 changed files with 18492 additions and 10967 deletions

42
.env Normal file
View File

@@ -0,0 +1,42 @@
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_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=''

4
.env.private.example Normal file
View File

@@ -0,0 +1,4 @@
# 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'

20
.env.test Normal file
View File

@@ -0,0 +1,20 @@
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=''

6
.eslintignore Executable file
View File

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

52
.eslintrc.js Normal file
View File

@@ -0,0 +1,52 @@
// 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',
},
});

1
.github/CODEOWNERS vendored
View File

@@ -1 +0,0 @@
* @openedx/2U-infinity

View File

@@ -1,7 +0,0 @@
version: 2
updates:
# Adding new check for github-actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -25,5 +25,5 @@ Include a link to the sandbox for design changes or screenshot for before and af
#### Post-merge Checklist #### Post-merge Checklist
* [ ] Deploy the changes to prod after verifying on stage or ask **@openedx/2u-infinity** to do it. * [ ] Deploy the changes to prod after verifying on stage or ask **@openedx/vanguards** to do it.
* [ ] 🎉 🙌 Celebrate! Thanks for your contribution. * [ ] 🎉 🙌 Celebrate! Thanks for your contribution.

View File

@@ -10,7 +10,7 @@ on:
jobs: jobs:
autoupdate: autoupdate:
name: autoupdate name: autoupdate
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: docker://chinthakagodawita/autoupdate-action:v1 - uses: docker://chinthakagodawita/autoupdate-action:v1
env: env:

View File

@@ -10,15 +10,17 @@ on:
jobs: jobs:
tests: tests:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- name: Setup Nodejs - name: Setup Nodejs
uses: actions/setup-node@v4 uses: actions/setup-node@v3
with: with:
node-version-file: '.nvmrc' node-version: ${{ env.NODE_VER }}
- name: Install Dependencies - name: Install Dependencies
run: npm ci run: npm ci
@@ -39,7 +41,4 @@ jobs:
run: npm run build run: npm run build
- name: Run Code Coverage - name: Run Code Coverage
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true

View File

@@ -10,4 +10,4 @@ on:
jobs: jobs:
version-check: version-check:
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master uses: openedx/.github/.github/workflows/lockfile-check.yml@master

15
.gitignore vendored
View File

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

View File

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

2
.nvmrc
View File

@@ -1 +1 @@
20 18

9
.tx/config Normal file
View File

@@ -0,0 +1,9 @@
[main]
host = https://www.transifex.com
[o:open-edx:p:edx-platform:r:frontend-app-authn]
file_filter = src/i18n/messages/<lang>.json
source_file = src/i18n/transifex_input.json
source_lang = en
type = KEYVALUEJSON

View File

@@ -1,2 +1,2 @@
# The following users are the owners of all frontend-app-authn files # The following users are the owners of all frontend-app-authn files
* @openedx/2u-infinity * @openedx/vanguards

View File

@@ -1,3 +1,6 @@
export TRANSIFEX_RESOURCE = frontend-app-authn
transifex_langs = "ar,de,de_DE,es_419,fa_IR,fr,fr_CA,hi,it,it_IT,pt,pt_PT,ru,uk,zh_CN"
intl_imports = ./node_modules/.bin/intl-imports.js intl_imports = ./node_modules/.bin/intl-imports.js
transifex_utils = ./node_modules/.bin/transifex-utils.js transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./src/i18n i18n = ./src/i18n
@@ -29,6 +32,23 @@ detect_changed_source_translations:
# Checking for changed translations... # Checking for changed translations...
git diff --exit-code $(i18n) git diff --exit-code $(i18n)
# Pushes translations to Transifex. You must run make extract_translations first.
push_translations:
# Pushing strings to Transifex...
tx push -s
# Fetching hashes from Transifex...
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
# Writing out comments to file...
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
# Pushing comments to Transifex...
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
ifeq ($(OPENEDX_ATLAS_PULL),)
# Pulls translations from Transifex.
pull_translations:
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
else
# Experimental: OEP-58 Pulls translations using atlas
pull_translations: pull_translations:
rm -rf src/i18n/messages rm -rf src/i18n/messages
mkdir src/i18n/messages mkdir src/i18n/messages
@@ -39,6 +59,7 @@ pull_translations:
translations/frontend-app-authn/src/i18n/messages:frontend-app-authn translations/frontend-app-authn/src/i18n/messages:frontend-app-authn
$(intl_imports) paragon frontend-platform frontend-app-authn $(intl_imports) paragon frontend-platform frontend-app-authn
endif
# This target is used by Travis. # This target is used by Travis.
validate-no-uncommitted-package-lock-changes: validate-no-uncommitted-package-lock-changes:

View File

@@ -29,15 +29,29 @@ Getting Started
Installation 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. This MFE is bundled with `Devstack <https://github.com/openedx/devstack>`_, see the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ section for setup instructions.
.. _Tutor: https://github.com/overhangio/tutor 1. Install Devstack using the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ instructions.
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development
2. Start up LMS, if it's not already started.
4. Within this project (frontend-app-authn), install requirements and start the development server:
.. code-block::
npm install
npm start # The server will run on port 1999
5. Once the dev server is up, visit http://localhost:1999 to access the MFE
.. image:: ./docs/images/frontend-app-authn-localhost-preview.png
**Note:** Follow `Enable social auth locally <docs/how_tos/enable_social_auth.rst>`_ for enabling Social Sign-on Buttons (SSO) locally
Environment Variables/Setup Notes Environment Variables/Setup Notes
================================= =================================
This MFE is configured via environment variables supplied at build time. All micro-frontends have a shared set of required environment variables, as documented in the Open edX Developer Guide under `Required Environment Variables <https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development>`__. This MFE is configured via environment variables supplied at build time. All micro-frontends have a shared set of required environment variables, as documented in the Open edX Developer Guide under `Required Environment Variables <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
The authentication micro-frontend also requires the following additional variable: The authentication micro-frontend also requires the following additional variable:
@@ -123,14 +137,18 @@ Furthermore, there are several edX-specific environment variables that enable in
- Enables support for opting in marketing emails that helps us getting user consent for sending marketing emails. - Enables support for opting in marketing emails that helps us getting user consent for sending marketing emails.
- ``true`` | ``''`` (empty strings are falsy) - ``true`` | ``''`` (empty strings are falsy)
* - ``SHOW_CONFIGURABLE_EDX_FIELDS``
- For edX, country and honor code fields are required by default. This flag enables edX specific required fields.
- ``true`` | ``''`` (empty strings are falsy)
For more information see the document: `Micro-frontend applications in Open For more information see the document: `Micro-frontend applications in Open
edX <https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development>`__. edX <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
How To Contribute How To Contribute
================= =================
Contributions are very welcome, and strongly encouraged! We've Contributions are very welcome, and strongly encouraged! We've
put together `some documentation that describes our contribution process <https://docs.openedx.org/en/latest/developers/references/developer_guide/process/index.html>`_. put together `some documentation that describes our contribution process <https://edx.readthedocs.org/projects/edx-developer-guide/en/latest/process/index.html>`_.
Even though they were written with edx-platform in mind, the guidelines should be followed for Open edX code in general. Even though they were written with edx-platform in mind, the guidelines should be followed for Open edX code in general.
@@ -169,7 +187,7 @@ All community members are expected to follow the `Open edX Code of Conduct <http
People People
====== ======
The assigned maintainers for this component and other project details may be The assigned maintainers for this component and other project details may be
found in `Backstage <https://backstage.openedx.org/catalog/default/group/2u-infinity>`_. Backstage pulls this data from the ``catalog-info.yaml`` found in `Backstage <https://backstage.openedx.org/catalog/default/group/vanguards>`_. Backstage pulls this data from the ``catalog-info.yaml``
file in this repo. file in this repo.
Reporting Security Issues Reporting Security Issues
@@ -201,4 +219,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 :target: https://github.com/openedx/edx-developer-docs/actions/workflows/ci.yml
:alt: Continuous Integration :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 .. |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
View File

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

View File

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

View File

@@ -12,8 +12,7 @@ metadata:
icon: 'Article' icon: 'Article'
annotations: annotations:
openedx.org/arch-interest-groups: "" openedx.org/arch-interest-groups: ""
openedx.org/release: "master"
spec: spec:
owner: group:2u-infinity owner: group:vanguards
type: 'service' type: 'service'
lifecycle: 'production' lifecycle: 'production'

View File

@@ -3,7 +3,7 @@ Enable Social Auth Locally
Please follow the steps below to enable social auth (SSO) locally. Please follow the steps below to enable social auth (SSO) locally.
1. Follow `Enabling Third Party Authentication <https://docs.openedx.org/en/latest/site_ops/install_configure_run_guide/configuration/tpa/index.html>`_ for backend configuration. 1. Follow `Enabling Third Party Authentication <https://edx.readthedocs.io/projects/edx-installing-configuring-and-running/en/latest/configuration/tpa/index.html>`_ for backend configuration.
2. Authn has a component for rendering Social Auth providers at frontend which goes through each provider. 2. Authn has a component for rendering Social Auth providers at frontend which goes through each provider.

View File

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

View File

@@ -1,15 +1,14 @@
const { createConfig } = require('@openedx/frontend-base/config'); const { createConfig } = require('@openedx/frontend-build');
module.exports = createConfig('test', { module.exports = createConfig('jest', {
setupFilesAfterEnv: [ setupFiles: [
'<rootDir>/src/setupTest.js', '<rootDir>/src/setupTest.js',
], ],
coveragePathIgnorePatterns: [ coveragePathIgnorePatterns: [
'src/setupTest.js', 'src/setupTest.js',
'src/i18n', 'src/i18n',
'src/index.jsx',
'MainApp.jsx',
], ],
moduleNameMapper: { testEnvironment: 'jsdom',
'\\.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',
},
}); });

8
openedx.yaml Normal file
View File

@@ -0,0 +1,8 @@
# This file describes this Open edX repo, as described in OEP-2:
# http://open-edx-proposals.readthedocs.io/en/latest/oeps/oep-0002.html#specification
nick: Authn MFE
oeps: {}
owner: openedx/vanguards
openedx-release:
ref: master

20613
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,28 @@
{ {
"name": "@openedx/frontend-app-authn", "name": "@edx/frontend-app-authn",
"version": "1.0.0-alpha.2", "version": "0.1.0",
"description": "Frontend authentication", "description": "Frontend application template",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/openedx/frontend-app-authn.git" "url": "git+https://github.com/openedx/frontend-app-authn.git"
}, },
"main": "src/index.ts",
"files": [
"/src"
],
"browserslist": [ "browserslist": [
"extends @edx/browserslist-config" "extends @edx/browserslist-config"
], ],
"sideEffects": [
"*.css",
"*.scss"
],
"scripts": { "scripts": {
"dev": "PORT=1999 PUBLIC_PATH=/authn openedx dev", "build": "fedx-scripts webpack",
"i18n_extract": "openedx formatjs extract", "i18n_extract": "fedx-scripts formatjs extract",
"lint": "openedx lint .", "lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"lint:fix": "openedx lint --fix .", "snapshot": "fedx-scripts jest --updateSnapshot",
"snapshot": "openedx test --updateSnapshot", "start": "fedx-scripts webpack-dev-server --progress",
"test": "openedx test --coverage --passWithNoTests" "test": "fedx-scripts jest --coverage --passWithNoTests"
}, },
"author": "Open edX", "husky": {
"hooks": {
"pre-commit": "npm run lint"
}
},
"author": "edX",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"homepage": "https://github.com/openedx/frontend-app-authn#readme", "homepage": "https://github.com/openedx/frontend-app-authn#readme",
"publishConfig": { "publishConfig": {
@@ -35,45 +32,54 @@
"url": "https://github.com/openedx/frontend-app-authn/issues" "url": "https://github.com/openedx/frontend-app-authn/issues"
}, },
"dependencies": { "dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.3", "@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/openedx-atlas": "^0.7.0", "@edx/frontend-platform": "7.0.1",
"@fortawesome/fontawesome-svg-core": "^6.7.2", "@edx/openedx-atlas": "^0.6.0",
"@fortawesome/free-brands-svg-icons": "^6.7.2", "@fortawesome/fontawesome-svg-core": "6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/free-brands-svg-icons": "6.5.1",
"@fortawesome/react-fontawesome": "^0.2.2", "@fortawesome/free-solid-svg-icons": "6.5.1",
"@redux-devtools/extension": "^3.3.0", "@fortawesome/react-fontawesome": "0.2.0",
"classnames": "^2.5.1", "@openedx/paragon": "^22.1.1",
"fastest-levenshtein": "^1.0.16", "@optimizely/react-sdk": "^2.9.1",
"form-urlencoded": "^6.1.5", "@redux-devtools/extension": "3.3.0",
"i18n-iso-countries": "^7.13.0", "@testing-library/react": "^12.1.5",
"prop-types": "^15.8.1", "@testing-library/react-hooks": "^8.0.1",
"query-string": "^7.1.3", "algoliasearch": "^4.14.3",
"react-helmet": "^6.1.0", "algoliasearch-helper": "^3.14.0",
"react-loading-skeleton": "^3.5.0", "classnames": "2.5.1",
"react-responsive": "^8.2.0", "core-js": "3.35.1",
"redux-logger": "^3.0.6", "fastest-levenshtein": "1.0.16",
"redux-mock-store": "^1.5.5", "form-urlencoded": "6.1.4",
"redux-saga": "^1.3.0", "prop-types": "15.8.1",
"redux-thunk": "^2.4.2", "query-string": "7.1.3",
"reselect": "^5.1.1", "react": "^17.0.2",
"universal-cookie": "^8.0.1" "react-dom": "^17.0.2",
"react-helmet": "6.1.0",
"react-loading-skeleton": "3.3.1",
"react-redux": "7.2.9",
"react-responsive": "8.2.0",
"react-router": "6.21.3",
"react-router-dom": "6.21.3",
"react-zendesk": "^0.1.13",
"redux": "4.2.0",
"redux-logger": "3.0.6",
"redux-mock-store": "1.5.4",
"redux-saga": "1.3.0",
"redux-thunk": "2.4.2",
"regenerator-runtime": "0.14.1",
"reselect": "4.1.8",
"universal-cookie": "4.0.4"
}, },
"devDependencies": { "devDependencies": {
"@edx/browserslist-config": "^1.5.0", "@edx/browserslist-config": "^1.1.1",
"@testing-library/react": "^16.3.0", "@edx/reactifex": "1.1.0",
"babel-plugin-formatjs": "10.5.38", "@openedx/frontend-build": "13.0.28",
"eslint-plugin-import": "2.31.0", "babel-plugin-formatjs": "10.5.13",
"jest": "^29.7.0", "eslint-plugin-import": "2.29.1",
"react-test-renderer": "^18.3.1" "glob": "7.2.3",
}, "history": "5.3.0",
"peerDependencies": { "husky": "7.0.4",
"@openedx/frontend-base": "^1.0.0-alpha.2", "jest": "29.7.0",
"@openedx/paragon": "^22", "react-test-renderer": "^17.0.2"
"react": "^18",
"react-dom": "^18",
"react-redux": "^8",
"react-router": "^6",
"react-router-dom": "^6",
"redux": "^4"
} }
} }

View File

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

View File

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

View File

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

View File

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

63
src/MainApp.jsx Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
const IMAGE_LAYOUT = 'image-layout';
const DEFAULT_LAYOUT = 'default-layout';
export { DEFAULT_LAYOUT, IMAGE_LAYOUT };

View File

@@ -1,4 +1,6 @@
import { useAppConfig } from '@openedx/frontend-base'; import React, { useEffect, useState } from 'react';
import { getConfig } from '@edx/frontend-platform';
import { breakpoints } from '@openedx/paragon'; import { breakpoints } from '@openedx/paragon';
import classNames from 'classnames'; import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -9,11 +11,28 @@ import {
ImageExtraSmallLayout, ImageLargeLayout, ImageMediumLayout, ImageSmallLayout, ImageExtraSmallLayout, ImageLargeLayout, ImageMediumLayout, ImageSmallLayout,
} from './components/image-layout'; } from './components/image-layout';
import { AuthLargeLayout, AuthMediumLayout, AuthSmallLayout } from './components/welcome-page-layout'; import { AuthLargeLayout, AuthMediumLayout, AuthSmallLayout } from './components/welcome-page-layout';
import { DEFAULT_LAYOUT, IMAGE_LAYOUT } from './data/constants';
const BaseContainer = ({ children, showWelcomeBanner, fullName }) => { const BaseContainer = ({ children, showWelcomeBanner, fullName }) => {
const enableImageLayout = useAppConfig().ENABLE_IMAGE_LAYOUT; const [baseContainerVersion, setBaseContainerVersion] = useState(DEFAULT_LAYOUT);
const enableImageLayout = getConfig().ENABLE_IMAGE_LAYOUT;
if (enableImageLayout) { useEffect(() => {
const initRebrandExperiment = () => {
if (window.experiments?.rebrandExperiment) {
setBaseContainerVersion(window.experiments?.rebrandExperiment?.variation);
} else {
window.experiments = window.experiments || {};
window.experiments.rebrandExperiment = {};
window.experiments.rebrandExperiment.handleLoaded = () => {
setBaseContainerVersion(window.experiments?.rebrandExperiment?.variation);
};
}
};
initRebrandExperiment();
}, []);
if (baseContainerVersion === IMAGE_LAYOUT || enableImageLayout) {
return ( return (
<div className="layout"> <div className="layout">
<MediaQuery maxWidth={breakpoints.extraSmall.maxWidth - 1}> <MediaQuery maxWidth={breakpoints.extraSmall.maxWidth - 1}>

View File

@@ -1,9 +1,11 @@
import { IntlProvider, mergeAppConfig } from '@openedx/frontend-base'; import React from 'react';
import { mergeConfig } from '@edx/frontend-platform';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { Context as ResponsiveContext } from 'react-responsive'; import { Context as ResponsiveContext } from 'react-responsive';
import BaseContainer from '../index'; import BaseContainer from '../index';
import { appId } from '../../constants';
const LargeScreen = { const LargeScreen = {
wrappingComponent: ResponsiveContext.Provider, wrappingComponent: ResponsiveContext.Provider,
@@ -14,9 +16,7 @@ describe('Base component tests', () => {
it('should show default layout', () => { it('should show default layout', () => {
const { container } = render( const { container } = render(
<IntlProvider locale="en"> <IntlProvider locale="en">
<BaseContainer> <BaseContainer />
<div>Test Content</div>
</BaseContainer>
</IntlProvider>, </IntlProvider>,
LargeScreen, LargeScreen,
); );
@@ -25,16 +25,31 @@ describe('Base component tests', () => {
expect(container.querySelector('.large-screen-svg-primary')).toBeDefined(); expect(container.querySelector('.large-screen-svg-primary')).toBeDefined();
}); });
it('[experiment] should show image layout for treatment group', () => {
window.experiments = {
rebrandExperiment: {
variation: 'image-layout',
},
};
const { container } = render(
<IntlProvider locale="en">
<BaseContainer />
</IntlProvider>,
LargeScreen,
);
expect(container.querySelector('.banner__image')).toBeDefined();
});
it('renders Image layout when ENABLE_IMAGE_LAYOUT configuration is enabled', () => { it('renders Image layout when ENABLE_IMAGE_LAYOUT configuration is enabled', () => {
mergeAppConfig(appId, { mergeConfig({
ENABLE_IMAGE_LAYOUT: true, ENABLE_IMAGE_LAYOUT: true,
}); });
const { container } = render( const { container } = render(
<IntlProvider locale="en"> <IntlProvider locale="en">
<BaseContainer showWelcomeBanner={false}> <BaseContainer showWelcomeBanner={false} />
<div>Test Content</div>
</BaseContainer>
</IntlProvider>, </IntlProvider>,
LargeScreen, LargeScreen,
); );

View File

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

View File

@@ -1,5 +1,8 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base';
import { import {
Button, Form, Button, Form,
Icon, Icon,
@@ -7,8 +10,8 @@ import {
import { Login } from '@openedx/paragon/icons'; import { Login } from '@openedx/paragon/icons';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
import messages from './messages'; import messages from './messages';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
/** /**
* This component renders the Single sign-on (SSO) button only for the tpa provider passed * This component renders the Single sign-on (SSO) button only for the tpa provider passed
@@ -16,12 +19,12 @@ import messages from './messages';
const EnterpriseSSO = (props) => { const EnterpriseSSO = (props) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const tpaProvider = props.provider; const tpaProvider = props.provider;
const hideRegistrationLink = useAppConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false const hideRegistrationLink = getConfig().ALLOW_PUBLIC_ACCOUNT_CREATION === false
|| useAppConfig().SHOW_REGISTRATION_LINKS === false; || getConfig().SHOW_REGISTRATION_LINKS === false;
const handleSubmit = (e, url) => { const handleSubmit = (e, url) => {
e.preventDefault(); e.preventDefault();
window.location.href = getSiteConfig().lmsBaseUrl + url; window.location.href = getConfig().LMS_BASE_URL + url;
}; };
const handleClick = (e) => { const handleClick = (e) => {
@@ -47,7 +50,7 @@ const EnterpriseSSO = (props) => {
{tpaProvider.iconImage ? ( {tpaProvider.iconImage ? (
<div aria-hidden="true"> <div aria-hidden="true">
<img className="btn-tpa__image-icon" src={tpaProvider.iconImage} alt={`icon ${tpaProvider.name}`} /> <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> </div>
) )
: ( : (
@@ -59,7 +62,7 @@ const EnterpriseSSO = (props) => {
<Icon className="h-75" src={Login} /> <Icon className="h-75" src={Login} />
)} )}
</div> </div>
<span className="pl-2" aria-hidden="true">{tpaProvider.name}</span> <span className="pl-2" aria-hidden="true">{ tpaProvider.name }</span>
</> </>
)} )}
</Button> </Button>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,18 @@
import { useAppConfig, getSiteConfig, useIntl } from '@openedx/frontend-base'; import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { import {
Hyperlink, Icon, Hyperlink, Icon,
} from '@openedx/paragon'; } from '@openedx/paragon';
import { Institution } from '@openedx/paragon/icons'; import { Institution } from '@openedx/paragon/icons';
import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Skeleton from 'react-loading-skeleton'; import Skeleton from 'react-loading-skeleton';
import messages from './messages';
import { import {
ENTERPRISE_LOGIN_URL, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE, ENTERPRISE_LOGIN_URL, LOGIN_PAGE, PENDING_STATE, REGISTER_PAGE,
} from '../data/constants'; } from '../data/constants';
import messages from './messages';
import { import {
RenderInstitutionButton, RenderInstitutionButton,
@@ -32,9 +34,8 @@ const ThirdPartyAuth = (props) => {
} = props; } = props;
const isInstitutionAuthActive = !!secondaryProviders.length && !currentProvider; const isInstitutionAuthActive = !!secondaryProviders.length && !currentProvider;
const isSocialAuthActive = !!providers.length && !currentProvider; const isSocialAuthActive = !!providers.length && !currentProvider;
const isEnterpriseLoginDisabled = useAppConfig().DISABLE_ENTERPRISE_LOGIN; const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN;
const enterpriseLoginURL = getSiteConfig().lmsBaseUrl + ENTERPRISE_LOGIN_URL; const enterpriseLoginURL = getConfig().LMS_BASE_URL + ENTERPRISE_LOGIN_URL;
const isThirdPartyAuthActive = isSocialAuthActive || (isEnterpriseLoginDisabled && isInstitutionAuthActive);
return ( return (
<> <>
@@ -46,23 +47,14 @@ const ThirdPartyAuth = (props) => {
</div> </div>
)} )}
{(isLoginPage && !isEnterpriseLoginDisabled && isSocialAuthActive) && ( {(isLoginPage && !isEnterpriseLoginDisabled && isSocialAuthActive) && (
<Hyperlink <Hyperlink className="btn btn-link btn-sm text-body p-0 mb-4" destination={enterpriseLoginURL}>
className={classNames(
'btn btn-link btn-sm text-body p-0',
{ 'mb-0': thirdPartyAuthApiStatus === PENDING_STATE },
{ 'mb-4': thirdPartyAuthApiStatus !== PENDING_STATE },
)}
destination={enterpriseLoginURL}
>
<Icon src={Institution} className="institute-icon" /> <Icon src={Institution} className="institute-icon" />
{formatMessage(messages['enterprise.login.btn.text'])} {formatMessage(messages['enterprise.login.btn.text'])}
</Hyperlink> </Hyperlink>
)} )}
{thirdPartyAuthApiStatus === PENDING_STATE && isThirdPartyAuthActive ? ( {thirdPartyAuthApiStatus === PENDING_STATE ? (
<div className="mt-4"> <Skeleton className="tpa-skeleton" height={36} count={2} />
<Skeleton className="tpa-skeleton" height={36} count={2} />
</div>
) : ( ) : (
<> <>
{(isEnterpriseLoginDisabled && isInstitutionAuthActive) && ( {(isEnterpriseLoginDisabled && isInstitutionAuthActive) && (

View File

@@ -1,14 +1,17 @@
import { getSiteConfig, useIntl } from '@openedx/frontend-base'; import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Alert } from '@openedx/paragon'; import { Alert } from '@openedx/paragon';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
import messages from './messages'; import messages from './messages';
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
const ThirdPartyAuthAlert = (props) => { const ThirdPartyAuthAlert = (props) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { currentProvider, referrer } = props; const { currentProvider, referrer } = props;
const platformName = getSiteConfig().siteName; const platformName = getConfig().SITE_NAME;
let message; let message;
if (referrer === LOGIN_PAGE) { if (referrer === LOGIN_PAGE) {
@@ -27,7 +30,7 @@ const ThirdPartyAuthAlert = (props) => {
{referrer === REGISTER_PAGE ? ( {referrer === REGISTER_PAGE ? (
<Alert.Heading>{formatMessage(messages['tpa.alert.heading'])}</Alert.Heading> <Alert.Heading>{formatMessage(messages['tpa.alert.heading'])}</Alert.Heading>
) : null} ) : null}
<p>{message}</p> <p>{ message }</p>
</Alert> </Alert>
{referrer === REGISTER_PAGE ? ( {referrer === REGISTER_PAGE ? (
<h4 className="mt-4 mb-4">{formatMessage(messages['registration.using.tpa.form.heading'])}</h4> <h4 className="mt-4 mb-4">{formatMessage(messages['registration.using.tpa.form.heading'])}</h4>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@openedx/frontend-base'; import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({ const messages = defineMessages({
// institution login strings // institution login strings
@@ -85,23 +85,33 @@ const messages = defineMessages({
'login.third.party.auth.account.not.linked': { 'login.third.party.auth.account.not.linked': {
id: '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} ' defaultMessage: 'You have successfully signed into {currentProvider}, but your {currentProvider} '
+ 'account does not have a linked {platformName} account. To link your accounts, ' + 'account does not have a linked {platformName} account. To link your accounts, '
+ 'sign in now using your {platformName} password.', + 'sign in now using your {platformName} password.',
description: 'Message that appears on login page if user has successfully authenticated with social ' 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': { 'register.third.party.auth.account.not.linked': {
id: '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 ' 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 ' 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': { 'registration.using.tpa.form.heading': {
id: 'registration.using.tpa.form.heading', id: 'registration.using.tpa.form.heading',
defaultMessage: 'Finish creating your account', defaultMessage: 'Finish creating your account',
description: 'Heading that appears above form when user is trying to create account using social auth', description: 'Heading that appears above form when user is trying to create account using social auth',
}, },
supportTitle: {
id: 'zendesk.supportTitle',
description: 'Title for the support button',
defaultMessage: 'edX Support',
},
selectTicketForm: {
id: 'zendesk.selectTicketForm',
description: 'Select ticket form',
defaultMessage: 'Please choose your request type:',
},
'registration.other.options.heading': { 'registration.other.options.heading': {
id: 'registration.other.options.heading', id: 'registration.other.options.heading',
defaultMessage: 'Or register with:', defaultMessage: 'Or register with:',

View File

@@ -1,20 +1,22 @@
/* eslint-disable import/no-import-module-exports */ /* eslint-disable import/no-import-module-exports */
/* eslint-disable react/function-component-definition */ /* eslint-disable react/function-component-definition */
import React from 'react';
import { getSiteConfig } from '@openedx/frontend-base'; import { getConfig } from '@edx/frontend-platform';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { REGISTER_EMBEDDED_PAGE } from '../../data/constants';
import EmbeddedRegistrationRoute from '../EmbeddedRegistrationRoute';
import { import {
MemoryRouter, Route, BrowserRouter as Router, Routes, MemoryRouter, Route, BrowserRouter as Router, Routes,
} from 'react-router-dom'; } from 'react-router-dom';
import { PAGE_NOT_FOUND, REGISTER_EMBEDDED_PAGE } from '../../data/constants';
import EmbeddedRegistrationRoute from '../EmbeddedRegistrationRoute';
const RRD = require('react-router-dom'); const RRD = require('react-router-dom');
// Just render plain div with its children // Just render plain div with its children
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
RRD.BrowserRouter = ({ children }) => <div>{children}</div>; RRD.BrowserRouter = ({ children }) => <div>{ children }</div>;
module.exports = RRD; module.exports = RRD;
const TestApp = () => ( const TestApp = () => (
@@ -25,10 +27,6 @@ const TestApp = () => (
path={REGISTER_EMBEDDED_PAGE} path={REGISTER_EMBEDDED_PAGE}
element={<EmbeddedRegistrationRoute><span>Embedded Register Page</span></EmbeddedRegistrationRoute>} element={<EmbeddedRegistrationRoute><span>Embedded Register Page</span></EmbeddedRegistrationRoute>}
/> />
<Route
path={PAGE_NOT_FOUND}
element={<span>Page not found</span>}
/>
</Routes> </Routes>
</div> </div>
</Router> </Router>
@@ -47,30 +45,34 @@ describe('EmbeddedRegistrationRoute', () => {
it('should not render embedded register page if host query param is not available in the url', async () => { it('should not render embedded register page if host query param is not available in the url', async () => {
let embeddedRegistrationPage = null; let embeddedRegistrationPage = null;
await act(async () => { await act(async () => {
const { container } = await render(routerWrapper()); const { container } = await render(routerWrapper());
embeddedRegistrationPage = container; embeddedRegistrationPage = container;
}); });
const renderedPage = embeddedRegistrationPage.querySelector('span'); const spanElement = embeddedRegistrationPage.querySelector('span');
expect(renderedPage.textContent).toBe('Page not found');
expect(spanElement).toBeNull();
}); });
it('should render embedded register page if host query param is available in the url (embedded)', async () => { it('should render embedded register page if host query param is available in the url (embedded)', async () => {
delete window.location; delete window.location;
window.location = { window.location = {
href: getSiteConfig().baseUrl.concat(REGISTER_EMBEDDED_PAGE), href: getConfig().BASE_URL.concat(REGISTER_EMBEDDED_PAGE),
search: '?host=http://localhost/host-websit', search: '?host=http://localhost/host-websit',
}; };
let embeddedRegistrationPage = null; let embeddedRegistrationPage = null;
await act(async () => { await act(async () => {
const { container } = await render(routerWrapper()); const { container } = await render(routerWrapper());
embeddedRegistrationPage = container; embeddedRegistrationPage = container;
}); });
const renderedPage = embeddedRegistrationPage.querySelector('span'); const spanElement = embeddedRegistrationPage.querySelector('span');
expect(renderedPage).toBeTruthy();
expect(renderedPage.textContent).toBe('Embedded Register Page'); expect(spanElement).toBeTruthy();
expect(spanElement.textContent).toBe('Embedded Register Page');
}); });
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -66,14 +66,14 @@ exports[`SocialAuthProviders should match social auth provider with iconClass sn
data-prefix="fab" data-prefix="fab"
focusable="false" focusable="false"
role="img" role="img"
style={{}} style={Object {}}
viewBox="0 0 488 512" viewBox="0 0 488 512"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z" d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"
fill="currentColor" fill="currentColor"
style={{}} style={Object {}}
/> />
</svg> </svg>
</div> </div>
@@ -93,7 +93,7 @@ exports[`SocialAuthProviders should match social auth provider with iconClass sn
`; `;
exports[`SocialAuthProviders should match social auth provider with iconImage snapshot 1`] = ` exports[`SocialAuthProviders should match social auth provider with iconImage snapshot 1`] = `
[ Array [
<button <button
className="btn-social btn-oa2-apple-id mr-3" className="btn-social btn-oa2-apple-id mr-3"
data-provider-url="/auth/login/apple-id/?auth_entry=login&next=/dashboard" data-provider-url="/auth/login/apple-id/?auth_entry=login&next=/dashboard"

View File

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

View File

@@ -0,0 +1,65 @@
// 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={
Object {
"answerBot": Object {
"avatar": Object {
"name": Object {
"*": "edX Support",
},
"url": undefined,
},
"contactOnlyAfterQuery": true,
"suppress": false,
"title": Object {
"*": "edX Support",
},
},
"chat": Object {
"departments": Object {
"enabled": Array [
"account settings",
"billing and payments",
"certificates",
"deadlines",
"errors and technical issues",
"other",
"proctoring",
],
},
"suppress": false,
},
"contactForm": Object {
"attachments": true,
"selectTicketForm": Object {
"*": "Please choose your request type:",
},
"ticketForms": Array [
Object {
"fields": Array [
Object {
"id": "description",
"prefill": Object {
"*": "",
},
},
],
"id": 360003368814,
"subject": false,
},
],
},
"contactOptions": Object {
"enabled": false,
},
"helpCenter": Object {
"originalArticleButton": true,
},
}
}
/>
`;

39
src/config/index.js Normal file
View File

@@ -0,0 +1,39 @@
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_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;

View File

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

20
src/data/algolia.js Normal file
View File

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

View File

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

View File

@@ -5,6 +5,7 @@ export const REGISTER_EMBEDDED_PAGE = '/register-embedded';
export const RESET_PAGE = '/reset'; export const RESET_PAGE = '/reset';
export const AUTHN_PROGRESSIVE_PROFILING = '/welcome'; export const AUTHN_PROGRESSIVE_PROFILING = '/welcome';
export const DEFAULT_REDIRECT_URL = '/dashboard'; export const DEFAULT_REDIRECT_URL = '/dashboard';
export const RECOMMENDATIONS = '/recommendations';
export const PASSWORD_RESET_CONFIRM = '/password_reset_confirm/:token/'; export const PASSWORD_RESET_CONFIRM = '/password_reset_confirm/:token/';
export const PAGE_NOT_FOUND = '/notfound'; export const PAGE_NOT_FOUND = '/notfound';
export const ENTERPRISE_LOGIN_URL = '/enterprise/login'; export const ENTERPRISE_LOGIN_URL = '/enterprise/login';
@@ -28,9 +29,9 @@ export const EMBEDDED = 'embedded';
export const LETTER_REGEX = /[a-zA-Z]/; export const LETTER_REGEX = /[a-zA-Z]/;
export const NUMBER_REGEX = /\d/; export const NUMBER_REGEX = /\d/;
export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*' export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*'
+ '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"' + '|^"([\\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})' + ')@((?:[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}\\]$'; + '|\\[(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 // Query string parameters that can be passed to LMS to manage
// things like auto-enrollment upon login and registration. // things like auto-enrollment upon login and registration.

View File

@@ -1,59 +0,0 @@
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 }));
}

17
src/data/optimizely.js Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { import { mergeConfig } from '@edx/frontend-platform';
configureI18n, injectIntl, IntlProvider, mergeAppConfig import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
} from '@openedx/frontend-base';
import { import {
fireEvent, render, screen, fireEvent, render, screen,
} from '@testing-library/react'; } from '@testing-library/react';
@@ -11,22 +11,16 @@ import configureStore from 'redux-mock-store';
import { INTERNAL_SERVER_ERROR, LOGIN_PAGE } from '../../data/constants'; import { INTERNAL_SERVER_ERROR, LOGIN_PAGE } from '../../data/constants';
import { PASSWORD_RESET } from '../../reset-password/data/constants'; import { PASSWORD_RESET } from '../../reset-password/data/constants';
import { appId } from '../../constants';
import { setForgotPasswordFormData } from '../data/actions'; import { setForgotPasswordFormData } from '../data/actions';
import ForgotPasswordPage from '../ForgotPasswordPage'; import ForgotPasswordPage from '../ForgotPasswordPage';
const mockedNavigator = jest.fn(); const mockedNavigator = jest.fn();
jest.mock('@openedx/frontend-base', () => ({ jest.mock('@edx/frontend-platform/analytics', () => ({
...jest.requireActual('@openedx/frontend-base'),
sendPageEvent: jest.fn(), sendPageEvent: jest.fn(),
sendTrackEvent: jest.fn(), sendTrackEvent: jest.fn(),
getAuthenticatedUser: jest.fn(() => ({
userId: 3,
username: 'test-user',
})),
})); }));
jest.mock('@edx/frontend-platform/auth');
jest.mock('react-router-dom', () => ({ jest.mock('react-router-dom', () => ({
...(jest.requireActual('react-router-dom')), ...(jest.requireActual('react-router-dom')),
useNavigate: () => mockedNavigator, useNavigate: () => mockedNavigator,
@@ -42,7 +36,7 @@ const initialState = {
}; };
describe('ForgotPasswordPage', () => { describe('ForgotPasswordPage', () => {
mergeAppConfig(appId, { mergeConfig({
LOGIN_ISSUE_SUPPORT_LINK: '', LOGIN_ISSUE_SUPPORT_LINK: '',
INFO_EMAIL: '', INFO_EMAIL: '',
}); });
@@ -60,8 +54,18 @@ describe('ForgotPasswordPage', () => {
beforeEach(() => { beforeEach(() => {
store = mockStore(initialState); store = mockStore(initialState);
jest.mock('@edx/frontend-platform/auth', () => ({
configureI18n({ 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': {} }, messages: { 'es-419': {}, de: {}, 'en-us': {} },
}); });
props = { props = {
@@ -80,7 +84,7 @@ describe('ForgotPasswordPage', () => {
}); });
it('should display need other help signing in button', () => { it('should display need other help signing in button', () => {
mergeAppConfig(appId, { mergeConfig({
LOGIN_ISSUE_SUPPORT_LINK: '/support', LOGIN_ISSUE_SUPPORT_LINK: '/support',
}); });
render(reduxWrapper(<IntlForgotPasswordPage {...props} />)); render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
@@ -109,7 +113,7 @@ describe('ForgotPasswordPage', () => {
forgotPassword: { status: INTERNAL_SERVER_ERROR }, forgotPassword: { status: INTERNAL_SERVER_ERROR },
}); });
const expectedMessage = 'We were unable to contact you.' const expectedMessage = 'We were unable to contact you.'
+ 'An error has occurred. Try refreshing the page, or check your internet connection.'; + 'An error has occurred. Try refreshing the page, or check your internet connection.';
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />)); const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
@@ -229,8 +233,8 @@ describe('ForgotPasswordPage', () => {
}); });
const successMessage = 'Check your emailWe sent an email to with instructions to reset your password. If you do not ' const successMessage = 'Check your emailWe sent an email to with instructions to reset your password. If you do not '
+ 'receive a password reset message after 1 minute, verify that you entered the correct email address,' + 'receive a password reset message after 1 minute, verify that you entered the correct email address,'
+ ' or check your spam folder. If you need further assistance, contact technical support.'; + ' or check your spam folder. If you need further assistance, contact technical support.';
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />)); const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
const successElement = findByTextContent(container, successMessage); const successElement = findByTextContent(container, successMessage);
@@ -247,8 +251,8 @@ describe('ForgotPasswordPage', () => {
}, },
}); });
const successMessage = 'Invalid password reset link' const successMessage = 'Invalid password reset link'
+ 'This password reset link is invalid. It may have been used already. ' + 'This password reset link is invalid. It may have been used already. '
+ 'Enter your email below to receive a new link.'; + 'Enter your email below to receive a new link.';
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />)); const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
const successElement = findByTextContent(container, successMessage); const successElement = findByTextContent(container, successMessage);

View File

@@ -1,25 +0,0 @@
// 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: {},
};

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