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 HistoryHeader from "../categories/developer-tools/Webhooks/WebhookHistory/sub-components/HistoryHeader";
|
||||
import DetailsNavigationHeader from "../categories/developer-tools/Webhooks/WebhookEventDetails/sub-components/DetailsNavigationHeader";
|
||||
import OAuthSectionHeader from "../categories/developer-tools/OAuth/OAuthSectionHeader";
|
||||
|
||||
const ArticleSettings = React.memo(() => {
|
||||
return (
|
||||
@ -41,6 +42,10 @@ const Layout = ({
|
||||
|
||||
const webhookHistoryPath = `/portal-settings/developer-tools/webhooks/${id}`;
|
||||
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;
|
||||
|
||||
return (
|
||||
@ -49,7 +54,10 @@ const Layout = ({
|
||||
{!isGeneralPage && (
|
||||
<Section withBodyScroll={true} settingsStudio={true}>
|
||||
<Section.SectionHeader>
|
||||
{currentPath === webhookHistoryPath ? (
|
||||
{currentPath === oauthCreatePath ||
|
||||
currentPath === oauthEditPath ? (
|
||||
<OAuthSectionHeader isEdit={currentPath === oauthEditPath} />
|
||||
) : currentPath === webhookHistoryPath ? (
|
||||
<HistoryHeader />
|
||||
) : currentPath === webhookDetailsPath ? (
|
||||
<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";
|
||||
|
||||
export const Container = styled.div`
|
||||
export const OAuthContainer = styled.div`
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
`;
|
@ -1,8 +1,19 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import OAuthEmptyScreen from "./sub-components/EmptyScreen";
|
||||
|
||||
import { OAuthContainer } from "./StyledOAuth";
|
||||
|
||||
const OAuth = ({}) => {
|
||||
return <div></div>;
|
||||
const { t } = useTranslation(["OAuth"]);
|
||||
|
||||
return (
|
||||
<OAuthContainer>
|
||||
<OAuthEmptyScreen t={t} />
|
||||
</OAuthContainer>
|
||||
);
|
||||
};
|
||||
|
||||
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",
|
||||
"Webhooks",
|
||||
"Settings",
|
||||
"OAuth",
|
||||
]);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
@ -76,7 +77,7 @@ const DeveloperToolsWrapper = (props) => {
|
||||
name: t("Webhooks:Webhooks"),
|
||||
content: <Webhooks />,
|
||||
},
|
||||
{ id: "oauth", name: "OAuth", content: <OAuth /> },
|
||||
{ id: "oauth", name: t("OAuth:OAuth"), content: <OAuth /> },
|
||||
];
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(
|
||||
|
@ -353,7 +353,7 @@ export const settingsTree = [
|
||||
},
|
||||
{
|
||||
id: "portal-settings_catalog-oauth",
|
||||
key: "5-2",
|
||||
key: "5-3",
|
||||
icon: "",
|
||||
link: "oauth",
|
||||
tKey: "OAuth",
|
||||
|
@ -127,6 +127,11 @@ const OAuthCreatePage = loadable(() =>
|
||||
"../pages/PortalSettings/categories/developer-tools/OAuth/OAuthCreatePage"
|
||||
)
|
||||
);
|
||||
const OAuthEditPage = loadable(() =>
|
||||
import(
|
||||
"../pages/PortalSettings/categories/developer-tools/OAuth/OAuthEditPage"
|
||||
)
|
||||
);
|
||||
|
||||
const Backup = loadable(() =>
|
||||
import("../pages/PortalSettings/categories/data-management/index")
|
||||
@ -314,7 +319,7 @@ const PortalSettingsRoutes = {
|
||||
},
|
||||
{
|
||||
path: "developer-tools/oauth/:id",
|
||||
element: <DeveloperTools />,
|
||||
element: <OAuthEditPage />,
|
||||
},
|
||||
{
|
||||
path: "backup",
|
||||
|
@ -61,7 +61,11 @@ class OAuthStore implements OAuthStoreProps {
|
||||
this.clients = clientList.content;
|
||||
};
|
||||
|
||||
//TODO: add tenant and other params
|
||||
saveClient = async (client: ClientProps) => {
|
||||
client.tenant = 1;
|
||||
client.authenticationMethod = "zxc";
|
||||
client.termsUrl = "zxc";
|
||||
const newClient = await addClient(client);
|
||||
|
||||
return newClient;
|
||||
|
@ -7,14 +7,14 @@ export interface ClientProps {
|
||||
clientId: string;
|
||||
secret: string;
|
||||
description: string;
|
||||
termsUrl: string;
|
||||
termsUrl?: string;
|
||||
policyUrl: string;
|
||||
logoUrl: string;
|
||||
authenticationMethod: string;
|
||||
authenticationMethod?: string;
|
||||
redirectUri: string;
|
||||
logoutRedirectUri: string;
|
||||
scopes: string[];
|
||||
tenant: number;
|
||||
tenant?: number;
|
||||
invalidated?: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user