diff --git a/src/components/Groups/GroupDetail/GroupDetail.js b/src/components/Groups/GroupDetail/GroupDetail.js index 8a702c145..ea78b406e 100644 --- a/src/components/Groups/GroupDetail/GroupDetail.js +++ b/src/components/Groups/GroupDetail/GroupDetail.js @@ -20,7 +20,7 @@ const GroupDetail = ({ parentGroupId, isPublic = false, childGroups, - adminId, + primaryAdminsIds, ...group }, groups, @@ -131,7 +131,7 @@ const GroupDetail = ({ groupId={id} users={supervisors} isAdmin={isAdmin} - mainAdminId={adminId} + primaryAdminsIds={primaryAdminsIds} isLoaded={supervisors.length === group.supervisors.length} /> @@ -151,7 +151,8 @@ GroupDetail.propTypes = { }), threshold: PropTypes.number, isPublic: PropTypes.bool, - supervisors: PropTypes.array.isRequired + supervisors: PropTypes.array.isRequired, + primaryAdminsIds: PropTypes.array.isRequired }), groups: PropTypes.object.isRequired, publicGroups: ImmutablePropTypes.map.isRequired, diff --git a/src/components/Groups/RemoveGroupAdminButton/RemoveGroupAdminButton.js b/src/components/Groups/RemoveGroupAdminButton/RemoveGroupAdminButton.js new file mode 100644 index 000000000..3a9de31c1 --- /dev/null +++ b/src/components/Groups/RemoveGroupAdminButton/RemoveGroupAdminButton.js @@ -0,0 +1,20 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import Button from '../../widgets/FlatButton'; +import Icon from 'react-fontawesome'; + +const RemoveGroupAdminButton = ({ onClick, ...props }) => + ; + +RemoveGroupAdminButton.propTypes = { + onClick: PropTypes.func.isRequired +}; + +export default RemoveGroupAdminButton; diff --git a/src/components/Groups/RemoveGroupAdminButton/index.js b/src/components/Groups/RemoveGroupAdminButton/index.js new file mode 100644 index 000000000..6b3acf5d1 --- /dev/null +++ b/src/components/Groups/RemoveGroupAdminButton/index.js @@ -0,0 +1 @@ +export default from './RemoveGroupAdminButton'; diff --git a/src/components/Users/SupervisorsList/SupervisorsList.js b/src/components/Users/SupervisorsList/SupervisorsList.js index 5d6631998..efaab793a 100644 --- a/src/components/Users/SupervisorsList/SupervisorsList.js +++ b/src/components/Users/SupervisorsList/SupervisorsList.js @@ -11,7 +11,7 @@ const SupervisorsList = ({ users, isLoaded = true, isAdmin, - mainAdminId + primaryAdminsIds }) => @@ -21,7 +21,7 @@ const SupervisorsList = ({ {...user} groupId={groupId} isAdmin={isAdmin} - mainAdminId={mainAdminId} + primaryAdminsIds={primaryAdminsIds} /> )} @@ -45,7 +45,7 @@ SupervisorsList.propTypes = { groupId: PropTypes.string.isRequired, isLoaded: PropTypes.bool, isAdmin: PropTypes.bool, - mainAdminId: PropTypes.string + primaryAdminsIds: PropTypes.array.isRequired }; export default SupervisorsList; diff --git a/src/components/Users/SupervisorsListItem/SupervisorsListItem.js b/src/components/Users/SupervisorsListItem/SupervisorsListItem.js index d206e0c32..178b64062 100644 --- a/src/components/Users/SupervisorsListItem/SupervisorsListItem.js +++ b/src/components/Users/SupervisorsListItem/SupervisorsListItem.js @@ -3,7 +3,8 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import MakeRemoveSupervisorButtonContainer from '../../../containers/MakeRemoveSupervisorButtonContainer'; import MakeGroupAdminButton from '../../Groups/MakeGroupAdminButton'; -import { makeAdmin } from '../../../redux/modules/groups'; +import RemoveGroupAdminButton from '../../Groups/RemoveGroupAdminButton'; +import { addAdmin, removeAdmin } from '../../../redux/modules/groups'; import { adminsOfGroup } from '../../../redux/selectors/groups'; import UsersNameContainer from '../../../containers/UsersNameContainer'; @@ -13,8 +14,9 @@ const SupervisorsListItem = ({ fullName, avatarUrl, groupId, - makeAdmin, - mainAdminId + addAdmin, + removeAdmin, + primaryAdminsIds }) => {isAdmin && } @@ -37,8 +49,9 @@ SupervisorsListItem.propTypes = { groupId: PropTypes.string.isRequired, fullName: PropTypes.string.isRequired, avatarUrl: PropTypes.string.isRequired, - makeAdmin: PropTypes.func.isRequired, - mainAdminId: PropTypes.string.isRequired + addAdmin: PropTypes.func.isRequired, + removeAdmin: PropTypes.func.isRequired, + primaryAdminsIds: PropTypes.array.isRequired }; const mapStateToProps = (state, { groupId }) => ({ @@ -46,7 +59,8 @@ const mapStateToProps = (state, { groupId }) => ({ }); const mapDispatchToProps = { - makeAdmin + addAdmin, + removeAdmin }; export default connect(mapStateToProps, mapDispatchToProps)( diff --git a/src/redux/modules/groups.js b/src/redux/modules/groups.js index 08c39d10c..43993e9f4 100644 --- a/src/redux/modules/groups.js +++ b/src/redux/modules/groups.js @@ -37,10 +37,14 @@ export const additionalActionTypes = { REMOVE_SUPERVISOR_PENDING: 'recodex/groups/REMOVE_SUPERVISOR_PENDING', REMOVE_SUPERVISOR_FULFILLED: 'recodex/groups/REMOVE_SUPERVISOR_FULFILLED', REMOVE_SUPERVISOR_REJECTED: 'recodex/groups/REMOVE_SUPERVISOR_REJECTED', - MAKE_ADMIN: 'recodex/groups/MAKE_ADMIN', - MAKE_ADMIN_PENDING: 'recodex/groups/MAKE_ADMIN_PENDING', - MAKE_ADMIN_FULFILLED: 'recodex/groups/MAKE_ADMIN_FULFILLED', - MAKE_ADMIN_REJECTED: 'recodex/groups/MAKE_ADMIN_REJECTED' + ADD_ADMIN: 'recodex/groups/ADD_ADMIN', + ADD_ADMIN_PENDING: 'recodex/groups/ADD_ADMIN_PENDING', + ADD_ADMIN_FULFILLED: 'recodex/groups/ADD_ADMIN_FULFILLED', + ADD_ADMIN_REJECTED: 'recodex/groups/ADD_ADMIN_REJECTED', + REMOVE_ADMIN: 'recodex/groups/REMOVE_ADMIN', + REMOVE_ADMIN_PENDING: 'recodex/groups/REMOVE_ADMIN_PENDING', + REMOVE_ADMIN_FULFILLED: 'recodex/groups/REMOVE_ADMIN_FULFILLED', + REMOVE_ADMIN_REJECTED: 'recodex/groups/REMOVE_ADMIN_REJECTED' }; export const loadGroup = actions.pushResource; @@ -52,9 +56,10 @@ export const validateAddGroup = (name, instanceId, parentGroupId = null) => type: 'VALIDATE_ADD_GROUP_DATA', endpoint: '/groups/validate-add-group-data', method: 'POST', - body: parentGroupId === null - ? { name, instanceId } - : { name, instanceId, parentGroupId } + body: + parentGroupId === null + ? { name, instanceId } + : { name, instanceId, parentGroupId } }); export const fetchSubgroups = groupId => @@ -132,10 +137,10 @@ export const removeSupervisor = (groupId, userId) => dispatch => }) ).catch(() => dispatch(addNotification('Cannot remove supervisor.', false))); // @todo: Make translatable -export const makeAdmin = (groupId, userId) => dispatch => +export const addAdmin = (groupId, userId) => dispatch => dispatch( createApiAction({ - type: additionalActionTypes.MAKE_ADMIN, + type: additionalActionTypes.ADD_ADMIN, endpoint: `/groups/${groupId}/admin`, method: 'POST', meta: { groupId, userId }, @@ -147,6 +152,23 @@ export const makeAdmin = (groupId, userId) => dispatch => ) ); // @todo: Make translatable +export const removeAdmin = (groupId, userId) => dispatch => + dispatch( + createApiAction({ + type: additionalActionTypes.REMOVE_ADMIN, + endpoint: `/groups/${groupId}/admin/${userId}`, + method: 'DELETE', + meta: { groupId, userId } + }) + ).catch(() => + dispatch( + addNotification( + 'Cannot remove this person from admins of the group.', + false + ) + ) + ); // @todo: Make translatable + /** * Reducer */ @@ -250,41 +272,60 @@ const reducer = handleActions( supervisors => supervisors.filter(id => id !== userId) ), - [additionalActionTypes.MAKE_ADMIN_PENDING]: ( + [additionalActionTypes.ADD_ADMIN_PENDING]: ( state, - { payload: { userId }, meta: { groupId } } + { payload, meta: { groupId, userId } } ) => - state.updateIn(['resources', groupId, 'data'], group => - group - .set('oldAdminId', group.get('adminId')) - .update('admins', admins => - admins.filter(id => id !== userId).push(userId) - ) - .set('adminId', userId) + state.updateIn( + ['resources', groupId, 'data', 'primaryAdminsIds'], + admins => admins.filter(id => id !== userId).concat([userId]) + ), + + [additionalActionTypes.ADD_ADMIN_FAILED]: ( + state, + { payload, meta: { groupId, userId } } + ) => + state.updateIn( + ['resources', groupId, 'data', 'primaryAdminsIds'], + admins => admins.filter(id => id !== userId) ), - [additionalActionTypes.MAKE_ADMIN_FAILED]: ( + [additionalActionTypes.ADD_ADMIN_FULFILLED]: ( state, - { payload: { userId }, meta: { groupId } } + { payload: { primaryAdminsIds, admins }, meta: { groupId } } ) => state.updateIn(['resources', groupId, 'data'], group => group - .update('admins', admins => - admins.filter(id => id !== group.get('adminId')).push(userId) - ) - .set('adminId', group.get('oldAdminId')) - .remove('oldAdminId') + .set('admins', List(admins)) + .set('primaryAdminsIds', primaryAdminsIds) + ), + + [additionalActionTypes.REMOVE_ADMIN_PENDING]: ( + state, + { payload, meta: { groupId, userId } } + ) => + state.updateIn( + ['resources', groupId, 'data', 'primaryAdminsIds'], + admins => admins.filter(id => id !== userId) + ), + + [additionalActionTypes.REMOVE_ADMIN_FAILED]: ( + state, + { payload, meta: { groupId, userId } } + ) => + state.updateIn( + ['resources', groupId, 'data', 'primaryAdminsIds'], + admins => admins.filter(id => id !== userId).concat([userId]) ), - [additionalActionTypes.MAKE_ADMIN_FULFILLED]: ( + [additionalActionTypes.REMOVE_ADMIN_FULFILLED]: ( state, - { payload: { adminId, admins }, meta: { groupId } } + { payload: { primaryAdminsIds, admins }, meta: { groupId } } ) => state.updateIn(['resources', groupId, 'data'], group => group - .remove('oldAdminId') .set('admins', List(admins)) - .set('adminId', adminId) + .set('primaryAdminsIds', primaryAdminsIds) ), [additionalActionTypes.LOAD_USERS_GROUPS_FULFILLED]: (
@@ -22,10 +24,20 @@ const SupervisorsListItem = ({ - - {id !== mainAdminId && - makeAdmin(groupId, id)} + {primaryAdminsIds.indexOf(id) < 0 && +
+ + addAdmin(groupId, id)} + bsSize="xs" + /> +
} + {primaryAdminsIds.indexOf(id) >= 0 && + removeAdmin(groupId, id)} bsSize="xs" />}