Client:OAuth2: add errors for client form create
This commit is contained in:
parent
f9bf6e08d0
commit
f3ce27d404
@ -12,6 +12,8 @@
|
||||
"ClientHelpButton": "Credentials for using OAth 2.0 as your Authentication type. Note: Any enterprise admin who knows the app's client ID will be able to retrieve information about the app including app name, authentication type, app scopes and redirect URI.",
|
||||
"EditApp": "Edit application",
|
||||
"EnterDescription": "Enter description",
|
||||
"ErrorName": "Minimal name length:",
|
||||
"ErrorWrongURL": "Wrong URL",
|
||||
"EnterURL": "Enter URL",
|
||||
"IconDescription": "JPG, PNG or SVG, 32x32",
|
||||
"ID": "ID",
|
||||
@ -37,6 +39,7 @@
|
||||
"RevokeConsentLogin": "If you want to renew an automatic login into {{name}} using ONLYOFFICE DocSpace, you will be asked to grant access to your DocSpace account data.",
|
||||
"RevokeConsentLoginGroup": "If you want to renew an automatic login using ONLYOFFICE DocSpace, you will be asked to grant access to your DocSpace account data.",
|
||||
"Secret": "Secret",
|
||||
"SelectNewImage": "Select new image",
|
||||
"Scopes": "Scopes",
|
||||
"SignIn": "Sign in with DocSpace",
|
||||
"SupportAndLegalInfo": "Support & Legal info",
|
||||
|
@ -17,6 +17,7 @@ interface BasicBlockProps {
|
||||
changeValue: (name: string, value: string) => void;
|
||||
|
||||
isEdit: boolean;
|
||||
errorFields: string[];
|
||||
}
|
||||
|
||||
const BasicBlock = ({
|
||||
@ -28,14 +29,8 @@ const BasicBlock = ({
|
||||
changeValue,
|
||||
|
||||
isEdit,
|
||||
errorFields,
|
||||
}: BasicBlockProps) => {
|
||||
const [error, setError] = React.useState({
|
||||
name: "",
|
||||
websiteUrl: "",
|
||||
logo: "",
|
||||
description: "",
|
||||
});
|
||||
|
||||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const target = e.target;
|
||||
|
||||
@ -67,22 +62,26 @@ const BasicBlock = ({
|
||||
name={"name"}
|
||||
placeholder={t("Common:EnterName")}
|
||||
value={nameValue}
|
||||
error={error.name}
|
||||
error={`${t("ErrorName")} 3`}
|
||||
onChange={onChange}
|
||||
isRequired
|
||||
isError={errorFields.includes("name")}
|
||||
/>
|
||||
<InputGroup
|
||||
label={t("WebsiteUrl")}
|
||||
name={"website_url"}
|
||||
placeholder={t("EnterURL")}
|
||||
value={websiteUrlValue}
|
||||
error={error.websiteUrl}
|
||||
error={`${t("ErrorWrongURL")}`}
|
||||
onChange={onChange}
|
||||
disabled={isEdit}
|
||||
isRequired
|
||||
isError={errorFields.includes("website_url")}
|
||||
/>
|
||||
<SelectGroup
|
||||
label={t("AppIcon")}
|
||||
value={logoValue}
|
||||
selectLabel={"Select new image"}
|
||||
selectLabel={t("SelectNewImage")}
|
||||
description={t("IconDescription")}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
@ -91,7 +90,6 @@ const BasicBlock = ({
|
||||
name={"description"}
|
||||
placeholder={t("EnterDescription")}
|
||||
value={descriptionValue}
|
||||
error={error.description}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</StyledInputBlock>
|
||||
|
@ -5,6 +5,8 @@ import InputBlock from "@docspace/components/input-block";
|
||||
import Button from "@docspace/components/button";
|
||||
//@ts-ignore
|
||||
import HelpButton from "@docspace/components/help-button";
|
||||
//@ts-ignore
|
||||
import FieldContainer from "@docspace/components/field-container";
|
||||
|
||||
import CopyReactSvgUrl from "PUBLIC_DIR/images/copy.react.svg?url";
|
||||
|
||||
@ -35,6 +37,9 @@ interface InputGroupProps {
|
||||
isPassword?: boolean;
|
||||
|
||||
disabled?: boolean;
|
||||
isRequired?: boolean;
|
||||
isError?: boolean;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const InputGroup = ({
|
||||
@ -57,48 +62,52 @@ const InputGroup = ({
|
||||
onCopyClick,
|
||||
isPassword,
|
||||
disabled,
|
||||
isRequired,
|
||||
isError,
|
||||
children,
|
||||
}: InputGroupProps) => {
|
||||
return (
|
||||
<StyledInputGroup>
|
||||
<StyledHeaderRow>
|
||||
<Text
|
||||
fontSize={"13px"}
|
||||
fontWeight={600}
|
||||
lineHeight={"20px"}
|
||||
title={""}
|
||||
tag={""}
|
||||
as={"p"}
|
||||
color={""}
|
||||
textAlign={""}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{helpButtonText && <HelpButton tooltipContent={helpButtonText} />}
|
||||
</StyledHeaderRow>
|
||||
<StyledInputRow>
|
||||
<InputBlock
|
||||
name={name}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
scale
|
||||
tabIndex={0}
|
||||
maxLength={255}
|
||||
isReadOnly={withCopy}
|
||||
isDisabled={withCopy || disabled}
|
||||
iconName={withCopy ? CopyReactSvgUrl : null}
|
||||
onIconClick={withCopy && onCopyClick}
|
||||
type={isPassword ? "password" : "text"}
|
||||
/>
|
||||
{buttonLabel && (
|
||||
<Button
|
||||
//@ts-ignore
|
||||
label={buttonLabel}
|
||||
size={"small"}
|
||||
onClick={onButtonClick}
|
||||
/>
|
||||
<FieldContainer
|
||||
isVertical
|
||||
isRequired={isRequired}
|
||||
labelVisible
|
||||
labelText={label}
|
||||
tooltipContent={helpButtonText}
|
||||
errorMessage={error}
|
||||
removeMargin
|
||||
hasError={isError}
|
||||
>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<>
|
||||
{" "}
|
||||
<InputBlock
|
||||
name={name}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
scale
|
||||
tabIndex={0}
|
||||
maxLength={255}
|
||||
isReadOnly={withCopy}
|
||||
isDisabled={withCopy || disabled}
|
||||
iconName={withCopy ? CopyReactSvgUrl : null}
|
||||
onIconClick={withCopy && onCopyClick}
|
||||
type={isPassword ? "password" : "text"}
|
||||
/>
|
||||
{buttonLabel && (
|
||||
<Button
|
||||
//@ts-ignore
|
||||
label={buttonLabel}
|
||||
size={"small"}
|
||||
onClick={onButtonClick}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</StyledInputRow>
|
||||
</FieldContainer>
|
||||
</StyledInputGroup>
|
||||
);
|
||||
};
|
||||
|
@ -15,8 +15,11 @@ import {
|
||||
StyledInputGroup,
|
||||
StyledInputRow,
|
||||
} from "../ClientForm.styled";
|
||||
import InputGroup from "./InputGroup";
|
||||
import { WEBSITE_REGEXP } from "..";
|
||||
|
||||
interface MultiInputGroupProps {
|
||||
t: any;
|
||||
label: string;
|
||||
|
||||
name: string;
|
||||
@ -31,6 +34,7 @@ interface MultiInputGroupProps {
|
||||
}
|
||||
|
||||
const MultiInputGroup = ({
|
||||
t,
|
||||
label,
|
||||
name,
|
||||
placeholder,
|
||||
@ -41,6 +45,8 @@ const MultiInputGroup = ({
|
||||
isDisabled,
|
||||
}: MultiInputGroupProps) => {
|
||||
const [value, setValue] = React.useState("");
|
||||
const timer = React.useRef<null | ReturnType<typeof setTimeout>>(null);
|
||||
const [isError, setIsError] = React.useState(false);
|
||||
|
||||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = e.target;
|
||||
@ -48,43 +54,61 @@ const MultiInputGroup = ({
|
||||
setValue(value);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (timer.current) {
|
||||
clearTimeout(timer.current);
|
||||
}
|
||||
console.log(value, "call");
|
||||
if (value) {
|
||||
console.log(value);
|
||||
if (WEBSITE_REGEXP.test(value)) {
|
||||
setIsError(false);
|
||||
} else {
|
||||
timer.current = setTimeout(() => {
|
||||
setIsError(true);
|
||||
}, 300);
|
||||
}
|
||||
} else {
|
||||
setIsError(false);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
console.log(WEBSITE_REGEXP.test(value));
|
||||
return (
|
||||
<StyledInputGroup>
|
||||
<StyledHeaderRow>
|
||||
<Text
|
||||
fontSize={"13px"}
|
||||
fontWeight={600}
|
||||
lineHeight={"20px"}
|
||||
title={""}
|
||||
tag={""}
|
||||
as={"p"}
|
||||
color={""}
|
||||
textAlign={""}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{helpButtonText && <HelpButton tooltipContent={helpButtonText} />}
|
||||
</StyledHeaderRow>
|
||||
<StyledInputRow>
|
||||
<InputBlock
|
||||
name={name}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
scale
|
||||
tabIndex={0}
|
||||
maxLength={255}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
<SelectorAddButton
|
||||
onClick={() => {
|
||||
if (isDisabled) return;
|
||||
onAdd(name, value);
|
||||
setValue("");
|
||||
}}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
</StyledInputRow>
|
||||
<InputGroup
|
||||
label={label}
|
||||
helpButtonText={helpButtonText}
|
||||
name={name}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
error={t("ErrorWrongURL")}
|
||||
isRequired
|
||||
isError={isError}
|
||||
>
|
||||
<StyledInputRow>
|
||||
<InputBlock
|
||||
name={name}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
scale
|
||||
tabIndex={0}
|
||||
maxLength={255}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
<SelectorAddButton
|
||||
onClick={() => {
|
||||
if (isDisabled || isError) return;
|
||||
onAdd(name, value);
|
||||
setValue("");
|
||||
}}
|
||||
isDisabled={isDisabled || isError}
|
||||
/>
|
||||
</StyledInputRow>
|
||||
</InputGroup>
|
||||
|
||||
<StyledChipsContainer>
|
||||
{currentValue.map((v, index) => (
|
||||
<SelectedItem
|
||||
|
@ -29,6 +29,7 @@ const OAuthBlock = ({
|
||||
<BlockHeader header={t("OAuthHeaderBlock")} />
|
||||
<StyledInputBlock>
|
||||
<MultiInputGroup
|
||||
t={t}
|
||||
label={t("RedirectsURLS")}
|
||||
placeholder={t("EnterURL")}
|
||||
name={"redirect_uris"}
|
||||
|
@ -13,6 +13,7 @@ interface SupportBlockProps {
|
||||
changeValue: (name: string, value: string) => void;
|
||||
|
||||
isEdit: boolean;
|
||||
errorFields: string[];
|
||||
}
|
||||
|
||||
const SupportBlock = ({
|
||||
@ -23,12 +24,8 @@ const SupportBlock = ({
|
||||
changeValue,
|
||||
|
||||
isEdit,
|
||||
errorFields,
|
||||
}: SupportBlockProps) => {
|
||||
const [error, setError] = React.useState({
|
||||
policyUrl: "",
|
||||
termsUrl: "",
|
||||
});
|
||||
|
||||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const target = e.target;
|
||||
|
||||
@ -44,20 +41,24 @@ const SupportBlock = ({
|
||||
name={"policy_url"}
|
||||
placeholder={t("EnterURL")}
|
||||
value={policyUrlValue}
|
||||
error={error.policyUrl}
|
||||
error={t("ErrorWrongURL")}
|
||||
onChange={onChange}
|
||||
helpButtonText={t("PrivacyPolicyURLHelpButton")}
|
||||
disabled={isEdit}
|
||||
isRequired
|
||||
isError={errorFields.includes("policy_url")}
|
||||
/>
|
||||
<InputGroup
|
||||
label={t("TermsOfServiceURL")}
|
||||
name={"terms_url"}
|
||||
placeholder={t("EnterURL")}
|
||||
value={termsUrlValue}
|
||||
error={error.termsUrl}
|
||||
error={t("ErrorWrongURL")}
|
||||
onChange={onChange}
|
||||
helpButtonText={t("TermsOfServiceURLHelpButton")}
|
||||
disabled={isEdit}
|
||||
isRequired
|
||||
isError={errorFields.includes("terms_url")}
|
||||
/>
|
||||
</StyledInputBlock>
|
||||
</StyledBlock>
|
||||
|
@ -13,8 +13,6 @@ interface TextAreaProps {
|
||||
value: string;
|
||||
placeholder: string;
|
||||
|
||||
error: string;
|
||||
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
@ -25,8 +23,6 @@ const TextAreaGroup = ({
|
||||
value,
|
||||
placeholder,
|
||||
|
||||
error,
|
||||
|
||||
onChange,
|
||||
}: TextAreaProps) => {
|
||||
return (
|
||||
|
@ -2,6 +2,7 @@ import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { debounce } from "lodash";
|
||||
|
||||
import {
|
||||
IClientProps,
|
||||
@ -20,6 +21,9 @@ import { StyledContainer } from "./ClientForm.styled";
|
||||
import { ClientFormProps, ClientStore } from "./ClientForm.types";
|
||||
import ClientFormLoader from "./Loader";
|
||||
|
||||
export const WEBSITE_REGEXP =
|
||||
/(http|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])/g;
|
||||
|
||||
const ClientForm = ({
|
||||
id,
|
||||
|
||||
@ -63,6 +67,14 @@ const ClientForm = ({
|
||||
|
||||
scopes: [],
|
||||
});
|
||||
|
||||
const nameTimer = React.useRef<null | ReturnType<typeof setTimeout>>(null);
|
||||
const websiteTimer = React.useRef<null | ReturnType<typeof setTimeout>>(null);
|
||||
const policyTimer = React.useRef<null | ReturnType<typeof setTimeout>>(null);
|
||||
const termsTimer = React.useRef<null | ReturnType<typeof setTimeout>>(null);
|
||||
|
||||
const [errorFields, setErrorFields] = React.useState<string[]>([]);
|
||||
|
||||
const { t } = useTranslation(["OAuth", "Common"]);
|
||||
|
||||
const [clientId, setClientId] = React.useState<string>("");
|
||||
@ -190,6 +202,31 @@ const ClientForm = ({
|
||||
case "name":
|
||||
isValid = isValid && !!form[key];
|
||||
|
||||
if (
|
||||
form[key] &&
|
||||
!errorFields.includes(key) &&
|
||||
(form[key].length < 3 || form[key].length > 256)
|
||||
) {
|
||||
isValid = false;
|
||||
|
||||
nameTimer.current = setTimeout(() => {
|
||||
setErrorFields((value) => {
|
||||
return [...value, key];
|
||||
});
|
||||
}, 300);
|
||||
}
|
||||
|
||||
if (
|
||||
errorFields.includes(key) &&
|
||||
(!form[key] || (form[key].length > 2 && form[key].length < 256))
|
||||
) {
|
||||
setErrorFields((value) => {
|
||||
return value.filter((n) => n !== key);
|
||||
});
|
||||
if (nameTimer.current) clearTimeout(nameTimer.current);
|
||||
nameTimer.current = null;
|
||||
}
|
||||
|
||||
break;
|
||||
case "logo":
|
||||
isValid = isValid && !!form[key];
|
||||
@ -202,6 +239,33 @@ const ClientForm = ({
|
||||
case "website_url":
|
||||
isValid = isValid && !!form[key];
|
||||
|
||||
if (
|
||||
form[key] &&
|
||||
!errorFields.includes(key) &&
|
||||
!WEBSITE_REGEXP.test(form[key])
|
||||
) {
|
||||
isValid = false;
|
||||
|
||||
websiteTimer.current = setTimeout(
|
||||
() =>
|
||||
setErrorFields((value) => {
|
||||
return [...value, key];
|
||||
}),
|
||||
300
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
errorFields.includes(key) &&
|
||||
(!form[key] || WEBSITE_REGEXP.test(form[key]))
|
||||
) {
|
||||
setErrorFields((value) => {
|
||||
return value.filter((n) => n !== key);
|
||||
});
|
||||
if (websiteTimer.current) clearTimeout(websiteTimer.current);
|
||||
websiteTimer.current = null;
|
||||
}
|
||||
|
||||
break;
|
||||
case "redirect_uris":
|
||||
isValid = isValid && form[key].length > 0;
|
||||
@ -217,9 +281,65 @@ const ClientForm = ({
|
||||
break;
|
||||
case "terms_url":
|
||||
isValid = isValid && !!form[key];
|
||||
|
||||
if (
|
||||
form[key] &&
|
||||
!errorFields.includes(key) &&
|
||||
!WEBSITE_REGEXP.test(form[key])
|
||||
) {
|
||||
isValid = false;
|
||||
|
||||
termsTimer.current = setTimeout(
|
||||
() =>
|
||||
setErrorFields((value) => {
|
||||
return [...value, key];
|
||||
}),
|
||||
300
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
errorFields.includes(key) &&
|
||||
(!form[key] || WEBSITE_REGEXP.test(form[key]))
|
||||
) {
|
||||
setErrorFields((value) => {
|
||||
return value.filter((n) => n !== key);
|
||||
});
|
||||
if (termsTimer.current) clearTimeout(termsTimer.current);
|
||||
termsTimer.current = null;
|
||||
}
|
||||
|
||||
break;
|
||||
case "policy_url":
|
||||
isValid = isValid && !!form[key];
|
||||
|
||||
if (
|
||||
form[key] &&
|
||||
!errorFields.includes(key) &&
|
||||
!WEBSITE_REGEXP.test(form[key])
|
||||
) {
|
||||
isValid = false;
|
||||
|
||||
policyTimer.current = setTimeout(
|
||||
() =>
|
||||
setErrorFields((value) => {
|
||||
return [...value, key];
|
||||
}),
|
||||
300
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
errorFields.includes(key) &&
|
||||
(!form[key] || WEBSITE_REGEXP.test(form[key]))
|
||||
) {
|
||||
setErrorFields((value) => {
|
||||
return value.filter((n) => n !== key);
|
||||
});
|
||||
if (policyTimer.current) clearTimeout(policyTimer.current);
|
||||
policyTimer.current = null;
|
||||
}
|
||||
|
||||
break;
|
||||
case "authentication_method":
|
||||
isValid = isValid;
|
||||
@ -252,6 +372,7 @@ const ClientForm = ({
|
||||
logoValue={form.logo}
|
||||
changeValue={onChangeForm}
|
||||
isEdit={isEdit}
|
||||
errorFields={errorFields}
|
||||
/>
|
||||
{isEdit && (
|
||||
<ClientBlock
|
||||
@ -281,6 +402,7 @@ const ClientForm = ({
|
||||
termsUrlValue={form.terms_url}
|
||||
changeValue={onChangeForm}
|
||||
isEdit={isEdit}
|
||||
errorFields={errorFields}
|
||||
/>
|
||||
<ButtonsBlock
|
||||
saveLabel={t("Common:SaveButton")}
|
||||
|
Loading…
Reference in New Issue
Block a user