From 9d5a89d4c6537e8160c38efd24c0630c845b4624 Mon Sep 17 00:00:00 2001 From: Eemaan Amir <57627710+eemaanamir@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:09:13 +0500 Subject: [PATCH] feat: removed flag for new profile UI and depreacted old code (#1233) --- .env | 1 - .env.development | 1 - .env.test | 1 - src/data/reducers.js | 7 +- src/data/sagas.js | 6 +- src/index-v2.scss | 6 - src/index.jsx | 12 +- src/index.scss | 2 +- src/profile-v2/DateJoined.jsx | 29 - src/profile-v2/NotFoundPage.jsx | 16 - src/profile-v2/PageLoading.jsx | 18 - src/profile-v2/ProfilePage.jsx | 463 - src/profile-v2/ProfilePage.messages.jsx | 26 - src/profile-v2/ProfilePage.test.jsx | 259 - .../__mocks__/invalidUser.mockStore.js | 42 - .../__mocks__/loadingApp.mockStore.js | 42 - .../__mocks__/savingEditedBio.mockStore.js | 139 - .../__mocks__/viewOtherProfile.mockStore.js | 105 - .../__mocks__/viewOwnProfile.mockStore.js | 139 - .../__snapshots__/ProfilePage.test.jsx.snap | 2019 --- src/profile-v2/assets/avatar.svg | 9 - src/profile-v2/assets/dot-pattern-light.png | Bin 38914 -> 0 bytes src/profile-v2/assets/micro-masters.svg | 13 - .../assets/professional-certificate.svg | 1 - .../assets/verified-certificate.svg | 1 - src/profile-v2/data/actions.js | 137 - src/profile-v2/data/actions.test.js | 98 - src/profile-v2/data/constants.js | 33 - src/profile-v2/data/mock_data.js | 7 - src/profile-v2/data/pact-profile.test.js | 84 - src/profile-v2/data/reducers.js | 181 - src/profile-v2/data/sagas.js | 191 - src/profile-v2/data/sagas.test.js | 167 - src/profile-v2/data/selectors.js | 338 - src/profile-v2/data/services.js | 168 - src/profile-v2/forms/Bio.jsx | 151 - src/profile-v2/forms/Bio.messages.jsx | 11 - src/profile-v2/forms/Country.jsx | 163 - src/profile-v2/forms/Country.messages.jsx | 16 - src/profile-v2/forms/Education.jsx | 171 - src/profile-v2/forms/Education.messages.jsx | 56 - src/profile-v2/forms/Name.jsx | 192 - src/profile-v2/forms/Name.messages.jsx | 26 - src/profile-v2/forms/PreferredLanguage.jsx | 166 - .../forms/PreferredLanguage.messages.jsx | 16 - src/profile-v2/forms/ProfileAvatar.jsx | 170 - .../forms/ProfileAvatar.messages.jsx | 26 - src/profile-v2/forms/SocialLinks.jsx | 258 - src/profile-v2/forms/SocialLinks.messages.jsx | 11 - src/profile-v2/forms/elements/EditButton.jsx | 46 - .../forms/elements/EditButton.messages.jsx | 11 - .../forms/elements/EditableItemHeader.jsx | 69 - .../forms/elements/EmptyContent.jsx | 35 - .../forms/elements/FormControls.jsx | 84 - .../forms/elements/FormControls.messages.jsx | 31 - .../forms/elements/SwitchContent.jsx | 59 - src/profile-v2/forms/elements/Visibility.jsx | 76 - .../forms/elements/Visibility.messages.jsx | 16 - src/profile-v2/index.js | 5 - src/profile-v2/index.scss | 265 - src/profile-v2/utils.js | 69 - src/profile-v2/utils.test.js | 103 - src/profile/AgeMessage.jsx | 43 - src/profile/Banner.jsx | 5 - .../CertificateCard.jsx | 0 src/{profile-v2 => profile}/Certificates.jsx | 0 .../Certificates.messages.jsx | 0 src/profile/DateJoined.jsx | 14 +- src/profile/NotFoundPage.jsx | 2 +- src/profile/PageLoading.jsx | 41 +- src/profile/ProfilePage.jsx | 679 +- src/profile/ProfilePage.messages.jsx | 10 + src/profile/ProfilePage.test.jsx | 295 +- .../UserCertificateSummary.jsx | 0 src/profile/UsernameDescription.jsx | 23 - .../__mocks__/invalidUser.mockStore.js | 2 +- src/profile/__mocks__/loadingApp.mockStore.js | 1 + .../__mocks__/savingEditedBio.mockStore.js | 2 +- .../__mocks__/viewOtherProfile.mockStore.js | 12 +- .../__mocks__/viewOwnProfile.mockStore.js | 4 +- .../__snapshots__/ProfilePage.test.jsx.snap | 11673 +++------------- src/profile/data/actions.js | 14 - src/profile/data/actions.test.js | 108 +- src/{profile-v2 => profile}/data/hooks.js | 2 +- src/profile/data/pact-profile.test.js | 4 + src/profile/data/reducers.js | 44 +- .../data/reducers.test.js | 0 src/profile/data/sagas.js | 40 +- src/profile/data/sagas.test.js | 4 - src/profile/data/selectors.js | 68 +- src/profile/data/services.js | 26 +- src/profile/forms/Bio.jsx | 223 +- src/profile/forms/Bio.messages.jsx | 2 +- src/profile/forms/Certificates.jsx | 231 - src/profile/forms/Certificates.messages.jsx | 31 - src/profile/forms/Country.jsx | 252 +- src/profile/forms/Country.messages.jsx | 4 +- src/profile/forms/Education.jsx | 268 +- src/profile/forms/LearningGoal.jsx | 92 - src/profile/forms/LearningGoal.messages.jsx | 31 - src/profile/forms/LearningGoal.test.jsx | 116 - src/profile/forms/Name.jsx | 267 +- src/profile/forms/Name.messages.jsx | 19 +- src/profile/forms/PreferredLanguage.jsx | 253 +- .../forms/PreferredLanguage.messages.jsx | 2 +- src/profile/forms/ProfileAvatar.jsx | 252 +- src/profile/forms/ProfileAvatar.messages.jsx | 10 + src/profile/forms/SocialLinks.jsx | 457 +- src/profile/forms/SocialLinks.test.jsx | 165 - .../__snapshots__/SocialLinks.test.jsx.snap | 504 - src/profile/forms/elements/EditButton.jsx | 37 +- .../forms/elements/EditableItemHeader.jsx | 47 +- src/profile/forms/elements/EmptyContent.jsx | 6 +- src/profile/forms/elements/FormControls.jsx | 74 +- src/profile/forms/elements/SwitchContent.jsx | 5 - src/profile/forms/elements/Visibility.jsx | 4 +- src/profile/index.scss | 178 +- src/profile/utils.js | 2 - src/routes/AppRoutes.jsx | 20 +- src/routes/routes.test.jsx | 5 - 120 files changed, 3662 insertions(+), 20074 deletions(-) delete mode 100755 src/index-v2.scss delete mode 100644 src/profile-v2/DateJoined.jsx delete mode 100644 src/profile-v2/NotFoundPage.jsx delete mode 100644 src/profile-v2/PageLoading.jsx delete mode 100644 src/profile-v2/ProfilePage.jsx delete mode 100644 src/profile-v2/ProfilePage.messages.jsx delete mode 100644 src/profile-v2/ProfilePage.test.jsx delete mode 100644 src/profile-v2/__mocks__/invalidUser.mockStore.js delete mode 100644 src/profile-v2/__mocks__/loadingApp.mockStore.js delete mode 100644 src/profile-v2/__mocks__/savingEditedBio.mockStore.js delete mode 100644 src/profile-v2/__mocks__/viewOtherProfile.mockStore.js delete mode 100644 src/profile-v2/__mocks__/viewOwnProfile.mockStore.js delete mode 100644 src/profile-v2/__snapshots__/ProfilePage.test.jsx.snap delete mode 100644 src/profile-v2/assets/avatar.svg delete mode 100644 src/profile-v2/assets/dot-pattern-light.png delete mode 100644 src/profile-v2/assets/micro-masters.svg delete mode 100644 src/profile-v2/assets/professional-certificate.svg delete mode 100644 src/profile-v2/assets/verified-certificate.svg delete mode 100644 src/profile-v2/data/actions.js delete mode 100644 src/profile-v2/data/actions.test.js delete mode 100644 src/profile-v2/data/constants.js delete mode 100644 src/profile-v2/data/mock_data.js delete mode 100644 src/profile-v2/data/pact-profile.test.js delete mode 100644 src/profile-v2/data/reducers.js delete mode 100644 src/profile-v2/data/sagas.js delete mode 100644 src/profile-v2/data/sagas.test.js delete mode 100644 src/profile-v2/data/selectors.js delete mode 100644 src/profile-v2/data/services.js delete mode 100644 src/profile-v2/forms/Bio.jsx delete mode 100644 src/profile-v2/forms/Bio.messages.jsx delete mode 100644 src/profile-v2/forms/Country.jsx delete mode 100644 src/profile-v2/forms/Country.messages.jsx delete mode 100644 src/profile-v2/forms/Education.jsx delete mode 100644 src/profile-v2/forms/Education.messages.jsx delete mode 100644 src/profile-v2/forms/Name.jsx delete mode 100644 src/profile-v2/forms/Name.messages.jsx delete mode 100644 src/profile-v2/forms/PreferredLanguage.jsx delete mode 100644 src/profile-v2/forms/PreferredLanguage.messages.jsx delete mode 100644 src/profile-v2/forms/ProfileAvatar.jsx delete mode 100644 src/profile-v2/forms/ProfileAvatar.messages.jsx delete mode 100644 src/profile-v2/forms/SocialLinks.jsx delete mode 100644 src/profile-v2/forms/SocialLinks.messages.jsx delete mode 100644 src/profile-v2/forms/elements/EditButton.jsx delete mode 100644 src/profile-v2/forms/elements/EditButton.messages.jsx delete mode 100644 src/profile-v2/forms/elements/EditableItemHeader.jsx delete mode 100644 src/profile-v2/forms/elements/EmptyContent.jsx delete mode 100644 src/profile-v2/forms/elements/FormControls.jsx delete mode 100644 src/profile-v2/forms/elements/FormControls.messages.jsx delete mode 100644 src/profile-v2/forms/elements/SwitchContent.jsx delete mode 100644 src/profile-v2/forms/elements/Visibility.jsx delete mode 100644 src/profile-v2/forms/elements/Visibility.messages.jsx delete mode 100644 src/profile-v2/index.js delete mode 100644 src/profile-v2/index.scss delete mode 100644 src/profile-v2/utils.js delete mode 100644 src/profile-v2/utils.test.js delete mode 100644 src/profile/AgeMessage.jsx delete mode 100644 src/profile/Banner.jsx rename src/{profile-v2 => profile}/CertificateCard.jsx (100%) rename src/{profile-v2 => profile}/Certificates.jsx (100%) rename src/{profile-v2 => profile}/Certificates.messages.jsx (100%) rename src/{profile-v2 => profile}/UserCertificateSummary.jsx (100%) delete mode 100644 src/profile/UsernameDescription.jsx rename src/{profile-v2 => profile}/data/hooks.js (93%) rename src/{profile-v2 => profile}/data/reducers.test.js (100%) delete mode 100644 src/profile/forms/Certificates.jsx delete mode 100644 src/profile/forms/Certificates.messages.jsx delete mode 100644 src/profile/forms/LearningGoal.jsx delete mode 100644 src/profile/forms/LearningGoal.messages.jsx delete mode 100644 src/profile/forms/LearningGoal.test.jsx delete mode 100644 src/profile/forms/SocialLinks.test.jsx delete mode 100644 src/profile/forms/__snapshots__/SocialLinks.test.jsx.snap diff --git a/.env b/.env index f73d2e4..4af7473 100644 --- a/.env +++ b/.env @@ -31,5 +31,4 @@ SEARCH_CATALOG_URL='' ENABLE_SKILLS_BUILDER_PROFILE='' # Fallback in local style files PARAGON_THEME_URLS={} -ENABLE_NEW_PROFILE_VIEW='' DISABLE_VISIBILITY_EDITING='' diff --git a/.env.development b/.env.development index a9a5e96..fa5041d 100644 --- a/.env.development +++ b/.env.development @@ -32,5 +32,4 @@ SEARCH_CATALOG_URL='http://localhost:18000/courses' ENABLE_SKILLS_BUILDER_PROFILE='' # Fallback in local style files PARAGON_THEME_URLS={} -ENABLE_NEW_PROFILE_VIEW='' DISABLE_VISIBILITY_EDITING='' diff --git a/.env.test b/.env.test index 839f7fe..15f0042 100644 --- a/.env.test +++ b/.env.test @@ -26,5 +26,4 @@ COLLECT_YEAR_OF_BIRTH=true APP_ID='' MFE_CONFIG_API_URL='' PARAGON_THEME_URLS={} -ENABLE_NEW_PROFILE_VIEW='' DISABLE_VISIBILITY_EDITING='' diff --git a/src/data/reducers.js b/src/data/reducers.js index 98b95d2..fa8f1bf 100755 --- a/src/data/reducers.js +++ b/src/data/reducers.js @@ -1,14 +1,9 @@ import { combineReducers } from 'redux'; -import { getConfig } from '@edx/frontend-platform'; - import { reducer as profilePageReducer } from '../profile'; -import { reducer as newProfilePageReducer } from '../profile-v2'; - -const isNewProfileEnabled = getConfig().ENABLE_NEW_PROFILE_VIEW; const createRootReducer = () => combineReducers({ - profilePage: isNewProfileEnabled ? newProfilePageReducer : profilePageReducer, + profilePage: profilePageReducer, }); export default createRootReducer; diff --git a/src/data/sagas.js b/src/data/sagas.js index fab5eca..91762b8 100644 --- a/src/data/sagas.js +++ b/src/data/sagas.js @@ -1,12 +1,8 @@ import { all } from 'redux-saga/effects'; -import { getConfig } from '@edx/frontend-platform'; import { saga as profileSaga } from '../profile'; -import { saga as newProfileSaga } from '../profile-v2'; - -const isNewProfileEnabled = getConfig().ENABLE_NEW_PROFILE_VIEW; export default function* rootSaga() { yield all([ - isNewProfileEnabled ? newProfileSaga() : profileSaga(), + profileSaga(), ]); } diff --git a/src/index-v2.scss b/src/index-v2.scss deleted file mode 100755 index d120211..0000000 --- a/src/index-v2.scss +++ /dev/null @@ -1,6 +0,0 @@ -@use "@openedx/paragon/styles/css/core/custom-media-breakpoints" as paragonCustomMediaBreakpoints; - -@import "~@edx/frontend-component-header/dist/index"; -@import "~@edx/frontend-component-footer/dist/footer"; - -@import './profile-v2/index'; diff --git a/src/index.jsx b/src/index.jsx index 44779d4..0b00264 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -7,7 +7,6 @@ import { initialize, mergeConfig, subscribe, - getConfig, } from '@edx/frontend-platform'; import { AppProvider, @@ -28,20 +27,16 @@ import Head from './head/Head'; import AppRoutes from './routes/AppRoutes'; +import './index.scss'; + const rootNode = createRoot(document.getElementById('root')); subscribe(APP_READY, async () => { - const isNewProfileEnabled = getConfig().ENABLE_NEW_PROFILE_VIEW === 'true'; - if (isNewProfileEnabled) { - await import('./index-v2.scss'); - } else { - await import('./index.scss'); - } rootNode.render(
- +
, @@ -61,7 +56,6 @@ initialize({ mergeConfig({ COLLECT_YEAR_OF_BIRTH: process.env.COLLECT_YEAR_OF_BIRTH, ENABLE_SKILLS_BUILDER_PROFILE: process.env.ENABLE_SKILLS_BUILDER_PROFILE, - ENABLE_NEW_PROFILE_VIEW: process.env.ENABLE_NEW_PROFILE_VIEW || null, }, 'App loadConfig override handler'); }, }, diff --git a/src/index.scss b/src/index.scss index 69756e8..d1c0cd9 100755 --- a/src/index.scss +++ b/src/index.scss @@ -3,4 +3,4 @@ @import "~@edx/frontend-component-header/dist/index"; @import "~@edx/frontend-component-footer/dist/footer"; -@import './profile/index'; +@import 'profile/index'; diff --git a/src/profile-v2/DateJoined.jsx b/src/profile-v2/DateJoined.jsx deleted file mode 100644 index 5b02d4b..0000000 --- a/src/profile-v2/DateJoined.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { memo } from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage, FormattedDate } from '@edx/frontend-platform/i18n'; - -const DateJoined = ({ date }) => { - if (!date) { return null; } - - return ( - - , - }} - /> - - ); -}; - -DateJoined.propTypes = { - date: PropTypes.string, -}; -DateJoined.defaultProps = { - date: null, -}; - -export default memo(DateJoined); diff --git a/src/profile-v2/NotFoundPage.jsx b/src/profile-v2/NotFoundPage.jsx deleted file mode 100644 index b33f0db..0000000 --- a/src/profile-v2/NotFoundPage.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import { FormattedMessage } from '@edx/frontend-platform/i18n'; - -const NotFoundPage = () => ( -
-

- -

-
-); - -export default NotFoundPage; diff --git a/src/profile-v2/PageLoading.jsx b/src/profile-v2/PageLoading.jsx deleted file mode 100644 index a730b39..0000000 --- a/src/profile-v2/PageLoading.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -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 deleted file mode 100644 index 2411755..0000000 --- a/src/profile-v2/ProfilePage.jsx +++ /dev/null @@ -1,463 +0,0 @@ -import React, { - useEffect, useState, useContext, useCallback, -} from 'react'; -import PropTypes from 'prop-types'; -import { useDispatch, useSelector } from 'react-redux'; -import { useNavigate } from 'react-router-dom'; - -import { sendTrackingLogEvent } from '@edx/frontend-platform/analytics'; -import { ensureConfig } from '@edx/frontend-platform'; -import { AppContext } from '@edx/frontend-platform/react'; -import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; -import { - Alert, Hyperlink, OverlayTrigger, Tooltip, -} from '@openedx/paragon'; -import { InfoOutline } from '@openedx/paragon/icons'; -import classNames from 'classnames'; - -import { - fetchProfile, - saveProfile, - saveProfilePhoto, - deleteProfilePhoto, - openForm, - closeForm, - updateDraft, -} from './data/actions'; - -import ProfileAvatar from './forms/ProfileAvatar'; -import Name from './forms/Name'; -import Country from './forms/Country'; -import PreferredLanguage from './forms/PreferredLanguage'; -import Education from './forms/Education'; -import SocialLinks from './forms/SocialLinks'; -import Bio from './forms/Bio'; -import DateJoined from './DateJoined'; -import UserCertificateSummary from './UserCertificateSummary'; -import PageLoading from './PageLoading'; -import Certificates from './Certificates'; - -import { profilePageSelector } from './data/selectors'; -import messages from './ProfilePage.messages'; -import withParams from '../utils/hoc'; -import { useIsOnMobileScreen, useIsOnTabletScreen } from './data/hooks'; - -ensureConfig(['CREDENTIALS_BASE_URL', 'LMS_BASE_URL', 'ACCOUNT_SETTINGS_URL'], 'ProfilePage'); - -const ProfilePage = ({ params }) => { - const dispatch = useDispatch(); - const intl = useIntl(); - const context = useContext(AppContext); - const { - dateJoined, - courseCertificates, - name, - visibilityName, - profileImage, - savePhotoState, - isLoadingProfile, - photoUploadError, - country, - visibilityCountry, - levelOfEducation, - visibilityLevelOfEducation, - socialLinks, - draftSocialLinksByPlatform, - visibilitySocialLinks, - languageProficiencies, - visibilityLanguageProficiencies, - bio, - visibilityBio, - saveState, - username, - } = useSelector(profilePageSelector); - - const navigate = useNavigate(); - const [viewMyRecordsUrl, setViewMyRecordsUrl] = useState(null); - const isMobileView = useIsOnMobileScreen(); - const isTabletView = useIsOnTabletScreen(); - - useEffect(() => { - 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]); - - useEffect(() => { - if (!username && saveState === 'error' && navigate) { - navigate('/notfound'); - } - }, [username, saveState, navigate]); - - const authenticatedUserName = context.authenticatedUser.username; - - const handleSaveProfilePhoto = useCallback((formData) => { - dispatch(saveProfilePhoto(authenticatedUserName, formData)); - }, [dispatch, authenticatedUserName]); - - const handleDeleteProfilePhoto = useCallback(() => { - dispatch(deleteProfilePhoto(authenticatedUserName)); - }, [dispatch, authenticatedUserName]); - - const handleClose = useCallback((formId) => { - dispatch(closeForm(formId)); - }, [dispatch]); - - const handleOpen = useCallback((formId) => { - dispatch(openForm(formId)); - }, [dispatch]); - - const handleSubmit = useCallback((formId) => { - dispatch(saveProfile(formId, authenticatedUserName)); - }, [dispatch, authenticatedUserName]); - - const handleChange = useCallback((fieldName, value) => { - dispatch(updateDraft(fieldName, value)); - }, [dispatch]); - - const isAuthenticatedUserProfile = () => params.username === authenticatedUserName; - - const isBlockVisible = (blockInfo) => isAuthenticatedUserProfile() - || (!isAuthenticatedUserProfile() && Boolean(blockInfo)); - - const renderViewMyRecordsButton = () => { - if (!(viewMyRecordsUrl && isAuthenticatedUserProfile())) { - return null; - } - - return ( - - {intl.formatMessage(messages['profile.viewMyRecords'])} - - ); - }; - - const renderPhotoUploadErrorMessage = () => ( - photoUploadError && ( -
-
- - {photoUploadError.userMessage} - -
-
- ) - ); - - const commonFormProps = { - openHandler: handleOpen, - closeHandler: handleClose, - submitHandler: handleSubmit, - changeHandler: handleChange, - }; - - return ( -
- {isLoadingProfile ? ( - - ) : ( - <> -
-
-
-
- -
-

- {params.username} -

- {isBlockVisible(name) && ( -

- {name} -

- )} -
- - -
-
-
- {renderViewMyRecordsButton()} -
-
-
-
- {renderPhotoUploadErrorMessage()} -
-
-
-
-
-
-
-

- {isMobileView ? ( - - ) - : ( - - )} -

-
-
-
-
-
-
-

- {intl.formatMessage(messages['profile.username'])} -

- -

- {intl.formatMessage(messages['profile.username.tooltip'])} -

- - )} - > - -
-
-

- {params.username} -

-
- {isBlockVisible(name) && ( - - )} - {isBlockVisible(country) && ( - - )} - {isBlockVisible((languageProficiencies || []).length) && ( - - )} - {isBlockVisible(levelOfEducation) && ( - - )} -
-
- {isBlockVisible(bio) && ( - - )} - {isBlockVisible((socialLinks || []).some((link) => link?.socialLink !== null)) && ( - - )} -
-
-
-
-
- {isBlockVisible((courseCertificates || []).length) && ( - - )} -
- - )} -
- ); -}; - -ProfilePage.propTypes = { - params: PropTypes.shape({ - username: PropTypes.string.isRequired, - }).isRequired, - requiresParentalConsent: PropTypes.bool, - dateJoined: PropTypes.string, - username: PropTypes.string, - bio: PropTypes.string, - visibilityBio: PropTypes.string, - courseCertificates: PropTypes.arrayOf(PropTypes.shape({ - title: PropTypes.string, - })), - country: PropTypes.string, - visibilityCountry: PropTypes.string, - levelOfEducation: PropTypes.string, - visibilityLevelOfEducation: PropTypes.string, - languageProficiencies: PropTypes.arrayOf(PropTypes.shape({ - code: PropTypes.string.isRequired, - })), - visibilityLanguageProficiencies: PropTypes.string, - name: PropTypes.string, - visibilityName: PropTypes.string, - socialLinks: PropTypes.arrayOf(PropTypes.shape({ - platform: PropTypes.string, - socialLink: PropTypes.string, - })), - draftSocialLinksByPlatform: PropTypes.objectOf(PropTypes.shape({ - platform: PropTypes.string, - socialLink: PropTypes.string, - })), - visibilitySocialLinks: PropTypes.string, - profileImage: PropTypes.shape({ - src: PropTypes.string, - isDefault: PropTypes.bool, - }), - saveState: PropTypes.oneOf([null, 'pending', 'complete', 'error']), - savePhotoState: PropTypes.oneOf([null, 'pending', 'complete', 'error']), - isLoadingProfile: PropTypes.bool, - photoUploadError: PropTypes.objectOf(PropTypes.string), -}; - -ProfilePage.defaultProps = { - saveState: null, - username: '', - savePhotoState: null, - photoUploadError: {}, - profileImage: {}, - name: null, - levelOfEducation: null, - country: null, - socialLinks: [], - draftSocialLinksByPlatform: {}, - bio: null, - languageProficiencies: [], - courseCertificates: [], - requiresParentalConsent: null, - dateJoined: null, - visibilityName: null, - visibilityCountry: null, - visibilityLevelOfEducation: null, - visibilitySocialLinks: null, - visibilityLanguageProficiencies: null, - visibilityBio: null, - isLoadingProfile: false, -}; - -export default withParams(ProfilePage); diff --git a/src/profile-v2/ProfilePage.messages.jsx b/src/profile-v2/ProfilePage.messages.jsx deleted file mode 100644 index c7cec79..0000000 --- a/src/profile-v2/ProfilePage.messages.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - 'profile.viewMyRecords': { - id: 'profile.viewMyRecords', - defaultMessage: 'View My Records', - description: 'A link to go view my academic records', - }, - 'profile.loading': { - id: 'profile.loading', - defaultMessage: 'Profile loading...', - description: 'Message displayed when the profile data is loading.', - }, - 'profile.username': { - id: 'profile.username', - defaultMessage: 'Username', - description: 'Label for the username field.', - }, - 'profile.username.tooltip': { - id: 'profile.username.tooltip', - defaultMessage: 'The name that identifies you on edX. You cannot change your username.', - description: 'Tooltip for the username field.', - }, -}); - -export default messages; diff --git a/src/profile-v2/ProfilePage.test.jsx b/src/profile-v2/ProfilePage.test.jsx deleted file mode 100644 index d186c48..0000000 --- a/src/profile-v2/ProfilePage.test.jsx +++ /dev/null @@ -1,259 +0,0 @@ -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 { render } from '@testing-library/react'; -import React from 'react'; -import PropTypes from 'prop-types'; -import { Provider } from 'react-redux'; -import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { - MemoryRouter, - Routes, - Route, - useNavigate, -} from 'react-router-dom'; - -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'; -import invalidUser from './__mocks__/invalidUser.mockStore'; - -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useNavigate: jest.fn(), -})); - -const mockStore = configureMockStore([thunk]); - -const storeMocks = { - loadingApp, - viewOwnProfile, - viewOtherProfile, - invalidUser, -}; - -const requiredProfilePageProps = { - params: { username: 'staff' }, -}; - -Object.defineProperty(global.document, 'cookie', { - writable: true, - value: `${getConfig().LANGUAGE_PREFERENCE_COOKIE_NAME}=en`, -}); - -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(); - useNavigate.mockReset(); -}); - -const ProfilePageWrapper = ({ - contextValue, store, params, -}) => ( - - - - - - } - /> - - - - - -); - -ProfilePageWrapper.defaultProps = { - // eslint-disable-next-line react/default-props-match-prop-types - params: { username: 'staff' }, -}; - -ProfilePageWrapper.propTypes = { - contextValue: PropTypes.shape({}).isRequired, - store: PropTypes.shape({}).isRequired, - params: PropTypes.shape({ - username: PropTypes.string.isRequired, - }).isRequired, -}; - -describe('', () => { - describe('Renders correctly in various states', () => { - it('app loading', () => { - const contextValue = { - authenticatedUser: { userId: null, username: null, administrator: false }, - config: getConfig(), - }; - const component = ( - - ); - const { container: tree } = render(component); - expect(tree).toMatchSnapshot(); - }); - - it('viewing own profile', () => { - const contextValue = { - authenticatedUser: { userId: 123, username: 'staff', administrator: true }, - config: getConfig(), - }; - const component = ( - - ); - const { container: tree } = render(component); - expect(tree).toMatchSnapshot(); - }); - - it('viewing other profile with all fields', () => { - const contextValue = { - authenticatedUser: { userId: 123, username: 'staff', administrator: true }, - config: getConfig(), - }; - const component = ( - - ); - const { container: tree } = render(component); - expect(tree).toMatchSnapshot(); - }); - - it('without credentials service', () => { - const config = getConfig(); - config.CREDENTIALS_BASE_URL = ''; - - const contextValue = { - authenticatedUser: { userId: 123, username: 'staff', administrator: true }, - config: getConfig(), - }; - const component = ( - - ); - const { container: tree } = render(component); - expect(tree).toMatchSnapshot(); - }); - - it('successfully redirected to not found page', () => { - const contextValue = { - authenticatedUser: { userId: 123, username: 'staff', administrator: true }, - config: getConfig(), - }; - const navigate = jest.fn(); - useNavigate.mockReturnValue(navigate); - const component = ( - - ); - const { container: tree } = render(component); - expect(tree).toMatchSnapshot(); - expect(navigate).toHaveBeenCalledWith('/notfound'); - }); - }); - - describe('handles analytics', () => { - it('calls sendTrackingLogEvent when mounting', () => { - const contextValue = { - authenticatedUser: { userId: 123, username: 'staff', administrator: true }, - config: getConfig(), - }; - render( - , - ); - - expect(analytics.sendTrackingLogEvent).toHaveBeenCalledTimes(1); - expect(analytics.sendTrackingLogEvent).toHaveBeenCalledWith('edx.profile.viewed', { - username: 'test-username', - }); - }); - }); - - describe('handles navigation', () => { - it('navigates to notfound on save error with no username', () => { - const contextValue = { - authenticatedUser: { userId: 123, username: 'staff', administrator: true }, - config: getConfig(), - }; - const navigate = jest.fn(); - useNavigate.mockReturnValue(navigate); - render( - , - ); - - expect(navigate).toHaveBeenCalledWith('/notfound'); - }); - }); -}); diff --git a/src/profile-v2/__mocks__/invalidUser.mockStore.js b/src/profile-v2/__mocks__/invalidUser.mockStore.js deleted file mode 100644 index 253ef87..0000000 --- a/src/profile-v2/__mocks__/invalidUser.mockStore.js +++ /dev/null @@ -1,42 +0,0 @@ -module.exports = { - userAccount: { - loading: false, - error: null, - username: 'staff', - email: null, - bio: null, - name: null, - country: null, - socialLinks: null, - profileImage: { - imageUrlMedium: null, - imageUrlLarge: null - }, - levelOfEducation: null, - learningGoal: null - }, - profilePage: { - errors: {}, - saveState: 'error', - savePhotoState: null, - currentlyEditingField: null, - account: { - username: '', - socialLinks: [] - }, - preferences: {}, - courseCertificates: [], - drafts: {}, - isLoadingProfile: false, - isAuthenticatedUserProfile: true, - countriesCodesList: ['US', 'CA', 'GB', 'ME'] - }, - router: { - location: { - pathname: '/u/staffTest', - search: '', - hash: '' - }, - action: 'POP' - } -}; diff --git a/src/profile-v2/__mocks__/loadingApp.mockStore.js b/src/profile-v2/__mocks__/loadingApp.mockStore.js deleted file mode 100644 index aaf1f63..0000000 --- a/src/profile-v2/__mocks__/loadingApp.mockStore.js +++ /dev/null @@ -1,42 +0,0 @@ -module.exports = { - userAccount: { - loading: false, - error: null, - username: 'staff', - email: null, - bio: null, - name: null, - country: null, - socialLinks: null, - profileImage: { - imageUrlMedium: null, - imageUrlLarge: null - }, - levelOfEducation: null, - learningGoal: null - }, - profilePage: { - errors: {}, - saveState: null, - savePhotoState: null, - currentlyEditingField: null, - account: { - username: 'staff', - socialLinks: [] - }, - preferences: {}, - courseCertificates: [], - drafts: {}, - isLoadingProfile: true, - isAuthenticatedUserProfile: true, - countriesCodesList: ['US', 'CA', 'GB', 'ME'] - }, - router: { - location: { - pathname: '/u/staff', - search: '', - hash: '' - }, - action: 'POP' - } -}; diff --git a/src/profile-v2/__mocks__/savingEditedBio.mockStore.js b/src/profile-v2/__mocks__/savingEditedBio.mockStore.js deleted file mode 100644 index a104762..0000000 --- a/src/profile-v2/__mocks__/savingEditedBio.mockStore.js +++ /dev/null @@ -1,139 +0,0 @@ -module.exports = { - userAccount: { - loading: false, - error: null, - username: 'staff', - email: 'staff@example.com', - bio: 'This is my bio', - name: 'Lemon Seltzer', - country: 'ME', - socialLinks: [ - { - platform: 'facebook', - socialLink: 'https://www.facebook.com/aloha' - }, - { - platform: 'twitter', - socialLink: 'https://www.twitter.com/ALOHA' - } - ], - profileImage: { - imageUrlFull: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012', - imageUrlLarge: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_120.jpg?v=1552495012', - imageUrlMedium: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_50.jpg?v=1552495012', - imageUrlSmall: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_30.jpg?v=1552495012', - hasImage: true - }, - levelOfEducation: 'el', - mailingAddress: null, - extendedProfile: [], - dateJoined: '2017-06-07T00:44:23Z', - accomplishmentsShared: false, - isActive: true, - yearOfBirth: 1901, - goals: null, - languageProficiencies: [ - { - code: 'yo' - } - ], - courseCertificates: null, - requiresParentalConsent: false, - secondaryEmail: null, - timeZone: null, - gender: null, - accountPrivacy: 'custom', - learningGoal: null, - }, - profilePage: { - errors: {}, - saveState: 'pending', - savePhotoState: null, - currentlyEditingField: 'bio', - isAuthenticatedUserProfile: true, - account: { - mailingAddress: null, - profileImage: { - imageUrlFull: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012', - imageUrlLarge: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_120.jpg?v=1552495012', - imageUrlMedium: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_50.jpg?v=1552495012', - imageUrlSmall: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_30.jpg?v=1552495012', - hasImage: true - }, - extendedProfile: [], - dateJoined: '2017-06-07T00:44:23Z', - accomplishmentsShared: false, - email: 'staff@example.com', - username: 'staff', - bio: 'This is my bio', - isActive: true, - yearOfBirth: 1901, - goals: null, - languageProficiencies: [ - { - code: 'yo' - } - ], - courseCertificates: null, - requiresParentalConsent: false, - name: 'Lemon Seltzer', - secondaryEmail: null, - country: 'ME', - socialLinks: [ - { - platform: 'facebook', - socialLink: 'https://www.facebook.com/aloha' - }, - { - platform: 'twitter', - socialLink: 'https://www.twitter.com/ALOHA' - } - ], - timeZone: null, - levelOfEducation: 'el', - gender: null, - accountPrivacy: 'custom', - learningGoal: null, - }, - preferences: { - visibilityUserLocation: 'all_users', - visibilitySocialLinks: 'all_users', - visibilityCertificates: 'private', - visibilityLevelOfEducation: 'private', - visibilityCourseCertificates: 'all_users', - prefLang: 'en', - visibilityBio: 'all_users', - visibilityName: 'private', - visibilityLanguageProficiencies: 'all_users', - visibilityCountry: 'all_users', - accountPrivacy: 'custom', - visibilityLearningGoal: 'private', - }, - courseCertificates: [ - { - username: 'staff', - status: 'downloadable', - courseDisplayName: 'edX Demonstration Course', - grade: '0.89', - courseId: 'course-v1:edX+DemoX+Demo_Course', - courseOrganization: 'edX', - modifiedDate: '2019-03-04T19:31:39.930255Z', - isPassing: true, - downloadUrl: 'http://www.example.com/', - certificateType: 'verified', - createdDate: '2019-03-04T19:31:39.896806Z' - } - ], - drafts: {}, - isLoadingProfile: false, - disabledCountries: [], - }, - router: { - location: { - pathname: '/u/staff', - search: '', - hash: '' - }, - action: 'POP' - } -}; diff --git a/src/profile-v2/__mocks__/viewOtherProfile.mockStore.js b/src/profile-v2/__mocks__/viewOtherProfile.mockStore.js deleted file mode 100644 index 7afdac1..0000000 --- a/src/profile-v2/__mocks__/viewOtherProfile.mockStore.js +++ /dev/null @@ -1,105 +0,0 @@ -module.exports = { - userAccount: { - loading: false, - error: null, - username: 'staff', - email: 'staff@example.com', - bio: 'This is my bio', - name: 'Lemon Seltzer', - country: 'ME', - socialLinks: [ - { - platform: 'facebook', - socialLink: 'https://www.facebook.com/aloha' - }, - { - platform: 'twitter', - socialLink: 'https://www.twitter.com/ALOHA' - } - ], - profileImage: { - imageUrlFull: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012', - imageUrlLarge: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_120.jpg?v=1552495012', - imageUrlMedium: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_50.jpg?v=1552495012', - imageUrlSmall: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_30.jpg?v=1552495012', - hasImage: true - }, - levelOfEducation: 'el', - mailingAddress: null, - extendedProfile: [], - dateJoined: '2017-06-07T00:44:23Z', - accomplishmentsShared: false, - isActive: true, - yearOfBirth: 1901, - goals: null, - languageProficiencies: [ - { - code: 'yo' - } - ], - courseCertificates: null, - requiresParentalConsent: false, - secondaryEmail: null, - timeZone: null, - gender: null, - accountPrivacy: 'custom', - learningGoal: 'advance_career', - }, - profilePage: { - errors: {}, - saveState: null, - savePhotoState: null, - currentlyEditingField: null, - isAuthenticatedUserProfile: false, - account: { - mailingAddress: null, - profileImage: { - imageUrlFull: 'http://localhost:18000/static/images/profiles/default_500.png', - imageUrlLarge: 'http://localhost:18000/static/images/profiles/default_120.png', - imageUrlMedium: 'http://localhost:18000/static/images/profiles/default_50.png', - imageUrlSmall: 'http://localhost:18000/static/images/profiles/default_30.png', - hasImage: false - }, - extendedProfile: [], - dateJoined: '2017-06-07T00:44:19Z', - accomplishmentsShared: false, - email: 'verified@example.com', - username: 'verified', - bio: null, - isActive: true, - yearOfBirth: null, - goals: null, - languageProficiencies: [], - courseCertificates: null, - requiresParentalConsent: true, - name: '', - secondaryEmail: null, - country: null, - socialLinks: [], - timeZone: null, - levelOfEducation: null, - gender: null, - accountPrivacy: 'private' - }, - preferences: { - visibilityName: 'all_users', - visibilityCountry: 'all_users', - visibilityLevelOfEducation: 'all_users', - visibilityLanguageProficiencies: 'all_users', - visibilitySocialLinks: 'all_users', - visibilityBio: 'all_users' - }, - courseCertificates: [], - drafts: {}, - isLoadingProfile: false, - countriesCodesList: ['US', 'CA', 'GB', 'ME'] - }, - router: { - location: { - pathname: '/u/verified', - search: '', - hash: '' - }, - action: 'POP' - } -}; diff --git a/src/profile-v2/__mocks__/viewOwnProfile.mockStore.js b/src/profile-v2/__mocks__/viewOwnProfile.mockStore.js deleted file mode 100644 index 4fb2510..0000000 --- a/src/profile-v2/__mocks__/viewOwnProfile.mockStore.js +++ /dev/null @@ -1,139 +0,0 @@ -module.exports = { - userAccount: { - loading: false, - error: null, - username: 'staff', - email: 'staff@example.com', - bio: 'This is my bio', - name: 'Lemon Seltzer', - country: 'ME', - socialLinks: [ - { - platform: 'facebook', - socialLink: 'https://www.facebook.com/aloha' - }, - { - platform: 'twitter', - socialLink: 'https://www.twitter.com/ALOHA' - } - ], - profileImage: { - imageUrlFull: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012', - imageUrlLarge: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_120.jpg?v=1552495012', - imageUrlMedium: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_50.jpg?v=1552495012', - imageUrlSmall: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_30.jpg?v=1552495012', - hasImage: true - }, - levelOfEducation: 'el', - mailingAddress: null, - extendedProfile: [], - dateJoined: '2017-06-07T00:44:23Z', - accomplishmentsShared: false, - isActive: true, - yearOfBirth: 1901, - goals: null, - languageProficiencies: [ - { - code: 'yo' - } - ], - courseCertificates: null, - requiresParentalConsent: false, - secondaryEmail: null, - timeZone: null, - gender: null, - accountPrivacy: 'custom', - learningGoal: 'advance_career' - }, - profilePage: { - errors: {}, - saveState: null, - savePhotoState: null, - currentlyEditingField: null, - isAuthenticatedUserProfile: true, - account: { - mailingAddress: null, - profileImage: { - imageUrlFull: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012', - imageUrlLarge: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_120.jpg?v=1552495012', - imageUrlMedium: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_50.jpg?v=1552495012', - imageUrlSmall: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_30.jpg?v=1552495012', - hasImage: true - }, - extendedProfile: [], - dateJoined: '2017-06-07T00:44:23Z', - accomplishmentsShared: false, - email: 'staff@example.com', - username: 'staff', - bio: 'This is my bio', - isActive: true, - yearOfBirth: 1901, - goals: null, - languageProficiencies: [ - { - code: 'yo' - } - ], - courseCertificates: null, - requiresParentalConsent: false, - name: 'Lemon Seltzer', - secondaryEmail: null, - country: 'ME', - socialLinks: [ - { - platform: 'facebook', - socialLink: 'https://www.facebook.com/aloha' - }, - { - platform: 'twitter', - socialLink: 'https://www.twitter.com/ALOHA' - } - ], - timeZone: null, - levelOfEducation: 'el', - gender: null, - accountPrivacy: 'custom', - learningGoal: 'advance_career' - }, - preferences: { - visibilityUserLocation: 'all_users', - visibilitySocialLinks: 'all_users', - visibilityCertificates: 'private', - visibilityLevelOfEducation: 'private', - visibilityCourseCertificates: 'all_users', - prefLang: 'en', - visibilityBio: 'all_users', - visibilityName: 'private', - visibilityLanguageProficiencies: 'all_users', - visibilityCountry: 'all_users', - accountPrivacy: 'custom', - visibilityLearningGoal: 'private', - }, - courseCertificates: [ - { - username: 'staff', - status: 'downloadable', - courseDisplayName: 'edX Demonstration Course', - grade: '0.89', - courseId: 'course-v1:edX+DemoX+Demo_Course', - courseOrganization: 'edX', - modifiedDate: '2019-03-04T19:31:39.930255Z', - isPassing: true, - downloadUrl: 'http://www.example.com/', - certificateType: 'verified', - createdDate: '2019-03-04T19:31:39.896806Z' - } - ], - drafts: {}, - isLoadingProfile: false, - countriesCodesList: ['US', 'CA', 'GB', 'ME'] - }, - router: { - location: { - pathname: '/u/staff', - search: '', - hash: '' - }, - action: 'POP' - } -}; diff --git a/src/profile-v2/__snapshots__/ProfilePage.test.jsx.snap b/src/profile-v2/__snapshots__/ProfilePage.test.jsx.snap deleted file mode 100644 index 2280c10..0000000 --- a/src/profile-v2/__snapshots__/ProfilePage.test.jsx.snap +++ /dev/null @@ -1,2019 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` Renders correctly in various states app loading 1`] = ` -
-
-
-
-
- - Profile loading... - -
-
-
-
-
-`; - -exports[` Renders correctly in various states successfully redirected to not found page 1`] = ` -
-
-
-
-
-
-
-
- -
- -
-
-
-

- staffTest -

-
-
-
-
-
-
-
-
-
-
-
-
-

- Profile information -

-
-
-
-
-
-
-

- Username -

- - - -
-

- staffTest -

-
-
-
-
-
-
-
-
-
-`; - -exports[` Renders correctly in various states viewing other profile with all fields 1`] = ` -
-
-
-
-
-
-
-
- -
- -
-
-
-

- verified -

-

- Verified User -

-
- - Member since - - - 2017 - - - -
-
-
-
-
-
-
-
-
-
-
-
-

- Profile information -

-
-
-
-
-
-
-

- Username -

- - - -
-

- verified -

-
-
-
-
-

- Full name -

- - - -
-
-
-

- Verified User -

-
-
-
-
-
-
-
-
-

- Country -

-
-
-

- United States of America -

-
-
-
-
-
-
-
-
-

- Primary language spoken -

-
-
-

- English -

-
-
-
-
-
-
-
-
-

- Education -

-
-
-

- Other education -

-
-
-
-
-
-
-
-
-
-
-

- Bio -

-
-
-

- About me -

-
-
-
-
-
-
-
-
-
-
-
-

- X -

-
-
-

- https://twitter.com/user -

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`; - -exports[` Renders correctly in various states viewing own profile 1`] = ` -
-
-
-
-
-
-
-
- profile avatar -
-
- -
-
- -
-
-
-

- staff -

-

- Lemon Seltzer -

-
- - Member since - - - 2017 - - - - - - 1 - - certifications - -
-
- -
-
-
-
-
-
-
-
-
-

- Profile information -

-
-
-
-
-
-
-

- Username -

- - - -
-

- staff -

-
-
-
-
-

- Full name -

- - - -
-
-
-

- Lemon Seltzer -

-
-
- -
-
-
-
-
-
-
-

- Country -

-
-
-

- Montenegro -

-
-
- -
-
-
-
-
-
-
-

- Primary language spoken -

-
-
-

- Yoruba -

-
-
- -
-
-
-
-
-
-
-

- Education -

-
-
-

- Elementary/primary school -

-
-
- -
-
-
-
-
-
-
-
-
-

- Bio -

-
-
-

- This is my bio -

-
-
- -
-
-
-
-
-
-
-
-
-
-

- X -

-
-
-
-

- https://www.twitter.com/ALOHA -

-
-
- -
-
-
-
-
-
-

- Facebook -

-
-
-
-

- https://www.facebook.com/aloha -

-
-
- -
-
-
-
-
-
-

- LinkedIn -

-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-

- Your certificates -

-
-
-

- Your learner records information is only visible to you. Only your username and profile image are visible to others on localhost. -

-
-
-
-
-
-
-
-
-
-

- Verified Certificate -

-

- edX Demonstration Course -

-

- From -

-
- edX -
-

- Completed on - 3/4/2019 -

-
- -

- Credential ID -

-
-
-
-
-
-
-
-
-
-`; - -exports[` Renders correctly in various states without credentials service 1`] = ` -
-
-
-
-
-
-
-
- profile avatar -
-
- -
-
- -
-
-
-

- staff -

-

- Lemon Seltzer -

-
- - Member since - - - 2017 - - - - - - 1 - - certifications - -
-
-
-
-
-
-
-
-
-
-
-
-

- Profile information -

-
-
-
-
-
-
-

- Username -

- - - -
-

- staff -

-
-
-
-
-

- Full name -

- - - -
-
-
-

- Lemon Seltzer -

-
-
- -
-
-
-
-
-
-
-

- Country -

-
-
-

- Montenegro -

-
-
- -
-
-
-
-
-
-
-

- Primary language spoken -

-
-
-

- Yoruba -

-
-
- -
-
-
-
-
-
-
-

- Education -

-
-
-

- Elementary/primary school -

-
-
- -
-
-
-
-
-
-
-
-
-

- Bio -

-
-
-

- This is my bio -

-
-
- -
-
-
-
-
-
-
-
-
-
-

- X -

-
-
-
-

- https://www.twitter.com/ALOHA -

-
-
- -
-
-
-
-
-
-

- Facebook -

-
-
-
-

- https://www.facebook.com/aloha -

-
-
- -
-
-
-
-
-
-

- LinkedIn -

-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-

- Your certificates -

-
-
-

- Your learner records information is only visible to you. Only your username and profile image are visible to others on localhost. -

-
-
-
-
-
-
-
-
-
-

- Verified Certificate -

-

- edX Demonstration Course -

-

- From -

-
- edX -
-

- Completed on - 3/4/2019 -

-
- -

- Credential ID -

-
-
-
-
-
-
-
-
-
-`; diff --git a/src/profile-v2/assets/avatar.svg b/src/profile-v2/assets/avatar.svg deleted file mode 100644 index d7fe4bc..0000000 --- a/src/profile-v2/assets/avatar.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - avatar - Created with Sketch. - - - - diff --git a/src/profile-v2/assets/dot-pattern-light.png b/src/profile-v2/assets/dot-pattern-light.png deleted file mode 100644 index c84a3c52a385bf62a54a7cf948d8cf6c7d3edd31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38914 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 - - - micro-masters - Created with Sketch. - - - - - - - - \ No newline at end of file diff --git a/src/profile-v2/assets/professional-certificate.svg b/src/profile-v2/assets/professional-certificate.svg deleted file mode 100644 index 2940d10..0000000 --- a/src/profile-v2/assets/professional-certificate.svg +++ /dev/null @@ -1 +0,0 @@ -cert-bg-logo \ No newline at end of file diff --git a/src/profile-v2/assets/verified-certificate.svg b/src/profile-v2/assets/verified-certificate.svg deleted file mode 100644 index 2940d10..0000000 --- a/src/profile-v2/assets/verified-certificate.svg +++ /dev/null @@ -1 +0,0 @@ -cert-bg-logo \ No newline at end of file diff --git a/src/profile-v2/data/actions.js b/src/profile-v2/data/actions.js deleted file mode 100644 index b960400..0000000 --- a/src/profile-v2/data/actions.js +++ /dev/null @@ -1,137 +0,0 @@ -import { AsyncActionType } from '../utils'; - -export const FETCH_PROFILE = new AsyncActionType('PROFILE', 'FETCH_PROFILE'); -export const SAVE_PROFILE = new AsyncActionType('PROFILE', 'SAVE_PROFILE'); -export const SAVE_PROFILE_PHOTO = new AsyncActionType('PROFILE', 'SAVE_PROFILE_PHOTO'); -export const DELETE_PROFILE_PHOTO = new AsyncActionType('PROFILE', 'DELETE_PROFILE_PHOTO'); -export const OPEN_FORM = 'OPEN_FORM'; -export const CLOSE_FORM = 'CLOSE_FORM'; -export const UPDATE_DRAFT = 'UPDATE_DRAFT'; -export const RESET_DRAFTS = 'RESET_DRAFTS'; - -export const fetchProfile = username => ({ - type: FETCH_PROFILE.BASE, - payload: { username }, -}); - -export const fetchProfileBegin = () => ({ - type: FETCH_PROFILE.BEGIN, -}); - -export const fetchProfileSuccess = ( - account, - preferences, - courseCertificates, - isAuthenticatedUserProfile, -) => ({ - type: FETCH_PROFILE.SUCCESS, - account, - preferences, - courseCertificates, - isAuthenticatedUserProfile, -}); - -export const fetchProfileReset = () => ({ - type: FETCH_PROFILE.RESET, -}); - -export const saveProfile = (formId, username) => ({ - type: SAVE_PROFILE.BASE, - payload: { - formId, - username, - }, -}); - -export const saveProfileBegin = () => ({ - type: SAVE_PROFILE.BEGIN, -}); - -export const saveProfileSuccess = (account, preferences) => ({ - type: SAVE_PROFILE.SUCCESS, - payload: { - account, - preferences, - }, -}); - -export const saveProfileReset = () => ({ - type: SAVE_PROFILE.RESET, -}); - -export const saveProfileFailure = errors => ({ - type: SAVE_PROFILE.FAILURE, - payload: { errors }, -}); - -export const saveProfilePhoto = (username, formData) => ({ - type: SAVE_PROFILE_PHOTO.BASE, - payload: { - username, - formData, - }, -}); - -export const saveProfilePhotoBegin = () => ({ - type: SAVE_PROFILE_PHOTO.BEGIN, -}); - -export const saveProfilePhotoSuccess = profileImage => ({ - type: SAVE_PROFILE_PHOTO.SUCCESS, - payload: { profileImage }, -}); - -export const saveProfilePhotoReset = () => ({ - type: SAVE_PROFILE_PHOTO.RESET, -}); - -export const saveProfilePhotoFailure = error => ({ - type: SAVE_PROFILE_PHOTO.FAILURE, - payload: { error }, -}); - -export const deleteProfilePhoto = username => ({ - type: DELETE_PROFILE_PHOTO.BASE, - payload: { - username, - }, -}); - -export const deleteProfilePhotoBegin = () => ({ - type: DELETE_PROFILE_PHOTO.BEGIN, -}); - -export const deleteProfilePhotoSuccess = profileImage => ({ - type: DELETE_PROFILE_PHOTO.SUCCESS, - payload: { profileImage }, -}); - -export const deleteProfilePhotoReset = () => ({ - type: DELETE_PROFILE_PHOTO.RESET, -}); - -export const openForm = formId => ({ - type: OPEN_FORM, - payload: { - formId, - }, -}); - -export const closeForm = formId => ({ - type: CLOSE_FORM, - payload: { - formId, - }, -}); - -export const updateDraft = (name, value) => ({ - type: UPDATE_DRAFT, - payload: { - name, - value, - }, -}); - -export const resetDrafts = () => ({ - type: RESET_DRAFTS, -}); diff --git a/src/profile-v2/data/actions.test.js b/src/profile-v2/data/actions.test.js deleted file mode 100644 index 275d695..0000000 --- a/src/profile-v2/data/actions.test.js +++ /dev/null @@ -1,98 +0,0 @@ -import { - SAVE_PROFILE_PHOTO, - saveProfilePhotoBegin, - saveProfilePhotoSuccess, - saveProfilePhotoFailure, - saveProfilePhotoReset, - saveProfilePhoto, - DELETE_PROFILE_PHOTO, - deleteProfilePhotoBegin, - deleteProfilePhotoSuccess, - deleteProfilePhotoReset, - deleteProfilePhoto, -} from './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'; - const expectedAction = { - type: SAVE_PROFILE_PHOTO.BASE, - payload: { - username: 'myusername', - formData, - }, - }; - expect(saveProfilePhoto('myusername', formData)).toEqual(expectedAction); - }); - - it('should create an action to signal user profile photo save beginning', () => { - const expectedAction = { - type: SAVE_PROFILE_PHOTO.BEGIN, - }; - expect(saveProfilePhotoBegin()).toEqual(expectedAction); - }); - - it('should create an action to signal user profile photo save success', () => { - const newPhotoData = { hasImage: true }; - const expectedAction = { - type: SAVE_PROFILE_PHOTO.SUCCESS, - payload: { - profileImage: newPhotoData, - }, - }; - expect(saveProfilePhotoSuccess(newPhotoData)).toEqual(expectedAction); - }); - - it('should create an action to signal user profile photo save reset', () => { - const expectedAction = { - type: SAVE_PROFILE_PHOTO.RESET, - }; - expect(saveProfilePhotoReset()).toEqual(expectedAction); - }); - - it('should create an action to signal user profile photo save failure', () => { - const error = 'Test failure'; - const expectedAction = { - type: SAVE_PROFILE_PHOTO.FAILURE, - payload: { error }, - }; - expect(saveProfilePhotoFailure(error)).toEqual(expectedAction); - }); -}); - -describe('DELETE profile photo actions', () => { - it('should create an action to signal the start of a profile photo deletion', () => { - const expectedAction = { - type: DELETE_PROFILE_PHOTO.BASE, - payload: { - username: 'myusername', - }, - }; - expect(deleteProfilePhoto('myusername')).toEqual(expectedAction); - }); - - it('should create an action to signal user profile photo deletion beginning', () => { - const expectedAction = { - type: DELETE_PROFILE_PHOTO.BEGIN, - }; - expect(deleteProfilePhotoBegin()).toEqual(expectedAction); - }); - - it('should create an action to signal user profile photo deletion success', () => { - const defaultPhotoData = { hasImage: false }; - const expectedAction = { - type: DELETE_PROFILE_PHOTO.SUCCESS, - payload: { - profileImage: defaultPhotoData, - }, - }; - expect(deleteProfilePhotoSuccess(defaultPhotoData)).toEqual(expectedAction); - }); - - it('should create an action to signal user profile photo deletion reset', () => { - const expectedAction = { - type: DELETE_PROFILE_PHOTO.RESET, - }; - expect(deleteProfilePhotoReset()).toEqual(expectedAction); - }); -}); diff --git a/src/profile-v2/data/constants.js b/src/profile-v2/data/constants.js deleted file mode 100644 index de97069..0000000 --- a/src/profile-v2/data/constants.js +++ /dev/null @@ -1,33 +0,0 @@ -const EDUCATION_LEVELS = [ - 'p', - 'm', - 'b', - 'a', - 'hs', - 'jhs', - 'el', - 'none', - 'other', -]; - -const SOCIAL = { - linkedin: { - title: 'LinkedIn', - }, - twitter: { - title: 'Twitter', - }, - facebook: { - title: 'Facebook', - }, -}; - -const FIELD_LABELS = { - COUNTRY: 'country', -}; - -export { - EDUCATION_LEVELS, - SOCIAL, - FIELD_LABELS, -}; diff --git a/src/profile-v2/data/mock_data.js b/src/profile-v2/data/mock_data.js deleted file mode 100644 index c43ed98..0000000 --- a/src/profile-v2/data/mock_data.js +++ /dev/null @@ -1,7 +0,0 @@ -const mockData = { - learningGoal: 'advance_career', - editMode: 'static', - visibilityLearningGoal: 'private', -}; - -export default mockData; diff --git a/src/profile-v2/data/pact-profile.test.js b/src/profile-v2/data/pact-profile.test.js deleted file mode 100644 index abd14e6..0000000 --- a/src/profile-v2/data/pact-profile.test.js +++ /dev/null @@ -1,84 +0,0 @@ -// This test file simply creates a contract that defines -// expectations and correct responses from the Pact stub server. - -import path from 'path'; - -import { PactV3, MatchersV3 } from '@pact-foundation/pact'; - -import { initializeMockApp, getConfig, setConfig } from '@edx/frontend-platform'; -import { getAccount } from './services'; - -const expectedUserInfo200 = { - username: 'staff', - email: 'staff@example.com', - bio: 'This is my bio', - name: 'Lemon Seltzer', - country: 'ME', - dateJoined: '2017-06-07T00:44:23Z', - isActive: true, - yearOfBirth: 1901, - languageProficiencies: [], - levelOfEducation: null, - profileImage: {}, - socialLinks: [], -}; - -const provider = new PactV3({ - log: path.resolve(process.cwd(), 'src/pact-logs/pact.log'), - dir: path.resolve(process.cwd(), 'src/pacts'), - consumer: 'frontend-app-profile', - provider: 'edx-platform', -}); - -describe('getAccount for one username', () => { - beforeAll(async () => { - initializeMockApp(); - }); - it('returns a HTTP 200 and user information', async () => { - const username200 = 'staff'; - await provider.addInteraction({ - states: [{ description: "I have a user's basic information" }], - uponReceiving: "A request for user's basic information", - withRequest: { - method: 'GET', - path: `/api/user/v1/accounts/${username200}`, - headers: {}, - }, - willRespondWith: { - status: 200, - headers: {}, - body: MatchersV3.like(expectedUserInfo200), - }, - }); - return provider.executeTest(async (mockserver) => { - setConfig({ - ...getConfig(), - LMS_BASE_URL: mockserver.url, - }); - const response = await getAccount(username200); - expect(response).toEqual(expectedUserInfo200); - }); - }); - - it('Account does not exist', async () => { - const username404 = 'staff_not_found'; - await provider.addInteraction({ - states: [{ description: "Account and user's information does not exist" }], - uponReceiving: "A request for user's basic information", - withRequest: { - method: 'GET', - path: `/api/user/v1/accounts/${username404}`, - }, - willRespondWith: { - status: 404, - }, - }); - await provider.executeTest(async (mockserver) => { - setConfig({ - ...getConfig(), - LMS_BASE_URL: mockserver.url, - }); - await expect(getAccount(username404).then((response) => response.data)).rejects.toThrow('Request failed with status code 404'); - }); - }); -}); diff --git a/src/profile-v2/data/reducers.js b/src/profile-v2/data/reducers.js deleted file mode 100644 index 3e4760c..0000000 --- a/src/profile-v2/data/reducers.js +++ /dev/null @@ -1,181 +0,0 @@ -import { - SAVE_PROFILE, - SAVE_PROFILE_PHOTO, - DELETE_PROFILE_PHOTO, - CLOSE_FORM, - OPEN_FORM, - FETCH_PROFILE, - UPDATE_DRAFT, - RESET_DRAFTS, -} from './actions'; - -export const initialState = { - errors: {}, - saveState: null, - savePhotoState: null, - currentlyEditingField: null, - account: { - socialLinks: [], - languageProficiencies: [], - name: '', - bio: '', - country: '', - levelOfEducation: '', - profileImage: {}, - yearOfBirth: '', - }, - preferences: { - visibilityName: '', - visibilityBio: '', - visibilityCountry: '', - visibilityLevelOfEducation: '', - visibilitySocialLinks: '', - visibilityLanguageProficiencies: '', - }, - courseCertificates: [], - drafts: {}, - isLoadingProfile: true, - isAuthenticatedUserProfile: false, - disabledCountries: ['RU'], - countriesCodesList: [], -}; - -const profilePage = (state = initialState, action = {}) => { - switch (action.type) { - case FETCH_PROFILE.BEGIN: - return { - ...state, - // TODO: uncomment this line after ARCH-438 Image Post API returns the url - // is complete. Right now we refetch the whole profile causing us to show a full reload - // instead of a partial one. - // isLoadingProfile: true, - }; - case FETCH_PROFILE.SUCCESS: - return { - ...state, - account: { - ...state.account, - ...action.account, - socialLinks: action.account.socialLinks || [], - languageProficiencies: action.account.languageProficiencies || [], - }, - preferences: action.preferences, - courseCertificates: action.courseCertificates || [], - isLoadingProfile: false, - isAuthenticatedUserProfile: action.isAuthenticatedUserProfile, - countriesCodesList: action.countriesCodesList || [], - }; - case SAVE_PROFILE.BEGIN: - return { - ...state, - saveState: 'pending', - errors: {}, - }; - case SAVE_PROFILE.SUCCESS: - return { - ...state, - saveState: 'complete', - errors: {}, - account: action.payload.account !== null ? { - ...state.account, - ...action.payload.account, - socialLinks: action.payload.account.socialLinks || [], - languageProficiencies: action.payload.account.languageProficiencies || [], - } : state.account, - preferences: { ...state.preferences, ...action.payload.preferences }, - }; - case SAVE_PROFILE.FAILURE: - return { - ...state, - saveState: 'error', - isLoadingProfile: false, - errors: { ...state.errors, ...action.payload.errors }, - }; - case SAVE_PROFILE.RESET: - return { - ...state, - saveState: null, - isLoadingProfile: false, - errors: {}, - }; - case SAVE_PROFILE_PHOTO.BEGIN: - return { - ...state, - savePhotoState: 'pending', - errors: {}, - }; - case SAVE_PROFILE_PHOTO.SUCCESS: - return { - ...state, - account: { ...state.account, profileImage: action.payload.profileImage }, - savePhotoState: 'complete', - errors: {}, - }; - case SAVE_PROFILE_PHOTO.FAILURE: - return { - ...state, - savePhotoState: 'error', - errors: { ...state.errors, photo: action.payload.error }, - }; - case SAVE_PROFILE_PHOTO.RESET: - return { - ...state, - savePhotoState: null, - errors: {}, - }; - case DELETE_PROFILE_PHOTO.BEGIN: - return { - ...state, - savePhotoState: 'pending', - errors: {}, - }; - case DELETE_PROFILE_PHOTO.SUCCESS: - return { - ...state, - account: { ...state.account, profileImage: action.payload.profileImage }, - savePhotoState: 'complete', - errors: {}, - }; - case DELETE_PROFILE_PHOTO.FAILURE: - return { - ...state, - savePhotoState: 'error', - errors: { ...state.errors, ...action.payload.errors }, - }; - case DELETE_PROFILE_PHOTO.RESET: - return { - ...state, - savePhotoState: null, - errors: {}, - }; - case UPDATE_DRAFT: - return { - ...state, - drafts: { ...state.drafts, [action.payload.name]: action.payload.value }, - }; - case RESET_DRAFTS: - return { - ...state, - drafts: {}, - }; - case OPEN_FORM: - return { - ...state, - currentlyEditingField: action.payload.formId, - drafts: {}, - }; - case CLOSE_FORM: - if (action.payload.formId === state.currentlyEditingField) { - return { - ...state, - currentlyEditingField: null, - drafts: {}, - }; - } - return state; - default: - return state; - } -}; - -export default profilePage; diff --git a/src/profile-v2/data/sagas.js b/src/profile-v2/data/sagas.js deleted file mode 100644 index b64c3fe..0000000 --- a/src/profile-v2/data/sagas.js +++ /dev/null @@ -1,191 +0,0 @@ -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 { - closeForm, - deleteProfilePhotoBegin, - deleteProfilePhotoReset, - deleteProfilePhotoSuccess, - DELETE_PROFILE_PHOTO, - fetchProfileBegin, - fetchProfileReset, - fetchProfileSuccess, - FETCH_PROFILE, - resetDrafts, - saveProfileBegin, - saveProfileFailure, - saveProfileReset, - saveProfileSuccess, - SAVE_PROFILE, - saveProfilePhotoBegin, - saveProfilePhotoReset, - saveProfilePhotoSuccess, - SAVE_PROFILE_PHOTO, -} from './actions'; -import { handleSaveProfileSelector, userAccountSelector } from './selectors'; -import * as ProfileApiService from './services'; - -export function* handleFetchProfile(action) { - const { username } = action.payload; - const userAccount = yield select(userAccountSelector); - const isAuthenticatedUserProfile = username === getAuthenticatedUser().username; - let preferences = {}; - let account = userAccount; - let courseCertificates = null; - let countriesCodesList = []; - - try { - yield put(fetchProfileBegin()); - - const calls = [ - call(ProfileApiService.getAccount, username), - call(ProfileApiService.getCourseCertificates, username), - call(ProfileApiService.getCountryList), - ]; - - if (isAuthenticatedUserProfile) { - calls.push(call(ProfileApiService.getPreferences, username)); - } - - const result = yield all(calls); - - if (isAuthenticatedUserProfile) { - [account, courseCertificates, countriesCodesList, preferences] = result; - } else { - [account, courseCertificates, countriesCodesList] = result; - } - - if (isAuthenticatedUserProfile && result[0].accountPrivacy === 'all_users') { - yield call(ProfileApiService.patchPreferences, action.payload.username, { - account_privacy: 'custom', - 'visibility.name': 'all_users', - 'visibility.bio': 'all_users', - 'visibility.course_certificates': 'all_users', - 'visibility.country': 'all_users', - 'visibility.date_joined': 'all_users', - 'visibility.level_of_education': 'all_users', - 'visibility.language_proficiencies': 'all_users', - 'visibility.social_links': 'all_users', - 'visibility.time_zone': 'all_users', - }); - } - - yield put(fetchProfileSuccess( - account, - preferences, - courseCertificates, - isAuthenticatedUserProfile, - countriesCodesList, - )); - - yield put(fetchProfileReset()); - } catch (e) { - if (e.response.status === 404) { - history.push('/notfound'); - } else { - throw e; - } - } -} - -export function* handleSaveProfile(action) { - try { - const { drafts, preferences } = yield select(handleSaveProfileSelector); - - const accountDrafts = pick(drafts, [ - 'bio', - 'country', - 'levelOfEducation', - 'languageProficiencies', - 'name', - 'socialLinks', - ]); - - const preferencesDrafts = pick(drafts, [ - 'visibilityBio', - 'visibilityCountry', - 'visibilityLevelOfEducation', - 'visibilityLanguageProficiencies', - 'visibilityName', - 'visibilitySocialLinks', - ]); - - if (Object.keys(preferencesDrafts).length > 0) { - preferencesDrafts.accountPrivacy = 'custom'; - } - - yield put(saveProfileBegin()); - let accountResult = null; - - if (Object.keys(accountDrafts).length > 0) { - accountResult = yield call( - ProfileApiService.patchProfile, - action.payload.username, - accountDrafts, - ); - } - - let preferencesResult = preferences; - if (Object.keys(preferencesDrafts).length > 0) { - yield call(ProfileApiService.patchPreferences, action.payload.username, preferencesDrafts); - // TODO: Temporary deoptimization since the patchPreferences call doesn't return anything. - - preferencesResult = yield call(ProfileApiService.getPreferences, action.payload.username); - } - - yield put(saveProfileSuccess(accountResult, preferencesResult)); - yield delay(1000); - yield put(closeForm(action.payload.formId)); - yield delay(300); - yield put(saveProfileReset()); - yield put(resetDrafts()); - } catch (e) { - if (e.processedData && e.processedData.fieldErrors) { - yield put(saveProfileFailure(e.processedData.fieldErrors)); - } else { - yield put(saveProfileReset()); - throw e; - } - } -} - -export function* handleSaveProfilePhoto(action) { - const { username, formData } = action.payload; - - try { - yield put(saveProfilePhotoBegin()); - const photoResult = yield call(ProfileApiService.postProfilePhoto, username, formData); - yield put(saveProfilePhotoSuccess(photoResult)); - yield put(saveProfilePhotoReset()); - } catch (e) { - yield put(saveProfilePhotoReset()); - } -} - -export function* handleDeleteProfilePhoto(action) { - const { username } = action.payload; - - try { - yield put(deleteProfilePhotoBegin()); - const photoResult = yield call(ProfileApiService.deleteProfilePhoto, username); - yield put(deleteProfilePhotoSuccess(photoResult)); - yield put(deleteProfilePhotoReset()); - } catch (e) { - yield put(deleteProfilePhotoReset()); - } -} - -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); -} diff --git a/src/profile-v2/data/sagas.test.js b/src/profile-v2/data/sagas.test.js deleted file mode 100644 index 2da09b3..0000000 --- a/src/profile-v2/data/sagas.test.js +++ /dev/null @@ -1,167 +0,0 @@ -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'; - -jest.mock('./services', () => ({ - getProfile: jest.fn(), - patchProfile: jest.fn(), - postProfilePhoto: jest.fn(), - deleteProfilePhoto: jest.fn(), - getPreferences: jest.fn(), - getAccount: jest.fn(), - getCourseCertificates: jest.fn(), - getCountryList: jest.fn(), -})); - -jest.mock('@edx/frontend-platform/auth', () => ({ - getAuthenticatedUser: jest.fn(), -})); - -/* eslint-disable import/first */ -import profileSaga, { - handleFetchProfile, - handleSaveProfile, - handleSaveProfilePhoto, - handleDeleteProfilePhoto, -} from './sagas'; -import * as ProfileApiService from './services'; -/* eslint-enable import/first */ - -describe('RootSaga', () => { - describe('profileSaga', () => { - it('should pass actions to the correct sagas', () => { - const gen = profileSaga(); - - expect(gen.next().value) - .toEqual(takeEvery(profileActions.FETCH_PROFILE.BASE, handleFetchProfile)); - expect(gen.next().value) - .toEqual(takeEvery(profileActions.SAVE_PROFILE.BASE, handleSaveProfile)); - expect(gen.next().value) - .toEqual(takeEvery(profileActions.SAVE_PROFILE_PHOTO.BASE, handleSaveProfilePhoto)); - expect(gen.next().value) - .toEqual(takeEvery(profileActions.DELETE_PROFILE_PHOTO.BASE, handleDeleteProfilePhoto)); - - expect(gen.next().value).toBeUndefined(); - }); - }); - - describe('handleFetchProfile', () => { - it('should fetch certificates and preferences for the current user profile', () => { - const userAccount = { - username: 'gonzo', - other: 'data', - }; - getAuthenticatedUser.mockReturnValue(userAccount); - const selectorData = { - userAccount, - }; - - const action = profileActions.fetchProfile('gonzo'); - const gen = handleFetchProfile(action); - - const result = [userAccount, [1, 2, 3], [], { preferences: 'stuff' }]; - - expect(gen.next().value).toEqual(select(userAccountSelector)); - expect(gen.next(selectorData).value).toEqual(put(profileActions.fetchProfileBegin())); - expect(gen.next().value).toEqual(all([ - call(ProfileApiService.getAccount, 'gonzo'), - call(ProfileApiService.getCourseCertificates, 'gonzo'), - call(ProfileApiService.getCountryList), - call(ProfileApiService.getPreferences, 'gonzo'), - ])); - expect(gen.next(result).value) - .toEqual(put(profileActions.fetchProfileSuccess(userAccount, result[3], result[1], true, []))); - expect(gen.next().value).toEqual(put(profileActions.fetchProfileReset())); - expect(gen.next().value).toBeUndefined(); - }); - - it('should fetch certificates and profile for some other user profile', () => { - const userAccount = { - username: 'gonzo', - other: 'data', - }; - const countriesCodesList = [{ code: 'AX' }, { code: 'AL' }]; - getAuthenticatedUser.mockReturnValue(userAccount); - const selectorData = { - userAccount, - }; - - const action = profileActions.fetchProfile('booyah'); - const gen = handleFetchProfile(action); - - const result = [{}, [1, 2, 3], countriesCodesList]; - - expect(gen.next().value).toEqual(select(userAccountSelector)); - expect(gen.next(selectorData).value).toEqual(put(profileActions.fetchProfileBegin())); - expect(gen.next().value).toEqual(all([ - call(ProfileApiService.getAccount, 'booyah'), - call(ProfileApiService.getCourseCertificates, 'booyah'), - call(ProfileApiService.getCountryList), - ])); - expect(gen.next(result).value) - .toEqual(put(profileActions.fetchProfileSuccess(result[0], {}, result[1], false, countriesCodesList))); - expect(gen.next().value).toEqual(put(profileActions.fetchProfileReset())); - expect(gen.next().value).toBeUndefined(); - }); - }); - - describe('handleSaveProfile', () => { - const selectorData = { - username: 'my username', - drafts: { - name: 'Full Name', - }, - preferences: {}, - }; - - it('should successfully process a saveProfile request if there are no exceptions', () => { - const action = profileActions.saveProfile('ze form id', 'my username'); - const gen = handleSaveProfile(action); - const profile = { - name: 'Full Name', - levelOfEducation: 'b', - }; - expect(gen.next().value).toEqual(select(handleSaveProfileSelector)); - expect(gen.next(selectorData).value).toEqual(put(profileActions.saveProfileBegin())); - expect(gen.next().value).toEqual(call(ProfileApiService.patchProfile, 'my username', { - name: 'Full Name', - })); - expect(gen.next(profile).value).toEqual(put(profileActions.saveProfileSuccess(profile, {}))); - expect(gen.next().value).toEqual(delay(1000)); - expect(gen.next().value).toEqual(put(profileActions.closeForm('ze form id'))); - expect(gen.next().value).toEqual(delay(300)); - expect(gen.next().value).toEqual(put(profileActions.saveProfileReset())); - expect(gen.next().value).toEqual(put(profileActions.resetDrafts())); - expect(gen.next().value).toBeUndefined(); - }); - - it('should successfully publish a failure action on exception', () => { - const error = new Error('uhoh'); - error.processedData = { - fieldErrors: { - uhoh: 'not good', - }, - }; - const action = profileActions.saveProfile( - 'ze form id', - 'my username', - ); - const gen = handleSaveProfile(action); - - expect(gen.next().value).toEqual(select(handleSaveProfileSelector)); - expect(gen.next(selectorData).value).toEqual(put(profileActions.saveProfileBegin())); - const result = gen.throw(error); - expect(result.value).toEqual(put(profileActions.saveProfileFailure({ uhoh: 'not good' }))); - expect(gen.next().value).toBeUndefined(); - }); - }); -}); diff --git a/src/profile-v2/data/selectors.js b/src/profile-v2/data/selectors.js deleted file mode 100644 index d398295..0000000 --- a/src/profile-v2/data/selectors.js +++ /dev/null @@ -1,338 +0,0 @@ -import { createSelector } from 'reselect'; -import { - getLocale, - getLanguageList, - getCountryList, - getCountryMessages, - getLanguageMessages, -} from '@edx/frontend-platform/i18n'; - -export const formIdSelector = (state, props) => props.formId; -export const userAccountSelector = state => state.userAccount; -export const profileAccountSelector = state => state.profilePage.account; -export const profileDraftsSelector = state => state.profilePage.drafts; -export const accountPrivacySelector = state => state.profilePage.preferences.accountPrivacy; -export const profilePreferencesSelector = state => state.profilePage.preferences; -export const profileCourseCertificatesSelector = state => state.profilePage.courseCertificates; -export const saveStateSelector = state => state.profilePage.saveState; -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 countriesCodesListSelector = state => state.profilePage.countriesCodesList; - -export const editableFormModeSelector = createSelector( - profileAccountSelector, - isAuthenticatedUserProfileSelector, - profileCourseCertificatesSelector, - formIdSelector, - currentlyEditingFieldSelector, - (account, isAuthenticatedUserProfile, certificates, formId, currentlyEditingField) => { - let propExists = account[formId] != null && account[formId].length > 0; - propExists = formId === 'certificates' ? certificates.length > 0 : propExists; - if (!isAuthenticatedUserProfile) { - return 'static'; - } - if (formId === currentlyEditingField) { - return 'editing'; - } - - if (!propExists) { - return 'empty'; - } - - return 'editable'; - }, -); - -export const accountDraftsFieldSelector = createSelector( - formIdSelector, - profileDraftsSelector, - (formId, drafts) => drafts[formId], -); - -export const visibilityDraftsFieldSelector = createSelector( - formIdSelector, - profileDraftsSelector, - (formId, drafts) => drafts[`visibility${formId.charAt(0).toUpperCase() + formId.slice(1)}`], -); - -export const formErrorSelector = createSelector( - accountErrorsSelector, - formIdSelector, - (errors, formId) => (errors[formId] ? errors[formId].userMessage : null), -); - -export const editableFormSelector = createSelector( - editableFormModeSelector, - formErrorSelector, - saveStateSelector, - (editMode, error, saveState) => ({ - editMode, - error, - saveState, - }), -); - -export const localeSelector = () => getLocale(); -export const countryMessagesSelector = createSelector( - localeSelector, - locale => getCountryMessages(locale), -); -export const languageMessagesSelector = createSelector( - localeSelector, - locale => getLanguageMessages(locale), -); - -export const sortedLanguagesSelector = createSelector( - localeSelector, - locale => getLanguageList(locale), -); - -export const sortedCountriesSelector = createSelector( - localeSelector, - countriesCodesListSelector, - profileAccountSelector, - (locale, countriesCodesList, profileAccount) => { - const countryList = getCountryList(locale); - const userCountry = profileAccount.country; - - return countryList.filter(({ code }) => code === userCountry || countriesCodesList.find(x => x === code)); - }, -); - -export const preferredLanguageSelector = createSelector( - editableFormSelector, - sortedLanguagesSelector, - languageMessagesSelector, - (editableForm, sortedLanguages, languageMessages) => ({ - ...editableForm, - sortedLanguages, - languageMessages, - }), -); - -export const countrySelector = createSelector( - editableFormSelector, - sortedCountriesSelector, - countryMessagesSelector, - countriesCodesListSelector, - profileAccountSelector, - (editableForm, translatedCountries, countryMessages, countriesCodesList, account) => ({ - ...editableForm, - translatedCountries, - countryMessages, - countriesCodesList, - committedCountry: account.country, - }), -); - -export const certificatesSelector = createSelector( - editableFormSelector, - profileCourseCertificatesSelector, - (editableForm, certificates) => ({ - ...editableForm, - certificates, - value: certificates, - }), -); - -export const profileImageSelector = createSelector( - profileAccountSelector, - account => (account.profileImage != null - ? { - src: account.profileImage.imageUrlFull, - isDefault: !account.profileImage.hasImage, - } - : {}), -); - -export const handleSaveProfileSelector = createSelector( - profileDraftsSelector, - profilePreferencesSelector, - (drafts, preferences) => ({ - drafts, - preferences, - }), -); - -const socialLinksByPlatformSelector = createSelector( - profileAccountSelector, - (account) => { - const linksByPlatform = {}; - if (Array.isArray(account.socialLinks)) { - account.socialLinks.forEach((socialLink) => { - linksByPlatform[socialLink.platform] = socialLink; - }); - } - return linksByPlatform; - }, -); - -const draftSocialLinksByPlatformSelector = createSelector( - profileDraftsSelector, - (drafts) => { - const linksByPlatform = {}; - if (Array.isArray(drafts.socialLinks)) { - drafts.socialLinks.forEach((socialLink) => { - linksByPlatform[socialLink.platform] = socialLink; - }); - } - return linksByPlatform; - }, -); - -export const formSocialLinksSelector = createSelector( - socialLinksByPlatformSelector, - draftSocialLinksByPlatformSelector, - (linksByPlatform, draftLinksByPlatform) => { - const knownPlatforms = ['twitter', 'facebook', 'linkedin']; - const socialLinks = []; - knownPlatforms.forEach((platform) => { - if (draftLinksByPlatform[platform] !== undefined) { - socialLinks.push(draftLinksByPlatform[platform]); - } else if (linksByPlatform[platform] !== undefined) { - socialLinks.push(linksByPlatform[platform]); - } else { - socialLinks.push({ - platform, - socialLink: null, - }); - } - }); - return socialLinks; - }, -); - -export const visibilitiesSelector = createSelector( - profilePreferencesSelector, - accountPrivacySelector, - (preferences, accountPrivacy) => { - switch (accountPrivacy) { - case 'custom': - return { - visibilityBio: preferences.visibilityBio || 'all_users', - visibilityCountry: preferences.visibilityCountry || 'all_users', - visibilityLevelOfEducation: preferences.visibilityLevelOfEducation || 'all_users', - visibilityLanguageProficiencies: preferences.visibilityLanguageProficiencies || 'all_users', - visibilityName: preferences.visibilityName || 'all_users', - visibilitySocialLinks: preferences.visibilitySocialLinks || 'all_users', - }; - case 'private': - return { - visibilityBio: 'private', - visibilityCountry: 'private', - visibilityLevelOfEducation: 'private', - visibilityLanguageProficiencies: 'private', - visibilityName: 'private', - visibilitySocialLinks: 'private', - }; - case 'all_users': - default: - return { - visibilityBio: 'all_users', - visibilityCountry: 'all_users', - visibilityLevelOfEducation: 'all_users', - visibilityLanguageProficiencies: 'all_users', - visibilityName: 'all_users', - visibilitySocialLinks: 'all_users', - }; - } - }, -); - -function chooseFormValue(draft, committed) { - return draft !== undefined ? draft : committed; -} - -export const formValuesSelector = createSelector( - profileAccountSelector, - visibilitiesSelector, - profileDraftsSelector, - profileCourseCertificatesSelector, - formSocialLinksSelector, - (account, visibilities, drafts, courseCertificates, socialLinks) => ({ - bio: chooseFormValue(drafts.bio, account.bio), - visibilityBio: chooseFormValue(drafts.visibilityBio, visibilities.visibilityBio), - courseCertificates, - country: chooseFormValue(drafts.country, account.country), - visibilityCountry: chooseFormValue(drafts.visibilityCountry, visibilities.visibilityCountry), - levelOfEducation: chooseFormValue(drafts.levelOfEducation, account.levelOfEducation), - visibilityLevelOfEducation: chooseFormValue( - drafts.visibilityLevelOfEducation, - visibilities.visibilityLevelOfEducation, - ), - languageProficiencies: chooseFormValue( - drafts.languageProficiencies, - account.languageProficiencies, - ), - visibilityLanguageProficiencies: chooseFormValue( - drafts.visibilityLanguageProficiencies, - visibilities.visibilityLanguageProficiencies, - ), - name: chooseFormValue(drafts.name, account.name), - visibilityName: chooseFormValue(drafts.visibilityName, visibilities.visibilityName), - socialLinks, - visibilitySocialLinks: chooseFormValue( - drafts.visibilitySocialLinks, - visibilities.visibilitySocialLinks, - ), - }), -); - -export const profilePageSelector = createSelector( - profileAccountSelector, - formValuesSelector, - profileImageSelector, - saveStateSelector, - savePhotoStateSelector, - isLoadingProfileSelector, - draftSocialLinksByPlatformSelector, - accountErrorsSelector, - isAuthenticatedUserProfileSelector, - ( - account, - formValues, - profileImage, - saveState, - savePhotoState, - isLoadingProfile, - draftSocialLinksByPlatform, - errors, - isAuthenticatedUserProfile, - ) => ({ - username: account.username, - profileImage, - requiresParentalConsent: account.requiresParentalConsent, - dateJoined: account.dateJoined, - yearOfBirth: account.yearOfBirth, - - bio: formValues.bio, - visibilityBio: formValues.visibilityBio, - - courseCertificates: formValues.courseCertificates, - - country: formValues.country, - visibilityCountry: formValues.visibilityCountry, - - levelOfEducation: formValues.levelOfEducation, - visibilityLevelOfEducation: formValues.visibilityLevelOfEducation, - - languageProficiencies: formValues.languageProficiencies, - visibilityLanguageProficiencies: formValues.visibilityLanguageProficiencies, - - name: formValues.name, - visibilityName: formValues.visibilityName, - - socialLinks: formValues.socialLinks, - visibilitySocialLinks: formValues.visibilitySocialLinks, - draftSocialLinksByPlatform, - - saveState, - savePhotoState, - isLoadingProfile, - photoUploadError: errors.photo || null, - isAuthenticatedUserProfile, - }), -); diff --git a/src/profile-v2/data/services.js b/src/profile-v2/data/services.js deleted file mode 100644 index e6104df..0000000 --- a/src/profile-v2/data/services.js +++ /dev/null @@ -1,168 +0,0 @@ -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'; -import { FIELD_LABELS } from './constants'; - -ensureConfig(['LMS_BASE_URL'], 'Profile API service'); - -function processAccountData(data) { - const processedData = camelCaseObject(data); - return { - ...processedData, - socialLinks: Array.isArray(processedData.socialLinks) ? processedData.socialLinks : [], - languageProficiencies: Array.isArray(processedData.languageProficiencies) - ? processedData.languageProficiencies : [], - name: processedData.name || null, - bio: processedData.bio || null, - country: processedData.country || null, - levelOfEducation: processedData.levelOfEducation || null, - profileImage: processedData.profileImage || {}, - yearOfBirth: processedData.yearOfBirth || null, - }; -} - -function processAndThrowError(error, errorDataProcessor) { - const processedError = Object.create(error); - if (error.response && error.response.data && typeof error.response.data === 'object') { - processedError.processedData = errorDataProcessor(error.response.data); - throw processedError; - } else { - throw error; - } -} - -export async function getAccount(username) { - const { data } = await getHttpClient().get(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}`); - - return processAccountData(data); -} - -export async function patchProfile(username, params) { - const processedParams = snakeCaseObject(params); - - const { data } = await getHttpClient() - .patch(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}`, processedParams, { - headers: { - 'Content-Type': 'application/merge-patch+json', - }, - }) - .catch((error) => { - processAndThrowError(error, processAccountData); - }); - - return processAccountData(data); -} - -export async function postProfilePhoto(username, formData) { - // eslint-disable-next-line no-unused-vars - const { data } = await getHttpClient().post( - `${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}/image`, - formData, - { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }, - ).catch((error) => { - processAndThrowError(error, camelCaseObject); - }); - - // TODO: Someday in the future the POST photo endpoint - // will return the new values. At that time we should - // use the commented line below instead of the separate - // getAccount request that follows. - // return camelCaseObject(data); - const updatedData = await getAccount(username); - return updatedData.profileImage; -} - -export async function deleteProfilePhoto(username) { - // eslint-disable-next-line no-unused-vars - 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 - // use the commented line below instead of the separate - // getAccount request that follows. - // return camelCaseObject(data); - const updatedData = await getAccount(username); - return updatedData.profileImage; -} - -export async function getPreferences(username) { - const { data } = await getHttpClient().get(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`); - - return camelCaseObject(data); -} - -export async function patchPreferences(username, params) { - let processedParams = snakeCaseObject(params); - processedParams = convertKeyNames(processedParams, { - visibility_bio: 'visibility.bio', - visibility_course_certificates: 'visibility.course_certificates', - visibility_country: 'visibility.country', - visibility_date_joined: 'visibility.date_joined', - visibility_level_of_education: 'visibility.level_of_education', - visibility_language_proficiencies: 'visibility.language_proficiencies', - visibility_name: 'visibility.name', - visibility_social_links: 'visibility.social_links', - visibility_time_zone: 'visibility.time_zone', - }); - - await getHttpClient().patch(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`, processedParams, { - headers: { 'Content-Type': 'application/merge-patch+json' }, - }); - - return params; // TODO: Once the server returns the updated preferences object, return that. -} - -function transformCertificateData(data) { - const transformedData = []; - 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 downloadUrl = urlIsPath - ? `${getConfig().LMS_BASE_URL}${cert.download_url}` - : cert.download_url; - - transformedData.push({ - ...camelCaseObject(cert), - certificateType: cert.certificate_type, - downloadUrl, - }); - }); - return transformedData; -} - -export async function getCourseCertificates(username) { - const url = `${getConfig().LMS_BASE_URL}/api/certificates/v0/certificates/${username}/`; - try { - const { data } = await getHttpClient().get(url); - return transformCertificateData(data); - } catch (e) { - logError(e); - return []; - } -} - -function extractCountryList(data) { - return data?.fields - .find(({ name }) => name === FIELD_LABELS.COUNTRY) - ?.options?.map(({ value }) => (value)) || []; -} - -export async function getCountryList() { - const url = `${getConfig().LMS_BASE_URL}/user_api/v1/account/registration/`; - - try { - const { data } = await getHttpClient().get(url); - return extractCountryList(data); - } catch (e) { - logError(e); - return []; - } -} diff --git a/src/profile-v2/forms/Bio.jsx b/src/profile-v2/forms/Bio.jsx deleted file mode 100644 index 797bbb6..0000000 --- a/src/profile-v2/forms/Bio.jsx +++ /dev/null @@ -1,151 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; -import { Form } from '@openedx/paragon'; - -import classNames from 'classnames'; -import messages from './Bio.messages'; - -import FormControls from './elements/FormControls'; -import EditableItemHeader from './elements/EditableItemHeader'; -import EmptyContent from './elements/EmptyContent'; -import SwitchContent from './elements/SwitchContent'; - -import { editableFormSelector } from '../data/selectors'; -import { - useCloseOpenHandler, - useHandleChange, - useHandleSubmit, - useIsOnMobileScreen, - useIsVisibilityEnabled, -} from '../data/hooks'; - -const Bio = ({ - formId, - bio, - visibilityBio, - editMode, - saveState, - error, - changeHandler, - submitHandler, - closeHandler, - openHandler, -}) => { - const isMobileView = useIsOnMobileScreen(); - const isVisibilityEnabled = useIsVisibilityEnabled(); - const intl = useIntl(); - - const handleChange = useHandleChange(changeHandler); - const handleSubmit = useHandleSubmit(submitHandler, formId); - const handleOpen = useCloseOpenHandler(openHandler, formId); - const handleClose = useCloseOpenHandler(closeHandler, formId); - - return ( - -
- -

- {intl.formatMessage(messages['profile.bio.about.me'])} -

- -
-
-
- - - - - - - -
-
- - -
-
- -
-
-
- -
-
-
-`; - -exports[` Renders correctly in various states while saving an edited bio with error 1`] = ` -
-
-
-
- -
-
-
- -

- staff -

-

- Member since - 2017 -

-
-
-
- -
-
-
-

- Full Name - -

-

- - - - Just me - -

-
-

- Lemon Seltzer -

- - This is the name that appears in your account and on your certificates. - -
-
-
-
-
-

- Location - -

-

- - - - Everyone on localhost - -

-
-

- Montenegro -

-
-
-
-
-
-

- Primary Language Spoken - -

-

- - - - Everyone on localhost - -

-
-

- Yoruba -

-
-
-
-
-
-

- Education - -

-

- - - - Just me - -

-
-

- Elementary/primary school -

-
-
-
-
-
-

- Social Links - -

-

- - - - Everyone on localhost - -

-
- -
-
-
-
-
-
-
-
-
- - -
-
- bio error -
-
-
-
-
- - - - - - - -
-
- - -
-
-
-
-
-
-
-
-
-

- My Certificates - -

-

- - - - Everyone on localhost - -

-
-
-
-
-
-

- Verified Certificate -

-

- edX Demonstration Course -

-
-

- From -

-

- edX -

-
-

- Completed on - 3/4/2019 -

- -
+ Verified Certificate +

+

+ edX Demonstration Course +

+

+ From +

+
+ edX +
+

+ Completed on + 3/4/2019 +

+ +

+ Credential ID +

@@ -9420,19 +1472,16 @@ exports[` Renders correctly in various states without credentials class="profile-page" >
-
Renders correctly in various states without credentials
-
- -
profile avatar
+
+ +
@@ -9477,726 +1552,723 @@ exports[` Renders correctly in various states without credentials />
-
-
-
-
- -

- staff -

-

- Member since - 2017 -

-
-
-
-
-
-
-
-
-
- -

- staff -

-

- Member since - 2017 -

-
-
-
-
-
-
-

- Full Name - -

-

- - - - Just me - -

-

+ staff +

+

Lemon Seltzer

- - This is the name that appears in your account and on your certificates. - -
-
-
-
-

- Location - -

-

+ Member since - - - Everyone on localhost - -

-
-

- Montenegro -

-
-
-
-
-
-

- Primary Language Spoken - -

-

- - - - Everyone on localhost - -

-
-

- Yoruba -

-
-
-
-
-
-

- Education - -

-

- - - - Just me - -

-
-

- Elementary/primary school -

-
-
-
-
-
-

- Social Links - -

-

- - - - Everyone on localhost - -

-
- + 1 + + certifications + +
+
+
+
+
+
+
+

+ Profile information +

+
+
+
+
-

- About Me - -

- - - - Everyone on localhost - + Username

+ + +
-

- This is my bio -

+ staff + +
+
+
+
+

+ Full name +

+ + + +
+
+
+

+ Lemon Seltzer +

+
+
+ +
+
+
+

+ + + + Just me + +

+
+
+
+
+
+

+ Country +

+
+
+

+ Montenegro +

+
+
+ +
+
+
+

+ + + + Everyone on localhost + +

+
+
+
+
+
+

+ Primary language spoken +

+
+
+

+ Yoruba +

+
+
+ +
+
+
+

+ + + + Everyone on localhost + +

+
+
+
+
+
+

+ Education +

+
+
+

+ Elementary/primary school +

+
+
+ +
+
+
+

+ + + + Just me + +

+
+
-

- My Certificates - -

- - - - Everyone on localhost - + Bio

-
-
-
-
-
+
+
+
+ + + +
+
+
+

+ + + + Everyone on localhost + +

+
+
+
+
+
+
+ +
+

+ Facebook +

+
+
+
+

+ https://www.facebook.com/aloha +

+
+
+ +
+
+
+

+ + + + Everyone on localhost + +

+
+
+
+
+

+ LinkedIn +

+
+
@@ -10207,6 +2279,105 @@ exports[` Renders correctly in various states without credentials
+
+
+
+
+

+ Your certificates +

+
+
+

+ Your learner records information is only visible to you. Only your username and profile image are visible to others on localhost. +

+
+
+
+
+
+
+
+
+
+

+ Verified Certificate +

+

+ edX Demonstration Course +

+

+ From +

+
+ edX +
+

+ Completed on + 3/4/2019 +

+
+ +

+ Credential ID +

+
+
+
+
+
+
+
`; diff --git a/src/profile/data/actions.js b/src/profile/data/actions.js index 85edb5b..b960400 100644 --- a/src/profile/data/actions.js +++ b/src/profile/data/actions.js @@ -9,8 +9,6 @@ export const CLOSE_FORM = 'CLOSE_FORM'; export const UPDATE_DRAFT = 'UPDATE_DRAFT'; export const RESET_DRAFTS = 'RESET_DRAFTS'; -// FETCH PROFILE ACTIONS - export const fetchProfile = username => ({ type: FETCH_PROFILE.BASE, payload: { username }, @@ -25,22 +23,18 @@ export const fetchProfileSuccess = ( preferences, courseCertificates, isAuthenticatedUserProfile, - countriesCodesList, ) => ({ type: FETCH_PROFILE.SUCCESS, account, preferences, courseCertificates, isAuthenticatedUserProfile, - countriesCodesList, }); export const fetchProfileReset = () => ({ type: FETCH_PROFILE.RESET, }); -// SAVE PROFILE ACTIONS - export const saveProfile = (formId, username) => ({ type: SAVE_PROFILE.BASE, payload: { @@ -70,8 +64,6 @@ export const saveProfileFailure = errors => ({ payload: { errors }, }); -// SAVE PROFILE PHOTO ACTIONS - export const saveProfilePhoto = (username, formData) => ({ type: SAVE_PROFILE_PHOTO.BASE, payload: { @@ -98,8 +90,6 @@ export const saveProfilePhotoFailure = error => ({ payload: { error }, }); -// DELETE PROFILE PHOTO ACTIONS - export const deleteProfilePhoto = username => ({ type: DELETE_PROFILE_PHOTO.BASE, payload: { @@ -120,8 +110,6 @@ export const deleteProfilePhotoReset = () => ({ type: DELETE_PROFILE_PHOTO.RESET, }); -// FIELD STATE ACTIONS - export const openForm = formId => ({ type: OPEN_FORM, payload: { @@ -136,8 +124,6 @@ export const closeForm = formId => ({ }, }); -// FORM STATE ACTIONS - export const updateDraft = (name, value) => ({ type: UPDATE_DRAFT, payload: { diff --git a/src/profile/data/actions.test.js b/src/profile/data/actions.test.js index 6268888..275d695 100644 --- a/src/profile/data/actions.test.js +++ b/src/profile/data/actions.test.js @@ -1,14 +1,4 @@ import { - openForm, - closeForm, - OPEN_FORM, - CLOSE_FORM, - SAVE_PROFILE, - saveProfileBegin, - saveProfileSuccess, - saveProfileFailure, - saveProfileReset, - saveProfile, SAVE_PROFILE_PHOTO, saveProfilePhotoBegin, saveProfilePhotoSuccess, @@ -22,76 +12,6 @@ import { deleteProfilePhoto, } from './actions'; -describe('editable field actions', () => { - it('should create an open action', () => { - const expectedAction = { - type: OPEN_FORM, - payload: { - formId: 'name', - }, - }; - expect(openForm('name')).toEqual(expectedAction); - }); - - it('should create a closed action', () => { - const expectedAction = { - type: CLOSE_FORM, - payload: { - formId: 'name', - }, - }; - expect(closeForm('name')).toEqual(expectedAction); - }); -}); - -describe('SAVE profile actions', () => { - it('should create an action to signal the start of a profile save', () => { - const expectedAction = { - type: SAVE_PROFILE.BASE, - payload: { - formId: 'name', - }, - }; - expect(saveProfile('name')).toEqual(expectedAction); - }); - - it('should create an action to signal user profile save success', () => { - const accountData = { name: 'Full Name' }; - const preferencesData = { visibility: { name: 'private' } }; - const expectedAction = { - type: SAVE_PROFILE.SUCCESS, - payload: { - account: accountData, - preferences: preferencesData, - }, - }; - expect(saveProfileSuccess(accountData, preferencesData)).toEqual(expectedAction); - }); - - it('should create an action to signal user profile save beginning', () => { - const expectedAction = { - type: SAVE_PROFILE.BEGIN, - }; - expect(saveProfileBegin()).toEqual(expectedAction); - }); - - it('should create an action to signal user profile save success', () => { - const expectedAction = { - type: SAVE_PROFILE.RESET, - }; - expect(saveProfileReset()).toEqual(expectedAction); - }); - - it('should create an action to signal user account save failure', () => { - const errors = ['Test failure']; - const expectedAction = { - type: SAVE_PROFILE.FAILURE, - payload: { errors }, - }; - expect(saveProfileFailure(errors)).toEqual(expectedAction); - }); -}); - describe('SAVE profile photo actions', () => { it('should create an action to signal the start of a profile photo save', () => { const formData = 'multipart form data'; @@ -123,7 +43,7 @@ describe('SAVE profile photo actions', () => { expect(saveProfilePhotoSuccess(newPhotoData)).toEqual(expectedAction); }); - it('should create an action to signal user profile photo save success', () => { + it('should create an action to signal user profile photo save reset', () => { const expectedAction = { type: SAVE_PROFILE_PHOTO.RESET, }; @@ -169,34 +89,10 @@ describe('DELETE profile photo actions', () => { expect(deleteProfilePhotoSuccess(defaultPhotoData)).toEqual(expectedAction); }); - it('should create an action to signal user profile photo deletion success', () => { + it('should create an action to signal user profile photo deletion reset', () => { const expectedAction = { type: DELETE_PROFILE_PHOTO.RESET, }; expect(deleteProfilePhotoReset()).toEqual(expectedAction); }); }); - -describe('Editable field opening and closing actions', () => { - const formId = 'name'; - - it('should create an action to signal the opening a field', () => { - const expectedAction = { - type: OPEN_FORM, - payload: { - formId, - }, - }; - expect(openForm(formId)).toEqual(expectedAction); - }); - - it('should create an action to signal the closing a field', () => { - const expectedAction = { - type: CLOSE_FORM, - payload: { - formId, - }, - }; - expect(closeForm(formId)).toEqual(expectedAction); - }); -}); diff --git a/src/profile-v2/data/hooks.js b/src/profile/data/hooks.js similarity index 93% rename from src/profile-v2/data/hooks.js rename to src/profile/data/hooks.js index 161a436..abf37c5 100644 --- a/src/profile-v2/data/hooks.js +++ b/src/profile/data/hooks.js @@ -12,7 +12,7 @@ export function useIsOnMobileScreen() { } export function useIsVisibilityEnabled() { - return getConfig().DISABLE_VISIBILITY_EDITING === 'true'; + return getConfig().DISABLE_VISIBILITY_EDITING !== 'true'; } export function useHandleChange(changeHandler) { diff --git a/src/profile/data/pact-profile.test.js b/src/profile/data/pact-profile.test.js index 3addaa4..abd14e6 100644 --- a/src/profile/data/pact-profile.test.js +++ b/src/profile/data/pact-profile.test.js @@ -17,6 +17,10 @@ const expectedUserInfo200 = { dateJoined: '2017-06-07T00:44:23Z', isActive: true, yearOfBirth: 1901, + languageProficiencies: [], + levelOfEducation: null, + profileImage: {}, + socialLinks: [], }; const provider = new PactV3({ diff --git a/src/profile/data/reducers.js b/src/profile/data/reducers.js index 0d374c6..3e4760c 100644 --- a/src/profile/data/reducers.js +++ b/src/profile/data/reducers.js @@ -16,12 +16,27 @@ export const initialState = { currentlyEditingField: null, account: { socialLinks: [], + languageProficiencies: [], + name: '', + bio: '', + country: '', + levelOfEducation: '', + profileImage: {}, + yearOfBirth: '', + }, + preferences: { + visibilityName: '', + visibilityBio: '', + visibilityCountry: '', + visibilityLevelOfEducation: '', + visibilitySocialLinks: '', + visibilityLanguageProficiencies: '', }, - preferences: {}, courseCertificates: [], drafts: {}, isLoadingProfile: true, isAuthenticatedUserProfile: false, + disabledCountries: ['RU'], countriesCodesList: [], }; @@ -38,12 +53,17 @@ const profilePage = (state = initialState, action = {}) => { case FETCH_PROFILE.SUCCESS: return { ...state, - account: action.account, + account: { + ...state.account, + ...action.account, + socialLinks: action.account.socialLinks || [], + languageProficiencies: action.account.languageProficiencies || [], + }, preferences: action.preferences, - courseCertificates: action.courseCertificates, + courseCertificates: action.courseCertificates || [], isLoadingProfile: false, isAuthenticatedUserProfile: action.isAuthenticatedUserProfile, - countriesCodesList: action.countriesCodesList, + countriesCodesList: action.countriesCodesList || [], }; case SAVE_PROFILE.BEGIN: return { @@ -56,9 +76,12 @@ const profilePage = (state = initialState, action = {}) => { ...state, saveState: 'complete', errors: {}, - // Account is always replaced completely. - account: action.payload.account !== null ? action.payload.account : state.account, - // Preferences changes get merged in. + account: action.payload.account !== null ? { + ...state.account, + ...action.payload.account, + socialLinks: action.payload.account.socialLinks || [], + languageProficiencies: action.payload.account.languageProficiencies || [], + } : state.account, preferences: { ...state.preferences, ...action.payload.preferences }, }; case SAVE_PROFILE.FAILURE: @@ -75,7 +98,6 @@ const profilePage = (state = initialState, action = {}) => { isLoadingProfile: false, errors: {}, }; - case SAVE_PROFILE_PHOTO.BEGIN: return { ...state, @@ -85,7 +107,6 @@ const profilePage = (state = initialState, action = {}) => { case SAVE_PROFILE_PHOTO.SUCCESS: return { ...state, - // Merge in new profile image data account: { ...state.account, profileImage: action.payload.profileImage }, savePhotoState: 'complete', errors: {}, @@ -102,7 +123,6 @@ const profilePage = (state = initialState, action = {}) => { savePhotoState: null, errors: {}, }; - case DELETE_PROFILE_PHOTO.BEGIN: return { ...state, @@ -112,7 +132,6 @@ const profilePage = (state = initialState, action = {}) => { case DELETE_PROFILE_PHOTO.SUCCESS: return { ...state, - // Merge in new profile image data (should be empty or default image) account: { ...state.account, profileImage: action.payload.profileImage }, savePhotoState: 'complete', errors: {}, @@ -129,13 +148,11 @@ const profilePage = (state = initialState, action = {}) => { savePhotoState: null, errors: {}, }; - case UPDATE_DRAFT: return { ...state, drafts: { ...state.drafts, [action.payload.name]: action.payload.value }, }; - case RESET_DRAFTS: return { ...state, @@ -148,7 +165,6 @@ const profilePage = (state = initialState, action = {}) => { drafts: {}, }; case CLOSE_FORM: - // Only close if the field to close is undefined or matches the field that is currently open if (action.payload.formId === state.currentlyEditingField) { return { ...state, diff --git a/src/profile-v2/data/reducers.test.js b/src/profile/data/reducers.test.js similarity index 100% rename from src/profile-v2/data/reducers.test.js rename to src/profile/data/reducers.test.js diff --git a/src/profile/data/sagas.js b/src/profile/data/sagas.js index e2ebba5..b64c3fe 100644 --- a/src/profile/data/sagas.js +++ b/src/profile/data/sagas.js @@ -1,3 +1,4 @@ +import { history } from '@edx/frontend-platform'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import pick from 'lodash.pick'; import { @@ -21,13 +22,12 @@ import { resetDrafts, saveProfileBegin, saveProfileFailure, - saveProfilePhotoBegin, - saveProfilePhotoFailure, - saveProfilePhotoReset, - saveProfilePhotoSuccess, saveProfileReset, saveProfileSuccess, SAVE_PROFILE, + saveProfilePhotoBegin, + saveProfilePhotoReset, + saveProfilePhotoSuccess, SAVE_PROFILE_PHOTO, } from './actions'; import { handleSaveProfileSelector, userAccountSelector } from './selectors'; @@ -37,7 +37,6 @@ export function* handleFetchProfile(action) { const { username } = action.payload; const userAccount = yield select(userAccountSelector); const isAuthenticatedUserProfile = username === getAuthenticatedUser().username; - // Default our data assuming the account is the current user's account. let preferences = {}; let account = userAccount; let courseCertificates = null; @@ -46,7 +45,6 @@ export function* handleFetchProfile(action) { try { yield put(fetchProfileBegin()); - // Depending on which profile we're loading, we need to make different calls. const calls = [ call(ProfileApiService.getAccount, username), call(ProfileApiService.getCourseCertificates, username), @@ -54,12 +52,9 @@ export function* handleFetchProfile(action) { ]; if (isAuthenticatedUserProfile) { - // If the profile is for the current user, get their preferences. - // We don't need them for other users. calls.push(call(ProfileApiService.getPreferences, username)); } - // Make all the calls in parallel. const result = yield all(calls); if (isAuthenticatedUserProfile) { @@ -68,9 +63,6 @@ export function* handleFetchProfile(action) { [account, courseCertificates, countriesCodesList] = result; } - // Set initial visibility values for account - // Set account_privacy as custom is necessary so that when viewing another user's profile, - // their full name is displayed and change visibility forms are worked correctly if (isAuthenticatedUserProfile && result[0].accountPrivacy === 'all_users') { yield call(ProfileApiService.patchPreferences, action.payload.username, { account_privacy: 'custom', @@ -97,11 +89,7 @@ export function* handleFetchProfile(action) { yield put(fetchProfileReset()); } catch (e) { if (e.response.status === 404) { - if (e.processedData && e.processedData.fieldErrors) { - yield put(saveProfileFailure(e.processedData.fieldErrors)); - } else { - yield put(saveProfileFailure(e.customAttributes)); - } + history.push('/notfound'); } else { throw e; } @@ -114,7 +102,6 @@ export function* handleSaveProfile(action) { const accountDrafts = pick(drafts, [ 'bio', - 'courseCertificates', 'country', 'levelOfEducation', 'languageProficiencies', @@ -124,7 +111,6 @@ export function* handleSaveProfile(action) { const preferencesDrafts = pick(drafts, [ 'visibilityBio', - 'visibilityCourseCertificates', 'visibilityCountry', 'visibilityLevelOfEducation', 'visibilityLanguageProficiencies', @@ -138,7 +124,6 @@ export function* handleSaveProfile(action) { yield put(saveProfileBegin()); let accountResult = null; - // Build the visibility drafts into a structure the API expects. if (Object.keys(accountDrafts).length > 0) { accountResult = yield call( @@ -148,17 +133,14 @@ export function* handleSaveProfile(action) { ); } - let preferencesResult = preferences; // assume it hasn't changed. + let preferencesResult = preferences; if (Object.keys(preferencesDrafts).length > 0) { yield call(ProfileApiService.patchPreferences, action.payload.username, preferencesDrafts); // TODO: Temporary deoptimization since the patchPreferences call doesn't return anything. - // Remove this second call once we can get a result from the one above. + preferencesResult = yield call(ProfileApiService.getPreferences, action.payload.username); } - // The account result is returned from the server. - // The preferences draft is valid if the server didn't complain, so - // pass it through directly. yield put(saveProfileSuccess(accountResult, preferencesResult)); yield delay(1000); yield put(closeForm(action.payload.formId)); @@ -184,12 +166,7 @@ export function* handleSaveProfilePhoto(action) { yield put(saveProfilePhotoSuccess(photoResult)); yield put(saveProfilePhotoReset()); } catch (e) { - if (e.processedData) { - yield put(saveProfilePhotoFailure(e.processedData)); - } else { - yield put(saveProfilePhotoReset()); - throw e; - } + yield put(saveProfilePhotoReset()); } } @@ -203,7 +180,6 @@ export function* handleDeleteProfilePhoto(action) { yield put(deleteProfilePhotoReset()); } catch (e) { yield put(deleteProfilePhotoReset()); - throw e; } } diff --git a/src/profile/data/sagas.test.js b/src/profile/data/sagas.test.js index 291ab1b..2da09b3 100644 --- a/src/profile/data/sagas.test.js +++ b/src/profile/data/sagas.test.js @@ -26,7 +26,6 @@ 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, { handleFetchProfile, @@ -78,7 +77,6 @@ describe('RootSaga', () => { call(ProfileApiService.getCourseCertificates, 'gonzo'), call(ProfileApiService.getCountryList), call(ProfileApiService.getPreferences, 'gonzo'), - ])); expect(gen.next(result).value) .toEqual(put(profileActions.fetchProfileSuccess(userAccount, result[3], result[1], true, []))); @@ -137,8 +135,6 @@ describe('RootSaga', () => { expect(gen.next().value).toEqual(call(ProfileApiService.patchProfile, 'my username', { name: 'Full Name', })); - // The library would supply the result of the above call - // as the parameter to the NEXT yield. Here: expect(gen.next(profile).value).toEqual(put(profileActions.saveProfileSuccess(profile, {}))); expect(gen.next().value).toEqual(delay(1000)); expect(gen.next().value).toEqual(put(profileActions.closeForm('ze form id'))); diff --git a/src/profile/data/selectors.js b/src/profile/data/selectors.js index 41d69fa..d398295 100644 --- a/src/profile/data/selectors.js +++ b/src/profile/data/selectors.js @@ -5,18 +5,15 @@ import { getCountryList, getCountryMessages, getLanguageMessages, -} from '@edx/frontend-platform/i18n'; // eslint-disable-line +} from '@edx/frontend-platform/i18n'; export const formIdSelector = (state, props) => props.formId; export const userAccountSelector = state => state.userAccount; - export const profileAccountSelector = state => state.profilePage.account; export const profileDraftsSelector = state => state.profilePage.drafts; export const accountPrivacySelector = state => state.profilePage.preferences.accountPrivacy; export const profilePreferencesSelector = state => state.profilePage.preferences; export const profileCourseCertificatesSelector = state => state.profilePage.courseCertificates; -export const profileAccountDraftsSelector = state => state.profilePage.accountDrafts; -export const profileVisibilityDraftsSelector = state => state.profilePage.visibilityDrafts; export const saveStateSelector = state => state.profilePage.saveState; export const savePhotoStateSelector = state => state.profilePage.savePhotoState; export const isLoadingProfileSelector = state => state.profilePage.isLoadingProfile; @@ -32,22 +29,11 @@ export const editableFormModeSelector = createSelector( formIdSelector, currentlyEditingFieldSelector, (account, isAuthenticatedUserProfile, certificates, formId, currentlyEditingField) => { - // If the prop doesn't exist, that means it hasn't been set (for the current user's profile) - // or is being hidden from us (for other users' profiles) let propExists = account[formId] != null && account[formId].length > 0; - propExists = formId === 'certificates' ? certificates.length > 0 : propExists; // overwrite for certificates - // If this isn't the current user's profile + propExists = formId === 'certificates' ? certificates.length > 0 : propExists; if (!isAuthenticatedUserProfile) { return 'static'; } - // the current user has no age set / under 13 ... - if (account.requiresParentalConsent) { - // then there are only two options: static or nothing. - // We use 'null' as a return value because the consumers of - // getMode render nothing at all on a mode of null. - return propExists ? 'static' : null; - } - // Otherwise, if this is the current user's profile... if (formId === currentlyEditingField) { return 'editing'; } @@ -68,12 +54,10 @@ export const accountDraftsFieldSelector = createSelector( export const visibilityDraftsFieldSelector = createSelector( formIdSelector, - profileVisibilityDraftsSelector, - (formId, visibilityDrafts) => visibilityDrafts[formId], + profileDraftsSelector, + (formId, drafts) => drafts[`visibility${formId.charAt(0).toUpperCase() + formId.slice(1)}`], ); -// Note: Error messages are delivered from the server -// localized according to a user's account settings export const formErrorSelector = createSelector( accountErrorsSelector, formIdSelector, @@ -91,11 +75,6 @@ export const editableFormSelector = createSelector( }), ); -// Because this selector has no input selectors, it will only be evaluated once. This is fine -// for now because we don't allow users to change the locale after page load. -// Once we DO allow this, we should create an actual action which dispatches the locale into redux, -// then we can modify this to get the locale from state rather than from getLocale() directly. -// Once we do that, this will work as expected and be re-evaluated when the locale changes. export const localeSelector = () => getLocale(); export const countryMessagesSelector = createSelector( localeSelector, @@ -169,9 +148,6 @@ export const profileImageSelector = createSelector( : {}), ); -/** - * This is used by a saga to pull out data to process. - */ export const handleSaveProfileSelector = createSelector( profileDraftsSelector, profilePreferencesSelector, @@ -181,7 +157,6 @@ export const handleSaveProfileSelector = createSelector( }), ); -// Reformats the social links in a platform-keyed hash. const socialLinksByPlatformSelector = createSelector( profileAccountSelector, (account) => { @@ -208,24 +183,18 @@ const draftSocialLinksByPlatformSelector = createSelector( }, ); -// Fleshes out our list of existing social links with all the other ones the user can set. export const formSocialLinksSelector = createSelector( socialLinksByPlatformSelector, draftSocialLinksByPlatformSelector, (linksByPlatform, draftLinksByPlatform) => { const knownPlatforms = ['twitter', 'facebook', 'linkedin']; const socialLinks = []; - // For each known platform knownPlatforms.forEach((platform) => { - // If the link is in our drafts. if (draftLinksByPlatform[platform] !== undefined) { - // Use the draft one. socialLinks.push(draftLinksByPlatform[platform]); } else if (linksByPlatform[platform] !== undefined) { - // Otherwise use the real one. socialLinks.push(linksByPlatform[platform]); } else { - // And if it's not in either, use a stub. socialLinks.push({ platform, socialLink: null, @@ -244,7 +213,6 @@ export const visibilitiesSelector = createSelector( case 'custom': return { visibilityBio: preferences.visibilityBio || 'all_users', - visibilityCourseCertificates: preferences.visibilityCourseCertificates || 'all_users', visibilityCountry: preferences.visibilityCountry || 'all_users', visibilityLevelOfEducation: preferences.visibilityLevelOfEducation || 'all_users', visibilityLanguageProficiencies: preferences.visibilityLanguageProficiencies || 'all_users', @@ -254,7 +222,6 @@ export const visibilitiesSelector = createSelector( case 'private': return { visibilityBio: 'private', - visibilityCourseCertificates: 'private', visibilityCountry: 'private', visibilityLevelOfEducation: 'private', visibilityLanguageProficiencies: 'private', @@ -263,13 +230,8 @@ export const visibilitiesSelector = createSelector( }; case 'all_users': default: - // All users is intended to fall through to default. - // If there is no value for accountPrivacy in perferences, that means it has not been - // explicitly set yet. The server assumes - today - that this means "all_users", - // so we emulate that here in the client. return { visibilityBio: 'all_users', - visibilityCourseCertificates: 'all_users', visibilityCountry: 'all_users', visibilityLevelOfEducation: 'all_users', visibilityLanguageProficiencies: 'all_users', @@ -280,9 +242,6 @@ export const visibilitiesSelector = createSelector( }, ); -/** - * If there's no draft present at all (undefined), use the original committed value. - */ function chooseFormValue(draft, committed) { return draft !== undefined ? draft : committed; } @@ -297,10 +256,6 @@ export const formValuesSelector = createSelector( bio: chooseFormValue(drafts.bio, account.bio), visibilityBio: chooseFormValue(drafts.visibilityBio, visibilities.visibilityBio), courseCertificates, - visibilityCourseCertificates: chooseFormValue( - drafts.visibilityCourseCertificates, - visibilities.visibilityCourseCertificates, - ), country: chooseFormValue(drafts.country, account.country), visibilityCountry: chooseFormValue(drafts.visibilityCountry, visibilities.visibilityCountry), levelOfEducation: chooseFormValue(drafts.levelOfEducation, account.levelOfEducation), @@ -318,7 +273,7 @@ export const formValuesSelector = createSelector( ), name: chooseFormValue(drafts.name, account.name), visibilityName: chooseFormValue(drafts.visibilityName, visibilities.visibilityName), - socialLinks, // Social links is calculated in its own selector, since it's complicated. + socialLinks, visibilitySocialLinks: chooseFormValue( drafts.visibilitySocialLinks, visibilities.visibilitySocialLinks, @@ -335,6 +290,7 @@ export const profilePageSelector = createSelector( isLoadingProfileSelector, draftSocialLinksByPlatformSelector, accountErrorsSelector, + isAuthenticatedUserProfileSelector, ( account, formValues, @@ -344,47 +300,39 @@ export const profilePageSelector = createSelector( isLoadingProfile, draftSocialLinksByPlatform, errors, + isAuthenticatedUserProfile, ) => ({ - // Account data we need username: account.username, profileImage, requiresParentalConsent: account.requiresParentalConsent, dateJoined: account.dateJoined, yearOfBirth: account.yearOfBirth, - // Bio form data bio: formValues.bio, visibilityBio: formValues.visibilityBio, - // Certificates form data courseCertificates: formValues.courseCertificates, - visibilityCourseCertificates: formValues.visibilityCourseCertificates, - // Country form data country: formValues.country, visibilityCountry: formValues.visibilityCountry, - // Education form data levelOfEducation: formValues.levelOfEducation, visibilityLevelOfEducation: formValues.visibilityLevelOfEducation, - // Language proficiency form data languageProficiencies: formValues.languageProficiencies, visibilityLanguageProficiencies: formValues.visibilityLanguageProficiencies, - // Name form data name: formValues.name, visibilityName: formValues.visibilityName, - // Social links form data socialLinks: formValues.socialLinks, visibilitySocialLinks: formValues.visibilitySocialLinks, draftSocialLinksByPlatform, - // Other data we need saveState, savePhotoState, isLoadingProfile, photoUploadError: errors.photo || null, + isAuthenticatedUserProfile, }), ); diff --git a/src/profile/data/services.js b/src/profile/data/services.js index 17f15a4..e6104df 100644 --- a/src/profile/data/services.js +++ b/src/profile/data/services.js @@ -7,7 +7,19 @@ import { FIELD_LABELS } from './constants'; ensureConfig(['LMS_BASE_URL'], 'Profile API service'); function processAccountData(data) { - return camelCaseObject(data); + const processedData = camelCaseObject(data); + return { + ...processedData, + socialLinks: Array.isArray(processedData.socialLinks) ? processedData.socialLinks : [], + languageProficiencies: Array.isArray(processedData.languageProficiencies) + ? processedData.languageProficiencies : [], + name: processedData.name || null, + bio: processedData.bio || null, + country: processedData.country || null, + levelOfEducation: processedData.levelOfEducation || null, + profileImage: processedData.profileImage || {}, + yearOfBirth: processedData.yearOfBirth || null, + }; } function processAndThrowError(error, errorDataProcessor) { @@ -20,15 +32,12 @@ function processAndThrowError(error, errorDataProcessor) { } } -// GET ACCOUNT export async function getAccount(username) { const { data } = await getHttpClient().get(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}`); - // Process response data return processAccountData(data); } -// PATCH PROFILE export async function patchProfile(username, params) { const processedParams = snakeCaseObject(params); @@ -42,12 +51,9 @@ export async function patchProfile(username, params) { processAndThrowError(error, processAccountData); }); - // Process response data return processAccountData(data); } -// POST PROFILE PHOTO - export async function postProfilePhoto(username, formData) { // eslint-disable-next-line no-unused-vars const { data } = await getHttpClient().post( @@ -71,8 +77,6 @@ export async function postProfilePhoto(username, formData) { return updatedData.profileImage; } -// DELETE PROFILE PHOTO - export async function deleteProfilePhoto(username) { // eslint-disable-next-line no-unused-vars const { data } = await getHttpClient().delete(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}/image`); @@ -86,14 +90,12 @@ export async function deleteProfilePhoto(username) { return updatedData.profileImage; } -// GET PREFERENCES export async function getPreferences(username) { const { data } = await getHttpClient().get(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`); return camelCaseObject(data); } -// PATCH PREFERENCES export async function patchPreferences(username, params) { let processedParams = snakeCaseObject(params); processedParams = convertKeyNames(processedParams, { @@ -115,8 +117,6 @@ export async function patchPreferences(username, params) { return params; // TODO: Once the server returns the updated preferences object, return that. } -// GET COURSE CERTIFICATES - function transformCertificateData(data) { const transformedData = []; data.forEach((cert) => { diff --git a/src/profile/forms/Bio.jsx b/src/profile/forms/Bio.jsx index e847c97..797bbb6 100644 --- a/src/profile/forms/Bio.jsx +++ b/src/profile/forms/Bio.jsx @@ -1,149 +1,140 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { Form } from '@openedx/paragon'; +import classNames from 'classnames'; import messages from './Bio.messages'; -// Components import FormControls from './elements/FormControls'; import EditableItemHeader from './elements/EditableItemHeader'; import EmptyContent from './elements/EmptyContent'; import SwitchContent from './elements/SwitchContent'; -// Selectors import { editableFormSelector } from '../data/selectors'; +import { + useCloseOpenHandler, + useHandleChange, + useHandleSubmit, + useIsOnMobileScreen, + useIsVisibilityEnabled, +} from '../data/hooks'; -class Bio extends React.Component { - constructor(props) { - super(props); +const Bio = ({ + formId, + bio, + visibilityBio, + editMode, + saveState, + error, + changeHandler, + submitHandler, + closeHandler, + openHandler, +}) => { + const isMobileView = useIsOnMobileScreen(); + const isVisibilityEnabled = useIsVisibilityEnabled(); + const intl = useIntl(); - this.handleChange = this.handleChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleClose = this.handleClose.bind(this); - this.handleOpen = this.handleOpen.bind(this); - } + const handleChange = useHandleChange(changeHandler); + const handleSubmit = useHandleSubmit(submitHandler, formId); + const handleOpen = useCloseOpenHandler(openHandler, formId); + const handleClose = useCloseOpenHandler(closeHandler, formId); - handleChange(e) { - const { name, value } = e.target; - this.props.changeHandler(name, value); - } - - handleSubmit(e) { - e.preventDefault(); - this.props.submitHandler(this.props.formId); - } - - handleClose() { - this.props.closeHandler(this.props.formId); - } - - handleOpen() { - this.props.openHandler(this.props.formId); - } - - render() { - const { - formId, bio, visibilityBio, editMode, saveState, error, intl, - } = this.props; - - return ( - -
- - -