fix form validation after refactoring

This commit is contained in:
Eduard Heimbuch
2020-12-01 11:35:49 +01:00
parent af9f6ab629
commit 6a93a198e8
12 changed files with 134 additions and 66 deletions

View File

@@ -61,6 +61,7 @@ const MarginLeft = styled.div`
const FlexContainer = styled.div`
display: flex;
flex-direction: row;
height: 2.25rem;
`;
export default class Page extends React.Component<Props> {

View File

@@ -71,6 +71,7 @@
"infoText": "Ihr Repository wird gerade importiert. Dies kann einen Moment dauern. Sie werden weitergeleitet, sobald der Import abgeschlossen ist. Wenn Sie diese Seite verlassen, können Sie nicht zurückkehren, um den Import-Status zu erfahren."
},
"importTypes": {
"label": "Import Modus",
"url": {
"label": "Import via URL",
"helpText": "Das Repository wird über eine URL importiert."

View File

@@ -72,6 +72,7 @@
"infoText": "Your repository is currently being imported. This may take a moment. You will be forwarded as soon as the import is finished. If you leave this page you cannot return to find out the import status."
},
"importTypes": {
"label": "Import Mode",
"url": {
"label": "Import via URL",
"helpText": "The Repository will be imported via the provided URL."

View File

@@ -32,6 +32,7 @@ type Props = {
repository: RepositoryUrlImport;
onChange: (repository: RepositoryUrlImport) => void;
setValid: (valid: boolean) => void;
disabled?: boolean;
};
const Column = styled.div`
@@ -42,7 +43,7 @@ const Columns = styled.div`
padding: 0.75rem 0 0;
`;
const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid }) => {
const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid, disabled }) => {
const [t] = useTranslation("repos");
const [urlValidationError, setUrlValidationError] = useState(false);
@@ -72,6 +73,7 @@ const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid }) => {
helpText={t("help.importUrlHelpText")}
validationError={urlValidationError}
errorMessage={t("validation.url-invalid")}
disabled={disabled}
/>
</Column>
<Column className="column is-half">
@@ -80,6 +82,7 @@ const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid }) => {
onChange={username => onChange({ ...repository, username })}
value={repository.username}
helpText={t("help.usernameHelpText")}
disabled={disabled}
/>
</Column>
<Column className="column is-half">
@@ -89,6 +92,7 @@ const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid }) => {
value={repository.password}
type="password"
helpText={t("help.passwordHelpText")}
disabled={disabled}
/>
</Column>
</Columns>

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { FC, useState } from "react";
import React, { FC, FormEvent, useState } from "react";
import NamespaceAndNameFields from "./NamespaceAndNameFields";
import { Repository, RepositoryUrlImport } from "@scm-manager/ui-types";
import ImportFromUrlForm from "./ImportFromUrlForm";
@@ -62,7 +62,9 @@ const ImportRepositoryFromUrl: FC<Props> = ({ url, setImportPending }) => {
setImportPending(loading);
};
const submit = () => {
const submit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const currentPath = history.location.pathname;
handleImportLoading(true);
apiClient
.post(url, repo, "application/vnd.scmm-repository+json;v=2")
@@ -72,7 +74,11 @@ const ImportRepositoryFromUrl: FC<Props> = ({ url, setImportPending }) => {
return apiClient.get(location);
})
.then(response => response.json())
.then(repo => history.push(`/repo/${repo.namespace}/${repo.name}/code/sources`))
.then(repo => {
if (history.location.pathname === currentPath) {
history.push(`/repo/${repo.namespace}/${repo.name}/code/sources`);
}
})
.catch(error => {
setError(error);
handleImportLoading(false);
@@ -81,22 +87,24 @@ const ImportRepositoryFromUrl: FC<Props> = ({ url, setImportPending }) => {
return (
<form onSubmit={submit}>
<ErrorNotification error={error} />
<ImportFromUrlForm
repository={repo}
onChange={setRepo}
setValid={(importUrl: boolean) => setValid({ ...valid, importUrl })}
disabled={loading}
/>
<ErrorNotification error={error} />
<hr />
<NamespaceAndNameFields
repository={repo}
onChange={setRepo as React.Dispatch<React.SetStateAction<Repository>>}
setValid={(namespaceAndName: boolean) => setValid({ ...valid, namespaceAndName })}
disabled={loading}
/>
<RepositoryInformationForm
repository={repo}
onChange={setRepo as React.Dispatch<React.SetStateAction<Repository>>}
disabled={false}
disabled={loading}
setValid={(contact: boolean) => setValid({ ...valid, contact })}
/>
<Level

View File

@@ -30,13 +30,16 @@ type Props = {
repositoryTypes: RepositoryType[];
repositoryType?: RepositoryType;
setRepositoryType: (repositoryType: RepositoryType) => void;
disabled?: boolean;
};
const ImportRepositoryTypeSelect: FC<Props> = ({ repositoryTypes, repositoryType, setRepositoryType }) => {
const ImportRepositoryTypeSelect: FC<Props> = ({ repositoryTypes, repositoryType, setRepositoryType, disabled }) => {
const [t] = useTranslation("repos");
const createSelectOptions = () => {
const options = repositoryTypes.filter(repoType => !!repoType._links.import).map(repositoryType => {
const options = repositoryTypes
.filter(repoType => !!repoType._links.import)
.map(repositoryType => {
return {
label: repositoryType.displayName,
value: repositoryType.name
@@ -58,6 +61,7 @@ const ImportRepositoryTypeSelect: FC<Props> = ({ repositoryTypes, repositoryType
value={repositoryType ? repositoryType.name : ""}
options={createSelectOptions()}
helpText={t("help.typeHelpText")}
disabled={disabled}
/>
);
};

View File

@@ -22,17 +22,18 @@
* SOFTWARE.
*/
import React, { FC } from "react";
import { RepositoryType, Link } from "@scm-manager/ui-types";
import { Radio } from "@scm-manager/ui-components";
import { Link, RepositoryType } from "@scm-manager/ui-types";
import { LabelWithHelpIcon, Radio } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
type Props = {
repositoryType: RepositoryType;
importType: string;
setImportType: (type: string) => void;
disabled?: boolean;
};
const ImportTypeSelect: FC<Props> = ({ repositoryType, importType, setImportType }) => {
const ImportTypeSelect: FC<Props> = ({ repositoryType, importType, setImportType, disabled }) => {
const [t] = useTranslation("repos");
const changeImportType = (checked: boolean, name?: string) => {
@@ -43,6 +44,7 @@ const ImportTypeSelect: FC<Props> = ({ repositoryType, importType, setImportType
return (
<>
<LabelWithHelpIcon label={t("import.importTypes.label")} key="import.importTypes.label" />
{(repositoryType._links.import as Link[]).map((type, index) => (
<Radio
name={type.name}
@@ -52,6 +54,7 @@ const ImportTypeSelect: FC<Props> = ({ repositoryType, importType, setImportType
helpText={t(`import.importTypes.${type.name}.helpText`)}
onChange={changeImportType}
key={index}
disabled={disabled}
/>
))}
</>

View File

@@ -36,45 +36,45 @@ type Props = {
onChange: (repository: Repository) => void;
namespaceStrategy: string;
setValid: (valid: boolean) => void;
disabled?: boolean;
};
const NamespaceAndNameFields: FC<Props> = ({ repository, onChange, namespaceStrategy, setValid }) => {
const NamespaceAndNameFields: FC<Props> = ({ repository, onChange, namespaceStrategy, setValid, disabled }) => {
const [t] = useTranslation("repos");
const [namespaceValidationError, setNamespaceValidationError] = useState(false);
const [nameValidationError, setNameValidationError] = useState(false);
useEffect(() => {
//TODO fix validation
if (repository.name) {
const valid = validator.isNameValid(repository.name);
setNameValidationError(!valid);
onFieldChange();
const nameValid = validator.isNameValid(repository.name);
setNameValidationError(!nameValid);
if (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY) {
if (repository.namespace) {
const namespaceValid = validator.isNamespaceValid(repository.namespace);
setValid(!(!namespaceValid || !nameValid));
} else {
setValid(false);
}
}, [repository.name]);
} else {
setValid(nameValid);
}
} else {
setValid(false);
}
}, [repository.name, repository.namespace]);
const handleNamespaceChange = (namespace: string) => {
const valid = validator.isNamespaceValid(namespace);
setNamespaceValidationError(!valid);
onFieldChange(valid);
onChange({ ...repository, namespace });
};
const handleNameChange = (name: string) => {
const valid = validator.isNameValid(name);
setNameValidationError(!valid);
onFieldChange();
onChange({ ...repository, name });
};
//TODO fix validation
const onFieldChange = (namespaceValid?: boolean) => {
if (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY && !validator.isNamespaceValid(repository.namespace)) {
setValid(false);
} else {
setValid(!nameValidationError && !namespaceValidationError);
}
};
const renderNamespaceField = () => {
const props = {
label: t("repository.namespace"),
@@ -82,7 +82,8 @@ const NamespaceAndNameFields: FC<Props> = ({ repository, onChange, namespaceStra
value: repository ? repository.namespace : "",
onChange: handleNamespaceChange,
errorMessage: t("validation.namespace-invalid"),
validationError: namespaceValidationError
validationError: namespaceValidationError,
disabled: disabled
};
if (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY) {
@@ -102,6 +103,7 @@ const NamespaceAndNameFields: FC<Props> = ({ repository, onChange, namespaceStra
validationError={nameValidationError}
errorMessage={t("validation.name-invalid")}
helpText={t("help.nameHelpText")}
disabled={disabled}
/>
</>
);

View File

@@ -78,7 +78,6 @@ const RepositoryForm: FC<Props> = ({
});
const [initRepository, setInitRepository] = useState(false);
const [contextEntries, setContextEntries] = useState({});
//TODO fix validation
const [valid, setValid] = useState({ namespaceAndName: false, contact: true });
const [t] = useTranslation("repos");
@@ -194,7 +193,7 @@ const RepositoryForm: FC<Props> = ({
<RepositoryInformationForm
repository={repo}
onChange={setRepo}
disabled={!createRepository}
disabled={!(isModifiable() || createRepository)}
setValid={contact => setValid({ ...valid, contact })}
/>
{submitButton()}

View File

@@ -25,12 +25,16 @@
import React, { FC } from "react";
import styled from "styled-components";
import { useTranslation } from "react-i18next";
import { Button, ButtonAddons, Level } from "@scm-manager/ui-components";
import { Button, ButtonAddons, Icon, Level } from "@scm-manager/ui-components";
type Props = {
creationMode: "CREATE" | "IMPORT";
};
const MarginIcon = styled(Icon)`
padding-right: 0.5rem;
`;
const SmallButton = styled(Button)`
border-radius: 4px;
font-size: 1rem;
@@ -38,9 +42,14 @@ const SmallButton = styled(Button)`
`;
const TopLevel = styled(Level)`
margin-top: -2.75rem;
margin-bottom: 2.75rem !important; //TODO Try to remove important
margin-top: 1.5rem;
margin-bottom: -1.5rem !important;
height: 0;
position: absolute;
right: 0;
@media (max-width: 785px) {
margin-top: 4.5rem;
}
`;
const RepositoryFormSwitcher: FC<Props> = ({ creationMode }) => {
@@ -59,17 +68,20 @@ const RepositoryFormSwitcher: FC<Props> = ({ creationMode }) => {
right={
<ButtonAddons>
<SmallButton
label={t("repositoryForm.createButton")}
icon="fa fa-plus"
color={isCreateMode() ? "link is-selected" : undefined}
link={isImportMode() ? "/repos/create" : undefined}
/>
>
<MarginIcon name="fa fa-plus" color={isCreateMode() ? "white" : "default"} />{" "}
<p className="is-hidden-mobile is-hidden-tablet-only">{t("repositoryForm.createButton")}</p>
</SmallButton>
<SmallButton
label={t("repositoryForm.importButton")}
icon="fa fa-file-upload"
color={isImportMode() ? "link is-selected" : undefined}
link={isCreateMode() ? "/repos/import" : undefined}
/>
className="has-text-left-desktop"
>
<MarginIcon name="fa fa-file-upload" color={isImportMode() ? "white" : "default"} />
<p className="is-hidden-mobile is-hidden-tablet-only">{t("repositoryForm.importButton")}</p>
</SmallButton>
</ButtonAddons>
}
/>

View File

@@ -35,12 +35,7 @@ import {
} from "../modules/repositoryTypes";
import RepositoryForm from "../components/form";
import RepositoryFormSwitcher from "../components/form/RepositoryFormSwitcher";
import {
createRepo,
createRepoReset,
getCreateRepoFailure,
isCreateRepoPending
} from "../modules/repos";
import { createRepo, createRepoReset, getCreateRepoFailure, isCreateRepoPending } from "../modules/repos";
import { getRepositoriesLink } from "../../modules/indexResource";
import {
fetchNamespaceStrategiesIfNeeded,
@@ -77,7 +72,19 @@ type Props = WithTranslation &
history: History;
};
class AddRepository extends React.Component<Props> {
type State = {
importPending: boolean;
};
class AddRepository extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
importPending: false
};
}
componentDidMount() {
this.props.resetForm();
this.props.fetchRepositoryTypesIfNeeded();
@@ -102,6 +109,16 @@ class AddRepository extends React.Component<Props> {
isImportPage = () => this.resolveLocation() === "import";
isCreatePage = () => this.resolveLocation() === "create";
getSubtitle = () => {
if (this.isCreatePage()) {
return "create.subtitle";
} else if (this.isImportPage() && this.state.importPending) {
return "import.pending.subtitle";
} else {
return "import.subtitle";
}
};
render() {
const {
pageLoading,
@@ -118,13 +135,18 @@ class AddRepository extends React.Component<Props> {
return (
<Page
title={t("create.title")}
subtitle={t("create.subtitle")}
subtitle={t(this.getSubtitle())}
afterTitle={<RepositoryFormSwitcher creationMode={this.isImportPage() ? "IMPORT" : "CREATE"} />}
loading={pageLoading}
error={error}
showContentOnError={true}
>
{!error && <RepositoryFormSwitcher creationMode={this.isImportPage() ? "IMPORT" : "CREATE"} />}
{this.isImportPage() && <ImportRepository repositoryTypes={repositoryTypes} />}
{this.isImportPage() && (
<ImportRepository
repositoryTypes={repositoryTypes}
setPending={(importPending: boolean) => this.setState({ importPending })}
/>
)}
{this.isCreatePage() && (
<RepositoryForm
repositoryTypes={repositoryTypes}

View File

@@ -22,8 +22,8 @@
* SOFTWARE.
*/
import React, { FC, useState } from "react";
import { RepositoryType, Link } from "@scm-manager/ui-types";
import React, { FC, useEffect, useState } from "react";
import { Link, RepositoryType } from "@scm-manager/ui-types";
import { useTranslation } from "react-i18next";
import ImportRepositoryTypeSelect from "../components/ImportRepositoryTypeSelect";
@@ -33,14 +33,19 @@ import { Loading, Notification } from "@scm-manager/ui-components";
type Props = {
repositoryTypes: RepositoryType[];
setPending: (pending: boolean) => void;
};
const ImportRepository: FC<Props> = ({ repositoryTypes }) => {
const ImportRepository: FC<Props> = ({ repositoryTypes, setPending }) => {
const [importPending, setImportPending] = useState(false);
const [repositoryType, setRepositoryType] = useState<RepositoryType | undefined>();
const [importType, setImportType] = useState("");
const [t] = useTranslation("repos");
useEffect(() => {
setPending(importPending);
}, [importPending]);
const changeRepositoryType = (repositoryType: RepositoryType) => {
setRepositoryType(repositoryType);
setImportType(repositoryType?._links ? ((repositoryType!._links?.import as Link[])[0] as Link).name! : "");
@@ -72,11 +77,17 @@ const ImportRepository: FC<Props> = ({ repositoryTypes }) => {
repositoryTypes={repositoryTypes}
repositoryType={repositoryType}
setRepositoryType={changeRepositoryType}
disabled={importPending}
/>
{repositoryType && (
<>
<hr />
<ImportTypeSelect repositoryType={repositoryType} importType={importType} setImportType={setImportType} />
<ImportTypeSelect
repositoryType={repositoryType}
importType={importType}
setImportType={setImportType}
disabled={importPending}
/>
<hr />
</>
)}