Add applications page to account ui (#4254)
This commit is contained in:
parent
e65a1effda
commit
d241f63a22
3 changed files with 302 additions and 5 deletions
|
@ -1,12 +1,18 @@
|
|||
{
|
||||
"accept": "Accept",
|
||||
"accessGrantedOn": "Access granted on",
|
||||
"accountSecurity": "Account security",
|
||||
"add": "Add",
|
||||
"application": "Application",
|
||||
"applicationDetails": "Application details",
|
||||
"applications": "Applications",
|
||||
"applicationsIntroMessage": "Track and manage your app permission to access your account",
|
||||
"applicationType": "Application type",
|
||||
"avatar": "Avatar",
|
||||
"cancel": "Cancel",
|
||||
"client": "Client",
|
||||
"close": "Close",
|
||||
"description": "Description",
|
||||
"deviceActivity": "Device activity",
|
||||
"doDeny": "Deny",
|
||||
"done": "Done",
|
||||
|
@ -16,16 +22,26 @@
|
|||
"firstName": "First name",
|
||||
"fullName": "{{givenName}} {{familyName}}",
|
||||
"groups": "Groups",
|
||||
"infoMessage": "By clicking Remove Access, you will remove granted permissions of this application. This application will no longer use your information.",
|
||||
"internalApp": "Internal",
|
||||
"inUse": "In use",
|
||||
"lastName": "Last name",
|
||||
"linkedAccounts": "Linked accounts",
|
||||
"logo": "Logo",
|
||||
"manageAccount": "Manage account",
|
||||
"myResources": "My Resources",
|
||||
"name": "Name",
|
||||
"notInUse": "Not in use",
|
||||
"offlineAccess": "Offline access",
|
||||
"permissionRequest": "Permission requests - {{0}}",
|
||||
"permissionRequests": "Permission requests",
|
||||
"permissions": "Permissions",
|
||||
"personalInfo": "Personal info",
|
||||
"personalInfoDescription": "Manage your basic information",
|
||||
"privacyPolicy": "Privacy policy",
|
||||
"removeButton": "Remove access",
|
||||
"removeModalMessage": "This will remove the currently granted access permission for {{0}}. You will need to grant access again if you want to use this app.",
|
||||
"removeModalTitle": "Remove access",
|
||||
"requestor": "Requestor",
|
||||
"required": "Required",
|
||||
"resourceAlreadyShared": "Resource is already shared with this user.",
|
||||
|
@ -44,6 +60,9 @@
|
|||
"signOut": "Sign out",
|
||||
"somethingWentWrong": "Something went wrong",
|
||||
"somethingWentWrongDescription": "Sorry, an unexpected error has occurred.",
|
||||
"status": "Status",
|
||||
"termsOfService": "Terms of service",
|
||||
"thirdPartyApp": "Third-party",
|
||||
"tryAgain": "Try again",
|
||||
"unknownUser": "Anonymous",
|
||||
"unShare": "Unshare all",
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { parseResponse } from "./parse-response";
|
||||
import { Permission, UserRepresentation } from "./representations";
|
||||
import {
|
||||
ClientRepresentation,
|
||||
Permission,
|
||||
UserRepresentation,
|
||||
} from "./representations";
|
||||
import { request } from "./request";
|
||||
|
||||
export type CallOptions = {
|
||||
|
@ -29,3 +33,14 @@ export async function getPermissionRequests(
|
|||
|
||||
return parseResponse<Permission[]>(response);
|
||||
}
|
||||
|
||||
export async function getApplications({ signal }: CallOptions = {}): Promise<
|
||||
ClientRepresentation[]
|
||||
> {
|
||||
const response = await request("/applications", { signal });
|
||||
return parseResponse<ClientRepresentation[]>(response);
|
||||
}
|
||||
|
||||
export async function deleteConsent(id: string) {
|
||||
return request(`/applications/${id}/consent`, { method: "DELETE" });
|
||||
}
|
||||
|
|
|
@ -1,7 +1,270 @@
|
|||
import { PageSection } from "@patternfly/react-core";
|
||||
import {
|
||||
Button,
|
||||
DataList,
|
||||
DataListCell,
|
||||
DataListContent,
|
||||
DataListItem,
|
||||
DataListItemCells,
|
||||
DataListItemRow,
|
||||
DataListToggle,
|
||||
DescriptionList,
|
||||
DescriptionListDescription,
|
||||
DescriptionListGroup,
|
||||
DescriptionListTerm,
|
||||
Grid,
|
||||
GridItem,
|
||||
Spinner,
|
||||
} from "@patternfly/react-core";
|
||||
import {
|
||||
CheckIcon,
|
||||
ExternalLinkAltIcon,
|
||||
InfoAltIcon,
|
||||
} from "@patternfly/react-icons";
|
||||
import { TFuncKey } from "i18next";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { deleteConsent, getApplications } from "../api/methods";
|
||||
import { ClientRepresentation } from "../api/representations";
|
||||
import { useAlerts } from "../components/alerts/Alerts";
|
||||
import { ContinueCancelModal } from "../components/continue-cancel/ContinueCancelModel";
|
||||
import { Page } from "../components/page/Page";
|
||||
import { usePromise } from "../utils/usePromise";
|
||||
|
||||
const Applications = () => (
|
||||
<PageSection>This is the applications page.</PageSection>
|
||||
type Application = ClientRepresentation & {
|
||||
open: boolean;
|
||||
};
|
||||
|
||||
const Applications = () => {
|
||||
const { t } = useTranslation();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
|
||||
const [applications, setApplications] = useState<Application[]>();
|
||||
const [key, setKey] = useState(1);
|
||||
const refresh = () => setKey(key + 1);
|
||||
|
||||
usePromise(
|
||||
(signal) => getApplications({ signal }),
|
||||
(clients) => setApplications(clients.map((c) => ({ ...c, open: false }))),
|
||||
[key]
|
||||
);
|
||||
|
||||
const toggleOpen = (clientId: string) => {
|
||||
setApplications([
|
||||
...applications!.map((a) =>
|
||||
a.clientId === clientId ? { ...a, open: !a.open } : a
|
||||
),
|
||||
]);
|
||||
};
|
||||
|
||||
const removeConsent = async (id: string) => {
|
||||
try {
|
||||
await deleteConsent(id);
|
||||
refresh();
|
||||
addAlert("removeConsentSuccess");
|
||||
} catch (error) {
|
||||
addError("removeConsentError", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (!applications) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page title={t("application")} description={t("applicationsIntroMessage")}>
|
||||
<DataList id="applications-list" aria-label={t("application")}>
|
||||
<DataListItem
|
||||
id="applications-list-header"
|
||||
aria-labelledby="Columns names"
|
||||
>
|
||||
<DataListItemRow>
|
||||
<span style={{ visibility: "hidden", height: 55 }}>
|
||||
<DataListToggle
|
||||
id="applications-list-header-invisible-toggle"
|
||||
aria-controls="hidden"
|
||||
/>
|
||||
</span>
|
||||
<DataListItemCells
|
||||
dataListCells={[
|
||||
<DataListCell
|
||||
key="applications-list-client-id-header"
|
||||
width={2}
|
||||
className="pf-u-pt-md"
|
||||
>
|
||||
<strong>{t("name")}</strong>
|
||||
</DataListCell>,
|
||||
<DataListCell
|
||||
key="applications-list-app-type-header"
|
||||
width={2}
|
||||
className="pf-u-pt-md"
|
||||
>
|
||||
<strong>{t("applicationType")}</strong>
|
||||
</DataListCell>,
|
||||
<DataListCell
|
||||
key="applications-list-status"
|
||||
width={2}
|
||||
className="pf-u-pt-md"
|
||||
>
|
||||
<strong>{t("status")}</strong>
|
||||
</DataListCell>,
|
||||
]}
|
||||
/>
|
||||
</DataListItemRow>
|
||||
</DataListItem>
|
||||
{applications.map((application) => (
|
||||
<DataListItem
|
||||
key={application.clientId}
|
||||
aria-labelledby="applications-list"
|
||||
isExpanded={application.open}
|
||||
>
|
||||
<DataListItemRow className="pf-u-align-items-center">
|
||||
<DataListToggle
|
||||
onClick={() => toggleOpen(application.clientId)}
|
||||
isExpanded={application.open}
|
||||
id={`toggle-${application.clientId}`}
|
||||
/>
|
||||
<DataListItemCells
|
||||
className="pf-u-align-items-center"
|
||||
dataListCells={[
|
||||
<DataListCell width={2} key={`client${application.clientId}`}>
|
||||
<Button
|
||||
className="pf-u-pl-0 title-case"
|
||||
component="a"
|
||||
variant="link"
|
||||
onClick={() => window.open(application.effectiveUrl)}
|
||||
>
|
||||
{application.clientName || application.clientId}{" "}
|
||||
<ExternalLinkAltIcon />
|
||||
</Button>
|
||||
</DataListCell>,
|
||||
<DataListCell
|
||||
width={2}
|
||||
key={`internal${application.clientId}`}
|
||||
>
|
||||
{application.userConsentRequired
|
||||
? t("thirdPartyApp")
|
||||
: t("internalApp")}
|
||||
{application.offlineAccess ? ", " + t("offlineAccess") : ""}
|
||||
</DataListCell>,
|
||||
<DataListCell width={2} key={`status${application.clientId}`}>
|
||||
{application.inUse ? t("inUse") : t("notInUse")}
|
||||
</DataListCell>,
|
||||
]}
|
||||
/>
|
||||
</DataListItemRow>
|
||||
|
||||
<DataListContent
|
||||
className="pf-u-pl-4xl"
|
||||
aria-label={t("applicationDetails")}
|
||||
isHidden={!application.open}
|
||||
>
|
||||
<DescriptionList>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>{t("client")}</DescriptionListTerm>
|
||||
<DescriptionListDescription>
|
||||
{application.clientId}
|
||||
</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
{application.description && (
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
{t("description")}
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>
|
||||
{application.description}
|
||||
</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
)}
|
||||
{application.effectiveUrl && (
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>URL</DescriptionListTerm>
|
||||
<DescriptionListDescription>
|
||||
{application.effectiveUrl.split('"')}
|
||||
</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
)}
|
||||
{application.consent && (
|
||||
<>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>Has access to</DescriptionListTerm>
|
||||
{application.consent.grantedScopes.map((scope) => (
|
||||
<DescriptionListDescription key={`scope${scope.id}`}>
|
||||
<CheckIcon /> {t(scope.name as TFuncKey)}
|
||||
</DescriptionListDescription>
|
||||
))}
|
||||
</DescriptionListGroup>
|
||||
{application.tosUri && (
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
{t("termsOfService")}
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>
|
||||
{application.tosUri}
|
||||
</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
)}
|
||||
{application.policyUri && (
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
{t("privacyPolicy")}
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>
|
||||
{application.policyUri}
|
||||
</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
)}
|
||||
{application.logoUri && (
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>{t("logo")}</DescriptionListTerm>
|
||||
<DescriptionListDescription>
|
||||
<img src={application.logoUri} />
|
||||
</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
)}
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
{t("accessGrantedOn") + ": "}
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>
|
||||
{new Intl.DateTimeFormat("en", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
second: "numeric",
|
||||
}).format(application.consent.createdDate)}
|
||||
</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
</>
|
||||
)}
|
||||
</DescriptionList>
|
||||
{(application.consent || application.offlineAccess) && (
|
||||
<Grid hasGutter>
|
||||
<hr />
|
||||
<GridItem>
|
||||
<ContinueCancelModal
|
||||
buttonTitle="removeButton"
|
||||
buttonVariant="secondary"
|
||||
modalTitle="removeModalTitle"
|
||||
modalMessage={t("removeModalMessage", [
|
||||
application.clientId,
|
||||
])}
|
||||
continueLabel="confirmButton"
|
||||
onContinue={() => removeConsent(application.clientId)} // required
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<InfoAltIcon /> {t("infoMessage")}
|
||||
</GridItem>
|
||||
</Grid>
|
||||
)}
|
||||
</DataListContent>
|
||||
</DataListItem>
|
||||
))}
|
||||
</DataList>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Applications;
|
||||
|
|
Loading…
Reference in a new issue