Client:Profile: add consent list
This commit is contained in:
parent
e691e88771
commit
f0c94941e8
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -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));
|
@ -0,0 +1,12 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 12px;
|
||||
`;
|
||||
|
||||
export { StyledContainer };
|
@ -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;
|
@ -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>
|
||||
);
|
||||
};
|
@ -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;
|
||||
`;
|
@ -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;
|
||||
}
|
@ -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));
|
@ -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;
|
@ -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;
|
@ -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 };
|
@ -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>;
|
||||
}
|
@ -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;
|
@ -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));
|
@ -47,6 +47,14 @@ const generalRoutes = [
|
||||
</PrivateRoute>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "authorized-apps",
|
||||
element: (
|
||||
<PrivateRoute>
|
||||
<Profile />
|
||||
</PrivateRoute>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user