PortalSettings:OAuth: add create client form

This commit is contained in:
Timofey Boyko 2023-10-27 18:24:18 +03:00
parent 348383e2fb
commit 86f17497a8
25 changed files with 1436 additions and 870 deletions

View File

@ -1,6 +1,17 @@
import React from "react";
import { useTranslation } from "react-i18next";
//@ts-ignore
import { setDocumentTitle } from "SRC_DIR/helpers/utils";
import ClientForm from "../sub-components/ClientForm";
const OAuthCreatePage = () => {
const { t } = useTranslation(["OAuth"]);
React.useEffect(() => {
setDocumentTitle(t("OAuth"));
}, []);
return <ClientForm />;
};

View File

@ -1,10 +1,21 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
//@ts-ignore
import { setDocumentTitle } from "SRC_DIR/helpers/utils";
import ClientForm from "../sub-components/ClientForm";
const OAuthEditPage = () => {
const { id } = useParams();
const { t } = useTranslation(["OAuth"]);
React.useEffect(() => {
setDocumentTitle(t("OAuth"));
}, []);
return <ClientForm id={id} />;
};

View File

@ -4,6 +4,8 @@ import { useTranslation } from "react-i18next";
//@ts-ignore
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
//@ts-ignore
import { setDocumentTitle } from "SRC_DIR/helpers/utils";
import OAuthEmptyScreen from "./sub-components/EmptyScreen";
import List from "./sub-components/List";
@ -48,6 +50,10 @@ const OAuth = ({
getClientList();
}, [getClientList]);
React.useEffect(() => {
setDocumentTitle(t("OAuth"));
}, []);
return (
<OAuthContainer>
{isLoading ? (

View File

@ -1,48 +1,18 @@
import styled from "styled-components";
//@ts-ignore
import Box from "@docspace/components/box";
import { hugeMobile, tablet } from "@docspace/components/utils/device";
import { mobile, tablet } from "@docspace/components/utils/device";
const Container = styled.div`
const StyledContainer = styled.div`
width: 100%;
display: grid;
grid-template-columns: 350px 350px;
gap: 16px;
.preview-container {
margin-top: 16px;
width: fit-content;
min-width: 350px;
height: fit-content;
border: 1px solid #a3aeae;
border-radius: 6px;
padding: 16px;
}
`;
const FormContainer = styled.div`
max-width: 350px;
max-width: 660px;
display: flex;
flex-direction: column;
gap: 20px;
.button-container {
width: 100;
display: flex;
flex-direction: raw;
gap: 8px;
}
gap: 24px;
`;
const BlockContainer = styled.div`
const StyledBlock = styled.div`
width: 100%;
height: auto;
@ -51,11 +21,11 @@ const BlockContainer = styled.div`
gap: 12px;
`;
const HeaderRaw = styled.div`
const StyledHeaderRow = styled.div`
width: 100%;
display: flex;
flex-direction: raw;
flex-direction: row;
gap: 4px;
align-items: center;
@ -65,20 +35,64 @@ const HeaderRaw = styled.div`
}
`;
const InputGroup = styled.div`
const StyledInputBlock = styled.div`
width: 100%;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
@media ${mobile} {
display: flex;
flex-direction: column;
}
`;
const StyledInputGroup = styled.div`
width: 100%;
height: auto;
display: flex;
flex-direction: column;
gap: 4px;
svg {
cursor: pointer;
}
.select {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
margin: 4px 0;
.logo {
max-width: 32px;
max-height: 32px;
width: 32px;
height: 32px;
border-radius: 3px;
}
p {
color: #a3a9ae;
}
}
.description {
color: #a3a9ae;
}
`;
const InputRaw = styled.div`
const StyledInputRow = styled.div`
width: 100%;
display: flex;
flex-direction: raw;
flex-direction: row;
justify-content: space-between;
gap: 8px;
@ -88,74 +102,139 @@ const InputRaw = styled.div`
}
`;
const CheckboxGroup = styled.div`
const StyledChipsContainer = styled.div`
width: 100%;
height: auto;
display: flex;
flex-direction: column;
flex-wrap: wrap;
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 CheckboxRaw = styled.div`
// 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`
width: 100%;
height: auto;
display: grid;
grid-template-columns: 1fr max-content max-content;
align-items: center;
gap: 16px 0;
.header {
padding-bottom: 8px;
padding-right: 24px;
margin-right: -12px;
border-bottom: 1px solid #474747;
}
.header-last {
margin-right: 0px;
padding-right: 0px;
}
.checkbox-read {
margin-right: 12px;
}
`;
const StyledScopesName = styled.div`
display: flex;
flex-direction: column;
.scope-name {
margin-bottom: 2px;
}
.scope-desc {
color: #858585;
}
`;
const StyledScopesCheckbox = styled.div`
width: 100%;
height: 100%;
display: flex;
flex-direction: raw;
align-items: center;
gap: 8px;
align-items: flex-start;
justify-content: flex-end;
.checkbox {
margin-right: 0px;
}
`;
const Frame = styled(Box)`
margin-top: 16px;
position: relative;
display: flex;
justify-content: center;
button {
width: auto;
max-width: auto;
padding: 0 20px;
}
`;
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;
}
}
`;
export {
Container,
FormContainer,
BlockContainer,
HeaderRaw,
InputGroup,
InputRaw,
CheckboxGroup,
CheckboxRaw,
Frame,
CategorySubHeader,
StyledContainer,
StyledBlock,
StyledHeaderRow,
StyledInputBlock,
StyledInputGroup,
StyledInputRow,
StyledChipsContainer,
StyledChips,
StyledScopesContainer,
StyledScopesName,
StyledScopesCheckbox,
};

View File

@ -1,4 +1,4 @@
import { ClientProps, Scope } from "@docspace/common/utils/oauth/interfaces";
import { IClientProps, IScope } from "@docspace/common/utils/oauth/interfaces";
export interface InputProps {
value: string;
@ -25,32 +25,24 @@ export interface CheckboxProps {
description: string;
}
export interface BlockHeaderProps {
header: string;
helpButtonText: string;
}
export interface BlockProps {
children: React.ReactNode;
}
export interface ClientFormProps {
id?: string;
client?: ClientProps;
client?: IClientProps;
tenant?: number;
fetchTenant?: () => Promise<number>;
scopeList?: IScope[];
scopeList?: Scope[];
fetchClient?: (clientId: string) => Promise<ClientProps>;
fetchClient?: (clientId: string) => Promise<IClientProps>;
fetchScopes?: () => Promise<void>;
saveClient?: (client: ClientProps) => Promise<ClientProps>;
saveClient?: (client: IClientProps) => Promise<IClientProps>;
updateClient?: (
clientId: string,
client: ClientProps
) => Promise<ClientProps>;
client: IClientProps
) => Promise<IClientProps>;
regenerateSecret?: (clientId: string) => Promise<string>;
}

View File

@ -0,0 +1,111 @@
import React from "react";
import { StyledBlock, StyledInputBlock } from "../ClientForm.styled";
import BlockHeader from "./BlockHeader";
import InputGroup from "./InputGroup";
import TextAreaGroup from "./TextAreaGroup";
import SelectGroup from "./SelectGroup";
interface BasicBlockProps {
t: any;
nameValue: string;
websiteUrlValue: string;
logoValue: string;
descriptionValue: string;
changeValue: (name: string, value: string) => void;
}
const BasicBlock = ({
t,
nameValue,
websiteUrlValue,
logoValue,
descriptionValue,
changeValue,
}: BasicBlockProps) => {
const [value, setValue] = React.useState<{ [key: string]: string }>({
name: nameValue,
websiteUrl: websiteUrlValue,
logo: logoValue,
description: descriptionValue,
});
const [error, setError] = React.useState({
name: "",
websiteUrl: "",
logo: "",
description: "",
});
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const target = e.target;
setValue((value) => {
value[target.name] = target.value;
return { ...value };
});
changeValue(target.name, target.value);
};
const onSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
const file =
e.target.files && e.target.files?.length > 0 && e.target.files[0];
if (file) {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const result = reader.result as string;
setValue((v) => ({ ...v, logo: result }));
changeValue("logo", result);
};
}
};
return (
<StyledBlock>
<BlockHeader header={"Basic info"} />
<StyledInputBlock>
<InputGroup
label={"App name"}
name={"name"}
placeholder={"Enter name"}
value={value.name}
error={error.name}
onChange={onChange}
/>
<InputGroup
label={"Website URL"}
name={"websiteUrl"}
placeholder={"Enter URL"}
value={value.websiteUrl}
error={error.websiteUrl}
onChange={onChange}
/>
<SelectGroup
label={"App icon"}
value={value.logo}
selectLabel={"Select new image"}
description={"JPG, PNG or SVG, 32x32"}
onSelect={onSelect}
/>
<TextAreaGroup
label={"Description"}
name={"description"}
placeholder={"Enter description"}
value={value.description}
error={error.description}
onChange={onChange}
/>
</StyledInputBlock>
</StyledBlock>
);
};
export default BasicBlock;

View File

@ -1,8 +0,0 @@
import { BlockContainer } from "../ClientForm.styled";
import { BlockProps } from "../ClientForm.types";
const Block = ({ children }: BlockProps) => {
return <BlockContainer>{children}</BlockContainer>;
};
export default Block;

View File

@ -2,12 +2,21 @@ import Text from "@docspace/components/text";
//@ts-ignore
import HelpButton from "@docspace/components/help-button";
import { HeaderRaw } from "../ClientForm.styled";
import { BlockHeaderProps } from "../ClientForm.types";
import { StyledHeaderRow } from "../ClientForm.styled";
const BlockHeader = ({ header, helpButtonText }: BlockHeaderProps) => {
interface BlockHeaderProps {
header: string;
helpButtonText?: string;
className?: string;
}
const BlockHeader = ({
header,
helpButtonText,
className,
}: BlockHeaderProps) => {
return (
<HeaderRaw>
<StyledHeaderRow className={className}>
<Text
fontSize={"16px"}
fontWeight={700}
@ -20,8 +29,8 @@ const BlockHeader = ({ header, helpButtonText }: BlockHeaderProps) => {
>
{header}
</Text>
<HelpButton tooltipContent={helpButtonText} />
</HeaderRaw>
{helpButtonText && <HelpButton tooltipContent={helpButtonText} />}
</StyledHeaderRow>
);
};

View File

@ -1,44 +0,0 @@
import Checkbox from "@docspace/components/checkbox";
import Text from "@docspace/components/text";
import { CheckboxRaw } from "../ClientForm.styled";
import { CheckboxProps } from "../ClientForm.types";
const CheckboxComponent = ({
isChecked,
onChange,
label,
description,
}: CheckboxProps) => {
return (
<CheckboxRaw>
<Checkbox isChecked={isChecked} onChange={onChange} />
<Text
fontSize={"13px"}
fontWeight={400}
lineHeight={"20px"}
title={label}
tag={""}
as={"p"}
color={""}
textAlign={""}
>
{label}
</Text>
<Text
fontSize={"13px"}
fontWeight={400}
lineHeight={"20px"}
title={label}
tag={""}
as={"p"}
color={"#A3A9AE"}
textAlign={""}
>
{description}
</Text>
</CheckboxRaw>
);
};
export default CheckboxComponent;

View File

@ -0,0 +1,55 @@
import React from "react";
import { StyledBlock, StyledInputBlock } from "../ClientForm.styled";
import BlockHeader from "./BlockHeader";
import InputGroup from "./InputGroup";
interface ClientBlockProps {
t: any;
idValue: string;
secretValue: string;
}
const ClientBlock = ({ t, idValue, secretValue }: ClientBlockProps) => {
const [value, setValue] = React.useState<{ [key: string]: string }>({
id: idValue,
secret: secretValue,
});
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {};
return (
<StyledBlock>
<BlockHeader
header={"Client"}
helpButtonText="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."
/>
<StyledInputBlock>
<InputGroup
label={"ID"}
name={""}
placeholder={""}
value={value.id}
error={""}
onChange={onChange}
withCopy
/>
<InputGroup
label={"Secret"}
name={""}
placeholder={""}
value={value.secret}
error={""}
onChange={onChange}
withCopy
isPassword
buttonLabel={"Reset"}
/>
</StyledInputBlock>
</StyledBlock>
);
};
export default ClientBlock;

View File

@ -1,65 +0,0 @@
import copy from "copy-to-clipboard";
import InputBlock from "@docspace/components/input-block";
import Button from "@docspace/components/button";
// @ts-ignore
import toastr from "@docspace/components/toast/toastr";
import CopyReactSvgUrl from "PUBLIC_DIR/images/copy.react.svg?url";
import { InputProps } from "../ClientForm.types";
import { InputRaw } from "../ClientForm.styled";
const Input = ({
value,
placeholder,
name,
onChange,
isReadOnly,
isSecret,
withCopy,
withButton,
buttonLabel,
onClickButton,
multiplyInput,
}: InputProps) => {
const onCopy = () => {
if (value) {
toastr.success(
isSecret
? "Secret has been copied to the clipboard"
: "ID has been copied to the clipboard"
);
copy(value);
}
};
return (
<InputRaw>
<InputBlock
value={value}
name={name}
placeholder={placeholder}
onChange={onChange}
size={"base"}
isReadOnly={isReadOnly}
isDisabled={isReadOnly}
iconName={withCopy ? CopyReactSvgUrl : null}
onIconClick={withCopy && onCopy}
scale={true}
type={isSecret ? "password" : "text"}
/>
{withButton && (
<Button
//@ts-ignore
label={buttonLabel}
size={"small"}
onClick={onClickButton}
/>
)}
</InputRaw>
);
};
export default Input;

View File

@ -0,0 +1,103 @@
import React from "react";
import Text from "@docspace/components/text";
import InputBlock from "@docspace/components/input-block";
import Button from "@docspace/components/button";
//@ts-ignore
import HelpButton from "@docspace/components/help-button";
import CopyReactSvgUrl from "PUBLIC_DIR/images/copy.react.svg?url";
import {
StyledHeaderRow,
StyledInputGroup,
StyledInputRow,
} from "../ClientForm.styled";
interface InputGroupProps {
label: string;
name: string;
value: string;
placeholder: string;
error: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
helpButtonText?: string;
buttonLabel?: string;
onButtonClick?: () => void;
withCopy?: boolean;
onCopyClick?: (name: string) => void;
isPassword?: boolean;
}
const InputGroup = ({
label,
name,
value,
placeholder,
error,
onChange,
helpButtonText,
buttonLabel,
onButtonClick,
withCopy,
onCopyClick,
isPassword,
}: 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}
iconName={withCopy ? CopyReactSvgUrl : null}
onIconClick={withCopy && onCopyClick}
type={isPassword ? "password" : "text"}
/>
{buttonLabel && (
<Button
//@ts-ignore
label={buttonLabel}
size={"small"}
onClick={onButtonClick}
/>
)}
</StyledInputRow>
</StyledInputGroup>
);
};
export default InputGroup;

View File

@ -1,20 +0,0 @@
import Text from "@docspace/components/text";
const InputHeader = ({ header }: { header: string }) => {
return (
<Text
fontSize={"13px"}
fontWeight={600}
lineHeight={"20px"}
title={header}
tag={""}
as={"p"}
color={""}
textAlign={""}
>
{header}
</Text>
);
};
export default InputHeader;

View File

@ -0,0 +1,81 @@
import React from "react";
import Text from "@docspace/components/text";
import InputBlock from "@docspace/components/input-block";
//@ts-ignore
import HelpButton from "@docspace/components/help-button";
//@ts-ignore
import SelectorAddButton from "@docspace/components/selector-add-button";
import {
StyledChipsContainer,
StyledHeaderRow,
StyledInputGroup,
StyledInputRow,
} from "../ClientForm.styled";
interface MultiInputGroupProps {
label: string;
name: string;
placeholder: string;
currentValue: string[];
onAdd: (name: string, value: string) => void;
onRemove: (name: string, value: string) => void;
helpButtonText?: string;
}
const MultiInputGroup = ({
label,
name,
placeholder,
currentValue,
onAdd,
onRemove,
helpButtonText,
}: MultiInputGroupProps) => {
const [value, setValue] = React.useState("");
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
setValue(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}
/>
<SelectorAddButton />
</StyledInputRow>
<StyledChipsContainer></StyledChipsContainer>
</StyledInputGroup>
);
};
export default MultiInputGroup;

View File

@ -0,0 +1,66 @@
import React from "react";
import { StyledBlock, StyledInputBlock } from "../ClientForm.styled";
import BlockHeader from "./BlockHeader";
import InputGroup from "./InputGroup";
import TextAreaGroup from "./TextAreaGroup";
import SelectGroup from "./SelectGroup";
import MultiInputGroup from "./MultiInputGroup";
interface OAuthBlockProps {
t: any;
redirectUrisValue: string[];
allowedOriginsValue: string[];
changeValue: (name: string, value: string) => void;
}
const OAuthBlock = ({
t,
redirectUrisValue,
allowedOriginsValue,
changeValue,
}: 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 (
<StyledBlock>
<BlockHeader header={"OAuth URLs"} />
<StyledInputBlock>
<MultiInputGroup
label={"Redirect URLs"}
placeholder={"Enter URL"}
name={"redirectUris"}
onAdd={onAdd}
onRemove={onAdd}
currentValue={value.redirect_uris}
helpButtonText={"Redirect uris"}
/>
<MultiInputGroup
label={"Allowed origins"}
placeholder={"Enter URL"}
name={"allowedOrigins"}
onAdd={onAdd}
onRemove={onAdd}
currentValue={value.allowed_origins}
helpButtonText={"Allowed origins"}
/>
</StyledInputBlock>
</StyledBlock>
);
};
export default OAuthBlock;

View File

@ -1,178 +0,0 @@
import React from "react";
//@ts-ignore
import Loaders from "@docspace/common/components/Loaders";
//@ts-ignore
import Box from "@docspace/components/box";
//@ts-ignore
import Textarea from "@docspace/components/textarea";
//@ts-ignore
import TabContainer from "@docspace/components/tabs-container";
//@ts-ignore
import SocialButton from "@docspace/components/social-button";
import ImagesLogoSvgUrl from "PUBLIC_DIR/images/logo/leftmenu.svg?url";
import { PreviewProps } from "../ClientForm.types";
import { Frame, CategorySubHeader } from "../ClientForm.styled";
const Preview = ({ clientId, redirectURI, scopes }: PreviewProps) => {
const getAuthLink = () => {
const origin = window.location.origin;
const path = "/oauth2/authorize";
const params: { [key: string]: string } = {
response_type: "code",
client_id: clientId,
redirect_uri: redirectURI,
scope: scopes[0],
code_challenge_method: "S256",
code_challenge: "ZX8YUY6qL0EweJXhjDug0S2XuKI8beOWb1LGujmgfuQ",
};
const paramsString = [];
for (let key in params) {
const str = `${key}=${params[key]}`;
paramsString.push(str);
}
const link = `${origin}${path}?${paramsString.join("&")}`;
return link;
};
const onDocSpaceLogin = () => {
const url = getAuthLink();
window.open(
url,
"login",
"width=800,height=500,status=no,toolbar=no,menubar=no,resizable=yes,scrollbars=no"
);
};
const preview = (
<Frame>
<SocialButton
iconName={ImagesLogoSvgUrl}
label={"Sign in with DocSpace"}
onClick={onDocSpaceLogin}
/>
</Frame>
);
const htmlBlock = `<button id="docspace-button" class="docspace-button">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28" fill="none" class="injected-svg logo-svg"
data-src="http://192.168.0.15/static/js/../../static/images/logo/leftmenu.svg?hash=c31b569ea8c6322337cd"
xmlns:xlink="http://www.w3.org/1999/xlink">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M12.411 25.7059L1.68526 20.7649C0.771581 20.3335 0.771581 19.6669 1.68526 19.2748L5.4194 17.5493L12.3713 20.7649C13.285 21.1963 14.7548 21.1963 15.6287 20.7649L22.5806 17.5493L26.3147 19.2748C27.2284 19.7061 27.2284 20.3728 26.3147 20.7649L15.589 25.7059C14.7548 26.0981 13.285 26.0981 12.411 25.7059Z"
fill="#FF6F3D"></path>
<path fill-rule="evenodd" clip-rule="evenodd"
d="M12.3762 19.5917L1.68317 14.6449C0.772277 14.213 0.772277 13.5456 1.68317 13.153L5.32673 11.4648L12.3762 14.7234C13.2871 15.1553 14.7525 15.1553 15.6238 14.7234L22.6733 11.4648L26.3168 13.153C27.2277 13.5849 27.2277 14.2523 26.3168 14.6449L15.6238 19.5917C14.7129 20.0235 13.2475 20.0235 12.3762 19.5917Z"
fill="#95C038"></path>
<path fill-rule="evenodd" clip-rule="evenodd"
d="M12.3762 13.5408L1.68317 8.66521C0.772277 8.23956 0.772277 7.58175 1.68317 7.1948L12.3762 2.31923C13.2871 1.89359 14.7525 1.89359 15.6238 2.31923L26.3168 7.1948C27.2277 7.62044 27.2277 8.27826 26.3168 8.66521L15.6238 13.5408C14.7129 13.9277 13.2475 13.9277 12.3762 13.5408Z"
fill="#5DC0E8"></path>
</svg>
Sign in with DocSpace
</button>`;
const styleBlock = `.docspace-button {
width: auto;
padding: 0 20px;
display: flex;
flex-direction: row;
align-items: center;
text-decoration: none;
border-radius: 2px;
height: 40px;
border: none;
stroke: none;
background: #ffffff;
box-shadow: rgba(0, 0, 0, 0.24) 0px 1px 1px, rgba(0, 0, 0, 0.12) 0px 0px 1px;
color: rgb(163, 169, 174);
font-weight: 600;
font-size: 14px;
line-height: 14px;
user-select: none;
font-family: Roboto, "Open Sans", sans-serif, Arial;
}
.docspace-button:hover {
box-shadow: rgba(0, 0, 0, 0.24) 0px 1px 1px, rgba(0, 0, 0, 0.12) 0px 0px 1px;
cursor: pointer;
color: #333333;
}
.logo-svg {
width: 18px;
min-width: 18px;
height: 18px;
min-height: 18px;
margin: 11px 16px;
}
`;
const url = getAuthLink();
const scriptCode = `const button = document.getElementById('docspace-button')
function openOAuthPage() {
window.open(
"${url}",
"login",
"width=800,height=500,status=no,toolbar=no,menubar=no,resizable=yes,scrollbars=no"
);
}
button.addEventListener('click', openOAuthPage)`;
const code = (
<>
<CategorySubHeader className="copy-window-code">
{"Copy HTML code"}
</CategorySubHeader>
<Textarea value={htmlBlock} />
<CategorySubHeader className="copy-window-code">
{"Copy CSS code"}
</CategorySubHeader>
<Textarea value={styleBlock} />
<CategorySubHeader className="copy-window-code">
{"Copy JS code"}
</CategorySubHeader>
<Textarea value={scriptCode} />
</>
);
const dataTabs = [
{
key: "preview",
title: "Preview",
content: preview,
},
{
key: "code",
title: "Code",
content: code,
},
];
return (
<div className="preview-container">
<TabContainer elements={dataTabs} />
</div>
);
};
export default Preview;

View File

@ -0,0 +1,215 @@
import React from "react";
import {
IFilteredScopes,
IScope,
} from "@docspace/common/utils/oauth/interfaces";
import {
filterScopeByGroup,
getScopeTKeyName,
} from "@docspace/common/utils/oauth";
import { ScopeGroup, ScopeType } from "@docspace/common/utils/oauth/enums";
import Text from "@docspace/components/text";
import Checkbox from "@docspace/components/checkbox";
import BlockHeader from "./BlockHeader";
import {
StyledScopesCheckbox,
StyledScopesContainer,
StyledScopesName,
} from "../ClientForm.styled";
interface IScopesBlockProps {
scopes: IScope[];
selectedScopes: string[];
onAddScope: (scope: string[]) => void;
t: any;
}
const ScopesBlock = ({
scopes,
selectedScopes,
onAddScope,
t,
}: IScopesBlockProps) => {
const [checkedScopes, setCheckedScopes] = React.useState<string[]>([]);
const [filteredScopes, setFilteredScopes] = React.useState<IFilteredScopes>(
filterScopeByGroup(selectedScopes, scopes)
);
React.useEffect(() => {
const filtered = filterScopeByGroup(selectedScopes, scopes);
setCheckedScopes([...selectedScopes]);
setFilteredScopes({ ...filtered });
}, []);
const onAddCheckedScope = (
group: ScopeGroup,
type: ScopeType,
name: string
) => {
const isChecked = checkedScopes.includes(name);
if (!isChecked) {
setFilteredScopes((val) => {
val[group].isChecked = true;
val[group].checkedType = type;
return { ...val };
});
setCheckedScopes((val) => [...val, name]);
} else {
if (type === ScopeType.read) {
setFilteredScopes((val) => {
val[group].isChecked = false;
val[group].checkedType = undefined;
return { ...val };
});
} else {
setFilteredScopes((val) => {
const isReadChecked = checkedScopes.includes(val[group].read.name);
val[group].isChecked = isReadChecked;
val[group].checkedType = isReadChecked ? ScopeType.read : undefined;
return { ...val };
});
}
setCheckedScopes((val) => val.filter((v) => v !== name));
}
};
const getRenderedScopeList = () => {
const list = [];
for (let key in filteredScopes) {
const name = getScopeTKeyName(key as ScopeGroup);
const scope = filteredScopes[key];
const isReadDisabled = scope.checkedType === ScopeType.write;
const isReadChecked = scope.isChecked;
const row = (
<React.Fragment key={name}>
<StyledScopesName>
{/* @ts-ignore */}
<Text
className="scope-name"
fontSize={"14px"}
fontWeight={600}
lineHeight={"16px"}
>
{t(`${name}`)}
</Text>
{/* @ts-ignore */}
<Text
className={"scope-desc"}
fontSize={"12px"}
fontWeight={400}
lineHeight={"16px"}
>
{/* @ts-ignore */}
<Text
className={"scope-desc"}
as={"span"}
fontSize={"12px"}
fontWeight={600}
lineHeight={"16px"}
>
{scope.read.name}
</Text>{" "}
{t(`${scope.read.tKey}`)}
</Text>
{/* @ts-ignore */}
<Text
className={"scope-desc"}
fontSize={"12px"}
fontWeight={400}
lineHeight={"16px"}
>
{/* @ts-ignore */}
<Text
className={"scope-desc"}
as={"span"}
fontSize={"12px"}
fontWeight={600}
lineHeight={"16px"}
>
{scope.write.name}
</Text>
{t(`${scope.write.tKey}`)}
</Text>
</StyledScopesName>
<StyledScopesCheckbox>
<Checkbox
className="checkbox-read"
isChecked={isReadChecked}
isDisabled={isReadDisabled}
onChange={() =>
onAddCheckedScope(
key as ScopeGroup,
ScopeType.read,
scope.read.name
)
}
/>
</StyledScopesCheckbox>
<StyledScopesCheckbox>
<Checkbox
isChecked={isReadDisabled}
onChange={() =>
onAddCheckedScope(
key as ScopeGroup,
ScopeType.write,
scope.write.name
)
}
/>
</StyledScopesCheckbox>
</React.Fragment>
);
list.push(row);
}
return list;
};
const list = getRenderedScopeList();
return (
<StyledScopesContainer>
<BlockHeader
className="header"
header={"Access scopes"}
helpButtonText="Access scopes help"
/>
{/* @ts-ignore */}
<Text
className="header"
fontSize={"14px"}
fontWeight={600}
lineHeight={"22px"}
>
Read
</Text>
{/* @ts-ignore */}
<Text
className="header header-last"
fontSize={"14px"}
fontWeight={600}
lineHeight={"22px"}
>
Write
</Text>
{list.map((item) => item)}
</StyledScopesContainer>
);
};
export default ScopesBlock;

View File

@ -0,0 +1,106 @@
import React from "react";
import Text from "@docspace/components/text";
//@ts-ignore
import SelectorAddButton from "@docspace/components/selector-add-button";
import { StyledInputGroup } from "../ClientForm.styled";
interface SelectGroupProps {
label: string;
selectLabel: string;
value: string;
description: string;
onSelect: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
const SelectGroup = ({
label,
selectLabel,
value,
description,
onSelect,
}: SelectGroupProps) => {
const inputRef = React.useRef<HTMLInputElement | null>(null);
const onClick = () => {
if (inputRef.current) {
inputRef.current.click();
}
};
const onInputClick = () => {
if (inputRef.current) {
inputRef.current.value = "";
inputRef.current.files = null;
}
};
return (
<StyledInputGroup>
<div className="label">
<Text
fontSize={"13px"}
fontWeight={600}
lineHeight={"20px"}
title={""}
tag={""}
as={"p"}
color={""}
textAlign={""}
>
{label}
</Text>
</div>
<div className="select">
{value && <img className="logo" src={value} />}
<SelectorAddButton onClick={onClick} />
<Text
fontSize={"13px"}
fontWeight={600}
lineHeight={"20px"}
title={""}
tag={""}
as={"p"}
color={""}
textAlign={""}
>
{selectLabel}
</Text>
</div>
<Text
fontSize={"12px"}
fontWeight={600}
lineHeight={"16px"}
title={""}
tag={""}
as={"p"}
color={""}
textAlign={""}
className="description"
>
{description}
</Text>
<input
ref={inputRef}
id="customFileInput"
className="custom-file-input"
multiple
type="file"
onChange={onSelect}
onClick={onInputClick}
style={{ display: "none" }}
accept="image/png, image/jpeg, svg"
/>
</StyledInputGroup>
);
};
export default SelectGroup;

View File

@ -0,0 +1,74 @@
import React from "react";
import { StyledBlock, StyledInputBlock } from "../ClientForm.styled";
import BlockHeader from "./BlockHeader";
import InputGroup from "./InputGroup";
import TextAreaGroup from "./TextAreaGroup";
import SelectGroup from "./SelectGroup";
interface SupportBlockProps {
t: any;
policyUrlValue: string;
termsUrlValue: string;
changeValue: (name: string, value: string) => void;
}
const SupportBlock = ({
t,
policyUrlValue,
termsUrlValue,
changeValue,
}: SupportBlockProps) => {
const [value, setValue] = React.useState<{ [key: string]: string }>({
policyUrl: policyUrlValue,
termsUrl: termsUrlValue,
});
const [error, setError] = React.useState({
policyUrl: "",
termsUrl: "",
});
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const target = e.target;
setValue((value) => {
value[target.name] = target.value;
return { ...value };
});
};
return (
<StyledBlock>
<BlockHeader header={"Basic info"} />
<StyledInputBlock>
<InputGroup
label={"Privacy policy URL"}
name={"policyUrl"}
placeholder={"Enter URL"}
value={value.policyUrl}
error={error.policyUrl}
onChange={onChange}
helpButtonText={
"Provide a URL link to your Privacy Policy that must comply with applicable laws and regulations and that make clear how you collect, use, share, retain and otherwise process personal information."
}
/>
<InputGroup
label={"Terms of Service URL"}
name={"termsUrl"}
placeholder={"Enter URL"}
value={value.termsUrl}
error={error.termsUrl}
onChange={onChange}
helpButtonText={"Terms of service help"}
/>
</StyledInputBlock>
</StyledBlock>
);
};
export default SupportBlock;

View File

@ -0,0 +1,62 @@
import React from "react";
import Text from "@docspace/components/text";
//@ts-ignore
import Textarea from "@docspace/components/textarea";
import { StyledInputGroup } from "../ClientForm.styled";
interface TextAreaProps {
label: string;
name: string;
value: string;
placeholder: string;
error: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
const TextAreaGroup = ({
label,
name,
value,
placeholder,
error,
onChange,
}: TextAreaProps) => {
return (
<StyledInputGroup>
<div className="label">
<Text
fontSize={"13px"}
fontWeight={600}
lineHeight={"20px"}
title={""}
tag={""}
as={"p"}
color={""}
textAlign={""}
>
{label}
</Text>
</div>
<Textarea
name={name}
value={value}
placeholder={placeholder}
onChange={onChange}
scale
tabIndex={0}
heightTextArea={60}
maxLength={255}
/>
</StyledInputGroup>
);
};
export default TextAreaGroup;

View File

@ -1,30 +1,26 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { useNavigate } from "react-router-dom";
import { isMobileOnly } from "react-device-detect";
import { useTranslation } from "react-i18next";
import { ClientProps, Scope } from "@docspace/common/utils/oauth/interfaces";
import Button from "@docspace/components/button";
import BlockHeader from "./components/BlockHeader";
import Block from "./components/Block";
import InputHeader from "./components/InputHeader";
import Input from "./components/Input";
import {
IClientProps,
IClientReqDTO,
IScope,
} from "@docspace/common/utils/oauth/interfaces";
// @ts-ignore
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
import CheckboxComponent from "./components/Checkbox";
import BasicBlock from "./components/BasicBlock";
import ClientBlock from "./components/ClientBlock";
import SupportBlock from "./components/SupportBlock";
import { StyledContainer } from "./ClientForm.styled";
import {
Container,
FormContainer,
CheckboxGroup,
InputGroup,
} from "./ClientForm.styled";
import { ClientFormProps } from "./ClientForm.types";
import Preview from "./components/Preview";
import OAuthBlock from "./components/OAuthBlock";
import ScopesBlock from "./components/ScopesBlock";
const ClientForm = ({
id,
@ -33,9 +29,6 @@ const ClientForm = ({
scopeList,
tenant,
fetchTenant,
fetchClient,
fetchScopes,
@ -44,387 +37,282 @@ const ClientForm = ({
regenerateSecret,
}: ClientFormProps) => {
const { t } = useTranslation(["Common"]);
const navigate = useNavigate();
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [initClient, setInitClient] = React.useState<ClientProps | null>();
const [initClient, setInitClient] = React.useState<IClientProps | null>(null);
const [form, setForm] = React.useState<{ [key: string]: string }>({
appName: "",
appIcon: "",
const [form, setForm] = React.useState<IClientReqDTO>({
name: "",
logo: "",
website_url: "",
description: "",
redirectUrl: "",
termsURL: "",
privacyURL: "",
logoutRedirectUrl: "",
redirect_uris: [""],
allowed_origins: [""],
logout_redirect_uris: [""],
authenticationMethod: "",
terms_url: "",
policy_url: "",
authentication_method: "",
scopes: [""],
});
const [clientId, setClientId] = React.useState<string>("");
const [secret, setSecret] = React.useState<string>("");
const [clientSecret, setClientSecret] = React.useState<string>("");
const [scopes, setScopes] = React.useState<Scope[]>([]);
const [checkedScopes, setCheckedScopes] = React.useState<string[]>([]);
const isEdit = !!id || !!client;
const onInputChange = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
// const onInputChange = React.useCallback(
// (e: React.ChangeEvent<HTMLInputElement>) => {
// const { name, value } = e.target;
setForm((v) => {
v[name] = value;
// setForm((v) => {
// v[name] = value;
return { ...v };
});
},
[]
);
// return { ...v };
// });
// },
// []
// );
const onCheckboxChange = React.useCallback(
(name: string) => {
const idx = checkedScopes.findIndex((scope) => scope === name);
// const onCheckboxChange = React.useCallback(
// (name: string) => {
// const idx = checkedScopes.findIndex((scope) => scope === name);
if (idx === -1) {
setCheckedScopes((val) => [...val, name]);
} else {
setCheckedScopes((val) => val.filter((scope) => scope !== name));
}
},
[checkedScopes]
);
// if (idx === -1) {
// setCheckedScopes((val) => [...val, name]);
// } else {
// setCheckedScopes((val) => val.filter((scope) => scope !== name));
// }
// },
// [checkedScopes]
// );
const onSaveClick = async () => {
const newClient: ClientProps = client ? { ...client } : ({} as ClientProps);
// const onSaveClick = async () => {
// const newClient: ClientProps = client ? { ...client } : ({} as ClientProps);
newClient.name = form.appName;
newClient.logoUrl = form.appIcon;
newClient.description = form.description;
newClient.redirectUri = form.redirectUrl;
newClient.logoutRedirectUri = form.logoutRedirectUrl;
newClient.policyUrl = form.privacyURL;
newClient.clientId = clientId;
newClient.secret = secret;
newClient.scopes = [...checkedScopes];
// newClient.name = form.appName;
// newClient.logoUrl = form.appIcon;
// newClient.description = form.description;
// newClient.redirectUri = form.redirectUrl;
// newClient.logoutRedirectUri = form.logoutRedirectUrl;
// newClient.policyUrl = form.privacyURL;
// newClient.clientId = clientId;
// newClient.secret = secret;
// newClient.scopes = [...checkedScopes];
if (!id) {
if (!saveClient) return;
// if (!id) {
// if (!saveClient) return;
if (tenant === -1 && fetchTenant) {
const t = await fetchTenant();
// if (tenant === -1 && fetchTenant) {
// const t = await fetchTenant();
newClient.tenant = t;
// newClient.tenant = t;
// }
// await saveClient(newClient);
// } else {
// if (!updateClient) return;
// await updateClient(clientId, newClient);
// }
// onCancelClick();
// };
// const onCancelClick = () => {
// navigate("/portal-settings/developer-tools/oauth");
// };
// const onResetClick = React.useCallback(async () => {
// if (!regenerateSecret) return;
// const newSecret = await regenerateSecret(clientId);
// setSecret(newSecret);
// }, [clientId, regenerateSecret]);
// const getClient = React.useCallback(async () => {
// if (!fetchClient || !id) return;
// const client = await fetchClient(id);
// setClient(client);
// }, [id, fetchClient]);
// const setClient = React.useCallback(async (client: ClientProps) => {
// setForm({
// appName: client.name,
// appIcon: client.logoUrl || "",
// description: client.description,
// redirectUrl: client.redirectUri,
// privacyURL: client.policyUrl,
// termsUrl: client.termsUrl,
// logoutRedirectUrl: client.logoutRedirectUri,
// authenticationMethod: client.authenticationMethod,
// });
// setSecret(client.secret);
// setCheckedScopes([...client.scopes]);
// setInitClient({ ...client, scopes: [...client.scopes] });
// setIsLoading(false);
// }, []);
// React.useEffect(() => {
// setIsLoading(true);
// }, []);
const onChangeForm = (name: string, value: string) => {
setForm((val) => {
const newVal = { ...val };
if (typeof newVal[name as keyof IClientReqDTO]) {
typeof newVal[name as keyof IClientReqDTO] === value;
}
await saveClient(newClient);
} else {
if (!updateClient) return;
await updateClient(clientId, newClient);
}
onCancelClick();
return { ...newVal };
});
};
const onCancelClick = () => {
navigate("/portal-settings/developer-tools/oauth");
};
const onResetClick = React.useCallback(async () => {
if (!regenerateSecret) return;
const newSecret = await regenerateSecret(clientId);
setSecret(newSecret);
}, [clientId, regenerateSecret]);
const getScopeList = React.useCallback(async () => {
if (!fetchScopes) return;
await fetchScopes();
setIsLoading(false);
}, [fetchScopes]);
const getClient = React.useCallback(async () => {
if (!fetchClient || !id) return;
const client = await fetchClient(id);
setClient(client);
}, [id, fetchClient]);
const setClient = React.useCallback(async (client: ClientProps) => {
setForm({
appName: client.name,
appIcon: client.logoUrl || "",
description: client.description,
redirectUrl: client.redirectUri,
privacyURL: client.policyUrl,
termsUrl: client.termsUrl,
logoutRedirectUrl: client.logoutRedirectUri,
authenticationMethod: client.authenticationMethod,
});
setSecret(client.secret);
setCheckedScopes([...client.scopes]);
setInitClient({ ...client, scopes: [...client.scopes] });
setIsLoading(false);
}, []);
React.useEffect(() => {
if (scopeList && scopeList?.length !== 0) return;
setIsLoading(true);
}, []);
React.useEffect(() => {
if (scopeList && scopeList?.length !== 0) return setScopes([...scopeList]);
getScopeList();
}, [id, scopeList, fetchScopes, getScopeList]);
}, [id, scopeList, getScopeList, fetchScopes]);
React.useEffect(() => {
if (id) {
setClientId(id);
if (!client) {
getClient();
} else {
setClient(client);
}
}
}, [id, client, fetchClient, getClient, setClient]);
// React.useEffect(() => {
// if (id) {
// setClientId(id);
// if (!client) {
// getClient();
// } else {
// setClient(client);
// }
// }
// }, [id, client, fetchClient, getClient, setClient]);
const compareAndValidate = () => {
let isValid = false;
// const compareAndValidate = () => {
// let isValid = false;
for (let key in form) {
if (!!form[key] || key === "appIcon" || key === "authenticationMethod") {
if (initClient) {
switch (key) {
case "appName":
isValid = isValid || initClient.name !== form[key];
// for (let key in form) {
// if (!!form[key] || key === "appIcon" || key === "authenticationMethod") {
// if (initClient) {
// switch (key) {
// case "appName":
// isValid = isValid || initClient.name !== form[key];
break;
case "appIcon":
isValid = isValid || initClient.name !== form[key];
// break;
// case "appIcon":
// isValid = isValid || initClient.name !== form[key];
break;
case "description":
isValid = isValid || initClient.description !== form[key];
// break;
// case "description":
// isValid = isValid || initClient.description !== form[key];
break;
case "redirectUrl":
isValid = isValid || initClient.redirectUri !== form[key];
// break;
// case "redirectUrl":
// isValid = isValid || initClient.redirectUri !== form[key];
break;
case "logoutRedirectUrl":
isValid = isValid || initClient.logoutRedirectUri !== form[key];
// break;
// case "logoutRedirectUrl":
// isValid = isValid || initClient.logoutRedirectUri !== form[key];
break;
case "privacyUrl":
isValid = isValid || initClient.policyUrl !== form[key];
// break;
// case "privacyUrl":
// isValid = isValid || initClient.policyUrl !== form[key];
break;
// break;
case "termsUrl":
isValid = isValid || initClient.termsUrl !== form[key];
// case "termsUrl":
// isValid = isValid || initClient.termsUrl !== form[key];
break;
}
}
isValid = true;
} else {
isValid = false;
}
}
// break;
// }
// }
// isValid = true;
// } else {
// isValid = false;
// }
// }
if (checkedScopes.length > 0) {
if (initClient) {
let isSame = checkedScopes.length === initClient?.scopes.length;
if (isSame) {
checkedScopes.forEach((scope) => {
if (!initClient?.scopes.includes(scope)) isSame = false;
});
}
// if (checkedScopes.length > 0) {
// 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;
}
// isValid = isValid || !isSame;
// }
// } else {
// isValid = false;
// }
return isValid;
};
// return isValid;
// };
const isValid = compareAndValidate();
// const isValid = compareAndValidate();
return (
<Container>
<FormContainer>
<Block>
<BlockHeader header={"Basic info"} helpButtonText="" />
<InputGroup>
<InputHeader header={"App name"} />
<Input
value={form.appName}
name={"appName"}
placeholder={"Enter name"}
onChange={onInputChange}
/>
</InputGroup>
<InputGroup>
<InputHeader header={"App icon"} />
<Input
value={form.appIcon}
name={"appIcon"}
placeholder={"Add icon"}
onChange={onInputChange}
/>
</InputGroup>
<InputGroup>
<InputHeader header={"Description"} />
<Input
value={form.description}
name={"description"}
placeholder={"Enter description"}
onChange={onInputChange}
/>
</InputGroup>
</Block>
{id && (
<Block>
<BlockHeader header={"Client"} helpButtonText="" />
<InputGroup>
<InputHeader header={"ID"} />
<Input
value={clientId}
name={"ID"}
placeholder={"Enter id"}
onChange={onInputChange}
isReadOnly
withCopy
/>
</InputGroup>
<InputGroup>
<InputHeader header={"Secret"} />
<Input
value={secret}
name={"secret"}
placeholder={"Enter secret"}
onChange={onInputChange}
isReadOnly
isSecret
withCopy
withButton
buttonLabel="Reset"
onClickButton={onResetClick}
/>
</InputGroup>
<InputGroup>
<InputHeader header={"Authentication method "} />
<Input
value={form.authenticationMethod}
name={"authenticationMethod"}
placeholder={"Enter secret"}
onChange={onInputChange}
isReadOnly
withCopy
/>
</InputGroup>
</Block>
)}
<Block>
<BlockHeader header={"OAuth URLs"} helpButtonText="" />
<InputGroup>
<InputHeader header={"Redirect url"} />
<Input
value={form.redirectUrl}
name={"redirectUrl"}
placeholder={"Enter URL"}
onChange={onInputChange}
/>
</InputGroup>
<InputGroup>
<InputHeader header={"Logout redirect url"} />
<Input
value={form.logoutRedirectUrl}
name={"logoutRedirectUrl"}
placeholder={"Enter URL"}
onChange={onInputChange}
/>
</InputGroup>
</Block>
<Block>
<BlockHeader header={"Access scopes"} helpButtonText="" />
<CheckboxGroup>
{scopes.length > 0 &&
scopes.map((scope) => (
<CheckboxComponent
key={`${scope.name}`}
isChecked={checkedScopes.includes(scope.name)}
onChange={() => onCheckboxChange(scope.name)}
label={scope.name}
description={scope.description}
/>
))}
</CheckboxGroup>
</Block>
<Block>
<BlockHeader header={"Support & Legal info"} helpButtonText="" />
<InputGroup>
<InputHeader header={"Privacy policy URL"} />
<Input
value={form.privacyURL}
name={"privacyURL"}
placeholder={"Enter URL"}
onChange={onInputChange}
/>
</InputGroup>
<InputGroup>
<InputHeader header={"Terms of Service URL"} />
<Input
value={form.termsURL}
name={"termsURL"}
placeholder={"Enter URL"}
onChange={onInputChange}
/>
</InputGroup>
</Block>
<div className="button-container">
<Button
//@ts-ignore
label={"Save"}
isDisabled={!isValid}
size={"normal"}
primary
scale={isMobileOnly}
onClick={onSaveClick}
<StyledContainer>
{isLoading ? (
<div> Loading...</div>
) : (
<>
<BasicBlock
t={t}
nameValue={form.name}
websiteUrlValue={form.website_url}
descriptionValue={form.description}
logoValue={form.logo}
changeValue={onChangeForm}
/>
<Button
//@ts-ignore
label={"Cancel"}
size={"normal"}
scale={isMobileOnly}
onClick={onCancelClick}
{isEdit && (
<ClientBlock t={t} idValue={clientId} secretValue={clientSecret} />
)}
<OAuthBlock
t={t}
redirectUrisValue={form.redirect_uris}
allowedOriginsValue={form.allowed_origins}
changeValue={(name: string, value: string) => {
console.log(name, value);
}}
/>
</div>
</FormContainer>
{id && (
<Preview
clientId={clientId}
redirectURI={form.redirectUrl}
scopes={checkedScopes}
/>
<ScopesBlock
t={t}
scopes={scopeList || []}
selectedScopes={[]}
onAddScope={() => {}}
/>
<SupportBlock
t={t}
policyUrlValue={form.policy_url}
termsUrlValue={form.terms_url}
changeValue={(name: string, value: string) => {
console.log(name, value);
}}
/>
</>
)}
</Container>
</StyledContainer>
);
};
@ -440,9 +328,6 @@ export default inject(
fetchClient,
fetchScopes,
tenant,
fetchTenant,
saveClient,
updateClient,
@ -455,9 +340,6 @@ export default inject(
fetchClient,
fetchScopes,
tenant,
fetchTenant,
saveClient,
updateClient,
@ -466,7 +348,7 @@ export default inject(
if (id) {
const client = clientList.find(
(client: ClientProps) => client.clientId === id
(client: IClientProps) => client.clientId === id
);
props.client = client;

View File

@ -1,8 +1,5 @@
import { makeAutoObservable, runInAction } from "mobx";
//@ts-ignore
import { getPortal } from "@docspace/common/api/portal";
import {
addClient,
getClient,
@ -16,9 +13,10 @@ import {
} from "@docspace/common/api/oauth";
import {
ClientListProps,
ClientProps,
Scope,
IClientListProps,
IClientProps,
IClientReqDTO,
IScope,
} from "@docspace/common/utils/oauth/interfaces";
import SettingsIcon from "PUBLIC_DIR/images/catalog.settings.react.svg?url";
@ -26,13 +24,13 @@ import DeleteIcon from "PUBLIC_DIR/images/delete.react.svg?url";
import EnableReactSvgUrl from "PUBLIC_DIR/images/enable.react.svg?url";
import RemoveReactSvgUrl from "PUBLIC_DIR/images/remove.react.svg?url";
const PAGE_LIMIT = 20;
const PAGE_LIMIT = 100;
export type ViewAsType = "table" | "row";
export interface OAuthStoreProps {
viewAs: ViewAsType;
setViewAs: (value: "table" | "row") => void;
setViewAs: (value: ViewAsType) => void;
deleteDialogVisible: boolean;
setDeleteDialogVisible: (value: boolean) => void;
@ -42,14 +40,19 @@ export interface OAuthStoreProps {
editClient: (clientId: string) => void;
clients: ClientProps[];
fetchClient: (clientId: string) => Promise<ClientProps | undefined>;
clients: IClientProps[];
fetchClient: (clientId: string) => Promise<IClientProps | undefined>;
fetchClients: () => Promise<void>;
fetchNextClients: (startIndex: number) => Promise<void>;
saveClient: (client: ClientProps) => Promise<void>;
updateClient: (clientId: string, client: ClientProps) => Promise<void>;
saveClient: (client: IClientReqDTO) => Promise<void>;
updateClient: (clientId: string, client: IClientProps) => Promise<void>;
changeClientStatus: (clientId: string, status: boolean) => Promise<void>;
regenerateSecret: (clientId: string) => Promise<string | undefined>;
deleteClient: (clientId: string) => Promise<void>;
currentPage: number;
@ -59,34 +62,31 @@ export interface OAuthStoreProps {
selection: string[];
setSelection: (clientId: string) => void;
bufferSelection: ClientProps | null;
bufferSelection: IClientProps | null;
setBufferSelection: (clientId: string) => void;
tenant: number;
fetchTenant: () => Promise<number>;
activeClients: string[];
setActiveClient: (clientId: string) => void;
scopes: Scope[];
fetchScope: (name: string) => Promise<Scope | undefined>;
scopes: IScope[];
fetchScope: (name: string) => Promise<IScope>;
fetchScopes: () => Promise<void>;
getContextMenuItems: (
t: any,
item: ClientProps
item: IClientProps
) => {
[key: string]: any | string | boolean | ((clientId: string) => void);
}[];
clientList: ClientProps[];
clientList: IClientProps[];
isEmptyClientList: boolean;
hasNextPage: boolean;
scopeList: Scope[];
scopeList: IScope[];
}
class OAuthStore implements OAuthStoreProps {
viewAs: "table" | "row" = "table";
viewAs: ViewAsType = "table";
currentPage: number = -1;
totalPages: number = 0;
@ -96,15 +96,13 @@ class OAuthStore implements OAuthStoreProps {
selection: string[] = [];
bufferSelection: ClientProps | null = null;
bufferSelection: IClientProps | null = null;
tenant: number = -1;
clients: ClientProps[] = [];
clients: IClientProps[] = [];
activeClients: string[] = [];
scopes: Scope[] = [];
scopes: IScope[] = [];
clientsIsLoading: boolean = true;
@ -112,7 +110,7 @@ class OAuthStore implements OAuthStoreProps {
makeAutoObservable(this);
}
setViewAs = (value: "table" | "row") => {
setViewAs = (value: ViewAsType) => {
this.viewAs = value;
};
@ -163,15 +161,6 @@ class OAuthStore implements OAuthStoreProps {
);
};
fetchTenant = async () => {
if (this.tenant > -1) return this.tenant;
const { tenant } = await getPortal();
this.tenant = tenant;
return tenant;
};
fetchClient = async (clientId: string) => {
try {
const client = await getClient(clientId);
@ -185,7 +174,7 @@ class OAuthStore implements OAuthStoreProps {
fetchClients = async () => {
try {
this.setClientsIsLoading(true);
const clientList: ClientListProps = await getClientList(0, PAGE_LIMIT);
const clientList: IClientListProps = await getClientList(0, PAGE_LIMIT);
runInAction(() => {
this.totalPages = clientList.totalPages;
@ -291,6 +280,7 @@ class OAuthStore implements OAuthStoreProps {
}
};
// COMPLETE
fetchScope = async (name: string) => {
try {
const scope = await getScope(name);
@ -298,9 +288,12 @@ class OAuthStore implements OAuthStoreProps {
return scope;
} catch (e) {
console.log(e);
return {} as IScope;
}
};
// COMPLETE
fetchScopes = async () => {
try {
const scopes = await getScopeList();

View File

@ -12,6 +12,7 @@ import * as files from "./files";
import * as rooms from "./rooms";
import * as plugins from "./plugins";
import * as oforms from "./oforms";
import * as oauth from "./oauth";
export default {
Filter,
@ -28,4 +29,5 @@ export default {
rooms,
plugins,
oforms,
oauth,
};

View File

@ -1,4 +1,5 @@
import axios, { AxiosRequestConfig } from "axios";
//@ts-ignore
import { request } from "../client";
import {
transformToClientProps,
@ -6,87 +7,73 @@ import {
} from "./../../utils/oauth/index";
import {
ClientProps,
ClientResDTO,
ClientListProps,
ClientListDTO,
Scope,
IClientProps,
IClientResDTO,
IClientListProps,
IClientListDTO,
IScope,
INoAuthClientProps,
} from "../../utils/oauth/interfaces";
const axiosConfig: AxiosRequestConfig = {
baseURL: "/api/2.0",
responseType: "json",
timeout: 0,
withCredentials: true,
};
export const getClient = async (
clientId: string,
isAuth: boolean = true
): Promise<IClientProps | INoAuthClientProps> => {
if (!isAuth) {
const client: IClientResDTO = await request({
method: "get",
url: `/clients/${clientId}/info`,
});
const client = axios.create(axiosConfig);
return {
...client,
websiteUrl: client.website_url,
};
}
const request = (options: any): Promise<any> => {
const onSuccess = (response: any) => {
return response.data;
};
const onError = (error: any) => {
return error;
};
return client(options).then(onSuccess).catch(onError);
};
export const getClient = async (clientId: string): Promise<ClientProps> => {
const client: ClientResDTO = await request({
const client: IClientResDTO = await request({
method: "get",
url: `/clients/${clientId}`,
headers: {},
});
client.enabled = true;
return transformToClientProps(client);
};
export const getClientList = async (
page: number,
limit: number
): Promise<ClientListProps> => {
const { data }: { data: ClientListDTO } = await request({
): Promise<IClientListProps> => {
const data: IClientListDTO = await request({
method: "get",
url: `/clients?page=${page}&limit=${limit}`,
});
const clients = { ...data, content: [] as ClientProps[] };
const clients: IClientListProps = { ...data, content: [] as IClientProps[] };
data.content.forEach((item) => {
data.data.forEach((item) => {
const client = transformToClientProps(item);
// TODO: OAuth, get it from request
client.enabled = true;
clients.content.push({ ...client });
});
return clients;
};
export const addClient = async (data: ClientProps): Promise<ClientProps> => {
const client: ClientResDTO = await request({
export const addClient = async (data: IClientProps): Promise<IClientProps> => {
const client: IClientResDTO = await request({
method: "post",
url: `/clients`,
data: transformToClientReqDTO(data),
});
// TODO: OAuth, get it from request
client.enabled = true;
return transformToClientProps(client);
};
export const updateClient = async (
clientId: string,
data: ClientProps
): Promise<ClientProps> => {
const client: ClientResDTO = await request({
data: IClientProps
): Promise<IClientProps> => {
const client: IClientResDTO = await request({
method: "put",
url: `/clients/${clientId}`,
data: transformToClientReqDTO(data),
@ -125,8 +112,8 @@ export const deleteClient = async (clientId: string): Promise<void> => {
});
};
export const getScope = async (name: string): Promise<Scope> => {
const scope: Scope = await request({
export const getScope = async (name: string): Promise<IScope> => {
const scope: IScope = await request({
method: "get",
url: `/scopes/${name}`,
});
@ -134,11 +121,41 @@ export const getScope = async (name: string): Promise<Scope> => {
return scope;
};
export const getScopeList = async (): Promise<Scope[]> => {
const scopeList: Scope[] = await request({
export const getScopeList = async (): Promise<IScope[]> => {
const scopeList: IScope[] = await request({
method: "get",
url: `/scopes`,
});
return scopeList;
};
export const onOAuthLogin = () => {
const formData = new FormData();
return request({
method: "post",
url: `/oauth2/login`,
data: formData,
});
};
export const onOAuthSubmit = (
clientId: string,
clientState: string,
scope: string[]
) => {
const formData = new FormData();
// console.log(window.location.search);
formData.append("client_id", clientId);
formData.append("state", clientState);
formData.append("scope", scope.join(","));
return request({
method: "post",
url: `/oauth2/authorize`,
data: formData,
});
};

View File

@ -103,6 +103,12 @@ export const getScopeTKeyDescription = (group: ScopeGroup, type: ScopeType) => {
return tKey;
};
export const getScopeTKeyName = (group: ScopeGroup) => {
const tKey = `OAuth${group.replace(/^./, group[0].toUpperCase())}Name`;
return tKey;
};
export const filterScopeByGroup = (
checkedScopes: string[],
scopes: IScope[]