Merged heads

This commit is contained in:
Philipp Czora
2018-08-07 16:20:12 +02:00
9 changed files with 142 additions and 131 deletions

View File

@@ -29,15 +29,15 @@
"edit-group-button": { "edit-group-button": {
"label": "Edit" "label": "Edit"
}, },
"add-user-button": { "add-member-button": {
"label": "Add user" "label": "Add member"
}, },
"remove-user-button": { "remove-member-button": {
"label": "Remove user" "label": "Remove member"
}, },
"add-user-textfield": { "add-member-textfield": {
"label": "Add user", "label": "Add member",
"error": "Username invalid" "error": "Invalid member name"
}, },
"group-form": { "group-form": {
"submit": "Submit", "submit": "Submit",

View File

@@ -8,19 +8,19 @@ import { isMemberNameValid } from "./groupValidation"
type Props = { type Props = {
t: string => string, t: string => string,
addUser: string => void addMember: string => void
}; };
type State = { type State = {
userToAdd: string, memberToAdd: string,
validationError: boolean validationError: boolean
}; };
class AddUserField extends React.Component<Props, State> { class AddMemberField extends React.Component<Props, State> {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
userToAdd: "", memberToAdd: "",
validationError: false validationError: false
}; };
} }
@@ -30,16 +30,15 @@ class AddUserField extends React.Component<Props, State> {
return ( return (
<div className="field"> <div className="field">
<InputField <InputField
label={t("add-user-textfield.label")} label={t("add-member-textfield.label")}
errorMessage={t("add-user-textfield.error")} errorMessage={t("add-member-textfield.error")}
onChange={this.handleAddUserChange} onChange={this.handleAddMemberChange}
validationError={this.state.validationError} validationError={this.state.validationError} //TODO: validate member name
value={this.state.userToAdd} value={this.state.memberToAdd}
onReturnPressed={this.appendMember} onReturnPressed={this.appendMember}
/> />
<AddButton <AddButton
label={t("add-member-button.label")}
label={t("add-user-button.label")}
action={this.addButtonClicked} action={this.addButtonClicked}
/> />
</div> </div>
@@ -53,13 +52,14 @@ class AddUserField extends React.Component<Props, State> {
}; };
appendMember = () => { appendMember = () => {
this.props.addUser(this.state.userToAdd); this.props.addMember(this.state.memberToAdd);
this.setState({ ...this.state, userToAdd: "" }); this.setState({ ...this.state, memberToAdd: "" });
} };
handleAddUserChange = (username: string) => {
this.setState({ ...this.state, userToAdd: username, validationError: !isMemberNameValid(username)}); handleAddMemberChange = (membername: string) => {
this.setState({ ...this.state, memberToAdd: membername, validationError: !isMemberNameValid(membername) });
}; };
} }
export default translate("groups")(AddUserField); export default translate("groups")(AddMemberField);

View File

@@ -6,8 +6,8 @@ import { SubmitButton } from "../../components/buttons";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import type { Group } from "../types/Group"; import type { Group } from "../types/Group";
import * as validator from "./groupValidation"; import * as validator from "./groupValidation";
import AddUserField from "./AddUserField"; import AddMemberField from "./AddMemberField";
import UserNameTable from "./UserNameTable"; import MemberNameTable from "./MemberNameTable";
type Props = { type Props = {
t: string => string, t: string => string,
@@ -95,11 +95,11 @@ class GroupForm extends React.Component<Props, State> {
value={group.description} value={group.description}
validationError={false} validationError={false}
/> />
<UserNameTable <MemberNameTable
users={this.state.group.members} members={this.state.group.members}
userListChanged={this.userListChanged} memberListChanged={this.memberListChanged}
/> />
<AddUserField addUser={this.addUser} /> <AddMemberField addMember={this.addMember} />
<SubmitButton <SubmitButton
disabled={!this.isValid()} disabled={!this.isValid()}
label={t("group-form.submit")} label={t("group-form.submit")}
@@ -109,19 +109,18 @@ class GroupForm extends React.Component<Props, State> {
); );
} }
userListChanged = usernames => { memberListChanged = membernames => {
this.setState({ this.setState({
...this.state, ...this.state,
group: { group: {
...this.state.group, ...this.state.group,
members: usernames members: membernames
} }
}); });
} };
addMember = (membername: string) => {
addUser = (username: string) => { if (this.isMember(membername)) {
if (this.isMember(username)) {
return; return;
} }
@@ -129,13 +128,13 @@ class GroupForm extends React.Component<Props, State> {
...this.state, ...this.state,
group: { group: {
...this.state.group, ...this.state.group,
members: [...this.state.group.members, username] members: [...this.state.group.members, membername]
} }
}); });
}; };
isMember = (username: string) => { isMember = (membername: string) => {
return this.state.group.members.includes(username); return this.state.group.members.includes(membername);
}; };
handleGroupNameChange = (name: string) => { handleGroupNameChange = (name: string) => {

View File

@@ -0,0 +1,47 @@
//@flow
import React from "react";
import { translate } from "react-i18next";
import RemoveMemberButton from "./buttons/RemoveMemberButton";
type Props = {
members: string[],
t: string => string,
memberListChanged: (string[]) => void
};
type State = {};
class MemberNameTable extends React.Component<Props, State> {
render() {
const { t } = this.props;
return (
<div>
<label className="label">{t("group.members")}</label>
<table className="table is-hoverable is-fullwidth">
<tbody>
{this.props.members.map((member, index) => {
return (
<tr key={member}>
<td key={member}>{member}</td>
<td>
<RemoveMemberButton
membername={member}
removeMember={this.removeMember}
/>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}
removeMember = (membername: string) => {
const newMembers = this.props.members.filter(name => name !== membername);
this.props.memberListChanged(newMembers);
};
}
export default translate("groups")(MemberNameTable);

View File

@@ -1,46 +0,0 @@
//@flow
import React from "react";
import { translate } from "react-i18next"
import RemoveUserButton from "./buttons/RemoveUserButton";
type Props = {
users: string[];
t: string => string,
userListChanged: (string[]) => void
};
type State = {
};
class UserNameTable extends React.Component<Props, State> {
render() {
const { t } = this.props;
return (
<div>
<label className="label">{t("group.members")}</label>
<table className="table is-hoverable is-fullwidth">
<tbody>
{this.props.users.map((user, index) => {
return (
<tr key={user}>
<td key={user}>{user}</td>
<td>
<RemoveUserButton username={user} removeUser={this.removeUser} />
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}
removeUser = (username: string) => {
const newUsers = this.props.users.filter(name => name !== username);
this.props.userListChanged(newUsers);
}
}
export default translate("groups")(UserNameTable);

View File

@@ -6,24 +6,24 @@ import classNames from "classnames";
type Props = { type Props = {
t: string => string, t: string => string,
username: string, membername: string,
removeUser: string => void removeMember: string => void
}; };
type State = {}; type State = {};
class RemoveUserButton extends React.Component<Props, State> { class RemoveMemberButton extends React.Component<Props, State> {
render() { render() {
const { t , username, removeUser} = this.props; const { t , membername, removeMember} = this.props;
return ( return (
<div className={classNames("is-pulled-right")}> <div className={classNames("is-pulled-right")}>
<DeleteButton <DeleteButton
label={t("remove-user-button.label")} label={t("remove-member-button.label")}
action={(event: Event) => { action={(event: Event) => {
event.preventDefault(); event.preventDefault();
removeUser(username); removeMember(membername);
}} }}
/> />
</div> </div>
@@ -31,4 +31,4 @@ class RemoveUserButton extends React.Component<Props, State> {
} }
} }
export default translate("groups")(RemoveUserButton); export default translate("groups")(RemoveMemberButton);

View File

@@ -1,10 +1,10 @@
// @flow // @flow
import React from "react"; import React from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import type { User } from "../../../users/types/User"; import type { Member } from "../../types/Group";
type Props = { type Props = {
member: User member: Member
}; };
export default class GroupMember extends React.Component<Props> { export default class GroupMember extends React.Component<Props> {
@@ -12,7 +12,7 @@ export default class GroupMember extends React.Component<Props> {
return <Link to={to}>{label}</Link>; return <Link to={to}>{label}</Link>;
} }
showName(to: any, member: User) { showName(to: any, member: Member) {
if (member._links.self) { if (member._links.self) {
return this.renderLink(to, member.name); return this.renderLink(to, member.name);
} else { } else {

View File

@@ -1,3 +1,4 @@
// @flow
import { apiClient } from "../../apiclient"; import { apiClient } from "../../apiclient";
import { isPending } from "../../modules/pending"; import { isPending } from "../../modules/pending";
import { getFailure } from "../../modules/failure"; import { getFailure } from "../../modules/failure";
@@ -5,7 +6,7 @@ import * as types from "../../modules/types";
import { combineReducers, Dispatch } from "redux"; import { combineReducers, Dispatch } from "redux";
import type { Action } from "../../types/Action"; import type { Action } from "../../types/Action";
import type { PagedCollection } from "../../types/Collection"; import type { PagedCollection } from "../../types/Collection";
import type { Groups } from "../types/Groups"; import type { Group } from "../types/Group";
export const FETCH_GROUPS = "scm/groups/FETCH_GROUPS"; export const FETCH_GROUPS = "scm/groups/FETCH_GROUPS";
export const FETCH_GROUPS_PENDING = `${FETCH_GROUPS}_${types.PENDING_SUFFIX}`; export const FETCH_GROUPS_PENDING = `${FETCH_GROUPS}_${types.PENDING_SUFFIX}`;
@@ -139,10 +140,11 @@ export function createGroup(group: Group, callback?: () => void) {
return apiClient return apiClient
.postWithContentType(GROUPS_URL, group, CONTENT_TYPE_GROUP) .postWithContentType(GROUPS_URL, group, CONTENT_TYPE_GROUP)
.then(() => { .then(() => {
dispatch(createGroupSuccess()) dispatch(createGroupSuccess());
if (callback) { if (callback) {
callback(); callback();
}}) }
})
.catch(error => { .catch(error => {
dispatch( dispatch(
createGroupFailure( createGroupFailure(
@@ -175,24 +177,29 @@ export function createGroupFailure(error: Error) {
export function createGroupReset() { export function createGroupReset() {
return { return {
type: CREATE_GROUP_RESET type: CREATE_GROUP_RESET
} };
} }
// modify group // modify group
export function modifyGroup(group: Group, callback?: () => void) { export function modifyGroup(group: Group, callback?: () => void) {
return function(dispatch: Dispatch) { return function(dispatch: Dispatch) {
dispatch(modifyGroupPending(group)); dispatch(modifyGroupPending(group));
return apiClient return apiClient
.putWithContentType(group._links.update.href, group, CONTENT_TYPE_GROUP) .putWithContentType(group._links.update.href, group, CONTENT_TYPE_GROUP)
.then(() => { .then(() => {
dispatch(modifyGroupSuccess(group)) dispatch(modifyGroupSuccess(group));
if (callback) { if (callback) {
callback() callback();
} }
}) })
.catch(cause => { .catch(cause => {
dispatch(modifyGroupFailure(group, new Error(`could not modify group ${group.name}: ${cause.message}`))) dispatch(
}) modifyGroupFailure(
group,
new Error(`could not modify group ${group.name}: ${cause.message}`)
)
);
});
}; };
} }
@@ -201,7 +208,7 @@ export function modifyGroupPending(group: Group): Action {
type: MODIFY_GROUP_PENDING, type: MODIFY_GROUP_PENDING,
payload: group, payload: group,
itemId: group.name itemId: group.name
} };
} }
export function modifyGroupSuccess(group: Group): Action { export function modifyGroupSuccess(group: Group): Action {
@@ -209,7 +216,7 @@ export function modifyGroupSuccess(group: Group): Action {
type: MODIFY_GROUP_SUCCESS, type: MODIFY_GROUP_SUCCESS,
payload: group, payload: group,
itemId: group.name itemId: group.name
} };
} }
export function modifyGroupFailure(group: Group, error: Error): Action { export function modifyGroupFailure(group: Group, error: Error): Action {
@@ -220,7 +227,7 @@ export function modifyGroupFailure(group: Group, error: Error): Action {
group group
}, },
itemId: group.name itemId: group.name
} };
} }
//delete group //delete group
@@ -274,7 +281,7 @@ export function deleteGroupFailure(group: Group, error: Error): Action {
//reducer //reducer
function extractGroupsByNames( function extractGroupsByNames(
groups: Groups[], groups: Group[],
groupNames: string[], groupNames: string[],
oldGroupsByNames: Object oldGroupsByNames: Object
) { ) {
@@ -332,14 +339,14 @@ function listReducer(state: any = {}, action: any = {}) {
}; };
// Delete single group actions // Delete single group actions
case DELETE_GROUP_SUCCESS: case DELETE_GROUP_SUCCESS:
const newGroupEntries = deleteGroupInEntries( const newGroupEntries = deleteGroupInEntries(
state.entries, state.entries,
action.payload.name action.payload.name
); );
return { return {
...state, ...state,
entries: newGroupEntries entries: newGroupEntries
}; };
default: default:
return state; return state;
} }
@@ -357,7 +364,7 @@ function byNamesReducer(state: any = {}, action: any = {}) {
}; };
case FETCH_GROUP_SUCCESS: case FETCH_GROUP_SUCCESS:
return reducerByName(state, action.payload.name, action.payload); return reducerByName(state, action.payload.name, action.payload);
case MODIFY_GROUP_SUCCESS: case MODIFY_GROUP_SUCCESS:
return reducerByName(state, action.payload.name, action.payload); return reducerByName(state, action.payload.name, action.payload);
case DELETE_GROUP_SUCCESS: case DELETE_GROUP_SUCCESS:
const newGroupByNames = deleteGroupInGroupsByNames( const newGroupByNames = deleteGroupInGroupsByNames(
@@ -436,11 +443,11 @@ export function getCreateGroupFailure(state: Object) {
} }
export function isModifyGroupPending(state: Object, name: string) { export function isModifyGroupPending(state: Object, name: string) {
return(isPending(state, MODIFY_GROUP, name)) return isPending(state, MODIFY_GROUP, name);
} }
export function getModifyGroupFailure(state: Object, name: string) { export function getModifyGroupFailure(state: Object, name: string) {
return(getFailure(state, MODIFY_GROUP, name)) return getFailure(state, MODIFY_GROUP, name);
} }
export function getGroupByName(state: Object, name: string) { export function getGroupByName(state: Object, name: string) {

View File

@@ -1,14 +1,18 @@
//@flow //@flow
import type { Collection } from "../../types/Collection";
import type { Links } from "../../types/hal"; import type { Links } from "../../types/hal";
import type { User } from "../../users/types/User";
export type Group = { export type Member = {
name: string,
_links: Links
};
export type Group = Collection & {
name: string, name: string,
description: string, description: string,
type: string, type: string,
members: string[], members: string[],
_links: Links,
_embedded: { _embedded: {
members: User[] members: Member[]
} }
}; };