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:
parent
e86bf1f0b2
commit
d796721e00
6 changed files with 95 additions and 41 deletions
|
@ -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",
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
45
js/libs/keycloak-masthead/src/KeycloakContext.tsx
Normal file
45
js/libs/keycloak-masthead/src/KeycloakContext.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 });
|
||||
|
|
Loading…
Reference in a new issue