diff --git a/scm-ui/src/apiclient.js b/scm-ui/src/apiclient.js index 0b945d1fbd..627bfacd27 100644 --- a/scm-ui/src/apiclient.js +++ b/scm-ui/src/apiclient.js @@ -42,6 +42,24 @@ class ApiClient { return this.httpRequestWithJSONBody(url, payload, "POST"); } + postWithContentType(url: string, payload: any, contentType: string) { + return this.httpRequestWithContentType( + url, + "POST", + JSON.stringify(payload), + contentType + ); + } + + putWithContentType(url: string, payload: any, contentType: string) { + return this.httpRequestWithContentType( + url, + "PUT", + JSON.stringify(payload), + contentType + ); + } + delete(url: string): Promise { let options: RequestOptions = { method: "DELETE" @@ -54,14 +72,38 @@ class ApiClient { url: string, payload: any, method: string + ): Promise { + // let options: RequestOptions = { + // method: method, + // body: JSON.stringify(payload) + // }; + // options = Object.assign(options, fetchOptions); + // // $FlowFixMe + // options.headers["Content-Type"] = "application/json"; + + // return fetch(createUrl(url), options).then(handleStatusCode); + + return this.httpRequestWithContentType( + url, + method, + JSON.stringify(payload), + "application/json" + ).then(handleStatusCode); + } + + httpRequestWithContentType( + url: string, + method: string, + payload: any, + contentType: string ): Promise { let options: RequestOptions = { method: method, - body: JSON.stringify(payload) + body: payload }; options = Object.assign(options, fetchOptions); // $FlowFixMe - options.headers["Content-Type"] = "application/json"; + options.headers["Content-Type"] = contentType; return fetch(createUrl(url), options).then(handleStatusCode); } diff --git a/scm-ui/src/components/Checkbox.js b/scm-ui/src/components/Checkbox.js new file mode 100644 index 0000000000..12d6f3f77f --- /dev/null +++ b/scm-ui/src/components/Checkbox.js @@ -0,0 +1,27 @@ +//@flow +import React from "react"; + +type Props = { + label: string, + onChange: boolean => void +}; +class Checkbox extends React.Component { + onCheckboxChange = (event: SyntheticInputEvent) => { + this.props.onChange(event.target.checked); + }; + + render() { + return ( +
+
+ +
+
+ ); + } +} + +export default Checkbox; diff --git a/scm-ui/src/components/InputField.js b/scm-ui/src/components/InputField.js index 19864ea832..9b1ec724db 100644 --- a/scm-ui/src/components/InputField.js +++ b/scm-ui/src/components/InputField.js @@ -22,7 +22,7 @@ class InputField extends React.Component { renderLabel = () => { const label = this.props.label; if (label) { - return ; + return ; } return ""; }; @@ -31,11 +31,11 @@ class InputField extends React.Component { const { type, placeholder } = this.props; return ( -
+
{this.renderLabel()} -
+
{ } } -const mapDispatchToProps = dispatch => { +const mapDispatchToProps = (dispatch: ThunkDispatch) => { return { getAuthState: () => dispatch(getIsAuthenticated()) }; diff --git a/scm-ui/src/users/containers/UserForm.js b/scm-ui/src/users/containers/UserForm.js new file mode 100644 index 0000000000..6c01fb7a18 --- /dev/null +++ b/scm-ui/src/users/containers/UserForm.js @@ -0,0 +1,86 @@ +// @flow +import React from "react"; +import type { User } from "../types/User"; +import InputField from "../../components/InputField"; +import Checkbox from "../../components/Checkbox"; +import SubmitButton from "../../components/SubmitButton"; + +type Props = { + submitForm: User => void, + user?: User +}; + +class UserForm extends React.Component { + submit = (event: Event) => { + event.preventDefault(); + this.props.submitForm(this.state); + }; + + render() { + const { submitForm, user } = this.props; + return ( +
+
+ + + + + + + + +
+ ); + } + + handleUsernameChange = (name: string) => { + this.setState({ name }); + }; + + handleDisplayNameChange = (displayName: string) => { + this.setState({ displayName }); + }; + + handleEmailChange = (mail: string) => { + this.setState({ mail }); + }; + + handlePasswordChange = (password: string) => { + this.setState({ password }); + }; + + handleAdminChange = (admin: boolean) => { + this.setState({ admin }); + }; + + handleActiveChange = (active: boolean) => { + this.setState({ active }); + }; +} + +export default UserForm; diff --git a/scm-ui/src/users/containers/UserTable.js b/scm-ui/src/users/containers/UserTable.js new file mode 100644 index 0000000000..338e0663c9 --- /dev/null +++ b/scm-ui/src/users/containers/UserTable.js @@ -0,0 +1,34 @@ +// @flow +import React from "react"; +import UserRow from "./UserRow"; +import type { User } from "../types/User"; + +type Props = { + users: Array, + deleteUser: string => void +}; + +class UserTable extends React.Component { + render() { + const { users, deleteUser } = this.props; + return ( + + + + + + + + + + + {users.map((user, index) => { + return ; + })} + +
NameDisplay NameE-MailAdmin
+ ); + } +} + +export default UserTable; diff --git a/scm-ui/src/users/containers/Users.js b/scm-ui/src/users/containers/Users.js index 9c9914e6dd..e431c2a43f 100644 --- a/scm-ui/src/users/containers/Users.js +++ b/scm-ui/src/users/containers/Users.js @@ -2,9 +2,9 @@ import React from "react"; import { connect } from "react-redux"; -import { fetchUsers, deleteUser } from "../modules/users"; -import Login from "../../containers/Login"; -import UserRow from "./UserRow"; +import { fetchUsers, addUser, editUser, deleteUser } from "../modules/users"; +import UserForm from "./UserForm"; +import UserTable from "./UserTable"; import type { User } from "../types/User"; type Props = { @@ -12,7 +12,9 @@ type Props = { error: Error, users: Array, fetchUsers: () => void, - deleteUser: string => void + deleteUser: string => void, + addUser: User => void, + editUser: User => void }; class Users extends React.Component { @@ -20,34 +22,35 @@ class Users extends React.Component { this.props.fetchUsers(); } + addUser = (user: User) => { + this.props.addUser(user); + }; + + editUser = (user: User) => { + this.props.editUser(user); + }; + render() { - if (this.props.users) { + const { users, deleteUser } = this.props; + const testUser: User = { + name: "user", + displayName: "user_display", + password: "pw", + mail: "mail@mail.de", + active: true, + admin: true + }; + if (users) { return ( -
-

SCM

-

Users

- - - - - - - - - - - {this.props.users.map((user, index) => { - return ( - - ); - })} - -
NameDisplay NameE-MailAdmin
-
+
+
+

SCM

+

Users

+ + {/* */} + {}} user={testUser} /> +
+
); } else { return
Loading...
; @@ -66,6 +69,12 @@ const mapDispatchToProps = dispatch => { fetchUsers: () => { dispatch(fetchUsers()); }, + addUser: (user: User) => { + dispatch(addUser(user)); + }, + editUser: (user: User) => { + dispatch(editUser(user)); + }, deleteUser: (link: string) => { dispatch(deleteUser(link)); } diff --git a/scm-ui/src/users/modules/users.js b/scm-ui/src/users/modules/users.js index 14c4db1851..cba960b484 100644 --- a/scm-ui/src/users/modules/users.js +++ b/scm-ui/src/users/modules/users.js @@ -1,5 +1,6 @@ // @flow import { apiClient, PAGE_NOT_FOUND_ERROR } from "../../apiclient"; +import type { User } from "../types/User"; import { ThunkDispatch } from "redux-thunk"; const FETCH_USERS = "scm/users/FETCH"; @@ -7,12 +8,21 @@ const FETCH_USERS_SUCCESS = "scm/users/FETCH_SUCCESS"; const FETCH_USERS_FAILURE = "scm/users/FETCH_FAILURE"; const FETCH_USERS_NOTFOUND = "scm/users/FETCH_NOTFOUND"; +const ADD_USER = "scm/users/ADD"; +const ADD_USER_SUCCESS = "scm/users/ADD_SUCCESS"; +const ADD_USER_FAILURE = "scm/users/ADD_FAILURE"; + +const EDIT_USER = "scm/users/EDIT"; +const EDIT_USER_SUCCESS = "scm/users/EDIT_SUCCESS"; +const EDIT_USER_FAILURE = "scm/users/EDIT_FAILURE"; + const DELETE_USER = "scm/users/DELETE"; const DELETE_USER_SUCCESS = "scm/users/DELETE_SUCCESS"; const DELETE_USER_FAILURE = "scm/users/DELETE_FAILURE"; const USERS_URL = "users"; +const CONTENT_TYPE_USER = "application/vnd.scmm-user+json;v=2"; function requestUsers() { return { type: FETCH_USERS @@ -67,6 +77,74 @@ function fetchUsersSuccess(users: any) { }; } +function requestAddUser(user: User) { + return { + type: ADD_USER, + user + }; +} + +export function addUser(user: User) { + return function(dispatch: ThunkDispatch) { + dispatch(requestAddUser(user)); + return apiClient + .postWithContentType(USERS_URL, user, CONTENT_TYPE_USER) + .then(() => { + dispatch(addUserSuccess()); + dispatch(fetchUsers()); + }) + .catch(err => dispatch(addUserFailure(user, err))); + }; +} + +function addUserSuccess() { + return { + type: ADD_USER_SUCCESS + }; +} + +function addUserFailure(user: User, err: Error) { + return { + type: ADD_USER_FAILURE, + payload: err, + user + }; +} + +function requestAddUser(user: User) { + return { + type: ADD_USER, + user + }; +} + +export function editUser(user: User) { + return function(dispatch: ThunkDispatch) { + dispatch(requestAddUser(user)); + return apiClient + .putWithContentType(USERS_URL + "/" + user.name, user, CONTENT_TYPE_USER) + .then(() => { + dispatch(addUserSuccess()); + dispatch(fetchUsers()); + }) + .catch(err => dispatch(addUserFailure(user, err))); + }; +} + +function editUserSuccess() { + return { + type: ADD_USER_SUCCESS + }; +} + +function addUserFailure(user: User, err: Error) { + return { + type: ADD_USER_FAILURE, + payload: err, + user + }; +} + function requestDeleteUser(url: string) { return { type: DELETE_USER, diff --git a/scm-ui/src/users/types/User.js b/scm-ui/src/users/types/User.js index 9cf4e79728..b782ef2fdb 100644 --- a/scm-ui/src/users/types/User.js +++ b/scm-ui/src/users/types/User.js @@ -5,6 +5,8 @@ export type User = { displayName: string, name: string, mail: string, + password: string, admin: boolean, - _links: Links + active: boolean, + _links?: Links };