Compare commits

...

69 Commits

Author SHA1 Message Date
Renovate Bot
e4b218a47e fix(deps): update dependency @edx/frontend-component-header to v2.2.4 2021-02-08 20:35:27 +00:00
Renovate Bot
1f6cfefe18 fix(deps): update dependency @edx/frontend-platform to v1.8.4 2021-02-05 23:03:57 +00:00
Renovate Bot
efa68ef0be fix(deps): update dependency @edx/frontend-component-header to v2.2.3 2021-02-05 21:26:13 +00:00
Renovate Bot
aeebd4de33 fix(deps): update dependency @edx/frontend-component-footer to v10.1.4 2021-02-05 20:39:02 +00:00
Renovate Bot
849b0101e3 chore(deps): update dependency enzyme-adapter-react-16 to v1.15.6 2021-02-05 19:24:14 +00:00
David Joy
e3b692b9f2 Update Paragon to 13 and make the app brandable (#397)
* fix: fixing broken linter script and linting

The linter script in package.json didn’t specify which files to lint, so it never linted anything.  As soon as I fixed the script line, there was suddenly a ton of stuff that needed linting.

This fixes the script and cleans up all the things that needed linting.  The vast majority were formatting auto-fixes in VSCode for me.

* fix: setting NODE_ENV to production in .env

Without this change, the NODE_ENV comes through as “null” (a string!) in the app.  This causes a number of third party dependencies like React and Redux to potentially go into development mode, slowing them down, or to not realize they’re in production mode, causing them to throw some warnings.

* style: some additional linting

* feat: upgrading to modern paragon and a brand package

This commit updates the app from Paragon 7 to 12 and fixes the breaking changes in between.  Mostly small changes to Button and Dropdown, as well as using “container” instead of “container-fluid” to preserve the page’s width as closely as possible.

It also adds the brand package, which is why it’s a feature.  Using the brand package allows the MFE to be rebranded by using an npm alias to override the source of the brand.

* test: fixing test snapshots that failed when updating paragon

The test snapshots got a bit out of date when updating paragon.  Also removing an unncessary “type” from Dropdown.Toggle which does nothing.

- container has been replaced by container-fluid
- The Button component is a different implementation, which adds slightly different properties to the rendered button.  i.e., onKeyDown and disabled, but doesn’t add the id or onBlur.
- The Dropdown doesn’t render its contents until it’s opened, which is why “Upload Photo” and “Remove” are no longer in the snapshot.
-

* build: bumping paragon to 13

* fix: fixing broken test snapshot

btn-outline is definitely not a correct button type.

* fix: using the ‘size’ property on Button

* fix: updating dependencies

We needed to upgrade paragon to 13.1.2 to fix a transpilation issue that was causing ES6 code to be included in the build artifact.  All other upgrades here were attempts at fixing that, but they’re all also perfectly valid and good to update, so I left them.

babel-polyfill has been replaced by including core-js and regenerator-runtime.

Upgrading frontend-build fixed an issue with eslint configuration that emerged during the other upgrades.

* fix: switch back to container-fluid

We want to leave it as container-fluid and solve the max width problem through paragon.

* style: cleanup and formatting of SCSS

Also removing an unnecessary variant of primary on a button.

* test: fix broken snapshot test
2021-02-05 13:38:36 -05:00
David Joy
616c620432 fix: bumping frontend-platform to latest (#395) 2021-01-05 16:39:51 -05:00
edX Transifex Bot
fb508152f3 fix(i18n): update translations 2020-12-27 15:40:47 -05:00
Renovate Bot
e68c280b11 chore(deps): update dependency enzyme-adapter-react-16 to v1.15.5 2020-12-21 06:11:08 +00:00
Adam Stankiewicz
cb738286ed fix: upgrade header and footer to use logo from shared config (#390) 2020-12-16 11:25:21 -05:00
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
57 changed files with 11004 additions and 8533 deletions

7
.env
View File

@@ -1,4 +1,4 @@
NODE_ENV=null
NODE_ENV='production'
ACCESS_TOKEN_COOKIE_NAME=null
BASE_URL=null
CREDENTIALS_BASE_URL=null
@@ -16,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
@@ -30,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=''

View File

@@ -8,7 +8,7 @@ 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'
@@ -17,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'
@@ -31,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

View File

@@ -6,10 +6,14 @@ 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

View File

@@ -4,3 +4,4 @@
nick: prof
oeps: {}
owner: edx/arch-team
openedx-release: {ref: master}

18308
package-lock.json generated

File diff suppressed because it is too large Load Diff

52
package.json Executable file → Normal file
View File

@@ -14,7 +14,7 @@
"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",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
"test": "fedx-scripts jest --coverage --passWithNoTests"
@@ -40,21 +40,18 @@
"ie 11"
],
"dependencies": {
"@edx/frontend-analytics": "3.0.0",
"@edx/frontend-auth": "7.0.1",
"@edx/frontend-base": "4.1.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/paragon": "7.1.3",
"@fortawesome/fontawesome-svg-core": "1.2.14",
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-component-footer": "10.1.4",
"@edx/frontend-component-header": "2.2.4",
"@edx/frontend-platform": "1.8.4",
"@edx/paragon": "13.1.2",
"@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",
"babel-polyfill": "6.26.0",
"@fortawesome/free-regular-svg-icons": "5.7.2",
"@fortawesome/free-solid-svg-icons": "5.7.2",
"@fortawesome/react-fontawesome": "0.1.8",
"classnames": "2.2.6",
"core-js": "3.8.3",
"email-prop-type": "1.1.7",
"font-awesome": "4.7.0",
"form-urlencoded": "3.0.2",
@@ -66,35 +63,34 @@
"lodash.snakecase": "4.1.1",
"newrelic": "5.5.0",
"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": "16.14.0",
"react-dom": "16.14.0",
"react-redux": "7.2.2",
"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",
"redux-thunk": "2.3.0",
"regenerator-runtime": "0.13.7",
"reselect": "4.0.0",
"universal-cookie": "3.1.0"
},
"devDependencies": {
"@commitlint/cli": "8.2.0",
"@commitlint/config-angular": "8.2.0",
"@commitlint/prompt": "8.2.0",
"@commitlint/prompt-cli": "8.2.0",
"@edx/frontend-build": "1.2.2",
"codecov": "3.1.0",
"@edx/frontend-build": "5.6.8",
"codecov": "3.7.2",
"enzyme": "3.10.0",
"enzyme-adapter-react-16": "1.14.0",
"enzyme-adapter-react-16": "1.15.6",
"es-check": "5.0.0",
"glob": "7.1.3",
"husky": "3.0.9",
"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,7 +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="/favicon.ico" type="image/x-icon" />
<link rel="shortcut icon" href="<%=webpackConfig.output.publicPath%>favicon.ico" type="image/x-icon" />
</head>
<body>
<div id="root"></div>

View File

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

View File

@@ -1,12 +1,9 @@
import { combineReducers } from 'redux';
import { userAccount } from '@edx/frontend-auth';
import { reducer as profilePage } from '../profile';
const createRootReducer = () =>
combineReducers({
userAccount,
profilePage,
});
const createRootReducer = () => combineReducers({
profilePage,
});
export default createRootReducer;

View File

@@ -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": "جاري تحميل الملف الشخصي ..."
}

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,6 +1,17 @@
import 'babel-polyfill';
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import {
APP_INIT_ERROR,
APP_READY,
initialize,
subscribe,
} from '@edx/frontend-platform';
import {
AppProvider,
ErrorPage,
} from '@edx/frontend-platform/react';
import { App, AppProvider, APP_ERROR, APP_READY, ErrorPage } from '@edx/frontend-base';
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, Switch } from 'react-router-dom';
@@ -15,7 +26,7 @@ import configureStore from './data/configureStore';
import './index.scss';
import './assets/favicon.ico';
App.subscribe(APP_READY, () => {
subscribe(APP_READY, () => {
ReactDOM.render(
<AppProvider store={configureStore()}>
<Header />
@@ -32,14 +43,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,
});

View File

@@ -1,7 +1,11 @@
@import '~@edx/paragon/scss/edx/theme.scss';
@import '~@edx/paragon/scss/edx/fonts.scss'; // Roboto
@import './profile/index.scss';
$fa-font-path: "~font-awesome/fonts";
@import "~font-awesome/scss/font-awesome";
@import "~@edx/brand/paragon/fonts";
@import "~@edx/brand/paragon/variables";
@import "~@edx/paragon/scss/core/core";
@import "~@edx/brand/paragon/overrides";
@import "~@edx/frontend-component-header/dist/index";
@import "~@edx/frontend-component-footer/dist/footer";
@import './profile/index';

View File

@@ -1,14 +1,14 @@
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 (
<StatusAlert
alertType="info"
dialog={
<React.Fragment>
dialog={(
<>
<FormattedMessage
id="profile.age.headline"
defaultMessage="Your profile cannot be shared."
@@ -28,8 +28,8 @@ function AgeMessage({ accountSettingsUrl }) {
description="label on a link to set birthday"
/>
</a>
</React.Fragment>
}
</>
)}
dismissible={false}
open
/>

View File

@@ -1,9 +1,11 @@
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;
if (date == null) {
return null;
}
return (
<p className="mb-0">

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.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,
@@ -111,11 +111,13 @@ class ProfilePage extends React.Component {
const { dateJoined } = this.props;
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" />
</React.Fragment>
<>
<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>
</>
);
}
@@ -177,7 +179,6 @@ class ProfilePage extends React.Component {
changeHandler: this.handleChange,
};
return (
<div className="container-fluid">
<div className="row align-items-center pt-4 mb-4 pt-md-0 mb-md-0">
@@ -332,7 +333,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 +372,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"
@@ -237,46 +245,24 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
>
<div
className="dropdown"
onKeyDown={[Function]}
>
<button
aria-expanded={false}
aria-haspopup={true}
className="dropdown-toggle btn btn-light"
id="pgn__dropdown-trigger-0"
className="dropdown-toggle btn btn-primary"
disabled={false}
onClick={[Function]}
type="btn-outline"
type="button"
>
Change
</button>
<div
aria-hidden={true}
aria-labelledby="pgn__dropdown-trigger-0"
className="dropdown-menu"
onKeyDown={[Function]}
role="menu"
>
<button
className="dropdown-item"
onClick={[Function]}
>
<span>
Upload Photo
</span>
</button>
<button
className="dropdown-item"
onClick={[Function]}
>
<span>
Remove
</span>
</button>
</div>
</div>
</div>
<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 +293,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 +349,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"
@@ -424,10 +418,9 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
>
Full Name
<button
className="btn btn-sm btn-link float-right px-0"
onBlur={[Function]}
className="float-right px-0 btn btn-link btn-sm"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"marginTop": "-.35rem",
@@ -485,6 +478,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
</div>
<p
className="h5"
data-hj-suppress={true}
>
Lemon Seltzer
</p>
@@ -519,10 +513,9 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
>
Location
<button
className="btn btn-sm btn-link float-right px-0"
onBlur={[Function]}
className="float-right px-0 btn btn-link btn-sm"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"marginTop": "-.35rem",
@@ -580,6 +573,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
</div>
<p
className="h5"
data-hj-suppress={true}
>
Montenegro
</p>
@@ -609,10 +603,9 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
>
Primary Language Spoken
<button
className="btn btn-sm btn-link float-right px-0"
onBlur={[Function]}
className="float-right px-0 btn btn-link btn-sm"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"marginTop": "-.35rem",
@@ -670,6 +663,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
</div>
<p
className="h5"
data-hj-suppress={true}
>
Yoruba
</p>
@@ -699,10 +693,9 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
>
Education
<button
className="btn btn-sm btn-link float-right px-0"
onBlur={[Function]}
className="float-right px-0 btn btn-link btn-sm"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"marginTop": "-.35rem",
@@ -760,6 +753,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
</div>
<p
className="h5"
data-hj-suppress={true}
>
Elementary/primary school
</p>
@@ -789,10 +783,9 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
>
Social Links
<button
className="btn btn-sm btn-link float-right px-0"
onBlur={[Function]}
className="float-right px-0 btn btn-link btn-sm"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"marginTop": "-.35rem",
@@ -914,6 +907,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
onClick={[Function]}
onKeyDown={[Function]}
tabIndex={0}
type="button"
>
<svg
aria-hidden="true"
@@ -968,10 +962,9 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
>
About Me
<button
className="btn btn-sm btn-link float-right px-0"
onBlur={[Function]}
className="float-right px-0 btn btn-link btn-sm"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"marginTop": "-.35rem",
@@ -1029,6 +1022,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
</div>
<p
className="lead"
data-hj-suppress={true}
>
This is my bio
</p>
@@ -1058,10 +1052,9 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
>
My Certificates
<button
className="btn btn-sm btn-link float-right px-0"
onBlur={[Function]}
className="float-right px-0 btn btn-link btn-sm"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"marginTop": "-.35rem",
@@ -1130,7 +1123,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)",
}
}
/>
@@ -1238,46 +1231,24 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
<div
className="dropdown"
onKeyDown={[Function]}
>
<button
aria-expanded={false}
aria-haspopup={true}
className="dropdown-toggle btn btn-light"
id="pgn__dropdown-trigger-1"
className="dropdown-toggle btn btn-primary"
disabled={false}
onClick={[Function]}
type="btn-outline"
type="button"
>
Change
</button>
<div
aria-hidden={true}
aria-labelledby="pgn__dropdown-trigger-1"
className="dropdown-menu"
onKeyDown={[Function]}
role="menu"
>
<button
className="dropdown-item"
onClick={[Function]}
>
<span>
Upload Photo
</span>
</button>
<button
className="dropdown-item"
onClick={[Function]}
>
<span>
Remove
</span>
</button>
</div>
</div>
</div>
<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 +1279,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 +1335,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"
@@ -1425,10 +1404,9 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
Full Name
<button
className="btn btn-sm btn-link float-right px-0"
onBlur={[Function]}
className="float-right px-0 btn btn-link btn-sm"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"marginTop": "-.35rem",
@@ -1486,6 +1464,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</div>
<p
className="h5"
data-hj-suppress={true}
>
Lemon Seltzer
</p>
@@ -1520,10 +1499,9 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
Location
<button
className="btn btn-sm btn-link float-right px-0"
onBlur={[Function]}
className="float-right px-0 btn btn-link btn-sm"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"marginTop": "-.35rem",
@@ -1581,6 +1559,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</div>
<p
className="h5"
data-hj-suppress={true}
>
Montenegro
</p>
@@ -1610,10 +1589,9 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
Primary Language Spoken
<button
className="btn btn-sm btn-link float-right px-0"
onBlur={[Function]}
className="float-right px-0 btn btn-link btn-sm"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"marginTop": "-.35rem",
@@ -1671,6 +1649,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</div>
<p
className="h5"
data-hj-suppress={true}
>
Yoruba
</p>
@@ -1700,10 +1679,9 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
Education
<button
className="btn btn-sm btn-link float-right px-0"
onBlur={[Function]}
className="float-right px-0 btn btn-link btn-sm"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"marginTop": "-.35rem",
@@ -1761,6 +1739,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</div>
<p
className="h5"
data-hj-suppress={true}
>
Elementary/primary school
</p>
@@ -1790,10 +1769,9 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
Social Links
<button
className="btn btn-sm btn-link float-right px-0"
onBlur={[Function]}
className="float-right px-0 btn btn-link btn-sm"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"marginTop": "-.35rem",
@@ -1915,6 +1893,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
onClick={[Function]}
onKeyDown={[Function]}
tabIndex={0}
type="button"
>
<svg
aria-hidden="true"
@@ -2053,10 +2032,9 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
<button
aria-disabled={false}
aria-live="assertive"
className="btn pgn__stateful-btn pgn__stateful-btn-state-pending btn-primary"
onBlur={[Function]}
className="pgn__stateful-btn pgn__stateful-btn-state-pending btn btn-primary"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
type="submit"
>
<span
@@ -2068,7 +2046,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
<span
aria-hidden={true}
className="icon fa fa-spinner fa-spin"
id="Icon2"
id="Icon1"
/>
</span>
Saving
@@ -2076,9 +2054,8 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</button>
<button
className="btn btn-link"
onBlur={[Function]}
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
Cancel
@@ -2113,10 +2090,9 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
My Certificates
<button
className="btn btn-sm btn-link float-right px-0"
onBlur={[Function]}
className="float-right px-0 btn btn-link btn-sm"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"marginTop": "-.35rem",
@@ -2185,7 +2161,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

@@ -92,7 +92,6 @@ describe('SAVE profile actions', () => {
});
});
describe('SAVE profile photo actions', () => {
it('should create an action to signal the start of a profile photo save', () => {
const formData = 'multipart form data';
@@ -141,7 +140,6 @@ describe('SAVE profile photo actions', () => {
});
});
describe('DELETE profile photo actions', () => {
it('should create an action to signal the start of a profile photo deletion', () => {
const expectedAction = {
@@ -179,7 +177,6 @@ describe('DELETE profile photo actions', () => {
});
});
describe('Editable field opening and closing actions', () => {
const formId = 'name';

View File

@@ -57,13 +57,13 @@ const profilePage = (state = initialState, action) => {
// Account is always replaced completely.
account: action.payload.account !== null ? action.payload.account : state.account,
// Preferences changes get merged in.
preferences: Object.assign({}, state.preferences, action.payload.preferences),
preferences: { ...state.preferences, ...action.payload.preferences },
};
case SAVE_PROFILE.FAILURE:
return {
...state,
saveState: 'error',
errors: Object.assign({}, state.errors, action.payload.errors),
errors: { ...state.errors, ...action.payload.errors },
};
case SAVE_PROFILE.RESET:
return {
@@ -82,7 +82,7 @@ const profilePage = (state = initialState, action) => {
return {
...state,
// Merge in new profile image data
account: Object.assign({}, state.account, { profileImage: action.payload.profileImage }),
account: { ...state.account, profileImage: action.payload.profileImage },
savePhotoState: 'complete',
errors: {},
};
@@ -90,7 +90,7 @@ const profilePage = (state = initialState, action) => {
return {
...state,
savePhotoState: 'error',
errors: Object.assign({}, state.errors, { photo: action.payload.error }),
errors: { ...state.errors, photo: action.payload.error },
};
case SAVE_PROFILE_PHOTO.RESET:
return {
@@ -109,7 +109,7 @@ const profilePage = (state = initialState, action) => {
return {
...state,
// Merge in new profile image data (should be empty or default image)
account: Object.assign({}, state.account, { profileImage: action.payload.profileImage }),
account: { ...state.account, profileImage: action.payload.profileImage },
savePhotoState: 'complete',
errors: {},
};
@@ -117,7 +117,7 @@ const profilePage = (state = initialState, action) => {
return {
...state,
savePhotoState: 'error',
errors: Object.assign({}, state.errors, action.payload.errors),
errors: { ...state.errors, ...action.payload.errors },
};
case DELETE_PROFILE_PHOTO.RESET:
return {
@@ -129,9 +129,7 @@ const profilePage = (state = initialState, action) => {
case UPDATE_DRAFT:
return {
...state,
drafts: Object.assign({}, state.drafts, {
[action.payload.name]: action.payload.value,
}),
drafts: { ...state.drafts, [action.payload.name]: action.payload.value },
};
case RESET_DRAFTS:

View File

@@ -1,8 +1,14 @@
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 {
all,
call,
delay,
put,
select,
takeEvery,
} from 'redux-saga/effects';
import {
closeForm,
deleteProfilePhotoBegin,
@@ -31,7 +37,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 +76,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 +182,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,12 @@
import { takeEvery, put, call, delay, select, all } from 'redux-saga/effects';
import { FETCH_USER_ACCOUNT_FAILURE } from '@edx/frontend-auth';
import {
takeEvery,
put,
call,
delay,
select,
all,
} from 'redux-saga/effects';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import * as profileActions from './actions';
import { handleSaveProfileSelector, userAccountSelector } from './selectors';
@@ -14,6 +21,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 +32,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 +49,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 +60,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 +88,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;
@@ -22,8 +22,7 @@ export const savePhotoStateSelector = state => state.profilePage.savePhotoState;
export const isLoadingProfileSelector = state => state.profilePage.isLoadingProfile;
export const currentlyEditingFieldSelector = state => state.profilePage.currentlyEditingField;
export const accountErrorsSelector = state => state.profilePage.errors;
export const isAuthenticatedUserProfileSelector = state =>
state.profilePage.isAuthenticatedUserProfile;
export const isAuthenticatedUserProfileSelector = state => state.profilePage.isAuthenticatedUserProfile;
export const editableFormModeSelector = createSelector(
profileAccountSelector,
@@ -147,13 +146,12 @@ export const certificatesSelector = createSelector(
export const profileImageSelector = createSelector(
profileAccountSelector,
account =>
(account.profileImage != null
? {
src: account.profileImage.imageUrlFull,
isDefault: !account.profileImage.hasImage,
}
: {}),
account => (account.profileImage != null
? {
src: account.profileImage.imageUrlFull,
isDefault: !account.profileImage.hasImage,
}
: {}),
);
/**

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';
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' },
});
@@ -120,12 +121,12 @@ function transformCertificateData(data) {
data.forEach((cert) => {
// download_url may be full url or absolute path.
// note: using the URL() api breaks in ie 11
const urlIsPath = typeof cert.download_url === 'string' &&
cert.download_url.search(/http[s]?:\/\//) !== 0;
const urlIsPath = typeof cert.download_url === 'string'
&& cert.download_url.search(/http[s]?:\/\//) !== 0;
const downloadUrl = urlIsPath ?
`${App.config.LMS_BASE_URL}${cert.download_url}` :
cert.download_url;
const downloadUrl = urlIsPath
? `${getConfig().LMS_BASE_URL}${cert.download_url}`
: cert.download_url;
transformedData.push({
...camelCaseObject(cert),
@@ -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 [];
}
}

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';
@@ -83,7 +83,7 @@ class Bio extends React.Component {
</div>
),
editable: (
<React.Fragment>
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.bio.about.me'])}
showEditButton
@@ -91,11 +91,11 @@ class Bio extends React.Component {
showVisibility={visibilityBio !== null}
visibility={visibilityBio}
/>
<p className="lead">{bio}</p>
</React.Fragment>
<p data-hj-suppress className="lead">{bio}</p>
</>
),
empty: (
<React.Fragment>
<>
<EditableItemHeader content={intl.formatMessage(messages['profile.bio.about.me'])} />
<EmptyContent onClick={this.handleOpen}>
<FormattedMessage
@@ -104,13 +104,13 @@ class Bio extends React.Component {
description="instructions when the user hasn't written an About Me"
/>
</EmptyContent>
</React.Fragment>
</>
),
static: (
<React.Fragment>
<>
<EditableItemHeader content={intl.formatMessage(messages['profile.bio.about.me'])} />
<p className="lead">{bio}</p>
</React.Fragment>
<p data-hj-suppress className="lead">{bio}</p>
</>
),
}}
/>

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,8 @@
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';
@@ -113,11 +115,13 @@ class Certificates extends React.Component {
renderCertificates() {
if (this.props.certificates === null || this.props.certificates.length === 0) {
return (<FormattedMessage
id="profile.no.certificates"
defaultMessage="You don't have any certificates yet."
description="displays when user has no course completion certificates"
/>);
return (
<FormattedMessage
id="profile.no.certificates"
defaultMessage="You don't have any certificates yet."
description="displays when user has no course completion certificates"
/>
);
}
return (
@@ -154,7 +158,7 @@ class Certificates extends React.Component {
</div>
),
editable: (
<React.Fragment>
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.certificates.my.certificates'])}
showEditButton
@@ -163,10 +167,10 @@ class Certificates extends React.Component {
visibility={visibilityCourseCertificates}
/>
{this.renderCertificates()}
</React.Fragment>
</>
),
empty: (
<React.Fragment>
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.certificates.my.certificates'])}
showEditButton
@@ -175,13 +179,13 @@ class Certificates extends React.Component {
visibility={visibilityCourseCertificates}
/>
{this.renderCertificates()}
</React.Fragment>
</>
),
static: (
<React.Fragment>
<>
<EditableItemHeader content={intl.formatMessage(messages['profile.certificates.my.certificates'])} />
{this.renderCertificates()}
</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.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}
@@ -83,7 +84,7 @@ class Country extends React.Component {
value={country}
onChange={this.handleChange}
>
<option value="" />
<option value="">&nbsp;</option>
{sortedCountries.map(({ code, name }) => (
<option key={code} value={code}>{name}</option>
))}
@@ -100,7 +101,7 @@ class Country extends React.Component {
</div>
),
editable: (
<React.Fragment>
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.country.label'])}
showEditButton
@@ -108,26 +109,26 @@ class Country extends React.Component {
showVisibility={visibilityCountry !== null}
visibility={visibilityCountry}
/>
<p className="h5">{countryMessages[country]}</p>
</React.Fragment>
<p data-hj-suppress className="h5">{countryMessages[country]}</p>
</>
),
empty: (
<React.Fragment>
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.country.label'])}
/>
<EmptyContent onClick={this.handleOpen}>
{intl.formatMessage(messages['profile.country.empty'])}
</EmptyContent>
</React.Fragment>
</>
),
static: (
<React.Fragment>
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.country.label'])}
/>
<p className="h5">{countryMessages[country]}</p>
</React.Fragment>
<p data-hj-suppress className="h5">{countryMessages[country]}</p>
</>
),
}}
/>

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,13 +72,14 @@ class Education extends React.Component {
{intl.formatMessage(messages['profile.education.education'])}
</label>
<select
data-hj-suppress
className="form-control"
id={formId}
name={formId}
value={levelOfEducation}
onChange={this.handleChange}
>
<option value="" />
<option value="">&nbsp;</option>
{EDUCATION_LEVELS.map(level => (
<option key={level} value={level}>
{intl.formatMessage(get(
@@ -101,7 +102,7 @@ class Education extends React.Component {
</div>
),
editable: (
<React.Fragment>
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.education.education'])}
showEditButton
@@ -109,17 +110,17 @@ 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}`,
messages['profile.education.levels.o'],
))}
</p>
</React.Fragment>
</>
),
empty: (
<React.Fragment>
<>
<EditableItemHeader content={intl.formatMessage(messages['profile.education.education'])} />
<EmptyContent onClick={this.handleOpen}>
<FormattedMessage
@@ -128,19 +129,19 @@ class Education extends React.Component {
description="instructions when the user doesn't have their level of education set"
/>
</EmptyContent>
</React.Fragment>
</>
),
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}`,
messages['profile.education.levels.o'],
))}
</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.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>
@@ -84,7 +84,7 @@ class Name extends React.Component {
</div>
),
editable: (
<React.Fragment>
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.name.full.name'])}
showEditButton
@@ -92,14 +92,14 @@ 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>
</React.Fragment>
</>
),
empty: (
<React.Fragment>
<>
<EditableItemHeader content={intl.formatMessage(messages['profile.name.full.name'])} />
<EmptyContent onClick={this.handleOpen}>
{intl.formatMessage(messages['profile.name.empty'])}
@@ -107,13 +107,13 @@ class Name extends React.Component {
<small className="form-text text-muted">
{intl.formatMessage(messages['profile.name.details'])}
</small>
</React.Fragment>
</>
),
static: (
<React.Fragment>
<>
<EditableItemHeader content={intl.formatMessage(messages['profile.name.full.name'])} />
<p className="h5">{name}</p>
</React.Fragment>
<p data-hj-suppress className="h5">{name}</p>
</>
),
}}
/>

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,13 +86,14 @@ class PreferredLanguage extends React.Component {
{intl.formatMessage(messages['profile.preferredlanguage.label'])}
</label>
<select
data-hj-suppress
id={formId}
name={formId}
className="form-control"
value={value}
onChange={this.handleChange}
>
<option value="" />
<option value="">&nbsp;</option>
{sortedLanguages.map(({ code, name }) => (
<option key={code} value={code}>{name}</option>
))}
@@ -109,7 +110,7 @@ class PreferredLanguage extends React.Component {
</div>
),
editable: (
<React.Fragment>
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.preferredlanguage.label'])}
showEditButton
@@ -117,26 +118,26 @@ class PreferredLanguage extends React.Component {
showVisibility={visibilityLanguageProficiencies !== null}
visibility={visibilityLanguageProficiencies}
/>
<p className="h5">{languageMessages[value]}</p>
</React.Fragment>
<p data-hj-suppress className="h5">{languageMessages[value]}</p>
</>
),
empty: (
<React.Fragment>
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.preferredlanguage.label'])}
/>
<EmptyContent onClick={this.handleOpen}>
{intl.formatMessage(messages['profile.preferredlanguage.empty'])}
</EmptyContent>
</React.Fragment>
</>
),
static: (
<React.Fragment>
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.preferredlanguage.label'])}
/>
<p className="h5">{languageMessages[value]}</p>
</React.Fragment>
<p data-hj-suppress className="h5">{languageMessages[value]}</p>
</>
),
}}
/>

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';
@@ -33,7 +33,9 @@ class ProfileAvatar extends React.Component {
}
onSubmit(e) {
if (e) e.preventDefault();
if (e) {
e.preventDefault();
}
this.props.onSave(new FormData(this.form.current));
this.form.current.reset();
}
@@ -55,7 +57,9 @@ class ProfileAvatar extends React.Component {
if (this.props.isDefault) {
return (
<Button
className="text-white btn-block btn-sm btn-link"
variant="link"
size="sm"
className="text-white btn-block"
onClick={this.onClickUpload}
>
<FormattedMessage
@@ -69,9 +73,9 @@ class ProfileAvatar extends React.Component {
return (
<Dropdown>
<Dropdown.Button type="btn-outline">
<Dropdown.Toggle>
{intl.formatMessage(messages['profile.profileavatar.change-button'])}
</Dropdown.Button>
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item type="button" onClick={this.onClickUpload}>
<FormattedMessage
@@ -93,7 +97,9 @@ class ProfileAvatar extends React.Component {
}
renderMenu() {
if (!this.props.isEditable) return null;
if (!this.props.isEditable) {
return null;
}
return (
<div className="profile-avatar-menu-container">
@@ -109,6 +115,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';
@@ -102,7 +102,7 @@ class SocialLinks extends React.Component {
expression={editMode}
cases={{
empty: (
<React.Fragment>
<>
<EditableItemHeader content={intl.formatMessage(messages['profile.sociallinks.social.links'])} />
<ul className="list-unstyled">
{socialLinks.map(({ platform }) => (
@@ -113,10 +113,10 @@ class SocialLinks extends React.Component {
/>
))}
</ul>
</React.Fragment>
</>
),
static: (
<React.Fragment>
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.sociallinks.social.links'])}
/>
@@ -130,13 +130,12 @@ class SocialLinks extends React.Component {
url={socialLink}
platform={platform}
/>
))
}
))}
</ul>
</React.Fragment>
</>
),
editable: (
<React.Fragment>
<>
<EditableItemHeader
content={intl.formatMessage(messages['profile.sociallinks.social.links'])}
showEditButton
@@ -155,7 +154,7 @@ class SocialLinks extends React.Component {
/>
))}
</ul>
</React.Fragment>
</>
),
editing: (
<div role="dialog" aria-labelledby="social-links-label">

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

@@ -1,9 +1,8 @@
import React from 'react';
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';
@@ -13,7 +12,9 @@ function EditButton({
}) {
return (
<Button
className={classNames('btn-sm btn-link', className)}
variant="link"
size="sm"
className={className}
onClick={onClick}
style={style}
>

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

@@ -13,7 +13,7 @@ function EditableItemHeader({
headingId,
}) {
return (
<React.Fragment>
<>
<div className="editable-item-header mb-2">
<h2 className="edit-section-header" id={headingId}>
{content}
@@ -21,7 +21,7 @@ function EditableItemHeader({
</h2>
{showVisibility ? <p className="mb-0"><Visibility to={visibility} /></p> : null}
</div>
</React.Fragment>
</>
);
}

View File

@@ -8,9 +8,10 @@ function EmptyContent({ children, onClick, showPlusIcon }) {
<div>
{onClick ? (
<button
type="button"
className="pl-0 text-left btn btn-link"
onClick={onClick}
onKeyDown={(e) => { if (e.key === 'Enter') onClick(); }}
onKeyDown={(e) => { if (e.key === 'Enter') { onClick(); } }}
tabIndex={0}
>
{showPlusIcon ? <FontAwesomeIcon size="xs" className="mr-2" icon={faPlus} /> : null}
@@ -21,7 +22,6 @@ function EmptyContent({ children, onClick, showPlusIcon }) {
);
}
export default EmptyContent;
EmptyContent.propTypes = {

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';
@@ -31,7 +31,6 @@ function FormControls({
<div className="form-group flex-shrink-0 flex-grow-1">
<StatefulButton
type="submit"
className="btn-primary"
state={buttonState}
labels={{
default: intl.formatMessage(messages['profile.formcontrols.button.save']),
@@ -46,11 +45,13 @@ function FormControls({
// Swallowing the onSubmit event on the form would be better, but
// we would have to add that logic for every field given our
// current structure of the application.
if (buttonState === 'pending') e.preventDefault();
if (buttonState === 'pending') {
e.preventDefault();
}
}}
disabledStates={[]}
/>
<Button className="btn-link" onClick={cancelHandler}>
<Button variant="link" onClick={cancelHandler}>
{intl.formatMessage(messages['profile.formcontrols.button.cancel'])}
</Button>
</div>

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

@@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import { TransitionReplace } from '@edx/paragon';
const onChildExit = (htmlNode) => {
// If the leaving child has focus, take control and redirect it
if (htmlNode.contains(document.activeElement)) {
@@ -11,7 +10,9 @@ const onChildExit = (htmlNode) => {
const enteringChild = htmlNode.previousSibling || htmlNode.nextSibling;
// There's no replacement, do nothing.
if (!enteringChild) return;
if (!enteringChild) {
return;
}
// Get all the focusable elements in the entering child and focus the first one
const focusableElements = enteringChild.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
@@ -21,7 +22,6 @@ const onChildExit = (htmlNode) => {
}
};
function SwitchContent({ expression, cases, className }) {
const getContent = (caseKey) => {
if (cases[caseKey]) {
@@ -29,7 +29,8 @@ function SwitchContent({ expression, cases, className }) {
return getContent(cases[caseKey]);
}
return React.cloneElement(cases[caseKey], { key: caseKey });
} else if (cases.default) {
}
if (cases.default) {
if (typeof cases.default === 'string') {
return getContent(cases.default);
}
@@ -49,7 +50,6 @@ function SwitchContent({ expression, cases, className }) {
);
}
SwitchContent.propTypes = {
expression: PropTypes.string,
cases: PropTypes.objectOf(PropTypes.node).isRequired,
@@ -61,5 +61,4 @@ SwitchContent.defaultProps = {
className: null,
};
export default SwitchContent;

View File

@@ -1,17 +1,16 @@
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';
import messages from './Visibility.messages';
function Visibility({ to, intl }) {
const icon = to === 'private' ? faEyeSlash : faEye;
const label = to === 'private' ?
intl.formatMessage(messages['profile.visibility.who.just.me']) :
intl.formatMessage(messages['profile.visibility.who.everyone']);
const label = to === 'private'
? intl.formatMessage(messages['profile.visibility.who.just.me'])
: intl.formatMessage(messages['profile.visibility.who.everyone']);
return (
<span className="ml-auto small text-muted">
@@ -30,7 +29,6 @@ Visibility.defaultProps = {
to: 'private',
};
function VisibilitySelect({ intl, className, ...props }) {
const { value } = props;
const icon = value === 'private' ? faEyeSlash : faEye;
@@ -73,7 +71,6 @@ VisibilitySelect.defaultProps = {
const intlVisibility = injectIntl(Visibility);
const intlVisibilitySelect = injectIntl(VisibilitySelect);
export {
intlVisibility as Visibility,
intlVisibilitySelect as VisibilitySelect,

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,8 +1,3 @@
$fa-font-path: "~font-awesome/fonts";
@import "~font-awesome/scss/font-awesome";
.word-break-all {
word-break: break-all !important;
}
@@ -36,9 +31,11 @@ $fa-font-path: "~font-awesome/fonts";
letter-spacing: 0;
margin: 0;
}
label.edit-section-header {
margin-bottom: $spacer * .5;
}
.profile-avatar-wrap {
@include media-breakpoint-up(md) {
max-width: 12rem;
@@ -57,10 +54,12 @@ $fa-font-path: "~font-awesome/fonts";
justify-content: center;
align-items: center;
border-radius: 50%;
@include media-breakpoint-up(md) {
background: linear-gradient(to top, rgba(0,0,0,.65) 4rem, rgba(0,0,0,0) 4rem);
align-items: flex-end;
}
.btn {
text-decoration: none;
@include media-breakpoint-up(md) {
@@ -72,6 +71,7 @@ $fa-font-path: "~font-awesome/fonts";
@include media-breakpoint-up(md) {
margin-bottom: 1.2rem;
}
.btn {
color: $white;
background: transparent;
@@ -118,10 +118,12 @@ $fa-font-path: "~font-awesome/fonts";
.certificate {
position: relative;
.certificate-title {
font-family: $font-family-serif;
font-weight: 400;
}
.certificate-type-illustration {
position: absolute;
top: 1rem;
@@ -133,9 +135,9 @@ $fa-font-path: "~font-awesome/fonts";
background-repeat: no-repeat;
background-position: right top;
}
.card-body {
position: relative;
}
}
}

View File

@@ -4,9 +4,9 @@ import snakeCase from 'lodash.snakecase';
export function modifyObjectKeys(object, modify) {
// If the passed in object is not an object, return it.
if (
object === undefined ||
object === null ||
(typeof object !== 'object' && !Array.isArray(object))
object === undefined
|| object === null
|| (typeof object !== 'object' && !Array.isArray(object))
) {
return object;
}

View File

@@ -1,4 +1,10 @@
import { AsyncActionType, modifyObjectKeys, camelCaseObject, snakeCaseObject, convertKeyNames } from './utils';
import {
AsyncActionType,
modifyObjectKeys,
camelCaseObject,
snakeCaseObject,
convertKeyNames,
} from './utils';
describe('modifyObjectKeys', () => {
it('should use the provided modify function to change all keys in and object and its children', () => {

View File

@@ -1,3 +1,6 @@
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';