From 73d18bfab46dcd6f8ea3a2d6d04815ffae21f2b3 Mon Sep 17 00:00:00 2001 From: Vladislav Keblysh Date: Tue, 1 Aug 2023 17:01:56 +0300 Subject: [PATCH 1/2] fix [2u-283] add alert --- src/course-team/CourseTeam.jsx | 192 ++++++++++-------- src/course-team/data/selectors.js | 1 + src/course-team/data/slice.js | 5 + src/course-team/data/thunk.js | 20 +- src/course-team/hooks.jsx | 37 +++- src/course-team/info-modal/InfoModal.jsx | 2 +- src/course-team/utils.js | 3 +- .../internet-connection-alert/index.jsx | 8 +- 8 files changed, 169 insertions(+), 99 deletions(-) diff --git a/src/course-team/CourseTeam.jsx b/src/course-team/CourseTeam.jsx index 92e101fb0d..dfdb5278ee 100644 --- a/src/course-team/CourseTeam.jsx +++ b/src/course-team/CourseTeam.jsx @@ -8,16 +8,17 @@ import { } from '@edx/paragon'; import { Add as IconAdd } from '@edx/paragon/icons'; +import InternetConnectionAlert from '../generic/internet-connection-alert'; import SubHeader from '../generic/sub-header/SubHeader'; +import { USER_ROLES } from '../constants'; import messages from './messages'; import CourseTeamSideBar from './course-team-sidebar/CourseTeamSidebar'; import AddUserForm from './add-user-form/AddUserForm'; import AddTeamMember from './add-team-member/AddTeamMember'; import CourseTeamMember from './course-team-member/CourseTeamMember'; -import { MODAL_TYPES } from './constants'; -import { USER_ROLES } from '../constants'; import InfoModal from './info-modal/InfoModal'; import { useCourseTeam } from './hooks'; +import { MODAL_TYPES } from './constants'; const CourseTeam = ({ courseId }) => { const intl = useIntl(); @@ -31,109 +32,124 @@ const CourseTeam = ({ courseId }) => { currentUserEmail, isSingleAdmin, isFormVisible, + isQueryPending, isAllowActions, isInfoModalOpen, isOwnershipHint, isShowAddTeamMember, isShowInitialSidebar, isShowUserFilledSidebar, + isInternetConnectionAlertShow, + isInternetConnectionAlertFailed, openForm, hideForm, closeInfoModal, handleAddUserSubmit, handleOpenInfoModal, - handleChangeRoleUserSubmit, handleDeleteUserSubmit, + handleChangeRoleUserSubmit, + handleInternetConnectionFailed, } = useCourseTeam({ intl, courseId }); return ( - -
- - -
-
- - {intl.formatMessage(messages.addNewMemberButton)} - - )} - /> -
-
- {isFormVisible && ( - - )} - {courseTeamUsers.length ? courseTeamUsers.map(({ username, role, email }) => ( - handleOpenInfoModal(MODAL_TYPES.delete, email)} - /> - )) : null} - {isShowAddTeamMember && ( - + <> + +
+ + +
+
+ + {intl.formatMessage(messages.addNewMemberButton)} + )} -
- {isShowInitialSidebar && ( -
- -
- )} - -
-
-
-
- - {isShowUserFilledSidebar && ( - - )} - -
-
-
+
+
+ {isFormVisible && ( + + )} + {courseTeamUsers.length ? courseTeamUsers.map(({ username, role, email }) => ( + handleOpenInfoModal(MODAL_TYPES.delete, email)} + /> + )) : null} + {isShowAddTeamMember && ( + + )} +
+ {isShowInitialSidebar && ( +
+ +
+ )} + +
+ + + + + {isShowUserFilledSidebar && ( + + )} + + + + +
+ {isInternetConnectionAlertShow && ( + + )} +
+ ); }; diff --git a/src/course-team/data/selectors.js b/src/course-team/data/selectors.js index b430b18d72..7b59051f5e 100644 --- a/src/course-team/data/selectors.js +++ b/src/course-team/data/selectors.js @@ -2,3 +2,4 @@ export const getCourseTeamUsers = (state) => state.courseTeam.users; export const getErrorEmail = (state) => state.courseTeam.errorEmail; export const getIsAllowActions = (state) => state.courseTeam.allow_actions; export const getIsOwnershipHint = (state) => state.courseTeam.show_transfer_ownership_hint; +export const getSavingStatus = (state) => state.courseTeam.savingStatus; diff --git a/src/course-team/data/slice.js b/src/course-team/data/slice.js index 72eb7fb17f..1281e8d934 100644 --- a/src/course-team/data/slice.js +++ b/src/course-team/data/slice.js @@ -4,6 +4,7 @@ import { createSlice } from '@reduxjs/toolkit'; const slice = createSlice({ name: 'courseTeam', initialState: { + savingStatus: '', users: [], show_transfer_ownership_hint: false, allow_actions: false, @@ -18,6 +19,9 @@ const slice = createSlice({ deleteCourseTeamUser: (state, { payload }) => { state.users = state.users.filter((user) => user.email !== payload); }, + updateSavingStatus: (state, { payload }) => { + state.savingStatus = payload.status; + }, setErrorEmail: (state, { payload }) => { state.errorEmail = payload; }, @@ -27,6 +31,7 @@ const slice = createSlice({ export const { fetchCourseTeamSuccess, deleteCourseTeamUser, + updateSavingStatus, setErrorEmail, } = slice.actions; diff --git a/src/course-team/data/thunk.js b/src/course-team/data/thunk.js index 7452c24186..77e7062518 100644 --- a/src/course-team/data/thunk.js +++ b/src/course-team/data/thunk.js @@ -1,3 +1,5 @@ +import { RequestStatus } from '../../data/constants'; +import { getErrorEmailFromMessage } from '../utils'; import { getCourseTeam, deleteTeamUser, @@ -7,9 +9,9 @@ import { import { fetchCourseTeamSuccess, deleteCourseTeamUser, + updateSavingStatus, setErrorEmail, } from './slice'; -import { getErrorEmailFromMessage } from '../utils'; export function fetchCourseTeamQuery(courseId) { return async (dispatch) => { @@ -25,13 +27,19 @@ export function fetchCourseTeamQuery(courseId) { export function createCourseTeamQuery(courseId, email) { return async (dispatch) => { + dispatch(updateSavingStatus({ status: RequestStatus.IN_PROGRESS })); + try { await createTeamUser(courseId, email); const courseTeam = await getCourseTeam(courseId); dispatch(fetchCourseTeamSuccess(courseTeam)); + + dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL })); return true; } catch ({ message }) { dispatch(setErrorEmail(getErrorEmailFromMessage(message))); + + dispatch(updateSavingStatus({ status: RequestStatus.FAILED })); return false; } }; @@ -39,12 +47,17 @@ export function createCourseTeamQuery(courseId, email) { export function changeRoleTeamUserQuery(courseId, email, role) { return async (dispatch) => { + dispatch(updateSavingStatus({ status: RequestStatus.IN_PROGRESS })); + try { await changeRoleTeamUser(courseId, email, role); const courseTeam = await getCourseTeam(courseId); dispatch(fetchCourseTeamSuccess(courseTeam)); + + dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL })); return true; } catch ({ message }) { + dispatch(updateSavingStatus({ status: RequestStatus.FAILED })); return false; } }; @@ -52,11 +65,16 @@ export function changeRoleTeamUserQuery(courseId, email, role) { export function deleteCourseTeamQuery(courseId, email) { return async (dispatch) => { + dispatch(updateSavingStatus({ status: RequestStatus.IN_PROGRESS })); + try { await deleteTeamUser(courseId, email); dispatch(deleteCourseTeamUser(email)); + + dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL })); return true; } catch (error) { + dispatch(updateSavingStatus({ status: RequestStatus.FAILED })); return false; } }; diff --git a/src/course-team/hooks.jsx b/src/course-team/hooks.jsx index 367b45cec3..58609ce8f0 100644 --- a/src/course-team/hooks.jsx +++ b/src/course-team/hooks.jsx @@ -3,8 +3,9 @@ import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { useEffect, useState } from 'react'; import { useToggle } from '@edx/paragon'; -import { useModel } from '../generic/model-store'; import { USER_ROLES } from '../constants'; +import { RequestStatus } from '../data/constants'; +import { useModel } from '../generic/model-store'; import { changeRoleTeamUserQuery, createCourseTeamQuery, @@ -15,7 +16,7 @@ import { getCourseTeamUsers, getErrorEmail, getIsAllowActions, - getIsOwnershipHint, + getIsOwnershipHint, getSavingStatus, } from './data/selectors'; import { setErrorEmail } from './data/slice'; import { MODAL_TYPES } from './constants'; @@ -31,8 +32,12 @@ const useCourseTeam = ({ courseId }) => { const [isFormVisible, openForm, hideForm] = useToggle(false); const [currentEmail, setCurrentEmail] = useState(''); + const [isInternetConnectionAlertShow, setInternetConnectionAlertShow] = useState(false); + const [isQueryPending, setIsQueryPending] = useState(false); + const courseTeamUsers = useSelector(getCourseTeamUsers); const errorEmail = useSelector(getErrorEmail); + const savingStatus = useSelector(getSavingStatus); const isAllowActions = useSelector(getIsAllowActions); const isOwnershipHint = useSelector(getIsOwnershipHint); const isSingleAdmin = courseTeamUsers.filter((user) => user.role === USER_ROLES.admin).length === 1; @@ -49,11 +54,12 @@ const useCourseTeam = ({ courseId }) => { }; const handleAddUserSubmit = (data) => { + setInternetConnectionAlertShow(true); + setIsQueryPending(true); + const { email } = data; const isUserContains = courseTeamUsers.some((user) => user.email === email); - setCurrentEmail(email); - if (isUserContains) { handleOpenInfoModal(MODAL_TYPES.warning, email); return; @@ -71,18 +77,35 @@ const useCourseTeam = ({ courseId }) => { }; const handleDeleteUserSubmit = () => { + setInternetConnectionAlertShow(true); + setIsQueryPending(true); + dispatch(deleteCourseTeamQuery(courseId, currentEmail)); handleCloseInfoModal(); }; const handleChangeRoleUserSubmit = (email, role) => { + setInternetConnectionAlertShow(true); + setIsQueryPending(true); + dispatch(changeRoleTeamUserQuery(courseId, email, role)); }; + const handleInternetConnectionFailed = () => { + setIsQueryPending(false); + }; + useEffect(() => { dispatch(fetchCourseTeamQuery(courseId)); }, [courseId]); + useEffect(() => { + if (savingStatus === RequestStatus.SUCCESSFUL) { + setIsQueryPending(false); + window.scrollTo({ top: 0, behavior: 'smooth' }); + } + }, [savingStatus]); + return { modalType, errorEmail, @@ -95,6 +118,9 @@ const useCourseTeam = ({ courseId }) => { isAllowActions, isInfoModalOpen, isOwnershipHint, + isQueryPending, + isInternetConnectionAlertShow, + isInternetConnectionAlertFailed: savingStatus === RequestStatus.FAILED, isShowAddTeamMember: courseTeamUsers.length === 1 && isAllowActions, isShowInitialSidebar: !courseTeamUsers.length && !isFormVisible, isShowUserFilledSidebar: Boolean(courseTeamUsers.length) || isFormVisible, @@ -103,8 +129,9 @@ const useCourseTeam = ({ courseId }) => { closeInfoModal, handleAddUserSubmit, handleOpenInfoModal, - handleChangeRoleUserSubmit, handleDeleteUserSubmit, + handleChangeRoleUserSubmit, + handleInternetConnectionFailed, }; }; diff --git a/src/course-team/info-modal/InfoModal.jsx b/src/course-team/info-modal/InfoModal.jsx index 9f9732a83d..e86547a6a4 100644 --- a/src/course-team/info-modal/InfoModal.jsx +++ b/src/course-team/info-modal/InfoModal.jsx @@ -34,7 +34,7 @@ const InfoModal = ({ diff --git a/src/course-team/utils.js b/src/course-team/utils.js index 620eb7d0dc..b7082de7a8 100644 --- a/src/course-team/utils.js +++ b/src/course-team/utils.js @@ -8,7 +8,8 @@ import messages from './info-modal/messages'; */ const getErrorEmailFromMessage = (message) => { const regex = /'([^']+)'/g; - return message.match(regex)[0]; + const match = message.match(regex); + return match ? match[0] : ''; }; /** diff --git a/src/generic/internet-connection-alert/index.jsx b/src/generic/internet-connection-alert/index.jsx index fbf8be5484..596d00e9a4 100644 --- a/src/generic/internet-connection-alert/index.jsx +++ b/src/generic/internet-connection-alert/index.jsx @@ -30,7 +30,9 @@ const InternetConnectionAlert = ({ useEffect(() => { if (isQueryPending) { - onQueryProcessing(); + if (onQueryProcessing) { + onQueryProcessing(); + } setShowAlert(!isOnline); if (!isOnline) { @@ -63,13 +65,13 @@ const InternetConnectionAlert = ({ InternetConnectionAlert.defaultProps = { isQueryPending: false, - + onQueryProcessing: null, }; InternetConnectionAlert.propTypes = { isFailed: PropTypes.bool.isRequired, isQueryPending: PropTypes.bool, - onQueryProcessing: PropTypes.func.isRequired, + onQueryProcessing: PropTypes.func, onInternetConnectionFailed: PropTypes.func.isRequired, }; From 7a317690dd53e10baf95a2925e09504961f22eb0 Mon Sep 17 00:00:00 2001 From: Vladislav Keblysh Date: Tue, 1 Aug 2023 17:03:02 +0300 Subject: [PATCH 2/2] fix [2u-283] fix indents --- src/course-team/hooks.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/course-team/hooks.jsx b/src/course-team/hooks.jsx index 58609ce8f0..8fe0743a6b 100644 --- a/src/course-team/hooks.jsx +++ b/src/course-team/hooks.jsx @@ -31,7 +31,6 @@ const useCourseTeam = ({ courseId }) => { const [isInfoModalOpen, openInfoModal, closeInfoModal] = useToggle(false); const [isFormVisible, openForm, hideForm] = useToggle(false); const [currentEmail, setCurrentEmail] = useState(''); - const [isInternetConnectionAlertShow, setInternetConnectionAlertShow] = useState(false); const [isQueryPending, setIsQueryPending] = useState(false);