Client:OAuth2: add info dialog

This commit is contained in:
Timofey Boyko 2023-11-03 17:51:40 +03:00
parent 7902e9824d
commit 76fdf6e9b6
9 changed files with 449 additions and 21 deletions

View File

@ -12,6 +12,9 @@ export interface OAuthProps {
clientList: IClientProps[];
isEmptyClientList: boolean;
fetchClients: () => Promise<void>;
fetchScopes: () => Promise<void>;
currentDeviceType: DeviceUnionType;
infoDialogVisible?: boolean;
}

View File

@ -14,6 +14,7 @@ import List from "./sub-components/List";
import { OAuthContainer } from "./StyledOAuth";
import { OAuthProps } from "./OAuth.types";
import InfoDialog from "./sub-components/InfoDialog";
const MIN_LOADER_TIME = 500;
@ -23,15 +24,21 @@ const OAuth = ({
isEmptyClientList,
setViewAs,
fetchClients,
fetchScopes,
currentDeviceType,
infoDialogVisible,
}: OAuthProps) => {
const { t } = useTranslation(["OAuth"]);
const [isLoading, setIsLoading] = React.useState<boolean>(true);
const startLoadingRef = React.useRef<null | Date>(null);
const getClientList = React.useCallback(async () => {
await fetchClients();
const getData = React.useCallback(async () => {
const actions = [];
actions.push(fetchScopes(), fetchClients());
await Promise.all(actions);
if (startLoadingRef.current) {
const currentDate = new Date();
@ -57,8 +64,8 @@ const OAuth = ({
React.useEffect(() => {
startLoadingRef.current = new Date();
getClientList();
}, [getClientList]);
getData();
}, [getData]);
React.useEffect(() => {
setDocumentTitle(t("OAuth"));
@ -66,13 +73,16 @@ const OAuth = ({
return (
<OAuthContainer>
{isLoading ? (
<div>Loading...</div>
) : isEmptyClientList ? (
<OAuthEmptyScreen t={t} />
) : (
<List t={t} clients={clientList} viewAs={viewAs} />
)}
<>
{isLoading ? (
<div>Loading...</div>
) : isEmptyClientList ? (
<OAuthEmptyScreen t={t} />
) : (
<List t={t} clients={clientList} viewAs={viewAs} />
)}
</>
{infoDialogVisible && <InfoDialog visible={infoDialogVisible} />}
</OAuthContainer>
);
};
@ -80,8 +90,15 @@ const OAuth = ({
export default inject(
({ oauthStore, auth }: { oauthStore: OAuthStoreProps; auth: any }) => {
const { currentDeviceType } = auth.settingsStore;
const { viewAs, setViewAs, clientList, isEmptyClientList, fetchClients } =
oauthStore;
const {
viewAs,
setViewAs,
clientList,
isEmptyClientList,
fetchClients,
fetchScopes,
infoDialogVisible,
} = oauthStore;
return {
viewAs,
setViewAs,
@ -89,6 +106,8 @@ export default inject(
isEmptyClientList,
fetchClients,
currentDeviceType,
infoDialogVisible,
fetchScopes,
};
}
)(observer(OAuth));

View File

@ -0,0 +1,386 @@
import React from "react";
import { inject, observer } from "mobx-react";
import styled from "styled-components";
import { useTranslation } from "react-i18next";
import { IClientProps, IScope } from "@docspace/common/utils/oauth/interfaces";
import ScopeList from "@docspace/common/utils/oauth/ScopeList";
//@ts-ignore
import getCorrectDate from "@docspace/components/utils/getCorrectDate";
//@ts-ignore
import { getCookie } from "@docspace/components/utils/cookie";
//@ts-ignore
import ModalDialog from "@docspace/components/modal-dialog";
import Text from "@docspace/components/text";
import ContextMenuButton from "@docspace/components/context-menu-button";
//@ts-ignore
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
import Avatar from "@docspace/components/avatar";
import Link from "@docspace/components/link";
const StyledContainer = styled.div<{
showDescription: boolean;
withShowText: boolean;
}>`
width: 100%;
height: 100%;
box-sizing: border-box;
padding-top: 8px;
display: flex;
flex-direction: column;
.client-block {
width: 100%;
height: 32px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
.client-block__info {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
.client-block__info-logo {
width: 32px;
height: 32px;
max-width: 32px;
max-height: 32px;
border-radius: 3px;
}
}
}
.description {
max-height: ${(props) =>
props.showDescription ? "100%" : props.withShowText ? "100px" : "100%"};
overflow: hidden;
margin-bottom: ${(props) => (props.withShowText ? "4px" : 0)};
}
.desc-link {
color: #adadad;
}
.block-header {
margin-top: 20px;
margin-bottom: 12px;
color: #858585;
}
.creator-block {
margin: 8px 0;
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.privacy-block {
display: flex;
.separator {
display: inline-block;
margin-top: 2px;
height: 16px;
width: 1px;
margin: 0 8px;
background: #ffffff;
}
}
`;
interface InfoDialogProps {
visible: boolean;
scopeList?: IScope[];
setInfoDialogVisible?: (value: boolean) => void;
getContextMenuItems?: (
t: any,
item: IClientProps,
isInfo?: boolean
) => {
[key: string]: any | string | boolean | ((clientId: string) => void);
}[];
client?: IClientProps;
}
const InfoDialog = ({
visible,
client,
scopeList,
setInfoDialogVisible,
getContextMenuItems,
}: InfoDialogProps) => {
const { t } = useTranslation(["Common"]);
const [showDescription, setShowDescription] = React.useState(false);
const [isRender, setIsRender] = React.useState(false);
const [withShowText, setWithShowText] = React.useState(false);
React.useEffect(() => {
setIsRender(true);
}, []);
React.useEffect(() => {
const el = document.getElementById("client-info-description-text");
if (!el) return;
setWithShowText(el?.offsetHeight >= 100);
}, [isRender]);
const getContextOptions = () => {
const contextOptions =
client && getContextMenuItems && getContextMenuItems(t, client, true);
return contextOptions;
};
const onClose = () => {
setInfoDialogVisible?.(false);
};
const locale = getCookie("asc_language");
const modifiedDate = getCorrectDate(locale, client?.modifiedOn);
return (
<ModalDialog visible={visible} displayType={"aside"} onClose={onClose}>
<ModalDialog.Header>Info</ModalDialog.Header>
<ModalDialog.Body>
<StyledContainer
showDescription={showDescription}
withShowText={withShowText}
>
<div className="client-block">
<div className="client-block__info">
<img className="client-block__info-logo" src={client?.logo} />
{/* @ts-ignore */}
<Text
fontSize={"16px"}
lineHeight={"22px"}
fontWeight={"700"}
noSelect
truncate
>
{client?.name}
</Text>
</div>
<ContextMenuButton getData={getContextOptions} />
</div>
{/* @ts-ignore */}
<Text
className={"block-header"}
fontSize={"14px"}
lineHeight={"16px"}
fontWeight={"600"}
noSelect
truncate
>
Creator
</Text>
<div className="creator-block">
<Avatar source={client?.creatorAvatar} size={"small"} />
{/* @ts-ignore */}
<Text
fontSize={"14px"}
lineHeight={"16px"}
fontWeight={"600"}
noSelect
truncate
>
{client?.creatorDisplayName}
</Text>
</div>
{/* @ts-ignore */}
<Text
className={"block-header"}
fontSize={"14px"}
lineHeight={"16px"}
fontWeight={"600"}
noSelect
truncate
>
Description
</Text>
{/* @ts-ignore */}
<Text
id={"client-info-description-text"}
className={"description"}
fontSize={"13px"}
lineHeight={"20px"}
fontWeight={"400"}
noSelect
>
{client?.description}
</Text>
{withShowText && (
<>
{/* @ts-ignore */}
<Link
className={"desc-link"}
fontSize={"13px"}
lineHeight={"15px"}
fontWeight={"600"}
isHovered
onClick={() => setShowDescription((val) => !val)}
type={"action"}
>
{showDescription ? "Hide" : "Show more"}
</Link>
</>
)}
{/* @ts-ignore */}
<Text
className={"block-header"}
fontSize={"14px"}
lineHeight={"16px"}
fontWeight={"600"}
noSelect
truncate
>
Website
</Text>
{/* @ts-ignore */}
<Link
fontSize={"13px"}
lineHeight={"15px"}
fontWeight={"600"}
isHovered
href={client?.websiteUrl}
type={"action"}
target={"_blank"}
>
{client?.websiteUrl}
</Link>
{/* @ts-ignore */}
<Text
className={"block-header"}
fontSize={"14px"}
lineHeight={"16px"}
fontWeight={"600"}
noSelect
truncate
>
Access
</Text>
<ScopeList
selectedScopes={client?.scopes || []}
scopes={scopeList || []}
t={t}
/>
{/* @ts-ignore */}
<Text
className={"block-header"}
fontSize={"14px"}
lineHeight={"20px"}
fontWeight={"600"}
noSelect
truncate
>
Support & Legal info
</Text>
{/* @ts-ignore */}
<Text
className={"privacy-block"}
fontSize={"13px"}
lineHeight={"15px"}
fontWeight={"600"}
noSelect
truncate
>
{/* @ts-ignore */}
<Link
fontSize={"13px"}
lineHeight={"15px"}
fontWeight={"600"}
isHovered
href={client?.policyUrl}
type={"action"}
target={"_blank"}
>
Privacy policy
</Link>
<span className="separator"></span>
{/* @ts-ignore */}
<Link
fontSize={"13px"}
lineHeight={"15px"}
fontWeight={"600"}
isHovered
href={client?.termsUrl}
type={"action"}
target={"_blank"}
>
Terms of Service
</Link>
</Text>
{/* @ts-ignore */}
<Text
className={"block-header"}
fontSize={"14px"}
lineHeight={"16px"}
fontWeight={"600"}
noSelect
truncate
>
Last modified
</Text>
{/* @ts-ignore */}
<Text
fontSize={"13px"}
lineHeight={"20px"}
fontWeight={"600"}
noSelect
truncate
>
{modifiedDate}
</Text>
</StyledContainer>
</ModalDialog.Body>
</ModalDialog>
);
};
export default inject(({ oauthStore }: { oauthStore: OAuthStoreProps }) => {
const {
setInfoDialogVisible,
bufferSelection,
scopeList,
getContextMenuItems,
} = oauthStore;
return {
setInfoDialogVisible,
client: bufferSelection,
scopeList,
getContextMenuItems,
};
})(observer(InfoDialog));

View File

@ -3,7 +3,6 @@ import { useTranslation } from "react-i18next";
//@ts-ignore
import Row from "@docspace/components/row";
import ToggleButton from "@docspace/components/toggle-button";
import { RowContent } from "./RowContent";
import { RowProps } from "./RowView.types";

View File

@ -65,8 +65,9 @@ const Row = (props: RowProps) => {
const contextOptions = getContextMenuItems && getContextMenuItems(t, item);
const local = getCookie("asc_language");
const modifiedDate = getCorrectDate(local, item.modifiedOn);
const locale = getCookie("asc_language");
const modifiedDate = getCorrectDate(locale, item.modifiedOn);
return (
<>

View File

@ -35,6 +35,9 @@ export interface OAuthStoreProps {
viewAs: ViewAsType;
setViewAs: (value: ViewAsType) => void;
infoDialogVisible: boolean;
setInfoDialogVisible: (value: boolean) => void;
deleteDialogVisible: boolean;
setDeleteDialogVisible: (value: boolean) => void;
@ -79,7 +82,8 @@ export interface OAuthStoreProps {
getContextMenuItems: (
t: any,
item: IClientProps
item: IClientProps,
isInfo?: boolean
) => {
[key: string]: any | string | boolean | ((clientId: string) => void);
}[];
@ -97,6 +101,7 @@ class OAuthStore implements OAuthStoreProps {
nextPage: number | null = null;
itemCount: number = 0;
infoDialogVisible: boolean = false;
deleteDialogVisible: boolean = false;
selection: string[] = [];
@ -119,6 +124,10 @@ class OAuthStore implements OAuthStoreProps {
this.viewAs = value;
};
setInfoDialogVisible = (value: boolean) => {
this.infoDialogVisible = value;
};
setDeleteDialogVisible = (value: boolean) => {
this.deleteDialogVisible = value;
};
@ -160,6 +169,7 @@ class OAuthStore implements OAuthStoreProps {
};
editClient = (clientId: string) => {
this.setInfoDialogVisible(false);
//@ts-ignore
window?.DocSpace?.navigate(
`/portal-settings/developer-tools/oauth/${clientId}`
@ -315,12 +325,13 @@ class OAuthStore implements OAuthStoreProps {
}
};
getContextMenuItems = (t: any, item: IClientProps) => {
getContextMenuItems = (t: any, item: IClientProps, isInfo?: boolean) => {
const { clientId } = item;
const isGroupContext = this.selection.length;
const onDelete = () => {
this.setInfoDialogVisible(false);
if (!isGroupContext) {
this.setBufferSelection(clientId);
}
@ -328,7 +339,13 @@ class OAuthStore implements OAuthStoreProps {
this.setDeleteDialogVisible(true);
};
const onShowInfo = () => {
this.setBufferSelection(clientId);
this.setInfoDialogVisible(true);
};
const onEnable = async (status: boolean) => {
this.setInfoDialogVisible(false);
if (isGroupContext) {
try {
const actions: Promise<void>[] = [];
@ -372,7 +389,8 @@ class OAuthStore implements OAuthStoreProps {
key: "info",
icon: SettingsIcon,
label: "Info",
onClick: () => console.log(clientId),
onClick: onShowInfo,
isDisabled: isInfo,
};
const enableOption = {
@ -424,7 +442,7 @@ class OAuthStore implements OAuthStoreProps {
contextOptions.unshift(enableOption);
}
contextOptions.unshift(infoOption);
if (!isInfo) contextOptions.unshift(infoOption);
contextOptions.unshift(authButtonOption);
contextOptions.unshift(editOption);
}

View File

@ -36,7 +36,7 @@ const StyledScopeItem = styled.div`
border-radius: 50%;
background-color: ${(props) => props.theme.mainText};
background: ${(props) => props.theme.mainText};
}
`;

View File

@ -65,6 +65,7 @@ const Base = {
fontSize: "13px",
interfaceDirection: "ltr",
separatorColor: "#eceef1",
mainText: black,
text: {
color: black,

View File

@ -59,6 +59,7 @@ const Dark = {
fontSize: "13px",
interfaceDirection: "ltr",
separatorColor: "#474747",
mainText: white,
text: {
color: grayMaxLight,