Web:Client:PortalSettings: add OAuth edit and create page
This commit is contained in:
parent
61c920555b
commit
310f2d5dc5
@ -10,6 +10,7 @@ import withLoading from "SRC_DIR/HOCs/withLoading";
|
|||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import HistoryHeader from "../categories/developer-tools/Webhooks/WebhookHistory/sub-components/HistoryHeader";
|
import HistoryHeader from "../categories/developer-tools/Webhooks/WebhookHistory/sub-components/HistoryHeader";
|
||||||
import DetailsNavigationHeader from "../categories/developer-tools/Webhooks/WebhookEventDetails/sub-components/DetailsNavigationHeader";
|
import DetailsNavigationHeader from "../categories/developer-tools/Webhooks/WebhookEventDetails/sub-components/DetailsNavigationHeader";
|
||||||
|
import OAuthSectionHeader from "../categories/developer-tools/OAuth/OAuthSectionHeader";
|
||||||
|
|
||||||
const ArticleSettings = React.memo(() => {
|
const ArticleSettings = React.memo(() => {
|
||||||
return (
|
return (
|
||||||
@ -41,6 +42,10 @@ const Layout = ({
|
|||||||
|
|
||||||
const webhookHistoryPath = `/portal-settings/developer-tools/webhooks/${id}`;
|
const webhookHistoryPath = `/portal-settings/developer-tools/webhooks/${id}`;
|
||||||
const webhookDetailsPath = `/portal-settings/developer-tools/webhooks/${id}/${eventId}`;
|
const webhookDetailsPath = `/portal-settings/developer-tools/webhooks/${id}/${eventId}`;
|
||||||
|
|
||||||
|
const oauthCreatePath = "/portal-settings/developer-tools/oauth/create";
|
||||||
|
const oauthEditPath = `/portal-settings/developer-tools/oauth/${id}`;
|
||||||
|
|
||||||
const currentPath = window.location.pathname;
|
const currentPath = window.location.pathname;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -49,7 +54,10 @@ const Layout = ({
|
|||||||
{!isGeneralPage && (
|
{!isGeneralPage && (
|
||||||
<Section withBodyScroll={true} settingsStudio={true}>
|
<Section withBodyScroll={true} settingsStudio={true}>
|
||||||
<Section.SectionHeader>
|
<Section.SectionHeader>
|
||||||
{currentPath === webhookHistoryPath ? (
|
{currentPath === oauthCreatePath ||
|
||||||
|
currentPath === oauthEditPath ? (
|
||||||
|
<OAuthSectionHeader isEdit={currentPath === oauthEditPath} />
|
||||||
|
) : currentPath === webhookHistoryPath ? (
|
||||||
<HistoryHeader />
|
<HistoryHeader />
|
||||||
) : currentPath === webhookDetailsPath ? (
|
) : currentPath === webhookDetailsPath ? (
|
||||||
<DetailsNavigationHeader />
|
<DetailsNavigationHeader />
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
import ClientForm from "../sub-components/ClientForm";
|
||||||
|
|
||||||
|
const OAuthCreatePage = () => {
|
||||||
|
return <ClientForm />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OAuthCreatePage;
|
@ -1,126 +0,0 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import { withTranslation } from "react-i18next";
|
|
||||||
import TextInput from "@docspace/components/text-input";
|
|
||||||
import Label from "@docspace/components/label";
|
|
||||||
import { inject, observer } from "mobx-react";
|
|
||||||
import StyledSettingsSeparator from "SRC_DIR/pages/PortalSettings/StyledSettingsSeparator";
|
|
||||||
import Category from "../sub-components/Category";
|
|
||||||
import { Container, Property } from "../StyledOAuth";
|
|
||||||
|
|
||||||
const OAuthDetails = (props) => {
|
|
||||||
const { t, setDocumentTitle, currentClient, theme } = props;
|
|
||||||
|
|
||||||
setDocumentTitle("OAuth");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
<Category
|
|
||||||
t={t}
|
|
||||||
title={"Basic info"}
|
|
||||||
tooltipTitle={"Test"}
|
|
||||||
tooltipUrl={""}
|
|
||||||
currentColorScheme={theme}
|
|
||||||
/>
|
|
||||||
<Property>
|
|
||||||
<Label htmlFor="name" text="App name:" title="Fill app name" />
|
|
||||||
<TextInput id="name" value={currentClient.name} />
|
|
||||||
</Property>
|
|
||||||
<Property>
|
|
||||||
<Label htmlFor="icon" text="App icon:" title="Fill app icon" />
|
|
||||||
<TextInput id="icon" value={currentClient.logo_uri} />
|
|
||||||
</Property>
|
|
||||||
<Property>
|
|
||||||
<Label
|
|
||||||
htmlFor="description"
|
|
||||||
text="Description:"
|
|
||||||
title="Fill description"
|
|
||||||
/>
|
|
||||||
<TextInput id="description" value={currentClient.description} />
|
|
||||||
</Property>
|
|
||||||
<StyledSettingsSeparator />
|
|
||||||
<Category
|
|
||||||
t={t}
|
|
||||||
title={"Client ID"}
|
|
||||||
tooltipTitle={"Test"}
|
|
||||||
tooltipUrl={""}
|
|
||||||
currentColorScheme={theme}
|
|
||||||
/>
|
|
||||||
<Property>
|
|
||||||
<Label htmlFor="id" text="Client ID:" title="Client ID" />
|
|
||||||
<TextInput id="id" value={currentClient.client_id} isDisabled />
|
|
||||||
</Property>
|
|
||||||
<Property>
|
|
||||||
<Label htmlFor="secret" text="Secret:" title="Client secret" />
|
|
||||||
<TextInput id="secret" value={currentClient.client_secret} isDisabled />
|
|
||||||
</Property>
|
|
||||||
<StyledSettingsSeparator />
|
|
||||||
<Category
|
|
||||||
t={t}
|
|
||||||
title={"OAuth urls"}
|
|
||||||
tooltipTitle={"Test"}
|
|
||||||
tooltipUrl={""}
|
|
||||||
currentColorScheme={theme}
|
|
||||||
/>
|
|
||||||
<Property>
|
|
||||||
<Label
|
|
||||||
htmlFor="redirectUris"
|
|
||||||
text="Redirect uris:"
|
|
||||||
title="Redirect uris"
|
|
||||||
/>
|
|
||||||
<TextInput id="redirectUris" value={currentClient.redirect_uris} />
|
|
||||||
</Property>
|
|
||||||
<Property>
|
|
||||||
<Label
|
|
||||||
htmlFor="allowedOrigins"
|
|
||||||
text="Allowed origins:"
|
|
||||||
title="Allowed origins"
|
|
||||||
/>
|
|
||||||
<TextInput id="allowedOrigins" value={currentClient.allowed_origins} />
|
|
||||||
</Property>
|
|
||||||
<StyledSettingsSeparator />
|
|
||||||
<Category
|
|
||||||
t={t}
|
|
||||||
title={"Access scopes"}
|
|
||||||
tooltipTitle={"Test"}
|
|
||||||
tooltipUrl={""}
|
|
||||||
currentColorScheme={theme}
|
|
||||||
/>
|
|
||||||
<Property>
|
|
||||||
<Label htmlFor="scopes" text="Scopes:" title="Scopes" />
|
|
||||||
<TextInput id="scopes" value={currentClient.scopes} isReadOnly />
|
|
||||||
</Property>
|
|
||||||
<StyledSettingsSeparator />
|
|
||||||
<Category
|
|
||||||
t={t}
|
|
||||||
title={"Support and legal info"}
|
|
||||||
tooltipTitle={"Test"}
|
|
||||||
tooltipUrl={""}
|
|
||||||
currentColorScheme={theme}
|
|
||||||
/>
|
|
||||||
<Property>
|
|
||||||
<Label htmlFor="rootUrl" text="Website URL:" title="Root url" />
|
|
||||||
<TextInput id="rootUrl" value={currentClient.root_url} />
|
|
||||||
</Property>
|
|
||||||
<Property>
|
|
||||||
<Label htmlFor="policy" text="Privacy policy URL:" title="Policy" />
|
|
||||||
<TextInput id="policy" value={currentClient.policy_uri} />
|
|
||||||
</Property>
|
|
||||||
<Property>
|
|
||||||
<Label htmlFor="terms" text="Terms of Service URL:" title="Terms" />
|
|
||||||
<TextInput id="terms" value={currentClient.terms_uri} />
|
|
||||||
</Property>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default inject(({ setup, auth, oauthStore }) => {
|
|
||||||
const { settingsStore, setDocumentTitle } = auth;
|
|
||||||
const { theme } = settingsStore;
|
|
||||||
const { currentClient } = oauthStore;
|
|
||||||
|
|
||||||
return {
|
|
||||||
theme,
|
|
||||||
setDocumentTitle,
|
|
||||||
currentClient,
|
|
||||||
};
|
|
||||||
})(withTranslation(["Common"])(observer(OAuthDetails)));
|
|
@ -0,0 +1,11 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
import ClientForm from "../sub-components/ClientForm";
|
||||||
|
|
||||||
|
const OAuthEditPage = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
return <ClientForm id={id} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OAuthEditPage;
|
@ -0,0 +1,63 @@
|
|||||||
|
import styled, { css } from "styled-components";
|
||||||
|
import { isMobile, isMobileOnly } from "react-device-detect";
|
||||||
|
|
||||||
|
import { Base } from "@docspace/components/themes";
|
||||||
|
import { tablet } from "@docspace/components/utils/device";
|
||||||
|
|
||||||
|
const HeaderContainer = styled.div`
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: ${(props) => props.theme.backgroundColor};
|
||||||
|
z-index: 201;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 70px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
${() =>
|
||||||
|
isMobile &&
|
||||||
|
css`
|
||||||
|
margin-bottom: 11px;
|
||||||
|
`}
|
||||||
|
|
||||||
|
${() =>
|
||||||
|
isMobileOnly &&
|
||||||
|
css`
|
||||||
|
margin-top: 7px;
|
||||||
|
margin-left: -14px;
|
||||||
|
padding-left: 14px;
|
||||||
|
margin-right: -14px;
|
||||||
|
padding-right: 14px;
|
||||||
|
`}
|
||||||
|
|
||||||
|
.arrow-button {
|
||||||
|
margin-inline-end: 18.5px;
|
||||||
|
|
||||||
|
@media ${tablet} {
|
||||||
|
padding-block: 8px;
|
||||||
|
padding-inline: 8px 0;
|
||||||
|
margin-inline-start: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
${() =>
|
||||||
|
isMobileOnly &&
|
||||||
|
css`
|
||||||
|
margin-inline-end: 13px;
|
||||||
|
`}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
${({ theme }) =>
|
||||||
|
theme.interfaceDirection === "rtl" && "transform: scaleX(-1);"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-inline-end: 16px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
HeaderContainer.defaultProps = { theme: Base };
|
||||||
|
|
||||||
|
export { HeaderContainer };
|
@ -0,0 +1,3 @@
|
|||||||
|
export interface OAuthSectionHeaderProps {
|
||||||
|
isEdit: boolean;
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { inject, observer } from "mobx-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import Headline from "@docspace/common/components/Headline";
|
||||||
|
// @ts-ignore
|
||||||
|
import IconButton from "@docspace/components/icon-button";
|
||||||
|
|
||||||
|
import ArrowPathReactSvgUrl from "PUBLIC_DIR/images/arrow.path.react.svg?url";
|
||||||
|
|
||||||
|
import { HeaderContainer } from "./SectionHeader.styled";
|
||||||
|
import { OAuthSectionHeaderProps } from "./SectionHeader.types";
|
||||||
|
|
||||||
|
const OAuthSectionHeader = ({ isEdit }: OAuthSectionHeaderProps) => {
|
||||||
|
const { t } = useTranslation(["OAuth"]);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const onBack = () => {
|
||||||
|
navigate("/portal-settings/developer-tools/oauth");
|
||||||
|
};
|
||||||
|
|
||||||
|
const NavigationHeader = () => (
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
iconName={ArrowPathReactSvgUrl}
|
||||||
|
size="17"
|
||||||
|
isFill={true}
|
||||||
|
onClick={onBack}
|
||||||
|
className="arrow-button"
|
||||||
|
/>
|
||||||
|
<Headline type="content" truncate={true} className="headline">
|
||||||
|
{isEdit ? t("EditApp") : t("NewApp")}
|
||||||
|
</Headline>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HeaderContainer>
|
||||||
|
<NavigationHeader />
|
||||||
|
</HeaderContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default inject(({}) => {
|
||||||
|
return {};
|
||||||
|
})(observer(OAuthSectionHeader));
|
@ -1,6 +1,6 @@
|
|||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
export const Container = styled.div`
|
export const OAuthContainer = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
`;
|
`;
|
@ -1,8 +1,19 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { inject, observer } from "mobx-react";
|
import { inject, observer } from "mobx-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import OAuthEmptyScreen from "./sub-components/EmptyScreen";
|
||||||
|
|
||||||
|
import { OAuthContainer } from "./StyledOAuth";
|
||||||
|
|
||||||
const OAuth = ({}) => {
|
const OAuth = ({}) => {
|
||||||
return <div></div>;
|
const { t } = useTranslation(["OAuth"]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OAuthContainer>
|
||||||
|
<OAuthEmptyScreen t={t} />
|
||||||
|
</OAuthContainer>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default inject(({}) => {
|
export default inject(({}) => {
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
max-width: 350px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
width: 100;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
flex-direction: raw;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BlockContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const HeaderRaw = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: raw;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
div {
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InputGroup = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InputRaw = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: raw;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Container,
|
||||||
|
BlockContainer,
|
||||||
|
HeaderRaw,
|
||||||
|
InputGroup,
|
||||||
|
InputRaw,
|
||||||
|
CheckboxGroup,
|
||||||
|
CheckboxRaw,
|
||||||
|
};
|
@ -0,0 +1,50 @@
|
|||||||
|
import { ClientProps, ScopeDTO } from "@docspace/common/utils/oauth/dto";
|
||||||
|
|
||||||
|
export interface InputProps {
|
||||||
|
value: string;
|
||||||
|
name: string;
|
||||||
|
placeholder: string;
|
||||||
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
|
||||||
|
isReadOnly?: boolean;
|
||||||
|
isSecret?: boolean;
|
||||||
|
withCopy?: boolean;
|
||||||
|
|
||||||
|
withButton?: boolean;
|
||||||
|
buttonLabel?: string;
|
||||||
|
onClickButton?: () => void;
|
||||||
|
|
||||||
|
multiplyInput?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CheckboxProps {
|
||||||
|
isChecked: boolean;
|
||||||
|
onChange: () => void;
|
||||||
|
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlockHeaderProps {
|
||||||
|
header: string;
|
||||||
|
helpButtonText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlockProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientFormProps {
|
||||||
|
id?: string;
|
||||||
|
client?: ClientProps;
|
||||||
|
|
||||||
|
scopeList?: ScopeDTO[];
|
||||||
|
|
||||||
|
fetchClient?: (clientId: string) => Promise<ClientProps>;
|
||||||
|
fetchScopes?: () => Promise<void>;
|
||||||
|
|
||||||
|
saveClient: (client: ClientProps) => Promise<ClientProps>;
|
||||||
|
updateClient: (clientId: string, client: ClientProps) => Promise<ClientProps>;
|
||||||
|
|
||||||
|
regenerateSecret?: (clientId: string) => Promise<string>;
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { BlockContainer } from "../ClientForm.styled";
|
||||||
|
import { BlockProps } from "../ClientForm.types";
|
||||||
|
|
||||||
|
const Block = ({ children }: BlockProps) => {
|
||||||
|
return <BlockContainer>{children}</BlockContainer>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Block;
|
@ -0,0 +1,30 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
const BlockHeader = ({ header, helpButtonText }: BlockHeaderProps) => {
|
||||||
|
return (
|
||||||
|
<HeaderRaw>
|
||||||
|
<Text
|
||||||
|
fontSize={"16px"}
|
||||||
|
fontWeight={700}
|
||||||
|
lineHeight={"22px"}
|
||||||
|
title={header}
|
||||||
|
tag={""}
|
||||||
|
as={"p"}
|
||||||
|
color={""}
|
||||||
|
textAlign={""}
|
||||||
|
>
|
||||||
|
{header}
|
||||||
|
</Text>
|
||||||
|
<HelpButton tooltipContent={helpButtonText} />
|
||||||
|
</HeaderRaw>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BlockHeader;
|
@ -0,0 +1,46 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
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,66 @@
|
|||||||
|
import React from "react";
|
||||||
|
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 : ""}
|
||||||
|
onIconClick={onCopy}
|
||||||
|
scale={true}
|
||||||
|
type={isSecret ? "password" : "text"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{withButton && (
|
||||||
|
<Button
|
||||||
|
//@ts-ignore
|
||||||
|
label={buttonLabel}
|
||||||
|
size={"small"}
|
||||||
|
onClick={onClickButton}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InputRaw>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Input;
|
@ -0,0 +1,22 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
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,436 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { inject, observer } from "mobx-react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { isMobileOnly } from "react-device-detect";
|
||||||
|
|
||||||
|
import { ClientProps, ScopeDTO } from "@docspace/common/utils/oauth/dto";
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
|
||||||
|
|
||||||
|
import CheckboxComponent from "./components/Checkbox";
|
||||||
|
|
||||||
|
import { CheckboxGroup, Container, InputGroup } from "./ClientForm.styled";
|
||||||
|
import { ClientFormProps } from "./ClientForm.types";
|
||||||
|
|
||||||
|
const ClientForm = ({
|
||||||
|
id,
|
||||||
|
|
||||||
|
client,
|
||||||
|
|
||||||
|
scopeList,
|
||||||
|
|
||||||
|
fetchClient,
|
||||||
|
fetchScopes,
|
||||||
|
|
||||||
|
saveClient,
|
||||||
|
updateClient,
|
||||||
|
|
||||||
|
regenerateSecret,
|
||||||
|
}: ClientFormProps) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
||||||
|
|
||||||
|
const [initClient, setInitClient] = React.useState<ClientProps | null>();
|
||||||
|
|
||||||
|
const [form, setForm] = React.useState<{ [key: string]: string }>({
|
||||||
|
appName: "",
|
||||||
|
appIcon: "",
|
||||||
|
description: "",
|
||||||
|
redirectUrl: "",
|
||||||
|
logoutRedirectUrl: "",
|
||||||
|
// allowedOrigins: "",
|
||||||
|
// websiteUrl: "",
|
||||||
|
privacyURL: "",
|
||||||
|
// serviceUrl: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const [clientId, setClientId] = React.useState<string>("");
|
||||||
|
const [secret, setSecret] = React.useState<string>("");
|
||||||
|
|
||||||
|
const [scopes, setScopes] = React.useState<ScopeDTO[]>([]);
|
||||||
|
const [checkedScopes, setCheckedScopes] = React.useState<string[]>([]);
|
||||||
|
|
||||||
|
const onInputChange = React.useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
|
||||||
|
setForm((v) => {
|
||||||
|
v[name] = value;
|
||||||
|
|
||||||
|
return { ...v };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
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]
|
||||||
|
);
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
await saveClient(newClient);
|
||||||
|
} else {
|
||||||
|
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 getScopeList = React.useCallback(async () => {
|
||||||
|
if (!fetchScopes) return;
|
||||||
|
|
||||||
|
await fetchScopes();
|
||||||
|
}, [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,
|
||||||
|
logoutRedirectUrl: client.logoutRedirectUri,
|
||||||
|
privacyURL: client.policyUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
setSecret(client.secret);
|
||||||
|
|
||||||
|
setCheckedScopes([...client.scopes]);
|
||||||
|
|
||||||
|
setInitClient({ ...client, scopes: [...client.scopes] });
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsLoading(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (scopeList && scopeList?.length !== 0) return setScopes([...scopeList]);
|
||||||
|
|
||||||
|
getScopeList();
|
||||||
|
}, [id, scopeList, fetchScopes, getScopeList]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (id) {
|
||||||
|
setClientId(id);
|
||||||
|
if (!client) {
|
||||||
|
getClient();
|
||||||
|
} else {
|
||||||
|
setClient(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [id, client, fetchClient, getClient, setClient]);
|
||||||
|
|
||||||
|
const compareAndValidate = () => {
|
||||||
|
let isValid = false;
|
||||||
|
|
||||||
|
for (let key in form) {
|
||||||
|
if (!!form[key]) {
|
||||||
|
if (initClient) {
|
||||||
|
switch (key) {
|
||||||
|
case "appName":
|
||||||
|
isValid = isValid || initClient.name !== form[key];
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "appIcon":
|
||||||
|
isValid = isValid || initClient.logoUrl !== form[key];
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "description":
|
||||||
|
isValid = isValid || initClient.description !== form[key];
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "redirectUrl":
|
||||||
|
isValid = isValid || initClient.redirectUri !== form[key];
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "logoutRedirectUrl":
|
||||||
|
isValid = isValid || initClient.logoutRedirectUri !== form[key];
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "privacyUrl":
|
||||||
|
isValid = isValid || initClient.policyUrl !== form[key];
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid = isValid || !isSame;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isValid = compareAndValidate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
{/* <InputGroup>
|
||||||
|
<InputHeader header={"Allowed origins"} />
|
||||||
|
<Input
|
||||||
|
value={form.allowedOrigins}
|
||||||
|
name={"allowedOrigins"}
|
||||||
|
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={"Website URL"} />
|
||||||
|
<Input
|
||||||
|
value={form.websiteUrl}
|
||||||
|
name={"websiteUrl"}
|
||||||
|
placeholder={"Enter URL"}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</InputGroup> */}
|
||||||
|
<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.serviceUrl}
|
||||||
|
name={"serviceUrl"}
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
//@ts-ignore
|
||||||
|
label={"Cancel"}
|
||||||
|
size={"normal"}
|
||||||
|
scale={isMobileOnly}
|
||||||
|
onClick={onCancelClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default inject(
|
||||||
|
(
|
||||||
|
{ oauthStore }: { oauthStore: OAuthStoreProps },
|
||||||
|
{ id }: ClientFormProps
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
clientList,
|
||||||
|
scopeList,
|
||||||
|
|
||||||
|
fetchClient,
|
||||||
|
fetchScopes,
|
||||||
|
|
||||||
|
saveClient,
|
||||||
|
updateClient,
|
||||||
|
|
||||||
|
regenerateSecret,
|
||||||
|
} = oauthStore;
|
||||||
|
|
||||||
|
const props: ClientFormProps = {
|
||||||
|
scopeList,
|
||||||
|
|
||||||
|
fetchClient,
|
||||||
|
fetchScopes,
|
||||||
|
|
||||||
|
saveClient,
|
||||||
|
updateClient,
|
||||||
|
|
||||||
|
regenerateSecret,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
const client = clientList.find(
|
||||||
|
(client: ClientProps) => client.clientId === id
|
||||||
|
);
|
||||||
|
|
||||||
|
props.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...props };
|
||||||
|
}
|
||||||
|
)(observer(ClientForm));
|
@ -41,6 +41,7 @@ const DeveloperToolsWrapper = (props) => {
|
|||||||
"JavascriptSdk",
|
"JavascriptSdk",
|
||||||
"Webhooks",
|
"Webhooks",
|
||||||
"Settings",
|
"Settings",
|
||||||
|
"OAuth",
|
||||||
]);
|
]);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ const DeveloperToolsWrapper = (props) => {
|
|||||||
name: t("Webhooks:Webhooks"),
|
name: t("Webhooks:Webhooks"),
|
||||||
content: <Webhooks />,
|
content: <Webhooks />,
|
||||||
},
|
},
|
||||||
{ id: "oauth", name: "OAuth", content: <OAuth /> },
|
{ id: "oauth", name: t("OAuth:OAuth"), content: <OAuth /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
const [currentTab, setCurrentTab] = useState(
|
const [currentTab, setCurrentTab] = useState(
|
||||||
|
@ -353,7 +353,7 @@ export const settingsTree = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "portal-settings_catalog-oauth",
|
id: "portal-settings_catalog-oauth",
|
||||||
key: "5-2",
|
key: "5-3",
|
||||||
icon: "",
|
icon: "",
|
||||||
link: "oauth",
|
link: "oauth",
|
||||||
tKey: "OAuth",
|
tKey: "OAuth",
|
||||||
|
@ -127,6 +127,11 @@ const OAuthCreatePage = loadable(() =>
|
|||||||
"../pages/PortalSettings/categories/developer-tools/OAuth/OAuthCreatePage"
|
"../pages/PortalSettings/categories/developer-tools/OAuth/OAuthCreatePage"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const OAuthEditPage = loadable(() =>
|
||||||
|
import(
|
||||||
|
"../pages/PortalSettings/categories/developer-tools/OAuth/OAuthEditPage"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const Backup = loadable(() =>
|
const Backup = loadable(() =>
|
||||||
import("../pages/PortalSettings/categories/data-management/index")
|
import("../pages/PortalSettings/categories/data-management/index")
|
||||||
@ -314,7 +319,7 @@ const PortalSettingsRoutes = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "developer-tools/oauth/:id",
|
path: "developer-tools/oauth/:id",
|
||||||
element: <DeveloperTools />,
|
element: <OAuthEditPage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "backup",
|
path: "backup",
|
||||||
|
@ -61,7 +61,11 @@ class OAuthStore implements OAuthStoreProps {
|
|||||||
this.clients = clientList.content;
|
this.clients = clientList.content;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//TODO: add tenant and other params
|
||||||
saveClient = async (client: ClientProps) => {
|
saveClient = async (client: ClientProps) => {
|
||||||
|
client.tenant = 1;
|
||||||
|
client.authenticationMethod = "zxc";
|
||||||
|
client.termsUrl = "zxc";
|
||||||
const newClient = await addClient(client);
|
const newClient = await addClient(client);
|
||||||
|
|
||||||
return newClient;
|
return newClient;
|
||||||
|
@ -7,14 +7,14 @@ export interface ClientProps {
|
|||||||
clientId: string;
|
clientId: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
description: string;
|
description: string;
|
||||||
termsUrl: string;
|
termsUrl?: string;
|
||||||
policyUrl: string;
|
policyUrl: string;
|
||||||
logoUrl: string;
|
logoUrl: string;
|
||||||
authenticationMethod: string;
|
authenticationMethod?: string;
|
||||||
redirectUri: string;
|
redirectUri: string;
|
||||||
logoutRedirectUri: string;
|
logoutRedirectUri: string;
|
||||||
scopes: string[];
|
scopes: string[];
|
||||||
tenant: number;
|
tenant?: number;
|
||||||
invalidated?: boolean;
|
invalidated?: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user