Filter unchanged attributes when updating user (#293)
* Only send changed user attributes to the server * Improve flash message styles * Do not allow admins to demote or disable their own account * Disable admin actions against self
This commit is contained in:
@@ -5,7 +5,13 @@ import { hideFlash } from '../../redux/nodes/notifications/actions';
|
||||
|
||||
const FlashMessage = ({ notification, dispatch }) => {
|
||||
const { alertType, isVisible, message, undoAction } = notification;
|
||||
const { containerStyles, contentStyles, undoStyles } = componentStyles;
|
||||
const {
|
||||
containerStyles,
|
||||
contentStyles,
|
||||
flashActionStyles,
|
||||
removeFlashMessageStyles,
|
||||
undoStyles,
|
||||
} = componentStyles;
|
||||
|
||||
const submitUndoAction = () => {
|
||||
dispatch(undoAction);
|
||||
@@ -25,11 +31,9 @@ const FlashMessage = ({ notification, dispatch }) => {
|
||||
<div style={contentStyles}>
|
||||
{message}
|
||||
</div>
|
||||
<div onClick={submitUndoAction} style={undoStyles}>
|
||||
Undo
|
||||
</div>
|
||||
<div onClick={removeFlashMessage}>
|
||||
X
|
||||
<div style={flashActionStyles}>
|
||||
<div onClick={submitUndoAction} style={undoStyles}>{undoAction && 'undo'}</div>
|
||||
<div onClick={removeFlashMessage} style={removeFlashMessageStyles(alertType)}>x</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,26 +1,49 @@
|
||||
import Style from '../../styles';
|
||||
|
||||
const { color } = Style;
|
||||
const { color, padding } = Style;
|
||||
|
||||
export default {
|
||||
containerStyles: (alertType) => {
|
||||
const successAlert = {
|
||||
backgroundColor: color.success,
|
||||
};
|
||||
|
||||
const successAlert = { backgroundColor: color.success };
|
||||
const errorAlert = { backgroundColor: color.alert };
|
||||
const baseStyles = {
|
||||
alignItems: 'center',
|
||||
color: color.white,
|
||||
display: 'flex',
|
||||
height: '50px',
|
||||
justifyContent: 'space-between',
|
||||
paddingLeft: padding.half,
|
||||
paddingRight: padding.half,
|
||||
};
|
||||
|
||||
if (alertType === 'success') {
|
||||
return {
|
||||
...baseStyles,
|
||||
...successAlert,
|
||||
};
|
||||
return { ...baseStyles, ...successAlert };
|
||||
}
|
||||
|
||||
if (alertType === 'error') {
|
||||
return { ...baseStyles, ...errorAlert };
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
contentStyles: {},
|
||||
undoStyles: {},
|
||||
flashActionStyles: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '96px',
|
||||
},
|
||||
removeFlashMessageStyles: (alertType) => {
|
||||
const backgroundColor = alertType === 'success' ? color.successLight : color.alertLight;
|
||||
return {
|
||||
backgroundColor,
|
||||
borderRadius: '50%',
|
||||
cursor: 'pointer',
|
||||
height: '30px',
|
||||
textAlign: 'center',
|
||||
width: '30px',
|
||||
};
|
||||
},
|
||||
undoStyles: {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -36,12 +36,8 @@ class EditUserForm extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
const { user } = props;
|
||||
|
||||
this.state = {
|
||||
formData: {
|
||||
...user,
|
||||
},
|
||||
formData: {},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import expect, { createSpy, restoreSpies } from 'expect';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import EditUserForm from './EditUserForm';
|
||||
import { fillInFormInput } from '../../../../test/helpers';
|
||||
|
||||
describe('EditUserForm - form', () => {
|
||||
afterEach(restoreSpies);
|
||||
|
||||
const user = {
|
||||
email: 'hi@gnar.dog',
|
||||
name: 'Gnar Dog',
|
||||
position: 'Head of Everything',
|
||||
username: 'gnardog',
|
||||
};
|
||||
|
||||
it('sends the users changed attributes when the form is submitted', () => {
|
||||
const email = 'newEmail@gnar.dog';
|
||||
const onSubmit = createSpy();
|
||||
const form = mount(<EditUserForm user={user} onSubmit={onSubmit} />);
|
||||
const emailInput = form.find({ name: 'email' });
|
||||
|
||||
fillInFormInput(emailInput, email);
|
||||
form.simulate('submit');
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledWith({ email });
|
||||
});
|
||||
});
|
||||
@@ -28,11 +28,11 @@ class Dropdown extends Component {
|
||||
}
|
||||
|
||||
renderOption = (option) => {
|
||||
const { value, text } = option;
|
||||
const { disabled = false, value, text } = option;
|
||||
const { optionWrapperStyles } = componentStyles;
|
||||
|
||||
return (
|
||||
<option key={value} style={optionWrapperStyles} value={value}>
|
||||
<option key={value} style={optionWrapperStyles} value={value} disabled={disabled}>
|
||||
{text}
|
||||
</option>
|
||||
);
|
||||
|
||||
@@ -7,17 +7,19 @@ import EditUserForm from '../../../../components/forms/Admin/EditUserForm';
|
||||
|
||||
class UserBlock extends Component {
|
||||
static propTypes = {
|
||||
currentUser: PropTypes.object,
|
||||
onEditUser: PropTypes.func,
|
||||
onSelect: PropTypes.func,
|
||||
user: PropTypes.object,
|
||||
};
|
||||
|
||||
static userActionOptions = (user) => {
|
||||
static userActionOptions = (currentUser, user) => {
|
||||
const disableActions = currentUser.id === user.id;
|
||||
const userEnableAction = user.enabled
|
||||
? { text: 'Disable Account', value: 'disable_account' }
|
||||
? { disabled: disableActions, text: 'Disable Account', value: 'disable_account' }
|
||||
: { text: 'Enable Account', value: 'enable_account' };
|
||||
const userPromotionAction = user.admin
|
||||
? { text: 'Demote User', value: 'demote_user' }
|
||||
? { disabled: disableActions, text: 'Demote User', value: 'demote_user' }
|
||||
: { text: 'Promote User', value: 'promote_user' };
|
||||
|
||||
return [
|
||||
@@ -88,7 +90,7 @@ class UserBlock extends Component {
|
||||
userStatusWrapperStyles,
|
||||
userWrapperStyles,
|
||||
} = componentStyles;
|
||||
const { user } = this.props;
|
||||
const { currentUser, user } = this.props;
|
||||
const {
|
||||
admin,
|
||||
email,
|
||||
@@ -99,7 +101,7 @@ class UserBlock extends Component {
|
||||
} = user;
|
||||
const userLabel = admin ? 'Admin' : 'User';
|
||||
const activeLabel = enabled ? 'Active' : 'Disabled';
|
||||
const userActionOptions = UserBlock.userActionOptions(user);
|
||||
const userActionOptions = UserBlock.userActionOptions(currentUser, user);
|
||||
const { isEdit } = this.state;
|
||||
const { onEditUserFormSubmit, onToggleEditing } = this;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { renderFlash } from '../../../redux/nodes/notifications/actions';
|
||||
|
||||
class UserManagementPage extends Component {
|
||||
static propTypes = {
|
||||
currentUser: PropTypes.object,
|
||||
dispatch: PropTypes.func,
|
||||
users: PropTypes.arrayOf(PropTypes.object),
|
||||
};
|
||||
@@ -33,21 +34,29 @@ class UserManagementPage extends Component {
|
||||
}
|
||||
|
||||
onUserActionSelect = (user, action) => {
|
||||
const { dispatch } = this.props;
|
||||
const { currentUser, dispatch } = this.props;
|
||||
const { update } = userActions;
|
||||
|
||||
if (action) {
|
||||
switch (action) {
|
||||
case 'demote_user':
|
||||
case 'demote_user': {
|
||||
if (currentUser.id === user.id) {
|
||||
return dispatch(renderFlash('error', 'You cannot demote yourself'));
|
||||
}
|
||||
return dispatch(update(user, { admin: false }))
|
||||
.then(() => {
|
||||
return dispatch(renderFlash('success', 'User demoted', update(user, { admin: true })));
|
||||
});
|
||||
case 'disable_account':
|
||||
}
|
||||
case 'disable_account': {
|
||||
if (currentUser.id === user.id) {
|
||||
return dispatch(renderFlash('error', 'You cannot disable your own account'));
|
||||
}
|
||||
return dispatch(userActions.update(user, { enabled: false }))
|
||||
.then(() => {
|
||||
return dispatch(renderFlash('success', 'User account disabled', update(user, { enabled: true })));
|
||||
});
|
||||
}
|
||||
case 'enable_account':
|
||||
return dispatch(update(user, { enabled: true }))
|
||||
.then(() => {
|
||||
@@ -105,10 +114,12 @@ class UserManagementPage extends Component {
|
||||
}
|
||||
|
||||
renderUserBlock = (user) => {
|
||||
const { currentUser } = this.props;
|
||||
const { onEditUser, onUserActionSelect } = this;
|
||||
|
||||
return (
|
||||
<UserBlock
|
||||
currentUser={currentUser}
|
||||
key={user.email}
|
||||
onEditUser={onEditUser}
|
||||
onSelect={onUserActionSelect}
|
||||
@@ -169,9 +180,10 @@ class UserManagementPage extends Component {
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { user: currentUser } = state.auth;
|
||||
const { entities: users } = entityGetter(state).get('users');
|
||||
|
||||
return { users };
|
||||
return { currentUser, users };
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(UserManagementPage);
|
||||
|
||||
Reference in New Issue
Block a user