Skip to content

Commit f2dfd8c

Browse files
Muhammad Faraz  MaqsoodMuhammad Faraz  Maqsood
authored andcommitted
feat: add error modal incase of unsaved changes
This commit covers TNL2-316 & TNL2-317, and - adds error modal incase of unsaved changes after update API is called. Which include custom Alert to show the modal. - It also include edge case handling for different cases and also updated the unit tests. - It also show an alert in case of backend error e.g. 500, 400, 401 etc - test coverage.
1 parent b69f7ec commit f2dfd8c

18 files changed

+857
-131
lines changed
Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,83 @@
1+
import { useEffect, useState, useContext } from 'react';
12
import { useLocation } from 'react-router-dom';
23
import { useIntl } from '@edx/frontend-platform/i18n';
34

5+
import CourseUpdatesErrorsAlert from './CourseUpdatesErrorsAlert';
46
import AlertList from '../userMessages/AlertList';
7+
import UserMessagesContext from '../userMessages/UserMessagesContext';
58
import CourseTeamPageUserSearch from '../users/UserPage';
69
import messages from './messages';
710

811
export default function CourseTeamManagementIndexPage() {
912
const location = useLocation();
1013
const intl = useIntl();
14+
const [showModal, setShowModal] = useState(false);
15+
const [apiErrors, setApiErrors] = useState('');
16+
17+
// This is required to keep custom checkbox column's state correct
18+
const [isAlertDismissed, setIsAlertDismissed] = useState(false);
19+
const { add, clear } = useContext(UserMessagesContext);
20+
const [courseUpdateErrors, setCourseUpdateErrors] = useState(
21+
{
22+
email: '',
23+
username: '',
24+
success: false,
25+
errors: {
26+
newlyCheckedWithRoleErrors: [],
27+
uncheckedWithRoleErrors: [],
28+
roleChangedRowsErrors: [],
29+
},
30+
},
31+
);
32+
const hasCourseUpdateErrors = Object.values(courseUpdateErrors.errors).some(arr => arr.length > 0);
33+
useEffect(() => {
34+
if (apiErrors) {
35+
clear('courseTeamManagementApiErrors');
36+
apiErrors.error.forEach(error => add(error));
37+
}
38+
}, [apiErrors]);
39+
1140
return (
12-
<div className="container-fluid">
41+
<div className="container-fluid course-updates-errors-alert">
1342
<AlertList topic="general" className="mb-3 mt-5" />
43+
{apiErrors && <AlertList topic="courseTeamManagementApiErrors" className="mb-3 mt-5" isDismissed={isAlertDismissed} setIsDismissed={setIsAlertDismissed} />}
44+
{hasCourseUpdateErrors && (
45+
<CourseUpdatesErrorsAlert
46+
errors={courseUpdateErrors.errors}
47+
email={courseUpdateErrors.email}
48+
username={courseUpdateErrors.username}
49+
onDismiss={() => {
50+
setCourseUpdateErrors({
51+
success: false,
52+
errors: {
53+
newlyCheckedWithRoleErrors: [],
54+
uncheckedWithRoleErrors: [],
55+
roleChangedRowsErrors: [],
56+
},
57+
});
58+
setShowModal(false);
59+
}}
60+
showModal={showModal}
61+
setShowModal={setShowModal}
62+
63+
/>
64+
)}
1465
<section className="course-team-management-header">
1566
<h2 className="font-weight-bold">
1667
{intl.formatMessage(messages.pageTitle)}
1768
</h2>
1869
</section>
19-
<CourseTeamPageUserSearch location={location} isOnCourseTeamPage />
70+
<CourseTeamPageUserSearch
71+
location={location}
72+
isOnCourseTeamPage
73+
courseUpdateErrors={courseUpdateErrors}
74+
setCourseUpdateErrors={setCourseUpdateErrors}
75+
showErrorsModal={showModal}
76+
apiErrors={apiErrors}
77+
setApiErrors={setApiErrors}
78+
isAlertDismissed={isAlertDismissed}
79+
setIsAlertDismissed={setIsAlertDismissed}
80+
/>
2081
</div>
2182
);
2283
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { Suspense, useRef } from 'react';
2+
import { Button } from '@openedx/paragon';
3+
import { useIntl } from '@edx/frontend-platform/i18n';
4+
import PropTypes from 'prop-types';
5+
6+
import Alert from '../userMessages/Alert';
7+
import CoursesChangesModal from './CoursesChangesModal';
8+
import messages from './messages';
9+
10+
export default function CourseUpdatesErrorsAlert({
11+
errors, email, username, onDismiss, showModal, setShowModal,
12+
}) {
13+
const intl = useIntl();
14+
const saveButtonRef = useRef();
15+
return (
16+
<div className="mb-3 mt-5">
17+
<Suspense key="CourseUpdatesErrorsAlert" fallback={null}>
18+
<Alert
19+
type="danger"
20+
dismissible
21+
onDismiss={onDismiss}
22+
>
23+
<span className="d-flex align-items-center">
24+
<span>{intl.formatMessage(messages.courseUpdatesErrorsAlertMessage)}</span>{' '}
25+
<Button variant="link" onClick={() => setShowModal(true)} className="p-0 ml-1">
26+
{intl.formatMessage(messages.courseUpdatesErrorsAlertViewDetailsMessage)}
27+
</Button>
28+
</span>
29+
</Alert>
30+
</Suspense>
31+
<CoursesChangesModal
32+
changedData={errors}
33+
isOpen={showModal}
34+
onCancel={() => setShowModal(false)}
35+
username={username}
36+
email={email}
37+
positionRef={saveButtonRef}
38+
hasError
39+
/>
40+
</div>
41+
);
42+
}
43+
44+
CourseUpdatesErrorsAlert.propTypes = {
45+
email: PropTypes.string,
46+
username: PropTypes.string,
47+
errors: PropTypes.shape({
48+
newlyCheckedWithRoleErrors: PropTypes.shape({
49+
runId: PropTypes.string,
50+
role: PropTypes.string,
51+
courseName: PropTypes.string,
52+
number: PropTypes.string,
53+
courseId: PropTypes.string,
54+
error: PropTypes.string,
55+
}),
56+
uncheckedWithRoleErrors: PropTypes.shape({
57+
runId: PropTypes.string,
58+
role: PropTypes.string,
59+
courseName: PropTypes.string,
60+
number: PropTypes.string,
61+
courseId: PropTypes.string,
62+
error: PropTypes.string,
63+
}),
64+
roleChangedRowsErrors: PropTypes.shape({
65+
runId: PropTypes.string,
66+
from: PropTypes.string,
67+
to: PropTypes.string,
68+
courseName: PropTypes.string,
69+
number: PropTypes.string,
70+
courseId: PropTypes.string,
71+
error: PropTypes.string,
72+
}),
73+
}),
74+
onDismiss: PropTypes.func.isRequired,
75+
showModal: PropTypes.bool,
76+
setShowModal: PropTypes.func,
77+
};

0 commit comments

Comments
 (0)