Client:Profile: add consent list

This commit is contained in:
Timofey Boyko 2023-11-09 18:35:46 +03:00
parent e691e88771
commit f0c94941e8
19 changed files with 1175 additions and 103 deletions

View File

@ -121,12 +121,14 @@ interface InfoDialogProps {
getContextMenuItems?: (
t: any,
item: IClientProps,
isInfo?: boolean
isInfo?: boolean,
isSettings?: boolean
) => {
[key: string]: any | string | boolean | ((clientId: string) => void);
}[];
client?: IClientProps;
isProfile?: boolean;
}
const InfoDialog = ({
@ -137,6 +139,8 @@ const InfoDialog = ({
setInfoDialogVisible,
getContextMenuItems,
isProfile,
}: InfoDialogProps) => {
const { t } = useTranslation(["Common"]);
@ -157,7 +161,9 @@ const InfoDialog = ({
const getContextOptions = () => {
const contextOptions =
client && getContextMenuItems && getContextMenuItems(t, client, true);
client &&
getContextMenuItems &&
getContextMenuItems(t, client, true, !isProfile);
return contextOptions;
};
@ -195,66 +201,74 @@ const InfoDialog = ({
<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={"min"} />
{/* @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 && (
{!isProfile && (
<>
{/* @ts-ignore */}
<Link
className={"desc-link"}
fontSize={"13px"}
lineHeight={"15px"}
<Text
className={"block-header"}
fontSize={"14px"}
lineHeight={"16px"}
fontWeight={"600"}
isHovered
onClick={() => setShowDescription((val) => !val)}
type={"action"}
noSelect
truncate
>
{showDescription ? "Hide" : "Show more"}
</Link>
Creator
</Text>
<div className="creator-block">
<Avatar source={client?.creatorAvatar} size={"min"} />
{/* @ts-ignore */}
<Text
fontSize={"14px"}
lineHeight={"16px"}
fontWeight={"600"}
noSelect
truncate
>
{client?.creatorDisplayName}
</Text>
</div>
</>
)}
{!isProfile && (
<>
{/* @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 */}
@ -296,6 +310,31 @@ const InfoDialog = ({
scopes={scopeList || []}
t={t}
/>
{isProfile && (
<>
{/* @ts-ignore */}
<Text
className={"block-header"}
fontSize={"14px"}
lineHeight={"16px"}
fontWeight={"600"}
noSelect
truncate
>
Access granted
</Text>
{/* @ts-ignore */}
<Text
fontSize={"13px"}
lineHeight={"20px"}
fontWeight={"600"}
noSelect
truncate
>
{modifiedDate}
</Text>
</>
)}
{/* @ts-ignore */}
<Text
className={"block-header"}
@ -342,27 +381,31 @@ const InfoDialog = ({
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>
{!isProfile && (
<>
{/* @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>

View File

@ -34,14 +34,12 @@ export const RowContent = ({
</Text>
</FlexWrapper>
{!isMobileOnly && (
<>
{/* @ts-ignore */}
<Text fontWeight={600} fontSize="12px" color="#A3A9AE">
{item.description}
</Text>
</>
)}
<>
{/* @ts-ignore */}
<Text fontWeight={600} fontSize="12px" color="#A3A9AE">
{item.description}
</Text>
</>
</ContentWrapper>
<ToggleButtonWrapper>

View File

@ -16,6 +16,7 @@ import InterfaceTheme from "./sub-components/interface-theme";
import { tablet } from "@docspace/components/utils/device";
import { DeviceType } from "@docspace/common/constants";
import AuthorizedApps from "./sub-components/authorized-apps";
const Wrapper = styled.div`
display: flex;
@ -48,6 +49,11 @@ const SectionBodyContent = (props) => {
name: t("InterfaceTheme"),
content: <InterfaceTheme />,
},
{
id: "authorized-apps",
name: "Authorized apps",
content: <AuthorizedApps />,
},
];
if (!profile?.isVisitor)

View File

@ -0,0 +1,115 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { IClientProps } from "@docspace/common/utils/oauth/interfaces";
import { useViewEffect } from "@docspace/common/hooks";
import { DeviceUnionType } from "@docspace/common/hooks/useViewEffect";
//@ts-ignore
import { Consumer } from "@docspace/components/utils/context";
import Text from "@docspace/components/text";
//@ts-ignore
import { OAuthStoreProps, ViewAsType } from "SRC_DIR/store/OAuthStore";
//@ts-ignore
import InfoDialog from "SRC_DIR/pages/PortalSettings/categories/developer-tools/OAuth/sub-components/InfoDialog";
import { StyledContainer } from "./styled-authorized-apps";
import TableView from "./sub-components/TableView";
import RowView from "./sub-components/RowView";
interface AuthorizedAppsProps {
consents?: IClientProps[];
fetchConsents?: () => Promise<void>;
viewAs: ViewAsType;
setViewAs: (value: ViewAsType) => void;
currentDeviceType: DeviceUnionType;
infoDialogVisible: boolean;
fetchScopes?: () => Promise<void>;
}
const AuthorizedApps = ({
consents,
fetchConsents,
viewAs,
setViewAs,
currentDeviceType,
infoDialogVisible,
fetchScopes,
}: AuthorizedAppsProps) => {
const getConsentList = React.useCallback(async () => {
fetchScopes?.();
await fetchConsents?.();
}, []);
React.useEffect(() => {
if (!!consents?.length) return;
getConsentList();
}, []);
useViewEffect({
view: viewAs,
setView: setViewAs,
currentDeviceType,
});
return (
<StyledContainer>
{/* @ts-ignore */}
<Text fontSize={"12px"} fontWeight={"400"} lineHeight={"16px"}>
Here you can check the apps info to which you have granted the auth
access, and revoke consent if needed.
</Text>
<Consumer>
{(context: { sectionWidth: number; sectionHeight: number }) => (
<>
{viewAs === "table" ? (
<TableView
items={consents || []}
sectionWidth={context.sectionWidth}
/>
) : (
<RowView
items={consents || []}
sectionWidth={context.sectionWidth}
/>
)}
</>
)}
</Consumer>
{infoDialogVisible && (
<InfoDialog visible={infoDialogVisible} isProfile />
)}
</StyledContainer>
);
};
export default inject(
({ oauthStore, auth }: { oauthStore: OAuthStoreProps; auth: any }) => {
const {
consents,
fetchConsents,
fetchScopes,
viewAs,
setViewAs,
infoDialogVisible,
} = oauthStore;
const { currentDeviceType } = auth.settingsStore;
return {
consents,
fetchConsents,
viewAs,
setViewAs,
currentDeviceType,
infoDialogVisible,
fetchScopes,
};
}
)(observer(AuthorizedApps));

View File

@ -0,0 +1,12 @@
import styled from "styled-components";
const StyledContainer = styled.div`
width: 100%;
display: flex;
flex-direction: column;
gap: 12px;
`;
export { StyledContainer };

View File

@ -0,0 +1,60 @@
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
//@ts-ignore
import Row from "@docspace/components/row";
import { RowContent } from "./RowContent";
import { RowProps } from "./RowView.types";
export const OAuthRow = (props: RowProps) => {
const {
item,
sectionWidth,
changeClientStatus,
isChecked,
inProgress,
getContextMenuItems,
setSelection,
} = props;
const navigate = useNavigate();
const { t } = useTranslation(["Common"]);
const editClient = () => {
navigate(`${item.clientId}`);
};
const contextOptions =
getContextMenuItems && getContextMenuItems(t, item, false, false);
const element = (
<img style={{ borderRadius: "3px" }} src={item.logo} alt={"App logo"} />
);
return (
<>
<Row
sectionWidth={sectionWidth}
key={item.clientId}
data={item}
contextOptions={contextOptions}
element={element}
mode={"modern"}
checked={isChecked}
inProgress={inProgress}
onSelect={() => setSelection && setSelection(item.clientId)}
>
<RowContent
sectionWidth={sectionWidth}
item={item}
isChecked={isChecked}
inProgress={inProgress}
setSelection={setSelection}
/>
</Row>
</>
);
};
export default OAuthRow;

View File

@ -0,0 +1,45 @@
import React from "react";
import Text from "@docspace/components/text";
import Link from "@docspace/components/link";
import {
StyledRowContent,
ContentWrapper,
FlexWrapper,
} from "./RowView.styled";
import { RowContentProps } from "./RowView.types";
export const RowContent = ({ sectionWidth, item }: RowContentProps) => {
return (
<StyledRowContent sectionWidth={sectionWidth}>
<ContentWrapper>
<FlexWrapper>
{/* @ts-ignore */}
<Text
fontWeight={600}
fontSize="14px"
style={{ marginInlineEnd: "8px" }}
>
{item.name}
</Text>
</FlexWrapper>
{/* @ts-ignore */}
<Text fontWeight={600} fontSize="12px" color="#A3A9AE">
{/* @ts-ignore */}
<Link
color="#A3A9AE"
href={item.websiteUrl}
type={"action"}
target={"_blank"}
isHovered
>
{item.websiteUrl}
</Link>
</Text>
</ContentWrapper>
<></>
</StyledRowContent>
);
};

View File

@ -0,0 +1,67 @@
import styled from "styled-components";
//@ts-ignore
import RowContainer from "@docspace/components/row-container";
//@ts-ignore
import RowContent from "@docspace/components/row-content";
export const StyledRowContainer = styled(RowContainer)`
margin-top: 0px;
.row-list-item {
padding-left: 21px;
}
.row-loader {
width: calc(100% - 46px) !important;
padding-left: 21px;
}
img {
width: 32px;
max-width: 32px;
height: 32px;
max-height: 32px;
}
`;
export const StyledRowContent = styled(RowContent)`
display: flex;
padding-bottom: 10px;
.rowMainContainer {
height: 100%;
width: 100%;
}
.mainIcons {
min-width: 76px;
}
`;
export const ContentWrapper = styled.div`
display: flex;
flex-direction: column;
justify-items: center;
`;
export const ToggleButtonWrapper = styled.div`
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
label {
margin-top: 1px;
position: relative;
gap: 0px;
margin-right: -8px;
}
`;
export const FlexWrapper = styled.div`
display: flex;
`;

View File

@ -0,0 +1,48 @@
import { IClientProps } from "@docspace/common/utils/oauth/interfaces";
export interface RowViewProps {
items: IClientProps[];
sectionWidth: number;
selection?: string[];
setSelection?: (clientId: string) => void;
getContextMenuItems?: (
t: any,
item: IClientProps,
isInfo: boolean,
isSettings: boolean
) => {
[key: string]: any | string | boolean | ((clientId: string) => void);
}[];
activeClients?: string[];
hasNextPage?: boolean;
itemCount?: number;
fetchNextClients?: (startIndex: number) => Promise<void>;
changeClientStatus?: (clientId: string, status: boolean) => Promise<void>;
}
export interface RowProps {
item: IClientProps;
isChecked: boolean;
inProgress: boolean;
sectionWidth: number;
getContextMenuItems?: (
t: any,
item: IClientProps,
isInfo: boolean,
isSettings: boolean
) => {
[key: string]: any | string | boolean | ((clientId: string) => void);
}[];
setSelection?: (clientId: string) => void;
changeClientStatus?: (clientId: string, status: boolean) => Promise<void>;
}
export interface RowContentProps {
sectionWidth: number;
item: IClientProps;
isChecked: boolean;
inProgress: boolean;
setSelection?: (clientId: string) => void;
}

View File

@ -0,0 +1,87 @@
import React from "react";
import { inject, observer } from "mobx-react";
import { isMobile } from "react-device-detect";
//@ts-ignore
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
import OAuthRow from "./Row";
import { RowViewProps } from "./RowView.types";
import { StyledRowContainer } from "./RowView.styled";
const RowView = (props: RowViewProps) => {
const {
items,
sectionWidth,
changeClientStatus,
selection,
setSelection,
activeClients,
getContextMenuItems,
hasNextPage,
itemCount,
fetchNextClients,
} = props;
return (
<StyledRowContainer
itemHeight={59}
filesLength={items.length}
fetchMoreFiles={({
startIndex,
}: {
startIndex: number;
stopIndex: number;
}) => fetchNextClients && fetchNextClients(startIndex)}
hasMoreFiles={hasNextPage}
itemCount={itemCount}
useReactWindow={true}
>
{items.map((item) => (
<OAuthRow
key={item.clientId}
item={item}
isChecked={selection?.includes(item.clientId) || false}
inProgress={activeClients?.includes(item.clientId) || false}
setSelection={setSelection}
changeClientStatus={changeClientStatus}
getContextMenuItems={getContextMenuItems}
sectionWidth={sectionWidth}
/>
))}
</StyledRowContainer>
);
};
export default inject(
({ oauthStore }: { auth: any; oauthStore: OAuthStoreProps }) => {
const {
viewAs,
setViewAs,
selection,
setSelection,
changeClientStatus,
getContextMenuItems,
activeClients,
hasNextPage,
itemCount,
fetchNextClients,
} = oauthStore;
return {
viewAs,
setViewAs,
changeClientStatus,
selection,
setSelection,
activeClients,
getContextMenuItems,
hasNextPage,
itemCount,
fetchNextClients,
};
}
)(observer(RowView));

View File

@ -0,0 +1,60 @@
import { useTranslation } from "react-i18next";
//@ts-ignore
import TableHeader from "@docspace/components/table-container/TableHeader";
import { HeaderProps } from "./TableView.types";
const Header = (props: HeaderProps) => {
const { sectionWidth, tableRef, columnStorageName, tagRef } = props;
const { t } = useTranslation(["Common"]);
const defaultColumns: {
[key: string]:
| string
| number
| boolean
| ((key: string, e: any) => void | undefined);
}[] = [
{
key: "App",
title: "Applications",
resizable: true,
enable: true,
default: true,
active: false,
minWidth: 210,
},
{
key: "Website",
title: "Website",
resizable: true,
enable: true,
minWidth: 150,
},
{
key: "Access granted",
title: "Access granted",
resizable: true,
enable: true,
minWidth: 150,
},
];
return (
<TableHeader
checkboxSize="48px"
containerRef={tableRef}
columns={defaultColumns}
columnStorageName={columnStorageName}
sectionWidth={sectionWidth}
checkboxMargin="12px"
showSettings={false}
useReactWindow
infoPanelVisible={false}
tagRef={tagRef}
/>
);
};
export default Header;

View File

@ -0,0 +1,89 @@
import { useTranslation } from "react-i18next";
//@ts-ignore
import TableCell from "@docspace/components/table-container/TableCell";
import Text from "@docspace/components/text";
//@ts-ignore
import getCorrectDate from "@docspace/components/utils/getCorrectDate";
//@ts-ignore
import { getCookie } from "@docspace/components/utils/cookie";
import Link from "@docspace/components/link";
import NameCell from "./columns/name";
import { StyledRowWrapper, StyledTableRow } from "./TableView.styled";
import { RowProps } from "./TableView.types";
const Row = (props: RowProps) => {
const {
item,
isChecked,
inProgress,
getContextMenuItems,
setSelection,
} = props;
const { t } = useTranslation(["Common"]);
const contextOptions =
getContextMenuItems && getContextMenuItems(t, item, false, false);
const locale = getCookie("asc_language");
const modifiedDate = getCorrectDate(locale, item.modifiedOn);
return (
<>
<StyledRowWrapper className="handle">
<StyledTableRow contextOptions={contextOptions}>
<TableCell className={"table-container_file-name-cell"}>
<NameCell
name={item.name}
icon={item.logo}
isChecked={isChecked}
inProgress={inProgress}
clientId={item.clientId}
setSelection={setSelection}
/>
</TableCell>
<TableCell>
{/* @ts-ignore */}
<Text
as="span"
fontWeight={400}
className="mr-8 textOverflow description-text"
>
{/* @ts-ignore */}
<Link
className="description-text"
href={item.websiteUrl}
type={"action"}
target={"_blank"}
isHovered
>
{item.websiteUrl}
</Link>
</Text>
</TableCell>
<TableCell>
{/* @ts-ignore */}
<Text
as="span"
fontWeight={400}
className="mr-8 textOverflow description-text"
>
{modifiedDate}
</Text>
</TableCell>
</StyledTableRow>
</StyledRowWrapper>
</>
);
};
export default Row;

View File

@ -0,0 +1,102 @@
import styled, { css } from "styled-components";
//@ts-ignore
import TableContainer from "@docspace/components/table-container/TableContainer";
//@ts-ignore
import TableRow from "@docspace/components/table-container/TableRow";
import { Base } from "@docspace/components/themes";
export const TableWrapper = styled(TableContainer)`
margin-top: 0px;
.header-container-text {
font-size: 12px;
}
.table-container_header {
position: absolute;
}
`;
const StyledRowWrapper = styled.div`
display: contents;
`;
const StyledTableRow = styled(TableRow)`
.table-container_cell {
text-overflow: ellipsis;
padding-right: 8px;
}
.mr-8 {
margin-right: 8px;
}
.textOverflow {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.description-text {
color: #858585;
}
.toggleButton {
display: contents;
input {
position: relative;
margin-left: -8px;
}
}
.table-container_row-loader {
margin-left: 8px;
margin-right: 16px;
}
:hover {
.table-container_cell {
cursor: pointer;
background: ${(props) =>
`${props.theme.filesSection.tableView.row.backgroundActive} !important`};
margin-top: -1px;
border-top: ${(props) =>
`1px solid ${props.theme.filesSection.tableView.row.borderColor}`};
}
.table-container_file-name-cell {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: -24px;
padding-right: 24px;
`
: css`
margin-left: -24px;
padding-left: 24px;
`}
}
.table-container_row-context-menu-wrapper {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: -20px;
padding-left: 18px;
`
: css`
margin-right: -20px;
padding-right: 18px;
`}
}
}
`;
StyledTableRow.defaultProps = { theme: Base };
export { StyledRowWrapper, StyledTableRow };

View File

@ -0,0 +1,47 @@
import { IClientProps } from "@docspace/common/utils/oauth/interfaces";
export interface TableViewProps {
items: IClientProps[];
sectionWidth: number;
userId?: string;
selection?: string[];
setSelection?: (clientId: string) => void;
getContextMenuItems?: (
t: any,
item: IClientProps,
isInfo: boolean,
isSettings: boolean
) => {
[key: string]: any | string | boolean | ((clientId: string) => void);
}[];
bufferSelection?: IClientProps | null;
activeClients?: string[];
hasNextPage?: boolean;
itemCount?: number;
fetchNextClients?: (startIndex: number) => Promise<void>;
changeClientStatus?: (clientId: string, status: boolean) => Promise<void>;
}
export interface HeaderProps {
sectionWidth: number;
tableRef: HTMLDivElement;
columnStorageName: string;
tagRef: (node: HTMLDivElement) => void;
}
export interface RowProps {
item: IClientProps;
isChecked: boolean;
inProgress: boolean;
getContextMenuItems?: (
t: any,
item: IClientProps,
isInfo: boolean,
isSettings: boolean
) => {
[key: string]: any | string | boolean | ((clientId: string) => void);
}[];
setSelection?: (clientId: string) => void;
changeClientStatus?: (clientId: string, status: boolean) => Promise<void>;
}

View File

@ -0,0 +1,90 @@
import React from "react";
import styled from "styled-components";
import Text from "@docspace/components/text";
import Checkbox from "@docspace/components/checkbox";
//@ts-ignore
import TableCell from "@docspace/components/table-container/TableCell";
import Loader from "@docspace/components/loader";
const StyledContainer = styled.div`
.table-container_row-checkbox {
margin-left: -8px;
width: 16px;
padding: 16px 8px 16px 16px;
}
`;
const StyledImage = styled.img`
width: 32px;
height: 32px;
border-radius: 3px;
`;
interface NameCellProps {
name: string;
clientId: string;
icon?: string;
inProgress?: boolean;
isChecked?: boolean;
setSelection?: (clientId: string) => void;
}
const NameCell = ({
name,
icon,
clientId,
inProgress,
isChecked,
setSelection,
}: NameCellProps) => {
const onChange = () => {
setSelection && setSelection(clientId);
};
return (
<>
{inProgress ? (
<Loader
className="table-container_row-loader"
type="oval"
size="16px"
/>
) : (
<TableCell
className="table-container_element-wrapper"
hasAccess={true}
checked={isChecked}
>
<StyledContainer className="table-container_element-container">
<div className="table-container_element">
{icon && <StyledImage src={icon} alt={"App icon"} />}
</div>
<Checkbox
className="table-container_row-checkbox"
onChange={onChange}
isChecked={isChecked}
title={name}
/>
</StyledContainer>
</TableCell>
)}
{/* @ts-ignore */}
<Text
type="page"
title={name}
fontWeight="600"
fontSize="13px"
isTextOverflow
>
{name}
</Text>
</>
);
};
export default NameCell;

View File

@ -0,0 +1,129 @@
import React from "react";
import { inject, observer } from "mobx-react";
//@ts-ignore
import TableBody from "@docspace/components/table-container/TableBody";
//@ts-ignore
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
import Row from "./Row";
import Header from "./Header";
import { TableViewProps } from "./TableView.types";
import { TableWrapper } from "./TableView.styled";
const TABLE_VERSION = "1";
const COLUMNS_SIZE = `consentColumnsSize_ver-${TABLE_VERSION}`;
const TableView = ({
items,
sectionWidth,
selection,
activeClients,
setSelection,
getContextMenuItems,
changeClientStatus,
userId,
hasNextPage,
itemCount,
fetchNextClients,
}: TableViewProps) => {
const tableRef = React.useRef<HTMLDivElement>(null);
const clickOutside = React.useCallback(
(e: any) => {
if (
e.target.closest(".checkbox") ||
e.target.closest(".table-container_row-checkbox") ||
e.detail === 0
) {
return;
}
setSelection && setSelection("");
},
[setSelection]
);
React.useEffect(() => {
window.addEventListener("click", clickOutside);
return () => {
window.removeEventListener("click", clickOutside);
};
}, [clickOutside, setSelection]);
const columnStorageName = `${COLUMNS_SIZE}=${userId}`;
return (
<TableWrapper forwardedRef={tableRef} useReactWindow>
<Header
sectionWidth={sectionWidth}
//@ts-ignore
tableRef={tableRef}
columnStorageName={columnStorageName}
/>
<TableBody
itemHeight={49}
useReactWindow
columnStorageName={columnStorageName}
filesLength={items.length}
fetchMoreFiles={({
startIndex,
}: {
startIndex: number;
stopIndex: number;
}) => fetchNextClients && fetchNextClients(startIndex)}
hasMoreFiles={hasNextPage}
itemCount={itemCount}
>
{items.map((item) => (
<Row
key={item.clientId}
item={item}
isChecked={selection?.includes(item.clientId) || false}
inProgress={activeClients?.includes(item.clientId) || false}
setSelection={setSelection}
changeClientStatus={changeClientStatus}
getContextMenuItems={getContextMenuItems}
/>
))}
</TableBody>
</TableWrapper>
);
};
export default inject(
({ auth, oauthStore }: { auth: any; oauthStore: OAuthStoreProps }) => {
const { id: userId } = auth.userStore.user;
const {
viewAs,
setViewAs,
selection,
setSelection,
setBufferSelection,
changeClientStatus,
getContextMenuItems,
activeClients,
hasNextPage,
itemCount,
fetchNextClients,
} = oauthStore;
return {
viewAs,
setViewAs,
userId,
changeClientStatus,
selection,
setSelection,
setBufferSelection,
activeClients,
getContextMenuItems,
hasNextPage,
itemCount,
fetchNextClients,
};
}
)(observer(TableView));

View File

@ -47,6 +47,14 @@ const generalRoutes = [
</PrivateRoute>
),
},
{
path: "authorized-apps",
element: (
<PrivateRoute>
<Profile />
</PrivateRoute>
),
},
],
},
];

View File

@ -10,6 +10,7 @@ import {
getClientList,
getScope,
getScopeList,
getConsentList,
} from "@docspace/common/api/oauth";
import {
@ -26,6 +27,7 @@ import EnableReactSvgUrl from "PUBLIC_DIR/images/enable.react.svg?url";
import RemoveReactSvgUrl from "PUBLIC_DIR/images/remove.react.svg?url";
import PencilReactSvgUrl from "PUBLIC_DIR/images/pencil.react.svg?url";
import CodeReactSvgUrl from "PUBLIC_DIR/images/code.react.svg?url";
import ExternalLinkReactSvgUrl from "PUBLIC_DIR/images/external.link.react.svg?url";
const PAGE_LIMIT = 100;
@ -53,6 +55,9 @@ export interface OAuthStoreProps {
fetchClients: () => Promise<void>;
fetchNextClients: (startIndex: number) => Promise<void>;
consents: IClientProps[];
fetchConsents: () => Promise<void>;
saveClient: (client: IClientReqDTO) => Promise<void>;
updateClient: (clientId: string, client: IClientReqDTO) => Promise<void>;
@ -83,7 +88,8 @@ export interface OAuthStoreProps {
getContextMenuItems: (
t: any,
item: IClientProps,
isInfo?: boolean
isInfo?: boolean,
isSettings?: boolean
) => {
[key: string]: any | string | boolean | ((clientId: string) => void);
}[];
@ -116,6 +122,8 @@ class OAuthStore implements OAuthStoreProps {
clientsIsLoading: boolean = true;
consents: IClientProps[] = [];
constructor() {
makeAutoObservable(this);
}
@ -149,6 +157,11 @@ class OAuthStore implements OAuthStoreProps {
if (client) {
this.bufferSelection = { ...client, scopes: [...client.scopes] };
} else {
const consent = this.consents.find((c) => c.clientId === clientId);
if (consent)
this.bufferSelection = { ...consent, scopes: [...consent.scopes] };
}
};
@ -170,6 +183,7 @@ class OAuthStore implements OAuthStoreProps {
editClient = (clientId: string) => {
this.setInfoDialogVisible(false);
this.setPreviewDialogVisible(false);
//@ts-ignore
window?.DocSpace?.navigate(
`/portal-settings/developer-tools/oauth/${clientId}`
@ -208,6 +222,20 @@ class OAuthStore implements OAuthStoreProps {
}
};
fetchConsents = async () => {
try {
const consentList: IClientProps[] = await getConsentList();
console.log(consentList);
runInAction(() => {
this.consents = [...consentList];
});
} catch (e) {
console.log(e);
}
};
fetchNextClients = async (startIndex: number) => {
if (this.clientsIsLoading) return;
@ -338,11 +366,46 @@ class OAuthStore implements OAuthStoreProps {
}
};
getContextMenuItems = (t: any, item: IClientProps, isInfo?: boolean) => {
getContextMenuItems = (
t: any,
item: IClientProps,
isInfo?: boolean,
isSettings?: boolean
) => {
const { clientId } = item;
const isGroupContext = this.selection.length;
const onShowInfo = () => {
this.setBufferSelection(clientId);
this.setPreviewDialogVisible(false);
this.setInfoDialogVisible(true);
};
const openOption = {
key: "open",
icon: ExternalLinkReactSvgUrl,
label: "Open",
onClick: () => window.open(item.websiteUrl, "_blank"),
isDisabled: isInfo,
};
const infoOption = {
key: "info",
icon: SettingsIcon,
label: "Info",
onClick: onShowInfo,
isDisabled: isInfo,
};
if (!isSettings) {
const items: any = [openOption];
if (!isInfo) items.push(infoOption);
return items;
}
const onDelete = () => {
this.setInfoDialogVisible(false);
this.setPreviewDialogVisible(false);
@ -354,12 +417,6 @@ class OAuthStore implements OAuthStoreProps {
}
};
const onShowInfo = () => {
this.setBufferSelection(clientId);
this.setPreviewDialogVisible(false);
this.setInfoDialogVisible(true);
};
const onShowPreview = () => {
this.setBufferSelection(clientId);
this.setInfoDialogVisible(false);
@ -409,14 +466,6 @@ class OAuthStore implements OAuthStoreProps {
onClick: onShowPreview,
};
const infoOption = {
key: "info",
icon: SettingsIcon,
label: "Info",
onClick: onShowInfo,
isDisabled: isInfo,
};
const enableOption = {
key: "enable",
icon: EnableReactSvgUrl,

View File

@ -160,3 +160,20 @@ export const onOAuthSubmit = (
withRedirect: true,
});
};
export const getConsentList = async (): Promise<IClientProps[]> => {
const clients: any = await request({
method: "get",
url: "/clients/consents",
});
const consents: IClientProps[] = [];
clients.forEach((item: any) => {
const client = transformToClientProps(item.client);
consents.push({ ...client, modifiedOn: item.modified_at });
});
return consents;
};