Compare commits

...

14 Commits

Author SHA1 Message Date
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
38 changed files with 10363 additions and 8365 deletions

6
.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
@@ -31,3 +31,7 @@ SUPPORT_URL=null
TERMS_OF_SERVICE_URL=null
TWITTER_URL=null
YOU_TUBE_URL=null
LOGO_URL=''
LOGO_TRADEMARK_URL=''
LOGO_WHITE_URL=''
FAVICON_URL=''

View File

@@ -32,3 +32,7 @@ SUPPORT_URL='http://localhost:18000/support'
TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service'
TWITTER_URL='https://twitter.com'
YOU_TUBE_URL='https://www.youtube.com'
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico

View File

@@ -13,3 +13,7 @@ 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

17886
package-lock.json generated

File diff suppressed because it is too large Load Diff

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,17 +40,18 @@
"ie 11"
],
"dependencies": {
"@edx/frontend-component-footer": "10.0.9",
"@edx/frontend-component-header": "2.0.5",
"@edx/frontend-platform": "1.1.14",
"@edx/paragon": "7.1.3",
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-component-footer": "10.1.4",
"@edx/frontend-component-header": "2.2.3",
"@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.2",
"@fortawesome/free-solid-svg-icons": "5.7.2",
"@fortawesome/react-fontawesome": "0.1.8",
"babel-polyfill": "6.26.0",
"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",
@@ -62,27 +63,28 @@
"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.3",
"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.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",
"@edx/frontend-build": "2.0.6",
"codecov": "3.7.1",
"@edx/frontend-build": "5.6.8",
"codecov": "3.7.2",
"enzyme": "3.10.0",
"enzyme-adapter-react-16": "1.15.2",
"enzyme-adapter-react-16": "1.15.6",
"es-check": "5.0.0",
"glob": "7.1.6",
"husky": "3.1.0",

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

@@ -2,9 +2,8 @@ import { combineReducers } from 'redux';
import { reducer as profilePage } from '../profile';
const createRootReducer = () =>
combineReducers({
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

@@ -1,7 +1,16 @@
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_INIT_ERROR,
APP_READY,
initialize,
subscribe,
} from '@edx/frontend-platform';
import {
AppProvider,
ErrorPage,
} from '@edx/frontend-platform/react';
import React from 'react';
import ReactDOM from 'react-dom';

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

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

@@ -3,7 +3,9 @@ import PropTypes from 'prop-types';
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

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

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

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

View File

@@ -1,4 +1,11 @@
import { takeEvery, put, call, delay, select, all } from 'redux-saga/effects';
import {
takeEvery,
put,
call,
delay,
select,
all,
} from 'redux-saga/effects';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import * as profileActions from './actions';

View File

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

@@ -121,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 ?
`${getConfig().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),

View File

@@ -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,6 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedDate, FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/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

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

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

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

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

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

@@ -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,6 +1,5 @@
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-platform/i18n';
@@ -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

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

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

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

@@ -6,12 +6,11 @@ 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,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';