moved keycloak to context so it can be updated (#22488)

* moved keycloak to context so it can be updated

fixes: #11931

* PR comments
This commit is contained in:
Erik Jan de Wit 2023-09-19 14:01:35 +02:00 committed by GitHub
parent e86bf1f0b2
commit d796721e00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 95 additions and 41 deletions

View file

@ -6,6 +6,7 @@ import {
Form,
Spinner,
} from "@patternfly/react-core";
import { useKeycloak } from "keycloak-masthead";
import { useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
@ -18,7 +19,6 @@ import {
import { Page } from "../components/page/Page";
import { environment } from "../environment";
import { TFuncKey } from "../i18n";
import { keycloak } from "../keycloak";
import { usePromise } from "../utils/usePromise";
import { UserProfileFields } from "./UserProfileFields";
@ -38,6 +38,7 @@ export const fieldName = (name: string) =>
const PersonalInfo = () => {
const { t } = useTranslation();
const keycloak = useKeycloak();
const [userProfileMetadata, setUserProfileMetadata] =
useState<UserProfileMetadata>();
const form = useForm<UserRepresentation>({ mode: "onChange" });
@ -55,6 +56,7 @@ const PersonalInfo = () => {
const onSubmit = async (user: UserRepresentation) => {
try {
await savePersonalInfo(user);
keycloak?.updateToken();
addAlert(t("accountUpdatedMessage"));
} catch (error) {
addError(t("accountUpdatedError").toString());
@ -114,7 +116,7 @@ const PersonalInfo = () => {
id="delete-account-btn"
variant="danger"
onClick={() =>
keycloak.login({
keycloak?.keycloak.login({
action: "delete_account",
})
}

View file

@ -1,6 +1,8 @@
import { Button, Page, Spinner } from "@patternfly/react-core";
import { ExternalLinkSquareAltIcon } from "@patternfly/react-icons";
import {
KeycloakMasthead,
KeycloakProvider,
Translations,
TranslationsProvider,
} from "keycloak-masthead";
@ -8,8 +10,6 @@ import { Suspense, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Outlet, useHref } from "react-router-dom";
import { AlertProvider } from "ui-shared";
import { ExternalLinkSquareAltIcon } from "@patternfly/react-icons";
import { environment } from "../environment";
import { keycloak } from "../keycloak";
import { joinPath } from "../utils/joinPath";
@ -56,31 +56,32 @@ export const Root = () => {
);
return (
<Page
header={
<TranslationsProvider translations={translations}>
<KeycloakMasthead
features={{ hasManageAccount: false }}
showNavToggle
brand={{
href: indexHref,
src: joinPath(environment.resourceUrl, brandImage),
alt: t("logo"),
className: style.brand,
}}
toolbarItems={[<ReferrerLink key="link" />]}
keycloak={keycloak}
/>
</TranslationsProvider>
}
sidebar={<PageNav />}
isManagedSidebar
>
<AlertProvider>
<Suspense fallback={<Spinner />}>
<Outlet />
</Suspense>
</AlertProvider>
</Page>
<KeycloakProvider keycloak={keycloak}>
<Page
header={
<TranslationsProvider translations={translations}>
<KeycloakMasthead
features={{ hasManageAccount: false }}
showNavToggle
brand={{
href: indexHref,
src: joinPath(environment.resourceUrl, brandImage),
alt: t("logo"),
className: style.brand,
}}
toolbarItems={[<ReferrerLink key="link" />]}
/>
</TranslationsProvider>
}
sidebar={<PageNav />}
isManagedSidebar
>
<AlertProvider>
<Suspense fallback={<Spinner />}>
<Outlet />
</Suspense>
</AlertProvider>
</Page>
</KeycloakProvider>
);
};

View file

@ -0,0 +1,45 @@
import Keycloak, { type KeycloakTokenParsed } from "keycloak-js";
import {
createContext,
PropsWithChildren,
useContext,
useMemo,
useState,
} from "react";
type KeycloakProps = {
keycloak: Keycloak;
token?: KeycloakTokenParsed;
updateToken: () => void;
};
const KeycloakContext = createContext<KeycloakProps | undefined>(undefined);
export type KeycloakProviderProps = {
keycloak: Keycloak;
};
export const useKeycloak = () => useContext(KeycloakContext);
export const KeycloakProvider = ({
keycloak,
children,
}: PropsWithChildren<KeycloakProviderProps>) => {
const [token, setToken] = useState(keycloak.tokenParsed);
const context = useMemo(
() => ({
keycloak,
token,
updateToken: async () => {
await keycloak.updateToken(-1);
setToken(keycloak.tokenParsed);
},
}),
[keycloak, token],
);
return (
<KeycloakContext.Provider value={context}>
{children}
</KeycloakContext.Provider>
);
};

View file

@ -10,13 +10,13 @@ import {
PageHeaderToolsGroup,
PageHeaderToolsItem,
} from "@patternfly/react-core";
import Keycloak from "keycloak-js";
import { ReactNode } from "react";
import { KeycloakDropdown } from "./KeycloakDropdown";
import { useTranslation } from "./translation/useTranslation";
import { loggedInUserName } from "./util";
import { DefaultAvatar } from "./DefaultAvatar";
import { useKeycloak } from "./KeycloakContext";
type BrandLogo = BrandProps & {
href: string;
@ -30,7 +30,6 @@ type KeycloakMastheadProps = PageHeaderProps & {
hasManageAccount?: boolean;
hasUsername?: boolean;
};
keycloak?: Keycloak;
kebabDropdownItems?: ReactNode[];
dropdownItems?: ReactNode[];
toolbarItems?: ReactNode[];
@ -44,13 +43,13 @@ const KeycloakMasthead = ({
hasManageAccount = true,
hasUsername = true,
} = {},
keycloak,
kebabDropdownItems,
dropdownItems = [],
toolbarItems,
...rest
}: KeycloakMastheadProps) => {
const { t } = useTranslation();
const { keycloak } = useKeycloak()!;
const extraItems = [];
if (hasManageAccount) {
extraItems.push(
@ -104,8 +103,8 @@ const KeycloakMasthead = ({
data-testid="options"
dropDownItems={[...dropdownItems, extraItems]}
title={
hasUsername && keycloak
? loggedInUserName(keycloak, t)
hasUsername
? loggedInUserName(keycloak?.tokenParsed, t)
: undefined
}
/>

View file

@ -1,5 +1,9 @@
export { default as KeycloakMasthead } from "./Masthead";
// Keycloak
export { KeycloakProvider, useKeycloak } from "./KeycloakContext";
export type { KeycloakProviderProps } from "./KeycloakContext";
// Translation
export { defaultTranslations } from "./translation/translations";
export type { Translations } from "./translation/translations";

View file

@ -1,15 +1,18 @@
import Keycloak from "keycloak-js";
import { type KeycloakTokenParsed } from "keycloak-js";
import { TranslateFunction } from "./translation/useTranslation";
export function loggedInUserName(keycloak: Keycloak, t: TranslateFunction) {
if (!keycloak.tokenParsed) {
export function loggedInUserName(
token: KeycloakTokenParsed | undefined,
t: TranslateFunction,
) {
if (!token) {
return t("unknownUser");
}
const givenName = keycloak.tokenParsed.given_name;
const familyName = keycloak.tokenParsed.family_name;
const preferredUsername = keycloak.tokenParsed.preferred_username;
const givenName = token.given_name;
const familyName = token.family_name;
const preferredUsername = token.preferred_username;
if (givenName && familyName) {
return t("fullName", { givenName, familyName });