Web:Client:PortalSettings: add OAuth edit and create page

This commit is contained in:
Timofey Boyko 2023-09-26 10:31:22 +03:00
parent 61c920555b
commit 310f2d5dc5
22 changed files with 927 additions and 135 deletions

View File

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

View File

@ -0,0 +1,7 @@
import ClientForm from "../sub-components/ClientForm";
const OAuthCreatePage = () => {
return <ClientForm />;
};
export default OAuthCreatePage;

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
export interface OAuthSectionHeaderProps {
isEdit: boolean;
}

View File

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

View File

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

View File

@ -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(({}) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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