Skip to content

Commit 0ca9efe

Browse files
committed
feat: add extended profile fields handling and visibility logic in profile components
1 parent dddfd90 commit 0ca9efe

File tree

11 files changed

+325
-127
lines changed

11 files changed

+325
-127
lines changed

package-lock.json

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"@redux-devtools/extension": "3.3.0",
4444
"classnames": "2.5.1",
4545
"core-js": "3.36.1",
46+
"dompurify": "^3.2.4",
4647
"history": "5.3.0",
4748
"lodash.camelcase": "4.3.0",
4849
"lodash.get": "4.4.2",

src/profile/ProfilePage.jsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ class ProfilePage extends React.Component {
186186
visibilityBio,
187187
requiresParentalConsent,
188188
isLoadingProfile,
189+
extendedProfileFields,
189190
} = this.props;
190191

191192
if (isLoadingProfile) {
@@ -209,6 +210,11 @@ class ProfilePage extends React.Component {
209210
const isCertificatesBlockVisible = isBlockVisible(courseCertificates.length);
210211
const isNameBlockVisible = isBlockVisible(name);
211212
const isLocationBlockVisible = isBlockVisible(country);
213+
// TODO: modify /api/user/v1/accounts/{{username}} to return extended profile fields
214+
// So this can be shown for no-authenticated user profiles
215+
const isExtendedProfileFieldsVisible = isBlockVisible(
216+
extendedProfileFields.length > 0 && this.isAuthenticatedUserProfile(),
217+
);
212218

213219
return (
214220
<div className="container-fluid">
@@ -276,6 +282,13 @@ class ProfilePage extends React.Component {
276282
{...commonFormProps}
277283
/>
278284
)}
285+
{isExtendedProfileFieldsVisible && (
286+
<ExtendedProfileFields
287+
extendedProfileFields={extendedProfileFields}
288+
formId="extendedProfile"
289+
{...commonFormProps}
290+
/>
291+
)}
279292
{isSocialLinksBLockVisible && (
280293
<SocialLinks
281294
socialLinks={socialLinks}
@@ -287,13 +300,6 @@ class ProfilePage extends React.Component {
287300
)}
288301
</div>
289302
<div className="pt-md-3 col-md-8 col-lg-7 offset-lg-1">
290-
{this.props.extendedProfileFields.length > 0 && (
291-
<ExtendedProfileFields
292-
extendedProfileFields={this.props.extendedProfileFields}
293-
formId="extendedProfile"
294-
{...commonFormProps}
295-
/>
296-
)}
297303
{!this.isYOBDisabled() && this.renderAgeMessage()}
298304
{isBioBlockVisible && (
299305
<Bio

src/profile/data/reducers.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,23 @@ const profilePage = (state = initialState, action = {}) => {
128128
errors: {},
129129
};
130130

131-
case UPDATE_DRAFT:
131+
case UPDATE_DRAFT: {
132+
const { name, value } = action.payload;
133+
const updatedDrafts = { ...state.drafts, [name]: value };
134+
135+
if (name === 'visibilityExtendedProfile') {
136+
const visibilityExtendedProfile = {
137+
...state.preferences.visibilityExtendedProfile,
138+
...value,
139+
};
140+
updatedDrafts[name] = visibilityExtendedProfile;
141+
}
142+
132143
return {
133144
...state,
134-
drafts: { ...state.drafts, [action.payload.name]: action.payload.value },
145+
drafts: updatedDrafts,
135146
};
147+
}
136148

137149
case RESET_DRAFTS:
138150
return {

src/profile/data/selectors.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,17 @@ export const editableFormModeSelector = createSelector(
3232
formIdSelector,
3333
currentlyEditingFieldSelector,
3434
(account, isAuthenticatedUserProfile, certificates, formId, currentlyEditingField) => {
35+
const [parentPropKey, fieldName] = formId.split('/');
3536
// If the prop doesn't exist, that means it hasn't been set (for the current user's profile)
3637
// or is being hidden from us (for other users' profiles)
3738
let propExists = account[formId] != null && account[formId].length > 0;
3839
propExists = formId === 'certificates' ? certificates.length > 0 : propExists; // overwrite for certificates
40+
41+
// Overwrite for extended profile fields
42+
propExists = formId.includes('extendedProfile') ? (
43+
account[parentPropKey]?.some((field) => field.fieldName === fieldName)
44+
) : propExists;
45+
3946
// If this isn't the current user's profile
4047
if (!isAuthenticatedUserProfile) {
4148
return 'static';
Lines changed: 64 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,86 @@
1-
import React, { useState } from 'react';
1+
import React from 'react';
22
import PropTypes from 'prop-types';
3-
import { connect, useSelector } from 'react-redux';
4-
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
3+
import { useSelector } from 'react-redux';
4+
import { intlShape } from '@edx/frontend-platform/i18n';
55

66
// Components
7-
import EditableItemHeader from './elements/EditableItemHeader';
87
import SwitchContent from './elements/SwitchContent';
98
import SelectField from './elements/SelectField';
10-
import { editableFormSelector } from '../data/selectors';
119
import TextField from './elements/TextField';
10+
import CheckboxField from './elements/CheckboxField';
11+
import { moveCheckboxFieldsToEnd } from '../utils';
1212

1313
const ExtendedProfileFields = (props) => {
1414
const {
15-
extendedProfileFields, formId, changeHandler, submitHandler, closeHandler, openHandler, editMode,
15+
extendedProfileFields, formId, changeHandler, submitHandler, closeHandler, openHandler,
1616
} = props;
1717

1818
const draftProfile = useSelector((state) => state.profilePage.drafts);
1919
const extendedProfile = useSelector((state) => state.profilePage.account.extendedProfile);
2020
const visibilityExtendedProfile = useSelector((state) => state.profilePage.preferences.visibilityExtendedProfile);
21-
const [currentEditingField, setCurrentEditingField] = useState(null);
22-
23-
const handleOpenEdit = (form) => {
24-
const [parentFormId, fieldId] = form.split('/');
25-
26-
openHandler(parentFormId);
27-
setCurrentEditingField(fieldId);
28-
};
29-
30-
const handleCloseEdit = () => {
31-
closeHandler(null);
32-
setCurrentEditingField(null);
33-
};
3421

3522
const handleChangeExtendedField = (name, value) => {
36-
const [parentFormId, fieldName] = name.split('/');
37-
if (name.includes('visibility')) {
38-
changeHandler(parentFormId, { [fieldName]: value });
39-
} else {
40-
const fieldIndex = extendedProfile.findIndex((field) => field.fieldName === fieldName);
41-
const newFields = extendedProfile.map(
42-
(extendedField, index) => (index === fieldIndex ? { ...extendedField, fieldValue: value } : extendedField),
23+
const [parentPropKey, fieldName] = name.split('/');
24+
const isVisibilityChange = name.includes('visibility');
25+
const fields = isVisibilityChange ? visibilityExtendedProfile : extendedProfile;
26+
const newFields = isVisibilityChange
27+
? {
28+
...fields,
29+
[fieldName]: value,
30+
}
31+
: fields.map(
32+
(extendedField) => (
33+
extendedField.fieldName === fieldName ? { ...extendedField, fieldValue: value } : extendedField
34+
),
4335
);
44-
changeHandler(parentFormId, newFields);
45-
}
36+
changeHandler(parentPropKey, newFields);
4637
};
4738

4839
const handleSubmitExtendedField = (form) => {
49-
const [parentFormId] = form.split('/');
50-
submitHandler(parentFormId);
51-
setCurrentEditingField(null);
40+
submitHandler(form);
5241
};
5342

54-
return extendedProfileFields?.map((field) => (
55-
<SwitchContent
56-
className="mb-5"
57-
expression={field.type}
58-
cases={{
59-
checkbox: (
60-
<>
61-
<EditableItemHeader content={field.label} />
62-
<p data-hj-suppress className="lead">
63-
{field.default}
64-
</p>
65-
</>
66-
),
67-
text: (
68-
<TextField
69-
formId={`${formId}/${field.name}`}
70-
editMode={currentEditingField === field.name ? editMode : 'editable'}
71-
changeHandler={handleChangeExtendedField}
72-
submitHandler={handleSubmitExtendedField}
73-
closeHandler={handleCloseEdit}
74-
openHandler={handleOpenEdit}
75-
{...field}
76-
/>
77-
),
78-
select: (
79-
<SelectField
80-
formId={`${formId}/${field.name}`}
81-
editMode={currentEditingField === field.name ? editMode : 'editable'}
82-
changeHandler={handleChangeExtendedField}
83-
submitHandler={handleSubmitExtendedField}
84-
closeHandler={handleCloseEdit}
85-
openHandler={handleOpenEdit}
86-
{...field}
87-
value={draftProfile?.extendedProfile?.find(
88-
(extendedField) => extendedField.fieldName === field.name,
89-
)?.fieldValue ?? field.value}
90-
visibility={
91-
draftProfile?.visibilityExtendedProfile?.[field.name] ?? visibilityExtendedProfile?.[field.name]
92-
}
43+
return (
44+
<div className="">
45+
{extendedProfileFields.sort(moveCheckboxFieldsToEnd)?.map((field) => {
46+
const value = draftProfile?.extendedProfile?.find(
47+
(extendedField) => extendedField.fieldName === field.name,
48+
)?.fieldValue ?? field.value;
49+
50+
const visibility = draftProfile?.visibilityExtendedProfile?.[field.name]
51+
?? visibilityExtendedProfile?.[field.name];
52+
53+
const commonProps = {
54+
...field,
55+
formId: `${formId}/${field.name}`,
56+
changeHandler: handleChangeExtendedField,
57+
submitHandler: handleSubmitExtendedField,
58+
closeHandler,
59+
openHandler,
60+
value,
61+
visibility,
62+
};
63+
64+
return (
65+
<SwitchContent
66+
className="mb-5"
67+
expression={field.type}
68+
cases={{
69+
checkbox: (
70+
<CheckboxField {...commonProps} />
71+
),
72+
text: (
73+
<TextField {...commonProps} />
74+
),
75+
select: (
76+
<SelectField {...commonProps} />
77+
),
78+
}}
9379
/>
94-
),
95-
}}
96-
/>
97-
));
80+
);
81+
})}
82+
</div>
83+
);
9884
};
9985

10086
ExtendedProfileFields.propTypes = {
@@ -126,11 +112,14 @@ ExtendedProfileFields.propTypes = {
126112
closeHandler: PropTypes.func.isRequired,
127113
openHandler: PropTypes.func.isRequired,
128114

115+
// Props
116+
isAuthenticatedUserProfile: PropTypes.bool.isRequired,
117+
129118
// i18n
130119
intl: intlShape.isRequired,
131120
};
132121

133122
ExtendedProfileFields.defaultProps = {
134123
};
135124

136-
export default connect(editableFormSelector, {})(injectIntl(ExtendedProfileFields));
125+
export default ExtendedProfileFields;

0 commit comments

Comments
 (0)