Fix various linting issues for Account Console
Signed-off-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
813b9f2f41
commit
aa86a0b94c
10 changed files with 195 additions and 154 deletions
|
@ -12,7 +12,11 @@ import {
|
|||
} from "@patternfly/react-core";
|
||||
import { LinkIcon, UnlinkIcon } from "@patternfly/react-icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconMapper, useAlerts, useEnvironment } from "@keycloak/keycloak-ui-shared";
|
||||
import {
|
||||
IconMapper,
|
||||
useAlerts,
|
||||
useEnvironment,
|
||||
} from "@keycloak/keycloak-ui-shared";
|
||||
import { linkAccount, unLinkAccount } from "../api/methods";
|
||||
import { LinkedAccountRepresentation } from "../api/representations";
|
||||
|
||||
|
|
|
@ -23,7 +23,11 @@ import {
|
|||
} from "@patternfly/react-icons";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ContinueCancelModal, useAlerts, useEnvironment } from "@keycloak/keycloak-ui-shared";
|
||||
import {
|
||||
ContinueCancelModal,
|
||||
useAlerts,
|
||||
useEnvironment,
|
||||
} from "@keycloak/keycloak-ui-shared";
|
||||
import { deleteSession, getDevices } from "../api/methods";
|
||||
import {
|
||||
ClientRepresentation,
|
||||
|
|
|
@ -3,7 +3,13 @@ import { BaseEnvironment } from "@keycloak/keycloak-ui-shared/dist/context/envir
|
|||
import { CallOptions } from "./api/methods";
|
||||
import { Links, parseLinks } from "./api/parse-links";
|
||||
import { parseResponse } from "./api/parse-response";
|
||||
import { Permission, Resource, Scope, CredentialsIssuer, SupportedCredentialConfiguration } from "./api/representations";
|
||||
import {
|
||||
Permission,
|
||||
Resource,
|
||||
Scope,
|
||||
CredentialsIssuer,
|
||||
SupportedCredentialConfiguration,
|
||||
} from "./api/representations";
|
||||
import { request } from "./api/request";
|
||||
import { joinPath } from "./utils/joinPath";
|
||||
|
||||
|
@ -70,16 +76,20 @@ function checkResponse<T>(response: T) {
|
|||
return response;
|
||||
}
|
||||
|
||||
|
||||
export async function getIssuer(
|
||||
context: KeycloakContext<BaseEnvironment>
|
||||
) {
|
||||
export async function getIssuer(context: KeycloakContext<BaseEnvironment>) {
|
||||
const response = await request(
|
||||
"/realms/" + context.environment.realm + "/.well-known/openid-credential-issuer",
|
||||
"/realms/" +
|
||||
context.environment.realm +
|
||||
"/.well-known/openid-credential-issuer",
|
||||
context,
|
||||
{},
|
||||
new URL(
|
||||
joinPath(context.environment.authUrl + "/realms/" + context.environment.realm + "/.well-known/openid-credential-issuer"),
|
||||
joinPath(
|
||||
context.environment.authUrl +
|
||||
"/realms/" +
|
||||
context.environment.realm +
|
||||
"/.well-known/openid-credential-issuer",
|
||||
),
|
||||
),
|
||||
);
|
||||
return parseResponse<CredentialsIssuer>(response);
|
||||
|
@ -87,16 +97,26 @@ export async function getIssuer(
|
|||
|
||||
export async function requestVCOffer(
|
||||
context: KeycloakContext<BaseEnvironment>,
|
||||
supportedCredentialConfiguration:SupportedCredentialConfiguration,
|
||||
credentialsIssuer:CredentialsIssuer
|
||||
supportedCredentialConfiguration: SupportedCredentialConfiguration,
|
||||
credentialsIssuer: CredentialsIssuer,
|
||||
) {
|
||||
const response = await request(
|
||||
"/protocol/oid4vc/credential-offer-uri",
|
||||
context,
|
||||
{ searchParams: {"credential_configuration_id": supportedCredentialConfiguration.id, "type": "qr-code", "width": "500", "height": "500"} },
|
||||
{
|
||||
searchParams: {
|
||||
credential_configuration_id: supportedCredentialConfiguration.id,
|
||||
type: "qr-code",
|
||||
width: "500",
|
||||
height: "500",
|
||||
},
|
||||
},
|
||||
new URL(
|
||||
joinPath(credentialsIssuer.credential_issuer + "/protocol/oid4vc/credential-offer-uri"),
|
||||
joinPath(
|
||||
credentialsIssuer.credential_issuer +
|
||||
"/protocol/oid4vc/credential-offer-uri",
|
||||
),
|
||||
),
|
||||
);
|
||||
return response.blob()
|
||||
}
|
||||
return response.blob();
|
||||
}
|
||||
|
|
|
@ -211,6 +211,9 @@ export interface SupportedCredentialConfiguration {
|
|||
export interface CredentialsIssuer {
|
||||
credential_issuer: string;
|
||||
credential_endpoint: string;
|
||||
authorization_servers: string[];
|
||||
credential_configurations_supported: Record<string, SupportedCredentialConfiguration>
|
||||
}
|
||||
authorization_servers: string[];
|
||||
credential_configurations_supported: Record<
|
||||
string,
|
||||
SupportedCredentialConfiguration
|
||||
>;
|
||||
}
|
||||
|
|
|
@ -37,10 +37,10 @@ export async function request(
|
|||
path: string,
|
||||
{ environment, keycloak }: KeycloakContext<BaseEnvironment>,
|
||||
opts: RequestOptions = {},
|
||||
fullUrl?: URL
|
||||
fullUrl?: URL,
|
||||
) {
|
||||
if (typeof fullUrl === 'undefined') {
|
||||
fullUrl = url(environment, path)
|
||||
if (typeof fullUrl === "undefined") {
|
||||
fullUrl = url(environment, path);
|
||||
}
|
||||
return _request(fullUrl, {
|
||||
...opts,
|
||||
|
|
|
@ -22,7 +22,11 @@ import {
|
|||
} from "@patternfly/react-icons";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ContinueCancelModal, useAlerts, useEnvironment } from "@keycloak/keycloak-ui-shared";
|
||||
import {
|
||||
ContinueCancelModal,
|
||||
useAlerts,
|
||||
useEnvironment,
|
||||
} from "@keycloak/keycloak-ui-shared";
|
||||
import { deleteConsent, getApplications } from "../api/methods";
|
||||
import { ClientRepresentation } from "../api/representations";
|
||||
import { Page } from "../components/page/Page";
|
||||
|
|
|
@ -1,132 +1,136 @@
|
|||
import { useEnvironment } from "@keycloak/keycloak-ui-shared";
|
||||
import {
|
||||
Select,
|
||||
SelectList,
|
||||
SelectOption,
|
||||
PageSectionVariants,
|
||||
PageSection,
|
||||
ActionList,
|
||||
ActionListItem,
|
||||
List,
|
||||
ListItem,
|
||||
MenuToggleElement,
|
||||
MenuToggle
|
||||
} from '@patternfly/react-core';
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
ActionList,
|
||||
ActionListItem,
|
||||
List,
|
||||
ListItem,
|
||||
MenuToggle,
|
||||
MenuToggleElement,
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
Select,
|
||||
SelectList,
|
||||
SelectOption,
|
||||
} from "@patternfly/react-core";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAlerts, useEnvironment } from "@keycloak/keycloak-ui-shared";
|
||||
import { usePromise } from "../utils/usePromise";
|
||||
import { Page } from "../components/page/Page";
|
||||
import { CredentialsIssuer } from "../api/representations";
|
||||
import { getIssuer, requestVCOffer } from "../api";
|
||||
import { CredentialsIssuer } from "../api/representations";
|
||||
import { Page } from "../components/page/Page";
|
||||
import { usePromise } from "../utils/usePromise";
|
||||
|
||||
export const Oid4Vci = () => {
|
||||
const context = useEnvironment();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const context = useEnvironment();
|
||||
|
||||
const initialSelected = t('verifiableCredentialsSelectionDefault')
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [selected, setSelected] = useState<string>(initialSelected);
|
||||
const [qrCode, setQrCode] = useState<string>("")
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false)
|
||||
const [offerQRVisible, setOfferQRVisible] = useState<boolean>(false)
|
||||
const [credentialsIssuer, setCredentialsIssuer] = useState<CredentialsIssuer>()
|
||||
const initialSelected = t("verifiableCredentialsSelectionDefault");
|
||||
|
||||
usePromise(() => getIssuer(context), setCredentialsIssuer);
|
||||
const [selected, setSelected] = useState<string>(initialSelected);
|
||||
const [qrCode, setQrCode] = useState<string>("");
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [offerQRVisible, setOfferQRVisible] = useState<boolean>(false);
|
||||
const [credentialsIssuer, setCredentialsIssuer] =
|
||||
useState<CredentialsIssuer>();
|
||||
|
||||
const selectOptions = useMemo(
|
||||
() => {
|
||||
if(typeof credentialsIssuer !== 'undefined') {
|
||||
return credentialsIssuer.credential_configurations_supported
|
||||
}
|
||||
return {}
|
||||
},
|
||||
[credentialsIssuer],
|
||||
)
|
||||
usePromise(() => getIssuer(context), setCredentialsIssuer);
|
||||
|
||||
const dropdownItems = useMemo(
|
||||
() => {
|
||||
if (typeof selectOptions !== 'undefined') {
|
||||
return Array.from(Object.keys(selectOptions))
|
||||
}
|
||||
return []
|
||||
},
|
||||
[selectOptions],
|
||||
)
|
||||
const selectOptions = useMemo(() => {
|
||||
if (typeof credentialsIssuer !== "undefined") {
|
||||
return credentialsIssuer.credential_configurations_supported;
|
||||
}
|
||||
return {};
|
||||
}, [credentialsIssuer]);
|
||||
|
||||
useEffect(() => {
|
||||
if(initialSelected !== selected && credentialsIssuer !== undefined){
|
||||
requestVCOffer(context, selectOptions[selected], credentialsIssuer)
|
||||
.then((blob) => {
|
||||
var reader = new FileReader();
|
||||
reader.readAsDataURL(blob)
|
||||
reader.onloadend = function() {
|
||||
let result = reader.result
|
||||
if (typeof result === "string") {
|
||||
setQrCode(result);
|
||||
setOfferQRVisible(true);
|
||||
setIsOpen(false);
|
||||
}
|
||||
const dropdownItems = useMemo(() => {
|
||||
if (typeof selectOptions !== "undefined") {
|
||||
return Array.from(Object.keys(selectOptions));
|
||||
}
|
||||
return [];
|
||||
}, [selectOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialSelected !== selected && credentialsIssuer !== undefined) {
|
||||
requestVCOffer(context, selectOptions[selected], credentialsIssuer).then(
|
||||
(blob) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(blob);
|
||||
reader.onloadend = function () {
|
||||
const result = reader.result;
|
||||
if (typeof result === "string") {
|
||||
setQrCode(result);
|
||||
setOfferQRVisible(true);
|
||||
setIsOpen(false);
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [selected]);
|
||||
|
||||
const onToggleClick = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
|
||||
<MenuToggle
|
||||
ref={toggleRef}
|
||||
onClick={onToggleClick}
|
||||
isExpanded={isOpen}
|
||||
data-testid="menu-toggle"
|
||||
>
|
||||
{selected}
|
||||
</MenuToggle>
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
}, [selected]);
|
||||
|
||||
return (
|
||||
<Page title={t('verifiableCredentialsTitle')} description={t('verifiableCredentialsDescription')}>
|
||||
<PageSection isFilled variant={PageSectionVariants.light}>
|
||||
<List isPlain>
|
||||
<ListItem>
|
||||
<Select
|
||||
data-testid="credential-select"
|
||||
onOpenChange={(isOpen) => setIsOpen(isOpen)}
|
||||
onSelect={(_event, val) => setSelected(val as string)}
|
||||
isOpen={isOpen}
|
||||
selected={selected}
|
||||
toggle={toggle}
|
||||
shouldFocusToggleOnSelect={true}
|
||||
>
|
||||
<SelectList>
|
||||
{dropdownItems.map((option, index) => (
|
||||
<SelectOption
|
||||
value={option}
|
||||
data-testid='select-${option}'
|
||||
>
|
||||
const onToggleClick = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
|
||||
<MenuToggle
|
||||
ref={toggleRef}
|
||||
onClick={onToggleClick}
|
||||
isExpanded={isOpen}
|
||||
data-testid="menu-toggle"
|
||||
>
|
||||
{selected}
|
||||
</MenuToggle>
|
||||
);
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={t("verifiableCredentialsTitle")}
|
||||
description={t("verifiableCredentialsDescription")}
|
||||
>
|
||||
<PageSection isFilled variant={PageSectionVariants.light}>
|
||||
<List isPlain>
|
||||
<ListItem>
|
||||
<Select
|
||||
data-testid="credential-select"
|
||||
onOpenChange={(isOpen) => setIsOpen(isOpen)}
|
||||
onSelect={(_event, val) => setSelected(val as string)}
|
||||
isOpen={isOpen}
|
||||
selected={selected}
|
||||
toggle={toggle}
|
||||
shouldFocusToggleOnSelect={true}
|
||||
>
|
||||
<SelectList>
|
||||
{dropdownItems.map((option) => (
|
||||
<SelectOption
|
||||
key={option}
|
||||
value={option}
|
||||
data-testid="select-${option}"
|
||||
>
|
||||
{option}
|
||||
</SelectOption>
|
||||
))}
|
||||
</SelectList>
|
||||
</Select>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ActionList>
|
||||
{ offerQRVisible &&
|
||||
<ActionListItem>
|
||||
<img width='500' height='500' src={`${qrCode}`} data-testid="qr-code"/>
|
||||
</ActionListItem>
|
||||
}
|
||||
</ActionList>
|
||||
</ListItem>
|
||||
</List>
|
||||
</PageSection>
|
||||
</Page>
|
||||
);
|
||||
</SelectOption>
|
||||
))}
|
||||
</SelectList>
|
||||
</Select>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ActionList>
|
||||
{offerQRVisible && (
|
||||
<ActionListItem>
|
||||
<img
|
||||
width="500"
|
||||
height="500"
|
||||
src={`${qrCode}`}
|
||||
data-testid="qr-code"
|
||||
/>
|
||||
</ActionListItem>
|
||||
)}
|
||||
</ActionList>
|
||||
</ListItem>
|
||||
</List>
|
||||
</PageSection>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default Oid4Vci;
|
||||
export default Oid4Vci;
|
||||
|
|
|
@ -32,7 +32,11 @@ import {
|
|||
} from "@patternfly/react-table";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ContinueCancelModal, useAlerts, useEnvironment } from "@keycloak/keycloak-ui-shared";
|
||||
import {
|
||||
ContinueCancelModal,
|
||||
useAlerts,
|
||||
useEnvironment,
|
||||
} from "@keycloak/keycloak-ui-shared";
|
||||
import { fetchPermission, fetchResources, updatePermissions } from "../api";
|
||||
import { getPermissionRequests } from "../api/methods";
|
||||
import { Links } from "../api/parse-links";
|
||||
|
|
|
@ -61,7 +61,7 @@ export const PersonalInfoRoute: IndexRouteObject = {
|
|||
export const Oid4VciRoute: RouteObject = {
|
||||
path: "oid4vci",
|
||||
element: <Oid4Vci />,
|
||||
}
|
||||
};
|
||||
|
||||
export const RootRoute: RouteObject = {
|
||||
path: decodeURIComponent(new URL(environment.baseUrl).pathname),
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
import { login } from "../login";
|
||||
|
||||
const realm = "verifiable-credentials"
|
||||
|
||||
test.describe("Verifiable Credentials page", () => {
|
||||
|
||||
test("Get offer for test-credential.", async ({ page }) => {
|
||||
|
||||
await login(page, "test-user", "test")
|
||||
await expect(page.getByTestId("qr-code")).toBeHidden
|
||||
await page.getByTestId("oid4vci").click
|
||||
await page.getByTestId("credential-select").click
|
||||
await expect(page.getByTestId("select-verifiable-credential")).toBeVisible
|
||||
await expect(page.getByTestId("select-natural-person")).toBeVisible
|
||||
await page.getByTestId("select-natural-person").click
|
||||
await expect(page.getByTestId("qr-code")).toBeVisible
|
||||
})
|
||||
})
|
||||
test("Get offer for test-credential.", async ({ page }) => {
|
||||
await login(page, "test-user", "test");
|
||||
await expect(page.getByTestId("qr-code")).toBeHidden();
|
||||
await page.getByTestId("oid4vci").click();
|
||||
await page.getByTestId("credential-select").click();
|
||||
await expect(
|
||||
page.getByTestId("select-verifiable-credential"),
|
||||
).toBeVisible();
|
||||
await expect(page.getByTestId("select-natural-person")).toBeVisible();
|
||||
await page.getByTestId("select-natural-person").click();
|
||||
await expect(page.getByTestId("qr-code")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue