Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
616c620432 | ||
|
|
fb508152f3 | ||
|
|
e68c280b11 | ||
|
|
cb738286ed | ||
|
|
cd24d0da8d | ||
|
|
2f9f573698 | ||
|
|
563cd4b8df | ||
|
|
25911bdfeb | ||
|
|
179fbaa452 | ||
|
|
73df912f29 | ||
|
|
d64e497407 | ||
|
|
3725a36b30 | ||
|
|
3cd6999886 | ||
|
|
3ff2a0a359 | ||
|
|
5b0f79f6f9 | ||
|
|
d3490985d2 | ||
|
|
82300bf99a | ||
|
|
c096bf3d28 | ||
|
|
ac885e49d7 | ||
|
|
c925666501 | ||
|
|
c3e30e8163 | ||
|
|
d5d2279797 | ||
|
|
8bb03e1404 | ||
|
|
d84ef68648 | ||
|
|
ba9cec39cf | ||
|
|
a13da63d25 | ||
|
|
22dd41f4ee | ||
|
|
f38a03b9ce | ||
|
|
04131d1636 | ||
|
|
2aab012b4d | ||
|
|
9f27335d2b | ||
|
|
cdac04a0d7 | ||
|
|
0b58870714 | ||
|
|
106cba1785 | ||
|
|
820768367f | ||
|
|
18df696902 | ||
|
|
f76383f034 | ||
|
|
f0ddf3bf09 | ||
|
|
11ac45f6b8 | ||
|
|
67c23630b9 | ||
|
|
0ef787b9ce | ||
|
|
c3e3024971 | ||
|
|
a95bf5006b | ||
|
|
517f3f57bf | ||
|
|
2e464498d8 | ||
|
|
3f519aa678 | ||
|
|
5b7a29422b | ||
|
|
f3d9f2ae06 | ||
|
|
b5607e9b83 | ||
|
|
fd9b393bc7 | ||
|
|
0fe5354047 | ||
|
|
71923503c4 | ||
|
|
f1a620a784 | ||
|
|
645f378600 | ||
|
|
057a39085c | ||
|
|
3f31aba5a7 | ||
|
|
321448c674 | ||
|
|
77c9984d34 | ||
|
|
c9d0a7f86f | ||
|
|
04d51ffee1 | ||
|
|
25f6b86e92 | ||
|
|
c700a47e40 | ||
|
|
db4b082247 | ||
|
|
919677b544 | ||
|
|
32f0f33975 | ||
|
|
0662177f40 | ||
|
|
f266bba04c | ||
|
|
20e7bb09bb | ||
|
|
da7e055d77 | ||
|
|
bee2e54b87 | ||
|
|
1b179f55d1 | ||
|
|
edef77f101 | ||
|
|
e4f1a6100a | ||
|
|
b3869b97f7 | ||
|
|
cd339d491d |
6
.env
6
.env
@@ -2,7 +2,6 @@ NODE_ENV=null
|
||||
ACCESS_TOKEN_COOKIE_NAME=null
|
||||
BASE_URL=null
|
||||
CREDENTIALS_BASE_URL=null
|
||||
CSRF_COOKIE_NAME=null
|
||||
CSRF_TOKEN_API_PATH=null
|
||||
ECOMMERCE_BASE_URL=null
|
||||
LANGUAGE_PREFERENCE_COOKIE_NAME=null
|
||||
@@ -17,6 +16,7 @@ SITE_NAME=null
|
||||
USER_INFO_COOKIE_NAME=null
|
||||
APPLE_APP_STORE_URL=null
|
||||
CONTACT_URL=null
|
||||
ENTERPRISE_LEARNER_PORTAL_HOSTNAME=null
|
||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM=null
|
||||
ENTERPRISE_MARKETING_URL=null
|
||||
ENTERPRISE_MARKETING_UTM_CAMPAIGN=null
|
||||
@@ -31,3 +31,7 @@ SUPPORT_URL=null
|
||||
TERMS_OF_SERVICE_URL=null
|
||||
TWITTER_URL=null
|
||||
YOU_TUBE_URL=null
|
||||
LOGO_URL=''
|
||||
LOGO_TRADEMARK_URL=''
|
||||
LOGO_WHITE_URL=''
|
||||
FAVICON_URL=''
|
||||
|
||||
@@ -3,13 +3,12 @@ PORT=1995
|
||||
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
||||
BASE_URL='localhost:1995'
|
||||
CREDENTIALS_BASE_URL='http://localhost:18150'
|
||||
CSRF_COOKIE_NAME='csrftoken'
|
||||
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
||||
ECOMMERCE_BASE_URL='http://localhost:18130'
|
||||
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
||||
LMS_BASE_URL='http://localhost:18000'
|
||||
LOGIN_URL='http://localhost:18000/login'
|
||||
LOGOUT_URL='http://localhost:18000/login'
|
||||
LOGOUT_URL='http://localhost:18000/logout'
|
||||
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||
ORDER_HISTORY_URL='localhost:1996/orders'
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||
@@ -18,6 +17,7 @@ SITE_NAME='edX'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
APPLE_APP_STORE_URL='https://www.apple.com/ios/app-store/'
|
||||
CONTACT_URL='http://localhost:18000/contact'
|
||||
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='http://localhost:8080'
|
||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
|
||||
ENTERPRISE_MARKETING_URL='http://example.com'
|
||||
ENTERPRISE_MARKETING_UTM_CAMPAIGN='my_campaign'
|
||||
@@ -32,3 +32,7 @@ SUPPORT_URL='http://localhost:18000/support'
|
||||
TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service'
|
||||
TWITTER_URL='https://twitter.com'
|
||||
YOU_TUBE_URL='https://www.youtube.com'
|
||||
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
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
||||
BASE_URL='localhost:1995'
|
||||
CREDENTIALS_BASE_URL='http://localhost:18150'
|
||||
CSRF_COOKIE_NAME='csrftoken'
|
||||
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
||||
ECOMMERCE_BASE_URL='http://localhost:18130'
|
||||
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
||||
LMS_BASE_URL='http://localhost:18000'
|
||||
LOGIN_URL='http://localhost:18000/login'
|
||||
LOGOUT_URL='http://localhost:18000/login'
|
||||
LOGOUT_URL='http://localhost:18000/logout'
|
||||
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||
ORDER_HISTORY_URL='localhost:1996/orders'
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||
SEGMENT_KEY=null
|
||||
SITE_NAME='edX'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
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
|
||||
|
||||
3
.eslintrc.js
Normal file
3
.eslintrc.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const { createConfig } = require('@edx/frontend-build');
|
||||
|
||||
module.exports = createConfig('eslint');
|
||||
@@ -1,11 +1,9 @@
|
||||
.tx
|
||||
coverage
|
||||
dist
|
||||
footer
|
||||
node_modules
|
||||
public
|
||||
src
|
||||
webpack
|
||||
.dockerignore
|
||||
.eslintignore
|
||||
.eslintrc
|
||||
|
||||
60
README.rst
60
README.rst
@@ -3,22 +3,58 @@
|
||||
frontend-app-profile
|
||||
====================
|
||||
|
||||
Please tag **@edx/arch-fed** on any PRs or issues.
|
||||
This is a micro-frontend application responsible for the display and updating of user profiles. Please tag **@edx/arch-fed** on any PRs or issues.
|
||||
|
||||
Introduction
|
||||
------------
|
||||
When a user views their own profile, they're given fields to edit their full name, location, primary spoken language, education, social links, and bio. Each field also has a dropdown to select the visibility of that field - i.e., whether it can be viewed by other learners.
|
||||
|
||||
React app for user account management.
|
||||
When a user views someone else's profile, they see all those fields that that user set as public.
|
||||
|
||||
Important Note
|
||||
--------------
|
||||
----------
|
||||
|
||||
The production Webpack configuration for this repo uses `Purgecss <https://www.purgecss.com/>`_
|
||||
to remove unused CSS from the production css file. In webpack/webpack.prod.config.js the Purgecss
|
||||
plugin is configured to scan directories to determine what css selectors should remain. Currently
|
||||
the src/ directory is scanned along with all @edx/frontend-component* node modules and paragon.
|
||||
If you add and use a component in this repo that relies on HTML classes or ids for styling you
|
||||
must add it to the Purgecss configuration or it will be unstyled in the production build.
|
||||
Development
|
||||
-----------
|
||||
|
||||
Start Devstack
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
To use this application `devstack <https://github.com/edx/devstack>`__ must be running and you must be logged into it.
|
||||
|
||||
- Start devstack
|
||||
- Log in (http://localhost:18000/login)
|
||||
|
||||
Start the development server
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In this project, install requirements and start the development server by running:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
npm install
|
||||
npm start # The server will run on port 1995
|
||||
|
||||
Once the dev server is up visit http://localhost:1995/u/staff.
|
||||
|
||||
----------
|
||||
|
||||
Configuration and Deployment
|
||||
----------------------------
|
||||
|
||||
This MFE is configured via node environment variables supplied at build time. See the .env file for the list of required environment variables. Example build syntax with a single environment variable:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
NODE_ENV=production ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' npm run build
|
||||
|
||||
|
||||
For more information see the document: `Micro-frontend applications in Open
|
||||
edX <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/micro-frontends-in-open-edx.html>`__.
|
||||
|
||||
----------
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
The production Webpack configuration for this repo uses `Purgecss <https://www.purgecss.com/>`__ to remove unused CSS from the production css file. In ``webpack.prod.config.js`` the Purgecss plugin is configured to scan directories to determine what css selectors should remain. Currently the src/ directory is scanned along with all ``@edx/frontend-component*`` node modules and ``@edx/paragon``. **If you add and use a component in this repo that relies on HTML classes or ids for styling you must add it to the Purgecss configuration or it will be unstyled in the production build.**
|
||||
|
||||
.. |Build Status| image:: https://api.travis-ci.org/edx/frontend-app-profile.svg?branch=master
|
||||
:target: https://travis-ci.org/edx/frontend-app-profile
|
||||
|
||||
3
commitlint.config.js
Normal file
3
commitlint.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-angular'],
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
2. Build time customization using NPM aliases
|
||||
---------------------------------------------
|
||||
|
||||
Status
|
||||
------
|
||||
|
||||
Accepted
|
||||
|
||||
Context
|
||||
-------
|
||||
|
||||
Frontend applications created throughout FY2019 contain hardcoded edX brand specific elements
|
||||
such as the site's header, footer, logo, visual style, and navigational links. This enabled
|
||||
teams to move more quickly in efforts to adopt a micro-frontend architecture to enable more
|
||||
rapid UI innovation in the future. There was no easy path for these new applications to be
|
||||
incorporated into the Open edX platform where they need to be branded properly.
|
||||
|
||||
Decision
|
||||
--------
|
||||
|
||||
In order to make frontend applications brand agnostic, we will split branded elements into
|
||||
npm packages such as ``frontend-component-header``. Frontend applications will expect to
|
||||
find components or other exports according to a defined interface. Package interfaces will
|
||||
be defined in the README for each npm package repository.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// exports React component as default and a 'messages' object for i18n
|
||||
import Header, { messages } from '@edx/frontend-component-header';
|
||||
|
||||
To build a frontend application for a specific brand (edX or other Open edX implementation) we
|
||||
will leverage npm aliases to override these npm packages with branded packages that implement the
|
||||
same interface before build. For example, ``frontend-component-header`` will be overriden with
|
||||
``frontend-component-header-edx``. This is done using the
|
||||
`npm alias syntax introduced in version 6.9.0`_.
|
||||
|
||||
We install aliases using the syntax below:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# npm install <package-name>@<type>:<branded-package>
|
||||
|
||||
# npm package
|
||||
npm install @edx/frontend-component-header@npm:@edx/frontend-component-header-edx@latest
|
||||
|
||||
# git repository
|
||||
npm install @edx/frontend-component-header@git:https://github.com/edx/frontend-component-header-edx.git
|
||||
|
||||
# local folder
|
||||
npm install @edx/frontend-component-header@file:../path/to/local/module/during/build
|
||||
|
||||
After installing overrides using npm aliases, we build the project normally:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
npm run build
|
||||
|
||||
Using this mechanism branded packages are substituted for the default unbranded packages at build
|
||||
and deployment time.
|
||||
|
||||
.. _npm alias syntax introduced in version 6.9.0: https://github.com/npm/rfcs/blob/latest/implemented/0001-package-aliases.md
|
||||
|
||||
Consequences
|
||||
------------
|
||||
|
||||
One drawback of this process is the inability to automatically test substituted packages before
|
||||
deployment. This is a risk we are willing to accept until it becomes an issue.
|
||||
|
||||
edX has built a deployment pipeline that will read configuration for npm packages to override
|
||||
and alias. This code is open source but heavily catered to edX's specific deployment needs.
|
||||
The Open edX community will need to collaborate on a simpler, less opinionated build and
|
||||
deployment process for micro-frontend applications.
|
||||
@@ -1,6 +1,7 @@
|
||||
# This file describes this Open edX repo, as described in OEP-2:
|
||||
# http://open-edx-proposals.readthedocs.io/en/latest/oeps/oep-0002.html#specification
|
||||
|
||||
nick: acct
|
||||
nick: prof
|
||||
oeps: {}
|
||||
owner: edx/arch-team
|
||||
openedx-release: {ref: master}
|
||||
|
||||
16039
package-lock.json
generated
16039
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
68
package.json
Executable file → Normal file
68
package.json
Executable file → Normal file
@@ -1,22 +1,20 @@
|
||||
{
|
||||
"name": "@edx/frontend-app-profile",
|
||||
"version": "1.0.0-semantically-released",
|
||||
"description": "User profile micro-frontend for edX",
|
||||
"description": "User profile micro-frontend for Open edX",
|
||||
"author": "edX",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "npm-dist/index.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/edx/frontend-app-profile.git"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 versions",
|
||||
"ie 11"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "fedx-scripts webpack",
|
||||
"npm-build": "make npm-build",
|
||||
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
|
||||
"is-es5": "es-check es5 ./dist/*.js",
|
||||
"lint": "fedx-scripts eslint",
|
||||
"precommit": "npm run lint",
|
||||
"snapshot": "fedx-scripts jest --updateSnapshot",
|
||||
"start": "fedx-scripts webpack-dev-server --progress",
|
||||
"test": "fedx-scripts jest --coverage --passWithNoTests"
|
||||
@@ -30,35 +28,32 @@
|
||||
"commit-msg": "commitlint -e $GIT_PARAMS"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/edx/frontend-app-profile.git"
|
||||
},
|
||||
"author": "edX",
|
||||
"license": "AGPL-3.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/edx/frontend-app-profile/issues"
|
||||
},
|
||||
"homepage": "https://github.com/edx/frontend-app-profile#readme",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 versions",
|
||||
"ie 11"
|
||||
],
|
||||
"dependencies": {
|
||||
"@edx/frontend-analytics": "3.0.0",
|
||||
"@edx/frontend-auth": "7.0.1",
|
||||
"@edx/frontend-base": "4.0.0",
|
||||
"@edx/frontend-component-footer": "9.0.0",
|
||||
"@edx/frontend-component-header": "1.1.4",
|
||||
"@edx/frontend-i18n": "3.0.2",
|
||||
"@edx/frontend-logging": "3.0.1",
|
||||
"@edx/frontend-component-footer": "10.1.1",
|
||||
"@edx/frontend-component-header": "2.2.1",
|
||||
"@edx/frontend-platform": "1.8.1",
|
||||
"@edx/paragon": "7.1.3",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.14",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.25",
|
||||
"@fortawesome/free-brands-svg-icons": "5.7.2",
|
||||
"@fortawesome/free-regular-svg-icons": "5.7.1",
|
||||
"@fortawesome/free-solid-svg-icons": "5.7.1",
|
||||
"@fortawesome/react-fontawesome": "0.1.4",
|
||||
"@fortawesome/free-regular-svg-icons": "5.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "5.7.2",
|
||||
"@fortawesome/react-fontawesome": "0.1.8",
|
||||
"babel-polyfill": "6.26.0",
|
||||
"classnames": "2.2.6",
|
||||
"email-prop-type": "1.1.7",
|
||||
"font-awesome": "4.7.0",
|
||||
"form-urlencoded": "3.0.0",
|
||||
"form-urlencoded": "3.0.2",
|
||||
"history": "4.7.2",
|
||||
"lodash.camelcase": "4.3.0",
|
||||
"lodash.get": "4.4.2",
|
||||
@@ -69,11 +64,11 @@
|
||||
"prop-types": "15.7.2",
|
||||
"react": "16.9.0",
|
||||
"react-dom": "16.9.0",
|
||||
"react-redux": "7.1.1",
|
||||
"react-router": "5.0.1",
|
||||
"react-router-dom": "5.0.1",
|
||||
"react-redux": "7.1.3",
|
||||
"react-router": "5.1.2",
|
||||
"react-router-dom": "5.1.2",
|
||||
"react-transition-group": "4.3.0",
|
||||
"redux": "4.0.4",
|
||||
"redux": "4.0.5",
|
||||
"redux-devtools-extension": "2.13.8",
|
||||
"redux-logger": "3.0.6",
|
||||
"redux-saga": "1.0.5",
|
||||
@@ -82,15 +77,18 @@
|
||||
"universal-cookie": "3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/frontend-build": "1.1.0",
|
||||
"codecov": "3.1.0",
|
||||
"@commitlint/cli": "8.2.0",
|
||||
"@commitlint/config-angular": "8.2.0",
|
||||
"@edx/frontend-build": "5.3.2",
|
||||
"codecov": "3.7.2",
|
||||
"enzyme": "3.10.0",
|
||||
"enzyme-adapter-react-16": "1.14.0",
|
||||
"enzyme-adapter-react-16": "1.15.5",
|
||||
"es-check": "5.0.0",
|
||||
"glob": "7.1.3",
|
||||
"glob": "7.1.6",
|
||||
"husky": "3.1.0",
|
||||
"purgecss-webpack-plugin": "1.6.0",
|
||||
"react-test-renderer": "16.9.0",
|
||||
"reactifex": "1.1.1",
|
||||
"redux-mock-store": "1.5.3"
|
||||
"redux-mock-store": "1.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700" rel="stylesheet">
|
||||
<link rel="shortcut icon" href="<%=webpackConfig.output.publicPath%>favicon.ico" type="image/x-icon" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
]
|
||||
],
|
||||
"patch": {
|
||||
"automerge": true
|
||||
},
|
||||
"rebaseStalePrs": true
|
||||
}
|
||||
|
||||
BIN
src/assets/favicon.ico
Normal file
BIN
src/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
33
src/data/configureStore.js
Normal file
33
src/data/configureStore.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { applyMiddleware, createStore, compose } from 'redux';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||
import { createLogger } from 'redux-logger';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
|
||||
import createRootReducer from './reducers';
|
||||
import rootSaga from './sagas';
|
||||
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
|
||||
function composeMiddleware() {
|
||||
if (getConfig().ENVIRONMENT === 'development') {
|
||||
const loggerMiddleware = createLogger({
|
||||
collapsed: true,
|
||||
});
|
||||
return composeWithDevTools(applyMiddleware(thunkMiddleware, sagaMiddleware, loggerMiddleware));
|
||||
}
|
||||
|
||||
return compose(applyMiddleware(thunkMiddleware, sagaMiddleware));
|
||||
}
|
||||
|
||||
export default function configureStore(initialState = {}) {
|
||||
const store = createStore(
|
||||
createRootReducer(),
|
||||
initialState,
|
||||
composeMiddleware(),
|
||||
);
|
||||
sagaMiddleware.run(rootSaga);
|
||||
|
||||
return store;
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { userAccount } from '@edx/frontend-auth';
|
||||
|
||||
import { reducer as profilePage } from './profile';
|
||||
import { reducer as profilePage } from '../profile';
|
||||
|
||||
const createRootReducer = () =>
|
||||
combineReducers({
|
||||
userAccount,
|
||||
profilePage,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { all } from 'redux-saga/effects';
|
||||
|
||||
import { saga as profileSaga } from './profile';
|
||||
import { saga as profileSaga } from '../profile';
|
||||
|
||||
export default function* rootSaga() {
|
||||
yield all([
|
||||
@@ -1,51 +1,51 @@
|
||||
{
|
||||
"profile.age.headline": "Your profile cannot be shared.",
|
||||
"profile.age.details": "To share your profile with other edX learners, you must confirm that you are over the age of 13.",
|
||||
"profile.age.set.date": "Set your date of birth",
|
||||
"profile.datejoined.member.since": "Member since {year}",
|
||||
"profile.bio.empty": "Add a short bio",
|
||||
"profile.bio.about.me": "About Me",
|
||||
"profile.certificate.organization.label": "From",
|
||||
"profile.certificate.completion.date.label": "Completed on {date}",
|
||||
"profile.no.certificates": "You don't have any certificates yet.",
|
||||
"profile.certificates.my.certificates": "My Certificates",
|
||||
"profile.certificates.view.certificate": "View Certificate",
|
||||
"profile.certificates.types.verified": "Verified Certificate",
|
||||
"profile.certificates.types.professional": "Professional Certificate",
|
||||
"profile.certificates.types.unknown": "Certificate",
|
||||
"profile.country.label": "Location",
|
||||
"profile.country.empty": "Add location",
|
||||
"profile.education.empty": "Add education",
|
||||
"profile.education.education": "Education",
|
||||
"profile.education.levels.p": "Doctorate",
|
||||
"profile.education.levels.m": "Master's or professional degree",
|
||||
"profile.education.levels.b": "Bachelor's Degree",
|
||||
"profile.education.levels.a": "Associate's degree",
|
||||
"profile.education.levels.hs": "Secondary/high school",
|
||||
"profile.education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"profile.education.levels.el": "Elementary/primary school",
|
||||
"profile.education.levels.none": "No formal education",
|
||||
"profile.education.levels.o": "Other education",
|
||||
"profile.editbutton.edit": "Edit",
|
||||
"profile.formcontrols.who.can.see": "Who can see this:",
|
||||
"profile.formcontrols.button.cancel": "Cancel",
|
||||
"profile.formcontrols.button.save": "Save",
|
||||
"profile.formcontrols.button.saving": "Saving",
|
||||
"profile.formcontrols.button.saved": "Saved",
|
||||
"profile.visibility.who.just.me": "Just me",
|
||||
"profile.visibility.who.everyone": "Everyone on edX",
|
||||
"profile.name.full.name": "Full Name",
|
||||
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
||||
"profile.name.empty": "Add name",
|
||||
"profile.preferredlanguage.empty": "Add language",
|
||||
"profile.preferredlanguage.label": "Primary Language Spoken",
|
||||
"profile.profileavatar.upload-button": "Upload Photo",
|
||||
"profile.profileavatar.remove.button": "Remove",
|
||||
"profile.image.alt.attribute": "profile avatar",
|
||||
"profile.profileavatar.change-button": "Change",
|
||||
"profile.sociallinks.add": "Add {network}",
|
||||
"profile.sociallinks.social.links": "Social Links",
|
||||
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||
"profile.viewMyRecords": "View My Records",
|
||||
"profile.loading": "Profile loading..."
|
||||
"profile.age.headline": "لا يمكن مشاركة ملفك الشخصي",
|
||||
"profile.age.details": "لمشاركة ملفك الشخصي مع متعلمي edX الآخرين يجب التحقق من أن يكون عمرك أكثر من ١٣ سنة",
|
||||
"profile.age.set.date": "اضبط تاريخ ميلاك",
|
||||
"profile.datejoined.member.since": "عضو منذُ {year}",
|
||||
"profile.bio.empty": "أضف نبذة قصيرة",
|
||||
"profile.bio.about.me": "نبذة عنّي",
|
||||
"profile.certificate.organization.label": "من",
|
||||
"profile.certificate.completion.date.label": "مكتمل في",
|
||||
"profile.no.certificates": "لم تحصل على أية شهادات حتى الآن.",
|
||||
"profile.certificates.my.certificates": "شهاداتي",
|
||||
"profile.certificates.view.certificate": "معاينة الشهادة",
|
||||
"profile.certificates.types.verified": "شهادة موثقة",
|
||||
"profile.certificates.types.professional": "شهادة مهنية",
|
||||
"profile.certificates.types.unknown": "شهادة",
|
||||
"profile.country.label": "الموقع",
|
||||
"profile.country.empty": "أضف موقعًا",
|
||||
"profile.education.empty": "أضف مؤهلًا تعليميًا",
|
||||
"profile.education.education": "المستوى التعليمي",
|
||||
"profile.education.levels.p": "شهادة دكتوراه",
|
||||
"profile.education.levels.m": "ماجستير أو شهادة مهنيّة",
|
||||
"profile.education.levels.b": "درجة البكالوريوس",
|
||||
"profile.education.levels.a": "درجة الزمالة",
|
||||
"profile.education.levels.hs": "شهادة الثانوية العامة",
|
||||
"profile.education.levels.jhs": "شهادة الثانوية الصغرى/الإعدادية/المرحلة المتوسّطة",
|
||||
"profile.education.levels.el": "شهادة المدرسة الابتدائية",
|
||||
"profile.education.levels.none": "لا يوجد تعليم رسمي",
|
||||
"profile.education.levels.o": "نوع آخر من التعليم",
|
||||
"profile.editbutton.edit": "تحرير",
|
||||
"profile.formcontrols.who.can.see": "من يمكنه مشاهدة هذا:",
|
||||
"profile.formcontrols.button.cancel": "إلغاء",
|
||||
"profile.formcontrols.button.save": "حفظ",
|
||||
"profile.formcontrols.button.saving": "جاري الحفظ",
|
||||
"profile.formcontrols.button.saved": "تم الحفظ",
|
||||
"profile.visibility.who.just.me": "أنا فقط",
|
||||
"profile.visibility.who.everyone": "جميع أعضاء edX",
|
||||
"profile.name.full.name": "الاسم الكامل",
|
||||
"profile.name.details": "هذا هو الاسم الذي سيظهر في حسابك وفي شهاداتك",
|
||||
"profile.name.empty": "إضافة اسم",
|
||||
"profile.preferredlanguage.empty": "إضافة اللغة",
|
||||
"profile.preferredlanguage.label": "لغة التحدث الأم",
|
||||
"profile.profileavatar.upload-button": "تحميل صورة",
|
||||
"profile.profileavatar.remove.button": "حذف",
|
||||
"profile.image.alt.attribute": "صورة عرض الملف الشخصي",
|
||||
"profile.profileavatar.change-button": "تغيير",
|
||||
"profile.sociallinks.add": "إضافة {network}",
|
||||
"profile.sociallinks.social.links": "روابط قنوات التواصل الاجتماعي",
|
||||
"profile.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في نص الرابط. الرجاء التحقق من الرابط والمحاولة مجددا.",
|
||||
"profile.viewMyRecords": "عرض سجلّاتي",
|
||||
"profile.loading": "جاري تحميل الملف الشخصي ..."
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
"profile.certificates.types.unknown": "Certificado",
|
||||
"profile.country.label": "Ubicación",
|
||||
"profile.country.empty": "Añade ubicación",
|
||||
"profile.education.empty": "Añade Education",
|
||||
"profile.education.empty": "Añade Educación",
|
||||
"profile.education.education": "Educación",
|
||||
"profile.education.levels.p": "Doctorado",
|
||||
"profile.education.levels.m": "Master o magíster",
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
{
|
||||
"profile.age.headline": "Votre profil ne peut pas être partagé.",
|
||||
"profile.age.details": "Pour partager votre profil avec d'autres apprenants edX, vous devez confirmer que vous avez plus de 13 ans.",
|
||||
"profile.age.set.date": "Définissez votre date de naissance",
|
||||
"profile.datejoined.member.since": "Membre depuis {year}",
|
||||
"profile.bio.empty": "Ajouter une courte biographie",
|
||||
"profile.bio.about.me": "À propos de moi",
|
||||
"profile.certificate.organization.label": "De",
|
||||
"profile.certificate.completion.date.label": "Terminé le {date}",
|
||||
"profile.no.certificates": "Vous n'avez pas encore de certificats.",
|
||||
"profile.certificates.my.certificates": "Mes certificats",
|
||||
"profile.certificates.view.certificate": "Voir le certificat",
|
||||
"profile.certificates.types.verified": "Certificat vérifié",
|
||||
"profile.certificates.types.professional": "Certificat professionnel",
|
||||
"profile.certificates.types.unknown": "Certificat",
|
||||
"profile.country.label": "Localisation",
|
||||
"profile.country.empty": "Ajouter localisation",
|
||||
"profile.education.empty": "Ajouter une éducation",
|
||||
"profile.age.headline": "Your profile cannot be shared.",
|
||||
"profile.age.details": "To share your profile with other edX learners, you must confirm that you are over the age of 13.",
|
||||
"profile.age.set.date": "Set your date of birth",
|
||||
"profile.datejoined.member.since": "Member since {year}",
|
||||
"profile.bio.empty": "Add a short bio",
|
||||
"profile.bio.about.me": "About Me",
|
||||
"profile.certificate.organization.label": "From",
|
||||
"profile.certificate.completion.date.label": "Completed on {date}",
|
||||
"profile.no.certificates": "You don't have any certificates yet.",
|
||||
"profile.certificates.my.certificates": "My Certificates",
|
||||
"profile.certificates.view.certificate": "View Certificate",
|
||||
"profile.certificates.types.verified": "Verified Certificate",
|
||||
"profile.certificates.types.professional": "Professional Certificate",
|
||||
"profile.certificates.types.unknown": "Certificate",
|
||||
"profile.country.label": "Location",
|
||||
"profile.country.empty": "Add location",
|
||||
"profile.education.empty": "Add education",
|
||||
"profile.education.education": "Education",
|
||||
"profile.education.levels.p": "Doctorat",
|
||||
"profile.education.levels.m": "Master ou diplôme professionnel",
|
||||
"profile.education.levels.b": "Diplôme de licence",
|
||||
"profile.education.levels.a": "Grade de l'associé",
|
||||
"profile.education.levels.hs": "Lycée / enseignement secondaire",
|
||||
"profile.education.levels.jhs": "Collège / enseignement secondaire inférieur",
|
||||
"profile.education.levels.el": "Enseignement primaire",
|
||||
"profile.education.levels.none": "Sans diplôme",
|
||||
"profile.education.levels.o": "Autre niveau d'étude",
|
||||
"profile.editbutton.edit": "Modifier",
|
||||
"profile.formcontrols.who.can.see": "Qui peut voir ça :",
|
||||
"profile.formcontrols.button.cancel": "Annuler",
|
||||
"profile.formcontrols.button.save": "Enregistrer",
|
||||
"profile.formcontrols.button.saving": "Enregistrement",
|
||||
"profile.formcontrols.button.saved": "Enregistré",
|
||||
"profile.visibility.who.just.me": "Juste moi",
|
||||
"profile.visibility.who.everyone": "Tout le monde sur edX",
|
||||
"profile.name.full.name": "Nom complet",
|
||||
"profile.name.details": "C'est le nom qui apparaît dans votre compte et sur vos certificats.",
|
||||
"profile.name.empty": "Ajouter un nom",
|
||||
"profile.preferredlanguage.empty": "Ajouter une langue",
|
||||
"profile.preferredlanguage.label": "Langue principale parlée",
|
||||
"profile.profileavatar.upload-button": "Envoyer la photo",
|
||||
"profile.profileavatar.remove.button": "Supprimer",
|
||||
"profile.image.alt.attribute": "Profil avatar",
|
||||
"profile.profileavatar.change-button": "Modifier",
|
||||
"profile.sociallinks.add": "Ajouter {network}",
|
||||
"profile.sociallinks.social.links": "Liens vers les réseaux sociaux",
|
||||
"profile.notfound.message": "La page que vous recherchez n'est pas disponible ou il y a une erreur dans l'URL. Veuillez vérifier l'URL et réessayer.",
|
||||
"profile.viewMyRecords": "Voir mes succès",
|
||||
"profile.loading": "Chargement du profil...."
|
||||
"profile.education.levels.p": "Doctorate",
|
||||
"profile.education.levels.m": "Master's or professional degree",
|
||||
"profile.education.levels.b": "Bachelor's Degree",
|
||||
"profile.education.levels.a": "Associate's degree",
|
||||
"profile.education.levels.hs": "Secondary/high school",
|
||||
"profile.education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"profile.education.levels.el": "Elementary/primary school",
|
||||
"profile.education.levels.none": "No formal education",
|
||||
"profile.education.levels.o": "Other education",
|
||||
"profile.editbutton.edit": "Edit",
|
||||
"profile.formcontrols.who.can.see": "Who can see this:",
|
||||
"profile.formcontrols.button.cancel": "Cancel",
|
||||
"profile.formcontrols.button.save": "Save",
|
||||
"profile.formcontrols.button.saving": "Saving",
|
||||
"profile.formcontrols.button.saved": "Saved",
|
||||
"profile.visibility.who.just.me": "Just me",
|
||||
"profile.visibility.who.everyone": "Everyone on edX",
|
||||
"profile.name.full.name": "Full Name",
|
||||
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
||||
"profile.name.empty": "Add name",
|
||||
"profile.preferredlanguage.empty": "Add language",
|
||||
"profile.preferredlanguage.label": "Primary Language Spoken",
|
||||
"profile.profileavatar.upload-button": "Upload Photo",
|
||||
"profile.profileavatar.remove.button": "Remove",
|
||||
"profile.image.alt.attribute": "profile avatar",
|
||||
"profile.profileavatar.change-button": "Change",
|
||||
"profile.sociallinks.add": "Add {network}",
|
||||
"profile.sociallinks.social.links": "Social Links",
|
||||
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||
"profile.viewMyRecords": "View My Records",
|
||||
"profile.loading": "Profile loading..."
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"profile.age.headline": "您的个人资料无法共享",
|
||||
"profile.age.details": "您必须确认您已年满13岁才能其他edX学员分享您的个人资料",
|
||||
"profile.age.set.date": "设置您的出生日期",
|
||||
"profile.datejoined.member.since": "会员自{year}",
|
||||
"profile.bio.empty": "添加简介",
|
||||
"profile.bio.about.me": "个人资料",
|
||||
"profile.certificate.organization.label": "从",
|
||||
"profile.age.headline": "Your profile cannot be shared.",
|
||||
"profile.age.details": "To share your profile with other edX learners, you must confirm that you are over the age of 13.",
|
||||
"profile.age.set.date": "Set your date of birth",
|
||||
"profile.datejoined.member.since": "Member since {year}",
|
||||
"profile.bio.empty": "Add a short bio",
|
||||
"profile.bio.about.me": "About Me",
|
||||
"profile.certificate.organization.label": "From",
|
||||
"profile.certificate.completion.date.label": "Completed on {date}",
|
||||
"profile.no.certificates": "你还没有任何证书。",
|
||||
"profile.certificates.my.certificates": "我的证书",
|
||||
"profile.certificates.view.certificate": "查看证书",
|
||||
"profile.no.certificates": "You don't have any certificates yet.",
|
||||
"profile.certificates.my.certificates": "My Certificates",
|
||||
"profile.certificates.view.certificate": "View Certificate",
|
||||
"profile.certificates.types.verified": "Verified Certificate",
|
||||
"profile.certificates.types.professional": "Professional Certificate",
|
||||
"profile.certificates.types.unknown": "Certificate",
|
||||
"profile.country.label": "位置",
|
||||
"profile.country.empty": "添加地址",
|
||||
"profile.education.empty": "添加教育程度",
|
||||
"profile.education.education": "教育程度",
|
||||
"profile.country.label": "Location",
|
||||
"profile.country.empty": "Add location",
|
||||
"profile.education.empty": "Add education",
|
||||
"profile.education.education": "Education",
|
||||
"profile.education.levels.p": "Doctorate",
|
||||
"profile.education.levels.m": "Master's or professional degree",
|
||||
"profile.education.levels.b": "Bachelor's Degree",
|
||||
@@ -26,26 +26,26 @@
|
||||
"profile.education.levels.el": "Elementary/primary school",
|
||||
"profile.education.levels.none": "No formal education",
|
||||
"profile.education.levels.o": "Other education",
|
||||
"profile.editbutton.edit": "编辑",
|
||||
"profile.formcontrols.who.can.see": "对谁可见:",
|
||||
"profile.formcontrols.button.cancel": "取消",
|
||||
"profile.formcontrols.button.save": "保存",
|
||||
"profile.formcontrols.button.saving": "正在保存",
|
||||
"profile.formcontrols.button.saved": "已保存",
|
||||
"profile.visibility.who.just.me": "仅自己",
|
||||
"profile.visibility.who.everyone": "edX的所有人",
|
||||
"profile.name.full.name": "全名",
|
||||
"profile.name.details": "用于显示在账户和证书上的姓名。",
|
||||
"profile.name.empty": "添加名字",
|
||||
"profile.preferredlanguage.empty": "添加语言",
|
||||
"profile.editbutton.edit": "Edit",
|
||||
"profile.formcontrols.who.can.see": "Who can see this:",
|
||||
"profile.formcontrols.button.cancel": "Cancel",
|
||||
"profile.formcontrols.button.save": "Save",
|
||||
"profile.formcontrols.button.saving": "Saving",
|
||||
"profile.formcontrols.button.saved": "Saved",
|
||||
"profile.visibility.who.just.me": "Just me",
|
||||
"profile.visibility.who.everyone": "Everyone on edX",
|
||||
"profile.name.full.name": "Full Name",
|
||||
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
||||
"profile.name.empty": "Add name",
|
||||
"profile.preferredlanguage.empty": "Add language",
|
||||
"profile.preferredlanguage.label": "Primary Language Spoken",
|
||||
"profile.profileavatar.upload-button": "上传照片",
|
||||
"profile.profileavatar.remove.button": "移除",
|
||||
"profile.profileavatar.upload-button": "Upload Photo",
|
||||
"profile.profileavatar.remove.button": "Remove",
|
||||
"profile.image.alt.attribute": "profile avatar",
|
||||
"profile.profileavatar.change-button": "更改",
|
||||
"profile.sociallinks.add": "添加{network}",
|
||||
"profile.sociallinks.social.links": "社交链接",
|
||||
"profile.notfound.message": "您访问的地址不存在或有误。请检查URL后重新尝试访问。",
|
||||
"profile.profileavatar.change-button": "Change",
|
||||
"profile.sociallinks.add": "Add {network}",
|
||||
"profile.sociallinks.social.links": "Social Links",
|
||||
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||
"profile.viewMyRecords": "View My Records",
|
||||
"profile.loading": "Profile loading..."
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'babel-polyfill';
|
||||
|
||||
import { App, AppProvider, APP_ERROR, APP_READY, ErrorPage } from '@edx/frontend-base';
|
||||
import { APP_INIT_ERROR, APP_READY, initialize, subscribe } from '@edx/frontend-platform';
|
||||
import { AppProvider, ErrorPage } from '@edx/frontend-platform/react';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
@@ -9,11 +11,13 @@ import Header, { messages as headerMessages } from '@edx/frontend-component-head
|
||||
import Footer, { messages as footerMessages } from '@edx/frontend-component-footer';
|
||||
|
||||
import appMessages from './i18n';
|
||||
import './index.scss';
|
||||
import { ProfilePage, NotFoundPage } from './profile';
|
||||
import configureStore from './store';
|
||||
import configureStore from './data/configureStore';
|
||||
|
||||
App.subscribe(APP_READY, () => {
|
||||
import './index.scss';
|
||||
import './assets/favicon.ico';
|
||||
|
||||
subscribe(APP_READY, () => {
|
||||
ReactDOM.render(
|
||||
<AppProvider store={configureStore()}>
|
||||
<Header />
|
||||
@@ -30,14 +34,16 @@ App.subscribe(APP_READY, () => {
|
||||
);
|
||||
});
|
||||
|
||||
App.subscribe(APP_ERROR, (error) => {
|
||||
subscribe(APP_INIT_ERROR, (error) => {
|
||||
ReactDOM.render(<ErrorPage message={error.message} />, document.getElementById('root'));
|
||||
});
|
||||
|
||||
App.initialize({
|
||||
initialize({
|
||||
messages: [
|
||||
appMessages,
|
||||
headerMessages,
|
||||
footerMessages,
|
||||
],
|
||||
requireAuthenticatedUser: true,
|
||||
hydrateAuthenticatedUser: true,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StatusAlert } from '@edx/paragon';
|
||||
import { FormattedMessage } from '@edx/frontend-i18n';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
function AgeMessage({ accountSettingsUrl }) {
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, FormattedDate } from '@edx/frontend-i18n';
|
||||
import { FormattedMessage, FormattedDate } from '@edx/frontend-platform/i18n';
|
||||
|
||||
function DateJoined({ date }) {
|
||||
if (date == null) return null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@edx/frontend-i18n';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
export default function NotFoundPage() {
|
||||
return (
|
||||
|
||||
@@ -2,9 +2,10 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { sendTrackingLogEvent } from '@edx/frontend-analytics';
|
||||
import { App, AppContext, fetchUserAccount } from '@edx/frontend-base';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
|
||||
import { ensureConfig } from '@edx/frontend-platform';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { StatusAlert, Hyperlink } from '@edx/paragon';
|
||||
|
||||
// Actions
|
||||
@@ -38,7 +39,7 @@ import { profilePageSelector } from './data/selectors';
|
||||
// i18n
|
||||
import messages from './ProfilePage.messages';
|
||||
|
||||
App.ensureConfig(['CREDENTIALS_BASE_URL', 'LMS_BASE_URL'], 'ProfilePage');
|
||||
ensureConfig(['CREDENTIALS_BASE_URL', 'LMS_BASE_URL'], 'ProfilePage');
|
||||
|
||||
class ProfilePage extends React.Component {
|
||||
constructor(props, context) {
|
||||
@@ -58,7 +59,6 @@ class ProfilePage extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchUserAccount(this.context.authenticatedUser.username);
|
||||
this.props.fetchProfile(this.props.match.params.username);
|
||||
sendTrackingLogEvent('edx.profile.viewed', {
|
||||
username: this.props.match.params.username,
|
||||
@@ -112,9 +112,11 @@ class ProfilePage extends React.Component {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1 className="h2 mb-0 font-weight-bold">{this.props.match.params.username}</h1>
|
||||
<DateJoined date={dateJoined} />
|
||||
<hr className="d-none d-md-block" />
|
||||
<span data-hj-suppress>
|
||||
<h1 className="h2 mb-0 font-weight-bold">{this.props.match.params.username}</h1>
|
||||
<DateJoined date={dateJoined} />
|
||||
<hr className="d-none d-md-block" />
|
||||
</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -332,7 +334,6 @@ ProfilePage.propTypes = {
|
||||
photoUploadError: PropTypes.objectOf(PropTypes.string),
|
||||
|
||||
// Actions
|
||||
fetchUserAccount: PropTypes.func.isRequired,
|
||||
fetchProfile: PropTypes.func.isRequired,
|
||||
saveProfile: PropTypes.func.isRequired,
|
||||
saveProfilePhoto: PropTypes.func.isRequired,
|
||||
@@ -372,7 +373,6 @@ ProfilePage.defaultProps = {
|
||||
export default connect(
|
||||
profilePageSelector,
|
||||
{
|
||||
fetchUserAccount,
|
||||
fetchProfile,
|
||||
saveProfilePhoto,
|
||||
deleteProfilePhoto,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-i18n';
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.viewMyRecords': {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* eslint-disable global-require */
|
||||
import * as analytics from '@edx/frontend-analytics';
|
||||
import { App, AppContext } from '@edx/frontend-base';
|
||||
import { configure as configureI18n, IntlProvider } from '@edx/frontend-i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import * as analytics from '@edx/frontend-platform/analytics';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { configure as configureI18n, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
@@ -33,21 +34,45 @@ const requiredProfilePageProps = {
|
||||
// Mock language cookie
|
||||
Object.defineProperty(global.document, 'cookie', {
|
||||
writable: true,
|
||||
value: `${App.config.LANGUAGE_PREFERENCE_COOKIE_NAME}=en`,
|
||||
value: `${getConfig().LANGUAGE_PREFERENCE_COOKIE_NAME}=en`,
|
||||
});
|
||||
App.apiClient = jest.fn();
|
||||
|
||||
configureI18n(App.config, messages);
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
configure: () => {},
|
||||
getAuthenticatedUser: () => null,
|
||||
fetchAuthenticatedUser: () => null,
|
||||
getAuthenticatedHttpClient: jest.fn(),
|
||||
AUTHENTICATED_USER_CHANGED: 'user_changed',
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
configure: () => {},
|
||||
identifyAnonymousUser: jest.fn(),
|
||||
identifyAuthenticatedUser: jest.fn(),
|
||||
sendTrackingLogEvent: jest.fn(),
|
||||
}));
|
||||
|
||||
configureI18n({
|
||||
loggingService: { logError: jest.fn() },
|
||||
config: {
|
||||
ENVIRONMENT: 'production',
|
||||
LANGUAGE_PREFERENCE_COOKIE_NAME: 'yum',
|
||||
},
|
||||
messages,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
analytics.sendTrackingLogEvent.mockReset();
|
||||
});
|
||||
|
||||
describe('<ProfilePage />', () => {
|
||||
describe('Renders correctly in various states', () => {
|
||||
it('app loading', () => {
|
||||
analytics.sendTrackingLogEvent = jest.fn();
|
||||
const component = (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
authenticatedUser: { userId: null, username: null, administrator: false },
|
||||
config: App.config,
|
||||
config: getConfig(),
|
||||
}}
|
||||
>
|
||||
<IntlProvider locale="en">
|
||||
@@ -62,12 +87,11 @@ describe('<ProfilePage />', () => {
|
||||
});
|
||||
|
||||
it('viewing own profile', () => {
|
||||
analytics.sendTrackingLogEvent = jest.fn();
|
||||
const component = (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
authenticatedUser: { userId: 123, username: 'staff', administrator: true },
|
||||
config: App.config,
|
||||
config: getConfig(),
|
||||
}}
|
||||
>
|
||||
<IntlProvider locale="en">
|
||||
@@ -82,12 +106,11 @@ describe('<ProfilePage />', () => {
|
||||
});
|
||||
|
||||
it('viewing other profile', () => {
|
||||
analytics.sendTrackingLogEvent = jest.fn();
|
||||
const component = (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
authenticatedUser: { userId: 123, username: 'staff', administrator: true },
|
||||
config: App.config,
|
||||
config: getConfig(),
|
||||
}}
|
||||
>
|
||||
<IntlProvider locale="en">
|
||||
@@ -105,12 +128,11 @@ describe('<ProfilePage />', () => {
|
||||
});
|
||||
|
||||
it('while saving an edited bio', () => {
|
||||
analytics.sendTrackingLogEvent = jest.fn();
|
||||
const component = (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
authenticatedUser: { userId: 123, username: 'staff', administrator: true },
|
||||
config: App.config,
|
||||
config: getConfig(),
|
||||
}}
|
||||
>
|
||||
<IntlProvider locale="en">
|
||||
@@ -127,12 +149,11 @@ describe('<ProfilePage />', () => {
|
||||
|
||||
describe('handles analytics', () => {
|
||||
it('calls sendTrackingLogEvent when mounting', () => {
|
||||
analytics.sendTrackingLogEvent = jest.fn();
|
||||
const component = (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
authenticatedUser: { userId: 123, username: 'staff', administrator: true },
|
||||
config: App.config,
|
||||
config: getConfig(),
|
||||
}}
|
||||
>
|
||||
<IntlProvider locale="en">
|
||||
@@ -145,7 +166,8 @@ describe('<ProfilePage />', () => {
|
||||
</IntlProvider>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
mount(component);
|
||||
const wrapper = mount(component);
|
||||
wrapper.update();
|
||||
|
||||
expect(analytics.sendTrackingLogEvent.mock.calls.length).toBe(1);
|
||||
expect(analytics.sendTrackingLogEvent.mock.calls[0][0]).toEqual('edx.profile.viewed');
|
||||
|
||||
@@ -86,24 +86,28 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
|
||||
<div
|
||||
className="d-md-none"
|
||||
>
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
<span
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
verified
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
Member since
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
>
|
||||
verified
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
2017
|
||||
Member since
|
||||
<span>
|
||||
2017
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="d-none d-md-block float-right"
|
||||
@@ -119,24 +123,28 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
|
||||
<div
|
||||
className="d-none d-md-block mb-4"
|
||||
>
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
<span
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
verified
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
Member since
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
>
|
||||
verified
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
2017
|
||||
Member since
|
||||
<span>
|
||||
2017
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="d-md-none mb-4"
|
||||
@@ -277,6 +285,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
|
||||
<img
|
||||
alt="profile avatar"
|
||||
className="w-100 h-100 d-block rounded-circle overflow-hidden"
|
||||
data-hj-suppress={true}
|
||||
src="http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012"
|
||||
style={
|
||||
Object {
|
||||
@@ -307,24 +316,28 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
|
||||
<div
|
||||
className="d-md-none"
|
||||
>
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
<span
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
staff
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
Member since
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
>
|
||||
staff
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
2017
|
||||
Member since
|
||||
<span>
|
||||
2017
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="d-none d-md-block float-right"
|
||||
@@ -359,24 +372,28 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
|
||||
<div
|
||||
className="d-none d-md-block mb-4"
|
||||
>
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
<span
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
staff
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
Member since
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
>
|
||||
staff
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
2017
|
||||
Member since
|
||||
<span>
|
||||
2017
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="d-md-none mb-4"
|
||||
@@ -485,6 +502,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Lemon Seltzer
|
||||
</p>
|
||||
@@ -580,6 +598,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Montenegro
|
||||
</p>
|
||||
@@ -670,6 +689,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Yoruba
|
||||
</p>
|
||||
@@ -760,6 +780,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Elementary/primary school
|
||||
</p>
|
||||
@@ -1029,6 +1050,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
|
||||
</div>
|
||||
<p
|
||||
className="lead"
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
This is my bio
|
||||
</p>
|
||||
@@ -1130,7 +1152,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
|
||||
className="certificate-type-illustration"
|
||||
style={
|
||||
Object {
|
||||
"backgroundImage": "url([object Object])",
|
||||
"backgroundImage": "url(icon/mock/path)",
|
||||
}
|
||||
}
|
||||
/>
|
||||
@@ -1278,6 +1300,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
||||
<img
|
||||
alt="profile avatar"
|
||||
className="w-100 h-100 d-block rounded-circle overflow-hidden"
|
||||
data-hj-suppress={true}
|
||||
src="http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012"
|
||||
style={
|
||||
Object {
|
||||
@@ -1308,24 +1331,28 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
||||
<div
|
||||
className="d-md-none"
|
||||
>
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
<span
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
staff
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
Member since
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
>
|
||||
staff
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
2017
|
||||
Member since
|
||||
<span>
|
||||
2017
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="d-none d-md-block float-right"
|
||||
@@ -1360,24 +1387,28 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
||||
<div
|
||||
className="d-none d-md-block mb-4"
|
||||
>
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
<span
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
staff
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
Member since
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
>
|
||||
staff
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
2017
|
||||
Member since
|
||||
<span>
|
||||
2017
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="d-md-none mb-4"
|
||||
@@ -1486,6 +1517,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Lemon Seltzer
|
||||
</p>
|
||||
@@ -1581,6 +1613,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Montenegro
|
||||
</p>
|
||||
@@ -1671,6 +1704,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Yoruba
|
||||
</p>
|
||||
@@ -1761,6 +1795,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Elementary/primary school
|
||||
</p>
|
||||
@@ -2185,7 +2220,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
||||
className="certificate-type-illustration"
|
||||
style={
|
||||
Object {
|
||||
"backgroundImage": "url([object Object])",
|
||||
"backgroundImage": "url(icon/mock/path)",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FETCH_USER_ACCOUNT_FAILURE } from '@edx/frontend-auth';
|
||||
import { App } from '@edx/frontend-base';
|
||||
import { logApiClientError } from '@edx/frontend-logging';
|
||||
import { history } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import pick from 'lodash.pick';
|
||||
import { all, call, delay, put, select, takeEvery } from 'redux-saga/effects';
|
||||
import {
|
||||
@@ -31,7 +30,7 @@ import * as ProfileApiService from './services';
|
||||
export function* handleFetchProfile(action) {
|
||||
const { username } = action.payload;
|
||||
const userAccount = yield select(userAccountSelector);
|
||||
const isAuthenticatedUserProfile = username === App.authenticatedUser.username;
|
||||
const isAuthenticatedUserProfile = username === getAuthenticatedUser().username;
|
||||
// Default our data assuming the account is the current user's account.
|
||||
let preferences = {};
|
||||
let account = userAccount;
|
||||
@@ -70,7 +69,7 @@ export function* handleFetchProfile(action) {
|
||||
yield put(fetchProfileReset());
|
||||
} catch (e) {
|
||||
if (e.response.status === 404) {
|
||||
App.history.push('/notfound');
|
||||
history.push('/notfound');
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
@@ -176,15 +175,9 @@ export function* handleDeleteProfilePhoto(action) {
|
||||
}
|
||||
}
|
||||
|
||||
export function handleFetchUserAccountFailure(action) {
|
||||
logApiClientError(action.payload.error);
|
||||
throw action.payload.error;
|
||||
}
|
||||
|
||||
export default function* profileSaga() {
|
||||
yield takeEvery(FETCH_PROFILE.BASE, handleFetchProfile);
|
||||
yield takeEvery(SAVE_PROFILE.BASE, handleSaveProfile);
|
||||
yield takeEvery(SAVE_PROFILE_PHOTO.BASE, handleSaveProfilePhoto);
|
||||
yield takeEvery(DELETE_PROFILE_PHOTO.BASE, handleDeleteProfilePhoto);
|
||||
yield takeEvery(FETCH_USER_ACCOUNT_FAILURE, handleFetchUserAccountFailure);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { takeEvery, put, call, delay, select, all } from 'redux-saga/effects';
|
||||
import { FETCH_USER_ACCOUNT_FAILURE } from '@edx/frontend-auth';
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
|
||||
import * as profileActions from './actions';
|
||||
import { handleSaveProfileSelector, userAccountSelector } from './selectors';
|
||||
@@ -14,6 +14,10 @@ jest.mock('./services', () => ({
|
||||
getCourseCertificates: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getAuthenticatedUser: jest.fn(),
|
||||
}));
|
||||
|
||||
// RootSaga and ProfileApiService must be imported AFTER the mock above.
|
||||
/* eslint-disable import/first */
|
||||
import profileSaga, {
|
||||
@@ -21,10 +25,8 @@ import profileSaga, {
|
||||
handleSaveProfile,
|
||||
handleSaveProfilePhoto,
|
||||
handleDeleteProfilePhoto,
|
||||
handleFetchUserAccountFailure,
|
||||
} from './sagas';
|
||||
import * as ProfileApiService from './services';
|
||||
import { App } from '@edx/frontend-base';
|
||||
/* eslint-enable import/first */
|
||||
|
||||
describe('RootSaga', () => {
|
||||
@@ -40,8 +42,6 @@ describe('RootSaga', () => {
|
||||
.toEqual(takeEvery(profileActions.SAVE_PROFILE_PHOTO.BASE, handleSaveProfilePhoto));
|
||||
expect(gen.next().value)
|
||||
.toEqual(takeEvery(profileActions.DELETE_PROFILE_PHOTO.BASE, handleDeleteProfilePhoto));
|
||||
expect(gen.next().value)
|
||||
.toEqual(takeEvery(FETCH_USER_ACCOUNT_FAILURE, handleFetchUserAccountFailure));
|
||||
|
||||
expect(gen.next().value).toBeUndefined();
|
||||
});
|
||||
@@ -53,11 +53,11 @@ describe('RootSaga', () => {
|
||||
username: 'gonzo',
|
||||
other: 'data',
|
||||
};
|
||||
getAuthenticatedUser.mockReturnValue(userAccount);
|
||||
const selectorData = {
|
||||
userAccount,
|
||||
};
|
||||
|
||||
App.authenticatedUser.username = 'gonzo';
|
||||
const action = profileActions.fetchProfile('gonzo');
|
||||
const gen = handleFetchProfile(action);
|
||||
|
||||
@@ -81,11 +81,11 @@ describe('RootSaga', () => {
|
||||
username: 'gonzo',
|
||||
other: 'data',
|
||||
};
|
||||
getAuthenticatedUser.mockReturnValue(userAccount);
|
||||
const selectorData = {
|
||||
userAccount,
|
||||
};
|
||||
|
||||
App.authenticatedUser.username = 'gonzo';
|
||||
const action = profileActions.fetchProfile('booyah');
|
||||
const gen = handleFetchProfile(action);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
getCountryList,
|
||||
getCountryMessages,
|
||||
getLanguageMessages,
|
||||
} from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
} from '@edx/frontend-platform/i18n'; // eslint-disable-line
|
||||
|
||||
export const formIdSelector = (state, props) => props.formId;
|
||||
export const userAccountSelector = state => state.userAccount;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { App } from '@edx/frontend-base';
|
||||
import { logApiClientError } from '@edx/frontend-logging';
|
||||
import { ensureConfig, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient as getHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
import { camelCaseObject, convertKeyNames, snakeCaseObject } from '../utils';
|
||||
|
||||
App.ensureConfig(['LMS_BASE_URL'], 'Profile API service');
|
||||
ensureConfig(['LMS_BASE_URL'], 'Profile API service');
|
||||
|
||||
function processAccountData(data) {
|
||||
return camelCaseObject(data);
|
||||
@@ -20,7 +21,7 @@ function processAndThrowError(error, errorDataProcessor) {
|
||||
|
||||
// GET ACCOUNT
|
||||
export async function getAccount(username) {
|
||||
const { data } = await App.apiClient.get(`${App.config.LMS_BASE_URL}/api/user/v1/accounts/${username}`);
|
||||
const { data } = await getHttpClient().get(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}`);
|
||||
|
||||
// Process response data
|
||||
return processAccountData(data);
|
||||
@@ -30,8 +31,8 @@ export async function getAccount(username) {
|
||||
export async function patchProfile(username, params) {
|
||||
const processedParams = snakeCaseObject(params);
|
||||
|
||||
const { data } = await App.apiClient
|
||||
.patch(`${App.config.LMS_BASE_URL}/api/user/v1/accounts/${username}`, processedParams, {
|
||||
const { data } = await getHttpClient()
|
||||
.patch(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}`, processedParams, {
|
||||
headers: {
|
||||
'Content-Type': 'application/merge-patch+json',
|
||||
},
|
||||
@@ -48,8 +49,8 @@ export async function patchProfile(username, params) {
|
||||
|
||||
export async function postProfilePhoto(username, formData) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { data } = await App.apiClient.post(
|
||||
`${App.config.LMS_BASE_URL}/api/user/v1/accounts/${username}/image`,
|
||||
const { data } = await getHttpClient().post(
|
||||
`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}/image`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
@@ -73,7 +74,7 @@ export async function postProfilePhoto(username, formData) {
|
||||
|
||||
export async function deleteProfilePhoto(username) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { data } = await App.apiClient.delete(`${App.config.LMS_BASE_URL}/api/user/v1/accounts/${username}/image`);
|
||||
const { data } = await getHttpClient().delete(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}/image`);
|
||||
|
||||
// TODO: Someday in the future the POST photo endpoint
|
||||
// will return the new values. At that time we should
|
||||
@@ -86,7 +87,7 @@ export async function deleteProfilePhoto(username) {
|
||||
|
||||
// GET PREFERENCES
|
||||
export async function getPreferences(username) {
|
||||
const { data } = await App.apiClient.get(`${App.config.LMS_BASE_URL}/api/user/v1/preferences/${username}`);
|
||||
const { data } = await getHttpClient().get(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`);
|
||||
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
@@ -106,7 +107,7 @@ export async function patchPreferences(username, params) {
|
||||
visibility_time_zone: 'visibility.time_zone',
|
||||
});
|
||||
|
||||
await App.apiClient.patch(`${App.config.LMS_BASE_URL}/api/user/v1/preferences/${username}`, processedParams, {
|
||||
await getHttpClient().patch(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`, processedParams, {
|
||||
headers: { 'Content-Type': 'application/merge-patch+json' },
|
||||
});
|
||||
|
||||
@@ -124,7 +125,7 @@ function transformCertificateData(data) {
|
||||
cert.download_url.search(/http[s]?:\/\//) !== 0;
|
||||
|
||||
const downloadUrl = urlIsPath ?
|
||||
`${App.config.LMS_BASE_URL}${cert.download_url}` :
|
||||
`${getConfig().LMS_BASE_URL}${cert.download_url}` :
|
||||
cert.download_url;
|
||||
|
||||
transformedData.push({
|
||||
@@ -137,12 +138,12 @@ function transformCertificateData(data) {
|
||||
}
|
||||
|
||||
export async function getCourseCertificates(username) {
|
||||
const url = `${App.config.LMS_BASE_URL}/api/certificates/v0/certificates/${username}/`;
|
||||
const url = `${getConfig().LMS_BASE_URL}/api/certificates/v0/certificates/${username}/`;
|
||||
try {
|
||||
const { data } = await App.apiClient.get(url);
|
||||
const { data } = await getHttpClient().get(url);
|
||||
return transformCertificateData(data);
|
||||
} catch (e) {
|
||||
logApiClientError(e);
|
||||
logError(e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { ValidationFormGroup } from '@edx/paragon';
|
||||
|
||||
import messages from './Bio.messages';
|
||||
@@ -91,7 +91,7 @@ class Bio extends React.Component {
|
||||
showVisibility={visibilityBio !== null}
|
||||
visibility={visibilityBio}
|
||||
/>
|
||||
<p className="lead">{bio}</p>
|
||||
<p data-hj-suppress className="lead">{bio}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
empty: (
|
||||
@@ -109,7 +109,7 @@ class Bio extends React.Component {
|
||||
static: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader content={intl.formatMessage(messages['profile.bio.about.me'])} />
|
||||
<p className="lead">{bio}</p>
|
||||
<p data-hj-suppress className="lead">{bio}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-i18n';
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.bio.about.me': {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedDate, FormattedMessage, injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { FormattedDate, FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
import { connect } from 'react-redux';
|
||||
import get from 'lodash.get';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-i18n';
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.certificates.my.certificates': {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { ValidationFormGroup } from '@edx/paragon';
|
||||
|
||||
import messages from './Country.messages';
|
||||
@@ -76,6 +76,7 @@ class Country extends React.Component {
|
||||
{intl.formatMessage(messages['profile.country.label'])}
|
||||
</label>
|
||||
<select
|
||||
data-hj-suppress
|
||||
className="form-control"
|
||||
type="select"
|
||||
id={formId}
|
||||
@@ -108,7 +109,7 @@ class Country extends React.Component {
|
||||
showVisibility={visibilityCountry !== null}
|
||||
visibility={visibilityCountry}
|
||||
/>
|
||||
<p className="h5">{countryMessages[country]}</p>
|
||||
<p data-hj-suppress className="h5">{countryMessages[country]}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
empty: (
|
||||
@@ -126,7 +127,7 @@ class Country extends React.Component {
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.country.label'])}
|
||||
/>
|
||||
<p className="h5">{countryMessages[country]}</p>
|
||||
<p data-hj-suppress className="h5">{countryMessages[country]}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-i18n';
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.country.label': {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import get from 'lodash.get';
|
||||
import { ValidationFormGroup } from '@edx/paragon';
|
||||
|
||||
@@ -72,6 +72,7 @@ class Education extends React.Component {
|
||||
{intl.formatMessage(messages['profile.education.education'])}
|
||||
</label>
|
||||
<select
|
||||
data-hj-suppress
|
||||
className="form-control"
|
||||
id={formId}
|
||||
name={formId}
|
||||
@@ -109,7 +110,7 @@ class Education extends React.Component {
|
||||
showVisibility={visibilityLevelOfEducation !== null}
|
||||
visibility={visibilityLevelOfEducation}
|
||||
/>
|
||||
<p className="h5">
|
||||
<p data-hj-suppress className="h5">
|
||||
{intl.formatMessage(get(
|
||||
messages,
|
||||
`profile.education.levels.${levelOfEducation}`,
|
||||
@@ -133,7 +134,7 @@ class Education extends React.Component {
|
||||
static: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader content={intl.formatMessage(messages['profile.education.education'])} />
|
||||
<p className="h5">
|
||||
<p data-hj-suppress className="h5">
|
||||
{intl.formatMessage(get(
|
||||
messages,
|
||||
`profile.education.levels.${levelOfEducation}`,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-i18n';
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.education.education': {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import messages from './Name.messages';
|
||||
|
||||
@@ -68,7 +68,7 @@ class Name extends React.Component {
|
||||
Once we're super sure we don't want it back, you could delete the name props and
|
||||
such to fully get rid of it.
|
||||
*/}
|
||||
<p className="h5">{name}</p>
|
||||
<p data-hj-suppress className="h5">{name}</p>
|
||||
<small className="form-text text-muted" id={`${formId}-help-text`}>
|
||||
{intl.formatMessage(messages['profile.name.details'])}
|
||||
</small>
|
||||
@@ -92,7 +92,7 @@ class Name extends React.Component {
|
||||
showVisibility={visibilityName !== null}
|
||||
visibility={visibilityName}
|
||||
/>
|
||||
<p className="h5">{name}</p>
|
||||
<p data-hj-suppress className="h5">{name}</p>
|
||||
<small className="form-text text-muted">
|
||||
{intl.formatMessage(messages['profile.name.details'])}
|
||||
</small>
|
||||
@@ -112,7 +112,7 @@ class Name extends React.Component {
|
||||
static: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader content={intl.formatMessage(messages['profile.name.full.name'])} />
|
||||
<p className="h5">{name}</p>
|
||||
<p data-hj-suppress className="h5">{name}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-i18n';
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.name.full.name': {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { ValidationFormGroup } from '@edx/paragon';
|
||||
|
||||
import messages from './PreferredLanguage.messages';
|
||||
@@ -86,6 +86,7 @@ class PreferredLanguage extends React.Component {
|
||||
{intl.formatMessage(messages['profile.preferredlanguage.label'])}
|
||||
</label>
|
||||
<select
|
||||
data-hj-suppress
|
||||
id={formId}
|
||||
name={formId}
|
||||
className="form-control"
|
||||
@@ -117,7 +118,7 @@ class PreferredLanguage extends React.Component {
|
||||
showVisibility={visibilityLanguageProficiencies !== null}
|
||||
visibility={visibilityLanguageProficiencies}
|
||||
/>
|
||||
<p className="h5">{languageMessages[value]}</p>
|
||||
<p data-hj-suppress className="h5">{languageMessages[value]}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
empty: (
|
||||
@@ -135,7 +136,7 @@ class PreferredLanguage extends React.Component {
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.preferredlanguage.label'])}
|
||||
/>
|
||||
<p className="h5">{languageMessages[value]}</p>
|
||||
<p data-hj-suppress className="h5">{languageMessages[value]}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-i18n';
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.preferredlanguage.empty': {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Dropdown } from '@edx/paragon';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { ReactComponent as DefaultAvatar } from '../assets/avatar.svg';
|
||||
|
||||
@@ -109,6 +109,7 @@ class ProfileAvatar extends React.Component {
|
||||
<DefaultAvatar className="text-muted" role="img" aria-hidden focusable="false" viewBox="0 0 24 24" />
|
||||
) : (
|
||||
<img
|
||||
data-hj-suppress
|
||||
className="w-100 h-100 d-block rounded-circle overflow-hidden"
|
||||
style={{ objectFit: 'cover' }}
|
||||
alt={intl.formatMessage(messages['profile.image.alt.attribute'])}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-i18n';
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.image.alt.attribute': {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { StatusAlert } from '@edx/paragon';
|
||||
import { connect } from 'react-redux';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faTwitter, faFacebook, faLinkedin } from '@fortawesome/free-brands-svg-icons';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import messages from './SocialLinks.messages';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-i18n';
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.sociallinks.social.links': {
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Button } from '@edx/paragon';
|
||||
|
||||
import messages from './EditButton.messages';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-i18n';
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.editbutton.edit': {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, StatefulButton } from '@edx/paragon';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import messages from './FormControls.messages';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-i18n';
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.formcontrols.who.can.see': {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faEyeSlash, faEye } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineMessages } from '@edx/frontend-i18n';
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.visibility.who.just.me': {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { applyMiddleware, createStore } from 'redux';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
|
||||
import { createLogger } from 'redux-logger';
|
||||
|
||||
import createRootReducer from '../reducers';
|
||||
import rootSaga from '../sagas';
|
||||
|
||||
export default function configureStore(initialState = {}) {
|
||||
const loggerMiddleware = createLogger({
|
||||
collapsed: true,
|
||||
});
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
|
||||
const store = createStore(
|
||||
createRootReducer(),
|
||||
initialState,
|
||||
composeWithDevTools(applyMiddleware(thunkMiddleware, sagaMiddleware, loggerMiddleware)), // eslint-disable-line
|
||||
);
|
||||
|
||||
sagaMiddleware.run(rootSaga);
|
||||
|
||||
return store;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { applyMiddleware, createStore, compose } from 'redux';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
|
||||
import createRootReducer from '../reducers';
|
||||
import rootSaga from '../sagas';
|
||||
|
||||
export default function configureStore(initialState = {}) {
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
|
||||
const store = createStore(
|
||||
createRootReducer(),
|
||||
initialState,
|
||||
compose(applyMiddleware(thunkMiddleware, sagaMiddleware)),
|
||||
);
|
||||
|
||||
sagaMiddleware.run(rootSaga);
|
||||
|
||||
return store;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import configureStoreProd from './configureStore.prod';
|
||||
import configureStoreDev from './configureStore.dev';
|
||||
|
||||
export default function configureStore(state, env) {
|
||||
if (env === 'production') {
|
||||
return configureStoreProd(state);
|
||||
}
|
||||
return configureStoreDev(state);
|
||||
}
|
||||
Reference in New Issue
Block a user