Compare commits

...

73 Commits

Author SHA1 Message Date
morenol
cd24d0da8d fix: update frontend-build and frontend-platform (#386)
* fix: update frontend-build

* Upgrade frontend-platform
2020-11-10 16:31:52 -05:00
Zainab Amir
2f9f573698 feat: add hotjar suppression to PII (#385) 2020-09-22 11:13:01 +05:00
Renovate Bot
563cd4b8df fix(deps): update dependency @edx/frontend-component-footer to v10.0.11 2020-09-20 22:39:15 +00:00
Renovate Bot
25911bdfeb chore(deps): update dependency enzyme-adapter-react-16 to v1.15.4 2020-09-20 21:39:11 +00:00
Renovate Bot
179fbaa452 chore(deps): update dependency codecov to v3.7.2 2020-09-20 20:37:15 +00:00
Jeff LaJoie
73df912f29 Merge pull request #378 from edx/jlajoie/ENT-3302
fix: ENTERPRISE_LEARNER_PORTAL_HOSTNAME added for header updates
2020-08-06 07:25:49 -04:00
Jeff LaJoie
d64e497407 fix: ENTERPRISE_LEARNER_PORTAL_HOSTNAME added for header updates 2020-08-06 07:09:53 -04:00
dependabot[bot]
3725a36b30 build(deps-dev): bump codecov from 3.6.5 to 3.7.1 (#376)
Bumps [codecov](https://github.com/codecov/codecov-node) from 3.6.5 to 3.7.1.
- [Release notes](https://github.com/codecov/codecov-node/releases)
- [Commits](https://github.com/codecov/codecov-node/compare/v3.6.5...v3.7.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-07-21 09:24:54 -04:00
Renovate Bot
3cd6999886 fix(deps): update dependency @edx/frontend-component-footer to v10.0.9 2020-04-26 11:11:35 +00:00
David Joy
3ff2a0a359 fix: fixing LOGOUT_URL for dev and test (#374) 2020-04-21 13:06:15 -04:00
Renovate Bot
5b0f79f6f9 fix(deps): update dependency @edx/frontend-component-footer to v10.0.7 2020-02-07 17:13:29 +00:00
Renovate Bot
d3490985d2 chore(deps): update dependency codecov to v3.6.5 2020-02-07 15:10:42 +00:00
Renovate Bot
82300bf99a chore(deps): update dependency codecov to v3.6.4 2020-02-01 03:11:48 +00:00
Renovate Bot
c096bf3d28 chore(deps): update dependency codecov to v3.6.3 2020-01-31 14:12:36 +00:00
Renovate Bot
ac885e49d7 chore(deps): update dependency @edx/frontend-build to v2.0.6 2020-01-29 21:12:22 +00:00
Renovate Bot
c925666501 fix(deps): update dependency @edx/frontend-platform to v1.1.14 2020-01-28 13:13:15 +00:00
Renovate Bot
c3e30e8163 chore(deps): update dependency codecov to v3.6.2 2020-01-23 19:13:36 +00:00
Renovate Bot
d5d2279797 fix(deps): update dependency @edx/frontend-component-header to v2.0.5 2020-01-22 18:41:21 +00:00
Renovate Bot
8bb03e1404 fix(deps): update dependency @edx/frontend-component-header to v2.0.4 2020-01-21 19:10:39 +00:00
Renovate Bot
d84ef68648 fix(deps): update dependency @edx/frontend-platform to v1.1.13 2019-12-28 00:11:05 +00:00
Renovate Bot
ba9cec39cf fix(deps): update dependency redux to v4.0.5 2019-12-24 03:12:01 +00:00
David Joy
a13da63d25 Updating openedx.yaml to include profile in Open edX releases. 2019-12-20 13:27:03 -05:00
Renovate Bot
22dd41f4ee chore(deps): update dependency enzyme-adapter-react-16 to v1.15.2 2019-12-20 00:10:14 +00:00
Renovate Bot
f38a03b9ce fix(deps): update dependency @edx/frontend-platform to v1.1.12 2019-12-15 01:11:34 +00:00
Renovate Bot
04131d1636 chore(deps): update dependency @edx/frontend-build to v2.0.5 2019-12-12 18:19:34 +00:00
Renovate Bot
2aab012b4d chore(deps): update dependency redux-mock-store to v1.5.4 2019-12-11 14:14:04 +00:00
Renovate Bot
9f27335d2b fix(deps): update dependency @edx/frontend-component-footer to v10.0.6 2019-12-11 01:05:29 +00:00
Renovate Bot
cdac04a0d7 fix(deps): update dependency @edx/frontend-platform to v1.1.11 2019-12-10 22:21:10 +00:00
Renovate Bot
0b58870714 fix(deps): update dependency @edx/frontend-platform to v1.1.10 2019-12-09 17:11:44 +00:00
Renovate Bot
106cba1785 fix(deps): update dependency @edx/frontend-component-footer to v10.0.5 2019-12-06 18:13:49 +00:00
Renovate Bot
820768367f fix(deps): update dependency @fortawesome/react-fontawesome to v0.1.8 2019-12-06 17:12:56 +00:00
Renovate Bot
18df696902 fix(deps): update dependency @edx/frontend-platform to v1.1.9 2019-12-05 23:14:59 +00:00
Renovate Bot
f76383f034 fix(deps): update dependency @edx/frontend-component-footer to v10.0.4 2019-12-05 00:27:48 +00:00
Renovate Bot
f0ddf3bf09 fix(deps): update dependency @edx/frontend-platform to v1.1.8 2019-12-04 22:13:44 +00:00
renovate[bot]
11ac45f6b8 chore(deps): update dependency codecov to v3.6.1 (#228) 2019-12-04 16:38:04 -05:00
edX Transifex Bot
67c23630b9 fix(i18n): update translations 2019-12-04 16:18:11 -05:00
Renovate Bot
0ef787b9ce fix(deps): update dependency @edx/frontend-platform to v1.1.6 2019-12-04 20:19:41 +00:00
Renovate Bot
c3e3024971 chore(deps): update dependency glob to v7.1.6 2019-12-04 17:17:28 +00:00
Adam Butterworth
a95bf5006b fix: remove unneeded commitlint dependencies (#339) 2019-12-04 11:21:36 -05:00
Renovate Bot
517f3f57bf fix(deps): update dependency @edx/frontend-component-header to v2.0.3 2019-12-03 23:15:06 +00:00
Renovate Bot
2e464498d8 fix(deps): update dependency @edx/frontend-component-footer to v10.0.3 2019-12-03 22:18:45 +00:00
Renovate Bot
3f519aa678 chore(deps): update dependency @edx/frontend-build to v2.0.4 2019-12-03 21:05:49 +00:00
Renovate Bot
5b7a29422b fix(deps): update dependency @edx/frontend-component-header to v2.0.2 2019-12-03 20:53:22 +00:00
Renovate Bot
f3d9f2ae06 fix(deps): update dependency @edx/frontend-component-footer to v10.0.2 2019-12-03 20:14:16 +00:00
edX Transifex Bot
b5607e9b83 fix(i18n): update translations 2019-12-03 13:55:04 -05:00
David Joy
fd9b393bc7 fix: updating frontend-build to fix translations (#332) 2019-12-03 13:28:28 -05:00
renovate[bot]
0fe5354047 chore(deps): update dependency enzyme-adapter-react-16 to v1.15.1 (#295) 2019-12-03 11:24:05 -05:00
Renovate Bot
71923503c4 fix(deps): update dependency @edx/frontend-component-footer to v10.0.1 2019-12-03 16:16:55 +00:00
Renovate Bot
f1a620a784 fix(deps): update dependency @edx/frontend-component-header to v2.0.1 2019-12-03 15:39:12 +00:00
David Joy
645f378600 fix: updating frontend-platform required small refactorings (#328) 2019-12-02 17:28:32 -05:00
David Joy
057a39085c Revert "build: add newrelic plugin back in as experiment (#326)" (#327)
This reverts commit 3f31aba5a7.
2019-12-02 13:32:30 -05:00
David Joy
3f31aba5a7 build: add newrelic plugin back in as experiment (#326) 2019-12-02 13:12:38 -05:00
David Joy
321448c674 fix: using latest published version of frontend-build again 2019-12-02 12:56:51 -05:00
David Joy
77c9984d34 fix: bumping frontend-build version
Debugging build process, so pushing to master to speed up workflow.  Yes, I know.
2019-12-02 11:28:30 -05:00
David Joy
c9d0a7f86f fix: temporarily pin frontend-build to github link to test stage build (#325) 2019-12-02 11:18:58 -05:00
edX Transifex Bot
04d51ffee1 fix(i18n): update translations 2019-12-01 15:40:22 -05:00
Renovate Bot
25f6b86e92 fix(deps): update font awesome 2019-11-25 16:20:53 +00:00
Renovate Bot
c700a47e40 fix(deps): update dependency react-redux to v7.1.3 2019-11-25 15:23:57 +00:00
Adam Butterworth
db4b082247 feat: use frontend-platform for runways (#318)
Move logging, analytics, auth, base, and i18n to implementations in frontend-platform.
2019-11-25 09:29:14 -05:00
Renovate Bot
919677b544 fix(deps): update dependency form-urlencoded to v3.0.2 2019-11-06 17:19:41 +00:00
David Joy
32f0f33975 build: automerge patch renovate PRs (#309) 2019-11-04 17:14:41 -05:00
renovate[bot]
0662177f40 chore(deps): pin dependencies (#304) 2019-11-04 17:03:26 -05:00
David Joy
f266bba04c fix: initialize redux in prod mode by default (#303)
We were accidentally initializing in dev mode in production.

Also cleaning up some other misc detritus.
2019-10-22 15:11:35 -04:00
renovate[bot]
20e7bb09bb chore(deps): update dependency @edx/frontend-build to v1.2.2 (#302) 2019-10-18 10:20:34 -04:00
David Joy
da7e055d77 fix: send analytics events (#301)
frontend-base wasn’t calling identifyAuthenticatedUser and sendPageEvent.  Version 4.1.0 does.
2019-10-16 12:14:42 -04:00
Adam Butterworth
bee2e54b87 Update README.rst 2019-10-15 11:20:44 -04:00
Adam Butterworth
1b179f55d1 Update README.rst (#294)
* Update README.rst

* Update README.rst

* Update README.rst

* Update README.rst

* Update README.rst

* doc: updating readme with feedback

* Update README.rst
2019-10-11 17:27:53 -04:00
David Joy
edef77f101 fix: bump frontend-build one more time (#299) 2019-10-11 14:28:32 -04:00
David Joy
e4f1a6100a feat: add a favicon to the distribution (#297) 2019-10-11 13:25:15 -04:00
David Joy
b3869b97f7 fix: remove unnecessary environment variable (#292)
* fix: remove unnecessary environment variable

* fix: bumping version of frontend-base for csrf env var change

* fix: get IDE linting to work again
2019-10-10 14:16:25 -04:00
Adam Butterworth
cd339d491d Create 0002-build-time-customization-with-npm-aliases.rst (#291) 2019-10-07 16:05:57 -04:00
David Joy
b66cbb3eb6 fix: frontend-base 4.0.0 and a few others (#288)
* fix: bump version of frontend-base to 4.0.0

* fix: bumping a few other versions

* fix: bump footer version and lock travis npm version

* fix: use dist not src for header/footer styless
2019-10-02 15:54:25 -04:00
Adam Butterworth
cd2fdae725 feat: use openedx footer by default (#282)
* feat: use openedx footer by default

* fix: delete footer directory and update package-lock
2019-09-30 16:53:31 -04:00
61 changed files with 10975 additions and 6013 deletions

2
.env
View File

@@ -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

View File

@@ -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'

View File

@@ -1,13 +1,12 @@
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'

3
.eslintrc.js Normal file
View File

@@ -0,0 +1,3 @@
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('eslint');

View File

@@ -1,11 +1,9 @@
.tx
coverage
dist
footer
node_modules
public
src
webpack
.dockerignore
.eslintignore
.eslintrc

View File

@@ -1,7 +1,7 @@
language: node_js
node_js: 12
before_install:
- npm install -g npm@latest
- npm install -g npm@6
install:
- npm ci
script:

View File

@@ -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
View File

@@ -0,0 +1,3 @@
module.exports = {
extends: ['@commitlint/config-angular'],
};

View File

@@ -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.

View File

@@ -1,99 +0,0 @@
import React from 'react';
import SiteFooter from '@edx/frontend-component-footer';
import { sendTrackEvent } from '@edx/frontend-analytics';
import { App, validateConfig } from '@edx/frontend-base';
import {
faFacebookSquare,
faTwitterSquare,
faYoutubeSquare,
faLinkedin,
faRedditSquare,
} from '@fortawesome/free-brands-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import FooterLogo from './edx-footer.png';
const config = {
APPLE_APP_STORE_URL: process.env.APPLE_APP_STORE_URL,
CONTACT_URL: process.env.CONTACT_URL,
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM: process.env.ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM,
ENTERPRISE_MARKETING_URL: process.env.ENTERPRISE_MARKETING_URL,
ENTERPRISE_MARKETING_UTM_CAMPAIGN: process.env.ENTERPRISE_MARKETING_UTM_CAMPAIGN,
ENTERPRISE_MARKETING_UTM_SOURCE: process.env.ENTERPRISE_MARKETING_UTM_SOURCE,
FACEBOOK_URL: process.env.FACEBOOK_URL,
GOOGLE_PLAY_URL: process.env.GOOGLE_PLAY_URL,
LINKED_IN_URL: process.env.LINKED_IN_URL,
OPEN_SOURCE_URL: process.env.OPEN_SOURCE_URL,
PRIVACY_POLICY_URL: process.env.PRIVACY_POLICY_URL,
REDDIT_URL: process.env.REDDIT_URL,
SUPPORT_URL: process.env.SUPPORT_URL,
TERMS_OF_SERVICE_URL: process.env.TERMS_OF_SERVICE_URL,
TWITTER_URL: process.env.TWITTER_URL,
YOU_TUBE_URL: process.env.YOU_TUBE_URL,
};
App.requireConfig(['SITE_NAME', 'MARKETING_SITE_BASE_URL'], 'ProfileFooter');
validateConfig(config, 'ProfileFooter');
export default function ProfileFooter() {
const socialLinks = [
{
title: 'Facebook',
url: config.FACEBOOK_URL,
icon: <FontAwesomeIcon icon={faFacebookSquare} className="social-icon" size="2x" />,
screenReaderText: 'Like edX on Facebook',
},
{
title: 'Twitter',
url: config.TWITTER_URL,
icon: <FontAwesomeIcon icon={faTwitterSquare} className="social-icon" size="2x" />,
screenReaderText: 'Follow edX on Twitter',
},
{
title: 'Youtube',
url: config.YOU_TUBE_URL,
icon: <FontAwesomeIcon icon={faYoutubeSquare} className="social-icon" size="2x" />,
screenReaderText: 'Subscribe to the edX YouTube channel',
},
{
title: 'LinkedIn',
url: config.LINKED_IN_URL,
icon: <FontAwesomeIcon icon={faLinkedin} className="social-icon" size="2x" />,
screenReaderText: 'Follow edX on LinkedIn',
},
{
title: 'Reddit',
url: config.REDDIT_URL,
icon: <FontAwesomeIcon icon={faRedditSquare} className="social-icon" size="2x" />,
screenReaderText: 'Subscribe to the edX subreddit',
},
];
const enterpriseMarketingLinkData = {
url: config.ENTERPRISE_MARKETING_URL,
queryParams: {
utm_campaign: config.ENTERPRISE_MARKETING_UTM_CAMPAIGN,
utm_source: config.ENTERPRISE_MARKETING_UTM_SOURCE,
utm_medium: config.ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM,
},
};
return (
<SiteFooter
appleAppStoreUrl={config.APPLE_APP_STORE_URL}
contactUrl={config.CONTACT_URL}
enterpriseMarketingLink={enterpriseMarketingLinkData}
googlePlayUrl={config.GOOGLE_PLAY_URL}
handleAllTrackEvents={sendTrackEvent}
marketingSiteBaseUrl={App.config.MARKETING_SITE_BASE_URL}
openSourceUrl={config.OPEN_SOURCE_URL}
privacyPolicyUrl={config.PRIVACY_POLICY_URL}
siteLogo={FooterLogo}
siteName={App.config.SITE_NAME}
socialLinks={socialLinks}
supportUrl={config.SUPPORT_URL}
termsOfServiceUrl={config.TERMS_OF_SERVICE_URL}
/>
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -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}

15936
package-lock.json generated

File diff suppressed because it is too large Load Diff

72
package.json Executable file → Normal file
View 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": "2.2.0",
"@edx/frontend-component-footer": "6.0.2",
"@edx/frontend-component-header": "1.1.2",
"@edx/frontend-i18n": "3.0.2",
"@edx/frontend-logging": "3.0.1",
"@edx/frontend-component-footer": "10.0.11",
"@edx/frontend-component-header": "2.0.5",
"@edx/frontend-platform": "1.6.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.0.1",
"codecov": "3.1.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.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.15.4",
"es-check": "5.0.0",
"glob": "^7.1.3",
"purgecss-webpack-plugin": "^1.6.0",
"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"
}
}

View File

@@ -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>

View File

@@ -1,5 +1,9 @@
{
"extends": [
"config:base"
]
],
"patch": {
"automerge": true
},
"rebaseStalePrs": true
}

BIN
src/assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View 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;
}

View File

@@ -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,
});

View File

@@ -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([

View File

@@ -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",

View File

@@ -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..."
}

View File

@@ -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..."
}

View File

@@ -1,19 +1,23 @@
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';
import Header, { messages as headerMessages } from '@edx/frontend-component-header';
import Footer from '../footer/Footer';
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,8 +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({ messages: [appMessages, headerMessages] });
initialize({
messages: [
appMessages,
headerMessages,
footerMessages,
],
requireAuthenticatedUser: true,
hydrateAuthenticatedUser: true,
});

View File

@@ -3,5 +3,5 @@
@import './profile/index.scss';
@import "~@edx/frontend-component-header/src/index";
@import "~@edx/frontend-component-footer/src/lib/scss/site-footer";
@import "~@edx/frontend-component-header/dist/index";
@import "~@edx/frontend-component-footer/dist/footer";

View File

@@ -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 (

View File

@@ -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;

View File

@@ -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 (

View File

@@ -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.requireConfig(['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,

View File

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

View File

@@ -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');

View File

@@ -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)",
}
}
/>

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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';
const { LMS_BASE_URL } = App.requireConfig(['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(`${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(`${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(
`${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(`${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(`${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(`${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 ?
`${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 = `${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 [];
}
}

View File

@@ -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>
),
}}

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-i18n';
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'profile.bio.about.me': {

View File

@@ -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';

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-i18n';
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'profile.certificates.my.certificates': {

View File

@@ -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>
),
}}

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-i18n';
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'profile.country.label': {

View File

@@ -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}`,

View File

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

View File

@@ -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>
),
}}

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-i18n';
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'profile.name.full.name': {

View File

@@ -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>
),
}}

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-i18n';
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'profile.preferredlanguage.empty': {

View File

@@ -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'])}

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-i18n';
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'profile.image.alt.attribute': {

View File

@@ -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';

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-i18n';
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'profile.sociallinks.social.links': {

View File

@@ -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';

View File

@@ -1,4 +1,4 @@
import { defineMessages } from '@edx/frontend-i18n';
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'profile.editbutton.edit': {

View File

@@ -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';

View File

@@ -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': {

View File

@@ -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';

View File

@@ -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': {

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}