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:
Mike Stone
2016-10-07 13:07:02 -04:00
committed by GitHub
parent 1cce02cf86
commit db57aaa1fc
7 changed files with 98 additions and 32 deletions
@@ -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>
);
+33 -10
View File
@@ -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);