Compare commits
15 Commits
master
...
v1.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f4bf0a13e | ||
|
|
6bed0308bd | ||
|
|
057b925589 | ||
|
|
6202f7bb54 | ||
|
|
d0c27f4377 | ||
|
|
b2c6ec2dc9 | ||
|
|
8175d7e2f6 | ||
|
|
d0051d0a7d | ||
|
|
c621d581bd | ||
|
|
268ccc864d | ||
|
|
2045854099 | ||
|
|
9b439d7d74 | ||
|
|
b8f4d49a55 | ||
|
|
1d29810f6c | ||
|
|
89559a4987 |
@@ -1,10 +0,0 @@
|
|||||||
node_modules
|
|
||||||
npm-debug.log
|
|
||||||
README.md
|
|
||||||
LICENSE
|
|
||||||
.babelrc
|
|
||||||
.eslintignore
|
|
||||||
.eslintrc.json
|
|
||||||
.gitignore
|
|
||||||
.npmignore
|
|
||||||
commitlint.config.js
|
|
||||||
44
.env
44
.env
@@ -1,44 +0,0 @@
|
|||||||
NODE_ENV='production'
|
|
||||||
NODE_PATH=./src
|
|
||||||
BASE_URL=''
|
|
||||||
LMS_BASE_URL=''
|
|
||||||
ECOMMERCE_BASE_URL=''
|
|
||||||
LOGIN_URL=''
|
|
||||||
LOGOUT_URL=''
|
|
||||||
CSRF_TOKEN_API_PATH=''
|
|
||||||
REFRESH_ACCESS_TOKEN_ENDPOINT=''
|
|
||||||
DATA_API_BASE_URL=''
|
|
||||||
SEGMENT_KEY=''
|
|
||||||
FEATURE_FLAGS={}
|
|
||||||
ACCESS_TOKEN_COOKIE_NAME=''
|
|
||||||
NEW_RELIC_APP_ID=''
|
|
||||||
NEW_RELIC_LICENSE_KEY=''
|
|
||||||
SITE_NAME=''
|
|
||||||
MARKETING_SITE_BASE_URL=''
|
|
||||||
SUPPORT_URL=''
|
|
||||||
CONTACT_URL=''
|
|
||||||
OPEN_SOURCE_URL=''
|
|
||||||
TERMS_OF_SERVICE_URL=''
|
|
||||||
PRIVACY_POLICY_URL=''
|
|
||||||
FACEBOOK_URL=''
|
|
||||||
TWITTER_URL=''
|
|
||||||
YOU_TUBE_URL=''
|
|
||||||
LINKED_IN_URL=''
|
|
||||||
REDDIT_URL=''
|
|
||||||
APPLE_APP_STORE_URL=''
|
|
||||||
GOOGLE_PLAY_URL=''
|
|
||||||
ENTERPRISE_MARKETING_URL=''
|
|
||||||
ENTERPRISE_MARKETING_UTM_SOURCE=''
|
|
||||||
ENTERPRISE_MARKETING_UTM_CAMPAIGN=''
|
|
||||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM=''
|
|
||||||
LEARNING_BASE_URL=''
|
|
||||||
HOTJAR_APP_ID=''
|
|
||||||
HOTJAR_VERSION='6'
|
|
||||||
HOTJAR_DEBUG=''
|
|
||||||
ACCOUNT_SETTINGS_URL=''
|
|
||||||
ACCOUNT_PROFILE_URL=''
|
|
||||||
ENABLE_NOTICES=''
|
|
||||||
CAREER_LINK_URL=''
|
|
||||||
ENABLE_EDX_PERSONAL_DASHBOARD=false
|
|
||||||
ENABLE_PROGRAMS=false
|
|
||||||
NON_BROWSABLE_COURSES=false
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
NODE_ENV='development'
|
|
||||||
PORT=1996
|
|
||||||
BASE_URL='localhost:1996'
|
|
||||||
LMS_BASE_URL='http://localhost:18000'
|
|
||||||
ECOMMERCE_BASE_URL='http://localhost:18130'
|
|
||||||
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
|
|
||||||
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
|
||||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
|
||||||
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
|
||||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
|
||||||
SITE_NAME=localhost
|
|
||||||
DATA_API_BASE_URL='http://localhost:8000'
|
|
||||||
// LMS_CLIENT_ID should match the lms DOT client application id your LMS containe
|
|
||||||
LMS_CLIENT_ID='login-service-client-id'
|
|
||||||
SEGMENT_KEY=''
|
|
||||||
FEATURE_FLAGS={}
|
|
||||||
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
|
||||||
SUPPORT_URL=''
|
|
||||||
CONTACT_URL='http://localhost:18000/contact'
|
|
||||||
OPEN_SOURCE_URL='http://localhost:18000/openedx'
|
|
||||||
TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service'
|
|
||||||
PRIVACY_POLICY_URL='http://localhost:18000/privacy-policy'
|
|
||||||
FACEBOOK_URL='https://www.facebook.com'
|
|
||||||
TWITTER_URL='https://twitter.com'
|
|
||||||
YOU_TUBE_URL='https://www.youtube.com'
|
|
||||||
LINKED_IN_URL='https://www.linkedin.com'
|
|
||||||
REDDIT_URL='https://www.reddit.com'
|
|
||||||
APPLE_APP_STORE_URL='https://www.apple.com/ios/app-store/'
|
|
||||||
GOOGLE_PLAY_URL='https://play.google.com/store'
|
|
||||||
ENTERPRISE_MARKETING_URL='http://example.com'
|
|
||||||
ENTERPRISE_MARKETING_UTM_SOURCE='example.com'
|
|
||||||
ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral'
|
|
||||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
|
|
||||||
LEARNING_BASE_URL='http://localhost:2000'
|
|
||||||
SESSION_COOKIE_DOMAIN='localhost'
|
|
||||||
HOTJAR_APP_ID=''
|
|
||||||
HOTJAR_VERSION='6'
|
|
||||||
HOTJAR_DEBUG=''
|
|
||||||
ACCOUNT_SETTINGS_URL='http://localhost:1997'
|
|
||||||
ACCOUNT_PROFILE_URL='http://localhost:1995'
|
|
||||||
ENABLE_NOTICES=''
|
|
||||||
CAREER_LINK_URL=''
|
|
||||||
ENABLE_EDX_PERSONAL_DASHBOARD=false
|
|
||||||
ENABLE_PROGRAMS=false
|
|
||||||
NON_BROWSABLE_COURSES=false
|
|
||||||
49
.env.test
49
.env.test
@@ -1,49 +0,0 @@
|
|||||||
NODE_ENV='test'
|
|
||||||
PORT=1996
|
|
||||||
BASE_URL='localhost:1996'
|
|
||||||
LMS_BASE_URL='http://localhost:18000'
|
|
||||||
ECOMMERCE_BASE_URL='http://localhost:18130'
|
|
||||||
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
|
|
||||||
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
|
||||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
|
||||||
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
|
||||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
|
||||||
SITE_NAME=localhost
|
|
||||||
DATA_API_BASE_URL='http://localhost:8000'
|
|
||||||
// LMS_CLIENT_ID should match the lms DOT client application id your LMS containe
|
|
||||||
LMS_CLIENT_ID='login-service-client-id'
|
|
||||||
SEGMENT_KEY=''
|
|
||||||
FEATURE_FLAGS={}
|
|
||||||
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
|
||||||
SUPPORT_URL=''
|
|
||||||
CONTACT_URL='http://localhost:18000/contact'
|
|
||||||
OPEN_SOURCE_URL='http://localhost:18000/openedx'
|
|
||||||
TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service'
|
|
||||||
PRIVACY_POLICY_URL='http://localhost:18000/privacy-policy'
|
|
||||||
FACEBOOK_URL='https://www.facebook.com'
|
|
||||||
TWITTER_URL='https://twitter.com'
|
|
||||||
YOU_TUBE_URL='https://www.youtube.com'
|
|
||||||
LINKED_IN_URL='https://www.linkedin.com'
|
|
||||||
REDDIT_URL='https://www.reddit.com'
|
|
||||||
APPLE_APP_STORE_URL='https://www.apple.com/ios/app-store/'
|
|
||||||
GOOGLE_PLAY_URL='https://play.google.com/store'
|
|
||||||
ENTERPRISE_MARKETING_URL='http://example.com'
|
|
||||||
ENTERPRISE_MARKETING_UTM_SOURCE='example.com'
|
|
||||||
ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral'
|
|
||||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
|
|
||||||
LEARNING_BASE_URL='http://localhost:2000'
|
|
||||||
HOTJAR_APP_ID='hot-jar-app-id'
|
|
||||||
HOTJAR_VERSION='6'
|
|
||||||
HOTJAR_DEBUG=''
|
|
||||||
ACCOUNT_SETTINGS_URL='http://account-settings-url.test'
|
|
||||||
ACCOUNT_PROFILE_URL='http://account-profile-url.test'
|
|
||||||
ENABLE_NOTICES=''
|
|
||||||
CAREER_LINK_URL=''
|
|
||||||
ENABLE_EDX_PERSONAL_DASHBOARD=true
|
|
||||||
ENABLE_PROGRAMS=false
|
|
||||||
NON_BROWSABLE_COURSES=false
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
coverage/*
|
|
||||||
dist/
|
|
||||||
node_modules/
|
|
||||||
src/postcss.config.js
|
|
||||||
src/segment.js
|
|
||||||
22
.eslintrc.js
22
.eslintrc.js
@@ -1,22 +0,0 @@
|
|||||||
const { createConfig } = require('@openedx/frontend-build');
|
|
||||||
|
|
||||||
const config = createConfig('eslint', {
|
|
||||||
rules: {
|
|
||||||
'import/no-named-as-default': 'off',
|
|
||||||
'import/no-named-as-default-member': 'off',
|
|
||||||
'import/no-self-import': 'off',
|
|
||||||
'import/no-import-module-exports': 'off',
|
|
||||||
'spaced-comment': ['error', 'always', { 'block': { 'exceptions': ['*'] } }],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
config.settings = {
|
|
||||||
"import/resolver": {
|
|
||||||
node: {
|
|
||||||
paths: ["src", "node_modules"],
|
|
||||||
extensions: [".js", ".jsx"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = config;
|
|
||||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1 +0,0 @@
|
|||||||
*.snap linguist-generated=false
|
|
||||||
32
.gitignore
vendored
32
.gitignore
vendored
@@ -1,29 +1,15 @@
|
|||||||
.DS_Store
|
|
||||||
.eslintcache
|
|
||||||
env.config.*
|
|
||||||
node_modules
|
node_modules
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
coverage
|
coverage
|
||||||
|
|
||||||
dist/
|
|
||||||
public/samples/
|
|
||||||
|
|
||||||
### pyenv ###
|
|
||||||
.python-version
|
|
||||||
|
|
||||||
### Emacs ###
|
|
||||||
*~
|
|
||||||
*.swo
|
|
||||||
*.swp
|
|
||||||
|
|
||||||
### Development environments ###
|
|
||||||
.idea
|
|
||||||
.vscode
|
|
||||||
|
|
||||||
# Local package dependencies
|
|
||||||
module.config.js
|
module.config.js
|
||||||
|
dist/
|
||||||
|
/*.tgz
|
||||||
|
|
||||||
### transifex ###
|
### i18n ###
|
||||||
src/i18n/transifex_input.json
|
src/i18n/transifex_input.json
|
||||||
temp
|
|
||||||
src/i18n/messages
|
### Editors ###
|
||||||
|
.DS_Store
|
||||||
|
*~
|
||||||
|
/temp
|
||||||
|
/.vscode
|
||||||
|
|||||||
16
.npmignore
16
.npmignore
@@ -1,12 +1,6 @@
|
|||||||
.eslintignore
|
__mocks__
|
||||||
.eslintrc.json
|
|
||||||
.gitignore
|
|
||||||
docker-compose.yml
|
|
||||||
Dockerfile
|
|
||||||
Makefile
|
|
||||||
npm-debug.log
|
|
||||||
|
|
||||||
config
|
|
||||||
coverage
|
|
||||||
node_modules
|
node_modules
|
||||||
public
|
*.test.js
|
||||||
|
*.test.jsx
|
||||||
|
*.test.ts
|
||||||
|
*.test.tsx
|
||||||
|
|||||||
5
Makefile
5
Makefile
@@ -45,12 +45,11 @@ pull_translations:
|
|||||||
mkdir src/i18n/messages
|
mkdir src/i18n/messages
|
||||||
cd src/i18n/messages \
|
cd src/i18n/messages \
|
||||||
&& atlas pull $(ATLAS_OPTIONS) \
|
&& atlas pull $(ATLAS_OPTIONS) \
|
||||||
translations/frontend-platform/src/i18n/messages:frontend-platform \
|
translations/frontend-base/src/i18n/messages:frontend-base \
|
||||||
translations/paragon/src/i18n/messages:paragon \
|
translations/paragon/src/i18n/messages:paragon \
|
||||||
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
|
|
||||||
translations/frontend-app-learner-dashboard/src/i18n/messages:frontend-app-learner-dashboard
|
translations/frontend-app-learner-dashboard/src/i18n/messages:frontend-app-learner-dashboard
|
||||||
|
|
||||||
$(intl_imports) frontend-platform paragon frontend-component-footer frontend-app-learner-dashboard
|
$(intl_imports) frontend-base paragon frontend-app-learner-dashboard
|
||||||
|
|
||||||
# This target is used by CI.
|
# This target is used by CI.
|
||||||
validate-no-uncommitted-package-lock-changes:
|
validate-no-uncommitted-package-lock-changes:
|
||||||
|
|||||||
29
README.rst
29
README.rst
@@ -18,7 +18,7 @@ frontend-app-learner-dashboard
|
|||||||
The Learner Home app is a microfrontend (MFE) course listing experience for the Open edX Learning Management System
|
The Learner Home app is a microfrontend (MFE) course listing experience for the Open edX Learning Management System
|
||||||
(LMS). This experience was designed to provide a clean and functional interface to allow learners to view all of their
|
(LMS). This experience was designed to provide a clean and functional interface to allow learners to view all of their
|
||||||
open enrollments, as well as take relevant actions on those enrollments. It also serves as host to a number of exposed
|
open enrollments, as well as take relevant actions on those enrollments. It also serves as host to a number of exposed
|
||||||
"widget" containers to provide upsell and discovery widgets as sidebar/footer components.
|
"widget" containers to provide upsell and discovery widgets as sidebar components.
|
||||||
|
|
||||||
Quickstart
|
Quickstart
|
||||||
----------
|
----------
|
||||||
@@ -30,31 +30,10 @@ To start the MFE and enable the feature in LMS:
|
|||||||
From there, simply load the configured address/port. You should be prompted to log into your LMS if you are not
|
From there, simply load the configured address/port. You should be prompted to log into your LMS if you are not
|
||||||
already, and then redirected to your home page.
|
already, and then redirected to your home page.
|
||||||
|
|
||||||
Plugins
|
Widgets
|
||||||
-------
|
-------
|
||||||
This MFE can be customized using `Frontend Plugin Framework <https://github.com/openedx/frontend-plugin-framework>`_.
|
This MFE can be customized with widgets. The parts of this MFE that can be customized in that manner are documented
|
||||||
|
`here </src/slots>`_.
|
||||||
The parts of this MFE that can be customized in that manner are documented `here </src/plugin-slots>`_.
|
|
||||||
|
|
||||||
Contributing
|
|
||||||
------------
|
|
||||||
|
|
||||||
A core goal of this app is to provide a clean experimentation interface. To promote this end, we have provided a
|
|
||||||
silo'ed code directory at ``src/widgets`` in which contributors should add their custom widget components. In order to
|
|
||||||
ensure our ability to maintain the code stability of the app, the code for these widgets should be strictly contained
|
|
||||||
within the bounds of that directory.
|
|
||||||
|
|
||||||
Once written, the widgets can be configured into one of our widget containers at ``src/containers/WidgetContainers``.
|
|
||||||
This can include conditional logic, as well as Optimizely triggers. It is important to note that our integration tests
|
|
||||||
will isolate and ignore these containers, and thus testing your widget is the response of the creator/maintainer of the
|
|
||||||
widget itself.
|
|
||||||
|
|
||||||
Some guidelines for writing widgets:
|
|
||||||
|
|
||||||
* Code for the widget should be strictly confined to the ``src/widgets`` directory.
|
|
||||||
* You can load data from the redux store, but should not add or modify fields in that structure.
|
|
||||||
* Network events should be managed in component hooks, though can use our ``data/constants/requests:requestStates`` for
|
|
||||||
ease of tracking the request states.
|
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|||||||
10
app.d.ts
vendored
Normal file
10
app.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/// <reference types="@openedx/frontend-base" />
|
||||||
|
|
||||||
|
declare module 'site.config' {
|
||||||
|
export default SiteConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.svg' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
3
babel.config.js
Normal file
3
babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
const { createConfig } = require('@openedx/frontend-base/config');
|
||||||
|
|
||||||
|
module.exports = createConfig('babel');
|
||||||
22
eslint.config.js
Normal file
22
eslint.config.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const { createLintConfig } = require('@openedx/frontend-base/config');
|
||||||
|
|
||||||
|
module.exports = createLintConfig(
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
'src/**/*',
|
||||||
|
'site.config.*',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
'coverage/*',
|
||||||
|
'dist/*',
|
||||||
|
'documentation/*',
|
||||||
|
'node_modules/*',
|
||||||
|
'**/__mocks__/*',
|
||||||
|
'**/__snapshots__/*',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
Learner Dashboard is now able to handle JS-based configuration!
|
|
||||||
|
|
||||||
For the time being, the `.env.*` files are still made available when cloning down this repo or pulling from
|
|
||||||
the master branch. To switch to using `env.config.js`, make a copy of `example.env.config.js` and configure as needed.
|
|
||||||
|
|
||||||
For testing with Jest Snapshot, there is a mock in `/src/setupTest.jsx` for `getConfig` that will need to be
|
|
||||||
uncommented.
|
|
||||||
|
|
||||||
Note: having both .env and env.config.js files will follow a predictable order, in which non-empty values in the
|
|
||||||
JS-based config will overwrite the .env environment variables.
|
|
||||||
|
|
||||||
frontend-platform's getConfig loads configuration in the following sequence:
|
|
||||||
- .env file config
|
|
||||||
- optional handlers (commonly used to merge MFE-specific config in via additional process.env variables)
|
|
||||||
- env.config.js file config
|
|
||||||
- runtime config
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
NODE_ENV: 'development',
|
|
||||||
NODE_PATH: './src',
|
|
||||||
PORT: 1996,
|
|
||||||
BASE_URL: 'localhost:1996',
|
|
||||||
LMS_BASE_URL: 'http://localhost:18000',
|
|
||||||
ECOMMERCE_BASE_URL: 'http://localhost:18130',
|
|
||||||
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',
|
|
||||||
CSRF_TOKEN_API_PATH: '/csrf/api/v1/token',
|
|
||||||
REFRESH_ACCESS_TOKEN_ENDPOINT: 'http://localhost:18000/login_refresh',
|
|
||||||
ACCESS_TOKEN_COOKIE_NAME: 'edx-jwt-cookie-header-payload',
|
|
||||||
USER_INFO_COOKIE_NAME: 'edx-user-info',
|
|
||||||
SITE_NAME: 'localhost',
|
|
||||||
DATA_API_BASE_URL: 'http://localhost:8000',
|
|
||||||
// LMS_CLIENT_ID should match the lms DOT client application in your LMS container
|
|
||||||
LMS_CLIENT_ID: 'login-service-client-id',
|
|
||||||
SEGMENT_KEY: '',
|
|
||||||
FEATURE_FLAGS: {},
|
|
||||||
MARKETING_SITE_BASE_URL: 'http://localhost:18000',
|
|
||||||
SUPPORT_URL: 'http://localhost:18000/support',
|
|
||||||
CONTACT_URL: 'http://localhost:18000/contact',
|
|
||||||
OPEN_SOURCE_URL: 'http://localhost:18000/openedx',
|
|
||||||
TERMS_OF_SERVICE_URL: 'http://localhost:18000/terms-of-service',
|
|
||||||
PRIVACY_POLICY_URL: 'http://localhost:18000/privacy-policy',
|
|
||||||
FACEBOOK_URL: 'https://www.facebook.com',
|
|
||||||
TWITTER_URL: 'https://twitter.com',
|
|
||||||
YOU_TUBE_URL: 'https://www.youtube.com',
|
|
||||||
LINKED_IN_URL: 'https://www.linkedin.com',
|
|
||||||
REDDIT_URL: 'https://www.reddit.com',
|
|
||||||
APPLE_APP_STORE_URL: 'https://www.apple.com/ios/app-store/',
|
|
||||||
GOOGLE_PLAY_URL: 'https://play.google.com/store',
|
|
||||||
ENTERPRISE_MARKETING_URL: 'http://example.com',
|
|
||||||
ENTERPRISE_MARKETING_UTM_SOURCE: 'example.com',
|
|
||||||
ENTERPRISE_MARKETING_UTM_CAMPAIGN: 'example.com Referral',
|
|
||||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM: 'Footer',
|
|
||||||
LEARNING_BASE_URL: 'http://localhost:2000',
|
|
||||||
SESSION_COOKIE_DOMAIN: 'localhost',
|
|
||||||
HOTJAR_APP_ID: '',
|
|
||||||
HOTJAR_VERSION: 6,
|
|
||||||
HOTJAR_DEBUG: '',
|
|
||||||
NEW_RELIC_APP_ID: '',
|
|
||||||
NEW_RELIC_LICENSE_KEY: '',
|
|
||||||
ACCOUNT_SETTINGS_URL: 'http://localhost:1997',
|
|
||||||
ACCOUNT_PROFILE_URL: 'http://localhost:1995',
|
|
||||||
ENABLE_NOTICES: '',
|
|
||||||
CAREER_LINK_URL: '',
|
|
||||||
EXPERIMENT_08_23_VAN_PAINTED_DOOR: true,
|
|
||||||
};
|
|
||||||
@@ -1,18 +1,21 @@
|
|||||||
const { createConfig } = require('@openedx/frontend-build');
|
const { createConfig } = require('@openedx/frontend-base/config');
|
||||||
|
|
||||||
module.exports = createConfig('jest', {
|
module.exports = createConfig('test', {
|
||||||
setupFilesAfterEnv: [
|
setupFilesAfterEnv: [
|
||||||
'jest-expect-message',
|
'jest-expect-message',
|
||||||
'<rootDir>/src/setupTest.jsx',
|
'<rootDir>/src/setupTest.jsx',
|
||||||
],
|
],
|
||||||
modulePaths: ['<rootDir>/src/'],
|
|
||||||
coveragePathIgnorePatterns: [
|
coveragePathIgnorePatterns: [
|
||||||
'src/segment.js',
|
'src/segment.js',
|
||||||
'src/postcss.config.js',
|
'src/postcss.config.js',
|
||||||
'testUtils', // don't unit test jest mocking tools
|
'testUtils', // don't unit test jest mocking tools
|
||||||
'src/data/services/lms/fakeData', // don't unit test mock data
|
'src/data/services/lms/fakeData', // don't unit test mock data
|
||||||
'src/test', // don't unit test integration test utils
|
'src/test', // don't unit test integration test utils
|
||||||
|
'src/__mocks__',
|
||||||
],
|
],
|
||||||
|
moduleNameMapper: {
|
||||||
|
'\\.svg$': '<rootDir>/src/__mocks__/svg.js',
|
||||||
|
'\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/__mocks__/file.js',
|
||||||
|
},
|
||||||
testTimeout: 120000,
|
testTimeout: 120000,
|
||||||
testEnvironment: 'jsdom',
|
|
||||||
});
|
});
|
||||||
|
|||||||
10259
package-lock.json
generated
10259
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
80
package.json
Executable file → Normal file
80
package.json
Executable file → Normal file
@@ -1,79 +1,64 @@
|
|||||||
{
|
{
|
||||||
"name": "@edx/frontend-app-learner-dashboard",
|
"name": "@openedx/frontend-app-learner-dashboard",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0-alpha.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/edx/frontend-app-learner-dashboard.git"
|
"url": "git+https://github.com/edx/frontend-app-learner-dashboard.git"
|
||||||
},
|
},
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"files": [
|
||||||
|
"/src"
|
||||||
|
],
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"extends @edx/browserslist-config"
|
"extends @edx/browserslist-config"
|
||||||
],
|
],
|
||||||
|
"sideEffects": [
|
||||||
|
"*.css",
|
||||||
|
"*.scss"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "fedx-scripts webpack",
|
"dev": "PORT=1996 PUBLIC_PATH=/learner-dashboard openedx dev",
|
||||||
"i18n_extract": "fedx-scripts formatjs extract",
|
"i18n_extract": "openedx formatjs extract",
|
||||||
"lint": "fedx-scripts eslint --ext .jsx,.js src/",
|
"lint": "openedx lint .",
|
||||||
"lint-fix": "fedx-scripts eslint --fix --ext .jsx,.js src/",
|
"lint:fix": "openedx lint --fix .",
|
||||||
"semantic-release": "semantic-release",
|
"snapshot": "openedx test --updateSnapshot",
|
||||||
"start": "fedx-scripts webpack-dev-server --progress",
|
"test": "openedx test --coverage --passWithNoTests"
|
||||||
"dev": "PUBLIC_PATH=/learner-dashboard/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
|
|
||||||
"test": "TZ=GMT fedx-scripts jest --coverage --passWithNoTests",
|
|
||||||
"quality": "npm run lint-fix && npm run test",
|
|
||||||
"watch-tests": "jest --watch",
|
|
||||||
"snapshot": "fedx-scripts jest --updateSnapshot"
|
|
||||||
},
|
},
|
||||||
"author": "edX",
|
"author": "Open edX",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"homepage": "",
|
"homepage": "https://github.com/openedx/frontend-app-learner-dashboard#readme",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/openedx/frontend-app-learner-dashboard/issues"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.3",
|
||||||
"@edx/frontend-component-footer": "^14.6.0",
|
|
||||||
"@edx/frontend-component-header": "^6.2.0",
|
|
||||||
"@edx/frontend-enterprise-hotjar": "7.2.0",
|
|
||||||
"@edx/frontend-platform": "^8.3.1",
|
|
||||||
"@edx/openedx-atlas": "^0.7.0",
|
"@edx/openedx-atlas": "^0.7.0",
|
||||||
"@edx/react-unit-test-utils": "^4.0.0",
|
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||||
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@openedx/frontend-plugin-framework": "^1.7.0",
|
|
||||||
"@openedx/paragon": "^22.16.0",
|
|
||||||
"@redux-devtools/extension": "3.3.0",
|
"@redux-devtools/extension": "3.3.0",
|
||||||
"@reduxjs/toolkit": "^2.0.0",
|
"@reduxjs/toolkit": "^2.0.0",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"core-js": "3.42.0",
|
|
||||||
"filesize": "^10.0.0",
|
"filesize": "^10.0.0",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"history": "5.3.0",
|
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"prop-types": "15.8.1",
|
"prop-types": "15.8.1",
|
||||||
"react": "^18.3.1",
|
|
||||||
"react-dom": "^18.3.1",
|
|
||||||
"react-helmet": "^6.1.0",
|
|
||||||
"react-intl": "6.8.9",
|
|
||||||
"react-redux": "^7.2.4",
|
|
||||||
"react-router-dom": "6.29.0",
|
|
||||||
"react-share": "^4.4.0",
|
"react-share": "^4.4.0",
|
||||||
"redux": "4.2.1",
|
|
||||||
"redux-logger": "3.0.6",
|
"redux-logger": "3.0.6",
|
||||||
"redux-thunk": "2.4.2",
|
"redux-thunk": "2.4.2",
|
||||||
"regenerator-runtime": "^0.14.0",
|
"reselect": "^4.0.0"
|
||||||
"reselect": "^4.0.0",
|
|
||||||
"universal-cookie": "^4.0.4",
|
|
||||||
"util": "^0.12.4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@edx/browserslist-config": "^1.3.0",
|
"@edx/browserslist-config": "^1.5.0",
|
||||||
"@edx/reactifex": "^2.1.1",
|
"@edx/react-unit-test-utils": "^4.0.0",
|
||||||
"@openedx/frontend-build": "^14.3.3",
|
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.2.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"copy-webpack-plugin": "^12.0.0",
|
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
@@ -82,5 +67,18 @@
|
|||||||
"react-dev-utils": "^12.0.0",
|
"react-dev-utils": "^12.0.0",
|
||||||
"react-test-renderer": "^18.3.1",
|
"react-test-renderer": "^18.3.1",
|
||||||
"redux-mock-store": "^1.5.4"
|
"redux-mock-store": "^1.5.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@openedx/frontend-base": "^1.0.0-alpha.1",
|
||||||
|
"@openedx/paragon": "^22",
|
||||||
|
"@tanstack/react-query": "^5",
|
||||||
|
"@types/react": "^18",
|
||||||
|
"@types/react-dom": "^18",
|
||||||
|
"react": "^18",
|
||||||
|
"react-dom": "^18",
|
||||||
|
"react-redux": "^8",
|
||||||
|
"react-router": "^6",
|
||||||
|
"react-router-dom": "^6",
|
||||||
|
"redux": "^4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en-us" dir="ltr">
|
<html lang="en-us" dir="ltr">
|
||||||
<head>
|
<head>
|
||||||
|
<title>Learner Dashboard Development Site></title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
User-agent: *
|
|
||||||
Disallow: /
|
|
||||||
41
site.config.dev.tsx
Normal file
41
site.config.dev.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { EnvironmentTypes, SiteConfig, footerApp, headerApp, shellApp } from '@openedx/frontend-base';
|
||||||
|
|
||||||
|
import { learnerDashboardApp } from './src';
|
||||||
|
|
||||||
|
import './src/app.scss';
|
||||||
|
|
||||||
|
const siteConfig: SiteConfig = {
|
||||||
|
siteId: 'learner-dashboard-dev',
|
||||||
|
siteName: 'Learner Dashboard Dev',
|
||||||
|
baseUrl: 'http://apps.local.openedx.io:1996',
|
||||||
|
lmsBaseUrl: 'http://local.openedx.io:8000',
|
||||||
|
loginUrl: 'http://local.openedx.io:8000/login',
|
||||||
|
logoutUrl: 'http://local.openedx.io:8000/logout',
|
||||||
|
|
||||||
|
environment: EnvironmentTypes.DEVELOPMENT,
|
||||||
|
basename: '/learner-dashboard',
|
||||||
|
apps: [
|
||||||
|
shellApp,
|
||||||
|
headerApp,
|
||||||
|
footerApp,
|
||||||
|
learnerDashboardApp
|
||||||
|
],
|
||||||
|
externalRoutes: [
|
||||||
|
{
|
||||||
|
role: 'profile',
|
||||||
|
url: 'http://apps.local.openedx.io:1995/profile/'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'account',
|
||||||
|
url: 'http://apps.local.openedx.io:1997/account/'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'logout',
|
||||||
|
url: 'http://local.openedx.io:8000/logout'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
accessTokenCookieName: 'edx-jwt-cookie-header-payload',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default siteConfig;
|
||||||
28
site.config.test.tsx
Normal file
28
site.config.test.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { EnvironmentTypes, SiteConfig } from '@openedx/frontend-base';
|
||||||
|
|
||||||
|
import { appId } from './src/constants';
|
||||||
|
|
||||||
|
const siteConfig: SiteConfig = {
|
||||||
|
siteId: 'learner-dashboard-test-site',
|
||||||
|
siteName: 'Learner Dashboard Test Site',
|
||||||
|
baseUrl: 'http://localhost:1996',
|
||||||
|
lmsBaseUrl: 'http://localhost:8000',
|
||||||
|
loginUrl: 'http://localhost:8000/login',
|
||||||
|
logoutUrl: 'http://localhost:8000/logout',
|
||||||
|
|
||||||
|
environment: EnvironmentTypes.TEST,
|
||||||
|
basename: '/learner-dashboard',
|
||||||
|
apps: [{
|
||||||
|
appId,
|
||||||
|
config: {
|
||||||
|
ECOMMERCE_BASE_URL: 'http://localhost:18130',
|
||||||
|
FAVICON_URL: 'https://edx-cdn.org/v3/default/favicon.ico',
|
||||||
|
LEARNING_BASE_URL: 'http://localhost:2000',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
|
||||||
|
accessTokenCookieName: 'edx-jwt-cookie-header-payload',
|
||||||
|
segmentKey: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default siteConfig;
|
||||||
100
src/App.jsx
100
src/App.jsx
@@ -1,100 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Helmet } from 'react-helmet';
|
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
import { logError } from '@edx/frontend-platform/logging';
|
|
||||||
import { initializeHotjar } from '@edx/frontend-enterprise-hotjar';
|
|
||||||
|
|
||||||
import { ErrorPage, AppContext } from '@edx/frontend-platform/react';
|
|
||||||
import { FooterSlot } from '@edx/frontend-component-footer';
|
|
||||||
import { Alert } from '@openedx/paragon';
|
|
||||||
|
|
||||||
import { RequestKeys } from 'data/constants/requests';
|
|
||||||
import store from 'data/store';
|
|
||||||
import {
|
|
||||||
selectors,
|
|
||||||
actions,
|
|
||||||
} from 'data/redux';
|
|
||||||
import { reduxHooks } from 'hooks';
|
|
||||||
import Dashboard from 'containers/Dashboard';
|
|
||||||
|
|
||||||
import track from 'tracking';
|
|
||||||
|
|
||||||
import fakeData from 'data/services/lms/fakeData/courses';
|
|
||||||
|
|
||||||
import AppWrapper from 'containers/WidgetContainers/AppWrapper';
|
|
||||||
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';
|
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import messages from './messages';
|
|
||||||
import './App.scss';
|
|
||||||
|
|
||||||
export const App = () => {
|
|
||||||
const { authenticatedUser } = React.useContext(AppContext);
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const isFailed = {
|
|
||||||
initialize: reduxHooks.useRequestIsFailed(RequestKeys.initialize),
|
|
||||||
refreshList: reduxHooks.useRequestIsFailed(RequestKeys.refreshList),
|
|
||||||
};
|
|
||||||
const hasNetworkFailure = isFailed.initialize || isFailed.refreshList;
|
|
||||||
const { supportEmail } = reduxHooks.usePlatformSettingsData();
|
|
||||||
const loadData = reduxHooks.useLoadData();
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (authenticatedUser?.administrator || getConfig().NODE_ENV === 'development') {
|
|
||||||
window.loadEmptyData = () => {
|
|
||||||
loadData({ ...fakeData.globalData, courses: [] });
|
|
||||||
};
|
|
||||||
window.loadMockData = () => {
|
|
||||||
loadData({
|
|
||||||
...fakeData.globalData,
|
|
||||||
courses: [
|
|
||||||
...fakeData.courseRunData,
|
|
||||||
...fakeData.entitlementData,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
window.store = store;
|
|
||||||
window.selectors = selectors;
|
|
||||||
window.actions = actions;
|
|
||||||
window.track = track;
|
|
||||||
}
|
|
||||||
if (getConfig().HOTJAR_APP_ID) {
|
|
||||||
try {
|
|
||||||
initializeHotjar({
|
|
||||||
hotjarId: getConfig().HOTJAR_APP_ID,
|
|
||||||
hotjarVersion: getConfig().HOTJAR_VERSION,
|
|
||||||
hotjarDebug: !!getConfig().HOTJAR_DEBUG,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [authenticatedUser, loadData]);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Helmet>
|
|
||||||
<title>{formatMessage(messages.pageTitle)}</title>
|
|
||||||
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
|
||||||
</Helmet>
|
|
||||||
<div>
|
|
||||||
<AppWrapper>
|
|
||||||
<LearnerDashboardHeader />
|
|
||||||
<main id="main">
|
|
||||||
{hasNetworkFailure
|
|
||||||
? (
|
|
||||||
<Alert variant="danger">
|
|
||||||
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
|
|
||||||
</Alert>
|
|
||||||
) : (
|
|
||||||
<Dashboard />
|
|
||||||
)}
|
|
||||||
</main>
|
|
||||||
</AppWrapper>
|
|
||||||
<FooterSlot />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
69
src/App.scss
69
src/App.scss
@@ -1,69 +0,0 @@
|
|||||||
// frontend-app-*/src/index.scss
|
|
||||||
@import "~@edx/brand/paragon/fonts";
|
|
||||||
@import "~@edx/brand/paragon/variables";
|
|
||||||
@import "~@openedx/paragon/scss/core/core";
|
|
||||||
@import "~@edx/brand/paragon/overrides";
|
|
||||||
|
|
||||||
$fa-font-path: "~font-awesome/fonts";
|
|
||||||
@import "~font-awesome/scss/font-awesome";
|
|
||||||
|
|
||||||
$input-focus-box-shadow: $input-box-shadow; // hack to get upgrade to paragon 4.0.0 to work
|
|
||||||
|
|
||||||
@import "~@edx/frontend-component-header/dist/index";
|
|
||||||
@import "~@edx/frontend-component-footer/dist/_footer";
|
|
||||||
|
|
||||||
.text-ellipsis {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert.alert-info .alert-icon {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
#root {
|
|
||||||
// Removing a odd 1.5 scaling on checkboxes.:
|
|
||||||
input[type=checkbox] {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 100vh;
|
|
||||||
|
|
||||||
main {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
display: block;
|
|
||||||
box-sizing: content-box;
|
|
||||||
position: relative;
|
|
||||||
top: 0.1em;
|
|
||||||
height: 1.75rem;
|
|
||||||
margin-right: 1rem;
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
flex: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#paragon-portal-root {
|
|
||||||
.pgn__modal-layer {
|
|
||||||
.pgn__modal-close-container {
|
|
||||||
right: 1rem !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.confirm-modal .pgn__modal-body {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
149
src/App.test.jsx
149
src/App.test.jsx
@@ -1,149 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Helmet } from 'react-helmet';
|
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
|
|
||||||
import { RequestKeys } from 'data/constants/requests';
|
|
||||||
import { reduxHooks } from 'hooks';
|
|
||||||
import Dashboard from 'containers/Dashboard';
|
|
||||||
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';
|
|
||||||
import AppWrapper from 'containers/WidgetContainers/AppWrapper';
|
|
||||||
import { App } from './App';
|
|
||||||
import messages from './messages';
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-component-footer', () => ({ FooterSlot: 'FooterSlot' }));
|
|
||||||
|
|
||||||
jest.mock('containers/Dashboard', () => 'Dashboard');
|
|
||||||
jest.mock('containers/LearnerDashboardHeader', () => 'LearnerDashboardHeader');
|
|
||||||
jest.mock('containers/WidgetContainers/AppWrapper', () => 'AppWrapper');
|
|
||||||
jest.mock('data/redux', () => ({
|
|
||||||
selectors: 'redux.selectors',
|
|
||||||
actions: 'redux.actions',
|
|
||||||
thunkActions: 'redux.thunkActions',
|
|
||||||
}));
|
|
||||||
jest.mock('hooks', () => ({
|
|
||||||
reduxHooks: {
|
|
||||||
useRequestIsFailed: jest.fn(),
|
|
||||||
usePlatformSettingsData: jest.fn(),
|
|
||||||
useLoadData: jest.fn(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
jest.mock('data/store', () => 'data/store');
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform', () => ({
|
|
||||||
getConfig: jest.fn(() => ({})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const loadData = jest.fn();
|
|
||||||
reduxHooks.useLoadData.mockReturnValue(loadData);
|
|
||||||
|
|
||||||
let el;
|
|
||||||
|
|
||||||
const supportEmail = 'test-support-url';
|
|
||||||
reduxHooks.usePlatformSettingsData.mockReturnValue({ supportEmail });
|
|
||||||
|
|
||||||
describe('App router component', () => {
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
describe('component', () => {
|
|
||||||
const runBasicTests = () => {
|
|
||||||
test('snapshot', () => { expect(el.snapshot).toMatchSnapshot(); });
|
|
||||||
it('displays title in helmet component', () => {
|
|
||||||
const control = el.instance
|
|
||||||
.findByType(Helmet)[0]
|
|
||||||
.findByType('title')[0];
|
|
||||||
expect(control.children[0].el).toEqual(formatMessage(messages.pageTitle));
|
|
||||||
});
|
|
||||||
it('displays learner dashboard header', () => {
|
|
||||||
expect(el.instance.findByType(LearnerDashboardHeader).length).toEqual(1);
|
|
||||||
});
|
|
||||||
it('wraps the header and main components in an AppWrapper widget container', () => {
|
|
||||||
const container = el.instance.findByType(AppWrapper)[0];
|
|
||||||
expect(container.children[0].type).toEqual('LearnerDashboardHeader');
|
|
||||||
expect(container.children[1].type).toEqual('main');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
describe('no network failure', () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
|
||||||
getConfig.mockReturnValue({});
|
|
||||||
el = shallow(<App />);
|
|
||||||
});
|
|
||||||
runBasicTests();
|
|
||||||
it('loads dashboard', () => {
|
|
||||||
const main = el.instance.findByType('main')[0];
|
|
||||||
expect(main.children.length).toEqual(1);
|
|
||||||
const dashboard = main.children[0].el;
|
|
||||||
expect(dashboard.type).toEqual('Dashboard');
|
|
||||||
expect(dashboard).toEqual(shallow(<Dashboard />));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('no network failure with optimizely url', () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
|
||||||
getConfig.mockReturnValue({ OPTIMIZELY_URL: 'fake.url' });
|
|
||||||
el = shallow(<App />);
|
|
||||||
});
|
|
||||||
runBasicTests();
|
|
||||||
it('loads dashboard', () => {
|
|
||||||
const main = el.instance.findByType('main')[0];
|
|
||||||
expect(main.children.length).toEqual(1);
|
|
||||||
const dashboard = main.children[0].el;
|
|
||||||
expect(dashboard.type).toEqual('Dashboard');
|
|
||||||
expect(dashboard).toEqual(shallow(<Dashboard />));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('no network failure with optimizely project id', () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
reduxHooks.useRequestIsFailed.mockReturnValue(false);
|
|
||||||
getConfig.mockReturnValue({ OPTIMIZELY_PROJECT_ID: 'fakeId' });
|
|
||||||
el = shallow(<App />);
|
|
||||||
});
|
|
||||||
runBasicTests();
|
|
||||||
it('loads dashboard', () => {
|
|
||||||
const main = el.instance.findByType('main')[0];
|
|
||||||
expect(main.children.length).toEqual(1);
|
|
||||||
const dashboard = main.children[0].el;
|
|
||||||
expect(dashboard.type).toEqual('Dashboard');
|
|
||||||
expect(dashboard).toEqual(shallow(<Dashboard />));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('initialize failure', () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.initialize);
|
|
||||||
getConfig.mockReturnValue({});
|
|
||||||
el = shallow(<App />);
|
|
||||||
});
|
|
||||||
runBasicTests();
|
|
||||||
it('loads error page', () => {
|
|
||||||
const main = el.instance.findByType('main')[0];
|
|
||||||
expect(main.children.length).toEqual(1);
|
|
||||||
const alert = main.children[0];
|
|
||||||
expect(alert.type).toEqual('Alert');
|
|
||||||
expect(alert.children.length).toEqual(1);
|
|
||||||
const errorPage = alert.children[0];
|
|
||||||
expect(errorPage.type).toEqual('ErrorPage');
|
|
||||||
expect(errorPage.props.message).toEqual(formatMessage(messages.errorMessage, { supportEmail }));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('refresh failure', () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.refreshList);
|
|
||||||
getConfig.mockReturnValue({});
|
|
||||||
el = shallow(<App />);
|
|
||||||
});
|
|
||||||
runBasicTests();
|
|
||||||
it('loads error page', () => {
|
|
||||||
const main = el.instance.findByType('main')[0];
|
|
||||||
expect(main.children.length).toEqual(1);
|
|
||||||
const alert = main.children[0];
|
|
||||||
expect(alert.type).toEqual('Alert');
|
|
||||||
expect(alert.children.length).toEqual(1);
|
|
||||||
const errorPage = alert.children[0];
|
|
||||||
expect(errorPage.type).toEqual('ErrorPage');
|
|
||||||
expect(errorPage.props.message).toEqual(formatMessage(messages.errorMessage, { supportEmail }));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
20
src/Main.jsx
Normal file
20
src/Main.jsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Provider as ReduxProvider } from 'react-redux';
|
||||||
|
import { CurrentAppProvider, PageWrap } from '@openedx/frontend-base';
|
||||||
|
|
||||||
|
import { appId } from './constants';
|
||||||
|
import store from './data/store';
|
||||||
|
import Dashboard from './containers/Dashboard';
|
||||||
|
|
||||||
|
import './app.scss';
|
||||||
|
|
||||||
|
const Main = () => (
|
||||||
|
<CurrentAppProvider appId={appId}>
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
<PageWrap>
|
||||||
|
<Dashboard />
|
||||||
|
</PageWrap>
|
||||||
|
</ReduxProvider>
|
||||||
|
</CurrentAppProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Main;
|
||||||
1
src/__mocks__/file.js
Normal file
1
src/__mocks__/file.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = 'FileMock';
|
||||||
1
src/__mocks__/svg.js
Normal file
1
src/__mocks__/svg.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = 'SvgURL';
|
||||||
@@ -2,21 +2,8 @@
|
|||||||
|
|
||||||
exports[`App router component component initialize failure snapshot 1`] = `
|
exports[`App router component component initialize failure snapshot 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<HelmetWrapper
|
|
||||||
defer={true}
|
|
||||||
encodeSpecialCharacters={true}
|
|
||||||
>
|
|
||||||
<title>
|
|
||||||
Learner Home
|
|
||||||
</title>
|
|
||||||
<link
|
|
||||||
rel="shortcut icon"
|
|
||||||
type="image/x-icon"
|
|
||||||
/>
|
|
||||||
</HelmetWrapper>
|
|
||||||
<div>
|
<div>
|
||||||
<AppWrapper>
|
<AppWrapper>
|
||||||
<LearnerDashboardHeader />
|
|
||||||
<main
|
<main
|
||||||
id="main"
|
id="main"
|
||||||
>
|
>
|
||||||
@@ -29,112 +16,56 @@ exports[`App router component component initialize failure snapshot 1`] = `
|
|||||||
</Alert>
|
</Alert>
|
||||||
</main>
|
</main>
|
||||||
</AppWrapper>
|
</AppWrapper>
|
||||||
<FooterSlot />
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`App router component component no network failure snapshot 1`] = `
|
exports[`App router component component no network failure snapshot 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<HelmetWrapper
|
|
||||||
defer={true}
|
|
||||||
encodeSpecialCharacters={true}
|
|
||||||
>
|
|
||||||
<title>
|
|
||||||
Learner Home
|
|
||||||
</title>
|
|
||||||
<link
|
|
||||||
rel="shortcut icon"
|
|
||||||
type="image/x-icon"
|
|
||||||
/>
|
|
||||||
</HelmetWrapper>
|
|
||||||
<div>
|
<div>
|
||||||
<AppWrapper>
|
<AppWrapper>
|
||||||
<LearnerDashboardHeader />
|
|
||||||
<main
|
<main
|
||||||
id="main"
|
id="main"
|
||||||
>
|
>
|
||||||
<Dashboard />
|
<Dashboard />
|
||||||
</main>
|
</main>
|
||||||
</AppWrapper>
|
</AppWrapper>
|
||||||
<FooterSlot />
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`App router component component no network failure with optimizely project id snapshot 1`] = `
|
exports[`App router component component no network failure with optimizely project id snapshot 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<HelmetWrapper
|
|
||||||
defer={true}
|
|
||||||
encodeSpecialCharacters={true}
|
|
||||||
>
|
|
||||||
<title>
|
|
||||||
Learner Home
|
|
||||||
</title>
|
|
||||||
<link
|
|
||||||
rel="shortcut icon"
|
|
||||||
type="image/x-icon"
|
|
||||||
/>
|
|
||||||
</HelmetWrapper>
|
|
||||||
<div>
|
<div>
|
||||||
<AppWrapper>
|
<AppWrapper>
|
||||||
<LearnerDashboardHeader />
|
|
||||||
<main
|
<main
|
||||||
id="main"
|
id="main"
|
||||||
>
|
>
|
||||||
<Dashboard />
|
<Dashboard />
|
||||||
</main>
|
</main>
|
||||||
</AppWrapper>
|
</AppWrapper>
|
||||||
<FooterSlot />
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`App router component component no network failure with optimizely url snapshot 1`] = `
|
exports[`App router component component no network failure with optimizely url snapshot 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<HelmetWrapper
|
|
||||||
defer={true}
|
|
||||||
encodeSpecialCharacters={true}
|
|
||||||
>
|
|
||||||
<title>
|
|
||||||
Learner Home
|
|
||||||
</title>
|
|
||||||
<link
|
|
||||||
rel="shortcut icon"
|
|
||||||
type="image/x-icon"
|
|
||||||
/>
|
|
||||||
</HelmetWrapper>
|
|
||||||
<div>
|
<div>
|
||||||
<AppWrapper>
|
<AppWrapper>
|
||||||
<LearnerDashboardHeader />
|
|
||||||
<main
|
<main
|
||||||
id="main"
|
id="main"
|
||||||
>
|
>
|
||||||
<Dashboard />
|
<Dashboard />
|
||||||
</main>
|
</main>
|
||||||
</AppWrapper>
|
</AppWrapper>
|
||||||
<FooterSlot />
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`App router component component refresh failure snapshot 1`] = `
|
exports[`App router component component refresh failure snapshot 1`] = `
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<HelmetWrapper
|
|
||||||
defer={true}
|
|
||||||
encodeSpecialCharacters={true}
|
|
||||||
>
|
|
||||||
<title>
|
|
||||||
Learner Home
|
|
||||||
</title>
|
|
||||||
<link
|
|
||||||
rel="shortcut icon"
|
|
||||||
type="image/x-icon"
|
|
||||||
/>
|
|
||||||
</HelmetWrapper>
|
|
||||||
<div>
|
<div>
|
||||||
<AppWrapper>
|
<AppWrapper>
|
||||||
<LearnerDashboardHeader />
|
|
||||||
<main
|
<main
|
||||||
id="main"
|
id="main"
|
||||||
>
|
>
|
||||||
@@ -147,7 +78,6 @@ exports[`App router component component refresh failure snapshot 1`] = `
|
|||||||
</Alert>
|
</Alert>
|
||||||
</main>
|
</main>
|
||||||
</AppWrapper>
|
</AppWrapper>
|
||||||
<FooterSlot />
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ exports[`app registry subscribe: APP_INIT_ERROR. snapshot: displays an ErrorPag
|
|||||||
|
|
||||||
exports[`app registry subscribe: APP_READY. links App to root element 1`] = `
|
exports[`app registry subscribe: APP_READY. links App to root element 1`] = `
|
||||||
<UNDEFINED>
|
<UNDEFINED>
|
||||||
<AppProvider
|
<SiteProvider
|
||||||
store={
|
store={
|
||||||
{
|
{
|
||||||
"redux": "store",
|
"redux": "store",
|
||||||
@@ -38,6 +38,6 @@ exports[`app registry subscribe: APP_READY. links App to root element 1`] = `
|
|||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</NoticesWrapper>
|
</NoticesWrapper>
|
||||||
</AppProvider>
|
</SiteProvider>
|
||||||
</UNDEFINED>
|
</UNDEFINED>
|
||||||
`;
|
`;
|
||||||
|
|||||||
41
src/app.scss
Executable file
41
src/app.scss
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
@import "~@edx/brand/paragon/fonts";
|
||||||
|
@import "~@edx/brand/paragon/variables";
|
||||||
|
@import "~@openedx/paragon/scss/core/core";
|
||||||
|
@import "~@edx/brand/paragon/overrides";
|
||||||
|
|
||||||
|
$fa-font-path: "~font-awesome/fonts";
|
||||||
|
@import "~font-awesome/scss/font-awesome";
|
||||||
|
|
||||||
|
$input-focus-box-shadow: $input-box-shadow; // hack to get upgrade to paragon 4.0.0 to work
|
||||||
|
|
||||||
|
#learnerDashboardRoot {
|
||||||
|
main {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removing a odd 1.5 scaling on checkboxes.:
|
||||||
|
input[type=checkbox] {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-ellipsis {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert.alert-info .alert-icon {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#paragon-portal-root {
|
||||||
|
.pgn__modal-layer {
|
||||||
|
.pgn__modal-close-container {
|
||||||
|
right: 1rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.confirm-modal .pgn__modal-body {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/app.ts
Normal file
23
src/app.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { App } from '@openedx/frontend-base';
|
||||||
|
import { appId } from './constants';
|
||||||
|
import routes from './routes';
|
||||||
|
import providers from './providers';
|
||||||
|
import messages from './i18n';
|
||||||
|
import slots from './slots';
|
||||||
|
|
||||||
|
const app: App = {
|
||||||
|
appId,
|
||||||
|
routes,
|
||||||
|
providers,
|
||||||
|
messages,
|
||||||
|
slots,
|
||||||
|
config: {
|
||||||
|
LEARNING_BASE_URL: 'http://apps.local.openedx.io:2000',
|
||||||
|
ENABLE_PROGRAMS: false,
|
||||||
|
ECOMMERCE_BASE_URL: '',
|
||||||
|
ORDER_HISTORY_URL: '',
|
||||||
|
SUPPORT_URL: '',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default app;
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<svg width="1350" height="7" viewBox="0 0 1350 7" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_3302_26079)">
|
|
||||||
<rect width="1350" height="6.75" transform="translate(0 -0.375)" fill="#03C7E8"/>
|
|
||||||
<rect y="-0.375" width="585.562" height="6.75" fill="#D23228"/>
|
|
||||||
<path d="M549.281 -0.375H933.188L929.491 6.375H549.281V-0.375Z" fill="#002121"/>
|
|
||||||
<path d="M550.129 13.125L545.062 -10.5L555.188 -10.5L550.129 13.125Z" fill="#D23228"/>
|
|
||||||
<path d="M931.082 13.125L925.594 -6.28125L936.563 -6.28125L931.082 13.125Z" fill="#002121"/>
|
|
||||||
<path d="M0 -0.375H106.312L105.289 6.375H0V-0.375Z" fill="#921108"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_3302_26079">
|
|
||||||
<rect width="1350" height="6.75" fill="white" transform="translate(0 -0.375)"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 765 B |
@@ -1,25 +0,0 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
|
||||||
import { logError, logInfo } from '@edx/frontend-platform/logging';
|
|
||||||
|
|
||||||
export const noticesUrl = `${getConfig().LMS_BASE_URL}/notices/api/v1/unacknowledged`;
|
|
||||||
|
|
||||||
export const getNotices = ({ onLoad, notFoundMessage }) => {
|
|
||||||
const authenticatedUser = getAuthenticatedUser();
|
|
||||||
|
|
||||||
const handleError = async (e) => {
|
|
||||||
// Error probably means that notices is not installed, which is fine.
|
|
||||||
const { customAttributes: { httpErrorStatus } } = e;
|
|
||||||
if (httpErrorStatus === 404) {
|
|
||||||
logInfo(`${e}. ${notFoundMessage}`);
|
|
||||||
} else {
|
|
||||||
logError(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (authenticatedUser) {
|
|
||||||
return getAuthenticatedHttpClient().get(noticesUrl, {}).then(onLoad).catch(handleError);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default { getNotices };
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
|
||||||
import { logError, logInfo } from '@edx/frontend-platform/logging';
|
|
||||||
|
|
||||||
import * as api from './api';
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform', () => ({
|
|
||||||
getConfig: jest.fn(() => ({
|
|
||||||
LMS_BASE_URL: 'test-lms-url',
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
|
||||||
getAuthenticatedHttpClient: jest.fn(),
|
|
||||||
getAuthenticatedUser: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/logging', () => ({
|
|
||||||
logError: jest.fn(),
|
|
||||||
logInfo: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const testData = 'test-data';
|
|
||||||
const successfulGet = () => Promise.resolve(testData);
|
|
||||||
const error404 = { customAttributes: { httpErrorStatus: 404 }, test: 'error' };
|
|
||||||
const error404Get = () => Promise.reject(error404);
|
|
||||||
const error500 = { customAttributes: { httpErrorStatus: 500 }, test: 'error' };
|
|
||||||
const error500Get = () => Promise.reject(error500);
|
|
||||||
|
|
||||||
const get = jest.fn().mockImplementation(successfulGet);
|
|
||||||
getAuthenticatedHttpClient.mockReturnValue({ get });
|
|
||||||
const authenticatedUser = { fake: 'user' };
|
|
||||||
getAuthenticatedUser.mockReturnValue(authenticatedUser);
|
|
||||||
|
|
||||||
const onLoad = jest.fn();
|
|
||||||
describe('getNotices api method', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
describe('behavior', () => {
|
|
||||||
describe('not authenticated', () => {
|
|
||||||
it('does not fetch anything', () => {
|
|
||||||
getAuthenticatedUser.mockReturnValueOnce(null);
|
|
||||||
api.getNotices({ onLoad });
|
|
||||||
expect(get).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('authenticated', () => {
|
|
||||||
it('fetches noticesUrl with onLoad behavior', async () => {
|
|
||||||
await api.getNotices({ onLoad });
|
|
||||||
expect(get).toHaveBeenCalledWith(api.noticesUrl, {});
|
|
||||||
expect(onLoad).toHaveBeenCalledWith(testData);
|
|
||||||
});
|
|
||||||
it('calls logInfo if fetch fails with 404', async () => {
|
|
||||||
get.mockImplementation(error404Get);
|
|
||||||
await api.getNotices({ onLoad });
|
|
||||||
expect(logInfo).toHaveBeenCalledWith(`${error404}. ${api.error404Message}`);
|
|
||||||
});
|
|
||||||
it('calls logError if fetch fails with non-404 error', async () => {
|
|
||||||
get.mockImplementation(error500Get);
|
|
||||||
await api.getNotices({ onLoad });
|
|
||||||
expect(logError).toHaveBeenCalledWith(error500);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import { useIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import { StrictDict } from 'utils';
|
|
||||||
import { getNotices } from './api';
|
|
||||||
import * as module from './hooks';
|
|
||||||
import messages from './messages';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component uses the platform-plugin-notices plugin to function.
|
|
||||||
* If the user has an unacknowledged notice, they will be rerouted off
|
|
||||||
* course home and onto a full-screen notice page. If the plugin is not
|
|
||||||
* installed, or there are no notices, we just passthrough this component.
|
|
||||||
*/
|
|
||||||
export const state = StrictDict({
|
|
||||||
isRedirected: (val) => React.useState(val), // eslint-disable-line
|
|
||||||
});
|
|
||||||
|
|
||||||
export const useNoticesWrapperData = () => {
|
|
||||||
const [isRedirected, setIsRedirected] = module.state.isRedirected();
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (getConfig().ENABLE_NOTICES) {
|
|
||||||
getNotices({
|
|
||||||
onLoad: (data) => {
|
|
||||||
if (data?.data?.results?.length > 0) {
|
|
||||||
setIsRedirected(true);
|
|
||||||
window.location.replace(`${data.data.results[0]}?next=${window.location.href}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
notFoundMessage: formatMessage(messages.error404Message),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [setIsRedirected, formatMessage]);
|
|
||||||
return { isRedirected };
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useNoticesWrapperData;
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { MockUseState } from 'testUtils';
|
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import { getNotices } from './api';
|
|
||||||
import * as hooks from './hooks';
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform', () => ({ getConfig: jest.fn() }));
|
|
||||||
jest.mock('./api', () => ({ getNotices: jest.fn() }));
|
|
||||||
const mockFormatMessage = jest.fn(message => message.defaultMessage || 'translated-string');
|
|
||||||
jest.mock('react-intl', () => ({
|
|
||||||
useIntl: () => ({
|
|
||||||
formatMessage: mockFormatMessage,
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
getConfig.mockReturnValue({ ENABLE_NOTICES: true });
|
|
||||||
const state = new MockUseState(hooks);
|
|
||||||
|
|
||||||
let hook;
|
|
||||||
describe('NoticesWrapper hooks', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
describe('state hooks', () => {
|
|
||||||
state.testGetter(state.keys.isRedirected);
|
|
||||||
});
|
|
||||||
describe('useNoticesWrapperData', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
state.mock();
|
|
||||||
});
|
|
||||||
describe('behavior', () => {
|
|
||||||
it('initializes state hooks', () => {
|
|
||||||
hooks.useNoticesWrapperData();
|
|
||||||
expect(hooks.state.isRedirected).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
describe('effects', () => {
|
|
||||||
it('does not call notices if not enabled', () => {
|
|
||||||
getConfig.mockReturnValueOnce({ ENABLE_NOTICES: false });
|
|
||||||
hooks.useNoticesWrapperData();
|
|
||||||
const [cb, prereqs] = React.useEffect.mock.calls[0];
|
|
||||||
expect(prereqs).toEqual([state.setState.isRedirected, mockFormatMessage]);
|
|
||||||
cb();
|
|
||||||
expect(getNotices).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
describe('getNotices call (if enabled) onLoad behavior', () => {
|
|
||||||
it('does not redirect if there are no results', () => {
|
|
||||||
hooks.useNoticesWrapperData();
|
|
||||||
expect(React.useEffect).toHaveBeenCalled();
|
|
||||||
const [cb, prereqs] = React.useEffect.mock.calls[0];
|
|
||||||
expect(prereqs).toEqual([state.setState.isRedirected, mockFormatMessage]);
|
|
||||||
cb();
|
|
||||||
expect(getNotices).toHaveBeenCalled();
|
|
||||||
const { onLoad } = getNotices.mock.calls[0][0];
|
|
||||||
onLoad({});
|
|
||||||
expect(state.setState.isRedirected).not.toHaveBeenCalled();
|
|
||||||
onLoad({ data: {} });
|
|
||||||
expect(state.setState.isRedirected).not.toHaveBeenCalled();
|
|
||||||
onLoad({ data: { results: [] } });
|
|
||||||
expect(state.setState.isRedirected).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
it('redirects and set isRedirected if results are returned', () => {
|
|
||||||
delete window.location;
|
|
||||||
window.location = { replace: jest.fn(), href: 'test-old-href' };
|
|
||||||
hooks.useNoticesWrapperData();
|
|
||||||
const [cb, prereqs] = React.useEffect.mock.calls[0];
|
|
||||||
expect(prereqs).toEqual([state.setState.isRedirected, mockFormatMessage]);
|
|
||||||
cb();
|
|
||||||
expect(getNotices).toHaveBeenCalled();
|
|
||||||
const { onLoad } = getNotices.mock.calls[0][0];
|
|
||||||
const target = 'url-target';
|
|
||||||
onLoad({ data: { results: [target] } });
|
|
||||||
expect(state.setState.isRedirected).toHaveBeenCalledWith(true);
|
|
||||||
expect(window.location.replace).toHaveBeenCalledWith(
|
|
||||||
`${target}?next=${window.location.href}`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('output', () => {
|
|
||||||
it('forwards isRedirected from state call', () => {
|
|
||||||
hook = hooks.useNoticesWrapperData();
|
|
||||||
expect(hook.isRedirected).toEqual(state.stateVals.isRedirected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import useNoticesWrapperData from './hooks';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component uses the platform-plugin-notices plugin to function.
|
|
||||||
* If the user has an unacknowledged notice, they will be rerouted off
|
|
||||||
* course home and onto a full-screen notice page. If the plugin is not
|
|
||||||
* installed, or there are no notices, we just passthrough this component.
|
|
||||||
*/
|
|
||||||
const NoticesWrapper = ({ children }) => {
|
|
||||||
const { isRedirected } = useNoticesWrapperData();
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{isRedirected === true ? null : children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
NoticesWrapper.propTypes = {
|
|
||||||
children: PropTypes.node.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NoticesWrapper;
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
|
||||||
|
|
||||||
import useNoticesWrapperData from './hooks';
|
|
||||||
import NoticesWrapper from '.';
|
|
||||||
|
|
||||||
jest.mock('./hooks', () => jest.fn());
|
|
||||||
|
|
||||||
const hookProps = { isRedirected: false };
|
|
||||||
useNoticesWrapperData.mockReturnValue(hookProps);
|
|
||||||
|
|
||||||
let el;
|
|
||||||
const children = [<b key={1}>some</b>, <i key={2}>children</i>];
|
|
||||||
describe('NoticesWrapper component', () => {
|
|
||||||
describe('behavior', () => {
|
|
||||||
it('initializes hooks', () => {
|
|
||||||
el = shallow(<NoticesWrapper>{children}</NoticesWrapper>);
|
|
||||||
expect(useNoticesWrapperData).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('output', () => {
|
|
||||||
it('does not show children if redirected', () => {
|
|
||||||
useNoticesWrapperData.mockReturnValueOnce({ isRedirected: true });
|
|
||||||
el = shallow(<NoticesWrapper>{children}</NoticesWrapper>);
|
|
||||||
expect(el.instance.children.length).toEqual(0);
|
|
||||||
});
|
|
||||||
it('shows children if not redirected', () => {
|
|
||||||
el = shallow(<NoticesWrapper>{children}</NoticesWrapper>);
|
|
||||||
expect(el.instance.children.length).toEqual(2);
|
|
||||||
expect(el.instance.children[0].type).toEqual(shallow(children[0]).type);
|
|
||||||
expect(el.instance.props).toEqual(shallow(children[0]).props);
|
|
||||||
expect(el.instance.children[1].type).toEqual(shallow(children[1]).type);
|
|
||||||
expect(el.instance.props).toEqual(shallow(children[1]).props);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
error404Message: {
|
|
||||||
id: 'learner-dash.notices.error404Message',
|
|
||||||
defaultMessage: 'This probably happened because the notices plugin is not installed on platform.',
|
|
||||||
description: 'Error message when notices API returns 404',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default messages;
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
const configuration = {
|
|
||||||
// BASE_URL: process.env.BASE_URL,
|
|
||||||
LMS_BASE_URL: process.env.LMS_BASE_URL,
|
|
||||||
ECOMMERCE_BASE_URL: process.env.ECOMMERCE_BASE_URL,
|
|
||||||
// LOGIN_URL: process.env.LOGIN_URL,
|
|
||||||
// LOGOUT_URL: process.env.LOGOUT_URL,
|
|
||||||
// CSRF_TOKEN_API_PATH: process.env.CSRF_TOKEN_API_PATH,
|
|
||||||
// REFRESH_ACCESS_TOKEN_ENDPOINT: process.env.REFRESH_ACCESS_TOKEN_ENDPOINT,
|
|
||||||
// DATA_API_BASE_URL: process.env.DATA_API_BASE_URL,
|
|
||||||
// SECURE_COOKIES: process.env.NODE_ENV !== 'development',
|
|
||||||
SEGMENT_KEY: process.env.SEGMENT_KEY,
|
|
||||||
// ACCESS_TOKEN_COOKIE_NAME: process.env.ACCESS_TOKEN_COOKIE_NAME,
|
|
||||||
LEARNING_BASE_URL: process.env.LEARNING_BASE_URL,
|
|
||||||
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN || '',
|
|
||||||
SUPPORT_URL: process.env.SUPPORT_URL || null,
|
|
||||||
ENABLE_NOTICES: process.env.ENABLE_NOTICES || null,
|
|
||||||
CAREER_LINK_URL: process.env.CAREER_LINK_URL || null,
|
|
||||||
LOGO_URL: process.env.LOGO_URL,
|
|
||||||
ENABLE_EDX_PERSONAL_DASHBOARD: process.env.ENABLE_EDX_PERSONAL_DASHBOARD === 'true',
|
|
||||||
SEARCH_CATALOG_URL: process.env.SEARCH_CATALOG_URL || null,
|
|
||||||
ENABLE_PROGRAMS: process.env.ENABLE_PROGRAMS === 'true',
|
|
||||||
NON_BROWSABLE_COURSES: process.env.NON_BROWSABLE_COURSES === 'true',
|
|
||||||
};
|
|
||||||
|
|
||||||
const features = {};
|
|
||||||
|
|
||||||
export { configuration, features };
|
|
||||||
1
src/constants.ts
Normal file
1
src/constants.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const appId = 'org.openedx.frontend.app.learnerDashboard';
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
|
import track from '../../../../tracking';
|
||||||
|
import { reduxHooks } from '../../../../hooks';
|
||||||
|
|
||||||
import track from 'tracking';
|
|
||||||
import { reduxHooks } from 'hooks';
|
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
import ActionButton from './ActionButton';
|
import ActionButton from './ActionButton';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
|
import track from '../../../../tracking';
|
||||||
|
import { reduxHooks } from '../../../../hooks';
|
||||||
|
|
||||||
import track from 'tracking';
|
|
||||||
import { reduxHooks } from 'hooks';
|
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
import ActionButton from './ActionButton';
|
import ActionButton from './ActionButton';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
|
import { reduxHooks } from '../../../../hooks';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
import ActionButton from './ActionButton';
|
import ActionButton from './ActionButton';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
|
import track from '../../../../tracking';
|
||||||
|
import { reduxHooks } from '../../../../hooks';
|
||||||
|
|
||||||
import track from 'tracking';
|
|
||||||
import { reduxHooks } from 'hooks';
|
|
||||||
import useActionDisabledState from '../hooks';
|
import useActionDisabledState from '../hooks';
|
||||||
import ActionButton from './ActionButton';
|
import ActionButton from './ActionButton';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { ActionRow } from '@openedx/paragon';
|
import { ActionRow } from '@openedx/paragon';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../../../hooks';
|
||||||
|
import CourseCardActionSlot from '../../../../slots/CourseCardActionSlot';
|
||||||
|
|
||||||
import CourseCardActionSlot from 'plugin-slots/CourseCardActionSlot';
|
|
||||||
import SelectSessionButton from './SelectSessionButton';
|
import SelectSessionButton from './SelectSessionButton';
|
||||||
import BeginCourseButton from './BeginCourseButton';
|
import BeginCourseButton from './BeginCourseButton';
|
||||||
import ResumeButton from './ResumeButton';
|
import ResumeButton from './ResumeButton';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { shallow } from '@edx/react-unit-test-utils';
|
|||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
|
|
||||||
import CourseCardActionSlot from 'plugin-slots/CourseCardActionSlot';
|
import CourseCardActionSlot from 'slots/CourseCardActionSlot';
|
||||||
import SelectSessionButton from './SelectSessionButton';
|
import SelectSessionButton from './SelectSessionButton';
|
||||||
import BeginCourseButton from './BeginCourseButton';
|
import BeginCourseButton from './BeginCourseButton';
|
||||||
import ResumeButton from './ResumeButton';
|
import ResumeButton from './ResumeButton';
|
||||||
@@ -19,7 +19,7 @@ jest.mock('hooks', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('plugin-slots/CourseCardActionSlot', () => 'CustomActionButton');
|
jest.mock('slots/CourseCardActionSlot', () => 'CustomActionButton');
|
||||||
jest.mock('./SelectSessionButton', () => 'SelectSessionButton');
|
jest.mock('./SelectSessionButton', () => 'SelectSessionButton');
|
||||||
jest.mock('./ViewCourseButton', () => 'ViewCourseButton');
|
jest.mock('./ViewCourseButton', () => 'ViewCourseButton');
|
||||||
jest.mock('./BeginCourseButton', () => 'BeginCourseButton');
|
jest.mock('./BeginCourseButton', () => 'BeginCourseButton');
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@openedx/frontend-base';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
beginCourse: {
|
beginCourse: {
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { MailtoLink, Hyperlink } from '@openedx/paragon';
|
import { MailtoLink, Hyperlink } from '@openedx/paragon';
|
||||||
import { CheckCircle } from '@openedx/paragon/icons';
|
import { CheckCircle } from '@openedx/paragon/icons';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { utilHooks, reduxHooks } from 'hooks';
|
import { utilHooks, reduxHooks } from '../../../../hooks';
|
||||||
import Banner from 'components/Banner';
|
import Banner from '../../../../components/Banner';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ export const CertificateBanner = ({ cardId }) => {
|
|||||||
if (certificate.isRestricted) {
|
if (certificate.isRestricted) {
|
||||||
return (
|
return (
|
||||||
<Banner variant="danger">
|
<Banner variant="danger">
|
||||||
{ supportEmail ? formatMessage(messages.certRestricted, { supportEmail: emailLink(supportEmail) }) : formatMessage(messages.certRestrictedNoEmail)}
|
{supportEmail ? formatMessage(messages.certRestricted, { supportEmail: emailLink(supportEmail) }) : formatMessage(messages.certRestrictedNoEmail)}
|
||||||
{isVerified && ' '}
|
{isVerified && ' '}
|
||||||
{isVerified && (billingEmail ? formatMessage(messages.certRefundContactBilling, { billingEmail: emailLink(billingEmail) }) : formatMessage(messages.certRefundContactBillingNoEmail))}
|
{isVerified && (billingEmail ? formatMessage(messages.certRefundContactBilling, { billingEmail: emailLink(billingEmail) }) : formatMessage(messages.certRefundContactBillingNoEmail))}
|
||||||
</Banner>
|
</Banner>
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Hyperlink } from '@openedx/paragon';
|
import { Hyperlink } from '@openedx/paragon';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
|
import { utilHooks, reduxHooks } from '../../../../hooks';
|
||||||
|
import Banner from '../../../../components/Banner';
|
||||||
|
|
||||||
import { utilHooks, reduxHooks } from 'hooks';
|
|
||||||
import Banner from 'components/Banner';
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
export const CourseBanner = ({ cardId }) => {
|
export const CourseBanner = ({ cardId }) => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { StrictDict } from 'utils';
|
import { StrictDict } from '../../../../../utils';
|
||||||
|
import { reduxHooks } from '../../../../../hooks';
|
||||||
import { reduxHooks } from 'hooks';
|
|
||||||
|
|
||||||
import ApprovedContent from './views/ApprovedContent';
|
import ApprovedContent from './views/ApprovedContent';
|
||||||
import EligibleContent from './views/EligibleContent';
|
import EligibleContent from './views/EligibleContent';
|
||||||
@@ -17,7 +16,9 @@ export const statusComponents = StrictDict({
|
|||||||
export const useCreditBannerData = (cardId) => {
|
export const useCreditBannerData = (cardId) => {
|
||||||
const credit = reduxHooks.useCardCreditData(cardId);
|
const credit = reduxHooks.useCardCreditData(cardId);
|
||||||
const { supportEmail } = reduxHooks.usePlatformSettingsData();
|
const { supportEmail } = reduxHooks.usePlatformSettingsData();
|
||||||
if (!credit.isEligible) { return null; }
|
if (!credit.isEligible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { error, purchased, requestStatus } = credit;
|
const { error, purchased, requestStatus } = credit;
|
||||||
let ContentComponent = EligibleContent;
|
let ContentComponent = EligibleContent;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import Banner from 'components/Banner';
|
import Banner from '../../../../../components/Banner';
|
||||||
|
|
||||||
import { MailtoLink } from '@openedx/paragon';
|
import { MailtoLink } from '@openedx/paragon';
|
||||||
import hooks from './hooks';
|
import hooks from './hooks';
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@openedx/frontend-base';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
error: {
|
error: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.error',
|
id: 'learner-dash.courseCard.banners.credit.error',
|
||||||
description: '',
|
description: 'Error message for credit transaction with support email link',
|
||||||
defaultMessage: 'An error occurred with this transaction. For help, contact {supportEmailLink}.',
|
defaultMessage: 'An error occurred with this transaction. For help, contact {supportEmailLink}.',
|
||||||
},
|
},
|
||||||
errorNoEmail: {
|
errorNoEmail: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.errorNoEmail',
|
id: 'learner-dash.courseCard.banners.credit.errorNoEmail',
|
||||||
description: '',
|
description: 'Error message for credit transaction without support email',
|
||||||
defaultMessage: 'An error occurred with this transaction.',
|
defaultMessage: 'An error occurred with this transaction.',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import MasqueradeUserContext from '../../../../../../data/contexts/MasqueradeUserContext';
|
||||||
|
import { reduxHooks } from '../../../../../../hooks';
|
||||||
import CreditContent from './components/CreditContent';
|
import CreditContent from './components/CreditContent';
|
||||||
import ProviderLink from './components/ProviderLink';
|
import ProviderLink from './components/ProviderLink';
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ import messages from './messages';
|
|||||||
|
|
||||||
export const ApprovedContent = ({ cardId }) => {
|
export const ApprovedContent = ({ cardId }) => {
|
||||||
const { providerStatusUrl: href, providerName } = reduxHooks.useCardCreditData(cardId);
|
const { providerStatusUrl: href, providerName } = reduxHooks.useCardCreditData(cardId);
|
||||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
const { isMasquerading } = useContext(MasqueradeUserContext);
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
return (
|
return (
|
||||||
<CreditContent
|
<CreditContent
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../../../../../hooks';
|
||||||
import track from 'tracking';
|
import track from '../../../../../../tracking';
|
||||||
|
|
||||||
import CreditContent from './components/CreditContent';
|
import CreditContent from './components/CreditContent';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import MasqueradeUserContext from '../../../../../../data/contexts/MasqueradeUserContext';
|
||||||
import CreditContent from './components/CreditContent';
|
import CreditContent from './components/CreditContent';
|
||||||
import ProviderLink from './components/ProviderLink';
|
import ProviderLink from './components/ProviderLink';
|
||||||
import hooks from './hooks';
|
import hooks from './hooks';
|
||||||
@@ -13,7 +13,7 @@ import messages from './messages';
|
|||||||
export const MustRequestContent = ({ cardId }) => {
|
export const MustRequestContent = ({ cardId }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { requestData, createCreditRequest } = hooks.useCreditRequestData(cardId);
|
const { requestData, createCreditRequest } = hooks.useCreditRequestData(cardId);
|
||||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
const { isMasquerading } = useContext(MasqueradeUserContext);
|
||||||
return (
|
return (
|
||||||
<CreditContent
|
<CreditContent
|
||||||
action={{
|
action={{
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import MasqueradeUserContext from '../../../../../../data/contexts/MasqueradeUserContext';
|
||||||
|
import { reduxHooks } from '../../../../../../hooks';
|
||||||
import CreditContent from './components/CreditContent';
|
import CreditContent from './components/CreditContent';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
export const PendingContent = ({ cardId }) => {
|
export const PendingContent = ({ cardId }) => {
|
||||||
const { providerStatusUrl: href, providerName } = reduxHooks.useCardCreditData(cardId);
|
const { providerStatusUrl: href, providerName } = reduxHooks.useCardCreditData(cardId);
|
||||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
const { isMasquerading } = useContext(MasqueradeUserContext);
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
return (
|
return (
|
||||||
<CreditContent
|
<CreditContent
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../../../../../hooks';
|
||||||
import CreditContent from './components/CreditContent';
|
import CreditContent from './components/CreditContent';
|
||||||
import ProviderLink from './components/ProviderLink';
|
import ProviderLink from './components/ProviderLink';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../../../../../../hooks';
|
||||||
import { Hyperlink } from '@openedx/paragon';
|
import { Hyperlink } from '@openedx/paragon';
|
||||||
|
|
||||||
export const ProviderLink = ({ cardId }) => {
|
export const ProviderLink = ({ cardId }) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { StrictDict } from 'utils';
|
import { StrictDict } from '../../../../../../utils';
|
||||||
import { apiHooks } from 'hooks';
|
import { apiHooks } from '../../../../../../hooks';
|
||||||
|
|
||||||
import * as module from './hooks';
|
import * as module from './hooks';
|
||||||
|
|
||||||
|
|||||||
@@ -1,59 +1,59 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@openedx/frontend-base';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
approved: {
|
approved: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.approved',
|
id: 'learner-dash.courseCard.banners.credit.approved',
|
||||||
description: '',
|
description: 'Message shown when credit request has been approved',
|
||||||
defaultMessage: '{congratulations} {providerName} has approved your request for course credit. To see your course credit, visit the {linkToProviderSite} website.',
|
defaultMessage: '{congratulations} {providerName} has approved your request for course credit. To see your course credit, visit the {linkToProviderSite} website.',
|
||||||
},
|
},
|
||||||
congratulations: {
|
congratulations: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.congratulations',
|
id: 'learner-dash.courseCard.banners.credit.congratulations',
|
||||||
description: '',
|
description: 'Congratulatory message for credit approval',
|
||||||
defaultMessage: 'Congratulations!',
|
defaultMessage: 'Congratulations!',
|
||||||
},
|
},
|
||||||
eligible: {
|
eligible: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.eligible',
|
id: 'learner-dash.courseCard.banners.credit.eligible',
|
||||||
description: '',
|
description: 'Message shown when user is eligible to purchase course credit',
|
||||||
defaultMessage: 'You have completed this course and are eligible to purchase course credit. Select {getCredit} to get started.',
|
defaultMessage: 'You have completed this course and are eligible to purchase course credit. Select {getCredit} to get started.',
|
||||||
},
|
},
|
||||||
eligibleFromProvider: {
|
eligibleFromProvider: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.eligibleFromProvider',
|
id: 'learner-dash.courseCard.banners.credit.eligibleFromProvider',
|
||||||
description: '',
|
description: 'Message shown when user is eligible for credit from a specific provider',
|
||||||
defaultMessage: 'You are now eligible for credit from {providerName}. Congratulations!',
|
defaultMessage: 'You are now eligible for credit from {providerName}. Congratulations!',
|
||||||
},
|
},
|
||||||
getCredit: {
|
getCredit: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.getCredit',
|
id: 'learner-dash.courseCard.banners.credit.getCredit',
|
||||||
description: '',
|
description: 'Button text for initiating the credit process',
|
||||||
defaultMessage: 'Get Credit',
|
defaultMessage: 'Get Credit',
|
||||||
},
|
},
|
||||||
mustRequest: {
|
mustRequest: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.mustRequest',
|
id: 'learner-dash.courseCard.banners.credit.mustRequest',
|
||||||
description: '',
|
description: 'Message shown after payment to instruct user to request credit',
|
||||||
defaultMessage: 'Thank you for your payment. To receive course credit, you must request credit at the {linkToProviderSite} website. Select {requestCredit} to get started',
|
defaultMessage: 'Thank you for your payment. To receive course credit, you must request credit at the {linkToProviderSite} website. Select {requestCredit} to get started',
|
||||||
},
|
},
|
||||||
received: {
|
received: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.received',
|
id: 'learner-dash.courseCard.banners.credit.received',
|
||||||
description: '',
|
description: 'Message shown when credit request has been received',
|
||||||
defaultMessage: '{providerName} has received your course credit request. We will update you when credit processing is complete.',
|
defaultMessage: '{providerName} has received your course credit request. We will update you when credit processing is complete.',
|
||||||
},
|
},
|
||||||
rejected: {
|
rejected: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.rejected',
|
id: 'learner-dash.courseCard.banners.credit.rejected',
|
||||||
description: '',
|
description: 'Message shown when credit request has been rejected',
|
||||||
defaultMessage: '{providerName} did not approve your request for course credit. For more information, contact {linkToProviderSite} directly.',
|
defaultMessage: '{providerName} did not approve your request for course credit. For more information, contact {linkToProviderSite} directly.',
|
||||||
},
|
},
|
||||||
requestCredit: {
|
requestCredit: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.requestCredit',
|
id: 'learner-dash.courseCard.banners.credit.requestCredit',
|
||||||
description: '',
|
description: 'Button text for requesting credit',
|
||||||
defaultMessage: 'Request Credit',
|
defaultMessage: 'Request Credit',
|
||||||
},
|
},
|
||||||
viewCredit: {
|
viewCredit: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.viewCredit',
|
id: 'learner-dash.courseCard.banners.credit.viewCredit',
|
||||||
description: '',
|
description: 'Button text for viewing credit details',
|
||||||
defaultMessage: 'View Credit',
|
defaultMessage: 'View Credit',
|
||||||
},
|
},
|
||||||
viewDetails: {
|
viewDetails: {
|
||||||
id: 'learner-dash.courseCard.banners.credit.viewDetails',
|
id: 'learner-dash.courseCard.banners.credit.viewDetails',
|
||||||
description: '',
|
description: 'Button text for viewing credit request details',
|
||||||
defaultMessage: 'View Details',
|
defaultMessage: 'View Details',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
import { Button, MailtoLink } from '@openedx/paragon';
|
import { Button, MailtoLink } from '@openedx/paragon';
|
||||||
|
|
||||||
import { utilHooks, reduxHooks } from 'hooks';
|
import { utilHooks, reduxHooks } from '../../../../hooks';
|
||||||
|
import Banner from '../../../../components/Banner';
|
||||||
|
|
||||||
import Banner from 'components/Banner';
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
export const EntitlementBanner = ({ cardId }) => {
|
export const EntitlementBanner = ({ cardId }) => {
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Program } from '@openedx/paragon/icons';
|
import { Program } from '@openedx/paragon/icons';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../../../../hooks';
|
||||||
import Banner from 'components/Banner';
|
import Banner from '../../../../../components/Banner';
|
||||||
|
|
||||||
import ProgramList from './ProgramsList';
|
import ProgramList from './ProgramsList';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from '@edx/react-unit-test-utils';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../../../../hooks';
|
||||||
import RelatedProgramsBanner from '.';
|
import RelatedProgramsBanner from '.';
|
||||||
|
|
||||||
jest.mock('./ProgramsList', () => 'ProgramsList');
|
jest.mock('./ProgramsList', () => 'ProgramsList');
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@openedx/frontend-base';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
relatedPrograms: {
|
relatedPrograms: {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../../../hooks';
|
||||||
|
import CourseBannerSlot from '../../../../slots/CourseBannerSlot';
|
||||||
|
|
||||||
import CourseBannerSlot from 'plugin-slots/CourseBannerSlot';
|
|
||||||
import CertificateBanner from './CertificateBanner';
|
import CertificateBanner from './CertificateBanner';
|
||||||
import CreditBanner from './CreditBanner';
|
import CreditBanner from './CreditBanner';
|
||||||
import EntitlementBanner from './EntitlementBanner';
|
import EntitlementBanner from './EntitlementBanner';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@openedx/frontend-base';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
auditAccessExpired: {
|
auditAccessExpired: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
import { utilHooks, reduxHooks } from 'hooks';
|
import { utilHooks, reduxHooks } from '../../../../hooks';
|
||||||
|
|
||||||
import * as hooks from './hooks';
|
import * as hooks from './hooks';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
@@ -10,8 +10,10 @@ export const useAccessMessage = ({ cardId }) => {
|
|||||||
const courseRun = reduxHooks.useCardCourseRunData(cardId);
|
const courseRun = reduxHooks.useCardCourseRunData(cardId);
|
||||||
const formatDate = utilHooks.useFormatDate();
|
const formatDate = utilHooks.useFormatDate();
|
||||||
if (!courseRun.isStarted) {
|
if (!courseRun.isStarted) {
|
||||||
if (!courseRun.startDate && !courseRun.advertisedStart) { return null; }
|
if (!courseRun.startDate && !courseRun.advertisedStart) {
|
||||||
const startDate = courseRun.advertisedStart ? courseRun.advertisedStart : formatDate(courseRun.startDate);
|
return null;
|
||||||
|
}
|
||||||
|
const startDate = courseRun.advertisedStart ?? formatDate(courseRun.startDate);
|
||||||
return formatMessage(messages.courseStarts, { startDate });
|
return formatMessage(messages.courseStarts, { startDate });
|
||||||
}
|
}
|
||||||
if (enrollment.isEnrolled) {
|
if (enrollment.isEnrolled) {
|
||||||
@@ -27,7 +29,9 @@ export const useAccessMessage = ({ cardId }) => {
|
|||||||
{ accessExpirationDate: formatDate(accessExpirationDate) },
|
{ accessExpirationDate: formatDate(accessExpirationDate) },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!endDate) { return null; }
|
if (!endDate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return formatMessage(
|
return formatMessage(
|
||||||
isArchived ? messages.courseEnded : messages.courseEnds,
|
isArchived ? messages.courseEnded : messages.courseEnds,
|
||||||
{ endDate: formatDate(endDate) },
|
{ endDate: formatDate(endDate) },
|
||||||
@@ -49,7 +53,7 @@ export const useCardDetailsData = ({ cardId }) => {
|
|||||||
const openSessionModal = reduxHooks.useUpdateSelectSessionModalCallback(cardId);
|
const openSessionModal = reduxHooks.useUpdateSelectSessionModalCallback(cardId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
providerName: providerName || formatMessage(messages.unknownProviderName),
|
providerName: providerName ?? formatMessage(messages.unknownProviderName),
|
||||||
accessMessage: hooks.useAccessMessage({ cardId }),
|
accessMessage: hooks.useAccessMessage({ cardId }),
|
||||||
isEntitlement,
|
isEntitlement,
|
||||||
isFulfilled,
|
isFulfilled,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { keyStore } from 'utils';
|
import { keyStore } from 'utils';
|
||||||
import { utilHooks, reduxHooks } from 'hooks';
|
import { utilHooks, reduxHooks } from 'hooks';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@openedx/frontend-base';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
accessExpired: {
|
accessExpired: {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { Badge } from '@openedx/paragon';
|
import { Badge } from '@openedx/paragon';
|
||||||
|
|
||||||
import track from 'tracking';
|
import track from '../../../tracking';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../../hooks';
|
||||||
import verifiedRibbon from 'assets/verified-ribbon.png';
|
import verifiedRibbon from '../../../assets/verified-ribbon.png';
|
||||||
import useActionDisabledState from './hooks';
|
import useActionDisabledState from './hooks';
|
||||||
|
|
||||||
import messages from '../messages';
|
import messages from '../messages';
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import PropTypes from 'prop-types';
|
|||||||
import * as ReactShare from 'react-share';
|
import * as ReactShare from 'react-share';
|
||||||
|
|
||||||
import { StrictDict } from '@edx/react-unit-test-utils';
|
import { StrictDict } from '@edx/react-unit-test-utils';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
import { Dropdown } from '@openedx/paragon';
|
import { Dropdown } from '@openedx/paragon';
|
||||||
|
|
||||||
import track from 'tracking';
|
import MasqueradeUserContext from '../../../../data/contexts/MasqueradeUserContext';
|
||||||
import { reduxHooks } from 'hooks';
|
import track from '../../../../tracking';
|
||||||
|
import { reduxHooks } from '../../../../hooks';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ export const SocialShareMenu = ({ cardId, emailSettings }) => {
|
|||||||
const { courseName } = reduxHooks.useCardCourseData(cardId);
|
const { courseName } = reduxHooks.useCardCourseData(cardId);
|
||||||
const { isEmailEnabled, isExecEd2UCourse } = reduxHooks.useCardEnrollmentData(cardId);
|
const { isEmailEnabled, isExecEd2UCourse } = reduxHooks.useCardEnrollmentData(cardId);
|
||||||
const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId);
|
const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId);
|
||||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
const { isMasquerading } = useContext(MasqueradeUserContext);
|
||||||
|
|
||||||
const handleTwitterShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'twitter');
|
const handleTwitterShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'twitter');
|
||||||
const handleFacebookShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'facebook');
|
const handleFacebookShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'facebook');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { when } from 'jest-when';
|
import { when } from 'jest-when';
|
||||||
import * as ReactShare from 'react-share';
|
import * as ReactShare from 'react-share';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
import { formatMessage, shallow } from '@edx/react-unit-test-utils';
|
import { formatMessage, shallow } from '@edx/react-unit-test-utils';
|
||||||
|
|
||||||
import track from 'tracking';
|
import track from 'tracking';
|
||||||
@@ -20,8 +20,8 @@ jest.mock('tracking', () => ({
|
|||||||
socialShare: 'test-social-share-key',
|
socialShare: 'test-social-share-key',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/i18n', () => ({
|
jest.mock('@openedx/frontend-base', () => ({
|
||||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
...jest.requireActual('@openedx/frontend-base'),
|
||||||
useIntl: jest.fn().mockReturnValue({
|
useIntl: jest.fn().mockReturnValue({
|
||||||
formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage,
|
formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useKeyedState, StrictDict } from '@edx/react-unit-test-utils';
|
import { useKeyedState, StrictDict } from '@edx/react-unit-test-utils';
|
||||||
|
|
||||||
import track from 'tracking';
|
import track from '../../../../tracking';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../../../hooks';
|
||||||
|
|
||||||
export const stateKeys = StrictDict({
|
export const stateKeys = StrictDict({
|
||||||
isUnenrollConfirmVisible: 'isUnenrollConfirmVisible',
|
isUnenrollConfirmVisible: 'isUnenrollConfirmVisible',
|
||||||
@@ -32,7 +32,9 @@ export const useHandleToggleDropdown = (cardId) => {
|
|||||||
cardId,
|
cardId,
|
||||||
);
|
);
|
||||||
return (isOpen) => {
|
return (isOpen) => {
|
||||||
if (isOpen) { trackCourseEvent(); }
|
if (isOpen) {
|
||||||
|
trackCourseEvent();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import React from 'react';
|
import { useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
import { Dropdown, Icon, IconButton } from '@openedx/paragon';
|
import { Dropdown, Icon, IconButton } from '@openedx/paragon';
|
||||||
import { MoreVert } from '@openedx/paragon/icons';
|
import { MoreVert } from '@openedx/paragon/icons';
|
||||||
import { StrictDict } from '@edx/react-unit-test-utils';
|
import { StrictDict } from '@edx/react-unit-test-utils';
|
||||||
|
|
||||||
import EmailSettingsModal from 'containers/EmailSettingsModal';
|
import MasqueradeUserContext from '../../../../data/contexts/MasqueradeUserContext';
|
||||||
import UnenrollConfirmModal from 'containers/UnenrollConfirmModal';
|
import EmailSettingsModal from '../../../../containers/EmailSettingsModal';
|
||||||
import { reduxHooks } from 'hooks';
|
import UnenrollConfirmModal from '../../../../containers/UnenrollConfirmModal';
|
||||||
|
import { reduxHooks } from '../../../../hooks';
|
||||||
|
|
||||||
import SocialShareMenu from './SocialShareMenu';
|
import SocialShareMenu from './SocialShareMenu';
|
||||||
import {
|
import {
|
||||||
useEmailSettings,
|
useEmailSettings,
|
||||||
@@ -30,7 +32,7 @@ export const CourseCardMenu = ({ cardId }) => {
|
|||||||
const unenrollModal = useUnenrollData();
|
const unenrollModal = useUnenrollData();
|
||||||
const handleToggleDropdown = useHandleToggleDropdown(cardId);
|
const handleToggleDropdown = useHandleToggleDropdown(cardId);
|
||||||
const { shouldShowUnenrollItem, shouldShowDropdown } = useOptionVisibility(cardId);
|
const { shouldShowUnenrollItem, shouldShowDropdown } = useOptionVisibility(cardId);
|
||||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
const { isMasquerading } = useContext(MasqueradeUserContext);
|
||||||
const { isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId);
|
const { isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId);
|
||||||
|
|
||||||
if (!shouldShowDropdown) {
|
if (!shouldShowDropdown) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { when } from 'jest-when';
|
|||||||
|
|
||||||
import { Dropdown } from '@openedx/paragon';
|
import { Dropdown } from '@openedx/paragon';
|
||||||
import { shallow } from '@edx/react-unit-test-utils';
|
import { shallow } from '@edx/react-unit-test-utils';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import EmailSettingsModal from 'containers/EmailSettingsModal';
|
import EmailSettingsModal from 'containers/EmailSettingsModal';
|
||||||
import UnenrollConfirmModal from 'containers/UnenrollConfirmModal';
|
import UnenrollConfirmModal from 'containers/UnenrollConfirmModal';
|
||||||
@@ -11,8 +11,8 @@ import SocialShareMenu from './SocialShareMenu';
|
|||||||
import * as hooks from './hooks';
|
import * as hooks from './hooks';
|
||||||
import CourseCardMenu, { testIds } from '.';
|
import CourseCardMenu, { testIds } from '.';
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/i18n', () => ({
|
jest.mock('@openedx/frontend-base', () => ({
|
||||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
...jest.requireActual('@openedx/frontend-base'),
|
||||||
useIntl: jest.fn().mockReturnValue({
|
useIntl: jest.fn().mockReturnValue({
|
||||||
formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage,
|
formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@openedx/frontend-base';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unenroll: {
|
unenroll: {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import track from 'tracking';
|
import track from '../../../tracking';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../../hooks';
|
||||||
import useActionDisabledState from './hooks';
|
import useActionDisabledState from './hooks';
|
||||||
|
|
||||||
const { courseTitleClicked } = track.course;
|
const { courseTitleClicked } = track.course;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { StrictDict } from 'utils';
|
import { StrictDict } from 'utils';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { MockUseState } from 'testUtils';
|
import { MockUseState } from 'testUtils';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { reduxHooks } from 'hooks';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
|
import MasqueradeUserContext from '../../../data/contexts/MasqueradeUserContext';
|
||||||
|
import { reduxHooks } from '../../../hooks';
|
||||||
|
|
||||||
export const useActionDisabledState = (cardId) => {
|
export const useActionDisabledState = (cardId) => {
|
||||||
const { isMasquerading } = reduxHooks.useMasqueradeData();
|
const { isMasquerading } = useContext(MasqueradeUserContext);
|
||||||
const {
|
const {
|
||||||
hasAccess, isAudit, isAuditAccessExpired,
|
hasAccess, isAudit, isAuditAccessExpired,
|
||||||
} = reduxHooks.useCardEnrollmentData(cardId);
|
} = reduxHooks.useCardEnrollmentData(cardId);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
import { useWindowSize, breakpoints } from '@openedx/paragon';
|
import { useWindowSize, breakpoints } from '@openedx/paragon';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../hooks';
|
||||||
|
|
||||||
export const useIsCollapsed = () => {
|
export const useIsCollapsed = () => {
|
||||||
const { width } = useWindowSize();
|
const { width } = useWindowSize();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from 'hooks';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@openedx/frontend-base';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
bannerAlt: {
|
bannerAlt: {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { Button, Chip } from '@openedx/paragon';
|
import { Button, Chip } from '@openedx/paragon';
|
||||||
import { CloseSmall } from '@openedx/paragon/icons';
|
import { CloseSmall } from '@openedx/paragon/icons';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../hooks';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
} from '@openedx/paragon';
|
} from '@openedx/paragon';
|
||||||
import { Close, Tune } from '@openedx/paragon/icons';
|
import { Close, Tune } from '@openedx/paragon/icons';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../hooks';
|
||||||
|
|
||||||
import FilterForm from './components/FilterForm';
|
import FilterForm from './components/FilterForm';
|
||||||
import SortForm from './components/SortForm';
|
import SortForm from './components/SortForm';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { Form } from '@openedx/paragon';
|
import { Form } from '@openedx/paragon';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { FilterKeys } from 'data/constants/app';
|
import { FilterKeys } from '../../../data/constants/app';
|
||||||
|
|
||||||
import { Form } from '@openedx/paragon';
|
import { Form } from '@openedx/paragon';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { SortKeys } from 'data/constants/app';
|
import { SortKeys } from '../../../data/constants/app';
|
||||||
|
|
||||||
import { Form } from '@openedx/paragon';
|
import { Form } from '@openedx/paragon';
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useToggle } from '@openedx/paragon';
|
import { useToggle } from '@openedx/paragon';
|
||||||
|
|
||||||
import { StrictDict } from 'utils';
|
import { StrictDict } from '../../utils';
|
||||||
import track from 'tracking';
|
import track from '../../tracking';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../hooks';
|
||||||
|
|
||||||
import * as module from './hooks';
|
import * as module from './hooks';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@openedx/frontend-base';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
courseStatus: {
|
courseStatus: {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import PropTypes from 'prop-types';
|
|||||||
import { Pagination } from '@openedx/paragon';
|
import { Pagination } from '@openedx/paragon';
|
||||||
import {
|
import {
|
||||||
ActiveCourseFilters,
|
ActiveCourseFilters,
|
||||||
} from 'containers/CourseFilterControls';
|
} from '../../../containers/CourseFilterControls';
|
||||||
import CourseCard from 'containers/CourseCard';
|
import CourseCard from '../../../containers/CourseCard';
|
||||||
|
|
||||||
import { useIsCollapsed } from './hooks';
|
import { useIsCollapsed } from './hooks';
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
import { Button, Image } from '@openedx/paragon';
|
import { Button, Image } from '@openedx/paragon';
|
||||||
import { Search } from '@openedx/paragon/icons';
|
import { Search } from '@openedx/paragon/icons';
|
||||||
import { baseAppUrl } from 'data/services/lms/urls';
|
|
||||||
|
|
||||||
import emptyCourseSVG from 'assets/empty-course.svg';
|
import { baseAppUrl } from '../../../data/services/lms/urls';
|
||||||
import { reduxHooks } from 'hooks';
|
import emptyCourseSVG from '../../../assets/empty-course.svg';
|
||||||
|
import { reduxHooks } from '../../../hooks';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@openedx/frontend-base';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
lookingForChallengePrompt: {
|
lookingForChallengePrompt: {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { ListPageSize, SortKeys } from 'data/constants/app';
|
import { ListPageSize, SortKeys } from '../../data/constants/app';
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../hooks';
|
||||||
import { StrictDict } from 'utils';
|
import { StrictDict } from '../../utils';
|
||||||
|
|
||||||
import * as module from './hooks';
|
import * as module from './hooks';
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
|
|
||||||
import { reduxHooks } from 'hooks';
|
import { reduxHooks } from '../../hooks';
|
||||||
import {
|
import {
|
||||||
CourseFilterControls,
|
CourseFilterControls,
|
||||||
} from 'containers/CourseFilterControls';
|
} from '../../containers/CourseFilterControls';
|
||||||
import CourseListSlot from 'plugin-slots/CourseListSlot';
|
import CourseListSlot from '../../slots/CourseListSlot';
|
||||||
import NoCoursesViewSlot from 'plugin-slots/NoCoursesViewSlot';
|
import NoCoursesViewSlot from '../../slots/NoCoursesViewSlot';
|
||||||
|
|
||||||
import { useCourseListData } from './hooks';
|
import { useCourseListData } from './hooks';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@openedx/frontend-base';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
myCourses: {
|
myCourses: {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { Container, Col, Row } from '@openedx/paragon';
|
import { Container, Col, Row } from '@openedx/paragon';
|
||||||
|
|
||||||
import WidgetSidebarSlot from 'plugin-slots/WidgetSidebarSlot';
|
import WidgetSidebarSlot from '../../slots/WidgetSidebarSlot';
|
||||||
|
|
||||||
import hooks from './hooks';
|
import hooks from './hooks';
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,19 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useWindowSize, breakpoints } from '@openedx/paragon';
|
import { useWindowSize, breakpoints } from '@openedx/paragon';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@openedx/frontend-base';
|
||||||
import { apiHooks } from 'hooks';
|
import { StrictDict } from '../../utils';
|
||||||
import { StrictDict } from 'utils';
|
import appMessages from '../../messages';
|
||||||
|
|
||||||
import appMessages from 'messages';
|
|
||||||
import * as module from './hooks';
|
import * as module from './hooks';
|
||||||
|
|
||||||
export const state = StrictDict({
|
export const state = StrictDict({
|
||||||
sidebarShowing: (val) => React.useState(val), // eslint-disable-line
|
sidebarShowing: (val) => React.useState(val), // eslint-disable-line
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useInitializeDashboard = () => {
|
|
||||||
const initialize = apiHooks.useInitializeApp();
|
|
||||||
React.useEffect(() => { initialize(); }, []); // eslint-disable-line
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useDashboardMessages = () => {
|
export const useDashboardMessages = () => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
return {
|
return {
|
||||||
spinnerScreenReaderText: formatMessage(appMessages.loadingSR),
|
spinnerScreenReaderText: formatMessage(appMessages['learner-dash.loadingSR']),
|
||||||
pageTitle: formatMessage(appMessages.pageTitle),
|
pageTitle: formatMessage(appMessages['learner-dash.title']),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,6 +30,5 @@ export const useDashboardLayoutData = () => {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
useDashboardLayoutData,
|
useDashboardLayoutData,
|
||||||
useInitializeDashboard,
|
|
||||||
useDashboardMessages,
|
useDashboardMessages,
|
||||||
};
|
};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user