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";
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";

View file

@ -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,

View file

@ -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();
}

View file

@ -212,5 +212,8 @@ export interface CredentialsIssuer {
credential_issuer: string;
credential_endpoint: 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,
{ 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,

View file

@ -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";

View file

@ -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 context = useEnvironment();
const { t } = useTranslation();
const { t } = useTranslation();
const initialSelected = t('verifiableCredentialsSelectionDefault')
const initialSelected = t("verifiableCredentialsSelectionDefault");
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 [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>();
usePromise(() => getIssuer(context), setCredentialsIssuer);
usePromise(() => getIssuer(context), setCredentialsIssuer);
const selectOptions = useMemo(
() => {
if(typeof credentialsIssuer !== 'undefined') {
return credentialsIssuer.credential_configurations_supported
}
return {}
},
[credentialsIssuer],
)
const selectOptions = useMemo(() => {
if (typeof credentialsIssuer !== "undefined") {
return credentialsIssuer.credential_configurations_supported;
}
return {};
}, [credentialsIssuer]);
const dropdownItems = useMemo(
() => {
if (typeof selectOptions !== 'undefined') {
return Array.from(Object.keys(selectOptions))
}
return []
},
[selectOptions],
)
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) => {
var reader = new FileReader();
reader.readAsDataURL(blob)
reader.onloadend = function() {
let result = reader.result
if (typeof result === "string") {
setQrCode(result);
setOfferQRVisible(true);
setIsOpen(false);
}
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;

View file

@ -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";

View file

@ -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),

View file

@ -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();
});
});