Fix various linting issues for Account Console

Signed-off-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
Jon Koops 2024-06-03 09:28:15 +02:00 committed by Hynek Mlnařík
parent 813b9f2f41
commit aa86a0b94c
10 changed files with 195 additions and 154 deletions

View file

@ -12,7 +12,11 @@ import {
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { LinkIcon, UnlinkIcon } from "@patternfly/react-icons"; import { LinkIcon, UnlinkIcon } from "@patternfly/react-icons";
import { useTranslation } from "react-i18next"; 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 { linkAccount, unLinkAccount } from "../api/methods";
import { LinkedAccountRepresentation } from "../api/representations"; import { LinkedAccountRepresentation } from "../api/representations";

View file

@ -23,7 +23,11 @@ import {
} from "@patternfly/react-icons"; } from "@patternfly/react-icons";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; 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 { deleteSession, getDevices } from "../api/methods";
import { import {
ClientRepresentation, ClientRepresentation,

View file

@ -3,7 +3,13 @@ import { BaseEnvironment } from "@keycloak/keycloak-ui-shared/dist/context/envir
import { CallOptions } from "./api/methods"; import { CallOptions } from "./api/methods";
import { Links, parseLinks } from "./api/parse-links"; import { Links, parseLinks } from "./api/parse-links";
import { parseResponse } from "./api/parse-response"; 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 { request } from "./api/request";
import { joinPath } from "./utils/joinPath"; import { joinPath } from "./utils/joinPath";
@ -70,16 +76,20 @@ function checkResponse<T>(response: T) {
return response; return response;
} }
export async function getIssuer(context: KeycloakContext<BaseEnvironment>) {
export async function getIssuer(
context: KeycloakContext<BaseEnvironment>
) {
const response = await request( const response = await request(
"/realms/" + context.environment.realm + "/.well-known/openid-credential-issuer", "/realms/" +
context.environment.realm +
"/.well-known/openid-credential-issuer",
context, context,
{}, {},
new URL( 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); return parseResponse<CredentialsIssuer>(response);
@ -87,16 +97,26 @@ export async function getIssuer(
export async function requestVCOffer( export async function requestVCOffer(
context: KeycloakContext<BaseEnvironment>, context: KeycloakContext<BaseEnvironment>,
supportedCredentialConfiguration:SupportedCredentialConfiguration, supportedCredentialConfiguration: SupportedCredentialConfiguration,
credentialsIssuer:CredentialsIssuer credentialsIssuer: CredentialsIssuer,
) { ) {
const response = await request( const response = await request(
"/protocol/oid4vc/credential-offer-uri", "/protocol/oid4vc/credential-offer-uri",
context, 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( 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();
} }

View file

@ -212,5 +212,8 @@ export interface CredentialsIssuer {
credential_issuer: string; credential_issuer: string;
credential_endpoint: string; credential_endpoint: string;
authorization_servers: string[]; authorization_servers: string[];
credential_configurations_supported: Record<string, SupportedCredentialConfiguration> credential_configurations_supported: Record<
string,
SupportedCredentialConfiguration
>;
} }

View file

@ -37,10 +37,10 @@ export async function request(
path: string, path: string,
{ environment, keycloak }: KeycloakContext<BaseEnvironment>, { environment, keycloak }: KeycloakContext<BaseEnvironment>,
opts: RequestOptions = {}, opts: RequestOptions = {},
fullUrl?: URL fullUrl?: URL,
) { ) {
if (typeof fullUrl === 'undefined') { if (typeof fullUrl === "undefined") {
fullUrl = url(environment, path) fullUrl = url(environment, path);
} }
return _request(fullUrl, { return _request(fullUrl, {
...opts, ...opts,

View file

@ -22,7 +22,11 @@ import {
} from "@patternfly/react-icons"; } from "@patternfly/react-icons";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; 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 { deleteConsent, getApplications } from "../api/methods";
import { ClientRepresentation } from "../api/representations"; import { ClientRepresentation } from "../api/representations";
import { Page } from "../components/page/Page"; import { Page } from "../components/page/Page";

View file

@ -1,74 +1,70 @@
import { useEnvironment } from "@keycloak/keycloak-ui-shared";
import { import {
Select,
SelectList,
SelectOption,
PageSectionVariants,
PageSection,
ActionList, ActionList,
ActionListItem, ActionListItem,
List, List,
ListItem, ListItem,
MenuToggle,
MenuToggleElement, MenuToggleElement,
MenuToggle PageSection,
} from '@patternfly/react-core'; PageSectionVariants,
import { useEffect, useState, useMemo } from "react"; Select,
SelectList,
SelectOption,
} from "@patternfly/react-core";
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; 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 { getIssuer, requestVCOffer } from "../api";
import { CredentialsIssuer } from "../api/representations";
import { Page } from "../components/page/Page";
import { usePromise } from "../utils/usePromise";
export const Oid4Vci = () => { export const Oid4Vci = () => {
const context = useEnvironment(); const context = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const initialSelected = t('verifiableCredentialsSelectionDefault') const initialSelected = t("verifiableCredentialsSelectionDefault");
const [selected, setSelected] = useState<string>(initialSelected); const [selected, setSelected] = useState<string>(initialSelected);
const [qrCode, setQrCode] = useState<string>("") const [qrCode, setQrCode] = useState<string>("");
const [isOpen, setIsOpen] = useState<boolean>(false) const [isOpen, setIsOpen] = useState<boolean>(false);
const [offerQRVisible, setOfferQRVisible] = useState<boolean>(false) const [offerQRVisible, setOfferQRVisible] = useState<boolean>(false);
const [credentialsIssuer, setCredentialsIssuer] = useState<CredentialsIssuer>() const [credentialsIssuer, setCredentialsIssuer] =
useState<CredentialsIssuer>();
usePromise(() => getIssuer(context), setCredentialsIssuer); usePromise(() => getIssuer(context), setCredentialsIssuer);
const selectOptions = useMemo( const selectOptions = useMemo(() => {
() => { if (typeof credentialsIssuer !== "undefined") {
if(typeof credentialsIssuer !== 'undefined') { return credentialsIssuer.credential_configurations_supported;
return credentialsIssuer.credential_configurations_supported
} }
return {} return {};
}, }, [credentialsIssuer]);
[credentialsIssuer],
)
const dropdownItems = useMemo( const dropdownItems = useMemo(() => {
() => { if (typeof selectOptions !== "undefined") {
if (typeof selectOptions !== 'undefined') { return Array.from(Object.keys(selectOptions));
return Array.from(Object.keys(selectOptions))
} }
return [] return [];
}, }, [selectOptions]);
[selectOptions],
)
useEffect(() => { useEffect(() => {
if(initialSelected !== selected && credentialsIssuer !== undefined){ if (initialSelected !== selected && credentialsIssuer !== undefined) {
requestVCOffer(context, selectOptions[selected], credentialsIssuer) requestVCOffer(context, selectOptions[selected], credentialsIssuer).then(
.then((blob) => { (blob) => {
var reader = new FileReader(); const reader = new FileReader();
reader.readAsDataURL(blob) reader.readAsDataURL(blob);
reader.onloadend = function() { reader.onloadend = function () {
let result = reader.result const result = reader.result;
if (typeof result === "string") { if (typeof result === "string") {
setQrCode(result); setQrCode(result);
setOfferQRVisible(true); setOfferQRVisible(true);
setIsOpen(false); setIsOpen(false);
} }
} };
}) },
);
} }
}, [selected]); }, [selected]);
@ -88,7 +84,10 @@ export const Oid4Vci = () => {
); );
return ( return (
<Page title={t('verifiableCredentialsTitle')} description={t('verifiableCredentialsDescription')}> <Page
title={t("verifiableCredentialsTitle")}
description={t("verifiableCredentialsDescription")}
>
<PageSection isFilled variant={PageSectionVariants.light}> <PageSection isFilled variant={PageSectionVariants.light}>
<List isPlain> <List isPlain>
<ListItem> <ListItem>
@ -102,10 +101,11 @@ export const Oid4Vci = () => {
shouldFocusToggleOnSelect={true} shouldFocusToggleOnSelect={true}
> >
<SelectList> <SelectList>
{dropdownItems.map((option, index) => ( {dropdownItems.map((option) => (
<SelectOption <SelectOption
key={option}
value={option} value={option}
data-testid='select-${option}' data-testid="select-${option}"
> >
{option} {option}
</SelectOption> </SelectOption>
@ -115,11 +115,16 @@ export const Oid4Vci = () => {
</ListItem> </ListItem>
<ListItem> <ListItem>
<ActionList> <ActionList>
{ offerQRVisible && {offerQRVisible && (
<ActionListItem> <ActionListItem>
<img width='500' height='500' src={`${qrCode}`} data-testid="qr-code"/> <img
width="500"
height="500"
src={`${qrCode}`}
data-testid="qr-code"
/>
</ActionListItem> </ActionListItem>
} )}
</ActionList> </ActionList>
</ListItem> </ListItem>
</List> </List>
@ -128,5 +133,4 @@ export const Oid4Vci = () => {
); );
}; };
export default Oid4Vci; export default Oid4Vci;

View file

@ -32,7 +32,11 @@ import {
} from "@patternfly/react-table"; } from "@patternfly/react-table";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; 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 { fetchPermission, fetchResources, updatePermissions } from "../api";
import { getPermissionRequests } from "../api/methods"; import { getPermissionRequests } from "../api/methods";
import { Links } from "../api/parse-links"; import { Links } from "../api/parse-links";

View file

@ -61,7 +61,7 @@ export const PersonalInfoRoute: IndexRouteObject = {
export const Oid4VciRoute: RouteObject = { export const Oid4VciRoute: RouteObject = {
path: "oid4vci", path: "oid4vci",
element: <Oid4Vci />, element: <Oid4Vci />,
} };
export const RootRoute: RouteObject = { export const RootRoute: RouteObject = {
path: decodeURIComponent(new URL(environment.baseUrl).pathname), path: decodeURIComponent(new URL(environment.baseUrl).pathname),

View file

@ -1,19 +1,17 @@
import { expect, test } from "@playwright/test"; import { expect, test } from "@playwright/test";
import { login } from "../login"; import { login } from "../login";
const realm = "verifiable-credentials"
test.describe("Verifiable Credentials page", () => { test.describe("Verifiable Credentials page", () => {
test("Get offer for test-credential.", async ({ page }) => { test("Get offer for test-credential.", async ({ page }) => {
await login(page, "test-user", "test");
await login(page, "test-user", "test") await expect(page.getByTestId("qr-code")).toBeHidden();
await expect(page.getByTestId("qr-code")).toBeHidden await page.getByTestId("oid4vci").click();
await page.getByTestId("oid4vci").click await page.getByTestId("credential-select").click();
await page.getByTestId("credential-select").click await expect(
await expect(page.getByTestId("select-verifiable-credential")).toBeVisible page.getByTestId("select-verifiable-credential"),
await expect(page.getByTestId("select-natural-person")).toBeVisible ).toBeVisible();
await page.getByTestId("select-natural-person").click await expect(page.getByTestId("select-natural-person")).toBeVisible();
await expect(page.getByTestId("qr-code")).toBeVisible await page.getByTestId("select-natural-person").click();
}) await expect(page.getByTestId("qr-code")).toBeVisible();
}) });
});