Skip to content

Commit 4842779

Browse files
authored
Merge pull request #7 from eduNEXT/bc/fix-errors-displaying
feat: intercept error to display 'em in the UI
2 parents 25efd99 + da436a3 commit 4842779

File tree

6 files changed

+135
-7
lines changed

6 files changed

+135
-7
lines changed

package-lock.json

Lines changed: 60 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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636
"@edx/frontend-platform": "^7.0.0 || ^8.0.0",
3737
"@openedx/frontend-plugin-framework": "^1.0.0",
3838
"dompurify": "^3.2.6",
39-
"prop-types": "15.8.1"
39+
"prop-types": "15.8.1",
40+
"@fortawesome/free-solid-svg-icons": "^6.0.0",
41+
"@fortawesome/react-fontawesome": "^0.2.0"
4042
},
4143
"peerDependencies": {
4244
"@edx/frontend-platform": "^7.0.0 || ^8.0.0",

src/extended-profile/ExtendedProfileProvider.jsx

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useEffect } from 'react';
22
import PropTypes from 'prop-types';
3+
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
34
import ExtendedProfileFieldsContext from './ExtendedProfileContext';
45
import { getExtendedProfileFieldsV2 } from './data/service';
56

@@ -10,10 +11,12 @@ import { FORM_MODE } from './constants';
1011
const ExtendedProfileFieldsProvider = ({
1112
patchProfile, profileFieldErrors, components, children,
1213
}) => {
14+
const editingInputRef = React.useRef(null);
1315
const [extendedProfileFields, setExtendedProfileFields] = React.useState();
1416
const [editMode, setEditMode] = React.useState(FORM_MODE.EDITABLE);
1517
const [editingInput, setEditingInput] = React.useState(null);
1618
const [saveState, setSaveState] = React.useState('default');
19+
const [localErrors, setLocalErrors] = React.useState({});
1720

1821
React.useEffect(() => {
1922
(async () => {
@@ -23,20 +26,25 @@ const ExtendedProfileFieldsProvider = ({
2326
}, []);
2427

2528
useEffect(() => {
26-
if (Object.keys(profileFieldErrors).length) {
29+
const filteredKeys = Object.keys(profileFieldErrors)
30+
.filter(key => key !== 'extended_profile');
31+
32+
if (filteredKeys.length > 0 || Object.keys(localErrors).length) {
2733
setSaveState('error');
2834
setEditMode(FORM_MODE.EDITING);
29-
setEditingInput(Object.keys(profileFieldErrors)[0]);
35+
setEditingInput(filteredKeys?.[0] ?? localErrors ? editingInputRef.current : null);
3036
} else {
3137
setSaveState('default');
3238
setEditMode(FORM_MODE.EDITABLE);
3339
}
3440
// eslint-disable-next-line react-hooks/exhaustive-deps
35-
}, [Object.keys(profileFieldErrors).length]);
41+
}, [Object.keys(profileFieldErrors).length, Object.keys(localErrors).length]);
3642

3743
const handleChangeFormMode = (mode, fieldName = null) => {
3844
setEditMode(mode);
3945
setEditingInput(fieldName);
46+
setSaveState('default');
47+
editingInputRef.current = fieldName;
4048
};
4149

4250
const handleSaveExtendedProfile = async ({ username, params }) => {
@@ -54,13 +62,58 @@ const ExtendedProfileFieldsProvider = ({
5462
setSaveState(state);
5563
};
5664

65+
useEffect(() => {
66+
const httpClient = getAuthenticatedHttpClient?.();
67+
68+
const interceptor = httpClient.interceptors.response.use(
69+
res => {
70+
if (res.config.url.includes('user/v1/accounts/')) {
71+
setLocalErrors((prevState) => {
72+
const fieldName = editingInputRef.current;
73+
74+
if (!fieldName) { return prevState; }
75+
76+
const { [fieldName]: _, ...rest } = prevState;
77+
return rest;
78+
});
79+
editingInputRef.current = null;
80+
handleResetFormEdition();
81+
}
82+
return res;
83+
},
84+
error => {
85+
if (
86+
error.response?.status === 400
87+
&& error.response.config.url.includes('user/v1/accounts/')
88+
) {
89+
const fieldErrors = error.response.data?.field_errors;
90+
91+
if (fieldErrors) {
92+
setLocalErrors({ [editingInputRef.current]: fieldErrors.extended_profile.user_message });
93+
}
94+
}
95+
96+
return Promise.reject(error);
97+
},
98+
);
99+
100+
return () => {
101+
httpClient.interceptors.response.eject(interceptor);
102+
};
103+
}, []);
104+
105+
const mergedErrors = {
106+
...profileFieldErrors,
107+
...localErrors,
108+
};
109+
57110
const value = React.useMemo(
58111
() => ({
59112
editMode,
60113
extendedProfileFields,
61114
editingInput,
62115
saveState,
63-
profileFieldErrors,
116+
profileFieldErrors: mergedErrors,
64117
handleChangeFormMode,
65118
handleResetFormEdition,
66119
handleSaveExtendedProfile,

src/extended-profile/account-fields/elements/BaseField.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ const BaseField = ({
7474
return renderEmptyLabel();
7575
}
7676

77-
// If it is a select field, we want to display the option label instead of the value
78-
const fieldOption = fieldOptions?.find((option) => option.value === rawValue);
77+
// If it is a select field, we want to display the option label instead of the value
78+
const fieldOption = fieldOptions?.find((option) => option.value === rawValue);
7979
return fieldOption ? fieldOption.name : rawValue;
8080
};
8181

@@ -170,6 +170,7 @@ BaseField.propTypes = {
170170
label: PropTypes.string.isRequired,
171171
}),
172172
),
173+
savingErrors: PropTypes.objectOf(PropTypes.string),
173174
};
174175

175176
BaseField.defaultProps = {

src/extended-profile/profile-fields/elements/BaseField.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ BaseField.propTypes = {
167167
label: PropTypes.string.isRequired,
168168
}),
169169
),
170+
savingErrors: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.string)),
170171
};
171172

172173
BaseField.defaultProps = {

src/extended-profile/profile-fields/index.jsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useContext } from 'react';
22
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
3+
import PropTypes from 'prop-types';
34

45
import ExtendedProfileFieldsContext from '../ExtendedProfileContext';
56

@@ -80,4 +81,14 @@ const ProfileFields = ({ fetchProfile, extendedProfileValues }) => {
8081
);
8182
};
8283

84+
ProfileFields.propTypes = {
85+
fetchProfile: PropTypes.func.isRequired,
86+
extendedProfileValues: PropTypes.arrayOf(
87+
PropTypes.shape({
88+
fieldName: PropTypes.string.isRequired,
89+
fieldValue: PropTypes.string,
90+
}),
91+
).isRequired,
92+
};
93+
8394
export default ProfileFields;

0 commit comments

Comments
 (0)