Skip to content

Commit 1e2a01e

Browse files
committed
feat: add extended profile fields functionality with context and form components
1 parent 981dccf commit 1e2a01e

File tree

15 files changed

+879
-0
lines changed

15 files changed

+879
-0
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
@@ -44,6 +44,7 @@
4444
"@redux-devtools/extension": "3.3.0",
4545
"classnames": "2.5.1",
4646
"core-js": "3.42.0",
47+
"dompurify": "^3.2.6",
4748
"history": "5.3.0",
4849
"lodash.camelcase": "4.3.0",
4950
"lodash.get": "4.4.2",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { PluginSlot } from '@openedx/frontend-plugin-framework';
2+
import ExtendedProfileFields from '../../profile/extended-profile';
3+
4+
const ExtendedProfileFieldsSlot = () => (
5+
<PluginSlot
6+
id="org.openedx.frontend.profile.extended_profile_fields.v1"
7+
idAliases={['extended_profile_fields_plugin']}
8+
>
9+
<ExtendedProfileFields />
10+
</PluginSlot>
11+
);
12+
13+
export default ExtendedProfileFieldsSlot;

src/profile/ProfilePage.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { AppContext } from '@edx/frontend-platform/react';
88
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
99
import { Alert, Hyperlink } from '@openedx/paragon';
1010

11+
import ExtendedProfileFieldsSlot from '../plugin-slots/ExtendedProfileFieldsSlot';
12+
1113
// Actions
1214
import {
1315
fetchProfile,
@@ -280,6 +282,9 @@ class ProfilePage extends React.Component {
280282
{...commonFormProps}
281283
/>
282284
)}
285+
286+
<ExtendedProfileFieldsSlot />
287+
283288
{isSocialLinksBLockVisible && (
284289
<SocialLinks
285290
socialLinks={socialLinks}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { createContext } from 'react';
2+
3+
const ExtendedProfileFieldsContext = createContext();
4+
5+
export default ExtendedProfileFieldsContext;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useEffect, useState, useMemo } from 'react';
2+
import PropTypes from 'prop-types';
3+
import ExtendedProfileFieldsContext from './ExtendedProfileContext';
4+
import { getExtendedProfileFields } from './data/service';
5+
import { patchProfile } from '../data/services';
6+
7+
const ExtendedProfileFieldsProvider = ({ children }) => {
8+
const [extendedProfileFields, setExtendedProfileFields] = useState();
9+
const [editMode, setEditMode] = useState('editable');
10+
const [editingInput, setEditingInput] = useState(null);
11+
const [saveState, setSaveState] = useState('default');
12+
13+
useEffect(() => {
14+
(async () => {
15+
const res = await getExtendedProfileFields();
16+
setExtendedProfileFields(res.fields);
17+
})();
18+
}, []);
19+
20+
const handleChangeFormMode = (mode, fieldName = null) => {
21+
setEditMode(mode);
22+
setEditingInput(fieldName);
23+
};
24+
25+
const handleSaveExtendedProfile = async (username, params) => {
26+
await patchProfile(username, params);
27+
};
28+
29+
const handleResetFormEdition = () => {
30+
setEditMode('editable');
31+
setEditingInput(null);
32+
setSaveState('default');
33+
};
34+
35+
const handleChangeSaveState = (state) => {
36+
setSaveState(state);
37+
};
38+
39+
const value = useMemo(() => ({
40+
editMode,
41+
extendedProfileFields,
42+
editingInput,
43+
saveState,
44+
handleChangeFormMode,
45+
handleResetFormEdition,
46+
handleSaveExtendedProfile,
47+
handleChangeSaveState,
48+
}), [editMode, editingInput, extendedProfileFields, saveState]);
49+
50+
return (
51+
<ExtendedProfileFieldsContext.Provider value={value}>
52+
{children}
53+
</ExtendedProfileFieldsContext.Provider>
54+
);
55+
};
56+
57+
ExtendedProfileFieldsProvider.propTypes = {
58+
children: PropTypes.node || PropTypes.arrayOf(PropTypes.node),
59+
};
60+
61+
export default ExtendedProfileFieldsProvider;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const FORM_MODE = {
2+
EDITING: 'editing',
3+
EDITABLE: 'editable',
4+
EMPTY: 'empty',
5+
STATIC: 'static',
6+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { getConfig } from '@edx/frontend-platform';
2+
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
3+
4+
export async function getExtendedProfileFields() {
5+
const url = `${getConfig().LMS_BASE_URL}/user_api/v1/account/registration/`;
6+
7+
const { data } = await getAuthenticatedHttpClient()
8+
.get(
9+
url,
10+
)
11+
.catch((e) => {
12+
throw (e);
13+
});
14+
15+
return { fields: data.fields };
16+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import ExtendedProfileFieldsProvider from './ExtendedProfileProvider';
2+
import ProfileFields from './profile-fields';
3+
4+
const ExtendedProfileFields = () => (
5+
<ExtendedProfileFieldsProvider>
6+
<ProfileFields />
7+
</ExtendedProfileFieldsProvider>
8+
);
9+
10+
export default ExtendedProfileFields;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { defineMessages } from '@edx/frontend-platform/i18n';
2+
3+
const messages = defineMessages({
4+
'profile.formcontrols.who.can.see': {
5+
id: 'profile.formcontrols.who.can.see',
6+
defaultMessage: 'Who can see this:',
7+
description: 'What users can see this area?',
8+
},
9+
'profile.formcontrols.button.cancel': {
10+
id: 'profile.formcontrols.button.cancel',
11+
defaultMessage: 'Cancel',
12+
description: 'A button label',
13+
},
14+
'profile.formcontrols.button.save': {
15+
id: 'profile.formcontrols.button.save',
16+
defaultMessage: 'Save',
17+
description: 'A button label',
18+
},
19+
'profile.formcontrols.button.saving': {
20+
id: 'profile.formcontrols.button.saving',
21+
defaultMessage: 'Saving',
22+
description: 'A button label',
23+
},
24+
'profile.formcontrols.button.saved': {
25+
id: 'profile.formcontrols.button.saved',
26+
defaultMessage: 'Saved',
27+
description: 'A button label',
28+
},
29+
'profile.formcontrols.error.min_length': {
30+
id: 'profile.formcontrols.error.min_length',
31+
defaultMessage: 'This field must be at least {minLength} characters long.',
32+
description: 'Error message when the field is too short',
33+
},
34+
'profile.formcontrols.error.max_length': {
35+
id: 'profile.formcontrols.error.max_length',
36+
defaultMessage: 'This field must be at most {maxLength} characters long.',
37+
description: 'Error message when the field is too long',
38+
},
39+
});
40+
41+
export default messages;

0 commit comments

Comments
 (0)