Client:OAuth2: add errors for client form create

This commit is contained in:
Timofey Boyko 2023-11-21 09:57:18 +03:00
parent f9bf6e08d0
commit f3ce27d404
8 changed files with 249 additions and 95 deletions

View File

@ -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",

View File

@ -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>

View File

@ -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>
);
};

View File

@ -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

View File

@ -29,6 +29,7 @@ const OAuthBlock = ({
<BlockHeader header={t("OAuthHeaderBlock")} />
<StyledInputBlock>
<MultiInputGroup
t={t}
label={t("RedirectsURLS")}
placeholder={t("EnterURL")}
name={"redirect_uris"}

View File

@ -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>

View File

@ -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 (

View File

@ -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")}