+
@@ -85,63 +169,55 @@ function UsersList({
Name
Username
Role
- {hasAccess('ADMIN') ? 'Action' : ''}
+
+ {hasAccess(ADMIN) ? 'Action' : ''}
+
-
- {users.map(item => (
-
-
- {formatDateWithLocale(item.createdAt, location.locale)}
- {item.name}
- {item.username || item.email}
- {renderRole(item.rootRole)}
-
-
- edit
-
-
- lock
-
-
- delete
-
-
- }
- elseShow={}
- />
-
- ))}
-
+ {renderUsers()}
+
}
elseShow={PS! Only admins can add/remove users.}
/>
+
+
- {updateDialog.open && }
+ />
+
{
- removeUser(delUser);
- closeDelDialog();
- }}
+ removeUser={onDeleteUser}
+ userLoading={userLoading}
+ userApiErrors={userApiErrors}
/>
)}
diff --git a/frontend/src/page/admin/users/UsersList/index.js b/frontend/src/page/admin/users/UsersList/index.js
index 4b8853c049..d3083cc9fb 100644
--- a/frontend/src/page/admin/users/UsersList/index.js
+++ b/frontend/src/page/admin/users/UsersList/index.js
@@ -1,27 +1,10 @@
import { connect } from 'react-redux';
import UsersList from './UsersList';
-import {
- fetchUsers,
- removeUser,
- addUser,
- changePassword,
- updateUser,
- validatePassword,
-} from '../../../../store/e-user-admin/actions';
const mapStateToProps = state => ({
- users: state.userAdmin.toJS(),
- roles: state.roles.get('root').toJS() || [],
location: state.settings.toJS().location || {},
});
-const Container = connect(mapStateToProps, {
- fetchUsers,
- removeUser,
- addUser,
- changePassword,
- updateUser,
- validatePassword,
-})(UsersList);
+const Container = connect(mapStateToProps)(UsersList);
export default Container;
diff --git a/frontend/src/page/admin/users/UsersList/loadingData.ts b/frontend/src/page/admin/users/UsersList/loadingData.ts
new file mode 100644
index 0000000000..e216574171
--- /dev/null
+++ b/frontend/src/page/admin/users/UsersList/loadingData.ts
@@ -0,0 +1,72 @@
+import { IUser } from '../../../../interfaces/user';
+
+const loadingData: IUser[] = [
+ {
+ id: 1,
+ username: 'admin',
+ email: 'some-email@email.com',
+ name: 'admin',
+ permissions: ['ADMIN'],
+ imageUrl:
+ 'https://gravatar.com/avatar/21232f297a57a5a743894a0e4a801fc3?size=42&default=retro',
+ seenAt: null,
+ loginAttempts: 0,
+ createdAt: '2021-04-21T12:09:55.923Z',
+ rootRole: 1,
+ inviteLink: '',
+ },
+ {
+ id: 16,
+ name: 'test',
+ email: 'test@test.no',
+ permissions: [],
+ imageUrl:
+ 'https://gravatar.com/avatar/879fdbb54e4a6cdba456fcb11abe5971?size=42&default=retro',
+ seenAt: null,
+ loginAttempts: 0,
+ createdAt: '2021-04-21T15:54:02.765Z',
+ rootRole: 2,
+ inviteLink: '',
+ },
+ {
+ id: 3,
+ name: 'Testesen',
+ email: 'test@test.com',
+ permissions: [],
+ imageUrl:
+ 'https://gravatar.com/avatar/6c15d63f08137733ec0828cd0a3a5dc4?size=42&default=retro',
+ seenAt: '2021-04-21T14:34:31.515Z',
+ loginAttempts: 0,
+ createdAt: '2021-04-21T12:33:17.712Z',
+ rootRole: 1,
+ inviteLink: '',
+ },
+ {
+ id: 4,
+ name: 'test',
+ email: 'test@test.io',
+ permissions: [],
+ imageUrl:
+ 'https://gravatar.com/avatar/879fdbb54e4a6cdba456fcb11abe5971?size=42&default=retro',
+ seenAt: null,
+ loginAttempts: 0,
+ createdAt: '2021-04-21T15:54:02.765Z',
+ rootRole: 2,
+ inviteLink: '',
+ },
+ {
+ id: 5,
+ name: 'Testesen',
+ email: 'test@test.uk',
+ permissions: [],
+ imageUrl:
+ 'https://gravatar.com/avatar/6c15d63f08137733ec0828cd0a3a5dc4?size=42&default=retro',
+ seenAt: '2021-04-21T14:34:31.515Z',
+ loginAttempts: 0,
+ createdAt: '2021-04-21T12:33:17.712Z',
+ rootRole: 1,
+ inviteLink: '',
+ },
+];
+
+export default loadingData;
diff --git a/frontend/src/page/admin/users/add-user-component.jsx b/frontend/src/page/admin/users/add-user-component.jsx
deleted file mode 100644
index ff05f18634..0000000000
--- a/frontend/src/page/admin/users/add-user-component.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import Dialogue from '../../../component/common/Dialogue';
-import UserForm from './user-form';
-
-function AddUser({ showDialog, closeDialog, addUser, roles }) {
- const [data, setData] = useState({});
- const [error, setError] = useState({});
-
- const submit = async e => {
- e.preventDefault();
- if (!data.email) {
- setError({ general: 'You must specify the email address' });
- return;
- }
-
- if (!data.rootRole) {
- setError({ general: 'You must specify a role for the user' });
- return;
- }
-
- try {
- await addUser(data);
- setData({});
- setError({});
- closeDialog();
- } catch (error) {
- const msg = error.message || 'Could not create user';
- setError({ general: msg });
- }
- };
-
- const onCancel = e => {
- e.preventDefault();
- setData({});
- setError({});
- closeDialog();
- };
-
- return (
-
{
- submit(e);
- }}
- open={showDialog}
- onClose={onCancel}
- primaryButtonText="Add user"
- secondaryButtonText="Cancel"
- fullWidth
- >
-
-
- );
-}
-
-AddUser.propTypes = {
- showDialog: PropTypes.bool.isRequired,
- closeDialog: PropTypes.func.isRequired,
- addUser: PropTypes.func.isRequired,
- validatePassword: PropTypes.func.isRequired,
- roles: PropTypes.array.isRequired,
-};
-
-export default AddUser;
diff --git a/frontend/src/page/admin/users/change-password-component.jsx b/frontend/src/page/admin/users/change-password-component.jsx
index 6a305c5579..8c97fe4c4d 100644
--- a/frontend/src/page/admin/users/change-password-component.jsx
+++ b/frontend/src/page/admin/users/change-password-component.jsx
@@ -1,16 +1,29 @@
-import React, { useState } from 'react';
+import { useState } from 'react';
import PropTypes from 'prop-types';
-import { TextField, DialogTitle, DialogContent } from '@material-ui/core';
+import classnames from 'classnames';
+import { TextField, Typography, Avatar } from '@material-ui/core';
import { trim } from '../../../component/common/util';
import { modalStyles } from './util';
import Dialogue from '../../../component/common/Dialogue/Dialogue';
-import commonStyles from '../../../component/common/common.module.scss';
+import PasswordChecker from '../../../component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker';
+import { useCommonStyles } from '../../../common.styles';
+import PasswordMatcher from '../../../component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher';
+import ConditionallyRender from '../../../component/common/ConditionallyRender';
+import { Alert } from '@material-ui/lab';
-function ChangePassword({ showDialog, closeDialog, changePassword, validatePassword, user = {} }) {
+function ChangePassword({
+ showDialog,
+ closeDialog,
+ changePassword,
+ user = {},
+}) {
const [data, setData] = useState({});
const [error, setError] = useState({});
+ const [validPassword, setValidPassword] = useState(false);
+ const commonStyles = useCommonStyles();
const updateField = e => {
+ setError({});
setData({
...data,
[e.target.name]: trim(e.target.value),
@@ -19,13 +32,19 @@ function ChangePassword({ showDialog, closeDialog, changePassword, validatePassw
const submit = async e => {
e.preventDefault();
- if (!data.password || data.password.length < 8) {
- setError({ password: 'You must specify a password with at least 8 chars.' });
- return;
- }
- if (!(data.password === data.confirm)) {
- setError({ confirm: 'Passwords does not match' });
- return;
+
+ if (!validPassword) {
+ if (!data.password || data.password.length < 8) {
+ setError({
+ password:
+ 'You must specify a password with at least 8 chars.',
+ });
+ return;
+ }
+ if (!(data.password === data.confirm)) {
+ setError({ confirm: 'Passwords does not match' });
+ return;
+ }
}
try {
@@ -38,19 +57,6 @@ function ChangePassword({ showDialog, closeDialog, changePassword, validatePassw
}
};
- const onPasswordBlur = async e => {
- e.preventDefault();
- setError({ password: '' });
- if (data.password) {
- try {
- await validatePassword(data.password);
- } catch (error) {
- const msg = error.message || '';
- setError({ password: msg });
- }
- }
- };
-
const onCancel = e => {
e.preventDefault();
setData({});
@@ -64,40 +70,70 @@ function ChangePassword({ showDialog, closeDialog, changePassword, validatePassw
style={modalStyles}
onClose={onCancel}
primaryButtonText="Save"
+ title="Update password"
secondaryButtonText="Cancel"
>
-
);
diff --git a/frontend/src/page/admin/users/del-user-component.jsx b/frontend/src/page/admin/users/del-user-component.jsx
index a409d79c0e..b2ee4ac289 100644
--- a/frontend/src/page/admin/users/del-user-component.jsx
+++ b/frontend/src/page/admin/users/del-user-component.jsx
@@ -2,27 +2,78 @@ import React from 'react';
import Dialogue from '../../../component/common/Dialogue/Dialogue';
import ConditionallyRender from '../../../component/common/ConditionallyRender/ConditionallyRender';
import propTypes from 'prop-types';
+import { REMOVE_USER_ERROR } from '../../../hooks/useAdminUsersApi';
+import { Alert } from '@material-ui/lab';
+import useLoading from '../../../hooks/useLoading';
+import { Avatar, Typography } from '@material-ui/core';
+import { useCommonStyles } from '../../../common.styles';
-const DelUserComponent = ({ showDialog, closeDialog, user, removeUser }) => (
-
removeUser(user)}
- primaryButtonText="Delete user"
- secondaryButtonText="Cancel"
- >
-
- Are you sure you want to delete{' '}
- {user ? `${user.name || 'user'} (${user.email || user.username})` : ''}?
+const DelUserComponent = ({
+ showDialog,
+ closeDialog,
+ user,
+ userLoading,
+ removeUser,
+ userApiErrors,
+}) => {
+ const ref = useLoading(userLoading);
+ const commonStyles = useCommonStyles();
+
+ return (
+
removeUser(user)}
+ primaryButtonText="Delete user"
+ secondaryButtonText="Cancel"
+ >
+
+
+ {userApiErrors[REMOVE_USER_ERROR]}
+
+ }
+ />
+
+
+
+ {user.username || user.email}
+
-
- }
- />
-);
+
+ Are you sure you want to delete{' '}
+ {user
+ ? `${user.name || 'user'} (${
+ user.email || user.username
+ })`
+ : ''}
+ ?
+
+
+
+ );
+};
DelUserComponent.propTypes = {
showDialog: propTypes.bool.isRequired,
diff --git a/frontend/src/page/admin/users/update-user-component.jsx b/frontend/src/page/admin/users/update-user-component.jsx
index 13e0a8f52d..ef28aa3021 100644
--- a/frontend/src/page/admin/users/update-user-component.jsx
+++ b/frontend/src/page/admin/users/update-user-component.jsx
@@ -1,22 +1,25 @@
-import React, { useState, useEffect } from 'react';
+import { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import Dialogue from '../../../component/common/Dialogue';
-import UserForm from './user-form';
+import UserForm from './AddUser/AddUserForm/AddUserForm';
-function AddUser({ user = {}, showDialog, closeDialog, updateUser, roles }) {
+function AddUser({
+ user,
+ showDialog,
+ closeDialog,
+ updateUser,
+ roles,
+ userApiErrors,
+ userLoading,
+}) {
const [data, setData] = useState({});
const [error, setError] = useState({});
useEffect(() => {
setData({
- id: user.id,
- email: user.email || '',
- rootRole: user.rootRole || '',
- name: user.name || '',
+ ...user,
});
- }, [user])
-
-
+ }, [user]);
if (!user) {
return null;
@@ -29,7 +32,6 @@ function AddUser({ user = {}, showDialog, closeDialog, updateUser, roles }) {
await updateUser(data);
setData({});
setError({});
- closeDialog();
} catch (error) {
setError({ general: 'Could not update user' });
}
@@ -51,9 +53,18 @@ function AddUser({ user = {}, showDialog, closeDialog, updateUser, roles }) {
onClose={onCancel}
primaryButtonText="Update user"
secondaryButtonText="Cancel"
+ title="Update team member"
fullWidth
>
-
+
);
}
diff --git a/frontend/src/page/admin/users/user-form.jsx b/frontend/src/page/admin/users/user-form.jsx
deleted file mode 100644
index aa811bf9b1..0000000000
--- a/frontend/src/page/admin/users/user-form.jsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {
- TextField,
- DialogTitle,
- DialogContent,
- RadioGroup,
- Radio,
- FormControl,
- FormLabel,
- FormControlLabel,
-} from '@material-ui/core';
-import commonStyles from '../../../component/common/common.module.scss';
-import { trim } from '../../../component/common/util';
-
-function UserForm({ title, submit, data, error, setData, roles }) {
- const updateField = e => {
- setData({
- ...data,
- [e.target.name]: e.target.value,
- });
- };
-
- const updateFieldWithTrim = e => {
- setData({
- ...data,
- [e.target.name]: trim(e.target.value),
- });
- };
-
- const updateNumberField = e => {
- setData({
- ...data,
- [e.target.name]: +e.target.value,
- });
- };
-
- return (
-
- }
- control={}
- value={role.id}
- />
- ))}
-
-
-
-
- );
-}
-
-UserForm.propTypes = {
- title: PropTypes.string.isRequired,
- data: PropTypes.object.isRequired,
- error: PropTypes.object.isRequired,
- submit: PropTypes.func.isRequired,
- setData: PropTypes.func.isRequired,
- roles: PropTypes.array.isRequired,
-};
-
-export default UserForm;
diff --git a/frontend/src/store/e-user-admin/actions.js b/frontend/src/store/e-user-admin/actions.js
deleted file mode 100644
index 3131559bdc..0000000000
--- a/frontend/src/store/e-user-admin/actions.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import api from './api';
-import { dispatchError } from '../util';
-export const START_FETCH_USERS = 'START_FETCH_USERS';
-export const RECEIVE_USERS = 'RECEIVE_USERS';
-export const ERROR_FETCH_USERS = 'ERROR_FETCH_USERS';
-export const REMOVE_USER = 'REMOVE_USER';
-export const REMOVE_USER_ERROR = 'REMOVE_USER_ERROR';
-export const ADD_USER = 'ADD_USER';
-export const ADD_USER_ERROR = 'ADD_USER_ERROR';
-export const UPDATE_USER = 'UPDATE_USER';
-export const UPDATE_USER_ERROR = 'UPDATE_USER_ERROR';
-export const CHANGE_PASSWORD_ERROR = 'CHANGE_PASSWORD_ERROR';
-export const VALIDATE_PASSWORD_ERROR = 'VALIDATE_PASSWORD_ERROR';
-
-const debug = require('debug')('unleash:e-user-admin-actions');
-
-const gotUsers = value => ({
- type: RECEIVE_USERS,
- value,
-});
-
-export function fetchUsers() {
- debug('Start fetching user');
- return dispatch => {
- dispatch({ type: START_FETCH_USERS });
-
- return api
- .fetchAll()
- .then(json => dispatch(gotUsers(json)))
- .catch(dispatchError(dispatch, ERROR_FETCH_USERS));
- };
-}
-
-export function removeUser(user) {
- return dispatch =>
- api
- .remove(user)
- .then(() => dispatch({ type: REMOVE_USER, user }))
- .catch(dispatchError(dispatch, REMOVE_USER_ERROR));
-}
-
-export function addUser(user) {
- return dispatch =>
- api
- .create(user)
- .then(newUser => dispatch({ type: ADD_USER, user: newUser }))
- .catch(dispatchError(dispatch, ADD_USER_ERROR));
-}
-
-export function updateUser(user) {
- return dispatch =>
- api
- .update(user)
- .then(newUser => dispatch({ type: UPDATE_USER, user: newUser }))
- .catch(dispatchError(dispatch, UPDATE_USER_ERROR));
-}
-
-export function changePassword(user, newPassword) {
- return dispatch => api.changePassword(user, newPassword).catch(dispatchError(dispatch, CHANGE_PASSWORD_ERROR));
-}
-
-export function validatePassword(password) {
- return dispatch => api.validatePassword(password).catch(dispatchError(dispatch, VALIDATE_PASSWORD_ERROR));
-}
diff --git a/frontend/src/store/e-user-admin/api.js b/frontend/src/store/e-user-admin/api.js
deleted file mode 100644
index bac580d995..0000000000
--- a/frontend/src/store/e-user-admin/api.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import { throwIfNotSuccess, headers } from '../api-helper';
-
-const URI = 'api/admin/user-admin';
-
-function fetchAll() {
- return fetch(URI, { headers, credentials: 'include' })
- .then(throwIfNotSuccess)
- .then(response => response.json());
-}
-
-function create(user) {
- return fetch(URI, {
- method: 'POST',
- headers,
- body: JSON.stringify(user),
- credentials: 'include',
- })
- .then(throwIfNotSuccess)
- .then(response => response.json());
-}
-
-function update(user) {
- return fetch(`${URI}/${user.id}`, {
- method: 'PUT',
- headers,
- body: JSON.stringify(user),
- credentials: 'include',
- })
- .then(throwIfNotSuccess)
- .then(response => response.json());
-}
-
-function changePassword(user, newPassword) {
- return fetch(`${URI}/${user.id}/change-password`, {
- method: 'POST',
- headers,
- body: JSON.stringify({ password: newPassword }),
- credentials: 'include',
- }).then(throwIfNotSuccess);
-}
-
-function validatePassword(password) {
- return fetch(`${URI}/validate-password`, {
- method: 'POST',
- headers,
- body: JSON.stringify({ password }),
- credentials: 'include',
- }).then(throwIfNotSuccess);
-}
-
-function remove(user) {
- return fetch(`${URI}/${user.id}`, {
- method: 'DELETE',
- headers,
- credentials: 'include',
- }).then(throwIfNotSuccess);
-}
-
-export default {
- fetchAll,
- create,
- update,
- changePassword,
- validatePassword,
- remove,
-};
diff --git a/frontend/src/store/e-user-admin/index.js b/frontend/src/store/e-user-admin/index.js
deleted file mode 100644
index 165ccc1b6d..0000000000
--- a/frontend/src/store/e-user-admin/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import { List } from 'immutable';
-import { RECEIVE_USERS, ADD_USER, REMOVE_USER, UPDATE_USER } from './actions';
-
-const store = (state = new List(), action) => {
- switch (action.type) {
- case RECEIVE_USERS:
- return new List(action.value.users);
- case ADD_USER:
- return state.push(action.user);
- case UPDATE_USER:
- return state.map(user => {
- if (user.id === action.user.id) {
- return action.user;
- } else {
- return user;
- }
- });
- case REMOVE_USER:
- return state.filter(v => v.id !== action.user.id);
- default:
- return state;
- }
-};
-
-export default store;
diff --git a/frontend/src/store/e-user-admin/roles-store.js b/frontend/src/store/e-user-admin/roles-store.js
deleted file mode 100644
index 7e3e71b11b..0000000000
--- a/frontend/src/store/e-user-admin/roles-store.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { List, fromJS } from 'immutable';
-import { RECEIVE_USERS } from './actions';
-
-function getInitialState() {
- return fromJS({
- root: [],
- });
-}
-
-const store = (state = getInitialState(), action) => {
- switch (action.type) {
- case RECEIVE_USERS:
- return state.set('root', new List(action.value.rootRoles));
- default:
- return state;
- }
-};
-
-export default store;
diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js
index a565205b75..d3726c52a5 100644
--- a/frontend/src/store/index.js
+++ b/frontend/src/store/index.js
@@ -6,7 +6,7 @@ import featureTags from './feature-tags';
import tagTypes from './tag-type';
import tags from './tag';
import strategies from './strategy';
-import history from "./history"; // eslint-disable-line
+import history from './history'; // eslint-disable-line
import archive from './archive';
import error from './error';
import settings from './settings';
@@ -16,8 +16,6 @@ import uiConfig from './ui-config';
import context from './context';
import projects from './project';
import addons from './addons';
-import userAdmin from './e-user-admin';
-import roles from './e-user-admin/roles-store';
import apiAdmin from './e-api-admin';
import authAdmin from './e-admin-auth';
import apiCalls from './api-calls';
@@ -40,8 +38,6 @@ const unleashStore = combineReducers({
context,
projects,
addons,
- userAdmin,
- roles,
apiAdmin,
authAdmin,
apiCalls,
diff --git a/frontend/src/themes/main-theme.js b/frontend/src/themes/main-theme.js
index 756b660817..f822d5842a 100644
--- a/frontend/src/themes/main-theme.js
+++ b/frontend/src/themes/main-theme.js
@@ -73,6 +73,11 @@ const theme = createMuiTheme({
},
main: '#fff',
},
+ dialogue: {
+ title: {
+ main: '#fff',
+ },
+ },
},
padding: {
pageContent: {