More UI changes

This commit is contained in:
Janika Kefel
2018-12-19 13:28:04 +01:00
parent 3a62f81ba0
commit 4d181a574a
17 changed files with 1425 additions and 1358 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -1,56 +1,56 @@
// @flow
import React from "react";
import AvatarWrapper from "../repos/components/changesets/AvatarWrapper";
import type { Me } from "@scm-manager/ui-types";
import { MailLink } from "@scm-manager/ui-components";
import { compose } from "redux";
import { translate } from "react-i18next";
type Props = {
me: Me,
// Context props
t: string => string
};
type State = {};
class ProfileInfo extends React.Component<Props, State> {
render() {
const { me, t } = this.props;
return (
<>
<AvatarWrapper>
<div>
<figure className="media-left">
<p className="image is-64x64">
{
// TODO: add avatar
}
</p>
</figure>
</div>
</AvatarWrapper>
<table className="table">
<tbody>
<tr>
<td>{t("profile.username")}</td>
<td>{me.name}</td>
</tr>
<tr>
<td>{t("profile.displayName")}</td>
<td>{me.displayName}</td>
</tr>
<tr>
<td>{t("profile.mail")}</td>
<td>
<MailLink address={me.mail} />
</td>
</tr>
</tbody>
</table>
</>
);
}
}
export default compose(translate("commons"))(ProfileInfo);
// @flow
import React from "react";
import AvatarWrapper from "../repos/components/changesets/AvatarWrapper";
import type { Me } from "@scm-manager/ui-types";
import { MailLink } from "@scm-manager/ui-components";
import { compose } from "redux";
import { translate } from "react-i18next";
type Props = {
me: Me,
// Context props
t: string => string
};
type State = {};
class ProfileInfo extends React.Component<Props, State> {
render() {
const { me, t } = this.props;
return (
<>
<AvatarWrapper>
<div>
<figure className="media-left">
<p className="image is-64x64">
{
// TODO: add avatar
}
</p>
</figure>
</div>
</AvatarWrapper>
<table className="table">
<tbody>
<tr>
<td className="has-text-weight-semibold">{t("profile.username")}</td>
<td>{me.name}</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("profile.displayName")}</td>
<td>{me.displayName}</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("profile.mail")}</td>
<td>
<MailLink address={me.mail} />
</td>
</tr>
</tbody>
</table>
</>
);
}
}
export default compose(translate("commons"))(ProfileInfo);

View File

@@ -1,69 +1,69 @@
//@flow
import React from "react";
import type { Group } from "@scm-manager/ui-types";
import { translate } from "react-i18next";
import GroupMember from "./GroupMember";
import { DateFromNow } from "@scm-manager/ui-components";
type Props = {
group: Group,
t: string => string
};
class Details extends React.Component<Props> {
render() {
const { group, t } = this.props;
return (
<table className="table content">
<tbody>
<tr>
<td>{t("group.name")}</td>
<td>{group.name}</td>
</tr>
<tr>
<td>{t("group.description")}</td>
<td>{group.description}</td>
</tr>
<tr>
<td>{t("group.type")}</td>
<td>{group.type}</td>
</tr>
<tr>
<td>{t("group.creationDate")}</td>
<td>
<DateFromNow date={group.creationDate} />
</td>
</tr>
<tr>
<td>{t("group.lastModified")}</td>
<td>
<DateFromNow date={group.lastModified} />
</td>
</tr>
{this.renderMembers()}
</tbody>
</table>
);
}
renderMembers() {
if (this.props.group.members.length > 0) {
return (
<tr>
<td>
{this.props.t("group.members")}
<ul>
{this.props.group._embedded.members.map((member, index) => {
return <GroupMember key={index} member={member} />;
})}
</ul>
</td>
</tr>
);
} else {
return;
}
}
}
export default translate("groups")(Details);
//@flow
import React from "react";
import type { Group } from "@scm-manager/ui-types";
import { translate } from "react-i18next";
import GroupMember from "./GroupMember";
import { DateFromNow } from "@scm-manager/ui-components";
type Props = {
group: Group,
t: string => string
};
class Details extends React.Component<Props> {
render() {
const { group, t } = this.props;
return (
<table className="table content">
<tbody>
<tr>
<td className="has-text-weight-semibold">{t("group.name")}</td>
<td>{group.name}</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("group.description")}</td>
<td>{group.description}</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("group.type")}</td>
<td>{group.type}</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("group.creationDate")}</td>
<td>
<DateFromNow date={group.creationDate} />
</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("group.lastModified")}</td>
<td>
<DateFromNow date={group.lastModified} />
</td>
</tr>
{this.renderMembers()}
</tbody>
</table>
);
}
renderMembers() {
if (this.props.group.members.length > 0) {
return (
<tr>
<td>
{this.props.t("group.members")}
<ul>
{this.props.group._embedded.members.map((member, index) => {
return <GroupMember key={index} member={member} />;
})}
</ul>
</td>
</tr>
);
} else {
return;
}
}
}
export default translate("groups")(Details);

View File

@@ -1,25 +1,25 @@
// @flow
import React from "react";
import { Link } from "react-router-dom";
import type { Group } from "@scm-manager/ui-types";
type Props = {
group: Group
};
export default class GroupRow extends React.Component<Props> {
renderLink(to: string, label: string) {
return <Link to={to}>{label}</Link>;
}
render() {
const { group } = this.props;
const to = `/group/${group.name}`;
return (
<tr>
<td>{this.renderLink(to, group.name)}</td>
<td className="is-hidden-mobile">{group.description}</td>
</tr>
);
}
}
// @flow
import React from "react";
import { Link } from "react-router-dom";
import type { Group } from "@scm-manager/ui-types";
type Props = {
group: Group
};
export default class GroupRow extends React.Component<Props> {
renderLink(to: string, label: string) {
return <Link to={to}>{label}</Link>;
}
render() {
const { group } = this.props;
const to = `/group/${group.name}`;
return (
<tr>
<td>{this.renderLink(to, group.name)}</td>
<td className="is-hidden-mobile">{group.description}</td>
</tr>
);
}
}

View File

@@ -1,55 +1,55 @@
//@flow
import React from "react";
import type { Repository } from "@scm-manager/ui-types";
import { MailLink, DateFromNow } from "@scm-manager/ui-components";
import { translate } from "react-i18next";
type Props = {
repository: Repository,
// context props
t: string => string
};
class RepositoryDetailTable extends React.Component<Props> {
render() {
const { repository, t } = this.props;
return (
<table className="table">
<tbody>
<tr>
<td>{t("repository.name")}</td>
<td>{repository.name}</td>
</tr>
<tr>
<td>{t("repository.type")}</td>
<td>{repository.type}</td>
</tr>
<tr>
<td>{t("repository.contact")}</td>
<td>
<MailLink address={repository.contact} />
</td>
</tr>
<tr>
<td>{t("repository.description")}</td>
<td>{repository.description}</td>
</tr>
<tr>
<td>{t("repository.creationDate")}</td>
<td>
<DateFromNow date={repository.creationDate} />
</td>
</tr>
<tr>
<td>{t("repository.lastModified")}</td>
<td>
<DateFromNow date={repository.lastModified} />
</td>
</tr>
</tbody>
</table>
);
}
}
export default translate("repos")(RepositoryDetailTable);
//@flow
import React from "react";
import type { Repository } from "@scm-manager/ui-types";
import { MailLink, DateFromNow } from "@scm-manager/ui-components";
import { translate } from "react-i18next";
type Props = {
repository: Repository,
// context props
t: string => string
};
class RepositoryDetailTable extends React.Component<Props> {
render() {
const { repository, t } = this.props;
return (
<table className="table">
<tbody>
<tr>
<td className="has-text-weight-semibold">{t("repository.name")}</td>
<td>{repository.name}</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("repository.type")}</td>
<td>{repository.type}</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("repository.contact")}</td>
<td>
<MailLink address={repository.contact} />
</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("repository.description")}</td>
<td>{repository.description}</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("repository.creationDate")}</td>
<td>
<DateFromNow date={repository.creationDate} />
</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("repository.lastModified")}</td>
<td>
<DateFromNow date={repository.lastModified} />
</td>
</tr>
</tbody>
</table>
);
}
}
export default translate("repos")(RepositoryDetailTable);

View File

@@ -1,29 +1,30 @@
//@flow
import React from "react";
import type { Repository } from "@scm-manager/ui-types";
import RepositoryDetailTable from "./RepositoryDetailTable";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
type Props = {
repository: Repository
};
class RepositoryDetails extends React.Component<Props> {
render() {
const { repository } = this.props;
return (
<div>
<RepositoryDetailTable repository={repository} />
<div className="content">
<ExtensionPoint
name="repos.repository-details.information"
renderAll={true}
props={{ repository }}
/>
</div>
</div>
);
}
}
export default RepositoryDetails;
//@flow
import React from "react";
import type { Repository } from "@scm-manager/ui-types";
import RepositoryDetailTable from "./RepositoryDetailTable";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
type Props = {
repository: Repository
};
class RepositoryDetails extends React.Component<Props> {
render() {
const { repository } = this.props;
return (
<div>
<RepositoryDetailTable repository={repository} />
<hr />
<div className="content">
<ExtensionPoint
name="repos.repository-details.information"
renderAll={true}
props={{ repository }}
/>
</div>
</div>
);
}
}
export default RepositoryDetails;

View File

@@ -1,113 +1,108 @@
//@flow
import React from "react";
import { Link } from "react-router-dom";
import injectSheet from "react-jss";
import type { Repository } from "@scm-manager/ui-types";
import { DateFromNow } from "@scm-manager/ui-components";
import RepositoryEntryLink from "./RepositoryEntryLink";
import classNames from "classnames";
import RepositoryAvatar from "./RepositoryAvatar";
const styles = {
outer: {
position: "relative"
},
overlay: {
position: "absolute",
left: 0,
top: 0,
bottom: 0,
right: 0
},
inner: {
position: "relative",
pointerEvents: "none",
zIndex: 1
},
innerLink: {
pointerEvents: "all"
}
};
type Props = {
repository: Repository,
// context props
classes: any
};
class RepositoryEntry extends React.Component<Props> {
createLink = (repository: Repository) => {
return `/repo/${repository.namespace}/${repository.name}`;
};
renderChangesetsLink = (repository: Repository, repositoryLink: string) => {
if (repository._links["changesets"]) {
return (
<RepositoryEntryLink
iconClass="fa-code-branch"
to={repositoryLink + "/changesets"}
/>
);
}
return null;
};
renderSourcesLink = (repository: Repository, repositoryLink: string) => {
if (repository._links["sources"]) {
return (
<RepositoryEntryLink
iconClass="fa-code"
to={repositoryLink + "/sources"}
/>
);
}
return null;
};
renderModifyLink = (repository: Repository, repositoryLink: string) => {
if (repository._links["update"]) {
return (
<RepositoryEntryLink iconClass="fa-cog" to={repositoryLink + "/edit"} />
);
}
return null;
};
render() {
const { repository, classes } = this.props;
const repositoryLink = this.createLink(repository);
return (
<div className={classNames("box", "box-link-shadow", classes.outer)}>
<Link className={classes.overlay} to={repositoryLink} />
<article className={classNames("media", classes.inner)}>
<figure className="media-left">
<RepositoryAvatar repository={repository} />
</figure>
<div className="media-content">
<div className="content">
<p>
<strong>{repository.name}</strong>
<br />
{repository.description}
</p>
</div>
<nav className="level is-mobile">
<div className="level-left">
{this.renderChangesetsLink(repository, repositoryLink)}
{this.renderSourcesLink(repository, repositoryLink)}
{this.renderModifyLink(repository, repositoryLink)}
</div>
<div className="level-right is-hidden-mobile">
<small className="level-item">
<DateFromNow date={repository.creationDate} />
</small>
</div>
</nav>
</div>
</article>
</div>
);
}
}
export default injectSheet(styles)(RepositoryEntry);
//@flow
import React from "react";
import { Link } from "react-router-dom";
import injectSheet from "react-jss";
import type { Repository } from "@scm-manager/ui-types";
import { DateFromNow } from "@scm-manager/ui-components";
import RepositoryEntryLink from "./RepositoryEntryLink";
import classNames from "classnames";
import RepositoryAvatar from "./RepositoryAvatar";
const styles = {
overlay: {
position: "absolute",
height: "calc(120px - 1.5rem)",
width: "calc(50% - 3rem)"
},
inner: {
position: "relative",
pointerEvents: "none",
zIndex: 1
},
innerLink: {
pointerEvents: "all"
}
};
type Props = {
repository: Repository,
// context props
classes: any
};
class RepositoryEntry extends React.Component<Props> {
createLink = (repository: Repository) => {
return `/repo/${repository.namespace}/${repository.name}`;
};
renderChangesetsLink = (repository: Repository, repositoryLink: string) => {
if (repository._links["changesets"]) {
return (
<RepositoryEntryLink
iconClass="fa-code-branch fa-lg"
to={repositoryLink + "/changesets"}
/>
);
}
return null;
};
renderSourcesLink = (repository: Repository, repositoryLink: string) => {
if (repository._links["sources"]) {
return (
<RepositoryEntryLink
iconClass="fa-code fa-lg"
to={repositoryLink + "/sources"}
/>
);
}
return null;
};
renderModifyLink = (repository: Repository, repositoryLink: string) => {
if (repository._links["update"]) {
return (
<RepositoryEntryLink iconClass="fa-cog fa-lg" to={repositoryLink + "/edit"} />
);
}
return null;
};
render() {
const { repository, classes } = this.props;
const repositoryLink = this.createLink(repository);
return (
<div className={classNames("box", "box-link-shadow", "column", "is-half")}>
<Link className={classNames(classes.overlay)} to={repositoryLink} />
<article className={classNames("media", classes.inner)}>
<figure className="media-left">
<RepositoryAvatar repository={repository} />
</figure>
<div className="media-content">
<div className="content">
<p>
<strong>{repository.name}</strong>
<br />
{repository.description}
</p>
</div>
<nav className="level is-mobile">
<div className="level-left">
{this.renderChangesetsLink(repository, repositoryLink)}
{this.renderSourcesLink(repository, repositoryLink)}
{this.renderModifyLink(repository, repositoryLink)}
</div>
<div className="level-right is-hidden-mobile">
<small className="level-item">
<DateFromNow date={repository.creationDate} />
</small>
</div>
</nav>
</div>
</article>
</div>
);
}
}
export default injectSheet(styles)(RepositoryEntry);

View File

@@ -1,34 +1,35 @@
//@flow
import React from "react";
import { Link } from "react-router-dom";
import injectSheet from "react-jss";
import classNames from "classnames";
const styles = {
link: {
pointerEvents: "all"
}
};
type Props = {
to: string,
iconClass: string,
// context props
classes: any
};
class RepositoryEntryLink extends React.Component<Props> {
render() {
const { to, iconClass, classes } = this.props;
return (
<Link className={classNames("level-item", classes.link)} to={to}>
<span className="icon is-small">
<i className={classNames("fa", iconClass)} />
</span>
</Link>
);
}
}
export default injectSheet(styles)(RepositoryEntryLink);
//@flow
import React from "react";
import { Link } from "react-router-dom";
import injectSheet from "react-jss";
import classNames from "classnames";
const styles = {
link: {
pointerEvents: "all",
marginRight: "1.25rem !important"
}
};
type Props = {
to: string,
iconClass: string,
// context props
classes: any
};
class RepositoryEntryLink extends React.Component<Props> {
render() {
const { to, iconClass, classes } = this.props;
return (
<Link className={classNames("level-item", classes.link)} to={to}>
<span className="icon is-small">
<i className={classNames("fa", iconClass)} />
</span>
</Link>
);
}
}
export default injectSheet(styles)(RepositoryEntryLink);

View File

@@ -12,6 +12,12 @@ const styles = {
},
repoGroup: {
marginBottom: "1em"
},
wrapper: {
padding: "0 0.75rem"
},
clearfix: {
clear: "both"
}
};
@@ -59,7 +65,10 @@ class RepositoryGroupEntry extends React.Component<Props, State> {
</span>
</h2>
<hr />
<div className={classNames("columns","is-multiline", classes.wrapper)}>
{content}
</div>
<div className={classes.clearfix}></div>
</div>
);
}

View File

@@ -1,87 +1,90 @@
// @flow
import React from "react";
import type { Branch } from "@scm-manager/ui-types";
import DropDown from "../components/DropDown";
import { translate } from "react-i18next";
import injectSheet from "react-jss";
import { compose } from "redux";
import classNames from "classnames";
const styles = {
zeroflex: {
flexGrow: 0
}
};
type Props = {
branches: Branch[], // TODO: Use generics?
selected: (branch?: Branch) => void,
selectedBranch: string,
// context props
classes: Object,
t: string => string
};
type State = { selectedBranch?: Branch };
class BranchSelector extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {};
}
componentDidMount() {
this.props.branches
.filter(branch => branch.name === this.props.selectedBranch)
.forEach(branch => this.setState({ selectedBranch: branch }));
}
render() {
const { branches, classes, t } = this.props;
if (branches) {
return (
<div className="box field is-horizontal">
<div
className={classNames("field-label", "is-normal", classes.zeroflex)}
>
<label className="label">{t("branch-selector.label")}</label>
</div>
<div className="field-body">
<div className="field is-narrow">
<div className="control">
<DropDown
className="is-fullwidth"
options={branches.map(b => b.name)}
optionSelected={this.branchSelected}
preselectedOption={
this.state.selectedBranch
? this.state.selectedBranch.name
: ""
}
/>
</div>
</div>
</div>
</div>
);
} else {
return null;
}
}
branchSelected = (branchName: string) => {
const { branches, selected } = this.props;
const branch = branches.find(b => b.name === branchName);
selected(branch);
this.setState({ selectedBranch: branch });
};
}
export default compose(
injectSheet(styles),
translate("repos")
)(BranchSelector);
// @flow
import React from "react";
import type { Branch } from "@scm-manager/ui-types";
import DropDown from "../components/DropDown";
import { translate } from "react-i18next";
import injectSheet from "react-jss";
import { compose } from "redux";
import classNames from "classnames";
const styles = {
zeroflex: {
flexGrow: 0
},
wrapper: {
padding: "1rem 1.5rem 0.25rem 1.5rem",
border: "1px solid #eee",
borderRadius: "5px 5px 0 0"
}
};
type Props = {
branches: Branch[], // TODO: Use generics?
selected: (branch?: Branch) => void,
selectedBranch: string,
// context props
classes: Object,
t: string => string
};
type State = { selectedBranch?: Branch };
class BranchSelector extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {};
}
componentDidMount() {
this.props.branches
.filter(branch => branch.name === this.props.selectedBranch)
.forEach(branch => this.setState({ selectedBranch: branch }));
}
render() {
const { branches, classes, t } = this.props;
if (branches) {
return (
<div className={classNames("has-background-light field", "is-horizontal", classes.wrapper)}>
<div className={classNames("field-label", "is-normal", classes.zeroflex)}>
<label className="label">{t("branch-selector.label")}</label>
</div>
<div className="field-body">
<div className="field is-narrow">
<div className="control">
<DropDown
className="is-fullwidth"
options={branches.map(b => b.name)}
optionSelected={this.branchSelected}
preselectedOption={
this.state.selectedBranch
? this.state.selectedBranch.name
: ""
}
/>
</div>
</div>
</div>
</div>
);
} else {
return null;
}
}
branchSelected = (branchName: string) => {
const { branches, selected } = this.props;
const branch = branches.find(b => b.name === branchName);
selected(branch);
this.setState({ selectedBranch: branch });
};
}
export default compose(
injectSheet(styles),
translate("repos")
)(BranchSelector);

View File

@@ -1,122 +1,134 @@
// @flow
import React from "react";
import {translate} from "react-i18next";
import {Checkbox, InputField, SubmitButton} from "@scm-manager/ui-components";
import TypeSelector from "./TypeSelector";
import type {PermissionCollection, PermissionCreateEntry} from "@scm-manager/ui-types";
import * as validator from "./permissionValidation";
type Props = {
t: string => string,
createPermission: (permission: PermissionCreateEntry) => void,
loading: boolean,
currentPermissions: PermissionCollection
};
type State = {
name: string,
type: string,
groupPermission: boolean,
valid: boolean
};
class CreatePermissionForm extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
name: "",
type: "READ",
groupPermission: false,
valid: true
};
}
render() {
const { t, loading } = this.props;
const { name, type, groupPermission } = this.state;
return (
<div>
<h2 className="subtitle">
{t("permission.add-permission.add-permission-heading")}
</h2>
<form onSubmit={this.submit}>
<InputField
label={t("permission.name")}
value={name ? name : ""}
onChange={this.handleNameChange}
validationError={!this.state.valid}
errorMessage={t("permission.add-permission.name-input-invalid")}
helpText={t("permission.help.nameHelpText")}
/>
<Checkbox
label={t("permission.group-permission")}
checked={groupPermission ? groupPermission : false}
onChange={this.handleGroupPermissionChange}
helpText={t("permission.help.groupPermissionHelpText")}
/>
<TypeSelector
label={t("permission.type")}
helpText={t("permission.help.typeHelpText")}
handleTypeChange={this.handleTypeChange}
type={type ? type : "READ"}
/>
<SubmitButton
label={t("permission.add-permission.submit-button")}
loading={loading}
disabled={!this.state.valid || this.state.name === ""}
/>
</form>
</div>
);
}
submit = e => {
this.props.createPermission({
name: this.state.name,
type: this.state.type,
groupPermission: this.state.groupPermission
});
this.removeState();
e.preventDefault();
};
removeState = () => {
this.setState({
name: "",
type: "READ",
groupPermission: false,
valid: true
});
};
handleTypeChange = (type: string) => {
this.setState({
type: type
});
};
handleNameChange = (name: string) => {
this.setState({
name: name,
valid: validator.isPermissionValid(
name,
this.state.groupPermission,
this.props.currentPermissions
)
});
};
handleGroupPermissionChange = (groupPermission: boolean) => {
this.setState({
groupPermission: groupPermission,
valid: validator.isPermissionValid(
this.state.name,
groupPermission,
this.props.currentPermissions
)
});
};
}
export default translate("repos")(CreatePermissionForm);
// @flow
import React from "react";
import {translate} from "react-i18next";
import {Checkbox, InputField, SubmitButton} from "@scm-manager/ui-components";
import TypeSelector from "./TypeSelector";
import type {PermissionCollection, PermissionCreateEntry} from "@scm-manager/ui-types";
import * as validator from "./permissionValidation";
type Props = {
t: string => string,
createPermission: (permission: PermissionCreateEntry) => void,
loading: boolean,
currentPermissions: PermissionCollection
};
type State = {
name: string,
type: string,
groupPermission: boolean,
valid: boolean
};
class CreatePermissionForm extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
name: "",
type: "READ",
groupPermission: false,
valid: true
};
}
render() {
const { t, loading } = this.props;
const { name, type, groupPermission } = this.state;
return (
<div>
<hr />
<h2 className="subtitle">
{t("permission.add-permission.add-permission-heading")}
</h2>
<form onSubmit={this.submit}>
<div class="columns">
<div class="column is-three-quarters">
<InputField
label={t("permission.name")}
value={name ? name : ""}
onChange={this.handleNameChange}
validationError={!this.state.valid}
errorMessage={t("permission.add-permission.name-input-invalid")}
helpText={t("permission.help.nameHelpText")}
/>
<Checkbox
label={t("permission.group-permission")}
checked={groupPermission ? groupPermission : false}
onChange={this.handleGroupPermissionChange}
helpText={t("permission.help.groupPermissionHelpText")}
/>
</div>
<div class="column is-one-quarter">
<TypeSelector
label={t("permission.type")}
helpText={t("permission.help.typeHelpText")}
handleTypeChange={this.handleTypeChange}
type={type ? type : "READ"}
/>
</div>
</div>
<div class="columns">
<div class="column">
<SubmitButton
label={t("permission.add-permission.submit-button")}
loading={loading}
disabled={!this.state.valid || this.state.name === ""}
/>
</div>
</div>
</form>
</div>
);
}
submit = e => {
this.props.createPermission({
name: this.state.name,
type: this.state.type,
groupPermission: this.state.groupPermission
});
this.removeState();
e.preventDefault();
};
removeState = () => {
this.setState({
name: "",
type: "READ",
groupPermission: false,
valid: true
});
};
handleTypeChange = (type: string) => {
this.setState({
type: type
});
};
handleNameChange = (name: string) => {
this.setState({
name: name,
valid: validator.isPermissionValid(
name,
this.state.groupPermission,
this.props.currentPermissions
)
});
};
handleGroupPermissionChange = (groupPermission: boolean) => {
this.setState({
groupPermission: groupPermission,
valid: validator.isPermissionValid(
this.state.name,
groupPermission,
this.props.currentPermissions
)
});
};
}
export default translate("repos")(CreatePermissionForm);

View File

@@ -1,42 +1,42 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
import { Select } from "@scm-manager/ui-components";
type Props = {
t: string => string,
handleTypeChange: string => void,
type: string,
label?: string,
helpText?: string,
loading?: boolean
};
class TypeSelector extends React.Component<Props> {
render() {
const { type, handleTypeChange, loading, label, helpText } = this.props;
const types = ["READ", "OWNER", "WRITE"];
return (
<Select
onChange={handleTypeChange}
value={type ? type : "READ"}
options={this.createSelectOptions(types)}
loading={loading}
label={label}
helpText={helpText}
/>
);
}
createSelectOptions(types: string[]) {
return types.map(type => {
return {
label: type,
value: type
};
});
}
}
export default translate("repos")(TypeSelector);
// @flow
import React from "react";
import { translate } from "react-i18next";
import { Select } from "@scm-manager/ui-components";
type Props = {
t: string => string,
handleTypeChange: string => void,
type: string,
label?: string,
helpText?: string,
loading?: boolean
};
class TypeSelector extends React.Component<Props> {
render() {
const { type, handleTypeChange, loading, label, helpText } = this.props;
const types = ["READ", "OWNER", "WRITE"];
return (
<Select
onChange={handleTypeChange}
value={type ? type : "READ"}
options={this.createSelectOptions(types)}
loading={loading}
label={label}
helpText={helpText}
/>
);
}
createSelectOptions(types: string[]) {
return types.map(type => {
return {
label: type,
value: type
};
});
}
}
export default translate("repos")(TypeSelector);

View File

@@ -1,209 +1,209 @@
//@flow
import React from "react";
import { connect } from "react-redux";
import { translate } from "react-i18next";
import {
fetchPermissions,
getFetchPermissionsFailure,
isFetchPermissionsPending,
getPermissionsOfRepo,
hasCreatePermission,
createPermission,
isCreatePermissionPending,
getCreatePermissionFailure,
createPermissionReset,
getDeletePermissionsFailure,
getModifyPermissionsFailure,
modifyPermissionReset,
deletePermissionReset
} from "../modules/permissions";
import { Loading, ErrorPage } from "@scm-manager/ui-components";
import type {
Permission,
PermissionCollection,
PermissionCreateEntry
} from "@scm-manager/ui-types";
import SinglePermission from "./SinglePermission";
import CreatePermissionForm from "../components/CreatePermissionForm";
import type { History } from "history";
import { getPermissionsLink } from "../../modules/repos";
type Props = {
namespace: string,
repoName: string,
loading: boolean,
error: Error,
permissions: PermissionCollection,
hasPermissionToCreate: boolean,
loadingCreatePermission: boolean,
permissionsLink: string,
//dispatch functions
fetchPermissions: (link: string, namespace: string, repoName: string) => void,
createPermission: (
link: string,
permission: PermissionCreateEntry,
namespace: string,
repoName: string,
callback?: () => void
) => void,
createPermissionReset: (string, string) => void,
modifyPermissionReset: (string, string) => void,
deletePermissionReset: (string, string) => void,
// context props
t: string => string,
match: any,
history: History
};
class Permissions extends React.Component<Props> {
componentDidMount() {
const {
fetchPermissions,
namespace,
repoName,
modifyPermissionReset,
createPermissionReset,
deletePermissionReset,
permissionsLink
} = this.props;
createPermissionReset(namespace, repoName);
modifyPermissionReset(namespace, repoName);
deletePermissionReset(namespace, repoName);
fetchPermissions(permissionsLink, namespace, repoName);
}
createPermission = (permission: Permission) => {
this.props.createPermission(
this.props.permissionsLink,
permission,
this.props.namespace,
this.props.repoName
);
};
render() {
const {
loading,
error,
permissions,
t,
namespace,
repoName,
loadingCreatePermission,
hasPermissionToCreate
} = this.props;
if (error) {
return (
<ErrorPage
title={t("permission.error-title")}
subtitle={t("permission.error-subtitle")}
error={error}
/>
);
}
if (loading || !permissions) {
return <Loading />;
}
const createPermissionForm = hasPermissionToCreate ? (
<CreatePermissionForm
createPermission={permission => this.createPermission(permission)}
loading={loadingCreatePermission}
currentPermissions={permissions}
/>
) : null;
return (
<div>
<table className="table is-hoverable is-fullwidth">
<thead>
<tr>
<th>{t("permission.name")}</th>
<th className="is-hidden-mobile">
{t("permission.group-permission")}
</th>
<th>{t("permission.type")}</th>
<th />
</tr>
</thead>
<tbody>
{permissions.map(permission => {
return (
<SinglePermission
key={permission.name + permission.groupPermission.toString()}
namespace={namespace}
repoName={repoName}
permission={permission}
/>
);
})}
</tbody>
</table>
{createPermissionForm}
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
const namespace = ownProps.namespace;
const repoName = ownProps.repoName;
const error =
getFetchPermissionsFailure(state, namespace, repoName) ||
getCreatePermissionFailure(state, namespace, repoName) ||
getDeletePermissionsFailure(state, namespace, repoName) ||
getModifyPermissionsFailure(state, namespace, repoName);
const loading = isFetchPermissionsPending(state, namespace, repoName);
const permissions = getPermissionsOfRepo(state, namespace, repoName);
const loadingCreatePermission = isCreatePermissionPending(
state,
namespace,
repoName
);
const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName);
const permissionsLink = getPermissionsLink(state, namespace, repoName);
return {
namespace,
repoName,
error,
loading,
permissions,
hasPermissionToCreate,
loadingCreatePermission,
permissionsLink
};
};
const mapDispatchToProps = dispatch => {
return {
fetchPermissions: (link: string, namespace: string, repoName: string) => {
dispatch(fetchPermissions(link, namespace, repoName));
},
createPermission: (
link: string,
permission: PermissionCreateEntry,
namespace: string,
repoName: string,
callback?: () => void
) => {
dispatch(createPermission(link, permission, namespace, repoName, callback));
},
createPermissionReset: (namespace: string, repoName: string) => {
dispatch(createPermissionReset(namespace, repoName));
},
modifyPermissionReset: (namespace: string, repoName: string) => {
dispatch(modifyPermissionReset(namespace, repoName));
},
deletePermissionReset: (namespace: string, repoName: string) => {
dispatch(deletePermissionReset(namespace, repoName));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(translate("repos")(Permissions));
//@flow
import React from "react";
import { connect } from "react-redux";
import { translate } from "react-i18next";
import {
fetchPermissions,
getFetchPermissionsFailure,
isFetchPermissionsPending,
getPermissionsOfRepo,
hasCreatePermission,
createPermission,
isCreatePermissionPending,
getCreatePermissionFailure,
createPermissionReset,
getDeletePermissionsFailure,
getModifyPermissionsFailure,
modifyPermissionReset,
deletePermissionReset
} from "../modules/permissions";
import { Loading, ErrorPage } from "@scm-manager/ui-components";
import type {
Permission,
PermissionCollection,
PermissionCreateEntry
} from "@scm-manager/ui-types";
import SinglePermission from "./SinglePermission";
import CreatePermissionForm from "../components/CreatePermissionForm";
import type { History } from "history";
import { getPermissionsLink } from "../../modules/repos";
type Props = {
namespace: string,
repoName: string,
loading: boolean,
error: Error,
permissions: PermissionCollection,
hasPermissionToCreate: boolean,
loadingCreatePermission: boolean,
permissionsLink: string,
//dispatch functions
fetchPermissions: (link: string, namespace: string, repoName: string) => void,
createPermission: (
link: string,
permission: PermissionCreateEntry,
namespace: string,
repoName: string,
callback?: () => void
) => void,
createPermissionReset: (string, string) => void,
modifyPermissionReset: (string, string) => void,
deletePermissionReset: (string, string) => void,
// context props
t: string => string,
match: any,
history: History
};
class Permissions extends React.Component<Props> {
componentDidMount() {
const {
fetchPermissions,
namespace,
repoName,
modifyPermissionReset,
createPermissionReset,
deletePermissionReset,
permissionsLink
} = this.props;
createPermissionReset(namespace, repoName);
modifyPermissionReset(namespace, repoName);
deletePermissionReset(namespace, repoName);
fetchPermissions(permissionsLink, namespace, repoName);
}
createPermission = (permission: Permission) => {
this.props.createPermission(
this.props.permissionsLink,
permission,
this.props.namespace,
this.props.repoName
);
};
render() {
const {
loading,
error,
permissions,
t,
namespace,
repoName,
loadingCreatePermission,
hasPermissionToCreate
} = this.props;
if (error) {
return (
<ErrorPage
title={t("permission.error-title")}
subtitle={t("permission.error-subtitle")}
error={error}
/>
);
}
if (loading || !permissions) {
return <Loading />;
}
const createPermissionForm = hasPermissionToCreate ? (
<CreatePermissionForm
createPermission={permission => this.createPermission(permission)}
loading={loadingCreatePermission}
currentPermissions={permissions}
/>
) : null;
return (
<div>
<table className="has-background-light table is-hoverable is-fullwidth">
<thead>
<tr>
<th>{t("permission.name")}</th>
<th className="is-hidden-mobile">
{t("permission.group-permission")}
</th>
<th>{t("permission.type")}</th>
<th />
</tr>
</thead>
<tbody>
{permissions.map(permission => {
return (
<SinglePermission
key={permission.name + permission.groupPermission.toString()}
namespace={namespace}
repoName={repoName}
permission={permission}
/>
);
})}
</tbody>
</table>
{createPermissionForm}
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
const namespace = ownProps.namespace;
const repoName = ownProps.repoName;
const error =
getFetchPermissionsFailure(state, namespace, repoName) ||
getCreatePermissionFailure(state, namespace, repoName) ||
getDeletePermissionsFailure(state, namespace, repoName) ||
getModifyPermissionsFailure(state, namespace, repoName);
const loading = isFetchPermissionsPending(state, namespace, repoName);
const permissions = getPermissionsOfRepo(state, namespace, repoName);
const loadingCreatePermission = isCreatePermissionPending(
state,
namespace,
repoName
);
const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName);
const permissionsLink = getPermissionsLink(state, namespace, repoName);
return {
namespace,
repoName,
error,
loading,
permissions,
hasPermissionToCreate,
loadingCreatePermission,
permissionsLink
};
};
const mapDispatchToProps = dispatch => {
return {
fetchPermissions: (link: string, namespace: string, repoName: string) => {
dispatch(fetchPermissions(link, namespace, repoName));
},
createPermission: (
link: string,
permission: PermissionCreateEntry,
namespace: string,
repoName: string,
callback?: () => void
) => {
dispatch(createPermission(link, permission, namespace, repoName, callback));
},
createPermissionReset: (namespace: string, repoName: string) => {
dispatch(createPermissionReset(namespace, repoName));
},
modifyPermissionReset: (namespace: string, repoName: string) => {
dispatch(modifyPermissionReset(namespace, repoName));
},
deletePermissionReset: (namespace: string, repoName: string) => {
dispatch(deletePermissionReset(namespace, repoName));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(translate("repos")(Permissions));

View File

@@ -1,176 +1,176 @@
// @flow
import React from "react";
import type { Permission } from "@scm-manager/ui-types";
import { translate } from "react-i18next";
import {
modifyPermission,
isModifyPermissionPending,
deletePermission,
isDeletePermissionPending
} from "../modules/permissions";
import { connect } from "react-redux";
import type { History } from "history";
import { Checkbox } from "@scm-manager/ui-components";
import DeletePermissionButton from "../components/buttons/DeletePermissionButton";
import TypeSelector from "../components/TypeSelector";
type Props = {
submitForm: Permission => void,
modifyPermission: (Permission, string, string) => void,
permission: Permission,
t: string => string,
namespace: string,
repoName: string,
match: any,
history: History,
loading: boolean,
deletePermission: (Permission, string, string) => void,
deleteLoading: boolean
};
type State = {
permission: Permission
};
class SinglePermission extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
permission: {
name: "",
type: "READ",
groupPermission: false,
_links: {}
}
};
}
componentDidMount() {
const { permission } = this.props;
if (permission) {
this.setState({
permission: {
name: permission.name,
type: permission.type,
groupPermission: permission.groupPermission,
_links: permission._links
}
});
}
}
deletePermission = () => {
this.props.deletePermission(
this.props.permission,
this.props.namespace,
this.props.repoName
);
};
render() {
const { permission } = this.state;
const { loading, namespace, repoName } = this.props;
const typeSelector =
this.props.permission._links && this.props.permission._links.update ? (
<td>
<TypeSelector
handleTypeChange={this.handleTypeChange}
type={permission.type ? permission.type : "READ"}
loading={loading}
/>
</td>
) : (
<td>{permission.type}</td>
);
return (
<tr>
<td>{permission.name}</td>
<td>
<Checkbox checked={permission ? permission.groupPermission : false} />
</td>
{typeSelector}
<td>
<DeletePermissionButton
permission={permission}
namespace={namespace}
repoName={repoName}
deletePermission={this.deletePermission}
loading={this.props.deleteLoading}
/>
</td>
</tr>
);
}
handleTypeChange = (type: string) => {
this.setState({
permission: {
...this.state.permission,
type: type
}
});
this.modifyPermission(type);
};
modifyPermission = (type: string) => {
let permission = this.state.permission;
permission.type = type;
this.props.modifyPermission(
permission,
this.props.namespace,
this.props.repoName
);
};
createSelectOptions(types: string[]) {
return types.map(type => {
return {
label: type,
value: type
};
});
}
}
const mapStateToProps = (state, ownProps) => {
const permission = ownProps.permission;
const loading = isModifyPermissionPending(
state,
ownProps.namespace,
ownProps.repoName,
permission
);
const deleteLoading = isDeletePermissionPending(
state,
ownProps.namespace,
ownProps.repoName,
permission
);
return { loading, deleteLoading };
};
const mapDispatchToProps = dispatch => {
return {
modifyPermission: (
permission: Permission,
namespace: string,
repoName: string
) => {
dispatch(modifyPermission(permission, namespace, repoName));
},
deletePermission: (
permission: Permission,
namespace: string,
repoName: string
) => {
dispatch(deletePermission(permission, namespace, repoName));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(translate("repos")(SinglePermission));
// @flow
import React from "react";
import type { Permission } from "@scm-manager/ui-types";
import { translate } from "react-i18next";
import {
modifyPermission,
isModifyPermissionPending,
deletePermission,
isDeletePermissionPending
} from "../modules/permissions";
import { connect } from "react-redux";
import type { History } from "history";
import { Checkbox } from "@scm-manager/ui-components";
import DeletePermissionButton from "../components/buttons/DeletePermissionButton";
import TypeSelector from "../components/TypeSelector";
type Props = {
submitForm: Permission => void,
modifyPermission: (Permission, string, string) => void,
permission: Permission,
t: string => string,
namespace: string,
repoName: string,
match: any,
history: History,
loading: boolean,
deletePermission: (Permission, string, string) => void,
deleteLoading: boolean
};
type State = {
permission: Permission
};
class SinglePermission extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
permission: {
name: "",
type: "READ",
groupPermission: false,
_links: {}
}
};
}
componentDidMount() {
const { permission } = this.props;
if (permission) {
this.setState({
permission: {
name: permission.name,
type: permission.type,
groupPermission: permission.groupPermission,
_links: permission._links
}
});
}
}
deletePermission = () => {
this.props.deletePermission(
this.props.permission,
this.props.namespace,
this.props.repoName
);
};
render() {
const { permission } = this.state;
const { loading, namespace, repoName } = this.props;
const typeSelector =
this.props.permission._links && this.props.permission._links.update ? (
<td>
<TypeSelector
handleTypeChange={this.handleTypeChange}
type={permission.type ? permission.type : "READ"}
loading={loading}
/>
</td>
) : (
<td>{permission.type}</td>
);
return (
<tr>
<td>{permission.name}</td>
<td>
<Checkbox checked={permission ? permission.groupPermission : false} />
</td>
{typeSelector}
<td>
<DeletePermissionButton
permission={permission}
namespace={namespace}
repoName={repoName}
deletePermission={this.deletePermission}
loading={this.props.deleteLoading}
/>
</td>
</tr>
);
}
handleTypeChange = (type: string) => {
this.setState({
permission: {
...this.state.permission,
type: type
}
});
this.modifyPermission(type);
};
modifyPermission = (type: string) => {
let permission = this.state.permission;
permission.type = type;
this.props.modifyPermission(
permission,
this.props.namespace,
this.props.repoName
);
};
createSelectOptions(types: string[]) {
return types.map(type => {
return {
label: type,
value: type
};
});
}
}
const mapStateToProps = (state, ownProps) => {
const permission = ownProps.permission;
const loading = isModifyPermissionPending(
state,
ownProps.namespace,
ownProps.repoName,
permission
);
const deleteLoading = isDeletePermissionPending(
state,
ownProps.namespace,
ownProps.repoName,
permission
);
return { loading, deleteLoading };
};
const mapDispatchToProps = dispatch => {
return {
modifyPermission: (
permission: Permission,
namespace: string,
repoName: string
) => {
dispatch(modifyPermission(permission, namespace, repoName));
},
deletePermission: (
permission: Permission,
namespace: string,
repoName: string
) => {
dispatch(deletePermission(permission, namespace, repoName));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(translate("repos")(SinglePermission));

View File

@@ -1,184 +1,198 @@
// @flow
import React from "react";
import { translate } from "react-i18next";
import type { User } from "@scm-manager/ui-types";
import {
Checkbox,
InputField,
PasswordConfirmation,
SubmitButton,
validation as validator
} from "@scm-manager/ui-components";
import * as userValidator from "./userValidation";
type Props = {
submitForm: User => void,
user?: User,
loading?: boolean,
t: string => string
};
type State = {
user: User,
mailValidationError: boolean,
nameValidationError: boolean,
displayNameValidationError: boolean
};
class UserForm extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
user: {
name: "",
displayName: "",
mail: "",
password: "",
admin: false,
active: true,
_links: {}
},
mailValidationError: false,
displayNameValidationError: false,
nameValidationError: false
};
}
componentDidMount() {
const { user } = this.props;
if (user) {
this.setState({ user: { ...user } });
}
}
isFalsy(value) {
if (!value) {
return true;
}
return false;
}
isValid = () => {
const user = this.state.user;
const passwordValid = this.props.user ? !this.isFalsy(user.password) : true;
return !(
this.state.nameValidationError ||
this.state.mailValidationError ||
this.state.displayNameValidationError ||
this.isFalsy(user.name) ||
this.isFalsy(user.displayName) ||
this.isFalsy(user.mail) ||
passwordValid
);
};
submit = (event: Event) => {
event.preventDefault();
if (this.isValid()) {
this.props.submitForm(this.state.user);
}
};
render() {
const { loading, t } = this.props;
const user = this.state.user;
let nameField = null;
let passwordChangeField = null;
if (!this.props.user) {
nameField = (
<InputField
label={t("user.name")}
onChange={this.handleUsernameChange}
value={user ? user.name : ""}
validationError={this.state.nameValidationError}
errorMessage={t("validation.name-invalid")}
helpText={t("help.usernameHelpText")}
/>
);
passwordChangeField = (
<PasswordConfirmation passwordChanged={this.handlePasswordChange} />
);
}
return (
<form onSubmit={this.submit}>
{nameField}
<InputField
label={t("user.displayName")}
onChange={this.handleDisplayNameChange}
value={user ? user.displayName : ""}
validationError={this.state.displayNameValidationError}
errorMessage={t("validation.displayname-invalid")}
helpText={t("help.displayNameHelpText")}
/>
<InputField
label={t("user.mail")}
onChange={this.handleEmailChange}
value={user ? user.mail : ""}
validationError={this.state.mailValidationError}
errorMessage={t("validation.mail-invalid")}
helpText={t("help.mailHelpText")}
/>
{passwordChangeField}
<Checkbox
label={t("user.admin")}
onChange={this.handleAdminChange}
checked={user ? user.admin : false}
helpText={t("help.adminHelpText")}
/>
<Checkbox
label={t("user.active")}
onChange={this.handleActiveChange}
checked={user ? user.active : false}
helpText={t("help.activeHelpText")}
/>
<SubmitButton
disabled={!this.isValid()}
loading={loading}
label={t("user-form.submit")}
/>
</form>
);
}
handleUsernameChange = (name: string) => {
this.setState({
nameValidationError: !validator.isNameValid(name),
user: { ...this.state.user, name }
});
};
handleDisplayNameChange = (displayName: string) => {
this.setState({
displayNameValidationError: !userValidator.isDisplayNameValid(
displayName
),
user: { ...this.state.user, displayName }
});
};
handleEmailChange = (mail: string) => {
this.setState({
mailValidationError: !validator.isMailValid(mail),
user: { ...this.state.user, mail }
});
};
handlePasswordChange = (password: string) => {
this.setState({
user: { ...this.state.user, password }
});
};
handleAdminChange = (admin: boolean) => {
this.setState({ user: { ...this.state.user, admin } });
};
handleActiveChange = (active: boolean) => {
this.setState({ user: { ...this.state.user, active } });
};
}
export default translate("users")(UserForm);
// @flow
import React from "react";
import { translate } from "react-i18next";
import type { User } from "@scm-manager/ui-types";
import {
Checkbox,
InputField,
PasswordConfirmation,
SubmitButton,
validation as validator
} from "@scm-manager/ui-components";
import * as userValidator from "./userValidation";
type Props = {
submitForm: User => void,
user?: User,
loading?: boolean,
t: string => string
};
type State = {
user: User,
mailValidationError: boolean,
nameValidationError: boolean,
displayNameValidationError: boolean
};
class UserForm extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
user: {
name: "",
displayName: "",
mail: "",
password: "",
admin: false,
active: true,
_links: {}
},
mailValidationError: false,
displayNameValidationError: false,
nameValidationError: false
};
}
componentDidMount() {
const { user } = this.props;
if (user) {
this.setState({ user: { ...user } });
}
}
isFalsy(value) {
if (!value) {
return true;
}
return false;
}
isValid = () => {
const user = this.state.user;
const passwordValid = this.props.user ? !this.isFalsy(user.password) : true;
return !(
this.state.nameValidationError ||
this.state.mailValidationError ||
this.state.displayNameValidationError ||
this.isFalsy(user.name) ||
this.isFalsy(user.displayName) ||
this.isFalsy(user.mail) ||
passwordValid
);
};
submit = (event: Event) => {
event.preventDefault();
if (this.isValid()) {
this.props.submitForm(this.state.user);
}
};
render() {
const { loading, t } = this.props;
const user = this.state.user;
let nameField = null;
let passwordChangeField = null;
if (!this.props.user) {
nameField = (
<InputField
label={t("user.name")}
onChange={this.handleUsernameChange}
value={user ? user.name : ""}
validationError={this.state.nameValidationError}
errorMessage={t("validation.name-invalid")}
helpText={t("help.usernameHelpText")}
/>
);
passwordChangeField = (
<PasswordConfirmation passwordChanged={this.handlePasswordChange} />
);
}
return (
<form onSubmit={this.submit}>
<div class="columns">
<div class="column is-half">
{nameField}
<InputField
label={t("user.displayName")}
onChange={this.handleDisplayNameChange}
value={user ? user.displayName : ""}
validationError={this.state.displayNameValidationError}
errorMessage={t("validation.displayname-invalid")}
helpText={t("help.displayNameHelpText")}
/>
</div>
<div class="column is-half">
<InputField
label={t("user.mail")}
onChange={this.handleEmailChange}
value={user ? user.mail : ""}
validationError={this.state.mailValidationError}
errorMessage={t("validation.mail-invalid")}
helpText={t("help.mailHelpText")}
/>
</div>
</div>
<div class="columns">
<div class="column">
{passwordChangeField}
<Checkbox
label={t("user.admin")}
onChange={this.handleAdminChange}
checked={user ? user.admin : false}
helpText={t("help.adminHelpText")}
/>
<Checkbox
label={t("user.active")}
onChange={this.handleActiveChange}
checked={user ? user.active : false}
helpText={t("help.activeHelpText")}
/>
</div>
</div>
<div class="columns">
<div class="column">
<SubmitButton
disabled={!this.isValid()}
loading={loading}
label={t("user-form.submit")}
/>
</div>
</div>
</form>
);
}
handleUsernameChange = (name: string) => {
this.setState({
nameValidationError: !validator.isNameValid(name),
user: { ...this.state.user, name }
});
};
handleDisplayNameChange = (displayName: string) => {
this.setState({
displayNameValidationError: !userValidator.isDisplayNameValid(
displayName
),
user: { ...this.state.user, displayName }
});
};
handleEmailChange = (mail: string) => {
this.setState({
mailValidationError: !validator.isMailValid(mail),
user: { ...this.state.user, mail }
});
};
handlePasswordChange = (password: string) => {
this.setState({
user: { ...this.state.user, password }
});
};
handleAdminChange = (admin: boolean) => {
this.setState({ user: { ...this.state.user, admin } });
};
handleActiveChange = (active: boolean) => {
this.setState({ user: { ...this.state.user, active } });
};
}
export default translate("users")(UserForm);

View File

@@ -1,66 +1,66 @@
//@flow
import React from "react";
import type { User } from "@scm-manager/ui-types";
import { translate } from "react-i18next";
import { Checkbox, MailLink, DateFromNow } from "@scm-manager/ui-components";
type Props = {
user: User,
t: string => string
};
class Details extends React.Component<Props> {
render() {
const { user, t } = this.props;
return (
<table className="table">
<tbody>
<tr>
<td>{t("user.name")}</td>
<td>{user.name}</td>
</tr>
<tr>
<td>{t("user.displayName")}</td>
<td>{user.displayName}</td>
</tr>
<tr>
<td>{t("user.mail")}</td>
<td>
<MailLink address={user.mail} />
</td>
</tr>
<tr>
<td>{t("user.admin")}</td>
<td>
<Checkbox checked={user.admin} />
</td>
</tr>
<tr>
<td>{t("user.active")}</td>
<td>
<Checkbox checked={user.active} />
</td>
</tr>
<tr>
<td>{t("user.type")}</td>
<td>{user.type}</td>
</tr>
<tr>
<td>{t("user.creationDate")}</td>
<td>
<DateFromNow date={user.creationDate} />
</td>
</tr>
<tr>
<td>{t("user.lastModified")}</td>
<td>
<DateFromNow date={user.lastModified} />
</td>
</tr>
</tbody>
</table>
);
}
}
export default translate("users")(Details);
//@flow
import React from "react";
import type { User } from "@scm-manager/ui-types";
import { translate } from "react-i18next";
import { Checkbox, MailLink, DateFromNow } from "@scm-manager/ui-components";
type Props = {
user: User,
t: string => string
};
class Details extends React.Component<Props> {
render() {
const { user, t } = this.props;
return (
<table className="table">
<tbody>
<tr>
<td className="has-text-weight-semibold">{t("user.name")}</td>
<td>{user.name}</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("user.displayName")}</td>
<td>{user.displayName}</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("user.mail")}</td>
<td>
<MailLink address={user.mail} />
</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("user.admin")}</td>
<td>
<Checkbox checked={user.admin} />
</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("user.active")}</td>
<td>
<Checkbox checked={user.active} />
</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("user.type")}</td>
<td>{user.type}</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("user.creationDate")}</td>
<td>
<DateFromNow date={user.creationDate} />
</td>
</tr>
<tr>
<td className="has-text-weight-semibold">{t("user.lastModified")}</td>
<td>
<DateFromNow date={user.lastModified} />
</td>
</tr>
</tbody>
</table>
);
}
}
export default translate("users")(Details);

View File

@@ -61,6 +61,123 @@ $fa-font-path: "webfonts";
// NEW STYLES
//typography
.subtitle {
color: #666;
}
.has-border-white {
border-color: #fff !important;
}
// buttons
.button{
padding-left: 1.5em;
padding-right: 1.5em;
height:2.5rem;
&.is-primary {
background-color: $mint;
}
}
// multiline Columns
.columns.is-multiline {
.column.is-half{
width: calc(50% - .75rem);
&:nth-child(odd){
margin-right: 1.5rem;
}
}
@media screen and (max-width:768px) {
.column.is-half{
width: 100%;
&:nth-child(odd){
margin-right: 0;
}
}
}
}
// tables
.table {
width: 100%;
td {
border-color: #eee;
padding: 1rem
}
}
// card tables
.card-table {
border-collapse: separate;
border-spacing: 0px 5px;
tr{
a{
color: #363636;
}
&:hover {
td {
background-color: whitesmoke;
&:nth-child(4){
background-color: #E1E1E1;
}
}
a{
color: $blue;
}
}
}
td {
border-bottom: 1px solid whitesmoke;
background-color: #fafafa;
padding: 1em 1.25em;
&:first-child{
border-left: 3px solid $mint;
}
&:nth-child(4){
background-color: whitesmoke;
}
}
&.is-hoverable tbody tr:not(.is-selected):hover {
background-color: whitesmoke;
}
thead th {
background-color: transparent;
border: none;
}
}
// forms
.field:not(.is-grouped){
margin-bottom: 1rem;
}
.input, .textarea {
/*background-color: whitesmoke;*/
border-color: #98d8f3;
box-shadow: none;
}
/*.input[disabled], .textarea[disabled] {
background-color: #ddd;
border-color: #ccc;
box-shadow: none;
color: #aaa;
}*/
// pagination
.pagination-next, .pagination-link, .pagination-ellipsis{
padding-left: 1.5em;
padding-right: 1.5em;
height:2.5rem;
}
.pagination-previous, .pagination-next {
min-width: 6.75em;
}
// dark hero colors
.hero.is-dark {
background-color: #002e4b;
@@ -77,33 +194,13 @@ $fa-font-path: "webfonts";
color: #fff;
}
}
// footer colors
// footer
.footer {
background-color: whitesmoke;
}
//typography
.subtitle {
color: #666;
}
// buttons
.button{
padding-left: 1.5em;
padding-right: 1.5em;
height:2.5rem;
&.is-primary {
background-color: $mint;
}
}
// pagination
.pagination-next, .pagination-link, .pagination-ellipsis{
padding-left: 1.5em;
padding-right: 1.5em;
height:2.5rem;
}
.pagination-previous, .pagination-next {
min-width: 6.75em;
}
// sidebar menu
.aside-background {
@@ -150,13 +247,13 @@ $fa-font-path: "webfonts";
border-right: 1px solid #eee;
&.is-active {
color: #33B2E8;
color: $blue;
background-color: #fff;
&:before{
position: relative;
content: " ";
background: #33B2E8;
background: $blue;
height: 53px;
width: 2px;
display: block;
@@ -173,69 +270,4 @@ $fa-font-path: "webfonts";
border-bottom: 1px solid #eee;
}
}
// tables
.table {
width: 100%;
td {
border-color: #eee;
padding: 1rem
}
}
// card tables
.card-table {
border-collapse: separate;
border-spacing: 0px 5px;
tr{
a{
color: #363636;
}
&:hover {
td {
background-color: whitesmoke;
&:nth-child(4){
background-color: #E1E1E1;
}
}
a{
color: $blue;
}
}
}
td {
border-bottom: 1px solid whitesmoke;
background-color: #fafafa;
padding: 1em 1.25em;
&:first-child{
border-left: 3px solid $mint;
}
&:nth-child(4){
background-color: whitesmoke;
}
}
&.is-hoverable tbody tr:not(.is-selected):hover {
background-color: whitesmoke;
}
thead th {
background-color: transparent;
border: none;
}
}
// forms
.field:not(.is-grouped){
margin-bottom: 1rem;
}
.input, .textarea {
background-color: whitesmoke;
border-color: #efefef;
box-shadow: none;
}
.input[disabled], .textarea[disabled] {
background-color: #ddd;
border-color: #ccc;
box-shadow: none;
color: #aaa;
}