Client:OAuth2: fix logic for new client form

This commit is contained in:
Timofey Boyko 2023-10-31 18:34:07 +03:00
parent 86f17497a8
commit 8318dbb8f5
9 changed files with 207 additions and 205 deletions

View File

@ -111,64 +111,6 @@ const StyledChipsContainer = styled.div`
gap: 4px; gap: 4px;
`; `;
const StyledChips = styled.div`
background: #eceef1;
padding: 6px 8px;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
`;
// const CheckboxGroup = styled.div`
// width: 100%;
// height: auto;
// display: flex;
// flex-direction: column;
// gap: 8px;
// `;
// const CheckboxRaw = styled.div`
// width: 100%;
// height: auto;
// display: flex;
// flex-direction: raw;
// align-items: center;
// gap: 8px;
// .checkbox {
// margin-right: 0px;
// }
// `;
// const CategorySubHeader = styled.div`
// margin-top: 8px;
// margin-bottom: 8px;
// font-size: 15px;
// font-style: normal;
// font-weight: 600;
// line-height: 16px;
// @media ${tablet} {
// &:not(&.copy-window-code) {
// margin-bottom: 0;
// }
// }
// @media ${hugeMobile} {
// &:first-of-type {
// margin-top: 0;
// }
// }
// `;
const StyledScopesContainer = styled.div` const StyledScopesContainer = styled.div`
width: 100%; width: 100%;
@ -225,6 +167,20 @@ const StyledScopesCheckbox = styled.div`
} }
`; `;
const StyledButtonContainer = styled.div`
width: fit-content;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 8px;
@media ${mobile} {
width: 100%;
}
`;
export { export {
StyledContainer, StyledContainer,
StyledBlock, StyledBlock,
@ -233,8 +189,8 @@ export {
StyledInputGroup, StyledInputGroup,
StyledInputRow, StyledInputRow,
StyledChipsContainer, StyledChipsContainer,
StyledChips,
StyledScopesContainer, StyledScopesContainer,
StyledScopesName, StyledScopesName,
StyledScopesCheckbox, StyledScopesCheckbox,
StyledButtonContainer,
}; };

View File

@ -1,3 +1,5 @@
//@ts-ignore
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
import { IClientProps, IScope } from "@docspace/common/utils/oauth/interfaces"; import { IClientProps, IScope } from "@docspace/common/utils/oauth/interfaces";
export interface InputProps { export interface InputProps {
@ -45,10 +47,11 @@ export interface ClientFormProps {
) => Promise<IClientProps>; ) => Promise<IClientProps>;
regenerateSecret?: (clientId: string) => Promise<string>; regenerateSecret?: (clientId: string) => Promise<string>;
currentDeviceType?: string;
} }
export interface PreviewProps { export interface ClientStore {
clientId: string; auth: { settingsStore: { currentDeviceType: string } };
redirectURI: string; oauthStore: OAuthStoreProps;
scopes: string[];
} }

View File

@ -25,13 +25,6 @@ const BasicBlock = ({
descriptionValue, descriptionValue,
changeValue, changeValue,
}: BasicBlockProps) => { }: BasicBlockProps) => {
const [value, setValue] = React.useState<{ [key: string]: string }>({
name: nameValue,
websiteUrl: websiteUrlValue,
logo: logoValue,
description: descriptionValue,
});
const [error, setError] = React.useState({ const [error, setError] = React.useState({
name: "", name: "",
websiteUrl: "", websiteUrl: "",
@ -42,12 +35,6 @@ const BasicBlock = ({
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => { const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const target = e.target; const target = e.target;
setValue((value) => {
value[target.name] = target.value;
return { ...value };
});
changeValue(target.name, target.value); changeValue(target.name, target.value);
}; };
@ -62,7 +49,6 @@ const BasicBlock = ({
reader.onload = () => { reader.onload = () => {
const result = reader.result as string; const result = reader.result as string;
setValue((v) => ({ ...v, logo: result }));
changeValue("logo", result); changeValue("logo", result);
}; };
} }
@ -76,21 +62,21 @@ const BasicBlock = ({
label={"App name"} label={"App name"}
name={"name"} name={"name"}
placeholder={"Enter name"} placeholder={"Enter name"}
value={value.name} value={nameValue}
error={error.name} error={error.name}
onChange={onChange} onChange={onChange}
/> />
<InputGroup <InputGroup
label={"Website URL"} label={"Website URL"}
name={"websiteUrl"} name={"website_url"}
placeholder={"Enter URL"} placeholder={"Enter URL"}
value={value.websiteUrl} value={websiteUrlValue}
error={error.websiteUrl} error={error.websiteUrl}
onChange={onChange} onChange={onChange}
/> />
<SelectGroup <SelectGroup
label={"App icon"} label={"App icon"}
value={value.logo} value={logoValue}
selectLabel={"Select new image"} selectLabel={"Select new image"}
description={"JPG, PNG or SVG, 32x32"} description={"JPG, PNG or SVG, 32x32"}
onSelect={onSelect} onSelect={onSelect}
@ -99,7 +85,7 @@ const BasicBlock = ({
label={"Description"} label={"Description"}
name={"description"} name={"description"}
placeholder={"Enter description"} placeholder={"Enter description"}
value={value.description} value={descriptionValue}
error={error.description} error={error.description}
onChange={onChange} onChange={onChange}
/> />

View File

@ -0,0 +1,60 @@
import React from "react";
import Button from "@docspace/components/button";
import { StyledButtonContainer } from "../ClientForm.styled";
import { DeviceType } from "@docspace/common/constants";
interface ButtonsBlockProps {
saveLabel: string;
cancelLabel: string;
isRequestRunning: boolean;
saveButtonDisabled: boolean;
cancelButtonDisabled: boolean;
onSaveClick: () => void;
onCancelClick: () => void;
currentDeviceType: string;
}
const ButtonsBlock = ({
saveLabel,
cancelLabel,
isRequestRunning,
saveButtonDisabled,
cancelButtonDisabled,
onSaveClick,
onCancelClick,
currentDeviceType,
}: ButtonsBlockProps) => {
const isDesktop = currentDeviceType === DeviceType.desktop;
const buttonSize = isDesktop ? "small" : "normal";
return (
<StyledButtonContainer>
<Button
// @ts-ignore
label={saveLabel}
isLoading={isRequestRunning}
isDisabled={saveButtonDisabled}
primary
size={buttonSize}
scale={!isDesktop}
onClick={onSaveClick}
/>
<Button
// @ts-ignore
label={cancelLabel}
isDisabled={cancelButtonDisabled}
size={buttonSize}
scale={!isDesktop}
onClick={onCancelClick}
/>
</StyledButtonContainer>
);
};
export default ButtonsBlock;

View File

@ -6,6 +6,8 @@ import InputBlock from "@docspace/components/input-block";
import HelpButton from "@docspace/components/help-button"; import HelpButton from "@docspace/components/help-button";
//@ts-ignore //@ts-ignore
import SelectorAddButton from "@docspace/components/selector-add-button"; import SelectorAddButton from "@docspace/components/selector-add-button";
//@ts-ignore
import SelectedItem from "@docspace/components/selected-item";
import { import {
StyledChipsContainer, StyledChipsContainer,
@ -21,8 +23,7 @@ interface MultiInputGroupProps {
placeholder: string; placeholder: string;
currentValue: string[]; currentValue: string[];
onAdd: (name: string, value: string) => void; onAdd: (name: string, value: string, remove?: boolean) => void;
onRemove: (name: string, value: string) => void;
helpButtonText?: string; helpButtonText?: string;
} }
@ -33,7 +34,7 @@ const MultiInputGroup = ({
placeholder, placeholder,
currentValue, currentValue,
onAdd, onAdd,
onRemove,
helpButtonText, helpButtonText,
}: MultiInputGroupProps) => { }: MultiInputGroupProps) => {
const [value, setValue] = React.useState(""); const [value, setValue] = React.useState("");
@ -71,9 +72,23 @@ const MultiInputGroup = ({
tabIndex={0} tabIndex={0}
maxLength={255} maxLength={255}
/> />
<SelectorAddButton /> <SelectorAddButton
onClick={() => {
onAdd(name, value);
setValue("");
}}
/>
</StyledInputRow> </StyledInputRow>
<StyledChipsContainer></StyledChipsContainer> <StyledChipsContainer>
{currentValue.map((v, index) => (
<SelectedItem
key={`${v}-${index}`}
isInline
label={v}
onClose={() => onAdd(name, v)}
/>
))}
</StyledChipsContainer>
</StyledInputGroup> </StyledInputGroup>
); );
}; };

View File

@ -2,9 +2,6 @@ import React from "react";
import { StyledBlock, StyledInputBlock } from "../ClientForm.styled"; import { StyledBlock, StyledInputBlock } from "../ClientForm.styled";
import BlockHeader from "./BlockHeader"; import BlockHeader from "./BlockHeader";
import InputGroup from "./InputGroup";
import TextAreaGroup from "./TextAreaGroup";
import SelectGroup from "./SelectGroup";
import MultiInputGroup from "./MultiInputGroup"; import MultiInputGroup from "./MultiInputGroup";
interface OAuthBlockProps { interface OAuthBlockProps {
@ -23,19 +20,6 @@ const OAuthBlock = ({
changeValue, changeValue,
}: OAuthBlockProps) => { }: OAuthBlockProps) => {
const [value, setValue] = React.useState<{ [key: string]: string[] }>({
redirectUris: redirectUrisValue,
allowedOrigins: allowedOriginsValue,
});
const onAdd = (name: string, value: string) => {
setValue((v) => {
v[name] = [...v[name], value];
return { ...v };
});
};
return ( return (
<StyledBlock> <StyledBlock>
<BlockHeader header={"OAuth URLs"} /> <BlockHeader header={"OAuth URLs"} />
@ -43,19 +27,17 @@ const OAuthBlock = ({
<MultiInputGroup <MultiInputGroup
label={"Redirect URLs"} label={"Redirect URLs"}
placeholder={"Enter URL"} placeholder={"Enter URL"}
name={"redirectUris"} name={"redirect_uris"}
onAdd={onAdd} onAdd={changeValue}
onRemove={onAdd} currentValue={redirectUrisValue}
currentValue={value.redirect_uris}
helpButtonText={"Redirect uris"} helpButtonText={"Redirect uris"}
/> />
<MultiInputGroup <MultiInputGroup
label={"Allowed origins"} label={"Allowed origins"}
placeholder={"Enter URL"} placeholder={"Enter URL"}
name={"allowedOrigins"} name={"allowed_origins"}
onAdd={onAdd} onAdd={changeValue}
onRemove={onAdd} currentValue={allowedOriginsValue}
currentValue={value.allowed_origins}
helpButtonText={"Allowed origins"} helpButtonText={"Allowed origins"}
/> />
</StyledInputBlock> </StyledInputBlock>

View File

@ -24,7 +24,7 @@ import {
interface IScopesBlockProps { interface IScopesBlockProps {
scopes: IScope[]; scopes: IScope[];
selectedScopes: string[]; selectedScopes: string[];
onAddScope: (scope: string[]) => void; onAddScope: (name: string, scope: string) => void;
t: any; t: any;
} }
@ -82,6 +82,8 @@ const ScopesBlock = ({
setCheckedScopes((val) => val.filter((v) => v !== name)); setCheckedScopes((val) => val.filter((v) => v !== name));
} }
onAddScope("scopes", name);
}; };
const getRenderedScopeList = () => { const getRenderedScopeList = () => {

View File

@ -3,8 +3,6 @@ import { StyledBlock, StyledInputBlock } from "../ClientForm.styled";
import BlockHeader from "./BlockHeader"; import BlockHeader from "./BlockHeader";
import InputGroup from "./InputGroup"; import InputGroup from "./InputGroup";
import TextAreaGroup from "./TextAreaGroup";
import SelectGroup from "./SelectGroup";
interface SupportBlockProps { interface SupportBlockProps {
t: any; t: any;
@ -22,11 +20,6 @@ const SupportBlock = ({
changeValue, changeValue,
}: SupportBlockProps) => { }: SupportBlockProps) => {
const [value, setValue] = React.useState<{ [key: string]: string }>({
policyUrl: policyUrlValue,
termsUrl: termsUrlValue,
});
const [error, setError] = React.useState({ const [error, setError] = React.useState({
policyUrl: "", policyUrl: "",
termsUrl: "", termsUrl: "",
@ -35,22 +28,18 @@ const SupportBlock = ({
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => { const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const target = e.target; const target = e.target;
setValue((value) => { changeValue(target.name, target.value);
value[target.name] = target.value;
return { ...value };
});
}; };
return ( return (
<StyledBlock> <StyledBlock>
<BlockHeader header={"Basic info"} /> <BlockHeader header={"Support & Legal info"} />
<StyledInputBlock> <StyledInputBlock>
<InputGroup <InputGroup
label={"Privacy policy URL"} label={"Privacy policy URL"}
name={"policyUrl"} name={"policy_url"}
placeholder={"Enter URL"} placeholder={"Enter URL"}
value={value.policyUrl} value={policyUrlValue}
error={error.policyUrl} error={error.policyUrl}
onChange={onChange} onChange={onChange}
helpButtonText={ helpButtonText={
@ -59,9 +48,9 @@ const SupportBlock = ({
/> />
<InputGroup <InputGroup
label={"Terms of Service URL"} label={"Terms of Service URL"}
name={"termsUrl"} name={"terms_url"}
placeholder={"Enter URL"} placeholder={"Enter URL"}
value={value.termsUrl} value={termsUrlValue}
error={error.termsUrl} error={error.termsUrl}
onChange={onChange} onChange={onChange}
helpButtonText={"Terms of service help"} helpButtonText={"Terms of service help"}

View File

@ -9,18 +9,16 @@ import {
IScope, IScope,
} from "@docspace/common/utils/oauth/interfaces"; } from "@docspace/common/utils/oauth/interfaces";
// @ts-ignore
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
import BasicBlock from "./components/BasicBlock"; import BasicBlock from "./components/BasicBlock";
import ClientBlock from "./components/ClientBlock"; import ClientBlock from "./components/ClientBlock";
import SupportBlock from "./components/SupportBlock"; import SupportBlock from "./components/SupportBlock";
import OAuthBlock from "./components/OAuthBlock";
import ScopesBlock from "./components/ScopesBlock";
import ButtonsBlock from "./components/ButtonsBlock";
import { StyledContainer } from "./ClientForm.styled"; import { StyledContainer } from "./ClientForm.styled";
import { ClientFormProps } from "./ClientForm.types"; import { ClientFormProps, ClientStore } from "./ClientForm.types";
import OAuthBlock from "./components/OAuthBlock";
import ScopesBlock from "./components/ScopesBlock";
const ClientForm = ({ const ClientForm = ({
id, id,
@ -36,11 +34,15 @@ const ClientForm = ({
updateClient, updateClient,
regenerateSecret, regenerateSecret,
currentDeviceType,
}: ClientFormProps) => { }: ClientFormProps) => {
const { t } = useTranslation(["Common"]); const { t } = useTranslation(["Common"]);
const navigate = useNavigate(); const navigate = useNavigate();
const [isLoading, setIsLoading] = React.useState<boolean>(false); const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [isRequestRunning, setIsRequestRunning] =
React.useState<boolean>(false);
const [initClient, setInitClient] = React.useState<IClientProps | null>(null); const [initClient, setInitClient] = React.useState<IClientProps | null>(null);
@ -50,16 +52,16 @@ const ClientForm = ({
website_url: "", website_url: "",
description: "", description: "",
redirect_uris: [""], redirect_uris: [],
allowed_origins: [""], allowed_origins: [],
logout_redirect_uris: [""], logout_redirect_uris: [],
terms_url: "", terms_url: "",
policy_url: "", policy_url: "",
authentication_method: "", authentication_method: "",
scopes: [""], scopes: [],
}); });
const [clientId, setClientId] = React.useState<string>(""); const [clientId, setClientId] = React.useState<string>("");
@ -174,8 +176,19 @@ const ClientForm = ({
setForm((val) => { setForm((val) => {
const newVal = { ...val }; const newVal = { ...val };
if (typeof newVal[name as keyof IClientReqDTO]) { if (newVal[name as keyof IClientReqDTO] instanceof Array) {
typeof newVal[name as keyof IClientReqDTO] === value; if (newVal[name as keyof IClientReqDTO].includes(value)) {
newVal[name as keyof IClientReqDTO] = newVal[
name as keyof IClientReqDTO
//@ts-ignore
].filter((v: string) => v !== value);
} else {
//@ts-ignore
newVal[name as keyof IClientReqDTO].push(value);
}
} else {
//@ts-ignore
newVal[name as keyof IClientReqDTO] = value;
} }
return { ...newVal }; return { ...newVal };
@ -197,6 +210,7 @@ const ClientForm = ({
}, [id, scopeList, getScopeList, fetchScopes]); }, [id, scopeList, getScopeList, fetchScopes]);
// React.useEffect(() => { // React.useEffect(() => {
// if (id) { // if (id) {
// setClientId(id); // setClientId(id);
// if (!client) { // if (!client) {
@ -207,69 +221,58 @@ const ClientForm = ({
// } // }
// }, [id, client, fetchClient, getClient, setClient]); // }, [id, client, fetchClient, getClient, setClient]);
// const compareAndValidate = () => { const compareAndValidate = () => {
// let isValid = false; let isValid = true;
// for (let key in form) { for (let key in form) {
// if (!!form[key] || key === "appIcon" || key === "authenticationMethod") { switch (key) {
// if (initClient) { case "name":
// switch (key) { isValid = isValid && !!form[key];
// case "appName":
// isValid = isValid || initClient.name !== form[key];
// break; break;
// case "appIcon": case "logo":
// isValid = isValid || initClient.name !== form[key]; isValid = isValid && !!form[key];
// break; break;
// case "description": case "description":
// isValid = isValid || initClient.description !== form[key]; isValid = isValid && !!form[key];
// break; break;
// case "redirectUrl": case "website_url":
// isValid = isValid || initClient.redirectUri !== form[key]; isValid = isValid && !!form[key];
// break; break;
// case "logoutRedirectUrl": case "redirect_uris":
// isValid = isValid || initClient.logoutRedirectUri !== form[key]; isValid = isValid && form[key].length > 0;
// break; break;
// case "privacyUrl": case "allowed_origins":
// isValid = isValid || initClient.policyUrl !== form[key]; isValid = isValid && form[key].length > 0;
// break; break;
case "logout_redirect_uris":
isValid = isValid;
// case "termsUrl": break;
// isValid = isValid || initClient.termsUrl !== form[key]; case "terms_url":
isValid = isValid && !!form[key];
break;
case "policy_url":
isValid = isValid && !!form[key];
break;
case "authentication_method":
isValid = isValid;
break;
case "scopes":
isValid = isValid && form[key].length > 0;
break;
}
}
// break; return isValid;
// } };
// }
// isValid = true;
// } else {
// isValid = false;
// }
// }
// if (checkedScopes.length > 0) { const isValid = compareAndValidate();
// if (initClient) {
// let isSame = checkedScopes.length === initClient?.scopes.length;
// if (isSame) {
// checkedScopes.forEach((scope) => {
// if (!initClient?.scopes.includes(scope)) isSame = false;
// });
// }
// isValid = isValid || !isSame;
// }
// } else {
// isValid = false;
// }
// return isValid;
// };
// const isValid = compareAndValidate();
return ( return (
<StyledContainer> <StyledContainer>
@ -292,23 +295,29 @@ const ClientForm = ({
t={t} t={t}
redirectUrisValue={form.redirect_uris} redirectUrisValue={form.redirect_uris}
allowedOriginsValue={form.allowed_origins} allowedOriginsValue={form.allowed_origins}
changeValue={(name: string, value: string) => { changeValue={onChangeForm}
console.log(name, value);
}}
/> />
<ScopesBlock <ScopesBlock
t={t} t={t}
scopes={scopeList || []} scopes={scopeList || []}
selectedScopes={[]} selectedScopes={[]}
onAddScope={() => {}} onAddScope={onChangeForm}
/> />
<SupportBlock <SupportBlock
t={t} t={t}
policyUrlValue={form.policy_url} policyUrlValue={form.policy_url}
termsUrlValue={form.terms_url} termsUrlValue={form.terms_url}
changeValue={(name: string, value: string) => { changeValue={onChangeForm}
console.log(name, value); />
}} <ButtonsBlock
saveLabel={"Save"}
cancelLabel={"Cancel"}
onSaveClick={() => {}}
onCancelClick={() => {}}
isRequestRunning={isRequestRunning}
saveButtonDisabled={!isValid}
cancelButtonDisabled={isRequestRunning}
currentDeviceType={currentDeviceType || ""}
/> />
</> </>
)} )}
@ -317,10 +326,7 @@ const ClientForm = ({
}; };
export default inject( export default inject(
( ({ oauthStore, auth }: ClientStore, { id }: ClientFormProps) => {
{ oauthStore }: { oauthStore: OAuthStoreProps },
{ id }: ClientFormProps
) => {
const { const {
clientList, clientList,
scopeList, scopeList,
@ -334,6 +340,8 @@ export default inject(
regenerateSecret, regenerateSecret,
} = oauthStore; } = oauthStore;
const { currentDeviceType } = auth.settingsStore;
const props: ClientFormProps = { const props: ClientFormProps = {
scopeList, scopeList,
@ -344,6 +352,7 @@ export default inject(
updateClient, updateClient,
regenerateSecret, regenerateSecret,
currentDeviceType,
}; };
if (id) { if (id) {