PortalSettings:OAuth: add create client form
This commit is contained in:
parent
348383e2fb
commit
86f17497a8
@ -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 />;
|
||||
};
|
||||
|
||||
|
@ -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} />;
|
||||
};
|
||||
|
||||
|
@ -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 ? (
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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;
|
@ -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;
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
@ -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[]
|
||||
|
Loading…
Reference in New Issue
Block a user