-
-
- {this.renderSrMessage()}
-
-
+const PageLoading = ({ srMessage }) => (
+
+
+
+ {srMessage && {srMessage}}
- );
- }
-}
+
+
+);
PageLoading.propTypes = {
srMessage: PropTypes.string.isRequired,
};
+
+export default PageLoading;
diff --git a/src/profile-v2/ProfilePage.jsx b/src/profile-v2/ProfilePage.jsx
index 4033c1266..cb2d1323b 100644
--- a/src/profile-v2/ProfilePage.jsx
+++ b/src/profile-v2/ProfilePage.jsx
@@ -1,10 +1,12 @@
-import React, { useEffect, useState, useContext } from 'react';
+import React, {
+ useEffect, useState, useContext, useCallback,
+} from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
import { ensureConfig, getConfig } from '@edx/frontend-platform';
import { AppContext } from '@edx/frontend-platform/react';
-import { injectIntl } from '@edx/frontend-platform/i18n';
+import { useIntl } from '@edx/frontend-platform/i18n';
import { Alert, Hyperlink } from '@openedx/paragon';
import {
fetchProfile,
@@ -14,7 +16,7 @@ import {
import ProfileAvatar from './forms/ProfileAvatar';
import Certificates from './Certificates';
import DateJoined from './DateJoined';
-import CertificateCount from './CertificateCount';
+import UserCertificateSummary from './UserCertificateSummary';
import UsernameDescription from './UsernameDescription';
import PageLoading from './PageLoading';
import { profilePageSelector } from './data/selectors';
@@ -23,8 +25,9 @@ import withParams from '../utils/hoc';
ensureConfig(['CREDENTIALS_BASE_URL', 'LMS_BASE_URL'], 'ProfilePage');
-const ProfilePage = ({ params, intl }) => {
+const ProfilePage = ({ params }) => {
const dispatch = useDispatch();
+ const intl = useIntl();
const context = useContext(AppContext);
const {
requiresParentalConsent,
@@ -41,24 +44,24 @@ const ProfilePage = ({ params, intl }) => {
const [viewMyRecordsUrl, setViewMyRecordsUrl] = useState(null);
useEffect(() => {
- const credentialsBaseUrl = context.config.CREDENTIALS_BASE_URL;
- if (credentialsBaseUrl) {
- setViewMyRecordsUrl(`${credentialsBaseUrl}/records`);
+ const { CREDENTIALS_BASE_URL } = context.config;
+ if (CREDENTIALS_BASE_URL) {
+ setViewMyRecordsUrl(`${CREDENTIALS_BASE_URL}/records`);
}
dispatch(fetchProfile(params.username));
sendTrackingLogEvent('edx.profile.viewed', {
username: params.username,
});
- }, [dispatch, params.username, context.config.CREDENTIALS_BASE_URL]);
+ }, [dispatch, params.username, context.config]);
- const handleSaveProfilePhoto = (formData) => {
+ const handleSaveProfilePhoto = useCallback((formData) => {
dispatch(saveProfilePhoto(context.authenticatedUser.username, formData));
- };
+ }, [dispatch, context.authenticatedUser.username]);
- const handleDeleteProfilePhoto = () => {
+ const handleDeleteProfilePhoto = useCallback(() => {
dispatch(deleteProfilePhoto(context.authenticatedUser.username));
- };
+ }, [dispatch, context.authenticatedUser.username]);
const isYOBDisabled = () => {
const currentYear = new Date().getFullYear();
@@ -83,21 +86,17 @@ const ProfilePage = ({ params, intl }) => {
);
};
- const renderPhotoUploadErrorMessage = () => {
- if (photoUploadError === null) {
- return null;
- }
-
- return (
-
-
-
- {photoUploadError.userMessage}
-
-
+ const renderPhotoUploadErrorMessage = () => (
+ photoUploadError && (
+
+
+
+ {photoUploadError.userMessage}
+
- );
- };
+
+ )
+ );
const renderContent = () => {
if (isLoadingProfile) {
@@ -107,7 +106,7 @@ const ProfilePage = ({ params, intl }) => {
return (
<>
-
+
@@ -165,9 +164,6 @@ ProfilePage.propTypes = {
params: PropTypes.shape({
username: PropTypes.string.isRequired,
}).isRequired,
- intl: PropTypes.shape({
- formatMessage: PropTypes.func.isRequired,
- }).isRequired,
};
-export default injectIntl(withParams(ProfilePage));
+export default withParams(ProfilePage);
diff --git a/src/profile-v2/ProfilePage.test.jsx b/src/profile-v2/ProfilePage.test.jsx
index 505b84109..7c0e2d46c 100644
--- a/src/profile-v2/ProfilePage.test.jsx
+++ b/src/profile-v2/ProfilePage.test.jsx
@@ -1,4 +1,3 @@
-/* eslint-disable global-require */
import { getConfig } from '@edx/frontend-platform';
import * as analytics from '@edx/frontend-platform/analytics';
import { AppContext } from '@edx/frontend-platform/react';
@@ -12,12 +11,16 @@ import thunk from 'redux-thunk';
import messages from '../i18n';
import ProfilePage from './ProfilePage';
+import loadingApp from './__mocks__/loadingApp.mockStore';
+import viewOwnProfile from './__mocks__/viewOwnProfile.mockStore';
+import viewOtherProfile from './__mocks__/viewOtherProfile.mockStore';
const mockStore = configureMockStore([thunk]);
+
const storeMocks = {
- loadingApp: require('./__mocks__/loadingApp.mockStore'),
- viewOwnProfile: require('./__mocks__/viewOwnProfile.mockStore'),
- viewOtherProfile: require('./__mocks__/viewOtherProfile.mockStore'),
+ loadingApp,
+ viewOwnProfile,
+ viewOtherProfile,
};
const requiredProfilePageProps = {
fetchUserAccount: () => {},
diff --git a/src/profile-v2/UserCertificateSummary.jsx b/src/profile-v2/UserCertificateSummary.jsx
new file mode 100644
index 000000000..f07f1a3ea
--- /dev/null
+++ b/src/profile-v2/UserCertificateSummary.jsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from '@edx/frontend-platform/i18n';
+
+const UserCertificateSummary = ({ count = 0 }) => (
+
+ {count} ,
+ }}
+ />
+
+);
+
+UserCertificateSummary.propTypes = {
+ count: PropTypes.number,
+};
+
+export default UserCertificateSummary;
diff --git a/src/profile-v2/__snapshots__/ProfilePage.test.jsx.snap b/src/profile-v2/__snapshots__/ProfilePage.test.jsx.snap
index c289482b4..29a9d33d9 100644
--- a/src/profile-v2/__snapshots__/ProfilePage.test.jsx.snap
+++ b/src/profile-v2/__snapshots__/ProfilePage.test.jsx.snap
@@ -35,7 +35,7 @@ exports[`
Renders correctly in various states viewing other profi
class="profile-page-bg-banner bg-primary d-md-block align-items-center px-75rem py-4rem h-100 w-100"
>
Renders correctly in various states viewing other profi
+
+
+
+ 0
+
+
+ certifications
+
Renders correctly in various states viewing other profi
- You don't have any certificates yet.
+
+ You don't have any certificates yet.
+
@@ -169,7 +185,7 @@ exports[`
Renders correctly in various states viewing own profile
class="profile-page-bg-banner bg-primary d-md-block align-items-center px-75rem py-4rem h-100 w-100"
>
Renders correctly in various states viewing own profile
style="background-image: url(icon/mock/path);"
/>
Renders correctly in various states viewing own profile
>
Verified Certificate
-
edX Demonstration Course
-
+
From
-
edX
-
+
@@ -396,7 +412,7 @@ exports[` Renders correctly in various states without credentials
class="profile-page-bg-banner bg-primary d-md-block align-items-center px-75rem py-4rem h-100 w-100"
>
Renders correctly in various states without credentials
style="background-image: url(icon/mock/path);"
/>
Renders correctly in various states without credentials
>
Verified Certificate
-
edX Demonstration Course
-
+
From
-
edX
-
+
diff --git a/src/profile-v2/data/selectors.js b/src/profile-v2/data/selectors.js
index 4457f8dd8..f5349a3dc 100644
--- a/src/profile-v2/data/selectors.js
+++ b/src/profile-v2/data/selectors.js
@@ -1,7 +1,6 @@
import { createSelector } from 'reselect';
export const userAccountSelector = state => state.userAccount;
-
export const profileAccountSelector = state => state.profilePage.account;
export const profileCourseCertificatesSelector = state => state.profilePage.courseCertificates;
export const savePhotoStateSelector = state => state.profilePage.savePhotoState;
diff --git a/src/profile-v2/forms/ProfileAvatar.jsx b/src/profile-v2/forms/ProfileAvatar.jsx
index 19ce9c702..8c064ed68 100644
--- a/src/profile-v2/forms/ProfileAvatar.jsx
+++ b/src/profile-v2/forms/ProfileAvatar.jsx
@@ -1,66 +1,60 @@
-import React from 'react';
+import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import { Button, Dropdown } from '@openedx/paragon';
-import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { ReactComponent as DefaultAvatar } from '../assets/avatar.svg';
-
import messages from './ProfileAvatar.messages';
-class ProfileAvatar extends React.Component {
- constructor(props) {
- super(props);
-
- this.fileInput = React.createRef();
- this.form = React.createRef();
-
- this.onClickUpload = this.onClickUpload.bind(this);
- this.onClickDelete = this.onClickDelete.bind(this);
- this.onChangeInput = this.onChangeInput.bind(this);
- this.onSubmit = this.onSubmit.bind(this);
- }
-
- onClickUpload() {
- this.fileInput.current.click();
- }
-
- onClickDelete() {
- this.props.onDelete();
- }
-
- onChangeInput() {
- this.onSubmit();
- }
-
- onSubmit(e) {
+const ProfileAvatar = ({
+ src,
+ isDefault,
+ onSave,
+ onDelete,
+ savePhotoState,
+ isEditable,
+}) => {
+ const intl = useIntl();
+ const fileInput = useRef(null);
+ const form = useRef(null);
+
+ const onClickUpload = () => {
+ fileInput.current.click();
+ };
+
+ const onClickDelete = () => {
+ onDelete();
+ };
+
+ const onSubmit = (e) => {
if (e) {
e.preventDefault();
}
- this.props.onSave(new FormData(this.form.current));
- this.form.current.reset();
- }
-
- renderPending() {
- return (
-
- );
- }
-
- renderMenuContent() {
- const { intl } = this.props;
-
- if (this.props.isDefault) {
+ onSave(new FormData(form.current));
+ form.current.reset();
+ };
+
+ const onChangeInput = () => {
+ onSubmit();
+ };
+
+ const renderPending = () => (
+
+ );
+
+ const renderMenuContent = () => {
+ if (isDefault) {
return (